Merge branch 'master' into update/travis-git-depth
This commit is contained in:
commit
e1856b961d
|
@ -1,35 +0,0 @@
|
||||||
engines:
|
|
||||||
phpcodesniffer:
|
|
||||||
enabled: true
|
|
||||||
config:
|
|
||||||
standard: "WordPress"
|
|
||||||
eslint:
|
|
||||||
enabled: true
|
|
||||||
scss-lint:
|
|
||||||
enabled: true
|
|
||||||
duplication:
|
|
||||||
enabled: true
|
|
||||||
config:
|
|
||||||
languages:
|
|
||||||
- php
|
|
||||||
- javascript
|
|
||||||
ratings:
|
|
||||||
paths:
|
|
||||||
- "includes/*"
|
|
||||||
exclude_paths:
|
|
||||||
- "tests/"
|
|
||||||
- "sample-data/"
|
|
||||||
- "i18n/"
|
|
||||||
- "includes/api/legacy/"
|
|
||||||
- "includes/libraries/"
|
|
||||||
- "includes/updates/"
|
|
||||||
- "includes/shipping/legacy-*"
|
|
||||||
- "includes/wc-deprecated-functions.php"
|
|
||||||
- "assets/js/accounting/"
|
|
||||||
- "assets/js/jquery-*"
|
|
||||||
- "assets/js/prettyPhoto/"
|
|
||||||
- "assets/js/round/"
|
|
||||||
- "assets/js/select2/"
|
|
||||||
- "assets/js/selectWoo/"
|
|
||||||
- "assets/js/stupidtable/"
|
|
||||||
- "assets/js/zeroclipboard/"
|
|
|
@ -34,7 +34,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Update nightly tag
|
- name: Update nightly tag
|
||||||
uses: richardsimko/github-tag-action@v1.0.4
|
uses: richardsimko/github-tag-action@v1.0.5
|
||||||
with:
|
with:
|
||||||
tag_name: nightly
|
tag_name: nightly
|
||||||
env:
|
env:
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
tools:
|
|
||||||
php_code_sniffer:
|
|
||||||
config:
|
|
||||||
standard: WordPress
|
|
||||||
sensiolabs_security_checker: true
|
|
||||||
external_code_coverage:
|
|
||||||
timeout: 2500
|
|
||||||
checks:
|
|
||||||
php:
|
|
||||||
avoid_closing_tag: false
|
|
||||||
avoid_superglobals: false
|
|
||||||
coding_standard:
|
|
||||||
name: WordPress
|
|
||||||
no_exit: false
|
|
||||||
no_global_keyword: false
|
|
||||||
one_class_per_file: false
|
|
||||||
psr2_class_declaration: false
|
|
||||||
psr2_control_structure_declaration: false
|
|
||||||
psr2_switch_declaration: false
|
|
||||||
variable_existence: false
|
|
||||||
verify_access_scope_valid: false
|
|
||||||
verify_argument_usable_as_reference: false
|
|
||||||
verify_property_names: false
|
|
||||||
filter:
|
|
||||||
excluded_paths:
|
|
||||||
- sample-data/
|
|
||||||
- i18n/
|
|
||||||
- includes/api/legacy/
|
|
||||||
- includes/legacy/
|
|
||||||
- includes/libraries/
|
|
||||||
- includes/shipping/legacy-*
|
|
||||||
- includes/updates/
|
|
||||||
- includes/vendor/
|
|
||||||
- includes/wc-deprecated-functions.php
|
|
||||||
- tests/
|
|
|
@ -70,10 +70,9 @@ install:
|
||||||
fi
|
fi
|
||||||
- composer install
|
- composer install
|
||||||
- |
|
- |
|
||||||
# Install WP Test suite, install PHPUnit globally:
|
# Install WP Test suite:
|
||||||
if [[ ! -z "$WP_VERSION" ]]; then
|
if [[ ! -z "$WP_VERSION" ]]; then
|
||||||
bash tests/bin/install.sh woocommerce_test root '' localhost $WP_VERSION
|
bash tests/bin/install.sh woocommerce_test root '' localhost $WP_VERSION
|
||||||
composer global require "phpunit/phpunit=6.5.*|7.5.*"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
@ -90,10 +89,12 @@ branches:
|
||||||
- /^\d+\.\d+(\.\d+)?(-\S*)?$/
|
- /^\d+\.\d+(\.\d+)?(-\S*)?$/
|
||||||
- /^release\//
|
- /^release\//
|
||||||
|
|
||||||
|
# Composer 2.0.7 introduced a change that broke the jetpack autoloader in PHP 7.0 - 7.3.
|
||||||
before_install:
|
before_install:
|
||||||
- composer self-update --1
|
- composer self-update 2.0.6
|
||||||
|
|
||||||
# Git clone depth
|
# Git clone depth
|
||||||
# By default Travis CI clones repositories to a depth of 50 commits. Using a depth of 1 makes this step a bit faster.
|
# By default Travis CI clones repositories to a depth of 50 commits. Using a depth of 1 makes this step a bit faster.
|
||||||
git:
|
git:
|
||||||
depth: 1
|
depth: 1
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
<img src="https://img.shields.io/wordpress/plugin/dt/woocommerce.svg" alt="WordPress.org downloads">
|
<img src="https://img.shields.io/wordpress/plugin/dt/woocommerce.svg" alt="WordPress.org downloads">
|
||||||
<img src="https://img.shields.io/wordpress/plugin/r/woocommerce.svg" alt="WordPress.org rating">
|
<img src="https://img.shields.io/wordpress/plugin/r/woocommerce.svg" alt="WordPress.org rating">
|
||||||
<a href="https://travis-ci.com/woocommerce/woocommerce"><img src="https://travis-ci.com/woocommerce/woocommerce.svg?branch=master" alt="Build Status"></a>
|
<a href="https://travis-ci.com/woocommerce/woocommerce"><img src="https://travis-ci.com/woocommerce/woocommerce.svg?branch=master" alt="Build Status"></a>
|
||||||
<a href="https://scrutinizer-ci.com/g/woocommerce/woocommerce/?branch=master"><img src="https://scrutinizer-ci.com/g/woocommerce/woocommerce/badges/quality-score.png?b=master" alt="Scrutinizer Code Quality"></a>
|
|
||||||
<a href="https://codecov.io/gh/woocommerce/woocommerce"><img src="https://codecov.io/gh/woocommerce/woocommerce/branch/master/graph/badge.svg" alt="codecov"></a>
|
<a href="https://codecov.io/gh/woocommerce/woocommerce"><img src="https://codecov.io/gh/woocommerce/woocommerce/branch/master/graph/badge.svg" alt="codecov"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
|
@ -360,7 +360,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.addons-button-solid {
|
.addons-button-solid {
|
||||||
background-color: #955a89;
|
background-color:#674399;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -379,6 +379,16 @@
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.addons-button-outline-purple {
|
||||||
|
border: 1px solid #674399;
|
||||||
|
color: #674399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addons-button-outline-purple:hover {
|
||||||
|
color: #674399;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
.addons-button-outline-white {
|
.addons-button-outline-white {
|
||||||
border: 1px solid #fff;
|
border: 1px solid #fff;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
|
@ -1661,6 +1661,16 @@ a.reset_variations {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form[name="checkout"] {
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blockUI.blockOverlay {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
@include loader();
|
||||||
|
}
|
||||||
|
|
||||||
form {
|
form {
|
||||||
|
|
||||||
.col2-set {
|
.col2-set {
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 5.6 KiB |
|
@ -19,7 +19,7 @@ jQuery( function ( $ ) {
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
/* State/Country select boxes */
|
/* State/Country select boxes */
|
||||||
this.states = $.parseJSON( woocommerce_admin_meta_boxes_order.countries.replace( /"/g, '"' ) );
|
this.states = JSON.parse( woocommerce_admin_meta_boxes_order.countries.replace( /"/g, '"' ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
$( '.js_field-country' ).selectWoo().change( this.change_country );
|
$( '.js_field-country' ).selectWoo().change( this.change_country );
|
||||||
|
|
|
@ -9,7 +9,7 @@ jQuery( function ( $ ) {
|
||||||
init: function() {
|
init: function() {
|
||||||
if ( typeof wc_users_params.countries !== 'undefined' ) {
|
if ( typeof wc_users_params.countries !== 'undefined' ) {
|
||||||
/* State/Country select boxes */
|
/* State/Country select boxes */
|
||||||
this.states = $.parseJSON( wc_users_params.countries.replace( /"/g, '"' ) );
|
this.states = JSON.parse( wc_users_params.countries.replace( /"/g, '"' ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
$( '.js_field-country' ).selectWoo().change( this.change_country );
|
$( '.js_field-country' ).selectWoo().change( this.change_country );
|
||||||
|
|
|
@ -6,7 +6,7 @@ jQuery( function( $ ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var locale_json = wc_address_i18n_params.locale.replace( /"/g, '"' ), locale = $.parseJSON( locale_json );
|
var locale_json = wc_address_i18n_params.locale.replace( /"/g, '"' ), locale = JSON.parse( locale_json );
|
||||||
|
|
||||||
function field_is_required( field, is_required ) {
|
function field_is_required( field, is_required ) {
|
||||||
if ( is_required ) {
|
if ( is_required ) {
|
||||||
|
@ -51,7 +51,7 @@ jQuery( function( $ ) {
|
||||||
$statefield.attr( 'data-o_class', $statefield.attr( 'class' ) );
|
$statefield.attr( 'data-o_class', $statefield.attr( 'class' ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
var locale_fields = $.parseJSON( wc_address_i18n_params.locale_fields );
|
var locale_fields = JSON.parse( wc_address_i18n_params.locale_fields );
|
||||||
|
|
||||||
$.each( locale_fields, function( key, value ) {
|
$.each( locale_fields, function( key, value ) {
|
||||||
|
|
||||||
|
|
|
@ -115,7 +115,7 @@ jQuery( function( $ ) {
|
||||||
} );
|
} );
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var wc_fragments = $.parseJSON( sessionStorage.getItem( wc_cart_fragments_params.fragment_name ) ),
|
var wc_fragments = JSON.parse( sessionStorage.getItem( wc_cart_fragments_params.fragment_name ) ),
|
||||||
cart_hash = sessionStorage.getItem( cart_hash_key ),
|
cart_hash = sessionStorage.getItem( cart_hash_key ),
|
||||||
cookie_hash = Cookies.get( 'woocommerce_cart_hash'),
|
cookie_hash = Cookies.get( 'woocommerce_cart_hash'),
|
||||||
cart_created = sessionStorage.getItem( 'wc_cart_created' );
|
cart_created = sessionStorage.getItem( 'wc_cart_created' );
|
||||||
|
|
|
@ -191,7 +191,7 @@ jQuery( function( $ ) {
|
||||||
},
|
},
|
||||||
is_valid_json: function( raw_json ) {
|
is_valid_json: function( raw_json ) {
|
||||||
try {
|
try {
|
||||||
var json = $.parseJSON( raw_json );
|
var json = JSON.parse( raw_json );
|
||||||
|
|
||||||
return ( json && 'object' === typeof json );
|
return ( json && 'object' === typeof json );
|
||||||
} catch ( e ) {
|
} catch ( e ) {
|
||||||
|
@ -507,9 +507,8 @@ jQuery( function( $ ) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
url: wc_checkout_params.checkout_url,
|
url: wc_checkout_params.checkout_url,
|
||||||
data: new FormData( this ),
|
data: $form.serialize(),
|
||||||
contentType: false,
|
dataType: 'json',
|
||||||
processData: false,
|
|
||||||
success: function( result ) {
|
success: function( result ) {
|
||||||
// Detach the unload handler that prevents a reload / redirect
|
// Detach the unload handler that prevents a reload / redirect
|
||||||
wc_checkout_form.detachUnloadEventsOnSubmit();
|
wc_checkout_form.detachUnloadEventsOnSubmit();
|
||||||
|
|
|
@ -77,7 +77,7 @@ jQuery( function( $ ) {
|
||||||
|
|
||||||
/* State/Country select boxes */
|
/* State/Country select boxes */
|
||||||
var states_json = wc_country_select_params.countries.replace( /"/g, '"' ),
|
var states_json = wc_country_select_params.countries.replace( /"/g, '"' ),
|
||||||
states = $.parseJSON( states_json ),
|
states = JSON.parse( states_json ),
|
||||||
wrapper_selectors = '.woocommerce-billing-fields,' +
|
wrapper_selectors = '.woocommerce-billing-fields,' +
|
||||||
'.woocommerce-shipping-fields,' +
|
'.woocommerce-shipping-fields,' +
|
||||||
'.woocommerce-address-fields,' +
|
'.woocommerce-address-fields,' +
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
const { e2eBabelConfig } = require( '@woocommerce/e2e-environment' );
|
|
||||||
|
|
||||||
module.exports = function( api ) {
|
|
||||||
api.cache( true );
|
|
||||||
|
|
||||||
return {
|
|
||||||
...e2eBabelConfig,
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -4,7 +4,7 @@
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"platform": {
|
"platform": {
|
||||||
"php": "7.1"
|
"php": "7.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "ee5c0c106a076ca3b426771807c3ffeb",
|
"content-hash": "c50f65dd9f9a26d397f7bb30228d7a88",
|
||||||
"packages": [],
|
"packages": [],
|
||||||
"packages-dev": [
|
"packages-dev": [
|
||||||
{
|
{
|
||||||
|
@ -71,6 +71,10 @@
|
||||||
"stylecheck",
|
"stylecheck",
|
||||||
"tests"
|
"tests"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues",
|
||||||
|
"source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer"
|
||||||
|
},
|
||||||
"time": "2020-06-25T14:57:39+00:00"
|
"time": "2020-06-25T14:57:39+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -129,6 +133,10 @@
|
||||||
"phpcs",
|
"phpcs",
|
||||||
"standards"
|
"standards"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues",
|
||||||
|
"source": "https://github.com/PHPCompatibility/PHPCompatibility"
|
||||||
|
},
|
||||||
"time": "2019-12-27T09:44:58+00:00"
|
"time": "2019-12-27T09:44:58+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -181,6 +189,10 @@
|
||||||
"polyfill",
|
"polyfill",
|
||||||
"standards"
|
"standards"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues",
|
||||||
|
"source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie"
|
||||||
|
},
|
||||||
"time": "2019-11-04T15:17:54+00:00"
|
"time": "2019-11-04T15:17:54+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -231,6 +243,10 @@
|
||||||
"standards",
|
"standards",
|
||||||
"wordpress"
|
"wordpress"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues",
|
||||||
|
"source": "https://github.com/PHPCompatibility/PHPCompatibilityWP"
|
||||||
|
},
|
||||||
"time": "2019-08-28T14:22:28+00:00"
|
"time": "2019-08-28T14:22:28+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -282,6 +298,11 @@
|
||||||
"phpcs",
|
"phpcs",
|
||||||
"standards"
|
"standards"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues",
|
||||||
|
"source": "https://github.com/squizlabs/PHP_CodeSniffer",
|
||||||
|
"wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki"
|
||||||
|
},
|
||||||
"time": "2020-10-23T02:01:07+00:00"
|
"time": "2020-10-23T02:01:07+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -322,6 +343,10 @@
|
||||||
"woocommerce",
|
"woocommerce",
|
||||||
"wordpress"
|
"wordpress"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/woocommerce/woocommerce-sniffs/issues",
|
||||||
|
"source": "https://github.com/woocommerce/woocommerce-sniffs/tree/master"
|
||||||
|
},
|
||||||
"time": "2020-08-06T18:23:45+00:00"
|
"time": "2020-08-06T18:23:45+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -368,6 +393,11 @@
|
||||||
"standards",
|
"standards",
|
||||||
"wordpress"
|
"wordpress"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues",
|
||||||
|
"source": "https://github.com/WordPress/WordPress-Coding-Standards",
|
||||||
|
"wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki"
|
||||||
|
},
|
||||||
"time": "2020-05-13T23:57:56+00:00"
|
"time": "2020-05-13T23:57:56+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -379,6 +409,7 @@
|
||||||
"platform": [],
|
"platform": [],
|
||||||
"platform-dev": [],
|
"platform-dev": [],
|
||||||
"platform-overrides": {
|
"platform-overrides": {
|
||||||
"php": "7.1"
|
"php": "7.0"
|
||||||
}
|
},
|
||||||
|
"plugin-api-version": "2.0.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
"minimum-stability": "dev",
|
"minimum-stability": "dev",
|
||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "7.5.20"
|
"phpunit/phpunit": "6.5.14"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"platform": {
|
"platform": {
|
||||||
"php": "7.1"
|
"php": "7.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -6,7 +6,7 @@
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"platform": {
|
"platform": {
|
||||||
"php": "7.1"
|
"php": "7.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "f89bceee93cc1d38e71a45e4cbf4f4aa",
|
"content-hash": "4d4f2befccefe100869d30305083672b",
|
||||||
"packages": [],
|
"packages": [],
|
||||||
"packages-dev": [
|
"packages-dev": [
|
||||||
{
|
{
|
||||||
|
@ -67,6 +67,11 @@
|
||||||
"po",
|
"po",
|
||||||
"translation"
|
"translation"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"email": "oom@oscarotero.com",
|
||||||
|
"issues": "https://github.com/oscarotero/Gettext/issues",
|
||||||
|
"source": "https://github.com/php-gettext/Gettext/tree/v4.8.2"
|
||||||
|
},
|
||||||
"time": "2019-12-02T10:21:14+00:00"
|
"time": "2019-12-02T10:21:14+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -128,6 +133,10 @@
|
||||||
"translations",
|
"translations",
|
||||||
"unicode"
|
"unicode"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/php-gettext/Languages/issues",
|
||||||
|
"source": "https://github.com/php-gettext/Languages/tree/2.6.0"
|
||||||
|
},
|
||||||
"time": "2019-11-13T10:30:21+00:00"
|
"time": "2019-11-13T10:30:21+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -173,6 +182,10 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Peast is PHP library that generates AST for JavaScript code",
|
"description": "Peast is PHP library that generates AST for JavaScript code",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/mck89/peast/issues",
|
||||||
|
"source": "https://github.com/mck89/peast/tree/v1.11.0"
|
||||||
|
},
|
||||||
"time": "2020-10-09T15:12:13+00:00"
|
"time": "2020-10-09T15:12:13+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -219,6 +232,10 @@
|
||||||
"mustache",
|
"mustache",
|
||||||
"templating"
|
"templating"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/bobthecow/mustache.php/issues",
|
||||||
|
"source": "https://github.com/bobthecow/mustache.php/tree/master"
|
||||||
|
},
|
||||||
"time": "2019-11-23T21:40:31+00:00"
|
"time": "2019-11-23T21:40:31+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -268,29 +285,33 @@
|
||||||
"iri",
|
"iri",
|
||||||
"sockets"
|
"sockets"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/rmccue/Requests/issues",
|
||||||
|
"source": "https://github.com/rmccue/Requests/tree/master"
|
||||||
|
},
|
||||||
"time": "2016-10-13T00:11:37+00:00"
|
"time": "2016-10-13T00:11:37+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/finder",
|
"name": "symfony/finder",
|
||||||
"version": "v3.4.45",
|
"version": "v3.3.6",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/finder.git",
|
"url": "https://github.com/symfony/finder.git",
|
||||||
"reference": "52140652ed31cee3dabd0c481b5577201fa769b4"
|
"reference": "baea7f66d30854ad32988c11a09d7ffd485810c4"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/finder/zipball/52140652ed31cee3dabd0c481b5577201fa769b4",
|
"url": "https://api.github.com/repos/symfony/finder/zipball/baea7f66d30854ad32988c11a09d7ffd485810c4",
|
||||||
"reference": "52140652ed31cee3dabd0c481b5577201fa769b4",
|
"reference": "baea7f66d30854ad32988c11a09d7ffd485810c4",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^5.5.9|>=7.0.8"
|
"php": ">=5.5.9"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "3.4-dev"
|
"dev-master": "3.3-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
@ -317,7 +338,10 @@
|
||||||
],
|
],
|
||||||
"description": "Symfony Finder Component",
|
"description": "Symfony Finder Component",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"time": "2020-09-02T16:06:40+00:00"
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/finder/tree/3.3"
|
||||||
|
},
|
||||||
|
"time": "2017-06-01T21:01:25+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "wp-cli/i18n-command",
|
"name": "wp-cli/i18n-command",
|
||||||
|
@ -374,6 +398,10 @@
|
||||||
],
|
],
|
||||||
"description": "Provides internationalization tools for WordPress projects.",
|
"description": "Provides internationalization tools for WordPress projects.",
|
||||||
"homepage": "https://github.com/wp-cli/i18n-command",
|
"homepage": "https://github.com/wp-cli/i18n-command",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/wp-cli/i18n-command/issues",
|
||||||
|
"source": "https://github.com/wp-cli/i18n-command/tree/master"
|
||||||
|
},
|
||||||
"time": "2020-07-08T15:20:38+00:00"
|
"time": "2020-07-08T15:20:38+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -422,6 +450,9 @@
|
||||||
],
|
],
|
||||||
"description": "A simple YAML loader/dumper class for PHP (WP-CLI fork)",
|
"description": "A simple YAML loader/dumper class for PHP (WP-CLI fork)",
|
||||||
"homepage": "https://github.com/mustangostang/spyc/",
|
"homepage": "https://github.com/mustangostang/spyc/",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/wp-cli/spyc/tree/autoload"
|
||||||
|
},
|
||||||
"time": "2017-04-25T11:26:20+00:00"
|
"time": "2017-04-25T11:26:20+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -472,6 +503,10 @@
|
||||||
"cli",
|
"cli",
|
||||||
"console"
|
"console"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/wp-cli/php-cli-tools/issues",
|
||||||
|
"source": "https://github.com/wp-cli/php-cli-tools/tree/master"
|
||||||
|
},
|
||||||
"time": "2018-09-04T13:28:00+00:00"
|
"time": "2018-09-04T13:28:00+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -534,6 +569,11 @@
|
||||||
"cli",
|
"cli",
|
||||||
"wordpress"
|
"wordpress"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"docs": "https://make.wordpress.org/cli/handbook/",
|
||||||
|
"issues": "https://github.com/wp-cli/wp-cli/issues",
|
||||||
|
"source": "https://github.com/wp-cli/wp-cli"
|
||||||
|
},
|
||||||
"time": "2020-02-18T08:15:37+00:00"
|
"time": "2020-02-18T08:15:37+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -545,6 +585,7 @@
|
||||||
"platform": [],
|
"platform": [],
|
||||||
"platform-dev": [],
|
"platform-dev": [],
|
||||||
"platform-overrides": {
|
"platform-overrides": {
|
||||||
"php": "7.1"
|
"php": "7.0"
|
||||||
}
|
},
|
||||||
|
"plugin-api-version": "2.0.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,64 @@
|
||||||
== Changelog ==
|
== Changelog ==
|
||||||
|
|
||||||
|
= 4.7.0 - 2020-11-10 =
|
||||||
|
|
||||||
|
**WooCommerce**
|
||||||
|
|
||||||
|
* Tweak - Update `product_cat/tag` taxonomy template file names to `product-cat/tag`. #27736
|
||||||
|
* Tweak - Exclude draft pages from the "Shop page" setting. #27890
|
||||||
|
* Tweak - Styling to properly display product reviews within the dashboard activity widget. #27968
|
||||||
|
* Fix - Fixes Photoswipe action buttons being obscured by admin bar. #27010
|
||||||
|
* Fix - Allow variation image to be removed via REST API. #27299
|
||||||
|
* Fix - Fixed WP CLI command to delete tax classes. #27310
|
||||||
|
* Fix - Prevent regenerate image filter loop. #27483
|
||||||
|
* Fix - Fixed some race conditions in `WC_Install`. #27696
|
||||||
|
* Fix - Improved PHP 8 support for `Automattic\WooCommerce\RestApi\Utilities\SingletonTrait`. #27707
|
||||||
|
* Fix - Adjust stock even if `reduce_stock` meta is not set in `wc_maybe_reduce_stock_levels`. #27763
|
||||||
|
* Fix - Removed duplicated CSS code from jQuery UI. #27767
|
||||||
|
* Fix - HTML syntax error in scheduled product message. #27842
|
||||||
|
* Fix - Update logic to determine if an order requires payment to check the order instead of the cart. #27893
|
||||||
|
* Fix - Use `Set password` title for lost password reset form when applicable. #27898
|
||||||
|
* Fix - REST API - Fixed deprecated notices while querying orders and refunds through REST API v1 endpoints. #27934
|
||||||
|
* Fix - Email address starting with `www` being displayed as a URL link in the admin order details page. #27983
|
||||||
|
* Fix - Unexpected HTTP 401 "Sorry, you cannot list resources" REST API responses that occur when a plugin or custom code determines the current WordPress user before WooCommerce is fully initialized. #27587
|
||||||
|
* Dev - Add `woocommerce_should_send_low_stock_notification` filter. #27819
|
||||||
|
* Dev - Introduce (again) a dependency injection framework for the code in the src directory. #27733
|
||||||
|
* Dev - Remove leftover code and data from the reverted improvement for variations filtering by attribute. #27748
|
||||||
|
* Dev - Escaped labels in `woocommerce_form_field()`. #27800
|
||||||
|
* Dev - Add a `NumberUtil::round` method to workaround a breaking change in the buil-in round function in PHP8. #27830
|
||||||
|
* Dev - Remove default value from optional parameters that are followed by required parameters in functions/methods, since those are de-facto required and trigger a deprectation notice in PHP 8. #27840
|
||||||
|
* Dev - REST API - Add user-friendly attribute names and values to order line items metadata.
|
||||||
|
* Dev - REST API - Adds `parent_name` to `line_items` of the GET /orders endpoint.
|
||||||
|
* Localization - Added Serbia districts. #27778
|
||||||
|
* Localization - Make city, and postcode non-required fields. #27779
|
||||||
|
* Localization - Add i18n locale information for Uganda, Kenya and Tanzania. #27164
|
||||||
|
* Localization - Renamed "Postcode / ZIP" to "Pin code", and renamed "State / County" to "State" for India. #27516
|
||||||
|
* Localization - Added postcode validation for addresses in India. #27546
|
||||||
|
|
||||||
|
**WooCommerce Blocks - 3.5.0 & 3.6.0**
|
||||||
|
|
||||||
|
* Make 'retry' property on errors from checkoutAfterProcessingWithSuccess/Error observers default to true if it's undefined. ([3261](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3261))
|
||||||
|
* Ensure new payment methods are only displayed when no saved payment method is selected. ([3247](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3247))
|
||||||
|
* Load WC Blocks CSS after editor CSS. ([3219](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3219))
|
||||||
|
* Restore saved payment method data after closing an express payment method. ([3210](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3210))
|
||||||
|
* Use light default background colour for country/state dropdowns. ([3189](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3189))
|
||||||
|
* Fix broken Express Payment Method use in the Checkout block for logged out or incognito users. ([3165](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3165))
|
||||||
|
* Fix State label for Spain. ([3147](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3147))
|
||||||
|
* Don't throw an error when registering a payment method fails. ([3134](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3134))
|
||||||
|
* Don't load contents of payment method hidden tabs. ([3227](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3227))
|
||||||
|
* Use noticeContexts from useEmitResponse instead of hardcoded values. ([3161](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3161))
|
||||||
|
|
||||||
|
**WooCommerce Admin - 1.6.3**
|
||||||
|
|
||||||
|
* Tweak: Add BR and IN to list of stripe countries [#5377](https://github.com/woocommerce/woocommerce-admin/pull/5377)
|
||||||
|
* Fix: Redirect instead of stalling on WCPay Inbox note action [#5413](https://github.com/woocommerce/woocommerce-admin/pull/5413)
|
||||||
|
|
||||||
|
= 4.6.2 - 2020-11-05 =
|
||||||
|
|
||||||
|
**WooCommerce**
|
||||||
|
|
||||||
|
* Prevent checkout from creating accounts when related setting is disabled.
|
||||||
|
|
||||||
= 4.6.1 - 2020-10-21 =
|
= 4.6.1 - 2020-10-21 =
|
||||||
|
|
||||||
**WooCommerce**
|
**WooCommerce**
|
||||||
|
|
|
@ -10,13 +10,13 @@
|
||||||
"php": ">=7.0",
|
"php": ">=7.0",
|
||||||
"automattic/jetpack-autoloader": "2.2.0",
|
"automattic/jetpack-autoloader": "2.2.0",
|
||||||
"automattic/jetpack-constants": "1.5.0",
|
"automattic/jetpack-constants": "1.5.0",
|
||||||
"composer/installers": "1.7.0",
|
"composer/installers": "~1.7",
|
||||||
"maxmind-db/reader": "1.6.0",
|
"maxmind-db/reader": "1.6.0",
|
||||||
"pelago/emogrifier": "3.1.0",
|
"pelago/emogrifier": "3.1.0",
|
||||||
"psr/container": "1.0.0",
|
"psr/container": "1.0.0",
|
||||||
"woocommerce/action-scheduler": "3.1.6",
|
"woocommerce/action-scheduler": "3.1.6",
|
||||||
"woocommerce/woocommerce-admin": "1.6.3",
|
"woocommerce/woocommerce-admin": "1.7.0",
|
||||||
"woocommerce/woocommerce-blocks": "3.6.0",
|
"woocommerce/woocommerce-blocks": "3.8.0",
|
||||||
"league/container": "3.3.3"
|
"league/container": "3.3.3"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"platform": {
|
"platform": {
|
||||||
"php": "7.1"
|
"php": "7.0"
|
||||||
},
|
},
|
||||||
"preferred-install": {
|
"preferred-install": {
|
||||||
"woocommerce/action-scheduler": "dist",
|
"woocommerce/action-scheduler": "dist",
|
||||||
|
@ -90,10 +90,11 @@
|
||||||
},
|
},
|
||||||
"extra": {
|
"extra": {
|
||||||
"installer-paths": {
|
"installer-paths": {
|
||||||
"packages/action-scheduler": ["woocommerce/action-scheduler"],
|
"packages/{$name}": [
|
||||||
"packages/woocommerce-rest-api": ["woocommerce/woocommerce-rest-api"],
|
"woocommerce/action-scheduler",
|
||||||
"packages/woocommerce-blocks": ["woocommerce/woocommerce-blocks"],
|
"woocommerce/woocommerce-blocks",
|
||||||
"packages/woocommerce-admin": ["woocommerce/woocommerce-admin"]
|
"woocommerce/woocommerce-admin"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"scripts-description": {
|
"scripts-description": {
|
||||||
"test": "Run unit tests",
|
"test": "Run unit tests",
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "ed84c4d91482a5c508caaf50e843de58",
|
"content-hash": "6494b4d4b956386e32381541ebd79839",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "automattic/jetpack-autoloader",
|
"name": "automattic/jetpack-autoloader",
|
||||||
|
@ -40,6 +40,9 @@
|
||||||
"GPL-2.0-or-later"
|
"GPL-2.0-or-later"
|
||||||
],
|
],
|
||||||
"description": "Creates a custom autoloader for a plugin or theme.",
|
"description": "Creates a custom autoloader for a plugin or theme.",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/Automattic/jetpack-autoloader/tree/master"
|
||||||
|
},
|
||||||
"time": "2020-08-14T20:34:36+00:00"
|
"time": "2020-08-14T20:34:36+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -71,32 +74,38 @@
|
||||||
"GPL-2.0-or-later"
|
"GPL-2.0-or-later"
|
||||||
],
|
],
|
||||||
"description": "A wrapper for defining constants in a more testable way.",
|
"description": "A wrapper for defining constants in a more testable way.",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/Automattic/jetpack-constants/tree/master"
|
||||||
|
},
|
||||||
"time": "2020-08-13T14:33:09+00:00"
|
"time": "2020-08-13T14:33:09+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "composer/installers",
|
"name": "composer/installers",
|
||||||
"version": "v1.7.0",
|
"version": "v1.9.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/composer/installers.git",
|
"url": "https://github.com/composer/installers.git",
|
||||||
"reference": "141b272484481432cda342727a427dc1e206bfa0"
|
"reference": "b93bcf0fa1fccb0b7d176b0967d969691cd74cca"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/composer/installers/zipball/141b272484481432cda342727a427dc1e206bfa0",
|
"url": "https://api.github.com/repos/composer/installers/zipball/b93bcf0fa1fccb0b7d176b0967d969691cd74cca",
|
||||||
"reference": "141b272484481432cda342727a427dc1e206bfa0",
|
"reference": "b93bcf0fa1fccb0b7d176b0967d969691cd74cca",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"composer-plugin-api": "^1.0"
|
"composer-plugin-api": "^1.0 || ^2.0"
|
||||||
},
|
},
|
||||||
"replace": {
|
"replace": {
|
||||||
"roundcube/plugin-installer": "*",
|
"roundcube/plugin-installer": "*",
|
||||||
"shama/baton": "*"
|
"shama/baton": "*"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"composer/composer": "1.0.*@dev",
|
"composer/composer": "1.6.* || 2.0.*@dev",
|
||||||
"phpunit/phpunit": "^4.8.36"
|
"composer/semver": "1.0.* || 2.0.*@dev",
|
||||||
|
"phpunit/phpunit": "^4.8.36",
|
||||||
|
"sebastian/comparator": "^1.2.4",
|
||||||
|
"symfony/process": "^2.3"
|
||||||
},
|
},
|
||||||
"type": "composer-plugin",
|
"type": "composer-plugin",
|
||||||
"extra": {
|
"extra": {
|
||||||
|
@ -132,6 +141,7 @@
|
||||||
"Kanboard",
|
"Kanboard",
|
||||||
"Lan Management System",
|
"Lan Management System",
|
||||||
"MODX Evo",
|
"MODX Evo",
|
||||||
|
"MantisBT",
|
||||||
"Mautic",
|
"Mautic",
|
||||||
"Maya",
|
"Maya",
|
||||||
"OXID",
|
"OXID",
|
||||||
|
@ -186,6 +196,7 @@
|
||||||
"shopware",
|
"shopware",
|
||||||
"silverstripe",
|
"silverstripe",
|
||||||
"sydes",
|
"sydes",
|
||||||
|
"sylius",
|
||||||
"symfony",
|
"symfony",
|
||||||
"typo3",
|
"typo3",
|
||||||
"wordpress",
|
"wordpress",
|
||||||
|
@ -193,7 +204,21 @@
|
||||||
"zend",
|
"zend",
|
||||||
"zikula"
|
"zikula"
|
||||||
],
|
],
|
||||||
"time": "2019-08-12T15:00:31+00:00"
|
"support": {
|
||||||
|
"issues": "https://github.com/composer/installers/issues",
|
||||||
|
"source": "https://github.com/composer/installers/tree/v1.9.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://packagist.com",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2020-04-07T06:57:05+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "league/container",
|
"name": "league/container",
|
||||||
|
@ -259,6 +284,10 @@
|
||||||
"provider",
|
"provider",
|
||||||
"service"
|
"service"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/thephpleague/container/issues",
|
||||||
|
"source": "https://github.com/thephpleague/container/tree/3.3.3"
|
||||||
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"url": "https://github.com/philipobenito",
|
"url": "https://github.com/philipobenito",
|
||||||
|
@ -325,6 +354,10 @@
|
||||||
"geolocation",
|
"geolocation",
|
||||||
"maxmind"
|
"maxmind"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/maxmind/MaxMind-DB-Reader-php/issues",
|
||||||
|
"source": "https://github.com/maxmind/MaxMind-DB-Reader-php/tree/v1.6.0"
|
||||||
|
},
|
||||||
"time": "2019-12-19T22:59:03+00:00"
|
"time": "2019-12-19T22:59:03+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -399,6 +432,10 @@
|
||||||
"email",
|
"email",
|
||||||
"pre-processing"
|
"pre-processing"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/MyIntervals/emogrifier/issues",
|
||||||
|
"source": "https://github.com/MyIntervals/emogrifier"
|
||||||
|
},
|
||||||
"time": "2019-12-26T19:37:31+00:00"
|
"time": "2019-12-26T19:37:31+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -448,26 +485,35 @@
|
||||||
"container-interop",
|
"container-interop",
|
||||||
"psr"
|
"psr"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/php-fig/container/issues",
|
||||||
|
"source": "https://github.com/php-fig/container/tree/master"
|
||||||
|
},
|
||||||
"time": "2017-02-14T16:28:37+00:00"
|
"time": "2017-02-14T16:28:37+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/css-selector",
|
"name": "symfony/css-selector",
|
||||||
"version": "v3.4.46",
|
"version": "v3.3.6",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/css-selector.git",
|
"url": "https://github.com/symfony/css-selector.git",
|
||||||
"reference": "da3d9da2ce0026771f5fe64cb332158f1bd2bc33"
|
"reference": "4d882dced7b995d5274293039370148e291808f2"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/css-selector/zipball/da3d9da2ce0026771f5fe64cb332158f1bd2bc33",
|
"url": "https://api.github.com/repos/symfony/css-selector/zipball/4d882dced7b995d5274293039370148e291808f2",
|
||||||
"reference": "da3d9da2ce0026771f5fe64cb332158f1bd2bc33",
|
"reference": "4d882dced7b995d5274293039370148e291808f2",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^5.5.9|>=7.0.8"
|
"php": ">=5.5.9"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "3.3-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Symfony\\Component\\CssSelector\\": ""
|
"Symfony\\Component\\CssSelector\\": ""
|
||||||
|
@ -481,14 +527,14 @@
|
||||||
"MIT"
|
"MIT"
|
||||||
],
|
],
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
|
||||||
"name": "Fabien Potencier",
|
|
||||||
"email": "fabien@symfony.com"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "Jean-François Simon",
|
"name": "Jean-François Simon",
|
||||||
"email": "jeanfrancois.simon@sensiolabs.com"
|
"email": "jeanfrancois.simon@sensiolabs.com"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Symfony Community",
|
"name": "Symfony Community",
|
||||||
"homepage": "https://symfony.com/contributors"
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
@ -496,21 +542,10 @@
|
||||||
],
|
],
|
||||||
"description": "Symfony CssSelector Component",
|
"description": "Symfony CssSelector Component",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"funding": [
|
"support": {
|
||||||
{
|
"source": "https://github.com/symfony/css-selector/tree/master"
|
||||||
"url": "https://symfony.com/sponsor",
|
|
||||||
"type": "custom"
|
|
||||||
},
|
},
|
||||||
{
|
"time": "2017-05-01T15:01:29+00:00"
|
||||||
"url": "https://github.com/fabpot",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
|
||||||
"type": "tidelift"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"time": "2020-10-24T10:57:07+00:00"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "woocommerce/action-scheduler",
|
"name": "woocommerce/action-scheduler",
|
||||||
|
@ -545,30 +580,35 @@
|
||||||
],
|
],
|
||||||
"description": "Action Scheduler for WordPress and WooCommerce",
|
"description": "Action Scheduler for WordPress and WooCommerce",
|
||||||
"homepage": "https://actionscheduler.org/",
|
"homepage": "https://actionscheduler.org/",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/woocommerce/action-scheduler/issues",
|
||||||
|
"source": "https://github.com/woocommerce/action-scheduler/tree/master"
|
||||||
|
},
|
||||||
"time": "2020-05-12T16:22:33+00:00"
|
"time": "2020-05-12T16:22:33+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "woocommerce/woocommerce-admin",
|
"name": "woocommerce/woocommerce-admin",
|
||||||
"version": "1.6.3",
|
"version": "1.7.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/woocommerce/woocommerce-admin.git",
|
"url": "https://github.com/woocommerce/woocommerce-admin.git",
|
||||||
"reference": "3015abbda8657ef097b7763e4c941daa06dab6f7"
|
"reference": "14dc0c78ce163ed0d5daf8f83765b65a76f61010"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/3015abbda8657ef097b7763e4c941daa06dab6f7",
|
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/14dc0c78ce163ed0d5daf8f83765b65a76f61010",
|
||||||
"reference": "3015abbda8657ef097b7763e4c941daa06dab6f7",
|
"reference": "14dc0c78ce163ed0d5daf8f83765b65a76f61010",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"automattic/jetpack-autoloader": "^2.2.0",
|
"automattic/jetpack-autoloader": "^2.2.0",
|
||||||
"composer/installers": "1.7.0",
|
"composer/installers": "^1.9.0",
|
||||||
"php": ">=5.6|>=7.0"
|
"php": ">=5.6|>=7.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "7.5.20",
|
"phpunit/phpunit": "7.5.20",
|
||||||
"woocommerce/woocommerce-sniffs": "0.0.9"
|
"suin/phpcs-psr4-sniff": "^2.2",
|
||||||
|
"woocommerce/woocommerce-sniffs": "0.1.0"
|
||||||
},
|
},
|
||||||
"type": "wordpress-plugin",
|
"type": "wordpress-plugin",
|
||||||
"extra": {
|
"extra": {
|
||||||
|
@ -579,9 +619,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"classmap": [
|
|
||||||
"includes/"
|
|
||||||
],
|
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Automattic\\WooCommerce\\Admin\\": "src/"
|
"Automattic\\WooCommerce\\Admin\\": "src/"
|
||||||
}
|
}
|
||||||
|
@ -592,29 +629,33 @@
|
||||||
],
|
],
|
||||||
"description": "A modern, javascript-driven WooCommerce Admin experience.",
|
"description": "A modern, javascript-driven WooCommerce Admin experience.",
|
||||||
"homepage": "https://github.com/woocommerce/woocommerce-admin",
|
"homepage": "https://github.com/woocommerce/woocommerce-admin",
|
||||||
"time": "2020-10-26T20:25:00+00:00"
|
"support": {
|
||||||
|
"issues": "https://github.com/woocommerce/woocommerce-admin/issues",
|
||||||
|
"source": "https://github.com/woocommerce/woocommerce-admin/tree/v1.7.0"
|
||||||
|
},
|
||||||
|
"time": "2020-11-11T22:56:39+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "woocommerce/woocommerce-blocks",
|
"name": "woocommerce/woocommerce-blocks",
|
||||||
"version": "v3.6.0",
|
"version": "v3.8.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/woocommerce/woocommerce-gutenberg-products-block.git",
|
"url": "https://github.com/woocommerce/woocommerce-gutenberg-products-block.git",
|
||||||
"reference": "1046697451f5e8906e48f0a7532b7637c3ded108"
|
"reference": "8b7d485ec8d26a6d5c9011dbdb49443cad9beee7"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/1046697451f5e8906e48f0a7532b7637c3ded108",
|
"url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/8b7d485ec8d26a6d5c9011dbdb49443cad9beee7",
|
||||||
"reference": "1046697451f5e8906e48f0a7532b7637c3ded108",
|
"reference": "8b7d485ec8d26a6d5c9011dbdb49443cad9beee7",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"automattic/jetpack-autoloader": "^2.0.0",
|
"automattic/jetpack-autoloader": "^2.0.0",
|
||||||
"composer/installers": "1.7.0"
|
"composer/installers": "^1.7.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "6.5.14",
|
"phpunit/phpunit": "6.5.14",
|
||||||
"woocommerce/woocommerce-sniffs": "0.0.7"
|
"woocommerce/woocommerce-sniffs": "0.1.0"
|
||||||
},
|
},
|
||||||
"type": "wordpress-plugin",
|
"type": "wordpress-plugin",
|
||||||
"extra": {
|
"extra": {
|
||||||
|
@ -639,7 +680,11 @@
|
||||||
"gutenberg",
|
"gutenberg",
|
||||||
"woocommerce"
|
"woocommerce"
|
||||||
],
|
],
|
||||||
"time": "2020-10-12T15:35:42+00:00"
|
"support": {
|
||||||
|
"issues": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues",
|
||||||
|
"source": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/v3.8.0"
|
||||||
|
},
|
||||||
|
"time": "2020-11-10T15:07:11+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packages-dev": [
|
"packages-dev": [
|
||||||
|
@ -687,6 +732,10 @@
|
||||||
"isolation",
|
"isolation",
|
||||||
"tool"
|
"tool"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/bamarni/composer-bin-plugin/issues",
|
||||||
|
"source": "https://github.com/bamarni/composer-bin-plugin/tree/master"
|
||||||
|
},
|
||||||
"time": "2020-05-03T08:27:20+00:00"
|
"time": "2020-05-03T08:27:20+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -700,7 +749,7 @@
|
||||||
},
|
},
|
||||||
"platform-dev": [],
|
"platform-dev": [],
|
||||||
"platform-overrides": {
|
"platform-overrides": {
|
||||||
"php": "7.1"
|
"php": "7.0"
|
||||||
},
|
},
|
||||||
"plugin-api-version": "1.1.0"
|
"plugin-api-version": "2.0.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -519,6 +519,50 @@ abstract class WC_Data {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to compute meta cache key. Different from WP Meta cache key in that meta data cached using this key also contains meta_id column.
|
||||||
|
*
|
||||||
|
* @since 4.7.0
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function get_meta_cache_key() {
|
||||||
|
if ( ! $this->get_id() ) {
|
||||||
|
wc_doing_it_wrong( 'get_meta_cache_key', 'ID needs to be set before fetching a cache key.', '4.7.0' );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return self::generate_meta_cache_key( $this->get_id(), $this->cache_group );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate cache key from id and group.
|
||||||
|
*
|
||||||
|
* @since 4.7.0
|
||||||
|
*
|
||||||
|
* @param int|string $id Object ID.
|
||||||
|
* @param string $cache_group Group name use to store cache. Whole group cache can be invalidated in one go.
|
||||||
|
*
|
||||||
|
* @return string Meta cache key.
|
||||||
|
*/
|
||||||
|
public static function generate_meta_cache_key( $id, $cache_group ) {
|
||||||
|
return WC_Cache_Helper::get_cache_prefix( $cache_group ) . WC_Cache_Helper::get_cache_prefix( 'object_' . $id ) . 'object_meta_' . $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prime caches for raw meta data. This includes meta_id column as well, which is not included by default in WP meta data.
|
||||||
|
*
|
||||||
|
* @since 4.7.0
|
||||||
|
*
|
||||||
|
* @param array $raw_meta_data_collection Array of objects of { object_id => array( meta_row_1, meta_row_2, ... }.
|
||||||
|
* @param string $cache_group Name of cache group.
|
||||||
|
*/
|
||||||
|
public static function prime_raw_meta_data_cache( $raw_meta_data_collection, $cache_group ) {
|
||||||
|
foreach ( $raw_meta_data_collection as $object_id => $raw_meta_data_array ) {
|
||||||
|
$cache_key = self::generate_meta_cache_key( $object_id, $cache_group );
|
||||||
|
wp_cache_set( $cache_key, $raw_meta_data_array, $cache_group );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read Meta Data from the database. Ignore any internal properties.
|
* Read Meta Data from the database. Ignore any internal properties.
|
||||||
* Uses it's own caches because get_metadata does not provide meta_ids.
|
* Uses it's own caches because get_metadata does not provide meta_ids.
|
||||||
|
@ -540,7 +584,7 @@ abstract class WC_Data {
|
||||||
|
|
||||||
if ( ! empty( $this->cache_group ) ) {
|
if ( ! empty( $this->cache_group ) ) {
|
||||||
// Prefix by group allows invalidation by group until https://core.trac.wordpress.org/ticket/4476 is implemented.
|
// Prefix by group allows invalidation by group until https://core.trac.wordpress.org/ticket/4476 is implemented.
|
||||||
$cache_key = WC_Cache_Helper::get_cache_prefix( $this->cache_group ) . WC_Cache_Helper::get_cache_prefix( 'object_' . $this->get_id() ) . 'object_meta_' . $this->get_id();
|
$cache_key = $this->get_meta_cache_key();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! $force_read ) {
|
if ( ! $force_read ) {
|
||||||
|
@ -550,7 +594,9 @@ abstract class WC_Data {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$raw_meta_data = $cache_loaded ? $cached_meta : $this->data_store->read_meta( $this );
|
// We filter the raw meta data again when loading from cache, in case we cached in an earlier version where filter conditions were different.
|
||||||
|
$raw_meta_data = $cache_loaded ? $this->data_store->filter_raw_meta_data( $this, $cached_meta ) : $this->data_store->read_meta( $this );
|
||||||
|
|
||||||
if ( $raw_meta_data ) {
|
if ( $raw_meta_data ) {
|
||||||
foreach ( $raw_meta_data as $meta ) {
|
foreach ( $raw_meta_data as $meta ) {
|
||||||
$this->meta_data[] = new WC_Meta_Data(
|
$this->meta_data[] = new WC_Meta_Data(
|
||||||
|
|
|
@ -370,9 +370,9 @@ class WC_Admin_Addons {
|
||||||
|
|
||||||
$defaults = array(
|
$defaults = array(
|
||||||
'image' => WC()->plugin_url() . '/assets/images/wcs-extensions-banner-3x.png',
|
'image' => WC()->plugin_url() . '/assets/images/wcs-extensions-banner-3x.png',
|
||||||
'image_alt' => __( 'WooCommerce Services', 'woocommerce' ),
|
'image_alt' => __( 'WooCommerce Shipping', 'woocommerce' ),
|
||||||
'title' => __( 'Buy discounted shipping labels — then print them from your dashboard.', 'woocommerce' ),
|
'title' => __( 'Buy discounted shipping labels — then print them from your dashboard.', 'woocommerce' ),
|
||||||
'description' => __( 'Integrate your store with USPS to buy discounted shipping labels, and print them directly from your WooCommerce dashboard. Powered by WooCommerce Services.', 'woocommerce' ),
|
'description' => __( 'Integrate your store with USPS to buy discounted shipping labels, and print them directly from your WooCommerce dashboard. Powered by WooCommerce Shipping.', 'woocommerce' ),
|
||||||
'button' => __( 'Free - Install now', 'woocommerce' ),
|
'button' => __( 'Free - Install now', 'woocommerce' ),
|
||||||
'href' => $button_url,
|
'href' => $button_url,
|
||||||
'logos' => array(),
|
'logos' => array(),
|
||||||
|
@ -383,7 +383,7 @@ class WC_Admin_Addons {
|
||||||
$local_defaults = array(
|
$local_defaults = array(
|
||||||
'image' => WC()->plugin_url() . '/assets/images/wcs-truck-banner-3x.png',
|
'image' => WC()->plugin_url() . '/assets/images/wcs-truck-banner-3x.png',
|
||||||
'title' => __( 'Show Canada Post shipping rates', 'woocommerce' ),
|
'title' => __( 'Show Canada Post shipping rates', 'woocommerce' ),
|
||||||
'description' => __( 'Display live rates from Canada Post at checkout to make shipping a breeze. Powered by WooCommerce Services.', 'woocommerce' ),
|
'description' => __( 'Display live rates from Canada Post at checkout to make shipping a breeze. Powered by WooCommerce Shipping.', 'woocommerce' ),
|
||||||
'logos' => array_merge(
|
'logos' => array_merge(
|
||||||
$defaults['logos'],
|
$defaults['logos'],
|
||||||
array(
|
array(
|
||||||
|
@ -440,7 +440,69 @@ class WC_Admin_Addons {
|
||||||
self::output_button(
|
self::output_button(
|
||||||
$block_data['href'],
|
$block_data['href'],
|
||||||
$block_data['button'],
|
$block_data['button'],
|
||||||
'addons-button-outline-green'
|
'addons-button-outline-purple'
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the outputting of the WooCommerce Pay banner block.
|
||||||
|
*
|
||||||
|
* @param object $block Block data.
|
||||||
|
*/
|
||||||
|
public static function output_wcpay_banner_block( $block = array() ) {
|
||||||
|
$is_active = is_plugin_active( 'woocommerce-payments/woocommerce-payments.php' );
|
||||||
|
$location = wc_get_base_location();
|
||||||
|
|
||||||
|
if (
|
||||||
|
! in_array( $location['country'], array( 'US' ), true ) ||
|
||||||
|
$is_active ||
|
||||||
|
! current_user_can( 'install_plugins' ) ||
|
||||||
|
! current_user_can( 'activate_plugins' )
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$button_url = wp_nonce_url(
|
||||||
|
add_query_arg(
|
||||||
|
array(
|
||||||
|
'install-addon' => 'woocommerce-payments',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'install-addon_woocommerce-payments'
|
||||||
|
);
|
||||||
|
|
||||||
|
$defaults = array(
|
||||||
|
'image' => WC()->plugin_url() . '/assets/images/wcpayments-icon-secure.png',
|
||||||
|
'image_alt' => __( 'WooCommerce Payments', 'woocommerce' ),
|
||||||
|
'title' => __( 'Payments made simple, with no monthly fees — exclusively for WooCommerce stores.', 'woocommerce' ),
|
||||||
|
'description' => __( 'Securely accept cards in your store. See payments, track cash flow into your bank account, and stay on top of disputes – right from your dashboard.', 'woocommerce' ),
|
||||||
|
'button' => __( 'Free - Install now', 'woocommerce' ),
|
||||||
|
'href' => $button_url,
|
||||||
|
'logos' => array(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$block_data = array_merge( $defaults, $block );
|
||||||
|
?>
|
||||||
|
<div class="addons-wcs-banner-block">
|
||||||
|
<div class="addons-wcs-banner-block-image">
|
||||||
|
<img
|
||||||
|
class="addons-img"
|
||||||
|
src="<?php echo esc_url( $block_data['image'] ); ?>"
|
||||||
|
alt="<?php echo esc_attr( $block_data['image_alt'] ); ?>"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="addons-wcs-banner-block-content">
|
||||||
|
<h1><?php echo esc_html( $block_data['title'] ); ?></h1>
|
||||||
|
<p><?php echo esc_html( $block_data['description'] ); ?></p>
|
||||||
|
<?php
|
||||||
|
self::output_button(
|
||||||
|
$block_data['href'],
|
||||||
|
$block_data['button'],
|
||||||
|
'addons-button-outline-purple'
|
||||||
);
|
);
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
|
@ -477,6 +539,9 @@ class WC_Admin_Addons {
|
||||||
case 'wcs_banner_block':
|
case 'wcs_banner_block':
|
||||||
self::output_wcs_banner_block( (array) $section );
|
self::output_wcs_banner_block( (array) $section );
|
||||||
break;
|
break;
|
||||||
|
case 'wcpay_banner_block':
|
||||||
|
self::output_wcpay_banner_block( (array) $section );
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -520,7 +585,7 @@ class WC_Admin_Addons {
|
||||||
* @param string $plugin The plugin the button is promoting.
|
* @param string $plugin The plugin the button is promoting.
|
||||||
*/
|
*/
|
||||||
public static function output_button( $url, $text, $style, $plugin = '' ) {
|
public static function output_button( $url, $text, $style, $plugin = '' ) {
|
||||||
$style = __( 'Free', 'woocommerce' ) === $text ? 'addons-button-outline-green' : $style;
|
$style = __( 'Free', 'woocommerce' ) === $text ? 'addons-button-outline-purple' : $style;
|
||||||
$style = is_plugin_active( $plugin ) ? 'addons-button-installed' : $style;
|
$style = is_plugin_active( $plugin ) ? 'addons-button-installed' : $style;
|
||||||
$text = is_plugin_active( $plugin ) ? __( 'Installed', 'woocommerce' ) : $text;
|
$text = is_plugin_active( $plugin ) ? __( 'Installed', 'woocommerce' ) : $text;
|
||||||
$url = self::add_in_app_purchase_url_params( $url );
|
$url = self::add_in_app_purchase_url_params( $url );
|
||||||
|
@ -546,8 +611,18 @@ class WC_Admin_Addons {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( isset( $_GET['install-addon'] ) && 'woocommerce-services' === $_GET['install-addon'] ) {
|
if ( isset( $_GET['install-addon'] ) ) {
|
||||||
|
switch ( $_GET['install-addon'] ) {
|
||||||
|
case 'woocommerce-services':
|
||||||
self::install_woocommerce_services_addon();
|
self::install_woocommerce_services_addon();
|
||||||
|
break;
|
||||||
|
case 'woocommerce-payments':
|
||||||
|
self::install_woocommerce_payments_addon();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Do nothing.
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$sections = self::get_sections();
|
$sections = self::get_sections();
|
||||||
|
@ -591,6 +666,26 @@ class WC_Admin_Addons {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install WooCommerce Payments from the Extensions screens.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function install_woocommerce_payments_addon() {
|
||||||
|
check_admin_referer( 'install-addon_woocommerce-payments' );
|
||||||
|
|
||||||
|
$wcpay_plugin_id = 'woocommerce-payments';
|
||||||
|
$wcpay_plugin = array(
|
||||||
|
'name' => __( 'WooCommerce Payments', 'woocommerce' ),
|
||||||
|
'repo-slug' => 'woocommerce-payments',
|
||||||
|
);
|
||||||
|
|
||||||
|
WC_Install::background_installer( $services_plugin_id, $wcpay_plugin );
|
||||||
|
|
||||||
|
wp_safe_redirect( remove_query_arg( array( 'install-addon', '_wpnonce' ) ) );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should an extension be shown on the featured page.
|
* Should an extension be shown on the featured page.
|
||||||
*
|
*
|
||||||
|
|
|
@ -377,6 +377,11 @@ class WC_Admin_Status {
|
||||||
private static function output_plugins_info( $plugins, $untested_plugins ) {
|
private static function output_plugins_info( $plugins, $untested_plugins ) {
|
||||||
$wc_version = Constants::get_constant( 'WC_VERSION' );
|
$wc_version = Constants::get_constant( 'WC_VERSION' );
|
||||||
|
|
||||||
|
if ( 'major' === WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE ) {
|
||||||
|
// Since we're only testing against major, we don't need to show minor and patch version.
|
||||||
|
$wc_version = $wc_version[0] . '.0';
|
||||||
|
}
|
||||||
|
|
||||||
foreach ( $plugins as $plugin ) {
|
foreach ( $plugins as $plugin ) {
|
||||||
if ( ! empty( $plugin['name'] ) ) {
|
if ( ! empty( $plugin['name'] ) ) {
|
||||||
// Link the plugin name to the plugin url if available.
|
// Link the plugin name to the plugin url if available.
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
use \Automattic\Jetpack\Constants;
|
use \Automattic\Jetpack\Constants;
|
||||||
use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Note;
|
use Automattic\WooCommerce\Admin\Notes\Note;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WC_Notes_Run_Db_Update.
|
* WC_Notes_Run_Db_Update.
|
||||||
|
@ -58,7 +58,7 @@ class WC_Notes_Run_Db_Update {
|
||||||
// Remove weird duplicates. Leave the first one.
|
// Remove weird duplicates. Leave the first one.
|
||||||
$current_notice = array_shift( $note_ids );
|
$current_notice = array_shift( $note_ids );
|
||||||
foreach ( $note_ids as $note_id ) {
|
foreach ( $note_ids as $note_id ) {
|
||||||
$note = new WC_Admin_Note( $note_id );
|
$note = new Note( $note_id );
|
||||||
$data_store->delete( $note );
|
$data_store->delete( $note );
|
||||||
}
|
}
|
||||||
return $current_notice;
|
return $current_notice;
|
||||||
|
@ -77,8 +77,8 @@ class WC_Notes_Run_Db_Update {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$note = new WC_Admin_Note( $note_id );
|
$note = new Note( $note_id );
|
||||||
$note->set_status( WC_Admin_Note::E_WC_ADMIN_NOTE_ACTIONED );
|
$note->set_status( Note::E_WC_ADMIN_NOTE_ACTIONED );
|
||||||
$note->save();
|
$note->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ class WC_Notes_Run_Db_Update {
|
||||||
* - actions are set up for the first 'Update database' notice, and
|
* - actions are set up for the first 'Update database' notice, and
|
||||||
* - URL for note's action is equal to the given URL (to check for potential nonce update).
|
* - URL for note's action is equal to the given URL (to check for potential nonce update).
|
||||||
*
|
*
|
||||||
* @param WC_Admin_Note $note Note to check.
|
* @param Note $note Note to check.
|
||||||
* @param string $update_url URL to check the note against.
|
* @param string $update_url URL to check the note against.
|
||||||
* @param array<int, string> $current_actions List of actions to check for.
|
* @param array<int, string> $current_actions List of actions to check for.
|
||||||
* @return bool
|
* @return bool
|
||||||
|
@ -135,9 +135,9 @@ class WC_Notes_Run_Db_Update {
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( $note_id ) {
|
if ( $note_id ) {
|
||||||
$note = new WC_Admin_Note( $note_id );
|
$note = new Note( $note_id );
|
||||||
} else {
|
} else {
|
||||||
$note = new WC_Admin_Note();
|
$note = new Note();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the note needs to be updated (e.g. expired nonce or different note type stored in the previous run).
|
// Check if the note needs to be updated (e.g. expired nonce or different note type stored in the previous run).
|
||||||
|
@ -151,13 +151,13 @@ class WC_Notes_Run_Db_Update {
|
||||||
/* translators: %1$s: opening <a> tag %2$s: closing </a> tag*/
|
/* translators: %1$s: opening <a> tag %2$s: closing </a> tag*/
|
||||||
. sprintf( ' ' . esc_html__( 'The database update process runs in the background and may take a little while, so please be patient. Advanced users can alternatively update via %1$sWP CLI%2$s.', 'woocommerce' ), '<a href="https://github.com/woocommerce/woocommerce/wiki/Upgrading-the-database-using-WP-CLI">', '</a>' )
|
. sprintf( ' ' . esc_html__( 'The database update process runs in the background and may take a little while, so please be patient. Advanced users can alternatively update via %1$sWP CLI%2$s.', 'woocommerce' ), '<a href="https://github.com/woocommerce/woocommerce/wiki/Upgrading-the-database-using-WP-CLI">', '</a>' )
|
||||||
);
|
);
|
||||||
$note->set_type( WC_Admin_Note::E_WC_ADMIN_NOTE_UPDATE );
|
$note->set_type( Note::E_WC_ADMIN_NOTE_UPDATE );
|
||||||
$note->set_name( self::NOTE_NAME );
|
$note->set_name( self::NOTE_NAME );
|
||||||
$note->set_content_data( (object) array() );
|
$note->set_content_data( (object) array() );
|
||||||
$note->set_source( 'woocommerce-core' );
|
$note->set_source( 'woocommerce-core' );
|
||||||
// In case db version is out of sync with WC version or during the next update, the notice needs to show up again,
|
// In case db version is out of sync with WC version or during the next update, the notice needs to show up again,
|
||||||
// so set it to unactioned.
|
// so set it to unactioned.
|
||||||
$note->set_status( WC_Admin_Note::E_WC_ADMIN_NOTE_UNACTIONED );
|
$note->set_status( Note::E_WC_ADMIN_NOTE_UNACTIONED );
|
||||||
|
|
||||||
// Set new actions.
|
// Set new actions.
|
||||||
$note->clear_actions();
|
$note->clear_actions();
|
||||||
|
@ -181,7 +181,7 @@ class WC_Notes_Run_Db_Update {
|
||||||
$cron_disabled = Constants::is_true( 'DISABLE_WP_CRON' );
|
$cron_disabled = Constants::is_true( 'DISABLE_WP_CRON' );
|
||||||
$cron_cta = $cron_disabled ? __( 'You can manually run queued updates here.', 'woocommerce' ) : __( 'View progress →', 'woocommerce' );
|
$cron_cta = $cron_disabled ? __( 'You can manually run queued updates here.', 'woocommerce' ) : __( 'View progress →', 'woocommerce' );
|
||||||
|
|
||||||
$note = new WC_Admin_Note( $note_id );
|
$note = new Note( $note_id );
|
||||||
$note->set_title( __( 'WooCommerce database update in progress', 'woocommerce' ) );
|
$note->set_title( __( 'WooCommerce database update in progress', 'woocommerce' ) );
|
||||||
$note->set_content( __( 'WooCommerce is updating the database in the background. The database update process may take a little while, so please be patient.', 'woocommerce' ) );
|
$note->set_content( __( 'WooCommerce is updating the database in the background. The database update process may take a little while, so please be patient.', 'woocommerce' ) );
|
||||||
|
|
||||||
|
@ -227,7 +227,7 @@ class WC_Notes_Run_Db_Update {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
$note = new WC_Admin_Note( $note_id );
|
$note = new Note( $note_id );
|
||||||
|
|
||||||
// Check if the note needs to be updated (e.g. expired nonce or different note type stored in the previous run).
|
// Check if the note needs to be updated (e.g. expired nonce or different note type stored in the previous run).
|
||||||
if ( self::note_up_to_date( $note, $hide_notices_url, wp_list_pluck( $note_actions, 'name' ) ) ) {
|
if ( self::note_up_to_date( $note, $hide_notices_url, wp_list_pluck( $note_actions, 'name' ) ) ) {
|
||||||
|
@ -266,7 +266,7 @@ class WC_Notes_Run_Db_Update {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$note = new WC_Admin_Note( $note_id );
|
$note = new Note( $note_id );
|
||||||
if ( $note::E_WC_ADMIN_NOTE_ACTIONED === $note->get_status() ) {
|
if ( $note::E_WC_ADMIN_NOTE_ACTIONED === $note->get_status() ) {
|
||||||
// Db update not needed && note actioned -> don't show it.
|
// Db update not needed && note actioned -> don't show it.
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -163,15 +163,6 @@ class WC_Plugin_Updates {
|
||||||
$version .= '.' . $new_version_parts[1];
|
$version .= '.' . $new_version_parts[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( 'major' === $release ) {
|
|
||||||
$current_version_parts = explode( '.', Constants::get_constant( 'WC_VERSION' ) );
|
|
||||||
|
|
||||||
// If user has already moved to the major version, we don't need to flag up anything.
|
|
||||||
if ( version_compare( $current_version_parts[0] . '.' . $current_version_parts[1], $new_version_parts[0] . '.0', '>=' ) ) {
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ( $extensions as $file => $plugin ) {
|
foreach ( $extensions as $file => $plugin ) {
|
||||||
if ( ! empty( $plugin[ self::VERSION_TESTED_HEADER ] ) ) {
|
if ( ! empty( $plugin[ self::VERSION_TESTED_HEADER ] ) ) {
|
||||||
$plugin_version_parts = explode( '.', $plugin[ self::VERSION_TESTED_HEADER ] );
|
$plugin_version_parts = explode( '.', $plugin[ self::VERSION_TESTED_HEADER ] );
|
||||||
|
|
|
@ -41,6 +41,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||||
|
|
||||||
<?php if ( isset( $_GET['search'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>
|
<?php if ( isset( $_GET['search'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>
|
||||||
<h1 class="search-form-title" >
|
<h1 class="search-form-title" >
|
||||||
|
<?php // translators: search keyword. ?>
|
||||||
<?php printf( esc_html__( 'Showing search results for: %s', 'woocommerce' ), '<strong>' . esc_html( sanitize_text_field( wp_unslash( $_GET['search'] ) ) ) . '</strong>' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>
|
<?php printf( esc_html__( 'Showing search results for: %s', 'woocommerce' ), '<strong>' . esc_html( sanitize_text_field( wp_unslash( $_GET['search'] ) ) ) . '</strong>' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>
|
||||||
</h1>
|
</h1>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
@ -71,6 +72,11 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||||
<?php WC_Admin_Addons::output_wcs_banner_block(); ?>
|
<?php WC_Admin_Addons::output_wcs_banner_block(); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<?php if ( 'payment-gateways' === $current_section ) : ?>
|
||||||
|
<div class="addons-shipping-methods">
|
||||||
|
<?php WC_Admin_Addons::output_wcpay_banner_block(); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
<ul class="products">
|
<ul class="products">
|
||||||
<?php foreach ( $addons as $addon ) : ?>
|
<?php foreach ( $addons as $addon ) : ?>
|
||||||
<?php
|
<?php
|
||||||
|
|
|
@ -9,6 +9,11 @@ defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
|
if ( ! defined( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE' ) ) {
|
||||||
|
// Define if we're checking against major or minor versions.
|
||||||
|
define( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE', 'major' );
|
||||||
|
}
|
||||||
|
|
||||||
$report = wc()->api->get_endpoint_data( '/wc/v3/system_status' );
|
$report = wc()->api->get_endpoint_data( '/wc/v3/system_status' );
|
||||||
$environment = $report['environment'];
|
$environment = $report['environment'];
|
||||||
$database = $report['database'];
|
$database = $report['database'];
|
||||||
|
@ -21,7 +26,7 @@ $security = $report['security'];
|
||||||
$settings = $report['settings'];
|
$settings = $report['settings'];
|
||||||
$wp_pages = $report['pages'];
|
$wp_pages = $report['pages'];
|
||||||
$plugin_updates = new WC_Plugin_Updates();
|
$plugin_updates = new WC_Plugin_Updates();
|
||||||
$untested_plugins = $plugin_updates->get_untested_plugins( WC()->version, 'minor' );
|
$untested_plugins = $plugin_updates->get_untested_plugins( WC()->version, WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE );
|
||||||
?>
|
?>
|
||||||
<div class="updated woocommerce-message inline">
|
<div class="updated woocommerce-message inline">
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -218,6 +218,14 @@ function wc_maybe_adjust_line_item_product_stock( $item, $item_quantity = -1 ) {
|
||||||
$refunded_item_quantity = $order->get_qty_refunded_for_item( $item->get_id() );
|
$refunded_item_quantity = $order->get_qty_refunded_for_item( $item->get_id() );
|
||||||
$diff = $item_quantity + $refunded_item_quantity - $already_reduced_stock;
|
$diff = $item_quantity + $refunded_item_quantity - $already_reduced_stock;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 0 as $item_quantity usually indicates we're deleting the order item.
|
||||||
|
* We need to perform different calculations for this case.
|
||||||
|
*/
|
||||||
|
if ( 0 === $item_quantity ) {
|
||||||
|
$diff = min( absint( $refunded_item_quantity ), $already_reduced_stock ) * -1;
|
||||||
|
}
|
||||||
|
|
||||||
if ( $diff < 0 ) {
|
if ( $diff < 0 ) {
|
||||||
$new_stock = wc_update_product_stock( $product, $diff * -1, 'increase' );
|
$new_stock = wc_update_product_stock( $product, $diff * -1, 'increase' );
|
||||||
} elseif ( $diff > 0 ) {
|
} elseif ( $diff > 0 ) {
|
||||||
|
|
|
@ -1572,10 +1572,15 @@ class WC_AJAX {
|
||||||
$data_store = WC_Data_Store::load( 'product' );
|
$data_store = WC_Data_Store::load( 'product' );
|
||||||
$ids = $data_store->search_products( $term, '', (bool) $include_variations, false, $limit, $include_ids, $exclude_ids );
|
$ids = $data_store->search_products( $term, '', (bool) $include_variations, false, $limit, $include_ids, $exclude_ids );
|
||||||
|
|
||||||
$product_objects = array_filter( array_map( 'wc_get_product', $ids ), 'wc_products_array_filter_readable' );
|
|
||||||
$products = array();
|
$products = array();
|
||||||
|
|
||||||
foreach ( $product_objects as $product_object ) {
|
foreach ( $ids as $id ) {
|
||||||
|
$product_object = wc_get_product( $id );
|
||||||
|
|
||||||
|
if ( ! wc_products_array_filter_readable( $product_object ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$formatted_name = $product_object->get_formatted_name();
|
$formatted_name = $product_object->get_formatted_name();
|
||||||
$managing_stock = $product_object->managing_stock();
|
$managing_stock = $product_object->managing_stock();
|
||||||
|
|
||||||
|
|
|
@ -667,15 +667,18 @@ class WC_Checkout {
|
||||||
* @return array of data.
|
* @return array of data.
|
||||||
*/
|
*/
|
||||||
public function get_posted_data() {
|
public function get_posted_data() {
|
||||||
$skipped = array();
|
// phpcs:disable WordPress.Security.NonceVerification.Missing
|
||||||
$data = array(
|
$data = array(
|
||||||
'terms' => (int) isset( $_POST['terms'] ), // WPCS: input var ok, CSRF ok.
|
'terms' => (int) isset( $_POST['terms'] ),
|
||||||
'createaccount' => (int) ! empty( $_POST['createaccount'] ), // WPCS: input var ok, CSRF ok.
|
'createaccount' => (int) ( $this->is_registration_enabled() ? ! empty( $_POST['createaccount'] ) : false ),
|
||||||
'payment_method' => isset( $_POST['payment_method'] ) ? wc_clean( wp_unslash( $_POST['payment_method'] ) ) : '', // WPCS: input var ok, CSRF ok.
|
'payment_method' => isset( $_POST['payment_method'] ) ? wc_clean( wp_unslash( $_POST['payment_method'] ) ) : '',
|
||||||
'shipping_method' => isset( $_POST['shipping_method'] ) ? wc_clean( wp_unslash( $_POST['shipping_method'] ) ) : '', // WPCS: input var ok, CSRF ok.
|
'shipping_method' => isset( $_POST['shipping_method'] ) ? wc_clean( wp_unslash( $_POST['shipping_method'] ) ) : '',
|
||||||
'ship_to_different_address' => ! empty( $_POST['ship_to_different_address'] ) && ! wc_ship_to_billing_address_only(), // WPCS: input var ok, CSRF ok.
|
'ship_to_different_address' => ! empty( $_POST['ship_to_different_address'] ) && ! wc_ship_to_billing_address_only(),
|
||||||
'woocommerce_checkout_update_totals' => isset( $_POST['woocommerce_checkout_update_totals'] ), // WPCS: input var ok, CSRF ok.
|
'woocommerce_checkout_update_totals' => isset( $_POST['woocommerce_checkout_update_totals'] ),
|
||||||
);
|
);
|
||||||
|
// phpcs:enable WordPress.Security.NonceVerification.Missing
|
||||||
|
|
||||||
|
$skipped = array();
|
||||||
foreach ( $this->get_checkout_fields() as $fieldset_key => $fieldset ) {
|
foreach ( $this->get_checkout_fields() as $fieldset_key => $fieldset ) {
|
||||||
if ( $this->maybe_skip_fieldset( $fieldset_key, $data ) ) {
|
if ( $this->maybe_skip_fieldset( $fieldset_key, $data ) ) {
|
||||||
$skipped[] = $fieldset_key;
|
$skipped[] = $fieldset_key;
|
||||||
|
|
|
@ -906,12 +906,9 @@ class WC_Form_Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent parent variable product from being added to cart.
|
// Prevent parent variable product from being added to cart.
|
||||||
if ( empty( $variation_id ) && $product->is_type( 'variable' ) ) {
|
if ( empty( $variation_id ) && $product && $product->is_type( 'variable' ) ) {
|
||||||
$url = get_permalink( $product_id );
|
/* translators: 1: product link, 2: product name */
|
||||||
$product_name = $product->get_name();
|
wc_add_notice( sprintf( __( 'Please choose product options by visiting <a href="%1$s" title="%2$s">%2$s</a>.', 'woocommerce' ), esc_url( get_permalink( $product_id ) ), esc_html( $product->get_name() ) ), 'error' );
|
||||||
|
|
||||||
/* translators: %1$s: Product link, %2$s: Product title, %3$s: Product name. */
|
|
||||||
wc_add_notice( sprintf( __( 'Please choose product options by visiting <a href="%1$s" title="%2$s">%3$s</a>.', 'woocommerce' ), esc_url( $url ), esc_html( $product_name ), esc_html( $product_name ) ), 'error' );
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -308,7 +308,7 @@ class WC_Frontend_Scripts {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register all WC sty;es.
|
* Register all WC styles.
|
||||||
*/
|
*/
|
||||||
private static function register_styles() {
|
private static function register_styles() {
|
||||||
$version = Constants::get_constant( 'WC_VERSION' );
|
$version = Constants::get_constant( 'WC_VERSION' );
|
||||||
|
|
|
@ -220,7 +220,7 @@ class WC_Shortcodes {
|
||||||
|
|
||||||
foreach ( $product_categories as $category ) {
|
foreach ( $product_categories as $category ) {
|
||||||
wc_get_template(
|
wc_get_template(
|
||||||
'content-product_cat.php',
|
'content-product-cat.php',
|
||||||
array(
|
array(
|
||||||
'category' => $category,
|
'category' => $category,
|
||||||
)
|
)
|
||||||
|
@ -230,7 +230,7 @@ class WC_Shortcodes {
|
||||||
woocommerce_product_loop_end();
|
woocommerce_product_loop_end();
|
||||||
}
|
}
|
||||||
|
|
||||||
woocommerce_reset_loop();
|
wc_reset_loop();
|
||||||
|
|
||||||
return '<div class="woocommerce columns-' . $columns . '">' . ob_get_clean() . '</div>';
|
return '<div class="woocommerce columns-' . $columns . '">' . ob_get_clean() . '</div>';
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,9 @@ class WC_Validation {
|
||||||
case 'BA':
|
case 'BA':
|
||||||
$valid = (bool) preg_match( '/^([7-8]{1})([0-9]{4})$/', $postcode );
|
$valid = (bool) preg_match( '/^([7-8]{1})([0-9]{4})$/', $postcode );
|
||||||
break;
|
break;
|
||||||
|
case 'BE':
|
||||||
|
$valid = (bool) preg_match( '/^([0-9]{4})$/i', $postcode );
|
||||||
|
break;
|
||||||
case 'BR':
|
case 'BR':
|
||||||
$valid = (bool) preg_match( '/^([0-9]{5})([-])?([0-9]{3})$/', $postcode );
|
$valid = (bool) preg_match( '/^([0-9]{5})([-])?([0-9]{3})$/', $postcode );
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -22,7 +22,7 @@ final class WooCommerce {
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $version = '4.8.0';
|
public $version = '4.9.0';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WooCommerce Schema version.
|
* WooCommerce Schema version.
|
||||||
|
|
|
@ -93,14 +93,13 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
|
||||||
/**
|
/**
|
||||||
* Method to read an order from the database.
|
* Method to read an order from the database.
|
||||||
*
|
*
|
||||||
* @param WC_Data $order Order object.
|
* @param WC_Order $order Order object.
|
||||||
*
|
*
|
||||||
* @throws Exception If passed order is invalid.
|
* @throws Exception If passed order is invalid.
|
||||||
*/
|
*/
|
||||||
public function read( &$order ) {
|
public function read( &$order ) {
|
||||||
$order->set_defaults();
|
$order->set_defaults();
|
||||||
$post_object = get_post( $order->get_id() );
|
$post_object = get_post( $order->get_id() );
|
||||||
|
|
||||||
if ( ! $order->get_id() || ! $post_object || ! in_array( $post_object->post_type, wc_get_order_types(), true ) ) {
|
if ( ! $order->get_id() || ! $post_object || ! in_array( $post_object->post_type, wc_get_order_types(), true ) ) {
|
||||||
throw new Exception( __( 'Invalid order.', 'woocommerce' ) );
|
throw new Exception( __( 'Invalid order.', 'woocommerce' ) );
|
||||||
}
|
}
|
||||||
|
@ -108,8 +107,8 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
|
||||||
$order->set_props(
|
$order->set_props(
|
||||||
array(
|
array(
|
||||||
'parent_id' => $post_object->post_parent,
|
'parent_id' => $post_object->post_parent,
|
||||||
'date_created' => 0 < $post_object->post_date_gmt ? wc_string_to_timestamp( $post_object->post_date_gmt ) : null,
|
'date_created' => $this->string_to_timestamp( $post_object->post_date_gmt ),
|
||||||
'date_modified' => 0 < $post_object->post_modified_gmt ? wc_string_to_timestamp( $post_object->post_modified_gmt ) : null,
|
'date_modified' => $this->string_to_timestamp( $post_object->post_modified_gmt ),
|
||||||
'status' => $post_object->post_status,
|
'status' => $post_object->post_status,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -123,8 +123,8 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat
|
||||||
array(
|
array(
|
||||||
'code' => $post_object->post_title,
|
'code' => $post_object->post_title,
|
||||||
'description' => $post_object->post_excerpt,
|
'description' => $post_object->post_excerpt,
|
||||||
'date_created' => 0 < $post_object->post_date_gmt ? wc_string_to_timestamp( $post_object->post_date_gmt ) : null,
|
'date_created' => $this->string_to_timestamp( $post_object->post_date_gmt ),
|
||||||
'date_modified' => 0 < $post_object->post_modified_gmt ? wc_string_to_timestamp( $post_object->post_modified_gmt ) : null,
|
'date_modified' => $this->string_to_timestamp( $post_object->post_modified_gmt ),
|
||||||
'date_expires' => metadata_exists( 'post', $coupon_id, 'date_expires' ) ? get_post_meta( $coupon_id, 'date_expires', true ) : get_post_meta( $coupon_id, 'expiry_date', true ), // @todo: Migrate expiry_date meta to date_expires in upgrade routine.
|
'date_expires' => metadata_exists( 'post', $coupon_id, 'date_expires' ) ? get_post_meta( $coupon_id, 'date_expires', true ) : get_post_meta( $coupon_id, 'expiry_date', true ), // @todo: Migrate expiry_date meta to date_expires in upgrade routine.
|
||||||
'discount_type' => get_post_meta( $coupon_id, 'discount_type', true ),
|
'discount_type' => get_post_meta( $coupon_id, 'discount_type', true ),
|
||||||
'amount' => get_post_meta( $coupon_id, 'coupon_amount', true ),
|
'amount' => get_post_meta( $coupon_id, 'coupon_amount', true ),
|
||||||
|
|
|
@ -152,10 +152,11 @@ class WC_Customer_Data_Store extends WC_Data_Store_WP implements WC_Customer_Dat
|
||||||
|
|
||||||
$customer_id = $customer->get_id();
|
$customer_id = $customer->get_id();
|
||||||
|
|
||||||
// Load meta but exclude deprecated props.
|
// Load meta but exclude deprecated props and parent keys.
|
||||||
$user_meta = array_diff_key(
|
$user_meta = array_diff_key(
|
||||||
array_change_key_case( array_map( 'wc_flatten_meta_callback', get_user_meta( $customer_id ) ) ),
|
array_change_key_case( array_map( 'wc_flatten_meta_callback', get_user_meta( $customer_id ) ) ),
|
||||||
array_flip( array( 'country', 'state', 'postcode', 'city', 'address', 'address_2', 'default', 'location' ) )
|
array_flip( array( 'country', 'state', 'postcode', 'city', 'address', 'address_2', 'default', 'location' ) ),
|
||||||
|
array_change_key_case( (array) $user_object->data )
|
||||||
);
|
);
|
||||||
|
|
||||||
$customer->set_props( $user_meta );
|
$customer->set_props( $user_meta );
|
||||||
|
|
|
@ -94,7 +94,20 @@ class WC_Data_Store_WP {
|
||||||
$object->get_id()
|
$object->get_id()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
return $this->filter_raw_meta_data( $object, $raw_meta_data );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to filter internal meta keys from all meta data rows for the object.
|
||||||
|
*
|
||||||
|
* @since 4.7.0
|
||||||
|
*
|
||||||
|
* @param WC_Data $object WC_Data object.
|
||||||
|
* @param array $raw_meta_data Array of std object of meta data to be filtered.
|
||||||
|
*
|
||||||
|
* @return mixed|void
|
||||||
|
*/
|
||||||
|
public function filter_raw_meta_data( &$object, $raw_meta_data ) {
|
||||||
$this->internal_meta_keys = array_merge( array_map( array( $this, 'prefix_key' ), $object->get_data_keys() ), $this->internal_meta_keys );
|
$this->internal_meta_keys = array_merge( array_map( array( $this, 'prefix_key' ), $object->get_data_keys() ), $this->internal_meta_keys );
|
||||||
$meta_data = array_filter( $raw_meta_data, array( $this, 'exclude_internal_meta_keys' ) );
|
$meta_data = array_filter( $raw_meta_data, array( $this, 'exclude_internal_meta_keys' ) );
|
||||||
return apply_filters( "woocommerce_data_store_wp_{$this->meta_type}_read_meta", $meta_data, $object, $this );
|
return apply_filters( "woocommerce_data_store_wp_{$this->meta_type}_read_meta", $meta_data, $object, $this );
|
||||||
|
@ -630,4 +643,16 @@ class WC_Data_Store_WP {
|
||||||
);
|
);
|
||||||
wp_cache_delete( 'lookup_table', 'object_' . $id );
|
wp_cache_delete( 'lookup_table', 'object_' . $id );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a WP post date string into a timestamp.
|
||||||
|
*
|
||||||
|
* @since 4.8.0
|
||||||
|
*
|
||||||
|
* @param string $time_string The WP post date string.
|
||||||
|
* @return int|null The date string converted to a timestamp or null.
|
||||||
|
*/
|
||||||
|
protected function string_to_timestamp( $time_string ) {
|
||||||
|
return '0000-00-00 00:00:00' !== $time_string ? wc_string_to_timestamp( $time_string ) : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -734,11 +734,12 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
|
||||||
* Get the order type based on Order ID.
|
* Get the order type based on Order ID.
|
||||||
*
|
*
|
||||||
* @since 3.0.0
|
* @since 3.0.0
|
||||||
* @param int $order_id Order ID.
|
* @param int|WP_Post $order Order | Order id.
|
||||||
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function get_order_type( $order_id ) {
|
public function get_order_type( $order ) {
|
||||||
return get_post_type( $order_id );
|
return get_post_type( $order );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -865,7 +866,13 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
|
||||||
$query = new WP_Query( $args );
|
$query = new WP_Query( $args );
|
||||||
}
|
}
|
||||||
|
|
||||||
$orders = ( isset( $query_vars['return'] ) && 'ids' === $query_vars['return'] ) ? $query->posts : array_filter( array_map( 'wc_get_order', $query->posts ) );
|
if ( isset( $query_vars['return'] ) && 'ids' === $query_vars['return'] ) {
|
||||||
|
$orders = $query->posts;
|
||||||
|
} else {
|
||||||
|
update_post_caches( $query->posts ); // We already fetching posts, might as well hydrate some caches.
|
||||||
|
$order_ids = wp_list_pluck( $query->posts, 'ID' );
|
||||||
|
$orders = $this->compile_orders( $order_ids, $query_vars, $query );
|
||||||
|
}
|
||||||
|
|
||||||
if ( isset( $query_vars['paginate'] ) && $query_vars['paginate'] ) {
|
if ( isset( $query_vars['paginate'] ) && $query_vars['paginate'] ) {
|
||||||
return (object) array(
|
return (object) array(
|
||||||
|
@ -878,6 +885,213 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
|
||||||
return $orders;
|
return $orders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile order response and set caches as needed for order ids.
|
||||||
|
*
|
||||||
|
* @param array $order_ids List of order IDS to compile.
|
||||||
|
* @param array $query_vars Original query arguments.
|
||||||
|
* @param WP_Query $query Query object.
|
||||||
|
*
|
||||||
|
* @return array Orders.
|
||||||
|
*/
|
||||||
|
private function compile_orders( $order_ids, $query_vars, $query ) {
|
||||||
|
if ( empty( $order_ids ) ) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
$orders = array();
|
||||||
|
|
||||||
|
// Lets do some cache hydrations so that we don't have to fetch data from DB for every order.
|
||||||
|
$this->prime_raw_meta_cache_for_orders( $order_ids, $query_vars );
|
||||||
|
$this->prime_refund_caches_for_order( $order_ids, $query_vars );
|
||||||
|
$this->prime_order_item_caches_for_orders( $order_ids, $query_vars );
|
||||||
|
|
||||||
|
foreach ( $query->posts as $post ) {
|
||||||
|
$orders[] = wc_get_order( $post );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $orders;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prime refund cache for orders.
|
||||||
|
*
|
||||||
|
* @param array $order_ids Order Ids to prime cache for.
|
||||||
|
* @param array $query_vars Query vars for the query.
|
||||||
|
*/
|
||||||
|
private function prime_refund_caches_for_order( $order_ids, $query_vars ) {
|
||||||
|
if ( ! isset( $query_vars['type'] ) || ! ( 'shop_order' === $query_vars['type'] ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( isset( $query_vars['fields'] ) && 'all' !== $query_vars['fields'] ) {
|
||||||
|
if ( is_array( $query_vars['fields'] ) && ! in_array( 'refunds', $query_vars['fields'] ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$cache_keys_mapping = array();
|
||||||
|
foreach ( $order_ids as $order_id ) {
|
||||||
|
$cache_keys_mapping[ $order_id ] = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refunds' . $order_id;
|
||||||
|
}
|
||||||
|
$non_cached_ids = array();
|
||||||
|
$cache_values = wc_cache_get_multiple( array_values( $cache_keys_mapping ), 'orders' );
|
||||||
|
foreach ( $order_ids as $order_id ) {
|
||||||
|
if ( false === $cache_values[ $cache_keys_mapping[ $order_id ] ] ) {
|
||||||
|
$non_cached_ids[] = $order_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( empty( $non_cached_ids ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$refunds = wc_get_orders(
|
||||||
|
array(
|
||||||
|
'type' => 'shop_order_refund',
|
||||||
|
'post_parent__in' => $non_cached_ids,
|
||||||
|
'limit' => - 1,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$order_refunds = array_reduce(
|
||||||
|
$refunds,
|
||||||
|
function ( $order_refunds_array, WC_Order_Refund $refund ) {
|
||||||
|
if ( ! isset( $order_refunds_array[ $refund->get_parent_id() ] ) ) {
|
||||||
|
$order_refunds_array[ $refund->get_parent_id() ] = array();
|
||||||
|
}
|
||||||
|
$order_refunds_array[ $refund->get_parent_id() ][] = $refund;
|
||||||
|
return $order_refunds_array;
|
||||||
|
},
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
foreach ( $non_cached_ids as $order_id ) {
|
||||||
|
$refunds = array();
|
||||||
|
if ( isset( $order_refunds[ $order_id ] ) ) {
|
||||||
|
$refunds = $order_refunds[ $order_id ];
|
||||||
|
}
|
||||||
|
wp_cache_set( $cache_keys_mapping[ $order_id ], $refunds, 'orders' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prime following caches:
|
||||||
|
* 1. item-$order_item_id For individual items.
|
||||||
|
* 2. order-items-$order-id For fetching items associated with an order.
|
||||||
|
* 3. order-item meta.
|
||||||
|
*
|
||||||
|
* @param array $order_ids Order Ids to prime cache for.
|
||||||
|
* @param array $query_vars Query vars for the query.
|
||||||
|
*/
|
||||||
|
private function prime_order_item_caches_for_orders( $order_ids, $query_vars ) {
|
||||||
|
global $wpdb;
|
||||||
|
if ( isset( $query_vars['fields'] ) && 'all' !== $query_vars['fields'] ) {
|
||||||
|
$line_items = array(
|
||||||
|
'line_items',
|
||||||
|
'shipping_lines',
|
||||||
|
'fee_lines',
|
||||||
|
'coupon_lines',
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( is_array( $query_vars['fields'] ) && 0 === count( array_intersect( $line_items, $query_vars['fields'] ) ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$cache_keys = array_map(
|
||||||
|
function ( $order_id ) {
|
||||||
|
return 'order-items-' . $order_id;
|
||||||
|
},
|
||||||
|
$order_ids
|
||||||
|
);
|
||||||
|
$cache_values = wc_cache_get_multiple( $cache_keys, 'orders' );
|
||||||
|
$non_cached_ids = array();
|
||||||
|
foreach ( $order_ids as $order_id ) {
|
||||||
|
if ( false === $cache_values[ 'order-items-' . $order_id ] ) {
|
||||||
|
$non_cached_ids[] = $order_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( empty( $non_cached_ids ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$non_cached_ids = esc_sql( $non_cached_ids );
|
||||||
|
$non_cached_ids_string = implode( ',', $non_cached_ids );
|
||||||
|
$order_items = $wpdb->get_results(
|
||||||
|
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||||
|
"SELECT order_item_type, order_item_id, order_id, order_item_name FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id in ( $non_cached_ids_string ) ORDER BY order_item_id;"
|
||||||
|
);
|
||||||
|
if ( empty( $order_items ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$order_items_for_all_orders = array_reduce(
|
||||||
|
$order_items,
|
||||||
|
function ( $order_items_collection, $order_item ) {
|
||||||
|
if ( ! isset( $order_items_collection[ $order_item->order_id ] ) ) {
|
||||||
|
$order_items_collection[ $order_item->order_id ] = array();
|
||||||
|
}
|
||||||
|
$order_items_collection[ $order_item->order_id ][] = $order_item;
|
||||||
|
return $order_items_collection;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
foreach ( $order_items_for_all_orders as $order_id => $items ) {
|
||||||
|
wp_cache_set( 'order-items-' . $order_id, $items, 'orders' );
|
||||||
|
}
|
||||||
|
foreach ( $order_items as $item ) {
|
||||||
|
wp_cache_set( 'item-' . $item->order_item_id, $item, 'order-items' );
|
||||||
|
}
|
||||||
|
$order_item_ids = wp_list_pluck( $order_items, 'order_item_id' );
|
||||||
|
update_meta_cache( 'order_item', $order_item_ids );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prime cache for raw meta data for orders in bulk. Difference between this and WP built-in metadata is that this method also fetches `meta_id` field which we use and cache it.
|
||||||
|
*
|
||||||
|
* @param array $order_ids Order Ids to prime cache for.
|
||||||
|
* @param array $query_vars Query vars for the query.
|
||||||
|
*/
|
||||||
|
private function prime_raw_meta_cache_for_orders( $order_ids, $query_vars ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
if ( isset( $query_vars['fields'] ) && 'all' !== $query_vars['fields'] ) {
|
||||||
|
if ( is_array( $query_vars['fields'] ) && ! in_array( 'meta_data', $query_vars['fields'] ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$cache_keys_mapping = array();
|
||||||
|
foreach ( $order_ids as $order_id ) {
|
||||||
|
$cache_keys_mapping[ $order_id ] = WC_Order::generate_meta_cache_key( $order_id, 'orders' );
|
||||||
|
}
|
||||||
|
$cache_values = wc_cache_get_multiple( array_values( $cache_keys_mapping ), 'orders' );
|
||||||
|
$non_cached_ids = array();
|
||||||
|
foreach ( $order_ids as $order_id ) {
|
||||||
|
if ( false === $cache_values[ $cache_keys_mapping[ $order_id ] ] ) {
|
||||||
|
$non_cached_ids[] = $order_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( empty( $non_cached_ids ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$order_ids = esc_sql( $non_cached_ids );
|
||||||
|
$order_ids_in = "'" . implode( "', '", $order_ids ) . "'";
|
||||||
|
$raw_meta_data_array = $wpdb->get_results(
|
||||||
|
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||||
|
"SELECT post_id as object_id, meta_id, meta_key, meta_value
|
||||||
|
FROM {$wpdb->postmeta}
|
||||||
|
WHERE post_id IN ( $order_ids_in )
|
||||||
|
ORDER BY post_id"
|
||||||
|
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||||
|
);
|
||||||
|
$raw_meta_data_collection = array_reduce(
|
||||||
|
$raw_meta_data_array,
|
||||||
|
function ( $collection, $raw_meta_data ) {
|
||||||
|
if ( ! isset( $collection[ $raw_meta_data->object_id ] ) ) {
|
||||||
|
$collection[ $raw_meta_data->object_id ] = array();
|
||||||
|
}
|
||||||
|
$collection[ $raw_meta_data->object_id ][] = $raw_meta_data;
|
||||||
|
return $collection;
|
||||||
|
},
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
WC_Order::prime_raw_meta_data_cache( $raw_meta_data_collection, 'orders' );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the order type of a given item which belongs to WC_Order.
|
* Return the order type of a given item which belongs to WC_Order.
|
||||||
*
|
*
|
||||||
|
|
|
@ -47,12 +47,15 @@ class WC_Order_Refund_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT im
|
||||||
*/
|
*/
|
||||||
public function delete( &$order, $args = array() ) {
|
public function delete( &$order, $args = array() ) {
|
||||||
$id = $order->get_id();
|
$id = $order->get_id();
|
||||||
|
$parent_order_id = $order->get_parent_id();
|
||||||
|
$refund_cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refunds' . $parent_order_id;
|
||||||
|
|
||||||
if ( ! $id ) {
|
if ( ! $id ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
wp_delete_post( $id );
|
wp_delete_post( $id );
|
||||||
|
wp_cache_delete( $refund_cache_key, 'orders' );
|
||||||
$order->set_id( 0 );
|
$order->set_id( 0 );
|
||||||
do_action( 'woocommerce_delete_order_refund', $id );
|
do_action( 'woocommerce_delete_order_refund', $id );
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,8 +170,8 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
|
||||||
array(
|
array(
|
||||||
'name' => $post_object->post_title,
|
'name' => $post_object->post_title,
|
||||||
'slug' => $post_object->post_name,
|
'slug' => $post_object->post_name,
|
||||||
'date_created' => 0 < $post_object->post_date_gmt ? wc_string_to_timestamp( $post_object->post_date_gmt ) : null,
|
'date_created' => $this->string_to_timestamp( $post_object->post_date_gmt ),
|
||||||
'date_modified' => 0 < $post_object->post_modified_gmt ? wc_string_to_timestamp( $post_object->post_modified_gmt ) : null,
|
'date_modified' => $this->string_to_timestamp( $post_object->post_modified_gmt ),
|
||||||
'status' => $post_object->post_status,
|
'status' => $post_object->post_status,
|
||||||
'description' => $post_object->post_content,
|
'description' => $post_object->post_content,
|
||||||
'short_description' => $post_object->post_excerpt,
|
'short_description' => $post_object->post_excerpt,
|
||||||
|
|
|
@ -62,8 +62,8 @@ class WC_Product_Variation_Data_Store_CPT extends WC_Product_Data_Store_CPT impl
|
||||||
array(
|
array(
|
||||||
'name' => $post_object->post_title,
|
'name' => $post_object->post_title,
|
||||||
'slug' => $post_object->post_name,
|
'slug' => $post_object->post_name,
|
||||||
'date_created' => 0 < $post_object->post_date_gmt ? wc_string_to_timestamp( $post_object->post_date_gmt ) : null,
|
'date_created' => $this->string_to_timestamp( $post_object->post_date_gmt ),
|
||||||
'date_modified' => 0 < $post_object->post_modified_gmt ? wc_string_to_timestamp( $post_object->post_modified_gmt ) : null,
|
'date_modified' => $this->string_to_timestamp( $post_object->post_modified_gmt ),
|
||||||
'status' => $post_object->post_status,
|
'status' => $post_object->post_status,
|
||||||
'menu_order' => $post_object->menu_order,
|
'menu_order' => $post_object->menu_order,
|
||||||
'reviews_allowed' => 'open' === $post_object->comment_status,
|
'reviews_allowed' => 'open' === $post_object->comment_status,
|
||||||
|
|
|
@ -1048,11 +1048,14 @@ class WC_Email extends WC_Settings_API {
|
||||||
var view = '" . esc_js( __( 'View template', 'woocommerce' ) ) . "';
|
var view = '" . esc_js( __( 'View template', 'woocommerce' ) ) . "';
|
||||||
var hide = '" . esc_js( __( 'Hide template', 'woocommerce' ) ) . "';
|
var hide = '" . esc_js( __( 'Hide template', 'woocommerce' ) ) . "';
|
||||||
|
|
||||||
jQuery( 'a.toggle_editor' ).text( view ).toggle( function() {
|
jQuery( 'a.toggle_editor' ).text( view ).click( function() {
|
||||||
jQuery( this ).text( hide ).closest(' .template' ).find( '.editor' ).slideToggle();
|
var label = hide;
|
||||||
return false;
|
|
||||||
}, function() {
|
if ( jQuery( this ).closest(' .template' ).find( '.editor' ).is(':visible') ) {
|
||||||
jQuery( this ).text( view ).closest( '.template' ).find( '.editor' ).slideToggle();
|
var label = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
jQuery( this ).text( label ).closest(' .template' ).find( '.editor' ).slideToggle();
|
||||||
return false;
|
return false;
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,9 @@ class WC_REST_Order_Refunds_V2_Controller extends WC_REST_Orders_V2_Controller {
|
||||||
*/
|
*/
|
||||||
public function register_routes() {
|
public function register_routes() {
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace, '/' . $this->rest_base, array(
|
$this->namespace,
|
||||||
|
'/' . $this->rest_base,
|
||||||
|
array(
|
||||||
'args' => array(
|
'args' => array(
|
||||||
'order_id' => array(
|
'order_id' => array(
|
||||||
'description' => __( 'The order ID.', 'woocommerce' ),
|
'description' => __( 'The order ID.', 'woocommerce' ),
|
||||||
|
@ -82,7 +84,9 @@ class WC_REST_Order_Refunds_V2_Controller extends WC_REST_Orders_V2_Controller {
|
||||||
);
|
);
|
||||||
|
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
|
$this->namespace,
|
||||||
|
'/' . $this->rest_base . '/(?P<id>[\d]+)',
|
||||||
|
array(
|
||||||
'args' => array(
|
'args' => array(
|
||||||
'order_id' => array(
|
'order_id' => array(
|
||||||
'description' => __( 'The order ID.', 'woocommerce' ),
|
'description' => __( 'The order ID.', 'woocommerce' ),
|
||||||
|
@ -140,7 +144,7 @@ class WC_REST_Order_Refunds_V2_Controller extends WC_REST_Orders_V2_Controller {
|
||||||
$data = $object->get_data();
|
$data = $object->get_data();
|
||||||
$format_decimal = array( 'amount' );
|
$format_decimal = array( 'amount' );
|
||||||
$format_date = array( 'date_created' );
|
$format_date = array( 'date_created' );
|
||||||
$format_line_items = array( 'line_items' );
|
$format_line_items = array( 'line_items', 'shipping_lines', 'tax_lines', 'fee_lines' );
|
||||||
|
|
||||||
// Format decimal values.
|
// Format decimal values.
|
||||||
foreach ( $format_decimal as $key ) {
|
foreach ( $format_decimal as $key ) {
|
||||||
|
@ -169,6 +173,9 @@ class WC_REST_Order_Refunds_V2_Controller extends WC_REST_Orders_V2_Controller {
|
||||||
'refunded_payment' => $data['refunded_payment'],
|
'refunded_payment' => $data['refunded_payment'],
|
||||||
'meta_data' => $data['meta_data'],
|
'meta_data' => $data['meta_data'],
|
||||||
'line_items' => $data['line_items'],
|
'line_items' => $data['line_items'],
|
||||||
|
'shipping_lines' => $data['shipping_lines'],
|
||||||
|
'tax_lines' => $data['tax_lines'],
|
||||||
|
'fee_lines' => $data['fee_lines'],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -283,6 +283,7 @@ abstract class WC_REST_CRUD_Controller extends WC_REST_Posts_Controller {
|
||||||
$args['post_parent__in'] = $request['parent'];
|
$args['post_parent__in'] = $request['parent'];
|
||||||
$args['post_parent__not_in'] = $request['parent_exclude'];
|
$args['post_parent__not_in'] = $request['parent_exclude'];
|
||||||
$args['s'] = $request['search'];
|
$args['s'] = $request['search'];
|
||||||
|
$args['fields'] = $this->get_fields_for_response( $request );
|
||||||
|
|
||||||
if ( 'date' === $args['orderby'] ) {
|
if ( 'date' === $args['orderby'] ) {
|
||||||
$args['orderby'] = 'date ID';
|
$args['orderby'] = 'date ID';
|
||||||
|
|
|
@ -2501,3 +2501,23 @@ function wc_is_running_from_async_action_scheduler() {
|
||||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||||
return isset( $_REQUEST['action'] ) && 'as_async_request_queue_runner' === $_REQUEST['action'];
|
return isset( $_REQUEST['action'] ) && 'as_async_request_queue_runner' === $_REQUEST['action'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Polyfill for wp_cache_get_multiple for WP versions before 5.5.
|
||||||
|
*
|
||||||
|
* @param array $keys Array of keys to get from group.
|
||||||
|
* @param string $group Optional. Where the cache contents are grouped. Default empty.
|
||||||
|
* @param bool $force Optional. Whether to force an update of the local cache from the persistent
|
||||||
|
* cache. Default false.
|
||||||
|
* @return array|bool Array of values.
|
||||||
|
*/
|
||||||
|
function wc_cache_get_multiple( $keys, $group = '', $force = false ) {
|
||||||
|
if ( function_exists( 'wp_cache_get_multiple' ) ) {
|
||||||
|
return wp_cache_get_multiple( $keys, $group, $force );
|
||||||
|
}
|
||||||
|
$values = array();
|
||||||
|
foreach ( $keys as $key ) {
|
||||||
|
$values[ $key ] = wp_cache_get( $key, $group, $force );
|
||||||
|
}
|
||||||
|
return $values;
|
||||||
|
}
|
||||||
|
|
|
@ -2491,7 +2491,7 @@ if ( ! function_exists( 'woocommerce_output_product_categories' ) ) {
|
||||||
|
|
||||||
foreach ( $product_categories as $category ) {
|
foreach ( $product_categories as $category ) {
|
||||||
wc_get_template(
|
wc_get_template(
|
||||||
'content-product_cat.php',
|
'content-product-cat.php',
|
||||||
array(
|
array(
|
||||||
'category' => $category,
|
'category' => $category,
|
||||||
)
|
)
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
25
package.json
25
package.json
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "woocommerce",
|
"name": "woocommerce",
|
||||||
"title": "WooCommerce",
|
"title": "WooCommerce",
|
||||||
"version": "4.8.0",
|
"version": "4.9.0",
|
||||||
"homepage": "https://woocommerce.com/",
|
"homepage": "https://woocommerce.com/",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
"wp_org_slug": "woocommerce"
|
"wp_org_slug": "woocommerce"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"install": "lerna bootstrap",
|
"install": "lerna bootstrap --hoist",
|
||||||
"build": "./bin/build-zip.sh",
|
"build": "./bin/build-zip.sh",
|
||||||
"build:core": "grunt && npm run makepot",
|
"build:core": "grunt && npm run makepot",
|
||||||
"build:dev": "npm run build:core && npm run build:packages",
|
"build:dev": "npm run build:core && npm run build:packages",
|
||||||
|
@ -34,11 +34,11 @@
|
||||||
"git:update-hooks": "rm -r .git/hooks && mkdir -p .git/hooks && node ./node_modules/husky/husky.js install"
|
"git:update-hooks": "rm -r .git/hooks && mkdir -p .git/hooks && node ./node_modules/husky/husky.js install"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "7.12.0",
|
"@babel/cli": "7.12.1",
|
||||||
"@babel/core": "7.12.0",
|
"@babel/core": "7.12.3",
|
||||||
"@babel/polyfill": "7.11.5",
|
"@babel/polyfill": "7.12.1",
|
||||||
"@babel/preset-env": "7.12.0",
|
"@babel/preset-env": "7.12.1",
|
||||||
"@babel/register": "7.12.0",
|
"@babel/register": "7.12.1",
|
||||||
"@typescript-eslint/eslint-plugin": "3.10.1",
|
"@typescript-eslint/eslint-plugin": "3.10.1",
|
||||||
"@typescript-eslint/experimental-utils": "3.10.1",
|
"@typescript-eslint/experimental-utils": "3.10.1",
|
||||||
"@typescript-eslint/parser": "3.10.1",
|
"@typescript-eslint/parser": "3.10.1",
|
||||||
|
@ -48,8 +48,8 @@
|
||||||
"@woocommerce/e2e-utils": "file:tests/e2e/utils",
|
"@woocommerce/e2e-utils": "file:tests/e2e/utils",
|
||||||
"@wordpress/babel-plugin-import-jsx-pragma": "1.1.3",
|
"@wordpress/babel-plugin-import-jsx-pragma": "1.1.3",
|
||||||
"@wordpress/babel-preset-default": "3.0.2",
|
"@wordpress/babel-preset-default": "3.0.2",
|
||||||
"@wordpress/e2e-test-utils": "4.6.0",
|
"@wordpress/e2e-test-utils": "^4.6.0",
|
||||||
"@wordpress/eslint-plugin": "7.1.0",
|
"@wordpress/eslint-plugin": "7.3.0",
|
||||||
"autoprefixer": "9.8.6",
|
"autoprefixer": "9.8.6",
|
||||||
"babel-eslint": "10.1.0",
|
"babel-eslint": "10.1.0",
|
||||||
"chai": "4.2.0",
|
"chai": "4.2.0",
|
||||||
|
@ -77,17 +77,16 @@
|
||||||
"gruntify-eslint": "5.0.0",
|
"gruntify-eslint": "5.0.0",
|
||||||
"husky": "4.3.0",
|
"husky": "4.3.0",
|
||||||
"istanbul": "1.0.0-alpha.2",
|
"istanbul": "1.0.0-alpha.2",
|
||||||
"jest": "25.1.0",
|
"jest": "^25.1.0",
|
||||||
"lerna": "3.22.1",
|
"lerna": "3.22.1",
|
||||||
"lint-staged": "9.5.0",
|
"lint-staged": "9.5.0",
|
||||||
"mocha": "7.2.0",
|
"mocha": "7.2.0",
|
||||||
"node-sass": "4.13.1",
|
"node-sass": "4.14.1",
|
||||||
"prettier": "npm:wp-prettier@2.0.5",
|
"prettier": "npm:wp-prettier@2.0.5",
|
||||||
"puppeteer": "^2.1.1",
|
|
||||||
"stylelint": "12.0.1",
|
"stylelint": "12.0.1",
|
||||||
"stylelint-config-wordpress": "16.0.0",
|
"stylelint-config-wordpress": "16.0.0",
|
||||||
"typescript": "3.9.7",
|
"typescript": "3.9.7",
|
||||||
"webpack": "4.44.1",
|
"webpack": "4.44.2",
|
||||||
"webpack-cli": "3.3.12",
|
"webpack-cli": "3.3.12",
|
||||||
"wp-textdomain": "1.0.1"
|
"wp-textdomain": "1.0.1"
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,7 @@ Tags: e-commerce, store, sales, sell, woo, shop, cart, checkout, downloadable, d
|
||||||
Requires at least: 5.3
|
Requires at least: 5.3
|
||||||
Tested up to: 5.5
|
Tested up to: 5.5
|
||||||
Requires PHP: 7.0
|
Requires PHP: 7.0
|
||||||
Stable tag: 4.6.1
|
Stable tag: 4.6.2
|
||||||
License: GPLv3
|
License: GPLv3
|
||||||
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
|
||||||
|
@ -160,7 +160,7 @@ WooCommerce comes with some sample data you can use to see how products look; im
|
||||||
|
|
||||||
== Changelog ==
|
== Changelog ==
|
||||||
|
|
||||||
= 4.8.0 - 2020-12-xx =
|
= 4.9.0 - 2021-01-xx =
|
||||||
|
|
||||||
[See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce/master/changelog.txt).
|
[See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce/master/changelog.txt).
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ do_action( 'woocommerce_before_add_to_cart_form' ); ?>
|
||||||
),
|
),
|
||||||
$product
|
$product
|
||||||
);
|
);
|
||||||
|
$show_add_to_cart_button = false;
|
||||||
|
|
||||||
do_action( 'woocommerce_grouped_product_list_before', $grouped_product_columns, $quantites_required, $product );
|
do_action( 'woocommerce_grouped_product_list_before', $grouped_product_columns, $quantites_required, $product );
|
||||||
|
|
||||||
|
@ -45,6 +46,10 @@ do_action( 'woocommerce_before_add_to_cart_form' ); ?>
|
||||||
$post = $post_object; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
$post = $post_object; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||||
setup_postdata( $post );
|
setup_postdata( $post );
|
||||||
|
|
||||||
|
if ( $grouped_product_child->is_in_stock() ) {
|
||||||
|
$show_add_to_cart_button = true;
|
||||||
|
}
|
||||||
|
|
||||||
echo '<tr id="product-' . esc_attr( $grouped_product_child->get_id() ) . '" class="woocommerce-grouped-product-list-item ' . esc_attr( implode( ' ', wc_get_product_class( '', $grouped_product_child ) ) ) . '">';
|
echo '<tr id="product-' . esc_attr( $grouped_product_child->get_id() ) . '" class="woocommerce-grouped-product-list-item ' . esc_attr( implode( ' ', wc_get_product_class( '', $grouped_product_child ) ) ) . '">';
|
||||||
|
|
||||||
// Output columns for each product.
|
// Output columns for each product.
|
||||||
|
@ -107,7 +112,7 @@ do_action( 'woocommerce_before_add_to_cart_form' ); ?>
|
||||||
|
|
||||||
<input type="hidden" name="add-to-cart" value="<?php echo esc_attr( $product->get_id() ); ?>" />
|
<input type="hidden" name="add-to-cart" value="<?php echo esc_attr( $product->get_id() ); ?>" />
|
||||||
|
|
||||||
<?php if ( $quantites_required ) : ?>
|
<?php if ( $quantites_required && $show_add_to_cart_button ) : ?>
|
||||||
|
|
||||||
<?php do_action( 'woocommerce_before_add_to_cart_button' ); ?>
|
<?php do_action( 'woocommerce_before_add_to_cart_button' ); ?>
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ if [[ ${RUN_PHPCS} == 1 ]] || [[ ${RUN_E2E} == 1 ]]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ${RUN_CODE_COVERAGE} == 1 ]]; then
|
if [[ ${RUN_CODE_COVERAGE} == 1 ]]; then
|
||||||
phpdbg -qrr $HOME/.composer/vendor/bin/phpunit -d memory_limit=-1 -c phpunit.xml --coverage-clover=coverage.clover --exclude-group=timeout $@
|
phpdbg -qrr ./vendor/bin/phpunit -d memory_limit=-1 -c phpunit.xml --coverage-clover=coverage.clover --exclude-group=timeout $@
|
||||||
else
|
else
|
||||||
$HOME/.composer/vendor/bin/phpunit -c phpunit.xml $@
|
vendor/bin/phpunit -c phpunit.xml $@
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Unreleased
|
||||||
|
|
||||||
|
## Breaking Changes
|
||||||
|
|
||||||
|
- The `HTTPClientFactory` API was changed to make it easier to configure instances with
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
- `HTTPClientFactory` methods `withIndexPermalinks()` and `withoutIndexPermalinks()` to enable/disable API pretty permalinks
|
||||||
|
- Expanded properties of `AbstractProduct` model type
|
||||||
|
- Added `list`, `read`, `update`, and `delete` operations for `SimpleProduct` repositories
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- Added a tranformation layer between API responses and internal models
|
||||||
|
|
||||||
|
# 0.1.0
|
||||||
|
|
||||||
|
- Initial/beta release
|
|
@ -24,24 +24,14 @@ The simplest way to use the client is directly:
|
||||||
import { HTTPClientFactory } from '@woocommerce/api';
|
import { HTTPClientFactory } from '@woocommerce/api';
|
||||||
|
|
||||||
// You can create an API client using the client factory with pre-configured middleware for convenience.
|
// You can create an API client using the client factory with pre-configured middleware for convenience.
|
||||||
let httpClient = HTTPClientFactory.withBasicAuth(
|
let client = HTTPClientFactory.build( 'https://example.com' )
|
||||||
// The base URL of your REST API.
|
.withBasicAuth( 'username', 'password' )
|
||||||
'https://example.com/wp-json/',
|
.create();
|
||||||
// The username for your WordPress user.
|
|
||||||
'username',
|
|
||||||
// The password for your WordPress user.
|
|
||||||
'password',
|
|
||||||
);
|
|
||||||
|
|
||||||
// You can also create an API client configured for requests using OAuth.
|
// You can also create an API client configured for requests using OAuth.
|
||||||
httpClient = HTTPClientFactory.withOAuth(
|
client = HTTPClientFactory.build( 'https://example.com' )
|
||||||
// The base URL of your REST API.
|
.withOAuth( 'consumer_secret', 'consumer_password' )
|
||||||
'https://example.com/wp-json/',
|
.create();
|
||||||
// The OAuth API Key's consumer secret.
|
|
||||||
'consumer_secret',
|
|
||||||
// The OAuth API Key's consumer password.
|
|
||||||
'consumer_pasword',
|
|
||||||
);
|
|
||||||
|
|
||||||
// You can then use the client to make API requests.
|
// You can then use the client to make API requests.
|
||||||
httpClient.get( '/wc/v3/products' ).then( ( response ) => {
|
httpClient.get( '/wc/v3/products' ).then( ( response ) => {
|
||||||
|
@ -54,6 +44,7 @@ httpClient.get( '/wc/v3/products' ).then( ( response ) => {
|
||||||
}, ( error ) => {
|
}, ( error ) => {
|
||||||
// Handle errors that may have come up.
|
// Handle errors that may have come up.
|
||||||
} );
|
} );
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Repositories
|
### Repositories
|
||||||
|
@ -66,7 +57,9 @@ import { SimpleProduct } from '@woocommerce/api';
|
||||||
|
|
||||||
// Prepare the HTTP client that will be consumed by the repository.
|
// Prepare the HTTP client that will be consumed by the repository.
|
||||||
// This is necessary so that it can make requests to the REST API.
|
// This is necessary so that it can make requests to the REST API.
|
||||||
const httpClient = HTTPClientFactory.withBasicAuth( 'https://example.com/wp-json/','username','password' );
|
const httpClient = HTTPClientFactory.build( 'https://example.com' )
|
||||||
|
.withBasicAuth( 'username', 'password' )
|
||||||
|
.create();
|
||||||
|
|
||||||
const repository = SimpleProduct.restRepository( httpClient );
|
const repository = SimpleProduct.restRepository( httpClient );
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,8 @@
|
||||||
"!*.tsbuildinfo",
|
"!*.tsbuildinfo",
|
||||||
"!/dist/**/__tests__/",
|
"!/dist/**/__tests__/",
|
||||||
"!/dist/**/__mocks__/",
|
"!/dist/**/__mocks__/",
|
||||||
"!/dist/**/__snapshops__/"
|
"!/dist/**/__snapshops__/",
|
||||||
|
"!/dist/**/__test_data__/"
|
||||||
],
|
],
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -42,7 +43,7 @@
|
||||||
"@types/jest": "25.2.1",
|
"@types/jest": "25.2.1",
|
||||||
"@types/moxios": "^0.4.9",
|
"@types/moxios": "^0.4.9",
|
||||||
"@types/node": "13.13.5",
|
"@types/node": "13.13.5",
|
||||||
"jest": "25.5.4",
|
"jest": "^25.1.0",
|
||||||
"jest-mock-extended": "^1.0.10",
|
"jest-mock-extended": "^1.0.10",
|
||||||
"moxios": "0.4.0",
|
"moxios": "0.4.0",
|
||||||
"ts-jest": "25.5.0",
|
"ts-jest": "25.5.0",
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { Model } from '../models/model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dummy model that can be used in test files.
|
||||||
|
*/
|
||||||
|
export class DummyModel extends Model {
|
||||||
|
public name: string = '';
|
||||||
|
|
||||||
|
public constructor( partial?: Partial< DummyModel > ) {
|
||||||
|
super();
|
||||||
|
Object.assign( this, partial );
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,15 +12,8 @@ import {
|
||||||
UpdatesChildModels,
|
UpdatesChildModels,
|
||||||
UpdatesModels,
|
UpdatesModels,
|
||||||
} from '../model-repository';
|
} from '../model-repository';
|
||||||
|
import { DummyModel } from '../../__test_data__/dummy-model';
|
||||||
|
|
||||||
class DummyModel extends Model {
|
|
||||||
public name: string = '';
|
|
||||||
|
|
||||||
public constructor( partial?: Partial< DummyModel > ) {
|
|
||||||
super();
|
|
||||||
Object.assign( this, partial );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type DummyModelParams = ModelRepositoryParams< DummyModel, never, { search: string }, 'name' >
|
type DummyModelParams = ModelRepositoryParams< DummyModel, never, { search: string }, 'name' >
|
||||||
|
|
||||||
class DummyChildModel extends Model {
|
class DummyChildModel extends Model {
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
import { ModelTransformation, ModelTransformer } from '../model-transformer';
|
||||||
|
import { DummyModel } from '../../__test_data__/dummy-model';
|
||||||
|
|
||||||
|
class DummyTransformation implements ModelTransformation {
|
||||||
|
public readonly fromModelOrder: number;
|
||||||
|
|
||||||
|
private readonly fn: ( ( p: any ) => any ) | null;
|
||||||
|
|
||||||
|
public constructor( order: number, fn: ( ( p: any ) => any ) | null ) {
|
||||||
|
this.fromModelOrder = order;
|
||||||
|
this.fn = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public fromModel( properties: any ): any {
|
||||||
|
if ( ! this.fn ) {
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.fn( properties );
|
||||||
|
}
|
||||||
|
|
||||||
|
public toModel( properties: any ): any {
|
||||||
|
if ( ! this.fn ) {
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.fn( properties );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe( 'ModelTransformer', () => {
|
||||||
|
it( 'should order transformers correctly', () => {
|
||||||
|
const fn1 = jest.fn();
|
||||||
|
fn1.mockReturnValue( { name: 'fn1' } );
|
||||||
|
const fn2 = jest.fn();
|
||||||
|
fn2.mockReturnValue( { name: 'fn2' } );
|
||||||
|
|
||||||
|
const transformer = new ModelTransformer< DummyModel >(
|
||||||
|
[
|
||||||
|
// Ensure the orders are backwards so sorting is tested.
|
||||||
|
new DummyTransformation( 1, fn2 ),
|
||||||
|
new DummyTransformation( 0, fn1 ),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
let transformed = transformer.fromModel( new DummyModel( { name: 'fn0' } ) );
|
||||||
|
|
||||||
|
expect( fn1 ).toHaveBeenCalledWith( { name: 'fn0' } );
|
||||||
|
expect( fn2 ).toHaveBeenCalledWith( { name: 'fn1' } );
|
||||||
|
expect( transformed ).toMatchObject( { name: 'fn2' } );
|
||||||
|
|
||||||
|
// Reset and make sure "toModel" happens in reverse order.
|
||||||
|
fn1.mockClear();
|
||||||
|
fn2.mockClear();
|
||||||
|
|
||||||
|
transformed = transformer.toModel( DummyModel, { name: 'fn3' } );
|
||||||
|
|
||||||
|
expect( fn2 ).toHaveBeenCalledWith( { name: 'fn3' } );
|
||||||
|
expect( fn1 ).toHaveBeenCalledWith( { name: 'fn2' } );
|
||||||
|
expect( transformed ).toMatchObject( { name: 'fn1' } );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should transform to model', () => {
|
||||||
|
const transformer = new ModelTransformer< DummyModel >(
|
||||||
|
[
|
||||||
|
new DummyTransformation(
|
||||||
|
0,
|
||||||
|
( p: any ) => {
|
||||||
|
p.name = 'Transformed-' + p.name;
|
||||||
|
return p;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const model = transformer.toModel( DummyModel, { name: 'Test' } );
|
||||||
|
|
||||||
|
expect( model ).toBeInstanceOf( DummyModel );
|
||||||
|
expect( model.name ).toEqual( 'Transformed-Test' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should transform from model', () => {
|
||||||
|
const transformer = new ModelTransformer< DummyModel >(
|
||||||
|
[
|
||||||
|
new DummyTransformation(
|
||||||
|
0,
|
||||||
|
( p: any ) => {
|
||||||
|
p.name = 'Transformed-' + p.name;
|
||||||
|
return p;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const transformed = transformer.fromModel( new DummyModel( { name: 'Test' } ) );
|
||||||
|
|
||||||
|
expect( transformed ).not.toBeInstanceOf( DummyModel );
|
||||||
|
expect( transformed.name ).toEqual( 'Transformed-Test' );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -31,13 +31,14 @@ export interface ModelRepositoryParams<
|
||||||
/**
|
/**
|
||||||
* These helpers will extract information about a model from its repository params to be used in the repository.
|
* These helpers will extract information about a model from its repository params to be used in the repository.
|
||||||
*/
|
*/
|
||||||
type ModelClass< T extends ModelRepositoryParams > = [ T ] extends [ ModelRepositoryParams< infer X > ] ? X : never;
|
export type ModelClass< T extends ModelRepositoryParams > = [ T ] extends [ ModelRepositoryParams< infer X > ] ? X : never;
|
||||||
type ParentID< T extends ModelRepositoryParams > = [ T ] extends [ ModelRepositoryParams< any, infer X > ] ? X : never;
|
export type ParentID< T extends ModelRepositoryParams > = [ T ] extends [ ModelRepositoryParams< any, infer X > ] ? X : never;
|
||||||
|
export type HasParent< T extends ModelRepositoryParams, P, C > = [ ParentID< T > ] extends [ never ] ? C : P;
|
||||||
type ListParams< T extends ModelRepositoryParams > = [ T ] extends [ ModelRepositoryParams< any, any, infer X > ] ? X : never;
|
type ListParams< T extends ModelRepositoryParams > = [ T ] extends [ ModelRepositoryParams< any, any, infer X > ] ? X : never;
|
||||||
|
type PickUpdateParams<T, K extends keyof T> = { [P in K]?: T[P]; };
|
||||||
type UpdateParams< T extends ModelRepositoryParams > = [ T ] extends [ ModelRepositoryParams< infer C, any, any, infer X > ] ?
|
type UpdateParams< T extends ModelRepositoryParams > = [ T ] extends [ ModelRepositoryParams< infer C, any, any, infer X > ] ?
|
||||||
( [ X ] extends [ keyof C ] ? Pick< C, X > : never ) :
|
( [ X ] extends [ keyof C ] ? PickUpdateParams< C, X > : never ) :
|
||||||
never;
|
never;
|
||||||
type HasParent< T extends ModelRepositoryParams, P, C > = [ ParentID< T > ] extends [ never ] ? C : P;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A callback for listing models using a data source.
|
* A callback for listing models using a data source.
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
import { Model } from '../models/model';
|
||||||
|
import { ModelConstructor } from '../models/shared-types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for an object that can perform transformations both to and from a representation
|
||||||
|
* and return the input data after performing the desired transformation.
|
||||||
|
*
|
||||||
|
* @interface ModelTransformation
|
||||||
|
*/
|
||||||
|
export interface ModelTransformation {
|
||||||
|
/**
|
||||||
|
* The order of execution for the transformer.
|
||||||
|
* - For "fromModel" higher numbers execute later.
|
||||||
|
* - For "toModel" the order is reversed.
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
readonly fromModelOrder: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a transformation from model properties to raw properties.
|
||||||
|
*
|
||||||
|
* @param {*} properties The properties to transform.
|
||||||
|
* @return {*} The transformed properties.
|
||||||
|
*/
|
||||||
|
fromModel( properties: any ): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a transformation from raw properties to model properties.
|
||||||
|
*
|
||||||
|
* @param {*} properties The properties to transform.
|
||||||
|
* @return {*} The transformed properties.
|
||||||
|
*/
|
||||||
|
toModel( properties: any ): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enum for defining the "toModel" transformation order values.
|
||||||
|
*/
|
||||||
|
export enum TransformationOrder {
|
||||||
|
First = 0,
|
||||||
|
Normal = 500000,
|
||||||
|
Last = 1000000,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A special value reserved for transformations that MUST come after all orders due to
|
||||||
|
* the way that they destroy the property keys or values.
|
||||||
|
*/
|
||||||
|
VeryLast = 2000000
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class for transforming models to/from a generic representation.
|
||||||
|
*/
|
||||||
|
export class ModelTransformer< T extends Model > {
|
||||||
|
/**
|
||||||
|
* An array of transformations to use when converting data to/from models.
|
||||||
|
*
|
||||||
|
* @type {Array.<ModelTransformation>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private transformations: readonly ModelTransformation[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new model transformer instance.
|
||||||
|
*
|
||||||
|
* @param {Array.<ModelTransformation>} transformations The transformations to use.
|
||||||
|
*/
|
||||||
|
public constructor( transformations: ModelTransformation[] ) {
|
||||||
|
// Ensure that the transformations are sorted by priority.
|
||||||
|
transformations.sort( ( a, b ) => ( a.fromModelOrder > b.fromModelOrder ) ? 1 : -1 );
|
||||||
|
|
||||||
|
this.transformations = transformations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes the input model and runs all of the transformations on it before returning the data.
|
||||||
|
*
|
||||||
|
* @param {Partial.<T>} model The model to transform.
|
||||||
|
* @return {*} The transformed data.
|
||||||
|
* @template T
|
||||||
|
*/
|
||||||
|
public fromModel( model: Partial< T > ): any {
|
||||||
|
// Convert the model class to raw properties so that the transformations can be simple.
|
||||||
|
const raw = Object.assign( {}, model );
|
||||||
|
|
||||||
|
return this.transformations.reduce(
|
||||||
|
( properties: any, transformer: ModelTransformation ) => {
|
||||||
|
return transformer.fromModel( properties );
|
||||||
|
},
|
||||||
|
raw,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes the input data and runs all of the transformations on it before returning the created model.
|
||||||
|
*
|
||||||
|
* @param {Function.<T>} modelClass The model class we're trying to create.
|
||||||
|
* @param {*} data The data we're transforming.
|
||||||
|
* @return {T} The transformed model.
|
||||||
|
* @template T
|
||||||
|
*/
|
||||||
|
public toModel( modelClass: ModelConstructor< T >, data: any ): T {
|
||||||
|
const transformed: any = this.transformations.reduceRight(
|
||||||
|
( properties: any, transformer: ModelTransformation ) => {
|
||||||
|
return transformer.toModel( properties );
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
|
||||||
|
return new modelClass( transformed );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { AddPropertyTransformation } from '../add-property-transformation';
|
||||||
|
|
||||||
|
describe( 'AddPropertyTransformation', () => {
|
||||||
|
let transformation: AddPropertyTransformation;
|
||||||
|
|
||||||
|
beforeEach( () => {
|
||||||
|
transformation = new AddPropertyTransformation(
|
||||||
|
{ toProperty: 'Test' },
|
||||||
|
{ fromProperty: 'Test' },
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should add property when missing', () => {
|
||||||
|
let transformed = transformation.toModel( { id: 1, name: 'Test' } );
|
||||||
|
|
||||||
|
expect( transformed ).toMatchObject(
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Test',
|
||||||
|
toProperty: 'Test',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
transformed = transformation.fromModel( { id: 1, name: 'Test' } );
|
||||||
|
|
||||||
|
expect( transformed ).toMatchObject(
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Test',
|
||||||
|
fromProperty: 'Test',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should not add property when present', () => {
|
||||||
|
let transformed = transformation.toModel( { id: 1, name: 'Test', toProperty: 'Existing' } );
|
||||||
|
|
||||||
|
expect( transformed ).toMatchObject(
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Test',
|
||||||
|
toProperty: 'Existing',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
transformed = transformation.fromModel( { id: 1, name: 'Test', fromProperty: 'Existing' } );
|
||||||
|
|
||||||
|
expect( transformed ).toMatchObject(
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Test',
|
||||||
|
fromProperty: 'Existing',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { CustomTransformation } from '../custom-transformation';
|
||||||
|
|
||||||
|
describe( 'CustomTransformation', () => {
|
||||||
|
it( 'should do nothing without hooks', () => {
|
||||||
|
const transformation = new CustomTransformation( 0, null, null );
|
||||||
|
|
||||||
|
const expected = { test: 'Test' };
|
||||||
|
|
||||||
|
expect( transformation.toModel( expected ) ).toMatchObject( expected );
|
||||||
|
expect( transformation.fromModel( expected ) ).toMatchObject( expected );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should execute hooks', () => {
|
||||||
|
const toHook = jest.fn();
|
||||||
|
toHook.mockReturnValue( { toModel: 'Test' } );
|
||||||
|
const fromHook = jest.fn();
|
||||||
|
fromHook.mockReturnValue( { fromModel: 'Test' } );
|
||||||
|
|
||||||
|
const transformation = new CustomTransformation( 0, toHook, fromHook );
|
||||||
|
|
||||||
|
expect( transformation.toModel( { test: 'Test' } ) ).toMatchObject( { toModel: 'Test' } );
|
||||||
|
expect( toHook ).toHaveBeenCalledWith( { test: 'Test' } );
|
||||||
|
expect( transformation.fromModel( { test: 'Test' } ) ).toMatchObject( { fromModel: 'Test' } );
|
||||||
|
expect( fromHook ).toHaveBeenCalledWith( { test: 'Test' } );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { IgnorePropertyTransformation } from '../ignore-property-transformation';
|
||||||
|
|
||||||
|
describe( 'IgnorePropertyTransformation', () => {
|
||||||
|
let transformation: IgnorePropertyTransformation;
|
||||||
|
|
||||||
|
beforeEach( () => {
|
||||||
|
transformation = new IgnorePropertyTransformation( [ 'skip' ] );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should remove ignored properties', () => {
|
||||||
|
let transformed = transformation.fromModel(
|
||||||
|
{
|
||||||
|
test: 'Test',
|
||||||
|
skip: 'Test',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect( transformed ).toHaveProperty( 'test', 'Test' );
|
||||||
|
expect( transformed ).not.toHaveProperty( 'skip' );
|
||||||
|
|
||||||
|
transformed = transformation.toModel(
|
||||||
|
{
|
||||||
|
test: 'Test',
|
||||||
|
skip: 'Test',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect( transformed ).toHaveProperty( 'test', 'Test' );
|
||||||
|
expect( transformed ).not.toHaveProperty( 'skip' );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { KeyChangeTransformation } from '../key-change-transformation';
|
||||||
|
import { DummyModel } from '../../../__test_data__/dummy-model';
|
||||||
|
|
||||||
|
describe( 'KeyChangeTransformation', () => {
|
||||||
|
let transformation: KeyChangeTransformation< DummyModel >;
|
||||||
|
|
||||||
|
beforeEach( () => {
|
||||||
|
transformation = new KeyChangeTransformation< DummyModel >(
|
||||||
|
{
|
||||||
|
name: 'new-name',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should transform to model', () => {
|
||||||
|
const transformed = transformation.toModel( { 'new-name': 'Test Name' } );
|
||||||
|
|
||||||
|
expect( transformed ).toHaveProperty( 'name', 'Test Name' );
|
||||||
|
expect( transformed ).not.toHaveProperty( 'new-name' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should transform from model', () => {
|
||||||
|
const transformed = transformation.fromModel( { name: 'Test Name' } );
|
||||||
|
|
||||||
|
expect( transformed ).toHaveProperty( 'new-name', 'Test Name' );
|
||||||
|
expect( transformed ).not.toHaveProperty( 'name' );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { ModelTransformerTransformation } from '../model-transformer-transformation';
|
||||||
|
import { ModelTransformer } from '../../model-transformer';
|
||||||
|
import { mock, MockProxy } from 'jest-mock-extended';
|
||||||
|
import { DummyModel } from '../../../__test_data__/dummy-model';
|
||||||
|
|
||||||
|
describe( 'ModelTransformerTransformation', () => {
|
||||||
|
let mockTransformer: MockProxy< ModelTransformer< any > > & ModelTransformer< any >;
|
||||||
|
let transformation: ModelTransformerTransformation< any >;
|
||||||
|
|
||||||
|
beforeEach( () => {
|
||||||
|
mockTransformer = mock< ModelTransformer< any > >();
|
||||||
|
transformation = new ModelTransformerTransformation< DummyModel >(
|
||||||
|
'test',
|
||||||
|
DummyModel,
|
||||||
|
mockTransformer,
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should execute child transformer', () => {
|
||||||
|
mockTransformer.toModel.mockReturnValue( { toModel: 'Test' } );
|
||||||
|
|
||||||
|
let transformed = transformation.toModel( { test: 'Test' } );
|
||||||
|
|
||||||
|
expect( transformed ).toMatchObject( { test: { toModel: 'Test' } } );
|
||||||
|
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyModel, 'Test' );
|
||||||
|
|
||||||
|
mockTransformer.fromModel.mockReturnValue( { fromModel: 'Test' } );
|
||||||
|
|
||||||
|
transformed = transformation.fromModel( { test: 'Test' } );
|
||||||
|
|
||||||
|
expect( transformed ).toMatchObject( { test: { fromModel: 'Test' } } );
|
||||||
|
expect( mockTransformer.fromModel ).toHaveBeenCalledWith( 'Test' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should execute child transformer on array', () => {
|
||||||
|
mockTransformer.toModel.mockReturnValue( { toModel: 'Test' } );
|
||||||
|
|
||||||
|
let transformed = transformation.toModel( { test: [ 'Test', 'Test2' ] } );
|
||||||
|
|
||||||
|
expect( transformed ).toMatchObject( { test: [ { toModel: 'Test' }, { toModel: 'Test' } ] } );
|
||||||
|
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyModel, 'Test' );
|
||||||
|
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyModel, 'Test2' );
|
||||||
|
|
||||||
|
mockTransformer.fromModel.mockReturnValue( { fromModel: 'Test' } );
|
||||||
|
|
||||||
|
transformed = transformation.fromModel( { test: [ 'Test', 'Test2' ] } );
|
||||||
|
|
||||||
|
expect( transformed ).toMatchObject( { test: [ { fromModel: 'Test' }, { fromModel: 'Test' } ] } );
|
||||||
|
expect( mockTransformer.fromModel ).toHaveBeenCalledWith( 'Test' );
|
||||||
|
expect( mockTransformer.fromModel ).toHaveBeenCalledWith( 'Test2' );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -0,0 +1,108 @@
|
||||||
|
import { PropertyType, PropertyTypeTransformation } from '../property-type-transformation';
|
||||||
|
|
||||||
|
describe( 'PropertyTypeTransformation', () => {
|
||||||
|
let transformation: PropertyTypeTransformation;
|
||||||
|
|
||||||
|
beforeEach( () => {
|
||||||
|
transformation = new PropertyTypeTransformation(
|
||||||
|
{
|
||||||
|
string: PropertyType.String,
|
||||||
|
integer: PropertyType.Integer,
|
||||||
|
float: PropertyType.Float,
|
||||||
|
boolean: PropertyType.Boolean,
|
||||||
|
date: PropertyType.Date,
|
||||||
|
callback: ( value: string ) => 'Transformed-' + value,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should convert strings', () => {
|
||||||
|
let transformed = transformation.toModel( { string: 'Test' } );
|
||||||
|
|
||||||
|
expect( transformed.string ).toStrictEqual( 'Test' );
|
||||||
|
|
||||||
|
transformed = transformation.fromModel( { string: 'Test' } );
|
||||||
|
|
||||||
|
expect( transformed.string ).toStrictEqual( 'Test' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should convert integers', () => {
|
||||||
|
let transformed = transformation.toModel( { integer: '100' } );
|
||||||
|
|
||||||
|
expect( transformed.integer ).toStrictEqual( 100 );
|
||||||
|
|
||||||
|
transformed = transformation.fromModel( { integer: 100 } );
|
||||||
|
|
||||||
|
expect( transformed.integer ).toStrictEqual( '100' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should convert floats', () => {
|
||||||
|
let transformed = transformation.toModel( { float: '2.5' } );
|
||||||
|
|
||||||
|
expect( transformed.float ).toStrictEqual( 2.5 );
|
||||||
|
|
||||||
|
transformed = transformation.fromModel( { float: 2.5 } );
|
||||||
|
|
||||||
|
expect( transformed.float ).toStrictEqual( '2.5' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should convert booleans', () => {
|
||||||
|
let transformed = transformation.toModel( { boolean: 'true' } );
|
||||||
|
|
||||||
|
expect( transformed.boolean ).toStrictEqual( true );
|
||||||
|
|
||||||
|
transformed = transformation.fromModel( { boolean: false } );
|
||||||
|
|
||||||
|
expect( transformed.boolean ).toStrictEqual( 'false' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should convert dates', () => {
|
||||||
|
let transformed = transformation.toModel( { date: '2020-11-06T03:11:41.000Z' } );
|
||||||
|
|
||||||
|
expect( transformed.date ).toStrictEqual( new Date( '2020-11-06T03:11:41.000Z' ) );
|
||||||
|
|
||||||
|
transformed = transformation.fromModel( { date: new Date( '2020-11-06T03:11:41.000Z' ) } );
|
||||||
|
|
||||||
|
expect( transformed.date ).toStrictEqual( '2020-11-06T03:11:41.000Z' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should use conversion callbacks', () => {
|
||||||
|
let transformed = transformation.toModel( { callback: 'Test' } );
|
||||||
|
|
||||||
|
expect( transformed.callback ).toStrictEqual( 'Transformed-Test' );
|
||||||
|
|
||||||
|
transformed = transformation.fromModel( { callback: 'Test' } );
|
||||||
|
|
||||||
|
expect( transformed.callback ).toStrictEqual( 'Transformed-Test' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should convert arrays', () => {
|
||||||
|
let transformed = transformation.toModel( { integer: [ '100', '200', '300' ] } );
|
||||||
|
|
||||||
|
expect( transformed.integer ).toStrictEqual( [ 100, 200, 300 ] );
|
||||||
|
|
||||||
|
transformed = transformation.fromModel( { integer: [ 100, 200, 300 ] } );
|
||||||
|
|
||||||
|
expect( transformed.integer ).toStrictEqual( [ '100', '200', '300' ] );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should do nothing without property', () => {
|
||||||
|
let transformed = transformation.toModel( { name: 'Test' } );
|
||||||
|
|
||||||
|
expect( transformed.name ).toStrictEqual( 'Test' );
|
||||||
|
|
||||||
|
transformed = transformation.fromModel( { name: 'Test' } );
|
||||||
|
|
||||||
|
expect( transformed.name ).toStrictEqual( 'Test' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should preserve null', () => {
|
||||||
|
let transformed = transformation.toModel( { integer: null } );
|
||||||
|
|
||||||
|
expect( transformed.integer ).toStrictEqual( null );
|
||||||
|
|
||||||
|
transformed = transformation.fromModel( { integer: null } );
|
||||||
|
|
||||||
|
expect( transformed.integer ).toStrictEqual( null );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -0,0 +1,76 @@
|
||||||
|
import { ModelTransformation, TransformationOrder } from '../model-transformer';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef AdditionalProperties
|
||||||
|
* @alias Object.<string,string>
|
||||||
|
*/
|
||||||
|
type AdditionalProperties = { [ key: string ]: any };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A model transformation that adds a property with
|
||||||
|
* a default value if it is not already set.
|
||||||
|
*/
|
||||||
|
export class AddPropertyTransformation implements ModelTransformation {
|
||||||
|
public readonly fromModelOrder = TransformationOrder.Normal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*The additional properties to add when executing toModel.
|
||||||
|
*
|
||||||
|
* @type {AdditionalProperties}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly toProperties: AdditionalProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The additional properties to add when executing fromModel.
|
||||||
|
*
|
||||||
|
* @type {AdditionalProperties}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly fromProperties: AdditionalProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new transformation.
|
||||||
|
*
|
||||||
|
* @param {AdditionalProperties} toProperties The properties to add when executing toModel.
|
||||||
|
* @param {AdditionalProperties} fromProperties The properties to add when executing fromModel.
|
||||||
|
*/
|
||||||
|
public constructor( toProperties: AdditionalProperties, fromProperties: AdditionalProperties ) {
|
||||||
|
this.toProperties = toProperties;
|
||||||
|
this.fromProperties = fromProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a transformation from model properties to raw properties.
|
||||||
|
*
|
||||||
|
* @param {*} properties The properties to transform.
|
||||||
|
* @return {*} The transformed properties.
|
||||||
|
*/
|
||||||
|
public fromModel( properties: any ): any {
|
||||||
|
for ( const key in this.fromProperties ) {
|
||||||
|
if ( properties.hasOwnProperty( key ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
properties[ key ] = this.fromProperties[ key ];
|
||||||
|
}
|
||||||
|
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a transformation from raw properties to model properties.
|
||||||
|
*
|
||||||
|
* @param {*} properties The properties to transform.
|
||||||
|
* @return {*} The transformed properties.
|
||||||
|
*/
|
||||||
|
public toModel( properties: any ): any {
|
||||||
|
for ( const key in this.toProperties ) {
|
||||||
|
if ( properties.hasOwnProperty( key ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
properties[ key ] = this.toProperties[ key ];
|
||||||
|
}
|
||||||
|
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
import { ModelTransformation } from '../model-transformer';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback for transforming model properties.
|
||||||
|
*
|
||||||
|
* @callback TransformationCallback
|
||||||
|
* @param {*} properties The properties to transform.
|
||||||
|
* @return {*} The transformed properties.
|
||||||
|
*/
|
||||||
|
type TransformationCallback = ( properties: any ) => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A model transformer for executing arbitrary callbacks on input properties.
|
||||||
|
*/
|
||||||
|
export class CustomTransformation implements ModelTransformation {
|
||||||
|
public readonly fromModelOrder: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hook to run for toModel.
|
||||||
|
*
|
||||||
|
* @type {TransformationCallback|null}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly toHook: TransformationCallback | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hook to run for fromModel.
|
||||||
|
*
|
||||||
|
* @type {TransformationCallback|null}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly fromHook: TransformationCallback | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new transformation.
|
||||||
|
*
|
||||||
|
* @param {number} order The order for the transformation.
|
||||||
|
* @param {TransformationCallback|null} toHook The hook to run for toModel.
|
||||||
|
* @param {TransformationCallback|null} fromHook The hook to run for fromModel.
|
||||||
|
*/
|
||||||
|
public constructor(
|
||||||
|
order: number,
|
||||||
|
toHook: TransformationCallback | null,
|
||||||
|
fromHook: TransformationCallback | null,
|
||||||
|
) {
|
||||||
|
this.fromModelOrder = order;
|
||||||
|
this.toHook = toHook;
|
||||||
|
this.fromHook = fromHook;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a transformation from model properties to raw properties.
|
||||||
|
*
|
||||||
|
* @param {*} properties The properties to transform.
|
||||||
|
* @return {*} The transformed properties.
|
||||||
|
*/
|
||||||
|
public fromModel( properties: any ): any {
|
||||||
|
if ( ! this.fromHook ) {
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.fromHook( properties );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a transformation from raw properties to model properties.
|
||||||
|
*
|
||||||
|
* @param {*} properties The properties to transform.
|
||||||
|
* @return {*} The transformed properties.
|
||||||
|
*/
|
||||||
|
public toModel( properties: any ): any {
|
||||||
|
if ( ! this.toHook ) {
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.toHook( properties );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { ModelTransformation, TransformationOrder } from '../model-transformer';
|
||||||
|
|
||||||
|
export class IgnorePropertyTransformation implements ModelTransformation {
|
||||||
|
public readonly fromModelOrder = TransformationOrder.Normal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of properties that should be removed.
|
||||||
|
*
|
||||||
|
* @type {Array.<string>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly ignoreList: readonly string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new transformation.
|
||||||
|
*
|
||||||
|
* @param {Array.<string>} ignoreList The properties to ignore.
|
||||||
|
*/
|
||||||
|
public constructor( ignoreList: string[] ) {
|
||||||
|
this.ignoreList = ignoreList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a transformation from model properties to raw properties.
|
||||||
|
*
|
||||||
|
* @param {*} properties The properties to transform.
|
||||||
|
* @return {*} The transformed properties.
|
||||||
|
*/
|
||||||
|
public fromModel( properties: any ): any {
|
||||||
|
for ( const key of this.ignoreList ) {
|
||||||
|
delete properties[ key ];
|
||||||
|
}
|
||||||
|
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a transformation from raw properties to model properties.
|
||||||
|
*
|
||||||
|
* @param {*} properties The properties to transform.
|
||||||
|
* @return {*} The transformed properties.
|
||||||
|
*/
|
||||||
|
public toModel( properties: any ): any {
|
||||||
|
for ( const key of this.ignoreList ) {
|
||||||
|
delete properties[ key ];
|
||||||
|
}
|
||||||
|
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { ModelTransformation, TransformationOrder } from '../model-transformer';
|
||||||
|
import { Model } from '../../models/model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef KeyChanges
|
||||||
|
* @alias Object.<string,string>
|
||||||
|
*/
|
||||||
|
type KeyChanges< T extends Model > = { readonly [ key in keyof Partial< T > ]: string };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A model transformation that can be used to change property keys between two formats.
|
||||||
|
* This transformation has a very high priority so that it will be executed after all
|
||||||
|
* other transformations to prevent the changed key from causing problems.
|
||||||
|
*/
|
||||||
|
export class KeyChangeTransformation< T extends Model > implements ModelTransformation {
|
||||||
|
/**
|
||||||
|
* Ensure that this transformation always happens at the very end since it changes the keys
|
||||||
|
* in the transformed object.
|
||||||
|
*/
|
||||||
|
public readonly fromModelOrder = TransformationOrder.VeryLast + 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The key change transformations that this object should perform.
|
||||||
|
* This is structured with the model's property key as the key
|
||||||
|
* of the object and the raw property key as the value.
|
||||||
|
*
|
||||||
|
* @type {KeyChanges}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly changes: KeyChanges< T >;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new transformation.
|
||||||
|
*
|
||||||
|
* @param {KeyChanges} changes The changes we want the transformation to make.
|
||||||
|
*/
|
||||||
|
public constructor( changes: KeyChanges< T > ) {
|
||||||
|
this.changes = changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a transformation from model properties to raw properties.
|
||||||
|
*
|
||||||
|
* @param {*} properties The properties to transform.
|
||||||
|
* @return {*} The transformed properties.
|
||||||
|
*/
|
||||||
|
public fromModel( properties: any ): any {
|
||||||
|
for ( const key in this.changes ) {
|
||||||
|
const value = this.changes[ key ];
|
||||||
|
|
||||||
|
if ( ! properties.hasOwnProperty( key ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
properties[ value ] = properties[ key ];
|
||||||
|
delete properties[ key ];
|
||||||
|
}
|
||||||
|
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a transformation from raw properties to model properties.
|
||||||
|
*
|
||||||
|
* @param {*} properties The properties to transform.
|
||||||
|
* @return {*} The transformed properties.
|
||||||
|
*/
|
||||||
|
public toModel( properties: any ): any {
|
||||||
|
for ( const key in this.changes ) {
|
||||||
|
const value = this.changes[ key ];
|
||||||
|
|
||||||
|
if ( ! properties.hasOwnProperty( value ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
properties[ key ] = properties[ value ];
|
||||||
|
delete properties[ value ];
|
||||||
|
}
|
||||||
|
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
import { ModelTransformation, ModelTransformer, TransformationOrder } from '../model-transformer';
|
||||||
|
import { Model } from '../../models/model';
|
||||||
|
import { ModelConstructor } from '../../models/shared-types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A model transformation that applies another transformer to a property.
|
||||||
|
*
|
||||||
|
* @template T
|
||||||
|
*/
|
||||||
|
export class ModelTransformerTransformation< T extends Model > implements ModelTransformation {
|
||||||
|
public readonly fromModelOrder = TransformationOrder.Normal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The property that the transformation should be applied to.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly property: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The model class we want to transform into.
|
||||||
|
*
|
||||||
|
* @type {Function.<T>}
|
||||||
|
* @private
|
||||||
|
* @template T
|
||||||
|
*/
|
||||||
|
private readonly modelClass: ModelConstructor< T >;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The transformer that should be used.
|
||||||
|
*
|
||||||
|
* @type {ModelTransformer}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly transformer: ModelTransformer< T >;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new transformation.
|
||||||
|
*
|
||||||
|
* @param {string} property The property we want to apply the transformer to.
|
||||||
|
* @param {ModelConstructor.<T>} modelClass The model to transform into.
|
||||||
|
* @param {ModelTransformer} transformer The transformer we want to apply.
|
||||||
|
* @template T
|
||||||
|
*/
|
||||||
|
public constructor( property: string, modelClass: ModelConstructor< T >, transformer: ModelTransformer< T > ) {
|
||||||
|
this.property = property;
|
||||||
|
this.modelClass = modelClass;
|
||||||
|
this.transformer = transformer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a transformation from model properties to raw properties.
|
||||||
|
*
|
||||||
|
* @param {*} properties The properties to transform.
|
||||||
|
* @return {*} The transformed properties.
|
||||||
|
*/
|
||||||
|
public fromModel( properties: any ): any {
|
||||||
|
const val = properties[ this.property ];
|
||||||
|
if ( val ) {
|
||||||
|
if ( Array.isArray( val ) ) {
|
||||||
|
properties[ this.property ] = val.map( ( v ) => this.transformer.fromModel( v ) );
|
||||||
|
} else {
|
||||||
|
properties[ this.property ] = this.transformer.fromModel( val );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a transformation from raw properties to model properties.
|
||||||
|
*
|
||||||
|
* @param {*} properties The properties to transform.
|
||||||
|
* @return {*} The transformed properties.
|
||||||
|
*/
|
||||||
|
public toModel( properties: any ): any {
|
||||||
|
const val = properties[ this.property ];
|
||||||
|
if ( val ) {
|
||||||
|
if ( Array.isArray( val ) ) {
|
||||||
|
properties[ this.property ] = val.map( ( v ) => this.transformer.toModel( this.modelClass, v ) );
|
||||||
|
} else {
|
||||||
|
properties[ this.property ] = this.transformer.toModel( this.modelClass, val );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,167 @@
|
||||||
|
import { ModelTransformation, TransformationOrder } from '../model-transformer';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enum defining all of the property types that we might want to transform.
|
||||||
|
*
|
||||||
|
* @enum {number}
|
||||||
|
*/
|
||||||
|
export enum PropertyType {
|
||||||
|
String,
|
||||||
|
Integer,
|
||||||
|
Float,
|
||||||
|
Boolean,
|
||||||
|
Date,
|
||||||
|
}
|
||||||
|
type PropertyTypeTypes = null | string | number | boolean | Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback that can be used to transform property types.
|
||||||
|
*
|
||||||
|
* @callback PropertyTypeCallback
|
||||||
|
* @param {*} value The value to transform.
|
||||||
|
* @return {*} The transformed value.
|
||||||
|
*/
|
||||||
|
type PropertyTypeCallback = ( value: any ) => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The types for all of a model's properties.
|
||||||
|
*
|
||||||
|
* @typedef PropertyTypes
|
||||||
|
* @alias Object.<string,PropertyType>
|
||||||
|
*/
|
||||||
|
type PropertyTypes = { [ key: string ]: PropertyType | PropertyTypeCallback };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A model transformer for converting property types between representation formats.
|
||||||
|
*/
|
||||||
|
export class PropertyTypeTransformation implements ModelTransformation {
|
||||||
|
/**
|
||||||
|
* We want the type transformation to take place after all of the others,
|
||||||
|
* since they may be operating on internal data types.
|
||||||
|
*/
|
||||||
|
public readonly fromModelOrder = TransformationOrder.VeryLast;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The property types we will want to transform.
|
||||||
|
*
|
||||||
|
* @type {PropertyTypes}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly types: PropertyTypes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new transformation.
|
||||||
|
*
|
||||||
|
* @param {PropertyTypes} types The property types we want to transform.
|
||||||
|
*/
|
||||||
|
public constructor( types: PropertyTypes ) {
|
||||||
|
this.types = types;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a transformation from model properties to raw properties.
|
||||||
|
*
|
||||||
|
* @param {*} properties The properties to transform.
|
||||||
|
* @return {*} The transformed properties.
|
||||||
|
*/
|
||||||
|
public fromModel( properties: any ): any {
|
||||||
|
for ( const key in this.types ) {
|
||||||
|
if ( ! properties.hasOwnProperty( key ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const value = properties[ key ];
|
||||||
|
|
||||||
|
const type = this.types[ key ];
|
||||||
|
if ( type instanceof Function ) {
|
||||||
|
properties[ key ] = type( value );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
properties[ key ] = this.convertFrom( value, type );
|
||||||
|
}
|
||||||
|
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a transformation from raw properties to model properties.
|
||||||
|
*
|
||||||
|
* @param {*} properties The properties to transform.
|
||||||
|
* @return {*} The transformed properties.
|
||||||
|
*/
|
||||||
|
public toModel( properties: any ): any {
|
||||||
|
for ( const key in this.types ) {
|
||||||
|
if ( ! properties.hasOwnProperty( key ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const value = properties[ key ];
|
||||||
|
|
||||||
|
const type = this.types[ key ];
|
||||||
|
if ( type instanceof Function ) {
|
||||||
|
properties[ key ] = type( value );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
properties[ key ] = this.convertTo( value, type );
|
||||||
|
}
|
||||||
|
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the given value into the requested type.
|
||||||
|
*
|
||||||
|
* @param {*} value The value to transform.
|
||||||
|
* @param {PropertyType} type The type to transform it into.
|
||||||
|
* @return {*} The converted type.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private convertTo( value: any, type: PropertyType ): PropertyTypeTypes | PropertyTypeTypes[] {
|
||||||
|
if ( Array.isArray( value ) ) {
|
||||||
|
return value.map( ( v: string ) => this.convertTo( v, type ) as PropertyTypeTypes );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( null === value ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ( type ) {
|
||||||
|
case PropertyType.String: return String( value );
|
||||||
|
case PropertyType.Integer: return parseInt( value );
|
||||||
|
case PropertyType.Float: return parseFloat( value );
|
||||||
|
case PropertyType.Boolean: return Boolean( value );
|
||||||
|
case PropertyType.Date:
|
||||||
|
return new Date( value );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the given type into a string.
|
||||||
|
*
|
||||||
|
* @param {*} value The value to transform.
|
||||||
|
* @param {PropertyType} type The type to transform it into.
|
||||||
|
* @return {*} The converted type.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private convertFrom( value: PropertyTypeTypes | PropertyTypeTypes[], type: PropertyType ): any {
|
||||||
|
if ( Array.isArray( value ) ) {
|
||||||
|
return value.map( ( v ) => this.convertFrom( v, type ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( null === value ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ( type ) {
|
||||||
|
case PropertyType.String:
|
||||||
|
case PropertyType.Integer:
|
||||||
|
case PropertyType.Float:
|
||||||
|
case PropertyType.Boolean:
|
||||||
|
return String( value );
|
||||||
|
|
||||||
|
case PropertyType.Date: {
|
||||||
|
return ( value as Date ).toISOString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
import axios, { AxiosInstance } from 'axios';
|
||||||
|
import * as moxios from 'moxios';
|
||||||
|
import { AxiosURLToQueryInterceptor } from '../axios-url-to-query-interceptor';
|
||||||
|
|
||||||
|
describe( 'AxiosURLToQueryInterceptor', () => {
|
||||||
|
let urlToQueryInterceptor: AxiosURLToQueryInterceptor;
|
||||||
|
let axiosInstance: AxiosInstance;
|
||||||
|
|
||||||
|
beforeEach( () => {
|
||||||
|
axiosInstance = axios.create();
|
||||||
|
moxios.install( axiosInstance );
|
||||||
|
urlToQueryInterceptor = new AxiosURLToQueryInterceptor( 'test' );
|
||||||
|
urlToQueryInterceptor.start( axiosInstance );
|
||||||
|
} );
|
||||||
|
|
||||||
|
afterEach( () => {
|
||||||
|
urlToQueryInterceptor.stop( axiosInstance );
|
||||||
|
moxios.uninstall();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should put path in query string', async () => {
|
||||||
|
moxios.stubRequest( 'http://test.test/?test=%2Ftest%2Froute', {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
responseText: JSON.stringify( { test: 'value' } ),
|
||||||
|
} );
|
||||||
|
|
||||||
|
const response = await axiosInstance.get( 'http://test.test/test/route' );
|
||||||
|
|
||||||
|
expect( response.status ).toEqual( 200 );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -1,4 +1,4 @@
|
||||||
import { buildURL } from '../utils';
|
import { buildURL, buildURLWithParams } from '../utils';
|
||||||
|
|
||||||
describe( 'buildURL', () => {
|
describe( 'buildURL', () => {
|
||||||
it( 'should use base when given no url', () => {
|
it( 'should use base when given no url', () => {
|
||||||
|
@ -15,9 +15,16 @@ describe( 'buildURL', () => {
|
||||||
const url = buildURL( { baseURL: 'http://test.test', url: 'yes/test' } );
|
const url = buildURL( { baseURL: 'http://test.test', url: 'yes/test' } );
|
||||||
expect( url ).toBe( 'http://test.test/yes/test' );
|
expect( url ).toBe( 'http://test.test/yes/test' );
|
||||||
} );
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
it( 'should combine base and url with trailing/leading slashes', () => {
|
describe( 'buildURLWithParams', () => {
|
||||||
const url = buildURL( { baseURL: 'http://test.test/////', url: '////yes/test' } );
|
it( 'should do nothing without query string', () => {
|
||||||
expect( url ).toBe( 'http://test.test/yes/test' );
|
const url = buildURLWithParams( { baseURL: 'http://test.test' } );
|
||||||
|
expect( url ).toBe( 'http://test.test' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should append query string', () => {
|
||||||
|
const url = buildURLWithParams( { baseURL: 'http://test.test', params: { test: 'yes' } } );
|
||||||
|
expect( url ).toBe( 'http://test.test?test=yes' );
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -2,10 +2,10 @@ import type { AxiosRequestConfig } from 'axios';
|
||||||
import * as createHmac from 'create-hmac';
|
import * as createHmac from 'create-hmac';
|
||||||
import * as OAuth from 'oauth-1.0a';
|
import * as OAuth from 'oauth-1.0a';
|
||||||
import { AxiosInterceptor } from './axios-interceptor';
|
import { AxiosInterceptor } from './axios-interceptor';
|
||||||
import { buildURL } from './utils';
|
import { buildURLWithParams } from './utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A utility class for managing the lifecycle of an authentication interceptor.
|
* An interceptor for adding OAuth 1.0a signatures to HTTP requests.
|
||||||
*/
|
*/
|
||||||
export class AxiosOAuthInterceptor extends AxiosInterceptor {
|
export class AxiosOAuthInterceptor extends AxiosInterceptor {
|
||||||
/**
|
/**
|
||||||
|
@ -14,7 +14,7 @@ export class AxiosOAuthInterceptor extends AxiosInterceptor {
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private oauth: OAuth;
|
private readonly oauth: OAuth;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new interceptor.
|
* Creates a new interceptor.
|
||||||
|
@ -44,7 +44,7 @@ export class AxiosOAuthInterceptor extends AxiosInterceptor {
|
||||||
* @return {AxiosRequestConfig} The request with the additional authorization headers.
|
* @return {AxiosRequestConfig} The request with the additional authorization headers.
|
||||||
*/
|
*/
|
||||||
protected handleRequest( request: AxiosRequestConfig ): AxiosRequestConfig {
|
protected handleRequest( request: AxiosRequestConfig ): AxiosRequestConfig {
|
||||||
const url = buildURL( request );
|
const url = buildURLWithParams( request );
|
||||||
if ( url.startsWith( 'https' ) ) {
|
if ( url.startsWith( 'https' ) ) {
|
||||||
request.auth = {
|
request.auth = {
|
||||||
username: this.oauth.consumer.key,
|
username: this.oauth.consumer.key,
|
||||||
|
|
|
@ -2,6 +2,9 @@ import { AxiosResponse } from 'axios';
|
||||||
import { AxiosInterceptor } from './axios-interceptor';
|
import { AxiosInterceptor } from './axios-interceptor';
|
||||||
import { HTTPResponse } from '../http-client';
|
import { HTTPResponse } from '../http-client';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interceptor for transforming the responses from axios into a consistent format for package consumers.
|
||||||
|
*/
|
||||||
export class AxiosResponseInterceptor extends AxiosInterceptor {
|
export class AxiosResponseInterceptor extends AxiosInterceptor {
|
||||||
/**
|
/**
|
||||||
* Transforms the Axios response into our HTTP response.
|
* Transforms the Axios response into our HTTP response.
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { AxiosInterceptor } from './axios-interceptor';
|
||||||
|
import { AxiosRequestConfig } from 'axios';
|
||||||
|
import { buildURL } from './utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interceptor for transforming the request's path into a query parameter.
|
||||||
|
*/
|
||||||
|
export class AxiosURLToQueryInterceptor extends AxiosInterceptor {
|
||||||
|
/**
|
||||||
|
* The query parameter we want to assign the path to.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly queryParam: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new interceptor.
|
||||||
|
*
|
||||||
|
* @param {string} queryParam The query parameter we want to assign the path to.
|
||||||
|
*/
|
||||||
|
public constructor( queryParam: string ) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.queryParam = queryParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the outgoing path into a query parameter.
|
||||||
|
*
|
||||||
|
* @param {AxiosRequestConfig} config The axios config.
|
||||||
|
* @return {AxiosRequestConfig} The axios config.
|
||||||
|
*/
|
||||||
|
protected handleRequest( config: AxiosRequestConfig ): AxiosRequestConfig {
|
||||||
|
const url = new URL( buildURL( config ) );
|
||||||
|
|
||||||
|
// Store the path in the query string.
|
||||||
|
if ( config.params instanceof URLSearchParams ) {
|
||||||
|
config.params.set( this.queryParam, url.pathname );
|
||||||
|
} else if ( config.params ) {
|
||||||
|
config.params[ this.queryParam ] = url.pathname;
|
||||||
|
} else {
|
||||||
|
config.params = { [ this.queryParam ]: url.pathname };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the URL without the path now that it's in the query string.
|
||||||
|
url.pathname = '';
|
||||||
|
config.url = url.toString();
|
||||||
|
delete config.baseURL;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,10 @@
|
||||||
import { AxiosRequestConfig } from 'axios';
|
import { AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import buildFullPath = require( 'axios/lib/core/buildFullPath' );
|
||||||
|
// @ts-ignore
|
||||||
|
import appendParams = require( 'axios/lib/helpers/buildURL' );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given an Axios request config this function generates the URL that Axios will
|
* Given an Axios request config this function generates the URL that Axios will
|
||||||
* use to make the request.
|
* use to make the request.
|
||||||
|
@ -8,17 +13,16 @@ import { AxiosRequestConfig } from 'axios';
|
||||||
* @return {string} The merged URL.
|
* @return {string} The merged URL.
|
||||||
*/
|
*/
|
||||||
export function buildURL( request: AxiosRequestConfig ): string {
|
export function buildURL( request: AxiosRequestConfig ): string {
|
||||||
const base = request.baseURL || '';
|
return buildFullPath( request.baseURL, request.url );
|
||||||
if ( ! request.url ) {
|
|
||||||
return base;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Axios ignores the base when the URL is absolute.
|
/**
|
||||||
const url = request.url;
|
* Given an Axios request config this function generates the URL that Axios will
|
||||||
if ( ! base || url.match( /^([a-z][a-z\d+\-.]*:)?\/\/[^\/]/i ) ) {
|
* use to make the request with the query parameters included.
|
||||||
return url;
|
*
|
||||||
}
|
* @param {AxiosRequestConfig} request The Axios request we're building the URL for.
|
||||||
|
* @return {string} The merged URL.
|
||||||
// Remove trailing slashes from the base and leading slashes from the URL so we can combine them consistently.
|
*/
|
||||||
return base.replace( /\/+$/, '' ) + '/' + url.replace( /^\/+/, '' );
|
export function buildURLWithParams( request: AxiosRequestConfig ): string {
|
||||||
|
return appendParams( buildURL( request ), request.params, request.paramsSerializer );
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +1,141 @@
|
||||||
import { HTTPClient } from './http-client';
|
import { HTTPClient } from './http-client';
|
||||||
import { AxiosClient, AxiosOAuthInterceptor } from './axios';
|
import { AxiosClient, AxiosOAuthInterceptor } from './axios';
|
||||||
|
import { AxiosRequestConfig } from 'axios';
|
||||||
|
import { AxiosInterceptor } from './axios/axios-interceptor';
|
||||||
|
import { AxiosURLToQueryInterceptor } from './axios/axios-url-to-query-interceptor';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class for generating HTTPClient instances with desired configurations.
|
* These types describe the shape of the different auth methods our factory supports.
|
||||||
|
*/
|
||||||
|
type OAuthMethod = {
|
||||||
|
type: 'oauth',
|
||||||
|
key: string,
|
||||||
|
secret: string,
|
||||||
|
};
|
||||||
|
type BasicAuthMethod = {
|
||||||
|
type: 'basic',
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for describing the shape of a client to create using the factory.
|
||||||
|
*/
|
||||||
|
interface BuildParams {
|
||||||
|
wpURL: string,
|
||||||
|
useIndexPermalinks?: boolean,
|
||||||
|
auth?: OAuthMethod | BasicAuthMethod,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A factory for generating an HTTPClient with a desired configuration.
|
||||||
*/
|
*/
|
||||||
export class HTTPClientFactory {
|
export class HTTPClientFactory {
|
||||||
/**
|
/**
|
||||||
* Creates a new client instance prepared for basic auth.
|
* The configuration object describing the client we're trying to create.
|
||||||
*
|
*
|
||||||
* @param {string} apiURL
|
* @private
|
||||||
* @param {string} username
|
|
||||||
* @param {string} password
|
|
||||||
* @return {HTTPClient} An HTTP client configured for OAuth requests.
|
|
||||||
*/
|
*/
|
||||||
public static withBasicAuth( apiURL: string, username: string, password: string ): HTTPClient {
|
private clientConfig: BuildParams;
|
||||||
return new AxiosClient(
|
|
||||||
{
|
private constructor( wpURL: string ) {
|
||||||
baseURL: apiURL,
|
this.clientConfig = { wpURL };
|
||||||
auth: { username, password },
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new client instance prepared for oauth.
|
* Creates a new factory that can be used to build clients.
|
||||||
*
|
*
|
||||||
* @param {string} apiURL
|
* @param {string} wpURL The root URL of the WordPress installation we're querying.
|
||||||
* @param {string} consumerKey
|
* @return {HTTPClientFactory} The new factory instance.
|
||||||
* @param {string} consumerSecret
|
|
||||||
* @return {HTTPClient} An HTTP client configured for OAuth requests.
|
|
||||||
*/
|
*/
|
||||||
public static withOAuth( apiURL: string, consumerKey: string, consumerSecret: string ): HTTPClient {
|
public static build( wpURL: string ): HTTPClientFactory {
|
||||||
return new AxiosClient(
|
return new HTTPClientFactory( wpURL );
|
||||||
{ baseURL: apiURL },
|
}
|
||||||
[ new AxiosOAuthInterceptor( consumerKey, consumerSecret ) ],
|
|
||||||
|
/**
|
||||||
|
* Configures the client to utilize OAuth.
|
||||||
|
*
|
||||||
|
* @param {string} key The OAuth consumer key to use.
|
||||||
|
* @param {string} secret The OAuth consumer secret to use.
|
||||||
|
* @return {HTTPClientFactory} This factory.
|
||||||
|
*/
|
||||||
|
public withOAuth( key: string, secret: string ): this {
|
||||||
|
this.clientConfig.auth = { type: 'oauth', key, secret };
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the client to utilize basic auth.
|
||||||
|
*
|
||||||
|
* @param {string} username The WordPress username to use.
|
||||||
|
* @param {string} password The password for the WordPress user.
|
||||||
|
* @return {HTTPClientFactory} This factory.
|
||||||
|
*/
|
||||||
|
public withBasicAuth( username: string, password: string ): this {
|
||||||
|
this.clientConfig.auth = { type: 'basic', username, password };
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the client to use index permalinks.
|
||||||
|
*
|
||||||
|
* @return {HTTPClientFactory} This factory.
|
||||||
|
*/
|
||||||
|
public withIndexPermalinks(): this {
|
||||||
|
this.clientConfig.useIndexPermalinks = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the client to use query permalinks.
|
||||||
|
*
|
||||||
|
* @return {HTTPClientFactory} This factory.
|
||||||
|
*/
|
||||||
|
public withoutIndexPermalinks(): this {
|
||||||
|
this.clientConfig.useIndexPermalinks = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a client instance using the configuration stored within.
|
||||||
|
*
|
||||||
|
* @return {HTTPClient} The created client.
|
||||||
|
*/
|
||||||
|
public create(): HTTPClient {
|
||||||
|
const axiosConfig: AxiosRequestConfig = {};
|
||||||
|
const interceptors: AxiosInterceptor[] = [];
|
||||||
|
|
||||||
|
axiosConfig.baseURL = this.clientConfig.wpURL;
|
||||||
|
if ( ! axiosConfig.baseURL.endsWith( '/' ) ) {
|
||||||
|
axiosConfig.baseURL += '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this.clientConfig.useIndexPermalinks ) {
|
||||||
|
axiosConfig.baseURL += 'wp-json/';
|
||||||
|
} else {
|
||||||
|
interceptors.push( new AxiosURLToQueryInterceptor( 'rest_route' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this.clientConfig.auth ) {
|
||||||
|
switch ( this.clientConfig.auth.type ) {
|
||||||
|
case 'basic':
|
||||||
|
axiosConfig.auth = {
|
||||||
|
username: this.clientConfig.auth.username,
|
||||||
|
password: this.clientConfig.auth.password,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'oauth':
|
||||||
|
interceptors.push(
|
||||||
|
new AxiosOAuthInterceptor(
|
||||||
|
this.clientConfig.auth.key,
|
||||||
|
this.clientConfig.auth.secret,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AxiosClient( axiosConfig, interceptors );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,32 @@
|
||||||
import { Model } from '../model';
|
import { Model } from '../model';
|
||||||
|
import { MetaData, PostStatus } from '../shared-types';
|
||||||
|
import {
|
||||||
|
BackorderStatus,
|
||||||
|
CatalogVisibility,
|
||||||
|
ProductAttribute,
|
||||||
|
ProductDownload,
|
||||||
|
ProductImage,
|
||||||
|
ProductTerm, StockStatus,
|
||||||
|
Taxability,
|
||||||
|
} from './shared-types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The common parameters that all products can use in search.
|
||||||
|
*/
|
||||||
|
export type ProductSearchParams = { search: string };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The common parameters that all products can update.
|
||||||
|
*/
|
||||||
|
export type ProductUpdateParams = 'name' | 'slug' | 'created' | 'postStatus' | 'shortDescription'
|
||||||
|
| 'description' | 'sku' | 'categories' | 'tags' | 'isFeatured'
|
||||||
|
| 'isVirtual' | 'attributes' | 'images' | 'catalogVisibility'
|
||||||
|
| 'regularPrice' | 'onePerOrder' | 'taxStatus' | 'taxClass'
|
||||||
|
| 'salePrice' | 'saleStart' | 'saleEnd' | 'isDownloadable'
|
||||||
|
| 'downloadLimit' | 'daysToDownload' | 'weight' | 'length'
|
||||||
|
| 'width' | 'height' | 'trackInventory' | 'remainingStock'
|
||||||
|
| 'stockStatus' | 'backorderStatus' | 'allowReviews'
|
||||||
|
| 'metaData';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base class for all product types.
|
* The base class for all product types.
|
||||||
|
@ -11,10 +39,325 @@ export abstract class AbstractProduct extends Model {
|
||||||
*/
|
*/
|
||||||
public readonly name: string = '';
|
public readonly name: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The slug of the product.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public readonly slug: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The permalink of the product.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public readonly permalink: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GMT datetime when the product was created.
|
||||||
|
*
|
||||||
|
* @type {Date}
|
||||||
|
*/
|
||||||
|
public readonly created: Date = new Date();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GMT datetime when the product was last modified.
|
||||||
|
*
|
||||||
|
* @type {Date}
|
||||||
|
*/
|
||||||
|
public readonly modified: Date = new Date();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The product's current post status.
|
||||||
|
*
|
||||||
|
* @type {PostStatus}
|
||||||
|
*/
|
||||||
|
public readonly postStatus: PostStatus = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The product's short description.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public readonly shortDescription: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The product's full description.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public readonly description: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The product's SKU.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public readonly sku: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of the categories this product is in.
|
||||||
|
*
|
||||||
|
* @type {ReadonlyArray.<ProductTerm>}
|
||||||
|
*/
|
||||||
|
public readonly categories: readonly ProductTerm[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of the tags this product has.
|
||||||
|
*
|
||||||
|
* @type {ReadonlyArray.<ProductTerm>}
|
||||||
|
*/
|
||||||
|
public readonly tags: readonly ProductTerm[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not the product is currently able to be purchased.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
public readonly isPurchasable: boolean = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not the product should be featured.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
public readonly isFeatured: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the product is delivered virtually.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
public readonly isVirtual: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes for the product.
|
||||||
|
*
|
||||||
|
* @type {ReadonlyArray.<ProductAttribute>}
|
||||||
|
*/
|
||||||
|
public readonly attributes: readonly ProductAttribute[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The images for the product.
|
||||||
|
*
|
||||||
|
* @type {ReadonlyArray.<ProductImage>}
|
||||||
|
*/
|
||||||
|
public readonly images: readonly ProductImage[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not the product should be visible in the catalog.
|
||||||
|
*
|
||||||
|
* @type {CatalogVisibility}
|
||||||
|
*/
|
||||||
|
public readonly catalogVisibility: CatalogVisibility = CatalogVisibility.Everywhere;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current price of the product.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public readonly price: string = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The regular price of the product when not discounted.
|
* The regular price of the product when not discounted.
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
public readonly regularPrice: string = '';
|
public readonly regularPrice: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that only one of a product may be held in the order at a time.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
public readonly onePerOrder: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The taxability of the product.
|
||||||
|
*
|
||||||
|
* @type {Taxability}
|
||||||
|
*/
|
||||||
|
public readonly taxStatus: Taxability = Taxability.ProductAndShipping;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tax class of the product
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public readonly taxClass: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not the product is currently on sale.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
public readonly onSale: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The price of the product when on sale.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public readonly salePrice: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GMT datetime when the product should start to be on sale.
|
||||||
|
*
|
||||||
|
* @type {Date|null}
|
||||||
|
*/
|
||||||
|
public readonly saleStart: Date | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GMT datetime when the product should no longer be on sale.
|
||||||
|
*
|
||||||
|
* @type {Date|null}
|
||||||
|
*/
|
||||||
|
public readonly saleEnd: Date | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not the product is downloadable.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
public readonly isDownloadable: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The downloads available for the product.
|
||||||
|
*
|
||||||
|
* @type {ReadonlyArray.<ProductDownload>}
|
||||||
|
*/
|
||||||
|
public readonly downloads: readonly ProductDownload[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of times a customer may download the product's contents.
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
public readonly downloadLimit: number = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of days after purchase that a customer may still download the product's contents.
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
public readonly daysToDownload: number = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The weight of the product in the store's current units.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public readonly weight: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The length of the product in the store's current units.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public readonly length: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The width of the product in the store's current units.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public readonly width: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The height of the product in the store's current units.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public readonly height: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the product must be shipped.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
public readonly requiresShipping: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the product's shipping is taxable.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
public readonly isShippingTaxable: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The shipping class for the product.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public readonly shippingClass: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that a product should use the inventory system.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
public readonly trackInventory: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of inventory units remaining for this product.
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
public readonly remainingStock: number = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The product's stock status.
|
||||||
|
*
|
||||||
|
* @type {StockStatus}
|
||||||
|
*/
|
||||||
|
public readonly stockStatus: StockStatus = ''
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The status of backordering for a product.
|
||||||
|
*
|
||||||
|
* @type {BackorderStatus}
|
||||||
|
*/
|
||||||
|
public readonly backorderStatus: BackorderStatus = BackorderStatus.Allowed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not a product can be backordered.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
public readonly canBackorder: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not a product is on backorder.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
public readonly isOnBackorder: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not a product allows reviews.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
public readonly allowReviews: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The average rating for the product.
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
public readonly averageRating: number = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of ratings for the product.
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
public readonly numRatings: number = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The extra metadata for the product.
|
||||||
|
*
|
||||||
|
* @type {ReadonlyArray.<MetaData>}
|
||||||
|
*/
|
||||||
|
public readonly metaData: readonly MetaData[] = [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,261 @@
|
||||||
|
/**
|
||||||
|
* An enum describing the catalog visibility options for products.
|
||||||
|
*
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
export enum CatalogVisibility {
|
||||||
|
/**
|
||||||
|
* The product should be visible everywhere.
|
||||||
|
*/
|
||||||
|
Everywhere = 'visible',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The product should only be visible in the shop catalog.
|
||||||
|
*/
|
||||||
|
ShopOnly = 'catalog',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The product should only be visible in search results.
|
||||||
|
*/
|
||||||
|
SearchOnly = 'search',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The product should be hidden everywhere.
|
||||||
|
*/
|
||||||
|
Hidden = 'hidden'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the taxability of a product.
|
||||||
|
*
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
export enum Taxability {
|
||||||
|
/**
|
||||||
|
* The product and shipping are both taxable.
|
||||||
|
*/
|
||||||
|
ProductAndShipping = 'taxable',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only the product's shipping is taxable.
|
||||||
|
*/
|
||||||
|
ShippingOnly = 'shipping',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The product and shipping are not taxable.
|
||||||
|
*/
|
||||||
|
None = 'none'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the status for backorders for a product.
|
||||||
|
*
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
export enum BackorderStatus {
|
||||||
|
/**
|
||||||
|
* The product is allowed to be backordered.
|
||||||
|
*/
|
||||||
|
Allowed = 'yes',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The product is allowed to be backordered but it will notify the customer of that fact.
|
||||||
|
*/
|
||||||
|
AllowedWithNotification = 'notify',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The product is not allowed to be backordered.
|
||||||
|
*/
|
||||||
|
NotAllowed = 'no'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A product's stock status.
|
||||||
|
*
|
||||||
|
* @typedef StockStatus
|
||||||
|
* @alias 'instock'|'outofstock'|'onbackorder'|string
|
||||||
|
*/
|
||||||
|
export type StockStatus = 'instock' | 'outofstock' | 'onbackorder' | string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A products taxonomy term such as categories or tags.
|
||||||
|
*/
|
||||||
|
export class ProductTerm {
|
||||||
|
/**
|
||||||
|
* The ID of the term.
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
public readonly id: number = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the term.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public readonly name: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The slug of the term.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public readonly slug: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new product term.
|
||||||
|
*
|
||||||
|
* @param {Partial.<ProductTerm>} properties The properties to set.
|
||||||
|
*/
|
||||||
|
public constructor( properties?: Partial< ProductTerm > ) {
|
||||||
|
Object.assign( this, properties );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A product's download.
|
||||||
|
*/
|
||||||
|
export class ProductDownload {
|
||||||
|
/**
|
||||||
|
* The ID of the downloadable file.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public readonly id: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the downloadable file.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public readonly name: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The URL of the downloadable file.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public readonly url: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new product download.
|
||||||
|
*
|
||||||
|
* @param {Partial.<ProductDownload>} properties The properties to set.
|
||||||
|
*/
|
||||||
|
public constructor( properties?: Partial< ProductDownload > ) {
|
||||||
|
Object.assign( this, properties );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A product's attributes.
|
||||||
|
*/
|
||||||
|
export class ProductAttribute {
|
||||||
|
/**
|
||||||
|
* The ID of the attribute.
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
public readonly id: number = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the attribute.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public readonly name: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sort order of the attribute.
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
public readonly sortOrder: number = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not the attribute is visible on the product page.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
public readonly isVisibleOnProductPage: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not the attribute should be used in variations.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
public readonly isForVariations: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The options which are available for the attribute.
|
||||||
|
*
|
||||||
|
* @type {ReadonlyArray.<string>}
|
||||||
|
*/
|
||||||
|
public readonly options: readonly string[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new product attribute.
|
||||||
|
*
|
||||||
|
* @param {Partial.<ProductAttribute>} properties The properties to set.
|
||||||
|
*/
|
||||||
|
public constructor( properties?: Partial< ProductAttribute > ) {
|
||||||
|
Object.assign( this, properties );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A product's image.
|
||||||
|
*/
|
||||||
|
export class ProductImage {
|
||||||
|
/**
|
||||||
|
* The ID of the image.
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
public readonly id: number = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GMT datetime when the image was created.
|
||||||
|
*
|
||||||
|
* @type {Date}
|
||||||
|
*/
|
||||||
|
public readonly created: Date = new Date();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GMT datetime when the image was last modified.
|
||||||
|
*
|
||||||
|
* @type {Date}
|
||||||
|
*/
|
||||||
|
public readonly modified: Date = new Date();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The URL for the image file.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public readonly url: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the image file.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public readonly name: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The alt text to use on the image.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public readonly altText: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new product image.
|
||||||
|
*
|
||||||
|
* @param {Partial.<ProductImage>} properties The properties to set.
|
||||||
|
*/
|
||||||
|
public constructor( properties?: Partial< ProductImage > ) {
|
||||||
|
Object.assign( this, properties );
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,27 @@
|
||||||
import { AbstractProduct } from './abstract-product';
|
import { AbstractProduct, ProductSearchParams, ProductUpdateParams } from './abstract-product';
|
||||||
import { HTTPClient } from '../../http';
|
import { HTTPClient } from '../../http';
|
||||||
import { simpleProductRESTRepository } from '../../repositories/rest/products/simple-product';
|
import { simpleProductRESTRepository } from '../../repositories/rest/products/simple-product';
|
||||||
import { CreatesModels, ModelRepositoryParams } from '../../framework/model-repository';
|
import {
|
||||||
|
CreatesModels,
|
||||||
|
DeletesModels, ListsModels,
|
||||||
|
ModelRepositoryParams,
|
||||||
|
ReadsModels,
|
||||||
|
UpdatesModels,
|
||||||
|
} from '../../framework/model-repository';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The parameters embedded in this generic can be used in the ModelRepository in order to give
|
* The parameters embedded in this generic can be used in the ModelRepository in order to give
|
||||||
* type-safety in an incredibly granular way.
|
* type-safety in an incredibly granular way.
|
||||||
*/
|
*/
|
||||||
export type SimpleProductRepositoryParams = ModelRepositoryParams< SimpleProduct, never, never, 'regularPrice' >;
|
export type SimpleProductRepositoryParams = ModelRepositoryParams< SimpleProduct, never, ProductSearchParams, ProductUpdateParams >;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for listing simple products using the repository.
|
||||||
|
*
|
||||||
|
* @typedef ListsSimpleProducts
|
||||||
|
* @alias ListsModels.<SimpleProduct>
|
||||||
|
*/
|
||||||
|
export type ListsSimpleProducts = ListsModels< SimpleProductRepositoryParams >;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface for creating simple products using the repository.
|
* An interface for creating simple products using the repository.
|
||||||
|
@ -17,6 +31,30 @@ export type SimpleProductRepositoryParams = ModelRepositoryParams< SimpleProduct
|
||||||
*/
|
*/
|
||||||
export type CreatesSimpleProducts = CreatesModels< SimpleProductRepositoryParams >;
|
export type CreatesSimpleProducts = CreatesModels< SimpleProductRepositoryParams >;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for reading simple products using the repository.
|
||||||
|
*
|
||||||
|
* @typedef ReadsSimpleProducts
|
||||||
|
* @alias ReadsModels.<SimpleProduct>
|
||||||
|
*/
|
||||||
|
export type ReadsSimpleProducts = ReadsModels< SimpleProductRepositoryParams >;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for updating simple products using the repository.
|
||||||
|
*
|
||||||
|
* @typedef UpdatesSimpleProducts
|
||||||
|
* @alias UpdatesModels.<SimpleProduct>
|
||||||
|
*/
|
||||||
|
export type UpdatesSimpleProducts = UpdatesModels< SimpleProductRepositoryParams >;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for deleting simple products using the repository.
|
||||||
|
*
|
||||||
|
* @typedef DeletesSimpleProducts
|
||||||
|
* @alias DeletesModels.<SimpleProduct>
|
||||||
|
*/
|
||||||
|
export type DeletesSimpleProducts = DeletesModels< SimpleProductRepositoryParams >;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple product object.
|
* A simple product object.
|
||||||
*/
|
*/
|
||||||
|
@ -26,7 +64,7 @@ export class SimpleProduct extends AbstractProduct {
|
||||||
*
|
*
|
||||||
* @param {Object} properties The properties to set in the object.
|
* @param {Object} properties The properties to set in the object.
|
||||||
*/
|
*/
|
||||||
public constructor( properties: Partial< SimpleProduct > = {} ) {
|
public constructor( properties?: Partial< SimpleProduct > ) {
|
||||||
super();
|
super();
|
||||||
Object.assign( this, properties );
|
Object.assign( this, properties );
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ export class SettingGroup extends Model {
|
||||||
*
|
*
|
||||||
* @param {Object} properties The properties to set in the object.
|
* @param {Object} properties The properties to set in the object.
|
||||||
*/
|
*/
|
||||||
public constructor( properties: Partial< SettingGroup > = {} ) {
|
public constructor( properties?: Partial< SettingGroup > ) {
|
||||||
super();
|
super();
|
||||||
Object.assign( this, properties );
|
Object.assign( this, properties );
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,7 +94,7 @@ export class Setting extends Model {
|
||||||
*
|
*
|
||||||
* @param {Object} properties The properties to set in the object.
|
* @param {Object} properties The properties to set in the object.
|
||||||
*/
|
*/
|
||||||
public constructor( properties: Partial< Setting > = {} ) {
|
public constructor( properties?: Partial< Setting > ) {
|
||||||
super();
|
super();
|
||||||
Object.assign( this, properties );
|
Object.assign( this, properties );
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { Model } from './model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A constructor for a model.
|
||||||
|
*
|
||||||
|
* @typedef ModelConstructor
|
||||||
|
* @alias Function.<T>
|
||||||
|
* @template T
|
||||||
|
*/
|
||||||
|
export type ModelConstructor< T extends Model > = new ( properties: Partial< T > ) => T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A post's status.
|
||||||
|
*
|
||||||
|
* @typedef PostStatus
|
||||||
|
* @alias 'draft'|'pending'|'private'|'publish'|string
|
||||||
|
*/
|
||||||
|
export type PostStatus = 'draft' | 'pending' | 'private' | 'publish' | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A metadata object.
|
||||||
|
*/
|
||||||
|
export class MetaData extends Model {
|
||||||
|
/**
|
||||||
|
* The key of the metadata.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public readonly key: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value of the metadata.
|
||||||
|
*
|
||||||
|
* @type {*}
|
||||||
|
*/
|
||||||
|
public readonly value: any = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new metadata.
|
||||||
|
*
|
||||||
|
* @param {Partial.<MetaData>} properties The properties to set.
|
||||||
|
*/
|
||||||
|
public constructor( properties?: Partial< MetaData > ) {
|
||||||
|
super();
|
||||||
|
Object.assign( this, properties );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,247 @@
|
||||||
|
import { mock, MockProxy } from 'jest-mock-extended';
|
||||||
|
import { HTTPClient, HTTPResponse } from '../../../http';
|
||||||
|
import { ModelTransformer } from '../../../framework/model-transformer';
|
||||||
|
import { DummyModel } from '../../../__test_data__/dummy-model';
|
||||||
|
import {
|
||||||
|
restCreate,
|
||||||
|
restDelete, restDeleteChild,
|
||||||
|
restList,
|
||||||
|
restListChild,
|
||||||
|
restRead,
|
||||||
|
restReadChild,
|
||||||
|
restUpdate,
|
||||||
|
restUpdateChild,
|
||||||
|
} from '../shared';
|
||||||
|
import { ModelRepositoryParams } from '../../../framework/model-repository';
|
||||||
|
import { Model } from '../../../models/model';
|
||||||
|
|
||||||
|
type DummyModelParams = ModelRepositoryParams< DummyModel, never, { search: string }, 'name' >
|
||||||
|
|
||||||
|
class DummyChildModel extends Model {
|
||||||
|
public childName: string = '';
|
||||||
|
|
||||||
|
public constructor( partial?: Partial< DummyModel > ) {
|
||||||
|
super();
|
||||||
|
Object.assign( this, partial );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type DummyChildParams = ModelRepositoryParams< DummyChildModel, { parent: string }, { childSearch: string }, 'childName' >
|
||||||
|
|
||||||
|
describe( 'Shared REST Functions', () => {
|
||||||
|
let mockClient: MockProxy< HTTPClient >;
|
||||||
|
let mockTransformer: MockProxy< ModelTransformer< any > > & ModelTransformer< any >;
|
||||||
|
|
||||||
|
beforeEach( () => {
|
||||||
|
mockClient = mock< HTTPClient >();
|
||||||
|
mockTransformer = mock< ModelTransformer< any > >();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'restList', async () => {
|
||||||
|
mockClient.get.mockResolvedValue( new HTTPResponse(
|
||||||
|
200,
|
||||||
|
{},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id: 'Test-1',
|
||||||
|
label: 'Test 1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Test-2',
|
||||||
|
label: 'Test 2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
) );
|
||||||
|
mockTransformer.toModel.mockReturnValue( new DummyModel( { name: 'Test' } ) );
|
||||||
|
|
||||||
|
const fn = restList< DummyModelParams >( () => 'test-url', DummyModel, mockClient, mockTransformer );
|
||||||
|
|
||||||
|
const result = await fn( { search: 'Test' } );
|
||||||
|
|
||||||
|
expect( result ).toHaveLength( 2 );
|
||||||
|
expect( result[ 0 ] ).toMatchObject( new DummyModel( { name: 'Test' } ) );
|
||||||
|
expect( result[ 1 ] ).toMatchObject( new DummyModel( { name: 'Test' } ) );
|
||||||
|
expect( mockClient.get ).toHaveBeenCalledWith( 'test-url', { search: 'Test' } );
|
||||||
|
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyModel, { id: 'Test-1', label: 'Test 1' } );
|
||||||
|
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyModel, { id: 'Test-2', label: 'Test 2' } );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'restListChildren', async () => {
|
||||||
|
mockClient.get.mockResolvedValue( new HTTPResponse(
|
||||||
|
200,
|
||||||
|
{},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id: 'Test-1',
|
||||||
|
label: 'Test 1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Test-2',
|
||||||
|
label: 'Test 2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
) );
|
||||||
|
mockTransformer.toModel.mockReturnValue( new DummyChildModel( { name: 'Test' } ) );
|
||||||
|
|
||||||
|
const fn = restListChild< DummyChildParams >(
|
||||||
|
( parent ) => 'test-url-' + parent.parent,
|
||||||
|
DummyChildModel,
|
||||||
|
mockClient,
|
||||||
|
mockTransformer,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await fn( { parent: '123' }, { childSearch: 'Test' } );
|
||||||
|
|
||||||
|
expect( result ).toHaveLength( 2 );
|
||||||
|
expect( result[ 0 ] ).toMatchObject( new DummyChildModel( { name: 'Test' } ) );
|
||||||
|
expect( result[ 1 ] ).toMatchObject( new DummyChildModel( { name: 'Test' } ) );
|
||||||
|
expect( mockClient.get ).toHaveBeenCalledWith( 'test-url-123', { childSearch: 'Test' } );
|
||||||
|
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyChildModel, { id: 'Test-1', label: 'Test 1' } );
|
||||||
|
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyChildModel, { id: 'Test-2', label: 'Test 2' } );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'restCreate', async () => {
|
||||||
|
mockClient.post.mockResolvedValue( new HTTPResponse(
|
||||||
|
200,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
id: 'Test-1',
|
||||||
|
label: 'Test 1',
|
||||||
|
},
|
||||||
|
) );
|
||||||
|
mockTransformer.fromModel.mockReturnValue( { name: 'From-Test' } );
|
||||||
|
mockTransformer.toModel.mockReturnValue( new DummyModel( { name: 'Test' } ) );
|
||||||
|
|
||||||
|
const fn = restCreate< DummyModelParams >(
|
||||||
|
( properties ) => 'test-url-' + properties.name,
|
||||||
|
DummyModel,
|
||||||
|
mockClient,
|
||||||
|
mockTransformer,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await fn( { name: 'Test' } );
|
||||||
|
|
||||||
|
expect( result ).toMatchObject( new DummyModel( { name: 'Test' } ) );
|
||||||
|
expect( mockTransformer.fromModel ).toHaveBeenCalledWith( { name: 'Test' } );
|
||||||
|
expect( mockClient.post ).toHaveBeenCalledWith( 'test-url-Test', { name: 'From-Test' } );
|
||||||
|
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyModel, { id: 'Test-1', label: 'Test 1' } );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'restRead', async () => {
|
||||||
|
mockClient.get.mockResolvedValue( new HTTPResponse(
|
||||||
|
200,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
id: 'Test-1',
|
||||||
|
label: 'Test 1',
|
||||||
|
},
|
||||||
|
) );
|
||||||
|
mockTransformer.toModel.mockReturnValue( new DummyModel( { name: 'Test' } ) );
|
||||||
|
|
||||||
|
const fn = restRead< DummyModelParams >( ( id ) => 'test-url-' + id, DummyModel, mockClient, mockTransformer );
|
||||||
|
|
||||||
|
const result = await fn( 123 );
|
||||||
|
|
||||||
|
expect( result ).toMatchObject( new DummyModel( { name: 'Test' } ) );
|
||||||
|
expect( mockClient.get ).toHaveBeenCalledWith( 'test-url-123' );
|
||||||
|
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyModel, { id: 'Test-1', label: 'Test 1' } );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'restReadChildren', async () => {
|
||||||
|
mockClient.get.mockResolvedValue( new HTTPResponse(
|
||||||
|
200,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
id: 'Test-1',
|
||||||
|
label: 'Test 1',
|
||||||
|
},
|
||||||
|
) );
|
||||||
|
mockTransformer.toModel.mockReturnValue( new DummyChildModel( { name: 'Test' } ) );
|
||||||
|
|
||||||
|
const fn = restReadChild< DummyChildParams >(
|
||||||
|
( parent, id ) => 'test-url-' + parent.parent + '-' + id,
|
||||||
|
DummyChildModel,
|
||||||
|
mockClient,
|
||||||
|
mockTransformer,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await fn( { parent: '123' }, 456 );
|
||||||
|
|
||||||
|
expect( result ).toMatchObject( new DummyModel( { name: 'Test' } ) );
|
||||||
|
expect( mockClient.get ).toHaveBeenCalledWith( 'test-url-123-456' );
|
||||||
|
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyChildModel, { id: 'Test-1', label: 'Test 1' } );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'restUpdate', async () => {
|
||||||
|
mockClient.patch.mockResolvedValue( new HTTPResponse(
|
||||||
|
200,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
id: 'Test-1',
|
||||||
|
label: 'Test 1',
|
||||||
|
},
|
||||||
|
) );
|
||||||
|
mockTransformer.fromModel.mockReturnValue( { name: 'From-Test' } );
|
||||||
|
mockTransformer.toModel.mockReturnValue( new DummyModel( { name: 'Test' } ) );
|
||||||
|
|
||||||
|
const fn = restUpdate< DummyModelParams >( ( id ) => 'test-url-' + id, DummyModel, mockClient, mockTransformer );
|
||||||
|
|
||||||
|
const result = await fn( 123, { name: 'Test' } );
|
||||||
|
|
||||||
|
expect( result ).toMatchObject( new DummyModel( { name: 'Test' } ) );
|
||||||
|
expect( mockTransformer.fromModel ).toHaveBeenCalledWith( { name: 'Test' } );
|
||||||
|
expect( mockClient.patch ).toHaveBeenCalledWith( 'test-url-123', { name: 'From-Test' } );
|
||||||
|
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyModel, { id: 'Test-1', label: 'Test 1' } );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'restUpdateChildren', async () => {
|
||||||
|
mockClient.patch.mockResolvedValue( new HTTPResponse(
|
||||||
|
200,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
id: 'Test-1',
|
||||||
|
label: 'Test 1',
|
||||||
|
},
|
||||||
|
) );
|
||||||
|
mockTransformer.fromModel.mockReturnValue( { name: 'From-Test' } );
|
||||||
|
mockTransformer.toModel.mockReturnValue( new DummyChildModel( { name: 'Test' } ) );
|
||||||
|
|
||||||
|
const fn = restUpdateChild< DummyChildParams >(
|
||||||
|
( parent, id ) => 'test-url-' + parent.parent + '-' + id,
|
||||||
|
DummyChildModel,
|
||||||
|
mockClient,
|
||||||
|
mockTransformer,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await fn( { parent: '123' }, 456, { childName: 'Test' } );
|
||||||
|
|
||||||
|
expect( result ).toMatchObject( new DummyChildModel( { name: 'Test' } ) );
|
||||||
|
expect( mockTransformer.fromModel ).toHaveBeenCalledWith( { childName: 'Test' } );
|
||||||
|
expect( mockClient.patch ).toHaveBeenCalledWith( 'test-url-123-456', { name: 'From-Test' } );
|
||||||
|
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyChildModel, { id: 'Test-1', label: 'Test 1' } );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'restDelete', async () => {
|
||||||
|
mockClient.delete.mockResolvedValue( new HTTPResponse( 200, {}, {} ) );
|
||||||
|
|
||||||
|
const fn = restDelete< DummyModelParams >( ( id ) => 'test-url-' + id, mockClient );
|
||||||
|
|
||||||
|
const result = await fn( 123 );
|
||||||
|
|
||||||
|
expect( result ).toBe( true );
|
||||||
|
expect( mockClient.delete ).toHaveBeenCalledWith( 'test-url-123' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'restDeleteChildren', async () => {
|
||||||
|
mockClient.delete.mockResolvedValue( new HTTPResponse( 200, {}, {} ) );
|
||||||
|
|
||||||
|
const fn = restDeleteChild< DummyChildParams >(
|
||||||
|
( parent, id ) => 'test-url-' + parent.parent + '-' + id,
|
||||||
|
mockClient,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await fn( { parent: '123' }, 456 );
|
||||||
|
|
||||||
|
expect( result ).toBe( true );
|
||||||
|
expect( mockClient.delete ).toHaveBeenCalledWith( 'test-url-123-456' );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -1,28 +0,0 @@
|
||||||
import { simpleProductRESTRepository } from '../simple-product';
|
|
||||||
import { mock, MockProxy } from 'jest-mock-extended';
|
|
||||||
import { HTTPClient, HTTPResponse } from '../../../../http';
|
|
||||||
import { SimpleProduct } from '../../../../models';
|
|
||||||
|
|
||||||
describe( 'simpleProductRESTRepository', () => {
|
|
||||||
let httpClient: MockProxy< HTTPClient >;
|
|
||||||
let repository: ReturnType< typeof simpleProductRESTRepository >;
|
|
||||||
|
|
||||||
beforeEach( () => {
|
|
||||||
httpClient = mock< HTTPClient >();
|
|
||||||
repository = simpleProductRESTRepository( httpClient );
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'should create', async () => {
|
|
||||||
httpClient.post.mockResolvedValue( new HTTPResponse(
|
|
||||||
200,
|
|
||||||
{},
|
|
||||||
{ id: 123 },
|
|
||||||
) );
|
|
||||||
|
|
||||||
const created = await repository.create( { name: 'Test Product' } );
|
|
||||||
|
|
||||||
expect( created ).toBeInstanceOf( SimpleProduct );
|
|
||||||
expect( created ).toMatchObject( { id: 123 } );
|
|
||||||
expect( httpClient.post ).toHaveBeenCalledWith( '/wc/v3/products', { type: 'simple', name: 'Test Product' } );
|
|
||||||
} );
|
|
||||||
} );
|
|
|
@ -0,0 +1,221 @@
|
||||||
|
import { ModelTransformation, ModelTransformer, TransformationOrder } from '../../../framework/model-transformer';
|
||||||
|
import { KeyChangeTransformation } from '../../../framework/transformations/key-change-transformation';
|
||||||
|
import { AbstractProduct } from '../../../models/products/abstract-product';
|
||||||
|
import { AddPropertyTransformation } from '../../../framework/transformations/add-property-transformation';
|
||||||
|
import { IgnorePropertyTransformation } from '../../../framework/transformations/ignore-property-transformation';
|
||||||
|
import {
|
||||||
|
PropertyType,
|
||||||
|
PropertyTypeTransformation,
|
||||||
|
} from '../../../framework/transformations/property-type-transformation';
|
||||||
|
import { CustomTransformation } from '../../../framework/transformations/custom-transformation';
|
||||||
|
import { ProductAttribute, ProductDownload, ProductImage, ProductTerm } from '../../../models/products/shared-types';
|
||||||
|
import { ModelTransformerTransformation } from '../../../framework/transformations/model-transformer-transformation';
|
||||||
|
import { MetaData } from '../../../models/shared-types';
|
||||||
|
import { createMetaDataTransformer } from '../shared';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a transformer for the product term object.
|
||||||
|
*
|
||||||
|
* @return {ModelTransformer} The created transformer.
|
||||||
|
*/
|
||||||
|
function createProductTermTransformer(): ModelTransformer< ProductTerm > {
|
||||||
|
return new ModelTransformer(
|
||||||
|
[
|
||||||
|
new PropertyTypeTransformation( { id: PropertyType.Integer } ),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a transformer for the product attribute object.
|
||||||
|
*
|
||||||
|
* @return {ModelTransformer} The created transformer.
|
||||||
|
*/
|
||||||
|
function createProductAttributeTransformer(): ModelTransformer< ProductAttribute > {
|
||||||
|
return new ModelTransformer(
|
||||||
|
[
|
||||||
|
new PropertyTypeTransformation(
|
||||||
|
{
|
||||||
|
id: PropertyType.Integer,
|
||||||
|
sortOrder: PropertyType.Integer,
|
||||||
|
isVisibleOnProductPage: PropertyType.Boolean,
|
||||||
|
isForVariations: PropertyType.Boolean,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
new KeyChangeTransformation< ProductAttribute >(
|
||||||
|
{
|
||||||
|
sortOrder: 'position',
|
||||||
|
isVisibleOnProductPage: 'visible',
|
||||||
|
isForVariations: 'variation',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a transformer for the product image object.
|
||||||
|
*
|
||||||
|
* @return {ModelTransformer} The created transformer.
|
||||||
|
*/
|
||||||
|
function createProductImageTransformer(): ModelTransformer< ProductImage > {
|
||||||
|
return new ModelTransformer(
|
||||||
|
[
|
||||||
|
new IgnorePropertyTransformation( [ 'date_created', 'date_modified' ] ),
|
||||||
|
new PropertyTypeTransformation(
|
||||||
|
{
|
||||||
|
id: PropertyType.Integer,
|
||||||
|
created: PropertyType.Date,
|
||||||
|
modified: PropertyType.Date,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
new KeyChangeTransformation< ProductImage >(
|
||||||
|
{
|
||||||
|
created: 'date_created_gmt',
|
||||||
|
modified: 'date_modified_gmt',
|
||||||
|
url: 'src',
|
||||||
|
altText: 'altText',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a transformer for the product download object.
|
||||||
|
*
|
||||||
|
* @return {ModelTransformer} The created transformer.
|
||||||
|
*/
|
||||||
|
function createProductDownloadTransformer(): ModelTransformer< ProductDownload > {
|
||||||
|
return new ModelTransformer(
|
||||||
|
[
|
||||||
|
new KeyChangeTransformation< ProductDownload >( { url: 'file' } ),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a transformer for the shared properties of all products.
|
||||||
|
*
|
||||||
|
* @param {string} type The product type.
|
||||||
|
* @param {Array.<ModelTransformation>} transformations Optional transformers to add to the transformer.
|
||||||
|
* @return {ModelTransformer} The created transformer.
|
||||||
|
*/
|
||||||
|
export function createProductTransformer< T extends AbstractProduct >(
|
||||||
|
type: string,
|
||||||
|
transformations?: ModelTransformation[],
|
||||||
|
): ModelTransformer< T > {
|
||||||
|
if ( ! transformations ) {
|
||||||
|
transformations = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
transformations.push(
|
||||||
|
new AddPropertyTransformation( {}, { type } ),
|
||||||
|
new IgnorePropertyTransformation(
|
||||||
|
[
|
||||||
|
'date_created',
|
||||||
|
'date_modified',
|
||||||
|
'date_on_sale_from',
|
||||||
|
'date_on_sale_to',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
new ModelTransformerTransformation( 'categories', ProductTerm, createProductTermTransformer() ),
|
||||||
|
new ModelTransformerTransformation( 'tags', ProductTerm, createProductTermTransformer() ),
|
||||||
|
new ModelTransformerTransformation( 'attributes', ProductAttribute, createProductAttributeTransformer() ),
|
||||||
|
new ModelTransformerTransformation( 'images', ProductImage, createProductImageTransformer() ),
|
||||||
|
new ModelTransformerTransformation( 'downloads', ProductDownload, createProductDownloadTransformer() ),
|
||||||
|
new ModelTransformerTransformation( 'metaData', MetaData, createMetaDataTransformer() ),
|
||||||
|
new CustomTransformation(
|
||||||
|
TransformationOrder.Normal,
|
||||||
|
( properties: any ) => {
|
||||||
|
if ( properties.hasOwnProperty( 'dimensions' ) ) {
|
||||||
|
properties.length = properties.dimensions.length;
|
||||||
|
properties.width = properties.dimensions.width;
|
||||||
|
properties.height = properties.dimensions.height;
|
||||||
|
delete properties.dimensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
return properties;
|
||||||
|
},
|
||||||
|
( properties: any ) => {
|
||||||
|
if ( properties.hasOwnProperty( 'length ' ) ||
|
||||||
|
properties.hasOwnProperty( 'width' ) ||
|
||||||
|
properties.hasOwnProperty( 'height' ) ) {
|
||||||
|
properties.dimensions = {
|
||||||
|
length: properties.length,
|
||||||
|
width: properties.width,
|
||||||
|
height: properties.height,
|
||||||
|
};
|
||||||
|
delete properties.length;
|
||||||
|
delete properties.width;
|
||||||
|
delete properties.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
return properties;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
new PropertyTypeTransformation(
|
||||||
|
{
|
||||||
|
created: PropertyType.Date,
|
||||||
|
modified: PropertyType.Date,
|
||||||
|
isPurchasable: PropertyType.Boolean,
|
||||||
|
isFeatured: PropertyType.Boolean,
|
||||||
|
isVirtual: PropertyType.Boolean,
|
||||||
|
onePerOrder: PropertyType.Boolean,
|
||||||
|
onSale: PropertyType.Boolean,
|
||||||
|
saleStart: PropertyType.Date,
|
||||||
|
saleEnd: PropertyType.Date,
|
||||||
|
isDownloadable: PropertyType.Boolean,
|
||||||
|
downloadLimit: PropertyType.Integer,
|
||||||
|
daysToDownload: PropertyType.Integer,
|
||||||
|
requiresShipping: PropertyType.Boolean,
|
||||||
|
isShippingTaxable: PropertyType.Boolean,
|
||||||
|
trackInventory: PropertyType.Boolean,
|
||||||
|
remainingStock: PropertyType.Integer,
|
||||||
|
canBackorder: PropertyType.Boolean,
|
||||||
|
isOnBackorder: PropertyType.Boolean,
|
||||||
|
allowReviews: PropertyType.Boolean,
|
||||||
|
averageRating: PropertyType.Integer,
|
||||||
|
numRatings: PropertyType.Integer,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
new KeyChangeTransformation< AbstractProduct >(
|
||||||
|
{
|
||||||
|
created: 'date_created_gmt',
|
||||||
|
modified: 'date_modified_gmt',
|
||||||
|
postStatus: 'status',
|
||||||
|
shortDescription: 'short_description',
|
||||||
|
isPurchasable: 'purchasable',
|
||||||
|
isFeatured: 'featured',
|
||||||
|
isVirtual: 'virtual',
|
||||||
|
catalogVisibility: 'catalog_visibility',
|
||||||
|
regularPrice: 'regular_price',
|
||||||
|
onePerOrder: 'sold_individually',
|
||||||
|
taxStatus: 'tax_status',
|
||||||
|
taxClass: 'tax_class',
|
||||||
|
onSale: 'on_sale',
|
||||||
|
salePrice: 'sale_price',
|
||||||
|
saleStart: 'date_on_sale_from_gmt',
|
||||||
|
saleEnd: 'date_on_sale_to_gmt',
|
||||||
|
isDownloadable: 'downloadable',
|
||||||
|
downloadLimit: 'download_limit',
|
||||||
|
daysToDownload: 'download_expiry',
|
||||||
|
requiresShipping: 'shipping_required',
|
||||||
|
isShippingTaxable: 'shipping_taxable',
|
||||||
|
shippingClass: 'shipping_class',
|
||||||
|
trackInventory: 'manage_stock',
|
||||||
|
remainingStock: 'stock_quantity',
|
||||||
|
stockStatus: 'stock_status',
|
||||||
|
backorderStatus: 'backorders',
|
||||||
|
canBackorder: 'backorders_allowed',
|
||||||
|
isOnBackorder: 'backordered',
|
||||||
|
allowReviews: 'reviews_allowed',
|
||||||
|
averageRating: 'average_rating',
|
||||||
|
numRatings: 'rating_count',
|
||||||
|
metaData: 'meta_data',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return new ModelTransformer( transformations );
|
||||||
|
}
|
|
@ -1,39 +1,43 @@
|
||||||
import { HTTPClient } from '../../../http';
|
import { HTTPClient } from '../../../http';
|
||||||
import { CreateFn, ModelRepository } from '../../../framework/model-repository';
|
import { ModelRepository } from '../../../framework/model-repository';
|
||||||
import { SimpleProduct } from '../../../models';
|
import { SimpleProduct } from '../../../models';
|
||||||
import { CreatesSimpleProducts, SimpleProductRepositoryParams } from '../../../models/products/simple-product';
|
import {
|
||||||
|
CreatesSimpleProducts,
|
||||||
function restCreate( httpClient: HTTPClient ): CreateFn< SimpleProductRepositoryParams > {
|
DeletesSimpleProducts,
|
||||||
return async ( properties ) => {
|
ListsSimpleProducts,
|
||||||
const response = await httpClient.post(
|
ReadsSimpleProducts,
|
||||||
'/wc/v3/products',
|
SimpleProductRepositoryParams,
|
||||||
{
|
UpdatesSimpleProducts,
|
||||||
type: 'simple',
|
} from '../../../models/products/simple-product';
|
||||||
name: properties.name,
|
import { createProductTransformer } from './shared';
|
||||||
regular_price: properties.regularPrice,
|
import { restCreate, restDelete, restList, restRead, restUpdate } from '../shared';
|
||||||
},
|
import { ModelID } from '../../../models/model';
|
||||||
);
|
|
||||||
|
|
||||||
return Promise.resolve( new SimpleProduct( {
|
|
||||||
id: response.data.id,
|
|
||||||
name: response.data.name,
|
|
||||||
regularPrice: response.data.regular_price,
|
|
||||||
} ) );
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new ModelRepository instance for interacting with models via the REST API.
|
* Creates a new ModelRepository instance for interacting with models via the REST API.
|
||||||
*
|
*
|
||||||
* @param {HTTPClient} httpClient The HTTP client for the REST requests to be made using.
|
* @param {HTTPClient} httpClient The HTTP client for the REST requests to be made using.
|
||||||
* @return {CreatesSimpleProducts} The created repository.
|
* @return {
|
||||||
|
* ListsSimpleProducts|
|
||||||
|
* CreatesSimpleProducts|
|
||||||
|
* ReadsSimpleProducts|
|
||||||
|
* UpdatesSimpleProducts|
|
||||||
|
* DeletesSimpleProducts
|
||||||
|
* } The created repository.
|
||||||
*/
|
*/
|
||||||
export function simpleProductRESTRepository( httpClient: HTTPClient ): CreatesSimpleProducts {
|
export function simpleProductRESTRepository( httpClient: HTTPClient ): ListsSimpleProducts
|
||||||
|
& CreatesSimpleProducts
|
||||||
|
& ReadsSimpleProducts
|
||||||
|
& UpdatesSimpleProducts
|
||||||
|
& DeletesSimpleProducts {
|
||||||
|
const buildURL = ( id: ModelID ) => '/wc/v3/products/' + id;
|
||||||
|
const transformer = createProductTransformer( 'simple' );
|
||||||
|
|
||||||
return new ModelRepository(
|
return new ModelRepository(
|
||||||
null,
|
restList< SimpleProductRepositoryParams >( () => '/wc/v3/products', SimpleProduct, httpClient, transformer ),
|
||||||
restCreate( httpClient ),
|
restCreate< SimpleProductRepositoryParams >( () => '/wc/v3/products', SimpleProduct, httpClient, transformer ),
|
||||||
null,
|
restRead< SimpleProductRepositoryParams >( buildURL, SimpleProduct, httpClient, transformer ),
|
||||||
null,
|
restUpdate< SimpleProductRepositoryParams >( buildURL, SimpleProduct, httpClient, transformer ),
|
||||||
null,
|
restDelete< SimpleProductRepositoryParams >( buildURL, httpClient ),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
import { mock, MockProxy } from 'jest-mock-extended';
|
|
||||||
import { HTTPClient, HTTPResponse } from '../../../../http';
|
|
||||||
import { settingGroupRESTRepository } from '../setting-group';
|
|
||||||
|
|
||||||
describe( 'settingGroupRESTRepository', () => {
|
|
||||||
let httpClient: MockProxy< HTTPClient >;
|
|
||||||
let repository: ReturnType< typeof settingGroupRESTRepository >;
|
|
||||||
|
|
||||||
beforeEach( () => {
|
|
||||||
httpClient = mock< HTTPClient >();
|
|
||||||
repository = settingGroupRESTRepository( httpClient );
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'should list', async () => {
|
|
||||||
httpClient.get.mockResolvedValue( new HTTPResponse(
|
|
||||||
200,
|
|
||||||
{},
|
|
||||||
[
|
|
||||||
{
|
|
||||||
id: 'group_1',
|
|
||||||
label: 'Test Group 1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'group_2',
|
|
||||||
label: 'Test Group 2',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
) );
|
|
||||||
|
|
||||||
const list = await repository.list();
|
|
||||||
|
|
||||||
expect( list ).toHaveLength( 2 );
|
|
||||||
expect( list[ 0 ] ).toMatchObject( { id: 'group_1', label: 'Test Group 1' } );
|
|
||||||
expect( list[ 1 ] ).toMatchObject( { id: 'group_2', label: 'Test Group 2' } );
|
|
||||||
expect( httpClient.get ).toHaveBeenCalledWith( '/wc/v3/settings' );
|
|
||||||
} );
|
|
||||||
} );
|
|
|
@ -1,73 +0,0 @@
|
||||||
import { mock, MockProxy } from 'jest-mock-extended';
|
|
||||||
import { HTTPClient, HTTPResponse } from '../../../../http';
|
|
||||||
import { settingRESTRepository } from '../setting';
|
|
||||||
|
|
||||||
describe( 'settingGroupRESTRepository', () => {
|
|
||||||
let httpClient: MockProxy< HTTPClient >;
|
|
||||||
let repository: ReturnType< typeof settingRESTRepository >;
|
|
||||||
|
|
||||||
beforeEach( () => {
|
|
||||||
httpClient = mock< HTTPClient >();
|
|
||||||
repository = settingRESTRepository( httpClient );
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'should list', async () => {
|
|
||||||
httpClient.get.mockResolvedValue( new HTTPResponse(
|
|
||||||
200,
|
|
||||||
{},
|
|
||||||
[
|
|
||||||
{
|
|
||||||
id: 'setting_1',
|
|
||||||
label: 'Test Setting 1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'setting_2',
|
|
||||||
label: 'Test Setting 2',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
) );
|
|
||||||
|
|
||||||
const list = await repository.list( 'general' );
|
|
||||||
|
|
||||||
expect( list ).toHaveLength( 2 );
|
|
||||||
expect( list[ 0 ] ).toMatchObject( { id: 'setting_1', label: 'Test Setting 1' } );
|
|
||||||
expect( list[ 1 ] ).toMatchObject( { id: 'setting_2', label: 'Test Setting 2' } );
|
|
||||||
expect( httpClient.get ).toHaveBeenCalledWith( '/wc/v3/settings/general' );
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'should read', async () => {
|
|
||||||
httpClient.get.mockResolvedValue( new HTTPResponse(
|
|
||||||
200,
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
id: 'setting_1',
|
|
||||||
label: 'Test Setting',
|
|
||||||
},
|
|
||||||
) );
|
|
||||||
|
|
||||||
const read = await repository.read( 'general', 'setting_1' );
|
|
||||||
|
|
||||||
expect( read ).toMatchObject( { id: 'setting_1', label: 'Test Setting' } );
|
|
||||||
expect( httpClient.get ).toHaveBeenCalledWith( '/wc/v3/settings/general/setting_1' );
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'should update', async () => {
|
|
||||||
httpClient.patch.mockResolvedValue( new HTTPResponse(
|
|
||||||
200,
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
id: 'setting_1',
|
|
||||||
label: 'Test Setting',
|
|
||||||
value: 'updated-value',
|
|
||||||
},
|
|
||||||
) );
|
|
||||||
|
|
||||||
const updated = await repository.update( 'general', 'setting_1', { value: 'test-value' } );
|
|
||||||
|
|
||||||
expect( updated ).toMatchObject( { id: 'setting_1', value: 'updated-value' } );
|
|
||||||
expect( httpClient.patch ).toHaveBeenCalledWith(
|
|
||||||
'/wc/v3/settings/general/setting_1',
|
|
||||||
{ value: 'test-value' },
|
|
||||||
);
|
|
||||||
} );
|
|
||||||
} );
|
|
|
@ -1,24 +1,17 @@
|
||||||
import { HTTPClient } from '../../../http';
|
import { HTTPClient } from '../../../http';
|
||||||
import { ListFn, ModelRepository } from '../../../framework/model-repository';
|
import { ModelRepository } from '../../../framework/model-repository';
|
||||||
import { SettingGroup } from '../../../models';
|
import { SettingGroup } from '../../../models';
|
||||||
import { ListsSettingGroups, SettingGroupRepositoryParams } from '../../../models/settings/setting-group';
|
import { ListsSettingGroups, SettingGroupRepositoryParams } from '../../../models/settings/setting-group';
|
||||||
|
import { ModelTransformer } from '../../../framework/model-transformer';
|
||||||
|
import { KeyChangeTransformation } from '../../../framework/transformations/key-change-transformation';
|
||||||
|
import { restList } from '../shared';
|
||||||
|
|
||||||
function restList( httpClient: HTTPClient ): ListFn< SettingGroupRepositoryParams > {
|
function createTransformer(): ModelTransformer< SettingGroup > {
|
||||||
return async () => {
|
return new ModelTransformer(
|
||||||
const response = await httpClient.get( '/wc/v3/settings' );
|
[
|
||||||
|
new KeyChangeTransformation< SettingGroup >( { parentID: 'parent_id' } ),
|
||||||
const list: SettingGroup[] = [];
|
],
|
||||||
for ( const raw of response.data ) {
|
);
|
||||||
list.push( new SettingGroup( {
|
|
||||||
id: raw.id,
|
|
||||||
label: raw.label,
|
|
||||||
description: raw.description,
|
|
||||||
parentID: raw.parent_id,
|
|
||||||
} ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve( list );
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,8 +21,10 @@ function restList( httpClient: HTTPClient ): ListFn< SettingGroupRepositoryParam
|
||||||
* @return {ListsSettingGroups} The created repository.
|
* @return {ListsSettingGroups} The created repository.
|
||||||
*/
|
*/
|
||||||
export function settingGroupRESTRepository( httpClient: HTTPClient ): ListsSettingGroups {
|
export function settingGroupRESTRepository( httpClient: HTTPClient ): ListsSettingGroups {
|
||||||
|
const transformer = createTransformer();
|
||||||
|
|
||||||
return new ModelRepository(
|
return new ModelRepository(
|
||||||
restList( httpClient ),
|
restList< SettingGroupRepositoryParams >( () => '/wc/v3/settings', SettingGroup, httpClient, transformer ),
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
import { HTTPClient } from '../../../http';
|
import { HTTPClient } from '../../../http';
|
||||||
import {
|
import { ModelRepository, ParentID } from '../../../framework/model-repository';
|
||||||
ListChildFn,
|
|
||||||
ModelRepository,
|
|
||||||
ReadChildFn,
|
|
||||||
UpdateChildFn,
|
|
||||||
} from '../../../framework/model-repository';
|
|
||||||
import { Setting } from '../../../models';
|
import { Setting } from '../../../models';
|
||||||
import {
|
import {
|
||||||
ListsSettings,
|
ListsSettings,
|
||||||
|
@ -12,61 +7,12 @@ import {
|
||||||
SettingRepositoryParams,
|
SettingRepositoryParams,
|
||||||
UpdatesSettings,
|
UpdatesSettings,
|
||||||
} from '../../../models/settings/setting';
|
} from '../../../models/settings/setting';
|
||||||
|
import { ModelTransformer } from '../../../framework/model-transformer';
|
||||||
|
import { restListChild, restReadChild, restUpdateChild } from '../shared';
|
||||||
|
import { ModelID } from '../../../models/model';
|
||||||
|
|
||||||
function restList( httpClient: HTTPClient ): ListChildFn< SettingRepositoryParams > {
|
function createTransformer(): ModelTransformer< Setting > {
|
||||||
return async ( parent ) => {
|
return new ModelTransformer( [] );
|
||||||
const response = await httpClient.get( '/wc/v3/settings/' + parent );
|
|
||||||
|
|
||||||
const list: Setting[] = [];
|
|
||||||
for ( const raw of response.data ) {
|
|
||||||
list.push( new Setting( {
|
|
||||||
id: raw.id,
|
|
||||||
label: raw.label,
|
|
||||||
description: raw.description,
|
|
||||||
type: raw.type,
|
|
||||||
options: raw.options,
|
|
||||||
default: raw.default,
|
|
||||||
value: raw.value,
|
|
||||||
} ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve( list );
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function restRead( httpClient: HTTPClient ): ReadChildFn< SettingRepositoryParams > {
|
|
||||||
return async ( parent, id ) => {
|
|
||||||
const response = await httpClient.get( '/wc/v3/settings/' + parent + '/' + id );
|
|
||||||
|
|
||||||
return Promise.resolve( new Setting( {
|
|
||||||
id: response.data.id,
|
|
||||||
label: response.data.label,
|
|
||||||
description: response.data.description,
|
|
||||||
type: response.data.type,
|
|
||||||
options: response.data.options,
|
|
||||||
default: response.data.default,
|
|
||||||
value: response.data.value,
|
|
||||||
} ) );
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function restUpdate( httpClient: HTTPClient ): UpdateChildFn< SettingRepositoryParams > {
|
|
||||||
return async ( parent, id, params ) => {
|
|
||||||
const response = await httpClient.patch(
|
|
||||||
'/wc/v3/settings/' + parent + '/' + id,
|
|
||||||
params,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Promise.resolve( new Setting( {
|
|
||||||
id: response.data.id,
|
|
||||||
label: response.data.label,
|
|
||||||
description: response.data.description,
|
|
||||||
type: response.data.type,
|
|
||||||
options: response.data.options,
|
|
||||||
default: response.data.default,
|
|
||||||
value: response.data.value,
|
|
||||||
} ) );
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -76,11 +22,14 @@ function restUpdate( httpClient: HTTPClient ): UpdateChildFn< SettingRepositoryP
|
||||||
* @return {ListsSettings|ReadsSettings|UpdatesSettings} The created repository.
|
* @return {ListsSettings|ReadsSettings|UpdatesSettings} The created repository.
|
||||||
*/
|
*/
|
||||||
export function settingRESTRepository( httpClient: HTTPClient ): ListsSettings & ReadsSettings & UpdatesSettings {
|
export function settingRESTRepository( httpClient: HTTPClient ): ListsSettings & ReadsSettings & UpdatesSettings {
|
||||||
|
const buildURL = ( parent: ParentID< SettingRepositoryParams >, id: ModelID ) => '/wc/v3/settings/' + parent + '/' + id;
|
||||||
|
const transformer = createTransformer();
|
||||||
|
|
||||||
return new ModelRepository(
|
return new ModelRepository(
|
||||||
restList( httpClient ),
|
restListChild< SettingRepositoryParams >( ( parent ) => '/wc/v3/settings/' + parent, Setting, httpClient, transformer ),
|
||||||
null,
|
null,
|
||||||
restRead( httpClient ),
|
restReadChild< SettingRepositoryParams >( buildURL, Setting, httpClient, transformer ),
|
||||||
restUpdate( httpClient ),
|
restUpdateChild< SettingRepositoryParams >( buildURL, Setting, httpClient, transformer ),
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,258 @@
|
||||||
|
import { ModelTransformer } from '../../framework/model-transformer';
|
||||||
|
import { MetaData, ModelConstructor } from '../../models/shared-types';
|
||||||
|
import { IgnorePropertyTransformation } from '../../framework/transformations/ignore-property-transformation';
|
||||||
|
import { HTTPClient } from '../../http';
|
||||||
|
import {
|
||||||
|
ListFn,
|
||||||
|
ModelRepositoryParams,
|
||||||
|
ModelClass,
|
||||||
|
HasParent,
|
||||||
|
ParentID,
|
||||||
|
ListChildFn,
|
||||||
|
ReadChildFn,
|
||||||
|
ReadFn,
|
||||||
|
DeleteFn,
|
||||||
|
UpdateFn,
|
||||||
|
UpdateChildFn,
|
||||||
|
DeleteChildFn,
|
||||||
|
CreateFn,
|
||||||
|
} from '../../framework/model-repository';
|
||||||
|
import { ModelID } from '../../models/model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new transformer for metadata models.
|
||||||
|
*
|
||||||
|
* @return {ModelTransformer} The created transformer.
|
||||||
|
*/
|
||||||
|
export function createMetaDataTransformer(): ModelTransformer< MetaData > {
|
||||||
|
return new ModelTransformer(
|
||||||
|
[
|
||||||
|
new IgnorePropertyTransformation( [ 'id' ] ),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback to build a URL for a request.
|
||||||
|
*
|
||||||
|
* @callback BuildURLFn
|
||||||
|
* @param {ModelID} [id] The ID of the model we're dealing with if used for the request.
|
||||||
|
* @return {string} The URL to make the request to.
|
||||||
|
*/
|
||||||
|
type BuildURLFn< T extends ( 'list' | 'general' ) = 'general' > = [ T ] extends [ 'list' ] ? () => string : ( id: ModelID ) => string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback to build a URL for a request.
|
||||||
|
*
|
||||||
|
* @callback BuildURLWithParentFn
|
||||||
|
* @param {P} parent The ID of the model's parent.
|
||||||
|
* @param {ModelID} [id] The ID of the model we're dealing with if used for the request.
|
||||||
|
* @return {string} The URL to make the request to.
|
||||||
|
* @template {ModelParentID} P
|
||||||
|
*/
|
||||||
|
type BuildURLWithParentFn< P extends ModelRepositoryParams, T extends ( 'list' | 'general' ) = 'general' > = [ T ] extends [ 'list' ]
|
||||||
|
? ( parent: ParentID< P > ) => string
|
||||||
|
: ( parent: ParentID< P >, id: ModelID ) => string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a callback for listing models using the REST API.
|
||||||
|
*
|
||||||
|
* @param {BuildURLFn} buildURL A callback to build the URL for the request.
|
||||||
|
* @param {Function} modelClass The model we're listing.
|
||||||
|
* @param {HTTPClient} httpClient The HTTP client to use for the request.
|
||||||
|
* @param {ModelTransformer} transformer The transformer to use for the response data.
|
||||||
|
* @return {ListFn} The callback for the repository.
|
||||||
|
*/
|
||||||
|
export function restList< T extends ModelRepositoryParams >(
|
||||||
|
buildURL: HasParent< T, never, BuildURLFn< 'list' > >,
|
||||||
|
modelClass: ModelConstructor< ModelClass< T > >,
|
||||||
|
httpClient: HTTPClient,
|
||||||
|
transformer: ModelTransformer< ModelClass< T > >,
|
||||||
|
): ListFn< T > {
|
||||||
|
return async ( params ) => {
|
||||||
|
const response = await httpClient.get( buildURL(), params );
|
||||||
|
|
||||||
|
const list: ModelClass< T >[] = [];
|
||||||
|
for ( const raw of response.data ) {
|
||||||
|
list.push( transformer.toModel( modelClass, raw ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve( list );
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a callback for listing child models using the REST API.
|
||||||
|
*
|
||||||
|
* @param {BuildURLWithParentFn} buildURL A callback to build the URL for the request.
|
||||||
|
* @param {Function} modelClass The model we're listing.
|
||||||
|
* @param {HTTPClient} httpClient The HTTP client to use for the request.
|
||||||
|
* @param {ModelTransformer} transformer The transformer to use for the response data.
|
||||||
|
* @return {ListChildFn} The callback for the repository.
|
||||||
|
*/
|
||||||
|
export function restListChild< T extends ModelRepositoryParams >(
|
||||||
|
buildURL: HasParent< T, BuildURLWithParentFn< T, 'list' >, never >,
|
||||||
|
modelClass: ModelConstructor< ModelClass< T > >,
|
||||||
|
httpClient: HTTPClient,
|
||||||
|
transformer: ModelTransformer< ModelClass< T > >,
|
||||||
|
): ListChildFn< T > {
|
||||||
|
return async ( parent, params ) => {
|
||||||
|
const response = await httpClient.get( buildURL( parent ), params );
|
||||||
|
|
||||||
|
const list: ModelClass< T >[] = [];
|
||||||
|
for ( const raw of response.data ) {
|
||||||
|
list.push( transformer.toModel( modelClass, raw ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve( list );
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a callback for creating models using the REST API.
|
||||||
|
*
|
||||||
|
* @param {Function} buildURL A callback to build the URL. (This is passed the properties for the new model.)
|
||||||
|
* @param {Function} modelClass The model we're listing.
|
||||||
|
* @param {HTTPClient} httpClient The HTTP client to use for the request.
|
||||||
|
* @param {ModelTransformer} transformer The transformer to use for the response data.
|
||||||
|
* @return {CreateFn} The callback for the repository.
|
||||||
|
*/
|
||||||
|
export function restCreate< T extends ModelRepositoryParams >(
|
||||||
|
buildURL: ( properties: Partial< ModelClass< T > > ) => string,
|
||||||
|
modelClass: ModelConstructor< ModelClass< T > >,
|
||||||
|
httpClient: HTTPClient,
|
||||||
|
transformer: ModelTransformer< ModelClass< T > >,
|
||||||
|
): CreateFn< T > {
|
||||||
|
return async ( properties ) => {
|
||||||
|
const response = await httpClient.post(
|
||||||
|
buildURL( properties ),
|
||||||
|
transformer.fromModel( properties ),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Promise.resolve( transformer.toModel( modelClass, response.data ) );
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a callback for reading models using the REST API.
|
||||||
|
*
|
||||||
|
* @param {BuildURLFn} buildURL A callback to build the URL for the request.
|
||||||
|
* @param {Function} modelClass The model we're listing.
|
||||||
|
* @param {HTTPClient} httpClient The HTTP client to use for the request.
|
||||||
|
* @param {ModelTransformer} transformer The transformer to use for the response data.
|
||||||
|
* @return {ReadFn} The callback for the repository.
|
||||||
|
*/
|
||||||
|
export function restRead< T extends ModelRepositoryParams >(
|
||||||
|
buildURL: HasParent< T, never, BuildURLFn >,
|
||||||
|
modelClass: ModelConstructor< ModelClass< T > >,
|
||||||
|
httpClient: HTTPClient,
|
||||||
|
transformer: ModelTransformer< ModelClass< T > >,
|
||||||
|
): ReadFn< T > {
|
||||||
|
return async ( id ) => {
|
||||||
|
const response = await httpClient.get( buildURL( id ) );
|
||||||
|
return Promise.resolve( transformer.toModel( modelClass, response.data ) );
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a callback for reading child models using the REST API.
|
||||||
|
*
|
||||||
|
* @param {BuildURLWithParentFn} buildURL A callback to build the URL for the request.
|
||||||
|
* @param {Function} modelClass The model we're listing.
|
||||||
|
* @param {HTTPClient} httpClient The HTTP client to use for the request.
|
||||||
|
* @param {ModelTransformer} transformer The transformer to use for the response data.
|
||||||
|
* @return {ReadChildFn} The callback for the repository.
|
||||||
|
*/
|
||||||
|
export function restReadChild< T extends ModelRepositoryParams >(
|
||||||
|
buildURL: HasParent< T, BuildURLWithParentFn< T >, never >,
|
||||||
|
modelClass: ModelConstructor< ModelClass< T > >,
|
||||||
|
httpClient: HTTPClient,
|
||||||
|
transformer: ModelTransformer< ModelClass< T > >,
|
||||||
|
): ReadChildFn< T > {
|
||||||
|
return async ( parent, id ) => {
|
||||||
|
const response = await httpClient.get( buildURL( parent, id ) );
|
||||||
|
return Promise.resolve( transformer.toModel( modelClass, response.data ) );
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a callback for updating models using the REST API.
|
||||||
|
*
|
||||||
|
* @param {BuildURLFn} buildURL A callback to build the URL for the request.
|
||||||
|
* @param {Function} modelClass The model we're listing.
|
||||||
|
* @param {HTTPClient} httpClient The HTTP client to use for the request.
|
||||||
|
* @param {ModelTransformer} transformer The transformer to use for the response data.
|
||||||
|
* @return {UpdateFn} The callback for the repository.
|
||||||
|
*/
|
||||||
|
export function restUpdate< T extends ModelRepositoryParams >(
|
||||||
|
buildURL: HasParent< T, never, BuildURLFn >,
|
||||||
|
modelClass: ModelConstructor< ModelClass< T > >,
|
||||||
|
httpClient: HTTPClient,
|
||||||
|
transformer: ModelTransformer< ModelClass< T > >,
|
||||||
|
): UpdateFn< T > {
|
||||||
|
return async ( id, params ) => {
|
||||||
|
const response = await httpClient.patch(
|
||||||
|
buildURL( id ),
|
||||||
|
transformer.fromModel( params as any ),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Promise.resolve( transformer.toModel( modelClass, response.data ) );
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a callback for updating child models using the REST API.
|
||||||
|
*
|
||||||
|
* @param {BuildURLWithParentFn} buildURL A callback to build the URL for the request.
|
||||||
|
* @param {Function} modelClass The model we're listing.
|
||||||
|
* @param {HTTPClient} httpClient The HTTP client to use for the request.
|
||||||
|
* @param {ModelTransformer} transformer The transformer to use for the response data.
|
||||||
|
* @return {UpdateChildFn} The callback for the repository.
|
||||||
|
*/
|
||||||
|
export function restUpdateChild< T extends ModelRepositoryParams >(
|
||||||
|
buildURL: HasParent< T, BuildURLWithParentFn< T >, never >,
|
||||||
|
modelClass: ModelConstructor< ModelClass< T > >,
|
||||||
|
httpClient: HTTPClient,
|
||||||
|
transformer: ModelTransformer< ModelClass< T > >,
|
||||||
|
): UpdateChildFn< T > {
|
||||||
|
return async ( parent, id, params ) => {
|
||||||
|
const response = await httpClient.patch(
|
||||||
|
buildURL( parent, id ),
|
||||||
|
transformer.fromModel( params as any ),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Promise.resolve( transformer.toModel( modelClass, response.data ) );
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a callback for deleting models using the REST API.
|
||||||
|
*
|
||||||
|
* @param {BuildURLFn} buildURL A callback to build the URL for the request.
|
||||||
|
* @param {HTTPClient} httpClient The HTTP client to use for the request.
|
||||||
|
* @return {DeleteFn} The callback for the repository.
|
||||||
|
*/
|
||||||
|
export function restDelete< T extends ModelRepositoryParams >(
|
||||||
|
buildURL: HasParent< T, never, BuildURLFn >,
|
||||||
|
httpClient: HTTPClient,
|
||||||
|
): DeleteFn {
|
||||||
|
return ( id ) => {
|
||||||
|
return httpClient.delete( buildURL( id ) ).then( () => true );
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a callback for deleting child models using the REST API.
|
||||||
|
*
|
||||||
|
* @param {BuildURLWithParentFn} buildURL A callback to build the URL for the request.
|
||||||
|
* @param {HTTPClient} httpClient The HTTP client to use for the request.
|
||||||
|
* @return {DeleteChildFn} The callback for the repository.
|
||||||
|
*/
|
||||||
|
export function restDeleteChild< T extends ModelRepositoryParams >(
|
||||||
|
buildURL: HasParent< T, BuildURLWithParentFn< T >, never >,
|
||||||
|
httpClient: HTTPClient,
|
||||||
|
): DeleteChildFn< T > {
|
||||||
|
return ( parent, id ) => {
|
||||||
|
return httpClient.delete( buildURL( parent, id ) ).then( () => true );
|
||||||
|
};
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue