Compare commits
105 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 | |
|
|
a6193c1a79 | |
|
|
98a2ef42f7 | |
|
|
8ba2071408 | |
|
|
5dacad960a | |
|
|
e38fdb9d42 | |
|
|
99a693a46f | |
|
|
5148a7cc09 | |
|
|
3ff3a8b701 | |
|
|
f83b7e363a | |
|
|
f85c892401 | |
|
|
721dda6e5c | |
|
|
37367ac369 | |
|
|
83dd2ef3e4 | |
|
|
ce24e61a4c | |
|
|
a3be3724dc | |
|
|
3a829316be | |
|
|
f008318e00 | |
|
|
f9932ac50e | |
|
|
3c16df04ae | |
|
|
69c6993fcf | |
|
|
126e4b6c6b | |
|
|
7dcf1a7e5f | |
|
|
1039aec2d9 | |
|
|
5fb62b0e6a | |
|
|
805a800c75 | |
|
|
0ff6d53f17 | |
|
|
a4ae134191 | |
|
|
05add38a82 | |
|
|
86123b4cd5 | |
|
|
e36b565bf5 | |
|
|
590cf5ca33 | |
|
|
925d9a4e36 | |
|
|
d409d54718 | |
|
|
1031d23d15 | |
|
|
7a87117404 | |
|
|
322cd43146 |
|
|
@ -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,15 +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%;
|
||||
}
|
||||
|
||||
#woocommerce_dashboard_status .wc_status_list li.cancel-count a:before {
|
||||
font-family: WooCommerce;
|
||||
content: "\e033";
|
||||
color: #aa0000;
|
||||
}
|
||||
|
||||
#woocommerce_dashboard_status .wc_status_list li.signup-revenue a:before {
|
||||
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;
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
#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,6 +1,146 @@
|
|||
@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;
|
||||
position: relative;
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
.subscription-auto-renew-toggle__i {
|
||||
height: 20px;
|
||||
width: 32px;
|
||||
border: 2px solid #00ba8a;
|
||||
background-color: #00ba8a;
|
||||
display: inline-block;
|
||||
text-indent: -9999px;
|
||||
border-radius: 10em;
|
||||
position: relative;
|
||||
margin-top: -1px;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
.subscription-auto-renew-toggle__i::before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: #fff;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.subscription-auto-renew-toggle--off .subscription-auto-renew-toggle__i {
|
||||
border-color: #999;
|
||||
background-color: #999;
|
||||
}
|
||||
|
||||
.subscription-auto-renew-toggle--off
|
||||
.subscription-auto-renew-toggle__i::before {
|
||||
right: auto;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.subscription-auto-renew-toggle--loading .subscription-auto-renew-toggle__i {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.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%;
|
||||
max-width: 200px;
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 69 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 |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 133 KiB |
|
After Width: | Height: | Size: 172 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 250 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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
jQuery( function ( $ ) {
|
||||
'use strict';
|
||||
|
||||
var renewals_field = document.querySelector( '.wcs_number_payments_field' ),
|
||||
$renewals_field = $( renewals_field );
|
||||
|
||||
/**
|
||||
* Subscription coupon actions.
|
||||
* @type {{init: function, type_options: function, move_field: function}}
|
||||
*/
|
||||
var wcs_meta_boxes_coupon_actions = {
|
||||
/**
|
||||
* Initialize variation actions.
|
||||
*/
|
||||
init: function () {
|
||||
if ( renewals_field ) {
|
||||
$( document.getElementById( 'discount_type' ) )
|
||||
.on( 'change', this.type_options )
|
||||
.trigger( 'change' );
|
||||
this.move_field();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Show/hide fields by coupon type options.
|
||||
*/
|
||||
type_options: function () {
|
||||
var select_val = $( this ).val();
|
||||
|
||||
switch ( select_val ) {
|
||||
case 'recurring_fee':
|
||||
case 'recurring_percent':
|
||||
$renewals_field.show();
|
||||
break;
|
||||
|
||||
default:
|
||||
$renewals_field.hide();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Move the renewal form field in the DOM to a better location.
|
||||
*/
|
||||
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,183 +1,431 @@
|
|||
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' )
|
||||
});
|
||||
|
||||
|
||||
// Make sure start date is before now
|
||||
if ( 'start' == date_type ) {
|
||||
|
||||
if ( 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' ) );
|
||||
}
|
||||
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
|
||||
else 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);
|
||||
});
|
||||
|
||||
function zeroise( val ) {
|
||||
return (val > 9 ) ? val : '0' + val;
|
||||
// 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);
|
||||
}
|
||||
|
||||
$('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);
|
||||
}
|
||||
});
|
||||
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('');
|
||||
}
|
||||
|
||||
$('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);
|
||||
function zeroise( val ) {
|
||||
return val > 9 ? val : '0' + val;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
if ( ! user_id ) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var data = {
|
||||
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,
|
||||
},
|
||||
} );
|
||||
|
||||
$.ajax( {
|
||||
url: WCSubscriptions.ajaxUrl,
|
||||
data: data,
|
||||
type: 'POST',
|
||||
success: function ( response ) {
|
||||
if ( response ) {
|
||||
var $orderlist = $( '#parent-order-id' );
|
||||
|
||||
$( '#parent-order-id' ).select2( 'val', '' );
|
||||
|
||||
$orderlist.empty(); // remove old options
|
||||
|
||||
$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 )
|
||||
);
|
||||
} );
|
||||
|
||||
$( '#parent-order-id' )
|
||||
.siblings( '.select2-container' )
|
||||
.unblock();
|
||||
}
|
||||
},
|
||||
} );
|
||||
return false;
|
||||
}
|
||||
|
||||
$( '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, 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();
|
||||
} );
|
||||
} );
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
( 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach DOM events.
|
||||
*/
|
||||
function attach_events() {
|
||||
if ( $cache.is_variable_subscription ) {
|
||||
$cache.document.on(
|
||||
'found_variation',
|
||||
update_first_payment_element
|
||||
);
|
||||
$cache.document.on( 'reset_data', clear_first_payment_element );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the variation's first payment element.
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
* @param {object} variation_data
|
||||
*/
|
||||
function update_first_payment_element( event, variation_data ) {
|
||||
$cache.first_payment_date.html( variation_data.first_payment_html );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the variation's first payment element.
|
||||
*/
|
||||
function clear_first_payment_element() {
|
||||
$cache.first_payment_date.html( '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise.
|
||||
*/
|
||||
function init() {
|
||||
generate_cache();
|
||||
attach_events();
|
||||
}
|
||||
|
||||
$( init );
|
||||
} )( document, jQuery );
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
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' );
|
||||
|
||||
// Early Renewal
|
||||
var $early_renewal_modal_submit = $( '#early_renewal_modal_submit' );
|
||||
var $early_renewal_modal_content = $( '.wcs-modal > .content-wrapper' );
|
||||
|
||||
function getTxtColor() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
function displayToggle() {
|
||||
$toggle.removeClass( 'subscription-auto-renew-toggle--hidden' );
|
||||
}
|
||||
|
||||
function onToggle( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
// Ignore the request if the toggle is disabled.
|
||||
if ( $toggle.hasClass( 'subscription-auto-renew-toggle--disabled' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var ajaxHandler = function ( action ) {
|
||||
var data = {
|
||||
subscription_id: WCSViewSubscription.subscription_id,
|
||||
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,
|
||||
data: data,
|
||||
type: 'POST',
|
||||
success: function ( result ) {
|
||||
if ( result.payment_method ) {
|
||||
$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 ) {
|
||||
alert( 'Exception:', exception );
|
||||
},
|
||||
complete: unblockToggle,
|
||||
} );
|
||||
};
|
||||
|
||||
// Enable auto-renew
|
||||
if ( $toggle.hasClass( 'subscription-auto-renew-toggle--off' ) ) {
|
||||
// if payment method already exists just turn automatic renewals on.
|
||||
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 {
|
||||
// Disable auto-renew
|
||||
ajaxHandler( 'wcs_disable_auto_renew' );
|
||||
displayToggleOff();
|
||||
}
|
||||
|
||||
maybeApplyColor();
|
||||
}
|
||||
|
||||
function displayToggleOn() {
|
||||
$icon.removeClass( 'fa-toggle-off' ).addClass( 'fa-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' )
|
||||
.attr( 'aria-checked', 'false' );
|
||||
}
|
||||
|
||||
function blockToggle() {
|
||||
$toggle.addClass( 'subscription-auto-renew-toggle--disabled' );
|
||||
$toggleContainer.block( {
|
||||
message: null,
|
||||
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
|
||||
);
|
||||
} );
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
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';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hide_non_applicable_coupons();
|
||||
|
||||
jQuery( function ( $ ) {
|
||||
$( document.body ).on( 'updated_cart_totals updated_checkout', function () {
|
||||
hide_non_applicable_coupons();
|
||||
} );
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
$( '.update-all-subscriptions-payment-method-wrap' ).hide();
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
|
@ -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}
|
||||
1339
changelog.txt
|
|
@ -1,132 +0,0 @@
|
|||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "f940d0fcab30dd0d30d8b2234dbbed1b",
|
||||
"packages": [
|
||||
{
|
||||
"name": "composer/installers",
|
||||
"version": "v1.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/installers.git",
|
||||
"reference": "79ad876c7498c0bbfe7eed065b8651c93bfd6045"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/installers/zipball/79ad876c7498c0bbfe7eed065b8651c93bfd6045",
|
||||
"reference": "79ad876c7498c0bbfe7eed065b8651c93bfd6045",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"composer-plugin-api": "^1.0"
|
||||
},
|
||||
"replace": {
|
||||
"roundcube/plugin-installer": "*",
|
||||
"shama/baton": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"composer/composer": "1.0.*@dev",
|
||||
"phpunit/phpunit": "4.1.*"
|
||||
},
|
||||
"type": "composer-plugin",
|
||||
"extra": {
|
||||
"class": "Composer\\Installers\\Plugin",
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Composer\\Installers\\": "src/Composer/Installers"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Kyle Robinson Young",
|
||||
"email": "kyle@dontkry.com",
|
||||
"homepage": "https://github.com/shama"
|
||||
}
|
||||
],
|
||||
"description": "A multi-framework Composer library installer",
|
||||
"homepage": "https://composer.github.io/installers/",
|
||||
"keywords": [
|
||||
"Craft",
|
||||
"Dolibarr",
|
||||
"Eliasis",
|
||||
"Hurad",
|
||||
"ImageCMS",
|
||||
"Kanboard",
|
||||
"MODX Evo",
|
||||
"Mautic",
|
||||
"Maya",
|
||||
"OXID",
|
||||
"Plentymarkets",
|
||||
"Porto",
|
||||
"RadPHP",
|
||||
"SMF",
|
||||
"Thelia",
|
||||
"WolfCMS",
|
||||
"agl",
|
||||
"aimeos",
|
||||
"annotatecms",
|
||||
"attogram",
|
||||
"bitrix",
|
||||
"cakephp",
|
||||
"chef",
|
||||
"cockpit",
|
||||
"codeigniter",
|
||||
"concrete5",
|
||||
"croogo",
|
||||
"dokuwiki",
|
||||
"drupal",
|
||||
"elgg",
|
||||
"expressionengine",
|
||||
"fuelphp",
|
||||
"grav",
|
||||
"installer",
|
||||
"itop",
|
||||
"joomla",
|
||||
"kohana",
|
||||
"laravel",
|
||||
"lavalite",
|
||||
"lithium",
|
||||
"magento",
|
||||
"mako",
|
||||
"mediawiki",
|
||||
"modulework",
|
||||
"moodle",
|
||||
"phpbb",
|
||||
"piwik",
|
||||
"ppi",
|
||||
"puppet",
|
||||
"reindex",
|
||||
"roundcube",
|
||||
"shopware",
|
||||
"silverstripe",
|
||||
"sydes",
|
||||
"symfony",
|
||||
"typo3",
|
||||
"wordpress",
|
||||
"yawik",
|
||||
"zend",
|
||||
"zikula"
|
||||
],
|
||||
"time": "2017-04-24T06:37:16+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": []
|
||||
}
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* An interface for creating a store for retry details.
|
||||
*
|
||||
* @package WooCommerce Subscriptions
|
||||
* @subpackage WCS_Retry_Store
|
||||
* @category Class
|
||||
* @author Prospress
|
||||
* @since 2.1
|
||||
*/
|
||||
|
||||
abstract class WCS_Retry_Store {
|
||||
|
||||
/** @var ActionScheduler_Store */
|
||||
private static $store = null;
|
||||
|
||||
/**
|
||||
* Save the details of a retry to the database
|
||||
*
|
||||
* @param WCS_Retry $retry
|
||||
* @return int the retry's ID
|
||||
*/
|
||||
abstract public function save( WCS_Retry $retry );
|
||||
|
||||
/**
|
||||
* Get the details of a retry from the database
|
||||
*
|
||||
* @param int $retry_id
|
||||
* @return WCS_Retry
|
||||
*/
|
||||
abstract public function get_retry( $retry_id );
|
||||
|
||||
/**
|
||||
* Get a set of retries from the database
|
||||
*
|
||||
* @param array $args A set of filters:
|
||||
* 'status': filter to only retries of a certain status, either 'pending', 'processing', 'failed' or 'complete'. Default: 'any', which will return all retries.
|
||||
* 'date_query': array of dates to filter retries those that occur 'after' or 'before' a certain (or inbetween those two dates). Should be a MySQL formated date/time string.
|
||||
* @return array An array of WCS_Retry objects
|
||||
*/
|
||||
abstract public function get_retries( $args );
|
||||
|
||||
/**
|
||||
* Get the IDs of all retries from the database for a given order
|
||||
*
|
||||
* @param int $order_id
|
||||
* @return array
|
||||
*/
|
||||
abstract protected function get_retry_ids_for_order( $order_id );
|
||||
|
||||
/**
|
||||
* Setup the class, if required
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
abstract public function init();
|
||||
|
||||
/**
|
||||
* Get the details of all retries (if any) for a given order
|
||||
*
|
||||
* @param int $order_id
|
||||
* @return array
|
||||
*/
|
||||
public function get_retries_for_order( $order_id ) {
|
||||
|
||||
$retries = array();
|
||||
|
||||
foreach ( $this->get_retry_ids_for_order( $order_id ) as $retry_id ) {
|
||||
$retries[ $retry_id ] = $this->get_retry( $retry_id );
|
||||
}
|
||||
|
||||
return $retries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the details of the last retry (if any) recorded for a given order
|
||||
*
|
||||
* @param int $order_id
|
||||
* @return WCS_Retry | null
|
||||
*/
|
||||
public function get_last_retry_for_order( $order_id ) {
|
||||
|
||||
$retry_ids = $this->get_retry_ids_for_order( $order_id );
|
||||
|
||||
if ( ! empty( $retry_ids ) ) {
|
||||
$last_retry_id = array_pop( $retry_ids );
|
||||
$last_retry = $this->get_retry( $last_retry_id );
|
||||
} else {
|
||||
$last_retry = null;
|
||||
}
|
||||
|
||||
return $last_retry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of retries stored in the database for a given order
|
||||
*
|
||||
* @param int $order_id
|
||||
* @return int
|
||||
*/
|
||||
public function get_retry_count_for_order( $order_id ) {
|
||||
|
||||
$retry_post_ids = $this->get_retry_ids_for_order( $order_id );
|
||||
|
||||
return count( $retry_post_ids );
|
||||
}
|
||||
}
|
||||
|
|
@ -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,250 +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_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( 'Billing 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, 2 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print admin styles/scripts
|
||||
*/
|
||||
public function enqueue_styles_scripts() {
|
||||
global $post;
|
||||
|
||||
// Get admin screen id
|
||||
$screen = get_current_screen();
|
||||
|
||||
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' ),
|
||||
) ) );
|
||||
} 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' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) && ! $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' );
|
||||
}
|
||||
|
||||
$actions['wcs_create_pending_renewal'] = esc_html__( 'Create pending renewal 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
|
||||
}
|
||||
|
||||
$subscription->add_order_note( __( 'Create pending renewal 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;
|
||||
}
|
||||
}
|
||||
|
||||
new WCS_Admin_Meta_Boxes();
|
||||
|
|
@ -23,12 +23,17 @@ 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 the reports layout to the WooCommerce -> Reports admin section
|
||||
add_filter( 'wc_admin_reports_path', __CLASS__ . '::initialize_reports_path', 12, 3 );
|
||||
add_filter( 'woocommerce_admin_reports', __CLASS__ . '::initialize_reports', 12, 1 );
|
||||
|
||||
// Add any necessary scripts
|
||||
add_action( 'admin_enqueue_scripts', __CLASS__ . '::reports_scripts' );
|
||||
|
|
@ -36,49 +41,96 @@ class WCS_Admin_Reports {
|
|||
// 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' ),
|
||||
'description' => '',
|
||||
'hide_title' => true,
|
||||
'callback' => array( 'WC_Admin_Reports', 'get_report' ),
|
||||
'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( 'WC_Admin_Reports', 'get_report' ),
|
||||
'callback' => array( 'WCS_Admin_Reports', 'get_report' ),
|
||||
),
|
||||
'retention_rate' => array(
|
||||
'retention_rate' => array(
|
||||
'title' => __( 'Retention Rate', 'woocommerce-subscriptions' ),
|
||||
'description' => '',
|
||||
'hide_title' => true,
|
||||
'callback' => array( 'WC_Admin_Reports', 'get_report' ),
|
||||
'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( 'WC_Admin_Reports', 'get_report' ),
|
||||
'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,
|
||||
'callback' => array( 'WC_Admin_Reports', 'get_report' ),
|
||||
'callback' => array( 'WCS_Admin_Reports', 'get_report' ),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -88,61 +140,45 @@ class WCS_Admin_Reports {
|
|||
'title' => __( 'Failed Payment Retries', 'woocommerce-subscriptions' ),
|
||||
'description' => '',
|
||||
'hide_title' => true,
|
||||
'callback' => array( 'WC_Admin_Reports', 'get_report' ),
|
||||
'callback' => array( 'WCS_Admin_Reports', 'get_report' ),
|
||||
);
|
||||
}
|
||||
|
||||
return $reports;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we hit one of our reports in the WC get_report function, change the path to our dir.
|
||||
*
|
||||
* @param report_path the parth to the report.
|
||||
* @param name the name of the report.
|
||||
* @param class the class of the report.
|
||||
* @return string path to the report template.
|
||||
* @since 2.1
|
||||
*/
|
||||
public static function initialize_reports_path( $report_path, $name, $class ) {
|
||||
|
||||
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/class-wcs-report-' . $name . '.php';
|
||||
}
|
||||
|
||||
return $report_path;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add any subscriptions report javascript to the admin pages.
|
||||
*
|
||||
* @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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -155,13 +191,65 @@ class WCS_Admin_Reports {
|
|||
|
||||
$screen = get_current_screen();
|
||||
|
||||
switch ( $screen->id ) {
|
||||
case 'dashboard' :
|
||||
include_once( 'reports/class-wcs-report-dashboard.php' );
|
||||
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 report name to be fetched.
|
||||
*/
|
||||
public static function get_report( $name ) {
|
||||
$name = sanitize_title( str_replace( '_', '-', $name ) );
|
||||
$class = 'WCS_Report_' . str_replace( '-', '_', $name );
|
||||
|
||||
if ( ! class_exists( $class ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$report = new $class();
|
||||
$report->output_report();
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new WCS_Admin_Reports();
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Automatic Failed Payment Retries Meta Box
|
||||
*
|
||||
* Display the automatic failed payment retries on the Edit Order screen for an order that has been automatically retried.
|
||||
*
|
||||
* @author Prospress
|
||||
* @category Admin
|
||||
* @package WooCommerce Subscriptions/Admin/Meta Boxes
|
||||
* @version 2.1
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* WCS_Meta_Box_Payment_Retries Class
|
||||
*/
|
||||
class WCS_Meta_Box_Payment_Retries {
|
||||
|
||||
/**
|
||||
* Output the metabox
|
||||
*/
|
||||
public static function output( $post ) {
|
||||
|
||||
$retries = WCS_Retry_Manager::store()->get_retries_for_order( $post->ID );
|
||||
|
||||
include_once( 'views/html-retries-table.php' );
|
||||
|
||||
do_action( 'woocommerce_subscriptions_retries_meta_box', $post->ID, $retries );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,124 +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( '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();
|
||||
|
||||
// On the subscription page, just show related orders
|
||||
if ( wcs_is_subscription( $post->ID ) ) {
|
||||
$subscriptions[] = wcs_get_subscription( $post->ID );
|
||||
} 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 ( wcs_is_subscription( $post->ID ) ) {
|
||||
|
||||
$initial_subscriptions = wcs_get_subscriptions_for_resubscribe_order( $post->ID );
|
||||
|
||||
$resubscribed_subscriptions = get_posts( array(
|
||||
'meta_key' => '_subscription_resubscribe',
|
||||
'meta_value' => $post->ID,
|
||||
'post_type' => 'shop_subscription',
|
||||
'post_status' => 'any',
|
||||
'posts_per_page' => -1,
|
||||
) );
|
||||
|
||||
foreach ( $resubscribed_subscriptions as $subscription ) {
|
||||
$subscription = wcs_get_subscription( $subscription );
|
||||
wcs_set_objects_property( $subscription, 'relationship', _x( 'Resubscribed Subscription', 'relation to order', 'woocommerce-subscriptions' ), 'set_prop_only' );
|
||||
$orders[] = $subscription;
|
||||
}
|
||||
} 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( 'views/html-related-orders-row.php' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,320 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Order Data
|
||||
*
|
||||
* Functions for displaying the order data meta box.
|
||||
*
|
||||
* @author Prospress
|
||||
* @category Admin
|
||||
* @package WooCommerce Subscriptions/Admin/Meta Boxes
|
||||
* @version 2.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* WCS_Meta_Box_Subscription_Data Class
|
||||
*/
|
||||
class WCS_Meta_Box_Subscription_Data extends WC_Meta_Box_Order_Data {
|
||||
|
||||
/**
|
||||
* Output the metabox
|
||||
*/
|
||||
public static function output( $post ) {
|
||||
global $the_subscription;
|
||||
|
||||
if ( ! is_object( $the_subscription ) || $the_subscription->get_id() !== $post->ID ) {
|
||||
$the_subscription = wc_get_order( $post->ID );
|
||||
}
|
||||
|
||||
$subscription = $the_subscription;
|
||||
|
||||
self::init_address_fields();
|
||||
|
||||
wp_nonce_field( 'woocommerce_save_data', 'woocommerce_meta_nonce' );
|
||||
?>
|
||||
<style type="text/css">
|
||||
#post-body-content, #titlediv, #major-publishing-actions, #minor-publishing-actions, #visibility, #submitdiv { display:none }
|
||||
</style>
|
||||
<div class="panel-wrap woocommerce">
|
||||
<input name="post_title" type="hidden" value="<?php echo empty( $post->post_title ) ? esc_attr( get_post_type_object( $post->post_type )->labels->singular_name ) : esc_attr( $post->post_title ); ?>" />
|
||||
<input name="post_status" type="hidden" value="<?php echo esc_attr( 'wc-' . $subscription->get_status() ); ?>" />
|
||||
<div id="order_data" class="panel">
|
||||
|
||||
<h2><?php
|
||||
// translators: placeholder is the ID of the subscription
|
||||
printf( esc_html_x( 'Subscription #%s details', 'edit subscription header', 'woocommerce-subscriptions' ), esc_html( $subscription->get_order_number() ) ); ?></h2>
|
||||
|
||||
<div class="order_data_column_container">
|
||||
<div class="order_data_column">
|
||||
|
||||
<p class="form-field form-field-wide wc-customer-user">
|
||||
<label for="customer_user"><?php esc_html_e( 'Customer:', 'woocommerce-subscriptions' ) ?> <?php
|
||||
if ( $subscription->get_user_id() ) {
|
||||
$args = array(
|
||||
'post_status' => 'all',
|
||||
'post_type' => 'shop_subscription',
|
||||
'_customer_user' => absint( $subscription->get_user_id() ),
|
||||
);
|
||||
printf( '<a href="%s">%s →</a>',
|
||||
esc_url( add_query_arg( $args, admin_url( 'edit.php' ) ) ),
|
||||
esc_html__( 'View other subscriptions', 'woocommerce-subscriptions' )
|
||||
);
|
||||
}
|
||||
?></label>
|
||||
<?php
|
||||
$user_string = '';
|
||||
$user_id = '';
|
||||
if ( $subscription->get_user_id() && ( false !== get_userdata( $subscription->get_user_id() ) ) ) {
|
||||
$user_id = absint( $subscription->get_user_id() );
|
||||
$user = get_user_by( 'id', $user_id );
|
||||
$user_string = esc_html( $user->display_name ) . ' (#' . absint( $user->ID ) . ' – ' . esc_html( $user->user_email ) . ')';
|
||||
}
|
||||
WCS_Select2::render( array(
|
||||
'class' => 'wc-customer-search',
|
||||
'name' => 'customer_user',
|
||||
'id' => 'customer_user',
|
||||
'placeholder' => esc_attr__( 'Search for a customer…', 'woocommerce-subscriptions' ),
|
||||
'selected' => $user_string,
|
||||
'value' => $user_id,
|
||||
) );
|
||||
?>
|
||||
</p>
|
||||
|
||||
<p class="form-field form-field-wide">
|
||||
<label for="order_status"><?php esc_html_e( 'Subscription status:', 'woocommerce-subscriptions' ); ?></label>
|
||||
<select id="order_status" name="order_status">
|
||||
<?php
|
||||
$statuses = wcs_get_subscription_statuses();
|
||||
foreach ( $statuses as $status => $status_name ) {
|
||||
if ( ! $subscription->can_be_updated_to( $status ) && ! $subscription->has_status( str_replace( 'wc-', '', $status ) ) ) {
|
||||
continue;
|
||||
}
|
||||
echo '<option value="' . esc_attr( $status ) . '" ' . selected( $status, 'wc-' . $subscription->get_status(), false ) . '>' . esc_html( $status_name ) . '</option>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</p>
|
||||
|
||||
<?php do_action( 'woocommerce_admin_order_data_after_order_details', $subscription ); ?>
|
||||
|
||||
</div>
|
||||
<div class="order_data_column">
|
||||
<h3>
|
||||
<?php esc_html_e( 'Billing Details', 'woocommerce-subscriptions' ); ?>
|
||||
<a href="#" class="edit_address"><?php esc_html_e( 'Edit', 'woocommerce-subscriptions' ); ?></a>
|
||||
<a href="#" class="tips load_customer_billing" data-tip="<?php esc_attr_e( 'Load billing address', 'woocommerce-subscriptions' ); ?>" style="display:none;"><?php esc_html_e( 'Load billing address', 'woocommerce-subscriptions' ); ?></a>
|
||||
</h3>
|
||||
<?php
|
||||
// Display values
|
||||
echo '<div class="address">';
|
||||
|
||||
if ( $subscription->get_formatted_billing_address() ) {
|
||||
echo '<p><strong>' . esc_html__( 'Address', 'woocommerce-subscriptions' ) . ':</strong>' . wp_kses( $subscription->get_formatted_billing_address(), array( 'br' => array() ) ) . '</p>';
|
||||
} else {
|
||||
echo '<p class="none_set"><strong>' . esc_html__( 'Address', 'woocommerce-subscriptions' ) . ':</strong> ' . esc_html__( 'No billing address set.', 'woocommerce-subscriptions' ) . '</p>';
|
||||
}
|
||||
|
||||
foreach ( self::$billing_fields as $key => $field ) {
|
||||
|
||||
if ( isset( $field['show'] ) && false === $field['show'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$function_name = 'get_billing_' . $key;
|
||||
|
||||
if ( is_callable( array( $subscription, $function_name ) ) ) {
|
||||
$field_value = $subscription->$function_name( 'edit' );
|
||||
} else {
|
||||
$field_value = $subscription->get_meta( '_billing_' . $key );
|
||||
}
|
||||
|
||||
echo '<p><strong>' . esc_html( $field['label'] ) . ':</strong> ' . wp_kses_post( make_clickable( esc_html( $field_value ) ) ) . '</p>';
|
||||
}
|
||||
|
||||
echo '<p' . ( ( '' != $subscription->get_payment_method() ) ? ' class="' . esc_attr( $subscription->get_payment_method() ) . '"' : '' ) . '><strong>' . esc_html__( 'Payment Method', 'woocommerce-subscriptions' ) . ':</strong>' . wp_kses_post( nl2br( $subscription->get_payment_method_to_display() ) );
|
||||
|
||||
// Display help tip
|
||||
if ( '' != $subscription->get_payment_method() && ! $subscription->is_manual() ) {
|
||||
echo wcs_help_tip( sprintf( _x( 'Gateway ID: [%s]', 'The gateway ID displayed on the Edit Subscriptions screen when editing payment method.', 'woocommerce-subscriptions' ), $subscription->get_payment_method() ) );
|
||||
}
|
||||
|
||||
echo '</p>';
|
||||
|
||||
echo '</div>';
|
||||
|
||||
// Display form
|
||||
echo '<div class="edit_address">';
|
||||
|
||||
foreach ( self::$billing_fields as $key => $field ) {
|
||||
if ( ! isset( $field['type'] ) ) {
|
||||
$field['type'] = 'text';
|
||||
}
|
||||
if ( ! isset( $field['id'] ) ) {
|
||||
$field['id'] = '_billing_' . $key;
|
||||
}
|
||||
switch ( $field['type'] ) {
|
||||
case 'select' :
|
||||
woocommerce_wp_select( $field );
|
||||
break;
|
||||
default :
|
||||
woocommerce_wp_text_input( $field );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
WCS_Change_Payment_Method_Admin::display_fields( $subscription );
|
||||
|
||||
echo '</div>';
|
||||
|
||||
do_action( 'woocommerce_admin_order_data_after_billing_address', $subscription );
|
||||
?>
|
||||
</div>
|
||||
<div class="order_data_column">
|
||||
|
||||
<h3>
|
||||
<?php esc_html_e( 'Shipping Details', 'woocommerce-subscriptions' ); ?>
|
||||
<a href="#" class="edit_address"><?php esc_html_e( 'Edit', 'woocommerce-subscriptions' ); ?></a>
|
||||
<a href="#" class="tips billing-same-as-shipping" data-tip="<?php esc_attr_e( 'Copy from billing', 'woocommerce-subscriptions' ); ?>" style="display:none;"><?php esc_html_e( 'Copy from billing', 'woocommerce-subscriptions' ); ?></a>
|
||||
<a href="#" class="tips load_customer_shipping" data-tip="<?php esc_attr_e( 'Load shipping address', 'woocommerce-subscriptions' ); ?>" style="display:none;"><?php esc_html_e( 'Load shipping address', 'woocommerce-subscriptions' ); ?></a>
|
||||
</h3>
|
||||
<?php
|
||||
// Display values
|
||||
echo '<div class="address">';
|
||||
|
||||
if ( $subscription->get_formatted_shipping_address() ) {
|
||||
echo '<p><strong>' . esc_html__( 'Address', 'woocommerce-subscriptions' ) . ':</strong>' . wp_kses( $subscription->get_formatted_shipping_address(), array( 'br' => array() ) ) . '</p>';
|
||||
} else {
|
||||
echo '<p class="none_set"><strong>' . esc_html__( 'Address', 'woocommerce-subscriptions' ) . ':</strong> ' . esc_html__( 'No shipping address set.', 'woocommerce-subscriptions' ) . '</p>';
|
||||
}
|
||||
|
||||
if ( ! empty( self::$shipping_fields ) ) {
|
||||
foreach ( self::$shipping_fields as $key => $field ) {
|
||||
if ( isset( $field['show'] ) && false === $field['show'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$function_name = 'get_shipping_' . $key;
|
||||
|
||||
if ( is_callable( array( $subscription, $function_name ) ) ) {
|
||||
$field_value = $subscription->$function_name( 'edit' );
|
||||
} else {
|
||||
$field_value = $subscription->get_meta( '_shipping_' . $key );
|
||||
}
|
||||
|
||||
echo '<p><strong>' . esc_html( $field['label'] ) . ':</strong> ' . wp_kses_post( make_clickable( esc_html( $field_value ) ) ) . '</p>';
|
||||
}
|
||||
}
|
||||
|
||||
if ( apply_filters( 'woocommerce_enable_order_notes_field', 'yes' == get_option( 'woocommerce_enable_order_comments', 'yes' ) ) && $post->post_excerpt ) {
|
||||
echo '<p><strong>' . esc_html__( 'Customer Provided Note', 'woocommerce-subscriptions' ) . ':</strong> ' . wp_kses_post( nl2br( $post->post_excerpt ) ) . '</p>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
|
||||
// Display form
|
||||
echo '<div class="edit_address">';
|
||||
|
||||
if ( ! empty( self::$shipping_fields ) ) {
|
||||
foreach ( self::$shipping_fields as $key => $field ) {
|
||||
if ( ! isset( $field['type'] ) ) {
|
||||
$field['type'] = 'text';
|
||||
}
|
||||
if ( ! isset( $field['id'] ) ) {
|
||||
$field['id'] = '_shipping_' . $key;
|
||||
}
|
||||
|
||||
switch ( $field['type'] ) {
|
||||
case 'select' :
|
||||
woocommerce_wp_select( $field );
|
||||
break;
|
||||
default :
|
||||
woocommerce_wp_text_input( $field );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( apply_filters( 'woocommerce_enable_order_notes_field', 'yes' == get_option( 'woocommerce_enable_order_comments', 'yes' ) ) ) {
|
||||
?>
|
||||
<p class="form-field form-field-wide"><label for="excerpt"><?php esc_html_e( 'Customer Provided Note', 'woocommerce-subscriptions' ) ?>:</label>
|
||||
<textarea rows="1" cols="40" name="excerpt" tabindex="6" id="excerpt" placeholder="<?php esc_attr_e( 'Customer\'s notes about the order', 'woocommerce-subscriptions' ); ?>"><?php echo wp_kses_post( $post->post_excerpt ); ?></textarea>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
|
||||
do_action( 'woocommerce_admin_order_data_after_shipping_address', $subscription );
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Save meta box data
|
||||
*
|
||||
* @param int $post_id
|
||||
* @param WP_Post $post
|
||||
*/
|
||||
public static function save( $post_id, $post = null ) {
|
||||
if ( 'shop_subscription' != $post->post_type || empty( $_POST['woocommerce_meta_nonce'] ) || ! wp_verify_nonce( $_POST['woocommerce_meta_nonce'], 'woocommerce_save_data' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::init_address_fields();
|
||||
|
||||
// Get subscription object.
|
||||
$subscription = wcs_get_subscription( $post_id );
|
||||
|
||||
// Ensure there is an order key.
|
||||
if ( ! $subscription->get_order_key() ) {
|
||||
$key = 'wc_' . apply_filters( 'woocommerce_generate_order_key', uniqid( 'order_' ) );
|
||||
wcs_set_objects_property( $subscription, 'order_key', $key );
|
||||
}
|
||||
|
||||
// Update meta
|
||||
update_post_meta( $post_id, '_customer_user', absint( $_POST['customer_user'] ) );
|
||||
|
||||
// Handle the billing fields.
|
||||
foreach ( self::$billing_fields as $key => $field ) {
|
||||
$field['id'] = isset( $field['id'] ) ? $field['id'] : "_billing_{$key}";
|
||||
if ( ! isset( $_POST[ $field['id'] ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
wcs_set_objects_property( $subscription, $field['id'], wc_clean( $_POST[ $field['id'] ] ) );
|
||||
}
|
||||
|
||||
// Handle the shipping fields.
|
||||
foreach ( self::$shipping_fields as $key => $field ) {
|
||||
$field['id'] = isset( $field['id'] ) ? $field['id'] : "_shipping_{$key}";
|
||||
if ( ! isset( $_POST[ $field['id'] ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
wcs_set_objects_property( $subscription, $field['id'], wc_clean( $_POST[ $field['id'] ] ) );
|
||||
}
|
||||
|
||||
try {
|
||||
WCS_Change_Payment_Method_Admin::save_meta( $subscription );
|
||||
|
||||
if ( 'cancelled' == $_POST['order_status'] ) {
|
||||
$subscription->cancel_order();
|
||||
} else {
|
||||
$subscription->update_status( $_POST['order_status'], '', true );
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
// translators: placeholder is error message from the payment gateway or subscriptions when updating the status
|
||||
wcs_add_admin_notice( sprintf( __( 'Error updating some information: %s', 'woocommerce-subscriptions' ), $e->getMessage() ), 'error' );
|
||||
}
|
||||
|
||||
do_action( 'woocommerce_process_shop_subscription_meta', $post_id, $post );
|
||||
}
|
||||
}
|
||||
|
|
@ -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( '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,45 +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 = 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 echo esc_html( ucwords( $order->get_status() ) ); ?>
|
||||
</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>
|
||||
|
|
@ -27,35 +27,38 @@ class WCS_Report_Cache_Manager {
|
|||
*/
|
||||
private $update_events_and_classes = array(
|
||||
'woocommerce_subscriptions_reports_schedule_cache_updates' => array( // a custom hook that can be called to schedule a full cache update, used by WC_Subscriptions_Upgrader
|
||||
0 => 'WC_Report_Subscription_Events_By_Date',
|
||||
1 => 'WC_Report_Upcoming_Recurring_Revenue',
|
||||
3 => 'WC_Report_Subscription_By_Product',
|
||||
4 => 'WC_Report_Subscription_By_Customer',
|
||||
0 => 'WCS_Report_Dashboard',
|
||||
1 => 'WCS_Report_Subscription_Events_By_Date',
|
||||
2 => 'WCS_Report_Upcoming_Recurring_Revenue',
|
||||
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
|
||||
0 => 'WC_Report_Subscription_Events_By_Date',
|
||||
4 => 'WC_Report_Subscription_By_Customer',
|
||||
'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',
|
||||
),
|
||||
'woocommerce_subscriptions_switch_completed' => array(
|
||||
0 => 'WC_Report_Subscription_Events_By_Date',
|
||||
1 => 'WCS_Report_Subscription_Events_By_Date',
|
||||
),
|
||||
'woocommerce_subscription_status_changed' => array(
|
||||
0 => 'WC_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
|
||||
4 => 'WC_Report_Subscription_By_Customer',
|
||||
'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(
|
||||
1 => 'WC_Report_Upcoming_Recurring_Revenue',
|
||||
'woocommerce_subscription_status_active' => array(
|
||||
2 => 'WCS_Report_Upcoming_Recurring_Revenue',
|
||||
),
|
||||
'woocommerce_new_order_item' => array(
|
||||
3 => 'WC_Report_Subscription_By_Product',
|
||||
'woocommerce_new_order_item' => array(
|
||||
4 => 'WCS_Report_Subscription_By_Product',
|
||||
),
|
||||
'woocommerce_update_order_item' => array(
|
||||
3 => 'WC_Report_Subscription_By_Product',
|
||||
'woocommerce_update_order_item' => array(
|
||||
4 => 'WCS_Report_Subscription_By_Product',
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Record of all the report calsses to need to have the cache updated during this request. Prevents duplicate updates in the same request for different events.
|
||||
* Record of all the report classes to need to have the cache updated during this request. Prevents duplicate updates in the same request for different events.
|
||||
*/
|
||||
private $reports_to_update = array();
|
||||
|
||||
|
|
@ -72,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',
|
||||
|
|
@ -103,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 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -112,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() ] ) ) {
|
||||
|
|
@ -136,45 +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() ) {
|
||||
|
||||
$four_am_site_time = new DateTime( '4 am', wcs_get_sites_timezone() );
|
||||
$cache_update_timestamp = $this->get_large_site_cache_update_timestamp();
|
||||
|
||||
// Convert to a UTC timestamp for scheduling
|
||||
$cache_update_timestamp = $four_am_site_time->format( 'U' );
|
||||
// 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 ) {
|
||||
|
||||
// PHP doesn't support a "next 4am" time format equivalent, so we need to manually handle getting 4am from earlier today (which will always happen when this is run after 4am and before midnight in the site's timezone)
|
||||
if ( $cache_update_timestamp <= gmdate( 'U' ) ) {
|
||||
$cache_update_timestamp += DAY_IN_SECONDS;
|
||||
$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 );
|
||||
}
|
||||
}
|
||||
} 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 ) {
|
||||
|
||||
$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 );
|
||||
}
|
||||
|
||||
// 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 );
|
||||
|
||||
if ( false === wp_next_scheduled( $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
|
||||
wp_schedule_single_event( $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
|
||||
|
||||
// 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 );
|
||||
|
||||
if ( false !== ( $next_scheduled = wp_next_scheduled( $this->cron_hook, $cron_args ) ) ) {
|
||||
wp_unschedule_event( $next_scheduled, $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
|
||||
wp_schedule_single_event( gmdate( 'U' ) + MINUTE_IN_SECONDS * ( $index + 1 ) * 5, $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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -183,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 ) {
|
||||
/**
|
||||
|
|
@ -215,23 +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' );
|
||||
|
||||
$report_name = strtolower( str_replace( '_', '-', str_replace( 'WC_Report_', '', $report_class ) ) );
|
||||
$report_path = WCS_Admin_Reports::initialize_reports_path( '', $report_name, $report_class );
|
||||
$within_ci_environment = getenv( 'CI' );
|
||||
$wc_core_dir_from_env = getenv( 'WC_CORE_DIR' );
|
||||
|
||||
require_once( $report_path );
|
||||
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 ) {
|
||||
|
|
@ -254,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 );
|
||||
|
|
@ -337,14 +323,16 @@ class WCS_Report_Cache_Manager {
|
|||
$new_data = array(
|
||||
'wcs_report_cache_enabled' => array(
|
||||
'name' => _x( 'Report Cache Enabled', 'Whether the Report Cache has been enabled', 'woocommerce-subscriptions' ),
|
||||
'label' => 'Report Cache Enabled',
|
||||
'note' => $cache_enabled ? __( 'Yes', 'woocommerce-subscriptions' ) : __( 'No', 'woocommerce-subscriptions' ),
|
||||
'success' => $cache_enabled,
|
||||
),
|
||||
'wcs_cache_update_failures' => array(
|
||||
'name' => __( 'Cache Update Failures', 'woocommerce-subscriptions' ),
|
||||
'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,
|
||||
),
|
||||
);
|
||||
|
||||
|
|
@ -352,6 +340,41 @@ class WCS_Report_Cache_Manager {
|
|||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
return new WCS_Report_Cache_Manager();
|
||||
/**
|
||||
* Get the scheduled update cache time for large sites.
|
||||
*
|
||||
* @return int The timestamp of the next occurring 4 am in the site's timezone converted to UTC.
|
||||
*/
|
||||
protected function get_large_site_cache_update_timestamp() {
|
||||
// Get the timestamp for 4 am in the site's timezone converted to the UTC equivalent.
|
||||
$cache_update_timestamp = wc_string_to_timestamp( '4 am', current_time( 'timestamp' ) ) - wc_timezone_offset();
|
||||
|
||||
// PHP doesn't support a "next 4am" time format equivalent, so we need to manually handle getting 4am from earlier today (which will always happen when this is run after 4am and before midnight in the site's timezone)
|
||||
if ( $cache_update_timestamp <= gmdate( 'U' ) ) {
|
||||
$cache_update_timestamp += DAY_IN_SECONDS;
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -29,62 +43,92 @@ class WCS_Report_Dashboard {
|
|||
add_action( 'admin_enqueue_scripts', __CLASS__ . '::dashboard_scripts' );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() ) {
|
||||
$default_args = array(
|
||||
'no_cache' => false,
|
||||
);
|
||||
|
||||
$args = apply_filters( 'wcs_reports_subscription_dashboard_args', $args );
|
||||
$args = wp_parse_args( $args, $default_args );
|
||||
|
||||
self::init_cache();
|
||||
|
||||
// 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->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'] );
|
||||
|
||||
if ( self::$should_update_cache ) {
|
||||
set_transient( strtolower( __CLASS__ ), self::$cached_report_results, HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
return $report_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the subscription specific details to the bottom of the dashboard widget
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
public static function add_stats_to_dashboard() {
|
||||
global $wpdb;
|
||||
|
||||
$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 H:i:s', current_time( 'timestamp' ) )
|
||||
);
|
||||
|
||||
$signup_count = $wpdb->get_var( apply_filters( 'woocommerce_subscription_dashboard_status_widget_signup_query', $query ) );
|
||||
|
||||
$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 H:i:s', current_time( 'timestamp' ) )
|
||||
);
|
||||
|
||||
$renewal_count = $wpdb->get_var( apply_filters( 'woocommerce_subscription_dashboard_status_widget_renewal_query', $query ) );
|
||||
$report_data = self::get_data();
|
||||
|
||||
?>
|
||||
<li class="signup-count">
|
||||
<a href="<?php echo esc_html( admin_url( 'admin.php?page=wc-reports&tab=subscriptions&report=subscription_events_by_date' ) ); ?>">
|
||||
<?php printf( wp_kses_post( _n( '<strong>%s signup</strong> subscription signups this month', '<strong>%s signups</strong> subscription signups this month', $signup_count, 'woocommerce-subscriptions' ) ), esc_html( $signup_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 signup%3$s subscription signups this month', '%2$s%1$s signups%3$s subscription signups this month', $report_data->signup_count, 'woocommerce-subscriptions' ), $report_data->signup_count, '<strong>', '</strong>' ) );
|
||||
?>
|
||||
</a>
|
||||
</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
|
||||
// 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">
|
||||
<a href="<?php echo esc_html( admin_url( 'admin.php?page=wc-reports&tab=subscriptions&report=subscription_events_by_date' ) ); ?>">
|
||||
<?php printf( wp_kses_post( _n( '<strong>%s renewal</strong> subscription renewals this month', '<strong>%s renewals</strong> subscription renewals this month', $renewal_count, 'woocommerce-subscriptions' ) ), esc_html( $renewal_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 renewal%3$s subscription renewals this month', '%2$s%1$s renewals%3$s subscription renewals this month', $report_data->renewal_count, 'woocommerce-subscriptions' ), $report_data->renewal_count, '<strong>', '</strong>' ) );
|
||||
?>
|
||||
</a>
|
||||
</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
|
||||
// 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>' ) );
|
||||
?>
|
||||
</a>
|
||||
</li>
|
||||
<?php
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -93,8 +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;
|
||||
}
|
||||
}
|
||||
|
||||
return new WCS_Report_Dashboard();
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@
|
|||
* 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
|
||||
* @package WooCommerce Subscriptions
|
||||
* @subpackage WC_Subscriptions_Admin_Reports
|
||||
* @category Class
|
||||
* @author Prospress
|
||||
* @since 2.1
|
||||
*/
|
||||
class WC_Report_Retention_Rate extends WC_Admin_Report {
|
||||
class WCS_Report_Retention_Rate extends WC_Admin_Report {
|
||||
|
||||
public $chart_colours = array();
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ class WC_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 WC_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 WC_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 WC_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 WC_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 WC_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 WC_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 WC_Report_Retention_Rate extends WC_Admin_Report {
|
|||
}
|
||||
);
|
||||
|
||||
jQuery('.chart-placeholder').resize();
|
||||
jQuery('.chart-placeholder').trigger( 'resize' );
|
||||
}
|
||||
|
||||
drawGraph();
|
||||
|
|
|
|||
|
|
@ -4,13 +4,19 @@
|
|||
*
|
||||
* 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
|
||||
*/
|
||||
class WC_Report_Subscription_By_Customer extends WP_List_Table {
|
||||
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 WC_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 WC_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 ) . '<br />';
|
||||
echo ' <strong>' . esc_html__( 'Active Subscriptions', 'woocommerce-subscriptions' ) . '</strong> : ' . esc_html( $this->totals->active_subscriptions ) . '<br />';
|
||||
echo ' <strong>' . esc_html__( 'Total Subscriptions', 'woocommerce-subscriptions' ) . '</strong> : ' . esc_html( $this->totals->total_subscriptions ) . '<br />';
|
||||
echo ' <strong>' . esc_html__( 'Total Subscription Orders', 'woocommerce-subscriptions' ) . '</strong> : ' . esc_html( $this->totals->initial_order_count + $this->totals->renewal_switch_count ) . '<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 ) ) . '</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 WC_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' :
|
||||
return sprintf( '<a href="%s%d">%d</a>', admin_url( 'edit.php?post_type=shop_order&_customer_user=' ), $user->customer_id, $user->initial_order_count + $user->renewal_switch_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 WC_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 WC_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 WC_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 WC_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 );
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,23 +4,32 @@
|
|||
*
|
||||
* 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
|
||||
*/
|
||||
class WC_Report_Subscription_By_Product extends WP_List_Table {
|
||||
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,17 +63,22 @@ class WC_Report_Subscription_By_Product extends WP_List_Table {
|
|||
|
||||
switch ( $column_name ) {
|
||||
|
||||
case 'product_name' :
|
||||
return edit_post_link( $report_item->product_name, null, null, $report_item->product_id );
|
||||
|
||||
case 'subscription_count' :
|
||||
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 ) {
|
||||
edit_post_link( $report_item->product_name, ' - ', null, $report_item->parent_product_id );
|
||||
} else {
|
||||
edit_post_link( $report_item->product_name, null, null, $report_item->product_id );
|
||||
}
|
||||
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;
|
||||
|
||||
|
|
@ -82,9 +96,12 @@ class WC_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;
|
||||
|
|
@ -100,20 +117,184 @@ class WC_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,
|
||||
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( $subscription_product_totals[ $product_id ] ) ? $subscription_product_totals[ $product_id ]->product_total : 0;
|
||||
}
|
||||
|
||||
return $ordered_report_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output product breakdown chart.
|
||||
*/
|
||||
public function product_breakdown_chart() {
|
||||
|
||||
$chart_colors = array( '#33a02c', '#1f78b4', '#6a3d9a', '#e31a1c', '#ff7f00', '#b15928', '#a6cee3', '#b2df8a', '#fb9a99', '#ffff99', '#fdbf6f', '#cab2d6' );
|
||||
|
||||
// We only will display the first 12 plans of parent products in the chart
|
||||
$products = array_slice( wp_list_filter( $this->items, array( 'parent_product_id' => 0 ) ), 0, 12 );
|
||||
|
||||
// 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
|
||||
$variations[] = $product;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<div class="chart-container" style="float: left; padding-top: 50px; min-width: 0px;">
|
||||
<div class="data-container" style="display: inline-block; margin-left: 30px; border: 1px solid #e5e5e5; background-color: #FFF; padding: 20px;">
|
||||
<div class="chart-placeholder product_breakdown_chart pie-chart" style="height: 200px; width: 200px; float: left;"></div>
|
||||
<div class="chart-placeholder variation_breakdown_chart pie-chart" style="height: 200px; width: 200px;"></div>
|
||||
<div class="legend-container" style="margin-left: 10px; float: left;"></div>
|
||||
<div style="clear: both;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
jQuery(function(){
|
||||
jQuery.plot(
|
||||
jQuery('.chart-placeholder.variation_breakdown_chart'),
|
||||
[
|
||||
<?php
|
||||
$colorindex = -1;
|
||||
$last_parent_id = -1;
|
||||
foreach ( $variations as $product ) {
|
||||
if ( '0' === $product->parent_product_id || $last_parent_id !== $product->parent_product_id ) {
|
||||
++$colorindex;
|
||||
$last_parent_id = $product->parent_product_id;
|
||||
}
|
||||
?>
|
||||
{
|
||||
label: '<?php echo esc_js( $product->product_name ); ?>',
|
||||
data: '<?php echo esc_js( $product->subscription_count ); ?>',
|
||||
color: '<?php echo esc_js( $chart_colors[ $colorindex ] ); ?>',
|
||||
},
|
||||
<?php
|
||||
|
||||
}
|
||||
?>
|
||||
],
|
||||
{
|
||||
grid: {
|
||||
hoverable: true
|
||||
},
|
||||
series: {
|
||||
pie: {
|
||||
show: true,
|
||||
radius: 1,
|
||||
innerRadius: 0.7,
|
||||
label: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
prepend_label: true,
|
||||
enable_tooltip: true,
|
||||
append_tooltip: "<?php echo ' ' . esc_js( __( 'subscriptions', 'woocommerce-subscriptions' ) ); ?>",
|
||||
},
|
||||
legend: {
|
||||
show: false,
|
||||
}
|
||||
}
|
||||
);
|
||||
jQuery('.chart-placeholder.variation_breakdown_chart').trigger( 'resize' );
|
||||
jQuery.plot(
|
||||
jQuery('.chart-placeholder.product_breakdown_chart'),
|
||||
[
|
||||
<?php
|
||||
$i = 0;
|
||||
foreach ( $products as $product ) {
|
||||
?>
|
||||
{
|
||||
label: '<?php echo esc_js( $product->product_name ); ?>',
|
||||
data: '<?php echo esc_js( $product->subscription_count ); ?>',
|
||||
color: '<?php echo esc_js( $chart_colors[ $i ] ); ?>'
|
||||
},
|
||||
<?php
|
||||
++$i;
|
||||
}
|
||||
?>
|
||||
],
|
||||
{
|
||||
grid: {
|
||||
hoverable: true
|
||||
},
|
||||
series: {
|
||||
pie: {
|
||||
show: true,
|
||||
radius: 0.7,
|
||||
innerRadius: 0.3,
|
||||
label: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
prepend_label: true,
|
||||
enable_tooltip: true,
|
||||
append_tooltip: "<?php echo ' ' . esc_js( __( 'subscriptions', 'woocommerce-subscriptions' ) ); ?>",
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
position: "ne",
|
||||
container: jQuery('.legend-container'),
|
||||
}
|
||||
}
|
||||
);
|
||||
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,
|
||||
|
|
@ -136,125 +317,224 @@ class WC_Report_Subscription_By_Product extends WP_List_Table {
|
|||
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'
|
||||
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'
|
||||
AND subscriptions.post_type = 'shop_subscription'
|
||||
AND subscriptions.post_status NOT IN( 'wc-pending', 'trash' )
|
||||
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" );
|
||||
|
||||
$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 );
|
||||
ORDER BY COUNT(subscription_line_items.subscription_id) DESC";
|
||||
}
|
||||
|
||||
$report_data = $cached_results[ $query_hash ];
|
||||
/**
|
||||
* 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 );
|
||||
|
||||
// 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
|
||||
// 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->posts} AS wcorders
|
||||
INNER JOIN {$wpdb->prefix}wc_orders 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'] ) . "' )
|
||||
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'
|
||||
WHERE ( wcoimeta.meta_key = '_product_id' OR wcoimeta.meta_key = '_variation_id' )
|
||||
AND wcoimeta2.meta_key = '_line_total'
|
||||
GROUP BY product_id" );
|
||||
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 );
|
||||
|
||||
if ( $args['no_cache'] || false === $cached_results || ! isset( $cached_results[ $query_hash ] ) ) {
|
||||
// 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' );
|
||||
$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 );
|
||||
$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 );
|
||||
}
|
||||
|
||||
// Add the product total to each item
|
||||
foreach ( array_keys( $report_data ) as $product_id ) {
|
||||
$report_data[ $product_id ]->product_total = isset( $cached_results[ $query_hash ][ $product_id ] ) ? $cached_results[ $query_hash ][ $product_id ]->product_total : 0;
|
||||
}
|
||||
|
||||
return $report_data;
|
||||
return self::$cached_report_results[ $query_hash ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Output product breakdown chart.
|
||||
* Initialize cache for report results.
|
||||
*/
|
||||
public function product_breakdown_chart() {
|
||||
private static function init_cache() {
|
||||
self::$cached_report_results = get_transient( strtolower( __CLASS__ ) );
|
||||
|
||||
$chart_colors = array( '#33a02c', '#1f78b4', '#6a3d9a', '#e31a1c', '#ff7f00', '#b15928', '#a6cee3', '#b2df8a', '#fb9a99', '#ffff99', '#fdbf6f', '#cab2d6' );
|
||||
// Set a default value for cached results for PHP 8.2+ compatibility.
|
||||
if ( empty( self::$cached_report_results ) ) {
|
||||
self::$cached_report_results = array();
|
||||
}
|
||||
}
|
||||
|
||||
//We only will display the first 12 plans in the chart
|
||||
$products = array_slice( $this->items, 0, 12 );
|
||||
|
||||
?>
|
||||
<div class="chart-container" style="float: left; padding-top: 50px; min-width: 0px;">
|
||||
<div class="data-container" style="display: inline-block; margin-left: 30px; border: 1px solid #e5e5e5; background-color: #FFF; padding: 20px;">
|
||||
<div class="chart-placeholder product_breakdown_chart pie-chart" style="height:200px; width: 200px; float: left;"></div>
|
||||
<div class="legend-container" style="margin-left: 10px; float: left;"></div>
|
||||
<div style="clear:both;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
jQuery(function(){
|
||||
jQuery.plot(
|
||||
jQuery('.chart-placeholder.product_breakdown_chart'),
|
||||
[
|
||||
<?php
|
||||
$i = 0;
|
||||
foreach ( $products as $product ) {
|
||||
?>
|
||||
{
|
||||
label: '<?php echo esc_js( $product->product_name ); ?>',
|
||||
data: '<?php echo esc_js( $product->subscription_count ); ?>',
|
||||
color: '<?php echo esc_js( $chart_colors[ $i ] ); ?>'
|
||||
},
|
||||
<?php
|
||||
$i++;
|
||||
}
|
||||
?>
|
||||
],
|
||||
{
|
||||
grid: {
|
||||
hoverable: true
|
||||
},
|
||||
series: {
|
||||
pie: {
|
||||
show: true,
|
||||
radius: 1,
|
||||
innerRadius: 0.6,
|
||||
label: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
enable_tooltip: true,
|
||||
append_tooltip: "<?php echo ' ' . esc_js( __( 'subscriptions', 'woocommerce-subscriptions' ) ); ?>",
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
container: jQuery('.legend-container'),
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
jQuery('.chart-placeholder.product_breakdown_chart').resize();
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
/**
|
||||
* 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 );
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@
|
|||
*
|
||||
* 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
|
||||
*/
|
||||
class WC_Report_Subscription_Payment_Retry extends WC_Admin_Report {
|
||||
class WCS_Report_Subscription_Payment_Retry extends WC_Admin_Report {
|
||||
|
||||
private $chart_colours = array();
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ class WC_Report_Subscription_Payment_Retry extends WC_Admin_Report {
|
|||
|
||||
/**
|
||||
* Get report data
|
||||
* @return array
|
||||
* @return stdClass
|
||||
*/
|
||||
public function get_report_data() {
|
||||
|
||||
|
|
@ -35,52 +35,39 @@ class WC_Report_Subscription_Payment_Retry extends WC_Admin_Report {
|
|||
private function query_report_data() {
|
||||
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_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_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_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', $this->start_date ) );
|
||||
$query_end_date = get_gmt_from_date( date( 'Y-m-d', wcs_strtotime_dark_knight( '+1 day', $this->end_date ) ) );
|
||||
|
||||
// Get the sum of order totals for completed retires (i.e. retries which eventually succeeded in processing the failed payment)
|
||||
$renewal_query = $wpdb->prepare(
|
||||
"SELECT COUNT(DISTINCT posts.ID) as count, posts.post_date as post_date, SUM(meta_order_total.meta_value) as renewal_totals
|
||||
FROM {$wpdb->prefix}posts AS orders
|
||||
INNER JOIN {$wpdb->prefix}posts AS posts ON ( orders.ID = posts.post_parent )
|
||||
LEFT JOIN {$wpdb->prefix}postmeta AS meta_order_total ON ( orders.ID = meta_order_total.post_id AND meta_order_total.meta_key = '_order_total' )
|
||||
WHERE posts.post_type = 'payment_retry'
|
||||
AND posts.post_status = 'complete'
|
||||
AND posts.post_modified_gmt >= %s
|
||||
AND posts.post_modified_gmt < %s
|
||||
GROUP BY {$this->group_by_query}
|
||||
ORDER BY post_date 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 posts.ID) as count, posts.post_status as status, posts.post_date as post_date
|
||||
FROM {$wpdb->prefix}posts AS posts
|
||||
WHERE posts.post_type = 'payment_retry'
|
||||
AND posts.post_status IN ( 'complete','failed','pending' )
|
||||
AND posts.post_modified_gmt >= %s
|
||||
AND posts.post_modified_gmt < %s
|
||||
GROUP BY {$this->group_by_query}, posts.post_status
|
||||
ORDER BY posts.post_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 = absint( array_sum( wp_list_pluck( $this->report_data->renewal_data, 'renewal_totals' ) ) );
|
||||
$this->report_data->renewal_total_amount = array_sum( wp_list_pluck( $this->report_data->renewal_data, 'renewal_totals' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -92,6 +79,7 @@ class WC_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'],
|
||||
|
|
@ -99,12 +87,14 @@ class WC_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'],
|
||||
|
|
@ -112,6 +102,7 @@ class WC_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'],
|
||||
|
|
@ -119,6 +110,7 @@ class WC_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'],
|
||||
|
|
@ -134,10 +126,10 @@ class WC_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(
|
||||
|
|
@ -182,19 +174,19 @@ class WC_Report_Subscription_Payment_Retry extends WC_Admin_Report {
|
|||
/**
|
||||
* Get the main chart
|
||||
*
|
||||
* @return string
|
||||
* @return void
|
||||
*/
|
||||
public function get_main_chart() {
|
||||
global $wp_locale;
|
||||
|
||||
// Prepare data for report
|
||||
$retry_count = $this->prepare_chart_data( $this->report_data->retry_data, 'post_date', 'count', $this->chart_interval, $this->start_date, $this->chart_groupby );
|
||||
$retry_success_count = $this->prepare_chart_data( wp_list_filter( $this->report_data->retry_data, array( 'status' => 'complete' ) ), 'post_date', 'count', $this->chart_interval, $this->start_date, $this->chart_groupby );
|
||||
$retry_failure_count = $this->prepare_chart_data( wp_list_filter( $this->report_data->retry_data, array( 'status' => 'failed' ) ), 'post_date', 'count', $this->chart_interval, $this->start_date, $this->chart_groupby );
|
||||
$retry_pending_count = $this->prepare_chart_data( wp_list_filter( $this->report_data->retry_data, array( 'status' => 'pending' ) ), 'post_date', 'count', $this->chart_interval, $this->start_date, $this->chart_groupby );
|
||||
$retry_count = $this->prepare_chart_data( $this->report_data->retry_data, 'retry_date', 'count', $this->chart_interval, $this->start_date, $this->chart_groupby );
|
||||
$retry_success_count = $this->prepare_chart_data( wp_list_filter( $this->report_data->retry_data, array( 'status' => 'complete' ) ), 'retry_date', 'count', $this->chart_interval, $this->start_date, $this->chart_groupby );
|
||||
$retry_failure_count = $this->prepare_chart_data( wp_list_filter( $this->report_data->retry_data, array( 'status' => 'failed' ) ), 'retry_date', 'count', $this->chart_interval, $this->start_date, $this->chart_groupby );
|
||||
$retry_pending_count = $this->prepare_chart_data( wp_list_filter( $this->report_data->retry_data, array( 'status' => 'pending' ) ), 'retry_date', 'count', $this->chart_interval, $this->start_date, $this->chart_groupby );
|
||||
|
||||
$renewal_count = $this->prepare_chart_data( $this->report_data->renewal_data, 'post_date', 'count', $this->chart_interval, $this->start_date, $this->chart_groupby );
|
||||
$renewal_amount = $this->prepare_chart_data( $this->report_data->renewal_data, 'post_date', 'renewal_totals', $this->chart_interval, $this->start_date, $this->chart_groupby );
|
||||
$renewal_count = $this->prepare_chart_data( $this->report_data->renewal_data, 'retry_date', 'count', $this->chart_interval, $this->start_date, $this->chart_groupby );
|
||||
$renewal_amount = $this->prepare_chart_data( $this->report_data->renewal_data, 'retry_date', 'renewal_totals', $this->chart_interval, $this->start_date, $this->chart_groupby );
|
||||
|
||||
// Encode in json format
|
||||
$chart_data = array(
|
||||
|
|
@ -217,7 +209,7 @@ class WC_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 = [
|
||||
|
|
@ -356,12 +348,12 @@ class WC_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') );
|
||||
},
|
||||
|
|
@ -386,4 +378,86 @@ class WC_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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,18 +5,20 @@
|
|||
* 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
|
||||
* @package WooCommerce Subscriptions
|
||||
* @subpackage WC_Subscriptions_Admin_Reports
|
||||
* @category Class
|
||||
* @author Prospress
|
||||
* @since 2.1
|
||||
*/
|
||||
class WC_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report {
|
||||
class WCS_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report {
|
||||
|
||||
public $chart_colours = array();
|
||||
|
||||
public $order_ids_recurring_totals = null;
|
||||
|
||||
public $average_sales = 0;
|
||||
|
||||
/**
|
||||
* Get the legend for the main chart sidebar
|
||||
* @return array
|
||||
|
|
@ -30,11 +32,6 @@ class WC_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report {
|
|||
|
||||
foreach ( $this->order_ids_recurring_totals as $r ) {
|
||||
|
||||
if ( strtotime( $r->scheduled_date ) >= $this->start_date ) {
|
||||
$total_renewal_revenue += $r->recurring_total;
|
||||
$total_renewal_count += $r->total_renewals;
|
||||
}
|
||||
|
||||
$subscription_ids = explode( ',', $r->subscription_ids );
|
||||
$billing_intervals = explode( ',', $r->billing_intervals );
|
||||
$billing_periods = explode( ',', $r->billing_periods );
|
||||
|
|
@ -47,17 +44,21 @@ class WC_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 {
|
||||
|
||||
$next_payment_timestamp = wcs_add_time( $billing_intervals[ $key ], $billing_periods[ $key ], $next_payment_timestamp );
|
||||
|
||||
// If there are more renewals add them to the existing object or create a new one
|
||||
if ( $next_payment_timestamp < $this->end_date && isset( $scheduled_ends[ $key ] ) && ( 0 == $scheduled_ends[ $key ] || $next_payment_timestamp < strtotime( $scheduled_ends[ $key ] ) ) ) {
|
||||
if ( $next_payment_timestamp <= $this->end_date && isset( $scheduled_ends[ $key ] ) && ( 0 == $scheduled_ends[ $key ] || $next_payment_timestamp < strtotime( $scheduled_ends[ $key ] ) ) ) {
|
||||
$update_key = date( 'Y-m-d', $next_payment_timestamp );
|
||||
|
||||
if ( $next_payment_timestamp >= $this->start_date ) {
|
||||
|
|
@ -70,29 +71,43 @@ class WC_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report {
|
|||
}
|
||||
$this->order_ids_recurring_totals[ $update_key ]->total_renewals += 1;
|
||||
$this->order_ids_recurring_totals[ $update_key ]->recurring_total += $subscription_totals[ $key ];
|
||||
$total_renewal_revenue += $subscription_totals[ $key ];;
|
||||
$total_renewal_count += 1;
|
||||
}
|
||||
}
|
||||
} 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 ] ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Sum up the total revenue and total renewal count separately to avoid adding up multiple times.
|
||||
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_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'],
|
||||
);
|
||||
|
|
@ -102,61 +117,117 @@ class WC_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_FORMAT(ms.meta_value, '%s') 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 BETWEEN '%s' AND '%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",
|
||||
'%Y-%m-%d',
|
||||
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 );
|
||||
}
|
||||
|
||||
|
|
@ -210,7 +281,7 @@ class WC_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report {
|
|||
|
||||
/**
|
||||
* Get the main chart
|
||||
* @return string
|
||||
* @return void
|
||||
*/
|
||||
public function get_main_chart() {
|
||||
global $wp_locale;
|
||||
|
|
@ -233,7 +304,7 @@ class WC_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 = [
|
||||
{
|
||||
|
|
@ -324,12 +395,12 @@ class WC_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') );
|
||||
},
|
||||
|
|
@ -349,7 +420,7 @@ class WC_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'] ) ) );
|
||||
|
||||
|
|
@ -364,28 +435,28 @@ class WC_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report {
|
|||
}
|
||||
|
||||
// 3 months max for day view
|
||||
if ( $interval > 3 ) {
|
||||
if ( $interval >= 3 ) {
|
||||
$this->chart_groupby = 'month';
|
||||
} else {
|
||||
$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';
|
||||
|
|
@ -394,13 +465,13 @@ class WC_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)';
|
||||
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 ) {
|
||||
|
|
@ -423,4 +494,15 @@ class WC_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,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;
|
||||
}
|
||||
}
|
||||
|
|
@ -121,7 +121,7 @@ class WC_API_Subscriptions extends WC_API_Orders {
|
|||
public function get_subscriptions( $fields = null, $filter = array(), $status = null, $page = 1 ) {
|
||||
// check user permissions
|
||||
if ( ! current_user_can( 'read_private_shop_orders' ) ) {
|
||||
return new WP_Error( 'wcs_api_user_cannot_read_susbcription_count', __( 'You do not have permission to read the subscriptions count', 'woocommerce-subscriptions' ), array( 'status' => 401 ) );
|
||||
return new WP_Error( 'wcs_api_user_cannot_read_subscription_count', __( 'You do not have permission to read the subscriptions count', 'woocommerce-subscriptions' ), array( 'status' => 401 ) );
|
||||
}
|
||||
|
||||
$status = $this->format_statuses( $status );
|
||||
|
|
@ -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() ) );
|
||||
|
|
@ -397,10 +404,8 @@ class WC_API_Subscriptions extends WC_API_Orders {
|
|||
$dates_to_update = array();
|
||||
|
||||
foreach ( array( 'start', 'trial_end', 'end', 'next_payment' ) as $date_type ) {
|
||||
|
||||
if ( isset( $data[ $date_type . '_date' ] ) ) {
|
||||
$date_type_key = ( 'start' === $date_type ) ? 'date_created' : $date_type;
|
||||
$dates_to_update[ $date_type_key ] = $data[ $date_type . '_date' ];
|
||||
$dates_to_update[ $date_type ] = $data[ $date_type . '_date' ];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -461,7 +466,7 @@ class WC_API_Subscriptions extends WC_API_Orders {
|
|||
$subscription_data['billing_schedule'] = array(
|
||||
'period' => $subscription->get_billing_period(),
|
||||
'interval' => $subscription->get_billing_interval(),
|
||||
'start_at' => $this->get_formatted_datetime( $subscription, 'date_created' ),
|
||||
'start_at' => $this->get_formatted_datetime( $subscription, 'start' ),
|
||||
'trial_end_at' => $this->get_formatted_datetime( $subscription, 'trial_end' ),
|
||||
'next_payment_at' => $this->get_formatted_datetime( $subscription, 'next_payment' ),
|
||||
'end_at' => $this->get_formatted_datetime( $subscription, 'end' ),
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
* REST API Subscriptions controller class.
|
||||
*
|
||||
* @package WooCommerce_Subscriptions/API
|
||||
* @extends WC_REST_Orders_Controller
|
||||
*/
|
||||
class WC_REST_Subscriptions_Controller extends WC_REST_Orders_Controller {
|
||||
|
||||
|
|
@ -61,10 +60,11 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_Controller {
|
|||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
) );
|
||||
|
||||
register_rest_route( $this->namespace, '/' . $this->rest_base . '/statuses', array(
|
||||
register_rest_route( $this->namespace, '/' . $this->rest_base . '/statuses', array( // nosemgrep: audit.php.wp.security.rest-route.permission-callback.return-true -- /subscriptions/statuses is a public endpoint and doesn't need any permission checks.
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_statuses' ),
|
||||
'permission_callback' => '__return_true',
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
) );
|
||||
|
|
@ -75,7 +75,7 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_Controller {
|
|||
*
|
||||
* @since 2.1
|
||||
* @param WP_REST_Response $response
|
||||
* @param WP_POST $post
|
||||
* @param WP_Post $post
|
||||
* @param WP_REST_Request $request
|
||||
*/
|
||||
public function filter_get_subscription_response( $response, $post, $request ) {
|
||||
|
|
@ -85,7 +85,7 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_Controller {
|
|||
|
||||
$response->data['billing_period'] = $subscription->get_billing_period();
|
||||
$response->data['billing_interval'] = $subscription->get_billing_interval();
|
||||
$response->data['start_date'] = wc_rest_prepare_date_response( $subscription->get_date( 'date_created' ) );
|
||||
$response->data['start_date'] = wc_rest_prepare_date_response( $subscription->get_date( 'start_date' ) );
|
||||
$response->data['trial_end_date'] = wc_rest_prepare_date_response( $subscription->get_date( 'trial_end' ) );
|
||||
$response->data['next_payment_date'] = wc_rest_prepare_date_response( $subscription->get_date( 'next_payment' ) );
|
||||
$response->data['end_date'] = wc_rest_prepare_date_response( $subscription->get_date( 'end_date' ) );
|
||||
|
|
@ -122,7 +122,7 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_Controller {
|
|||
*
|
||||
* @since 2.1
|
||||
* @param WP_REST_Request $request
|
||||
* @param WP_POST $post
|
||||
* @param WP_Post $post
|
||||
*/
|
||||
protected function update_order( $request, $post ) {
|
||||
try {
|
||||
|
|
@ -165,7 +165,7 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_Controller {
|
|||
* @return WP_Error|WP_REST_Response $response
|
||||
*/
|
||||
public function get_subscription_orders( $request ) {
|
||||
$id = (int) $request['id'];
|
||||
$id = absint( $request['id'] );
|
||||
|
||||
if ( empty( $id ) || ! wcs_is_subscription( $id ) ) {
|
||||
return new WP_Error( 'woocommerce_rest_invalid_shop_subscription_id', __( 'Invalid subscription id.', 'woocommerce-subscriptions' ), array( 'status' => 404 ) );
|
||||
|
|
@ -237,6 +237,7 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_Controller {
|
|||
$subscription = wcs_create_subscription( $args );
|
||||
|
||||
if ( is_wp_error( $subscription ) ) {
|
||||
// translators: placeholder is an error message.
|
||||
throw new WC_REST_Exception( 'woocommerce_rest_cannot_create_subscription', sprintf( __( 'Cannot create subscription: %s.', 'woocommerce-subscriptions' ), implode( ', ', $subscription->get_error_messages() ) ), 400 );
|
||||
}
|
||||
|
||||
|
|
@ -281,6 +282,7 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_Controller {
|
|||
$subscription->update_dates( $dates_to_update );
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
// translators: placeholder is an error message.
|
||||
throw new WC_REST_Exception( 'woocommerce_rest_cannot_update_subscription_dates', sprintf( __( 'Updating subscription dates errored with message: %s', 'woocommerce-subscriptions' ), $e->getMessage() ), 400 );
|
||||
}
|
||||
}
|
||||
|
|
@ -343,18 +345,18 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_Controller {
|
|||
$schema = parent::get_item_schema();
|
||||
|
||||
$subscriptions_schema = array(
|
||||
'billing_interval' => array(
|
||||
'billing_interval' => array(
|
||||
'description' => __( 'The number of billing periods between subscription renewals.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'billing_period' => array(
|
||||
'billing_period' => array(
|
||||
'description' => __( 'Billing period for the subscription.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'string',
|
||||
'enum' => array_keys( wcs_get_subscription_period_strings() ),
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'payment_details' => array(
|
||||
'payment_details' => array(
|
||||
'description' => __( 'Subscription payment details.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'edit' ),
|
||||
|
|
@ -366,12 +368,12 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_Controller {
|
|||
),
|
||||
),
|
||||
),
|
||||
'start_date' => array(
|
||||
'start_date' => array(
|
||||
'description' => __( "The subscription's start date.", 'woocommerce-subscriptions' ),
|
||||
'type' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'trial_date' => array(
|
||||
'trial_date' => array(
|
||||
'description' => __( "The subscription's trial date", 'woocommerce-subscriptions' ),
|
||||
'type' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
|
|
@ -381,7 +383,7 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_Controller {
|
|||
'type' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'end_date' => array(
|
||||
'end_date' => array(
|
||||
'description' => __( "The subscription's end date.", 'woocommerce-subscriptions' ),
|
||||
'type' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API subscription notes controller
|
||||
*
|
||||
* Handles requests to the /subscription/<id>/notes endpoint.
|
||||
*
|
||||
* @author Prospress
|
||||
* @since 2.1
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
/**
|
||||
* REST API Subscription Notes controller class.
|
||||
*
|
||||
* @package WooCommerce_Subscriptions/API
|
||||
*/
|
||||
class WC_REST_Subscription_Notes_V1_Controller extends WC_REST_Order_Notes_V1_Controller {
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'subscriptions/(?P<order_id>[\d]+)/notes';
|
||||
|
||||
/**
|
||||
* Post type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $post_type = 'shop_subscription';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,721 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API Subscriptions controller
|
||||
*
|
||||
* Handles requests to the /subscriptions endpoint.
|
||||
*
|
||||
* @package WooCommerce Subscriptions\Rest Api
|
||||
* @author WooCommerce
|
||||
* @since 2.1.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* REST API Subscriptions controller class.
|
||||
*
|
||||
* @package WooCommerce_Subscriptions/API
|
||||
*/
|
||||
class WC_REST_Subscriptions_V1_Controller extends WC_REST_Orders_V1_Controller {
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'subscriptions';
|
||||
|
||||
/**
|
||||
* Post type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $post_type = 'shop_subscription';
|
||||
|
||||
/**
|
||||
* Initialize subscription actions and filters
|
||||
*/
|
||||
public function __construct() {
|
||||
add_filter( 'woocommerce_rest_prepare_shop_subscription', array( $this, 'filter_get_subscription_response' ), 10, 3 );
|
||||
|
||||
add_filter( 'woocommerce_rest_shop_subscription_query', array( $this, 'query_args' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the routes for subscriptions.
|
||||
*/
|
||||
public function register_routes() {
|
||||
parent::register_routes();
|
||||
|
||||
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)/orders', array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_subscription_orders' ),
|
||||
'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
||||
'args' => $this->get_collection_params(),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
) );
|
||||
|
||||
register_rest_route( $this->namespace, '/' . $this->rest_base . '/statuses', array( // nosemgrep: audit.php.wp.security.rest-route.permission-callback.return-true -- /subscriptions/statuses is a public endpoint and doesn't need any permission checks.
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_statuses' ),
|
||||
'permission_callback' => '__return_true',
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter WC_REST_Orders_Controller::get_item response for subscription post types
|
||||
*
|
||||
* @since 2.1
|
||||
* @param WP_REST_Response $response
|
||||
* @param WP_Post $post
|
||||
* @param WP_REST_Request $request
|
||||
*/
|
||||
public function filter_get_subscription_response( $response, $post, $request ) {
|
||||
$decimal_places = is_null( $request['dp'] ) ? wc_get_price_decimals() : absint( $request['dp'] );
|
||||
|
||||
if ( ! empty( $post->post_type ) && ! empty( $post->ID ) && 'shop_subscription' == $post->post_type ) {
|
||||
$subscription = wcs_get_subscription( $post->ID );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response->data['billing_period'] = $subscription->get_billing_period();
|
||||
$response->data['billing_interval'] = $subscription->get_billing_interval();
|
||||
|
||||
// Send resubscribe data
|
||||
$resubscribed_subscriptions = array_filter( $subscription->get_related_orders( 'ids', 'resubscribe' ), 'wcs_is_subscription' );
|
||||
$response->data['resubscribed_from'] = strval( wcs_get_objects_property( $subscription, 'subscription_resubscribe' ) );
|
||||
$response->data['resubscribed_subscription'] = strval( reset( $resubscribed_subscriptions ) ); // Subscriptions can only be resubscribed to once so return the first and only element.
|
||||
|
||||
foreach ( array( 'start', 'trial_end', 'next_payment', 'end' ) as $date_type ) {
|
||||
$date = $subscription->get_date( $date_type );
|
||||
$response->data[ $date_type . '_date' ] = ( ! empty( $date ) ) ? wc_rest_prepare_date_response( $date ) : '';
|
||||
}
|
||||
|
||||
// v1 API includes some date types in site time, include those dates in UTC as well.
|
||||
$response->data['date_completed_gmt'] = wc_rest_prepare_date_response( $subscription->get_date_completed() );
|
||||
$response->data['date_paid_gmt'] = wc_rest_prepare_date_response( $subscription->get_date_paid() );
|
||||
$response->data['removed_line_items'] = array();
|
||||
|
||||
// Include removed line items of a subscription
|
||||
foreach ( $subscription->get_items( 'line_item_removed' ) as $item_id => $item ) {
|
||||
$product = $item->get_product();
|
||||
$product_id = 0;
|
||||
$variation_id = 0;
|
||||
$product_sku = null;
|
||||
|
||||
// Check if the product exists.
|
||||
if ( is_object( $product ) ) {
|
||||
$product_id = $item->get_product_id();
|
||||
$variation_id = $item->get_variation_id();
|
||||
$product_sku = $product->get_sku();
|
||||
}
|
||||
|
||||
$item_meta = array();
|
||||
|
||||
$hideprefix = 'true' === $request['all_item_meta'] ? null : '_';
|
||||
|
||||
foreach ( $item->get_formatted_meta_data( $hideprefix, true ) as $meta_key => $formatted_meta ) {
|
||||
$item_meta[] = array(
|
||||
'key' => $formatted_meta->key,
|
||||
'label' => $formatted_meta->display_key,
|
||||
'value' => wc_clean( $formatted_meta->display_value ),
|
||||
);
|
||||
}
|
||||
|
||||
$line_item = array(
|
||||
'id' => $item_id,
|
||||
'name' => $item['name'],
|
||||
'sku' => $product_sku,
|
||||
'product_id' => (int) $product_id,
|
||||
'variation_id' => (int) $variation_id,
|
||||
'quantity' => wc_stock_amount( $item['qty'] ),
|
||||
'tax_class' => ! empty( $item['tax_class'] ) ? $item['tax_class'] : '',
|
||||
'price' => wc_format_decimal( $subscription->get_item_total( $item, false, false ), $decimal_places ),
|
||||
'subtotal' => wc_format_decimal( $subscription->get_line_subtotal( $item, false, false ), $decimal_places ),
|
||||
'subtotal_tax' => wc_format_decimal( $item['line_subtotal_tax'], $decimal_places ),
|
||||
'total' => wc_format_decimal( $subscription->get_line_total( $item, false, false ), $decimal_places ),
|
||||
'total_tax' => wc_format_decimal( $item['line_tax'], $decimal_places ),
|
||||
'taxes' => array(),
|
||||
'meta' => $item_meta,
|
||||
);
|
||||
|
||||
$item_line_taxes = maybe_unserialize( $item['line_tax_data'] );
|
||||
if ( isset( $item_line_taxes['total'] ) ) {
|
||||
$line_tax = array();
|
||||
|
||||
foreach ( $item_line_taxes['total'] as $tax_rate_id => $tax ) {
|
||||
$line_tax[ $tax_rate_id ] = array(
|
||||
'id' => $tax_rate_id,
|
||||
'total' => $tax,
|
||||
'subtotal' => '',
|
||||
);
|
||||
}
|
||||
|
||||
foreach ( $item_line_taxes['subtotal'] as $tax_rate_id => $tax ) {
|
||||
$line_tax[ $tax_rate_id ]['subtotal'] = $tax;
|
||||
}
|
||||
|
||||
$line_item['taxes'] = array_values( $line_tax );
|
||||
}
|
||||
|
||||
$response->data['removed_line_items'][] = $line_item;
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the order_total value on the subscription after WC_REST_Orders_Controller::create_order
|
||||
* calls calculate_totals(). This allows store admins to create a recurring payment via the api
|
||||
* without needing to attach a product to the subscription.
|
||||
*
|
||||
* @since 2.1
|
||||
* @param WP_REST_Request $request
|
||||
*/
|
||||
protected function create_order( $request ) {
|
||||
try {
|
||||
if ( ! is_null( $request['customer_id'] ) && 0 !== $request['customer_id'] && false === get_user_by( 'id', $request['customer_id'] ) ) {
|
||||
throw new WC_REST_Exception( 'woocommerce_rest_invalid_customer_id', __( 'Customer ID is invalid.', 'woocommerce-subscriptions' ), 400 );
|
||||
}
|
||||
|
||||
// If the start date is not set in the request, set its default to now
|
||||
if ( ! isset( $request['start_date'] ) ) {
|
||||
$request['start_date'] = gmdate( 'Y-m-d H:i:s' );
|
||||
}
|
||||
|
||||
// prepare all subscription data from the request
|
||||
$subscription = $this->prepare_item_for_database( $request );
|
||||
$subscription->set_created_via( 'rest-api' );
|
||||
$subscription->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
|
||||
$subscription->calculate_totals();
|
||||
|
||||
// allow the order total to be overriden (i.e. if you want to have a subscription with no order items but a flat $10.00 recurring payment )
|
||||
if ( isset( $request['order_total'] ) ) {
|
||||
$subscription->set_total( wc_format_decimal( $request['order_total'], get_option( 'woocommerce_price_num_decimals' ) ) );
|
||||
}
|
||||
|
||||
// Store the post meta on the subscription after it's saved, this is to avoid compat. issue with the filters in WC_Subscription::set_payment_method_meta() expecting the $subscription to have an ID (therefore it needs to be called after the WC_Subscription has been saved)
|
||||
$payment_data = ( ! empty( $request['payment_details'] ) ) ? $request['payment_details'] : array();
|
||||
if ( empty( $payment_data['payment_details']['method_id'] ) && ! empty( $request['payment_method'] ) ) {
|
||||
$payment_data['method_id'] = $request['payment_method'];
|
||||
}
|
||||
|
||||
$this->update_payment_method( $subscription, $payment_data );
|
||||
|
||||
$subscription->save();
|
||||
|
||||
// Handle set paid.
|
||||
if ( true === $request['set_paid'] ) {
|
||||
$subscription->payment_complete( $request['transaction_id'] );
|
||||
}
|
||||
|
||||
do_action( 'wcs_api_subscription_created', $subscription->get_id() );
|
||||
|
||||
return $subscription->get_id();
|
||||
} catch ( WC_Data_Exception $e ) {
|
||||
return new WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() );
|
||||
} catch ( WC_REST_Exception $e ) {
|
||||
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides WC_REST_Orders_Controller::update_order to update subscription specific meta
|
||||
* calls parent::update_order to update the rest.
|
||||
*
|
||||
* @since 2.1
|
||||
* @param WP_REST_Request $request
|
||||
*/
|
||||
protected function update_order( $request ) {
|
||||
try {
|
||||
$subscription = $this->prepare_item_for_database( $request );
|
||||
|
||||
// If any line items have changed, recalculate subscription totals.
|
||||
if ( isset( $request['line_items'] ) || isset( $request['shipping_lines'] ) || isset( $request['fee_lines'] ) || isset( $request['coupon_lines'] ) ) {
|
||||
$subscription->calculate_totals();
|
||||
}
|
||||
|
||||
// allow the order total to be overriden (i.e. if you want to have a subscription with no order items but a flat $10.00 recurring payment )
|
||||
if ( isset( $request['order_total'] ) ) {
|
||||
$subscription->set_total( wc_format_decimal( $request['order_total'], get_option( 'woocommerce_price_num_decimals' ) ) );
|
||||
}
|
||||
|
||||
$subscription->save();
|
||||
|
||||
// Update the post meta on the subscription after it's saved, this is to avoid compat. issue with the filters in WC_Subscription::set_payment_method_meta() expecting the $subscription to have an ID (therefore it needs to be called after the WC_Subscription has been saved)
|
||||
$payment_data = ( ! empty( $request['payment_details'] ) ) ? $request['payment_details'] : array();
|
||||
$existing_payment_method_id = $subscription->get_payment_method();
|
||||
|
||||
if ( empty( $payment_data['method_id'] ) && isset( $request['payment_method'] ) ) {
|
||||
$payment_data['method_id'] = $request['payment_method'];
|
||||
|
||||
} elseif ( ! empty( $existing_payment_method_id ) ) {
|
||||
$payment_data['method_id'] = $existing_payment_method_id;
|
||||
}
|
||||
|
||||
if ( isset( $payment_data['method_id'] ) ) {
|
||||
$this->update_payment_method( $subscription, $payment_data, true );
|
||||
}
|
||||
|
||||
// Handle set paid.
|
||||
if ( $subscription->needs_payment() && true === $request['set_paid'] ) {
|
||||
$subscription->payment_complete();
|
||||
}
|
||||
|
||||
do_action( 'wcs_api_subscription_updated', $subscription->get_id() );
|
||||
|
||||
return $subscription->get_id();
|
||||
} catch ( WC_Data_Exception $e ) {
|
||||
return new WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() );
|
||||
} catch ( WC_REST_Exception $e ) {
|
||||
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subscription orders
|
||||
*
|
||||
* @since 2.1
|
||||
* @param WP_REST_Request $request
|
||||
* @return WP_Error|WP_REST_Response $response
|
||||
*/
|
||||
public function get_subscription_orders( $request ) {
|
||||
$id = (int) $request['id'];
|
||||
|
||||
if ( empty( $id ) || ! wcs_is_subscription( $id ) ) {
|
||||
return new WP_Error( 'woocommerce_rest_invalid_shop_subscription_id', __( 'Invalid subscription id.', 'woocommerce-subscriptions' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
$this->post_type = 'shop_order';
|
||||
$subscription = wcs_get_subscription( $id );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
return new WP_Error( 'woocommerce_rest_invalid_shop_subscription_id', __( 'Invalid subscription id.', 'woocommerce-subscriptions' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
$subscription_orders = $subscription->get_related_orders();
|
||||
|
||||
$orders = array();
|
||||
|
||||
foreach ( $subscription_orders as $order_id ) {
|
||||
// Validate that the order can be loaded before trying to generate a response object for it.
|
||||
$order = wc_get_order( $order_id );
|
||||
|
||||
if ( ! $order || ! wc_rest_check_post_permissions( $this->post_type, 'read', $order_id ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$response = $this->prepare_item_for_response( $order, $request );
|
||||
|
||||
foreach ( array( 'parent', 'renewal', 'switch' ) as $order_type ) {
|
||||
if ( wcs_order_contains_subscription( $order_id, $order_type ) ) {
|
||||
$response->data['order_type'] = $order_type . '_order';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$orders[] = $this->prepare_response_for_collection( $response );
|
||||
}
|
||||
|
||||
$response = rest_ensure_response( $orders );
|
||||
$response->header( 'X-WP-Total', count( $orders ) );
|
||||
$response->header( 'X-WP-TotalPages', 1 );
|
||||
|
||||
return apply_filters( 'wcs_rest_subscription_orders_response', $response, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subscription statuses
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
public function get_statuses() {
|
||||
return rest_ensure_response( wcs_get_subscription_statuses() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides WC_REST_Orders_Controller::get_order_statuses() so that subscription statuses are
|
||||
* validated correctly in WC_REST_Orders_Controller::get_collection_params()
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
protected function get_order_statuses() {
|
||||
$subscription_statuses = array();
|
||||
|
||||
foreach ( array_keys( wcs_get_subscription_statuses() ) as $status ) {
|
||||
$subscription_statuses[] = str_replace( 'wc-', '', $status );
|
||||
}
|
||||
return $subscription_statuses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and update payment method on a subscription
|
||||
*
|
||||
* @since 2.1
|
||||
* @param WC_Subscription $subscription
|
||||
* @param array $data
|
||||
* @param bool $updating
|
||||
*/
|
||||
public function update_payment_method( $subscription, $data, $updating = false ) {
|
||||
$payment_method = ( ! empty( $data['method_id'] ) ) ? $data['method_id'] : '';
|
||||
|
||||
try {
|
||||
if ( $updating && ! array_key_exists( $payment_method, WCS_Change_Payment_Method_Admin::get_valid_payment_methods( $subscription ) ) ) {
|
||||
throw new Exception( __( 'Gateway does not support admin changing the payment method on a Subscription.', 'woocommerce-subscriptions' ) );
|
||||
}
|
||||
|
||||
$payment_method_meta = apply_filters( 'woocommerce_subscription_payment_meta', array(), $subscription );
|
||||
|
||||
// Reload the subscription to update the meta values.
|
||||
// In particular, the update_post_meta() called while _stripe_card_id is updated to _stripe_source_id
|
||||
$subscription = wcs_get_subscription( $subscription->get_id() );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
throw new WC_REST_Exception( 'woocommerce_rest_payment_update_failed', __( 'Subscription payment method could not be set updated due to technical issues.', 'woocommerce-subscriptions' ), 500 );
|
||||
}
|
||||
|
||||
if ( isset( $payment_method_meta[ $payment_method ] ) ) {
|
||||
$payment_method_meta = $payment_method_meta[ $payment_method ];
|
||||
|
||||
if ( ! empty( $payment_method_meta ) ) {
|
||||
|
||||
foreach ( $payment_method_meta as $meta_table => &$meta ) {
|
||||
if ( ! is_array( $meta ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( $meta as $meta_key => &$meta_data ) {
|
||||
|
||||
if ( isset( $data[ $meta_table ][ $meta_key ] ) ) {
|
||||
$meta_data['value'] = $data[ $meta_table ][ $meta_key ];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$subscription->set_payment_method( $payment_method, $payment_method_meta );
|
||||
|
||||
// Save the subscription to reflect the new values
|
||||
$subscription->save();
|
||||
|
||||
} catch ( Exception $e ) {
|
||||
$subscription->set_payment_method();
|
||||
$subscription->save();
|
||||
// translators: 1$: gateway id, 2$: error message
|
||||
throw new WC_REST_Exception( 'woocommerce_rest_invalid_payment_data', sprintf( __( 'Subscription payment method could not be set to %1$s with error message: %2$s', 'woocommerce-subscriptions' ), $payment_method, $e->getMessage() ), 400 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a single subscription for create.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_Error|WC_Subscription $data Object.
|
||||
*/
|
||||
protected function prepare_item_for_database( $request ) {
|
||||
$id = isset( $request['id'] ) ? absint( $request['id'] ) : 0;
|
||||
$subscription = new WC_Subscription( $id );
|
||||
$schema = $this->get_item_schema();
|
||||
$data_keys = array_keys( array_filter( $schema['properties'], array( $this, 'filter_writable_props' ) ) );
|
||||
|
||||
$dates_to_update = array();
|
||||
|
||||
// Handle all writable props
|
||||
foreach ( $data_keys as $key ) {
|
||||
$value = $request[ $key ];
|
||||
|
||||
if ( ! is_null( $value ) ) {
|
||||
switch ( $key ) {
|
||||
case 'billing':
|
||||
case 'shipping':
|
||||
$this->update_address( $subscription, $value, $key );
|
||||
break;
|
||||
case 'line_items':
|
||||
case 'shipping_lines':
|
||||
case 'fee_lines':
|
||||
case 'coupon_lines':
|
||||
if ( is_array( $value ) ) {
|
||||
foreach ( $value as $item ) {
|
||||
if ( is_array( $item ) ) {
|
||||
if ( $this->item_is_null( $item ) || ( isset( $item['quantity'] ) && 0 === $item['quantity'] ) ) {
|
||||
$subscription->remove_item( $item['id'] );
|
||||
} else {
|
||||
$this->set_item( $subscription, $key, $item );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'transition_status':
|
||||
$subscription->update_status( $value );
|
||||
break;
|
||||
case 'start_date':
|
||||
case 'trial_end_date':
|
||||
case 'next_payment_date':
|
||||
case 'end_date':
|
||||
$dates_to_update[ $key ] = $value;
|
||||
break;
|
||||
default:
|
||||
if ( is_callable( array( $subscription, "set_{$key}" ) ) ) {
|
||||
$subscription->{"set_{$key}"}( $value );
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$subscription->save();
|
||||
|
||||
try {
|
||||
if ( ! empty( $dates_to_update ) ) {
|
||||
$subscription->update_dates( $dates_to_update );
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
// translators: placeholder is an error message.
|
||||
throw new WC_REST_Exception( 'woocommerce_rest_cannot_update_subscription_dates', sprintf( __( 'Updating subscription dates errored with message: %s', 'woocommerce-subscriptions' ), $e->getMessage() ), 400 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the data for the insert.
|
||||
*
|
||||
* The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being
|
||||
* prepared for the response.
|
||||
*
|
||||
* @param WC_Subscription $subscription The subscription object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*/
|
||||
return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}", $subscription, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds additional item schema information for subscription requests
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
$schema = parent::get_item_schema();
|
||||
|
||||
$subscriptions_schema = array(
|
||||
'transition_status' => array(
|
||||
'description' => __( 'The status to transition the subscription to. Unlike the "status" param, this will calculate and update the subscription dates.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'string',
|
||||
'enum' => $this->get_order_statuses(),
|
||||
'context' => array( 'edit' ),
|
||||
),
|
||||
'billing_interval' => array(
|
||||
'description' => __( 'The number of billing periods between subscription renewals.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'billing_period' => array(
|
||||
'description' => __( 'Billing period for the subscription.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'string',
|
||||
'enum' => array_keys( wcs_get_subscription_period_strings() ),
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'payment_details' => array(
|
||||
'description' => __( 'Subscription payment details.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'edit' ),
|
||||
'properties' => array(
|
||||
'method_id' => array(
|
||||
'description' => __( 'Payment gateway ID.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'edit' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
'start_date' => array(
|
||||
'description' => __( "The subscription's start date.", 'woocommerce-subscriptions' ),
|
||||
'type' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'trial_end_date' => array(
|
||||
'description' => __( "The subscription's trial date", 'woocommerce-subscriptions' ),
|
||||
'type' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'next_payment_date' => array(
|
||||
'description' => __( "The subscription's next payment date.", 'woocommerce-subscriptions' ),
|
||||
'type' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'end_date' => array(
|
||||
'description' => __( "The subscription's end date.", 'woocommerce-subscriptions' ),
|
||||
'type' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'resubscribed_from' => array(
|
||||
'description' => __( "The subscription's original subscription ID if this is a resubscribed subscription.", 'woocommerce-subscriptions' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'resubscribed_subscription' => array(
|
||||
'description' => __( "The subscription's resubscribed subscription ID.", 'woocommerce-subscriptions' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'date_completed_gmt' => array(
|
||||
'description' => __( "The date the subscription's latest order was completed, in GMT.", 'woocommerce-subscriptions' ),
|
||||
'type' => 'date-time',
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'date_paid_gmt' => array(
|
||||
'description' => __( "The date the subscription's latest order was paid, in GMT.", 'woocommerce-subscriptions' ),
|
||||
'type' => 'date-time',
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'removed_line_items' => array(
|
||||
'description' => __( 'Removed line items data.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'array',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'Item ID.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'name' => array(
|
||||
'description' => __( 'Product name.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'mixed',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'sku' => array(
|
||||
'description' => __( 'Product SKU.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'product_id' => array(
|
||||
'description' => __( 'Product ID.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'mixed',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'variation_id' => array(
|
||||
'description' => __( 'Variation ID, if applicable.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'quantity' => array(
|
||||
'description' => __( 'Quantity ordered.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'tax_class' => array(
|
||||
'description' => __( 'Tax class of product.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'price' => array(
|
||||
'description' => __( 'Product price.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'subtotal' => array(
|
||||
'description' => __( 'Line subtotal (before discounts).', 'woocommerce-subscriptions' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'subtotal_tax' => array(
|
||||
'description' => __( 'Line subtotal tax (before discounts).', 'woocommerce-subscriptions' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'total' => array(
|
||||
'description' => __( 'Line total (after discounts).', 'woocommerce-subscriptions' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'total_tax' => array(
|
||||
'description' => __( 'Line total tax (after discounts).', 'woocommerce-subscriptions' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'taxes' => array(
|
||||
'description' => __( 'Line taxes.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'array',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'Tax rate ID.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'total' => array(
|
||||
'description' => __( 'Tax total.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'subtotal' => array(
|
||||
'description' => __( 'Tax subtotal.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'meta' => array(
|
||||
'description' => __( 'Removed line item meta data.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'array',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'key' => array(
|
||||
'description' => __( 'Meta key.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'label' => array(
|
||||
'description' => __( 'Meta label.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'value' => array(
|
||||
'description' => __( 'Meta value.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'mixed',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$schema['properties'] += $subscriptions_schema;
|
||||
return $schema;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API subscription notes controller
|
||||
*
|
||||
* Handles requests to the /subscription/<id>/notes endpoint.
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
/**
|
||||
* REST API Subscription Notes controller class.
|
||||
*
|
||||
* @package WooCommerce_Subscriptions/API
|
||||
*/
|
||||
class WC_REST_Subscription_Notes_V2_Controller extends WC_REST_Order_Notes_V2_Controller {
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'subscriptions/(?P<order_id>[\d]+)/notes';
|
||||
|
||||
/**
|
||||
* Post type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $post_type = 'shop_subscription';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,797 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API Subscriptions V2 Controller
|
||||
*
|
||||
* Handles requests to the wc/v2/subscriptions and wc/v2/orders/ID/subscriptions endpoint.
|
||||
*
|
||||
* @since 6.4.0
|
||||
* @package WooCommerce Subscriptions\Rest Api
|
||||
*/
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class WC_REST_Subscriptions_V2_Controller extends WC_REST_Orders_V2_Controller {
|
||||
|
||||
/**
|
||||
* @var string Route base.
|
||||
*/
|
||||
protected $rest_base = 'subscriptions';
|
||||
|
||||
/**
|
||||
* @var string The post type.
|
||||
*/
|
||||
protected $post_type = 'shop_subscription';
|
||||
|
||||
/**
|
||||
* Register the routes for the subscriptions endpoint.
|
||||
*
|
||||
* GET|POST /subscriptions
|
||||
* GET|PUT|DELETE /subscriptions/<subscription_id>
|
||||
* GET /subscriptions/status
|
||||
* GET /subscriptions/<subscription_id>/orders
|
||||
* POST /orders/<order_id>/subscriptions
|
||||
*
|
||||
* @since 6.4.0
|
||||
*/
|
||||
public function register_routes() {
|
||||
parent::register_routes();
|
||||
|
||||
register_rest_route( $this->namespace, "/{$this->rest_base}/statuses", [ // nosemgrep: audit.php.wp.security.rest-route.permission-callback.return-true -- /subscriptions/statuses is a public endpoint and doesn't need any permission checks.
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_statuses' ],
|
||||
'permission_callback' => '__return_true',
|
||||
],
|
||||
'schema' => [ $this, 'get_statuses_schema' ],
|
||||
] );
|
||||
|
||||
register_rest_route( $this->namespace, "/{$this->rest_base}/(?P<id>[\d]+)/orders", [
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_subscription_orders' ],
|
||||
'permission_callback' => [ $this, 'get_items_permissions_check' ],
|
||||
'args' => $this->get_collection_params(),
|
||||
],
|
||||
'schema' => [ $this, 'get_subscription_orders_schema' ],
|
||||
] );
|
||||
|
||||
register_rest_route( $this->namespace, "/orders/(?P<id>[\d]+)/{$this->rest_base}", [
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'create_subscriptions_from_order' ],
|
||||
'permission_callback' => [ $this, 'create_item_permissions_check' ],
|
||||
'args' => $this->get_collection_params(),
|
||||
],
|
||||
'schema' => [ $this, 'create_subscriptions_from_order_schema' ],
|
||||
] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request object. Return false if the ID is not a subscription.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @param int $id Object ID.
|
||||
*
|
||||
* @return WC_Subscription|bool
|
||||
*/
|
||||
protected function get_object( $id ) {
|
||||
$subscription = wcs_get_subscription( $id );
|
||||
|
||||
if ( ! $subscription || ! is_a( $subscription, 'WC_Subscription' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $subscription;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a single subscription output for response.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @param WC_Subscription $object Subscription object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function prepare_object_for_response( $object, $request ) {
|
||||
$response = parent::prepare_object_for_response( $object, $request );
|
||||
|
||||
// When generating the `/subscriptions/[id]/orders` response this function is called to generate related-order data so exit early if this isn't a subscription.
|
||||
if ( ! wcs_is_subscription( $object ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
// Add subscription specific data to the base order response data.
|
||||
$response->data['billing_period'] = $object->get_billing_period();
|
||||
$response->data['billing_interval'] = $object->get_billing_interval();
|
||||
|
||||
foreach ( wcs_get_subscription_date_types() as $date_type => $date_name ) {
|
||||
$date = $object->get_date( wcs_normalise_date_type_key( $date_type ) );
|
||||
$response->data[ $date_type . '_date' ] = ( ! empty( $date ) ) ? wc_rest_prepare_date_response( $date, false ) : '';
|
||||
$response->data[ $date_type . '_date_gmt' ] = ( ! empty( $date ) ) ? wc_rest_prepare_date_response( $date ) : '';
|
||||
}
|
||||
|
||||
// Some base WC_Order dates need to be pulled from the subscription object to be correct.
|
||||
$response->data['date_paid'] = wc_rest_prepare_date_response( $object->get_date_paid(), false );
|
||||
$response->data['date_paid_gmt'] = wc_rest_prepare_date_response( $object->get_date_paid() );
|
||||
$response->data['date_completed'] = wc_rest_prepare_date_response( $object->get_date_completed(), false );
|
||||
$response->data['date_completed_gmt'] = wc_rest_prepare_date_response( $object->get_date_completed() );
|
||||
|
||||
// Include resubscribe data.
|
||||
$resubscribed_subscriptions = array_filter( $object->get_related_orders( 'ids', 'resubscribe' ), 'wcs_is_subscription' );
|
||||
$response->data['resubscribed_from'] = strval( $object->get_meta( '_subscription_resubscribe' ) );
|
||||
$response->data['resubscribed_subscription'] = strval( reset( $resubscribed_subscriptions ) ); // Subscriptions can only be resubscribed to once so return the first and only element.
|
||||
|
||||
// Include the removed line items.
|
||||
$response->data['removed_line_items'] = [];
|
||||
|
||||
foreach ( $object->get_items( 'line_item_removed' ) as $item ) {
|
||||
$response->data['removed_line_items'][] = $this->get_order_item_data( $item );
|
||||
}
|
||||
|
||||
// Remove non-subscription properties
|
||||
unset( $response->data['cart_hash'] );
|
||||
unset( $response->data['transaction_id'] );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the /subscriptions/statuses response.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @return WP_REST_Response The response object.
|
||||
*/
|
||||
public function get_statuses() {
|
||||
return rest_ensure_response( wcs_get_subscription_statuses() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the /subscriptions/[id]/orders response.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_Error|WP_REST_Response $response The response or an error if one occurs.
|
||||
*/
|
||||
public function get_subscription_orders( $request ) {
|
||||
$id = absint( $request['id'] );
|
||||
|
||||
if ( empty( $id ) || ! wcs_is_subscription( $id ) ) {
|
||||
return new WP_Error( 'woocommerce_rest_invalid_shop_subscription_id', __( 'Invalid subscription ID.', 'woocommerce-subscriptions' ), [ 'status' => 404 ] );
|
||||
}
|
||||
|
||||
$subscription = wcs_get_subscription( $id );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
return new WP_Error( 'woocommerce_rest_invalid_shop_subscription_id', sprintf( __( 'Failed to load subscription object with the ID %d.', 'woocommerce-subscriptions' ), $id ), [ 'status' => 404 ] );
|
||||
}
|
||||
|
||||
$orders = [];
|
||||
|
||||
foreach ( [ 'parent', 'renewal', 'switch' ] as $order_type ) {
|
||||
foreach ( $subscription->get_related_orders( 'ids', $order_type ) as $order_id ) {
|
||||
|
||||
if ( ! wc_rest_check_post_permissions( 'shop_order', 'read', $order_id ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate that the order can be loaded before trying to generate a response object for it.
|
||||
$order = wc_get_order( $order_id );
|
||||
|
||||
if ( ! $order ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$response = parent::prepare_object_for_response( $order, $request );
|
||||
|
||||
// Add the order's relationship to the response.
|
||||
$response->data['order_type'] = $order_type . '_order';
|
||||
|
||||
$orders[] = $this->prepare_response_for_collection( $response );
|
||||
}
|
||||
}
|
||||
|
||||
$response = rest_ensure_response( $orders );
|
||||
$response->header( 'X-WP-Total', count( $orders ) );
|
||||
$response->header( 'X-WP-TotalPages', 1 );
|
||||
|
||||
return apply_filters( 'wcs_rest_subscription_orders_response', $response, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides WC_REST_Orders_V2_Controller::get_order_statuses() so that subscription statuses are
|
||||
* validated correctly.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @return array An array of valid subscription statuses.
|
||||
*/
|
||||
protected function get_order_statuses() {
|
||||
$subscription_statuses = [];
|
||||
|
||||
foreach ( wcs_get_subscription_statuses() as $status => $status_name ) {
|
||||
$subscription_statuses[] = str_replace( 'wc-', '', $status );
|
||||
}
|
||||
|
||||
return $subscription_statuses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a single subscription for creation or update.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @param bool $creating If the request is for creating a new object.
|
||||
*
|
||||
* @return WP_Error|WC_Subscription
|
||||
*/
|
||||
public function prepare_object_for_database( $request, $creating = false ) {
|
||||
$id = isset( $request['id'] ) ? absint( $request['id'] ) : 0;
|
||||
$subscription = new WC_Subscription( $id );
|
||||
$schema = $this->get_item_schema();
|
||||
$data_keys = array_keys( array_filter( $schema['properties'], [ $this, 'filter_writable_props' ] ) );
|
||||
|
||||
// Prepare variables for properties which need to be saved late (like status) or in a group (dates and payment data).
|
||||
$status = '';
|
||||
$payment_method = '';
|
||||
$payment_meta = [];
|
||||
$dates = [];
|
||||
|
||||
// Both setting (set_status()) and updating (update_status()) are valid ways for requests to set a subscription's status.
|
||||
$status_transition = 'set';
|
||||
|
||||
foreach ( $data_keys as $i => $key ) {
|
||||
$value = $request[ $key ];
|
||||
|
||||
if ( is_null( $value ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ( $key ) {
|
||||
case 'parent_id':
|
||||
$subscription->set_parent_id( $value );
|
||||
break;
|
||||
case 'transition_status':
|
||||
$status_transition = 'update';
|
||||
case 'status':
|
||||
// This needs to be done later so status changes take into account other data like dates.
|
||||
$status = $value;
|
||||
break;
|
||||
case 'billing':
|
||||
case 'shipping':
|
||||
$this->update_address( $subscription, $value, $key );
|
||||
break;
|
||||
case 'start_date':
|
||||
case 'trial_end_date':
|
||||
case 'next_payment_date':
|
||||
case 'cancelled_date':
|
||||
case 'end_date':
|
||||
// Group all the subscription date properties so they can be validated together.
|
||||
$dates[ $key ] = $value;
|
||||
break;
|
||||
case 'payment_method':
|
||||
$payment_method = $value;
|
||||
break;
|
||||
case 'payment_details':
|
||||
// Format the value in a way payment gateways expect so it can be validated.
|
||||
$payment_meta = $value;
|
||||
break;
|
||||
case 'line_items':
|
||||
case 'shipping_lines':
|
||||
case 'fee_lines':
|
||||
if ( is_array( $value ) ) {
|
||||
foreach ( $value as $item ) {
|
||||
if ( is_array( $item ) ) {
|
||||
if ( $this->item_is_null( $item ) || ( isset( $item['quantity'] ) && 0 === $item['quantity'] ) ) {
|
||||
if ( isset( $item['id'] ) ) {
|
||||
$subscription->remove_item( $item['id'] );
|
||||
}
|
||||
} else {
|
||||
$this->set_item( $subscription, $key, $item );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'meta_data':
|
||||
if ( is_array( $value ) ) {
|
||||
foreach ( $value as $meta ) {
|
||||
$subscription->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' );
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if ( is_callable( [ $subscription, "set_{$key}" ] ) ) {
|
||||
$subscription->{"set_{$key}"}( $value );
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $payment_method ) ) {
|
||||
$this->update_payment_method( $subscription, $payment_method, $payment_meta );
|
||||
}
|
||||
|
||||
if ( ! empty( $dates ) ) {
|
||||
// If the start date is not set in the request when a subscription is being created, set its default to now.
|
||||
if ( empty( $id ) && ! isset( $dates['start_date'] ) ) {
|
||||
$dates['start_date'] = gmdate( 'Y-m-d H:i:s' );
|
||||
}
|
||||
|
||||
try {
|
||||
$subscription->update_dates( $dates );
|
||||
} catch ( Exception $e ) {
|
||||
throw new WC_REST_Exception( 'woocommerce_rest_invalid_payment_data', sprintf( __( 'Subscription dates could not be set. Error message: %s', 'woocommerce-subscriptions' ), $e->getMessage() ), 400 );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $status ) ) {
|
||||
if ( 'set' === $status_transition ) {
|
||||
$subscription->set_status( $status );
|
||||
} else {
|
||||
$subscription->update_status( $status );
|
||||
$request['status'] = $status; // Set the request status so parent::save_object() doesn't set it to the default 'pending' status.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters an object before it is inserted via the REST API.
|
||||
*
|
||||
* The dynamic portion of the hook name, `$this->post_type`,
|
||||
* refers to the object type slug.
|
||||
*
|
||||
* @param WC_Subscription $subscription The subscription object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @param bool $creating If is creating a new object.
|
||||
*/
|
||||
return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}_object", $subscription, $request, $creating );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds additional item schema information for subscription requests.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
// If this is a request for a subscription's orders, return the subscription orders schema.
|
||||
if ( $this->request instanceof WP_REST_Request && preg_match( "#/{$this->rest_base}/(?P<id>[\d]+)/orders#", $this->request->get_route() ) ) {
|
||||
return $this->get_subscription_orders_schema();
|
||||
}
|
||||
|
||||
$schema = parent::get_item_schema();
|
||||
|
||||
// Base order schema overrides.
|
||||
$schema['properties']['status']['description'] = __( 'Subscription status.', 'woocommerce-subscriptions' );
|
||||
$schema['properties']['status']['enum'] = $this->get_order_statuses();
|
||||
|
||||
$schema['properties']['created_via']['description'] = __( 'Where the subscription was created.', 'woocommerce-subscriptions' );
|
||||
$schema['properties']['currency']['description'] = __( 'Currency the subscription was created with, in ISO format.', 'woocommerce-subscriptions' );
|
||||
$schema['properties']['date_created']['description'] = __( "The date the subscription was created, in the site's timezone.", 'woocommerce-subscriptions' );
|
||||
$schema['properties']['date_created_gmt']['description'] = __( 'The date the subscription was created, as GMT.', 'woocommerce-subscriptions' );
|
||||
$schema['properties']['date_modified']['description'] = __( "The date the subscription was last modified, in the site's timezone.", 'woocommerce-subscriptions' );
|
||||
$schema['properties']['date_modified_gmt']['description'] = __( 'The date the subscription was last modified, as GMT.', 'woocommerce-subscriptions' );
|
||||
$schema['properties']['customer_id']['description'] = __( 'User ID who owns the subscription.', 'woocommerce-subscriptions' );
|
||||
|
||||
unset( $schema['properties']['transaction_id'] );
|
||||
unset( $schema['properties']['refunds'] );
|
||||
unset( $schema['properties']['set_paid'] );
|
||||
unset( $schema['properties']['cart_hash'] );
|
||||
|
||||
// Add subscription schema.
|
||||
$schema['properties'] += [
|
||||
'transition_status' => [
|
||||
'description' => __( 'The status to transition a subscription to.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'edit' ],
|
||||
'enum' => $this->get_order_statuses(),
|
||||
],
|
||||
'billing_interval' => [
|
||||
'description' => __( 'The number of billing periods between subscription renewals.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'integer',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'billing_period' => [
|
||||
'description' => __( 'Billing period for the subscription.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'string',
|
||||
'enum' => array_keys( wcs_get_subscription_period_strings() ),
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'payment_details' => [
|
||||
'description' => __( 'Subscription payment details.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'object',
|
||||
'context' => [ 'edit' ],
|
||||
'properties' => [
|
||||
'post_meta' => [
|
||||
'description' => __( 'Payment method meta and token in a post_meta_key: token format.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'object',
|
||||
'context' => [ 'edit' ],
|
||||
],
|
||||
'user_meta' => [
|
||||
'description' => __( 'Payment method meta and token in a user_meta_key : token format.', 'woocommerce-subscriptions' ),
|
||||
'type' => 'object',
|
||||
'context' => [ 'view' ],
|
||||
],
|
||||
],
|
||||
],
|
||||
'start_date' => [
|
||||
'description' => __( "The subscription's start date, as GMT.", 'woocommerce-subscriptions' ),
|
||||
'type' => 'date-time',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'trial_end_date' => [
|
||||
'description' => __( "The subscription's trial end date, as GMT.", 'woocommerce-subscriptions' ),
|
||||
'type' => 'date-time',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'next_payment_date' => [
|
||||
'description' => __( "The subscription's next payment date, as GMT.", 'woocommerce-subscriptions' ),
|
||||
'type' => 'date-time',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'cancelled_date' => [
|
||||
'description' => __( "The subscription's cancelled date, as GMT.", 'woocommerce-subscriptions' ),
|
||||
'type' => 'date-time',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'end_date' => [
|
||||
'description' => __( "The subscription's end date, as GMT.", 'woocommerce-subscriptions' ),
|
||||
'type' => 'date-time',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
];
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query params for collections.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$params = parent::get_collection_params();
|
||||
|
||||
// Override the base order status description to be subscription specific.
|
||||
$params['status']['description'] = __( 'Limit result set to subscriptions which have specific statuses.', 'woocommerce-subscriptions' );
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an object's links to include in the response.
|
||||
*
|
||||
* Because this class also handles retrieving order data, we need
|
||||
* to edit the links generated so the correct REST API href is included
|
||||
* when its generated for an order.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @param WC_Data $object Object data.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*
|
||||
* @return array Links for the given object.
|
||||
*/
|
||||
protected function prepare_links( $object, $request ) {
|
||||
$links = parent::prepare_links( $object, $request );
|
||||
|
||||
if ( isset( $links['self'] ) && wcs_is_order( $object ) ) {
|
||||
$links['self'] = [
|
||||
'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, 'orders', $object->get_id() ) ),
|
||||
];
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a subscription's payment method and meta from data provided in a REST API request.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @param WC_Subscription $subscription The subscription to update.
|
||||
* @param string $payment_method The ID of the payment method to set.
|
||||
* @param array $payment_meta The payment method meta.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function update_payment_method( $subscription, $payment_method, $payment_meta ) {
|
||||
$updating_subscription = (bool) $subscription->get_id();
|
||||
|
||||
try {
|
||||
if ( $updating_subscription && ! array_key_exists( $payment_method, WCS_Change_Payment_Method_Admin::get_valid_payment_methods( $subscription ) ) ) {
|
||||
// translators: placeholder is the payment method ID.
|
||||
throw new Exception( sprintf( __( 'The %s payment gateway does not support admin changing the payment method.', 'woocommerce-subscriptions' ), $payment_method ) );
|
||||
}
|
||||
|
||||
// Format the payment meta in the way payment gateways expect so it can be validated.
|
||||
$payment_method_meta = [];
|
||||
|
||||
foreach ( $payment_meta as $table => $meta ) {
|
||||
foreach ( $meta as $meta_key => $value ) {
|
||||
$payment_method_meta[ $table ][ $meta_key ] = [ 'value' => $value ];
|
||||
}
|
||||
}
|
||||
|
||||
$subscription->set_payment_method( $payment_method, $payment_method_meta );
|
||||
} catch ( Exception $e ) {
|
||||
$subscription->set_payment_method();
|
||||
$subscription->save();
|
||||
// translators: 1$: gateway id, 2$: error message
|
||||
throw new WC_REST_Exception( 'woocommerce_rest_invalid_payment_data', sprintf( __( 'Subscription payment method could not be set to %1$s with error message: %2$s', 'woocommerce-subscriptions' ), $payment_method, $e->getMessage() ), 400 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates subscriptions from an order.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return array Subscriptions created from the order.
|
||||
*/
|
||||
public function create_subscriptions_from_order( $request ) {
|
||||
$order_id = absint( $request->get_param( 'id' ) );
|
||||
|
||||
if ( empty( $order_id ) ) {
|
||||
return new WP_Error( 'woocommerce_rest_order_invalid_id', __( 'Invalid order ID.', 'woocommerce-subscriptions' ), [ 'status' => 404 ] );
|
||||
}
|
||||
|
||||
$order = wc_get_order( $order_id );
|
||||
|
||||
if ( ! $order || ! wcs_is_order( $order ) ) {
|
||||
return new WP_Error( 'woocommerce_rest_order_invalid_id', sprintf( __( 'Failed to load order object with the ID %d.', 'woocommerce-subscriptions' ), $order_id ), [ 'status' => 404 ] );
|
||||
}
|
||||
|
||||
if ( ! $order->get_customer_id() ) {
|
||||
return new WP_Error( 'woocommerce_rest_invalid_order', __( 'Order does not have a customer associated with it. Subscriptions require a customer.', 'woocommerce-subscriptions' ), [ 'status' => 404 ] );
|
||||
}
|
||||
|
||||
if ( wcs_order_contains_subscription( $order, 'any' ) ) {
|
||||
return new WP_Error( 'woocommerce_rest_invalid_order', __( 'Order already has subscriptions associated with it.', 'woocommerce-subscriptions' ), [ 'status' => 404 ] );
|
||||
}
|
||||
|
||||
$subscription_groups = [];
|
||||
$subscriptions = [];
|
||||
|
||||
// Group the order items into subscription groups.
|
||||
foreach ( $order->get_items() as $item ) {
|
||||
$product = $item->get_product();
|
||||
|
||||
if ( ! WC_Subscriptions_Product::is_subscription( $product ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$subscription_groups[ wcs_get_subscription_item_grouping_key( $item ) ][] = $item;
|
||||
}
|
||||
|
||||
// Return a 204 if there are no subscriptions to be created.
|
||||
if ( empty( $subscription_groups ) ) {
|
||||
$response = rest_ensure_response( $subscriptions );
|
||||
$response->set_status( 204 );
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start creating any subscriptions start transaction if available.
|
||||
*
|
||||
* To ensure data integrity, if any subscription fails to be created, the transaction will be rolled back. This will enable
|
||||
* the client to resubmit the request without having to worry about duplicate subscriptions being created.
|
||||
*/
|
||||
$transaction = new WCS_SQL_Transaction();
|
||||
$transaction->start();
|
||||
|
||||
try {
|
||||
// Create subscriptions.
|
||||
foreach ( $subscription_groups as $items ) {
|
||||
// Get the first item in the group to use as the base for the subscription.
|
||||
$product = $items[0]->get_product();
|
||||
$start_date = wcs_get_datetime_utc_string( $order->get_date_created( 'edit' ) );
|
||||
$subscription = wcs_create_subscription( [
|
||||
'order_id' => $order_id,
|
||||
'created_via' => 'rest-api',
|
||||
'start_date' => $start_date,
|
||||
'status' => $order->is_paid() ? 'active' : 'pending',
|
||||
'billing_period' => WC_Subscriptions_Product::get_period( $product ),
|
||||
'billing_interval' => WC_Subscriptions_Product::get_interval( $product ),
|
||||
'customer_note' => $order->get_customer_note(),
|
||||
] );
|
||||
|
||||
if ( is_wp_error( $subscription ) ) {
|
||||
throw new Exception( $subscription->get_error_message() );
|
||||
}
|
||||
|
||||
wcs_copy_order_address( $order, $subscription );
|
||||
|
||||
$subscription->update_dates(
|
||||
[
|
||||
'trial_end' => WC_Subscriptions_Product::get_trial_expiration_date( $product, $start_date ),
|
||||
'next_payment' => WC_Subscriptions_Product::get_first_renewal_payment_date( $product, $start_date ),
|
||||
'end' => WC_Subscriptions_Product::get_expiration_date( $product, $start_date ),
|
||||
]
|
||||
);
|
||||
|
||||
$subscription->set_payment_method( $order->get_payment_method() );
|
||||
|
||||
wcs_copy_order_meta( $order, $subscription, 'subscription' );
|
||||
|
||||
// Add items.
|
||||
$subscription_needs_shipping = false;
|
||||
foreach ( $items as $item ) {
|
||||
// Create order line item.
|
||||
$item_id = wc_add_order_item(
|
||||
$subscription->get_id(),
|
||||
[
|
||||
'order_item_name' => $item->get_name(),
|
||||
'order_item_type' => $item->get_type(),
|
||||
]
|
||||
);
|
||||
|
||||
$subscription_item = $subscription->get_item( $item_id );
|
||||
|
||||
wcs_copy_order_item( $item, $subscription_item );
|
||||
|
||||
// Don't include sign-up fees or $0 trial periods when setting the subscriptions item totals.
|
||||
wcs_set_recurring_item_total( $subscription_item );
|
||||
|
||||
$subscription_item->save();
|
||||
|
||||
// Check if this subscription will need shipping.
|
||||
if ( ! $subscription_needs_shipping ) {
|
||||
$product = $item->get_product();
|
||||
|
||||
if ( $product ) {
|
||||
$subscription_needs_shipping = $product->needs_shipping() && ! WC_Subscriptions_Product::needs_one_time_shipping( $product );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add coupons.
|
||||
foreach ( $order->get_coupons() as $coupon_item ) {
|
||||
$coupon = new WC_Coupon( $coupon_item->get_code() );
|
||||
|
||||
try {
|
||||
// validate_subscription_coupon_for_order will throw an exception if the coupon cannot be applied to the subscription.
|
||||
WC_Subscriptions_Coupon::validate_subscription_coupon_for_order( true, $coupon, $subscription );
|
||||
|
||||
$subscription->apply_coupon( $coupon->get_code() );
|
||||
} catch ( Exception $e ) {
|
||||
// Do nothing. The coupon will not be applied to the subscription.
|
||||
}
|
||||
}
|
||||
|
||||
// Add shipping.
|
||||
if ( $subscription_needs_shipping ) {
|
||||
foreach ( $order->get_shipping_methods() as $shipping_item ) {
|
||||
$rate = new WC_Shipping_Rate( $shipping_item->get_method_id(), $shipping_item->get_method_title(), $shipping_item->get_total(), $shipping_item->get_taxes(), $shipping_item->get_instance_id() );
|
||||
|
||||
$item = new WC_Order_Item_Shipping();
|
||||
$item->set_order_id( $subscription->get_id() );
|
||||
$item->set_shipping_rate( $rate );
|
||||
|
||||
$subscription->add_item( $item );
|
||||
}
|
||||
}
|
||||
|
||||
// Add fees.
|
||||
foreach ( $order->get_fees() as $fee_item ) {
|
||||
if ( ! apply_filters( 'wcs_should_copy_fee_item_to_subscription', true, $fee_item, $subscription, $order ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item = new WC_Order_Item_Fee();
|
||||
$item->set_props(
|
||||
[
|
||||
'name' => $fee_item->get_name(),
|
||||
'tax_class' => $fee_item->get_tax_class(),
|
||||
'amount' => $fee_item->get_amount(),
|
||||
'total' => $fee_item->get_total(),
|
||||
'total_tax' => $fee_item->get_total_tax(),
|
||||
'taxes' => $fee_item->get_taxes(),
|
||||
]
|
||||
);
|
||||
|
||||
$subscription->add_item( $item );
|
||||
}
|
||||
|
||||
/*
|
||||
* Fetch a fresh instance of the subscription because the current instance has an empty line item cache generated before we had copied the line items.
|
||||
* Fetching a new instance will ensure the line items are used when calculating totals.
|
||||
*/
|
||||
$subscription = wcs_get_subscription( $subscription->get_id() );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
throw new Exception( __( 'There was a problem completing this request. The subscription may have been deleted by another process.', 'woocommerce-subscriptions' ) );
|
||||
}
|
||||
|
||||
$subscription->calculate_totals();
|
||||
|
||||
/**
|
||||
* Fires after a single subscription is created or updated via the REST API.
|
||||
*
|
||||
* @param WC_Subscription $object Inserted subscription.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @param boolean $creating True when creating object, false when updating.
|
||||
*/
|
||||
do_action( "woocommerce_rest_insert_{$this->post_type}_object", $subscription, $request, true );
|
||||
|
||||
$fresh_subscription = wcs_get_subscription( $subscription->get_id() );
|
||||
|
||||
if ( ! $fresh_subscription ) {
|
||||
throw new Exception( __( 'There was a problem completing this request. The subscription may have been deleted by another process.', 'woocommerce-subscriptions' ) );
|
||||
}
|
||||
|
||||
$response = $this->prepare_object_for_response( $fresh_subscription, $request );
|
||||
$subscriptions[] = $this->prepare_response_for_collection( $response );
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
$transaction->rollback();
|
||||
return new WP_Error( 'woocommerce_rest_invalid_subscription_data', $e->getMessage(), [ 'status' => 404 ] );
|
||||
}
|
||||
|
||||
// If we got here, the subscription was created without problems
|
||||
$transaction->commit();
|
||||
|
||||
return rest_ensure_response( $subscriptions );
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscriptions statuses schema, conforming to JSON Schema.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_statuses_schema() {
|
||||
$schema = [
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'shop_subscription statuses', // Use a unique title for the schema so that CLI commands aren't overridden.
|
||||
'type' => 'object',
|
||||
'properties' => [],
|
||||
];
|
||||
|
||||
// Add the subscription statuses to the schema.
|
||||
foreach ( wcs_get_subscription_statuses() as $status => $status_name ) {
|
||||
$schema['properties'][ $status ] = [
|
||||
'type' => 'string',
|
||||
'description' => sprintf( __( 'Subscription status: %s', 'woocommerce-subscription' ), $status_name ),
|
||||
];
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscriptions orders schema, conforming to JSON Schema.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_subscription_orders_schema() {
|
||||
$schema = parent::get_item_schema(); // Fetch the order schema.
|
||||
$schema['title'] = 'shop_subscription orders'; // Use a unique title for the schema so that CLI commands aren't overridden.
|
||||
$schema['properties']['order_type'] = [
|
||||
'type' => 'string',
|
||||
'description' => __( 'The type of order related to the subscription.', 'woocommerce-subscriptions' ),
|
||||
];
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscriptions schema, conforming to JSON Schema.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function create_subscriptions_from_order_schema() {
|
||||
$schema = $this->get_public_item_schema();
|
||||
$schema['title'] = 'shop_order subscriptions'; // Use a unique title for the schema so that CLI commands aren't overridden and we can target this endpoint specifically.
|
||||
|
||||
return $schema;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
<?php
|
||||
|
||||
class WC_Order_Item_Pending_Switch extends WC_Order_Item_Product {
|
||||
public function get_type() {
|
||||
return 'line_item_pending_switch';
|
||||
}
|
||||
}
|
||||
|
|
@ -1,181 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Subscriptions Address Class
|
||||
*
|
||||
* Hooks into WooCommerce to handle editing addresses for subscriptions (by editing the original order for the subscription)
|
||||
*
|
||||
* @package WooCommerce Subscriptions
|
||||
* @subpackage WC_Subscriptions_Addresses
|
||||
* @category Class
|
||||
* @author Brent Shepherd
|
||||
* @since 1.3
|
||||
*/
|
||||
class WC_Subscriptions_Addresses {
|
||||
|
||||
/**
|
||||
* Bootstraps the class and hooks required actions & filters.
|
||||
*
|
||||
* @since 1.3
|
||||
*/
|
||||
public static function init() {
|
||||
|
||||
add_filter( 'wcs_view_subscription_actions', __CLASS__ . '::add_edit_address_subscription_action', 10, 2 );
|
||||
|
||||
add_action( 'woocommerce_after_edit_address_form_billing', __CLASS__ . '::maybe_add_edit_address_checkbox', 10 );
|
||||
add_action( 'woocommerce_after_edit_address_form_shipping', __CLASS__ . '::maybe_add_edit_address_checkbox', 10 );
|
||||
|
||||
add_action( 'woocommerce_customer_save_address', __CLASS__ . '::maybe_update_subscription_addresses', 10, 2 );
|
||||
|
||||
add_filter( 'woocommerce_address_to_edit', __CLASS__ . '::maybe_populate_subscription_addresses', 10 );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a "Change Shipping Address" button to the "My Subscriptions" table for those subscriptions
|
||||
* which require shipping.
|
||||
*
|
||||
* @param array $all_actions The $subscription_id => $actions array with all actions that will be displayed for a subscription on the "My Subscriptions" table
|
||||
* @param array $subscriptions All of a given users subscriptions that will be displayed on the "My Subscriptions" table
|
||||
* @since 1.3
|
||||
*/
|
||||
public static function add_edit_address_subscription_action( $actions, $subscription ) {
|
||||
|
||||
if ( $subscription->needs_shipping_address() && $subscription->has_status( array( 'active', 'on-hold' ) ) ) {
|
||||
$actions['change_address'] = array(
|
||||
'url' => add_query_arg( array( 'subscription' => $subscription->get_id() ), wc_get_endpoint_url( 'edit-address', 'shipping' ) ),
|
||||
'name' => __( 'Change Address', 'woocommerce-subscriptions' ),
|
||||
);
|
||||
}
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the necessary markup on the "My Account" > "Edit Address" page for editing a single subscription's
|
||||
* address or to check if the customer wants to update the addresses for all of their subscriptions.
|
||||
*
|
||||
* If editing their default shipping address, this function adds a checkbox to the to allow subscribers to
|
||||
* also update the address on their active subscriptions. If editing a single subscription's address, the
|
||||
* subscription key is added as a hidden field.
|
||||
*
|
||||
* @since 1.3
|
||||
*/
|
||||
public static function maybe_add_edit_address_checkbox() {
|
||||
global $wp;
|
||||
|
||||
if ( wcs_user_has_subscription() ) {
|
||||
|
||||
if ( isset( $_GET['subscription'] ) ) {
|
||||
|
||||
echo '<p>' . esc_html__( 'Both the shipping address used for the subscription and your default shipping address for future purchases will be updated.', 'woocommerce-subscriptions' ) . '</p>';
|
||||
|
||||
echo '<input type="hidden" name="update_subscription_address" value="' . absint( $_GET['subscription'] ) . '" id="update_subscription_address" />';
|
||||
|
||||
} elseif ( ( ( isset( $wp->query_vars['edit-address'] ) && ! empty( $wp->query_vars['edit-address'] ) ) || isset( $_GET['address'] ) ) ) {
|
||||
|
||||
if ( isset( $wp->query_vars['edit-address'] ) ) {
|
||||
$address_type = esc_attr( $wp->query_vars['edit-address'] ) . ' ';
|
||||
} else {
|
||||
$address_type = ( ! isset( $_GET['address'] ) ) ? esc_attr( $_GET['address'] ) . ' ' : '';
|
||||
}
|
||||
|
||||
// translators: $1: address type (Shipping Address / Billing Address), $2: opening <strong> tag, $3: closing </strong> tag
|
||||
$label = sprintf( __( 'Update the %1$s used for %2$sall%3$s of my active subscriptions', 'woocommerce-subscriptions' ), wcs_get_address_type_to_display( $address_type ), '<strong>', '</strong>' );
|
||||
|
||||
woocommerce_form_field( 'update_all_subscriptions_addresses', array(
|
||||
'type' => 'checkbox',
|
||||
'class' => array( 'form-row-wide' ),
|
||||
'label' => $label,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
wp_nonce_field( 'wcs_edit_address', '_wcsnonce' );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When a subscriber's billing or shipping address is successfully updated, check if the subscriber
|
||||
* has also requested to update the addresses on existing subscriptions and if so, go ahead and update
|
||||
* the addresses on the initial order for each subscription.
|
||||
*
|
||||
* @param int $user_id The ID of a user who own's the subscription (and address)
|
||||
* @since 1.3
|
||||
*/
|
||||
public static function maybe_update_subscription_addresses( $user_id, $address_type ) {
|
||||
|
||||
if ( ! wcs_user_has_subscription( $user_id ) || wc_notice_count( 'error' ) > 0 || empty( $_POST['_wcsnonce'] ) || ! wp_verify_nonce( $_POST['_wcsnonce'], 'wcs_edit_address' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$address_type = ( 'billing' == $address_type || 'shipping' == $address_type ) ? $address_type : '';
|
||||
$address_fields = WC()->countries->get_address_fields( esc_attr( $_POST[ $address_type . '_country' ] ), $address_type . '_' );
|
||||
$address = array();
|
||||
|
||||
foreach ( $address_fields as $key => $field ) {
|
||||
if ( isset( $_POST[ $key ] ) ) {
|
||||
$address[ str_replace( $address_type . '_', '', $key ) ] = wc_clean( $_POST[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $_POST['update_all_subscriptions_addresses'] ) ) {
|
||||
|
||||
$users_subscriptions = wcs_get_users_subscriptions( $user_id );
|
||||
|
||||
foreach ( $users_subscriptions as $subscription ) {
|
||||
if ( $subscription->has_status( array( 'active', 'on-hold' ) ) ) {
|
||||
$subscription->set_address( $address, $address_type );
|
||||
}
|
||||
}
|
||||
} elseif ( isset( $_POST['update_subscription_address'] ) ) {
|
||||
|
||||
$subscription = wcs_get_subscription( intval( $_POST['update_subscription_address'] ) );
|
||||
|
||||
// Update the address only if the user actually owns the subscription
|
||||
if ( ! empty( $subscription ) ) {
|
||||
$subscription->set_address( $address, $address_type );
|
||||
}
|
||||
|
||||
wp_safe_redirect( $subscription->get_view_order_url() );
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepopulate the address fields on a subscription item
|
||||
*
|
||||
* @param array $address A WooCommerce address array
|
||||
* @since 1.5
|
||||
*/
|
||||
public static function maybe_populate_subscription_addresses( $address ) {
|
||||
|
||||
if ( isset( $_GET['subscription'] ) ) {
|
||||
$subscription = wcs_get_subscription( absint( $_GET['subscription'] ) );
|
||||
|
||||
foreach ( array_keys( $address ) as $key ) {
|
||||
|
||||
$function_name = 'get_' . $key;
|
||||
|
||||
if ( is_callable( array( $subscription, $function_name ) ) ) {
|
||||
$address[ $key ]['value'] = $subscription->$function_name();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the address fields on an order
|
||||
*
|
||||
* @param array $subscription A WooCommerce Subscription array
|
||||
* @param array $address_fields Locale aware address fields of the form returned by WC_Countries->get_address_fields() for a given country
|
||||
* @since 1.3
|
||||
*/
|
||||
public static function maybe_update_order_address( $subscription, $address_fields ) {
|
||||
_deprecated_function( __METHOD__, '2.0', 'WC_Order::set_address() or WC_Subscription::set_address()' );
|
||||
}
|
||||
}
|
||||
|
||||
WC_Subscriptions_Addresses::init();
|
||||
|
|
@ -1,630 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Make it possible for customers to change the payment gateway used for an existing subscription.
|
||||
*
|
||||
* @package WooCommerce Subscriptions
|
||||
* @subpackage WC_Subscriptions_Change_Payment_Gateway
|
||||
* @category Class
|
||||
* @author Brent Shepherd
|
||||
* @since 1.4
|
||||
*/
|
||||
class WC_Subscriptions_Change_Payment_Gateway {
|
||||
|
||||
public static $is_request_to_change_payment = false;
|
||||
|
||||
private static $woocommerce_messages = array();
|
||||
|
||||
private static $woocommerce_errors = array();
|
||||
|
||||
private static $original_order_dates = array();
|
||||
|
||||
/**
|
||||
* Bootstraps the class and hooks required actions & filters.
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
public static function init() {
|
||||
|
||||
// Maybe allow for a recurring payment method to be changed
|
||||
add_action( 'plugins_loaded', __CLASS__ . '::set_change_payment_method_flag' );
|
||||
|
||||
// Attach hooks which depend on WooCommerce constants
|
||||
add_action( 'woocommerce_loaded', __CLASS__ . '::attach_dependant_hooks' );
|
||||
|
||||
// Keep a record of any messages or errors that should be displayed
|
||||
add_action( 'before_woocommerce_pay', __CLASS__ . '::store_pay_shortcode_mesages', 100 );
|
||||
|
||||
// Hijack the default pay shortcode
|
||||
add_action( 'after_woocommerce_pay', __CLASS__ . '::maybe_replace_pay_shortcode', 100 );
|
||||
|
||||
// Maybe allow for a recurring payment method to be changed
|
||||
add_filter( 'wcs_view_subscription_actions', __CLASS__ . '::change_payment_method_button', 10, 2 );
|
||||
|
||||
// Maybe allow for a recurring payment method to be changed
|
||||
add_action( 'wp_loaded', __CLASS__ . '::change_payment_method_via_pay_shortcode', 20 );
|
||||
|
||||
// Filter the available payment gateways to only show those which support acting as the new payment method
|
||||
add_filter( 'woocommerce_available_payment_gateways', __CLASS__ . '::get_available_payment_gateways' );
|
||||
|
||||
// If we're changing the payment method, we want to make sure a number of totals return $0 (to prevent payments being processed now)
|
||||
add_filter( 'woocommerce_subscriptions_total_initial_payment', __CLASS__ . '::maybe_zero_total', 11, 2 );
|
||||
add_filter( 'woocommerce_subscriptions_sign_up_fee', __CLASS__ . '::maybe_zero_total', 11, 2 );
|
||||
|
||||
// Redirect to My Account page after changing payment method
|
||||
add_filter( 'woocommerce_get_return_url', __CLASS__ . '::get_return_url', 11 );
|
||||
|
||||
// Update the recurring payment method when a customer has completed the payment for a renewal payment which previously failed
|
||||
add_action( 'woocommerce_subscriptions_paid_for_failed_renewal_order', __CLASS__ . '::change_failing_payment_method', 10, 2 );
|
||||
|
||||
// Add a 'new-payment-method' handler to the WC_Subscription::can_be_updated_to() function
|
||||
add_filter( 'woocommerce_can_subscription_be_updated_to_new-payment-method', __CLASS__ . '::can_subscription_be_updated_to_new_payment_method', 10, 2 );
|
||||
|
||||
// Change the "Pay for Order" page title to "Change Payment Method"
|
||||
add_filter( 'the_title', __CLASS__ . '::change_payment_method_page_title', 100 );
|
||||
|
||||
// Maybe filter subscriptions_needs_payment to return false when processing change-payment-gateway requests
|
||||
add_filter( 'woocommerce_subscription_needs_payment', __CLASS__ . '::maybe_override_needs_payment', 10, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach WooCommerce version dependent hooks
|
||||
*
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public static function attach_dependant_hooks() {
|
||||
|
||||
if ( WC_Subscriptions::is_woocommerce_pre( '3.0' ) ) {
|
||||
|
||||
// If we're changing the payment method, we want to make sure a number of totals return $0 (to prevent payments being processed now)
|
||||
add_filter( 'woocommerce_order_amount_total', __CLASS__ . '::maybe_zero_total', 11, 2 );
|
||||
|
||||
} else {
|
||||
|
||||
// If we're changing the payment method, we want to make sure a number of totals return $0 (to prevent payments being processed now)
|
||||
add_filter( 'woocommerce_subscription_get_total', __CLASS__ . '::maybe_zero_total', 11, 2 );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a flag to indicate that the current request is for changing payment. Better than requiring other extensions
|
||||
* to check the $_GET global as it allows for the flag to be overridden.
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
public static function set_change_payment_method_flag() {
|
||||
if ( isset( $_GET['change_payment_method'] ) ) {
|
||||
self::$is_request_to_change_payment = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store any messages or errors added by other plugins, particularly important for those occasions when the new payment
|
||||
* method caused and error or failure.
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
public static function store_pay_shortcode_mesages() {
|
||||
|
||||
if ( wc_notice_count( 'notice' ) > 0 ) {
|
||||
self::$woocommerce_messages = wc_get_notices( 'success' );
|
||||
self::$woocommerce_messages += wc_get_notices( 'notice' );
|
||||
}
|
||||
|
||||
if ( wc_notice_count( 'error' ) > 0 ) {
|
||||
self::$woocommerce_errors = wc_get_notices( 'error' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If requesting a payment method change, replace the woocommerce_pay_shortcode() with a change payment form.
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
public static function maybe_replace_pay_shortcode() {
|
||||
global $wp;
|
||||
$valid_request = false;
|
||||
|
||||
// if the request to pay for the order belongs to a subscription but there's no GET params for changing payment method, show receipt page.
|
||||
if ( ! self::$is_request_to_change_payment && isset( $wp->query_vars['order-pay'] ) && wcs_is_subscription( absint( $wp->query_vars['order-pay'] ) ) ) {
|
||||
|
||||
$valid_request = true;
|
||||
|
||||
ob_clean();
|
||||
|
||||
do_action( 'before_woocommerce_pay' );
|
||||
|
||||
$subscription_key = isset( $_GET['key'] ) ? wc_clean( $_GET['key'] ) : '';
|
||||
$subscription = wcs_get_subscription( absint( $wp->query_vars['order-pay'] ) );
|
||||
|
||||
if ( $subscription->get_id() == absint( $wp->query_vars['order-pay'] ) && $subscription->get_order_key() == $subscription_key ) {
|
||||
|
||||
?>
|
||||
<div class="woocommerce">
|
||||
<ul class="order_details">
|
||||
<li class="order">
|
||||
<?php
|
||||
// translators: placeholder is the subscription order number wrapped in <strong> tags
|
||||
echo wp_kses( sprintf( esc_html__( 'Subscription Number: %s', 'woocommerce-subscriptions' ), '<strong>' . esc_html( $subscription->get_order_number() ) . '</strong>' ), array( 'strong' => true ) );
|
||||
?>
|
||||
</li>
|
||||
<li class="date">
|
||||
<?php
|
||||
// translators: placeholder is the subscription's next payment date (either human readable or normal date) wrapped in <strong> tags
|
||||
echo wp_kses( sprintf( esc_html__( 'Next Payment Date: %s', 'woocommerce-subscriptions' ), '<strong>' . esc_html( $subscription->get_date_to_display( 'next_payment' ) ) . '</strong>' ), array( 'strong' => true ) );
|
||||
?>
|
||||
</li>
|
||||
<li class="total">
|
||||
<?php
|
||||
// translators: placeholder is the formatted total to be paid for the subscription wrapped in <strong> tags
|
||||
echo wp_kses_post( sprintf( esc_html__( 'Total: %s', 'woocommerce-subscriptions' ), '<strong>' . $subscription->get_formatted_order_total() . '</strong>' ) );
|
||||
?>
|
||||
</li>
|
||||
<?php if ( $subscription->get_payment_method_title() ) : ?>
|
||||
<li class="method">
|
||||
<?php
|
||||
// translators: placeholder is the display name of the payment method
|
||||
echo wp_kses( sprintf( esc_html__( 'Payment Method: %s', 'woocommerce-subscriptions' ), '<strong>' . esc_html( $subscription->get_payment_method_to_display() ) . '</strong>' ), array( 'strong' => true ) );
|
||||
?>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
|
||||
<?php do_action( 'woocommerce_receipt_' . $subscription->get_payment_method(), $subscription->get_id() ); ?>
|
||||
|
||||
<div class="clear"></div>
|
||||
<?php
|
||||
|
||||
} else {
|
||||
wc_add_notice( __( 'Sorry, this subscription change payment method request is invalid and cannot be processed.', 'woocommerce-subscriptions' ), 'error' );
|
||||
}
|
||||
|
||||
wc_print_notices();
|
||||
|
||||
} elseif ( ! self::$is_request_to_change_payment ) {
|
||||
return;
|
||||
|
||||
} else {
|
||||
|
||||
ob_clean();
|
||||
|
||||
do_action( 'before_woocommerce_pay' );
|
||||
|
||||
echo '<div class="woocommerce">';
|
||||
|
||||
if ( ! empty( self::$woocommerce_errors ) ) {
|
||||
foreach ( self::$woocommerce_errors as $error ) {
|
||||
WC_Subscriptions::add_notice( $error, 'error' );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( self::$woocommerce_messages ) ) {
|
||||
foreach ( self::$woocommerce_messages as $message ) {
|
||||
WC_Subscriptions::add_notice( $message, 'success' );
|
||||
}
|
||||
}
|
||||
|
||||
$subscription = wcs_get_subscription( absint( $_GET['change_payment_method'] ) );
|
||||
|
||||
if ( wp_verify_nonce( $_GET['_wpnonce'] ) === false ) {
|
||||
|
||||
WC_Subscriptions::add_notice( __( 'There was an error with your request. Please try again.', 'woocommerce-subscriptions' ), 'error' );
|
||||
|
||||
} elseif ( empty( $subscription ) ) {
|
||||
|
||||
WC_Subscriptions::add_notice( __( 'Invalid Subscription.', 'woocommerce-subscriptions' ), 'error' );
|
||||
|
||||
} elseif ( ! current_user_can( 'edit_shop_subscription_payment_method', $subscription->get_id() ) ) {
|
||||
|
||||
WC_Subscriptions::add_notice( __( 'That doesn\'t appear to be one of your subscriptions.', 'woocommerce-subscriptions' ), 'error' );
|
||||
|
||||
} elseif ( ! $subscription->can_be_updated_to( 'new-payment-method' ) ) {
|
||||
|
||||
WC_Subscriptions::add_notice( __( 'The payment method can not be changed for that subscription.', 'woocommerce-subscriptions' ), 'error' );
|
||||
|
||||
} else {
|
||||
|
||||
if ( $subscription->get_time( 'next_payment' ) > 0 ) {
|
||||
// translators: placeholder is next payment's date
|
||||
$next_payment_string = sprintf( __( ' Next payment is due %s.', 'woocommerce-subscriptions' ), $subscription->get_date_to_display( 'next_payment' ) );
|
||||
} else {
|
||||
$next_payment_string = '';
|
||||
}
|
||||
|
||||
// translators: placeholder is either empty or "Next payment is due..."
|
||||
WC_Subscriptions::add_notice( sprintf( __( 'Choose a new payment method.%s', 'woocommerce-subscriptions' ), $next_payment_string ), 'notice' );
|
||||
WC_Subscriptions::print_notices();
|
||||
|
||||
if ( $subscription->get_order_key() == $_GET['key'] ) {
|
||||
|
||||
$subscription_billing_country = $subscription->get_billing_country();
|
||||
$subscription_billing_state = $subscription->get_billing_state();
|
||||
$subscription_billing_postcode = $subscription->get_billing_postcode();
|
||||
|
||||
// Set customer location to order location
|
||||
if ( $subscription_billing_country ) {
|
||||
$setter = is_callable( array( WC()->customer, 'set_billing_country' ) ) ? 'set_billing_country' : 'set_country';
|
||||
WC()->customer->$setter( $subscription_billing_country );
|
||||
}
|
||||
if ( $subscription_billing_state ) {
|
||||
$setter = is_callable( array( WC()->customer, 'set_billing_state' ) ) ? 'set_billing_state' : 'set_state';
|
||||
WC()->customer->$setter( $subscription_billing_state );
|
||||
}
|
||||
if ( $subscription_billing_postcode ) {
|
||||
$setter = is_callable( array( WC()->customer, 'set_billing_postcode' ) ) ? 'set_billing_postcode' : 'set_postcode';
|
||||
WC()->customer->$setter( $subscription_billing_postcode );
|
||||
}
|
||||
|
||||
wc_get_template( 'checkout/form-change-payment-method.php', array( 'subscription' => $subscription ), '', plugin_dir_path( WC_Subscriptions::$plugin_file ) . 'templates/' );
|
||||
|
||||
$valid_request = true;
|
||||
|
||||
} else {
|
||||
|
||||
WC_Subscriptions::add_notice( __( 'Invalid order.', 'woocommerce-subscriptions' ), 'error' );
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( false === $valid_request ) {
|
||||
WC_Subscriptions::print_notices();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a "Change Payment Method" button to the "My Subscriptions" table.
|
||||
*
|
||||
* @param array $all_actions The $subscription_key => $actions array with all actions that will be displayed for a subscription on the "My Subscriptions" table
|
||||
* @param array $subscriptions All of a given users subscriptions that will be displayed on the "My Subscriptions" table
|
||||
* @since 1.4
|
||||
*/
|
||||
public static function change_payment_method_button( $actions, $subscription ) {
|
||||
|
||||
if ( $subscription->can_be_updated_to( 'new-payment-method' ) ) {
|
||||
|
||||
$actions['change_payment_method'] = array(
|
||||
'url' => wp_nonce_url( add_query_arg( array( 'change_payment_method' => $subscription->get_id() ), $subscription->get_checkout_payment_url() ) ),
|
||||
'name' => _x( 'Change Payment', 'label on button, imperative', 'woocommerce-subscriptions' ),
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the change payment form.
|
||||
*
|
||||
* Based on the @see woocommerce_pay_action() function.
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
* @since 1.4
|
||||
*/
|
||||
public static function change_payment_method_via_pay_shortcode() {
|
||||
|
||||
if ( isset( $_POST['_wcsnonce'] ) && wp_verify_nonce( $_POST['_wcsnonce'], 'wcs_change_payment_method' ) ) {
|
||||
|
||||
$subscription = wcs_get_subscription( absint( $_POST['woocommerce_change_payment'] ) );
|
||||
|
||||
do_action( 'woocommerce_subscription_change_payment_method_via_pay_shortcode', $subscription );
|
||||
|
||||
ob_start();
|
||||
|
||||
if ( $subscription->get_order_key() == $_GET['key'] ) {
|
||||
|
||||
$subscription_billing_country = $subscription->get_billing_country();
|
||||
$subscription_billing_state = $subscription->get_billing_state();
|
||||
$subscription_billing_postcode = $subscription->get_billing_postcode();
|
||||
$subscription_billing_city = $subscription->get_billing_postcode();
|
||||
|
||||
// Set customer location to order location
|
||||
if ( $subscription_billing_country ) {
|
||||
$setter = is_callable( array( WC()->customer, 'set_billing_country' ) ) ? 'set_billing_country' : 'set_country';
|
||||
WC()->customer->$setter( $subscription_billing_country );
|
||||
}
|
||||
if ( $subscription_billing_state ) {
|
||||
$setter = is_callable( array( WC()->customer, 'set_billing_state' ) ) ? 'set_billing_state' : 'set_state';
|
||||
WC()->customer->$setter( $subscription_billing_state );
|
||||
}
|
||||
if ( $subscription_billing_postcode ) {
|
||||
$setter = is_callable( array( WC()->customer, 'set_billing_postcode' ) ) ? 'set_billing_postcode' : 'set_postcode';
|
||||
WC()->customer->$setter( $subscription_billing_postcode );
|
||||
}
|
||||
if ( $subscription_billing_city ) {
|
||||
$setter = is_callable( array( WC()->customer, 'set_billing_city' ) ) ? 'set_billing_city' : 'set_city';
|
||||
WC()->customer->$setter( $subscription_billing_city );
|
||||
}
|
||||
|
||||
// Update payment method
|
||||
$new_payment_method = wc_clean( $_POST['payment_method'] );
|
||||
|
||||
// Allow some payment gateways which can't process the payment immediately, like PayPal, to do it later after the payment/sign-up is confirmed
|
||||
if ( apply_filters( 'woocommerce_subscriptions_update_payment_via_pay_shortcode', true, $new_payment_method, $subscription ) ) {
|
||||
self::update_payment_method( $subscription, $new_payment_method );
|
||||
}
|
||||
|
||||
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
|
||||
|
||||
// Validate
|
||||
$available_gateways[ $new_payment_method ]->validate_fields();
|
||||
|
||||
// Process payment for the new method (with a $0 order total)
|
||||
if ( wc_notice_count( 'error' ) == 0 ) {
|
||||
|
||||
$result = $available_gateways[ $new_payment_method ]->process_payment( $subscription->get_id() );
|
||||
|
||||
if ( 'success' == $result['result'] && wc_get_page_permalink( 'myaccount' ) == $result['redirect'] ) {
|
||||
$result['redirect'] = $subscription->get_view_order_url();
|
||||
}
|
||||
|
||||
$result = apply_filters( 'woocommerce_subscriptions_process_payment_for_change_method_via_pay_shortcode', $result, $subscription );
|
||||
|
||||
// Redirect to success/confirmation/payment page
|
||||
if ( 'success' == $result['result'] ) {
|
||||
WC_Subscriptions::add_notice( __( 'Payment method updated.', 'woocommerce-subscriptions' ), 'success' );
|
||||
wp_redirect( $result['redirect'] );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the recurring payment method on a subscription order.
|
||||
*
|
||||
* @param array $available_gateways The payment gateways which are currently being allowed.
|
||||
* @since 1.4
|
||||
*/
|
||||
public static function update_payment_method( $subscription, $new_payment_method ) {
|
||||
|
||||
$old_payment_method = $subscription->get_payment_method();
|
||||
$old_payment_method_title = $subscription->get_payment_method_title();
|
||||
$available_gateways = WC()->payment_gateways->get_available_payment_gateways(); // Also inits all payment gateways to make sure that hooks are attached correctly
|
||||
|
||||
do_action( 'woocommerce_subscriptions_pre_update_payment_method', $subscription, $new_payment_method, $old_payment_method );
|
||||
|
||||
// Make sure the subscription is cancelled with the current gateway
|
||||
WC_Subscriptions_Payment_Gateways::trigger_gateway_status_updated_hook( $subscription, 'cancelled' );
|
||||
|
||||
// Update meta
|
||||
update_post_meta( $subscription->get_id(), '_old_payment_method', $old_payment_method );
|
||||
update_post_meta( $subscription->get_id(), '_payment_method', $new_payment_method );
|
||||
|
||||
if ( isset( $available_gateways[ $new_payment_method ] ) ) {
|
||||
$new_payment_method_title = $available_gateways[ $new_payment_method ]->get_title();
|
||||
} else {
|
||||
$new_payment_method_title = '';
|
||||
}
|
||||
|
||||
update_post_meta( $subscription->get_id(), '_old_payment_method_title', $old_payment_method_title );
|
||||
update_post_meta( $subscription->get_id(), '_payment_method_title', $new_payment_method_title );
|
||||
|
||||
if ( empty( $old_payment_method_title ) ) {
|
||||
$old_payment_method_title = $old_payment_method;
|
||||
}
|
||||
|
||||
if ( empty( $new_payment_method_title ) ) {
|
||||
$new_payment_method_title = $new_payment_method;
|
||||
}
|
||||
|
||||
// Log change on order
|
||||
$subscription->add_order_note( sprintf( _x( 'Payment method changed from "%1$s" to "%2$s" by the subscriber from their account page.', '%1$s: old payment title, %2$s: new payment title', 'woocommerce-subscriptions' ), $old_payment_method_title, $new_payment_method_title ) );
|
||||
|
||||
do_action( 'woocommerce_subscription_payment_method_updated', $subscription, $new_payment_method, $old_payment_method );
|
||||
do_action( 'woocommerce_subscription_payment_method_updated_to_' . $new_payment_method, $subscription, $old_payment_method );
|
||||
do_action( 'woocommerce_subscription_payment_method_updated_from_' . $old_payment_method, $subscription, $new_payment_method );
|
||||
}
|
||||
|
||||
/**
|
||||
* Only display gateways which support changing payment method when paying for a failed renewal order or
|
||||
* when requesting to change the payment method.
|
||||
*
|
||||
* @param array $available_gateways The payment gateways which are currently being allowed.
|
||||
* @since 1.4
|
||||
*/
|
||||
public static function get_available_payment_gateways( $available_gateways ) {
|
||||
|
||||
if ( isset( $_GET['change_payment_method'] ) || wcs_cart_contains_failed_renewal_order_payment() ) {
|
||||
foreach ( $available_gateways as $gateway_id => $gateway ) {
|
||||
if ( true !== $gateway->supports( 'subscription_payment_method_change_customer' ) ) {
|
||||
unset( $available_gateways[ $gateway_id ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $available_gateways;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure certain totals are set to 0 when the request is to change the payment method without charging anything.
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
public static function maybe_zero_total( $total, $subscription ) {
|
||||
global $wp;
|
||||
|
||||
if ( ! empty( $_POST['_wcsnonce'] ) && wp_verify_nonce( $_POST['_wcsnonce'], 'wcs_change_payment_method' ) && isset( $_POST['woocommerce_change_payment'] ) && $subscription->get_order_key() == $_GET['key'] && $subscription->get_id() == absint( $_POST['woocommerce_change_payment'] ) ) {
|
||||
$total = 0;
|
||||
} elseif ( ! self::$is_request_to_change_payment && isset( $wp->query_vars['order-pay'] ) && wcs_is_subscription( absint( $wp->query_vars['order-pay'] ) ) ) {
|
||||
// if the request to pay for the order belongs to a subscription but there's no GET params for changing payment method, the receipt page is being used to collect credit card details so we still need to $0 the total
|
||||
$total = 0;
|
||||
}
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect back to the "My Account" page instead of the "Thank You" page after changing the payment method.
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
public static function get_return_url( $return_url ) {
|
||||
|
||||
if ( ! empty( $_POST['_wcsnonce'] ) && wp_verify_nonce( $_POST['_wcsnonce'], 'wcs_change_payment_method' ) && isset( $_POST['woocommerce_change_payment'] ) ) {
|
||||
$return_url = get_permalink( wc_get_page_id( 'myaccount' ) );
|
||||
}
|
||||
|
||||
return $return_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the recurring payment method for a subscription after a customer has paid for a failed renewal order
|
||||
* (which usually failed because of an issue with the existing payment, like an expired card or token).
|
||||
*
|
||||
* Also trigger a hook for payment gateways to update any meta on the original order for a subscription.
|
||||
*
|
||||
* @param WC_Order $renewal_order The order which recorded the successful payment (to make up for the failed automatic payment).
|
||||
* @param WC_Order $original_order The original order in which the subscription was purchased.
|
||||
* @since 1.4
|
||||
*/
|
||||
public static function change_failing_payment_method( $renewal_order, $subscription ) {
|
||||
|
||||
if ( ! $subscription->is_manual() ) {
|
||||
|
||||
if ( ! empty( $_POST['_wcsnonce'] ) && wp_verify_nonce( $_POST['_wcsnonce'], 'wcs_change_payment_method' ) && isset( $_POST['payment_method'] ) ) {
|
||||
$new_payment_method = wc_clean( $_POST['payment_method'] );
|
||||
} else {
|
||||
$new_payment_method = wcs_get_objects_property( $renewal_order, 'payment_method' );
|
||||
}
|
||||
|
||||
self::update_payment_method( $subscription, $new_payment_method );
|
||||
|
||||
do_action( 'woocommerce_subscription_failing_payment_method_updated', $subscription, $renewal_order );
|
||||
do_action( 'woocommerce_subscription_failing_payment_method_updated_' . $new_payment_method, $subscription, $renewal_order );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a 'new-payment-method' handler to the @see WC_Subscription::can_be_updated_to() function
|
||||
* to determine whether the recurring payment method on a subscription can be changed.
|
||||
*
|
||||
* For the recurring payment method to be changeable, the subscription must be active, have future (automatic) payments
|
||||
* and use a payment gateway which allows the subscription to be cancelled.
|
||||
*
|
||||
* @param bool $subscription_can_be_changed Flag of whether the subscription can be changed to
|
||||
* @param string $new_status_or_meta The status or meta data you want to change th subscription to. Can be 'active', 'on-hold', 'cancelled', 'expired', 'trash', 'deleted', 'failed', 'new-payment-date' or some other value attached to the 'woocommerce_can_subscription_be_changed_to' filter.
|
||||
* @param object $args Set of values used in @see WC_Subscriptions_Manager::can_subscription_be_changed_to() for determining if a subscription can be changes, include:
|
||||
* 'subscription_key' string A subscription key of the form created by @see WC_Subscriptions_Manager::get_subscription_key()
|
||||
* 'subscription' array Subscription of the form returned by @see WC_Subscriptions_Manager::get_subscription()
|
||||
* 'user_id' int The ID of the subscriber.
|
||||
* 'order' WC_Order The order which recorded the successful payment (to make up for the failed automatic payment).
|
||||
* 'payment_gateway' WC_Payment_Gateway The subscription's recurring payment gateway
|
||||
* 'order_uses_manual_payments' bool A boolean flag indicating whether the subscription requires manual renewal payment.
|
||||
* @since 1.4
|
||||
*/
|
||||
public static function can_subscription_be_updated_to_new_payment_method( $subscription_can_be_changed, $subscription ) {
|
||||
|
||||
if ( WC_Subscriptions_Payment_Gateways::one_gateway_supports( 'subscription_payment_method_change_customer' ) && $subscription->get_time( 'next_payment' ) > 0 && ! $subscription->is_manual() && $subscription->payment_method_supports( 'subscription_cancellation' ) && $subscription->has_status( 'active' ) ) {
|
||||
$subscription_can_be_changed = true;
|
||||
} else {
|
||||
$subscription_can_be_changed = false;
|
||||
}
|
||||
|
||||
return $subscription_can_be_changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a page title with the endpoint title
|
||||
*
|
||||
* @param string $title
|
||||
* @return string
|
||||
* @since 2.0
|
||||
*/
|
||||
public static function change_payment_method_page_title( $title ) {
|
||||
|
||||
if ( is_main_query() && in_the_loop() && is_page() && is_checkout_pay_page() && self::$is_request_to_change_payment ) {
|
||||
$title = _x( 'Change Payment Method', 'the page title of the change payment method form', 'woocommerce-subscriptions' );
|
||||
}
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* When processing a change_payment_method request on a subscription that has a failed or pending renewal,
|
||||
* we don't want the `$order->needs_payment()` check inside WC_Shortcode_Checkout::order_pay() to pass.
|
||||
* This is causing `$gateway->payment_fields()` to be called multiple times.
|
||||
*
|
||||
* @param bool $needs_payment
|
||||
* @param WC_Subscription $subscription
|
||||
* @return bool
|
||||
* @since 2.0.7
|
||||
*/
|
||||
public static function maybe_override_needs_payment( $needs_payment ) {
|
||||
|
||||
if ( $needs_payment && self::$is_request_to_change_payment ) {
|
||||
$needs_payment = false;
|
||||
}
|
||||
|
||||
return $needs_payment;
|
||||
}
|
||||
|
||||
/** Deprecated Functions **/
|
||||
|
||||
/**
|
||||
* Update the recurring payment method on a subscription order.
|
||||
*
|
||||
* @param array $available_gateways The payment gateways which are currently being allowed.
|
||||
* @since 1.4
|
||||
* @deprecated 2.0
|
||||
*/
|
||||
public static function update_recurring_payment_method( $subscription_key, $order, $new_payment_method ) {
|
||||
_deprecated_function( __METHOD__, '2.0', __CLASS__ . '::update_payment_method()' );
|
||||
self::update_payment_method( wcs_get_subscription_from_key( $subscription_key ), $new_payment_method );
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep a record of an order's dates if we're marking it as completed during a request to change the payment method.
|
||||
*
|
||||
* Deprecated as we now operate on a WC_Subscription object instead of the parent order, so we don't need to hack around date changes.
|
||||
*
|
||||
* @since 1.4
|
||||
* @deprecated 2.0
|
||||
*/
|
||||
public static function store_original_order_dates( $new_order_status, $subscription_id ) {
|
||||
_deprecated_function( __METHOD__, '2.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore an order's dates if we marked it as completed during a request to change the payment method.
|
||||
*
|
||||
* Deprecated as we now operate on a WC_Subscription object instead of the parent order, so we don't need to hack around date changes.
|
||||
*
|
||||
* @since 1.4
|
||||
* @deprecated 2.0
|
||||
*/
|
||||
public static function restore_original_order_dates( $order_id ) {
|
||||
_deprecated_function( __METHOD__, '2.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a 'new-payment-method' handler to the @see WC_Subscription::can_be_updated_to() function
|
||||
* to determine whether the recurring payment method on a subscription can be changed.
|
||||
*
|
||||
* For the recurring payment method to be changeable, the subscription must be active, have future (automatic) payments
|
||||
* and use a payment gateway which allows the subscription to be cancelled.
|
||||
*
|
||||
* @param bool $subscription_can_be_changed Flag of whether the subscription can be changed to
|
||||
* @param string $new_status_or_meta The status or meta data you want to change th subscription to. Can be 'active', 'on-hold', 'cancelled', 'expired', 'trash', 'deleted', 'failed', 'new-payment-date' or some other value attached to the 'woocommerce_can_subscription_be_changed_to' filter.
|
||||
* @param object $args Set of values used in @see WC_Subscriptions_Manager::can_subscription_be_changed_to() for determining if a subscription can be changes, include:
|
||||
* 'subscription_key' string A subscription key of the form created by @see WC_Subscriptions_Manager::get_subscription_key()
|
||||
* 'subscription' array Subscription of the form returned by @see WC_Subscriptions_Manager::get_subscription()
|
||||
* 'user_id' int The ID of the subscriber.
|
||||
* 'order' WC_Order The order which recorded the successful payment (to make up for the failed automatic payment).
|
||||
* 'payment_gateway' WC_Payment_Gateway The subscription's recurring payment gateway
|
||||
* 'order_uses_manual_payments' bool A boolean flag indicating whether the subscription requires manual renewal payment.
|
||||
* @since 1.4
|
||||
*/
|
||||
public static function can_subscription_be_changed_to( $subscription_can_be_changed, $new_status_or_meta, $args ) {
|
||||
_deprecated_function( __METHOD__, '2.0', __CLASS__ . '::can_subscription_be_updated_to_new_payment_method()' );
|
||||
|
||||
if ( 'new-payment-method' === $new_status_or_meta ) {
|
||||
$subscription_can_be_changed = wcs_get_subscription_from_key( $args->subscription_key )->can_be_updated_to( 'new-payment-method' );
|
||||
}
|
||||
|
||||
return $subscription_can_be_changed;
|
||||
}
|
||||
}
|
||||
WC_Subscriptions_Change_Payment_Gateway::init();
|
||||
|
|
@ -1,493 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Subscriptions Checkout
|
||||
*
|
||||
* Extends the WooCommerce checkout class to add subscription meta on checkout.
|
||||
*
|
||||
* @package WooCommerce Subscriptions
|
||||
* @subpackage WC_Subscriptions_Checkout
|
||||
* @category Class
|
||||
* @author Brent Shepherd
|
||||
*/
|
||||
class WC_Subscriptions_Checkout {
|
||||
|
||||
private static $guest_checkout_option_changed = false;
|
||||
|
||||
/**
|
||||
* Bootstraps the class and hooks required actions & filters.
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public static function init() {
|
||||
|
||||
// We need to create subscriptions on checkout and want to do it after almost all other extensions have added their products/items/fees
|
||||
add_action( 'woocommerce_checkout_order_processed', __CLASS__ . '::process_checkout', 100, 2 );
|
||||
|
||||
// Make sure users can register on checkout (before any other hooks before checkout)
|
||||
add_action( 'woocommerce_before_checkout_form', __CLASS__ . '::make_checkout_registration_possible', -1 );
|
||||
|
||||
// Display account fields as required
|
||||
add_action( 'woocommerce_checkout_fields', __CLASS__ . '::make_checkout_account_fields_required', 10 );
|
||||
|
||||
// Restore the settings after switching them for the checkout form
|
||||
add_action( 'woocommerce_after_checkout_form', __CLASS__ . '::restore_checkout_registration_settings', 100 );
|
||||
|
||||
// Make sure guest checkout is not enabled in option param passed to WC JS
|
||||
add_filter( 'woocommerce_params', __CLASS__ . '::filter_woocommerce_script_paramaters', 10, 1 );
|
||||
add_filter( 'wc_checkout_params', __CLASS__ . '::filter_woocommerce_script_paramaters', 10, 1 );
|
||||
|
||||
// Force registration during checkout process
|
||||
add_action( 'woocommerce_before_checkout_process', __CLASS__ . '::force_registration_during_checkout', 10 );
|
||||
|
||||
// When a line item is added to a subscription on checkout, ensure the backorder data added by WC is removed
|
||||
add_action( 'woocommerce_checkout_create_order_line_item', __CLASS__ . '::remove_backorder_meta_from_subscription_line_item', 10, 4 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create subscriptions purchased on checkout.
|
||||
*
|
||||
* @param int $order_id The post_id of a shop_order post/WC_Order object
|
||||
* @param array $posted_data The data posted on checkout
|
||||
* @since 2.0
|
||||
*/
|
||||
public static function process_checkout( $order_id, $posted_data ) {
|
||||
|
||||
if ( ! WC_Subscriptions_Cart::cart_contains_subscription() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$order = new WC_Order( $order_id );
|
||||
|
||||
$subscriptions = array();
|
||||
|
||||
// First clear out any subscriptions created for a failed payment to give us a clean slate for creating new subscriptions
|
||||
$subscriptions = wcs_get_subscriptions_for_order( wcs_get_objects_property( $order, 'id' ), array( 'order_type' => 'parent' ) );
|
||||
|
||||
if ( ! empty( $subscriptions ) ) {
|
||||
remove_action( 'before_delete_post', 'WC_Subscriptions_Manager::maybe_cancel_subscription' );
|
||||
foreach ( $subscriptions as $subscription ) {
|
||||
wp_delete_post( $subscription->get_id() );
|
||||
}
|
||||
add_action( 'before_delete_post', 'WC_Subscriptions_Manager::maybe_cancel_subscription' );
|
||||
}
|
||||
|
||||
WC_Subscriptions_Cart::set_global_recurring_shipping_packages();
|
||||
|
||||
// Create new subscriptions for each group of subscription products in the cart (that is not a renewal)
|
||||
foreach ( WC()->cart->recurring_carts as $recurring_cart ) {
|
||||
|
||||
$subscription = self::create_subscription( $order, $recurring_cart, $posted_data ); // Exceptions are caught by WooCommerce
|
||||
|
||||
if ( is_wp_error( $subscription ) ) {
|
||||
throw new Exception( $subscription->get_error_message() );
|
||||
}
|
||||
|
||||
do_action( 'woocommerce_checkout_subscription_created', $subscription, $order, $recurring_cart );
|
||||
}
|
||||
|
||||
do_action( 'subscriptions_created_for_order', $order ); // Backward compatibility
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new subscription from a cart item on checkout.
|
||||
*
|
||||
* The function doesn't validate whether the cart item is a subscription product, meaning it can be used for any cart item,
|
||||
* but the item will need a `subscription_period` and `subscription_period_interval` value set on it, at a minimum.
|
||||
*
|
||||
* @param WC_Order $order
|
||||
* @param WC_Cart $cart
|
||||
* @since 2.0
|
||||
*/
|
||||
public static function create_subscription( $order, $cart, $posted_data ) {
|
||||
global $wpdb;
|
||||
|
||||
try {
|
||||
// Start transaction if available
|
||||
$wpdb->query( 'START TRANSACTION' );
|
||||
|
||||
// Set the recurring line totals on the subscription
|
||||
$variation_id = wcs_cart_pluck( $cart, 'variation_id' );
|
||||
$product_id = empty( $variation_id ) ? wcs_cart_pluck( $cart, 'product_id' ) : $variation_id;
|
||||
|
||||
$subscription = wcs_create_subscription( array(
|
||||
'start_date' => $cart->start_date,
|
||||
'order_id' => wcs_get_objects_property( $order, 'id' ),
|
||||
'customer_id' => $order->get_user_id(),
|
||||
'billing_period' => wcs_cart_pluck( $cart, 'subscription_period' ),
|
||||
'billing_interval' => wcs_cart_pluck( $cart, 'subscription_period_interval' ),
|
||||
'customer_note' => wcs_get_objects_property( $order, 'customer_note' ),
|
||||
) );
|
||||
|
||||
if ( is_wp_error( $subscription ) ) {
|
||||
throw new Exception( $subscription->get_error_message() );
|
||||
}
|
||||
|
||||
// Set the subscription's billing and shipping address
|
||||
$subscription = wcs_copy_order_address( $order, $subscription );
|
||||
|
||||
$subscription->update_dates( array(
|
||||
'trial_end' => $cart->trial_end_date,
|
||||
'next_payment' => $cart->next_payment_date,
|
||||
'end' => $cart->end_date,
|
||||
) );
|
||||
|
||||
// Store trial period for PayPal
|
||||
if ( wcs_cart_pluck( $cart, 'subscription_trial_length' ) > 0 ) {
|
||||
$subscription->set_trial_period( wcs_cart_pluck( $cart, 'subscription_trial_period' ) );
|
||||
}
|
||||
|
||||
// Set the payment method on the subscription
|
||||
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
|
||||
$order_payment_method = wcs_get_objects_property( $order, 'payment_method' );
|
||||
|
||||
if ( $cart->needs_payment() && isset( $available_gateways[ $order_payment_method ] ) ) {
|
||||
$subscription->set_payment_method( $available_gateways[ $order_payment_method ] );
|
||||
}
|
||||
|
||||
if ( ! $cart->needs_payment() || 'yes' == get_option( WC_Subscriptions_Admin::$option_prefix . '_turn_off_automatic_payments', 'no' ) ) {
|
||||
$subscription->set_requires_manual_renewal( true );
|
||||
} elseif ( ! isset( $available_gateways[ $order_payment_method ] ) || ! $available_gateways[ $order_payment_method ]->supports( 'subscriptions' ) ) {
|
||||
$subscription->set_requires_manual_renewal( true );
|
||||
}
|
||||
|
||||
wcs_copy_order_meta( $order, $subscription, 'subscription' );
|
||||
|
||||
// Store the line items
|
||||
if ( is_callable( array( WC()->checkout, 'create_order_line_items' ) ) ) {
|
||||
WC()->checkout->create_order_line_items( $subscription, $cart );
|
||||
} else {
|
||||
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
|
||||
$item_id = self::add_cart_item( $subscription, $cart_item, $cart_item_key );
|
||||
}
|
||||
}
|
||||
|
||||
// Store fees (although no fees recur by default, extensions may add them)
|
||||
if ( is_callable( array( WC()->checkout, 'create_order_fee_lines' ) ) ) {
|
||||
WC()->checkout->create_order_fee_lines( $subscription, $cart );
|
||||
} else {
|
||||
foreach ( $cart->get_fees() as $fee_key => $fee ) {
|
||||
$item_id = $subscription->add_fee( $fee );
|
||||
|
||||
if ( ! $item_id ) {
|
||||
// translators: placeholder is an internal error number
|
||||
throw new Exception( sprintf( __( 'Error %d: Unable to create subscription. Please try again.', 'woocommerce-subscriptions' ), 403 ) );
|
||||
}
|
||||
|
||||
// Allow plugins to add order item meta to fees
|
||||
do_action( 'woocommerce_add_order_fee_meta', $subscription->get_id(), $item_id, $fee, $fee_key );
|
||||
}
|
||||
}
|
||||
|
||||
self::add_shipping( $subscription, $cart );
|
||||
|
||||
// Store tax rows
|
||||
if ( is_callable( array( WC()->checkout, 'create_order_tax_lines' ) ) ) {
|
||||
WC()->checkout->create_order_tax_lines( $subscription, $cart );
|
||||
} else {
|
||||
foreach ( array_keys( $cart->taxes + $cart->shipping_taxes ) as $tax_rate_id ) {
|
||||
if ( $tax_rate_id && ! $subscription->add_tax( $tax_rate_id, $cart->get_tax_amount( $tax_rate_id ), $cart->get_shipping_tax_amount( $tax_rate_id ) ) && apply_filters( 'woocommerce_cart_remove_taxes_zero_rate_id', 'zero-rated' ) !== $tax_rate_id ) {
|
||||
// translators: placeholder is an internal error number
|
||||
throw new Exception( sprintf( __( 'Error %d: Unable to add tax to subscription. Please try again.', 'woocommerce-subscriptions' ), 405 ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store coupons
|
||||
if ( is_callable( array( WC()->checkout, 'create_order_coupon_lines' ) ) ) {
|
||||
WC()->checkout->create_order_coupon_lines( $subscription, $cart );
|
||||
} else {
|
||||
foreach ( $cart->get_coupons() as $code => $coupon ) {
|
||||
if ( ! $subscription->add_coupon( $code, $cart->get_coupon_discount_amount( $code ), $cart->get_coupon_discount_tax_amount( $code ) ) ) {
|
||||
// translators: placeholder is an internal error number
|
||||
throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-subscriptions' ), 406 ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the recurring totals on the subscription
|
||||
$subscription->set_shipping_total( $cart->shipping_total );
|
||||
$subscription->set_discount_total( $cart->get_cart_discount_total() );
|
||||
$subscription->set_discount_tax( $cart->get_cart_discount_tax_total() );
|
||||
$subscription->set_cart_tax( $cart->tax_total );
|
||||
$subscription->set_shipping_tax( $cart->shipping_tax_total );
|
||||
$subscription->set_total( $cart->total );
|
||||
|
||||
// Hook to adjust subscriptions before saving with WC 3.0+ (matches WC 3.0's new 'woocommerce_checkout_create_order' hook)
|
||||
do_action( 'woocommerce_checkout_create_subscription', $subscription, $posted_data );
|
||||
|
||||
// Save the subscription if using WC 3.0 & CRUD
|
||||
$subscription->save();
|
||||
|
||||
// If we got here, the subscription was created without problems
|
||||
$wpdb->query( 'COMMIT' );
|
||||
|
||||
} catch ( Exception $e ) {
|
||||
// There was an error adding the subscription
|
||||
$wpdb->query( 'ROLLBACK' );
|
||||
return new WP_Error( 'checkout-error', $e->getMessage() );
|
||||
}
|
||||
|
||||
return $subscription;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stores shipping info on the subscription
|
||||
*
|
||||
* @param WC_Subscription $subscription instance of a subscriptions object
|
||||
* @param WC_Cart $cart A cart with recurring items in it
|
||||
*/
|
||||
public static function add_shipping( $subscription, $cart ) {
|
||||
|
||||
// We need to make sure we only get recurring shipping packages
|
||||
WC_Subscriptions_Cart::set_calculation_type( 'recurring_total' );
|
||||
|
||||
foreach ( $cart->get_shipping_packages() as $package_index => $base_package ) {
|
||||
|
||||
$package = WC_Subscriptions_Cart::get_calculated_shipping_for_package( $base_package );
|
||||
|
||||
$recurring_shipping_package_key = WC_Subscriptions_Cart::get_recurring_shipping_package_key( $cart->recurring_cart_key, $package_index );
|
||||
|
||||
$shipping_method_id = isset( WC()->checkout()->shipping_methods[ $package_index ] ) ? WC()->checkout()->shipping_methods[ $package_index ] : '';
|
||||
|
||||
if ( isset( WC()->checkout()->shipping_methods[ $recurring_shipping_package_key ] ) ) {
|
||||
$shipping_method_id = WC()->checkout()->shipping_methods[ $recurring_shipping_package_key ];
|
||||
$package_key = $recurring_shipping_package_key;
|
||||
} else {
|
||||
$package_key = $package_index;
|
||||
}
|
||||
|
||||
if ( isset( $package['rates'][ $shipping_method_id ] ) ) {
|
||||
|
||||
if ( WC_Subscriptions::is_woocommerce_pre( '3.0' ) ) {
|
||||
|
||||
$item_id = $subscription->add_shipping( $package['rates'][ $shipping_method_id ] );
|
||||
|
||||
// Allows plugins to add order item meta to shipping
|
||||
do_action( 'woocommerce_add_shipping_order_item', $subscription->get_id(), $item_id, $package_key );
|
||||
do_action( 'woocommerce_subscriptions_add_recurring_shipping_order_item', $subscription->get_id(), $item_id, $package_key );
|
||||
|
||||
} else { // WC 3.0+
|
||||
|
||||
$shipping_rate = $package['rates'][ $shipping_method_id ];
|
||||
$item = new WC_Order_Item_Shipping();
|
||||
$item->legacy_package_key = $package_key; // @deprecated For legacy actions.
|
||||
$item->set_props( array(
|
||||
'method_title' => $shipping_rate->label,
|
||||
'method_id' => $shipping_rate->id,
|
||||
'total' => wc_format_decimal( $shipping_rate->cost ),
|
||||
'taxes' => array( 'total' => $shipping_rate->taxes ),
|
||||
'order_id' => $subscription->get_id(),
|
||||
) );
|
||||
|
||||
foreach ( $shipping_rate->get_meta_data() as $key => $value ) {
|
||||
$item->add_meta_data( $key, $value, true );
|
||||
}
|
||||
|
||||
$subscription->add_item( $item );
|
||||
|
||||
$item->save(); // We need the item ID for old hooks, this can be removed once support for WC < 3.0 is dropped
|
||||
wc_do_deprecated_action( 'woocommerce_subscriptions_add_recurring_shipping_order_item', array( $subscription->get_id(), $item->get_id(), $package_key ), '2.2.0', 'CRUD and woocommerce_checkout_create_subscription_shipping_item action instead' );
|
||||
|
||||
do_action( 'woocommerce_checkout_create_order_shipping_item', $item, $package_key, $package ); // WC 3.0+ will also trigger the deprecated 'woocommerce_add_shipping_order_item' hook
|
||||
do_action( 'woocommerce_checkout_create_subscription_shipping_item', $item, $package_key, $package );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WC_Subscriptions_Cart::set_calculation_type( 'none' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the Backordered meta data from subscription line items added on the checkout.
|
||||
*
|
||||
* @param WC_Order_Item_Product $order_item
|
||||
* @param string $cart_item_key The hash used to identify the item in the cart
|
||||
* @param array $cart_item The cart item's data.
|
||||
* @param WC_Order|WC_Subscription $subscription The order or subscription object to which the line item relates
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public static function remove_backorder_meta_from_subscription_line_item( $item, $cart_item_key, $cart_item, $subscription ) {
|
||||
|
||||
if ( wcs_is_subscription( $subscription ) ) {
|
||||
$item->delete_meta_data( apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce-subscriptions' ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a cart item to a subscription.
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public static function add_cart_item( $subscription, $cart_item, $cart_item_key ) {
|
||||
if ( ! WC_Subscriptions::is_woocommerce_pre( '3.0' ) ) {
|
||||
_deprecated_function( __METHOD__, '2.2.0', 'WC_Checkout::create_order_line_items( $subscription, $cart )' );
|
||||
}
|
||||
|
||||
$item_id = $subscription->add_product(
|
||||
$cart_item['data'],
|
||||
$cart_item['quantity'],
|
||||
array(
|
||||
'variation' => $cart_item['variation'],
|
||||
'totals' => array(
|
||||
'subtotal' => $cart_item['line_subtotal'],
|
||||
'subtotal_tax' => $cart_item['line_subtotal_tax'],
|
||||
'total' => $cart_item['line_total'],
|
||||
'tax' => $cart_item['line_tax'],
|
||||
'tax_data' => $cart_item['line_tax_data'],
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! $item_id ) {
|
||||
// translators: placeholder is an internal error number
|
||||
throw new Exception( sprintf( __( 'Error %d: Unable to create subscription. Please try again.', 'woocommerce-subscriptions' ), 402 ) );
|
||||
}
|
||||
|
||||
$cart_item_product_id = ( 0 != $cart_item['variation_id'] ) ? $cart_item['variation_id'] : $cart_item['product_id'];
|
||||
|
||||
if ( WC_Subscriptions_Product::get_trial_length( wcs_get_canonical_product_id( $cart_item ) ) > 0 ) {
|
||||
wc_add_order_item_meta( $item_id, '_has_trial', 'true' );
|
||||
}
|
||||
|
||||
// Allow plugins to add order item meta
|
||||
if ( WC_Subscriptions::is_woocommerce_pre( '3.0' ) ) {
|
||||
do_action( 'woocommerce_add_order_item_meta', $item_id, $cart_item, $cart_item_key );
|
||||
do_action( 'woocommerce_add_subscription_item_meta', $item_id, $cart_item, $cart_item_key );
|
||||
} else {
|
||||
wc_do_deprecated_action( 'woocommerce_add_order_item_meta', array( $item_id, $cart_item, $cart_item_key ), '3.0', 'CRUD and woocommerce_checkout_create_order_line_item action instead' );
|
||||
wc_do_deprecated_action( 'woocommerce_add_subscription_item_meta', array( $item_id, $cart_item, $cart_item_key ), '3.0', 'CRUD and woocommerce_checkout_create_order_line_item action instead' );
|
||||
}
|
||||
|
||||
return $item_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* When a new order is inserted, add subscriptions related order meta.
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public static function add_order_meta( $order_id, $posted ) {
|
||||
_deprecated_function( __METHOD__, '2.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add each subscription product's details to an order so that the state of the subscription persists even when a product is changed
|
||||
*
|
||||
* @since 1.2.5
|
||||
*/
|
||||
public static function add_order_item_meta( $item_id, $values ) {
|
||||
_deprecated_function( __METHOD__, '2.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* If shopping cart contains subscriptions, make sure a user can register on the checkout page
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public static function make_checkout_registration_possible( $checkout = '' ) {
|
||||
|
||||
if ( WC_Subscriptions_Cart::cart_contains_subscription() && ! is_user_logged_in() ) {
|
||||
|
||||
// Make sure users are required to register an account
|
||||
if ( true === $checkout->enable_guest_checkout ) {
|
||||
$checkout->enable_guest_checkout = false;
|
||||
self::$guest_checkout_option_changed = true;
|
||||
|
||||
$checkout->must_create_account = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure account fields display the required "*" when they are required.
|
||||
*
|
||||
* @since 1.3.5
|
||||
*/
|
||||
public static function make_checkout_account_fields_required( $checkout_fields ) {
|
||||
|
||||
if ( WC_Subscriptions_Cart::cart_contains_subscription() && ! is_user_logged_in() ) {
|
||||
|
||||
$account_fields = array(
|
||||
'account_username',
|
||||
'account_password',
|
||||
'account_password-2',
|
||||
);
|
||||
|
||||
foreach ( $account_fields as $account_field ) {
|
||||
if ( isset( $checkout_fields['account'][ $account_field ] ) ) {
|
||||
$checkout_fields['account'][ $account_field ]['required'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $checkout_fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* After displaying the checkout form, restore the store's original registration settings.
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
public static function restore_checkout_registration_settings( $checkout = '' ) {
|
||||
|
||||
if ( self::$guest_checkout_option_changed ) {
|
||||
$checkout->enable_guest_checkout = true;
|
||||
if ( ! is_user_logged_in() ) { // Also changed must_create_account
|
||||
$checkout->must_create_account = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Also make sure the guest checkout option value passed to the woocommerce.js forces registration.
|
||||
* Otherwise the registration form is hidden by woocommerce.js.
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
public static function filter_woocommerce_script_paramaters( $woocommerce_params ) {
|
||||
|
||||
if ( WC_Subscriptions_Cart::cart_contains_subscription() && ! is_user_logged_in() && isset( $woocommerce_params['option_guest_checkout'] ) && 'yes' == $woocommerce_params['option_guest_checkout'] ) {
|
||||
$woocommerce_params['option_guest_checkout'] = 'no';
|
||||
}
|
||||
|
||||
return $woocommerce_params;
|
||||
}
|
||||
|
||||
/**
|
||||
* During the checkout process, force registration when the cart contains a subscription.
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
public static function force_registration_during_checkout( $woocommerce_params ) {
|
||||
|
||||
if ( WC_Subscriptions_Cart::cart_contains_subscription() && ! is_user_logged_in() ) {
|
||||
$_POST['createaccount'] = 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* When creating an order at checkout, if the checkout is to renew a subscription from a failed
|
||||
* payment, hijack the order creation to make a renewal order - not a plain WooCommerce order.
|
||||
*
|
||||
* @since 1.3
|
||||
* @deprecated 2.0
|
||||
*/
|
||||
public static function filter_woocommerce_create_order( $order_id, $checkout_object ) {
|
||||
_deprecated_function( __METHOD__, '2.0' );
|
||||
return $order_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Customise which actions are shown against a subscriptions order on the My Account page.
|
||||
*
|
||||
* @since 1.3
|
||||
*/
|
||||
public static function filter_woocommerce_my_account_my_orders_actions( $actions, $order ) {
|
||||
_deprecated_function( __METHOD__, '2.0', 'WCS_Cart_Renewal::filter_my_account_my_orders_actions()' );
|
||||
return $actions;
|
||||
}
|
||||
}
|
||||
|
||||
WC_Subscriptions_Checkout::init();
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
/**
|
||||
* WooCommerce Subscriptions CLI class.
|
||||
*
|
||||
* @since 6.3.0
|
||||
*/
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
|
||||
class WC_Subscriptions_CLI {
|
||||
|
||||
/**
|
||||
* Loads WooCommerce Subscriptions CLI related hooks.
|
||||
*/
|
||||
public function __construct() {
|
||||
WP_CLI::add_hook( 'before_invoke:wc shop_order subscriptions create', [ $this, 'abort_create_subscriptions_from_order' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an error when the `wc shop_order subscriptions create` WP CLI command is used.
|
||||
*
|
||||
* WooCommerce core adds WP CLI commands for each WC REST API endpoints beginning with /wc/v2. This means all of our subscription
|
||||
* REST API endpoints are added. While the `wc shop_order subscriptions create` CLI command technically works, WooCommerce doesn't have support for
|
||||
* batch creation via CLI and results in the success message not being displayed correctly.
|
||||
*
|
||||
* @param string $command The command name.
|
||||
*/
|
||||
public function abort_create_subscriptions_from_order( $command ) {
|
||||
WP_CLI::error( "The '{$command}' command isn't supported via WP CLI." );
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
<?php
|
||||
/**
|
||||
* WC_Subscriptions_Dependency_Manager class
|
||||
*
|
||||
* @package WooCommerce Subscriptions
|
||||
* @since 5.0.0
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class WC_Subscriptions_Dependency_Manager {
|
||||
|
||||
/**
|
||||
* The minimum supported WooCommerce version.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $minimum_supported_wc_version;
|
||||
|
||||
/**
|
||||
* @var string|null The active WooCommerce version, or null if WooCommerce is not active.
|
||||
*/
|
||||
private $wc_active_version = null;
|
||||
|
||||
/**
|
||||
* @var bool Whether the active WooCommerce version has been cached.
|
||||
*/
|
||||
private $wc_version_cached = false;
|
||||
|
||||
/**
|
||||
* @var boolean Whether to skip the class_exists and WC_VERSION constant checks.
|
||||
*/
|
||||
private $skip_class_exists_and_wc_version_constant_checks = false;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct( $minimum_supported_wc_version ) {
|
||||
$this->minimum_supported_wc_version = $minimum_supported_wc_version;
|
||||
/**
|
||||
* Filter allows to skip the class_exists and WC_VERSION constant checks.
|
||||
*
|
||||
* @since 7.8.0
|
||||
*
|
||||
* @param bool $use_class_exists Whether to use the class_exists and WC_VERSION constant checks.
|
||||
*
|
||||
* @return bool false to use the class_exists and WC_VERSION checks, true to skip them.
|
||||
*/
|
||||
if ( defined( 'WCS_ENVIRONMENT_TYPE' ) && WCS_ENVIRONMENT_TYPE === 'tests' && apply_filters( 'woocommerce_subscriptions_skip_class_exists_and_wc_version_constant_checks', false ) ) {
|
||||
$this->skip_class_exists_and_wc_version_constant_checks = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the required dependencies are met.
|
||||
*
|
||||
* @since 5.0.0
|
||||
* @return bool True if the required dependencies are met. Otherwise, false.
|
||||
*/
|
||||
public function has_valid_dependencies() {
|
||||
// We don't need to check is_woocommerce_active() here because is_woocommerce_version_supported() will return false if WooCommerce is not active.
|
||||
return $this->is_woocommerce_version_supported();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the WooCommerce plugin is active.
|
||||
*
|
||||
* @since 5.0.0
|
||||
* @return bool True if the plugin is active, false otherwise.
|
||||
*/
|
||||
public function is_woocommerce_active() {
|
||||
if ( class_exists( 'WooCommerce' ) && ! $this->skip_class_exists_and_wc_version_constant_checks ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->get_woocommerce_active_version() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the WooCommerce version is supported by Subscriptions.
|
||||
*
|
||||
* The minimum supported WooCommerce version is defined in the WC_Subscriptions::$wc_minimum_supported_version property.
|
||||
*
|
||||
* @return bool true if the WooCommerce version is supported, false otherwise.
|
||||
*/
|
||||
public function is_woocommerce_version_supported() {
|
||||
return version_compare(
|
||||
// In php8.2+ version_compare requires a string so ensure we always pass a string.
|
||||
// version_compare treats an empty string as less than 0.
|
||||
$this->get_woocommerce_active_version() ?? '',
|
||||
$this->minimum_supported_wc_version,
|
||||
'>='
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method detects the active version of WooCommerce.
|
||||
*
|
||||
* If the WC_VERSION constant is already defined, use that as a first preference.
|
||||
* If it's not defined, fetch the version based on the WooCommerce plugin data.
|
||||
*
|
||||
* The WooCommerce plugin is determined by this logic:
|
||||
* 1. Installed at 'woocommerce/woocommerce.php'
|
||||
* 2. Installed at any '{x}/woocommerce.php' where the plugin name is 'WooCommerce'
|
||||
*
|
||||
* @return string|null The active WooCommerce version, or null if WooCommerce is not active.
|
||||
*/
|
||||
private function get_woocommerce_active_version() {
|
||||
if ( defined( 'WC_VERSION' ) && ! $this->skip_class_exists_and_wc_version_constant_checks ) {
|
||||
return WC_VERSION;
|
||||
}
|
||||
|
||||
// Use a cached value to avoid calling get_plugins() and looping multiple times.
|
||||
if ( true === $this->wc_version_cached ) {
|
||||
return $this->wc_active_version;
|
||||
}
|
||||
|
||||
$this->wc_version_cached = true;
|
||||
|
||||
// Try to get version from transient first
|
||||
$this->wc_active_version = get_transient( 'wcs_woocommerce_active_version' );
|
||||
|
||||
if ( false !== $this->wc_active_version ) {
|
||||
return $this->wc_active_version;
|
||||
}
|
||||
|
||||
// Load plugin.php if it's not already loaded.
|
||||
if ( ! function_exists( 'is_plugin_active' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
||||
// Loop through all active plugins and check if WooCommerce is active.
|
||||
foreach ( get_plugins() as $plugin_slug => $plugin_data ) {
|
||||
$is_woocommerce = false;
|
||||
|
||||
/**
|
||||
* The WooCommerce plugin can be installed in two supported ways:
|
||||
* 1. Installed at 'woocommerce/woocommerce.php'
|
||||
* 2. Installed at any '{x}/woocommerce.php' where the plugin name is 'WooCommerce'
|
||||
*/
|
||||
if ( 'woocommerce/woocommerce.php' === $plugin_slug ) {
|
||||
$is_woocommerce = true;
|
||||
} elseif ( 'woocommerce.php' === basename( $plugin_slug ) && 'WooCommerce' === $plugin_data['Name'] ) {
|
||||
$is_woocommerce = true;
|
||||
}
|
||||
|
||||
if ( $is_woocommerce && is_plugin_active( $plugin_slug ) ) {
|
||||
$this->wc_active_version = $plugin_data['Version'];
|
||||
break; // Found it, no need to continue looping
|
||||
}
|
||||
}
|
||||
|
||||
// Cache the result in a transient for 1 hour
|
||||
if ( ! empty( $this->wc_active_version ) ) {
|
||||
set_transient( 'wcs_woocommerce_active_version', $this->wc_active_version, HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
return $this->wc_active_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an admin notice if the required dependencies are not met.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function display_dependency_admin_notice() {
|
||||
if ( ! current_user_can( 'activate_plugins' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$admin_notice_content = '';
|
||||
|
||||
if ( ! $this->is_woocommerce_active() ) {
|
||||
$install_url = wp_nonce_url(
|
||||
add_query_arg(
|
||||
array(
|
||||
'action' => 'install-plugin',
|
||||
'plugin' => 'woocommerce',
|
||||
),
|
||||
admin_url( 'update.php' )
|
||||
),
|
||||
'install-plugin_woocommerce'
|
||||
);
|
||||
|
||||
// translators: 1$-2$: opening and closing <strong> tags, 3$-4$: link tags, takes to woocommerce plugin on wp.org, 5$-6$: opening and closing link tags, leads to plugins.php in admin
|
||||
$admin_notice_content = sprintf( esc_html__( '%1$sWooCommerce Subscriptions is inactive.%2$s The %3$sWooCommerce plugin%4$s must be active for WooCommerce Subscriptions to work. Please %5$sinstall & activate WooCommerce »%6$s', 'woocommerce-subscriptions' ), '<strong>', '</strong>', '<a href="http://wordpress.org/extend/plugins/woocommerce/">', '</a>', '<a href="' . esc_url( $install_url ) . '">', '</a>' );
|
||||
} elseif ( ! $this->is_woocommerce_version_supported() ) {
|
||||
// translators: 1$-2$: opening and closing <strong> tags, 3$: minimum supported WooCommerce version, 4$-5$: opening and closing link tags, leads to plugin admin
|
||||
$admin_notice_content = sprintf( esc_html__( '%1$sWooCommerce Subscriptions is inactive.%2$s This version of Subscriptions requires WooCommerce %3$s or newer. Please %4$supdate WooCommerce to version %3$s or newer »%5$s', 'woocommerce-subscriptions' ), '<strong>', '</strong>', $this->minimum_supported_wc_version, '<a href="' . esc_url( admin_url( 'plugins.php' ) ) . '">', '</a>' );
|
||||
}
|
||||
|
||||
if ( $admin_notice_content ) {
|
||||
echo '<div class="error">';
|
||||
echo '<p>' . wp_kses_post( $admin_notice_content ) . '</p>';
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||