Compare commits
69 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
56e03c0544 | |
|
|
bfe7fb609d | |
|
|
c7e30d354e | |
|
|
dda5b19e32 | |
|
|
c8ec766bf2 | |
|
|
7b355505bf | |
|
|
708a1fa4a4 | |
|
|
7d081fb691 | |
|
|
9932a571ff | |
|
|
7041a483f0 | |
|
|
8d2f68f6e7 | |
|
|
c0994caaed | |
|
|
924e4d86bd | |
|
|
c2175bfc37 | |
|
|
60e548cc2d | |
|
|
37e1464122 | |
|
|
147748f6c7 | |
|
|
1d59fa1a59 | |
|
|
d7d1890c54 | |
|
|
15b2c611fc | |
|
|
60a125d936 | |
|
|
8c0d9ae33c | |
|
|
8b0433022a | |
|
|
b811acfcf3 | |
|
|
ccb5fe37b5 | |
|
|
072c76acb5 | |
|
|
ca6f1789c9 | |
|
|
a8df935923 | |
|
|
b2a4dd8022 | |
|
|
73217b1ff9 | |
|
|
f98586942f | |
|
|
fbfd2242d2 | |
|
|
7bd88fa86c | |
|
|
cc9b3f8f53 | |
|
|
d3348c6c8f | |
|
|
e40fc0f53b | |
|
|
e3e24b965e | |
|
|
0e446ec239 | |
|
|
5521497bf2 | |
|
|
a87183cf97 | |
|
|
5eedf6aedb | |
|
|
ad47a6a8b3 | |
|
|
e36f44ebee | |
|
|
cda4fb2a79 | |
|
|
fc69aacd2f | |
|
|
c9162747ee | |
|
|
bea15a0c5d | |
|
|
eee9565334 | |
|
|
eb6759f16c | |
|
|
d778c1420c | |
|
|
54ec0804c3 | |
|
|
0f737972c1 | |
|
|
5ec8f4c223 | |
|
|
50b02db331 | |
|
|
2bbca688af | |
|
|
420ccb2e64 | |
|
|
997fad94d7 | |
|
|
8faa5e8577 | |
|
|
9bcb1fc839 | |
|
|
d554039476 | |
|
|
6a2d11b111 | |
|
|
92239cc451 | |
|
|
091a1c9089 | |
|
|
c18b51ffff | |
|
|
a60db3815d | |
|
|
42964bef17 | |
|
|
2aba12ffcd | |
|
|
42ae80fc42 | |
|
|
1a334c1ead |
|
|
@ -0,0 +1,15 @@
|
|||
# WooCommerce Subscriptions Releaser
|
||||
|
||||
## WooCommerce Helper
|
||||
|
||||
> **Note**
|
||||
> WooCommerce Helper is part of WooCommerce as of version 3.1. Purchase, connect, download products, and activate keys in one place with ease. Read more at Managing WooCommerce.com subscriptions.
|
||||
|
||||
- https://woocommerce.com/document/woocommerce-helper/
|
||||
- http://woodojo.s3.amazonaws.com/downloads/woothemes-updater/woothemes-updater.zip
|
||||
|
||||
## WooCommerce API
|
||||
|
||||
- https://woocommerce.com/wc-api/product-key-api
|
||||
- https://woocommerce.com/wc-api/product-key-api?request=check
|
||||
- https://woocommerce.com/wp-json/helper/1.0
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
<?php
|
||||
|
||||
function line( $text = '' ) {
|
||||
echo $text, PHP_EOL;
|
||||
}
|
||||
|
||||
function run( $command, &$result_code = null ) {
|
||||
line( $command );
|
||||
|
||||
$last_line = system( $command, $result_code );
|
||||
|
||||
line();
|
||||
|
||||
return $last_line;
|
||||
}
|
||||
|
||||
/**
|
||||
* WooCommerce Helper API authentication.
|
||||
*
|
||||
* @link https://github.com/woocommerce/woocommerce/blob/ca91250b2e17e88c902e460135c0531b3f632d90/plugins/woocommerce/includes/admin/helper/class-wc-helper-api.php#L67-L118
|
||||
*/
|
||||
$access_token = getenv( 'WOOCOMMERCE_HELPER_ACCESS_TOKEN' );
|
||||
$access_token_secret = getenv( 'WOOCOMMERCE_HELPER_ACCESS_TOKEN_SECRET' );
|
||||
|
||||
$product_id = 27147;
|
||||
|
||||
if ( empty( $access_token ) ) {
|
||||
echo 'WooCommerce Helper API acces token not defined in `WOOCOMMERCE_HELPER_ACCESS_TOKEN` environment variable.';
|
||||
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
if ( empty( $access_token_secret ) ) {
|
||||
echo 'WooCommerce Helper API acces token secret not defined in `WOOCOMMERCE_HELPER_ACCESS_TOKEN_SECRET` environment variable.';
|
||||
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Request info.
|
||||
*/
|
||||
line( '::group::Check WooCommerce.com' );
|
||||
|
||||
// Subscriptions.
|
||||
$url = 'https://woocommerce.com/wp-json/helper/1.0/subscriptions';
|
||||
|
||||
$data = array(
|
||||
'host' => parse_url( $url, PHP_URL_HOST ),
|
||||
'request_uri' => parse_url( $url, PHP_URL_PATH ),
|
||||
'method' => 'GET',
|
||||
);
|
||||
|
||||
$signature = hash_hmac( 'sha256', json_encode( $data ), $access_token_secret );
|
||||
|
||||
$url .= '?' . http_build_query(
|
||||
[
|
||||
'token' => $access_token,
|
||||
'signature' => $signature,
|
||||
]
|
||||
);
|
||||
|
||||
$command = "curl -X GET '$url' -H 'Authorization: Bearer $access_token' -H 'X-Woo-Signature: $signature';";
|
||||
|
||||
run( $command );
|
||||
|
||||
// Check
|
||||
$payload = [
|
||||
$product_id => [
|
||||
'product_id' => $product_id,
|
||||
'file_id' => '',
|
||||
],
|
||||
];
|
||||
|
||||
ksort( $payload );
|
||||
|
||||
$body = json_encode( array( 'products' => $payload ) );
|
||||
|
||||
$url = 'https://woocommerce.com/wp-json/helper/1.0/update-check';
|
||||
|
||||
$data = array(
|
||||
'host' => parse_url( $url, PHP_URL_HOST ),
|
||||
'request_uri' => parse_url( $url, PHP_URL_PATH ),
|
||||
'method' => 'POST',
|
||||
'body' => $body,
|
||||
);
|
||||
|
||||
$signature = hash_hmac( 'sha256', json_encode( $data ), $access_token_secret );
|
||||
|
||||
$url .= '?' . http_build_query(
|
||||
[
|
||||
'token' => $access_token,
|
||||
'signature' => $signature,
|
||||
]
|
||||
);
|
||||
|
||||
$data = run(
|
||||
sprintf(
|
||||
'curl --data %s --request POST %s --header %s --header %s',
|
||||
escapeshellarg( $body ),
|
||||
escapeshellarg( $url ),
|
||||
escapeshellarg( 'Authorization: Bearer ' . $access_token ),
|
||||
escapeshellarg( 'X-Woo-Signature: ' . $signature )
|
||||
)
|
||||
);
|
||||
|
||||
$result = json_decode( $data );
|
||||
|
||||
if ( ! is_object( $result ) ) {
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
'Unknow response from: %s.',
|
||||
$url
|
||||
)
|
||||
);
|
||||
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
if ( ! property_exists( $result, $product_id ) ) {
|
||||
printf(
|
||||
'No update information for product ID: %s.',
|
||||
$product_id
|
||||
);
|
||||
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
$update_data = $result->{$product_id};
|
||||
|
||||
$version = $update_data->version;
|
||||
$zip_url = $update_data->package;
|
||||
|
||||
line(
|
||||
sprintf(
|
||||
'WooCommerce Subscriptions Version: %s',
|
||||
$version
|
||||
)
|
||||
);
|
||||
|
||||
line(
|
||||
sprintf(
|
||||
'WooCommerce Subscriptions ZIP URL: %s',
|
||||
$zip_url
|
||||
)
|
||||
);
|
||||
|
||||
line( '::endgroup::' );
|
||||
|
||||
/**
|
||||
* Files.
|
||||
*/
|
||||
$work_dir = tempnam( sys_get_temp_dir(), '' );
|
||||
|
||||
unlink( $work_dir );
|
||||
|
||||
mkdir( $work_dir );
|
||||
|
||||
$archives_dir = $work_dir . '/archives';
|
||||
$plugins_dir = $work_dir . '/plugins';
|
||||
|
||||
mkdir( $archives_dir );
|
||||
mkdir( $plugins_dir );
|
||||
|
||||
$plugin_dir = $plugins_dir . '/woocommerce-subscriptions';
|
||||
|
||||
$zip_file = $archives_dir . '/woocommerce-subscriptions.' . $version . '.zip';
|
||||
|
||||
/**
|
||||
* Download ZIP.
|
||||
*/
|
||||
line( '::group::Download WooCommerce Subscriptions' );
|
||||
|
||||
run(
|
||||
sprintf(
|
||||
'curl %s --output %s',
|
||||
escapeshellarg( $zip_url ),
|
||||
$zip_file
|
||||
)
|
||||
);
|
||||
|
||||
line( '::endgroup::' );
|
||||
|
||||
/**
|
||||
* Unzip.
|
||||
*/
|
||||
line( '::group::Unzip WooCommerce Subscriptions' );
|
||||
|
||||
run(
|
||||
sprintf(
|
||||
'unzip %s -d %s',
|
||||
escapeshellarg( $zip_file ),
|
||||
escapeshellarg( $plugins_dir )
|
||||
)
|
||||
);
|
||||
|
||||
line( '::endgroup::' );
|
||||
|
||||
/**
|
||||
* Synchronize.
|
||||
*
|
||||
* @link http://stackoverflow.com/a/14789400
|
||||
* @link http://askubuntu.com/a/476048
|
||||
*/
|
||||
line( '::group::Synchronize WooCommerce Subscriptions' );
|
||||
|
||||
run(
|
||||
sprintf(
|
||||
'rsync --archive --delete-before --exclude=%s --exclude=%s --exclude=%s --verbose %s %s',
|
||||
escapeshellarg( '.git' ),
|
||||
escapeshellarg( '.github' ),
|
||||
escapeshellarg( 'composer.json' ),
|
||||
escapeshellarg( $plugin_dir . '/' ),
|
||||
escapeshellarg( '.' )
|
||||
)
|
||||
);
|
||||
|
||||
line( '::endgroup::' );
|
||||
|
||||
/**
|
||||
* Git user.
|
||||
*
|
||||
* @link https://github.com/roots/wordpress/blob/13ba8c17c80f5c832f29cf4c2960b11489949d5f/bin/update-repo.php#L62-L67
|
||||
*/
|
||||
run(
|
||||
sprintf(
|
||||
'git config user.email %s',
|
||||
escapeshellarg( 'info@woocommerce.com' )
|
||||
)
|
||||
);
|
||||
|
||||
run(
|
||||
sprintf(
|
||||
'git config user.name %s',
|
||||
escapeshellarg( 'WooCommerce' )
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Git commit.
|
||||
*
|
||||
* @link https://git-scm.com/docs/git-commit
|
||||
*/
|
||||
run( 'git add --all' );
|
||||
|
||||
run(
|
||||
sprintf(
|
||||
'git commit --all -m %s',
|
||||
escapeshellarg(
|
||||
sprintf(
|
||||
'Updates to %s',
|
||||
$version
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
run( 'git config --unset user.email' );
|
||||
run( 'git config --unset user.name' );
|
||||
|
||||
run( 'gh auth status' );
|
||||
|
||||
run( 'git push origin main' );
|
||||
|
||||
/**
|
||||
* GitHub release view.
|
||||
*/
|
||||
$tag = 'v' . $version;
|
||||
|
||||
run(
|
||||
sprintf(
|
||||
'gh release view %s',
|
||||
$tag
|
||||
),
|
||||
$result_code
|
||||
);
|
||||
|
||||
$release_not_found = ( 1 === $result_code );
|
||||
|
||||
/**
|
||||
* GitHub release.
|
||||
*
|
||||
* @link https://cli.github.com/manual/gh_release_create
|
||||
*/
|
||||
if ( $release_not_found ) {
|
||||
run(
|
||||
sprintf(
|
||||
'gh release create %s %s --title %s',
|
||||
$tag,
|
||||
$zip_file,
|
||||
$version
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
name: Deploy to Pronamic WordPress directory
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to deploy'
|
||||
type: string
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
environment:
|
||||
name: Pronamic WordPress directory
|
||||
url: https://wp.pronamic.directory/plugins/woocommerce-subscriptions/
|
||||
|
||||
permissions:
|
||||
contents: 'read'
|
||||
id-token: 'write'
|
||||
|
||||
steps:
|
||||
- name: Deploy
|
||||
uses: pronamic/action-wp-pronamic-directory-plugin-deploy@main
|
||||
with:
|
||||
username: ${{ vars.WP_PRONAMIC_DIRECTORY_USERNAME }}
|
||||
password: ${{ secrets.WP_PRONAMIC_DIRECTORY_PASSWORD }}
|
||||
slug: woocommerce-subscriptions
|
||||
tag: ${{ inputs.tag || github.event.release.tag_name }}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 10 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
WOOCOMMERCE_HELPER_ACCESS_TOKEN: ${{ secrets.WOOCOMMERCE_HELPER_ACCESS_TOKEN }}
|
||||
WOOCOMMERCE_HELPER_ACCESS_TOKEN_SECRET: ${{ secrets.WOOCOMMERCE_HELPER_ACCESS_TOKEN_SECRET }}
|
||||
run: php .github/scripts/release.php
|
||||
|
|
@ -7,9 +7,9 @@ ul {
|
|||
margin-top: 0;
|
||||
margin-bottom: 1.6em;
|
||||
}
|
||||
.wcs-badge:before {
|
||||
font-family: WooCommerce !important;
|
||||
content: "\e03d";
|
||||
.wcs-badge::before {
|
||||
font-family: 'WooCommerce' !important;
|
||||
content: '\e03d';
|
||||
color: #fff;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
|
@ -26,8 +26,8 @@ ul {
|
|||
vertical-align: middle;
|
||||
}
|
||||
.wcs-badge {
|
||||
position: relative;;
|
||||
background: #9c5d90;
|
||||
position: relative;
|
||||
background: #7F54B3;
|
||||
text-rendering: optimizeLegibility;
|
||||
padding-top: 150px;
|
||||
height: 52px;
|
||||
|
|
@ -35,13 +35,13 @@ ul {
|
|||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
color: #ddc8d9;
|
||||
color: #fff;
|
||||
margin: 5px 0 0 0;
|
||||
-webkit-box-shadow: 0 1px 3px rgba(0,0,0,.2);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,.2);
|
||||
-webkit-box-shadow: 0 1px 3px rgba( 0, 0, 0, 0.2 );
|
||||
box-shadow: 0 1px 3px rgba( 0, 0, 0, 0.2 );
|
||||
}
|
||||
.about-wrap h2 {
|
||||
border-bottom: 1px solid #DDD;
|
||||
border-bottom: 1px solid #ddd;
|
||||
margin: 0;
|
||||
padding: 1em 0;
|
||||
}
|
||||
|
|
@ -51,12 +51,12 @@ ul {
|
|||
.about-wrap .feature-section {
|
||||
padding-top: 1.6em;
|
||||
padding-bottom: 0.4em;
|
||||
border-bottom: 1px solid #DDD;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
.about-wrap .still-more .feature-section {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0.4em;
|
||||
border-bottom: 1px solid #DDD;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
.about-wrap .under-the-hood {
|
||||
padding-top: 0;
|
||||
|
|
@ -68,14 +68,14 @@ ul {
|
|||
}
|
||||
.about-wrap .wcs-feature {
|
||||
overflow: visible !important;
|
||||
*zoom:1;
|
||||
*zoom: 1;
|
||||
}
|
||||
.about-wrap .wcs-feature:before,
|
||||
.about-wrap .wcs-feature:after {
|
||||
content: " ";
|
||||
.about-wrap .wcs-feature::before,
|
||||
.about-wrap .wcs-feature::after {
|
||||
content: ' ';
|
||||
display: table;
|
||||
}
|
||||
.about-wrap .wcs-feature:after {
|
||||
.about-wrap .wcs-feature::after {
|
||||
clear: both;
|
||||
}
|
||||
.about-wrap .two-col .feature-right {
|
||||
|
|
@ -92,40 +92,17 @@ ul {
|
|||
}
|
||||
.woocommerce-message {
|
||||
position: relative;
|
||||
border-left-color: #cc99c2!important;
|
||||
border-left-color: #7F54B3 !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.woocommerce-message a.button-primary,p.woocommerce-actions a.button-primary,
|
||||
.woocommerce-message a.button-primary:focus, p.woocommerce-actions a.button-primary:focus,
|
||||
.woocommerce-message a.button-primary:active, p.woocommerce-actions a.button-primary:active {
|
||||
background: #b366a4;
|
||||
border-color: #b366a4;
|
||||
-webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.25),0 1px 0 rgba(0,0,0,.15);
|
||||
box-shadow: inset 0 1px 0 rgba(255,255,255,.25),0 1px 0 rgba(0,0,0,.15);
|
||||
text-shadow: 0 -1px 1px #b366a4, 1px 0 1px #b366a4, 0 1px 1px #b366a4, -1px 0 1px #b366a4;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.woocommerce-message a.button-primary:hover,p.woocommerce-actions a.button-primary:hover {
|
||||
background: #bb77ae;
|
||||
border-color: #aa559a;
|
||||
-webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.25),0 1px 0 rgba(0,0,0,.15);
|
||||
box-shadow: inset 0 1px 0 rgba(255,255,255,.25),0 1px 0 rgba(0,0,0,.15);
|
||||
}
|
||||
|
||||
.woocommerce-message a.button-primary:active,p.woocommerce-actions a.button-primary:active {
|
||||
background: #aa559a;
|
||||
border-color: #aa559a;
|
||||
}
|
||||
|
||||
.woocommerce-message a.skip,
|
||||
p.woocommerce-actions a.skip {
|
||||
opacity: .7;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.woocommerce-message .twitter-share-button,p.woocommerce-actions .twitter-share-button {
|
||||
.woocommerce-message .twitter-share-button,
|
||||
p.woocommerce-actions .twitter-share-button {
|
||||
vertical-align: middle;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
|
@ -135,7 +112,7 @@ p.woocommerce-actions {
|
|||
}
|
||||
|
||||
.woocommerce-about-text {
|
||||
margin-bottom: 1em!important;
|
||||
margin-bottom: 1em !important;
|
||||
}
|
||||
|
||||
.about-wrap .feature-section.three-col .col {
|
||||
|
|
@ -157,13 +134,13 @@ p.woocommerce-actions {
|
|||
float: right !important;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1200px) {
|
||||
@media only screen and ( max-width: 1200px ) {
|
||||
.about-wrap .two-col .feature-copy {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 781px) {
|
||||
@media only screen and ( max-width: 781px ) {
|
||||
.about-wrap .two-col .feature-copy,
|
||||
.about-wrap .feature-section {
|
||||
padding-bottom: 1em;
|
||||
|
|
@ -178,12 +155,12 @@ p.woocommerce-actions {
|
|||
width: 100%;
|
||||
margin: 40px 0 0;
|
||||
padding: 0 0 40px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-bottom: 1px solid rgba( 0, 0, 0, 0.1 );
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 500px) {
|
||||
.about-wrap .wcs-badge:before {
|
||||
@media only screen and ( max-width: 500px ) {
|
||||
.about-wrap .wcs-badge::before {
|
||||
width: 100%;
|
||||
}
|
||||
.about-wrap .wcs-badge {
|
||||
|
|
@ -195,4 +172,4 @@ p.woocommerce-actions {
|
|||
width: 100% !important;
|
||||
float: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
.woo_subscriptions_empty_state__container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 48px 0;
|
||||
position: static;
|
||||
height: 344px;
|
||||
left: calc(50% - 1240px / 2);
|
||||
top: 271px;
|
||||
flex: none;
|
||||
order: 1;
|
||||
flex-grow: 0;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
.woo_subscriptions_empty_state__container .woo_subscriptions_empty_state__description {
|
||||
max-width: 535px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
.woo_subscriptions_empty_state__button_container {
|
||||
margin: 8px 0;
|
||||
}
|
||||
.woo_subscriptions_empty_state__container .woo_subscriptions_empty_state__button_container * + * {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Pre WC 3.3 support for order status admin styles.
|
||||
*/
|
||||
.order-status {
|
||||
display: -webkit-inline-box;
|
||||
display: -webkit-inline-flex;
|
||||
display: -ms-inline-flexbox;
|
||||
display: inline-flex;
|
||||
line-height: 2.5em;
|
||||
color: #777;
|
||||
background: #e5e5e5;
|
||||
border-radius: 4px;
|
||||
border-bottom: 1px solid rgba( 0, 0, 0, 0.05 );
|
||||
margin: -0.25em 0;
|
||||
cursor: inherit !important;
|
||||
white-space: nowrap;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.order-status.status-completed {
|
||||
background: #c8d7e1;
|
||||
color: #2e4453;
|
||||
}
|
||||
|
||||
.order-status.status-on-hold {
|
||||
background: #f8dda7;
|
||||
color: #94660c;
|
||||
}
|
||||
|
||||
.order-status.status-failed {
|
||||
background: #eba3a3;
|
||||
color: #761919;
|
||||
}
|
||||
|
||||
.order-status.status-processing {
|
||||
background: #c6e1c6;
|
||||
color: #5b841b;
|
||||
}
|
||||
|
||||
.order-status.status-trash {
|
||||
background: #eba3a3;
|
||||
color: #761919;
|
||||
}
|
||||
|
||||
.order-status > span {
|
||||
margin: 0 1em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
}
|
||||
.shipping.recurring-total ul li {
|
||||
margin: 0;
|
||||
padding: .25em 0 .25em 22px;
|
||||
padding: 0.25em 0 0.25em 22px;
|
||||
text-indent: -22px;
|
||||
list-style: none outside;
|
||||
}
|
||||
|
|
@ -20,8 +20,11 @@
|
|||
font-weight: 700;
|
||||
}
|
||||
.woocommerce-page table.shop_table_responsive tbody .recurring-totals th {
|
||||
display:table-cell;
|
||||
display: table-cell;
|
||||
}
|
||||
.woocommerce-page table.shop_table_responsive tr.recurring-total td:not([data-title]):before {
|
||||
content:"";
|
||||
.woocommerce-page
|
||||
table.shop_table_responsive
|
||||
tr.recurring-total
|
||||
td:not( [data-title] )::before {
|
||||
content: '';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,40 +2,67 @@
|
|||
Subscriptions 2.1 Dashboard Stats
|
||||
------------------------------------------------------------------------------*/
|
||||
#woocommerce_dashboard_status .wc_status_list li.signup-count a:before {
|
||||
content: "\e02c";
|
||||
color: #5da5da;
|
||||
font-family: WooCommerce;
|
||||
content: '\e014';
|
||||
color: #59cf8a;
|
||||
}
|
||||
|
||||
#woocommerce_dashboard_status .wc_status_list li.renewal-count a:before {
|
||||
content: "\e02c";
|
||||
color: #f29ec4;
|
||||
font-family: Dashicons;
|
||||
content: "\f321";
|
||||
color: #f29ec4;
|
||||
}
|
||||
|
||||
#woocommerce_dashboard_status .wc_status_list li.cancel-count {
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#woocommerce_dashboard_status .wc_status_list li.cancel-count a:before {
|
||||
content: "\e02c";
|
||||
color: #aa0000;
|
||||
font-family: WooCommerce;
|
||||
content: "\e033";
|
||||
color: #aa0000;
|
||||
}
|
||||
|
||||
#woocommerce_dashboard_status .wc_status_list li.signup-revenue a:before {
|
||||
font-family: Dashicons;
|
||||
content: '\f185';
|
||||
color: #5da5da;
|
||||
font-family: Dashicons;
|
||||
content: '\f185';
|
||||
color: #59cf8a;
|
||||
}
|
||||
|
||||
#woocommerce_dashboard_status .wc_status_list li.renewal-revenue a:before {
|
||||
font-family: Dashicons;
|
||||
content: '\f185';
|
||||
color: #f29ec4;
|
||||
font-family: Dashicons;
|
||||
content: '\f185';
|
||||
color: #f29ec4;
|
||||
}
|
||||
|
||||
#woocommerce_dashboard_status .wc_status_list li.signup-count {
|
||||
border-right: 1px solid #ececec;
|
||||
border-right: 1px solid #ececec;
|
||||
}
|
||||
|
||||
#woocommerce_dashboard_status .wc_status_list li.renewal-count {
|
||||
border-right: 1px solid #ececec;
|
||||
border-right: 1px solid #ececec;
|
||||
}
|
||||
|
||||
#woocommerce_dashboard_status .wc_status_list li.renewal-count a,
|
||||
#woocommerce_dashboard_status .wc_status_list li.renewal-revenue a,
|
||||
#woocommerce_dashboard_status .wc_status_list li.signup-count a,
|
||||
#woocommerce_dashboard_status .wc_status_list li.signup-revenue a,
|
||||
#woocommerce_dashboard_status .wc_status_list li.cancel-count a {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@media (max-width: 1706px) and (min-width: 1485px),
|
||||
(max-width: 2193px) and (min-width: 1936px),
|
||||
(max-width: 1200px) and (min-width: 1052px),
|
||||
(max-width: 960px) {
|
||||
#woocommerce_dashboard_status .wc_status_list li.renewal-count,
|
||||
#woocommerce_dashboard_status .wc_status_list li.renewal-revenue,
|
||||
#woocommerce_dashboard_status .wc_status_list li.signup-count,
|
||||
#woocommerce_dashboard_status .wc_status_list li.signup-revenue,
|
||||
#woocommerce_dashboard_status .wc_status_list li.cancel-count {
|
||||
border-right: none;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
.wc-shortcode-components-validation-error {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#shortcode-validate-error-invalid-gifting-recipient {
|
||||
font-size: 0.75em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: -12px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
}
|
||||
|
||||
#shortcode-validate-error-invalid-gifting-recipient svg {
|
||||
fill: var(--wc-red, #cc1818);
|
||||
|
||||
}
|
||||
|
||||
#shortcode-validate-error-invalid-gifting-recipient span {
|
||||
color: var(--wc-red, #cc1818);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.woocommerce .woocommerce_subscriptions_gifting_recipient_email .input-text.recipient_email.wcsg-email-error {
|
||||
border-color: var(--wc-red, #cc1818);
|
||||
color: var(--wc-red, #cc1818);
|
||||
}
|
||||
|
||||
.wcsg_add_recipient_fields_container label {
|
||||
display: inline-block;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.woocommerce-cart-form__cart-item .wcsg_add_recipient_fields_container label {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.wcsg_add_recipient_fields_container .wcsg_add_recipient_fields .woocommerce_subscriptions_gifting_recipient_email {
|
||||
padding: 0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.wcsg_add_recipient_fields_container .wcsg_add_recipient_fields.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.wcsg_add_recipient_fields_container .recipient_email:focus {
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
#woocommerce_subscriptions_gifting_field {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#woocommerce_subscriptions_gifting_field div {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
body.wcs-modal-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wcs-modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 0;
|
||||
background-color: transparent;
|
||||
overflow: hidden;
|
||||
transition: background-color 0.25s ease;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.wcs-modal.open {
|
||||
display: flex;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-color: rgba( 0, 0, 0, 0.5 );
|
||||
transition: background-color 0.25s;
|
||||
}
|
||||
|
||||
.wcs-modal.open > .content-wrapper {
|
||||
transform: scale( 1 );
|
||||
min-width: 30%;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.wcs-modal .content-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
margin: 0;
|
||||
padding: 2em;
|
||||
background-color: #fff;
|
||||
border-radius: 0.3em;
|
||||
transform: scale( 0 );
|
||||
transition: transform 0.25s;
|
||||
transition-delay: 0.15s;
|
||||
}
|
||||
|
||||
.wcs-modal .content-wrapper .close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 50;
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.wcs-modal .content-wrapper .modal-header {
|
||||
position: relative;
|
||||
display: block;
|
||||
height: 5%;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.modal-header > h2 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.wcs-modal .content-wrapper .content {
|
||||
position: relative;
|
||||
min-width: 100%;
|
||||
height: 90%;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.wcs-modal .content-wrapper .content p {
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.wcs-modal .content-wrapper .modal-footer {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wcs-modal .content-wrapper .modal-footer .action {
|
||||
position: relative;
|
||||
margin-left: 0.625rem;
|
||||
}
|
||||
|
||||
.wcs-modal footer > a:not( :first-child ) {
|
||||
margin-left: 0.8em;
|
||||
}
|
||||
|
||||
/*
|
||||
* Mobile Display Styles
|
||||
*/
|
||||
@media only screen and ( max-width: 414px ) {
|
||||
.wcs-modal.open > .content-wrapper {
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0.8em;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.wcs-modal.open > .content-wrapper > .content {
|
||||
width: 100%;
|
||||
height: 75%; /* WooCommerce has a nav at the bottom of mobile displays so we need to account for it */
|
||||
}
|
||||
|
||||
.wcs-modal.open .order_details {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and ( max-width: 320px ) {
|
||||
.wcs-modal.open .content-wrapper .modal-header {
|
||||
height: 7%;
|
||||
}
|
||||
|
||||
.wcs-modal.open > .content-wrapper > .content {
|
||||
width: 100%;
|
||||
height: 65%; /* WooCommerce has a nav at the bottom of mobile displays so we need to account for it */
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and ( max-width: 768px ) {
|
||||
.wcs-modal.open > .content-wrapper {
|
||||
min-width: 60%;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,31 @@
|
|||
@media only screen and (max-width:768px) {
|
||||
@media only screen and ( max-width: 768px ) {
|
||||
.subscription_details .button {
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 2px;
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
width: 100%;
|
||||
max-width: 200px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.subscription_details .button {
|
||||
display: inline-block;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
button.subscription-auto-renew-toggle,
|
||||
button.subscription-auto-renew-toggle:hover {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
box-shadow: none;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.subscription-auto-renew-toggle {
|
||||
margin-left: 5px;
|
||||
margin-bottom: 2px;
|
||||
|
|
@ -17,8 +36,8 @@
|
|||
.subscription-auto-renew-toggle__i {
|
||||
height: 20px;
|
||||
width: 32px;
|
||||
border: 2px solid #00BA8A;
|
||||
background-color: #00BA8A;
|
||||
border: 2px solid #00ba8a;
|
||||
background-color: #00ba8a;
|
||||
display: inline-block;
|
||||
text-indent: -9999px;
|
||||
border-radius: 10em;
|
||||
|
|
@ -27,8 +46,8 @@
|
|||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
.subscription-auto-renew-toggle__i:before {
|
||||
content: "";
|
||||
.subscription-auto-renew-toggle__i::before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
|
@ -44,7 +63,8 @@
|
|||
background-color: #999;
|
||||
}
|
||||
|
||||
.subscription-auto-renew-toggle--off .subscription-auto-renew-toggle__i:before {
|
||||
.subscription-auto-renew-toggle--off
|
||||
.subscription-auto-renew-toggle__i::before {
|
||||
right: auto;
|
||||
left: 0;
|
||||
}
|
||||
|
|
@ -56,3 +76,71 @@
|
|||
.subscription-auto-renew-toggle--hidden {
|
||||
display: none;
|
||||
}
|
||||
.subscription-auto-renew-toggle-disabled-note {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
/**
|
||||
* Early renewal Modal
|
||||
**/
|
||||
.wcs_early_renew_modal_totals_table {
|
||||
overflow: scroll;
|
||||
height: 80%;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.wcs_early_renew_modal_note {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
min-width: 100%;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
#early_renewal_modal_submit {
|
||||
width: 100%;
|
||||
font-size: 1.4em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.woocommerce-subscriptions-related-orders-pagination-links.woocommerce-pagination {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.woocommerce-subscriptions-related-orders-pagination-links .pagination-links a {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.woocommerce-subscriptions-related-orders-pagination-links .pagination-links a.disabled:hover {
|
||||
cursor: default;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.woocommerce-subscriptions-related-orders-pagination-links .pagination-links a .symbol {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rtl .woocommerce-subscriptions-related-orders-pagination-links {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.rtl .woocommerce-subscriptions-related-orders-pagination-links .pagination-links {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
@media ( max-width: 30em ) {
|
||||
.woocommerce-subscriptions-related-orders-pagination-links .pagination-links a {
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
|
||||
.woocommerce-subscriptions-related-orders-pagination-links .pagination-links a .label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.woocommerce-subscriptions-related-orders-pagination-links .pagination-links a .symbol {
|
||||
display: inherit;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,23 +16,23 @@ body {
|
|||
padding: 10px;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
color: rgb(18, 18, 18);
|
||||
background: rgb(249, 249, 249);
|
||||
border: 4px solid rgb(249, 249, 249);
|
||||
color: rgb( 18, 18, 18 );
|
||||
background: rgb( 249, 249, 249 );
|
||||
border: 4px solid rgb( 249, 249, 249 );
|
||||
border-radius: 5px;
|
||||
text-shadow: none;
|
||||
box-shadow: rgba(0, 0, 0, 0.2) 0px 0px 4px 1px;
|
||||
box-shadow: rgba( 0, 0, 0, 0.2 ) 0 0 4px 1px;
|
||||
margin-top: -332px;
|
||||
margin-left: 100px;
|
||||
}
|
||||
.tooltip:after {
|
||||
content: "";
|
||||
.tooltip::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-width: 14px;
|
||||
border-style: solid;
|
||||
border-color: #F9F9F9 transparent transparent transparent;
|
||||
border-color: #f9f9f9 transparent transparent transparent;
|
||||
top: 292px;
|
||||
left: 366px;
|
||||
}
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 885 B After Width: | Height: | Size: 885 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
|
|
@ -0,0 +1,26 @@
|
|||
<svg width="141" height="115" viewBox="0 0 141 115" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M104.686 23.5272H42.6647C38.3831 23.5272 34.9121 26.9982 34.9121 31.2798V86.8401C34.9121 91.1218 38.3831 94.5927 42.6647 94.5927H104.686C108.967 94.5927 112.438 91.1218 112.438 86.8401V31.2798C112.438 26.9982 108.967 23.5272 104.686 23.5272Z" fill="#D1C1FF" stroke="#2C045D" stroke-width="1.62161" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M104.686 23.5272H42.6647C38.3827 23.5272 34.9121 26.9978 34.9121 31.2798V42.9087H112.438V31.2798C112.438 26.9978 108.968 23.5272 104.686 23.5272Z" fill="#A77EFF" stroke="#2C045D" stroke-width="1.62161" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M112.434 61.4844V86.8405C112.434 91.1226 108.963 94.5932 104.681 94.5932H79.3252C97.611 94.5932 112.434 79.7702 112.434 61.4844Z" fill="#2C045D" stroke="#2C045D" stroke-width="1.62161" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M73.6715 34.8336C76.3476 34.8336 78.5169 32.6642 78.5169 29.9882C78.5169 27.3122 76.3476 25.1428 73.6715 25.1428C70.9955 25.1428 68.8262 27.3122 68.8262 29.9882C68.8262 32.6642 70.9955 34.8336 73.6715 34.8336Z" fill="#2C045D" stroke="#2C045D" stroke-width="1.62161" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M54.2917 34.8336C56.9677 34.8336 59.137 32.6642 59.137 29.9882C59.137 27.3122 56.9677 25.1428 54.2917 25.1428C51.6156 25.1428 49.4463 27.3122 49.4463 29.9882C49.4463 32.6642 51.6156 34.8336 54.2917 34.8336Z" fill="#2C045D" stroke="#2C045D" stroke-width="1.62161" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M76.9023 29.0193V19.6516C76.9023 18.2244 75.7454 17.0674 74.3181 17.0674H73.026C71.5988 17.0674 70.4418 18.2244 70.4418 19.6516V29.0193C70.4418 30.4465 71.5988 31.6035 73.026 31.6035H74.3181C75.7454 31.6035 76.9023 30.4465 76.9023 29.0193Z" fill="#F2EDFF" stroke="#2C045D" stroke-width="1.62161" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M57.5215 29.0193V19.6516C57.5215 18.2244 56.3645 17.0674 54.9373 17.0674H53.6452C52.218 17.0674 51.061 18.2244 51.061 19.6516V29.0193C51.061 30.4465 52.218 31.6035 53.6452 31.6035H54.9373C56.3645 31.6035 57.5215 30.4465 57.5215 29.0193Z" fill="#F2EDFF" stroke="#2C045D" stroke-width="1.62161" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M61.6236 82.6491C59.458 82.6491 57.6956 80.8866 57.6956 78.7211V63.6707C57.6956 63.5285 57.5457 63.5234 57.5431 63.5234C57.5121 63.5234 57.4863 63.5337 57.4553 63.557L56.4733 64.3503C55.8867 64.8258 55.1864 65.0765 54.4499 65.0765C52.659 65.0765 51.2041 63.6242 51.2041 61.8385C51.2041 60.8126 51.6977 59.8383 52.5246 59.231L58.7164 54.6828C59.458 54.1376 60.3367 53.8507 61.2566 53.8507C63.6238 53.8507 65.549 55.7759 65.549 58.1431V78.7236C65.549 80.8892 63.7866 82.6516 61.621 82.6516L61.6236 82.6491Z" fill="#F2EDFF" stroke="#2C045D" stroke-width="1.62161" stroke-miterlimit="10"/>
|
||||
<path d="M72.0717 82.6493C70.0587 82.6493 68.4229 81.0109 68.4229 79.0004C68.4229 77.7109 69.1154 76.504 70.2266 75.8502L80.4213 69.8807C82.3078 68.7979 83.7911 67.8547 84.8377 67.0743C85.8274 66.3378 86.5278 65.6581 86.9231 65.056C87.2849 64.5056 87.4581 63.9371 87.4581 63.3194C87.4581 62.6656 87.2875 62.1049 86.9387 61.6087C86.5872 61.11 86.0393 60.7094 85.308 60.4174C84.5198 60.1021 83.4887 59.9419 82.2432 59.9419C80.7727 59.9419 79.5659 60.1512 78.6589 60.5621C77.7906 60.9549 77.1394 61.4795 76.7233 62.1204C76.5786 62.3426 76.452 62.5752 76.346 62.8129C75.7155 64.2368 74.3303 65.1568 72.8212 65.1568C71.312 65.1568 70.1388 64.4229 69.4488 63.1902C68.7691 61.9757 68.795 60.5466 69.5186 59.3656C69.7356 59.0142 69.976 58.6705 70.237 58.3423C71.5265 56.7297 73.2527 55.4686 75.3666 54.5978C77.4469 53.7398 79.8347 53.3057 82.4576 53.3057C85.0806 53.3057 87.1945 53.6985 89.0965 54.4711C91.0398 55.2619 92.5825 56.3938 93.686 57.8358C94.8127 59.3114 95.3838 61.0453 95.3838 62.9912C95.3838 64.4952 94.9884 65.9114 94.2106 67.2009C93.4586 68.4465 92.2621 69.6973 90.6573 70.9248C89.1016 72.1135 87.0213 73.4056 84.4707 74.7649L82.8995 75.6306C82.8659 75.6487 82.7988 75.6848 82.8272 75.796C82.8556 75.9071 82.9306 75.9071 82.9693 75.9071H92.7712C94.6292 75.9071 96.141 77.4188 96.141 79.2769C96.141 81.1349 94.6292 82.6467 92.7712 82.6467H72.0717V82.6493Z" fill="#F2EDFF" stroke="#2C045D" stroke-width="1.62161" stroke-miterlimit="10"/>
|
||||
<path d="M33.3676 95.9851L2.50293 46.8893L21.0797 35.2107L51.9444 84.3065C52.6064 85.3596 52.998 86.5619 53.0802 87.8047L55.4775 106.262C55.754 108.384 53.4144 109.855 51.6221 108.686L36.0278 98.5249C34.9456 97.9108 34.0297 97.0381 33.3676 95.9851Z" fill="#B999FF" stroke="#2C045D" stroke-width="1.62161" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21.0813 35.211L27.7547 45.8263C29.1375 48.0258 26.0982 52.4217 20.9689 55.6463C15.8396 58.8709 10.5607 59.7043 9.17795 57.5049L2.5045 46.8896C1.12176 44.6901 4.16103 40.2942 9.29035 37.0695C14.4197 33.8449 19.6985 33.0115 21.0813 35.211Z" fill="#E1D7FF" stroke="#2C045D" stroke-width="1.62161" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21.0786 35.2102L18.4248 36.8785L51.7921 89.9551L51.9433 84.306L21.0786 35.2102Z" fill="#2C045D" stroke="#2C045D" stroke-width="1.62161" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M27.7526 45.8261L31.0893 51.1337C32.472 53.3332 29.4328 57.7291 24.3034 60.9538C19.1741 64.1784 13.8952 65.0118 12.5125 62.8123L9.17578 57.5046C10.5585 59.7041 15.8374 58.8707 20.9667 55.6461C26.096 52.4215 29.1353 48.0256 27.7526 45.8261Z" fill="#F2EDFF" stroke="#2C045D" stroke-width="1.62161" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14.2954 45.031C19.4253 41.8061 22.4634 37.4095 21.0813 35.211C19.6992 33.0125 14.4202 33.8446 9.29035 37.0695C4.16051 40.2945 1.12238 44.6911 2.5045 46.8896C3.88662 49.0881 9.1656 48.256 14.2954 45.031Z" fill="#D1C1FF" stroke="#2C045D" stroke-width="1.62161" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M51.6229 108.686C53.4153 109.855 55.7549 108.384 55.4784 106.262L53.0811 87.8044C52.9967 86.5629 51.7731 84.0323 51.1111 82.9793L50.0662 87.3389C49.5551 88.9902 48.5434 91.0184 46.833 90.7666L45.6279 90.5883C43.9182 90.333 42.2792 91.3634 41.766 93.0161L41.4043 94.1794C40.8933 95.8307 38.6256 95.8611 36.9139 95.6073L32.5322 94.6592C33.1942 95.7122 34.9422 97.9131 36.0265 98.5259L51.6208 108.687L51.6229 108.686Z" fill="#F2EDFF" stroke="#2C045D" stroke-width="1.62161" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M54.3923 97.999L55.4678 106.269C55.7443 108.39 53.4046 109.861 51.6123 108.692L44.6782 104.174C46.3741 104.186 48.3947 103.588 50.3012 102.39C52.2077 101.191 53.6832 99.5615 54.3923 97.999Z" fill="#2C045D" stroke="#2C045D" stroke-width="1.62161" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M136.455 23.6709C134.348 23.6709 132.471 22.3832 131.667 20.4353C131.658 20.4135 131.649 20.3936 131.64 20.3718C130.827 18.4203 131.237 16.1768 132.732 14.6823L135.238 12.1758L128.825 5.76269L126.319 8.26918C124.824 9.76365 122.581 10.1717 120.629 9.3592C120.607 9.35013 120.587 9.34106 120.566 9.33199C118.618 8.52854 117.33 6.65139 117.33 4.54391V1H108.262V4.54572C108.262 6.6532 106.974 8.53035 105.026 9.33381C105.004 9.34288 104.984 9.35194 104.963 9.36101C103.011 10.1735 100.768 9.76365 99.2731 8.271L96.7666 5.76451L90.3535 12.1776L92.86 14.6841C94.3545 16.1786 94.7625 18.4239 93.9518 20.3736C93.9428 20.3954 93.9337 20.4153 93.9246 20.4371C93.1212 22.385 91.244 23.6727 89.1365 23.6727H85.5908V32.741H89.1365C91.244 32.741 93.1212 34.0287 93.9246 35.9766C93.9337 35.9984 93.9428 36.0183 93.9518 36.0401C94.7643 37.9916 94.3545 40.2351 92.86 41.7296L90.3535 44.236L96.7666 50.6492L99.2731 48.1427C100.768 46.6482 103.011 46.2401 104.963 47.0527C104.984 47.0617 105.004 47.0708 105.026 47.0799C106.974 47.8833 108.262 49.7605 108.262 51.868V55.4137H117.33V51.868C117.33 49.7605 118.618 47.8833 120.566 47.0799C120.587 47.0708 120.607 47.0617 120.629 47.0527C122.581 46.2401 124.824 46.65 126.319 48.1427L128.825 50.6492L135.238 44.236L132.732 41.7296C131.237 40.2351 130.829 37.9898 131.64 36.0401C131.649 36.0183 131.658 35.9984 131.667 35.9766C132.471 34.0287 134.348 32.741 136.455 32.741H140.001V23.6727H136.455V23.6709ZM112.796 37.2734C107.788 37.2734 103.728 33.2126 103.728 28.205C103.728 23.1975 107.788 19.1367 112.796 19.1367C117.803 19.1367 121.864 23.1975 121.864 28.205C121.864 33.2126 117.803 37.2734 112.796 37.2734Z" fill="#F2EDFF" stroke="#2C045D" stroke-width="1.62161" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<g style="mix-blend-mode:multiply">
|
||||
<path d="M121.832 28.2051C121.832 33.2127 117.771 37.2735 112.764 37.2735C110.259 37.2735 107.992 36.2578 106.351 34.6183L93.5264 47.4427L96.7329 50.6493L99.2394 48.1428C100.734 46.6483 102.977 46.2403 104.929 47.0528C104.951 47.0619 104.971 47.0709 104.992 47.08C106.94 47.8834 108.228 49.7606 108.228 51.8681V55.4138H117.296V51.8681C117.296 49.7606 118.584 47.8834 120.532 47.08C120.554 47.0709 120.574 47.0619 120.595 47.0528C122.547 46.2403 124.79 46.6502 126.285 48.1428L128.791 50.6493L135.204 44.2362L132.698 41.7297C131.204 40.2352 130.795 37.9899 131.606 36.0402C131.615 36.0184 131.624 35.9985 131.633 35.9767C132.437 34.0288 134.314 32.7411 136.421 32.7411H139.967V23.6728H136.421C134.314 23.6728 132.437 22.3851 131.633 20.4372C131.624 20.4154 131.615 20.3955 131.606 20.3737C130.794 18.4222 131.204 16.1787 132.698 14.6842L135.204 12.1778L131.998 8.97119L119.173 21.7956C120.815 23.437 121.829 25.7041 121.829 28.2088L121.832 28.2051Z" fill="#B999FF"/>
|
||||
</g>
|
||||
<path d="M112.798 14.6027C105.286 14.6027 99.1953 20.693 99.1953 28.2052C99.1953 35.7174 105.286 41.8077 112.798 41.8077C120.31 41.8077 126.4 35.7174 126.4 28.2052C126.4 20.693 120.31 14.6027 112.798 14.6027ZM112.798 37.2735C107.79 37.2735 103.729 33.2127 103.729 28.2052C103.729 23.1976 107.79 19.1368 112.798 19.1368C117.805 19.1368 121.866 23.1976 121.866 28.2052C121.866 33.2127 117.805 37.2735 112.798 37.2735Z" fill="#F2EDFF"/>
|
||||
<path d="M112.796 37.2735C117.804 37.2735 121.864 33.2135 121.864 28.2052C121.864 23.1969 117.804 19.1368 112.796 19.1368C107.788 19.1368 103.728 23.1969 103.728 28.2052C103.728 33.2135 107.788 37.2735 112.796 37.2735Z" stroke="#2C045D" stroke-width="1.62161" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M112.798 14.6027C120.31 14.6027 126.4 20.693 126.4 28.2052C126.4 35.7174 120.31 41.8077 112.798 41.8077C105.286 41.8077 99.1953 35.7174 99.1953 28.2052" stroke="#2C045D" stroke-width="1.62161" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M121.893 18.0848C124.105 20.5043 125.458 23.7253 125.458 27.2638C125.458 34.776 119.368 40.8663 111.856 40.8663C108.317 40.8663 105.096 39.5151 102.677 37.3006C105.163 40.0193 108.74 41.7242 112.715 41.7242C120.228 41.7242 126.318 35.6339 126.318 28.1217C126.318 24.1461 124.613 20.5714 121.894 18.083L121.893 18.0848Z" fill="#2C045D" stroke="#2C045D" stroke-width="1.62161" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 172 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 250 KiB After Width: | Height: | Size: 250 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
|
@ -1,57 +1,79 @@
|
|||
jQuery(document).ready(function($){
|
||||
jQuery( function ( $ ) {
|
||||
let observer = null;
|
||||
|
||||
if(arePointersEnabled()){
|
||||
setTimeout(showSubscriptionPointers, 800); // give TinyMCE a chance to finish loading
|
||||
if ( arePointersEnabled() ) {
|
||||
observer = new MutationObserver( showSubscriptionPointers );
|
||||
|
||||
observer.observe( document.getElementById( 'poststuff' ), {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
characterData: false,
|
||||
subtree:true,
|
||||
} );
|
||||
}
|
||||
|
||||
$('select#product-type').change(function(){
|
||||
if(arePointersEnabled()){
|
||||
$('#product-type').pointer('close');
|
||||
$( 'select#product-type' ).on( 'change', function () {
|
||||
if ( arePointersEnabled() ) {
|
||||
$( '#product-type' ).pointer( 'close' );
|
||||
}
|
||||
});
|
||||
} );
|
||||
|
||||
$('#_subscription_price, #_subscription_period, #_subscription_length').change(function(){
|
||||
if(arePointersEnabled()){
|
||||
$('.options_group.subscription_pricing').pointer('close');
|
||||
$('#product-type').pointer('close');
|
||||
$(
|
||||
'#_subscription_price, #_subscription_period, #_subscription_length'
|
||||
).on( 'change', function () {
|
||||
if ( arePointersEnabled() ) {
|
||||
$( '.options_group.subscription_pricing' ).pointer( 'close' );
|
||||
$( '#product-type' ).pointer( 'close' );
|
||||
}
|
||||
});
|
||||
} );
|
||||
|
||||
function arePointersEnabled(){
|
||||
if($.getParameterByName('subscription_pointers')=='true'){
|
||||
function arePointersEnabled() {
|
||||
if ( $.getParameterByName( 'subscription_pointers' ) == 'true' ) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function showSubscriptionPointers(){
|
||||
$('#product-type').pointer({
|
||||
content: WCSPointers.typePointerContent,
|
||||
position: {
|
||||
edge: 'left',
|
||||
align: 'center'
|
||||
},
|
||||
close: function() {
|
||||
if ($('select#product-type').val()==WCSubscriptions.productType){
|
||||
$('.options_group.subscription_pricing:not(".subscription_sync")').pointer({
|
||||
content: WCSPointers.pricePointerContent,
|
||||
position: 'bottom',
|
||||
close: function() {
|
||||
dismissSubscriptionPointer();
|
||||
}
|
||||
}).pointer('open');
|
||||
}
|
||||
dismissSubscriptionPointer();
|
||||
}
|
||||
}).pointer('open');
|
||||
function showSubscriptionPointers() {
|
||||
$( '#product-type' )
|
||||
.pointer( {
|
||||
content: WCSPointers.typePointerContent,
|
||||
position: {
|
||||
edge: 'left',
|
||||
align: 'center',
|
||||
},
|
||||
close: function () {
|
||||
if (
|
||||
$( 'select#product-type' ).val() ==
|
||||
WCSubscriptions.productType
|
||||
) {
|
||||
$(
|
||||
'.options_group.subscription_pricing:not(".subscription_sync")'
|
||||
)
|
||||
.pointer( {
|
||||
content: WCSPointers.pricePointerContent,
|
||||
position: 'bottom',
|
||||
close: function () {
|
||||
dismissSubscriptionPointer();
|
||||
},
|
||||
} )
|
||||
.pointer( 'open' );
|
||||
}
|
||||
dismissSubscriptionPointer();
|
||||
},
|
||||
} )
|
||||
.pointer( 'open' );
|
||||
}
|
||||
|
||||
function dismissSubscriptionPointer(){
|
||||
function dismissSubscriptionPointer() {
|
||||
$.post( ajaxurl, {
|
||||
pointer: 'wcs_pointer',
|
||||
action: 'dismiss-wp-pointer'
|
||||
});
|
||||
}
|
||||
action: 'dismiss-wp-pointer',
|
||||
} );
|
||||
|
||||
});
|
||||
if ( observer ) {
|
||||
observer.disconnect();
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ var jstz = (function () {
|
|||
'Australia/Sydney': ['Australia/Lord_Howe'],
|
||||
'Asia/Tokyo': ['Asia/Yakutsk'],
|
||||
'Asia/Dhaka': ['Asia/Omsk'],
|
||||
// In the real world Yerevan is not ambigous for Baku... but Windows.
|
||||
// In the real world Yerevan is not ambiguous for Baku... but Windows.
|
||||
'Asia/Baku': ['Asia/Yerevan'],
|
||||
'Australia/Brisbane': ['Asia/Vladivostok'],
|
||||
'Pacific/Noumea': ['Asia/Vladivostok'],
|
||||
|
|
@ -332,7 +332,7 @@ var jstz = (function () {
|
|||
* Builds up the current timezones DST rules for the years defined
|
||||
* in the jstz.olson.dst_rules.years array.
|
||||
*
|
||||
* If there are no DST occurences for those years, immediately returns
|
||||
* If there are no DST occurrences for those years, immediately returns
|
||||
* the preliminary timezone. Otherwise proceeds and tries to solve
|
||||
* ambiguities.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
jQuery( document ).ready( function( $ ) {
|
||||
jQuery( function ( $ ) {
|
||||
'use strict';
|
||||
|
||||
var renewals_field = document.querySelector( '.wcs_number_payments_field' ),
|
||||
|
|
@ -9,13 +9,14 @@ jQuery( document ).ready( function( $ ) {
|
|||
* @type {{init: function, type_options: function, move_field: function}}
|
||||
*/
|
||||
var wcs_meta_boxes_coupon_actions = {
|
||||
|
||||
/**
|
||||
* Initialize variation actions.
|
||||
*/
|
||||
init: function() {
|
||||
init: function () {
|
||||
if ( renewals_field ) {
|
||||
$( document.getElementById( 'discount_type' ) ).on( 'change', this.type_options ).change();
|
||||
$( document.getElementById( 'discount_type' ) )
|
||||
.on( 'change', this.type_options )
|
||||
.trigger( 'change' );
|
||||
this.move_field();
|
||||
}
|
||||
},
|
||||
|
|
@ -23,7 +24,7 @@ jQuery( document ).ready( function( $ ) {
|
|||
/**
|
||||
* Show/hide fields by coupon type options.
|
||||
*/
|
||||
type_options: function() {
|
||||
type_options: function () {
|
||||
var select_val = $( this ).val();
|
||||
|
||||
switch ( select_val ) {
|
||||
|
|
@ -41,12 +42,12 @@ jQuery( document ).ready( function( $ ) {
|
|||
/**
|
||||
* Move the renewal form field in the DOM to a better location.
|
||||
*/
|
||||
move_field: function() {
|
||||
move_field: function () {
|
||||
var parent = document.getElementById( 'general_coupon_data' ),
|
||||
shipping = parent.querySelector( '.free_shipping_field' );
|
||||
|
||||
parent.insertBefore( renewals_field, shipping );
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
wcs_meta_boxes_coupon_actions.init();
|
||||
|
|
|
|||
|
|
@ -1,169 +1,318 @@
|
|||
jQuery(document).ready(function($){
|
||||
jQuery( function ( $ ) {
|
||||
var timezone = jstz.determine();
|
||||
|
||||
// Display the timezone for date changes
|
||||
$( '#wcs-timezone' ).text( timezone.name() );
|
||||
|
||||
// Display times in client's timezone (based on UTC)
|
||||
$( '.woocommerce-subscriptions.date-picker' ).each(function(){
|
||||
var $date_input = $(this),
|
||||
date_type = $date_input.attr( 'id' ),
|
||||
$hour_input = $( '#'+date_type+'_hour' ),
|
||||
$minute_input = $( '#'+date_type+'_minute' ),
|
||||
time = $('#'+date_type+'_timestamp_utc').val(),
|
||||
date = moment.unix(time);
|
||||
$( '.woocommerce-subscriptions.date-picker' ).each( function () {
|
||||
var $date_input = $( this ),
|
||||
date_type = $date_input.attr( 'id' ),
|
||||
$hour_input = $( '#' + date_type + '_hour' ),
|
||||
$minute_input = $( '#' + date_type + '_minute' ),
|
||||
time = $( '#' + date_type + '_timestamp_utc' ).val(),
|
||||
date = moment.unix( time );
|
||||
|
||||
if ( time > 0 ) {
|
||||
date.local();
|
||||
$date_input.val( date.year() + '-' + ( zeroise( date.months() + 1 ) ) + '-' + ( date.format( 'DD' ) ) );
|
||||
$date_input.val(
|
||||
date.year() +
|
||||
'-' +
|
||||
zeroise( date.months() + 1 ) +
|
||||
'-' +
|
||||
date.format( 'DD' )
|
||||
);
|
||||
$hour_input.val( date.format( 'HH' ) );
|
||||
$minute_input.val( date.format( 'mm' ) );
|
||||
}
|
||||
});
|
||||
} );
|
||||
|
||||
// Make sure date pickers are in the future
|
||||
$( '.woocommerce-subscriptions.date-picker:not(#start)' ).datepicker( 'option','minDate',moment().add(1,'hours').toDate());
|
||||
// Make sure start date picker is in the past
|
||||
$( '.woocommerce-subscriptions.date-picker#start' ).datepicker(
|
||||
'option',
|
||||
'maxDate',
|
||||
moment().toDate()
|
||||
);
|
||||
|
||||
// Make sure other date pickers are in the future
|
||||
$( '.woocommerce-subscriptions.date-picker:not(#start)' ).datepicker(
|
||||
'option',
|
||||
'minDate',
|
||||
moment().add( 1, 'hours' ).toDate()
|
||||
);
|
||||
|
||||
// Validate date when hour/minute inputs change
|
||||
$( '[name$="_hour"], [name$="_minute"]' ).on( 'change', function() {
|
||||
$( '#' + $(this).attr( 'name' ).replace( '_hour', '' ).replace( '_minute', '' ) ).change();
|
||||
});
|
||||
$( '[name$="_hour"], [name$="_minute"]' ).on( 'change', function () {
|
||||
$(
|
||||
'#' +
|
||||
$( this )
|
||||
.attr( 'name' )
|
||||
.replace( '_hour', '' )
|
||||
.replace( '_minute', '' )
|
||||
).trigger( 'change' );
|
||||
} );
|
||||
|
||||
// Validate entire date
|
||||
$( '.woocommerce-subscriptions.date-picker' ).on( 'change',function(){
|
||||
|
||||
$( '.woocommerce-subscriptions.date-picker' ).on( 'change', function () {
|
||||
// The date was deleted, clear hour/minute inputs values and set the UTC timestamp to 0
|
||||
if( '' == $(this).val() ) {
|
||||
$( '#' + $(this).attr( 'id' ) + '_hour' ).val('');
|
||||
$( '#' + $(this).attr( 'id' ) + '_minute' ).val('');
|
||||
$( '#' + $(this).attr( 'id' ) + '_timestamp_utc' ).val(0);
|
||||
if ( '' == $( this ).val() ) {
|
||||
$( '#' + $( this ).attr( 'id' ) + '_hour' ).val( '' );
|
||||
$( '#' + $( this ).attr( 'id' ) + '_minute' ).val( '' );
|
||||
$( '#' + $( this ).attr( 'id' ) + '_timestamp_utc' ).val( 0 );
|
||||
return;
|
||||
}
|
||||
|
||||
var time_now = moment(),
|
||||
one_hour_from_now = moment().add(1,'hours' ),
|
||||
$date_input = $(this),
|
||||
date_type = $date_input.attr( 'id' ),
|
||||
date_pieces = $date_input.val().split( '-' ),
|
||||
$hour_input = $( '#'+date_type+'_hour' ),
|
||||
$minute_input = $( '#'+date_type+'_minute' ),
|
||||
chosen_hour = (0 == $hour_input.val().length) ? one_hour_from_now.format( 'HH' ) : $hour_input.val(),
|
||||
chosen_minute = (0 == $minute_input.val().length) ? one_hour_from_now.format( 'mm' ) : $minute_input.val(),
|
||||
chosen_date = moment({
|
||||
years: date_pieces[0],
|
||||
months: (date_pieces[1] - 1),
|
||||
date: (date_pieces[2]),
|
||||
hours: chosen_hour,
|
||||
var time_now = moment(),
|
||||
one_hour_from_now = moment().add( 1, 'hours' ),
|
||||
minimum_date = wcs_admin_meta_boxes.is_duplicate_site
|
||||
? moment().add( 2, 'minutes' )
|
||||
: one_hour_from_now,
|
||||
$date_input = $( this ),
|
||||
date_type = $date_input.attr( 'id' ),
|
||||
date_pieces = $date_input.val().split( '-' ),
|
||||
$hour_input = $( '#' + date_type + '_hour' ),
|
||||
$minute_input = $( '#' + date_type + '_minute' ),
|
||||
chosen_hour =
|
||||
0 == $hour_input.val().length
|
||||
? one_hour_from_now.format( 'HH' )
|
||||
: $hour_input.val(),
|
||||
chosen_minute =
|
||||
0 == $minute_input.val().length
|
||||
? one_hour_from_now.format( 'mm' )
|
||||
: $minute_input.val(),
|
||||
chosen_date = moment( {
|
||||
years: date_pieces[ 0 ],
|
||||
months: date_pieces[ 1 ] - 1,
|
||||
date: date_pieces[ 2 ],
|
||||
hours: chosen_hour,
|
||||
minutes: chosen_minute,
|
||||
seconds: one_hour_from_now.format( 'ss' )
|
||||
});
|
||||
seconds: one_hour_from_now.format( 'ss' ),
|
||||
} );
|
||||
|
||||
// Make sure start date is before now.
|
||||
if (
|
||||
'start' == date_type &&
|
||||
false === chosen_date.isBefore( time_now )
|
||||
) {
|
||||
alert( wcs_admin_meta_boxes.i18n_start_date_notice );
|
||||
$date_input.val(
|
||||
time_now.year() +
|
||||
'-' +
|
||||
zeroise( time_now.months() + 1 ) +
|
||||
'-' +
|
||||
time_now.format( 'DD' )
|
||||
);
|
||||
$hour_input.val( time_now.format( 'HH' ) );
|
||||
$minute_input.val( time_now.format( 'mm' ) );
|
||||
}
|
||||
|
||||
// Make sure trial end and next payment are after start date
|
||||
if ( ( 'trial_end' == date_type || 'next_payment' == date_type ) && '' != $( '#start_timestamp_utc' ).val() ) {
|
||||
if (
|
||||
( 'trial_end' == date_type || 'next_payment' == date_type ) &&
|
||||
'' != $( '#start_timestamp_utc' ).val()
|
||||
) {
|
||||
var change_date = false,
|
||||
start = moment.unix( $('#start_timestamp_utc').val() );
|
||||
start = moment.unix( $( '#start_timestamp_utc' ).val() );
|
||||
|
||||
// Make sure trial end is after start date
|
||||
if ( 'trial_end' == date_type && chosen_date.isBefore( start, 'minute' ) ) {
|
||||
|
||||
if (
|
||||
'trial_end' == date_type &&
|
||||
chosen_date.isBefore( start, 'minute' )
|
||||
) {
|
||||
if ( 'trial_end' == date_type ) {
|
||||
alert( wcs_admin_meta_boxes.i18n_trial_end_start_notice );
|
||||
} else if ( 'next_payment' == date_type ) {
|
||||
alert( wcs_admin_meta_boxes.i18n_next_payment_start_notice );
|
||||
alert(
|
||||
wcs_admin_meta_boxes.i18n_next_payment_start_notice
|
||||
);
|
||||
}
|
||||
|
||||
// Change the date
|
||||
$date_input.val( start.year() + '-' + ( zeroise( start.months() + 1 ) ) + '-' + ( start.format( 'DD' ) ) );
|
||||
$date_input.val(
|
||||
start.year() +
|
||||
'-' +
|
||||
zeroise( start.months() + 1 ) +
|
||||
'-' +
|
||||
start.format( 'DD' )
|
||||
);
|
||||
$hour_input.val( start.format( 'HH' ) );
|
||||
$minute_input.val( start.format( 'mm' ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure next payment is after trial end
|
||||
if ( 'next_payment' == date_type && '' != $( '#trial_end_timestamp_utc' ).val() ) {
|
||||
var trial_end = moment.unix( $('#trial_end_timestamp_utc').val() );
|
||||
if (
|
||||
'next_payment' == date_type &&
|
||||
'' != $( '#trial_end_timestamp_utc' ).val()
|
||||
) {
|
||||
var trial_end = moment.unix(
|
||||
$( '#trial_end_timestamp_utc' ).val()
|
||||
);
|
||||
|
||||
if ( chosen_date.isBefore( trial_end, 'minute' ) ) {
|
||||
alert( wcs_admin_meta_boxes.i18n_next_payment_trial_notice );
|
||||
$date_input.val( trial_end.year() + '-' + ( zeroise( trial_end.months() + 1 ) ) + '-' + ( trial_end.format( 'DD' ) ) );
|
||||
$date_input.val(
|
||||
trial_end.year() +
|
||||
'-' +
|
||||
zeroise( trial_end.months() + 1 ) +
|
||||
'-' +
|
||||
trial_end.format( 'DD' )
|
||||
);
|
||||
$hour_input.val( trial_end.format( 'HH' ) );
|
||||
$minute_input.val( trial_end.format( 'mm' ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure trial end is before next payment and expiration is after next payment date
|
||||
else if ( ( 'trial_end' == date_type || 'end' == date_type ) && '' != $( '#next_payment' ).val() ) {
|
||||
var change_date = false,
|
||||
next_payment = moment.unix( $('#next_payment_timestamp_utc').val() );
|
||||
else if (
|
||||
( 'trial_end' == date_type || 'end' == date_type ) &&
|
||||
'' != $( '#next_payment' ).val()
|
||||
) {
|
||||
var change_date = false,
|
||||
next_payment = moment.unix(
|
||||
$( '#next_payment_timestamp_utc' ).val()
|
||||
);
|
||||
|
||||
// Make sure trial end is before or equal to next payment
|
||||
if ( 'trial_end' == date_type && next_payment.isBefore( chosen_date, 'minute' ) ) {
|
||||
if (
|
||||
'trial_end' == date_type &&
|
||||
next_payment.isBefore( chosen_date, 'minute' )
|
||||
) {
|
||||
alert( wcs_admin_meta_boxes.i18n_trial_end_next_notice );
|
||||
change_date = true;
|
||||
}
|
||||
// Make sure end date is after next payment date
|
||||
else if ( 'end' == date_type && chosen_date.isBefore( next_payment, 'minute' ) ) {
|
||||
else if (
|
||||
'end' == date_type &&
|
||||
chosen_date.isBefore( next_payment, 'minute' )
|
||||
) {
|
||||
alert( wcs_admin_meta_boxes.i18n_end_date_notice );
|
||||
change_date = true;
|
||||
}
|
||||
|
||||
if ( true === change_date ) {
|
||||
$date_input.val( next_payment.year() + '-' + ( zeroise( next_payment.months() + 1 ) ) + '-' + ( next_payment.format( 'DD' ) ) );
|
||||
$date_input.val(
|
||||
next_payment.year() +
|
||||
'-' +
|
||||
zeroise( next_payment.months() + 1 ) +
|
||||
'-' +
|
||||
next_payment.format( 'DD' )
|
||||
);
|
||||
$hour_input.val( next_payment.format( 'HH' ) );
|
||||
$minute_input.val( next_payment.format( 'mm' ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the date is more than an hour in the future
|
||||
if ( 'trial_end' != date_type && 'start' != date_type && chosen_date.unix() < one_hour_from_now.unix() ) {
|
||||
|
||||
if (
|
||||
'trial_end' != date_type &&
|
||||
'start' != date_type &&
|
||||
chosen_date.unix() < minimum_date.unix()
|
||||
) {
|
||||
alert( wcs_admin_meta_boxes.i18n_past_date_notice );
|
||||
|
||||
// Set date to current day
|
||||
$date_input.val( one_hour_from_now.year() + '-' + ( zeroise( one_hour_from_now.months() + 1 ) ) + '-' + ( one_hour_from_now.format( 'DD' ) ) );
|
||||
$date_input.val(
|
||||
one_hour_from_now.year() +
|
||||
'-' +
|
||||
zeroise( one_hour_from_now.months() + 1 ) +
|
||||
'-' +
|
||||
one_hour_from_now.format( 'DD' )
|
||||
);
|
||||
|
||||
// Set time if current time is in the past
|
||||
if ( chosen_date.hours() < one_hour_from_now.hours() || ( chosen_date.hours() == one_hour_from_now.hours() && chosen_date.minutes() < one_hour_from_now.minutes() ) ) {
|
||||
if (
|
||||
chosen_date.hours() < one_hour_from_now.hours() ||
|
||||
( chosen_date.hours() == one_hour_from_now.hours() &&
|
||||
chosen_date.minutes() < one_hour_from_now.minutes() )
|
||||
) {
|
||||
$hour_input.val( one_hour_from_now.format( 'HH' ) );
|
||||
$minute_input.val( one_hour_from_now.format( 'mm' ) );
|
||||
}
|
||||
}
|
||||
|
||||
if( 0 == $hour_input.val().length ){
|
||||
$hour_input.val(one_hour_from_now.format( 'HH' ));
|
||||
if ( 0 == $hour_input.val().length ) {
|
||||
$hour_input.val( one_hour_from_now.format( 'HH' ) );
|
||||
}
|
||||
|
||||
if( 0 == $minute_input.val().length ){
|
||||
$minute_input.val(one_hour_from_now.format( 'mm' ));
|
||||
if ( 0 == $minute_input.val().length ) {
|
||||
$minute_input.val( one_hour_from_now.format( 'mm' ) );
|
||||
}
|
||||
|
||||
// Update the UTC timestamp sent to the server
|
||||
date_pieces = $date_input.val().split( '-' );
|
||||
|
||||
$('#'+date_type+'_timestamp_utc').val(moment({
|
||||
years: date_pieces[0],
|
||||
months: (date_pieces[1] - 1),
|
||||
date: (date_pieces[2]),
|
||||
hours: $hour_input.val(),
|
||||
var newTimeStampValue = moment( {
|
||||
years: date_pieces[ 0 ],
|
||||
months: date_pieces[ 1 ] - 1,
|
||||
date: date_pieces[ 2 ],
|
||||
hours: $hour_input.val(),
|
||||
minutes: $minute_input.val(),
|
||||
seconds: one_hour_from_now.format( 'ss' )
|
||||
}).utc().unix());
|
||||
seconds: one_hour_from_now.format( 'ss' ),
|
||||
} )
|
||||
.utc()
|
||||
.unix();
|
||||
|
||||
$( 'body' ).trigger( 'wcs-updated-date',date_type);
|
||||
});
|
||||
|
||||
// Moment will return NaN if the date is invalid, that's why we need to check for NaN only.
|
||||
if ( isNaN( newTimeStampValue ) ) {
|
||||
wcsShowDateFieldError( date_type );
|
||||
} else {
|
||||
wcsHideDateFieldError( date_type );
|
||||
}
|
||||
|
||||
// Intentionally do not prevent timestamp updates if the date is invalid.
|
||||
// This way it's easier to catch invalid fields during submit event if attempted without editing invalid values.
|
||||
$( '#' + date_type + '_timestamp_utc' ).val(
|
||||
newTimeStampValue
|
||||
);
|
||||
|
||||
$( 'body' ).trigger( 'wcs-updated-date', date_type );
|
||||
} );
|
||||
|
||||
function wcsShowDateFieldError( date_type ) {
|
||||
var $fieldContainer = $( '#subscription-' + date_type + '-date' );
|
||||
$fieldContainer.addClass( 'has-error' );
|
||||
var $messageContainer = $fieldContainer.find( '.message' );
|
||||
var $messageContent = $messageContainer.find( '.message-content' );
|
||||
|
||||
// Clear and set content before showing to ensure screen readers announce the new message
|
||||
$messageContent.text('');
|
||||
$messageContainer.show();
|
||||
|
||||
// Use setTimeout to ensure DOM update occurs before adding new text
|
||||
setTimeout(function() {
|
||||
// If the focus switched to the next field voice over skips announcing the error message.
|
||||
// This is a workaround to ensure the error message is announced.
|
||||
$fieldContainer
|
||||
.find( `input#${date_type}` )
|
||||
.trigger( 'focus' )
|
||||
.trigger( 'blur' );
|
||||
$messageContent.text( wcs_admin_meta_boxes.i18n_invalid_date_notice );
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function wcsHideDateFieldError( date_type ) {
|
||||
var $fieldContainer = $( '#subscription-' + date_type + '-date' );
|
||||
$fieldContainer.removeClass( 'has-error' );
|
||||
var $messageContainer = $fieldContainer.find( '.message' );
|
||||
var $messageContent = $messageContainer.find( '.message-content' );
|
||||
|
||||
$messageContainer.hide();
|
||||
$messageContent.text('');
|
||||
}
|
||||
|
||||
function zeroise( val ) {
|
||||
return (val > 9 ) ? val : '0' + val;
|
||||
return val > 9 ? val : '0' + val;
|
||||
}
|
||||
|
||||
if( $( '#parent-order-id' ).is( 'select' ) ) {
|
||||
|
||||
if ( $( '#parent-order-id' ).is( 'select' ) ) {
|
||||
wcs_update_parent_order_options();
|
||||
|
||||
$( '#customer_user' ).on( 'change', wcs_update_parent_order_options );
|
||||
}
|
||||
|
||||
function wcs_update_parent_order_options() {
|
||||
|
||||
// Get user ID to load orders for
|
||||
var user_id = $( '#customer_user' ).val();
|
||||
|
||||
|
|
@ -172,24 +321,26 @@ jQuery(document).ready(function($){
|
|||
}
|
||||
|
||||
var data = {
|
||||
user_id: user_id,
|
||||
action: 'wcs_get_customer_orders',
|
||||
security: wcs_admin_meta_boxes.get_customer_orders_nonce
|
||||
user_id: user_id,
|
||||
action: 'wcs_get_customer_orders',
|
||||
security: wcs_admin_meta_boxes.get_customer_orders_nonce,
|
||||
};
|
||||
|
||||
$( '#parent-order-id' ).siblings( '.select2-container' ).block({
|
||||
message: null,
|
||||
overlayCSS: {
|
||||
background: '#fff',
|
||||
opacity: 0.6
|
||||
}
|
||||
});
|
||||
$( '#parent-order-id' )
|
||||
.siblings( '.select2-container' )
|
||||
.block( {
|
||||
message: null,
|
||||
overlayCSS: {
|
||||
background: '#fff',
|
||||
opacity: 0.6,
|
||||
},
|
||||
} );
|
||||
|
||||
$.ajax({
|
||||
$.ajax( {
|
||||
url: WCSubscriptions.ajaxUrl,
|
||||
data: data,
|
||||
type: 'POST',
|
||||
success: function( response ) {
|
||||
success: function ( response ) {
|
||||
if ( response ) {
|
||||
var $orderlist = $( '#parent-order-id' );
|
||||
|
||||
|
|
@ -197,28 +348,84 @@ jQuery(document).ready(function($){
|
|||
|
||||
$orderlist.empty(); // remove old options
|
||||
|
||||
$orderlist.append( $( '<option></option>' ).attr( 'value', '' ).text( 'Select an order' ) );
|
||||
$orderlist.append(
|
||||
$( '<option></option>' )
|
||||
.attr( 'value', '' )
|
||||
.text( 'Select an order' )
|
||||
);
|
||||
|
||||
$.each( response, function( order_id, order_number ) {
|
||||
$orderlist.append( $( '<option></option>' ).attr( 'value', order_id ).text( order_number ) );
|
||||
});
|
||||
$.each( response, function ( order_id, order_number ) {
|
||||
$orderlist.append(
|
||||
$( '<option></option>' )
|
||||
.attr( 'value', order_id )
|
||||
.text( order_number )
|
||||
);
|
||||
} );
|
||||
|
||||
$( '#parent-order-id' ).siblings( '.select2-container' ).unblock();
|
||||
$( '#parent-order-id' )
|
||||
.siblings( '.select2-container' )
|
||||
.unblock();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
} );
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
$('body.post-type-shop_subscription #post').submit(function(){
|
||||
if('wcs_process_renewal' == $( "body.post-type-shop_subscription select[name='wc_order_action']" ).val()) {
|
||||
return confirm(wcs_admin_meta_boxes.process_renewal_action_warning);
|
||||
$( 'body.post-type-shop_subscription #post, body.woocommerce_page_wc-orders--shop_subscription #order' ).on( 'submit', function ( evt ) {
|
||||
var invalid_dates = [];
|
||||
$( '.woocommerce-subscriptions.date-picker' ).each( function () {
|
||||
var $date_input = $( this );
|
||||
var date_type = $date_input.attr( 'id' );
|
||||
var timestamp = $( '#' + date_type + '_timestamp_utc' ).val();
|
||||
// At this point, timestamp is a string, not a number.
|
||||
// We check for NaN only because everything else should be a valid timestamp set during the change event.
|
||||
if ( timestamp === 'NaN' ) {
|
||||
invalid_dates.push( date_type );
|
||||
}
|
||||
} );
|
||||
|
||||
if ( invalid_dates.length > 0 ) {
|
||||
// Focus the first invalid date to make it noticeable.
|
||||
$( '#subscription-' + invalid_dates[0] + '-date' ).find( '.wcs-date-input input' ).first().focus();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} )
|
||||
|
||||
$('body.post-type-shop_subscription #post').submit(function(){
|
||||
if ( typeof wcs_admin_meta_boxes.change_payment_method_warning != 'undefined' && wcs_admin_meta_boxes.payment_method != $('#_payment_method').val() ) {
|
||||
return confirm(wcs_admin_meta_boxes.change_payment_method_warning);
|
||||
$( 'body.post-type-shop_subscription #post, body.woocommerce_page_wc-orders--shop_subscription #order' ).on( 'submit', function () {
|
||||
if (
|
||||
'wcs_process_renewal' ==
|
||||
$(
|
||||
'body.post-type-shop_subscription select[name="wc_order_action"], body.woocommerce_page_wc-orders--shop_subscription select[name="wc_order_action"]'
|
||||
).val()
|
||||
) {
|
||||
return confirm(
|
||||
wcs_admin_meta_boxes.process_renewal_action_warning
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
} );
|
||||
|
||||
$( 'body.post-type-shop_subscription #post, body.woocommerce_page_wc-orders--shop_subscription #order' ).on( 'submit', function () {
|
||||
if (
|
||||
typeof wcs_admin_meta_boxes.change_payment_method_warning !=
|
||||
'undefined' &&
|
||||
wcs_admin_meta_boxes.payment_method != $( '#_payment_method' ).val()
|
||||
) {
|
||||
return confirm(
|
||||
wcs_admin_meta_boxes.change_payment_method_warning
|
||||
);
|
||||
}
|
||||
} );
|
||||
|
||||
/**
|
||||
* When the auto-renewal is toggled on or off, show or hide the chosen payment methods meta fields.
|
||||
*/
|
||||
$( '#wc-subscription-auto-renew' ).on( 'change', function() {
|
||||
var $payment_method_meta_elements = $( '#wcs_' + $( '#_payment_method' ).val() + '_fields' );
|
||||
|
||||
if ( $( this ).is( ':checked' ) ) {
|
||||
$payment_method_meta_elements.fadeIn();
|
||||
} else {
|
||||
$payment_method_meta_elements.fadeOut();
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
jQuery( function( $ ) {
|
||||
|
||||
/**
|
||||
* If the WC core validation passes (errors removed), check our own validation.
|
||||
*/
|
||||
$( document.body ).on( 'wc_remove_error_tip', function( e, element, removed_error_type ) {
|
||||
var product_type = $( '#product-type' ).val();
|
||||
|
||||
if ( 'subscription' !== product_type && 'variable-subscription' !== product_type ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We're only interested in the product's recurring price and sale price input.
|
||||
if ( 'subscription' === product_type && ! $( element ).is( '#_subscription_price' ) && ! $( element ).is( '#_sale_price' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 'variable-subscription' === product_type && ! $( element ).hasClass( 'wc_input_subscription_price' ) && ! $( element ).is( '.wc_input_price[name^=variable_sale_price]' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reformat the product price - remove the decimal place separator and remove excess decimal places.
|
||||
var price = accounting.unformat( $( element ).val(), wcs_gateway_restrictions.decimal_point_separator );
|
||||
price = accounting.formatNumber( price, wcs_gateway_restrictions.number_of_decimal_places, '' );
|
||||
|
||||
// Error types to validate.
|
||||
var zero_error = 'i18n_zero_subscription_error';
|
||||
var displaying_zero_error = element.parent().find( '.wc_error_tip, .' + zero_error ).length !== 0;
|
||||
|
||||
// Check if the product price is 0 or less.
|
||||
if ( 0 >= price ) {
|
||||
$( document.body ).triggerHandler( 'wc_subscriptions_add_error_tip', [ element, zero_error ] );
|
||||
displaying_zero_error = true;
|
||||
} else if ( displaying_zero_error && removed_error_type !== zero_error ) {
|
||||
$( document.body ).triggerHandler( 'wc_remove_error_tip', [ element, zero_error ] );
|
||||
displaying_zero_error = false;
|
||||
}
|
||||
|
||||
// Check if the product price is below the amount that can be processed by the payment gateway.
|
||||
if ( ! displaying_zero_error && 'undefined' !== typeof wcs_gateway_restrictions.minimum_subscription_amount ) {
|
||||
var below_minimum_error = 'i18n_below_minimum_subscription_error';
|
||||
var displaying_minimum_error = element.parent().find( '.wc_error_tip, .' + below_minimum_error ).length !== 0;
|
||||
|
||||
if ( parseFloat( wcs_gateway_restrictions.minimum_subscription_amount ) > parseFloat( price ) ) {
|
||||
$( document.body ).triggerHandler( 'wc_subscriptions_add_error_tip', [ element, below_minimum_error ] );
|
||||
displaying_minimum_error = true;
|
||||
} else if ( displaying_minimum_error && removed_error_type !== below_minimum_error ) {
|
||||
$( document.body ).triggerHandler( 'wc_remove_error_tip', [ element, below_minimum_error ] );
|
||||
displaying_minimum_error = false;
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
/**
|
||||
* Validate the recurring price or sale price field on element change event or when a validate event is triggered.
|
||||
*/
|
||||
$( document.body ).on( 'change wc_subscriptions_validate_zero_recurring_price', '#_subscription_price, #_sale_price, .wc_input_subscription_price, .wc_input_price[name^=variable_sale_price]', function() {
|
||||
var product_type = $( '#product-type' ).val();
|
||||
|
||||
if ( 'subscription' !== product_type && 'variable-subscription' !== product_type ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reformat the product price - remove the decimal place separator and remove excess decimal places.
|
||||
var price = accounting.unformat( $( this ).val(), wcs_gateway_restrictions.decimal_point_separator );
|
||||
price = accounting.formatNumber( price, wcs_gateway_restrictions.number_of_decimal_places );
|
||||
|
||||
if ( 0 >= price ) {
|
||||
$( this ).val( '' );
|
||||
}
|
||||
} );
|
||||
|
||||
/**
|
||||
* When the product type is changed to a subscription product type, validate generic product sale price elements.
|
||||
*/
|
||||
$( document.body ).on( 'change', '#product-type', function() {
|
||||
var product_type = $( '#product-type' ).val();
|
||||
|
||||
if ( 'subscription' !== product_type && 'variable-subscription' !== product_type ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$( '#_sale_price, .wc_input_price[name^=variable_sale_price]' ).each( function() {
|
||||
$( this ).trigger( 'wc_subscriptions_validate_zero_recurring_price' );
|
||||
});
|
||||
} );
|
||||
|
||||
|
||||
/**
|
||||
* Displays a WC error tip against an element for a given error type.
|
||||
*
|
||||
* Based on the WC core `wc_add_error_tip` handler callback in woocommerce_admin.js.
|
||||
*/
|
||||
$( document.body ).on( 'wc_subscriptions_add_error_tip', function( e, element, error_type ) {
|
||||
var offset = element.position();
|
||||
|
||||
// Remove any error that is already being shown before adding a new one.
|
||||
if ( element.parent().find( '.wc_error_tip' ).length !== 0 ) {
|
||||
element.parent().find( '.wc_error_tip' ).remove();
|
||||
}
|
||||
|
||||
element.after( '<div class="wc_error_tip ' + error_type + '">' + wcs_gateway_restrictions[ error_type ] + '</div>' );
|
||||
element.parent().find( '.wc_error_tip' )
|
||||
.css( 'left', offset.left + element.width() - ( element.width() / 2 ) - ( $( '.wc_error_tip' ).width() / 2 ) )
|
||||
.css( 'top', offset.top + element.height() )
|
||||
.fadeIn( '100' );
|
||||
|
||||
})
|
||||
} );
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
jQuery(function($) {
|
||||
jQuery( function( $ ) {
|
||||
|
||||
$.extend({
|
||||
wcs_format_money: function(value,decimal_precision) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
jQuery( function( $ ) {
|
||||
|
||||
if ( ! window.hasOwnProperty( 'wcTracks' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
function record_event( eventName, properties = {} ) {
|
||||
window.wcTracks.recordEvent( eventName, properties );
|
||||
}
|
||||
|
||||
// Add event listeners to Subscription Events by Date report clickable filters.
|
||||
if ( $( "#report_subscription_events_by_date_new" ).length ) {
|
||||
|
||||
var filters = {
|
||||
'new': 'subscriptions_report_events_by_date_new_filter_click',
|
||||
'signups': 'subscriptions_report_events_by_date_signups_filter_click',
|
||||
'resubscribes': 'subscriptions_report_events_by_date_resubscribes_filter_click',
|
||||
'renewals': 'subscriptions_report_events_by_date_renewals_filter_click',
|
||||
'switches': 'subscriptions_report_events_by_date_switches_filter_click',
|
||||
'cancellations': 'subscriptions_report_events_by_date_cancellations_filter_click',
|
||||
'ended': 'subscriptions_report_events_by_date_ended_filter_click',
|
||||
'current': 'subscriptions_report_events_by_date_current_filter_click',
|
||||
}
|
||||
|
||||
$.each( filters, function( key, value ) {
|
||||
$( "#report_subscription_events_by_date_" + key ).on( 'click', function() {
|
||||
// if range is not a URL param, we are looking at the default 7 day range.
|
||||
var properties = {
|
||||
range: location.search.includes( 'range' ) ? location.search.match( /range=([^&#]*)/ )[1] : '7day'
|
||||
};
|
||||
|
||||
if ( 'custom' === properties.range ) {
|
||||
// Start or end dates may be ommitted.
|
||||
properties.start_date = location.search.includes( 'start_date=' )
|
||||
? location.search.match( /start_date=([^&#]*)/ )[1]
|
||||
: null;
|
||||
|
||||
properties.end_date = location.search.includes( 'end_date=' )
|
||||
? location.search.match( /end_date=([^&#]*)/ )[1]
|
||||
: new Date().toISOString().split( 'T' )[0];
|
||||
|
||||
properties.span = properties.start_date
|
||||
? Math.floor( ( new Date( properties.end_date ) - new Date( properties.start_date ) ) / 86400000 ) + 1 + 'day'
|
||||
: null;
|
||||
}
|
||||
|
||||
record_event( value, properties );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
// Add event listeners to Subscription by Product report links.
|
||||
if ( $( "tbody[ data-wp-lists='list:product' ]" ).length ) {
|
||||
|
||||
$( "td.product_name a" ).on( 'click', function() {
|
||||
record_event( 'subscriptions_report_by_product_name_click' );
|
||||
} );
|
||||
|
||||
$( "td.subscription_count a" ).on( 'click', function() {
|
||||
record_event( 'subscriptions_report_by_product_count_click' );
|
||||
} );
|
||||
}
|
||||
|
||||
// Add event listeners to Subscription by Customer report links.
|
||||
if ( $( "tbody[ data-wp-lists='list:customer' ]" ).length ) {
|
||||
|
||||
$( "td.customer_name a" ).on( 'click', function() {
|
||||
record_event( 'subscriptions_report_by_customer_name_click' );
|
||||
} );
|
||||
|
||||
$( "td.total_subscription_count a" ).on( 'click', function() {
|
||||
record_event( 'subscriptions_report_by_customer_total_count_click' );
|
||||
} );
|
||||
|
||||
$( "td.total_subscription_order_count a" ).on( 'click', function() {
|
||||
record_event( 'subscriptions_report_by_customer_total_order_count_click' );
|
||||
} );
|
||||
}
|
||||
});
|
||||
|
|
@ -1,8 +1,43 @@
|
|||
jQuery(document).ready(function($){
|
||||
|
||||
$('body.post-type-shop_order #post').submit(function(){
|
||||
if('wcs_retry_renewal_payment' == $( "body.post-type-shop_order select[name='wc_order_action']" ).val()) {
|
||||
return confirm(wcs_admin_order_meta_boxes.retry_renewal_payment_action_warning);
|
||||
jQuery( function ( $ ) {
|
||||
$( 'body.post-type-shop_order #post' ).on( 'submit', function () {
|
||||
if (
|
||||
'wcs_retry_renewal_payment' ==
|
||||
$(
|
||||
"body.post-type-shop_order select[name='wc_order_action']"
|
||||
).val()
|
||||
) {
|
||||
return confirm(
|
||||
wcs_admin_order_meta_boxes.retry_renewal_payment_action_warning
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
} );
|
||||
|
||||
$( document ).on( 'change', '#wcs-order-price-lock', function () {
|
||||
// Block the checkbox element while we update the order.
|
||||
$( '#wcs_order_price_lock' ).block( {
|
||||
message: null,
|
||||
overlayCSS: {
|
||||
background: '#fff',
|
||||
opacity: 0.6,
|
||||
},
|
||||
} );
|
||||
|
||||
var data = {
|
||||
wcs_order_price_lock: $( '#wcs-order-price-lock' ).is( ':checked' )
|
||||
? 'yes'
|
||||
: 'no',
|
||||
order_id: $( '#post_ID' ).val(),
|
||||
action: 'wcs_order_price_lock',
|
||||
woocommerce_meta_nonce: $( '#woocommerce_meta_nonce' ).val(),
|
||||
};
|
||||
|
||||
$.ajax( {
|
||||
type: 'post',
|
||||
url: woocommerce_admin_meta_boxes.ajax_url,
|
||||
data: data,
|
||||
complete: function () {
|
||||
$( '#wcs_order_price_lock' ).unblock();
|
||||
},
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
jQuery( function ( $ ) {
|
||||
/**
|
||||
* Displays an appropriate error message when the delete token button is clicked for a token used by subscriptions.
|
||||
*/
|
||||
$( '.wcs_deletion_error' ).on( 'click', function ( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
var notice_content_container = $( '#wcs_delete_token_warning' ).find(
|
||||
'li'
|
||||
);
|
||||
|
||||
// For block based WC notices we need to find the notice content container.
|
||||
if (
|
||||
$( '#wcs_delete_token_warning' ).find(
|
||||
'.wc-block-components-notice-banner'
|
||||
).length > 0
|
||||
) {
|
||||
notice_content_container = $( '#wcs_delete_token_warning' ).find(
|
||||
'.wc-block-components-notice-banner__content'
|
||||
);
|
||||
}
|
||||
|
||||
// Use the href to determine which notice needs to be displayed.
|
||||
if ( '#choose_default' === $( this ).attr( 'href' ) ) {
|
||||
notice_content_container.html(
|
||||
wcs_payment_methods.choose_default_error
|
||||
);
|
||||
} else {
|
||||
notice_content_container.html(
|
||||
wcs_payment_methods.add_method_error
|
||||
);
|
||||
}
|
||||
|
||||
// Display the notice.
|
||||
$( '#wcs_delete_token_warning' ).slideDown();
|
||||
} );
|
||||
} );
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
(function ( document, $ ) {
|
||||
|
||||
( function ( document, $ ) {
|
||||
var $cache = {};
|
||||
|
||||
/**
|
||||
* Cache our DOM selectors.
|
||||
*/
|
||||
function generate_cache() {
|
||||
$cache.document = $( document );
|
||||
$cache.first_payment_date = $( '.first-payment-date' );
|
||||
$cache.is_variable_subscription = 0 < $( 'div.product-type-variable-subscription' ).length;
|
||||
$cache.document = $( document );
|
||||
$cache.first_payment_date = $( '.first-payment-date' );
|
||||
$cache.is_variable_subscription =
|
||||
0 < $( 'div.product-type-variable-subscription' ).length;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -16,7 +16,10 @@
|
|||
*/
|
||||
function attach_events() {
|
||||
if ( $cache.is_variable_subscription ) {
|
||||
$cache.document.on( 'found_variation', update_first_payment_element );
|
||||
$cache.document.on(
|
||||
'found_variation',
|
||||
update_first_payment_element
|
||||
);
|
||||
$cache.document.on( 'reset_data', clear_first_payment_element );
|
||||
}
|
||||
}
|
||||
|
|
@ -47,6 +50,4 @@
|
|||
}
|
||||
|
||||
$( init );
|
||||
|
||||
})( document, jQuery );
|
||||
|
||||
} )( document, jQuery );
|
||||
|
|
|
|||
|
|
@ -1,25 +1,36 @@
|
|||
jQuery( document ).ready( function( $ ) {
|
||||
jQuery( function ( $ ) {
|
||||
// Auto Renewal Toggle
|
||||
var $toggleContainer = $( '.wcs-auto-renew-toggle' );
|
||||
var $toggle = $( '.subscription-auto-renew-toggle', $toggleContainer );
|
||||
var $icon = $toggle.find( 'i' );
|
||||
var txtColor = null;
|
||||
var $paymentMethod = $( '.subscription-payment-method' );
|
||||
var $toggle = $( '.subscription-auto-renew-toggle', $toggleContainer );
|
||||
var $icon = $toggle.find( 'i' );
|
||||
var txtColor = null;
|
||||
var $paymentMethod = $( '.subscription-payment-method' );
|
||||
|
||||
// Early Renewal
|
||||
var $early_renewal_modal_submit = $( '#early_renewal_modal_submit' );
|
||||
var $early_renewal_modal_content = $( '.wcs-modal > .content-wrapper' );
|
||||
|
||||
function getTxtColor() {
|
||||
if ( !txtColor && ( $icon && $icon.length ) ) {
|
||||
txtColor = getComputedStyle( $icon[0] ).color;
|
||||
if ( ! txtColor ) {
|
||||
// Create a temporary link to get the theme's accent color.
|
||||
var $tempLink = $( '<a href="#" style="display:none;"></a>' ).appendTo( $toggleContainer );
|
||||
txtColor = getComputedStyle( $tempLink[ 0 ] ).color;
|
||||
$tempLink.remove();
|
||||
}
|
||||
|
||||
return txtColor;
|
||||
}
|
||||
|
||||
function maybeApplyColor() {
|
||||
if ( $toggle.hasClass( 'subscription-auto-renew-toggle--on' ) && $icon.length ) {
|
||||
$icon[0].style.backgroundColor = getTxtColor();
|
||||
$icon[0].style.borderColor = getTxtColor();
|
||||
} else if( $icon.length ) {
|
||||
$icon[0].style.backgroundColor = null;
|
||||
$icon[0].style.borderColor = null;
|
||||
if (
|
||||
$toggle.hasClass( 'subscription-auto-renew-toggle--on' ) &&
|
||||
$icon.length
|
||||
) {
|
||||
$icon[ 0 ].style.backgroundColor = getTxtColor();
|
||||
$icon[ 0 ].style.borderColor = getTxtColor();
|
||||
} else if ( $icon.length ) {
|
||||
$icon[ 0 ].style.backgroundColor = null;
|
||||
$icon[ 0 ].style.borderColor = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -30,35 +41,42 @@ jQuery( document ).ready( function( $ ) {
|
|||
function onToggle( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
// Remove focus from the toggle element.
|
||||
$toggle.blur();
|
||||
// Ignore the request if the toggle is disabled.
|
||||
if ( $toggle.hasClass( 'subscription-auto-renew-toggle--disabled' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var ajaxHandler = function( action ) {
|
||||
var ajaxHandler = function ( action ) {
|
||||
var data = {
|
||||
subscription_id: WCSViewSubscription.subscription_id,
|
||||
action: action,
|
||||
security: WCSViewSubscription.auto_renew_nonce,
|
||||
action: action,
|
||||
security: WCSViewSubscription.auto_renew_nonce,
|
||||
};
|
||||
|
||||
// While we're waiting for an AJAX response, block the toggle element to prevent spamming the server.
|
||||
blockToggle();
|
||||
|
||||
$.ajax({
|
||||
url: WCSViewSubscription.ajax_url,
|
||||
$.ajax( {
|
||||
url: WCSViewSubscription.ajax_url,
|
||||
data: data,
|
||||
type: 'POST',
|
||||
success: function( result ) {
|
||||
success: function ( result ) {
|
||||
if ( result.payment_method ) {
|
||||
$paymentMethod.fadeOut( function() {
|
||||
$paymentMethod.html( result.payment_method ).fadeIn();
|
||||
});
|
||||
$paymentMethod.fadeOut( function () {
|
||||
$paymentMethod
|
||||
.html( result.payment_method )
|
||||
.fadeIn();
|
||||
} );
|
||||
}
|
||||
if ( undefined !== result.is_manual ) {
|
||||
$paymentMethod.data( 'is_manual', result.is_manual );
|
||||
}
|
||||
},
|
||||
error: function( jqxhr, status, exception ) {
|
||||
error: function ( jqxhr, status, exception ) {
|
||||
alert( 'Exception:', exception );
|
||||
},
|
||||
complete: unblockToggle
|
||||
});
|
||||
complete: unblockToggle,
|
||||
} );
|
||||
};
|
||||
|
||||
// Enable auto-renew
|
||||
|
|
@ -67,11 +85,15 @@ jQuery( document ).ready( function( $ ) {
|
|||
if ( WCSViewSubscription.has_payment_gateway ) {
|
||||
ajaxHandler( 'wcs_enable_auto_renew' );
|
||||
displayToggleOn();
|
||||
} else if ( window.confirm( WCSViewSubscription.add_payment_method_msg ) ) { // else add payment method
|
||||
window.location.href = WCSViewSubscription.add_payment_method_url;
|
||||
} else if (
|
||||
window.confirm( WCSViewSubscription.add_payment_method_msg )
|
||||
) {
|
||||
// else add payment method
|
||||
window.location.href =
|
||||
WCSViewSubscription.add_payment_method_url;
|
||||
}
|
||||
|
||||
} else { // Disable auto-renew
|
||||
} else {
|
||||
// Disable auto-renew
|
||||
ajaxHandler( 'wcs_disable_auto_renew' );
|
||||
displayToggleOff();
|
||||
}
|
||||
|
|
@ -81,27 +103,75 @@ jQuery( document ).ready( function( $ ) {
|
|||
|
||||
function displayToggleOn() {
|
||||
$icon.removeClass( 'fa-toggle-off' ).addClass( 'fa-toggle-on' );
|
||||
$toggle.removeClass( 'subscription-auto-renew-toggle--off' ).addClass( 'subscription-auto-renew-toggle--on' );
|
||||
$toggle
|
||||
.removeClass( 'subscription-auto-renew-toggle--off' )
|
||||
.addClass( 'subscription-auto-renew-toggle--on' )
|
||||
.attr( 'aria-checked', 'true' );
|
||||
}
|
||||
|
||||
function displayToggleOff() {
|
||||
$icon.removeClass( 'fa-toggle-on' ).addClass( 'fa-toggle-off' );
|
||||
$toggle.removeClass( 'subscription-auto-renew-toggle--on' ).addClass( 'subscription-auto-renew-toggle--off' );
|
||||
$toggle
|
||||
.removeClass( 'subscription-auto-renew-toggle--on' )
|
||||
.addClass( 'subscription-auto-renew-toggle--off' )
|
||||
.attr( 'aria-checked', 'false' );
|
||||
}
|
||||
|
||||
function blockToggle() {
|
||||
$toggleContainer.block({
|
||||
$toggle.addClass( 'subscription-auto-renew-toggle--disabled' );
|
||||
$toggleContainer.block( {
|
||||
message: null,
|
||||
overlayCSS: { opacity: 0.0 }
|
||||
});
|
||||
overlayCSS: { opacity: 0.0 },
|
||||
} );
|
||||
}
|
||||
|
||||
function unblockToggle() {
|
||||
$toggle.removeClass( 'subscription-auto-renew-toggle--disabled' );
|
||||
$toggleContainer.unblock();
|
||||
}
|
||||
|
||||
function blockEarlyRenewalModal() {
|
||||
$early_renewal_modal_content.block( {
|
||||
message: null,
|
||||
overlayCSS: {
|
||||
background: '#fff',
|
||||
opacity: 0.6,
|
||||
},
|
||||
} );
|
||||
}
|
||||
|
||||
// Don't display the early renewal modal for manual subscriptions, they will need to renew via the checkout.
|
||||
function shouldShowEarlyRenewalModal( event ) {
|
||||
// We're only interested in requests to show the early renewal modal.
|
||||
if (
|
||||
'.subscription_renewal_early' !==
|
||||
$( event.modal ).data( 'modal-trigger' )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $paymentMethod.data( 'is_manual' ) === 'no';
|
||||
}
|
||||
|
||||
function blockActionsOnTrigger() {
|
||||
$( '.subscription_details' ).block( {
|
||||
message: null,
|
||||
overlayCSS: {
|
||||
background: '#fff',
|
||||
opacity: 0.6,
|
||||
},
|
||||
} );
|
||||
}
|
||||
|
||||
$toggle.on( 'click', onToggle );
|
||||
maybeApplyColor();
|
||||
displayToggle();
|
||||
});
|
||||
|
||||
$early_renewal_modal_submit.on( 'click', blockEarlyRenewalModal );
|
||||
$( document ).on( 'wcs_show_modal', shouldShowEarlyRenewalModal );
|
||||
$( document ).on(
|
||||
'click',
|
||||
'.wcs_block_ui_on_click',
|
||||
blockActionsOnTrigger
|
||||
);
|
||||
} );
|
||||
|
|
|
|||
|
|
@ -2,20 +2,57 @@ function hide_non_applicable_coupons() {
|
|||
var coupon_elements = document.getElementsByClassName( 'cart-discount' );
|
||||
|
||||
for ( var i = 0; i < coupon_elements.length; i++ ) {
|
||||
if ( 0 !== coupon_elements[i].getElementsByClassName( 'wcs-hidden-coupon' ).length ) {
|
||||
coupon_elements[i].style.display = 'none';
|
||||
if (
|
||||
0 !==
|
||||
coupon_elements[ i ].getElementsByClassName( 'wcs-hidden-coupon' )
|
||||
.length
|
||||
) {
|
||||
coupon_elements[ i ].style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hide_non_applicable_coupons();
|
||||
|
||||
jQuery( document ).ready( function( $ ) {
|
||||
$( document.body ).on( 'updated_cart_totals updated_checkout', function() {
|
||||
jQuery( function ( $ ) {
|
||||
$( document.body ).on( 'updated_cart_totals updated_checkout', function () {
|
||||
hide_non_applicable_coupons();
|
||||
} );
|
||||
|
||||
$( '.payment_methods [name="payment_method"]' ).click( function() {
|
||||
/**
|
||||
* Update all subscriptions shipping methods which inherit the chosen method from the initial
|
||||
* cart when the customer changes the shipping method.
|
||||
*/
|
||||
$( document ).on(
|
||||
'change',
|
||||
'select.shipping_method, :input[name^=shipping_method]',
|
||||
function ( event ) {
|
||||
var shipping_method_option = $( event.target );
|
||||
var shipping_method_id = shipping_method_option.val();
|
||||
var package_index = shipping_method_option.data( 'index' );
|
||||
|
||||
// We're only interested in the initial cart shipping method options which have int package indexes.
|
||||
if ( ! Number.isInteger( package_index ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find all recurring cart info elements with the same package index as the changed shipping method.
|
||||
$(
|
||||
'.recurring-cart-shipping-mapping-info[data-index=' +
|
||||
package_index +
|
||||
']'
|
||||
).each( function () {
|
||||
// Update the corresponding subscription's hidden chosen shipping method.
|
||||
$(
|
||||
'input[name="shipping_method[' +
|
||||
$( this ).data( 'recurring_index' ) +
|
||||
']"]'
|
||||
).val( shipping_method_id );
|
||||
} );
|
||||
}
|
||||
);
|
||||
|
||||
$( '.payment_methods [name="payment_method"]' ).on( 'click', function () {
|
||||
if ( $( this ).hasClass( 'supports-payment-method-changes' ) ) {
|
||||
$( '.update-all-subscriptions-payment-method-wrap' ).show();
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,358 @@
|
|||
jQuery( document ).ready( function ( $ ) {
|
||||
setShippingAddressNoticeVisibility( true );
|
||||
|
||||
$( document ).on(
|
||||
'change',
|
||||
'.woocommerce_subscription_gifting_checkbox[type="checkbox"]',
|
||||
function ( e, eventContext ) {
|
||||
if ( $( this ).is( ':checked' ) ) {
|
||||
$( this )
|
||||
.closest( '.wcsg_add_recipient_fields_container' )
|
||||
.find( '.wcsg_add_recipient_fields' )
|
||||
.slideDown( 250, function () {
|
||||
if (
|
||||
typeof eventContext === 'undefined' ||
|
||||
eventContext !== 'pageload'
|
||||
) {
|
||||
$( this )
|
||||
.find( '.recipient_email' )
|
||||
.trigger( 'focus' );
|
||||
}
|
||||
} )
|
||||
.removeClass( 'hidden' );
|
||||
|
||||
const shipToDifferentAddressCheckbox = $( document ).find(
|
||||
'#ship-to-different-address-checkbox'
|
||||
);
|
||||
if ( ! shipToDifferentAddressCheckbox.is( ':checked' ) ) {
|
||||
shipToDifferentAddressCheckbox.click();
|
||||
}
|
||||
setShippingAddressNoticeVisibility( false );
|
||||
} else {
|
||||
$( this )
|
||||
.closest( '.wcsg_add_recipient_fields_container' )
|
||||
.find( '.wcsg_add_recipient_fields' )
|
||||
.slideUp( 250 )
|
||||
.addClass( 'hidden' );
|
||||
|
||||
const recipientEmailElement = $( this )
|
||||
.closest( '.wcsg_add_recipient_fields_container' )
|
||||
.find( '.recipient_email' );
|
||||
recipientEmailElement.val( '' );
|
||||
hideValidationErrorForEmailField( recipientEmailElement );
|
||||
setShippingAddressNoticeVisibility( true );
|
||||
|
||||
if ( $( 'form.checkout' ).length !== 0 ) {
|
||||
// Trigger the event to update the checkout after the recipient field has been cleared.
|
||||
updateCheckout();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Handles showing and hiding the gifting checkbox on variable subscription products.
|
||||
*/
|
||||
function hideGiftingCheckbox() {
|
||||
$( '.woocommerce_subscription_gifting_checkbox[type="checkbox"]' )
|
||||
.prop( 'checked', false )
|
||||
.trigger( 'change' );
|
||||
$( '.wcsg_add_recipient_fields_container' ).hide();
|
||||
}
|
||||
|
||||
// When a variation is found, show the gifting checkbox if it's enabled for the variation, otherwise hide it.
|
||||
$( document ).on( 'found_variation', function ( event, variationData ) {
|
||||
if ( variationData.gifting ) {
|
||||
$( '.wcsg_add_recipient_fields_container' ).show();
|
||||
return;
|
||||
}
|
||||
|
||||
hideGiftingCheckbox();
|
||||
} );
|
||||
|
||||
// When the data is reset, hide the gifting checkbox.
|
||||
$( document ).on( 'reset_data', hideGiftingCheckbox );
|
||||
|
||||
/**
|
||||
* Handles recipient e-mail inputs on the cart page.
|
||||
*/
|
||||
const cart = {
|
||||
init: function () {
|
||||
$( document ).on(
|
||||
'submit',
|
||||
'div.woocommerce > form',
|
||||
this.set_update_cart_as_clicked
|
||||
);
|
||||
|
||||
// We need to make sure our callback is hooked before WC's.
|
||||
const handlers = $._data( document, 'events' );
|
||||
if ( typeof handlers.submit !== 'undefined' ) {
|
||||
handlers.submit.unshift( handlers.submit.pop() );
|
||||
}
|
||||
},
|
||||
|
||||
set_update_cart_as_clicked: function ( evt ) {
|
||||
const $form = $( evt.target );
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
const $submit = $( document.activeElement );
|
||||
|
||||
// If we're not on the cart page exit.
|
||||
if ( $form.find( 'table.shop_table.cart' ).length === 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the recipient email element is the active element, the clicked button is the update cart button.
|
||||
if ( $submit.is( 'input.recipient_email' ) ) {
|
||||
$( ':input[type="submit"][name="update_cart"]' ).attr(
|
||||
'clicked',
|
||||
'true'
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
cart.init();
|
||||
|
||||
/**
|
||||
* Email validation function
|
||||
*
|
||||
* @param {string} email - The email to validate
|
||||
* @return {boolean} - Whether the email is valid
|
||||
*/
|
||||
function isValidEmail( email ) {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test( email );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate all recipient emails and return overall validation status
|
||||
*
|
||||
* @param {boolean} showErrors - Whether to show validation errors
|
||||
* @return {boolean} - Whether all emails are valid
|
||||
*/
|
||||
function validateAllRecipientEmails( showErrors = true ) {
|
||||
const $allEmailFields = $( '.recipient_email' );
|
||||
let allValid = true;
|
||||
|
||||
// Check each email field
|
||||
$allEmailFields.each( function () {
|
||||
const $emailField = $( this );
|
||||
const $giftingCheckbox = $( this )
|
||||
.closest( '.wcsg_add_recipient_fields_container' )
|
||||
.find( '.woocommerce_subscription_gifting_checkbox' );
|
||||
const email = $emailField.val().trim();
|
||||
|
||||
if ( ! $giftingCheckbox.is( ':checked' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if email format is valid
|
||||
if ( ! isValidEmail( email ) ) {
|
||||
if ( showErrors ) {
|
||||
showValidationErrorForEmailField( $emailField );
|
||||
}
|
||||
allValid = false;
|
||||
}
|
||||
} );
|
||||
|
||||
// Control update cart button state
|
||||
const $updateCartButton = $(
|
||||
'.woocommerce-cart-form :input[type="submit"][name="update_cart"]'
|
||||
);
|
||||
|
||||
if ( $updateCartButton.length && ! allValid ) {
|
||||
$updateCartButton.prop( 'disabled', true );
|
||||
}
|
||||
|
||||
return allValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate recipient email and show error if invalid
|
||||
*
|
||||
* @param {jQuery} $emailField - The email input field jQuery object
|
||||
* @return {boolean} - Whether the email is valid
|
||||
*/
|
||||
function validateRecipientEmail( $emailField ) {
|
||||
const email = $emailField.val().trim();
|
||||
|
||||
hideValidationErrorForEmailField( $emailField );
|
||||
|
||||
// Check if email format is valid
|
||||
if ( ! isValidEmail( email ) ) {
|
||||
showValidationErrorForEmailField( $emailField );
|
||||
|
||||
// Only validate all emails and update button state on cart and checkout shortcode pages.
|
||||
if ( isShortcodeCartOrCheckoutPage() ) {
|
||||
validateAllRecipientEmails();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only validate all emails and update button state on cart and checkout shortcode pages.
|
||||
if ( isShortcodeCartOrCheckoutPage() ) {
|
||||
validateAllRecipientEmails();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle add to cart button click with email validation
|
||||
*/
|
||||
$( document ).on(
|
||||
'click',
|
||||
'.single_add_to_cart_button, .add_to_cart_button',
|
||||
function ( e ) {
|
||||
// Check if we're on a product page with gifting enabled
|
||||
const $giftingContainer = $(
|
||||
'.wcsg_add_recipient_fields_container'
|
||||
);
|
||||
if ( $giftingContainer.length === 0 ) {
|
||||
return; // No gifting on this page
|
||||
}
|
||||
|
||||
// Check if gifting checkbox is checked
|
||||
const $giftingCheckbox = $giftingContainer.find(
|
||||
'.woocommerce_subscription_gifting_checkbox'
|
||||
);
|
||||
if ( ! $giftingCheckbox.is( ':checked' ) ) {
|
||||
return; // Gifting not enabled for this item
|
||||
}
|
||||
|
||||
// Get the recipient email field
|
||||
const $emailField = $giftingContainer.find( '.recipient_email' );
|
||||
if ( $emailField.length === 0 ) {
|
||||
return; // No email field found
|
||||
}
|
||||
|
||||
// Validate the email
|
||||
if ( ! validateRecipientEmail( $emailField ) ) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Focus on the email field
|
||||
$emailField.focus();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Real-time email validation on input
|
||||
*/
|
||||
$( document ).on( 'blur', '.recipient_email', function () {
|
||||
const $emailField = $( this );
|
||||
validateRecipientEmail( $emailField );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Clear error styling when user starts typing
|
||||
*/
|
||||
$( document ).on( 'input', '.recipient_email', function () {
|
||||
const $emailField = $( this );
|
||||
|
||||
hideValidationErrorForEmailField( $emailField );
|
||||
} );
|
||||
|
||||
/*******************************************
|
||||
* Update checkout on input changed events *
|
||||
*******************************************/
|
||||
let updateTimer;
|
||||
|
||||
$( document ).on( 'change', '.recipient_email', function () {
|
||||
if ( $( 'form.checkout' ).length === 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( validateAllRecipientEmails() ) {
|
||||
updateCheckout();
|
||||
}
|
||||
} );
|
||||
|
||||
$( document ).on( 'keyup', '.recipient_email', function ( e ) {
|
||||
const code = e.keyCode || e.which || 0;
|
||||
|
||||
if ( $( 'form.checkout' ).length === 0 || code === 9 ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const currentRecipient = $( this ).val();
|
||||
const originalRecipient = $( this ).attr( 'data-recipient' );
|
||||
resetCheckoutUpdateTimer();
|
||||
|
||||
// If the recipient has changed since last load, mark the element as needing an update.
|
||||
if ( currentRecipient !== originalRecipient ) {
|
||||
$( this ).addClass( 'wcsg_needs_update' );
|
||||
// Only set timer if all emails are valid
|
||||
if ( validateAllRecipientEmails( false ) ) {
|
||||
updateTimer = setTimeout( updateCheckout, 1500 );
|
||||
}
|
||||
} else {
|
||||
$( this ).removeClass( 'wcsg_needs_update' );
|
||||
}
|
||||
} );
|
||||
|
||||
function updateCheckout() {
|
||||
resetCheckoutUpdateTimer();
|
||||
$( '.recipient_email' ).removeClass( 'wcsg_needs_update' );
|
||||
$( document.body ).trigger( 'update_checkout' );
|
||||
}
|
||||
|
||||
function resetCheckoutUpdateTimer() {
|
||||
clearTimeout( updateTimer );
|
||||
}
|
||||
|
||||
function setShippingAddressNoticeVisibility( hide = true ) {
|
||||
const notice = $( 'form.checkout' )
|
||||
.find( '.woocommerce-shipping-fields' )
|
||||
.find( '.woocommerce-info' );
|
||||
|
||||
if ( ! notice.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( hide ) {
|
||||
notice.css( { display: 'none' } );
|
||||
} else {
|
||||
notice.css( { display: '' } );
|
||||
}
|
||||
}
|
||||
|
||||
function isShortcodeCartOrCheckoutPage() {
|
||||
return (
|
||||
$( 'form.woocommerce-cart-form' ).length > 0 ||
|
||||
$( 'form.woocommerce-checkout' ).length > 0
|
||||
);
|
||||
}
|
||||
|
||||
function showValidationErrorForEmailField( $emailField ) {
|
||||
$emailField.addClass( 'wcsg-email-error' );
|
||||
$emailField
|
||||
.closest( '.wcsg_add_recipient_fields' )
|
||||
.find( '.wc-shortcode-components-validation-error' )
|
||||
.show();
|
||||
}
|
||||
|
||||
function hideValidationErrorForEmailField( $emailField ) {
|
||||
$emailField.removeClass( 'wcsg-email-error' );
|
||||
$emailField
|
||||
.closest( '.wcsg_add_recipient_fields' )
|
||||
.find( '.wc-shortcode-components-validation-error' )
|
||||
.hide();
|
||||
}
|
||||
|
||||
// Triggers
|
||||
$( '.woocommerce_subscription_gifting_checkbox[type="checkbox"]' ).trigger(
|
||||
'change',
|
||||
'pageload'
|
||||
);
|
||||
|
||||
// Validate all recipient emails on page load to set initial button state
|
||||
$( document ).ready( function () {
|
||||
setTimeout( function () {
|
||||
// Only run validation on cart and checkout shortcode pages
|
||||
if ( isShortcodeCartOrCheckoutPage() ) {
|
||||
validateAllRecipientEmails();
|
||||
}
|
||||
}, 1000 );
|
||||
} );
|
||||
} );
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
jQuery( document ).ready( function ( $ ) {
|
||||
// Remove WC's revoke handler to make sure that only our handler is called (to make sure only the correct permissions are revoked not all permissions matching the product/order ID)
|
||||
$( '.order_download_permissions' ).off( 'click', 'button.revoke_access' );
|
||||
|
||||
$( '.order_download_permissions' ).on(
|
||||
'click',
|
||||
'button.revoke_access',
|
||||
function () {
|
||||
if (
|
||||
window.confirm(
|
||||
woocommerce_admin_meta_boxes.i18n_permission_revoke
|
||||
)
|
||||
) {
|
||||
var el = $( this ).parent().parent();
|
||||
var permission_id = $( this )
|
||||
.siblings()
|
||||
.find( '.wcsg_download_permission_id' )
|
||||
.val();
|
||||
var post_id = $( '#post_ID' ).val();
|
||||
|
||||
if ( 0 < permission_id ) {
|
||||
$( el ).block( {
|
||||
message: null,
|
||||
overlayCSS: {
|
||||
background: '#fff',
|
||||
opacity: 0.6,
|
||||
},
|
||||
} );
|
||||
|
||||
var data = {
|
||||
action: 'wcsg_revoke_access_to_download',
|
||||
post_id: post_id,
|
||||
download_permission_id: permission_id,
|
||||
nonce: wcs_gifting.revoke_download_permission_nonce,
|
||||
};
|
||||
|
||||
$.ajax( {
|
||||
url: wcs_gifting.ajax_url,
|
||||
data: data,
|
||||
type: 'POST',
|
||||
success: function () {
|
||||
// Success
|
||||
$( el ).fadeOut( '300', function () {
|
||||
$( el ).remove();
|
||||
} );
|
||||
},
|
||||
} );
|
||||
} else {
|
||||
$( el ).fadeOut( '300', function () {
|
||||
$( el ).remove();
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
} );
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
jQuery( function ( $ ) {
|
||||
const $modals = $( '.wcs-modal' );
|
||||
let $currentModal;
|
||||
let $triggerElement;
|
||||
|
||||
// Resize all open modals on window resize.
|
||||
$( window ).on( 'resize', resizeModals );
|
||||
|
||||
// Initialize modals
|
||||
$( $modals ).each( function () {
|
||||
trigger = $( this ).data( 'modal-trigger' );
|
||||
$( trigger ).on( 'click', { modal: this }, show_modal );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Displays the modal linked to a click event.
|
||||
*
|
||||
* Attaches all close callbacks and resizes to fit.
|
||||
*
|
||||
* @param {JQuery event} event
|
||||
*/
|
||||
function show_modal( event ) {
|
||||
$triggerElement = $( event.target );
|
||||
$currentModal = $( event.data.modal );
|
||||
|
||||
if ( ! should_show_modal( $currentModal ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent the trigger element event being triggered.
|
||||
event.preventDefault();
|
||||
|
||||
const $contentWrapper = $currentModal.find( '.content-wrapper' );
|
||||
const $close = $currentModal.find( '.close' );
|
||||
|
||||
$currentModal.addClass( 'open' );
|
||||
resizeModal( $currentModal );
|
||||
|
||||
$( document.body ).toggleClass( 'wcs-modal-open', true );
|
||||
$currentModal.focus();
|
||||
document.addEventListener( 'focusin', keepFocusInModal );
|
||||
|
||||
// Attach callbacks to handle closing the modal.
|
||||
$close.on( 'click', () => close_modal( $currentModal ) );
|
||||
$currentModal.on( 'click', () => close_modal( $currentModal ) );
|
||||
$contentWrapper.on( 'click', ( e ) => e.stopPropagation() );
|
||||
|
||||
// Close the modal if the escape key is pressed.
|
||||
$currentModal.on( 'keyup', function ( e ) {
|
||||
if ( 27 === e.keyCode ) {
|
||||
close_modal( $currentModal );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes a modal and resets any forced height styles.
|
||||
*
|
||||
* @param {JQuery Object} $modal
|
||||
*/
|
||||
function close_modal( $modal ) {
|
||||
$modal.removeClass( 'open' );
|
||||
$( $modal ).find( '.content-wrapper' ).css( 'height', '' );
|
||||
|
||||
if ( 0 === $modals.filter( '.open' ).length ) {
|
||||
$( document.body ).removeClass( 'wcs-modal-open' );
|
||||
$currentModal = false;
|
||||
document.removeEventListener( 'focusin', keepFocusInModal );
|
||||
$triggerElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a modal should be displayed.
|
||||
*
|
||||
* A custom trigger is called to allow third-parties to filter whether the modal should be displayed or not.
|
||||
*
|
||||
* @param {JQuery Object} modal
|
||||
*/
|
||||
function should_show_modal( modal ) {
|
||||
// Allow third-parties to filter whether the modal should be displayed.
|
||||
var event = jQuery.Event( 'wcs_show_modal' );
|
||||
event.modal = modal;
|
||||
|
||||
$( document ).trigger( event );
|
||||
|
||||
// Fallback to true (show modal) if the result is undefined.
|
||||
return undefined === event.result ? true : event.result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize all open modals to fit the display.
|
||||
*/
|
||||
function resizeModals() {
|
||||
$( $modals ).each( function () {
|
||||
if ( ! $( this ).hasClass( 'open' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
resizeModal( this );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize a modal to fit the display.
|
||||
*
|
||||
* @param {JQuery Object} $modal
|
||||
*/
|
||||
function resizeModal( $modal ) {
|
||||
const $modal_container = $( $modal ).find( '.content-wrapper' );
|
||||
|
||||
// On smaller displays the height is already forced to be 100% in CSS. We just clear any height we might set previously.
|
||||
if ( $( window ).width() <= 414 ) {
|
||||
$modal_container.css( 'height', '' );
|
||||
} else if ( $modal_container.height() > $( window ).height() ) {
|
||||
// Force the container height to trigger scroll etc if it doesn't fit on the screen.
|
||||
$modal_container.css( 'height', '90%' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If focus moves out of the open modal, return focus to it.
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
function keepFocusInModal( event ) {
|
||||
if ( $currentModal && ! $currentModal[0].contains( event.target ) ) {
|
||||
$currentModal.focus();
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
|
@ -1,89 +1,109 @@
|
|||
jQuery(document).ready(function($){
|
||||
/**
|
||||
* @deprecated subscriptions-core 7.7.0 This file is no longer in use and can be removed in future.
|
||||
*/
|
||||
jQuery( function ( $ ) {
|
||||
var upgrade_start_time = null,
|
||||
total_subscriptions = wcs_update_script_data.subscription_count;
|
||||
total_subscriptions = wcs_update_script_data.subscription_count;
|
||||
|
||||
$('#update-messages').slideUp();
|
||||
$('#upgrade-step-3').slideUp();
|
||||
$( '#update-messages' ).slideUp();
|
||||
$( '#upgrade-step-3' ).slideUp();
|
||||
|
||||
$('form#subscriptions-upgrade').on('submit',function(e){
|
||||
$('#update-welcome').slideUp(600);
|
||||
$('#update-messages').slideDown(600);
|
||||
if('true'==wcs_update_script_data.really_old_version){
|
||||
$( 'form#subscriptions-upgrade' ).on( 'submit', function ( e ) {
|
||||
$( '#update-welcome' ).slideUp( 600 );
|
||||
$( '#update-messages' ).slideDown( 600 );
|
||||
if ( 'true' == wcs_update_script_data.really_old_version ) {
|
||||
wcs_ajax_update_really_old_version();
|
||||
} else if('true'==wcs_update_script_data.upgrade_to_1_5){
|
||||
} else if ( 'true' == wcs_update_script_data.upgrade_to_1_5 ) {
|
||||
wcs_ajax_update_products();
|
||||
wcs_ajax_update_hooks();
|
||||
} else if('true'==wcs_update_script_data.upgrade_to_2_0){
|
||||
} else if ( 'true' == wcs_update_script_data.upgrade_to_2_0 ) {
|
||||
wcs_ajax_update_subscriptions();
|
||||
} else if('true'==wcs_update_script_data.repair_2_0){
|
||||
} else if ( 'true' == wcs_update_script_data.repair_2_0 ) {
|
||||
wcs_ajax_repair_subscriptions();
|
||||
} else {
|
||||
wcs_ajax_update_complete();
|
||||
}
|
||||
e.preventDefault();
|
||||
});
|
||||
function wcs_ajax_update_really_old_version(){
|
||||
$.ajax({
|
||||
url: wcs_update_script_data.ajax_url,
|
||||
} );
|
||||
function wcs_ajax_update_really_old_version() {
|
||||
$.ajax( {
|
||||
url: wcs_update_script_data.ajax_url,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'wcs_upgrade',
|
||||
action: 'wcs_upgrade',
|
||||
upgrade_step: 'really_old_version',
|
||||
nonce: wcs_update_script_data.upgrade_nonce
|
||||
nonce: wcs_update_script_data.upgrade_nonce,
|
||||
},
|
||||
success: function(results) {
|
||||
$('#update-messages ol').append($('<li />').text(results.message));
|
||||
success: function ( results ) {
|
||||
$( '#update-messages ol' ).append(
|
||||
$( '<li />' ).text( results.message )
|
||||
);
|
||||
wcs_ajax_update_products();
|
||||
wcs_ajax_update_hooks();
|
||||
},
|
||||
error: function(results,status,errorThrown){
|
||||
error: function ( results, status, errorThrown ) {
|
||||
wcs_ajax_update_error();
|
||||
}
|
||||
});
|
||||
},
|
||||
} );
|
||||
}
|
||||
function wcs_ajax_update_products(){
|
||||
$.ajax({
|
||||
url: wcs_update_script_data.ajax_url,
|
||||
function wcs_ajax_update_products() {
|
||||
$.ajax( {
|
||||
url: wcs_update_script_data.ajax_url,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'wcs_upgrade',
|
||||
action: 'wcs_upgrade',
|
||||
upgrade_step: 'products',
|
||||
nonce: wcs_update_script_data.upgrade_nonce
|
||||
nonce: wcs_update_script_data.upgrade_nonce,
|
||||
},
|
||||
success: function(results) {
|
||||
$('#update-messages ol').append($('<li />').text(results.message));
|
||||
success: function ( results ) {
|
||||
$( '#update-messages ol' ).append(
|
||||
$( '<li />' ).text( results.message )
|
||||
);
|
||||
},
|
||||
error: function(results,status,errorThrown){
|
||||
error: function ( results, status, errorThrown ) {
|
||||
wcs_ajax_update_error();
|
||||
}
|
||||
});
|
||||
},
|
||||
} );
|
||||
}
|
||||
function wcs_ajax_update_hooks() {
|
||||
var start_time = new Date();
|
||||
$.ajax({
|
||||
url: wcs_update_script_data.ajax_url,
|
||||
$.ajax( {
|
||||
url: wcs_update_script_data.ajax_url,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'wcs_upgrade',
|
||||
action: 'wcs_upgrade',
|
||||
upgrade_step: 'hooks',
|
||||
nonce: wcs_update_script_data.upgrade_nonce
|
||||
nonce: wcs_update_script_data.upgrade_nonce,
|
||||
},
|
||||
success: function(results) {
|
||||
if(results.message){
|
||||
success: function ( results ) {
|
||||
if ( results.message ) {
|
||||
var end_time = new Date(),
|
||||
execution_time = Math.ceil( ( end_time.getTime() - start_time.getTime() ) / 1000 );
|
||||
$('#update-messages ol').append($('<li />').text(results.message.replace('{execution_time}',execution_time)));
|
||||
execution_time = Math.ceil(
|
||||
( end_time.getTime() - start_time.getTime() ) / 1000
|
||||
);
|
||||
$( '#update-messages ol' ).append(
|
||||
$( '<li />' ).text(
|
||||
results.message.replace(
|
||||
'{execution_time}',
|
||||
execution_time
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
if( undefined == typeof(results.upgraded_count) || parseInt(results.upgraded_count) <= ( wcs_update_script_data.hooks_per_request - 1 ) ){
|
||||
if (
|
||||
undefined == typeof results.upgraded_count ||
|
||||
parseInt( results.upgraded_count ) <=
|
||||
wcs_update_script_data.hooks_per_request - 1
|
||||
) {
|
||||
wcs_ajax_update_subscriptions();
|
||||
} else {
|
||||
wcs_ajax_update_hooks();
|
||||
}
|
||||
},
|
||||
error: function(results,status,errorThrown){
|
||||
error: function ( results, status, errorThrown ) {
|
||||
wcs_ajax_update_error();
|
||||
}
|
||||
});
|
||||
},
|
||||
} );
|
||||
}
|
||||
function wcs_ajax_update_subscriptions() {
|
||||
var start_time = new Date();
|
||||
|
|
@ -92,38 +112,58 @@ jQuery(document).ready(function($){
|
|||
upgrade_start_time = start_time;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: wcs_update_script_data.ajax_url,
|
||||
$.ajax( {
|
||||
url: wcs_update_script_data.ajax_url,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'wcs_upgrade',
|
||||
action: 'wcs_upgrade',
|
||||
upgrade_step: 'subscriptions',
|
||||
nonce: wcs_update_script_data.upgrade_nonce
|
||||
nonce: wcs_update_script_data.upgrade_nonce,
|
||||
},
|
||||
success: function(results) {
|
||||
if('success'==results.status){
|
||||
success: function ( results ) {
|
||||
if ( 'success' == results.status ) {
|
||||
var end_time = new Date(),
|
||||
execution_time = Math.ceil( ( end_time.getTime() - start_time.getTime() ) / 1000 );
|
||||
execution_time = Math.ceil(
|
||||
( end_time.getTime() - start_time.getTime() ) / 1000
|
||||
);
|
||||
|
||||
$('#update-messages ol').append($('<li />').text(results.message.replace('{execution_time}',execution_time)));
|
||||
$( '#update-messages ol' ).append(
|
||||
$( '<li />' ).text(
|
||||
results.message.replace(
|
||||
'{execution_time}',
|
||||
execution_time
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
wcs_update_script_data.subscription_count -= results.upgraded_count;
|
||||
wcs_update_script_data.subscription_count -=
|
||||
results.upgraded_count;
|
||||
|
||||
if( "undefined" === typeof(results.upgraded_count) || parseInt(wcs_update_script_data.subscription_count) <= 0 ) {
|
||||
if (
|
||||
'undefined' === typeof results.upgraded_count ||
|
||||
parseInt( wcs_update_script_data.subscription_count ) <=
|
||||
0
|
||||
) {
|
||||
wcs_ajax_update_complete();
|
||||
} else {
|
||||
wcs_ajax_update_estimated_time(results.time_message);
|
||||
wcs_ajax_update_estimated_time( results.time_message );
|
||||
wcs_ajax_update_subscriptions();
|
||||
}
|
||||
} else {
|
||||
wcs_ajax_update_error(results.message);
|
||||
wcs_ajax_update_error( results.message );
|
||||
}
|
||||
},
|
||||
error: function(results,status,errorThrown){
|
||||
$('<br/><span>Error: ' + results.status + ' ' + errorThrown + '</span>').appendTo('#update-error p');
|
||||
wcs_ajax_update_error( $('#update-error p').html() );
|
||||
}
|
||||
});
|
||||
error: function ( results, status, errorThrown ) {
|
||||
$(
|
||||
'<br/><span>Error: ' +
|
||||
results.status +
|
||||
' ' +
|
||||
errorThrown +
|
||||
'</span>'
|
||||
).appendTo( '#update-error p' );
|
||||
wcs_ajax_update_error( $( '#update-error p' ).html() );
|
||||
},
|
||||
} );
|
||||
}
|
||||
function wcs_ajax_repair_subscriptions() {
|
||||
var start_time = new Date();
|
||||
|
|
@ -132,56 +172,77 @@ jQuery(document).ready(function($){
|
|||
upgrade_start_time = start_time;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: wcs_update_script_data.ajax_url,
|
||||
$.ajax( {
|
||||
url: wcs_update_script_data.ajax_url,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'wcs_upgrade',
|
||||
action: 'wcs_upgrade',
|
||||
upgrade_step: 'subscription_dates_repair',
|
||||
nonce: wcs_update_script_data.upgrade_nonce
|
||||
nonce: wcs_update_script_data.upgrade_nonce,
|
||||
},
|
||||
success: function(results) {
|
||||
if('success'==results.status){
|
||||
success: function ( results ) {
|
||||
if ( 'success' == results.status ) {
|
||||
var end_time = new Date(),
|
||||
execution_time = Math.ceil( ( end_time.getTime() - start_time.getTime() ) / 1000 );
|
||||
execution_time = Math.ceil(
|
||||
( end_time.getTime() - start_time.getTime() ) / 1000
|
||||
);
|
||||
|
||||
$('#update-messages ol').append($('<li />').text(results.message.replace('{execution_time}',execution_time)));
|
||||
$( '#update-messages ol' ).append(
|
||||
$( '<li />' ).text(
|
||||
results.message.replace(
|
||||
'{execution_time}',
|
||||
execution_time
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
wcs_update_script_data.subscription_count -= results.repaired_count;
|
||||
wcs_update_script_data.subscription_count -= results.unrepaired_count;
|
||||
wcs_update_script_data.subscription_count -=
|
||||
results.repaired_count;
|
||||
wcs_update_script_data.subscription_count -=
|
||||
results.unrepaired_count;
|
||||
|
||||
if( parseInt(wcs_update_script_data.subscription_count) <= 0 ) {
|
||||
if (
|
||||
parseInt( wcs_update_script_data.subscription_count ) <=
|
||||
0
|
||||
) {
|
||||
wcs_ajax_update_complete();
|
||||
} else {
|
||||
wcs_ajax_update_estimated_time(results.time_message);
|
||||
wcs_ajax_update_estimated_time( results.time_message );
|
||||
wcs_ajax_repair_subscriptions();
|
||||
}
|
||||
} else {
|
||||
wcs_ajax_update_error(results.message);
|
||||
wcs_ajax_update_error( results.message );
|
||||
}
|
||||
},
|
||||
error: function(results,status,errorThrown){
|
||||
$('<br/><span>Error: ' + results.status + ' ' + errorThrown + '</span>').appendTo('#update-error p');
|
||||
wcs_ajax_update_error( $('#update-error p').html() );
|
||||
}
|
||||
});
|
||||
error: function ( results, status, errorThrown ) {
|
||||
$(
|
||||
'<br/><span>Error: ' +
|
||||
results.status +
|
||||
' ' +
|
||||
errorThrown +
|
||||
'</span>'
|
||||
).appendTo( '#update-error p' );
|
||||
wcs_ajax_update_error( $( '#update-error p' ).html() );
|
||||
},
|
||||
} );
|
||||
}
|
||||
function wcs_ajax_update_complete() {
|
||||
$('#update-ajax-loader, #estimated_time').slideUp(function(){
|
||||
$('#update-complete').slideDown();
|
||||
});
|
||||
$( '#update-ajax-loader, #estimated_time' ).slideUp( function () {
|
||||
$( '#update-complete' ).slideDown();
|
||||
} );
|
||||
}
|
||||
function wcs_ajax_update_error(message) {
|
||||
function wcs_ajax_update_error( message ) {
|
||||
message = message || '';
|
||||
if ( message.length > 0 ){
|
||||
$('#update-error p').html(message);
|
||||
if ( message.length > 0 ) {
|
||||
$( '#update-error p' ).html( message );
|
||||
}
|
||||
$('#update-ajax-loader, #estimated_time').slideUp(function(){
|
||||
$('#update-error').slideDown();
|
||||
});
|
||||
$( '#update-ajax-loader, #estimated_time' ).slideUp( function () {
|
||||
$( '#update-error' ).slideDown();
|
||||
} );
|
||||
}
|
||||
function wcs_ajax_update_estimated_time(message) {
|
||||
var total_updated = total_subscriptions - wcs_update_script_data.subscription_count,
|
||||
function wcs_ajax_update_estimated_time( message ) {
|
||||
var total_updated =
|
||||
total_subscriptions - wcs_update_script_data.subscription_count,
|
||||
now = new Date(),
|
||||
execution_time,
|
||||
time_per_update,
|
||||
|
|
@ -189,18 +250,27 @@ jQuery(document).ready(function($){
|
|||
time_left_minutes,
|
||||
time_left_seconds;
|
||||
|
||||
execution_time = Math.ceil( ( now.getTime() - upgrade_start_time.getTime() ) / 1000 );
|
||||
execution_time = Math.ceil(
|
||||
( now.getTime() - upgrade_start_time.getTime() ) / 1000
|
||||
);
|
||||
time_per_update = execution_time / total_updated;
|
||||
|
||||
time_left = Math.floor( wcs_update_script_data.subscription_count * time_per_update );
|
||||
time_left = Math.floor(
|
||||
wcs_update_script_data.subscription_count * time_per_update
|
||||
);
|
||||
time_left_minutes = Math.floor( time_left / 60 );
|
||||
time_left_seconds = time_left % 60;
|
||||
|
||||
$('#estimated_time').html(message.replace( '{time_left}', time_left_minutes + ":" + zeropad(time_left_seconds) ));
|
||||
$( '#estimated_time' ).html(
|
||||
message.replace(
|
||||
'{time_left}',
|
||||
time_left_minutes + ':' + zeropad( time_left_seconds )
|
||||
)
|
||||
);
|
||||
}
|
||||
function zeropad(number) {
|
||||
function zeropad( number ) {
|
||||
var pad_char = 0,
|
||||
pad = new Array(3).join(pad_char);
|
||||
return (pad + number).slice(-pad.length);
|
||||
pad = new Array( 3 ).join( pad_char );
|
||||
return ( pad + number ).slice( -pad.length );
|
||||
}
|
||||
});
|
||||
} );
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
<?php return array('dependencies' => array('react', 'react-dom', 'wp-api-fetch', 'wp-components', 'wp-i18n', 'wp-primitives'), 'version' => '8bad0ce18409676b15d1');
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.wcs-recurring-totals-panel{padding:1em 0 0;position:relative}.wcs-recurring-totals-panel:after{border-style:solid;border-width:1px 0;bottom:0;content:"";display:block;right:0;opacity:.3;pointer-events:none;position:absolute;left:0;top:0}.wcs-recurring-totals-panel+.wcs-recurring-totals-panel:after{border-top-width:0}.wcs-recurring-totals-panel .wc-block-components-panel .wc-block-components-totals-item{padding-right:0;padding-left:0}.wcs-recurring-totals-panel .wc-block-components-totals-item__label:first-letter{text-transform:capitalize}.wcs-recurring-totals-panel .wcs-recurring-totals-panel__title .wc-block-components-totals-item__label{font-weight:500}.wcs-recurring-totals-panel__title{margin:0}.wc-block-components-main .wcs-recurring-totals-panel__details{padding:0 16px}.wcs-recurring-totals-panel__details .wc-block-components-panel__button,.wcs-recurring-totals-panel__details .wc-block-components-panel__button:focus,.wcs-recurring-totals-panel__details .wc-block-components-panel__button:hover{font-size:.875em}.wcs-recurring-totals-panel__details .wc-block-components-panel__content>.wc-block-components-totals-item:first-child{margin-top:0}.wcs-recurring-totals-panel__details .wc-block-components-panel__content>.wc-block-components-totals-item:last-child{margin-bottom:0}.wcs-recurring-totals-panel__details .wcs-recurring-totals-panel__details-total .wc-block-components-totals-item__label{font-weight:700}.wcs-recurring-totals__subscription-length{float:left}
|
||||
.wc-block-components-local-pickup-rates-control .wc-block-components-local-pickup-select:not(:last-child){margin-bottom:16px}
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
<?php return array('dependencies' => array('react', 'wc-blocks-checkout', 'wc-blocks-data-store', 'wc-price-format', 'wc-settings', 'wp-data', 'wp-element', 'wp-i18n', 'wp-plugins'), 'version' => '32bfa486d902830f29c7');
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.wcs-recurring-totals-panel{padding:1em 0 0;position:relative}.wcs-recurring-totals-panel:after{border-style:solid;border-width:1px 0;bottom:0;content:"";display:block;left:0;opacity:.3;pointer-events:none;position:absolute;right:0;top:0}.wcs-recurring-totals-panel+.wcs-recurring-totals-panel:after{border-top-width:0}.wcs-recurring-totals-panel .wc-block-components-panel .wc-block-components-totals-item{padding-left:0;padding-right:0}.wcs-recurring-totals-panel .wc-block-components-totals-item__label:first-letter{text-transform:capitalize}.wcs-recurring-totals-panel .wcs-recurring-totals-panel__title .wc-block-components-totals-item__label{font-weight:500}.wcs-recurring-totals-panel__title{margin:0}.wc-block-components-main .wcs-recurring-totals-panel__details{padding:0 16px}.wcs-recurring-totals-panel__details .wc-block-components-panel__button,.wcs-recurring-totals-panel__details .wc-block-components-panel__button:focus,.wcs-recurring-totals-panel__details .wc-block-components-panel__button:hover{font-size:.875em}.wcs-recurring-totals-panel__details .wc-block-components-panel__content>.wc-block-components-totals-item:first-child{margin-top:0}.wcs-recurring-totals-panel__details .wc-block-components-panel__content>.wc-block-components-totals-item:last-child{margin-bottom:0}.wcs-recurring-totals-panel__details .wcs-recurring-totals-panel__details-total .wc-block-components-totals-item__label{font-weight:700}.wcs-recurring-totals__subscription-length{float:right}
|
||||
.wc-block-components-local-pickup-rates-control .wc-block-components-local-pickup-select:not(:last-child){margin-bottom:16px}
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
.woocommerce-subscriptions-announcement__container{border-radius:2px;bottom:44px;cursor:default;display:inline;position:fixed;left:16px;z-index:9999}.woocommerce-subscriptions-announcement .woocommerce-tour-kit-step{box-shadow:0 2px 3px 0 rgba(0,0,0,.05),0 4px 5px 0 rgba(0,0,0,.04),0 4px 5px 0 rgba(0,0,0,.03),0 16px 16px 0 rgba(0,0,0,.02)}.woocommerce-subscriptions-announcement .woocommerce-tour-kit-step .components-elevation{display:none}.woocommerce-subscriptions-announcement .woocommerce-tour-kit-step__header{position:absolute}.woocommerce-subscriptions-announcement .woocommerce-tour-kit-step__header-image{background-color:#f2edff;border-radius:0;height:140px;padding:18px}.woocommerce-subscriptions-announcement .woocommerce-tour-kit-step__header-image img{margin:0 auto;max-width:100%;width:120px!important}.woocommerce-subscriptions-announcement .woocommerce-tour-kit-step__body{padding-top:8px!important}.woocommerce-subscriptions-announcement .woocommerce-tour-kit-step-navigation{justify-content:end!important}
|
||||
|
|
@ -0,0 +1 @@
|
|||
.woocommerce-subscriptions-announcement__container{border-radius:2px;bottom:44px;cursor:default;display:inline;position:fixed;right:16px;z-index:9999}.woocommerce-subscriptions-announcement .woocommerce-tour-kit-step{box-shadow:0 2px 3px 0 rgba(0,0,0,.05),0 4px 5px 0 rgba(0,0,0,.04),0 4px 5px 0 rgba(0,0,0,.03),0 16px 16px 0 rgba(0,0,0,.02)}.woocommerce-subscriptions-announcement .woocommerce-tour-kit-step .components-elevation{display:none}.woocommerce-subscriptions-announcement .woocommerce-tour-kit-step__header{position:absolute}.woocommerce-subscriptions-announcement .woocommerce-tour-kit-step__header-image{background-color:#f2edff;border-radius:0;height:140px;padding:18px}.woocommerce-subscriptions-announcement .woocommerce-tour-kit-step__header-image img{margin:0 auto;max-width:100%;width:120px!important}.woocommerce-subscriptions-announcement .woocommerce-tour-kit-step__body{padding-top:8px!important}.woocommerce-subscriptions-announcement .woocommerce-tour-kit-step-navigation{justify-content:end!important}
|
||||
|
|
@ -0,0 +1 @@
|
|||
.wcsg-gifting-to-container-editing{display:flex;gap:5px;margin-top:10px;min-width:210px}.wcsg-gifting-to-container-editing .wc-block-components-text-input{flex-grow:1;margin-top:0}.wcsg-gifting-to-container-editing .wp-element-button.gifting-update-button:not(.is-link){min-height:unset;padding:0 var(--xs,20px)}.wcsg-gifting-to-container-editing .components-base-control__field{margin-bottom:0}.wcsg-gifting-to-container-editing .wc-block-components-text-input input:-webkit-autofill{padding-top:.5em}.wcsg-gifting-to-container-editing .has-error .components-text-control__input{color:var(--wc-red,#cc1818)}.wp-block-woocommerce-mini-cart-contents .wcsg-gifting-to-container-editing{flex-direction:column}.wp-block-woocommerce-mini-cart-contents .gifting-update-button{height:40px}.wcsg-gifting-to-container-view{align-items:center;display:flex;flex-wrap:wrap;gap:5px;max-width:100%}.wcsg-gifting-to-container-view span{align-items:center;display:inline-flex;max-width:100%;word-break:break-all;word-wrap:break-word;gap:5px}.wcsg-gifting-to-container-view .components-button.is-link{color:var(--wp--preset--color--contrast);flex-shrink:0;font-size:medium}.wc-block-checkout,.wc-block-components-product-details__gifting-to{align-items:center;display:flex;flex-wrap:wrap;max-width:100%}.wc-block-checkout .wc-block-components-product-details__name:after,.wc-block-components-product-details__gifting-to .wc-block-components-product-details__name:after{content:" "}.wc-block-checkout .wc-block-components-product-details__value,.wc-block-components-product-details__gifting-to .wc-block-components-product-details__value{align-items:center;display:inline-flex;max-width:100%;word-break:break-all;word-wrap:break-word}.wcsg-block-recipient-container{font-size:var(--wp--preset--font-size--small,14px);line-height:20px}.wcsg-block-recipient-container .components-checkbox-control__label{font-size:var(--wp--preset--font-size--small,14px);margin-bottom:0}.wc-block-cart .wc-block-components-product-details__gifting-to,.wc-block-cart .wc-block-components-product-details__gifting-to-hidden,.wc-block-cart .wc-block-components-product-details__item-key,.wc-block-checkout .wc-block-components-product-details__gifting-to-hidden,.wc-block-checkout .wc-block-components-product-details__item-key,.wp-block-woocommerce-mini-cart-contents .wc-block-components-product-details__gifting-to,.wp-block-woocommerce-mini-cart-contents .wc-block-components-product-details__gifting-to-hidden,.wp-block-woocommerce-mini-cart-contents .wc-block-components-product-details__item-key{display:none}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<?php return array('dependencies' => array('react', 'react-dom', 'wc-blocks-checkout', 'wp-components', 'wp-data', 'wp-i18n', 'wp-plugins', 'wp-url'), 'version' => 'cf4dd71c0c7418b591fd');
|
||||
|
|
@ -0,0 +1 @@
|
|||
.wcsg-gifting-to-container-editing{display:flex;gap:5px;margin-top:10px;min-width:210px}.wcsg-gifting-to-container-editing .wc-block-components-text-input{flex-grow:1;margin-top:0}.wcsg-gifting-to-container-editing .wp-element-button.gifting-update-button:not(.is-link){min-height:unset;padding:0 var(--xs,20px)}.wcsg-gifting-to-container-editing .components-base-control__field{margin-bottom:0}.wcsg-gifting-to-container-editing .wc-block-components-text-input input:-webkit-autofill{padding-top:.5em}.wcsg-gifting-to-container-editing .has-error .components-text-control__input{color:var(--wc-red,#cc1818)}.wp-block-woocommerce-mini-cart-contents .wcsg-gifting-to-container-editing{flex-direction:column}.wp-block-woocommerce-mini-cart-contents .gifting-update-button{height:40px}.wcsg-gifting-to-container-view{align-items:center;display:flex;flex-wrap:wrap;gap:5px;max-width:100%}.wcsg-gifting-to-container-view span{align-items:center;display:inline-flex;max-width:100%;word-break:break-all;word-wrap:break-word;gap:5px}.wcsg-gifting-to-container-view .components-button.is-link{color:var(--wp--preset--color--contrast);flex-shrink:0;font-size:medium}.wc-block-checkout,.wc-block-components-product-details__gifting-to{align-items:center;display:flex;flex-wrap:wrap;max-width:100%}.wc-block-checkout .wc-block-components-product-details__name:after,.wc-block-components-product-details__gifting-to .wc-block-components-product-details__name:after{content:" "}.wc-block-checkout .wc-block-components-product-details__value,.wc-block-components-product-details__gifting-to .wc-block-components-product-details__value{align-items:center;display:inline-flex;max-width:100%;word-break:break-all;word-wrap:break-word}.wcsg-block-recipient-container{font-size:var(--wp--preset--font-size--small,14px);line-height:20px}.wcsg-block-recipient-container .components-checkbox-control__label{font-size:var(--wp--preset--font-size--small,14px);margin-bottom:0}.wc-block-cart .wc-block-components-product-details__gifting-to,.wc-block-cart .wc-block-components-product-details__gifting-to-hidden,.wc-block-cart .wc-block-components-product-details__item-key,.wc-block-checkout .wc-block-components-product-details__gifting-to-hidden,.wc-block-checkout .wc-block-components-product-details__item-key,.wp-block-woocommerce-mini-cart-contents .wc-block-components-product-details__gifting-to,.wp-block-woocommerce-mini-cart-contents .wc-block-components-product-details__gifting-to-hidden,.wp-block-woocommerce-mini-cart-contents .wc-block-components-product-details__item-key{display:none}
|
||||
|
|
@ -1,5 +1,980 @@
|
|||
*** WooCommerce Subscriptions Changelog ***
|
||||
|
||||
2026-01-06 - version 8.3.0
|
||||
* Add: Support for ajax add-to-cart flows from the single product page, when upgrading or downgrading subscriptions.
|
||||
* Add: Enhanced functionality from the WooCommerce Subscription Downloads plugin has now been integrated directly into WooCommerce Subscriptions.
|
||||
* Fix: Ensured the subject line for the "Customer Renewal Invoice" email correctly updates.
|
||||
|
||||
2025-12-22 - version 8.2.1
|
||||
* Fix: Subscription pricing strings now correctly display singular and plural billing intervals like "$10 / month" and "$10 every 3 months" in cart and checkout totals.
|
||||
* Fix: Display all recurring coupon names in the totals section of classic cart and checkout when multiple recurring coupons are applied.
|
||||
* Fix: Payment method selector options on Subscriptions list page are now properly displaying the payment method name for gateways providing multiple payment methods.
|
||||
* Fix: PayPal Standard plugin IPN renewal orders not being created for failed payment retries.
|
||||
* Fix: Prevent fatal errors on shop pages when special characters like % are used in a product name.
|
||||
* Fix: Prevent an email validation error from briefly and inadvertently flashing on the screen when the "this is a gift" checkbox is used.
|
||||
|
||||
2025-12-10 - version 8.2.0
|
||||
* Fix: Various accessibility issues.
|
||||
* Added button roles.
|
||||
* Added labels to remove product links.
|
||||
* Adding missing header labels on related orders table.
|
||||
* Prevented various hidden elements from being exposed to screen readers.
|
||||
* Fixed focus problems for the renewal dialog.
|
||||
* Fixed focus when closing modals.
|
||||
* Fixed toggle state change using spacebar.
|
||||
* Fixed label for sign up now button.
|
||||
* Fixed toggle aria-label text to reflect state.
|
||||
* Added aria-haspopup attribute to buttons that open modals.
|
||||
* Fixed link for view order aria-label.
|
||||
* Fixed renew now button text and aria-label.
|
||||
* Added aria-modal and role attributes to modals and dialogs.
|
||||
* Fix: Resubscribe button now appears correctly for cancelled limited subscriptions.
|
||||
* Fix: Missing translation for pricing string on languages without plural form.
|
||||
* Fix: Customized subscriptions links in WooCommerce emails now work correctly.
|
||||
* Fix: Possible fatal errors when switching grouped subscriptions.
|
||||
* Fix: Prevent unnecessary log entries related to gifted subscriptions.
|
||||
* Fix: Make it easier for translation plugins to respect individual customer language preferences when sending subscription emails.
|
||||
* Fix: Prevent recurring local pickup options from displaying for virtual subscriptions when the cart contains physical products.
|
||||
* Fix: Prevent shipping summary from displaying on Blocks Checkout for virtual subscriptions.
|
||||
* Fix: Item price in blocks checkout is correctly displayed without "due today" words for items with zero sign up fee.
|
||||
|
||||
2025-11-13 - version 8.1.0
|
||||
* Fix: Prevent a fatal error that can occur when previewing emails in WooCommerce email settings.
|
||||
* Fix: Prevent technical subscription-specific discount types from appearing in the coupon edit UI.
|
||||
* Fix: Add admin notice and debug tool to recreate the payment retry database table when it's missing.
|
||||
* Fix: Prevent resubscribe and reactivate buttons from showing for subscriptions containing disabled, deleted, draft, or limited products.
|
||||
* Fix: Prevent error that blocks the subscriptions list from loading when gifted subscriptions are purchased with certain payment methods.
|
||||
* Fix: Ensure gift recipient email address is displayed in the order confirmation email sent to the purchaser.
|
||||
* Fix: Prevent pickup location options from displaying incorrectly alongside shipping methods on the Blocks Checkout page.
|
||||
* Fix: Fix invalid HTML tags that cause fatal errors when using themes with WordPress Interactivity API.
|
||||
* Fix: Add password reset links to the new account email template for gifted subscription recipients.
|
||||
* Tweak: Improve the gift recipient email input field on Blocks Cart and Mini Cart to match the style of other checkout fields.
|
||||
* Dev: Improve housekeeping of output buffers when handling requests to change subscription payment methods.
|
||||
* Dev: Improve type safety in relation to the `wcs_get_subscription()` function.
|
||||
|
||||
2025-10-15 - version 8.0.0
|
||||
* Add: Blocks Checkout now displays a single shipping method selection for both initial and recurring carts, similar to Classic Checkout. This can be disabled using the `wcs_cart_totals_shipping_html_price_only` filter.
|
||||
* Fix: Resubscribing to a limited subscription from the product page will now reactivate the existing subscription.
|
||||
* Fix: "New initial order - Recipient" email template preview no longer causes critical error.
|
||||
* Fix: Shipping value on blocks checkout no longer shows as "Free" when no shipping method is available.
|
||||
* Dev: Improved data telemetry which is sent to WooCommerce:
|
||||
* WooCommerce Subscriptions setting values
|
||||
* Order totals of subscription-related orders by order type and payment gateway, aggregated by month
|
||||
* Number of subscription-related orders by order type and payment gateway, aggregated by month
|
||||
* Number of products in parent subscription orders, aggregated by month
|
||||
* Number of non-zero value parent subscription orders, aggregated by month
|
||||
* Number of subscription products by billing interval
|
||||
* Number of giftable subscription products
|
||||
* Number of subscriptions by payment method, billing period, and renewal type
|
||||
* Number of gifted subscriptions
|
||||
* Number of subscribers
|
||||
* Purchase event for subscription products
|
||||
* Exclude orders with a "processing" status from the "subscription_orders" telemetry data.
|
||||
|
||||
2025-09-16 - version 7.9.0
|
||||
* Add: REST API endpoint to retrieve subscriptions associated with a specific order ID.
|
||||
* Fix: Keep subscription with successful renewals active when parent order fails.
|
||||
* Fix: Error when renewing subscriptions with downloadable gifts.
|
||||
* Fix: Improve performance by loading assets only when necessary.
|
||||
* Fix: "Update cart" button being always enabled.
|
||||
* Fix: Gifting recipient email layout issues when using a long e-mail address.
|
||||
* Fix: Gifting checkbox not showing up on blocks cart when using WooCommerce 10.2+.
|
||||
* Fix: Deleting a gifted subscription recipient will not delete their subscriptions, they will be moved to the subscription purchaser instead.
|
||||
* Dev: The `wcs_get_subscriptions()` and `wcs_get_subscriptions_for_order()` functions are now stricter, and only return instances of `WC_Subscription`.
|
||||
|
||||
2025-09-02 - version 7.8.2
|
||||
* Fix: Error when updating/renewing subscriptions containing a pseudo coupon.
|
||||
* Fix: Fix gifting recipient notice being shown for non gifting orders.
|
||||
|
||||
2025-08-25 - version 7.8.1
|
||||
* Fix: Fix checkout button default label.
|
||||
* Fix: Fix error when WP CLI is enabled in some environments.
|
||||
|
||||
2025-08-19 - version 7.8.0
|
||||
* Add: Native support for subscriptions gifting. Gifting for WooCommerce Subscriptions extension is no longer required.
|
||||
* Add: Enable subscriptions gifting storewide and per product.
|
||||
* Add: Blocks support for gifting on product, cart, and checkout pages.
|
||||
* Update: Make WooCommerce Subscriptions reports compatible with High Performance Order Storage.
|
||||
* Update: Rename Subscribe now button to Add to Cart to follow WooCommerce convention.
|
||||
* Fix: Fix integration with WooCommerce dashboard widget.
|
||||
* Fix: Cancel pending related orders when a subscription is cancelled to prevent orphaned orders that need payment.
|
||||
* Fix: Allow manual payments for pending renewal orders of Product Bundles or Composite Products when Mixed Checkout options is disabled.
|
||||
* Dev: Update moment.js package to the latest version 2.30.1
|
||||
|
||||
2025-07-09 - version 7.7.0
|
||||
* Fix: Restores normal behavior for the report caching updates scheduled action, which was failing due to a bad filepath.
|
||||
* Fix: Fix error when placing an order with a valid card after using a declined one.
|
||||
* Fix: Fix order renewal errors when using some plugins.
|
||||
* Fix: Prevent fatal errors when loading or deleting subscriptions with corrupted date values stored in a database.
|
||||
* Fix: Prevent fatal errors when trying to save invalid date values and display better error messages instead.
|
||||
* Fix: Fix broken blocks and javascript translations.
|
||||
* Fix: Keep newly created subscriptions in pending status when initial payment fails instead of putting them on hold.
|
||||
* Dev: Improve test suite bootstrap process by postponing interactions with Action Scheduler until it has fully initialized.
|
||||
* Dev: Plugin upgrade routines will now be triggered by changes in the main plugin version (and not the core library version).
|
||||
* Dev: Update wcs_is_paypal_profile_a() so it handles non-string parameters more gracefully. This is principally to reduce noise levels when running the test suite.
|
||||
* Dev: Proactively reviewed and hardened code to improve our security posture going forward.
|
||||
|
||||
2025-06-11 - version 7.6.0
|
||||
* Update: Allow updating billing info on existing subscriptions when customer changes account details.
|
||||
* Fix: Use floats instead of integers for tax calculation in subscription switching when prices include tax.
|
||||
* Fix: Enhance checks in email templates upgrade script for WooCommerce Subscriptions core 8.3.0.
|
||||
* Dev: Refactor argument processing in run-tests.sh to add a direct check for the -w flag.
|
||||
* Fix: Undefined array key `new`
|
||||
* Fix: Ensure subscription exists before updating its last order date.
|
||||
* Fix: Prevent fatal error when the line items no longer exist in the database.
|
||||
|
||||
2025-05-20 - version 7.5.0
|
||||
* Update: Add pagination to the related orders list in the My Account > View Subscription page.
|
||||
* Fix: Infinite loop when trying to load subscription related orders meta after cache busting.
|
||||
* Fix: Replace blogname with site_title on translation files.
|
||||
* Dev: Make the `wcs_get_subscriptions_for_order()` function more robust. It should not return any subscriptions if presented with an invalid order.
|
||||
|
||||
2025-04-14 - version 7.4.0
|
||||
* Update: Increase the number of args accepted by wcs_get_subscriptions(), to bring about parity with wc_get_orders().
|
||||
* Dev: Update wcs_maybe_prefix_key() and wcs_maybe_unprefix_key() to support an array of keys.
|
||||
* Fix: Prevent sending renewal reminders for orders with a 0 total.
|
||||
* Fix: Ensure the second parameter passed to the 'get_edit_post_link' filter is an integer.
|
||||
* Fix: Prevent WooCommerce Subscriptions buttons from overflowing in the View subscription page
|
||||
* Dev: Update subscriptions-core to 8.2.0
|
||||
|
||||
2025-03-31 - version 7.3.1
|
||||
* Update: Display the subscription parent order icon in the WooCommerce Orders list table by default.
|
||||
* Dev: Update subscriptions-core to 8.1.1
|
||||
|
||||
2025-03-24 - version 7.3.0
|
||||
* Tweak: Adjust the warning that appears on the reports page when HPOS is enabled but Compatibility Mode is disabled (provide a direct link to the relevant settings).
|
||||
* Update: Improved subscription search performance for WP Post stores by removing unnecessary _order_key and _billing_email meta queries.
|
||||
* Update: Make it possible to dispatch the Cancelled Subscription email more than once (when initially set to pending-cancellation, and again when it reaches final cancellation).
|
||||
* Update: Reduced duplicate queries when fetching multiple subscription related orders types.
|
||||
* Update: Removed unnecessary get_time() calls to reduce redundant get_last_order() queries in the Subscriptions list table.
|
||||
* Update: Improved performance on the Orders list table when rendering the Subscription Relationship column.
|
||||
* Update: Improved performance of the Generate Related Order Cache tool found under WooCommerce > Status > Tools.
|
||||
* Fix: Do not schedule updates to the report data cache if reporting is not available (ie, if HPOS is enabled but Compatibility Mode is not).
|
||||
* Fix: Resolved deprecated dynamic property warnings in WCS_Email_Payment_Retry and WCS_Email_Customer_Payment_Retry.
|
||||
* Fix: Added support for previewing payment retry emails in WooCommerce email settings.
|
||||
* Fix: Updated subscription email item table template to align with WooCommerce 9.7 email improvements.
|
||||
* Fix: Prevent PHP warning on cart page shipping method updates by removing unused method: maybe_restore_shipping_methods.
|
||||
* Fix: Removed unnecessary setting of renewal order paid date on status transition, relying on WooCommerce core behavior instead.
|
||||
* Fix: Ensure the order_awaiting_payment session arg is restored when loading a renewal cart from the session to prevent duplicate orders.
|
||||
* Fix: Ensure custom placeholders (time_until_renewal, customers_first_name) are included in customer notification email previews.
|
||||
* Fix: For stores with HPOS + compatibility mode enabled, using the bulk delete related orders cache tool was not correctly deleting the meta from the WP Posts table.
|
||||
* Fix: Prevent empty strings being saved in related orders cache ID meta when backfilling order data to the WP Posts table.
|
||||
* Fix: Correctly load product names with HTML on the cart and checkout shipping rates.
|
||||
* Dev: Fix Node version mismatch between package.json and .nvmrc (both are now set to v16.17.1).
|
||||
* Dev: Update subscriptions-core to 8.1.0
|
||||
|
||||
2025-02-13 - version 7.2.1
|
||||
* Fix: Revert a change released in 7.2.0 which triggered the "woocommerce_cart_item_name" filter with the wrong number of parameters.
|
||||
* Dev: Update subscriptions-core to 8.0.1.
|
||||
|
||||
2025-02-13 - version 7.2.0
|
||||
* Fix: In the system status report, show correct icon in the Cache Update Failures section when the total failures is 0.
|
||||
* Fix: Recommend WooPayments when there is no available payment gateway.
|
||||
* Fix: Safeguards added to the Subscriptions Totals template used in the My Account area, to guard against fatal errors that could arise in unusual conditions.
|
||||
* Fix: Correctly load product names with HTML on the cart and checkout shipping rates.
|
||||
* Fix: Improve our admin notice handling to ensure notices are displayed to the intended admin user.
|
||||
* Fix: Improve protections around the pending renewal order-creation process, to prevent uncaught exceptions and other errors from breaking the subscription editor.
|
||||
* Fix: Prevent unnecessary subscription object lookups on non-subscription admin pages when global $post is set.
|
||||
* Fix: After enabling the Customer Notification feature or changing the reminder timing setting, ensure notifications are correctly scheduled for all subscriptions with pending cancellation status.
|
||||
* Update: Improve performance of displaying recurring coupon limits in the admin coupon list table by optimizing coupon object retrieval.
|
||||
* Update: Display the subscription items table in all customer notification emails.
|
||||
* Update: Subscription notes for unsent customer notification emails now only occurs if WCS_DEBUG is enabled.
|
||||
* Dev: Introduces a new `woocommerce_subscription_valid_customer_notification_types` filter to modify which notification types are scheduled in Action Scheduler.
|
||||
* Dev: Update subscriptions-core to 8.0.0.
|
||||
|
||||
2025-01-10 - version 7.1.0
|
||||
* Add: Compatibility with WooCommerce's new preview email feature introduced in 9.6.
|
||||
* Fix: Prevents PHP fatal error when wcs_can_user_renew_early() is called with a non-existent subscription ID.
|
||||
* Fix: Prevent payment gateways that complete Change Payment requests asynchronously from blocking future attempts to update payment methods for all subscriptions.
|
||||
* Fix: Resolved an issue that could lead to undefined variable when attempting to send a customer notification email with no order.
|
||||
* Fix: Prevents the customer's postal code being used as their billing city in the current session, following a change to payment details.
|
||||
* Dev: Use the subscription's customer ID during the `wcs_can_user_renew_early()` function call when sending customer notification emails.
|
||||
* Dev: Fix PHP deprecated warnings caused by calling esc_url with null.
|
||||
* Dev: Update subscriptions-core to 7.9
|
||||
|
||||
2024-12-16 - version 7.0.0
|
||||
* Fix: Prevent failing non-recent renewal order from suspending the subscription and marking the most recent renewal order as failed.
|
||||
* Fix: Use block theme-styled buttons for subscription and related-orders actions on My Account pages.
|
||||
* Fix: Subscription totals not properly updating when customers remove items via the My Account > View Subscription page on some stores with caching enabled.
|
||||
* Fix: Resolved unexpected errors during the renewal process when a subscription contains metadata with key "id".
|
||||
* Update: Changed the link on the order thank-you page to take customers directly to their "My Account > Subscriptions" page.
|
||||
* Dev: Update subscriptions-core to 7.8.0
|
||||
|
||||
2024-11-27 - version 6.9.1
|
||||
* Fix: Resolved compatibility issues with WordPress 6.7 caused by translating strings too early.
|
||||
* Dev: Update subscriptions-core to 7.7.2
|
||||
|
||||
2024-11-14 - version 6.9.0
|
||||
* Add: New Customer Notification feature - sends reminder emails for upcoming subscription renewals, trials ending, and subscription expirations.
|
||||
* Fix: Prevent adding products to the cart if a subscription renewal is already present.
|
||||
* Update: Improved performance of wcs_get_subscription() when querying by product and customer or order.
|
||||
* Update: Improved performance when checking limited subscription product availability.
|
||||
* Update: Deprecate upgrading from versions of WooCommerce Subscriptions prior to 3.0.0 (released Jan 2020).
|
||||
* Dev: Minor refactoring of `init` method in `WC_Subscriptions_Upgrader` class.
|
||||
* Dev: Introduce the filter `woocommerce_subscriptions_synced_first_renewal_payment_timestamp` to enable plugins to modify the first renewal date of synced subscriptions.
|
||||
* Dev: Update `get_post_meta()` calls to fetch product meta using CRUD getters.
|
||||
* Dev: Update subscriptions-core to 7.7.1
|
||||
|
||||
2024-10-14 - version 6.8.0
|
||||
* Fix: Restore Retry icon in Orders table for HPOS-enabled stores.
|
||||
* Fix: Correctly updates a subscription status to `cancelled` during a payment failure call when the current status is `pending-cancel`.
|
||||
* Fix: Clear the `cancelled_email_sent` meta when a subscription is reactivated to allow the customer to receive future cancellation emails.
|
||||
* Fix: Prevent deprecation notices after updating to WooCommerce 9.3.
|
||||
* Fix: Prevent PHP fatal error that occurs when calculating the total tax refunded on a subscription.
|
||||
* Dev: Only initialise the `WCS_WC_Admin_Manager` class on stores running WC 9.2 or older. This class handled integration with the Woo Navigation feature that was removed in WC 9.3.
|
||||
|
||||
2024-09-13 - version 6.7.0
|
||||
* Fix: Resolved two issues preventing the correct display of the "Subscription items can no longer be edited." message on the Edit Subscription page.
|
||||
* Fix: Checks for the existence of `wc_get_page_screen_id` before calling it on `wcs_get_page_screen_id`, and for the admin context before calling `list_table_primary_column` to prevent fatal errors.
|
||||
* Fix: Blocks the reactivation of a subscription when the end date is in the past.
|
||||
* Fix: Ensure a subscription's modified date is updated when its related order cache is updated on non-HPOS sites.
|
||||
* Fix: Ensure trial period form data is set before use to prevent fatal errors when the data is missing.
|
||||
* Fix: Resolved an error with coupon discount calculations for manual or early renewal orders on stores with tax-inclusive pricing.
|
||||
* Fix: Add the theme-compatible button class to the switch button on the My Account > Subscription page.
|
||||
* Dev: Removing the unused method `maybe_remove_formatted_order_total_filter` hooked to `woocommerce_get_formatted_order_total` which was deprecated.
|
||||
* Dev: Update subscriptions-core to 7.5.0
|
||||
|
||||
2024-09-05 - version 6.6.2
|
||||
* Fix: Prevent errors during checkout when a customer is switching their subscription product and does not require payment.
|
||||
* Dev: Update subscriptions-core to 7.4.3.
|
||||
|
||||
2024-08-27 - version 6.6.1
|
||||
* Fix: Resolved an issue where simple product prices were incorrectly set to $0 when purchasing subscriptions and simple products with a coupon in WC 9.2.
|
||||
* Dev: Update subscriptions-core to 7.4.2.
|
||||
|
||||
2024-08-22 - version 6.6.0
|
||||
* Fix: Resolve duplicate subscription creation and PHP warning when switching subscriptions with Prepaid for WooCommerce Subscriptions plugin active.
|
||||
* Fix: Switch calculations now exclude previous switch orders with a checkout-draft status.
|
||||
* Dev: Introduce new parameter to WC_Subscription::get_last_order() to enable filtering out orders with specific statuses.
|
||||
* Update: Schedule subscription-related events with a priority of 1 to allow for earlier execution within the Action Scheduler.
|
||||
* Fix: Ensure admin notices are displayed after performing bulk actions on subscriptions when HPOS is enabled.
|
||||
* Fix: Add a year to the next renewal date billing interval is 12 months or more for a synced subscription.
|
||||
* Dev: Added filter to enable overriding the total paid for current switch period.
|
||||
* Dev: Updated subscriptions-core to 7.4.1.
|
||||
|
||||
2024-07-16 - version 6.5.0
|
||||
* Add: Include trial_period, suspension_count, and requires_manual_renewal in the REST API response for subscriptions.
|
||||
* Update: When a renewal order's payment is skipped, include the order's current status in the note to help troubleshooting.
|
||||
* Fix: Label improvements on subscription and order page templates.
|
||||
* Fix: Fixed an issue with subscriptions containing multiple renewal orders to mark a random item as processing, instead of the last order.
|
||||
* Fix: Prevent errors from invalid subscription objects during customer payment method updates.
|
||||
* Dev: Updated subscriptions-core to 7.3.0.
|
||||
|
||||
2024-06-28 - version 6.4.1
|
||||
* Fix: Resolved errors preventing subscription-related webhooks from sending on WC 9.0 stores.
|
||||
|
||||
2024-06-13 - version 6.4.0
|
||||
* Add: New WP CLI support to manage subscriptions via command line.
|
||||
* Add: Introduce wc/v2 subscription REST API endpoints.
|
||||
* Fix: label improvement on my subscription page template.
|
||||
* Fix: Regenerate subscriptions related order caches (renewal, resubscribe, switch) if it's stored as an invalid value to prevent fatal errors.
|
||||
* Update: Show "FREE" instead of 0 when there is no shipping cost in the recurring totals section of the Cart and Checkout blocks (requires WooCommerce 9.0+).
|
||||
* Dev: New function wcs_set_recurring_item_total() to set line item totals that have been copied from an initial order to their proper recurring totals (i.e. remove sign-up fees).
|
||||
* Dev: Updated subscriptions-core to 7.2.0.
|
||||
|
||||
2024-05-24 - version 6.3.2
|
||||
* Fix: Prevent overriding line item totals provided in request data when creating Orders via the REST API.
|
||||
|
||||
2024-05-09 - version 6.3.1
|
||||
* Fix: Resolved an issue that caused "WC_DateTime could not be converted to int" warnings to occur on non-hpos sites while editing a subscription.
|
||||
|
||||
2024-05-09 - version 6.3.0
|
||||
* Fix: Uncaught exception from 'WCS_Related_Order_Store_Cached_CPT::delete_relations' when order is not an instance of WC_Order.
|
||||
* Fix: Include subscription sign-up fees on orders created via the REST API.
|
||||
* Fix: Add check to prevent fatal error in rsort and array_sum.
|
||||
* Fix: Use `add_to_cart_ajax_redirect` instead of the deprecated `redirect_ajax_add_to_cart` function as callback.
|
||||
* Fix: Filtered order links in subscription reports returns all the orders when HPOS is enabled.
|
||||
* Fix: Typo in confirmation alert when users remove an item from a subscription.
|
||||
* Fix: Ensure the scheduled sale price for subscription products ends at the end of the "to" day set in product settings.
|
||||
* Fix: Subscription table is empty in mobile view when HPOS is enabled.
|
||||
* Fix: WooCommerce page header is hidden when HPOS is enabled.
|
||||
* Fix: Subscription updated messages missing on the Edit Subscription page when HPOS is enabled.
|
||||
* Fix: Resolved an issue that prevented bulk actions from running on the Subscriptions list table when the table was filtered by date, payment method, product or customer.
|
||||
* Fix: Subscriptions created via the POST `/wc/v3/orders/{order_id}/subscriptions` endpoint shouldn't include sign-up fees and/or $0 trial periods in line item totals.
|
||||
* Update: Update the shipping method styling to apply borders to the highlighted shipping option in the Checkout block.
|
||||
* Update: Include a full stack trace in failed scheduled action logs to improve troubleshooting issues.
|
||||
* Update: Show notice about product being removed from the cart when a subscription is for disabled mixed checkout setting.
|
||||
* Update: Change plugin name back to WooCommerce Subscriptions.
|
||||
* Dev: Calling wcs_create_subscription() will no longer attempt to fetch a fresh instance of the subscription at the end. This is to prevent loading the subscription from the database potentially unnecessarily.
|
||||
* Dev: Updated subscriptions-core to 7.1.0.
|
||||
* Dev: Bump WooCommerce minimum required version to 7.9.0.
|
||||
|
||||
2024-04-11 - version 6.2.0
|
||||
* Add: Declare WooCommerce as a plugin dependency in the plugin header.
|
||||
* Fix: Ensure next payment dates are only extended when early renewal orders paid via the modal are fully paid. Prevents extending dates on authorized but not captured payments.
|
||||
* Fix: Updated the switching calculator to handle situations where an upgrade has a new price per day less than the old price per day. Previously this would result in a negative upgrade cost.
|
||||
* Fix: Update the failing payment method on a subscription when customers successfully pay for a failed renewal order via the block checkout.
|
||||
* Fix: Resolved an issue that would cause subscriptions to be directly cancelled by the WooCommerce process of automatically cancelling unpaid orders in-line with the hold stock setting.
|
||||
* Fix: Prevent duplicate status transition notes on subscriptions and potential infinite loops when processing subscription status transitions.
|
||||
* Fix: Resolved an issue that could lead to "Undefined array key 'order-received'" errors.
|
||||
* Fix: Resolved errors that could occur when paying for renewal orders via the checkout when the store has custom checkout fields.
|
||||
* Fix: Resolved database errors that would occur when ordering the subscriptions list table by the 'Last order date' on sites with HPOS enabled.
|
||||
* Dev: Introduced a new filter ('wcs_setup_cart_for_subscription_initial_payment') to enable third-party plugins to use the pay-for-order flow to complete a subscription's initial payment.
|
||||
* Dev: Updated subscriptions-core to 7.0.0.
|
||||
|
||||
2024-03-28 - version 6.1.0
|
||||
* Fix: Ensure the subscription renewal payment process doesn't attempt to reprocess previously paid renewal orders.
|
||||
* Fix: Resolved an issue where discounts, when reapplied to failed or manual subscription order payments, would incorrectly account for inclusive tax.
|
||||
* Fix: Resolved an issue that could cause an empty error notice to appear on the My Account > Payment methods page when a customer attempted to delete a token used by subscriptions.
|
||||
* Fix: Make sure we always clear the subscription object from cache after updating dates.
|
||||
* Fix: Use block theme styles for the 'Add to Cart' button on subscription product pages.
|
||||
* Fix: Customer notes not being saved on the Edit Subscription page for stores with HPOS enabled.
|
||||
* Fix: Ensure products that have a yearly billing period can choose a date that the subscription is synchronized to.
|
||||
* Fix: Improved alignment of subscription product pricing fields on the edit product screen for consistency with other fields.
|
||||
* Fix: Avoid setting empty meta keys on subscriptions when changing the customer's default payment method.
|
||||
* Fix: Use a more scalable way to filter the orders admin list table by parent orders on HPOS stores.
|
||||
* Fix: Resolved an issue that prevented subscription custom place order button labels from working on the block checkout.
|
||||
* Update: Change the update all subscriptions checkbox displayed on the change payment method page to be enabled by default.
|
||||
* Dev: Update subscriptions-core to 6.9.0
|
||||
|
||||
2024-02-08 - version 6.0.0
|
||||
* Add: Subscription plugin settings can now be fetched via the /wc/v3/settings/subscriptions REST API endpoint.
|
||||
* Fix: Trigger the subscription.updated webhook event for all subscription updates.
|
||||
* Fix: Block the UI after a customer clicks actions on the My Account > Subscriptions page to prevent multiple requests from being sent.
|
||||
* Fix: WC 8.6.0 compatibility: Resolved wc_get_log_file_path() deprecation warnings.
|
||||
* Dev: Update subscriptions-core to 6.8.0
|
||||
|
||||
2024-01-17 - version 5.9.1
|
||||
* Fix: Resolved an error that would occur with WooCommerce 8.5.0 when editing a subscription customer from the admin dashboard.
|
||||
|
||||
2024-01-11 - version 5.9.0
|
||||
* Fix: Resolved an issue that caused ordering the Admin Subscriptions List Table to not work when HPOS is enabled.
|
||||
* Fix: When switching all subscription items in the same cart, ensure the previous subscription is reused where possible instead of creating all new subscriptions.
|
||||
* Update: Changed the edit subscription product "Expire after" (Subscription length) so it more clearly describes when a subscription will automatically stop renewing.
|
||||
* Update: Log all exceptions caught by WooCommerce while processing a subscription scheduled action.
|
||||
* Dev: Update subscriptions-core to 6.7.0.
|
||||
|
||||
2023-12-21 - version 5.8.0
|
||||
* Fix: Incorrect switch calculation when upgrading a synchronised product before the first renewal is due where the initial payment was prorated.
|
||||
* Fix: When updating a subscription via the REST API, don't update the start date to the current time if the start date is not set in the request.
|
||||
* Fix: When using the checkout block to pay for renewal orders, ensure the order's cart hash is updated to make sure the existing order can be used.
|
||||
* Fix: Prevents a PHP fatal error that occurs when the cart contains a renewal order item that no longer exists.
|
||||
* Fix: When HPOS is enabled and data compatibility mode is turned on, make sure subscription date changes made to postmeta are synced to orders_meta table.
|
||||
* Fix: Resolved an issue that would cause undefined $current_page, $max_num_pages, and $paginate variable errors when viewing a page with the subscriptions-shortcode.
|
||||
* Dev: Update subscriptions-core to 6.6.0.
|
||||
|
||||
2023-11-09 - version 5.7.0
|
||||
* Add: Introduce a new REST API endpoint to create subscription(s) from a given order. This endpoint is available at POST `/wc/v3/orders/{order_id}/subscriptions` and accepts a single order ID.
|
||||
* Add: When a customer toggles automatic renewals on or off via their My Account page, add a note to the subscription to record that event.
|
||||
* Fix: Set and update a subscription's trial end date via the REST API.
|
||||
* Fix: Ensure the `woocommerce_subscription_renewal_payment_complete` hook is always triggered when processing early renewals via the checkout page (introduced in 5.6.0).
|
||||
* Fix: When a subscription is flagged as requiring manual payments, allow admin users to turn on automatic payments for a subscription via the Edit Subscription page by selecting a new payment method.
|
||||
* Fix: When processing an early renewal order, make sure the suspension count is reset back to 0 on payment complete.
|
||||
* Fix: Ensure proper backfilling of subscription metadata (i.e. dates and cache) to the postmeta table when HPOS is enabled and compatibility mode (data syncing) is turned on.
|
||||
* Fix: Fetch and update the `_cancelled_email_sent` meta in a HPOS compatibile way.
|
||||
* Dev: Introduce a new wcs_get_subscription_grouping_key() function to generate a unique key for a subscription based on its billing schedule. This function uses the existing recurring cart key concept.
|
||||
* Dev: Deprecate the WC_Subscriptions_Synchroniser::add_to_recurring_cart_key(). Use WC_Subscriptions_Synchroniser::add_to_recurring_product_grouping_key() instead.
|
||||
* Dev: Update subscriptions-core to 6.5.0.
|
||||
|
||||
2023-10-18 - version 5.6.0
|
||||
* Add: Introduce the "Subscription Relationship" column under the Orders list admin page when HPOS is enabled.
|
||||
* Add: Use admin theme color and the correct WooCommerce colors.
|
||||
* Fix: Resolved an issue that caused subscriptions to go on-hold when a customer fails or abandons an early renewal order payment.
|
||||
* Fix: Resolved an issue that caused subscriptions with an unpaid early renewal order to be incorrectly considered as needing payment.
|
||||
* Fix: When HPOS is enabled, make the orders_by_type_query filter box work in the WooCommerce orders screen.
|
||||
* Fix: Ensure renewal orders paid via the Block Checkout are correctly linked to their subscription.
|
||||
* Fix: Resolved an issue that caused paying for failed/pending parent orders that include Product Add-ons to not calculate the correct total.
|
||||
* Fix: Ensure the order needs processing transient is deleted when a subscription order (eg renewal) is created. Fixes issues with renewal orders going straight to a completed status.
|
||||
* Fix: Store the correct subscription start date in postmeta and ordermeta when HPOS and data syncing is being used.
|
||||
* Fix: When HPOS is enabled, deleting a customer will now delete their subscriptions.
|
||||
* Fix: Missing styles on the Edit Subscription page when HPOS is enabled.
|
||||
* Fix: Resolve an issue that would cause additional subscriptions to be created when completing a switch via the Block Checkout.
|
||||
* Fix: Resolve an issue that would cause 3rd party plugin edit product fields with the show_if_variable-subscription class to be incorrectly hidden.
|
||||
* Fix: Allow gateways to execute action on payment method deletion before updating the subscription.
|
||||
* Fix: Ensure subscriptions have a date created that correctly accounts for the site's timezone. Fixes issues with subscriptions having a date created double the site's UTC offset.
|
||||
* Fix: When HPOS is enabled, fix quick-editing the subscription statuses on the admin list table.
|
||||
* Dev: PHP 8.2: Fix "Creation of dynamic property" warnings.
|
||||
* Dev: PHP 8.2: Fix "Automatic conversion of false to array is deprecated" warnings.
|
||||
* Dev: PHP warnings from using debug_backtrace().
|
||||
* Dev: Updated subscriptions-core to 6.4.0
|
||||
* Dev: Updated the hooks for Checkout Blocks, replacing the deprecated `woocommerce_blocks_checkout_` prefixed hooks with `woocommerce_store_api_checkout`.
|
||||
|
||||
2023-09-21 - version 5.5.0
|
||||
* Tweak - Use admin theme color in selectors.
|
||||
* Tweak - Change plugin name to Woo Subscriptions.
|
||||
|
||||
2023-08-11 - version 5.4.0
|
||||
* Add: Introduce an updated empty state screen for the WooCommerce > Subscriptions list table.
|
||||
* Fix: Ensure subscription checkout and cart block integrations are loaded on store environments where WooPayments is not enabled.
|
||||
* Fix: Ensure the shipping phone number field is copied to subscriptions and their orders when copying address meta.
|
||||
* Update: When HPOS is disabled, fetch subscriptions by customer_id using the user's subscription cache to improve performance.
|
||||
* Dev: Deprecated the 'woocommerce_subscriptions_not_found_label' filter.
|
||||
* Dev: Updated subscriptions-core to 6.2.0
|
||||
|
||||
2023-07-26 - version 5.3.1
|
||||
* Fix: Resolved an issue that prevented the "Used for variations" checkbox to not be enabled on the edit product page load causing variations to be deleted erroneously.
|
||||
* Dev: Fixed wcs_get_subscription_orders() returning an empty list when querying parent orders when HPOS is enabled with data syncing off.
|
||||
* Dev: Use the WC_VERSION constant when determining the active WooCommerce version inside our dependency manager if it's defined.
|
||||
* Dev: Updated subscriptions-core to 6.1.0
|
||||
|
||||
2023-07-19 - version 5.3.0
|
||||
* Fix: Ensure when a customer changes the shipping method on cart and checkout that the recurring totals correctly reflect the chosen method.
|
||||
* Fix: Resolve an issue that prevented the "Used for variations" checkbox from being enabled on the variable subscription product edit screen on WC version v7.9.0.
|
||||
* Fix: When HPOS is enabled, the `wc/v3/subscriptions` REST API endpoint will now fetch subscriptions of all statuses.
|
||||
* Dev: Filter the WC_Order_Query args to properly query subscription statuses when HPOS is enabled.
|
||||
* Dev: Updated subscriptions-core to 6.0.0
|
||||
|
||||
2023-07-05 - version 5.2.0
|
||||
* Fix: Resolved an issue that prevented the selected Shipping Method from being saved when switching an assembled Product Bundle.
|
||||
* Fix: When HPOS is enabled, permanently deleting a subscription related order wasn't updating the related orders cache properly.
|
||||
* Fix: Added logic to check if the recurring cart array is present before displaying the recurring totals section in the cart.
|
||||
* Dev: Updated subscriptions-core to 5.8.0
|
||||
|
||||
2023-06-05 - version 5.1.3
|
||||
* Fix: Resolved an issue with customers being redirected to an incorrect Pay for Order URL after login.
|
||||
* Dev: Updated subscriptions-core to 5.7.2
|
||||
|
||||
2023-05-11 - version 5.1.2
|
||||
* Fix: Resolve errors for third-party code using the URLs returned from WC_Subscriptions_Admin::add_subscription_url() and WCS_Cart_Renewal::get_checkout_payment_url() because they were erroneously escaped. #4526
|
||||
* Dev: Enable third-party code to alter the delete payment token URL returned from flag_subscription_payment_token_deletions. #4526
|
||||
* Dev: Update subscriptions-core to 5.7.1. #4526
|
||||
|
||||
2023-05-05 - version 5.1.1
|
||||
* Fix: Error when third-party extensions use the `woocommerce_subscriptions_add_switch_query_args` filter. #4522
|
||||
|
||||
2023-05-04 - version 5.1.0
|
||||
* Fix: Correctly determine subscription free shipping eligibility when the initial payment cart isn't eligible. Fixes erroneous "Invalid recurring shipping method" errors on checkout. #4513
|
||||
* Fix: Fatal error from third-party extensions using the `woocommerce_update_order` expecting the second parameter. #4519
|
||||
* Dev: Fixed precision loss notice that occurs when running PHP 8.1. #4513
|
||||
* Dev: Fix phpcs and semgrep warnings to improve code quality. #4513 #4514
|
||||
* Dev: Use `wp_safe_redirect()` when processing a payment method change request. #4513
|
||||
* Dev: Update subscriptions-core to 5.7.0. #4519
|
||||
* Dev: Pass the subscription object as the second parameter to `woocommerce_update_subscription` hook (and `woocommerce_update_order` for backwards compatibility). #4519
|
||||
* Dev: Return a response from the WC_Subscription::set_status() function in line with the parent WC_Order::set_status() function. #4519
|
||||
* Dev: Add the 'wcs_recurring_shipping_package_rates_match_standard_rates' filter to enable third-parties to override whether the subscription packages match during checkout validation. #4519
|
||||
|
||||
2023-03-31 - version 5.0.1
|
||||
* Fix: WooCommerce dependency check was erroneously failing on stores with other plugins including a woocommerce.php file. #4497
|
||||
|
||||
2023-03-10 - version 5.0.0
|
||||
* Fix: When a customer changes their address during a subscription switch, don't save their new address in the postmeta table when HPOS is enabled. #4489
|
||||
* Fix: When HPOS is enabled, changing your address while paying for a renewal order will update the address on the subscription. #4491
|
||||
* Fix: Prevent admin error notices being shown for the "subscription trial end" event that was caused by no callbacks being attached to this scheduled action. #4491
|
||||
* Fix: Prevent fatal error when copying the `_shipping_address` meta data where the value is not an array. #4495
|
||||
* Update: Use the WC installed version to determine if Subscription dependencies are met rather than the database version.
|
||||
* Dev: Update subscriptions-core to 5.5.0. #4491
|
||||
|
||||
2023-02-22 - version 4.9.1
|
||||
* Fix: Revert minimum required version of WooCommerce to 6.5 due to fatal errors on some sites.
|
||||
|
||||
2023-02-22 - version 4.9.0
|
||||
* Add: Declare WooCommerce Subscriptions compatible with High-Performance Order Storage.
|
||||
* Dev: Update subscriptions-core to 5.4.0.
|
||||
* Dev: Remove the recurring shipping method cache that caused bugs for third-party plugins like Conditional Shipping and Payments.
|
||||
* Dev: Bump minimum required version of WooCommerce to 7.0.
|
||||
|
||||
2023-02-01 - version 4.8.1
|
||||
* Fix: Fatal error when loading the Edit Subscription page with custom admin billing or shipping fields.
|
||||
* Dev: Update subscriptions-core to 5.3.1.
|
||||
|
||||
2023-01-30 - version 4.8.0
|
||||
* Add: Highlight subscriptions with overdue payment in list view with red icon & tooltip.
|
||||
* Add: New wcs_set_order_address() helper function to set an array of address fields on an order or subscription.
|
||||
* Update: Admin subscription reports are disabled on stores that have opted into HPOS without data syncing enabled.
|
||||
* Fix: Shipping address correctly set when resubscribing or switching subscriptions that contain different billing and shipping addresses.
|
||||
* Fix: When processing customer requests to update all their subscription payment methods, ensure the updated subscription is used to fetch the new payment meta, not and old instance.
|
||||
* Fix: Catch exceptions when changing payment method associated with a subscription to avoid fatal errors.
|
||||
* Fix: Show the payment retries metabox for renewal orders that have retry attempts on stores that have HPOS enabled.
|
||||
* Fix: Scheduled retry actions are now cancelled when trashing/deleting a renewal order on stores that have HPOS enabled.
|
||||
* Fix: On HPOS stores, return the correct count per subscription status from the `/system_status` WC API endpoint.
|
||||
* Fix: Refactor `WC_Subscriptions_Switcher::process_checkout()` to support stores with HPOS enabled.
|
||||
* Fix: Refactor `WC_REST_Subscriptions_V1_Controller::get_subscription_orders()` to support stores with HPOS enabled.
|
||||
* Fix: Edit, add, and list Subscription admin pages now work when HPOS is enabled.
|
||||
* Fix: Fixed issues where multiple subscription purchases wouldn't appear on the My Account > Subscriptions screen, on HPOS environments.
|
||||
* Fix: Refactor `WCS_Meta_Box_Subscription_Data::save` to support HPOS stores, fixing a PHP warning notice when updating an order via the Edit Order screen.
|
||||
* Fix: Set the `download_permissions_granted` value when purchasing a downloadable subscription product when HPOS is enabled.
|
||||
* Fix: When a customer changes their address on their account or subscription, make sure the new address is saved when HPOS is enabled.
|
||||
* Fix: Removed the potential for an infinite loop when getting a subscription's related orders while the subscription is being loaded.
|
||||
* Fix: Refactor `WC_Subscriptions_Renewal_Order` and `WC_Subscriptions_Tracker` classes to support HPOS stores.
|
||||
* Fix: "Subscriptions by Payment Gateway" in WooCommerce → Status now shows the correct values when HPOS is enabled.
|
||||
* Fix: Check whether the order actually exists before accessing order properties in wcs_order_contains_subscription().
|
||||
* Fix: When a subscription's parent order is trashed or deleted, make sure the related subscription is also trashed or deleted on stores with HPOS enabled.
|
||||
* Fix: When a subscription is trashed or deleted, make sure it is cancelled first on stores with HPOS enabled.
|
||||
* Fix: Merge any custom meta_query args passed to wcs_get_orders_with_meta_query() to avoid overriding WC core args that map onto meta_query.
|
||||
* Fix: Prevent erroneously resyncing a subscription every time it is loaded from the database on HPOS environments.
|
||||
* Fix: On HPOS environments, ensure subscription related order caches are updated when relationship order meta (eg `_subscription_renewal` or `_subscription_switch`) is updated.
|
||||
* Fix: On HPOS environments, update related orders cache when subscription is trashed, deleted, or restored / untrashed.
|
||||
* Fix: Replace code using wp_count_posts(), get_post_type(), get_posts and wp_delete_post() with equivalent WC Data Store functions to support stores that have HPOS enabled.
|
||||
* Dev: Add subscriptions-core library version to the WooCommerce system status report.
|
||||
* Dev: Introduced a WCS_Object_Data_Cache_Manager and WCS_Object_Data_Cache_Manager_Many_To_One class as HPOS equivalents of the WCS_Post_Meta_Cache_Manager classes.
|
||||
* Dev: Introduced a new `untrash_order()` in the `WCS_Orders_Table_Subscription_Data_Store` class to fix untrashing subscriptions on stores that have HPOS enabled.
|
||||
* Dev: Moved the trash, untrash & delete related `add_actions()` in the `WC_Subscriptions_Manager` class to be added on the `woocommerce_loaded` action.
|
||||
* Dev: Fix phpcs violations in the `WC_Subscriptions_Tracker` and `WCS_Admin_System_Status` classes to improve code quality.
|
||||
* Dev: Deprecate the `WC_Subscriptions_Switcher::update_shipping_methods()` function.
|
||||
* Dev: Fix phpcs violations in the `WC_REST_Subscription_System_Status_Manager` class to improve code quality.
|
||||
* Dev: Remove deprecated `strptime` function in favour of `DateTime::createFromFormat`.
|
||||
* Dev: Update subscriptions-core to 5.3.0.
|
||||
* Dev: Bump minimum required version of WooCommerce to 6.5.
|
||||
|
||||
2022-12-06 - version 4.7.0
|
||||
* Add: New wcs_get_orders_with_meta_query() helper function to query for orders and subscriptions.
|
||||
* Add: New WCS_Orders_Table_Subscription_Data_Store class to support subscriptions stored in High-Performance Order Storage (HPOS).
|
||||
* Add: New WCS_Orders_Table_Data_Store_Controller class to load the proper subscriptions data store when the store has HPOS enabled.
|
||||
* Add: New data copier class to copy data to subscriptions and related orders in place of direct database queries in prepraration for HPOS support.
|
||||
* Fix: Set payment tokens when copying data between orders and subscriptions in a CRUD compatible way. Fixes PHP notices during renewal order process.
|
||||
* Fix: Infinite loop that can occur with `WCS_Orders_Table_Subscription_Data_Store::read_multiple()` on HPOS-enabled stores.
|
||||
* Fix: On HPOS stores, when querying for subscriptions with wcs_get_orders_with_meta_query() with status 'any', ensure that wc_get_orders() queries for subscription statuses.
|
||||
* Fix: On HPOS stores, when saving a subscription make sure subscription properties (ie `_requires_manual_renewal`) are saved to the database.
|
||||
* Fix: On HPOS stores, when a subscription is loaded from the database, make sure all core subscription properties are read directly from meta.
|
||||
* Fix: When viewing My Account > Subscriptions, fix an issue where no subscriptions were listed when HPOS is enabled.
|
||||
* Fix: On HPOS stores, ensure payment tokens are copied from the subscription to the renewal order.
|
||||
* Fix: Refactor `WCS_Meta_Box_Schedule::save` to support HPOS stores, fixing a PHP warning notice when updating an order via the Edit Order screen.
|
||||
* Fix: Return a fresh instance of the renewal order after creating it. Fixes caching issues on HPOS sites where the returned order has no line items.
|
||||
* Fix: Processing a manual renewal order with HPOS and data syncing enabled correctly saves the related order cache metadata on the subscription and prevents the post and order meta data getting out of sync.
|
||||
* Fix: Use supported CRUD apis to determine if subscriptions are present on store (`wcs_do_subscriptions_exist`)
|
||||
* Fix: With HPOS and data syncing enabled, updating the status of a pending manual renewal order to a paid status correctly activates the related subscription.
|
||||
* Fix: Switch orders not appearing in related orders table on edit subscription screen when HPOS is active.
|
||||
* Fix: On HPOS stores, make sure the links in the related-orders table redirect to the new Edit Order URL.
|
||||
* Fix: When saving sync meta data on a new subscription, use 'woocommerce_new_subscription' instead of 'save_post'. This is to prevent errors when purchasing a subscription on stores that have HPOS enabled.
|
||||
* Fix: When WooCommerce is network activated on multisites, don't show the "WooCommerce Subscriptions is inactive".
|
||||
* Update: Improve maybe_add_subscription_meta() and subscription_contains_synced_product() inside our WC_Subscriptions_Synchroniser class to use CRUD methods.
|
||||
* Update: Refactor the `wcs_is_subscription` helper function to support HPOS.
|
||||
* Update: Refactor our Related Orders data store classes (WCS_Related_Order_Store_Cached_CPT and WCS_Related_Order_Store_CPT) to use CRUD methods to support subscriptions and orders stored in HPOS.
|
||||
* Update: Display related orders table when viewing the new "Edit Order" page (HPOS enabled stores).
|
||||
* Update: Replace instances of `get_posts()` across codebase with new wcs_get_orders_with_meta_query() function.
|
||||
* Update: The subscription creation function `wcs_create_subscription` has been updated to use WooCommerce CRUD methods in preparation for supporting High Performance Order Storage (HPOS).
|
||||
* Update: Improve wcs_copy_order_address() to use modern APIs for setting address fields.
|
||||
* Dev: Removed the deprecated "wcs_subscriptions_for_{$relation_type}_order" dynamic hook used to filter the list of related subscriptions for the given relation type. The following hooks have been removed with no alternative:
|
||||
wcs_subscriptions_for_renewal_order
|
||||
wcs_subscriptions_for_switch_order
|
||||
wcs_subscriptions_for_resubscribe_order
|
||||
* Dev: Introduce a WC_Subscription::set_status() function to handle subscriptions set with a draft or auto-draft status. Replaces the need for the overriding WC_Subscription::get_status() which has been deleted.
|
||||
* Dev: Manual renewal orders created with HPOS and data syncing enabled are properly linked to the subscription by its `_subscription_renewal` meta and backfilled to posts table.
|
||||
* Dev: Update subscriptions-core to 5.1.0
|
||||
* Dev: Replace use of deprecated hook `wcs_renewal_order_meta_query` with `wc_subscriptions_renewal_order_data` in `WC_Subscriptions_Switcher`.
|
||||
* Dev: Usage of \WC_Subscriptions_Core_Plugin::get_plugin_version() is no longer recommended for version detection. \WC_Subscriptions_Core_Plugin::get_library_version() should be used instead.
|
||||
* Dev: Code that was tagged with a version and moved from WooCommerce Subscriptions now explicitly mentions this and shows the correct subscriptions-core and WC Subscriptions versions.
|
||||
* Dev: Refactor the saving of subscription dates in the subscription datastore to separate fetching changes and saving. Enables backfilling subscription dates when HPOS syncing is enabled.
|
||||
* Dev: Replace the use of the deprecated wcs_renewal_order_meta hook with wc_subscription_renewal_order_data in the WCS_Related_Order_Store_Cached_CPT class.
|
||||
* Dev: wcs_get_objects_property and wcs_set_objects_property have been marked as deprecated. Getters/Setters should be used on the objects instead.
|
||||
* Dev: Deprecated the "wcs_{type}_meta_query" dynamic hook used to alter the database query used to fetch the meta data to copy between subscriptions and renewal orders. There is no direct replacement. Third-parties should use the "wc_subscriptions_{type}_data" or "wc_subscriptions_object_data" hooks instead.
|
||||
* Dev: Deprecated the "wcs_{type}_meta" dynamic hook used to filter data copied to subscriptions and renewal orders. Third-parties should use wc_subscriptions_{type}_data instead.
|
||||
wcs_subscription_meta -> wc_subscriptions_subscription_data
|
||||
wcs_parent_meta -> wc_subscriptions_parent_data
|
||||
wcs_resubscribe_order_meta -> wc_subscriptions_resubscribe_order_data
|
||||
wcs_renewal_order_meta -> wc_subscriptions_renewal_order_data
|
||||
* Dev: woocommerce_new_subscription_data hook will only work with CPT datastore and so has been deprecated.
|
||||
* Dev: i18n usage of strftime has been deprecated for subscription titles. Date is now formatted using woocommerce standard date formatting.
|
||||
* Dev: Removes the legacy `woo-includes/` directory.
|
||||
* Dev: Updated internal version references to the version of subscriptions-core.
|
||||
* Dev: Bump minimum required version of WooCommerce to 6.0.
|
||||
|
||||
2022-10-11 - version 4.6.0
|
||||
* Add: Declare incompatibility with WooCommerce High-Performance Order Storage (HPOS).
|
||||
* Fix: Move One Time Shipping metabox fields to use the woocommerce_product_options_shipping_product_data hook introduced in WC 6.0.
|
||||
* Update: Improve handling of bulk action execution.
|
||||
* Dev: Update subscriptions-core to 2.3.0
|
||||
|
||||
2022-08-26 - version 4.5.1
|
||||
* Fix - Fatal Error caused in rare cases where quantity is zero during renewal, builds upon fix released in 4.4.0.
|
||||
|
||||
2022-08-04 - version 4.5.0
|
||||
* Dev: Add missing `woocommerce_subscriptions_switch_link_text` and `woocommerce_subscriptions_switch_link_classes` filters for finer control of switch link. PR#4382
|
||||
* Fix: Update subscription address when changed with renewals on block checkout.
|
||||
|
||||
2022-06-07 - version 4.4.0
|
||||
* Fix - Fatal Error caused in rare cases where quantity is zero during renewal.
|
||||
|
||||
2022-05-24 - version 4.3.0
|
||||
* Dev: Retrieving users subscriptions order has been updated to use the WooCommerce specific APIs in WC_Subscriptions_Order.
|
||||
* Dev: Deprecate the WC_Subscriptions_Order::get_meta() function. Use wcs_get_objects_property( $order, $meta_key, "single", $default ) instead.
|
||||
* Dev: Update the wcs_get_objects_property() function to prevent calls to get_post_meta() on objects that support calling the get_meta() function.
|
||||
* Dev: Replace the get_post_meta() calls in WCS_Post_Meta_Cache_Manager with WC_Order::get_meta().
|
||||
* Dev: Replace code using get_post_type( $order_id ) with WC Data Store get_order_type().
|
||||
* Dev: Replace all cases of update_post_meta() where an Order ID is passed to use WC_Order::update_meta_data() instead.
|
||||
|
||||
2022-04-29 - version 4.2.0
|
||||
* Fix: Remove WooThemes helper/updater admin notice/banner. PR#4328
|
||||
* Fix: Remove PHP/deprecation notices during the early renewal and switch process when using WooCommerce Blocks 7.2. PR#4341
|
||||
* Fix: Display subscription billing details in the Cart Block when purchasing products with subscription plans created using the All Products extension. subscriptions-core#149
|
||||
* Update: Switch to global functions to remove deprecation warnings originating from WooCommerce Blocks. subscriptions-core#124
|
||||
* Dev: Update subscriptions-core to 1.9.0. PR#4345
|
||||
|
||||
2022-03-22 - version 4.1.0
|
||||
* Fix: Undefined variable `user_can_suspend` when a customer suspension limit is defined and the max number of suspensions has been reached. PR#4318
|
||||
* Fix: Sets up subscriptions integration with the Mini Cart Block and adds new hook to filter compatible blocks. subscriptions-core#103
|
||||
* Fix: When using a WooCommerce Blocks powered checkout, fix an issue that led to limited products being removed from the cart when completing a switch or renewal order. subscriptions-core#119
|
||||
* Fix: When there is only one Shipping Method available in the recurring shipping package, make sure that this method is treated as selected in the current session and the `woocommerce_after_shipping_rate` action runs. subscriptions-core#115
|
||||
* Fix: Don't anonymize new subscriptions related to old subscriptions via a resubscribe relationship. subscriptions-core#121
|
||||
* Fix: Content that appears on the My account > Payment methods page should be translatable. subscriptions-core#125
|
||||
* Dev: Update subscriptions-core to 1.7.0. PR#4319
|
||||
|
||||
2022-02-07 - version 4.0.2
|
||||
* Dev: Update subscriptions-core to 1.6.3. PR#4307
|
||||
* Fix: Replace uses of is_ajax() with wp_doing_ajax(). wcs#4296 PR#4307
|
||||
* Improve handling of session data.
|
||||
|
||||
2022-01-19 - version 4.0.1
|
||||
* Fix: Prevent fatal error when too few arguments passed to widget_title filter. PR#4302
|
||||
* Dev: Update subscriptions-core to 1.6.2. PR#4302
|
||||
|
||||
2022-01-19 - version 4.0.0
|
||||
* New: Update the minimum required WooCommerce version from 3.9 to 4.4. PR#4282
|
||||
* Fix: Unable to remove subscription line items via the REST API. PR#4258
|
||||
* Fix: Don't show subscription related product fields when the custom variable type products are created. PR#4215
|
||||
* Fix: Add consistent margins to the recurring taxes totals row on the Checkout and Cart block. PR#4273
|
||||
* Fix: Fatal error due to order with no created date in order row template. PR#4273
|
||||
* Fix: Fatal error on the customer payment page for renewal orders with deleted products. PR#4273
|
||||
* Fix: Prevent fatal errors on the admin subscriptions screen when a subscription fails to load. PR#4290
|
||||
* Fix: Incorrect message on the subscriptions table when filtering returns no results. PR#4290
|
||||
* Fix: Update Cart and Checkout Block hooks to prevent deprecation warnings. PR#4280
|
||||
* Tweak: Update tooltip wording when deleting product variation. PR#4273
|
||||
* Tweak: Don't show an admin error notice when a store downgrades to a previous minor version of Subscriptions. PR#4273
|
||||
* Tweak: Misleading order note on payment method change. PR#4273
|
||||
* Dev: Moved subscription core files moved into a new subscriptions-core library (loaded from vendor/woocommerce/subscriptions-core). PR#4121
|
||||
* Dev: New `WC_Subscriptions_Plugin` class to be the main Subscriptions plugin handler class. PR#4116
|
||||
* Dev: Moved and deprecated 33 functions from class `WC_Subscriptions` to more suitable classes. PR#4114
|
||||
* Dev: Moved switching feature related classes into its own directory (`includes/switching/`). PR#4122
|
||||
* Dev: Moved payment retry feature related classes into its own directory (`includes/payment-retry/`). PR#4146
|
||||
* Dev: Moved early renewal feature related classes into its own directory (`includes/early-renewals/`). PR#4148
|
||||
* Dev: Moved the "Accept manual renewals" feature and settings to a new `WCS_Manual_Renewal_Manager` class. PR#4124
|
||||
* Dev: Moved the "Customer Suspensions" feature and settings code to a new `WCS_Customer_Suspension_Manager` class. PR#4138
|
||||
* Dev: Moved the "Drip Downloadable Content" feature and settings to a new `WCS_Drip_Downloads_Manager` class. PR#4142
|
||||
* Dev: Moved the "Allow $0 initial checkout without a payment method" feature and settings to a new `WCS_Zero_Initial_Payment_Checkout_Manager` class. PR#4145
|
||||
* Dev: Moved the "Limited payment recurring coupon" feature to a new `WCS_Limited_Recurring_Coupon_Manager` class. PR#4150
|
||||
* Dev: Moved the custom subscription product and checkout button text feature to a new `WCS_Call_To_Action_Button_Text_Manager` class. PR#4152
|
||||
* Dev: Moved the custom active and inactive subscriber roles feature to a new `WCS_Subscriber_Role_Manager` class. PR#4153
|
||||
* Dev: Removed the Action Scheduler library from WC Subscriptions since it has been included in WooCommerce core since version 3.0. PR#4202
|
||||
|
||||
2021-10-06 - version 3.1.6
|
||||
* Fix: Add back the limited subscription setting for simple subscription products (introduced in v3.1.5). PR#4214
|
||||
|
||||
2021-09-28 - version 3.1.5
|
||||
* Fix: Update subtracted tax amounts to account for quantity changes. PR#4107
|
||||
* Fix: Correctly remove limited coupons (i.e. "Active for x payments") when the coupon code is made up of only numeric characters. PR#4139
|
||||
* Fix: Only set subtracted taxes on new items when the rates don't match the base location. PR#4177
|
||||
* Fix: Hide variable subscription meta fields on the Edit Product page of custom variable products. PR#4193
|
||||
* Fix: Use the shipping fields to get and save the edit subscription shipping field data. PR#4161
|
||||
* Fix: Fix TypeError exceptions by checking for WC_Order types inside wcs_get_subscriptions_for_order(). PR#4188
|
||||
* Fix: Incorrect subtracted tax calculations when updating the subscription when the store currency uses a comma decimal separator. PR#4182
|
||||
* Fix: Hide the shipping address fields on checkout when the cart contains a subscription product and the 'Force shipping to billing address' setting is selected. PR#4172
|
||||
* Fix: Get the signup fee for coupon calculation inclusive or excluding tax depending on store settings. PR#4166
|
||||
|
||||
2021-07-22 - version 3.1.4
|
||||
* Fix: Points and Rewards discounts (including WC Coupons when Points and Rewards is active) being removed from the cart when applied on the checkout page. PR#4158
|
||||
* Fix: Subscriptions with one-time shipping having shipping charged on all renewal orders. PR#4156
|
||||
* Fix: Rare fatal error caused by missing WC_Query class. PR#4155
|
||||
* Fix: Make loading the variation edit product tab more performant on large sites. PR#4144
|
||||
* Fix: Add a primary key to the last payment temporary table to improve compatibility with some hosts on larger sites. PR#4151
|
||||
* Tweak: Update the wording when a customer edits their subscription address that this applies to "future renewals". PR#4118
|
||||
* Dev: Add missing `woocommerce_rest_pre_insert_shop_subscription_object` filter to prepare_object_for_database() within the Subscriptions REST API controller class. PR#4119
|
||||
* Dev: Add a `data-payment-method` attribute to the Early Renewal modal button action. PR#4123
|
||||
|
||||
2021-06-09 - version 3.1.3
|
||||
* Fix: Switch calculations not correctly charging the full sign up fee when the "Prorate Sign up Fee" option is set to "Never (charge the full sign up fee)". PR#4096
|
||||
* Fix: Fixes PayPal Reference Transactions integration with Checkout blocks. PR#4105
|
||||
* Fix: Set the updated payment token on all pending-payment renewals so that manually retrying a failed payment uses the updated token set on the subscription. PR#4108
|
||||
* Dev: Moved the `WC_Subscriptions::enqueue_scripts` and` WC_Subscriptions::equeue_styles` static functions to the new `WC_Subscriptions_Frontend_Scripts` class (deprecated existing functions). PR#4104
|
||||
|
||||
2021-05-19 - version 3.1.2
|
||||
* Fix: Don't show recurring shipping method selection when the rates and options match the initial cart options. PR#4091
|
||||
* Fix: Update the recurring shipping totals on selection and fix invalid recurring shipping method notices. PR#4099
|
||||
* Fix: Update the subscription shipping address after on switch. PR#4095
|
||||
|
||||
2021-05-12 - version 3.1.1
|
||||
* Fix: "Invalid recurring shipping method" error on checkout when attempting to purchase a subscription with one-time shipping. PR#4088
|
||||
* Fix: Rework subscription webhook migration script added in 3.1 to clear cached webhook data. PR#4082
|
||||
|
||||
2021-05-10 - version 3.1.0
|
||||
* New: Add support for WooCommerce Cart and Checkout blocks. PR#3993
|
||||
* New: Support v3 subscription endpoints. PR#4001
|
||||
* New: Add woocommerce_inbox_variant option. PR#4084
|
||||
* New: Update the WC minimum required version to WC 3.7. PR#3914
|
||||
* New: Add a new Accounts setting to allow admin to enable registration on checkout of subscription customers. PR#3883
|
||||
* New: Display switch, renewal and resubscribe context in the 'Add to cart' and 'Place order' buttons. PR#3624
|
||||
* Fix: Fix customer subscription caching errors on multisite installations. PR#4074
|
||||
* Fix: Update the `_subtracted_base_location_taxes` when an admin edits the subscription line item subtotal. PR#4068
|
||||
* Fix: Migrate existing v3 subscription webhooks to v1 when upgrading to 3.1 to maintain the same payload delivered. PR#4079
|
||||
* Fix: Fatal error when processing an initial PayPal Standard payment on a manually created subscription that has no parent order. PR#4033
|
||||
* Fix: Only allow reactivation of pending-canceled subscription when end date is in the future. PR#4037
|
||||
* Fix: Don't add a free trial period when resubscribing to a synced subscription. PR#4045
|
||||
* Fix: Catch exceptions thrown when updating a subscription's payment method. PR#4051
|
||||
* Fix: Incorrect sign-up fee discount amount calculated when the customer is outside of all tax zones and prices are entered with tax included. PR#4052
|
||||
* Fix: Allow paying for pending parent orders that contain a limited subscription product where the subscription is already active. PR#4061
|
||||
* Fix: Show recurring shipping totals/options when a virtual product is the last product added to the cart. PR#4053
|
||||
* Fix: Refactor how `_subtracted_base_location_tax` order item meta is stored on subscription items to avoid calculation issues when the quantity is changed. PR#4062
|
||||
* Fix: Fixes PHP notices when using the v1/subscriptions/ID/orders REST endpoint on a subscription where one of its related orders was directly deleted from the database. PR#4058
|
||||
* Tweak: Always show the delete save card button but now display a notice when trying to delete a card tied to an active subscription. PR#4076
|
||||
* Tweak: Update the subscription order note to when the PayPal Standard subscription is suspended and migrated over to PayPal Reference Transactions during a switch request. PR#4057
|
||||
* Tweak: Display orders in the related orders admin table sorted by date. PR#3575
|
||||
* Dev: Refactor the recurring totals template. PR#3550
|
||||
|
||||
2021-04-16 - version 3.0.15
|
||||
* Tweak: Improve compatibility with WPML. PR#4034
|
||||
* Dev: Introduce filter to allow third-parties gateways to filter whether a token meta was updated. PR#4030
|
||||
* Fix: Fixes for endpoint in "My Account".
|
||||
|
||||
2021-04-07 - version 3.0.14
|
||||
* New: Add support for importing and exporting subscription variations via the WooCommerce > Products > Export/Import tools. PR#3970
|
||||
* Fix: Only show one Subscriptions settings menu item on the WC Admin navigation. PR#4054
|
||||
* Fix: Prevent PHP notices on the Edit Subscription page caused accessing an undefined index. PR#4056
|
||||
* Fix: Existing variations will no longer be deleted when updating a regular variable product to a subscription variable product. PR#3795
|
||||
* Fix: Update the `_subtracted_base_location_tax` meta data when a subscription's line item quantity is modified. PR#4039
|
||||
* Fix: Next payment date calculations when the start date and last payment date are the same. PR#4017
|
||||
* Dev: Update jQuery 3.0 deprecations. PR#4015
|
||||
|
||||
2021-02-16 - version 3.0.13
|
||||
* Fix: Pass an order status array to get_order_report_data() to prevent possible fatals by 3rd parties. PR#3930
|
||||
* Fix: Change logic to allow customers to pay for failed order with limited product. PR#3947
|
||||
* Fix: Prevent base length from being saved in the DB when retrieved in 'apportion_length'. PR#3954
|
||||
* Fix: Prevent errors when using Product Bundles (which uses WCS_Add_Cart_Item) by introducing a WCS_Add_Cart_Item::is_switch_after_fully_reduced_prepaid_term() function. PR#3957
|
||||
* Fix: Introduce an SQL Transactions helper with automatic commit/rollback on shutdown. Prevents strange behaviour when a shutdown occurs during a SQL transaction. PR#3827
|
||||
* Fix: [PHP 8.0] Change maybe_retry_payment() arg name to prevent unknown variable name errors. PR#3984
|
||||
* Fix: Delete '_switch_totals_calc_base_length' meta incorrectly saved in the database on upgrade. PR#3958
|
||||
* Fix: Prevent WC_Order_Item_Coupon::offsetGet errors during renewal. PR#4009
|
||||
* Dev: Introduce new hooks to various Switch_Totals_Calculator functions. PR#3872
|
||||
|
||||
2021-01-06 - version 3.0.12
|
||||
* New: Add an order note when subscriptions are deleted after the customer is deleted. PR#3783
|
||||
* New: [WC Admin] Register items in new navigation menu. PR#3868
|
||||
* New: [WC Admin] Add the Subscriptions setting to WC Admin's Settings navigation list. PR#3911
|
||||
* Tweak: Fix capitalisation of Subscription webhook topic names so they match WooCommerce core. PR#3902
|
||||
* Tweak: Refactor `wcs_help_tip()` to enable custom tooltip classes. PR#3871
|
||||
* Tweak: Replace remaining uses of new WC_Order() with wc_get_order() to get an order instance. PR#3858
|
||||
* Fix: Fix the amount customers are charged after switching after a previous switch which reduced the pre-paid term fully. PR#3805
|
||||
* Fix: Calculate a gap payment when switching to a subscription with one length and no payment date to extend. PR#3879
|
||||
* Fix: Only require cart payment if there's a subscription with a next payment date. PR#3899
|
||||
* Fix: Only display the failed scheduled action warning to admin users who can view subscriptions. PR#3905
|
||||
* Fix: Prefix attribute keys correctly before getting switch url. Fixes issues with auto-switch redirects removing pre-exisiting URL params. PR#3913
|
||||
* Fix: Prevent reducing stock while saving a subscription on the admin edit subscription screen. PR#3926
|
||||
* Fix: Use product object to read the original, unmodified length of a cart item during a switch. PR#3929
|
||||
* Dev: Trigger create/update webhooks during Subscription REST API calls. PR#3919
|
||||
* Dev: Add filters to enable third-parties to change the new and old payment method titles used in subscription changed payment method notes. PR#3908
|
||||
|
||||
2020-11-25 - version 3.0.11
|
||||
* Tweak: Improve the missing customer error message displayed for third-party built checkouts when registration is disabled on checkout. PR#3893
|
||||
* Fix: Remove the possibility of a negative calculation of prepaid days on subscription downgrades. PR#3881
|
||||
* Fix: Fix issues preventing stores using the $0 Initial Checkout feature from being able to checkout. PR#3887
|
||||
* Fix: Remove potential fatal error when processing end-of-prepaid term scheduled actions caused by the subscription not existing. PR#3875
|
||||
* Fix: Trigger payment complete actions after renewing early via modal. PR#3888
|
||||
* Fix: Performance improvements to limited products running slow queries on the shop page. PR#3895
|
||||
* Fix: [PHP 8.0] Only pass array values to call_user_func_array calls. PR#3884
|
||||
* Fix: [PHP 8.0] Replaces certain uses of method_exists() with is_callable(). PR#3892
|
||||
|
||||
2020-11-10 - version 3.0.10
|
||||
* Fix: Store shipping line item instance ID separately and copy it to renewal line item meta. PR#3712
|
||||
* Fix: Account for the base store tax rates subtracted from line items when paying for renewals manually. PR#3745
|
||||
* Fix: Update the persistent cart while paying for renewal orders via the checkout. Fixes division by zero errors while manually renewing. PR#3824
|
||||
* Fix: Clear report cache data before regenerating new preset date results preventing infinitely growing report caches. PR#3800
|
||||
* Fix: Remove subscription period string transient caching to fix issues after translating. PR#3770
|
||||
* Fix: WC 4.7 compatibility. Use filter to change endpoint titles in My Account. PR#3857
|
||||
* Fix: When generating product price strings fallback to a default period string if no period is set. PR#3848
|
||||
* Fix: Remove uses of jQuery( document ).ready() which was deprecated. PR#3846
|
||||
* New: Add option to enable admin to lock in increased parent order line item prices. PR#3816
|
||||
* Tweak: Allow admin to set a next payment date in 2 minutes via the edit subscription screen on staging sites. PR#3778
|
||||
|
||||
2020-09-29 - version 3.0.9
|
||||
* Fix: Offset subscription next payment date calculations based on site time. All dates still remain in UTC, but calculations on the 1st and last day of the month will now take into account the site timezone. PR#3708
|
||||
* Fix: Only attempt to display the customer notice when updating the default token from the My Account page. PR#3799
|
||||
* Fix: Prefix variation attributes with `attribute_` when setting up renewal carts. PR#3809
|
||||
* Dev: Add a new hook to the change_users_subscription() function - this hook is triggered before the status change. PR#3787
|
||||
|
||||
2020-09-24 - version 3.0.8
|
||||
* Tweak: Remove the '$X now' from price string when synced product sign up date is today. PR#3797
|
||||
* Fix: Treat a subscription with a cancelled parent order as "needing payment". This ensures cancelling the subscription will go right to cancelled, rather than pending-cancellation. PR#3784
|
||||
* Fix: Remove code responsible for hiding/showing the WC order totals meta box. This fixes the deprecated jQuery.fn.load() warnings. PR#3786
|
||||
* Fix: Fix updating status and end dates via the REST API. PR#3796
|
||||
* Dev: Add an action hook to edit subscription admin payment meta inputs. Enables third-parties to display custom fields for each gateway. PR#3798
|
||||
* Dev: Opt-in tracking data for Subscriptions Reports PR#3738
|
||||
Data sent to WooCommerce:
|
||||
Subscriptions report views
|
||||
Subscriptions report filter usage - clicks and report range
|
||||
Number of subscriptions, orders, products
|
||||
Plugin version
|
||||
To disable this tracking, opt-out of WooCommerce tracking, see https://woocommerce.com/usage-tracking/
|
||||
|
||||
2020-08-11 - version 3.0.7
|
||||
* Fix: Prevent possible errors caused by inaccessible get_current_screen() function. PR#3678
|
||||
* Fix: Decouple single_add_to_cart_text() from add_to_cart_text() and remove double and incorrect filtering. PR#3705
|
||||
* Fix: Account for the pre-paid term when canceling a subscription which is on-hold without a required payment. PR#3726
|
||||
* Fix: [WC4.4] Replace uses of `get_product_from_item()` with `$item->get_product()`. PR#3769
|
||||
* Fix: [WC4.4] Deprecate use of the WC_Cart::tax_display_cart. PR#3741
|
||||
* Fix: [WC4.4] Update WC_Product_Variable_Subscription::get_available_variations() function signature. PR#3761
|
||||
* Fix: [WC4.0] Use the WC version appropriate hook to override the text explaining uneditable subscriptions. PR#3678
|
||||
* Fix: [WP5.5] Add permission callback for the REST statuses endpoint. PR#3765
|
||||
* Fix: [WP5.5] Remove the post box header from edit subscription meta boxes #3771
|
||||
* Performance: Validate product type changes on large sites to avoid running queries on edit product page loads. PR#3699
|
||||
* Tweak: [WC Code Standards] Fixes for i18n-related issues. PR#3757
|
||||
|
||||
2020-07-29 - version 3.0.6
|
||||
* Tweak: Add placeholder explanation for the upcoming recurring report. PR#3655
|
||||
* Tweak: Add a link to QuickForget.com in the IPN error notice. PR#3744
|
||||
* Fix: Fix COD gateway availability for trial subscriptions. PR#3740
|
||||
* Fix: Fetch customer error notice messages on the change payment method page in WC post 3.9 version compatible way. PR#3730
|
||||
* Fix: Don't reduce stock for items added to a subscription manually via admin screen. PR#3725
|
||||
* Fix: Only display the automatic renewal note in emails when there's a next payment. PR#3679
|
||||
* Fix: Use the interval to calculate first payment date for synced products. Fixes an issue that caused a purchase outside the sync grace period to not account for the subscription interval. PR#3303
|
||||
* Dev: Add two new action hooks before and after change payment submit button. PR#3718
|
||||
* Dev: Add a new helper function wcs_trial_has_passed(). PR#3289
|
||||
* Dev: Replace uses of cal_days_in_month() with a date() equivalent. PR#3734
|
||||
* Dev: Make various changes to improve coding standards. PRs#3748, #3749, #3753
|
||||
|
||||
2020-06-16 - version 3.0.5
|
||||
* Fix: Ensure the mixed checkout setting correctly prevents mixed carts after customer login. PR#3696
|
||||
* Fix: Translate the automatic subscription recurring note in emails. PR#3684
|
||||
* Fix: Account for limited coupon use across recurring carts. Fixes an issue which leads to limited coupons applying beyond the limit. PR#3552
|
||||
* Fix: Copy the item usage limit to coupons applied to manual renewal carts. PR#3686
|
||||
* Dev: Add a filter to control access to the early renewal modal. PR#3675
|
||||
|
||||
2020-04-28 - version 3.0.4
|
||||
* Fix: Save changes to payment method meta on the edit subscription screen. PR#3666
|
||||
* Fix: Grant Shop Managers edit capabilities over subscriber users. PR#3669
|
||||
* Fix: Remove original item from switched subscription during a multiple line item, different billing schedule switch. PR#3659
|
||||
* Fix: Use strict conditionals when comparing results of as_next_scheduled_action. Fixes rescheduling of actions which were ran manually. PR#3661
|
||||
* Tweak: To comply with Visa's changes to rules governing free trial subscriptions, add additional subscription details to email templates. PR#3664
|
||||
|
||||
2020-04-01 - version 3.0.3
|
||||
* Fix: Only set the payment method if it has changed via admin edit. PR#3646
|
||||
* Fix: Performance issue in WCS_Payment_Tokens::get_subscriptions_from_token. PR#3649
|
||||
* Fix: Exclude empty nonce.
|
||||
|
||||
2020-03-11 - version 3.0.2
|
||||
* New: Add WC Admin Compatibility. PR#3628
|
||||
* Fix: Restore the trial end date after reactivating a subscription which was cancelled. PR#3572
|
||||
* Fix: Hide the order again button for orders which contain limited products. PR#3612
|
||||
* Fix: Don't show recurring cart taxes when no taxes apply. PR#3633
|
||||
* Fix: Prevent autocompleting orders which contain free, simple, non-digital products. PR#3621
|
||||
* Fix: Remove redundant double line from customer report query. PR#3640
|
||||
* Fix: [PayPal] Modify WC payload regex to account for alphanumeric chars. PR#3629
|
||||
* Fix: Hide the free shipping label for free shipping and free local pickup options. PR#3618
|
||||
* Fix: Prevent fatal errors when getting price for variable products which aren't synced. PR#3616
|
||||
* Fix: Don't send WC core emails for subscription related orders when in Staging mode. PR#3576
|
||||
* Fix: Update PayPal settings URL used in admin notices. PR#3617
|
||||
* Tweak: Update wording in warning when attempting to change the subscription product type. PR#3623
|
||||
|
||||
2020.01.17 - version 3.0.1
|
||||
* Fix: Support multiple modals on the customer's view subscription page. Fixes an issue where preventing the early renewal modal being displayed was preventing all modals on the page from being displayed. PR#3571
|
||||
* Fix: Silence PHP warning in events by date report caused by empty set of data. PR#3577
|
||||
* Fix: [WC3.7] Copy the rate percent meta to tax line items during renewal meta copy. PR#3578
|
||||
* Fix: Introduce a user permissions check for the auto-renewal toggle. PR#3579
|
||||
* Fix: Update Action Scheduler library to 3.0.1. This release includes a database tables schema version bump. PR#3593
|
||||
|
||||
2020.01.06 - version 3.0.0
|
||||
* New: Update Action Scheduler to 3.0.
|
||||
* New: Apply WC styling to subscription statuses, subscription related order and retry meta table statuses. PR#3467, PR#3506
|
||||
* New: Introduce new Renewal Order On-hold Emails. PR#3475
|
||||
* Fix: Display gateway specific errors on the subscription change payment method page. PR#3278
|
||||
* Fix: Order the results from WCS_Retry_Manager::store()->get_retries() correctly according to the orderby arg. Previously this function would always return results ordered by ascending ID. PR#3498
|
||||
* Fix: Send customer Renewal Order Processing Emails on the correct status transitions. PR#3475
|
||||
* Fix: Only update the subscription status once when processing $0 renewals orders. Fixes race condition caused by updating two instances of the same order. PR#3531
|
||||
* Fix: Adjust admin CSS and data metabox for recent changes in WP and WC. PR#3540
|
||||
* Fix: Fix admin notice error link for failed scheduled action when using WC_Log_Handler_DB. PR#3544
|
||||
* Fix: Correctly calculate inclusive line item taxes when paying for manual renewal orders. PR#3545
|
||||
* Fix: Add customer note details to renewal order email templates. PR#3536
|
||||
* Fix: Validate the subscription start date during admin manual subscription creation. PR#3556
|
||||
* Fix: Only display the WC Subscriptions activation notice on sites without a subscription product. PR#3558
|
||||
* Fix: [WC 3.9] Ensure WCS_Retry_Manager::check_order_statuses_for_payment() has default value for the $order parameter. PR3565
|
||||
* Fix: [WP 5.3] Fix variable subscription product admin field CSS. PR#3564
|
||||
* Tweak: [WooCommerce dashboard widget] tweak media screen size breakpoints to avoid misalignment issues. PR#3553
|
||||
* Tweak: Use product object rather than ID when validating mixed checkout setting. Improves compatibility with plugins like Mix and Match and Product Bundles. PR#3551
|
||||
* Tweak: Display the cart and checkout recurring totals included taxes in a more readable way. PR#3546
|
||||
* Dev: Allow 3rd party developers using the 'woocommerce_subscriptions_allow_switching_options' filter to add tooltip content. PR#3542
|
||||
|
||||
2019.11.14 - version 2.6.5
|
||||
* Fix: Account for prorated switch sign up fees in multi-switches. PR#3519
|
||||
* Fix: Tooltip content displayed on WooCommerce > Subscriptions administration screen.
|
||||
* Dev: Add additional hooks in My Account Subscription Details table template. PR#3523
|
||||
|
||||
2019.11.12 - version 2.6.4
|
||||
* Tweak: Update the My Account customer has no subscriptions notice to match WC core. PR#3516
|
||||
* Fix: Add hidden class to subscription_pricing and subscription_sync edit product fields. Fixes a bug when the subscription product types are removed from the edit product type drop-down. PR#3514
|
||||
* Fix: error on My Account > Payment Methods for non credit card tokens and allow deleting tokens with clear alternative. PR#3482
|
||||
* Fix: Allow only number of days in specific month for synchronize renewals. PR#3470
|
||||
* Fix: Save subscription meta box data via set_props(). Fixes fatal error when saving invalid data. PR#3524
|
||||
* Fix: Use update_option() rather than add_option() to record WC Subscriptions activation. Fixes infinitely running activation hook under some circumstances. PR#3525
|
||||
* Dev - Opt-in tracking data for Subscriptions PR#3504
|
||||
Data sent to WooCommerce:
|
||||
Staging or live site
|
||||
Live URL
|
||||
WooCommerce Subscriptions Settings
|
||||
Dates of the first and last created subscriptions
|
||||
Number of subscriptions
|
||||
Number of subscriptions with each status
|
||||
Gross totals for switch, renewal, resubscribe, and initial totals
|
||||
Order counts for for switch, renewal, resubscribe, and initial orders
|
||||
To disable this tracking, opt out of WooCommerce tracking, see https://woocommerce.com/usage-tracking/
|
||||
|
||||
2019.10.29 - version 2.6.3
|
||||
* Fix: use dashicon over fa icon. PR#3497
|
||||
* Fix: remove button type from close modal link. PR#3497
|
||||
* Fix: Remove double filtering of the meta label through the woocommerce_attribute_label filter. PR#3476
|
||||
* Fix: Restore the subscription's end date after reactivation. PR#3399
|
||||
* Fix: Tooltip content displayed on WooCommerce > Subscriptions administration screen.
|
||||
* Tweak: Allow modals to be displayed on admin screens. PR#3497
|
||||
* Tweak: Redirect the customer to checkout after failed early renewal attempt. PR#3494
|
||||
* Dev: Allow third parties to filter the switch cart item object properties. PR#3484
|
||||
|
||||
2019.10.10 - version 2.6.2
|
||||
* Tweak: Add the switch direction the switch data stored in _subscription_switch_data order meta. PR#3440
|
||||
* Tweak: Add an order note to manual renewal order to note the order is awaiting customer payment. PR#3456
|
||||
* Tweak: Add note on the renewal order when a manual payment retry is ran. PR#3477
|
||||
* Fix: Hide sync meta data on the edit order and subscription screen. PR#3454
|
||||
* Fix: Fix an issue that led to missing _switched_subscription_item_id line item meta which caused incorrect multi-switch upgrade costs among other issues. PR#3461
|
||||
* Fix: Store the full set of current subscription counts not just the last element. Fixes issues when exporting subscription report data. PR#3455
|
||||
* Fix: Add the manually admin requested renewal order notes in the correct order. PR#3462
|
||||
* Fix: Display the customer facing subscription dates in site time. Fixing display inconsistencies. PR#3469
|
||||
* Fix: Updated the link in the staging site admin notice. PR#3473
|
||||
* Fix: Only get return retries from the post store which are retry posts. Fixes an issue where it would return a retry object for other post types. PR#3481
|
||||
* Fix: Load renewal order fee items to manual renewal carts. PR#3480
|
||||
* Dev: Fixed WC_Subscription::get_date() returning dates in the site time if the site was using GMT offsets in their site settings. PR#3469
|
||||
|
||||
2019.09.04 - version 2.6.1
|
||||
* Fix a bug that would lead to switch log entries not including all information. PR#3441
|
||||
* Fix fatal errors that would occur on the admin edit order screen on staging sites. PR#3443
|
||||
* Performance: Sort using subscription related order IDs on the application layer with rsort() instead of MySQL orderby clause. PR#3442
|
||||
|
||||
2019.09.02 - version 2.6.0
|
||||
* New: New option to allow customers with automatically renewing subscriptions to renew early via a modal rather than going through the checkout. PR#3293
|
||||
* New: Link subscription report counts in the 'by date' report to order and subscription listing page. PR#3318
|
||||
* New: Add different message and link to documentation for store managers when no payment methods available on checkout. PR#3340
|
||||
* New: Add a note and a tooltip explaining locked manual subscriptions on staging sites. PR#3327
|
||||
* New: Use the switching flow to enable items to be added to existing subscriptions. PR#3394
|
||||
* New: Log switch debug information when a switch order is processed. PR#3424
|
||||
* Tweak: Use sentence case for table and section headings in customer facing templates. PR#3392,#3407,#3412,#3432
|
||||
* Tweak: Make improvements to the subscription admin dashboard reports. Fixes misalignment issues on certain display sizes. PR#3401
|
||||
* Tweak: Update the PayPal admin notices to be more correct based on if Standard is enabled or not. PR#3393
|
||||
* Tweak: Display PayPal type specific features in the System Status and feature tooltip. PR#3411
|
||||
* Tweak: Display un-loadable orders in the subscriptions related order table. PR#3342
|
||||
* Tweak: Always pass product instances to WC_Subscriptions_Product methods. PR#3396
|
||||
* Tweak: Update the ended subscriptions report legend key for more grammatically correct option. PR#3415
|
||||
* Tweak: Disable the auto-renewal toggle on staging sites. PR#3387
|
||||
* Tweak: [REST API] Include 'removed line items' in subscription response. PR#3198
|
||||
* Tweak: Replace self::class with php __CLASS__ constant in report classes. PR#
|
||||
* Tweak: Transform "Allow Switching" admin settings into to a multi-checkbox option. PR#3373
|
||||
* Fix: Refactor the WC_Subscriptions_Switcher::calculate_prorated_totals() function and fix a number of switching issues in the process. PR#3250
|
||||
* Fix: Keep the trial end when switching between two products with matching trial periods when the subscription is still on trial. PR#3409
|
||||
* Fix: Use the last order's paid date in switching calculations when determining the number of days consumed in the current cycle. PR#3420
|
||||
* Fix: Ignore the WP_SITEURL global on multisites when determining the site URL for staging sites. PR#3397
|
||||
* Fix: Repair subscription line items with missing `_has_trial` line item meta. PR#3239
|
||||
* Fix: Only retrieve subscriptions with the product as a line item - exclude switched and removed products while using `wcs_get_subscriptions_for_product()`. PR#3386
|
||||
* Fix: Don't display tax values in recurring cart section when taxes aren't enabled under certain circumstances. PR#3408
|
||||
* Fix: Trigger the `woocommerce_before\after_add_to_cart_quantity` actions on the subscription single product pages. PR#3388
|
||||
* Fix: [WC 3.7] Include the "additional content" in subscription-related emails. #3416
|
||||
* Fix: [WC 3.7] Remove uses of deprecated $order->get_used_coupons(). PR#3421
|
||||
* Fix: Copy or replace fees from cart to subscription while switching. PR#3184
|
||||
* Fix: Save subscription after setting payment meta via wcs_set_payment_meta(). Fixes issues after updating the payment meta before payment retry. PR#3425
|
||||
* Fix: Don't allow users to partially pay for renewal orders if some products are out of stock. This was previously fixed by broke in Subscriptions 2.1. PR#3436
|
||||
* Fix: Add filter which can be turned on to allow out of stock manual renewals to pass cart and checkout validations. PR#3435
|
||||
* Fix: [WC Services] Fixed compatibility bug which caused coupons to be removed when automatic tax rates enabled with WC Services. PR#3376
|
||||
* Fix: Remove uninitiated database transaction rollback in WC_Subscriptions_Switcher::process_checkout. PR#3307
|
||||
* Fix: Validate cart contents after login when mixed checkout is disabled. PR#3151
|
||||
* Fix: Fix issue which led to the 'save changes' button always being active on product variations tab. PR#3357
|
||||
* Fix: Fix division by zero warning in subscriptions by customer report. PR#3371
|
||||
* Fix: Use site time while adding a period to the first payment date so that last day of month is right. PR#3368
|
||||
* Performance: Limit the "product has a subscription" query to just a single result to be more performant. PR#3389
|
||||
* Dev: Deprecate get_completed_payment_count() in favor of get_payment_count(). PR#2971
|
||||
* Dev: [WC 3.7] Only use deprecated woocommerce_before_cart_item_quantity_zero hooks on WC pre 3.7. PR#3377
|
||||
* Dev: Introduce WCS_Dependent_Hook_Manager class to assist in attaching callbacks on specific WC versions. PR#3377
|
||||
* Dev: Add BEM classes to templates and make general code improvements. PR#3135
|
||||
* Dev: Always use get_date_types_to_schedule() to schedule dates. PR#3091
|
||||
* Dev: Deprecate old and unused WC_Subscriptions_Manager::process_subscription_payments_on_order() and WC_Subscriptions_Manager::process_subscription_payment_failure_on_order() functions. PR#3378
|
||||
|
||||
2019.07.04 - version 2.5.7
|
||||
* Fix: Check for any free shipping which has its requirements met - not just the first one. PR#3329
|
||||
* Fix: Fix un-purchasability issues with limited subscriptions in manual renewal carts. PR#3358
|
||||
* Tweak: Update email address to create support admin user with. PR#3363
|
||||
* Tweak: Update the plugin headers to include Automattic. PR#3365
|
||||
|
||||
2019.05.30 - version 2.5.6
|
||||
* Fix: Exclude core order properties from the meta copied from the subscription to early renewal orders. #PR3331
|
||||
* Tweak: Add the 'is-active' class to My Account > My Subscription menu item when user is on View Subscription page. PR#3328
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
/**
|
||||
* WCS_Admin_Assets Class
|
||||
*
|
||||
* Handles admin assets (scripts and styles) for WooCommerce Subscriptions.
|
||||
*
|
||||
* @package WooCommerce Subscriptions/Admin
|
||||
*/
|
||||
class WCS_Admin_Assets {
|
||||
|
||||
/**
|
||||
* Initialize the tour handler
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue required scripts and styles
|
||||
*/
|
||||
public static function enqueue_scripts() {
|
||||
$script_asset_path = \WC_Subscriptions_Plugin::instance()->get_plugin_directory( 'build/admin.asset.php' );
|
||||
$script_asset = file_exists( $script_asset_path )
|
||||
? require $script_asset_path
|
||||
: array(
|
||||
'dependencies' => array(
|
||||
'react',
|
||||
'wc-blocks-checkout',
|
||||
'wc-price-format',
|
||||
'wc-settings',
|
||||
'wp-element',
|
||||
'wp-i18n',
|
||||
'wp-plugins',
|
||||
),
|
||||
'version' => WC_Subscriptions::$version,
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'wcs-admin',
|
||||
plugins_url( '/build/admin.js', WC_Subscriptions::$plugin_file ),
|
||||
$script_asset['dependencies'],
|
||||
$script_asset['version'],
|
||||
true
|
||||
);
|
||||
|
||||
wp_enqueue_style(
|
||||
'wcs-admin',
|
||||
plugins_url( '/build/style-admin.css', WC_Subscriptions::$plugin_file ),
|
||||
array( 'wp-components' ),
|
||||
$script_asset['version']
|
||||
);
|
||||
|
||||
wp_set_script_translations(
|
||||
'wcs-admin',
|
||||
'woocommerce-subscriptions',
|
||||
plugin_dir_path( WC_Subscriptions::$plugin_file ) . 'languages'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,305 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* WooCommerce Subscriptions Admin Meta Boxes
|
||||
*
|
||||
* Sets up the write panels used by the subscription custom order/post type
|
||||
*
|
||||
* @author Prospress
|
||||
* @category Admin
|
||||
* @package WooCommerce Subscriptions/Admin
|
||||
* @version 2.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Admin_Meta_Boxes
|
||||
*/
|
||||
class WCS_Admin_Meta_Boxes {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ), 25 );
|
||||
|
||||
add_action( 'add_meta_boxes', array( $this, 'remove_meta_boxes' ), 35 );
|
||||
|
||||
// We need to remove core WC save methods for meta boxes we don't use
|
||||
add_action( 'woocommerce_process_shop_order_meta', array( $this, 'remove_meta_box_save' ), -1, 2 );
|
||||
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_styles_scripts' ), 20 );
|
||||
|
||||
// We need to hook to the 'shop_order' rather than 'shop_subscription' because we declared that the 'shop_susbcription' order type supports 'order-meta-boxes'
|
||||
add_action( 'woocommerce_process_shop_order_meta', 'WCS_Meta_Box_Schedule::save', 10, 2 );
|
||||
add_action( 'woocommerce_process_shop_order_meta', 'WCS_Meta_Box_Subscription_Data::save', 10, 2 );
|
||||
|
||||
add_filter( 'woocommerce_order_actions', __CLASS__ . '::add_subscription_actions', 10, 1 );
|
||||
|
||||
add_action( 'woocommerce_order_action_wcs_process_renewal', __CLASS__ . '::process_renewal_action_request', 10, 1 );
|
||||
add_action( 'woocommerce_order_action_wcs_create_pending_renewal', __CLASS__ . '::create_pending_renewal_action_request', 10, 1 );
|
||||
add_action( 'woocommerce_order_action_wcs_create_pending_parent', __CLASS__ . '::create_pending_parent_action_request', 10, 1 );
|
||||
|
||||
if ( WC_Subscriptions::is_woocommerce_pre( '3.2' ) ) {
|
||||
add_filter( 'woocommerce_resend_order_emails_available', __CLASS__ . '::remove_order_email_actions', 0, 1 );
|
||||
}
|
||||
|
||||
add_action( 'woocommerce_order_action_wcs_retry_renewal_payment', __CLASS__ . '::process_retry_renewal_payment_action_request', 10, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add WC Meta boxes
|
||||
*/
|
||||
public function add_meta_boxes() {
|
||||
global $post_ID;
|
||||
|
||||
add_meta_box( 'woocommerce-subscription-data', _x( 'Subscription Data', 'meta box title', 'woocommerce-subscriptions' ), 'WCS_Meta_Box_Subscription_Data::output', 'shop_subscription', 'normal', 'high' );
|
||||
|
||||
add_meta_box( 'woocommerce-subscription-schedule', _x( 'Schedule', 'meta box title', 'woocommerce-subscriptions' ), 'WCS_Meta_Box_Schedule::output', 'shop_subscription', 'side', 'default' );
|
||||
|
||||
remove_meta_box( 'woocommerce-order-data', 'shop_subscription', 'normal' );
|
||||
|
||||
add_meta_box( 'subscription_renewal_orders', __( 'Related Orders', 'woocommerce-subscriptions' ), 'WCS_Meta_Box_Related_Orders::output', 'shop_subscription', 'normal', 'low' );
|
||||
|
||||
// Only display the meta box if an order relates to a subscription
|
||||
if ( 'shop_order' === get_post_type( $post_ID ) && wcs_order_contains_subscription( $post_ID, 'any' ) ) {
|
||||
add_meta_box( 'subscription_renewal_orders', __( 'Related Orders', 'woocommerce-subscriptions' ), 'WCS_Meta_Box_Related_Orders::output', 'shop_order', 'normal', 'low' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the core Order Data meta box as we add our own Subscription Data meta box
|
||||
*/
|
||||
public function remove_meta_boxes() {
|
||||
remove_meta_box( 'woocommerce-order-data', 'shop_subscription', 'normal' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't save save some order related meta boxes
|
||||
*/
|
||||
public function remove_meta_box_save( $post_id, $post ) {
|
||||
|
||||
if ( 'shop_subscription' == $post->post_type ) {
|
||||
remove_action( 'woocommerce_process_shop_order_meta', 'WC_Meta_Box_Order_Data::save', 40 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print admin styles/scripts
|
||||
*/
|
||||
public function enqueue_styles_scripts() {
|
||||
global $post;
|
||||
|
||||
// Get admin screen id
|
||||
$screen = get_current_screen();
|
||||
$screen_id = isset( $screen->id ) ? $screen->id : '';
|
||||
|
||||
if ( 'shop_subscription' == $screen_id ) {
|
||||
|
||||
wp_register_script( 'jstz', plugin_dir_url( WC_Subscriptions::$plugin_file ) . 'assets/js/admin/jstz.min.js' );
|
||||
|
||||
wp_register_script( 'momentjs', plugin_dir_url( WC_Subscriptions::$plugin_file ) . 'assets/js/admin/moment.min.js' );
|
||||
|
||||
wp_enqueue_script( 'wcs-admin-meta-boxes-subscription', plugin_dir_url( WC_Subscriptions::$plugin_file ) . 'assets/js/admin/meta-boxes-subscription.js', array( 'wc-admin-meta-boxes', 'jstz', 'momentjs' ), WC_VERSION );
|
||||
|
||||
wp_localize_script( 'wcs-admin-meta-boxes-subscription', 'wcs_admin_meta_boxes', apply_filters( 'woocommerce_subscriptions_admin_meta_boxes_script_parameters', array(
|
||||
'i18n_start_date_notice' => __( 'Please enter a start date in the past.', 'woocommerce-subscriptions' ),
|
||||
'i18n_past_date_notice' => __( 'Please enter a date at least one hour into the future.', 'woocommerce-subscriptions' ),
|
||||
'i18n_next_payment_start_notice' => __( 'Please enter a date after the trial end.', 'woocommerce-subscriptions' ),
|
||||
'i18n_next_payment_trial_notice' => __( 'Please enter a date after the start date.', 'woocommerce-subscriptions' ),
|
||||
'i18n_trial_end_start_notice' => __( 'Please enter a date after the start date.', 'woocommerce-subscriptions' ),
|
||||
'i18n_trial_end_next_notice' => __( 'Please enter a date before the next payment.', 'woocommerce-subscriptions' ),
|
||||
'i18n_end_date_notice' => __( 'Please enter a date after the next payment.', 'woocommerce-subscriptions' ),
|
||||
'process_renewal_action_warning' => __( "Are you sure you want to process a renewal?\n\nThis will charge the customer and email them the renewal order (if emails are enabled).", 'woocommerce-subscriptions' ),
|
||||
'payment_method' => wcs_get_subscription( $post )->get_payment_method(),
|
||||
'search_customers_nonce' => wp_create_nonce( 'search-customers' ),
|
||||
'get_customer_orders_nonce' => wp_create_nonce( 'get-customer-orders' ),
|
||||
) ) );
|
||||
} else if ( 'shop_order' == $screen_id ) {
|
||||
|
||||
wp_enqueue_script( 'wcs-admin-meta-boxes-order', plugin_dir_url( WC_Subscriptions::$plugin_file ) . 'assets/js/admin/wcs-meta-boxes-order.js' );
|
||||
|
||||
wp_localize_script( 'wcs-admin-meta-boxes-order', 'wcs_admin_order_meta_boxes', array(
|
||||
'retry_renewal_payment_action_warning' => __( "Are you sure you want to retry payment for this renewal order?\n\nThis will attempt to charge the customer and send renewal order emails (if emails are enabled).", 'woocommerce-subscriptions' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Enqueue the metabox script for coupons.
|
||||
if ( ! WC_Subscriptions::is_woocommerce_pre( '3.2' ) && in_array( $screen_id, array( 'shop_coupon', 'edit-shop_coupon' ) ) ) {
|
||||
wp_enqueue_script(
|
||||
'wcs-admin-coupon-meta-boxes',
|
||||
plugin_dir_url( WC_Subscriptions::$plugin_file ) . 'assets/js/admin/meta-boxes-coupon.js',
|
||||
array( 'jquery', 'wc-admin-meta-boxes' ),
|
||||
WC_Subscriptions::$version
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds actions to the admin edit subscriptions page, if the subscription hasn't ended and the payment method supports them.
|
||||
*
|
||||
* @param array $actions An array of available actions
|
||||
* @return array An array of updated actions
|
||||
* @since 2.0
|
||||
*/
|
||||
public static function add_subscription_actions( $actions ) {
|
||||
global $theorder;
|
||||
|
||||
if ( wcs_is_subscription( $theorder ) ) {
|
||||
if ( ! WC_Subscriptions::is_woocommerce_pre( '3.2' ) ) {
|
||||
unset( $actions['send_order_details'], $actions['send_order_details_admin'] );
|
||||
}
|
||||
|
||||
if ( ! $theorder->has_status( wcs_get_subscription_ended_statuses() ) ) {
|
||||
if ( $theorder->payment_method_supports( 'subscription_date_changes' ) && $theorder->has_status( 'active' ) ) {
|
||||
$actions['wcs_process_renewal'] = esc_html__( 'Process renewal', 'woocommerce-subscriptions' );
|
||||
}
|
||||
|
||||
if ( count( $theorder->get_related_orders() ) > 0 ) {
|
||||
$actions['wcs_create_pending_renewal'] = esc_html__( 'Create pending renewal order', 'woocommerce-subscriptions' );
|
||||
} else {
|
||||
$actions['wcs_create_pending_parent'] = esc_html__( 'Create pending parent order', 'woocommerce-subscriptions' );
|
||||
}
|
||||
}
|
||||
} else if ( self::can_renewal_order_be_retried( $theorder ) ) {
|
||||
$actions['wcs_retry_renewal_payment'] = esc_html__( 'Retry Renewal Payment', 'woocommerce-subscriptions' );
|
||||
}
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the action request to process a renewal order.
|
||||
*
|
||||
* @param array $subscription
|
||||
* @since 2.0
|
||||
*/
|
||||
public static function process_renewal_action_request( $subscription ) {
|
||||
do_action( 'woocommerce_scheduled_subscription_payment', $subscription->get_id() );
|
||||
$subscription->add_order_note( __( 'Process renewal order action requested by admin.', 'woocommerce-subscriptions' ), false, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the action request to create a pending renewal order.
|
||||
*
|
||||
* @param array $subscription
|
||||
* @since 2.0
|
||||
*/
|
||||
public static function create_pending_renewal_action_request( $subscription ) {
|
||||
|
||||
$subscription->update_status( 'on-hold' );
|
||||
|
||||
$renewal_order = wcs_create_renewal_order( $subscription );
|
||||
|
||||
if ( ! $subscription->is_manual() ) {
|
||||
|
||||
$renewal_order->set_payment_method( wc_get_payment_gateway_by_order( $subscription ) ); // We need to pass the payment gateway instance to be compatible with WC < 3.0, only WC 3.0+ supports passing the string name
|
||||
|
||||
if ( is_callable( array( $renewal_order, 'save' ) ) ) { // WC 3.0+
|
||||
$renewal_order->save();
|
||||
}
|
||||
}
|
||||
|
||||
$subscription->add_order_note( __( 'Create pending renewal order requested by admin action.', 'woocommerce-subscriptions' ), false, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the action request to create a pending parent order.
|
||||
*
|
||||
* @param array $subscription
|
||||
* @since 2.3
|
||||
*/
|
||||
public static function create_pending_parent_action_request( $subscription ) {
|
||||
|
||||
if ( ! $subscription->has_status( array( 'pending', 'on-hold' ) ) ) {
|
||||
$subscription->update_status( 'on-hold' );
|
||||
}
|
||||
|
||||
$parent_order = wcs_create_order_from_subscription( $subscription, 'parent' );
|
||||
|
||||
$subscription->set_parent_id( wcs_get_objects_property( $parent_order, 'id' ) );
|
||||
$subscription->save();
|
||||
|
||||
if ( ! $subscription->is_manual() ) {
|
||||
|
||||
$parent_order->set_payment_method( wc_get_payment_gateway_by_order( $subscription ) ); // We need to pass the payment gateway instance to be compatible with WC < 3.0, only WC 3.0+ supports passing the string name
|
||||
|
||||
if ( is_callable( array( $parent_order, 'save' ) ) ) { // WC 3.0+
|
||||
$parent_order->save();
|
||||
}
|
||||
}
|
||||
|
||||
$subscription->add_order_note( __( 'Create pending parent order requested by admin action.', 'woocommerce-subscriptions' ), false, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes order related emails from the available actions.
|
||||
*
|
||||
* @param array $available_emails
|
||||
* @since 2.0
|
||||
*/
|
||||
public static function remove_order_email_actions( $email_actions ) {
|
||||
global $theorder;
|
||||
|
||||
if ( wcs_is_subscription( $theorder ) ) {
|
||||
$email_actions = array();
|
||||
}
|
||||
|
||||
return $email_actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the action request to retry renewal payment for failed renewal orders.
|
||||
*
|
||||
* @param WC_Order $order
|
||||
* @since 2.1
|
||||
*/
|
||||
public static function process_retry_renewal_payment_action_request( $order ) {
|
||||
|
||||
if ( self::can_renewal_order_be_retried( $order ) ) {
|
||||
// init payment gateways
|
||||
WC()->payment_gateways();
|
||||
|
||||
do_action( 'woocommerce_scheduled_subscription_payment_' . wcs_get_objects_property( $order, 'payment_method' ), $order->get_total(), $order );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a renewal order payment can be retried. A renewal order payment can only be retried when:
|
||||
* - Order is a renewal order
|
||||
* - Order status is failed
|
||||
* - Order payment method isn't empty
|
||||
* - Order total > 0
|
||||
* - Subscription/s aren't manual
|
||||
* - Subscription payment method supports date changes
|
||||
* - Order payment method has_action('woocommerce_scheduled_subscription_payment_..')
|
||||
*
|
||||
* @param WC_Order $order
|
||||
* @return bool
|
||||
* @since 2.1
|
||||
*/
|
||||
private static function can_renewal_order_be_retried( $order ) {
|
||||
|
||||
$can_be_retried = false;
|
||||
|
||||
if ( wcs_order_contains_renewal( $order ) && $order->needs_payment() && '' != wcs_get_objects_property( $order, 'payment_method' ) ) {
|
||||
$supports_date_changes = false;
|
||||
$order_payment_gateway = wc_get_payment_gateway_by_order( $order );
|
||||
$order_payment_gateway_supports = ( isset( $order_payment_gateway->id ) ) ? has_action( 'woocommerce_scheduled_subscription_payment_' . $order_payment_gateway->id ) : false;
|
||||
|
||||
foreach ( wcs_get_subscriptions_for_renewal_order( $order ) as $subscription ) {
|
||||
$supports_date_changes = $subscription->payment_method_supports( 'subscription_date_changes' );
|
||||
$is_automatic = ! $subscription->is_manual();
|
||||
break;
|
||||
}
|
||||
|
||||
$can_be_retried = $order_payment_gateway_supports && $supports_date_changes && $is_automatic;
|
||||
}
|
||||
|
||||
return $can_be_retried;
|
||||
}
|
||||
}
|
||||
|
|
@ -23,27 +23,84 @@ class WCS_Admin_Reports {
|
|||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
// The subscription reports are compatible with HPOS since 7.8.0.
|
||||
// We can inform users running data sync mode, that it's no longer needed.
|
||||
if (
|
||||
wcs_is_custom_order_tables_usage_enabled() &&
|
||||
wcs_is_custom_order_tables_data_sync_enabled()
|
||||
) {
|
||||
add_action( 'admin_notices', [ __CLASS__, 'display_hpos_compatibility_notice' ] );
|
||||
}
|
||||
|
||||
// Add the reports layout to the WooCommerce -> Reports admin section
|
||||
add_filter( 'woocommerce_admin_reports', __CLASS__ . '::initialize_reports', 12, 1 );
|
||||
add_filter( 'woocommerce_admin_reports', __CLASS__ . '::initialize_reports', 12, 1 );
|
||||
|
||||
// Add any necessary scripts
|
||||
add_action( 'admin_enqueue_scripts', __CLASS__ . '::reports_scripts' );
|
||||
|
||||
// Add any actions we need based on the screen
|
||||
add_action( 'current_screen', __CLASS__ . '::conditional_reporting_includes' );
|
||||
|
||||
// Starting from WooCommerce 10.0 the dashboard widget is loaded asynchronously.
|
||||
// We also need to hook into AJAX request before WooCommerce so we can attach our hook to widget rendering flow.
|
||||
add_action( 'wp_ajax_woocommerce_load_status_widget', __CLASS__ . '::init_dashboard_report', 9 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an admin notice indicating subscription reports are compatible with HPOS.
|
||||
*
|
||||
* @since 7.8.0
|
||||
*/
|
||||
public static function display_hpos_compatibility_notice() {
|
||||
$screen = get_current_screen();
|
||||
|
||||
// Only display the admin notice on report admin screens.
|
||||
if ( ! $screen || 'woocommerce_page_wc-reports' !== $screen->id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$nonce_name = 'wcs_reports_hpos_compatibility_notice';
|
||||
$option_name = 'woocommerce_subscriptions_reports_hpos_compatibility_notice_dismissed';
|
||||
|
||||
$is_dismissed = get_option( $option_name );
|
||||
|
||||
if ( 'yes' === $is_dismissed ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isset( $_GET['_wcsnonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wcsnonce'] ) ), $nonce_name ) && ! empty( $_GET[ $nonce_name ] ) ) {
|
||||
update_option( $option_name, 'yes' );
|
||||
return;
|
||||
}
|
||||
|
||||
$dismiss_url = wp_nonce_url( add_query_arg( $nonce_name, '1' ), $nonce_name, '_wcsnonce' );
|
||||
|
||||
$admin_notice = new WCS_Admin_Notice( 'notice notice-info is-dismissible', array(), $dismiss_url );
|
||||
|
||||
$content = sprintf(
|
||||
// translators: placeholders $1 and $2 are opening <a> tags linking to the WooCommerce documentation on HPOS, and to the Advanced Features settings screen. Placeholder $3 is a closing link (</a>) tag.
|
||||
__( 'WooCommerce Subscriptions now supports %1$sHigh-Performance Order Storage (HPOS)%3$s - compatibility mode is no longer required to view subscriptions reports. You can disable compatibility mode in your %2$sstore settings%3$s.', 'woocommerce-subscriptions' ),
|
||||
'<a href="https://woocommerce.com/document/high-performance-order-storage/">',
|
||||
'<a href="' . esc_url( get_admin_url( null, 'admin.php?page=wc-settings&tab=advanced§ion=features' ) ) . '">',
|
||||
'</a>'
|
||||
);
|
||||
|
||||
$admin_notice->set_html_content( "<p>{$content}</p>" );
|
||||
|
||||
$admin_notice->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the 'Subscriptions' report type to the WooCommerce reports screen.
|
||||
*
|
||||
* @param array Array of Report types & their labels, excluding the Subscription product type.
|
||||
* @param array $reports Array of Report types & their labels, excluding the Subscription product type.
|
||||
* @return array Array of Report types & their labels, including the Subscription product type.
|
||||
* @since 2.1
|
||||
*/
|
||||
public static function initialize_reports( $reports ) {
|
||||
|
||||
$reports['subscriptions'] = array(
|
||||
'title' => __( 'Subscriptions', 'woocommerce-subscriptions' ),
|
||||
'title' => __( 'Subscriptions', 'woocommerce-subscriptions' ),
|
||||
'reports' => array(
|
||||
'subscription_events_by_date' => array(
|
||||
'title' => __( 'Subscription Events by Date', 'woocommerce-subscriptions' ),
|
||||
|
|
@ -51,25 +108,25 @@ class WCS_Admin_Reports {
|
|||
'hide_title' => true,
|
||||
'callback' => array( 'WCS_Admin_Reports', 'get_report' ),
|
||||
),
|
||||
'upcoming_recurring_revenue' => array(
|
||||
'upcoming_recurring_revenue' => array(
|
||||
'title' => __( 'Upcoming Recurring Revenue', 'woocommerce-subscriptions' ),
|
||||
'description' => '',
|
||||
'hide_title' => true,
|
||||
'callback' => array( 'WCS_Admin_Reports', 'get_report' ),
|
||||
),
|
||||
'retention_rate' => array(
|
||||
'retention_rate' => array(
|
||||
'title' => __( 'Retention Rate', 'woocommerce-subscriptions' ),
|
||||
'description' => '',
|
||||
'hide_title' => true,
|
||||
'callback' => array( 'WCS_Admin_Reports', 'get_report' ),
|
||||
),
|
||||
'subscription_by_product' => array(
|
||||
'subscription_by_product' => array(
|
||||
'title' => __( 'Subscriptions by Product', 'woocommerce-subscriptions' ),
|
||||
'description' => '',
|
||||
'hide_title' => true,
|
||||
'callback' => array( 'WCS_Admin_Reports', 'get_report' ),
|
||||
),
|
||||
'subscription_by_customer' => array(
|
||||
'subscription_by_customer' => array(
|
||||
'title' => __( 'Subscriptions by Customer', 'woocommerce-subscriptions' ),
|
||||
'description' => '',
|
||||
'hide_title' => true,
|
||||
|
|
@ -96,29 +153,32 @@ class WCS_Admin_Reports {
|
|||
* @since 1.5
|
||||
*/
|
||||
public static function reports_scripts() {
|
||||
global $wp_query, $post;
|
||||
|
||||
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
|
||||
|
||||
$screen = get_current_screen();
|
||||
$wc_screen_id = sanitize_title( __( 'WooCommerce', 'woocommerce-subscriptions' ) );
|
||||
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
|
||||
$screen = get_current_screen();
|
||||
$wc_screen_id = sanitize_title( __( 'WooCommerce', 'woocommerce-subscriptions' ) );
|
||||
$version = WC_Subscriptions_Core_Plugin::instance()->get_library_version();
|
||||
|
||||
// Reports Subscriptions Pages
|
||||
if ( in_array( $screen->id, apply_filters( 'woocommerce_reports_screen_ids', array( $wc_screen_id . '_page_wc-reports', 'toplevel_page_wc-reports', 'dashboard' ) ) ) && isset( $_GET['tab'] ) && 'subscriptions' == $_GET['tab'] ) {
|
||||
|
||||
wp_enqueue_script( 'wcs-reports', plugin_dir_url( WC_Subscriptions::$plugin_file ) . 'assets/js/admin/reports.js', array( 'jquery', 'jquery-ui-datepicker', 'wc-reports', 'accounting' ), WC_Subscriptions::$version );
|
||||
wp_enqueue_script( 'wcs-reports', WC_Subscriptions_Plugin::instance()->get_plugin_directory_url( 'assets/js/admin/reports.js' ), array( 'jquery', 'jquery-ui-datepicker', 'wc-reports', 'accounting' ), $version );
|
||||
|
||||
// Add currency localisation params for axis label
|
||||
wp_localize_script( 'wcs-reports', 'wcs_reports', array(
|
||||
'currency_format_num_decimals' => wc_get_price_decimals(),
|
||||
'currency_format_symbol' => get_woocommerce_currency_symbol(),
|
||||
'currency_format_decimal_sep' => esc_js( wc_get_price_decimal_separator() ),
|
||||
'currency_format_thousand_sep' => esc_js( wc_get_price_thousand_separator() ),
|
||||
'currency_format' => esc_js( str_replace( array( '%1$s', '%2$s' ), array( '%s', '%v' ), get_woocommerce_price_format() ) ), // For accounting JS
|
||||
'currency_format_num_decimals' => wc_get_price_decimals(),
|
||||
'currency_format_symbol' => get_woocommerce_currency_symbol(),
|
||||
'currency_format_decimal_sep' => esc_js( wc_get_price_decimal_separator() ),
|
||||
'currency_format_thousand_sep' => esc_js( wc_get_price_thousand_separator() ),
|
||||
'currency_format' => esc_js( str_replace( array( '%1$s', '%2$s' ), array( '%s', '%v' ), get_woocommerce_price_format() ) ), // For accounting JS
|
||||
) );
|
||||
|
||||
wp_enqueue_script( 'flot-order', plugin_dir_url( WC_Subscriptions::$plugin_file ) . 'assets/js/admin/jquery.flot.orderBars' . $suffix . '.js', array( 'jquery', 'flot' ), WC_Subscriptions::$version );
|
||||
wp_enqueue_script( 'flot-axis-labels', plugin_dir_url( WC_Subscriptions::$plugin_file ) . 'assets/js/admin/jquery.flot.axislabels' . $suffix . '.js', array( 'jquery', 'flot' ), WC_Subscriptions::$version );
|
||||
wp_enqueue_script( 'flot-order', WC_Subscriptions_Plugin::instance()->get_plugin_directory_url( 'assets/js/admin/jquery.flot.orderBars' ) . $suffix . '.js', array( 'jquery', 'flot' ), $version );
|
||||
wp_enqueue_script( 'flot-axis-labels', WC_Subscriptions_Plugin::instance()->get_plugin_directory_url( 'assets/js/admin/jquery.flot.axislabels' ) . $suffix . '.js', array( 'jquery', 'flot' ), $version );
|
||||
|
||||
// Add tracks script if tracking is enabled.
|
||||
if ( 'yes' === get_option( 'woocommerce_allow_tracking', 'no' ) ) {
|
||||
wp_enqueue_script( 'wcs-tracks', WC_Subscriptions_Plugin::instance()->get_plugin_directory_url( 'assets/js/admin/tracks.js' ), array( 'jquery' ), $version, true );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -131,18 +191,25 @@ class WCS_Admin_Reports {
|
|||
|
||||
$screen = get_current_screen();
|
||||
|
||||
switch ( $screen->id ) {
|
||||
case 'dashboard' :
|
||||
new WCS_Report_Dashboard();
|
||||
break;
|
||||
// Before WooCommerce 10.0 the dashboard widget was loaded synchronously on the dashboard screen. Keep this for backward compatibility.
|
||||
if ( isset( $screen->id ) && 'dashboard' === $screen->id ) {
|
||||
self::init_dashboard_report();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the dashboard report.
|
||||
*
|
||||
* Used for loading the dashboard widget sync and async.
|
||||
*/
|
||||
public static function init_dashboard_report() {
|
||||
new WCS_Report_Dashboard();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a report from one of our classes.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $name report name to be fetched.
|
||||
*/
|
||||
public static function get_report( $name ) {
|
||||
$name = sanitize_title( str_replace( '_', '-', $name ) );
|
||||
|
|
@ -154,33 +221,35 @@ class WCS_Admin_Reports {
|
|||
|
||||
$report = new $class();
|
||||
$report->output_report();
|
||||
}
|
||||
|
||||
/**
|
||||
* If we hit one of our reports in the WC get_report function, change the path to our dir.
|
||||
*
|
||||
* @param string $report_path the parth to the report.
|
||||
* @param string $name the name of the report.
|
||||
* @param string $class the class of the report.
|
||||
*
|
||||
* @return string path to the report template.
|
||||
* @since 2.1
|
||||
* @deprecated in favor of autoloading
|
||||
* @access private
|
||||
*/
|
||||
public static function initialize_reports_path( $report_path, $name, $class ) {
|
||||
_deprecated_function( __METHOD__, '2.4.0' );
|
||||
if ( in_array( strtolower( $class ), array(
|
||||
'wc_report_subscription_events_by_date',
|
||||
'wc_report_upcoming_recurring_revenue',
|
||||
'wc_report_retention_rate',
|
||||
'wc_report_subscription_by_product',
|
||||
'wc_report_subscription_by_customer',
|
||||
'wc_report_subscription_payment_retry',
|
||||
) ) ) {
|
||||
$report_path = dirname( __FILE__ ) . '/reports/classwcsreport' . $name . '.php';
|
||||
if ( class_exists( 'WC_Tracks' ) ) {
|
||||
|
||||
$reports = array(
|
||||
'subscription-events-by-date' => 'subscriptions_report_events_by_date_view',
|
||||
'upcoming-recurring-revenue' => 'subscriptions_report_upcoming_recurring_revenue_view',
|
||||
'retention-rate' => 'subscriptions_report_retention_rate_view',
|
||||
'subscription-by-product' => 'subscriptions_report_by_product_view',
|
||||
'subscription-by-customer' => 'subscriptions_report_by_customer_view',
|
||||
'subscription-payment-retry' => 'subscriptions_report_payment_retry_view',
|
||||
);
|
||||
|
||||
$properties = array(
|
||||
'orders_count' => array_sum( (array) wp_count_posts( 'shop_order' ) ),
|
||||
'subscriptions_count' => array_sum( (array) wp_count_posts( 'shop_subscription' ) ),
|
||||
'subscriptions_version' => WC_Subscriptions_Plugin::instance()->get_plugin_version(), // get_plugin_version() is used here to report the correct WCS version.
|
||||
);
|
||||
|
||||
if ( in_array( $name, array( 'subscription-events-by-date', 'upcoming-recurring-revenue', 'subscription-payment-retry' ), true ) ) {
|
||||
$properties['range'] = ! empty( $_GET['range'] ) ? sanitize_text_field( $_GET['range'] ) : '7day'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.NonceVerification.Recommended
|
||||
if ( 'custom' === $properties['range'] ) {
|
||||
// We have to get start date from _GET variables since $report sets this far into the past when empty.
|
||||
$properties['start_date'] = ! empty( $_GET['start_date'] ) ? sanitize_text_field( $_GET['start_date'] ) : null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.NonceVerification.Recommended
|
||||
$properties['end_date'] = gmdate( 'Y-m-d', $report->end_date );
|
||||
$properties['span'] = $properties['start_date'] ? floor( ( $report->end_date - $report->start_date ) / DAY_IN_SECONDS ) + 1 . 'day' : null;
|
||||
}
|
||||
}
|
||||
|
||||
WC_Tracks::record_event( $reports[ $name ], $properties );
|
||||
}
|
||||
|
||||
return $report_path;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,121 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Related Orders Meta Box
|
||||
*
|
||||
* Display the related orders table on the Edit Order and Edit Subscription screens.
|
||||
*
|
||||
* @author Prospress
|
||||
* @category Admin
|
||||
* @package WooCommerce Subscriptions/Admin/Meta Boxes
|
||||
* @version 2.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* WCS_Meta_Box_Related_Orders Class
|
||||
*/
|
||||
class WCS_Meta_Box_Related_Orders {
|
||||
|
||||
/**
|
||||
* Output the metabox
|
||||
*/
|
||||
public static function output( $post ) {
|
||||
|
||||
if ( wcs_is_subscription( $post->ID ) ) {
|
||||
$subscription = wcs_get_subscription( $post->ID );
|
||||
$order = ( false == $subscription->get_parent_id() ) ? $subscription : $subscription->get_parent();
|
||||
} else {
|
||||
$order = wc_get_order( $post->ID );
|
||||
}
|
||||
|
||||
add_action( 'woocommerce_subscriptions_related_orders_meta_box_rows', __CLASS__ . '::output_rows', 10 );
|
||||
|
||||
include_once( dirname( __FILE__ ) . '/views/html-related-orders-table.php' );
|
||||
|
||||
do_action( 'woocommerce_subscriptions_related_orders_meta_box', $order, $post );
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the renewal orders in the Related Orders meta box.
|
||||
*
|
||||
* @param object $post A WordPress post
|
||||
* @since 2.0
|
||||
*/
|
||||
public static function output_rows( $post ) {
|
||||
|
||||
$subscriptions = array();
|
||||
$orders = array();
|
||||
$is_subscription_screen = wcs_is_subscription( $post->ID );
|
||||
|
||||
// On the subscription page, just show related orders
|
||||
if ( $is_subscription_screen ) {
|
||||
$this_subscription = wcs_get_subscription( $post->ID );
|
||||
$subscriptions[] = $this_subscription;
|
||||
} elseif ( wcs_order_contains_subscription( $post->ID, array( 'parent', 'renewal' ) ) ) {
|
||||
$subscriptions = wcs_get_subscriptions_for_order( $post->ID, array( 'order_type' => array( 'parent', 'renewal' ) ) );
|
||||
}
|
||||
|
||||
// First, display all the subscriptions
|
||||
foreach ( $subscriptions as $subscription ) {
|
||||
wcs_set_objects_property( $subscription, 'relationship', __( 'Subscription', 'woocommerce-subscriptions' ), 'set_prop_only' );
|
||||
$orders[] = $subscription;
|
||||
}
|
||||
|
||||
//Resubscribed
|
||||
$initial_subscriptions = array();
|
||||
|
||||
if ( $is_subscription_screen ) {
|
||||
|
||||
$initial_subscriptions = wcs_get_subscriptions_for_resubscribe_order( $this_subscription );
|
||||
|
||||
$resubscribe_order_ids = WCS_Related_Order_Store::instance()->get_related_order_ids( $this_subscription, 'resubscribe' );
|
||||
|
||||
foreach ( $resubscribe_order_ids as $order_id ) {
|
||||
$order = wc_get_order( $order_id );
|
||||
$relation = wcs_is_subscription( $order ) ? _x( 'Resubscribed Subscription', 'relation to order', 'woocommerce-subscriptions' ) : _x( 'Resubscribe Order', 'relation to order', 'woocommerce-subscriptions' );
|
||||
wcs_set_objects_property( $order, 'relationship', $relation, 'set_prop_only' );
|
||||
$orders[] = $order;
|
||||
}
|
||||
} else if ( wcs_order_contains_subscription( $post->ID, array( 'resubscribe' ) ) ) {
|
||||
$initial_subscriptions = wcs_get_subscriptions_for_order( $post->ID, array( 'order_type' => array( 'resubscribe' ) ) );
|
||||
}
|
||||
|
||||
foreach ( $initial_subscriptions as $subscription ) {
|
||||
wcs_set_objects_property( $subscription, 'relationship', _x( 'Initial Subscription', 'relation to order', 'woocommerce-subscriptions' ), 'set_prop_only' );
|
||||
$orders[] = $subscription;
|
||||
}
|
||||
|
||||
// Now, if we're on a single subscription or renewal order's page, display the parent orders
|
||||
if ( 1 == count( $subscriptions ) ) {
|
||||
foreach ( $subscriptions as $subscription ) {
|
||||
if ( $subscription->get_parent_id() ) {
|
||||
$order = $subscription->get_parent();
|
||||
wcs_set_objects_property( $order, 'relationship', _x( 'Parent Order', 'relation to order', 'woocommerce-subscriptions' ), 'set_prop_only' );
|
||||
$orders[] = $order;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, display the renewal orders
|
||||
foreach ( $subscriptions as $subscription ) {
|
||||
|
||||
foreach ( $subscription->get_related_orders( 'all', 'renewal' ) as $order ) {
|
||||
wcs_set_objects_property( $order, 'relationship', _x( 'Renewal Order', 'relation to order', 'woocommerce-subscriptions' ), 'set_prop_only' );
|
||||
$orders[] = $order;
|
||||
}
|
||||
}
|
||||
|
||||
$orders = apply_filters( 'woocommerce_subscriptions_admin_related_orders_to_display', $orders, $subscriptions, $post );
|
||||
|
||||
foreach ( $orders as $order ) {
|
||||
|
||||
if ( wcs_get_objects_property( $order, 'id' ) == $post->ID ) {
|
||||
continue;
|
||||
}
|
||||
include( dirname( __FILE__ ) . '/views/html-related-orders-row.php' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Subscription Billing Schedule
|
||||
*
|
||||
* @author Prospress
|
||||
* @category Admin
|
||||
* @package WooCommerce Subscriptions/Admin/Meta Boxes
|
||||
* @version 2.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* WCS_Meta_Box_Schedule
|
||||
*/
|
||||
class WCS_Meta_Box_Schedule {
|
||||
|
||||
/**
|
||||
* Output the metabox
|
||||
*/
|
||||
public static function output( $post ) {
|
||||
global $post, $the_subscription;
|
||||
|
||||
if ( empty( $the_subscription ) ) {
|
||||
$the_subscription = wcs_get_subscription( $post->ID );
|
||||
}
|
||||
|
||||
include( dirname( __FILE__ ) . '/views/html-subscription-schedule.php' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save meta box data
|
||||
*/
|
||||
public static function save( $post_id, $post ) {
|
||||
|
||||
if ( 'shop_subscription' == $post->post_type && ! empty( $_POST['woocommerce_meta_nonce'] ) && wp_verify_nonce( $_POST['woocommerce_meta_nonce'], 'woocommerce_save_data' ) ) {
|
||||
|
||||
if ( isset( $_POST['_billing_interval'] ) ) {
|
||||
update_post_meta( $post_id, '_billing_interval', $_POST['_billing_interval'] );
|
||||
}
|
||||
|
||||
if ( ! empty( $_POST['_billing_period'] ) ) {
|
||||
update_post_meta( $post_id, '_billing_period', $_POST['_billing_period'] );
|
||||
}
|
||||
|
||||
$subscription = wcs_get_subscription( $post_id );
|
||||
|
||||
$dates = array();
|
||||
|
||||
foreach ( wcs_get_subscription_date_types() as $date_type => $date_label ) {
|
||||
$date_key = wcs_normalise_date_type_key( $date_type );
|
||||
|
||||
if ( 'last_order_date_created' == $date_key ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$utc_timestamp_key = $date_type . '_timestamp_utc';
|
||||
|
||||
// A subscription needs a created date, even if it wasn't set or is empty
|
||||
if ( 'date_created' === $date_key && empty( $_POST[ $utc_timestamp_key ] ) ) {
|
||||
$datetime = current_time( 'timestamp', true );
|
||||
} elseif ( isset( $_POST[ $utc_timestamp_key ] ) ) {
|
||||
$datetime = $_POST[ $utc_timestamp_key ];
|
||||
} else { // No date to set
|
||||
continue;
|
||||
}
|
||||
|
||||
$dates[ $date_key ] = gmdate( 'Y-m-d H:i:s', $datetime );
|
||||
}
|
||||
|
||||
try {
|
||||
$subscription->update_dates( $dates, 'gmt' );
|
||||
|
||||
wp_cache_delete( $post_id, 'posts' );
|
||||
} catch ( Exception $e ) {
|
||||
wcs_add_admin_notice( $e->getMessage(), 'error' );
|
||||
}
|
||||
|
||||
$subscription->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Display a row in the related orders table for a subscription or order
|
||||
*
|
||||
* @var array $order A WC_Order or WC_Subscription order object to display
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
// WC 3.0+ compatibility
|
||||
$order_post = wcs_get_objects_property( $order, 'post' );
|
||||
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="<?php echo esc_url( get_edit_post_link( wcs_get_objects_property( $order, 'id' ) ) ); ?>">
|
||||
<?php echo sprintf( esc_html_x( '#%s', 'hash before order number', 'woocommerce-subscriptions' ), esc_html( $order->get_order_number() ) ); ?>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<?php echo esc_html( wcs_get_objects_property( $order, 'relationship' ) ); ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php
|
||||
$timestamp_gmt = wcs_get_objects_property( $order, 'date_created' )->getTimestamp();
|
||||
if ( $timestamp_gmt > 0 ) {
|
||||
// translators: php date format
|
||||
$t_time = get_the_time( _x( 'Y/m/d g:i:s A', 'post date', 'woocommerce-subscriptions' ), $order_post );
|
||||
$date_to_display = ucfirst( wcs_get_human_time_diff( $timestamp_gmt ) );
|
||||
} else {
|
||||
$t_time = $date_to_display = __( 'Unpublished', 'woocommerce-subscriptions' );
|
||||
} ?>
|
||||
<abbr title="<?php echo esc_attr( $t_time ); ?>">
|
||||
<?php echo esc_html( apply_filters( 'post_date_column_time', $date_to_display, $order_post ) ); ?>
|
||||
</abbr>
|
||||
</td>
|
||||
<td>
|
||||
<?php
|
||||
if ( wcs_is_subscription( $order ) ) {
|
||||
echo esc_html( wcs_get_subscription_status_name( $order->get_status( 'view' ) ) );
|
||||
} else {
|
||||
echo esc_html( wc_get_order_status_name( $order->get_status( 'view' ) ) );
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
<td>
|
||||
<span class="amount"><?php echo wp_kses( $order->get_formatted_order_total(), array( 'small' => array(), 'span' => array( 'class' => array() ), 'del' => array(), 'ins' => array() ) ); ?></span>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Display the related orders for a subscription or order
|
||||
*
|
||||
* @var object $post The primitive post object that is being displayed (as an order or subscription)
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="woocommerce_subscriptions_related_orders">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php esc_html_e( 'Order Number', 'woocommerce-subscriptions' ); ?></th>
|
||||
<th><?php esc_html_e( 'Relationship', 'woocommerce-subscriptions' ); ?></th>
|
||||
<th><?php esc_html_e( 'Date', 'woocommerce-subscriptions' ); ?></th>
|
||||
<th><?php esc_html_e( 'Status', 'woocommerce-subscriptions' ); ?></th>
|
||||
<th><?php echo esc_html_x( 'Total', 'table heading', 'woocommerce-subscriptions' ); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php do_action( 'woocommerce_subscriptions_related_orders_meta_box_rows', $post ); ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
@ -33,7 +33,7 @@ class WCS_Report_Cache_Manager {
|
|||
4 => 'WCS_Report_Subscription_By_Product',
|
||||
5 => 'WCS_Report_Subscription_By_Customer',
|
||||
),
|
||||
'woocommerce_subscription_payment_complete' => array( // this hook takes care of renewal, switch and initial payments
|
||||
'woocommerce_subscription_payment_complete' => array( // this hook takes care of renewal, switch and initial payments
|
||||
0 => 'WCS_Report_Dashboard',
|
||||
1 => 'WCS_Report_Subscription_Events_By_Date',
|
||||
5 => 'WCS_Report_Subscription_By_Customer',
|
||||
|
|
@ -41,18 +41,18 @@ class WCS_Report_Cache_Manager {
|
|||
'woocommerce_subscriptions_switch_completed' => array(
|
||||
1 => 'WCS_Report_Subscription_Events_By_Date',
|
||||
),
|
||||
'woocommerce_subscription_status_changed' => array(
|
||||
'woocommerce_subscription_status_changed' => array(
|
||||
0 => 'WCS_Report_Dashboard',
|
||||
1 => 'WCS_Report_Subscription_Events_By_Date', // we really only need cancelled, expired and active status here, but we'll use a more generic hook for convenience
|
||||
5 => 'WCS_Report_Subscription_By_Customer',
|
||||
),
|
||||
'woocommerce_subscription_status_active' => array(
|
||||
'woocommerce_subscription_status_active' => array(
|
||||
2 => 'WCS_Report_Upcoming_Recurring_Revenue',
|
||||
),
|
||||
'woocommerce_new_order_item' => array(
|
||||
'woocommerce_new_order_item' => array(
|
||||
4 => 'WCS_Report_Subscription_By_Product',
|
||||
),
|
||||
'woocommerce_update_order_item' => array(
|
||||
'woocommerce_update_order_item' => array(
|
||||
4 => 'WCS_Report_Subscription_By_Product',
|
||||
),
|
||||
);
|
||||
|
|
@ -75,12 +75,11 @@ class WCS_Report_Cache_Manager {
|
|||
/**
|
||||
* Attach callbacks to manage cache updates
|
||||
*
|
||||
* @since 2.1
|
||||
* @since 7.8.0 - Compatible with HPOS, originally introduced in 2.1
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
// Use the old hooks
|
||||
if ( WC_Subscriptions::is_woocommerce_pre( '3.0' ) ) {
|
||||
if ( wcs_is_woocommerce_pre( '3.0' ) ) {
|
||||
|
||||
$hooks = array(
|
||||
'woocommerce_order_add_product' => 'woocommerce_new_order_item',
|
||||
|
|
@ -106,6 +105,8 @@ class WCS_Report_Cache_Manager {
|
|||
|
||||
// Add system status information.
|
||||
add_filter( 'wcs_system_status', array( $this, 'add_system_status_info' ) );
|
||||
|
||||
add_action( 'woocommerce_subscriptions_upgraded', array( $this, 'transfer_large_site_cache_option' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -115,7 +116,7 @@ class WCS_Report_Cache_Manager {
|
|||
* This function is attached as a callback on the events in the $update_events_and_classes property.
|
||||
*
|
||||
* @since 2.1
|
||||
* @return null
|
||||
* @return void
|
||||
*/
|
||||
public function set_reports_to_update() {
|
||||
if ( isset( $this->update_events_and_classes[ current_filter() ] ) ) {
|
||||
|
|
@ -139,37 +140,38 @@ class WCS_Report_Cache_Manager {
|
|||
*/
|
||||
public function schedule_cache_updates() {
|
||||
|
||||
if ( ! empty( $this->reports_to_update ) ) {
|
||||
if ( empty( $this->reports_to_update ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// On large sites, we want to run the cache update once at 4am in the site's timezone
|
||||
if ( $this->use_large_site_cache() ) {
|
||||
// On large sites, we want to run the cache update once at 4am in the site's timezone
|
||||
if ( $this->use_large_site_cache() ) {
|
||||
|
||||
$cache_update_timestamp = $this->get_large_site_cache_update_timestamp();
|
||||
$cache_update_timestamp = $this->get_large_site_cache_update_timestamp();
|
||||
|
||||
// Schedule one update event for each class to avoid updating cache more than once for the same class for different events
|
||||
foreach ( $this->reports_to_update as $index => $report_class ) {
|
||||
// Schedule one update event for each class to avoid updating cache more than once for the same class for different events
|
||||
foreach ( $this->reports_to_update as $index => $report_class ) {
|
||||
|
||||
$cron_args = array( 'report_class' => $report_class );
|
||||
$cron_args = array( 'report_class' => $report_class );
|
||||
|
||||
if ( false === as_next_scheduled_action( $this->cron_hook, $cron_args ) ) {
|
||||
// Use the index to space out caching of each report to make them 15 minutes apart so that on large sites, where we assume they'll get a request at least once every few minutes, we don't try to update the caches of all reports in the same request
|
||||
as_schedule_single_action( $cache_update_timestamp + 15 * MINUTE_IN_SECONDS * ( $index + 1 ), $this->cron_hook, $cron_args );
|
||||
}
|
||||
if ( false === as_next_scheduled_action( $this->cron_hook, $cron_args ) ) {
|
||||
// Use the index to space out caching of each report to make them 15 minutes apart so that on large sites, where we assume they'll get a request at least once every few minutes, we don't try to update the caches of all reports in the same request
|
||||
as_schedule_single_action( $cache_update_timestamp + 15 * MINUTE_IN_SECONDS * ( $index + 1 ), $this->cron_hook, $cron_args );
|
||||
}
|
||||
} else { // Otherwise, run it 10 minutes after the last cache invalidating event
|
||||
}
|
||||
} else { // Otherwise, run it 10 minutes after the last cache invalidating event
|
||||
|
||||
// Schedule one update event for each class to avoid updating cache more than once for the same class for different events
|
||||
foreach ( $this->reports_to_update as $index => $report_class ) {
|
||||
// Schedule one update event for each class to avoid updating cache more than once for the same class for different events
|
||||
foreach ( $this->reports_to_update as $index => $report_class ) {
|
||||
|
||||
$cron_args = array( 'report_class' => $report_class );
|
||||
$cron_args = array( 'report_class' => $report_class );
|
||||
|
||||
if ( false !== as_next_scheduled_action( $this->cron_hook, $cron_args ) ) {
|
||||
as_unschedule_action( $this->cron_hook, $cron_args );
|
||||
}
|
||||
|
||||
// Use the index to space out caching of each report to make them 5 minutes apart so that on large sites, where we assume they'll get a request at least once every few minutes, we don't try to update the caches of all reports in the same request
|
||||
as_schedule_single_action( gmdate( 'U' ) + MINUTE_IN_SECONDS * ( $index + 1 ) * 5, $this->cron_hook, $cron_args );
|
||||
if ( false !== as_next_scheduled_action( $this->cron_hook, $cron_args ) ) {
|
||||
as_unschedule_action( $this->cron_hook, $cron_args );
|
||||
}
|
||||
|
||||
// Use the index to space out caching of each report to make them 5 minutes apart so that on large sites, where we assume they'll get a request at least once every few minutes, we don't try to update the caches of all reports in the same request
|
||||
as_schedule_single_action( (int) gmdate( 'U' ) + MINUTE_IN_SECONDS * ( $index + 1 ) * 5, $this->cron_hook, $cron_args );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -178,7 +180,6 @@ class WCS_Report_Cache_Manager {
|
|||
* Update the cache data for a given report, as specified with $report_class, by call it's get_data() method.
|
||||
*
|
||||
* @since 2.1
|
||||
* @return null
|
||||
*/
|
||||
public function update_cache( $report_class ) {
|
||||
/**
|
||||
|
|
@ -210,18 +211,29 @@ class WCS_Report_Cache_Manager {
|
|||
|
||||
// Load report class dependencies
|
||||
require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
|
||||
require_once( WC()->plugin_path() . '/includes/admin/reports/class-wc-admin-report.php' );
|
||||
|
||||
$within_ci_environment = getenv( 'CI' );
|
||||
$wc_core_dir_from_env = getenv( 'WC_CORE_DIR' );
|
||||
|
||||
if ( $within_ci_environment && ! empty( $wc_core_dir_from_env ) ) {
|
||||
$wc_core_dir = $wc_core_dir_from_env;
|
||||
} elseif ( $within_ci_environment ) {
|
||||
$wc_core_dir = '/tmp/woocommerce';
|
||||
} else {
|
||||
$wc_core_dir = WC()->plugin_path();
|
||||
}
|
||||
|
||||
require_once( $wc_core_dir . '/includes/admin/reports/class-wc-admin-report.php' );
|
||||
|
||||
$reflector = new ReflectionMethod( $report_class, 'get_data' );
|
||||
|
||||
// Some report classes extend WP_List_Table which has a constructor using methods not available on WP-Cron (and unable to be loaded with a __doing_it_wrong() notice), so they have a static get_data() method and do not need to be instantiated
|
||||
if ( $reflector->isStatic() ) {
|
||||
|
||||
call_user_func( array( $report_class, 'clear_cache' ) );
|
||||
call_user_func( array( $report_class, 'get_data' ), array( 'no_cache' => true ) );
|
||||
|
||||
} else {
|
||||
|
||||
$report = new $report_class();
|
||||
$report->clear_cache();
|
||||
|
||||
// Classes with a non-static get_data() method can be displayed for different time series, so we need to update the cache for each of those ranges
|
||||
foreach ( array( 'year', 'last_month', 'month', '7day' ) as $range ) {
|
||||
|
|
@ -244,23 +256,7 @@ class WCS_Report_Cache_Manager {
|
|||
protected function use_large_site_cache() {
|
||||
|
||||
if ( null === $this->use_large_site_cache ) {
|
||||
|
||||
if ( false == get_option( 'wcs_report_use_large_site_cache' ) ) {
|
||||
|
||||
$subscription_counts = (array) wp_count_posts( 'shop_subscription' );
|
||||
$order_counts = (array) wp_count_posts( 'shop_order' );
|
||||
|
||||
if ( array_sum( $subscription_counts ) > 3000 || array_sum( $order_counts ) > 25000 ) {
|
||||
|
||||
update_option( 'wcs_report_use_large_site_cache', 'true', false );
|
||||
|
||||
$this->use_large_site_cache = true;
|
||||
} else {
|
||||
$this->use_large_site_cache = false;
|
||||
}
|
||||
} else {
|
||||
$this->use_large_site_cache = true;
|
||||
}
|
||||
$this->use_large_site_cache = wcs_is_large_site();
|
||||
}
|
||||
|
||||
return apply_filters( 'wcs_report_use_large_site_cache', $this->use_large_site_cache );
|
||||
|
|
@ -336,7 +332,7 @@ class WCS_Report_Cache_Manager {
|
|||
'label' => 'Cache Update Failures',
|
||||
/* translators: %d refers to the number of times we have detected cache update failures */
|
||||
'note' => sprintf( _n( '%d failures', '%d failure', $failures, 'woocommerce-subscriptions' ), $failures ),
|
||||
'success' => 0 === $failures,
|
||||
'success' => 0 === (int)$failures,
|
||||
),
|
||||
);
|
||||
|
||||
|
|
@ -361,4 +357,24 @@ class WCS_Report_Cache_Manager {
|
|||
|
||||
return $cache_update_timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfers the 'wcs_report_use_large_site_cache' option to the new 'wcs_is_large_site' option.
|
||||
*
|
||||
* In 3.0.7 we introduced a more general use option, 'wcs_is_large_site', replacing the need for one specifically
|
||||
* for report caching. This function migrates the existing option value if it was previously set.
|
||||
*
|
||||
* @since 3.0.7
|
||||
*
|
||||
* @param string $new_version The new Subscriptions plugin version.
|
||||
* @param string $previous_version The version of Subscriptions prior to upgrade.
|
||||
*/
|
||||
public function transfer_large_site_cache_option( $new_version, $previous_version ) {
|
||||
|
||||
// Check if the plugin upgrade is from a version prior to the option being deprecated (before 3.0.7).
|
||||
if ( version_compare( $previous_version, '3.0.7', '<' ) && false !== get_option( 'wcs_report_use_large_site_cache' ) ) {
|
||||
update_option( 'wcs_is_large_site', 'yes', false );
|
||||
delete_option( 'wcs_report_use_large_site_cache' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@
|
|||
*
|
||||
* Creates the subscription admin reports area.
|
||||
*
|
||||
* @package WooCommerce Subscriptions
|
||||
* @subpackage WC_Subscriptions_Admin_Reports
|
||||
* @category Class
|
||||
* @author Prospress
|
||||
* @since 2.1
|
||||
* @package WooCommerce Subscriptions
|
||||
* @subpackage WC_Subscriptions_Admin_Reports
|
||||
* @category Class
|
||||
* @author Prospress
|
||||
* @since 2.1
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
|
|
@ -16,6 +16,20 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
}
|
||||
|
||||
class WCS_Report_Dashboard {
|
||||
/**
|
||||
* Tracks whether the cache should be updated after generating report data.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private static $should_update_cache = false;
|
||||
|
||||
/**
|
||||
* Cached report results for performance optimization.
|
||||
*
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $cached_report_results = array();
|
||||
|
||||
/**
|
||||
* Hook in additional reporting to WooCommerce dashboard widget
|
||||
|
|
@ -31,10 +45,13 @@ class WCS_Report_Dashboard {
|
|||
|
||||
/**
|
||||
* Get all data needed for this report and store in the class
|
||||
*
|
||||
* @see WCS_Report_Cache_Manager::update_cache() - This method is called by the cache manager to update the cache.
|
||||
*
|
||||
* @param array $args The arguments for the report.
|
||||
* @return object The report data.
|
||||
*/
|
||||
public static function get_data( $args = array() ) {
|
||||
global $wpdb;
|
||||
|
||||
$default_args = array(
|
||||
'no_cache' => false,
|
||||
);
|
||||
|
|
@ -42,157 +59,23 @@ class WCS_Report_Dashboard {
|
|||
$args = apply_filters( 'wcs_reports_subscription_dashboard_args', $args );
|
||||
$args = wp_parse_args( $args, $default_args );
|
||||
|
||||
$offset = get_option( 'gmt_offset' );
|
||||
self::init_cache();
|
||||
|
||||
// Use this once it is merged - wcs_get_gmt_offset_string();
|
||||
// Convert from Decimal format(eg. 11.5) to a suitable format(eg. +11:30) for CONVERT_TZ() of SQL query.
|
||||
$site_timezone = sprintf( '%+02d:%02d', (int) $offset, ( $offset - floor( $offset ) ) * 60 );
|
||||
// Use current month as default date range.
|
||||
$start_date = $args['start_date'] ?? date( 'Y-m-01', current_time( 'timestamp' ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date,WordPress.DateTime.CurrentTimeTimestamp.Requested -- Keep default date values for backward compatibility.
|
||||
$end_date = $args['end_date'] ?? date( 'Y-m-d', strtotime( '+1 DAY', current_time( 'timestamp' ) ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date,WordPress.DateTime.CurrentTimeTimestamp.Requested -- Keep default date values for backward compatibility.
|
||||
|
||||
$report_data = new stdClass;
|
||||
$report_data = new stdClass();
|
||||
$report_data->signup_count = self::fetch_signup_count( $start_date, $end_date, $args['no_cache'] );
|
||||
$report_data->signup_revenue = self::fetch_signup_revenue( $start_date, $end_date, $args['no_cache'] );
|
||||
$report_data->renewal_count = self::fetch_renewal_count( $start_date, $end_date, $args['no_cache'] );
|
||||
$report_data->renewal_revenue = self::fetch_renewal_revenue( $start_date, $end_date, $args['no_cache'] );
|
||||
$report_data->cancel_count = self::fetch_cancel_count( $start_date, $end_date, $args['no_cache'] );
|
||||
|
||||
$cached_results = get_transient( strtolower( self::class ) );
|
||||
|
||||
// Subscription signups this month
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT COUNT(DISTINCT wcsubs.ID) AS count
|
||||
FROM {$wpdb->posts} AS wcsubs
|
||||
INNER JOIN {$wpdb->posts} AS wcorder
|
||||
ON wcsubs.post_parent = wcorder.ID
|
||||
WHERE wcorder.post_type IN ( 'shop_order' )
|
||||
AND wcsubs.post_type IN ( 'shop_subscription' )
|
||||
AND wcorder.post_status IN ( 'wc-completed', 'wc-processing', 'wc-on-hold', 'wc-refunded' )
|
||||
AND wcorder.post_date >= '%s'
|
||||
AND wcorder.post_date < '%s'",
|
||||
date( 'Y-m-01', current_time( 'timestamp' ) ),
|
||||
date( 'Y-m-d', strtotime( '+1 DAY', current_time( 'timestamp' ) ) )
|
||||
);
|
||||
|
||||
$query_hash = md5( $query );
|
||||
|
||||
if ( $args['no_cache'] || false === $cached_results || ! isset( $cached_results[ $query_hash ] ) ) {
|
||||
$wpdb->query( 'SET SESSION SQL_BIG_SELECTS=1' );
|
||||
$cached_results[ $query_hash ] = $wpdb->get_var( apply_filters( 'woocommerce_subscription_dashboard_status_widget_signup_query', $query ) );
|
||||
set_transient( strtolower( __CLASS__ ), $cached_results, WEEK_IN_SECONDS );
|
||||
if ( self::$should_update_cache ) {
|
||||
set_transient( strtolower( __CLASS__ ), self::$cached_report_results, HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
$report_data->signup_count = $cached_results[ $query_hash ];
|
||||
|
||||
// Signup revenue this month
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT SUM(order_total_meta.meta_value)
|
||||
FROM {$wpdb->postmeta} AS order_total_meta
|
||||
RIGHT JOIN
|
||||
(
|
||||
SELECT DISTINCT wcorder.ID
|
||||
FROM {$wpdb->posts} AS wcsubs
|
||||
INNER JOIN {$wpdb->posts} AS wcorder
|
||||
ON wcsubs.post_parent = wcorder.ID
|
||||
WHERE wcorder.post_type IN ( 'shop_order' )
|
||||
AND wcsubs.post_type IN ( 'shop_subscription' )
|
||||
AND wcorder.post_status IN ( 'wc-completed', 'wc-processing', 'wc-on-hold', 'wc-refunded' )
|
||||
AND wcorder.post_date >= '%s'
|
||||
AND wcorder.post_date < '%s'
|
||||
) AS orders ON orders.ID = order_total_meta.post_id
|
||||
WHERE order_total_meta.meta_key = '_order_total'",
|
||||
date( 'Y-m-01', current_time( 'timestamp' ) ),
|
||||
date( 'Y-m-d', strtotime( '+1 DAY', current_time( 'timestamp' ) ) )
|
||||
);
|
||||
|
||||
$query_hash = md5( $query );
|
||||
|
||||
if ( $args['no_cache'] || false === $cached_results || ! isset( $cached_results[ $query_hash ] ) ) {
|
||||
$wpdb->query( 'SET SESSION SQL_BIG_SELECTS=1' );
|
||||
$cached_results[ $query_hash ] = $wpdb->get_var( apply_filters( 'woocommerce_subscription_dashboard_status_widget_signup_revenue_query', $query ) );
|
||||
set_transient( strtolower( self::class ), $cached_results, HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
$report_data->signup_revenue = $cached_results[ $query_hash ];
|
||||
|
||||
// Subscription renewals this month
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT COUNT(DISTINCT wcorder.ID) AS count
|
||||
FROM {$wpdb->posts} AS wcorder
|
||||
INNER JOIN {$wpdb->postmeta} AS meta__subscription_renewal
|
||||
ON (
|
||||
wcorder.id = meta__subscription_renewal.post_id
|
||||
AND
|
||||
meta__subscription_renewal.meta_key = '_subscription_renewal'
|
||||
)
|
||||
WHERE wcorder.post_type IN ( 'shop_order' )
|
||||
AND wcorder.post_status IN ( 'wc-completed', 'wc-processing', 'wc-on-hold', 'wc-refunded' )
|
||||
AND wcorder.post_date >= '%s'
|
||||
AND wcorder.post_date < '%s'",
|
||||
date( 'Y-m-01', current_time( 'timestamp' ) ),
|
||||
date( 'Y-m-d', strtotime( '+1 DAY', current_time( 'timestamp' ) ) )
|
||||
);
|
||||
|
||||
$query_hash = md5( $query );
|
||||
|
||||
if ( $args['no_cache'] || false === $cached_results || ! isset( $cached_results[ $query_hash ] ) ) {
|
||||
$wpdb->query( 'SET SESSION SQL_BIG_SELECTS=1' );
|
||||
$cached_results[ $query_hash ] = $wpdb->get_var( apply_filters( 'woocommerce_subscription_dashboard_status_widget_renewal_query', $query ) );
|
||||
set_transient( strtolower( self::class ), $cached_results, HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
$report_data->renewal_count = $cached_results[ $query_hash ];
|
||||
|
||||
// Renewal revenue this month
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT SUM(order_total_meta.meta_value)
|
||||
FROM {$wpdb->postmeta} as order_total_meta
|
||||
RIGHT JOIN
|
||||
(
|
||||
SELECT DISTINCT wcorder.ID
|
||||
FROM {$wpdb->posts} AS wcorder
|
||||
INNER JOIN {$wpdb->postmeta} AS meta__subscription_renewal
|
||||
ON (
|
||||
wcorder.id = meta__subscription_renewal.post_id
|
||||
AND
|
||||
meta__subscription_renewal.meta_key = '_subscription_renewal'
|
||||
)
|
||||
WHERE wcorder.post_type IN ( 'shop_order' )
|
||||
AND wcorder.post_status IN ( 'wc-completed', 'wc-processing', 'wc-on-hold', 'wc-refunded' )
|
||||
AND wcorder.post_date >= '%s'
|
||||
AND wcorder.post_date < '%s'
|
||||
) AS orders ON orders.ID = order_total_meta.post_id
|
||||
WHERE order_total_meta.meta_key = '_order_total'",
|
||||
date( 'Y-m-01', current_time( 'timestamp' ) ),
|
||||
date( 'Y-m-d', strtotime( '+1 DAY', current_time( 'timestamp' ) ) )
|
||||
);
|
||||
|
||||
$query_hash = md5( $query );
|
||||
|
||||
if ( $args['no_cache'] || false === $cached_results || ! isset( $cached_results[ $query_hash ] ) ) {
|
||||
$wpdb->query( 'SET SESSION SQL_BIG_SELECTS=1' );
|
||||
$cached_results[ $query_hash ] = $wpdb->get_var( apply_filters( 'woocommerce_subscription_dashboard_status_widget_renewal_revenue_query', $query ) );
|
||||
set_transient( strtolower( self::class ), $cached_results, HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
$report_data->renewal_revenue = $cached_results[ $query_hash ];
|
||||
|
||||
// Cancellation count this month
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT COUNT(DISTINCT wcsubs.ID) AS count
|
||||
FROM {$wpdb->posts} AS wcsubs
|
||||
JOIN {$wpdb->postmeta} AS wcsmeta_cancel
|
||||
ON wcsubs.ID = wcsmeta_cancel.post_id
|
||||
AND wcsmeta_cancel.meta_key = '_schedule_cancelled'
|
||||
AND wcsubs.post_status NOT IN ( 'trash', 'auto-draft' )
|
||||
AND CONVERT_TZ( wcsmeta_cancel.meta_value, '+00:00', '{$site_timezone}' ) BETWEEN '%s' AND '%s'",
|
||||
date( 'Y-m-01', current_time( 'timestamp' ) ),
|
||||
date( 'Y-m-d', strtotime( '+1 DAY', current_time( 'timestamp' ) ) )
|
||||
);
|
||||
|
||||
$query_hash = md5( $query );
|
||||
|
||||
if ( $args['no_cache'] || false === $cached_results || ! isset( $cached_results[ $query_hash ] ) ) {
|
||||
$wpdb->query( 'SET SESSION SQL_BIG_SELECTS=1' );
|
||||
$cached_results[ $query_hash ] = $wpdb->get_var( apply_filters( 'woocommerce_subscription_dashboard_status_widget_cancellation_query', $query ) );
|
||||
set_transient( strtolower( self::class ), $cached_results, HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
$report_data->cancel_count = $cached_results[ $query_hash ];
|
||||
|
||||
return $report_data;
|
||||
}
|
||||
|
||||
|
|
@ -215,7 +98,10 @@ class WCS_Report_Dashboard {
|
|||
</li>
|
||||
<li class="signup-revenue">
|
||||
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-reports&tab=subscriptions&report=subscription_events_by_date&range=month' ) ); ?>">
|
||||
<?php echo wp_kses_post( sprintf( __( '%s signup revenue this month', 'woocommerce-subscriptions' ), '<strong>' . wc_price( $report_data->signup_revenue ) . '</strong>' ) ); ?>
|
||||
<?php
|
||||
// translators: %s: formatted amount.
|
||||
echo wp_kses_post( sprintf( __( '%s signup revenue this month', 'woocommerce-subscriptions' ), '<strong>' . wc_price( $report_data->signup_revenue ) . '</strong>' ) );
|
||||
?>
|
||||
</a>
|
||||
</li>
|
||||
<li class="renewal-count">
|
||||
|
|
@ -228,18 +114,21 @@ class WCS_Report_Dashboard {
|
|||
</li>
|
||||
<li class="renewal-revenue">
|
||||
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-reports&tab=subscriptions&report=subscription_events_by_date&range=month' ) ); ?>">
|
||||
<?php echo wp_kses_post( sprintf( __( '%s renewal revenue this month', 'woocommerce-subscriptions' ), '<strong>' . wc_price( $report_data->renewal_revenue ) . '</strong>' ) ); ?>
|
||||
<?php
|
||||
// translators: %s: formatted amount.
|
||||
echo wp_kses_post( sprintf( __( '%s renewal revenue this month', 'woocommerce-subscriptions' ), '<strong>' . wc_price( $report_data->renewal_revenue ) . '</strong>' ) );
|
||||
?>
|
||||
</a>
|
||||
</li>
|
||||
<li class="cancel-count">
|
||||
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-reports&tab=subscriptions&report=subscription_events_by_date&range=month' ) ); ?>">
|
||||
<?php
|
||||
// translators: 1$: count, 2$ and 3$ are opening and closing strong tags, respectively.
|
||||
echo wp_kses_post( sprintf( _n( '%2$s%1$s cancellation%3$s subscription cancellations this month', '%2$s%1$s cancellations%3$s subscription cancellations this month', $report_data->cancel_count, 'woocommerce-subscriptions' ), $report_data->cancel_count, '<strong>', '</strong>' ) ); ?>
|
||||
echo wp_kses_post( sprintf( _n( '%2$s%1$s cancellation%3$s subscription cancellations this month', '%2$s%1$s cancellations%3$s subscription cancellations this month', $report_data->cancel_count, 'woocommerce-subscriptions' ), $report_data->cancel_count, '<strong>', '</strong>' ) );
|
||||
?>
|
||||
</a>
|
||||
</li>
|
||||
<?php
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -248,6 +137,380 @@ class WCS_Report_Dashboard {
|
|||
* @since 2.1
|
||||
*/
|
||||
public static function dashboard_scripts() {
|
||||
wp_enqueue_style( 'wcs-dashboard-report', plugin_dir_url( WC_Subscriptions::$plugin_file ) . 'assets/css/dashboard.css', array(), WC_Subscriptions::$version );
|
||||
wp_enqueue_style( 'wcs-dashboard-report', WC_Subscriptions_Plugin::instance()->get_plugin_directory_url( 'assets/css/dashboard.css' ), array(), WC_Subscriptions_Plugin::instance()->get_library_version() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the cached report data.
|
||||
*
|
||||
* @see WCS_Report_Cache_Manager::update_cache() - This method is called by the cache manager before updating the cache.
|
||||
*
|
||||
* @since 3.0.10
|
||||
*/
|
||||
public static function clear_cache() {
|
||||
delete_transient( strtolower( __CLASS__ ) );
|
||||
self::$should_update_cache = false;
|
||||
self::$cached_report_results = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the signup count for the dashboard.
|
||||
*
|
||||
* @param string $start_date The start date.
|
||||
* @param string $end_date The end date.
|
||||
* @param bool $force_cache_update Whether to force update the cache.
|
||||
* @return int The signup count.
|
||||
*/
|
||||
private static function fetch_signup_count( $start_date, $end_date, $force_cache_update = false ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( wcs_is_custom_order_tables_usage_enabled() ) {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT COUNT(DISTINCT wcsubs.ID) AS count
|
||||
FROM {$wpdb->prefix}wc_orders AS wcsubs
|
||||
INNER JOIN {$wpdb->prefix}wc_orders AS wcorder
|
||||
ON wcsubs.parent_order_id = wcorder.ID
|
||||
WHERE wcorder.type IN ( 'shop_order' )
|
||||
AND wcsubs.type IN ( 'shop_subscription' )
|
||||
AND wcorder.status IN ( 'wc-completed', 'wc-processing', 'wc-on-hold', 'wc-refunded' )
|
||||
AND wcorder.date_created_gmt >= %s
|
||||
AND wcorder.date_created_gmt < %s",
|
||||
$start_date,
|
||||
$end_date
|
||||
);
|
||||
} else {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT COUNT(DISTINCT wcsubs.ID) AS count
|
||||
FROM {$wpdb->posts} AS wcsubs
|
||||
INNER JOIN {$wpdb->posts} AS wcorder
|
||||
ON wcsubs.post_parent = wcorder.ID
|
||||
WHERE wcorder.post_type IN ( 'shop_order' )
|
||||
AND wcsubs.post_type IN ( 'shop_subscription' )
|
||||
AND wcorder.post_status IN ( 'wc-completed', 'wc-processing', 'wc-on-hold', 'wc-refunded' )
|
||||
AND wcorder.post_date >= %s
|
||||
AND wcorder.post_date < %s",
|
||||
$start_date,
|
||||
$end_date
|
||||
);
|
||||
}
|
||||
|
||||
$query_hash = md5( $query );
|
||||
|
||||
if ( $force_cache_update || ! isset( self::$cached_report_results[ $query_hash ] ) ) {
|
||||
$wpdb->query( 'SET SESSION SQL_BIG_SELECTS=1' );
|
||||
/**
|
||||
* Filter the query for the signup count.
|
||||
*
|
||||
* @param string $query The query to execute.
|
||||
* @return string The filtered query.
|
||||
*
|
||||
* @since 3.0.10
|
||||
*/
|
||||
$query = apply_filters( 'woocommerce_subscription_dashboard_status_widget_signup_query', $query );
|
||||
$query_results = $wpdb->get_var( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- This query is prepared above.
|
||||
self::cache_report_results( $query_hash, $query_results );
|
||||
}
|
||||
|
||||
return self::$cached_report_results[ $query_hash ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the signup revenue for the dashboard.
|
||||
*
|
||||
* @param string $start_date The start date.
|
||||
* @param string $end_date The end date.
|
||||
* @param bool $force_cache_update Whether to force update the cache.
|
||||
* @return float The signup revenue.
|
||||
*/
|
||||
private static function fetch_signup_revenue( $start_date, $end_date, $force_cache_update = false ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( wcs_is_custom_order_tables_usage_enabled() ) {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT SUM(parent_orders.total_amount)
|
||||
FROM {$wpdb->prefix}wc_orders AS subscripitons
|
||||
INNER JOIN {$wpdb->prefix}wc_orders AS parent_orders
|
||||
ON subscripitons.parent_order_id = parent_orders.ID
|
||||
WHERE parent_orders.type IN ( 'shop_order' )
|
||||
AND subscripitons.type IN ( 'shop_subscription' )
|
||||
AND parent_orders.status IN ( 'wc-completed', 'wc-processing', 'wc-on-hold', 'wc-refunded' )
|
||||
AND parent_orders.date_created_gmt >= %s
|
||||
AND parent_orders.date_created_gmt < %s
|
||||
",
|
||||
$start_date,
|
||||
$end_date
|
||||
);
|
||||
} else {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT SUM(order_total_meta.meta_value)
|
||||
FROM {$wpdb->postmeta} AS order_total_meta
|
||||
RIGHT JOIN
|
||||
(
|
||||
SELECT DISTINCT wcorder.ID
|
||||
FROM {$wpdb->posts} AS wcsubs
|
||||
INNER JOIN {$wpdb->posts} AS wcorder
|
||||
ON wcsubs.post_parent = wcorder.ID
|
||||
WHERE wcorder.post_type IN ( 'shop_order' )
|
||||
AND wcsubs.post_type IN ( 'shop_subscription' )
|
||||
AND wcorder.post_status IN ( 'wc-completed', 'wc-processing', 'wc-on-hold', 'wc-refunded' )
|
||||
AND wcorder.post_date >= %s
|
||||
AND wcorder.post_date < %s
|
||||
) AS orders ON orders.ID = order_total_meta.post_id
|
||||
WHERE order_total_meta.meta_key = '_order_total'",
|
||||
$start_date,
|
||||
$end_date
|
||||
);
|
||||
}
|
||||
|
||||
$query_hash = md5( $query );
|
||||
|
||||
if ( $force_cache_update || ! isset( self::$cached_report_results[ $query_hash ] ) ) {
|
||||
$wpdb->query( 'SET SESSION SQL_BIG_SELECTS=1' );
|
||||
/**
|
||||
* Filter the query for the signup revenue.
|
||||
*
|
||||
* @param string $query The query to execute.
|
||||
* @return string The filtered query.
|
||||
*
|
||||
* @since 3.0.10
|
||||
*/
|
||||
$query = apply_filters( 'woocommerce_subscription_dashboard_status_widget_signup_revenue_query', $query );
|
||||
$query_results = $wpdb->get_var( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- This query is prepared above.
|
||||
self::cache_report_results( $query_hash, $query_results );
|
||||
}
|
||||
|
||||
return self::$cached_report_results[ $query_hash ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the renewal count for the dashboard.
|
||||
*
|
||||
* @param string $start_date The start date.
|
||||
* @param string $end_date The end date.
|
||||
* @param bool $force_cache_update Whether to force update the cache.
|
||||
* @return int The renewal count.
|
||||
*/
|
||||
private static function fetch_renewal_count( $start_date, $end_date, $force_cache_update = false ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( wcs_is_custom_order_tables_usage_enabled() ) {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT COUNT(DISTINCT wcorder.ID) AS count
|
||||
FROM {$wpdb->prefix}wc_orders AS wcorder
|
||||
INNER JOIN {$wpdb->prefix}wc_orders_meta AS meta__subscription_renewal
|
||||
ON (
|
||||
wcorder.id = meta__subscription_renewal.order_id
|
||||
AND
|
||||
meta__subscription_renewal.meta_key = '_subscription_renewal'
|
||||
)
|
||||
WHERE wcorder.type IN ( 'shop_order' )
|
||||
AND wcorder.status IN ( 'wc-completed', 'wc-processing', 'wc-on-hold', 'wc-refunded' )
|
||||
AND wcorder.date_created_gmt >= %s
|
||||
AND wcorder.date_created_gmt < %s",
|
||||
$start_date,
|
||||
$end_date
|
||||
);
|
||||
} else {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT COUNT(DISTINCT wcorder.ID) AS count
|
||||
FROM {$wpdb->posts} AS wcorder
|
||||
INNER JOIN {$wpdb->postmeta} AS meta__subscription_renewal
|
||||
ON (
|
||||
wcorder.id = meta__subscription_renewal.post_id
|
||||
AND
|
||||
meta__subscription_renewal.meta_key = '_subscription_renewal'
|
||||
)
|
||||
WHERE wcorder.post_type IN ( 'shop_order' )
|
||||
AND wcorder.post_status IN ( 'wc-completed', 'wc-processing', 'wc-on-hold', 'wc-refunded' )
|
||||
AND wcorder.post_date >= %s
|
||||
AND wcorder.post_date < %s",
|
||||
$start_date,
|
||||
$end_date
|
||||
);
|
||||
}
|
||||
|
||||
$query_hash = md5( $query );
|
||||
|
||||
if ( $force_cache_update || ! isset( self::$cached_report_results[ $query_hash ] ) ) {
|
||||
$wpdb->query( 'SET SESSION SQL_BIG_SELECTS=1' );
|
||||
/**
|
||||
* Filter the query for the renewal count.
|
||||
*
|
||||
* @param string $query The query to execute.
|
||||
* @return string The filtered query.
|
||||
*
|
||||
* @since 3.0.10
|
||||
*/
|
||||
$query = apply_filters( 'woocommerce_subscription_dashboard_status_widget_renewal_query', $query );
|
||||
$query_results = $wpdb->get_var( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- This query is prepared above.
|
||||
self::cache_report_results( $query_hash, $query_results );
|
||||
}
|
||||
|
||||
return self::$cached_report_results[ $query_hash ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the renewal revenue for the dashboard.
|
||||
*
|
||||
* @param string $start_date The start date.
|
||||
* @param string $end_date The end date.
|
||||
* @param bool $force_cache_update Whether to force update the cache.
|
||||
* @return float The renewal revenue.
|
||||
*/
|
||||
private static function fetch_renewal_revenue( $start_date, $end_date, $force_cache_update = false ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( wcs_is_custom_order_tables_usage_enabled() ) {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT SUM(wcorder.total_amount)
|
||||
FROM {$wpdb->prefix}wc_orders AS wcorder
|
||||
INNER JOIN {$wpdb->prefix}wc_orders_meta AS meta__subscription_renewal
|
||||
ON (
|
||||
wcorder.id = meta__subscription_renewal.order_id
|
||||
AND
|
||||
meta__subscription_renewal.meta_key = '_subscription_renewal'
|
||||
)
|
||||
WHERE wcorder.type IN ( 'shop_order' )
|
||||
AND wcorder.status IN ( 'wc-completed', 'wc-processing', 'wc-on-hold', 'wc-refunded' )
|
||||
AND wcorder.date_created_gmt >= %s
|
||||
AND wcorder.date_created_gmt < %s",
|
||||
$start_date,
|
||||
$end_date
|
||||
);
|
||||
} else {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT SUM(order_total_meta.meta_value)
|
||||
FROM {$wpdb->postmeta} as order_total_meta
|
||||
RIGHT JOIN
|
||||
(
|
||||
SELECT DISTINCT wcorder.ID
|
||||
FROM {$wpdb->posts} AS wcorder
|
||||
INNER JOIN {$wpdb->postmeta} AS meta__subscription_renewal
|
||||
ON (
|
||||
wcorder.id = meta__subscription_renewal.post_id
|
||||
AND
|
||||
meta__subscription_renewal.meta_key = '_subscription_renewal'
|
||||
)
|
||||
WHERE wcorder.post_type IN ( 'shop_order' )
|
||||
AND wcorder.post_status IN ( 'wc-completed', 'wc-processing', 'wc-on-hold', 'wc-refunded' )
|
||||
AND wcorder.post_date >= %s
|
||||
AND wcorder.post_date < %s
|
||||
) AS orders ON orders.ID = order_total_meta.post_id
|
||||
WHERE order_total_meta.meta_key = '_order_total'",
|
||||
$start_date,
|
||||
$end_date
|
||||
);
|
||||
}
|
||||
|
||||
$query_hash = md5( $query );
|
||||
|
||||
if ( $force_cache_update || ! isset( self::$cached_report_results[ $query_hash ] ) ) {
|
||||
$wpdb->query( 'SET SESSION SQL_BIG_SELECTS=1' );
|
||||
/**
|
||||
* Filter the query for the renewal revenue.
|
||||
*
|
||||
* @param string $query The query to execute.
|
||||
* @return string The filtered query.
|
||||
*
|
||||
* @since 3.0.10
|
||||
*/
|
||||
$query = apply_filters( 'woocommerce_subscription_dashboard_status_widget_renewal_revenue_query', $query );
|
||||
$query_results = $wpdb->get_var( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- This query is prepared above.
|
||||
self::cache_report_results( $query_hash, $query_results );
|
||||
}
|
||||
|
||||
return self::$cached_report_results[ $query_hash ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the cancellation count for the dashboard.
|
||||
*
|
||||
* @param string $start_date The start date.
|
||||
* @param string $end_date The end date.
|
||||
* @param bool $force_cache_update Whether to force update the cache.
|
||||
* @return int The cancellation count.
|
||||
*/
|
||||
private static function fetch_cancel_count( $start_date, $end_date, $force_cache_update = false ) {
|
||||
global $wpdb;
|
||||
|
||||
$offset = get_option( 'gmt_offset' );
|
||||
|
||||
// Use this once it is merged - wcs_get_gmt_offset_string();
|
||||
// Convert from Decimal format(eg. 11.5) to a suitable format(eg. +11:30) for CONVERT_TZ() of SQL query.
|
||||
$site_timezone = sprintf( '%+02d:%02d', (int) $offset, ( $offset - floor( $offset ) ) * 60 );
|
||||
|
||||
if ( wcs_is_custom_order_tables_usage_enabled() ) {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT COUNT(DISTINCT wcsubs.ID) AS count
|
||||
FROM {$wpdb->prefix}wc_orders AS wcsubs
|
||||
JOIN {$wpdb->prefix}wc_orders_meta AS wcsmeta_cancel
|
||||
ON wcsubs.ID = wcsmeta_cancel.order_id
|
||||
AND wcsmeta_cancel.meta_key = '_schedule_cancelled'
|
||||
AND wcsubs.status NOT IN ( 'trash', 'auto-draft' )
|
||||
AND CONVERT_TZ( wcsmeta_cancel.meta_value, '+00:00', %s ) BETWEEN %s AND %s",
|
||||
$site_timezone,
|
||||
$start_date,
|
||||
$end_date
|
||||
);
|
||||
} else {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT COUNT(DISTINCT wcsubs.ID) AS count
|
||||
FROM {$wpdb->posts} AS wcsubs
|
||||
JOIN {$wpdb->postmeta} AS wcsmeta_cancel
|
||||
ON wcsubs.ID = wcsmeta_cancel.post_id
|
||||
AND wcsmeta_cancel.meta_key = '_schedule_cancelled'
|
||||
AND wcsubs.post_status NOT IN ( 'trash', 'auto-draft' )
|
||||
AND CONVERT_TZ( wcsmeta_cancel.meta_value, '+00:00', %s ) BETWEEN %s AND %s",
|
||||
$site_timezone,
|
||||
$start_date,
|
||||
$end_date
|
||||
);
|
||||
}
|
||||
|
||||
$query_hash = md5( $query );
|
||||
|
||||
if ( $force_cache_update || ! isset( self::$cached_report_results[ $query_hash ] ) ) {
|
||||
$wpdb->query( 'SET SESSION SQL_BIG_SELECTS=1' );
|
||||
/**
|
||||
* Filter the query for the cancellation count.
|
||||
*
|
||||
* @param string $query The query to execute.
|
||||
* @return string The filtered query.
|
||||
*
|
||||
* @since 3.0.10
|
||||
*/
|
||||
$query = apply_filters( 'woocommerce_subscription_dashboard_status_widget_cancellation_query', $query );
|
||||
$query_results = $wpdb->get_var( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- This query is prepared above.
|
||||
self::cache_report_results( $query_hash, $query_results );
|
||||
}
|
||||
|
||||
return self::$cached_report_results[ $query_hash ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize cache for report results.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function init_cache() {
|
||||
self::$should_update_cache = false;
|
||||
self::$cached_report_results = get_transient( strtolower( __CLASS__ ) );
|
||||
|
||||
// Set a default value for cached results for PHP 8.2+ compatibility.
|
||||
if ( empty( self::$cached_report_results ) ) {
|
||||
self::$cached_report_results = array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache report results for performance optimization.
|
||||
*
|
||||
* @param string $query_hash The hash of the query for caching.
|
||||
* @param array $report_data The report data to cache.
|
||||
* @return void
|
||||
*/
|
||||
private static function cache_report_results( $query_hash, $report_data ) {
|
||||
self::$cached_report_results[ $query_hash ] = $report_data;
|
||||
self::$should_update_cache = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class WCS_Report_Retention_Rate extends WC_Admin_Report {
|
|||
* Get report data
|
||||
*
|
||||
* @since 2.1
|
||||
* @return array
|
||||
* @return stdClass
|
||||
*/
|
||||
public function get_report_data() {
|
||||
if ( empty( $this->report_data ) ) {
|
||||
|
|
@ -38,25 +38,13 @@ class WCS_Report_Retention_Rate extends WC_Admin_Report {
|
|||
* subscription may not have been active all of that time. Instead, it may have been on-hold for part of it.
|
||||
*
|
||||
* @since 2.1
|
||||
* @return null
|
||||
* @return void
|
||||
*/
|
||||
private function query_report_data() {
|
||||
global $wpdb;
|
||||
|
||||
$this->report_data = new stdClass;
|
||||
|
||||
// First, let's find the age of the longest living subscription in days
|
||||
$oldest_subscription_age_in_days = $wpdb->get_var( $wpdb->prepare(
|
||||
"SELECT MAX(DATEDIFF(CAST(postmeta.meta_value AS DATETIME),posts.post_date_gmt)) as age_in_days
|
||||
FROM {$wpdb->prefix}posts posts
|
||||
LEFT JOIN {$wpdb->prefix}postmeta postmeta ON posts.ID = postmeta.post_id
|
||||
WHERE posts.post_type = 'shop_subscription'
|
||||
AND postmeta.meta_key = %s
|
||||
AND postmeta.meta_value <> '0'
|
||||
ORDER BY age_in_days DESC
|
||||
LIMIT 1",
|
||||
wcs_get_date_meta_key( 'end' )
|
||||
) );
|
||||
$oldest_subscription_age_in_days = $this->get_max_subscription_age_in_days();
|
||||
|
||||
// Now determine what interval to use based on that length
|
||||
if ( $oldest_subscription_age_in_days > 365 ) {
|
||||
|
|
@ -74,32 +62,12 @@ class WCS_Report_Retention_Rate extends WC_Admin_Report {
|
|||
$oldest_subscription_age = floor( $oldest_subscription_age_in_days / $days_in_interval_period );
|
||||
|
||||
// Now get all subscriptions, not just those that have ended, and find out how long they have lived (or if they haven't ended yet, consider them as being alive for one period longer than the longest living subsription)
|
||||
$base_query = $wpdb->prepare(
|
||||
"SELECT
|
||||
IF(COALESCE(cancelled_date.meta_value,end_date.meta_value) <> '0',CEIL(DATEDIFF(CAST(COALESCE(cancelled_date.meta_value,end_date.meta_value) AS DATETIME),posts.post_date_gmt)/%d),%d) as periods_active,
|
||||
COUNT(posts.ID) as count
|
||||
FROM {$wpdb->prefix}posts posts
|
||||
LEFT JOIN {$wpdb->prefix}postmeta cancelled_date
|
||||
ON posts.ID = cancelled_date.post_id
|
||||
AND cancelled_date.meta_key = %s
|
||||
AND cancelled_date.meta_value <> '0'
|
||||
LEFT JOIN {$wpdb->prefix}postmeta end_date
|
||||
ON posts.ID = end_date.post_id
|
||||
AND end_date.meta_key = %s
|
||||
WHERE posts.post_type = 'shop_subscription'
|
||||
AND posts.post_status NOT IN( 'wc-pending', 'trash' )
|
||||
GROUP BY periods_active
|
||||
ORDER BY periods_active ASC",
|
||||
$days_in_interval_period,
|
||||
( $oldest_subscription_age + 1 ), // Consider living subscriptions as being alive for one period longer than the longest living subsription
|
||||
wcs_get_date_meta_key( 'cancelled' ), // If a subscription has a cancelled date, use that to determine a more accurate lifetime
|
||||
wcs_get_date_meta_key( 'end' ) // Otherwise, we want to use the end date for subscritions that have expired
|
||||
);
|
||||
$subscription_ages = $this->fetch_subscriptions_ages( $days_in_interval_period, $oldest_subscription_age );
|
||||
|
||||
$subscription_ages = $wpdb->get_results( $base_query, OBJECT_K );
|
||||
|
||||
$this->report_data->total_subscriptions = $this->report_data->unended_subscriptions = absint( array_sum( wp_list_pluck( $subscription_ages, 'count' ) ) );
|
||||
$this->report_data->living_subscriptions = array();
|
||||
// Set initial values for the report data.
|
||||
$this->report_data->total_subscriptions = absint( array_sum( wp_list_pluck( $subscription_ages, 'count' ) ) );
|
||||
$this->report_data->unended_subscriptions = $this->report_data->total_subscriptions;
|
||||
$this->report_data->living_subscriptions = array();
|
||||
|
||||
// At day zero, no subscriptions have ended
|
||||
$this->report_data->living_subscriptions[0] = $this->report_data->total_subscriptions;
|
||||
|
|
@ -120,23 +88,122 @@ class WCS_Report_Retention_Rate extends WC_Admin_Report {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the age of the longest living subscription in days.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function get_max_subscription_age_in_days() {
|
||||
global $wpdb;
|
||||
|
||||
$end_date_meta_key = wcs_get_date_meta_key( 'end' );
|
||||
|
||||
if ( wcs_is_custom_order_tables_usage_enabled() ) {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT MAX(DATEDIFF(CAST(meta.meta_value AS DATETIME),orders.date_created_gmt)) as age_in_days
|
||||
FROM {$wpdb->prefix}wc_orders orders
|
||||
LEFT JOIN {$wpdb->prefix}wc_orders_meta meta ON orders.ID = meta.order_id
|
||||
WHERE orders.type = 'shop_subscription'
|
||||
AND meta.meta_key = %s
|
||||
AND meta.meta_value <> '0'
|
||||
ORDER BY age_in_days DESC
|
||||
LIMIT 1",
|
||||
$end_date_meta_key
|
||||
);
|
||||
} else {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT MAX(DATEDIFF(CAST(postmeta.meta_value AS DATETIME),posts.post_date_gmt)) as age_in_days
|
||||
FROM {$wpdb->prefix}posts posts
|
||||
LEFT JOIN {$wpdb->prefix}postmeta postmeta ON posts.ID = postmeta.post_id
|
||||
WHERE posts.post_type = 'shop_subscription'
|
||||
AND postmeta.meta_key = %s
|
||||
AND postmeta.meta_value <> '0'
|
||||
ORDER BY age_in_days DESC
|
||||
LIMIT 1",
|
||||
$end_date_meta_key
|
||||
);
|
||||
}
|
||||
|
||||
return $wpdb->get_var( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- The query is prepared above.
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the number of periods each subscription has between creating and ending.
|
||||
*
|
||||
* @param int $days_in_interval_period
|
||||
* @param int $oldest_subscription_age
|
||||
* @return array
|
||||
*/
|
||||
private function fetch_subscriptions_ages( $days_in_interval_period, $oldest_subscription_age ) {
|
||||
global $wpdb;
|
||||
|
||||
$end_date_meta_key = wcs_get_date_meta_key( 'end' );
|
||||
$cancelled_date_meta_key = wcs_get_date_meta_key( 'cancelled' );
|
||||
|
||||
if ( wcs_is_custom_order_tables_usage_enabled() ) {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT
|
||||
IF(COALESCE(cancelled_date.meta_value,end_date.meta_value) <> '0',CEIL(DATEDIFF(CAST(COALESCE(cancelled_date.meta_value,end_date.meta_value) AS DATETIME),orders.date_created_gmt)/%d),%d) as periods_active,
|
||||
COUNT(orders.ID) as count
|
||||
FROM {$wpdb->prefix}wc_orders orders
|
||||
LEFT JOIN {$wpdb->prefix}wc_orders_meta cancelled_date
|
||||
ON orders.ID = cancelled_date.order_id
|
||||
AND cancelled_date.meta_key = %s
|
||||
AND cancelled_date.meta_value <> '0'
|
||||
LEFT JOIN {$wpdb->prefix}wc_orders_meta end_date
|
||||
ON orders.ID = end_date.order_id
|
||||
AND end_date.meta_key = %s
|
||||
WHERE orders.type = 'shop_subscription'
|
||||
AND orders.status NOT IN( 'wc-pending', 'trash' )
|
||||
GROUP BY periods_active
|
||||
ORDER BY periods_active ASC",
|
||||
$days_in_interval_period,
|
||||
( $oldest_subscription_age + 1 ), // Consider living subscriptions as being alive for one period longer than the longest living subscription
|
||||
$cancelled_date_meta_key, // If a subscription has a cancelled date, use that to determine a more accurate lifetime
|
||||
$end_date_meta_key // Otherwise, we want to use the end date for subscriptions that have expired
|
||||
);
|
||||
} else {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT
|
||||
IF(COALESCE(cancelled_date.meta_value,end_date.meta_value) <> '0',CEIL(DATEDIFF(CAST(COALESCE(cancelled_date.meta_value,end_date.meta_value) AS DATETIME),posts.post_date_gmt)/%d),%d) as periods_active,
|
||||
COUNT(posts.ID) as count
|
||||
FROM {$wpdb->prefix}posts posts
|
||||
LEFT JOIN {$wpdb->prefix}postmeta cancelled_date
|
||||
ON posts.ID = cancelled_date.post_id
|
||||
AND cancelled_date.meta_key = %s
|
||||
AND cancelled_date.meta_value <> '0'
|
||||
LEFT JOIN {$wpdb->prefix}postmeta end_date
|
||||
ON posts.ID = end_date.post_id
|
||||
AND end_date.meta_key = %s
|
||||
WHERE posts.post_type = 'shop_subscription'
|
||||
AND posts.post_status NOT IN( 'wc-pending', 'trash' )
|
||||
GROUP BY periods_active
|
||||
ORDER BY periods_active ASC",
|
||||
$days_in_interval_period,
|
||||
( $oldest_subscription_age + 1 ), // Consider living subscriptions as being alive for one period longer than the longest living subscription
|
||||
$cancelled_date_meta_key, // If a subscription has a cancelled date, use that to determine a more accurate lifetime
|
||||
$end_date_meta_key // Otherwise, we want to use the end date for subscriptions that have expired
|
||||
);
|
||||
}
|
||||
|
||||
return $wpdb->get_results( $query, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- The query is prepared above.
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the report
|
||||
*
|
||||
* Use a custom report as we don't need the date filters provided by the WooCommerce html-report-by-date.php template.
|
||||
*
|
||||
* @since 2.1
|
||||
* @return null
|
||||
*/
|
||||
public function output_report() {
|
||||
include( plugin_dir_path( WC_Subscriptions::$plugin_file ) . '/includes/admin/views/html-report-by-period.php' );
|
||||
include( WC_Subscriptions_Plugin::instance()->get_plugin_directory( 'includes/admin/views/html-report-by-period.php' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the HTML and JavaScript to plot the chart
|
||||
*
|
||||
* @since 2.1
|
||||
* @return null
|
||||
*/
|
||||
public function get_main_chart() {
|
||||
|
||||
|
|
@ -151,6 +218,8 @@ class WCS_Report_Retention_Rate extends WC_Admin_Report {
|
|||
);
|
||||
}
|
||||
|
||||
$x_axes_label = '';
|
||||
|
||||
switch ( $this->report_data->interval_period ) {
|
||||
case 'day':
|
||||
$x_axes_label = _x( 'Number of days after sign-up', 'X axis label on retention rate graph', 'woocommerce-subscriptions' );
|
||||
|
|
@ -172,7 +241,7 @@ class WCS_Report_Retention_Rate extends WC_Admin_Report {
|
|||
var main_chart;
|
||||
|
||||
jQuery(function(){
|
||||
var subscription_lifespans = jQuery.parseJSON( '<?php echo json_encode( $data_to_plot ); ?>' ),
|
||||
var subscription_lifespans = JSON.parse( '<?php echo json_encode( $data_to_plot ); ?>' ),
|
||||
unended_subscriptions = <?php echo esc_js( $this->report_data->unended_subscriptions ); ?>;
|
||||
|
||||
var drawGraph = function( highlight ) {
|
||||
|
|
@ -212,7 +281,7 @@ class WCS_Report_Retention_Rate extends WC_Admin_Report {
|
|||
color: '#aaa',
|
||||
position: "bottom",
|
||||
tickDecimals: 0,
|
||||
axisLabel: "<?php echo esc_js( $x_axes_label ); ?>",
|
||||
axisLabel: "<?php esc_js( $x_axes_label ); ?>",
|
||||
axisLabelPadding: 18,
|
||||
font: {
|
||||
color: "#aaa"
|
||||
|
|
@ -232,7 +301,7 @@ class WCS_Report_Retention_Rate extends WC_Admin_Report {
|
|||
}
|
||||
);
|
||||
|
||||
jQuery('.chart-placeholder').resize();
|
||||
jQuery('.chart-placeholder').trigger( 'resize' );
|
||||
}
|
||||
|
||||
drawGraph();
|
||||
|
|
|
|||
590
includes/admin/reports/class-wcs-report-subscription-by-customer.php
Executable file → Normal file
|
|
@ -11,6 +11,12 @@
|
|||
* @since 2.1
|
||||
*/
|
||||
class WCS_Report_Subscription_By_Customer extends WP_List_Table {
|
||||
/**
|
||||
* Cached report results.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $cached_report_results = array();
|
||||
|
||||
private $totals;
|
||||
|
||||
|
|
@ -19,12 +25,21 @@ class WCS_Report_Subscription_By_Customer extends WP_List_Table {
|
|||
*/
|
||||
public function __construct() {
|
||||
parent::__construct( array(
|
||||
'singular' => __( 'Customer', 'woocommerce-subscriptions' ),
|
||||
'plural' => __( 'Customers', 'woocommerce-subscriptions' ),
|
||||
'ajax' => false,
|
||||
'singular' => __( 'Customer', 'woocommerce-subscriptions' ),
|
||||
'plural' => __( 'Customers', 'woocommerce-subscriptions' ),
|
||||
'ajax' => false,
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the totals.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function get_totals() {
|
||||
return $this->totals;
|
||||
}
|
||||
|
||||
/**
|
||||
* No subscription products found text.
|
||||
*/
|
||||
|
|
@ -41,11 +56,13 @@ class WCS_Report_Subscription_By_Customer extends WP_List_Table {
|
|||
echo '<div id="poststuff" class="woocommerce-reports-wide">';
|
||||
echo ' <div id="postbox-container-1" class="postbox-container" style="width: 280px;"><div class="postbox" style="padding: 10px;">';
|
||||
echo ' <h3>' . esc_html__( 'Customer Totals', 'woocommerce-subscriptions' ) . '</h3>';
|
||||
echo ' <p><strong>' . esc_html__( 'Total Subscribers', 'woocommerce-subscriptions' ) . '</strong>: ' . esc_html( $this->totals->total_customers ) . wcs_help_tip( __( 'The number of unique customers with a subscription of any status other than pending or trashed.', 'woocommerce-subscriptions' ) ) . '<br />';
|
||||
echo ' <strong>' . esc_html__( 'Active Subscriptions', 'woocommerce-subscriptions' ) . '</strong>: ' . esc_html( $this->totals->active_subscriptions ) . wcs_help_tip( __( 'The total number of subscriptions with a status of active or pending cancellation.', 'woocommerce-subscriptions' ) ) . '<br />';
|
||||
echo ' <strong>' . esc_html__( 'Total Subscriptions', 'woocommerce-subscriptions' ) . '</strong>: ' . esc_html( $this->totals->total_subscriptions ) . wcs_help_tip( __( 'The total number of subscriptions with a status other than pending or trashed.', 'woocommerce-subscriptions' ) ) . '<br />';
|
||||
echo ' <strong>' . esc_html__( 'Total Subscription Orders', 'woocommerce-subscriptions' ) . '</strong>: ' . esc_html( $this->totals->initial_order_count + $this->totals->renewal_switch_count ) . wcs_help_tip( __( 'The total number of sign-up, switch and renewal orders placed with your store with a paid status (i.e. processing or complete).', 'woocommerce-subscriptions' ) ) . '<br />';
|
||||
echo ' <strong>' . esc_html__( 'Average Lifetime Value', 'woocommerce-subscriptions' ) . '</strong>: ' . wp_kses_post( wc_price( ( $this->totals->initial_order_total + $this->totals->renewal_switch_total ) / $this->totals->total_customers ) ) . wcs_help_tip( __( 'The average value of all customers\' sign-up, switch and renewal orders.', 'woocommerce-subscriptions' ) ) . '</p>';
|
||||
echo ' <p><strong>' . esc_html__( 'Total Subscribers', 'woocommerce-subscriptions' ) . '</strong>: ' . esc_html( $this->totals->total_customers ) . wc_help_tip( __( 'The number of unique customers with a subscription of any status other than pending or trashed.', 'woocommerce-subscriptions' ) ) . '<br />';
|
||||
echo ' <strong>' . esc_html__( 'Active Subscriptions', 'woocommerce-subscriptions' ) . '</strong>: ' . esc_html( $this->totals->active_subscriptions ) . wc_help_tip( __( 'The total number of subscriptions with a status of active or pending cancellation.', 'woocommerce-subscriptions' ) ) . '<br />';
|
||||
echo ' <strong>' . esc_html__( 'Total Subscriptions', 'woocommerce-subscriptions' ) . '</strong>: ' . esc_html( $this->totals->total_subscriptions ) . wc_help_tip( __( 'The total number of subscriptions with a status other than pending or trashed.', 'woocommerce-subscriptions' ) ) . '<br />';
|
||||
echo ' <strong>' . esc_html__( 'Total Subscription Orders', 'woocommerce-subscriptions' ) . '</strong>: ' . esc_html( $this->totals->initial_order_count + $this->totals->renewal_switch_count ) . wc_help_tip( __( 'The total number of sign-up, switch and renewal orders placed with your store with a paid status (i.e. processing or complete).', 'woocommerce-subscriptions' ) ) . '<br />';
|
||||
echo ' <strong>' . esc_html__( 'Average Lifetime Value', 'woocommerce-subscriptions' ) . '</strong>: ';
|
||||
echo wp_kses_post( wc_price( $this->totals->total_customers > 0 ? ( ( $this->totals->initial_order_total + $this->totals->renewal_switch_total ) / $this->totals->total_customers ) : 0 ) );
|
||||
echo wc_help_tip( __( 'The average value of all customers\' sign-up, switch and renewal orders.', 'woocommerce-subscriptions' ) ) . '</p>';
|
||||
echo '</div></div>';
|
||||
$this->display();
|
||||
echo '</div>';
|
||||
|
|
@ -64,20 +81,20 @@ class WCS_Report_Subscription_By_Customer extends WP_List_Table {
|
|||
|
||||
switch ( $column_name ) {
|
||||
|
||||
case 'customer_name' :
|
||||
case 'customer_name':
|
||||
$user_info = get_userdata( $user->customer_id );
|
||||
return '<a href="' . get_edit_user_link( $user->customer_id ) . '">' . $user_info->user_email . '</a>';
|
||||
return '<a href="' . get_edit_user_link( $user->customer_id ) . '">' . $user_info->user_email . '</a>';
|
||||
|
||||
case 'active_subscription_count' :
|
||||
case 'active_subscription_count':
|
||||
return $user->active_subscriptions;
|
||||
|
||||
case 'total_subscription_count' :
|
||||
case 'total_subscription_count':
|
||||
return sprintf( '<a href="%s%d">%d</a>', admin_url( 'edit.php?post_type=shop_subscription&_customer_user=' ), $user->customer_id, $user->total_subscriptions );
|
||||
|
||||
case 'total_subscription_order_count' :
|
||||
case 'total_subscription_order_count':
|
||||
return sprintf( '<a href="%s%d">%d</a>', admin_url( 'edit.php?post_type=shop_order&_paid_subscription_orders_for_customer_user=' ), $user->customer_id, $user->initial_order_count + $user->renewal_switch_count );
|
||||
|
||||
case 'customer_lifetime_value' :
|
||||
case 'customer_lifetime_value':
|
||||
return wc_price( $user->initial_order_total + $user->renewal_switch_total );
|
||||
|
||||
}
|
||||
|
|
@ -93,10 +110,14 @@ class WCS_Report_Subscription_By_Customer extends WP_List_Table {
|
|||
public function get_columns() {
|
||||
$columns = array(
|
||||
'customer_name' => __( 'Customer', 'woocommerce-subscriptions' ),
|
||||
'active_subscription_count' => sprintf( __( 'Active Subscriptions %s', 'woocommerce-subscriptions' ), wcs_help_tip( __( 'The number of subscriptions this customer has with a status of active or pending cancellation.', 'woocommerce-subscriptions' ) ) ),
|
||||
'total_subscription_count' => sprintf( __( 'Total Subscriptions %s', 'woocommerce-subscriptions' ), wcs_help_tip( __( 'The number of subscriptions this customer has with a status other than pending or trashed.', 'woocommerce-subscriptions' ) ) ),
|
||||
'total_subscription_order_count' => sprintf( __( 'Total Subscription Orders %s', 'woocommerce-subscriptions' ), wcs_help_tip( __( 'The number of sign-up, switch and renewal orders this customer has placed with your store with a paid status (i.e. processing or complete).', 'woocommerce-subscriptions' ) ) ),
|
||||
'customer_lifetime_value' => sprintf( __( 'Lifetime Value from Subscriptions %s', 'woocommerce-subscriptions' ), wcs_help_tip( __( 'The total value of this customer\'s sign-up, switch and renewal orders.', 'woocommerce-subscriptions' ) ) ),
|
||||
// translators: %s: help tip.
|
||||
'active_subscription_count' => sprintf( __( 'Active Subscriptions %s', 'woocommerce-subscriptions' ), wc_help_tip( __( 'The number of subscriptions this customer has with a status of active or pending cancellation.', 'woocommerce-subscriptions' ) ) ),
|
||||
// translators: %s: help tip.
|
||||
'total_subscription_count' => sprintf( __( 'Total Subscriptions %s', 'woocommerce-subscriptions' ), wc_help_tip( __( 'The number of subscriptions this customer has with a status other than pending or trashed.', 'woocommerce-subscriptions' ) ) ),
|
||||
// translators: %s: help tip.
|
||||
'total_subscription_order_count' => sprintf( __( 'Total Subscription Orders %s', 'woocommerce-subscriptions' ), wc_help_tip( __( 'The number of sign-up, switch and renewal orders this customer has placed with your store with a paid status (i.e. processing or complete).', 'woocommerce-subscriptions' ) ) ),
|
||||
// translators: %s: help tip.
|
||||
'customer_lifetime_value' => sprintf( __( 'Lifetime Value from Subscriptions %s', 'woocommerce-subscriptions' ), wc_help_tip( __( 'The total value of this customer\'s sign-up, switch and renewal orders.', 'woocommerce-subscriptions' ) ) ),
|
||||
);
|
||||
|
||||
return $columns;
|
||||
|
|
@ -106,8 +127,6 @@ class WCS_Report_Subscription_By_Customer extends WP_List_Table {
|
|||
* Prepare subscription list items.
|
||||
*/
|
||||
public function prepare_items() {
|
||||
global $wpdb;
|
||||
|
||||
$this->_column_headers = array( $this->get_columns(), array(), $this->get_sortable_columns() );
|
||||
$current_page = absint( $this->get_pagenum() );
|
||||
$per_page = absint( apply_filters( 'wcs_reports_customers_per_page', 20 ) );
|
||||
|
|
@ -115,106 +134,348 @@ class WCS_Report_Subscription_By_Customer extends WP_List_Table {
|
|||
|
||||
$this->totals = self::get_data();
|
||||
|
||||
$customer_query = apply_filters( 'wcs_reports_current_customer_query',
|
||||
"SELECT customer_ids.meta_value as customer_id,
|
||||
COUNT(subscription_posts.ID) as total_subscriptions,
|
||||
COALESCE( SUM(parent_total.meta_value), 0) as initial_order_total,
|
||||
COUNT(DISTINCT parent_order.ID) as initial_order_count,
|
||||
SUM(CASE
|
||||
WHEN subscription_posts.post_status
|
||||
IN ( 'wc-" . implode( "','wc-", apply_filters( 'wcs_reports_active_statuses', array( 'active', 'pending-cancel' ) ) ) . "' ) THEN 1
|
||||
ELSE 0
|
||||
END) AS active_subscriptions
|
||||
FROM {$wpdb->posts} subscription_posts
|
||||
INNER JOIN {$wpdb->postmeta} customer_ids
|
||||
ON customer_ids.post_id = subscription_posts.ID
|
||||
AND customer_ids.meta_key = '_customer_user'
|
||||
LEFT JOIN {$wpdb->posts} parent_order
|
||||
ON parent_order.ID = subscription_posts.post_parent
|
||||
AND parent_order.post_status IN ( 'wc-" . implode( "','wc-", apply_filters( 'woocommerce_reports_paid_order_statuses', array( 'completed', 'processing' ) ) ) . "' )
|
||||
LEFT JOIN {$wpdb->postmeta} parent_total
|
||||
ON parent_total.post_id = parent_order.ID
|
||||
AND parent_total.meta_key = '_order_total'
|
||||
WHERE subscription_posts.post_type = 'shop_subscription'
|
||||
AND subscription_posts.post_status NOT IN ('wc-pending', 'trash')
|
||||
GROUP BY customer_ids.meta_value
|
||||
ORDER BY customer_id DESC
|
||||
LIMIT {$offset}, {$per_page}" );
|
||||
|
||||
$this->items = $wpdb->get_results( $customer_query );
|
||||
|
||||
// Now get each customer's renewal and switch total
|
||||
$customer_renewal_switch_total_query = apply_filters( 'wcs_reports_current_customer_renewal_switch_total_query',
|
||||
"SELECT
|
||||
customer_ids.meta_value as customer_id,
|
||||
COALESCE( SUM(renewal_switch_totals.meta_value), 0) as renewal_switch_total,
|
||||
COUNT(DISTINCT renewal_order_posts.ID) as renewal_switch_count
|
||||
FROM {$wpdb->postmeta} renewal_order_ids
|
||||
INNER JOIN {$wpdb->posts} subscription_posts
|
||||
ON renewal_order_ids.meta_value = subscription_posts.ID
|
||||
AND subscription_posts.post_type = 'shop_subscription'
|
||||
AND subscription_posts.post_status NOT IN ('wc-pending', 'trash')
|
||||
INNER JOIN {$wpdb->postmeta} customer_ids
|
||||
ON renewal_order_ids.meta_value = customer_ids.post_id
|
||||
AND customer_ids.meta_key = '_customer_user'
|
||||
AND customer_ids.meta_value IN ('" . implode( "','", wp_list_pluck( $this->items, 'customer_id' ) ) . "' )
|
||||
INNER JOIN {$wpdb->posts} renewal_order_posts
|
||||
ON renewal_order_ids.post_id = renewal_order_posts.ID
|
||||
AND renewal_order_posts.post_status IN ( 'wc-" . implode( "','wc-", apply_filters( 'woocommerce_reports_paid_order_statuses', array( 'completed', 'processing' ) ) ) . "' )
|
||||
LEFT JOIN {$wpdb->postmeta} renewal_switch_totals
|
||||
ON renewal_switch_totals.post_id = renewal_order_ids.post_id
|
||||
AND renewal_switch_totals.meta_key = '_order_total'
|
||||
WHERE renewal_order_ids.meta_key = '_subscription_renewal'
|
||||
OR renewal_order_ids.meta_key = '_subscription_switch'
|
||||
GROUP BY customer_id
|
||||
ORDER BY customer_id"
|
||||
$active_statuses = wcs_maybe_prefix_key( apply_filters( 'wcs_reports_active_statuses', [ 'active', 'pending-cancel' ] ), 'wc-' );
|
||||
$paid_statuses = wcs_maybe_prefix_key( apply_filters( 'woocommerce_reports_paid_order_statuses', [ 'completed', 'processing' ] ), 'wc-' );
|
||||
$query_options = array(
|
||||
'active_statuses' => $active_statuses,
|
||||
'paid_statuses' => $paid_statuses,
|
||||
'offset' => $offset,
|
||||
'per_page' => $per_page,
|
||||
);
|
||||
|
||||
$customer_renewal_switch_totals = $wpdb->get_results( $customer_renewal_switch_total_query, OBJECT_K );
|
||||
$this->items = self::fetch_subscriptions_by_customer( $query_options );
|
||||
$customer_ids = wp_list_pluck( $this->items, 'customer_id' );
|
||||
|
||||
$related_orders_query_options = array(
|
||||
'order_status' => $paid_statuses,
|
||||
'customer_ids' => $customer_ids,
|
||||
);
|
||||
|
||||
$related_orders_totals_by_customer = self::fetch_subscriptions_related_orders_totals_by_customer( $related_orders_query_options );
|
||||
|
||||
foreach ( $this->items as $index => $item ) {
|
||||
if ( isset( $customer_renewal_switch_totals[ $item->customer_id ] ) ) {
|
||||
$this->items[ $index ]->renewal_switch_total = $customer_renewal_switch_totals[ $item->customer_id ]->renewal_switch_total;
|
||||
$this->items[ $index ]->renewal_switch_count = $customer_renewal_switch_totals[ $item->customer_id ]->renewal_switch_count;
|
||||
if ( isset( $related_orders_totals_by_customer[ $item->customer_id ] ) ) {
|
||||
$this->items[ $index ]->renewal_switch_total = $related_orders_totals_by_customer[ $item->customer_id ]->renewal_switch_total;
|
||||
$this->items[ $index ]->renewal_switch_count = $related_orders_totals_by_customer[ $item->customer_id ]->renewal_switch_count;
|
||||
} else {
|
||||
$this->items[ $index ]->renewal_switch_total = $this->items[ $index ]->renewal_switch_count = 0;
|
||||
$this->items[ $index ]->renewal_switch_total = 0;
|
||||
$this->items[ $index ]->renewal_switch_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pagination.
|
||||
*/
|
||||
$this->set_pagination_args( array(
|
||||
'total_items' => $this->totals->total_customers,
|
||||
'per_page' => $per_page,
|
||||
'total_pages' => ceil( $this->totals->total_customers / $per_page ),
|
||||
) );
|
||||
|
||||
$this->set_pagination_args(
|
||||
array(
|
||||
'total_items' => $this->totals->total_customers,
|
||||
'per_page' => $per_page,
|
||||
'total_pages' => ceil( $this->totals->total_customers / $per_page ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather totals for customers
|
||||
*/
|
||||
* Gather totals for customers.
|
||||
*
|
||||
* @see WCS_Report_Cache_Manager::update_cache() - This method is called by the cache manager to update the cache.
|
||||
*
|
||||
* @param array $args The arguments for the report.
|
||||
* @return object The totals for customers.
|
||||
*/
|
||||
public static function get_data( $args = array() ) {
|
||||
global $wpdb;
|
||||
|
||||
$default_args = array(
|
||||
'no_cache' => false,
|
||||
/**
|
||||
* Filter the order statuses considered as "paid" for the report.
|
||||
*
|
||||
* @param array $order_statuses The default paid order statuses: completed, processing.
|
||||
* @return array The filtered order statuses.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
'order_status' => apply_filters( 'woocommerce_reports_paid_order_statuses', array( 'completed', 'processing' ) ),
|
||||
);
|
||||
|
||||
/**
|
||||
* Filter the arguments for the totals of subscriptions by customer report.
|
||||
*
|
||||
* @param array $args The arguments for the report.
|
||||
* @return array The filtered arguments.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
$args = apply_filters( 'wcs_reports_customer_total_args', $args );
|
||||
$args = wp_parse_args( $args, $default_args );
|
||||
|
||||
$total_query = apply_filters( 'wcs_reports_customer_total_query',
|
||||
"SELECT COUNT( DISTINCT customer_ids.meta_value) as total_customers,
|
||||
self::init_cache();
|
||||
$subscriptions_totals = self::fetch_customer_subscription_totals( $args );
|
||||
$related_orders_totals = self::fetch_customer_subscription_related_orders_totals( $args );
|
||||
|
||||
$subscriptions_totals->renewal_switch_total = $related_orders_totals->renewal_switch_total;
|
||||
$subscriptions_totals->renewal_switch_count = $related_orders_totals->renewal_switch_count;
|
||||
|
||||
return $subscriptions_totals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the cached report data.
|
||||
*
|
||||
* @see WCS_Report_Cache_Manager::update_cache() - This method is called by the cache manager before updating the cache.
|
||||
*
|
||||
* @since 3.0.10
|
||||
*/
|
||||
public static function clear_cache() {
|
||||
delete_transient( strtolower( __CLASS__ ) );
|
||||
self::$cached_report_results = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch totals by customer for subscriptions.
|
||||
*
|
||||
* @param array $args The arguments for the report.
|
||||
* @return object The totals by customer for subscriptions.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
public static function fetch_customer_subscription_totals( $args = array() ) {
|
||||
global $wpdb;
|
||||
|
||||
/**
|
||||
* Filter the active subscription statuses used for reporting.
|
||||
*
|
||||
* @param array $active_statuses The default active subscription statuses: active, pending-cancel.
|
||||
* @return array The filtered active statuses.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
$active_statuses = wcs_maybe_prefix_key( apply_filters( 'wcs_reports_active_statuses', [ 'active', 'pending-cancel' ] ), 'wc-' );
|
||||
$order_statuses = wcs_maybe_prefix_key( $args['order_status'], 'wc-' );
|
||||
|
||||
$active_statuses_placeholders = implode( ',', array_fill( 0, count( $active_statuses ), '%s' ) );
|
||||
$order_statuses_placeholders = implode( ',', array_fill( 0, count( $order_statuses ), '%s' ) );
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Ignored for allowing interpolation in the IN statements.
|
||||
if ( wcs_is_custom_order_tables_usage_enabled() ) {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT COUNT( DISTINCT subscriptions.customer_id) as total_customers,
|
||||
COUNT(subscriptions.ID) as total_subscriptions,
|
||||
COALESCE( SUM(parent_orders.total_amount), 0) as initial_order_total,
|
||||
COUNT(DISTINCT parent_orders.ID) as initial_order_count,
|
||||
COALESCE(SUM(CASE
|
||||
WHEN subscriptions.status
|
||||
IN ( {$active_statuses_placeholders} ) THEN 1
|
||||
ELSE 0
|
||||
END), 0) AS active_subscriptions
|
||||
FROM {$wpdb->prefix}wc_orders subscriptions
|
||||
LEFT JOIN {$wpdb->prefix}wc_orders parent_orders
|
||||
ON parent_orders.ID = subscriptions.parent_order_id
|
||||
AND parent_orders.status IN ( {$order_statuses_placeholders} )
|
||||
WHERE subscriptions.type = 'shop_subscription'
|
||||
AND subscriptions.status NOT IN ('wc-pending', 'auto-draft', 'wc-checkout-draft', 'trash')
|
||||
", // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- Placeholders are prepared above.
|
||||
array_merge( $active_statuses, $order_statuses )
|
||||
);
|
||||
} else {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT COUNT( DISTINCT customer_ids.meta_value) as total_customers,
|
||||
COUNT(subscription_posts.ID) as total_subscriptions,
|
||||
COALESCE( SUM(parent_total.meta_value), 0) as initial_order_total,
|
||||
COUNT(DISTINCT parent_order.ID) as initial_order_count,
|
||||
COALESCE(SUM(CASE
|
||||
WHEN subscription_posts.post_status
|
||||
IN ( {$active_statuses_placeholders} ) THEN 1
|
||||
ELSE 0
|
||||
END), 0) AS active_subscriptions
|
||||
FROM {$wpdb->posts} subscription_posts
|
||||
INNER JOIN {$wpdb->postmeta} customer_ids
|
||||
ON customer_ids.post_id = subscription_posts.ID
|
||||
AND customer_ids.meta_key = '_customer_user'
|
||||
LEFT JOIN {$wpdb->posts} parent_order
|
||||
ON parent_order.ID = subscription_posts.post_parent
|
||||
AND parent_order.post_status IN ( {$order_statuses_placeholders} )
|
||||
LEFT JOIN {$wpdb->postmeta} parent_total
|
||||
ON parent_total.post_id = parent_order.ID
|
||||
AND parent_total.meta_key = '_order_total'
|
||||
WHERE subscription_posts.post_type = 'shop_subscription'
|
||||
AND subscription_posts.post_status NOT IN ('wc-pending', 'auto-draft', 'wc-checkout-draft', 'trash')
|
||||
", // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- Placeholders are prepared above.
|
||||
array_merge( $active_statuses, $order_statuses )
|
||||
);
|
||||
}
|
||||
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared.
|
||||
|
||||
/**
|
||||
* Filter the query used to fetch the customer subscription totals.
|
||||
*
|
||||
* @param string $query The query to fetch the customer subscription totals.
|
||||
* @return string The filtered query.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
$query = apply_filters( 'wcs_reports_customer_total_query', $query );
|
||||
$query_hash = md5( $query );
|
||||
|
||||
// We expect that cache was initialized before calling this method.
|
||||
// Skip running the query if cache is available.
|
||||
if ( $args['no_cache'] || ! isset( self::$cached_report_results[ $query_hash ] ) ) {
|
||||
$wpdb->query( 'SET SESSION SQL_BIG_SELECTS=1' );
|
||||
$query_results = $wpdb->get_row( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- This query is prepared above.
|
||||
|
||||
/**
|
||||
* Filter the query results for customer totals.
|
||||
*
|
||||
* @param object $query_results The query results.
|
||||
* @return object The filtered query results.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
$query_results = apply_filters( 'wcs_reports_customer_total_data', $query_results );
|
||||
self::cache_report_results( $query_hash, $query_results );
|
||||
}
|
||||
|
||||
return self::$cached_report_results[ $query_hash ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch totals by customer for related renewal and switch orders.
|
||||
*
|
||||
* @param array $args The arguments for the report.
|
||||
* @return object The totals by customer for related renewal and switch orders.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
public static function fetch_customer_subscription_related_orders_totals( $args = array() ) {
|
||||
global $wpdb;
|
||||
|
||||
$status_placeholders = implode( ',', array_fill( 0, count( $args['order_status'] ), '%s' ) );
|
||||
$statuses = wcs_maybe_prefix_key( $args['order_status'], 'wc-' );
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Ignored for allowing interpolation in the IN statements.
|
||||
if ( wcs_is_custom_order_tables_usage_enabled() ) {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT COALESCE( SUM(renewal_orders.total_amount), 0) as renewal_switch_total,
|
||||
COUNT(DISTINCT renewal_orders.ID) as renewal_switch_count
|
||||
FROM {$wpdb->prefix}wc_orders_meta renewal_order_ids
|
||||
INNER JOIN {$wpdb->prefix}wc_orders subscriptions
|
||||
ON renewal_order_ids.meta_value = subscriptions.ID
|
||||
AND subscriptions.type = 'shop_subscription'
|
||||
AND subscriptions.status NOT IN ('wc-pending', 'auto-draft', 'wc-checkout-draft', 'trash')
|
||||
INNER JOIN {$wpdb->prefix}wc_orders renewal_orders
|
||||
ON renewal_order_ids.order_id = renewal_orders.ID
|
||||
AND renewal_orders.status IN ( {$status_placeholders} )
|
||||
WHERE renewal_order_ids.meta_key = '_subscription_renewal'
|
||||
OR renewal_order_ids.meta_key = '_subscription_switch'
|
||||
", // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- Placeholders are prepared above.
|
||||
$statuses
|
||||
);
|
||||
} else {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT COALESCE( SUM(renewal_switch_totals.meta_value), 0) as renewal_switch_total,
|
||||
COUNT(DISTINCT renewal_order_posts.ID) as renewal_switch_count
|
||||
FROM {$wpdb->postmeta} renewal_order_ids
|
||||
INNER JOIN {$wpdb->posts} subscription_posts
|
||||
ON renewal_order_ids.meta_value = subscription_posts.ID
|
||||
AND subscription_posts.post_type = 'shop_subscription'
|
||||
AND subscription_posts.post_status NOT IN ('wc-pending', 'auto-draft', 'wc-checkout-draft', 'trash')
|
||||
INNER JOIN {$wpdb->posts} renewal_order_posts
|
||||
ON renewal_order_ids.post_id = renewal_order_posts.ID
|
||||
AND renewal_order_posts.post_status IN ( {$status_placeholders} )
|
||||
LEFT JOIN {$wpdb->postmeta} renewal_switch_totals
|
||||
ON renewal_switch_totals.post_id = renewal_order_ids.post_id
|
||||
AND renewal_switch_totals.meta_key = '_order_total'
|
||||
WHERE renewal_order_ids.meta_key = '_subscription_renewal'
|
||||
OR renewal_order_ids.meta_key = '_subscription_switch'
|
||||
", // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- Placeholders are prepared above.
|
||||
$statuses
|
||||
);
|
||||
}
|
||||
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared.
|
||||
|
||||
/**
|
||||
* Filter the query used to fetch the customer subscription related orders totals.
|
||||
*
|
||||
* @param string $query The query to fetch the customer subscription related orders totals.
|
||||
* @return string The filtered query.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
$query = apply_filters( 'wcs_reports_customer_total_renewal_switch_query', $query );
|
||||
$query_hash = md5( $query );
|
||||
|
||||
if ( $args['no_cache'] || ! isset( self::$cached_report_results[ $query_hash ] ) ) {
|
||||
// Enable big selects for reports
|
||||
$wpdb->query( 'SET SESSION SQL_BIG_SELECTS=1' );
|
||||
$query_results = $wpdb->get_row( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- This query is prepared above.
|
||||
|
||||
/**
|
||||
* Filter the query results for customer subscription related orders totals.
|
||||
*
|
||||
* @param object $query_results The query results.
|
||||
* @return object The filtered query results.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
$query_results = apply_filters( 'wcs_reports_customer_total_renewal_switch_data', $query_results );
|
||||
self::cache_report_results( $query_hash, $query_results );
|
||||
}
|
||||
|
||||
return self::$cached_report_results[ $query_hash ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch subscriptions by customer.
|
||||
*
|
||||
* @param array $query_options The query options.
|
||||
* @return array The subscriptions by customer.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
private static function fetch_subscriptions_by_customer( $query_options = array() ) {
|
||||
global $wpdb;
|
||||
|
||||
$active_statuses = $query_options['active_statuses'] ?? array();
|
||||
$paid_statuses = $query_options['paid_statuses'] ?? array();
|
||||
$offset = $query_options['offset'] ?? 0;
|
||||
$per_page = $query_options['per_page'] ?? 20;
|
||||
|
||||
$active_statuses_placeholders = implode( ',', array_fill( 0, count( $active_statuses ), '%s' ) );
|
||||
$paid_statuses_placeholders = implode( ',', array_fill( 0, count( $paid_statuses ), '%s' ) );
|
||||
|
||||
// Ignored for allowing interpolation in the IN statements.
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
|
||||
if ( wcs_is_custom_order_tables_usage_enabled() ) {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT subscriptions.customer_id as customer_id,
|
||||
COUNT(subscriptions.ID) as total_subscriptions,
|
||||
COALESCE( SUM(parent_order.total_amount), 0) as initial_order_total,
|
||||
COUNT(DISTINCT parent_order.ID) as initial_order_count,
|
||||
SUM(CASE
|
||||
WHEN subscriptions.status
|
||||
IN ( {$active_statuses_placeholders} ) THEN 1
|
||||
ELSE 0
|
||||
END) AS active_subscriptions
|
||||
FROM {$wpdb->prefix}wc_orders subscriptions
|
||||
LEFT JOIN {$wpdb->prefix}wc_orders parent_order
|
||||
ON parent_order.ID = subscriptions.parent_order_id
|
||||
AND parent_order.status IN ( {$paid_statuses_placeholders} )
|
||||
WHERE subscriptions.type = 'shop_subscription'
|
||||
AND subscriptions.status NOT IN ('wc-pending','auto-draft', 'wc-checkout-draft', 'trash')
|
||||
GROUP BY subscriptions.customer_id
|
||||
ORDER BY customer_id DESC
|
||||
LIMIT %d, %d
|
||||
",
|
||||
array_merge( $active_statuses, $paid_statuses, array( $offset, $per_page ) )
|
||||
);
|
||||
} else {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT customer_ids.meta_value as customer_id,
|
||||
COUNT(subscription_posts.ID) as total_subscriptions,
|
||||
COALESCE( SUM(parent_total.meta_value), 0) as initial_order_total,
|
||||
COUNT(DISTINCT parent_order.ID) as initial_order_count,
|
||||
SUM(CASE
|
||||
WHEN subscription_posts.post_status
|
||||
IN ( 'wc-" . implode( "','wc-", apply_filters( 'wcs_reports_active_statuses', array( 'active', 'pending-cancel' ) ) ) . "' ) THEN 1
|
||||
IN ( {$active_statuses_placeholders} ) THEN 1
|
||||
ELSE 0
|
||||
END) AS active_subscriptions
|
||||
FROM {$wpdb->posts} subscription_posts
|
||||
|
|
@ -223,56 +484,139 @@ class WCS_Report_Subscription_By_Customer extends WP_List_Table {
|
|||
AND customer_ids.meta_key = '_customer_user'
|
||||
LEFT JOIN {$wpdb->posts} parent_order
|
||||
ON parent_order.ID = subscription_posts.post_parent
|
||||
AND parent_order.post_status IN ( 'wc-" . implode( "','wc-", $args['order_status'] ) . "' )
|
||||
AND parent_order.post_status IN ( {$paid_statuses_placeholders} )
|
||||
LEFT JOIN {$wpdb->postmeta} parent_total
|
||||
ON parent_total.post_id = parent_order.ID
|
||||
AND parent_total.meta_key = '_order_total'
|
||||
WHERE subscription_posts.post_type = 'shop_subscription'
|
||||
AND subscription_posts.post_status NOT IN ('wc-pending', 'trash')
|
||||
");
|
||||
|
||||
$cached_results = get_transient( strtolower( __CLASS__ ) );
|
||||
$query_hash = md5( $total_query );
|
||||
|
||||
if ( $args['no_cache'] || false === $cached_results || ! isset( $cached_results[ $query_hash ] ) ) {
|
||||
// Enable big selects for reports
|
||||
$wpdb->query( 'SET SESSION SQL_BIG_SELECTS=1' );
|
||||
$cached_results[ $query_hash ] = apply_filters( 'wcs_reports_customer_total_data', $wpdb->get_row( $total_query ) );
|
||||
set_transient( strtolower( __CLASS__ ), $cached_results, WEEK_IN_SECONDS );
|
||||
AND subscription_posts.post_status NOT IN ('wc-pending', 'auto-draft', 'wc-checkout-draft', 'trash')
|
||||
GROUP BY customer_ids.meta_value
|
||||
ORDER BY customer_id DESC
|
||||
LIMIT %d, %d
|
||||
",
|
||||
array_merge( $active_statuses, $paid_statuses, array( $offset, $per_page ) )
|
||||
);
|
||||
}
|
||||
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
|
||||
|
||||
$customer_totals = $cached_results[ $query_hash ];
|
||||
/**
|
||||
* Filter the query used to fetch the subscriptions by customer.
|
||||
*
|
||||
* @param string $query The query to fetch the subscriptions by customer.
|
||||
* @return string The filtered query.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
$query = apply_filters( 'wcs_reports_current_customer_query', $query );
|
||||
|
||||
$renewal_switch_total_query = apply_filters( 'wcs_reports_customer_total_renewal_switch_query',
|
||||
"SELECT COALESCE( SUM(renewal_switch_totals.meta_value), 0) as renewal_switch_total,
|
||||
COUNT(DISTINCT renewal_order_posts.ID) as renewal_switch_count
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- This query is prepared above.
|
||||
return $wpdb->get_results( $query );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch totals by customer for related renewal and switch orders.
|
||||
*
|
||||
* @param array $query_options The query options.
|
||||
* @return array The totals by customer for related renewal and switch orders.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
private static function fetch_subscriptions_related_orders_totals_by_customer( $query_options = array() ) {
|
||||
global $wpdb;
|
||||
|
||||
$paid_statuses = $query_options['order_status'] ?? array();
|
||||
$customer_ids = $query_options['customer_ids'] ?? array();
|
||||
|
||||
$customer_placeholders = implode( ',', array_fill( 0, count( $customer_ids ), '%s' ) );
|
||||
$status_placeholders = implode( ',', array_fill( 0, count( $paid_statuses ), '%s' ) );
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- Ignored for allowing interpolation in the IN statements.
|
||||
if ( wcs_is_custom_order_tables_usage_enabled() ) {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT
|
||||
renewal_orders.customer_id as customer_id,
|
||||
COALESCE( SUM(renewal_orders.total_amount), 0) as renewal_switch_total,
|
||||
COUNT(DISTINCT renewal_orders.ID) as renewal_switch_count
|
||||
FROM {$wpdb->prefix}wc_orders_meta renewal_order_ids
|
||||
INNER JOIN {$wpdb->prefix}wc_orders subscriptions
|
||||
ON renewal_order_ids.meta_value = subscriptions.ID
|
||||
AND subscriptions.type = 'shop_subscription'
|
||||
AND subscriptions.status NOT IN ('wc-pending', 'auto-draft', 'wc-checkout-draft', 'trash')
|
||||
INNER JOIN {$wpdb->prefix}wc_orders renewal_orders
|
||||
ON renewal_order_ids.order_id = renewal_orders.ID
|
||||
AND renewal_orders.status IN ( {$status_placeholders} )
|
||||
AND renewal_orders.customer_id IN ( {$customer_placeholders} )
|
||||
WHERE renewal_order_ids.meta_key = '_subscription_renewal'
|
||||
OR renewal_order_ids.meta_key = '_subscription_switch'
|
||||
GROUP BY renewal_orders.customer_id
|
||||
ORDER BY renewal_orders.customer_id
|
||||
",
|
||||
array_merge( $paid_statuses, $customer_ids )
|
||||
);
|
||||
} else {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT
|
||||
customer_ids.meta_value as customer_id,
|
||||
COALESCE( SUM(renewal_switch_totals.meta_value), 0) as renewal_switch_total,
|
||||
COUNT(DISTINCT renewal_order_posts.ID) as renewal_switch_count
|
||||
FROM {$wpdb->postmeta} renewal_order_ids
|
||||
INNER JOIN {$wpdb->posts} subscription_posts
|
||||
ON renewal_order_ids.meta_value = subscription_posts.ID
|
||||
AND subscription_posts.post_type = 'shop_subscription'
|
||||
AND subscription_posts.post_status NOT IN ('wc-pending', 'trash')
|
||||
AND subscription_posts.post_status NOT IN ('wc-pending', 'auto-draft', 'wc-checkout-draft', 'trash')
|
||||
INNER JOIN {$wpdb->postmeta} customer_ids
|
||||
ON renewal_order_ids.meta_value = customer_ids.post_id
|
||||
AND customer_ids.meta_key = '_customer_user'
|
||||
AND customer_ids.meta_value IN ( {$customer_placeholders} )
|
||||
INNER JOIN {$wpdb->posts} renewal_order_posts
|
||||
ON renewal_order_ids.post_id = renewal_order_posts.ID
|
||||
AND renewal_order_posts.post_status IN ( 'wc-" . implode( "','wc-", $args['order_status'] ) . "' )
|
||||
AND renewal_order_posts.post_status IN ( {$status_placeholders} )
|
||||
LEFT JOIN {$wpdb->postmeta} renewal_switch_totals
|
||||
ON renewal_switch_totals.post_id = renewal_order_ids.post_id
|
||||
AND renewal_switch_totals.meta_key = '_order_total'
|
||||
WHERE renewal_order_ids.meta_key = '_subscription_renewal'
|
||||
OR renewal_order_ids.meta_key = '_subscription_switch'"
|
||||
);
|
||||
|
||||
$query_hash = md5( $renewal_switch_total_query );
|
||||
|
||||
if ( $args['no_cache'] || false === $cached_results || ! isset( $cached_results[ $query_hash ] ) ) {
|
||||
// Enable big selects for reports
|
||||
$wpdb->query( 'SET SESSION SQL_BIG_SELECTS=1' );
|
||||
$cached_results[ $query_hash ] = apply_filters( 'wcs_reports_customer_total_renewal_switch_data', $wpdb->get_row( $renewal_switch_total_query ) );
|
||||
set_transient( strtolower( __CLASS__ ), $cached_results, WEEK_IN_SECONDS );
|
||||
WHERE renewal_order_ids.meta_key = '_subscription_renewal'
|
||||
OR renewal_order_ids.meta_key = '_subscription_switch'
|
||||
GROUP BY customer_id
|
||||
ORDER BY customer_id
|
||||
",
|
||||
array_merge( $customer_ids, $paid_statuses )
|
||||
);
|
||||
}
|
||||
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare.
|
||||
|
||||
$customer_totals->renewal_switch_total = $cached_results[ $query_hash ]->renewal_switch_total;
|
||||
$customer_totals->renewal_switch_count = $cached_results[ $query_hash ]->renewal_switch_count;
|
||||
/**
|
||||
* Filter the query used to fetch the totals by customer for related renewal and switch orders.
|
||||
*
|
||||
* @param string $query The query to fetch the totals by customer for related renewal and switch orders.
|
||||
* @return string The filtered query.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
$query = apply_filters( 'wcs_reports_current_customer_renewal_switch_total_query', $query );
|
||||
|
||||
return $customer_totals;
|
||||
return $wpdb->get_results( $query, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- This query is prepared above.
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize cache for report results.
|
||||
*/
|
||||
private static function init_cache() {
|
||||
self::$cached_report_results = get_transient( strtolower( __CLASS__ ) );
|
||||
|
||||
// Set a default value for cached results for PHP 8.2+ compatibility.
|
||||
if ( empty( self::$cached_report_results ) ) {
|
||||
self::$cached_report_results = array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache report results.
|
||||
*
|
||||
* @param string $query_hash The query hash.
|
||||
* @param array $report_data The report data.
|
||||
*/
|
||||
private static function cache_report_results( $query_hash, $report_data ) {
|
||||
self::$cached_report_results[ $query_hash ] = $report_data;
|
||||
set_transient( strtolower( __CLASS__ ), self::$cached_report_results, WEEK_IN_SECONDS );
|
||||
}
|
||||
}
|
||||
|
|
|
|||
445
includes/admin/reports/class-wcs-report-subscription-by-product.php
Executable file → Normal file
|
|
@ -12,15 +12,24 @@
|
|||
*/
|
||||
class WCS_Report_Subscription_By_Product extends WP_List_Table {
|
||||
|
||||
/**
|
||||
* Cached report results.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $cached_report_results = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct( array(
|
||||
'singular' => __( 'Product', 'woocommerce-subscriptions' ),
|
||||
'plural' => __( 'Products', 'woocommerce-subscriptions' ),
|
||||
'ajax' => false,
|
||||
) );
|
||||
parent::__construct(
|
||||
array(
|
||||
'singular' => __( 'Product', 'woocommerce-subscriptions' ),
|
||||
'plural' => __( 'Products', 'woocommerce-subscriptions' ),
|
||||
'ajax' => false,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -54,22 +63,22 @@ class WCS_Report_Subscription_By_Product extends WP_List_Table {
|
|||
|
||||
switch ( $column_name ) {
|
||||
|
||||
case 'product_name' :
|
||||
case 'product_name':
|
||||
// If the product is a subscription variation, use the parent product's edit post link
|
||||
if ( $report_item->parent_product_id > 0 ) {
|
||||
return edit_post_link( $report_item->product_name, ' - ', null, $report_item->parent_product_id );
|
||||
edit_post_link( $report_item->product_name, ' - ', null, $report_item->parent_product_id );
|
||||
} else {
|
||||
return edit_post_link( $report_item->product_name, null, null, $report_item->product_id );
|
||||
edit_post_link( $report_item->product_name, null, null, $report_item->product_id );
|
||||
}
|
||||
|
||||
case 'subscription_count' :
|
||||
break;
|
||||
case 'subscription_count':
|
||||
return sprintf( '<a href="%s%d">%d</a>', admin_url( 'edit.php?post_type=shop_subscription&_wcs_product=' ), $report_item->product_id, $report_item->subscription_count );
|
||||
|
||||
case 'average_recurring_total' :
|
||||
case 'average_recurring_total':
|
||||
$average_subscription_amount = ( 0 !== $report_item->subscription_count ? wc_price( $report_item->recurring_total / $report_item->subscription_count ) : '-' );
|
||||
return $average_subscription_amount;
|
||||
|
||||
case 'average_lifetime_value' :
|
||||
case 'average_lifetime_value':
|
||||
$average_subscription_amount = ( 0 !== $report_item->subscription_count ? wc_price( $report_item->product_total / $report_item->subscription_count ) : '-' );
|
||||
return $average_subscription_amount;
|
||||
|
||||
|
|
@ -87,9 +96,12 @@ class WCS_Report_Subscription_By_Product extends WP_List_Table {
|
|||
|
||||
$columns = array(
|
||||
'product_name' => __( 'Subscription Product', 'woocommerce-subscriptions' ),
|
||||
'subscription_count' => sprintf( __( 'Subscription Count %s', 'woocommerce-subscriptions' ), wcs_help_tip( __( 'The number of subscriptions that include this product as a line item and have a status other than pending or trashed.', 'woocommerce-subscriptions' ) ) ),
|
||||
'average_recurring_total' => sprintf( __( 'Average Recurring Line Total %s', 'woocommerce-subscriptions' ), wcs_help_tip( __( 'The average line total for this product on each subscription.', 'woocommerce-subscriptions' ) ) ),
|
||||
'average_lifetime_value' => sprintf( __( 'Average Lifetime Value %s', 'woocommerce-subscriptions' ), wcs_help_tip( __( 'The average line total on all orders for this product line item.', 'woocommerce-subscriptions' ) ) ),
|
||||
// translators: %s: help tip.
|
||||
'subscription_count' => sprintf( __( 'Subscription Count %s', 'woocommerce-subscriptions' ), wc_help_tip( __( 'The number of subscriptions that include this product as a line item and have a status other than pending or trashed.', 'woocommerce-subscriptions' ) ) ),
|
||||
// translators: %s: help tip.
|
||||
'average_recurring_total' => sprintf( __( 'Average Recurring Line Total %s', 'woocommerce-subscriptions' ), wc_help_tip( __( 'The average line total for this product on each subscription.', 'woocommerce-subscriptions' ) ) ),
|
||||
// translators: %s: help tip.
|
||||
'average_lifetime_value' => sprintf( __( 'Average Lifetime Value %s', 'woocommerce-subscriptions' ), wc_help_tip( __( 'The average line total on all orders for this product line item.', 'woocommerce-subscriptions' ) ) ),
|
||||
);
|
||||
|
||||
return $columns;
|
||||
|
|
@ -105,121 +117,37 @@ class WCS_Report_Subscription_By_Product extends WP_List_Table {
|
|||
|
||||
/**
|
||||
* Get subscription product data, either from the cache or the database.
|
||||
*
|
||||
* @see WCS_Report_Cache_Manager::update_cache() - This method is called by the cache manager to update the cache.
|
||||
*
|
||||
* @param array $args The arguments for the report.
|
||||
* @return array The subscription product data.
|
||||
*/
|
||||
public static function get_data( $args = array() ) {
|
||||
global $wpdb;
|
||||
|
||||
$default_args = array(
|
||||
'no_cache' => false,
|
||||
'order_status' => apply_filters( 'woocommerce_reports_paid_order_statuses', array( 'completed', 'processing' ) ),
|
||||
);
|
||||
|
||||
/**
|
||||
* Filter the arguments for the subscription by product report.
|
||||
*
|
||||
* @param array $args The arguments for the report.
|
||||
* @return array The filtered arguments.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
$args = apply_filters( 'wcs_reports_product_args', $args );
|
||||
$args = wp_parse_args( $args, $default_args );
|
||||
|
||||
$query = apply_filters( 'wcs_reports_product_query',
|
||||
"SELECT product.id as product_id,
|
||||
product.post_parent as parent_product_id,
|
||||
product.post_title as product_name,
|
||||
mo.product_type,
|
||||
COUNT(subscription_line_items.subscription_id) as subscription_count,
|
||||
SUM(subscription_line_items.product_total) as recurring_total
|
||||
FROM {$wpdb->posts} AS product
|
||||
LEFT JOIN (
|
||||
SELECT tr.object_id AS product_id, t.slug AS product_type
|
||||
FROM {$wpdb->prefix}term_relationships AS tr
|
||||
INNER JOIN {$wpdb->prefix}term_taxonomy AS x
|
||||
ON ( x.taxonomy = 'product_type' AND x.term_taxonomy_id = tr.term_taxonomy_id )
|
||||
INNER JOIN {$wpdb->prefix}terms AS t
|
||||
ON t.term_id = x.term_id
|
||||
) AS mo
|
||||
ON product.id = mo.product_id
|
||||
LEFT JOIN (
|
||||
SELECT wcoitems.order_id as subscription_id, wcoimeta.meta_value as product_id, wcoimeta.order_item_id, wcoimeta2.meta_value as product_total
|
||||
FROM {$wpdb->prefix}woocommerce_order_items AS wcoitems
|
||||
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS wcoimeta
|
||||
ON wcoimeta.order_item_id = wcoitems.order_item_id
|
||||
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS wcoimeta2
|
||||
ON wcoimeta2.order_item_id = wcoitems.order_item_id
|
||||
WHERE wcoitems.order_item_type = 'line_item'
|
||||
AND ( wcoimeta.meta_key = '_product_id' OR wcoimeta.meta_key = '_variation_id' )
|
||||
AND wcoimeta2.meta_key = '_line_total'
|
||||
) as subscription_line_items
|
||||
ON product.id = subscription_line_items.product_id
|
||||
LEFT JOIN {$wpdb->posts} as subscriptions
|
||||
ON subscriptions.ID = subscription_line_items.subscription_id
|
||||
WHERE product.post_status = 'publish'
|
||||
AND ( product.post_type = 'product' OR product.post_type = 'product_variation' )
|
||||
AND subscriptions.post_type = 'shop_subscription'
|
||||
AND subscriptions.post_status NOT IN( 'wc-pending', 'trash' )
|
||||
GROUP BY product.id
|
||||
ORDER BY COUNT(subscription_line_items.subscription_id) DESC" );
|
||||
|
||||
$cached_results = get_transient( strtolower( __CLASS__ ) );
|
||||
$query_hash = md5( $query );
|
||||
|
||||
if ( $args['no_cache'] || false === $cached_results || ! isset( $cached_results[ $query_hash ] ) ) {
|
||||
$wpdb->query( 'SET SESSION SQL_BIG_SELECTS=1' );
|
||||
$cached_results[ $query_hash ] = apply_filters( 'wcs_reports_product_data', $wpdb->get_results( $query, OBJECT_K ), $args );
|
||||
set_transient( strtolower( __CLASS__ ), $cached_results, WEEK_IN_SECONDS );
|
||||
}
|
||||
|
||||
$report_data = $cached_results[ $query_hash ];
|
||||
|
||||
// Organize subscription variations under the parent product in a tree structure
|
||||
$tree = array();
|
||||
foreach ( $report_data as $product_id => $product ) {
|
||||
if ( ! $product->parent_product_id ) {
|
||||
if ( isset( $tree[ $product_id ] ) ) {
|
||||
array_unshift( $tree[ $product_id ], $product_id );
|
||||
} else {
|
||||
$tree[ $product_id ][] = $product_id;
|
||||
}
|
||||
} else {
|
||||
$tree[ $product->parent_product_id ][] = $product_id;
|
||||
}
|
||||
}
|
||||
|
||||
// Create an array with all the report data in the correct order
|
||||
$ordered_report_data = array();
|
||||
foreach ( $tree as $parent_id => $children ) {
|
||||
foreach ( $children as $child_id ) {
|
||||
$ordered_report_data[ $child_id ] = $report_data[ $child_id ];
|
||||
|
||||
// When there are variations, store the variation ids.
|
||||
if ( 'variable-subscription' === $report_data[ $child_id ]->product_type ) {
|
||||
$ordered_report_data[ $child_id ]->variations = array_diff( $children, array( $parent_id ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now let's get the total revenue for each product so we can provide an average lifetime value for that product
|
||||
$query = apply_filters( 'wcs_reports_product_lifetime_value_query',
|
||||
"SELECT wcoimeta.meta_value as product_id, SUM(wcoimeta2.meta_value) as product_total
|
||||
FROM {$wpdb->prefix}woocommerce_order_items AS wcoitems
|
||||
INNER JOIN {$wpdb->posts} AS wcorders
|
||||
ON wcoitems.order_id = wcorders.ID
|
||||
AND wcorders.post_type = 'shop_order'
|
||||
AND wcorders.post_status IN ( 'wc-" . implode( "','wc-", $args['order_status'] ) . "' )
|
||||
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS wcoimeta
|
||||
ON wcoimeta.order_item_id = wcoitems.order_item_id
|
||||
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS wcoimeta2
|
||||
ON wcoimeta2.order_item_id = wcoitems.order_item_id
|
||||
WHERE ( wcoimeta.meta_key = '_product_id' OR wcoimeta.meta_key = '_variation_id' )
|
||||
AND wcoimeta2.meta_key = '_line_total'
|
||||
GROUP BY product_id" );
|
||||
|
||||
$query_hash = md5( $query );
|
||||
|
||||
if ( $args['no_cache'] || false === $cached_results || ! isset( $cached_results[ $query_hash ] ) ) {
|
||||
$wpdb->query( 'SET SESSION SQL_BIG_SELECTS=1' );
|
||||
$cached_results[ $query_hash ] = apply_filters( 'wcs_reports_product_lifetime_value_data', $wpdb->get_results( $query, OBJECT_K ), $args );
|
||||
set_transient( strtolower( __CLASS__ ), $cached_results, WEEK_IN_SECONDS );
|
||||
}
|
||||
self::init_cache();
|
||||
$subscriptions_by_product = self::fetch_subscription_products_data( $args );
|
||||
$subscription_product_totals = self::fetch_product_totals_data( $args );
|
||||
$ordered_report_data = self::organize_subscription_products_data( $subscriptions_by_product );
|
||||
|
||||
// Add the product total to each item
|
||||
foreach ( array_keys( $ordered_report_data ) as $product_id ) {
|
||||
$ordered_report_data[ $product_id ]->product_total = isset( $cached_results[ $query_hash ][ $product_id ] ) ? $cached_results[ $query_hash ][ $product_id ]->product_total : 0;
|
||||
$ordered_report_data[ $product_id ]->product_total = isset( $subscription_product_totals[ $product_id ] ) ? $subscription_product_totals[ $product_id ]->product_total : 0;
|
||||
}
|
||||
|
||||
return $ordered_report_data;
|
||||
|
|
@ -238,13 +166,13 @@ class WCS_Report_Subscription_By_Product extends WP_List_Table {
|
|||
// Filter top n variations corresponding to top 12 parent products
|
||||
$variations = array();
|
||||
foreach ( $products as $product ) {
|
||||
if ( ! empty( $product->variations ) ) { // Variable subscription
|
||||
foreach ( $product->variations as $variation_id ) {
|
||||
$variations[] = $this->items[ $variation_id ];
|
||||
}
|
||||
} else { // Simple subscription
|
||||
if ( ! empty( $product->variations ) ) { // Variable subscription
|
||||
foreach ( $product->variations as $variation_id ) {
|
||||
$variations[] = $this->items[ $variation_id ];
|
||||
}
|
||||
} else { // Simple subscription
|
||||
$variations[] = $product;
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<div class="chart-container" style="float: left; padding-top: 50px; min-width: 0px;">
|
||||
|
|
@ -261,11 +189,11 @@ class WCS_Report_Subscription_By_Product extends WP_List_Table {
|
|||
jQuery('.chart-placeholder.variation_breakdown_chart'),
|
||||
[
|
||||
<?php
|
||||
$colorindex = -1;
|
||||
$colorindex = -1;
|
||||
$last_parent_id = -1;
|
||||
foreach ( $variations as $product ) {
|
||||
if ( '0' === $product->parent_product_id || $last_parent_id !== $product->parent_product_id ) {
|
||||
$colorindex++;
|
||||
++$colorindex;
|
||||
$last_parent_id = $product->parent_product_id;
|
||||
}
|
||||
?>
|
||||
|
|
@ -301,7 +229,7 @@ class WCS_Report_Subscription_By_Product extends WP_List_Table {
|
|||
}
|
||||
}
|
||||
);
|
||||
jQuery('.chart-placeholder.variation_breakdown_chart').resize();
|
||||
jQuery('.chart-placeholder.variation_breakdown_chart').trigger( 'resize' );
|
||||
jQuery.plot(
|
||||
jQuery('.chart-placeholder.product_breakdown_chart'),
|
||||
[
|
||||
|
|
@ -315,7 +243,7 @@ class WCS_Report_Subscription_By_Product extends WP_List_Table {
|
|||
color: '<?php echo esc_js( $chart_colors[ $i ] ); ?>'
|
||||
},
|
||||
<?php
|
||||
$i++;
|
||||
++$i;
|
||||
}
|
||||
?>
|
||||
],
|
||||
|
|
@ -343,9 +271,270 @@ class WCS_Report_Subscription_By_Product extends WP_List_Table {
|
|||
}
|
||||
}
|
||||
);
|
||||
jQuery('.chart-placeholder.product_breakdown_chart').resize();
|
||||
jQuery('.chart-placeholder.product_breakdown_chart').trigger( 'resize' );
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the cached report data.
|
||||
*
|
||||
* @see WCS_Report_Cache_Manager::update_cache() - This method is called by the cache manager before updating the cache.
|
||||
*
|
||||
* @since 3.0.10
|
||||
*/
|
||||
public static function clear_cache() {
|
||||
delete_transient( strtolower( __CLASS__ ) );
|
||||
self::$cached_report_results = array();
|
||||
}
|
||||
|
||||
private static function fetch_subscription_products_data( $args = array() ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( wcs_is_custom_order_tables_usage_enabled() ) {
|
||||
$query = "SELECT product.id as product_id,
|
||||
product.post_parent as parent_product_id,
|
||||
product.post_title as product_name,
|
||||
mo.product_type,
|
||||
COUNT(subscription_line_items.subscription_id) as subscription_count,
|
||||
SUM(subscription_line_items.product_total) as recurring_total
|
||||
FROM {$wpdb->posts} AS product
|
||||
LEFT JOIN (
|
||||
SELECT tr.object_id AS product_id, t.slug AS product_type
|
||||
FROM {$wpdb->prefix}term_relationships AS tr
|
||||
INNER JOIN {$wpdb->prefix}term_taxonomy AS x
|
||||
ON ( x.taxonomy = 'product_type' AND x.term_taxonomy_id = tr.term_taxonomy_id )
|
||||
INNER JOIN {$wpdb->prefix}terms AS t
|
||||
ON t.term_id = x.term_id
|
||||
) AS mo
|
||||
ON product.id = mo.product_id
|
||||
LEFT JOIN (
|
||||
SELECT wcoitems.order_id as subscription_id, wcoimeta.meta_value as product_id, wcoimeta.order_item_id, wcoimeta2.meta_value as product_total
|
||||
FROM {$wpdb->prefix}woocommerce_order_items AS wcoitems
|
||||
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS wcoimeta
|
||||
ON wcoimeta.order_item_id = wcoitems.order_item_id
|
||||
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS wcoimeta2
|
||||
ON wcoimeta2.order_item_id = wcoitems.order_item_id
|
||||
WHERE wcoitems.order_item_type = 'line_item'
|
||||
AND ( wcoimeta.meta_key = '_product_id' OR wcoimeta.meta_key = '_variation_id' )
|
||||
AND wcoimeta2.meta_key = '_line_total'
|
||||
) as subscription_line_items
|
||||
ON product.id = subscription_line_items.product_id
|
||||
LEFT JOIN {$wpdb->prefix}wc_orders as subscriptions
|
||||
ON subscriptions.ID = subscription_line_items.subscription_id
|
||||
WHERE product.post_status = 'publish'
|
||||
AND ( product.post_type = 'product' OR product.post_type = 'product_variation' )
|
||||
AND subscriptions.type = 'shop_subscription'
|
||||
AND subscriptions.status NOT IN( 'wc-pending', 'auto-draft', 'wc-checkout-draft', 'trash' )
|
||||
GROUP BY product.id
|
||||
ORDER BY COUNT(subscription_line_items.subscription_id) DESC";
|
||||
} else {
|
||||
$query = "SELECT product.id as product_id,
|
||||
product.post_parent as parent_product_id,
|
||||
product.post_title as product_name,
|
||||
mo.product_type,
|
||||
COUNT(subscription_line_items.subscription_id) as subscription_count,
|
||||
SUM(subscription_line_items.product_total) as recurring_total
|
||||
FROM {$wpdb->posts} AS product
|
||||
LEFT JOIN (
|
||||
SELECT tr.object_id AS product_id, t.slug AS product_type
|
||||
FROM {$wpdb->prefix}term_relationships AS tr
|
||||
INNER JOIN {$wpdb->prefix}term_taxonomy AS x
|
||||
ON ( x.taxonomy = 'product_type' AND x.term_taxonomy_id = tr.term_taxonomy_id )
|
||||
INNER JOIN {$wpdb->prefix}terms AS t
|
||||
ON t.term_id = x.term_id
|
||||
) AS mo
|
||||
ON product.id = mo.product_id
|
||||
LEFT JOIN (
|
||||
SELECT wcoitems.order_id as subscription_id, wcoimeta.meta_value as product_id, wcoimeta.order_item_id, wcoimeta2.meta_value as product_total
|
||||
FROM {$wpdb->prefix}woocommerce_order_items AS wcoitems
|
||||
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS wcoimeta
|
||||
ON wcoimeta.order_item_id = wcoitems.order_item_id
|
||||
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS wcoimeta2
|
||||
ON wcoimeta2.order_item_id = wcoitems.order_item_id
|
||||
WHERE wcoitems.order_item_type = 'line_item'
|
||||
AND ( wcoimeta.meta_key = '_product_id' OR wcoimeta.meta_key = '_variation_id' )
|
||||
AND wcoimeta2.meta_key = '_line_total'
|
||||
) as subscription_line_items
|
||||
ON product.id = subscription_line_items.product_id
|
||||
LEFT JOIN {$wpdb->posts} as subscriptions
|
||||
ON subscriptions.ID = subscription_line_items.subscription_id
|
||||
WHERE product.post_status = 'publish'
|
||||
AND ( product.post_type = 'product' OR product.post_type = 'product_variation' )
|
||||
AND subscriptions.post_type = 'shop_subscription'
|
||||
AND subscriptions.post_status NOT IN( 'wc-pending', 'auto-draft', 'wc-checkout-draft', 'trash' )
|
||||
GROUP BY product.id
|
||||
ORDER BY COUNT(subscription_line_items.subscription_id) DESC";
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the query to get the subscription products data.
|
||||
*
|
||||
* @param string $query The query to get the subscription products data.
|
||||
* @return string The filtered query.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
$query = apply_filters( 'wcs_reports_product_query', $query );
|
||||
$query_hash = md5( $query );
|
||||
|
||||
// We expect that cache was initialized before calling this method.
|
||||
// Skip running the query if cache is available.
|
||||
if ( $args['no_cache'] || ! isset( self::$cached_report_results[ $query_hash ] ) ) {
|
||||
$wpdb->query( 'SET SESSION SQL_BIG_SELECTS=1' );
|
||||
$query_results = (array) $wpdb->get_results( $query, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
|
||||
/**
|
||||
* Filter the query results for subscription products.
|
||||
*
|
||||
* @param array $query_results The query results.
|
||||
* @param array $args The arguments for the report.
|
||||
* @return array The filtered query results.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
$query_results = apply_filters( 'wcs_reports_product_data', $query_results, $args );
|
||||
self::cache_report_results( $query_hash, $query_results );
|
||||
}
|
||||
|
||||
return self::$cached_report_results[ $query_hash ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Organize subscription products data for futher reporting.
|
||||
*
|
||||
* Group subscription product variations under variable subscription products.
|
||||
*
|
||||
* @param array $report_data The report data.
|
||||
* @return array The organized report data.
|
||||
*/
|
||||
private static function organize_subscription_products_data( $report_data ) {
|
||||
$tree = array();
|
||||
foreach ( $report_data as $product_id => $product ) {
|
||||
if ( ! $product->parent_product_id ) {
|
||||
if ( isset( $tree[ $product_id ] ) ) {
|
||||
array_unshift( $tree[ $product_id ], $product_id );
|
||||
} else {
|
||||
$tree[ $product_id ][] = $product_id;
|
||||
}
|
||||
} else {
|
||||
$tree[ $product->parent_product_id ][] = $product_id;
|
||||
}
|
||||
}
|
||||
|
||||
// Create an array with all the report data in the correct order
|
||||
$ordered_report_data = array();
|
||||
foreach ( $tree as $parent_id => $children ) {
|
||||
foreach ( $children as $child_id ) {
|
||||
$ordered_report_data[ $child_id ] = $report_data[ $child_id ];
|
||||
|
||||
// When there are variations, store the variation ids.
|
||||
if ( 'variable-subscription' === $report_data[ $child_id ]->product_type ) {
|
||||
$ordered_report_data[ $child_id ]->variations = array_diff( $children, array( $parent_id ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $ordered_report_data;
|
||||
}
|
||||
|
||||
private static function fetch_product_totals_data( $args = array() ) {
|
||||
global $wpdb;
|
||||
$placeholders = implode( ',', array_fill( 0, count( $args['order_status'] ), '%s' ) );
|
||||
$statuses = wcs_maybe_prefix_key( $args['order_status'], 'wc-' );
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- Ignored for allowing interpolation in the IN statements.
|
||||
if ( wcs_is_custom_order_tables_usage_enabled() ) {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT wcoimeta.meta_value as product_id, SUM(wcoimeta2.meta_value) as product_total
|
||||
FROM {$wpdb->prefix}woocommerce_order_items AS wcoitems
|
||||
INNER JOIN {$wpdb->prefix}wc_orders AS wcorders
|
||||
ON wcoitems.order_id = wcorders.ID
|
||||
AND wcorders.type = 'shop_order'
|
||||
AND wcorders.status IN ( {$placeholders} )
|
||||
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS wcoimeta
|
||||
ON wcoimeta.order_item_id = wcoitems.order_item_id
|
||||
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS wcoimeta2
|
||||
ON wcoimeta2.order_item_id = wcoitems.order_item_id
|
||||
WHERE ( wcoimeta.meta_key = '_product_id' OR wcoimeta.meta_key = '_variation_id' )
|
||||
AND wcoimeta2.meta_key = '_line_total'
|
||||
GROUP BY product_id",
|
||||
$statuses
|
||||
);
|
||||
} else {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT wcoimeta.meta_value as product_id, SUM(wcoimeta2.meta_value) as product_total
|
||||
FROM {$wpdb->prefix}woocommerce_order_items AS wcoitems
|
||||
INNER JOIN {$wpdb->posts} AS wcorders
|
||||
ON wcoitems.order_id = wcorders.ID
|
||||
AND wcorders.post_type = 'shop_order'
|
||||
AND wcorders.post_status IN ( {$placeholders} )
|
||||
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS wcoimeta
|
||||
ON wcoimeta.order_item_id = wcoitems.order_item_id
|
||||
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS wcoimeta2
|
||||
ON wcoimeta2.order_item_id = wcoitems.order_item_id
|
||||
WHERE ( wcoimeta.meta_key = '_product_id' OR wcoimeta.meta_key = '_variation_id' )
|
||||
AND wcoimeta2.meta_key = '_line_total'
|
||||
GROUP BY product_id",
|
||||
$statuses
|
||||
);
|
||||
}
|
||||
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
|
||||
|
||||
/**
|
||||
* Filter the query to get the product totals data.
|
||||
*
|
||||
* @param string $query The query to get the product totals data.
|
||||
* @return string The filtered query.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
$query = apply_filters( 'wcs_reports_product_lifetime_value_query', $query );
|
||||
|
||||
$query_hash = md5( $query );
|
||||
|
||||
// We expect that cache was initialized before calling this method.
|
||||
// Skip running the query if cache is available.
|
||||
if ( $args['no_cache'] || ! isset( self::$cached_report_results[ $query_hash ] ) ) {
|
||||
$wpdb->query( 'SET SESSION SQL_BIG_SELECTS=1' );
|
||||
$query_results = (array) $wpdb->get_results( $query, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
/**
|
||||
* Filter the query results for product totals.
|
||||
*
|
||||
* @param array $query_results The query results.
|
||||
* @param array $args The arguments for the report.
|
||||
* @return array The filtered query results.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
$query_results = apply_filters( 'wcs_reports_product_lifetime_value_data', $query_results, $args );
|
||||
self::cache_report_results( $query_hash, $query_results );
|
||||
}
|
||||
|
||||
return self::$cached_report_results[ $query_hash ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize cache for report results.
|
||||
*/
|
||||
private static function init_cache() {
|
||||
self::$cached_report_results = get_transient( strtolower( __CLASS__ ) );
|
||||
|
||||
// Set a default value for cached results for PHP 8.2+ compatibility.
|
||||
if ( empty( self::$cached_report_results ) ) {
|
||||
self::$cached_report_results = array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache report results.
|
||||
*
|
||||
* @param string $query_hash The query hash.
|
||||
* @param array $report_data The report data.
|
||||
*/
|
||||
private static function cache_report_results( $query_hash, $report_data ) {
|
||||
self::$cached_report_results[ $query_hash ] = $report_data;
|
||||
set_transient( strtolower( __CLASS__ ), self::$cached_report_results, WEEK_IN_SECONDS );
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1731
includes/admin/reports/class-wcs-report-subscription-events-by-date.php
Executable file → Normal file
169
includes/admin/reports/class-wcs-report-subscription-payment-retry.php
Executable file → Normal file
|
|
@ -18,7 +18,7 @@ class WCS_Report_Subscription_Payment_Retry extends WC_Admin_Report {
|
|||
|
||||
/**
|
||||
* Get report data
|
||||
* @return array
|
||||
* @return stdClass
|
||||
*/
|
||||
public function get_report_data() {
|
||||
|
||||
|
|
@ -36,65 +36,35 @@ class WCS_Report_Subscription_Payment_Retry extends WC_Admin_Report {
|
|||
global $wpdb;
|
||||
|
||||
// Convert from Decimal format(eg. 11.5) to a suitable format(eg. +11:30) for CONVERT_TZ() of SQL query.
|
||||
$offset = get_option( 'gmt_offset' );
|
||||
$site_timezone = sprintf( '%+02d:%02d', (int) $offset, ( $offset - floor( $offset ) ) * 60 );
|
||||
$retry_date_in_local_time = $wpdb->prepare( "CONVERT_TZ(retries.date_gmt, '+00:00', %s)", $site_timezone );
|
||||
$offset = get_option( 'gmt_offset' );
|
||||
$site_timezone = sprintf( '%+02d:%02d', (int) $offset, ( $offset - floor( $offset ) ) * 60 );
|
||||
$retry_date_in_local_time_query = $wpdb->prepare( "CONVERT_TZ(retries.date_gmt, '+00:00', %s)", $site_timezone );
|
||||
|
||||
// We need to compute this on our own since 'group_by_query' from the parent class uses posts table column names.
|
||||
switch ( $this->chart_groupby ) {
|
||||
case 'day':
|
||||
$this->group_by_query = "YEAR({$retry_date_in_local_time}), MONTH({$retry_date_in_local_time}), DAY({$retry_date_in_local_time})";
|
||||
$this->group_by_query = "YEAR({$retry_date_in_local_time_query}), MONTH({$retry_date_in_local_time_query}), DAY({$retry_date_in_local_time_query})";
|
||||
break;
|
||||
case 'month':
|
||||
$this->group_by_query = "YEAR({$retry_date_in_local_time}), MONTH({$retry_date_in_local_time})";
|
||||
$this->group_by_query = "YEAR({$retry_date_in_local_time_query}), MONTH({$retry_date_in_local_time_query})";
|
||||
break;
|
||||
}
|
||||
|
||||
$this->report_data = new stdClass;
|
||||
|
||||
$query_start_date = get_gmt_from_date( date( 'Y-m-d H:i:s', $this->start_date ) );
|
||||
$query_end_date = get_gmt_from_date( date( 'Y-m-d H:i:s', wcs_strtotime_dark_knight( '+1 day', $this->end_date ) ) );
|
||||
|
||||
// Get the sum of order totals for completed retries (i.e. retries which eventually succeeded in processing the failed payment)
|
||||
$renewal_query = $wpdb->prepare(
|
||||
"
|
||||
SELECT COUNT(DISTINCT retries.retry_id) as count, MIN(retries.date_gmt) AS retry_date_gmt, MIN({$retry_date_in_local_time}) AS retry_date, SUM(meta_order_total.meta_value) AS renewal_totals
|
||||
FROM {$wpdb->posts} AS orders
|
||||
INNER JOIN {$wpdb->prefix}wcs_payment_retries AS retries ON ( orders.ID = retries.order_id )
|
||||
LEFT JOIN {$wpdb->postmeta} AS meta_order_total ON ( orders.ID = meta_order_total.post_id AND meta_order_total.meta_key = '_order_total' )
|
||||
WHERE retries.status = 'complete'
|
||||
AND retries.date_gmt >= %s
|
||||
AND retries.date_gmt < %s
|
||||
GROUP BY {$this->group_by_query}
|
||||
ORDER BY retry_date_gmt ASC
|
||||
",
|
||||
$query_start_date,
|
||||
$query_end_date
|
||||
$query_options = array(
|
||||
'site_timezone' => $site_timezone,
|
||||
'query_start_date' => get_gmt_from_date( date( 'Y-m-d H:i:s', $this->start_date ) ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
|
||||
'query_end_date' => get_gmt_from_date( date( 'Y-m-d H:i:s', wcs_strtotime_dark_knight( '+1 day', $this->end_date ) ) ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
|
||||
);
|
||||
|
||||
$this->report_data->renewal_data = $wpdb->get_results( $renewal_query );
|
||||
|
||||
// Get the counts for all retries, grouped by day or month and status
|
||||
$retry_query = $wpdb->prepare(
|
||||
"
|
||||
SELECT COUNT(DISTINCT retries.retry_id) AS count, retries.status AS status, MIN(retries.date_gmt) AS retry_date_gmt, MIN({$retry_date_in_local_time}) AS retry_date
|
||||
FROM {$wpdb->prefix}wcs_payment_retries AS retries
|
||||
WHERE retries.status IN ( 'complete', 'failed', 'pending' )
|
||||
AND retries.date_gmt >= %s
|
||||
AND retries.date_gmt < %s
|
||||
GROUP BY {$this->group_by_query}, status
|
||||
ORDER BY retry_date_gmt ASC
|
||||
",
|
||||
$query_start_date,
|
||||
$query_end_date
|
||||
);
|
||||
|
||||
$this->report_data->retry_data = $wpdb->get_results( $retry_query );
|
||||
$this->fetch_renewal_data( $query_options );
|
||||
$this->fetch_retry_data( $query_options );
|
||||
|
||||
// Total up the query data
|
||||
$this->report_data->retry_failed_count = absint( array_sum( wp_list_pluck( wp_list_filter( $this->report_data->retry_data, array( 'status' => 'failed' ) ), 'count' ) ) );
|
||||
$this->report_data->retry_success_count = absint( array_sum( wp_list_pluck( wp_list_filter( $this->report_data->retry_data, array( 'status' => 'complete' ) ), 'count' ) ) );
|
||||
$this->report_data->retry_pending_count = absint( array_sum( wp_list_pluck( wp_list_filter( $this->report_data->retry_data, array( 'status' => 'pending' ) ), 'count' ) ) );
|
||||
$this->report_data->retry_failed_count = absint( array_sum( wp_list_pluck( wp_list_filter( $this->report_data->retry_data, array( 'status' => 'failed' ) ), 'count' ) ) );
|
||||
$this->report_data->retry_success_count = absint( array_sum( wp_list_pluck( wp_list_filter( $this->report_data->retry_data, array( 'status' => 'complete' ) ), 'count' ) ) );
|
||||
$this->report_data->retry_pending_count = absint( array_sum( wp_list_pluck( wp_list_filter( $this->report_data->retry_data, array( 'status' => 'pending' ) ), 'count' ) ) );
|
||||
|
||||
$this->report_data->renewal_total_count = absint( array_sum( wp_list_pluck( $this->report_data->renewal_data, 'count' ) ) );
|
||||
$this->report_data->renewal_total_amount = array_sum( wp_list_pluck( $this->report_data->renewal_data, 'renewal_totals' ) );
|
||||
|
|
@ -109,6 +79,7 @@ class WCS_Report_Subscription_Payment_Retry extends WC_Admin_Report {
|
|||
$data = $this->get_report_data();
|
||||
|
||||
$legend[] = array(
|
||||
// translators: %s: formatted amount.
|
||||
'title' => sprintf( __( '%s renewal revenue recovered', 'woocommerce-subscriptions' ), '<strong>' . wc_price( $data->renewal_total_amount ) . '</strong>' ),
|
||||
'placeholder' => __( 'The total amount of revenue, including tax and shipping, recovered with the failed payment retry system for renewal orders with a failed payment.', 'woocommerce-subscriptions' ),
|
||||
'color' => $this->chart_colours['renewal_total'],
|
||||
|
|
@ -116,12 +87,14 @@ class WCS_Report_Subscription_Payment_Retry extends WC_Admin_Report {
|
|||
);
|
||||
|
||||
$legend[] = array(
|
||||
'title' => sprintf( __( '%s renewal orders', 'woocommerce-subscriptions' ), '<strong>' . $data->renewal_total_count . '</strong>' ),
|
||||
'placeholder' => __( 'The number of renewal orders which had a failed payment use the retry system.', 'woocommerce-subscriptions' ),
|
||||
'color' => $this->chart_colours['renewal_count'],
|
||||
// translators: %s: renewal count.
|
||||
'title' => sprintf( __( '%s renewal orders', 'woocommerce-subscriptions' ), '<strong>' . $data->renewal_total_count . '</strong>' ),
|
||||
'placeholder' => __( 'The number of renewal orders which had a failed payment use the retry system.', 'woocommerce-subscriptions' ),
|
||||
'color' => $this->chart_colours['renewal_count'],
|
||||
);
|
||||
|
||||
$legend[] = array(
|
||||
// translators: %s: retry count.
|
||||
'title' => sprintf( __( '%s retry attempts succeeded', 'woocommerce-subscriptions' ), '<strong>' . $data->retry_success_count . '</strong>' ),
|
||||
'placeholder' => __( 'The number of renewal payment retries for this period which were able to process the payment which had previously failed one or more times.', 'woocommerce-subscriptions' ),
|
||||
'color' => $this->chart_colours['retry_success_count'],
|
||||
|
|
@ -129,6 +102,7 @@ class WCS_Report_Subscription_Payment_Retry extends WC_Admin_Report {
|
|||
);
|
||||
|
||||
$legend[] = array(
|
||||
// translators: %s: retry count.
|
||||
'title' => sprintf( __( '%s retry attempts failed', 'woocommerce-subscriptions' ), '<strong>' . $data->retry_failed_count . '</strong>' ),
|
||||
'placeholder' => __( 'The number of renewal payment retries for this period which did not result in a successful payment.', 'woocommerce-subscriptions' ),
|
||||
'color' => $this->chart_colours['retry_failure_count'],
|
||||
|
|
@ -136,6 +110,7 @@ class WCS_Report_Subscription_Payment_Retry extends WC_Admin_Report {
|
|||
);
|
||||
|
||||
$legend[] = array(
|
||||
// translators: %s: retry count.
|
||||
'title' => sprintf( __( '%s retry attempts pending', 'woocommerce-subscriptions' ), '<strong>' . $data->retry_pending_count . '</strong>' ),
|
||||
'placeholder' => __( 'The number of renewal payment retries not yet processed.', 'woocommerce-subscriptions' ),
|
||||
'color' => $this->chart_colours['retry_pending_count'],
|
||||
|
|
@ -151,10 +126,10 @@ class WCS_Report_Subscription_Payment_Retry extends WC_Admin_Report {
|
|||
public function output_report() {
|
||||
|
||||
$ranges = array(
|
||||
'year' => __( 'Year', 'woocommerce-subscriptions' ),
|
||||
'last_month' => __( 'Last Month', 'woocommerce-subscriptions' ),
|
||||
'month' => __( 'This Month', 'woocommerce-subscriptions' ),
|
||||
'7day' => __( 'Last 7 Days', 'woocommerce-subscriptions' ),
|
||||
'year' => __( 'Year', 'woocommerce-subscriptions' ),
|
||||
'last_month' => __( 'Last Month', 'woocommerce-subscriptions' ),
|
||||
'month' => __( 'This Month', 'woocommerce-subscriptions' ),
|
||||
'7day' => __( 'Last 7 Days', 'woocommerce-subscriptions' ),
|
||||
);
|
||||
|
||||
$this->chart_colours = array(
|
||||
|
|
@ -199,7 +174,7 @@ class WCS_Report_Subscription_Payment_Retry extends WC_Admin_Report {
|
|||
/**
|
||||
* Get the main chart
|
||||
*
|
||||
* @return string
|
||||
* @return void
|
||||
*/
|
||||
public function get_main_chart() {
|
||||
global $wp_locale;
|
||||
|
|
@ -234,7 +209,7 @@ class WCS_Report_Subscription_Payment_Retry extends WC_Admin_Report {
|
|||
var main_chart;
|
||||
|
||||
jQuery(function(){
|
||||
var chart_data = jQuery.parseJSON( '<?php echo json_encode( $chart_data ); ?>' );
|
||||
var chart_data = JSON.parse( '<?php echo json_encode( $chart_data ); ?>' );
|
||||
|
||||
var drawGraph = function( highlight ) {
|
||||
var series = [
|
||||
|
|
@ -373,12 +348,12 @@ class WCS_Report_Subscription_Payment_Retry extends WC_Admin_Report {
|
|||
}
|
||||
);
|
||||
|
||||
jQuery('.chart-placeholder').resize();
|
||||
jQuery('.chart-placeholder').trigger( 'resize' );
|
||||
}
|
||||
|
||||
drawGraph();
|
||||
|
||||
jQuery('.highlight_series').hover(
|
||||
jQuery('.highlight_series').on( 'hover',
|
||||
function() {
|
||||
drawGraph( jQuery(this).data('series') );
|
||||
},
|
||||
|
|
@ -403,4 +378,86 @@ class WCS_Report_Subscription_Payment_Retry extends WC_Admin_Report {
|
|||
return wc_format_decimal( $amount, wc_get_price_decimals() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sum of order totals for completed retries (i.e. retries which eventually succeeded in processing the failed payment)
|
||||
*
|
||||
* @param array $query_options Query options.
|
||||
*/
|
||||
private function fetch_renewal_data( $query_options ) {
|
||||
global $wpdb;
|
||||
$site_timezone = $query_options['site_timezone'];
|
||||
$query_start_date = $query_options['query_start_date'];
|
||||
$query_end_date = $query_options['query_end_date'];
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- The $this->group_by_query clause is hard coded.
|
||||
if ( wcs_is_custom_order_tables_usage_enabled() ) {
|
||||
$query = $wpdb->prepare(
|
||||
"
|
||||
SELECT COUNT(DISTINCT retries.retry_id) as count, MIN(retries.date_gmt) AS retry_date_gmt, MIN(CONVERT_TZ(retries.date_gmt, '+00:00', %s)) AS retry_date, SUM(orders.total_amount) AS renewal_totals
|
||||
FROM {$wpdb->prefix}wcs_payment_retries AS retries
|
||||
INNER JOIN {$wpdb->prefix}wc_orders AS orders ON ( orders.id = retries.order_id )
|
||||
WHERE retries.status = 'complete'
|
||||
AND retries.date_gmt >= %s
|
||||
AND retries.date_gmt < %s
|
||||
GROUP BY {$this->group_by_query}
|
||||
ORDER BY retry_date_gmt ASC
|
||||
",
|
||||
$site_timezone,
|
||||
$query_start_date,
|
||||
$query_end_date
|
||||
);
|
||||
} else {
|
||||
$query = $wpdb->prepare(
|
||||
"
|
||||
SELECT COUNT(DISTINCT retries.retry_id) as count, MIN(retries.date_gmt) AS retry_date_gmt, MIN(CONVERT_TZ(retries.date_gmt, '+00:00', %s)) AS retry_date, SUM(meta_order_total.meta_value) AS renewal_totals
|
||||
FROM {$wpdb->posts} AS orders
|
||||
INNER JOIN {$wpdb->prefix}wcs_payment_retries AS retries ON ( orders.ID = retries.order_id )
|
||||
LEFT JOIN {$wpdb->postmeta} AS meta_order_total ON ( orders.ID = meta_order_total.post_id AND meta_order_total.meta_key = '_order_total' )
|
||||
WHERE retries.status = 'complete'
|
||||
AND retries.date_gmt >= %s
|
||||
AND retries.date_gmt < %s
|
||||
GROUP BY {$this->group_by_query}
|
||||
ORDER BY retry_date_gmt ASC
|
||||
",
|
||||
$site_timezone,
|
||||
$query_start_date,
|
||||
$query_end_date
|
||||
);
|
||||
}
|
||||
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
|
||||
$this->report_data->renewal_data = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the counts for all retries, grouped by day or month and status
|
||||
*
|
||||
* @param array $query_options Query options.
|
||||
*/
|
||||
private function fetch_retry_data( $query_options ) {
|
||||
global $wpdb;
|
||||
$site_timezone = $query_options['site_timezone'];
|
||||
$query_start_date = $query_options['query_start_date'];
|
||||
$query_end_date = $query_options['query_end_date'];
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- The $this->group_by_query clause is hard coded.
|
||||
// We don't use HPOS tables here, so we can use it for both CPT and HPOS data stores.
|
||||
$query = $wpdb->prepare(
|
||||
"
|
||||
SELECT COUNT(DISTINCT retries.retry_id) AS count, retries.status AS status, MIN(retries.date_gmt) AS retry_date_gmt, MIN(CONVERT_TZ(retries.date_gmt, '+00:00', %s)) AS retry_date
|
||||
FROM {$wpdb->prefix}wcs_payment_retries AS retries
|
||||
WHERE retries.status IN ( 'complete', 'failed', 'pending' )
|
||||
AND retries.date_gmt >= %s
|
||||
AND retries.date_gmt < %s
|
||||
GROUP BY {$this->group_by_query}, status
|
||||
ORDER BY retry_date_gmt ASC
|
||||
",
|
||||
$site_timezone,
|
||||
$query_start_date,
|
||||
$query_end_date
|
||||
);
|
||||
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$this->report_data->retry_data = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
}
|
||||
|
|
|
|||
202
includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php
Executable file → Normal file
|
|
@ -17,6 +17,8 @@ class WCS_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report {
|
|||
|
||||
public $order_ids_recurring_totals = null;
|
||||
|
||||
public $average_sales = 0;
|
||||
|
||||
/**
|
||||
* Get the legend for the main chart sidebar
|
||||
* @return array
|
||||
|
|
@ -42,10 +44,14 @@ class WCS_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report {
|
|||
$next_payment_timestamp = strtotime( $r->scheduled_date );
|
||||
|
||||
//Remove the time part of the end date, if there is one
|
||||
if ( '0' !== $scheduled_ends[ $key ] ) {
|
||||
if ( '0' !== $scheduled_ends[ $key ] ) {
|
||||
$scheduled_ends[ $key ] = date( 'Y-m-d', strtotime( $scheduled_ends[ $key ] ) );
|
||||
}
|
||||
|
||||
if ( ! isset( $billing_intervals[ $key ] ) || ! isset( $billing_periods[ $key ] ) || ! in_array( $billing_periods[ $key ], array_keys( wcs_get_subscription_period_strings() ), true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Keep calculating all the new payments until we hit the end date of the search
|
||||
do {
|
||||
|
||||
|
|
@ -67,7 +73,9 @@ class WCS_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report {
|
|||
$this->order_ids_recurring_totals[ $update_key ]->recurring_total += $subscription_totals[ $key ];
|
||||
}
|
||||
}
|
||||
} while ( $next_payment_timestamp <= $this->end_date && isset( $scheduled_ends[ $key ] ) && ( 0 == $scheduled_ends[ $key ] || $next_payment_timestamp < strtotime( $scheduled_ends[ $key ] ) ) );
|
||||
} while ( $next_payment_timestamp > 0 && $next_payment_timestamp <= $this->end_date
|
||||
&& isset( $scheduled_ends[ $key ] )
|
||||
&& ( 0 == $scheduled_ends[ $key ] || $next_payment_timestamp < strtotime( $scheduled_ends[ $key ] ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -75,26 +83,31 @@ class WCS_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report {
|
|||
foreach ( $this->order_ids_recurring_totals as $r ) {
|
||||
if ( strtotime( $r->scheduled_date ) >= $this->start_date && strtotime( $r->scheduled_date ) <= $this->end_date ) {
|
||||
|
||||
$total_renewal_revenue += $r->recurring_total ;
|
||||
$total_renewal_revenue += $r->recurring_total;
|
||||
$total_renewal_count += $r->total_renewals;
|
||||
}
|
||||
}
|
||||
|
||||
$legend = array();
|
||||
|
||||
$this->average_sales = ( 0 != $total_renewal_count ? $total_renewal_revenue / $total_renewal_count : 0);
|
||||
$this->average_sales = ( 0 != $total_renewal_count ? $total_renewal_revenue / $total_renewal_count : 0 );
|
||||
|
||||
$legend[] = array(
|
||||
'title' => sprintf( __( '%s renewal income in this period', 'woocommerce-subscriptions' ), '<strong>' . wc_price( $total_renewal_revenue ) . '</strong>' ),
|
||||
'color' => $this->chart_colours['renewals_amount'],
|
||||
// translators: %s: formatted amount.
|
||||
'title' => sprintf( __( '%s renewal income in this period', 'woocommerce-subscriptions' ), '<strong>' . wc_price( $total_renewal_revenue ) . '</strong>' ),
|
||||
'placeholder' => __( 'The sum of all the upcoming renewal orders, including items, fees, tax and shipping, for currently active subscriptions.', 'woocommerce-subscriptions' ),
|
||||
'color' => $this->chart_colours['renewals_amount'],
|
||||
'highlight_series' => 1,
|
||||
);
|
||||
$legend[] = array(
|
||||
'title' => sprintf( __( '%s renewal orders', 'woocommerce-subscriptions' ), '<strong>' . $total_renewal_count . '</strong>' ),
|
||||
'color' => $this->chart_colours['renewals_count'],
|
||||
// translators: %s: renewal count.
|
||||
'title' => sprintf( __( '%s renewal orders', 'woocommerce-subscriptions' ), '<strong>' . $total_renewal_count . '</strong>' ),
|
||||
'placeholder' => __( 'The number of upcoming renewal orders, for currently active subscriptions.', 'woocommerce-subscriptions' ),
|
||||
'color' => $this->chart_colours['renewals_count'],
|
||||
'highlight_series' => 0,
|
||||
);
|
||||
$legend[] = array(
|
||||
// translators: %s: formatted amount.
|
||||
'title' => sprintf( __( '%s average renewal amount', 'woocommerce-subscriptions' ), '<strong>' . wc_price( $this->average_sales ) . '</strong>' ),
|
||||
'color' => $this->chart_colours['renewals_average'],
|
||||
);
|
||||
|
|
@ -104,61 +117,117 @@ class WCS_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report {
|
|||
|
||||
/**
|
||||
* Get report data.
|
||||
* @return stdClass
|
||||
*
|
||||
* @see WCS_Report_Cache_Manager::update_cache() - This method is called by the cache manager to update the cache.
|
||||
*
|
||||
* @param array $args The arguments for the report.
|
||||
* @return stdClass[] - Upcoming renewal data grouped by scheduled date.
|
||||
*/
|
||||
public function get_data( $args = array() ) {
|
||||
global $wpdb;
|
||||
|
||||
$default_args = array(
|
||||
'no_cache' => false,
|
||||
'no_cache' => false,
|
||||
);
|
||||
|
||||
$args = apply_filters( 'wcs_reports_upcoming_recurring_revenue_args', $args );
|
||||
$args = wp_parse_args( $args, $default_args );
|
||||
|
||||
// Query based on whole days, not minutes/hours so that we can cache the query for at least 24 hours
|
||||
$base_query = $wpdb->prepare(
|
||||
"SELECT
|
||||
DATE(ms.meta_value) as scheduled_date,
|
||||
SUM(mo.meta_value) as recurring_total,
|
||||
COUNT(mo.meta_value) as total_renewals,
|
||||
group_concat(p.ID) as subscription_ids,
|
||||
group_concat(mi.meta_value) as billing_intervals,
|
||||
group_concat(mp.meta_value) as billing_periods,
|
||||
group_concat(me.meta_value) as scheduled_ends,
|
||||
group_concat(mo.meta_value) as subscription_totals
|
||||
FROM {$wpdb->prefix}posts p
|
||||
LEFT JOIN {$wpdb->prefix}postmeta ms
|
||||
ON p.ID = ms.post_id
|
||||
LEFT JOIN {$wpdb->prefix}postmeta mo
|
||||
ON p.ID = mo.post_id
|
||||
LEFT JOIN {$wpdb->prefix}postmeta mi
|
||||
ON p.ID = mi.post_id
|
||||
LEFT JOIN {$wpdb->prefix}postmeta mp
|
||||
ON p.ID = mp.post_id
|
||||
LEFT JOIN {$wpdb->prefix}postmeta me
|
||||
ON p.ID = me.post_id
|
||||
WHERE p.post_type = 'shop_subscription'
|
||||
AND p.post_status = 'wc-active'
|
||||
AND mo.meta_key = '_order_total'
|
||||
AND ms.meta_key = '_schedule_next_payment'
|
||||
AND ( ( ms.meta_value < '%s' AND me.meta_value = 0 ) OR ( me.meta_value > '%s' AND ms.meta_value < '%s' ) )
|
||||
AND mi.meta_key = '_billing_interval'
|
||||
AND mp.meta_key = '_billing_period'
|
||||
AND me.meta_key = '_schedule_end '
|
||||
GROUP BY {$this->group_by_query}
|
||||
ORDER BY ms.meta_value ASC",
|
||||
date( 'Y-m-d', strtotime( '+1 DAY', $this->end_date ) ),
|
||||
date( 'Y-m-d', $this->start_date ),
|
||||
date( 'Y-m-d', strtotime( '+1 DAY', $this->end_date ) )
|
||||
);
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- The $this->group_by_query clause is hard coded.
|
||||
if ( wcs_is_custom_order_tables_usage_enabled() ) {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT
|
||||
DATE(meta_next_payment.meta_value) as scheduled_date,
|
||||
SUM(subscriptions.total_amount) as recurring_total,
|
||||
COUNT(subscriptions.total_amount) as total_renewals,
|
||||
group_concat(subscriptions.ID) as subscription_ids,
|
||||
group_concat(meta_billing_interval.meta_value) as billing_intervals,
|
||||
group_concat(meta_billing_period.meta_value) as billing_periods,
|
||||
group_concat(meta_schedule_end.meta_value) as scheduled_ends,
|
||||
group_concat(subscriptions.total_amount) as subscription_totals
|
||||
FROM {$wpdb->prefix}wc_orders subscriptions
|
||||
LEFT JOIN {$wpdb->prefix}wc_orders_meta meta_next_payment
|
||||
ON subscriptions.ID = meta_next_payment.order_id
|
||||
LEFT JOIN {$wpdb->prefix}wc_orders_meta meta_billing_interval
|
||||
ON subscriptions.ID = meta_billing_interval.order_id
|
||||
LEFT JOIN {$wpdb->prefix}wc_orders_meta meta_billing_period
|
||||
ON subscriptions.ID = meta_billing_period.order_id
|
||||
LEFT JOIN {$wpdb->prefix}wc_orders_meta meta_schedule_end
|
||||
ON subscriptions.ID = meta_schedule_end.order_id
|
||||
WHERE subscriptions.type = 'shop_subscription'
|
||||
AND subscriptions.status = 'wc-active'
|
||||
AND meta_next_payment.meta_key = '_schedule_next_payment'
|
||||
AND ( ( meta_next_payment.meta_value < %s AND meta_schedule_end.meta_value = 0 ) OR ( meta_schedule_end.meta_value > %s AND meta_next_payment.meta_value < %s ) )
|
||||
AND meta_billing_interval.meta_key = '_billing_interval'
|
||||
AND meta_billing_period.meta_key = '_billing_period'
|
||||
AND meta_schedule_end.meta_key = '_schedule_end'
|
||||
GROUP BY {$this->group_by_query}
|
||||
ORDER BY meta_next_payment.meta_value ASC",
|
||||
date( 'Y-m-d', strtotime( '+1 DAY', $this->end_date ) ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- Keep date formatting from original report.
|
||||
date( 'Y-m-d', $this->start_date ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- Keep date formatting from original report.
|
||||
date( 'Y-m-d', strtotime( '+1 DAY', $this->end_date ) ) // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- Keep date formatting from original report.
|
||||
);
|
||||
} else {
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT
|
||||
DATE(meta_next_payment.meta_value) as scheduled_date,
|
||||
SUM(meta_order_total.meta_value) as recurring_total,
|
||||
COUNT(meta_order_total.meta_value) as total_renewals,
|
||||
group_concat(posts.ID) as subscription_ids,
|
||||
group_concat(meta_billing_interval.meta_value) as billing_intervals,
|
||||
group_concat(meta_billing_period.meta_value) as billing_periods,
|
||||
group_concat(meta_schedule_end.meta_value) as scheduled_ends,
|
||||
group_concat(meta_order_total.meta_value) as subscription_totals
|
||||
FROM {$wpdb->prefix}posts posts
|
||||
LEFT JOIN {$wpdb->prefix}postmeta meta_next_payment
|
||||
ON posts.ID = meta_next_payment.post_id
|
||||
LEFT JOIN {$wpdb->prefix}postmeta meta_order_total
|
||||
ON posts.ID = meta_order_total.post_id
|
||||
LEFT JOIN {$wpdb->prefix}postmeta meta_billing_interval
|
||||
ON posts.ID = meta_billing_interval.post_id
|
||||
LEFT JOIN {$wpdb->prefix}postmeta meta_billing_period
|
||||
ON posts.ID = meta_billing_period.post_id
|
||||
LEFT JOIN {$wpdb->prefix}postmeta meta_schedule_end
|
||||
ON posts.ID = meta_schedule_end.post_id
|
||||
WHERE posts.post_type = 'shop_subscription'
|
||||
AND posts.post_status = 'wc-active'
|
||||
AND meta_order_total.meta_key = '_order_total'
|
||||
AND meta_next_payment.meta_key = '_schedule_next_payment'
|
||||
AND ( ( meta_next_payment.meta_value < %s AND meta_schedule_end.meta_value = 0 ) OR ( meta_schedule_end.meta_value > %s AND meta_next_payment.meta_value < %s ) )
|
||||
AND meta_billing_interval.meta_key = '_billing_interval'
|
||||
AND meta_billing_period.meta_key = '_billing_period'
|
||||
AND meta_schedule_end.meta_key = '_schedule_end'
|
||||
GROUP BY {$this->group_by_query}
|
||||
ORDER BY meta_next_payment.meta_value ASC",
|
||||
date( 'Y-m-d', strtotime( '+1 DAY', $this->end_date ) ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- Keep date formatting from original report.
|
||||
date( 'Y-m-d', $this->start_date ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- Keep date formatting from original report.
|
||||
date( 'Y-m-d', strtotime( '+1 DAY', $this->end_date ) ) // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- Keep date formatting from original report.
|
||||
);
|
||||
}
|
||||
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
|
||||
$cached_results = get_transient( strtolower( get_class( $this ) ) );
|
||||
$query_hash = md5( $base_query );
|
||||
$query_hash = md5( $query );
|
||||
|
||||
if ( $args['no_cache'] || false === $cached_results || ! isset( $cached_results[ $query_hash ] ) ) {
|
||||
// Set a default value for cached results for PHP 8.2+ compatibility.
|
||||
if ( empty( $cached_results ) ) {
|
||||
$cached_results = array();
|
||||
}
|
||||
|
||||
if ( $args['no_cache'] || ! isset( $cached_results[ $query_hash ] ) ) {
|
||||
$wpdb->query( 'SET SESSION SQL_BIG_SELECTS=1' );
|
||||
$cached_results[ $query_hash ] = apply_filters( 'wcs_reports_upcoming_recurring_revenue_data', $wpdb->get_results( $base_query, OBJECT_K ), $args );
|
||||
$results = $wpdb->get_results( $query, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- This query is prepared above.
|
||||
/**
|
||||
* Filter the upcoming recurring revenue data.
|
||||
*
|
||||
* @param array $results The upcoming recurring revenue data.
|
||||
* @param array $args The arguments for the report.
|
||||
* @since 2.1.0
|
||||
*/
|
||||
$results = apply_filters( 'wcs_reports_upcoming_recurring_revenue_data', $results, $args );
|
||||
|
||||
$cached_results[ $query_hash ] = $results;
|
||||
set_transient( strtolower( get_class( $this ) ), $cached_results, WEEK_IN_SECONDS );
|
||||
}
|
||||
|
||||
|
|
@ -212,7 +281,7 @@ class WCS_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report {
|
|||
|
||||
/**
|
||||
* Get the main chart
|
||||
* @return string
|
||||
* @return void
|
||||
*/
|
||||
public function get_main_chart() {
|
||||
global $wp_locale;
|
||||
|
|
@ -235,7 +304,7 @@ class WCS_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report {
|
|||
var main_chart;
|
||||
|
||||
jQuery(function(){
|
||||
var order_data = jQuery.parseJSON( '<?php echo json_encode( $chart_data ); ?>' );
|
||||
var order_data = JSON.parse( '<?php echo json_encode( $chart_data ); ?>' );
|
||||
var drawGraph = function( highlight ) {
|
||||
var series = [
|
||||
{
|
||||
|
|
@ -326,12 +395,12 @@ class WCS_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report {
|
|||
}
|
||||
);
|
||||
|
||||
jQuery('.chart-placeholder').resize();
|
||||
jQuery('.chart-placeholder').trigger( 'resize' );
|
||||
}
|
||||
|
||||
drawGraph();
|
||||
|
||||
jQuery('.highlight_series').hover(
|
||||
jQuery('.highlight_series').on( 'hover',
|
||||
function() {
|
||||
drawGraph( jQuery(this).data('series') );
|
||||
},
|
||||
|
|
@ -351,7 +420,7 @@ class WCS_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report {
|
|||
*/
|
||||
public function calculate_current_range( $current_range ) {
|
||||
switch ( $current_range ) {
|
||||
case 'custom' :
|
||||
case 'custom':
|
||||
$this->start_date = strtotime( sanitize_text_field( $_GET['start_date'] ) );
|
||||
$this->end_date = strtotime( 'midnight', strtotime( sanitize_text_field( $_GET['end_date'] ) ) );
|
||||
|
||||
|
|
@ -372,22 +441,22 @@ class WCS_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report {
|
|||
$this->chart_groupby = 'day';
|
||||
}
|
||||
break;
|
||||
case 'year' :
|
||||
case 'year':
|
||||
$this->start_date = strtotime( 'now', current_time( 'timestamp' ) );
|
||||
$this->end_date = strtotime( 'last day', strtotime( '+1 YEAR', current_time( 'timestamp' ) ) );
|
||||
$this->chart_groupby = 'month';
|
||||
break;
|
||||
case 'last_month' : // misnomer to match historical reports keys, handy for caching
|
||||
case 'last_month': // misnomer to match historical reports keys, handy for caching
|
||||
$this->start_date = strtotime( date( 'Y-m-01', wcs_add_months( current_time( 'timestamp' ), '1' ) ) );
|
||||
$this->end_date = strtotime( date( 'Y-m-t', $this->start_date ) );
|
||||
$this->chart_groupby = 'day';
|
||||
break;
|
||||
case 'month' :
|
||||
case 'month':
|
||||
$this->start_date = strtotime( 'now', current_time( 'timestamp' ) );
|
||||
$this->end_date = wcs_add_months( current_time( 'timestamp' ), '1' );
|
||||
$this->chart_groupby = 'day';
|
||||
break;
|
||||
case '7day' :
|
||||
case '7day':
|
||||
$this->start_date = strtotime( 'now', current_time( 'timestamp' ) );
|
||||
$this->end_date = strtotime( '+7 days', current_time( 'timestamp' ) );
|
||||
$this->chart_groupby = 'day';
|
||||
|
|
@ -396,13 +465,13 @@ class WCS_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report {
|
|||
|
||||
// Group by
|
||||
switch ( $this->chart_groupby ) {
|
||||
case 'day' :
|
||||
$this->group_by_query = 'YEAR(ms.meta_value), MONTH(ms.meta_value), DAY(ms.meta_value)';
|
||||
case 'day':
|
||||
$this->group_by_query = 'YEAR(meta_next_payment.meta_value), MONTH(meta_next_payment.meta_value), DAY(meta_next_payment.meta_value)';
|
||||
$this->chart_interval = ceil( max( 0, ( $this->end_date - $this->start_date ) / ( 60 * 60 * 24 ) ) );
|
||||
$this->barwidth = 60 * 60 * 24 * 1000;
|
||||
break;
|
||||
case 'month' :
|
||||
$this->group_by_query = 'YEAR(ms.meta_value), MONTH(ms.meta_value), DAY(ms.meta_value)';
|
||||
case 'month':
|
||||
$this->group_by_query = 'YEAR(meta_next_payment.meta_value), MONTH(meta_next_payment.meta_value), DAY(meta_next_payment.meta_value)';
|
||||
$this->chart_interval = 0;
|
||||
$min_date = $this->start_date;
|
||||
while ( ( $min_date = wcs_add_months( $min_date, '1' ) ) <= $this->end_date ) {
|
||||
|
|
@ -425,4 +494,15 @@ class WCS_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report {
|
|||
|
||||
return $current_range;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the cached query results.
|
||||
*
|
||||
* @see WCS_Report_Cache_Manager::update_cache() - This method is called by the cache manager before updating the cache.
|
||||
*
|
||||
* @since 3.0.10
|
||||
*/
|
||||
public function clear_cache() {
|
||||
delete_transient( strtolower( get_class( $this ) ) );
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Subscriptions Admin Report - Retention Rate
|
||||
*
|
||||
* Find the number of periods between when each subscription is created and ends or ended
|
||||
* then plot all subscriptions using this data to provide a curve of retention rates.
|
||||
*
|
||||
* @package WooCommerce Subscriptions
|
||||
* @subpackage WC_Subscriptions_Admin_Reports
|
||||
* @category Class
|
||||
* @author Prospress
|
||||
* @since 2.1
|
||||
* @deprecated in favor of WCS_Report_Retention_Rate
|
||||
*/
|
||||
class WC_Report_Retention_Rate extends WCS_Report_Retention_Rate {
|
||||
public function __construct() {
|
||||
wcs_deprecated_function( __CLASS__, '2.4.0', get_parent_class( __CLASS__ ) );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Subscriptions Admin Report - Subscriptions by customer
|
||||
*
|
||||
* Creates the subscription admin reports area.
|
||||
*
|
||||
* @package WooCommerce Subscriptions
|
||||
* @subpackage WC_Subscriptions_Admin_Reports
|
||||
* @category Class
|
||||
* @author Prospress
|
||||
* @since 2.1
|
||||
* @deprecated in favor of WCS_Report_Subscription_By_Customer
|
||||
*/
|
||||
class WC_Report_Subscription_By_Customer extends WCS_Report_Subscription_By_Customer {
|
||||
public function __construct() {
|
||||
wcs_deprecated_function( __CLASS__, '2.4.0', get_parent_class( __CLASS__ ) );
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Subscriptions Admin Report - Subscriptions by product
|
||||
*
|
||||
* Creates the subscription admin reports area.
|
||||
*
|
||||
* @package WooCommerce Subscriptions
|
||||
* @subpackage WC_Subscriptions_Admin_Reports
|
||||
* @category Class
|
||||
* @author Prospress
|
||||
* @since 2.1
|
||||
* @deprecated In favor of WCS_Report_Subscription_By_Product
|
||||
*/
|
||||
class WC_Report_Subscription_By_Product extends WCS_Report_Subscription_By_Product {
|
||||
public function __construct() {
|
||||
wcs_deprecated_function( __CLASS__, '2.4.0', get_parent_class( __CLASS__ ) );
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Subscriptions Admin Report - Subscription Events by Date
|
||||
*
|
||||
* Display important historical data for subscription revenue and events, like switches and cancellations.
|
||||
*
|
||||
* @package WooCommerce Subscriptions
|
||||
* @subpackage WC_Subscriptions_Admin_Reports
|
||||
* @category Class
|
||||
* @author Prospress
|
||||
* @since 2.1
|
||||
* @deprecated In favor of WCS_Report_Subscription_Events_By_Date
|
||||
*/
|
||||
class WC_Report_Subscription_Events_By_Date extends WCS_Report_Subscription_Events_By_Date {
|
||||
public function __construct() {
|
||||
wcs_deprecated_function( __CLASS__, '2.4.0', get_parent_class( __CLASS__ ) );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Subscriptions Admin Report - Subscription Events by Date
|
||||
*
|
||||
* Creates the subscription admin reports area.
|
||||
*
|
||||
* @package WooCommerce Subscriptions
|
||||
* @subpackage WC_Subscriptions_Admin_Reports
|
||||
* @category Class
|
||||
* @author Prospress
|
||||
* @since 2.1
|
||||
* @deprecated In favor of WCS_Report_Subscription_Payment_Retry
|
||||
*/
|
||||
class WC_Report_Subscription_Payment_Retry extends WCS_Report_Subscription_Payment_Retry {
|
||||
public function __construct() {
|
||||
wcs_deprecated_function( __CLASS__, '2.4.0', get_parent_class( __CLASS__ ) );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Subscriptions Admin Report - Upcoming Recurring Revenue
|
||||
*
|
||||
* Display the renewal order count and revenue that will be processed for all currently active subscriptions
|
||||
* for a given period of time in the future.
|
||||
*
|
||||
* @package WooCommerce Subscriptions
|
||||
* @subpackage WC_Subscriptions_Admin_Reports
|
||||
* @category Class
|
||||
* @author Prospress
|
||||
* @since 2.1
|
||||
* @deprecated In favor of WCS_Report_Upcoming_Recurring_Revenue
|
||||
*/
|
||||
class WC_Report_Upcoming_Recurring_Revenue extends WCS_Report_Upcoming_Recurring_Revenue {
|
||||
public function __construct() {
|
||||
wcs_deprecated_function( __CLASS__, '2.4.0', get_parent_class( __CLASS__ ) );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* WooCommerce Subscriptions Admin Functions
|
||||
*
|
||||
* @author Prospress
|
||||
* @category Core
|
||||
* @package WooCommerce Subscriptions/Functions
|
||||
* @version 2.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a message to display via @see wcs_display_admin_notices().
|
||||
*
|
||||
* @param string The message to display
|
||||
* @since 2.0
|
||||
*/
|
||||
function wcs_add_admin_notice( $message, $notice_type = 'success' ) {
|
||||
|
||||
$notices = get_transient( '_wcs_admin_notices' );
|
||||
|
||||
if ( false === $notices ) {
|
||||
$notices = array();
|
||||
}
|
||||
|
||||
$notices[ $notice_type ][] = $message;
|
||||
|
||||
set_transient( '_wcs_admin_notices', $notices, 60 * 60 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Display any notices added with @see wcs_add_admin_notice()
|
||||
*
|
||||
* This method is also hooked to 'admin_notices' to display notices there.
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
function wcs_display_admin_notices( $clear = true ) {
|
||||
|
||||
$notices = get_transient( '_wcs_admin_notices' );
|
||||
|
||||
if ( false !== $notices && ! empty( $notices ) ) {
|
||||
|
||||
if ( ! empty( $notices['success'] ) ) {
|
||||
array_walk( $notices['success'], 'esc_html' );
|
||||
echo '<div id="moderated" class="updated"><p>' . wp_kses_post( implode( "</p>\n<p>", $notices['success'] ) ) . '</p></div>';
|
||||
}
|
||||
|
||||
if ( ! empty( $notices['error'] ) ) {
|
||||
array_walk( $notices['error'], 'esc_html' );
|
||||
echo '<div id="moderated" class="error"><p>' . wp_kses_post( implode( "</p>\n<p>", $notices['error'] ) ) . '</p></div>';
|
||||
}
|
||||
}
|
||||
|
||||
if ( false !== $clear ) {
|
||||
wcs_clear_admin_notices();
|
||||
}
|
||||
}
|
||||
add_action( 'admin_notices', 'wcs_display_admin_notices' );
|
||||
|
||||
/**
|
||||
* Delete any admin notices we stored for display later.
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
function wcs_clear_admin_notices() {
|
||||
delete_transient( '_wcs_admin_notices' );
|
||||
}
|
||||
|
|
@ -1,23 +1,16 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API subscription notes controller
|
||||
* REST API Subscription notes controller.
|
||||
*
|
||||
* Handles requests to the /subscription/<id>/notes endpoint.
|
||||
* Handles requests to the /subscriptions/<id>/notes endpoint.
|
||||
*
|
||||
* @author Prospress
|
||||
* @since 2.1
|
||||
* @package WooCommerce Subscriptions\Rest Api
|
||||
* @since 3.1.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
/**
|
||||
* REST API Subscription Notes controller class.
|
||||
*
|
||||
* @package WooCommerce_Subscriptions/API
|
||||
* @extends WC_REST_Order_Notes_Controller
|
||||
*/
|
||||
class WC_REST_Subscription_Notes_Controller extends WC_REST_Order_Notes_V1_Controller {
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class WC_REST_Subscription_notes_Controller extends WC_REST_Order_Notes_Controller {
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
|
|
@ -33,4 +26,18 @@ class WC_REST_Subscription_Notes_Controller extends WC_REST_Order_Notes_V1_Contr
|
|||
*/
|
||||
protected $post_type = 'shop_subscription';
|
||||
|
||||
/**
|
||||
* Prepare links for the request.
|
||||
*
|
||||
* @since 3.1.0
|
||||
*
|
||||
* @param WP_Comment $note
|
||||
* @return array Links for the given order note.
|
||||
*/
|
||||
protected function prepare_links( $note ) {
|
||||
$links = parent::prepare_links( $note );
|
||||
$links['up'] = array( 'href' => rest_url( sprintf( '/%s/subscriptions/%d', $this->namespace, (int) $note->comment_post_ID ) ) );
|
||||
|
||||
return $links;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,176 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API System Status Endpoint Manager.
|
||||
*
|
||||
* Adds additional subscription-related data to the /wc/<version>/system_status endpoint.
|
||||
*
|
||||
* @package WooCommerce Subscriptions\Rest Api
|
||||
* @since 3.1.0
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class WC_REST_Subscription_System_Status_Manager {
|
||||
|
||||
/**
|
||||
* Attach callbacks.
|
||||
*/
|
||||
public static function init() {
|
||||
add_filter( 'woocommerce_rest_prepare_system_status', array( __CLASS__, 'add_subscription_fields_to_response' ) );
|
||||
add_filter( 'woocommerce_rest_system_status_schema', array( __CLASS__, 'add_additional_fields_to_schema' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds subscription fields to System Status response.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @deprecated 4.8.0
|
||||
*
|
||||
* @param WP_REST_Response $response The base system status response.
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public static function add_subscription_fields_to_reponse( $response ) {
|
||||
wcs_deprecated_function( __METHOD__, '4.8.0', __CLASS__ . '::add_subscription_fields_to_response' );
|
||||
$response->data['subscriptions'] = array(
|
||||
'wcs_debug' => defined( 'WCS_DEBUG' ) ? WCS_DEBUG : false,
|
||||
'mode' => ( WCS_Staging::is_duplicate_site() ) ? __( 'staging', 'woocommerce-subscriptions' ) : __( 'live', 'woocommerce-subscriptions' ),
|
||||
'live_url' => esc_url( WCS_Staging::get_site_url_from_source( 'subscriptions_install' ) ),
|
||||
'statuses' => array_filter( (array) wp_count_posts( 'shop_subscription' ) ),
|
||||
'report_cache_enabled' => ( 'yes' === get_option( 'woocommerce_subscriptions_cache_updates_enabled', 'yes' ) ),
|
||||
'cache_update_failures' => absint( get_option( 'woocommerce_subscriptions_cache_updates_failures', 0 ) ),
|
||||
'subscriptions_by_payment_gateway' => WCS_Admin_System_Status::get_subscriptions_by_gateway(),
|
||||
'payment_gateway_feature_support' => self::get_payment_gateway_feature_support(),
|
||||
);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds subscription fields to System Status response.
|
||||
*
|
||||
* @since 4.8.0
|
||||
*
|
||||
* @param WP_REST_Response $response The base system status response.
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public static function add_subscription_fields_to_response( $response ) {
|
||||
$count_by_status = WCS_Admin_System_Status::get_subscription_status_counts();
|
||||
|
||||
$response->data['subscriptions'] = array(
|
||||
'wcs_debug' => defined( 'WCS_DEBUG' ) ? WCS_DEBUG : false,
|
||||
'mode' => ( WCS_Staging::is_duplicate_site() ) ? __( 'staging', 'woocommerce-subscriptions' ) : __( 'live', 'woocommerce-subscriptions' ),
|
||||
'live_url' => esc_url( WCS_Staging::get_site_url_from_source( 'subscriptions_install' ) ),
|
||||
'statuses' => array_map( 'strval', $count_by_status ), // Enforce values as strings.
|
||||
'report_cache_enabled' => ( 'yes' === get_option( 'woocommerce_subscriptions_cache_updates_enabled', 'yes' ) ),
|
||||
'cache_update_failures' => absint( get_option( 'woocommerce_subscriptions_cache_updates_failures', 0 ) ),
|
||||
'subscriptions_by_payment_gateway' => WCS_Admin_System_Status::get_subscriptions_by_gateway(),
|
||||
'payment_gateway_feature_support' => self::get_payment_gateway_feature_support(),
|
||||
);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the store's payment gateways and the features they support.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @return array Payment gateway and their features.
|
||||
*/
|
||||
private static function get_payment_gateway_feature_support() {
|
||||
$gateway_features = array();
|
||||
|
||||
// @phpstan-ignore property.notFound
|
||||
foreach ( WC()->payment_gateways->get_available_payment_gateways() as $gateway_id => $gateway ) {
|
||||
// Some gateways include array keys. For consistency, only send the values.
|
||||
$gateway_features[ $gateway_id ] = array_values( (array) apply_filters( 'woocommerce_subscriptions_payment_gateway_features_list', $gateway->supports, $gateway ) );
|
||||
|
||||
if ( 'paypal' === $gateway_id && WCS_PayPal::are_reference_transactions_enabled() ) {
|
||||
$gateway_features[ $gateway_id ][] = 'paypal_reference_transactions';
|
||||
}
|
||||
}
|
||||
|
||||
return $gateway_features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds subscription system status fields the system status schema.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @param array $schema
|
||||
*
|
||||
* @return array the system status schema.
|
||||
*/
|
||||
public static function add_additional_fields_to_schema( $schema ) {
|
||||
|
||||
$schema['properties']['subscriptions'] = array(
|
||||
array(
|
||||
'description' => __( 'Subscriptions.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
'properties' => array(
|
||||
'wcs_debug_enabled' => array(
|
||||
'description' => __( 'WCS debug constant.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'boolean',
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'mode' => array(
|
||||
'description' => __( 'Subscriptions Mode', 'woocommerce-subscriptions' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'live_url' => array(
|
||||
'description' => __( 'Subscriptions Live Site URL', 'woocommerce-subscriptions' ),
|
||||
'type' => 'string',
|
||||
'format' => 'uri',
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'statuses' => array(
|
||||
'description' => __( 'Subscriptions broken down by status.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'array',
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
'report_cache_enabled' => array(
|
||||
'description' => __( 'Whether the Report Cache is enabled.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'boolean',
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'cache_update_failures' => array(
|
||||
'description' => __( 'Number of report cache failures.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'subscriptions_by_payment_gateway' => array(
|
||||
'description' => __( 'Subscriptions by Payment Gateway.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'array',
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
'payment_gateway_feature_support' => array(
|
||||
'description' => __( 'Payment Gateway Feature Support.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'array',
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $schema;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
/**
|
||||
* Class WC_REST_Subscriptions_Settings_Option_Controller
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST controller for settings options.
|
||||
*/
|
||||
class WC_REST_Subscriptions_Settings_Option_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
|
||||
/**
|
||||
* List of allowed option names that can be updated via the REST API.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private const ALLOWED_OPTIONS = [
|
||||
'woocommerce_subscriptions_gifting_is_welcome_announcement_dismissed',
|
||||
'woocommerce_subscriptions_downloads_is_welcome_announcement_dismissed',
|
||||
];
|
||||
|
||||
/**
|
||||
* Endpoint path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'subscriptions/settings';
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<option_name>[a-zA-Z0-9_-]+)',
|
||||
[
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => [ $this, 'update_option' ],
|
||||
'permission_callback' => [ $this, 'check_permission' ],
|
||||
'args' => [
|
||||
'option_name' => [
|
||||
'required' => true,
|
||||
'validate_callback' => [ $this, 'validate_option_name' ],
|
||||
],
|
||||
'value' => [
|
||||
'required' => true,
|
||||
'validate_callback' => [ $this, 'validate_value' ],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the option name.
|
||||
*
|
||||
* @param string $option_name The option name to validate.
|
||||
* @return bool
|
||||
*/
|
||||
public function validate_option_name( string $option_name ): bool {
|
||||
return in_array( $option_name, self::ALLOWED_OPTIONS, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the value parameter.
|
||||
*
|
||||
* @param mixed $value The value to validate.
|
||||
* @return bool|WP_Error True if valid, WP_Error if invalid.
|
||||
*/
|
||||
public function validate_value( $value ) {
|
||||
if ( is_bool( $value ) || is_array( $value ) ) {
|
||||
return true;
|
||||
}
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
__( 'Invalid value type; must be either boolean or array', 'woocommerce-subscriptions' ),
|
||||
[ 'status' => 400 ]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the option value.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
* @return WP_Error|WP_REST_Response
|
||||
*/
|
||||
public function update_option( WP_REST_Request $request ) {
|
||||
$option_name = $request->get_param( 'option_name' );
|
||||
$value = $request->get_param( 'value' );
|
||||
|
||||
update_option( $option_name, $value );
|
||||
|
||||
return rest_ensure_response(
|
||||
[
|
||||
'success' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify access.
|
||||
*
|
||||
* Override this method if custom permissions required.
|
||||
*/
|
||||
public function check_permission() {
|
||||
return current_user_can( 'manage_woocommerce' );
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* WC REST API Subscriptions Settings class.
|
||||
*
|
||||
* Adds subscription settings to the wc/<version>/settings and wc/<version>/settings/{group_id} endpoint.
|
||||
*/
|
||||
class WC_REST_Subscriptions_Settings {
|
||||
|
||||
/**
|
||||
* Init class and attach callbacks.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_filter( 'woocommerce_settings_groups', [ $this, 'add_settings_group' ] );
|
||||
add_filter( 'woocommerce_settings-subscriptions', [ $this, 'add_settings' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the subscriptions settings group for use in the WC REST API /settings endpoint
|
||||
*
|
||||
* @param array $groups Array of setting groups.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_settings_group( $groups ) {
|
||||
$groups[] = [
|
||||
'id' => 'subscriptions',
|
||||
'label' => __( 'Subscriptions', 'woocommerce-subscriptions' ),
|
||||
];
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subscriptions specific settings to the WC REST API /settings/subscriptions endpoint.
|
||||
*
|
||||
* @param array $settings Array of settings.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_settings( $settings ) {
|
||||
$subscription_settings = WC_Subscriptions_Admin::get_settings();
|
||||
|
||||
foreach( $subscription_settings as $setting ) {
|
||||
// Skip settings that don't have a id, type or are an invalid setting type i.e. skip over title, sectionend, etc.
|
||||
if ( empty( $setting['id'] ) || empty( $setting['type'] ) || ! $this->is_setting_type_valid( $setting['type'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$settings[] = $this->format_setting( $setting );
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a setting type is a valid supported setting type.
|
||||
*
|
||||
* @param string $type Type.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_setting_type_valid( $type ) {
|
||||
return in_array( $type, [ 'text', 'email', 'number', 'color', 'password', 'textarea', 'select', 'multiselect', 'radio', 'checkbox', 'image_width', 'thumbnail_cropping' ], true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the subscriptions setting in the format expected by the WC /settings REST API.
|
||||
*
|
||||
* @param array $setting Subscription setting.
|
||||
*
|
||||
* @return array|bool
|
||||
*/
|
||||
private function format_setting( $setting ) {
|
||||
$description = '';
|
||||
|
||||
if ( ! empty( $setting['desc'] ) ) {
|
||||
$description = $setting['desc'];
|
||||
} elseif ( ! empty( $setting['description'] ) ) {
|
||||
$description = $setting['description'];
|
||||
}
|
||||
|
||||
$new_setting = [
|
||||
'id' => $setting['id'],
|
||||
'label' => ! empty( $setting['title'] ) ? $setting['title'] : '',
|
||||
'description' => $description,
|
||||
'type' => $setting['type'],
|
||||
'option_key' => $setting['id'],
|
||||
'default' => ! empty( $setting['default'] ) ? $setting['default'] : '',
|
||||
];
|
||||
|
||||
if ( isset( $setting['options'] ) ) {
|
||||
$new_setting['options'] = $setting['options'];
|
||||
}
|
||||
|
||||
if ( isset( $setting['desc_tip'] ) ) {
|
||||
if ( true === $setting['desc_tip'] ) {
|
||||
$new_setting['tip'] = $description;
|
||||
} elseif ( ! empty( $setting['desc_tip'] ) ) {
|
||||
$new_setting['tip'] = $setting['desc_tip'];
|
||||
}
|
||||
}
|
||||
|
||||
return $new_setting;
|
||||
}
|
||||
}
|
||||
|
|
@ -212,7 +212,15 @@ class WC_API_Subscriptions extends WC_API_Orders {
|
|||
} catch ( WC_API_Exception $e ) {
|
||||
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
||||
} catch ( Exception $e ) {
|
||||
$response = array( 'error', array( 'wcs_api_error_create_subscription' => array( 'message' => $e->getMessage(), 'status' => $e->getCode() ) ) );
|
||||
$response = array(
|
||||
'error',
|
||||
array(
|
||||
'wcs_api_error_create_subscription' => array(
|
||||
'message' => $e->getMessage(),
|
||||
'status' => $e->getCode(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// show the subscription in response if it was still created but errored.
|
||||
if ( ! empty( $subscription ) && ! is_wp_error( $subscription ) ) {
|
||||
|
|
@ -294,14 +302,13 @@ class WC_API_Subscriptions extends WC_API_Orders {
|
|||
* @since 2.0
|
||||
*/
|
||||
public function update_payment_method( $subscription, $payment_details, $updating ) {
|
||||
global $wpdb;
|
||||
|
||||
$payment_gateways = WC()->payment_gateways->get_available_payment_gateways();
|
||||
$payment_method = ( ! empty( $payment_details['method_id'] ) ) ? $payment_details['method_id'] : 'manual';
|
||||
$payment_gateway = ( isset( $payment_gateways[ $payment_details['method_id'] ] ) ) ? $payment_gateways[ $payment_details['method_id'] ] : '';
|
||||
|
||||
try {
|
||||
$wpdb->query( 'START TRANSACTION' );
|
||||
$transaction = new WCS_SQL_Transaction();
|
||||
$transaction->start();
|
||||
|
||||
if ( $updating && ! array_key_exists( $payment_method, WCS_Change_Payment_Method_Admin::get_valid_payment_methods( $subscription ) ) ) {
|
||||
throw new Exception( 'wcs_api_edit_subscription_error', __( 'Gateway does not support admin changing the payment method on a Subscription.', 'woocommerce-subscriptions' ) );
|
||||
|
|
@ -336,10 +343,10 @@ class WC_API_Subscriptions extends WC_API_Orders {
|
|||
|
||||
$subscription->set_payment_method( $payment_gateway, $payment_method_meta );
|
||||
|
||||
$wpdb->query( 'COMMIT' );
|
||||
$transaction->commit();
|
||||
|
||||
} catch ( Exception $e ) {
|
||||
$wpdb->query( 'ROLLBACK' );
|
||||
$transaction->rollback();
|
||||
|
||||
// translators: 1$: gateway id, 2$: error message
|
||||
throw new Exception( sprintf( __( 'Subscription payment method could not be set to %1$s and has been set to manual with error message: %2$s', 'woocommerce-subscriptions' ), ( ! empty( $payment_gateway->id ) ) ? $payment_gateway->id : 'manual', $e->getMessage() ) );
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
* REST API Subscription Notes controller class.
|
||||
*
|
||||
* @package WooCommerce_Subscriptions/API
|
||||
* @extends WC_REST_Order_Notes_Controller
|
||||
*/
|
||||
class WC_REST_Subscription_Notes_Controller extends WC_REST_Order_Notes_Controller {
|
||||
|
||||
|
|
|
|||