Compare commits

..

No commits in common. "main" and "2.3.2" have entirely different histories.
main ... 2.3.2

697 changed files with 42429 additions and 95168 deletions

View File

@ -1,15 +0,0 @@
# 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

View File

@ -1,293 +0,0 @@
<?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
)
);
}

View File

@ -1,31 +0,0 @@
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 }}

View File

@ -1,21 +0,0 @@
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

77
assets/css/about.css Normal file → Executable file
View File

@ -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: #7F54B3;
position: relative;;
background: #9c5d90;
text-rendering: optimizeLegibility;
padding-top: 150px;
height: 52px;
@ -35,13 +35,13 @@ ul {
font-weight: 600;
font-size: 14px;
text-align: center;
color: #fff;
color: #ddc8d9;
margin: 5px 0 0 0;
-webkit-box-shadow: 0 1px 3px rgba( 0, 0, 0, 0.2 );
box-shadow: 0 1px 3px rgba( 0, 0, 0, 0.2 );
-webkit-box-shadow: 0 1px 3px rgba(0,0,0,.2);
box-shadow: 0 1px 3px rgba(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,17 +92,40 @@ ul {
}
.woocommerce-message {
position: relative;
border-left-color: #7F54B3 !important;
border-left-color: #cc99c2!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: 0.7;
opacity: .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;
}
@ -112,7 +135,7 @@ p.woocommerce-actions {
}
.woocommerce-about-text {
margin-bottom: 1em !important;
margin-bottom: 1em!important;
}
.about-wrap .feature-section.three-col .col {
@ -134,13 +157,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;
@ -155,12 +178,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 {
@ -172,4 +195,4 @@ p.woocommerce-actions {
width: 100% !important;
float: none !important;
}
}
}

View File

@ -1,26 +0,0 @@
.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;
}

View File

@ -1,49 +0,0 @@
/*
* 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;
}

764
assets/css/admin.css Normal file → Executable file

File diff suppressed because it is too large Load Diff

11
assets/css/checkout.css Normal file → Executable file
View File

@ -6,7 +6,7 @@
}
.shipping.recurring-total ul li {
margin: 0;
padding: 0.25em 0 0.25em 22px;
padding: .25em 0 .25em 22px;
text-indent: -22px;
list-style: none outside;
}
@ -20,11 +20,8 @@
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:"";
}

62
assets/css/dashboard.css Normal file → Executable file
View File

@ -2,67 +2,15 @@
Subscriptions 2.1 Dashboard Stats
------------------------------------------------------------------------------*/
#woocommerce_dashboard_status .wc_status_list li.signup-count a:before {
font-family: WooCommerce;
content: '\e014';
color: #59cf8a;
content: "\e02c";
color: #5da5da;
}
#woocommerce_dashboard_status .wc_status_list li.renewal-count a:before {
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;
content: "\e02c";
color: #f29ec4;
}
#woocommerce_dashboard_status .wc_status_list li.signup-count {
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%;
}
border-right: 1px solid #ececec;
}

View File

@ -1,67 +0,0 @@
.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;
}

View File

@ -1,140 +0,0 @@
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%;
}
}

142
assets/css/view-subscription.css Normal file → Executable file
View File

@ -1,146 +1,6 @@
@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%;
font-size: 1.4em;
max-width: 200px;
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;
}
}

14
assets/css/wcs-upgrade.css Normal file → Executable file
View File

@ -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 ) 0 0 4px 1px;
box-shadow: rgba(0, 0, 0, 0.2) 0px 0px 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;
}

0
assets/images/add-edit-subscription-screen.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

0
assets/images/admin-change-payment-method.jpg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

0
assets/images/ajax-loader.gif Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 885 B

After

Width:  |  Height:  |  Size: 885 B

0
assets/images/ajax-loader@2x.gif Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

0
assets/images/billing-schedules-meta-box.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

0
assets/images/checkout-recurring-totals.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

0
assets/images/drip-downloadable-content.jpg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

0
assets/images/gift-subscription.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

View File

@ -1,26 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 11 KiB

0
assets/images/renewal-retry-settings.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

0
assets/images/subscribe-all-the-things.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

0
assets/images/subscription-reports.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

0
assets/images/subscription-suspended-email.jpg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 172 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

0
assets/images/subscriptions-importer-exporter.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

0
assets/images/view-subscription.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 250 KiB

After

Width:  |  Height:  |  Size: 250 KiB

0
assets/images/woocommerce_subscriptions_logo.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

100
assets/js/admin/admin-pointers.js Normal file → Executable file
View File

@ -1,79 +1,57 @@
jQuery( function ( $ ) {
let observer = null;
jQuery(document).ready(function($){
if ( arePointersEnabled() ) {
observer = new MutationObserver( showSubscriptionPointers );
observer.observe( document.getElementById( 'poststuff' ), {
attributes: true,
childList: true,
characterData: false,
subtree:true,
} );
if(arePointersEnabled()){
setTimeout(showSubscriptionPointers, 800); // give TinyMCE a chance to finish loading
}
$( 'select#product-type' ).on( 'change', function () {
if ( arePointersEnabled() ) {
$( '#product-type' ).pointer( 'close' );
$('select#product-type').change(function(){
if(arePointersEnabled()){
$('#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' );
$('#_subscription_price, #_subscription_period, #_subscription_length').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',
} );
if ( observer ) {
observer.disconnect();
}
action: 'dismiss-wp-pointer'
});
}
} );
});

1601
assets/js/admin/admin.js Normal file → Executable file

File diff suppressed because it is too large Load Diff

0
assets/js/admin/jquery.flot.axislabels.js Normal file → Executable file
View File

0
assets/js/admin/jquery.flot.axislabels.min.js vendored Normal file → Executable file
View File

0
assets/js/admin/jquery.flot.orderBars.js Normal file → Executable file
View File

0
assets/js/admin/jquery.flot.orderBars.min.js vendored Normal file → Executable file
View File

4
assets/js/admin/jstz.js Normal file → Executable file
View File

@ -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 ambiguous for Baku... but Windows.
// In the real world Yerevan is not ambigous 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 occurrences for those years, immediately returns
* If there are no DST occurences for those years, immediately returns
* the preliminary timezone. Otherwise proceeds and tries to solve
* ambiguities.
*

0
assets/js/admin/jstz.min.js vendored Normal file → Executable file
View File

15
assets/js/admin/meta-boxes-coupon.js Normal file → Executable file
View File

@ -1,4 +1,4 @@
jQuery( function ( $ ) {
jQuery( document ).ready( function( $ ) {
'use strict';
var renewals_field = document.querySelector( '.wcs_number_payments_field' ),
@ -9,14 +9,13 @@ jQuery( function ( $ ) {
* @type {{init: function, type_options: function, move_field: function}}
*/
var wcs_meta_boxes_coupon_actions = {
/**
* Initialize variation actions.
*/
init: function () {
init: function() {
if ( renewals_field ) {
$( document.getElementById( 'discount_type' ) )
.on( 'change', this.type_options )
.trigger( 'change' );
$( document.getElementById( 'discount_type' ) ).on( 'change', this.type_options ).change();
this.move_field();
}
},
@ -24,7 +23,7 @@ jQuery( function ( $ ) {
/**
* Show/hide fields by coupon type options.
*/
type_options: function () {
type_options: function() {
var select_val = $( this ).val();
switch ( select_val ) {
@ -42,12 +41,12 @@ jQuery( function ( $ ) {
/**
* Move the renewal form field in the DOM to a better location.
*/
move_field: function () {
move_field: function() {
var parent = document.getElementById( 'general_coupon_data' ),
shipping = parent.querySelector( '.free_shipping_field' );
parent.insertBefore( renewals_field, shipping );
},
}
};
wcs_meta_boxes_coupon_actions.init();

420
assets/js/admin/meta-boxes-subscription.js Normal file → Executable file
View File

@ -1,318 +1,182 @@
jQuery( function ( $ ) {
jQuery(document).ready(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 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()
);
// Make sure 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', '' )
).trigger( 'change' );
} );
$( '[name$="_hour"], [name$="_minute"]' ).on( 'change', function() {
$( '#' + $(this).attr( 'name' ).replace( '_hour', '' ).replace( '_minute', '' ) ).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' ),
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,
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,
minutes: chosen_minute,
seconds: one_hour_from_now.format( 'ss' ),
} );
seconds: one_hour_from_now.format( 'ss' )
});
// Make sure start date is before now
if ( 'start' == date_type ) {
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' ) );
}
// Make sure start date is before now.
if (
'start' == date_type &&
false === chosen_date.isBefore( time_now )
) {
alert( wcs_admin_meta_boxes.i18n_start_date_notice );
$date_input.val(
time_now.year() +
'-' +
zeroise( time_now.months() + 1 ) +
'-' +
time_now.format( 'DD' )
);
$hour_input.val( time_now.format( 'HH' ) );
$minute_input.val( time_now.format( 'mm' ) );
}
// Make sure trial end and next payment are after start date
if (
( 'trial_end' == date_type || 'next_payment' == date_type ) &&
'' != $( '#start_timestamp_utc' ).val()
) {
else 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() < minimum_date.unix()
) {
if ( 'trial_end' != date_type && 'start' != date_type && chosen_date.unix() < one_hour_from_now.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( '-' );
var newTimeStampValue = moment( {
years: date_pieces[ 0 ],
months: date_pieces[ 1 ] - 1,
date: date_pieces[ 2 ],
hours: $hour_input.val(),
$('#'+date_type+'_timestamp_utc').val(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());
// Moment will return NaN if the date is invalid, that's why we need to check for NaN only.
if ( isNaN( newTimeStampValue ) ) {
wcsShowDateFieldError( date_type );
} else {
wcsHideDateFieldError( date_type );
}
// Intentionally do not prevent timestamp updates if the date is invalid.
// This way it's easier to catch invalid fields during submit event if attempted without editing invalid values.
$( '#' + date_type + '_timestamp_utc' ).val(
newTimeStampValue
);
$( 'body' ).trigger( 'wcs-updated-date', date_type );
} );
function wcsShowDateFieldError( date_type ) {
var $fieldContainer = $( '#subscription-' + date_type + '-date' );
$fieldContainer.addClass( 'has-error' );
var $messageContainer = $fieldContainer.find( '.message' );
var $messageContent = $messageContainer.find( '.message-content' );
// Clear and set content before showing to ensure screen readers announce the new message
$messageContent.text('');
$messageContainer.show();
// Use setTimeout to ensure DOM update occurs before adding new text
setTimeout(function() {
// If the focus switched to the next field voice over skips announcing the error message.
// This is a workaround to ensure the error message is announced.
$fieldContainer
.find( `input#${date_type}` )
.trigger( 'focus' )
.trigger( 'blur' );
$messageContent.text( wcs_admin_meta_boxes.i18n_invalid_date_notice );
}, 100);
}
function wcsHideDateFieldError( date_type ) {
var $fieldContainer = $( '#subscription-' + date_type + '-date' );
$fieldContainer.removeClass( 'has-error' );
var $messageContainer = $fieldContainer.find( '.message' );
var $messageContent = $messageContainer.find( '.message-content' );
$messageContainer.hide();
$messageContent.text('');
}
$( 'body' ).trigger( 'wcs-updated-date',date_type);
});
function zeroise( val ) {
return val > 9 ? val : '0' + val;
return (val > 9 ) ? val : '0' + val;
}
if ( $( '#parent-order-id' ).is( 'select' ) ) {
if( $( '#parent-order-id' ).is( 'select' ) ) {
wcs_update_parent_order_options();
$( '#customer_user' ).on( 'change', wcs_update_parent_order_options );
}
function wcs_update_parent_order_options() {
// Get user ID to load orders for
var user_id = $( '#customer_user' ).val();
@ -321,26 +185,24 @@ jQuery( function ( $ ) {
}
var data = {
user_id: user_id,
action: 'wcs_get_customer_orders',
security: wcs_admin_meta_boxes.get_customer_orders_nonce,
user_id: user_id,
action: 'wcs_get_customer_orders',
security: wcs_admin_meta_boxes.get_customer_orders_nonce
};
$( '#parent-order-id' )
.siblings( '.select2-container' )
.block( {
message: null,
overlayCSS: {
background: '#fff',
opacity: 0.6,
},
} );
$( '#parent-order-id' ).siblings( '.select2-container' ).block({
message: null,
overlayCSS: {
background: '#fff',
opacity: 0.6
}
});
$.ajax( {
$.ajax({
url: WCSubscriptions.ajaxUrl,
data: data,
type: 'POST',
success: function ( response ) {
success: function( response ) {
if ( response ) {
var $orderlist = $( '#parent-order-id' );
@ -348,84 +210,28 @@ jQuery( function ( $ ) {
$orderlist.empty(); // remove old options
$orderlist.append(
$( '<option></option>' )
.attr( 'value', '' )
.text( 'Select an order' )
);
$orderlist.append( $( '<option></option>' ).attr( 'value', '' ).text( 'Select an order' ) );
$.each( response, function ( order_id, order_number ) {
$orderlist.append(
$( '<option></option>' )
.attr( 'value', order_id )
.text( order_number )
);
} );
$.each( response, function( order_id, order_number ) {
$orderlist.append( $( '<option></option>' ).attr( 'value', order_id ).text( order_number ) );
});
$( '#parent-order-id' )
.siblings( '.select2-container' )
.unblock();
$( '#parent-order-id' ).siblings( '.select2-container' ).unblock();
}
},
} );
return false;
}
$( 'body.post-type-shop_subscription #post, 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;
}
} )
});
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').submit(function(){
if('wcs_process_renewal' == $( "body.post-type-shop_subscription select[name='wc_order_action']" ).val()) {
return confirm(wcs_admin_meta_boxes.process_renewal_action_warning);
}
} );
});
$( 'body.post-type-shop_subscription #post, body.woocommerce_page_wc-orders--shop_subscription #order' ).on( 'submit', function () {
if (
typeof wcs_admin_meta_boxes.change_payment_method_warning !=
'undefined' &&
wcs_admin_meta_boxes.payment_method != $( '#_payment_method' ).val()
) {
return confirm(
wcs_admin_meta_boxes.change_payment_method_warning
);
$('body.post-type-shop_subscription #post').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();
}
} );
} );
});
});

7674
assets/js/admin/moment.js Normal file → Executable file

File diff suppressed because it is too large Load Diff

6
assets/js/admin/moment.min.js vendored Normal file → Executable file

File diff suppressed because one or more lines are too long

View File

@ -1,109 +0,0 @@
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' );
})
} );

2
assets/js/admin/reports.js Normal file → Executable file
View File

@ -1,4 +1,4 @@
jQuery( function( $ ) {
jQuery(function($) {
$.extend({
wcs_format_money: function(value,decimal_precision) {

View File

@ -1,79 +0,0 @@
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' );
} );
}
});

49
assets/js/admin/wcs-meta-boxes-order.js Normal file → Executable file
View File

@ -1,43 +1,8 @@
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
);
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);
}
} );
$( 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();
},
} );
} );
} );
});
});

View File

@ -1,37 +0,0 @@
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();
} );
} );

View File

@ -1,53 +0,0 @@
( 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 );

View File

@ -1,177 +0,0 @@
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
);
} );

View File

@ -2,61 +2,16 @@ function hide_non_applicable_coupons() {
var coupon_elements = document.getElementsByClassName( 'cart-discount' );
for ( var i = 0; i < coupon_elements.length; i++ ) {
if (
0 !==
coupon_elements[ i ].getElementsByClassName( 'wcs-hidden-coupon' )
.length
) {
coupon_elements[ i ].style.display = 'none';
if ( 0 !== coupon_elements[i].getElementsByClassName( 'wcs-hidden-coupon' ).length ) {
coupon_elements[i].style.display = 'none';
}
}
}
hide_non_applicable_coupons();
jQuery( function ( $ ) {
$( document.body ).on( 'updated_cart_totals updated_checkout', function () {
jQuery( document ).ready( 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();
}
} );
} );

View File

@ -1,358 +0,0 @@
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 );
} );
} );

View File

@ -1,56 +0,0 @@
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();
} );
}
}
}
);
} );

View File

@ -1,131 +0,0 @@
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();
}
}
} );

264
assets/js/wcs-upgrade.js Normal file → Executable file
View File

@ -1,109 +1,89 @@
/**
* @deprecated subscriptions-core 7.7.0 This file is no longer in use and can be removed in future.
*/
jQuery( function ( $ ) {
jQuery(document).ready(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();
@ -112,58 +92,38 @@ jQuery( 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();
@ -172,77 +132,56 @@ jQuery( 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,
@ -250,27 +189,18 @@ jQuery( 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);
}
} );
});

View File

@ -1 +0,0 @@
<?php return array('dependencies' => array('react', 'react-dom', 'wp-api-fetch', 'wp-components', 'wp-i18n', 'wp-primitives'), 'version' => '8bad0ce18409676b15d1');

File diff suppressed because one or more lines are too long

View File

@ -1,3 +0,0 @@
.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}

View File

@ -1 +0,0 @@
<?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');

View File

@ -1,3 +0,0 @@
.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}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
.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}

View File

@ -1 +0,0 @@
.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}

View File

@ -1 +0,0 @@
.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}

View File

@ -1 +0,0 @@
<?php return array('dependencies' => array('react', 'react-dom', 'wc-blocks-checkout', 'wp-components', 'wp-data', 'wp-i18n', 'wp-plugins', 'wp-url'), 'version' => 'cf4dd71c0c7418b591fd');

View File

@ -1 +0,0 @@
.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}

File diff suppressed because one or more lines are too long

1194
changelog.txt Normal file → Executable file

File diff suppressed because it is too large Load Diff

View File

@ -8,8 +8,8 @@
* @author Prospress
* @category Admin
* @package WooCommerce Subscriptions/Admin
* @version 1.0.0 - Migrated from WooCommerce Subscriptions v2.3
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3
* @version 2.3
* @since 2.3
*/
if ( ! defined( 'ABSPATH' ) ) {
@ -89,7 +89,7 @@ abstract class WCS_Background_Updater {
*
* Importantly, the overlap between the next scheduled update and the current batch is also useful for running
* Action Scheduler via WP CLI, because it will allow for continuous execution of updates (i.e. updating a new
* batch as soon as one batch has exceeded the time limit rather than having to run Action Scheduler via WP CLI
* batch as soon as one batch has execeeded the time limit rather than having to run Action Scheduler via WP CLI
* again later).
*/
public function run_update() {
@ -97,8 +97,7 @@ abstract class WCS_Background_Updater {
$this->schedule_background_update();
// If the update is being run via WP CLI, we don't need to worry about the request time, just the processing time for this method
// @phpstan-ignore constant.notFound
$start_time = $this->is_wp_cli_request() ? (int) gmdate( 'U' ) : WCS_INIT_TIMESTAMP;
$start_time = $this->is_wp_cli_request() ? gmdate( 'U' ) : WCS_INIT_TIMESTAMP;
do {
@ -108,7 +107,7 @@ abstract class WCS_Background_Updater {
$this->update_item( $item );
$time_elapsed = (int) gmdate( 'U' ) - $start_time;
$time_elapsed = ( gmdate( 'U' ) - $start_time );
if ( $time_elapsed >= $this->time_limit ) {
break 2;
@ -126,9 +125,8 @@ abstract class WCS_Background_Updater {
* Schedule the instance's hook to run in $this->time_limit seconds, if it's not already scheduled.
*/
protected function schedule_background_update() {
// A timestamp is returned if there's a pending action already scheduled. Otherwise true if its running or false if one doesn't exist.
if ( ! is_numeric( as_next_scheduled_action( $this->scheduled_hook ) ) ) {
as_schedule_single_action( (int) gmdate( 'U' ) + $this->time_limit, $this->scheduled_hook );
if ( false === wc_next_scheduled_action( $this->scheduled_hook ) ) {
wc_schedule_single_action( gmdate( 'U' ) + $this->time_limit, $this->scheduled_hook );
}
}
@ -136,7 +134,7 @@ abstract class WCS_Background_Updater {
* Unschedule the instance's hook in Action Scheduler
*/
protected function unschedule_background_updates() {
as_unschedule_action( $this->scheduled_hook );
wc_unschedule_action( $this->scheduled_hook );
}
/**

View File

@ -7,7 +7,7 @@
* @author Prospress
* @category Admin
* @package WooCommerce Subscriptions/Admin/Upgrades
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3
* @since 2.3
*/
// Exit if accessed directly
@ -20,7 +20,7 @@ abstract class WCS_Background_Upgrader extends WCS_Background_Updater {
/**
* WC Logger instance for logging messages.
*
* @var WC_Logger_Interface
* @var WC_Logger
*/
protected $logger;
@ -33,7 +33,7 @@ abstract class WCS_Background_Upgrader extends WCS_Background_Updater {
* Schedule the @see $this->scheduled_hook action to start repairing subscriptions in
* @see $this->time_limit seconds (60 seconds by default).
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
* @since 2.3.0
*/
public function schedule_repair() {
$this->schedule_background_update();
@ -43,7 +43,7 @@ abstract class WCS_Background_Upgrader extends WCS_Background_Updater {
* Add a message to the wcs-upgrade-subscriptions-paypal-suspended log
*
* @param string $message The message to be logged
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
* @since 2.3.0
*/
protected function log( $message ) {
$this->logger->add( $this->log_handle, $message );

View File

@ -5,7 +5,7 @@
* Implements methods to deal with the soft caching layer
*
* @class WCS_Cache_Manager
* @version 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
* @version 2.0
* @package WooCommerce Subscriptions/Classes
* @category Class
* @author Gabor Javorszky
@ -17,9 +17,16 @@ abstract class WCS_Cache_Manager {
* Modeled after WP_Session_Tokens
*/
$manager = apply_filters( 'wcs_cache_manager_class', 'WCS_Cached_Data_Manager' );
return new $manager();
return new $manager;
}
/**
* WCS_Cache_Manager constructor.
*
* Loads the logger if it's not overwritten.
*/
abstract function __construct();
/**
* Initialises some form of logger
*/

View File

@ -13,8 +13,8 @@ if ( ! defined( 'ABSPATH' ) ) {
* it is being moved to use the 'post_author' column of the posts table from WC v2.4 or v2.5. It
* will eventually also be moved quite likely to custom tables.
*
* @version 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
* @version 2.3.0
* @since 2.3.0
* @category Class
* @author Prospress
*/
@ -43,7 +43,7 @@ abstract class WCS_Customer_Store {
wcs_doing_it_wrong( __METHOD__, 'This method was called before the "plugins_loaded" hook. It applies a filter to the customer data store instantiated. For that to work, it should first be called after all plugins are loaded.', '2.3.0' );
}
$class = apply_filters( 'wcs_customer_store_class', 'WCS_Customer_Store_Cached_CPT' );
$class = apply_filters( 'wcs_customer_store_class', 'WCS_Customer_Store_Cached_CPT' );
self::$instance = new $class();
self::$instance->init();
}

View File

@ -8,8 +8,8 @@
* @author Prospress
* @category Admin
* @package WooCommerce Subscriptions/Admin
* @version 1.0.0 - Migrated from WooCommerce Subscriptions v2.3
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3
* @version 2.3
* @since 2.3
*/
if ( ! defined( 'ABSPATH' ) ) {

View File

@ -8,8 +8,8 @@
* @author Prospress
* @category Admin
* @package WooCommerce Subscriptions/Admin
* @version 1.0.0 - Migrated from WooCommerce Subscriptions v2.3
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3
* @version 2.3
* @since 2.3
*/
if ( ! defined( 'ABSPATH' ) ) {

View File

@ -2,11 +2,11 @@
/**
* Deprecate actions and filters that use a dynamic hook by appending a variable, like a payment gateway's name.
*
* @package WooCommerce Subscriptions
* @subpackage WCS_Hook_Deprecator
* @category Class
* @author Prospress
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
* @package WooCommerce Subscriptions
* @subpackage WCS_Hook_Deprecator
* @category Class
* @author Prospress
* @since 2.0
*/
abstract class WCS_Dynamic_Hook_Deprecator extends WCS_Hook_Deprecator {
@ -22,7 +22,7 @@ abstract class WCS_Dynamic_Hook_Deprecator extends WCS_Hook_Deprecator {
* $wp_filter global for our hooks either, because sometime, hooks are dynamically hooked based
* on other hooks. Sigh.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
* @since 2.0
*/
public function __construct() {
add_filter( 'all', array( &$this, 'check_for_deprecated_hooks' ) );
@ -31,7 +31,7 @@ abstract class WCS_Dynamic_Hook_Deprecator extends WCS_Hook_Deprecator {
/**
* Check if the current hook contains the prefix of any dynamic hook that has been deprecated.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
* @since 2.0
*/
public function check_for_deprecated_hooks() {
@ -53,7 +53,7 @@ abstract class WCS_Dynamic_Hook_Deprecator extends WCS_Hook_Deprecator {
* Check if a given hook contains the prefix and if it does, attach the @see $this->maybe_handle_deprecated_hook() method
* as a callback to it.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
* @since 2.0
*/
protected function check_for_deprecated_hook( $current_hook, $new_hook_prefix, $old_hook_prefix ) {

View File

@ -8,11 +8,11 @@
*
* This is the base class for handling those deprecated hooks.
*
* @package WooCommerce Subscriptions
* @subpackage WCS_Hook_Deprecator
* @category Class
* @author Prospress
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
* @package WooCommerce Subscriptions
* @subpackage WCS_Hook_Deprecator
* @category Class
* @author Prospress
* @since 2.0
*/
abstract class WCS_Hook_Deprecator {
@ -23,7 +23,7 @@ abstract class WCS_Hook_Deprecator {
/**
* Bootstraps the class and hooks required actions & filters.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
* @since 2.0
*/
public function __construct() {
foreach ( $this->deprecated_hooks as $new_hook => $old_hook ) {
@ -34,7 +34,7 @@ abstract class WCS_Hook_Deprecator {
/**
* Check if an old hook still has callbacks attached to it, and if so, display a notice and trigger the old hook.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
* @since 2.0
*/
public function maybe_handle_deprecated_hook() {
@ -61,7 +61,7 @@ abstract class WCS_Hook_Deprecator {
/**
* Check if an old hook still has callbacks attached to it, and if so, display a notice and trigger the old hook.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
* @since 2.0
*/
protected function handle_deprecated_hook( $new_hook, $old_hook, $new_callback_args, $return_value ) {
@ -78,7 +78,7 @@ abstract class WCS_Hook_Deprecator {
/**
* Display a deprecated notice for old hooks.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
* @since 2.0
*/
protected static function display_notice( $old_hook, $new_hook ) {
_deprecated_function( sprintf( 'The "%s" hook uses out of date data structures so', esc_html( $old_hook ) ), '2.0 of WooCommerce Subscriptions', esc_html( $new_hook ) );
@ -87,7 +87,7 @@ abstract class WCS_Hook_Deprecator {
/**
* Trigger the old hook with the original callback parameters
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
* @since 2.0
*/
abstract protected function trigger_hook( $old_hook, $new_callback_args );
@ -97,10 +97,10 @@ abstract class WCS_Hook_Deprecator {
* Because a subscription can exist without an order in Subscriptions 2.0, the order might actually
* fallback to being the subscription rather than the order used to purchase the subscription.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
* @since 2.0
*/
protected static function get_order( $subscription ) {
return ( false == $subscription->get_parent_id() ) ? $subscription : $subscription->get_parent();
return ( false == $subscription->get_parent_id() ) ? $subscription : $subscription->get_parent();
}
/**
@ -109,7 +109,7 @@ abstract class WCS_Hook_Deprecator {
* Because a subscription can exist without an order in Subscriptions 2.0, the order might actually
* fallback to being the subscription rather than the order used to purchase the subscription.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
* @since 2.0
*/
protected static function get_order_id( $subscription ) {
return ( false == $subscription->get_parent_id() ) ? $subscription->get_id() : $subscription->get_parent_id();
@ -118,11 +118,11 @@ abstract class WCS_Hook_Deprecator {
/**
* Get the first product ID for a subscription to pass to callbacks.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
* @since 2.0
*/
protected static function get_product_id( $subscription ) {
$order_items = $subscription->get_items();
$product_id = ( empty( $order_items ) ) ? 0 : WC_Subscriptions_Order::get_items_product_id( reset( $order_items ) );
$order_items = $subscription->get_items();
$product_id = ( empty( $order_items ) ) ? 0 : WC_Subscriptions_Order::get_items_product_id( reset( $order_items ) );
return $product_id;
}
}

View File

@ -13,8 +13,8 @@ if ( ! defined( 'ABSPATH' ) ) {
* Parent orders are not managed via this data store as the order data stores inherited by Subscriptions already
* provide APIs for managing the parent relationship.
*
* @version 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
* @version 2.3.0
* @since 2.3.0
* @category Class
* @author Prospress
*/
@ -41,7 +41,11 @@ abstract class WCS_Related_Order_Store {
*
* @var array
*/
private static $relation_type_keys = array();
private static $relation_type_keys = array(
'renewal' => true,
'switch' => true,
'resubscribe' => true,
);
/**
* Get the active related order data store.
@ -55,19 +59,7 @@ abstract class WCS_Related_Order_Store {
wcs_doing_it_wrong( __METHOD__, 'This method was called before the "plugins_loaded" hook. It applies a filter to the related order data store instantiated. For that to work, it should first be called after all plugins are loaded.', '2.3.0' );
}
/**
* Allow third-parties to register their own custom order relationship types which should be handled by this store.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.5.0
*/
foreach ( (array) apply_filters( 'wcs_additional_related_order_relation_types', array() ) as $relation_type ) {
self::$relation_types[] = $relation_type;
}
self::$relation_type_keys = array_fill_keys( self::$relation_types, true );
$class = apply_filters( 'wcs_related_order_store_class', 'WCS_Related_Order_Store_Cached_CPT' );
$class = apply_filters( 'wcs_related_order_store_class', 'WCS_Related_Order_Store_Cached_CPT' );
self::$instance = new $class();
self::$instance->init();
}
@ -143,26 +135,7 @@ abstract class WCS_Related_Order_Store {
*/
protected function check_relation_type( $relation_type ) {
if ( ! isset( self::$relation_type_keys[ $relation_type ] ) ) {
// translators: 1: relation type, 2: list of valid relation types.
throw new InvalidArgumentException( sprintf( __( 'Invalid relation type: %1$s. Order relationship type must be one of: %2$s.', 'woocommerce-subscriptions' ), $relation_type, implode( ', ', $this->get_relation_types() ) ) );
throw new InvalidArgumentException( sprintf( __( 'Invalid relation type: %s. Order relationship type must be one of: %s.', 'woocommerce-subscriptions' ), $relation_type, implode( ', ', $this->get_relation_types() ) ) );
}
}
/**
* Get related order IDs grouped by relation type.
*
* @param WC_Order $subscription The subscription to find related orders.
* @param array $relation_types An array of relation types to fetch. Must be an array containing 'renewal', 'switch' or 'resubscribe' unless custom relationships are implemented.
*
* @return array An associative array where keys are relation types and values are arrays of related order IDs.
*/
public function get_related_order_ids_by_types( WC_Order $subscription, $relation_types ) {
$related_orders = [];
foreach ( $relation_types as $relation_type ) {
$related_orders[ $relation_type ] = $this->get_related_order_ids( $subscription, $relation_type );
}
return $related_orders;
}
}

View File

@ -0,0 +1,107 @@
<?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 );
}
}

View File

@ -6,7 +6,7 @@
* or subscription expires.
*
* @class WCS_Scheduler
* @version 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.0
* @version 2.0.0
* @package WooCommerce Subscriptions/Abstracts
* @category Abstract Class
* @author Prospress

File diff suppressed because it is too large Load Diff

View File

@ -1,59 +0,0 @@
<?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'
);
}
}

View File

@ -0,0 +1,300 @@
<?php
/**
* WooCommerce Subscriptions Admin Meta Boxes
*
* Sets up the write panels used by the subscription custom order/post type
*
* @author Prospress
* @category Admin
* @package WooCommerce Subscriptions/Admin
* @version 2.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* WC_Admin_Meta_Boxes
*/
class WCS_Admin_Meta_Boxes {
/**
* Constructor
*/
public function __construct() {
add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ), 25 );
add_action( 'add_meta_boxes', array( $this, 'remove_meta_boxes' ), 35 );
// We need to remove core WC save methods for meta boxes we don't use
add_action( 'woocommerce_process_shop_order_meta', array( $this, 'remove_meta_box_save' ), -1, 2 );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_styles_scripts' ), 20 );
// We need to hook to the 'shop_order' rather than 'shop_subscription' because we declared that the 'shop_susbcription' order type supports 'order-meta-boxes'
add_action( 'woocommerce_process_shop_order_meta', 'WCS_Meta_Box_Schedule::save', 10, 2 );
add_action( 'woocommerce_process_shop_order_meta', 'WCS_Meta_Box_Subscription_Data::save', 10, 2 );
add_filter( 'woocommerce_order_actions', __CLASS__ . '::add_subscription_actions', 10, 1 );
add_action( 'woocommerce_order_action_wcs_process_renewal', __CLASS__ . '::process_renewal_action_request', 10, 1 );
add_action( 'woocommerce_order_action_wcs_create_pending_renewal', __CLASS__ . '::create_pending_renewal_action_request', 10, 1 );
add_action( 'woocommerce_order_action_wcs_create_pending_parent', __CLASS__ . '::create_pending_parent_action_request', 10, 1 );
add_filter( 'woocommerce_resend_order_emails_available', __CLASS__ . '::remove_order_email_actions', 0, 1 );
add_action( 'woocommerce_order_action_wcs_retry_renewal_payment', __CLASS__ . '::process_retry_renewal_payment_action_request', 10, 1 );
}
/**
* Add WC Meta boxes
*/
public function add_meta_boxes() {
global $post_ID;
add_meta_box( 'woocommerce-subscription-data', _x( 'Subscription Data', 'meta box title', 'woocommerce-subscriptions' ), 'WCS_Meta_Box_Subscription_Data::output', 'shop_subscription', 'normal', 'high' );
add_meta_box( 'woocommerce-subscription-schedule', _x( 'Schedule', 'meta box title', 'woocommerce-subscriptions' ), 'WCS_Meta_Box_Schedule::output', 'shop_subscription', 'side', 'default' );
remove_meta_box( 'woocommerce-order-data', 'shop_subscription', 'normal' );
add_meta_box( 'subscription_renewal_orders', __( 'Related Orders', 'woocommerce-subscriptions' ), 'WCS_Meta_Box_Related_Orders::output', 'shop_subscription', 'normal', 'low' );
// Only display the meta box if an order relates to a subscription
if ( 'shop_order' === get_post_type( $post_ID ) && wcs_order_contains_subscription( $post_ID, 'any' ) ) {
add_meta_box( 'subscription_renewal_orders', __( 'Related Orders', 'woocommerce-subscriptions' ), 'WCS_Meta_Box_Related_Orders::output', 'shop_order', 'normal', 'low' );
}
}
/**
* Removes the core Order Data meta box as we add our own Subscription Data meta box
*/
public function remove_meta_boxes() {
remove_meta_box( 'woocommerce-order-data', 'shop_subscription', 'normal' );
}
/**
* Don't save save some order related meta boxes
*/
public function remove_meta_box_save( $post_id, $post ) {
if ( 'shop_subscription' == $post->post_type ) {
remove_action( 'woocommerce_process_shop_order_meta', 'WC_Meta_Box_Order_Data::save', 40, 2 );
}
}
/**
* Print admin styles/scripts
*/
public function enqueue_styles_scripts() {
global $post;
// Get admin screen id
$screen = get_current_screen();
$screen_id = isset( $screen->id ) ? $screen->id : '';
if ( 'shop_subscription' == $screen_id ) {
wp_register_script( 'jstz', plugin_dir_url( WC_Subscriptions::$plugin_file ) . 'assets/js/admin/jstz.min.js' );
wp_register_script( 'momentjs', plugin_dir_url( WC_Subscriptions::$plugin_file ) . 'assets/js/admin/moment.min.js' );
wp_enqueue_script( 'wcs-admin-meta-boxes-subscription', plugin_dir_url( WC_Subscriptions::$plugin_file ) . 'assets/js/admin/meta-boxes-subscription.js', array( 'wc-admin-meta-boxes', 'jstz', 'momentjs' ), WC_VERSION );
wp_localize_script( 'wcs-admin-meta-boxes-subscription', 'wcs_admin_meta_boxes', apply_filters( 'woocommerce_subscriptions_admin_meta_boxes_script_parameters', array(
'i18n_start_date_notice' => __( 'Please enter a start date in the past.', 'woocommerce-subscriptions' ),
'i18n_past_date_notice' => __( 'Please enter a date at least one hour into the future.', 'woocommerce-subscriptions' ),
'i18n_next_payment_start_notice' => __( 'Please enter a date after the trial end.', 'woocommerce-subscriptions' ),
'i18n_next_payment_trial_notice' => __( 'Please enter a date after the start date.', 'woocommerce-subscriptions' ),
'i18n_trial_end_start_notice' => __( 'Please enter a date after the start date.', 'woocommerce-subscriptions' ),
'i18n_trial_end_next_notice' => __( 'Please enter a date before the next payment.', 'woocommerce-subscriptions' ),
'i18n_end_date_notice' => __( 'Please enter a date after the next payment.', 'woocommerce-subscriptions' ),
'process_renewal_action_warning' => __( "Are you sure you want to process a renewal?\n\nThis will charge the customer and email them the renewal order (if emails are enabled).", 'woocommerce-subscriptions' ),
'payment_method' => wcs_get_subscription( $post )->get_payment_method(),
'search_customers_nonce' => wp_create_nonce( 'search-customers' ),
'get_customer_orders_nonce' => wp_create_nonce( 'get-customer-orders' ),
) ) );
} else if ( 'shop_order' == $screen_id ) {
wp_enqueue_script( 'wcs-admin-meta-boxes-order', plugin_dir_url( WC_Subscriptions::$plugin_file ) . 'assets/js/admin/wcs-meta-boxes-order.js' );
wp_localize_script( 'wcs-admin-meta-boxes-order', 'wcs_admin_order_meta_boxes', array(
'retry_renewal_payment_action_warning' => __( "Are you sure you want to retry payment for this renewal order?\n\nThis will attempt to charge the customer and send renewal order emails (if emails are enabled).", 'woocommerce-subscriptions' ),
)
);
}
// Enqueue the metabox script for coupons.
if ( ! WC_Subscriptions::is_woocommerce_pre( '3.2' ) && in_array( $screen_id, array( 'shop_coupon', 'edit-shop_coupon' ) ) ) {
wp_enqueue_script(
'wcs-admin-coupon-meta-boxes',
plugin_dir_url( WC_Subscriptions::$plugin_file ) . 'assets/js/admin/meta-boxes-coupon.js',
array( 'jquery', 'wc-admin-meta-boxes' ),
WC_Subscriptions::$version
);
}
}
/**
* Adds actions to the admin edit subscriptions page, if the subscription hasn't ended and the payment method supports them.
*
* @param array $actions An array of available actions
* @return array An array of updated actions
* @since 2.0
*/
public static function add_subscription_actions( $actions ) {
global $theorder;
if ( wcs_is_subscription( $theorder ) && ! $theorder->has_status( wcs_get_subscription_ended_statuses() ) ) {
if ( $theorder->payment_method_supports( 'subscription_date_changes' ) && $theorder->has_status( 'active' ) ) {
$actions['wcs_process_renewal'] = esc_html__( 'Process renewal', 'woocommerce-subscriptions' );
}
if ( count( $theorder->get_related_orders() ) > 0 ) {
$actions['wcs_create_pending_renewal'] = esc_html__( 'Create pending renewal order', 'woocommerce-subscriptions' );
} else {
$actions['wcs_create_pending_parent'] = esc_html__( 'Create pending parent order', 'woocommerce-subscriptions' );
}
} else if ( self::can_renewal_order_be_retried( $theorder ) ) {
$actions['wcs_retry_renewal_payment'] = esc_html__( 'Retry Renewal Payment', 'woocommerce-subscriptions' );
}
return $actions;
}
/**
* Handles the action request to process a renewal order.
*
* @param array $subscription
* @since 2.0
*/
public static function process_renewal_action_request( $subscription ) {
do_action( 'woocommerce_scheduled_subscription_payment', $subscription->get_id() );
$subscription->add_order_note( __( 'Process renewal order action requested by admin.', 'woocommerce-subscriptions' ), false, true );
}
/**
* Handles the action request to create a pending renewal order.
*
* @param array $subscription
* @since 2.0
*/
public static function create_pending_renewal_action_request( $subscription ) {
$subscription->update_status( 'on-hold' );
$renewal_order = wcs_create_renewal_order( $subscription );
if ( ! $subscription->is_manual() ) {
$renewal_order->set_payment_method( wc_get_payment_gateway_by_order( $subscription ) ); // We need to pass the payment gateway instance to be compatible with WC < 3.0, only WC 3.0+ supports passing the string name
if ( is_callable( array( $renewal_order, 'save' ) ) ) { // WC 3.0+
$renewal_order->save();
}
}
$subscription->add_order_note( __( 'Create pending renewal order requested by admin action.', 'woocommerce-subscriptions' ), false, true );
}
/**
* Handles the action request to create a pending parent order.
*
* @param array $subscription
* @since 2.3
*/
public static function create_pending_parent_action_request( $subscription ) {
if ( ! $subscription->has_status( array( 'pending', 'on-hold' ) ) ) {
$subscription->update_status( 'on-hold' );
}
$parent_order = wcs_create_order_from_subscription( $subscription, 'parent' );
$subscription->set_parent_id( wcs_get_objects_property( $parent_order, 'id' ) );
$subscription->save();
if ( ! $subscription->is_manual() ) {
$parent_order->set_payment_method( wc_get_payment_gateway_by_order( $subscription ) ); // We need to pass the payment gateway instance to be compatible with WC < 3.0, only WC 3.0+ supports passing the string name
if ( is_callable( array( $parent_order, 'save' ) ) ) { // WC 3.0+
$parent_order->save();
}
}
$subscription->add_order_note( __( 'Create pending parent order requested by admin action.', 'woocommerce-subscriptions' ), false, true );
}
/**
* Removes order related emails from the available actions.
*
* @param array $available_emails
* @since 2.0
*/
public static function remove_order_email_actions( $email_actions ) {
global $theorder;
if ( wcs_is_subscription( $theorder ) ) {
$email_actions = array();
}
return $email_actions;
}
/**
* Process the action request to retry renewal payment for failed renewal orders.
*
* @param WC_Order $order
* @since 2.1
*/
public static function process_retry_renewal_payment_action_request( $order ) {
if ( self::can_renewal_order_be_retried( $order ) ) {
// init payment gateways
WC()->payment_gateways();
do_action( 'woocommerce_scheduled_subscription_payment_' . wcs_get_objects_property( $order, 'payment_method' ), $order->get_total(), $order );
}
}
/**
* Determines if a renewal order payment can be retried. A renewal order payment can only be retried when:
* - Order is a renewal order
* - Order status is failed
* - Order payment method isn't empty
* - Order total > 0
* - Subscription/s aren't manual
* - Subscription payment method supports date changes
* - Order payment method has_action('woocommerce_scheduled_subscription_payment_..')
*
* @param WC_Order $order
* @return bool
* @since 2.1
*/
private static function can_renewal_order_be_retried( $order ) {
$can_be_retried = false;
if ( wcs_order_contains_renewal( $order ) && $order->needs_payment() && '' != wcs_get_objects_property( $order, 'payment_method' ) ) {
$supports_date_changes = false;
$order_payment_gateway = wc_get_payment_gateway_by_order( $order );
$order_payment_gateway_supports = ( isset( $order_payment_gateway->id ) ) ? has_action( 'woocommerce_scheduled_subscription_payment_' . $order_payment_gateway->id ) : false;
foreach ( wcs_get_subscriptions_for_renewal_order( $order ) as $subscription ) {
$supports_date_changes = $subscription->payment_method_supports( 'subscription_date_changes' );
$is_automatic = ! $subscription->is_manual();
break;
}
$can_be_retried = $order_payment_gateway_supports && $supports_date_changes && $is_automatic;
}
return $can_be_retried;
}
}
new WCS_Admin_Meta_Boxes();

View File

@ -2,7 +2,7 @@
/**
* API for creating and displaying an admin notice.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
* @since 2.3.0
* @category Class
* @author Prospress
*/
@ -69,7 +69,7 @@ class WCS_Admin_Notice {
* @param string $type The notice type. Can be notice, notice-info, updated, error or a custom notice type.
* @param array $attributes The container div's attributes. Optional.
* @param string $dismiss_url The URL used to dismiss the notice. Optional.
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
* @since 2.3.0
*/
public function __construct( $type, array $attributes = array(), $dismiss_url = '' ) {
$this->type = $type;
@ -82,29 +82,27 @@ class WCS_Admin_Notice {
*
* Will print the notice if called during the 'admin_notices' action. Otherwise will attach a callback and display the notice when the 'admin_notices' is triggered.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
* @since 2.3.0
*/
public function display() {
if ( 'admin_notices' !== current_filter() ) {
add_action( 'admin_notices', array( $this, __FUNCTION__ ) );
return;
}
$template_name = 'html-admin-notice.php';
$template_path = WC_Subscriptions_Plugin::instance()->get_plugin_directory( 'templates/admin/' );
if ( function_exists( 'wc_get_template' ) ) {
wc_get_template( $template_name, array( 'notice' => $this ), '', $template_path );
} else {
$notice = $this;
include( $template_path . $template_name );
}
wc_get_template(
'html-admin-notice.php',
array( 'notice' => $this ),
'',
plugin_dir_path( WC_Subscriptions::$plugin_file ) . 'templates/admin/'
);
}
/**
* Whether the admin notice is dismissible.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
* @since 2.3.0
* @return boolean
*/
public function is_dismissible() {
@ -114,7 +112,7 @@ class WCS_Admin_Notice {
/**
* Whether the admin notice has a heading or not.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
* @since 2.3.0
* @return boolean
*/
public function has_heading() {
@ -124,7 +122,7 @@ class WCS_Admin_Notice {
/**
* Whether the admin notice has actions or not.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
* @since 2.3.0
* @return boolean
*/
public function has_actions() {
@ -136,7 +134,7 @@ class WCS_Admin_Notice {
/**
* Print the notice's heading.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
* @since 2.3.0
*/
public function print_heading() {
echo esc_html( $this->heading );
@ -147,7 +145,7 @@ class WCS_Admin_Notice {
*
* Will wrap simple notices in paragraph elements (<p></p>) for correct styling and print HTML notices unchanged.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
* @since 2.3.0
*/
public function print_content() {
switch ( $this->content_type ) {
@ -168,7 +166,7 @@ class WCS_Admin_Notice {
*
* Turns the attributes array into 'id="id" class="class class class"' strings.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
* @since 2.3.0
*/
public function print_attributes() {
$attributes = $this->attributes;
@ -188,10 +186,10 @@ class WCS_Admin_Notice {
/**
* Print the notice's dismiss URL.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
* @since 2.3.0
*/
public function print_dismiss_url() {
echo esc_url( $this->dismiss_url );
echo esc_attr( $this->dismiss_url );
}
/* Getters */
@ -199,7 +197,7 @@ class WCS_Admin_Notice {
/**
* Get the notice's actions.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
* @since 2.3.0
* @return array
*/
public function get_actions() {
@ -231,7 +229,7 @@ class WCS_Admin_Notice {
/**
* Set the notice's content to a string containing HTML elements.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
* @since 2.3.0
* @param string $template_name Template name.
* @param string $template_path Template path.
* @param array $args Arguments. (default: array).
@ -248,7 +246,7 @@ class WCS_Admin_Notice {
/**
* Set actions the user can make in response to this notice.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
* @since 2.3.0
* @param array $actions The actions the user can make. Example format:
* array(
* array(
@ -265,7 +263,7 @@ class WCS_Admin_Notice {
/**
* Set notice's heading. If set this will appear at the top of the notice wrapped in a h2 element.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
* @since 2.3.0
* @param string $heading The notice heading.
*/
public function set_heading( $heading ) {

File diff suppressed because it is too large Load Diff

198
includes/admin/class-wcs-admin-reports.php Normal file → Executable file
View File

@ -23,17 +23,12 @@ class WCS_Admin_Reports {
* Constructor
*/
public function __construct() {
// The subscription reports are compatible with HPOS since 7.8.0.
// We can inform users running data sync mode, that it's no longer needed.
if (
wcs_is_custom_order_tables_usage_enabled() &&
wcs_is_custom_order_tables_data_sync_enabled()
) {
add_action( 'admin_notices', [ __CLASS__, 'display_hpos_compatibility_notice' ] );
}
// Add the reports layout to the WooCommerce -> Reports admin section
add_filter( 'woocommerce_admin_reports', __CLASS__ . '::initialize_reports', 12, 1 );
add_filter( 'woocommerce_admin_reports', __CLASS__ . '::initialize_reports', 12, 1 );
// Add the reports layout to the WooCommerce -> Reports admin section
add_filter( 'wc_admin_reports_path', __CLASS__ . '::initialize_reports_path', 12, 3 );
// Add any necessary scripts
add_action( 'admin_enqueue_scripts', __CLASS__ . '::reports_scripts' );
@ -41,96 +36,49 @@ 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&section=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 $reports Array of Report types & their labels, excluding the Subscription product type.
* @param array 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( 'WCS_Admin_Reports', 'get_report' ),
'callback' => array( 'WC_Admin_Reports', 'get_report' ),
),
'upcoming_recurring_revenue' => array(
'upcoming_recurring_revenue' => array(
'title' => __( 'Upcoming Recurring Revenue', 'woocommerce-subscriptions' ),
'description' => '',
'hide_title' => true,
'callback' => array( 'WCS_Admin_Reports', 'get_report' ),
'callback' => array( 'WC_Admin_Reports', 'get_report' ),
),
'retention_rate' => array(
'retention_rate' => array(
'title' => __( 'Retention Rate', 'woocommerce-subscriptions' ),
'description' => '',
'hide_title' => true,
'callback' => array( 'WCS_Admin_Reports', 'get_report' ),
'callback' => array( 'WC_Admin_Reports', 'get_report' ),
),
'subscription_by_product' => array(
'subscription_by_product' => array(
'title' => __( 'Subscriptions by Product', 'woocommerce-subscriptions' ),
'description' => '',
'hide_title' => true,
'callback' => array( 'WCS_Admin_Reports', 'get_report' ),
'callback' => array( 'WC_Admin_Reports', 'get_report' ),
),
'subscription_by_customer' => array(
'subscription_by_customer' => array(
'title' => __( 'Subscriptions by Customer', 'woocommerce-subscriptions' ),
'description' => '',
'hide_title' => true,
'callback' => array( 'WCS_Admin_Reports', 'get_report' ),
'callback' => array( 'WC_Admin_Reports', 'get_report' ),
),
),
);
@ -140,45 +88,61 @@ class WCS_Admin_Reports {
'title' => __( 'Failed Payment Retries', 'woocommerce-subscriptions' ),
'description' => '',
'hide_title' => true,
'callback' => array( 'WCS_Admin_Reports', 'get_report' ),
'callback' => array( 'WC_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() {
$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();
global $wp_query, $post;
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
$screen = get_current_screen();
$wc_screen_id = sanitize_title( __( 'WooCommerce', 'woocommerce-subscriptions' ) );
// 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', WC_Subscriptions_Plugin::instance()->get_plugin_directory_url( 'assets/js/admin/reports.js' ), array( 'jquery', 'jquery-ui-datepicker', 'wc-reports', 'accounting' ), $version );
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 );
// 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', 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 );
}
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 );
}
}
@ -191,65 +155,13 @@ class WCS_Admin_Reports {
$screen = get_current_screen();
// 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;
switch ( $screen->id ) {
case 'dashboard' :
include_once( 'reports/class-wcs-report-dashboard.php' );
break;
}
$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();

View File

@ -7,7 +7,8 @@
* @package WooCommerce Subscriptions
* @subpackage WC_Subscriptions_Admin
* @category Class
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
* @author Prospress
* @since 2.3.0
*/
class WCS_Admin_System_Status {
@ -16,39 +17,10 @@ class WCS_Admin_System_Status {
*/
const WCS_PRODUCT_ID = 27147;
/**
* Contains pre-determined SSR report data.
*
* @var array
*/
private static $report_data = [];
/**
* Used to cache the result of the comparatively expensive queries executed by
* the get_subscriptions_by_gateway() method.
*
* This cache is short-lived by design, as we don't necessarily want to cache this
* across requests (in some troubleshooting/debug scenarios, that could be confusing
* for the troubleshooter), which is why a transient or WP caching functions are not
* used.
*
* @var null|array
*/
private static $statuses_by_gateway = null;
/**
* Used to cache the subscriptions-by-status counts.
*
* As with with self::$statuses_by_gateway, the cache is deliberately short-lived.
*
* @var null|array
*/
private static $subscription_status_counts = null;
/**
* Attach callbacks
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
* @since 2.3.0
*/
public static function init() {
add_filter( 'woocommerce_system_status_report', array( __CLASS__, 'render_system_status_items' ) );
@ -57,37 +29,20 @@ class WCS_Admin_System_Status {
/**
* Renders the Subscription information in the WC status page
*
* @since 1.0.0 Migrated from WooCommerce Subscriptions v2.3.0
* @since 7.2.0 Uses supplied report data if available.
*
* @param mixed $report Pre-determined SSR report data.
* @since 2.3.0
*/
public static function render_system_status_items( $report = null ) {
/**
* From WooCommerce 9.8.0, we will be supplied with SSR data fetched via a (programmatic)
* REST API request. Using this when available can help prevent duplicated work.
*
* @see WC_REST_Subscription_System_Status_Manager::add_subscription_fields_to_response()
*/
if ( is_array( $report ) && is_array( $report['subscriptions'] ) && ! empty( $report['subscriptions'] ) ) {
self::$report_data = $report['subscriptions'];
}
public static function render_system_status_items() {
$store_data = [];
$subscriptions_data = [];
$subscriptions_by_payment_gateway_data = [];
$payment_gateway_data = [];
$store_data = $subscriptions_data = $subscriptions_by_payment_gateway_data = $payment_gateway_data = array();
self::set_debug_mode( $subscriptions_data );
self::set_staging_mode( $subscriptions_data );
self::set_live_site_url( $subscriptions_data );
self::set_library_version( $subscriptions_data );
self::set_theme_overrides( $subscriptions_data );
self::set_subscription_statuses( $subscriptions_data );
self::set_woocommerce_account_data( $subscriptions_data );
// Subscriptions by Payment Gateway
self::set_subscriptions_by_payment_gateway( $subscriptions_by_payment_gateway_data );
self::set_subscriptions_by_payment_gateway( $subscriptions_by_payment_gateway );
// Payment gateway features
self::set_subscriptions_payment_gateway_support( $payment_gateway_data );
@ -109,7 +64,7 @@ class WCS_Admin_System_Status {
array(
'title' => __( 'Subscriptions by Payment Gateway', 'woocommerce-subscriptions' ),
'tooltip' => __( 'This section shows information about Subscription payment methods.', 'woocommerce-subscriptions' ),
'data' => $subscriptions_by_payment_gateway_data,
'data' => $subscriptions_by_payment_gateway,
),
array(
'title' => __( 'Payment Gateway Support', 'woocommerce-subscriptions' ),
@ -123,7 +78,7 @@ class WCS_Admin_System_Status {
$section_tooltip = $section['tooltip'];
$debug_data = $section['data'];
include WC_Subscriptions_Plugin::instance()->get_plugin_directory( 'templates/admin/status.php' );
include( plugin_dir_path( WC_Subscriptions::$plugin_file ) . 'templates/admin/status.php' );
}
}
@ -136,53 +91,20 @@ class WCS_Admin_System_Status {
$debug_data['wcs_debug'] = array(
'name' => _x( 'WCS_DEBUG', 'label that indicates whether debugging is turned on for the plugin', 'woocommerce-subscriptions' ),
'label' => 'WCS_DEBUG',
'note' => ( $is_wcs_debug ) ? __( 'Yes', 'woocommerce-subscriptions' ) : __( 'No', 'woocommerce-subscriptions' ),
'note' => ( $is_wcs_debug ) ? __( 'Yes', 'woocommerce-subscriptions' ) : __( 'No', 'woocommerce-subscriptions' ),
'success' => $is_wcs_debug ? 0 : 1,
);
}
/**
* Include the staging/live mode the store is running in.
*
* @param array $debug_data
*/
private static function set_staging_mode( &$debug_data ) {
$debug_data['wcs_staging'] = array(
'name' => _x( 'Subscriptions Mode', 'Live or Staging, Label on WooCommerce -> System Status page', 'woocommerce-subscriptions' ),
'label' => 'Subscriptions Mode',
'note' => '<strong>' . ( ( WCS_Staging::is_duplicate_site() ) ? _x( 'Staging', 'refers to staging site', 'woocommerce-subscriptions' ) : _x( 'Live', 'refers to live site', 'woocommerce-subscriptions' ) ) . '</strong>',
'success' => ( WCS_Staging::is_duplicate_site() ) ? 0 : 1,
);
}
/**
* @param array $debug_data
*/
private static function set_live_site_url( &$debug_data ) {
// Use pre-determined SSR data if possible.
$site_url = isset( self::$report_data['live_url'] )
? self::$report_data['live_url']
: WCS_Staging::get_site_url_from_source( 'subscriptions_install' );
$debug_data['wcs_live_site_url'] = array(
'name' => _x( 'Subscriptions Live URL', 'Live URL, Label on WooCommerce -> System Status page', 'woocommerce-subscriptions' ),
'label' => 'Subscriptions Live URL',
'note' => '<a href="' . esc_url( $site_url ) . '">' . esc_html( $site_url ) . '</a>',
'mark' => '',
'mark_icon' => '',
);
}
/**
* @param array $debug_data
*/
private static function set_library_version( &$debug_data ) {
$debug_data['wcs_subs_core_version'] = array(
'name' => _x( 'Subscriptions-core Library Version', 'Subscriptions-core Version, Label on WooCommerce -> System Status page', 'woocommerce-subscriptions' ),
'label' => 'Subscriptions-core Library Version',
'note' => WC_Subscriptions_Core_Plugin::instance()->get_library_version(),
'mark' => '',
'mark_icon' => '',
'note' => '<strong>' . ( ( WC_Subscriptions::is_duplicate_site() ) ? _x( 'Staging', 'refers to staging site', 'woocommerce-subscriptions' ) : _x( 'Live', 'refers to live site', 'woocommerce-subscriptions' ) ) . '</strong>',
'success' => ( WC_Subscriptions::is_duplicate_site() ) ? 0 : 1,
);
}
@ -194,17 +116,16 @@ class WCS_Admin_System_Status {
if ( ! empty( $theme_overrides['overrides'] ) ) {
$debug_data['wcs_theme_overrides'] = array(
'name' => _x( 'Subscriptions Template Theme Overrides', 'label for the system status page', 'woocommerce-subscriptions' ),
'label' => 'Subscriptions Template Theme Overrides',
'data' => $theme_overrides['overrides'],
'name' => _x( 'Subscriptions Template Theme Overrides', 'label for the system status page', 'woocommerce-subscriptions' ),
'label' => 'Subscriptions Template Theme Overrides',
'data' => $theme_overrides['overrides'],
);
// Include a note on how to update if the templates are out of date.
if ( ! empty( $theme_overrides['has_outdated_templates'] ) && true === $theme_overrides['has_outdated_templates'] ) {
$debug_data['wcs_theme_overrides'] += array(
'mark_icon' => 'warning',
// translators: placeholders are opening/closing tags linking to documentation on outdated templates.
'note' => sprintf( __( '%1$sLearn how to update%2$s', 'woocommerce-subscriptions' ), '<a href="https://developer.woocommerce.com/docs/how-to-fix-outdated-woocommerce-templates/" target="_blank">', '</a>' ),
'note' => sprintf( __( '%sLearn how to update%s', 'woocommerce-subscriptions' ), '<a href="https://docs.woocommerce.com/document/fix-outdated-templates-woocommerce/" target="_blank">', '</a>' ),
);
}
}
@ -213,10 +134,11 @@ class WCS_Admin_System_Status {
/**
* Determine which of our files have been overridden by the theme.
*
* @author Jeremy Pry
* @return array Theme override data.
*/
private static function get_theme_overrides() {
$wcs_template_dir = WC_Subscriptions_Plugin::instance()->get_plugin_directory( 'templates/' );
$wcs_template_dir = dirname( WC_Subscriptions::$plugin_file ) . '/templates/';
$wc_template_path = trailingslashit( wc()->template_path() );
$theme_root = trailingslashit( get_theme_root() );
$overridden = array();
@ -224,7 +146,7 @@ class WCS_Admin_System_Status {
$templates = WC_Admin_Status::scan_template_files( $wcs_template_dir );
foreach ( $templates as $file ) {
$theme_file = false;
$theme_file = $is_outdated = false;
$locations = array(
get_stylesheet_directory() . "/{$file}",
get_stylesheet_directory() . "/{$wc_template_path}{$file}",
@ -246,7 +168,7 @@ class WCS_Admin_System_Status {
$overridden_template_output = sprintf( '<code>%s</code>', esc_html( str_replace( $theme_root, '', $theme_file ) ) );
if ( $core_version && ( empty( $theme_version ) || version_compare( $theme_version, $core_version, '<' ) ) ) {
$outdated = true;
$outdated = true;
$overridden_template_output .= sprintf(
/* translators: %1$s is the file version, %2$s is the core version */
esc_html__( 'version %1$s is out of date. The core version is %2$s', 'woocommerce-subscriptions' ),
@ -268,12 +190,22 @@ class WCS_Admin_System_Status {
* Add a breakdown of Subscriptions per status.
*/
private static function set_subscription_statuses( &$debug_data ) {
$subscriptions_by_status = (array) wp_count_posts( 'shop_subscription' );
$subscriptions_by_status_output = array();
foreach ( $subscriptions_by_status as $status => $count ) {
if ( ! empty( $count ) ) {
$subscriptions_by_status_output[] = $status . ': ' . $count;
}
}
$debug_data['wcs_subscriptions_by_status'] = array(
'name' => _x( 'Subscription Statuses', 'label for the system status page', 'woocommerce-subscriptions' ),
'label' => 'Subscription Statuses',
'mark' => '',
'mark_icon' => '',
'data' => self::get_subscription_statuses(),
'data' => $subscriptions_by_status_output,
);
}
@ -290,10 +222,10 @@ class WCS_Admin_System_Status {
$woocommerce_account_connected = ! empty( $woocommerce_account_auth );
$debug_data['wcs_woocommerce_account_connected'] = array(
'name' => _x( 'WooCommerce Account Connected', 'label for the system status page', 'woocommerce-subscriptions' ),
'label' => 'WooCommerce Account Connected',
'note' => $woocommerce_account_connected ? 'Yes' : 'No',
'success' => $woocommerce_account_connected,
'name' => _x( 'WooCommerce Account Connected', 'label for the system status page', 'woocommerce-subscriptions' ),
'label' => 'WooCommerce Account Connected',
'note' => $woocommerce_account_connected ? 'Yes' : 'No',
'success' => $woocommerce_account_connected,
);
if ( ! $woocommerce_account_connected ) {
@ -307,16 +239,16 @@ class WCS_Admin_System_Status {
foreach ( $woocommerce_account_subscriptions as $subscription ) {
if ( isset( $subscription['product_id'] ) && self::WCS_PRODUCT_ID === $subscription['product_id'] ) {
$has_active_product_key = in_array( $site_id, $subscription['connections'], false ); // phpcs:ignore WordPress.PHP.StrictInArray.FoundNonStrictFalse -- In case the value from $subscription['connections'] is a string.
$has_active_product_key = in_array( $site_id, $subscription['connections'] );
break;
}
}
$debug_data['wcs_active_product_key'] = array(
'name' => _x( 'Active Product Key', 'label for the system status page', 'woocommerce-subscriptions' ),
'label' => 'Active Product Key',
'note' => $has_active_product_key ? 'Yes' : 'No',
'success' => $has_active_product_key,
'name' => _x( 'Active Product Key', 'label for the system status page', 'woocommerce-subscriptions' ),
'label' => 'Active Product Key',
'note' => $has_active_product_key ? 'Yes' : 'No',
'success' => $has_active_product_key,
);
}
@ -324,33 +256,39 @@ class WCS_Admin_System_Status {
* Add a breakdown of subscriptions per payment gateway.
*/
private static function set_subscriptions_by_payment_gateway( &$debug_data ) {
global $wpdb;
$gateways = WC()->payment_gateways->get_available_payment_gateways();
$results = $wpdb->get_results( "
SELECT COUNT(subscriptions.ID) as count, post_meta.meta_value as payment_method, subscriptions.post_status
FROM $wpdb->posts as subscriptions RIGHT JOIN $wpdb->postmeta as post_meta ON post_meta.post_id = subscriptions.ID
WHERE subscriptions.post_type = 'shop_subscription' && post_meta.meta_key = '_payment_method'
GROUP BY post_meta.meta_value, subscriptions.post_status", ARRAY_A );
$subscriptions_by_gateway = isset( self::$report_data['subscriptions_by_payment_gateway'] )
? self::$report_data['subscriptions_by_payment_gateway']
: self::get_subscriptions_by_gateway();
$subscriptions_payment_gateway_data = array();
foreach ( $results as $result ) {
$payment_method = $result['payment_method'];
$subscription_status = $result['post_status'];
foreach ( $subscriptions_by_gateway as $payment_method => $status_counts ) {
if ( isset( $gateways[ $payment_method ] ) ) {
$payment_method_name = $gateways[ $payment_method ]->method_title;
$payment_method_label = $gateways[ $payment_method ]->method_title;
$payment_method_name = $payment_method_label = $gateways[ $payment_method ]->method_title;
} else {
$payment_method_label = 'other';
$payment_method = 'other';
$payment_method_label = $payment_method = 'other';
$payment_method_name = _x( 'Other', 'label for the system status page', 'woocommerce-subscriptions' );
}
$key = 'wcs_payment_method_subscriptions_by' . $payment_method;
$debug_data[ $key ] = array(
'name' => $payment_method_name,
'label' => $payment_method_label,
'data' => array(),
);
foreach ( $status_counts as $status => $count ) {
$debug_data[ $key ]['data'][] = "$status: $count";
if ( ! isset( $debug_data[ $key ] ) ) {
$debug_data[ $key ] = array(
'name' => $payment_method_name,
'label' => $payment_method_label,
'data' => array(),
);
}
$debug_data[ $key ]['data'][] = $subscription_status . ': ' . $result['count'];
}
}
@ -364,7 +302,7 @@ class WCS_Admin_System_Status {
$debug_data[ 'wcs_' . $gateway_id . '_feature_support' ] = array(
'name' => $gateway->method_title,
'label' => $gateway->method_title,
'data' => (array) apply_filters( 'woocommerce_subscriptions_payment_gateway_features_list', $gateway->supports, $gateway ),
'data' => $gateway->supports,
);
if ( 'paypal' === $gateway_id ) {
@ -406,118 +344,4 @@ class WCS_Admin_System_Status {
'mark_icon' => '',
);
}
/**
* Gets the store's subscription broken down by payment gateway and status.
*
* @since 1.0.0 Migrated from WooCommerce Subscriptions v3.1.0.
* @since 7.2.0 Information is cached per request.
*
* @return array The subscription gateway and status data array( 'gateway_id' => array( 'status' => count ) );
*/
public static function get_subscriptions_by_gateway() {
// Return cached result if possible.
if ( isset( self::$statuses_by_gateway ) ) {
return self::$statuses_by_gateway;
}
global $wpdb;
$subscription_gateway_data = [];
$is_hpos_in_use = wcs_is_custom_order_tables_usage_enabled();
$order_status_column_name = $is_hpos_in_use ? 'status' : 'post_status';
// Conduct a different query for HPOS and non-HPOS stores.
if ( $is_hpos_in_use ) {
// With HPOS enabled, `payment_method` is a column in the `wc_orders` table.
$results = $wpdb->get_results(
"SELECT
COUNT(subscriptions.id) as count,
subscriptions.payment_method,
subscriptions.status
FROM {$wpdb->prefix}wc_orders as subscriptions
WHERE subscriptions.type = 'shop_subscription'
GROUP BY subscriptions.payment_method, subscriptions.status",
ARRAY_A
);
} else {
// With HPOS disabled, `_payment_method` is a column in the `postmeta` table.
$results = $wpdb->get_results(
"SELECT
COUNT(subscriptions.ID) as count,
post_meta.meta_value as payment_method,
subscriptions.post_status
FROM {$wpdb->prefix}posts as subscriptions
RIGHT JOIN {$wpdb->prefix}postmeta as post_meta ON post_meta.post_id = subscriptions.ID
WHERE
subscriptions.post_type = 'shop_subscription'
&& post_meta.meta_key = '_payment_method'
GROUP BY post_meta.meta_value, subscriptions.post_status",
ARRAY_A
);
}
foreach ( $results as $result ) {
// Ignore any results that don't have a payment method.
if ( empty( $result['payment_method'] ) ) {
continue;
}
$subscription_gateway_data[ $result['payment_method'] ][ $result[ $order_status_column_name ] ] = $result['count'];
}
self::$statuses_by_gateway = $subscription_gateway_data;
return $subscription_gateway_data;
}
/**
* Gets the store's subscriptions by status.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v3.1.0
* @return array
*/
public static function get_subscription_statuses() {
// We don't look inside self::$report_data here, because the REST API report itself
// also uses self::get_subscription_status_counts().
$subscriptions_by_status = self::get_subscription_status_counts();
$subscriptions_by_status_output = array();
foreach ( $subscriptions_by_status as $status => $count ) {
if ( ! empty( $count ) ) {
$subscriptions_by_status_output[] = $status . ': ' . $count;
}
}
return $subscriptions_by_status_output;
}
/**
* Returns a cached array of subscription statuses along with the corresponding number
* of subscriptions for each (the values).
*
* Example:
*
* [
* 'wc-active' => 100,
* 'wc-cancelled' => 200,
* '...' => 300,
* ]
*
* @param bool $fresh If cached results should be discarded.
*
* @return array
*/
public static function get_subscription_status_counts( bool $fresh = false ): array {
// Return cached result if possible.
if ( ! $fresh && isset( self::$subscription_status_counts ) ) {
return self::$subscription_status_counts;
}
try {
self::$subscription_status_counts = WC_Data_Store::load( 'subscription' )->get_subscriptions_count_by_status();
} catch ( Exception $e ) {
// If an exception was raised, don't cache the result.
return [];
}
return self::$subscription_status_counts;
}
}

View File

@ -8,8 +8,8 @@
* @author Prospress
* @category Admin
* @package WooCommerce Subscriptions/Admin
* @version 1.0.0 - Migrated from WooCommerce Subscriptions v2.3
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3
* @version 2.3
* @since 2.3
*/
if ( ! defined( 'ABSPATH' ) ) {

View File

@ -8,8 +8,8 @@
* @author Prospress
* @category Admin
* @package WooCommerce Subscriptions/Admin
* @version 1.0.0 - Migrated from WooCommerce Subscriptions v2.3
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3
* @version 2.3
* @since 2.3
*/
if ( ! defined( 'ABSPATH' ) ) {

View File

@ -7,8 +7,8 @@
* @author Prospress
* @category Admin
* @package WooCommerce Subscriptions/Admin
* @version 1.0.0 - Migrated from WooCommerce Subscriptions v2.3
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3
* @version 2.3
* @since 2.3
*/
if ( ! defined( 'ABSPATH' ) ) {

View File

@ -8,8 +8,8 @@
* @author Prospress
* @category Admin
* @package WooCommerce Subscriptions/Admin
* @version 1.0.0 - Migrated from WooCommerce Subscriptions v2.3
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3
* @version 2.3
* @since 2.3
*/
if ( ! defined( 'ABSPATH' ) ) {
@ -30,13 +30,14 @@ final class WCS_Debug_Tool_Factory {
* @param string $tool_name The section name given to the tool on the admin screen.
* @param string $tool_desc The long description for the tool on the admin screen.
* @param WCS_Cache_Updater $data_store
* @throws InvalidArgumentException When a class for the given tool is not found.
*/
public static function add_cache_tool( $tool_type, $tool_name, $tool_desc, WCS_Cache_Updater $data_store ) {
if ( ! is_admin() && ! defined( 'DOING_CRON' ) && ! defined( 'WP_CLI' ) ) {
return;
}
self::load_cache_tool_file( $tool_type );
$tool_class_name = self::get_cache_tool_class_name( $tool_type );
$tool_key = self::get_tool_key( $tool_name );
if ( 'generator' === $tool_type ) {
@ -45,15 +46,13 @@ final class WCS_Debug_Tool_Factory {
} else {
$tool = new $tool_class_name( $tool_key, $tool_name, $tool_desc, $data_store );
}
/** @var WCS_Debug_Tool $tool */
$tool->init();
}
/**
* Get the string used to identify the tool.
*
* @param string $tool_name The name of the cache tool being created
* @param string The name of the cache tool being created
* @return string The key used to identify the tool - sanitized name with wcs_ prefix.
*/
protected static function get_tool_key( $tool_name ) {
@ -67,20 +66,49 @@ final class WCS_Debug_Tool_Factory {
*
* To make sure the class's file is loaded, call @see self::load_cache_tool_class() first.
*
* @param string $cache_tool_type The type of cache tool. Known tools are 'eraser' and 'generator'.
* @param array $cache_tool_type The type of cache tool. Known tools are 'eraser' and 'generator'.
* @return string The cache tool's class name.
*/
protected static function get_cache_tool_class_name( $cache_tool_type ) {
$tool_class_name = sprintf( 'WCS_Debug_Tool_Cache_%s', ucfirst( $cache_tool_type ) );
if ( ! class_exists( $tool_class_name ) ) {
throw new InvalidArgumentException( sprintf(
'%s() requires a valid tool name. Class "%s" does not exist.',
__METHOD__,
$tool_class_name
) );
throw new InvalidArgumentException( sprintf( '%s() requires a path to load %s. Class does not exist after loading %s.', __METHOD__, $class_name, $file_path ) );
}
return $tool_class_name;
}
/**
* Load a cache tool file in the default file path.
*
* For example, load_cache_tool( 'related-order', 'generator' ) will load the file includes/admin/debug-tools/class-wcs-debug-tool-related-order-cache-generator.php
*
* @param array $cache_tool_type The type of cache tool. Known tools are 'eraser' and 'generator'.
*/
protected static function load_cache_tool_file( $cache_tool_type ) {
$file_path = sprintf( 'includes/admin/debug-tools/class-wcs-debug-tool-cache-%s.php', $cache_tool_type );
$file_path = plugin_dir_path( WC_Subscriptions::$plugin_file ) . $file_path;
if ( ! file_exists( $file_path ) ) {
throw new InvalidArgumentException( sprintf( '%s() requires a cache name linked to a valid debug tool. File does not exist: %s', __METHOD__, $rel_file_path ) );
}
self::load_required_classes( $cache_tool_type );
require_once( $file_path );
}
/**
* Load classes that debug tools extend.
*
* @param array $cache_tool_type The type of cache tool. Known tools are 'eraser' and 'generator'.
*/
protected static function load_required_classes( $cache_tool_type ) {
require_once( plugin_dir_path( WC_Subscriptions::$plugin_file ) . 'includes/abstracts/abstract-wcs-debug-tool-cache-updater.php' );
if ( 'generator' === $cache_tool_type ) {
require_once( plugin_dir_path( WC_Subscriptions::$plugin_file ) . 'includes/admin/debug-tools/class-wcs-debug-tool-cache-background-updater.php' );
}
}
}

View File

@ -20,26 +20,16 @@ if ( ! defined( 'ABSPATH' ) ) {
class WCS_Meta_Box_Payment_Retries {
/**
* Outputs the Payment retry metabox.
*
* @param WC_Order|WP_Post $order The order object or post object.
* Output the metabox
*/
public static function output( $order ) {
// For backwards compatibility the $order parameter could be a Post.
if ( is_a( $order, 'WP_Post' ) ) {
$order = wc_get_order( $order->ID );
}
if ( ! wcs_is_order( $order ) ) {
return;
}
public static function output( $post ) {
WC()->mailer();
$retries = WCS_Retry_Manager::store()->get_retries_for_order( $order->get_id() );
$retries = WCS_Retry_Manager::store()->get_retries_for_order( $post->ID );
include_once( dirname( __FILE__ ) . '/html-retries-table.php' );
include_once( 'views/html-retries-table.php' );
do_action( 'woocommerce_subscriptions_retries_meta_box', $order->get_id(), $retries );
do_action( 'woocommerce_subscriptions_retries_meta_box', $post->ID, $retries );
}
}

View File

@ -0,0 +1,121 @@
<?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();
$is_subscription_screen = wcs_is_subscription( $post->ID );
// On the subscription page, just show related orders
if ( $is_subscription_screen ) {
$this_subscription = wcs_get_subscription( $post->ID );
$subscriptions[] = $this_subscription;
} elseif ( wcs_order_contains_subscription( $post->ID, array( 'parent', 'renewal' ) ) ) {
$subscriptions = wcs_get_subscriptions_for_order( $post->ID, array( 'order_type' => array( 'parent', 'renewal' ) ) );
}
// First, display all the subscriptions
foreach ( $subscriptions as $subscription ) {
wcs_set_objects_property( $subscription, 'relationship', __( 'Subscription', 'woocommerce-subscriptions' ), 'set_prop_only' );
$orders[] = $subscription;
}
//Resubscribed
$initial_subscriptions = array();
if ( $is_subscription_screen ) {
$initial_subscriptions = wcs_get_subscriptions_for_resubscribe_order( $this_subscription );
$resubscribe_order_ids = WCS_Related_Order_Store::instance()->get_related_order_ids( $this_subscription, 'resubscribe' );
foreach ( $resubscribe_order_ids as $order_id ) {
$order = wc_get_order( $order_id );
$relation = wcs_is_subscription( $order ) ? _x( 'Resubscribed Subscription', 'relation to order', 'woocommerce-subscriptions' ) : _x( 'Resubscribe Order', 'relation to order', 'woocommerce-subscriptions' );
wcs_set_objects_property( $order, 'relationship', $relation, 'set_prop_only' );
$orders[] = $order;
}
} else if ( wcs_order_contains_subscription( $post->ID, array( 'resubscribe' ) ) ) {
$initial_subscriptions = wcs_get_subscriptions_for_order( $post->ID, array( 'order_type' => array( 'resubscribe' ) ) );
}
foreach ( $initial_subscriptions as $subscription ) {
wcs_set_objects_property( $subscription, 'relationship', _x( 'Initial Subscription', 'relation to order', 'woocommerce-subscriptions' ), 'set_prop_only' );
$orders[] = $subscription;
}
// Now, if we're on a single subscription or renewal order's page, display the parent orders
if ( 1 == count( $subscriptions ) ) {
foreach ( $subscriptions as $subscription ) {
if ( $subscription->get_parent_id() ) {
$order = $subscription->get_parent();
wcs_set_objects_property( $order, 'relationship', _x( 'Parent Order', 'relation to order', 'woocommerce-subscriptions' ), 'set_prop_only' );
$orders[] = $order;
}
}
}
// Finally, display the renewal orders
foreach ( $subscriptions as $subscription ) {
foreach ( $subscription->get_related_orders( 'all', 'renewal' ) as $order ) {
wcs_set_objects_property( $order, 'relationship', _x( 'Renewal Order', 'relation to order', 'woocommerce-subscriptions' ), 'set_prop_only' );
$orders[] = $order;
}
}
$orders = apply_filters( 'woocommerce_subscriptions_admin_related_orders_to_display', $orders, $subscriptions, $post );
foreach ( $orders as $order ) {
if ( wcs_get_objects_property( $order, 'id' ) == $post->ID ) {
continue;
}
include( 'views/html-related-orders-row.php' );
}
}
}

View File

@ -4,9 +4,10 @@
*
* Functions for displaying the order data meta box.
*
* @author Prospress
* @category Admin
* @package WooCommerce Subscriptions/Admin/Meta Boxes
* @version 1.0.0 - Migrated from WooCommerce Subscriptions v3.0.0
* @version 2.0
*/
if ( ! defined( 'ABSPATH' ) ) {
@ -19,70 +20,50 @@ if ( ! defined( 'ABSPATH' ) ) {
class WCS_Meta_Box_Subscription_Data extends WC_Meta_Box_Order_Data {
/**
* Outputs the Subscription data metabox.
*
* @param WC_Subscription|WP_Post $subscription The subscription object to display the data metabox for. On CPT stores, this will be a WP Post object.
* Output the metabox
*/
public static function output( $subscription ) {
public static function output( $post ) {
global $the_subscription;
if ( $subscription instanceof WP_Post ) {
$subscription = wcs_get_subscription( $subscription->ID );
if ( ! is_object( $the_subscription ) || $the_subscription->get_id() !== $post->ID ) {
$the_subscription = wc_get_order( $post->ID );
}
if ( ! is_object( $the_subscription ) || $the_subscription->get_id() !== $subscription->get_id() ) {
$the_subscription = $subscription;
}
$subscription = $the_subscription;
self::init_address_fields();
wp_nonce_field( 'woocommerce_save_data', 'woocommerce_meta_nonce' );
$subscription_title = $subscription->get_data_store()->get_title( $subscription );
?>
<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
// @phpstan-ignore empty.variable
echo empty( $order_title ) ? esc_attr( get_post_type_object( $subscription->get_type() )->labels->singular_name ) : esc_attr( $subscription_title );
?>" />
<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
<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>
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">
<h3><?php esc_html_e( 'General', 'woocommerce-subscriptions' ); ?></h3>
<p class="form-field form-field-wide wc-customer-user">
<label for="customer_user"><?php esc_html_e( 'Customer:', 'woocommerce-subscriptions' ); ?> <?php
<label for="customer_user"><?php esc_html_e( 'Customer:', 'woocommerce-subscriptions' ) ?> <?php
if ( $subscription->get_user_id() ) {
$args = array(
'post_status' => 'all',
'post_status' => 'all',
'post_type' => 'shop_subscription',
'_customer_user' => absint( $subscription->get_user_id() ),
);
printf(
'<a href="%s">%s</a>',
printf( '<a href="%s">%s &rarr;</a>',
esc_url( add_query_arg( $args, admin_url( 'edit.php' ) ) ),
esc_html__( 'View other subscriptions &rarr;', 'woocommerce-subscriptions' )
);
printf(
'<a href="%s">%s</a>',
esc_url( add_query_arg( 'user_id', $subscription->get_user_id(), admin_url( 'user-edit.php' ) ) ),
esc_html__( 'Profile &rarr;', 'woocommerce-subscriptions' )
esc_html__( 'View other subscriptions', 'woocommerce-subscriptions' )
);
}
?>
</label>
?></label>
<?php
$user_string = '';
$user_id = '';
@ -91,22 +72,20 @@ class WCS_Meta_Box_Subscription_Data extends WC_Meta_Box_Order_Data {
$user = get_user_by( 'id', $user_id );
$user_string = esc_html( $user->display_name ) . ' (#' . absint( $user->ID ) . ' &ndash; ' . 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&hellip;', 'woocommerce-subscriptions' ),
'selected' => $user_string,
'value' => $user_id,
)
);
WCS_Select2::render( array(
'class' => 'wc-customer-search',
'name' => 'customer_user',
'id' => 'customer_user',
'placeholder' => esc_attr__( 'Search for a customer&hellip;', '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" class="wc-enhanced-select">
<select id="order_status" name="order_status">
<?php
$statuses = wcs_get_subscription_statuses();
foreach ( $statuses as $status => $status_name ) {
@ -120,46 +99,35 @@ class WCS_Meta_Box_Subscription_Data extends WC_Meta_Box_Order_Data {
</p>
<?php
$parent_order = $subscription->get_parent();
if ( $parent_order ) {
?>
if ( $parent_order ) { ?>
<p class="form-field form-field-wide">
<?php echo esc_html__( 'Parent order: ', 'woocommerce-subscriptions' ); ?>
<a href="<?php echo esc_url( wcs_get_edit_post_link( $subscription->get_parent_id() ) ); ?>">
<?php
// translators: placeholder is an order number.
echo sprintf( esc_html__( '#%1$s', 'woocommerce-subscriptions' ), esc_html( $parent_order->get_order_number() ) );
?>
<?php echo esc_html__( 'Parent order: ', 'woocommerce-subscriptions' ) ?>
<a href="<?php echo esc_url( get_edit_post_link( $subscription->get_parent_id() ) ); ?>">
<?php echo sprintf( esc_html__( '#%1$s', 'woocommerce-subscriptions' ), esc_html( $parent_order->get_order_number() ) ); ?>
</a>
</p>
<?php
} else {
?>
<?php } else {
?>
<p class="form-field form-field-wide">
<label for="parent-order-id"><?php esc_html_e( 'Parent order:', 'woocommerce-subscriptions' ); ?> </label>
<?php
WCS_Select2::render(
array(
'class' => 'wc-enhanced-select',
'name' => 'parent-order-id',
'id' => 'parent-order-id',
'placeholder' => esc_attr__( 'Select an order&hellip;', 'woocommerce-subscriptions' ),
)
);
WCS_Select2::render( array(
'class' => 'wc-enhanced-select',
'name' => 'parent-order-id',
'id' => 'parent-order-id',
'placeholder' => esc_attr__( 'Select an order&hellip;', 'woocommerce-subscriptions' ),
) );
?>
</p>
<?php
}
do_action( 'woocommerce_admin_order_data_after_order_details', $subscription );
?>
<?php }
do_action( 'woocommerce_admin_order_data_after_order_details', $subscription ); ?>
</div>
<div class="order_data_column">
<h3>
<?php esc_html_e( 'Billing', 'woocommerce-subscriptions' ); ?>
<?php esc_html_e( 'Billing Details', 'woocommerce-subscriptions' ); ?>
<a href="#" class="edit_address"><?php esc_html_e( 'Edit', 'woocommerce-subscriptions' ); ?></a>
<span>
<a href="#" class="load_customer_billing" style="display:none;"><?php esc_html_e( 'Load billing address', 'woocommerce-subscriptions' ); ?></a>
</span>
<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
@ -188,12 +156,11 @@ class WCS_Meta_Box_Subscription_Data extends WC_Meta_Box_Order_Data {
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() ) ); // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
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() ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
// translators: %s: gateway ID.
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() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
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>';
@ -210,20 +177,13 @@ class WCS_Meta_Box_Subscription_Data extends WC_Meta_Box_Order_Data {
if ( ! isset( $field['id'] ) ) {
$field['id'] = '_billing_' . $key;
}
switch ( $field['type'] ) {
case 'select':
wcs_woocommerce_wp_select( $field, $subscription );
break;
default:
if ( is_callable( array( $subscription, 'get_billing_' . $key ) ) ) {
$field['value'] = $subscription->{"get_billing_$key"}();
} else {
$field['value'] = $subscription->get_meta( $field['id'] );
}
case 'select' :
woocommerce_wp_select( $field );
break;
default :
woocommerce_wp_text_input( $field );
break;
break;
}
}
@ -232,33 +192,15 @@ class WCS_Meta_Box_Subscription_Data extends WC_Meta_Box_Order_Data {
echo '</div>';
do_action( 'woocommerce_admin_order_data_after_billing_address', $subscription );
// Display a link to the customer's add/change payment method screen.
if ( $subscription->can_be_updated_to( 'new-payment-method' ) ) {
if ( $subscription->has_payment_gateway() ) {
$link_text = __( 'Customer change payment method page &rarr;', 'woocommerce-subscriptions' );
} else {
$link_text = __( 'Customer add payment method page &rarr;', 'woocommerce-subscriptions' );
}
printf(
'<a href="%s">%s</a>',
esc_url( $subscription->get_change_payment_method_url() ),
esc_html( $link_text )
);
}
?>
</div>
<div class="order_data_column">
<h3>
<?php esc_html_e( 'Shipping', 'woocommerce-subscriptions' ); ?>
<?php esc_html_e( 'Shipping Details', 'woocommerce-subscriptions' ); ?>
<a href="#" class="edit_address"><?php esc_html_e( 'Edit', 'woocommerce-subscriptions' ); ?></a>
<span>
<a href="#" class="load_customer_shipping" style="display:none;"><?php esc_html_e( 'Load shipping address', 'woocommerce-subscriptions' ); ?></a>
<a href="#" class="billing-same-as-shipping" style="display:none;"><?php esc_html_e( 'Copy billing address', 'woocommerce-subscriptions' ); ?></a>
</span>
<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
@ -288,8 +230,8 @@ class WCS_Meta_Box_Subscription_Data extends WC_Meta_Box_Order_Data {
}
}
if ( apply_filters( 'woocommerce_enable_order_notes_field', 'yes' === get_option( 'woocommerce_enable_order_comments', 'yes' ) ) && $subscription->get_customer_note() ) {
echo '<p><strong>' . esc_html__( 'Customer Provided Note', 'woocommerce-subscriptions' ) . ':</strong> ' . wp_kses_post( nl2br( $subscription->get_customer_note() ) ) . '</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>';
@ -307,26 +249,20 @@ class WCS_Meta_Box_Subscription_Data extends WC_Meta_Box_Order_Data {
}
switch ( $field['type'] ) {
case 'select':
wcs_woocommerce_wp_select( $field, $subscription );
break;
default:
if ( is_callable( array( $subscription, 'get_shipping_' . $key ) ) ) {
$field['value'] = $subscription->{"get_shipping_$key"}();
} else {
$field['value'] = $subscription->get_meta( $field['id'] );
}
case 'select' :
woocommerce_wp_select( $field );
break;
default :
woocommerce_wp_text_input( $field );
break;
break;
}
}
}
if ( apply_filters( 'woocommerce_enable_order_notes_field', 'yes' === get_option( 'woocommerce_enable_order_comments', 'yes' ) ) ) {
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( $subscription->get_customer_note() ); ?></textarea>
<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
}
@ -344,123 +280,76 @@ class WCS_Meta_Box_Subscription_Data extends WC_Meta_Box_Order_Data {
}
/**
* Saves the subscription data meta box.
* Save meta box data
*
* @see woocommerce_process_shop_order_meta
*
* @param int $subscription_id Subscription ID.
* @param WC_Subscription $subscription Optional. Subscription object. Default null - will be loaded from the ID.
* @param int $post_id
* @param WP_Post $post
*/
public static function save( $subscription_id, $subscription = null ) {
if ( ! wcs_is_subscription( $subscription_id ) ) {
return;
}
if ( empty( $_POST['woocommerce_meta_nonce'] ) || ! wp_verify_nonce( wc_clean( wp_unslash( $_POST['woocommerce_meta_nonce'] ) ), 'woocommerce_save_data' ) ) {
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 = is_a( $subscription, 'WC_Subscription' ) ? $subscription : wcs_get_subscription( $subscription_id );
$props = array();
$subscription = wcs_get_subscription( $post_id );
// Ensure there is an order key.
if ( ! $subscription->get_order_key() ) {
$props['order_key'] = wcs_generate_order_key();
$key = 'wc_' . apply_filters( 'woocommerce_generate_order_key', uniqid( 'order_' ) );
wcs_set_objects_property( $subscription, 'order_key', $key );
}
// Update customer.
$customer_id = isset( $_POST['customer_user'] ) ? absint( $_POST['customer_user'] ) : 0;
if ( $customer_id !== $subscription->get_customer_id() ) {
$props['customer_id'] = $customer_id;
}
// Update meta
update_post_meta( $post_id, '_customer_user', absint( $_POST['customer_user'] ) );
// Update billing fields.
// 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;
}
$value = wc_clean( wp_unslash( $_POST[ $field['id'] ] ) );
if ( is_callable( array( $subscription, 'set_billing_' . $key ) ) ) {
$props[ "billing_{$key}" ] = $value;
} else {
$subscription->update_meta_data( $field['id'], $value );
}
wcs_set_objects_property( $subscription, $field['id'], wc_clean( $_POST[ $field['id'] ] ) );
}
// Update shipping fields.
// 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;
}
$value = wc_clean( wp_unslash( $_POST[ $field['id'] ] ) );
if ( is_callable( array( $subscription, 'set_shipping_' . $key ) ) ) {
$props[ "shipping_{$key}" ] = $value;
} else {
$subscription->update_meta_data( $field['id'], $value );
}
wcs_set_objects_property( $subscription, $field['id'], wc_clean( $_POST[ $field['id'] ] ) );
}
// Customer note.
if ( isset( $_POST['excerpt'] ) ) {
$props['customer_note'] = sanitize_textarea_field( wp_unslash( $_POST['excerpt'] ) );
}
$subscription->set_props( $props );
$subscription->save();
// Save the linked parent order ID.
// Save the linked parent order id
if ( ! empty( $_POST['parent-order-id'] ) ) {
$parent_order_id = wc_clean( wp_unslash( $_POST['parent-order-id'] ) );
// If the parent order to be set is a renewal order.
if ( wcs_order_contains_renewal( $parent_order_id ) ) {
// remove renewal order meta flag.
$parent = wc_get_order( $parent_order_id );
// if the parent order to be set is a renewal order
if ( wcs_order_contains_renewal( $_POST['parent-order-id'] ) ) {
// remove renewal meta
$parent = wc_get_order( $_POST['parent-order-id'] );
wcs_delete_objects_property( $parent, 'subscription_renewal' );
}
$subscription->set_parent_id( $parent_order_id );
// translators: %s: parent order number (linked to its details screen).
$subscription->set_parent_id( wc_clean( $_POST['parent-order-id'] ) );
$subscription->add_order_note( sprintf( _x( 'Subscription linked to parent order %s via admin.', 'subscription note after linking to a parent order', 'woocommerce-subscriptions' ), sprintf( '<a href="%1$s">#%2$s</a> ', esc_url( wcs_get_edit_post_link( $subscription->get_parent_id() ) ), $subscription->get_parent()->get_order_number() ) ), false, true );
$subscription->save();
}
try {
WCS_Change_Payment_Method_Admin::save_meta( $subscription );
$order_status = wc_clean( wp_unslash( $_POST['order_status'] ?? '' ) );
if ( 'cancelled' === $order_status ) {
if ( 'cancelled' == $_POST['order_status'] ) {
$subscription->cancel_order();
} else {
$subscription->update_status( $order_status, '', true );
$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' );
}
if ( isset( $_POST['original_post_status'] ) && 'auto-draft' === $_POST['original_post_status'] ) {
$subscription->set_created_via( 'admin' );
$subscription->save();
/**
* Fire an action after a subscription is created via the admin screen.
*
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.4.1
* @param WC_Subscription $subscription The subscription object.
*/
do_action( 'woocommerce_admin_created_subscription', $subscription );
}
do_action( 'woocommerce_process_shop_subscription_meta', $subscription_id, $subscription );
do_action( 'woocommerce_process_shop_subscription_meta', $post_id, $post );
}
}

View File

@ -0,0 +1,84 @@
<?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();
}
}
}

View File

@ -0,0 +1,45 @@
<?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>

View File

@ -0,0 +1,28 @@
<?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>

View File

@ -17,19 +17,19 @@ if ( ! defined( 'ABSPATH' ) ) {
<th><?php esc_html_e( 'Retry Date', 'woocommerce-subscriptions' ); ?></th>
<th>
<?php esc_html_e( 'Retry Status', 'woocommerce-subscriptions' ); ?>
<?php echo wc_help_tip( __( 'The status of the automatic payment retry: pending means the retry will be processed in the future, failed means the payment was not successful when retried and completed means the payment succeeded when retried.', 'woocommerce-subscriptions' ) ); ?>
<?php echo wcs_help_tip( __( 'The status of the automatic payment retry: pending means the retry will be processed in the future, failed means the payment was not successful when retried and completed means the payment succeeded when retried.', 'woocommerce-subscriptions' ) ); ?>
</th>
<th>
<?php esc_html_e( 'Status of Order', 'woocommerce-subscriptions' ); ?>
<?php echo wc_help_tip( __( 'The status applied to the order for the time between when the renewal payment failed or last retry occurred and when this retry was processed.', 'woocommerce-subscriptions' ) ); ?>
<?php echo wcs_help_tip( __( 'The status applied to the order for the time between when the renewal payment failed or last retry occurred and when this retry was processed.', 'woocommerce-subscriptions' ) ); ?>
</th>
<th>
<?php esc_html_e( 'Status of Subscription', 'woocommerce-subscriptions' ); ?>
<?php echo wc_help_tip( __( 'The status applied to the subscription for the time between when the renewal payment failed or last retry occurred and when this retry was processed.', 'woocommerce-subscriptions' ) ); ?>
<?php echo wcs_help_tip( __( 'The status applied to the subscription for the time between when the renewal payment failed or last retry occurred and when this retry was processed.', 'woocommerce-subscriptions' ) ); ?>
</th>
<th>
<?php esc_html_e( 'Email', 'woocommerce-subscriptions' ); ?>
<?php echo wc_help_tip( __( 'The email sent to the customer when the renewal payment or payment retry failed to notify them that the payment would be retried.', 'woocommerce-subscriptions' ) ); ?>
<?php echo wcs_help_tip( __( 'The email sent to the customer when the renewal payment or payment retry failed to notify them that the payment would be retried.', 'woocommerce-subscriptions' ) ); ?>
</th>
</tr>
</thead>
@ -42,7 +42,7 @@ if ( ! defined( 'ABSPATH' ) ) {
if ( $retry->get_time() > 0 ) {
// translators: php date format
$t_time = date( _x( 'Y/m/d g:i:s A', 'post date', 'woocommerce-subscriptions' ), $retry->get_time() );
$date_to_display = ucfirst( wcs_get_human_time_diff( $retry->get_time() ) );
$date_to_display = wcs_get_human_time_diff( $retry->get_time() );
} else {
$t_time = $date_to_display = __( 'Unpublished', 'woocommerce-subscriptions' );
} ?>
@ -53,22 +53,12 @@ if ( ! defined( 'ABSPATH' ) ) {
<td>
<?php echo esc_html( ucwords( $retry->get_status() ) ); ?>
</td>
<?php
foreach ( array( 'order', 'subscription' ) as $object_type ) :
$status = $rule->get_status_to_apply( $object_type );
$css_classes = array( 'order-status', 'status-' . $status );
if ( 'subscription' === $object_type ) :
$status_name = wcs_get_subscription_status_name( $status );
$css_classes[] = 'subscription-status';
else :
$status_name = wc_get_order_status_name( $status );
endif;
?>
<td>
<mark class="<?php echo esc_attr( implode( ' ', $css_classes ) ); ?>"><span><?php echo esc_html( $status_name ); ?></span></mark>
<?php echo esc_html( ucwords( $rule->get_status_to_apply( 'order' ) ) ); ?>
</td>
<td>
<?php echo esc_html( ucwords( $rule->get_status_to_apply( 'subscription' ) ) ); ?>
</td>
<?php endforeach; ?>
<td>
<?php $email_class = $rule->get_email_template(); ?>
<?php if ( ! empty( $email_class ) && class_exists( $email_class ) ) : ?>

View File

@ -14,31 +14,26 @@ if ( ! defined( 'ABSPATH' ) ) {
<div id="billing-schedule">
<?php if ( $the_subscription->can_date_be_updated( 'next_payment' ) ) : ?>
<div class="billing-schedule-edit wcs-date-input">
<?php
// Subscription Period Interval
wcs_woocommerce_wp_select(
array(
'id' => '_billing_interval',
'class' => 'billing_interval',
'label' => __( 'Payment:', 'woocommerce-subscriptions' ),
'value' => $the_subscription->get_billing_interval(),
'options' => wcs_get_subscription_period_interval_strings(),
),
$the_subscription
);
<div class="billing-schedule-edit wcs-date-input"><?php
// Subscription Period Interval
echo woocommerce_wp_select( array(
'id' => '_billing_interval',
'class' => 'billing_interval',
'label' => __( 'Payment:', 'woocommerce-subscriptions' ),
'value' => $the_subscription->get_billing_interval(),
'options' => wcs_get_subscription_period_interval_strings(),
)
);
// Billing Period
wcs_woocommerce_wp_select(
array(
'id' => '_billing_period',
'class' => 'billing_period',
'label' => __( 'Billing Period', 'woocommerce-subscriptions' ),
'value' => $the_subscription->get_billing_period(),
'options' => wcs_get_subscription_period_strings(),
),
$the_subscription
);
// Billing Period
echo woocommerce_wp_select( array(
'id' => '_billing_period',
'class' => 'billing_period',
'label' => __( 'Billing Period', 'woocommerce-subscriptions' ),
'value' => $the_subscription->get_billing_period(),
'options' => wcs_get_subscription_period_strings(),
)
);
?>
<input type="hidden" name="wcs-lengths" id="wcs-lengths" data-subscription_lengths="<?php echo esc_attr( wcs_json_encode( wcs_get_subscription_ranges() ) ); ?>">
</div>
@ -52,19 +47,15 @@ if ( ! defined( 'ABSPATH' ) ) {
<?php $internal_date_key = wcs_normalise_date_type_key( $date_key ) ?>
<?php if ( false === wcs_display_date_type( $date_key, $the_subscription ) ) : ?>
<?php continue; ?>
<?php endif; ?>
<?php endif;?>
<div id="subscription-<?php echo esc_attr( $date_key ); ?>-date" class="date-fields">
<strong><?php echo esc_html( $date_label ); ?>:</strong>
<input type="hidden" name="<?php echo esc_attr( $date_key ); ?>_timestamp_utc" id="<?php echo esc_attr( $date_key ); ?>_timestamp_utc" value="<?php echo esc_attr( $the_subscription->get_time( $internal_date_key, 'gmt' ) ); ?>"/>
<?php if ( $the_subscription->can_date_be_updated( $internal_date_key ) ) : ?>
<?php echo wp_kses( wcs_date_input( $the_subscription->get_time( $internal_date_key, 'site' ), array( 'name_attr' => $date_key ) ), array( 'input' => array( 'type' => array(), 'class' => array(), 'placeholder' => array(), 'name' => array(), 'id' => array(), 'maxlength' => array(), 'size' => array(), 'value' => array(), 'pattern' => array() ), 'div' => array( 'class' => array() ), 'span' => array(), 'br' => array() ) ); // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound ?>
<?php echo wp_kses( wcs_date_input( $the_subscription->get_time( $internal_date_key, 'site' ), array( 'name_attr' => $date_key ) ), array( 'input' => array( 'type' => array(), 'class' => array(), 'placeholder' => array(), 'name' => array(), 'id' => array(), 'maxlength' => array(), 'size' => array(), 'value' => array(), 'patten' => array() ), 'div' => array( 'class' => array() ), 'span' => array(), 'br' => array() ) ); ?>
<?php else : ?>
<?php echo esc_html( $the_subscription->get_date_to_display( $internal_date_key ) ); ?>
<?php endif; ?>
<div class="message" aria-live="assertive" aria-atomic="true" role="alert" style="display: none;">
<span class="screen-reader-text"><?php esc_html_e( 'Error:', 'woocommerce-subscriptions' ); ?></span>
<span class="message-content"></span>
</div>
</div>
<?php endforeach; ?>
<p><?php esc_html_e( 'Timezone:', 'woocommerce-subscriptions' ); ?> <span id="wcs-timezone"><?php esc_html_e( 'Error: unable to find timezone of your browser.', 'woocommerce-subscriptions' ); ?></span></p>

187
includes/admin/reports/class-wcs-report-cache-manager.php Normal file → Executable file
View File

@ -27,38 +27,35 @@ 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 => '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',
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',
),
'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_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_subscriptions_switch_completed' => array(
1 => 'WCS_Report_Subscription_Events_By_Date',
0 => 'WC_Report_Subscription_Events_By_Date',
),
'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_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_active' => array(
2 => 'WCS_Report_Upcoming_Recurring_Revenue',
'woocommerce_subscription_status_active' => array(
1 => 'WC_Report_Upcoming_Recurring_Revenue',
),
'woocommerce_new_order_item' => array(
4 => 'WCS_Report_Subscription_By_Product',
'woocommerce_new_order_item' => array(
3 => 'WC_Report_Subscription_By_Product',
),
'woocommerce_update_order_item' => array(
4 => 'WCS_Report_Subscription_By_Product',
'woocommerce_update_order_item' => array(
3 => 'WC_Report_Subscription_By_Product',
),
);
/**
* 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.
* 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.
*/
private $reports_to_update = array();
@ -75,11 +72,12 @@ class WCS_Report_Cache_Manager {
/**
* Attach callbacks to manage cache updates
*
* @since 7.8.0 - Compatible with HPOS, originally introduced in 2.1
* @since 2.1
*/
public function __construct() {
// Use the old hooks
if ( wcs_is_woocommerce_pre( '3.0' ) ) {
if ( WC_Subscriptions::is_woocommerce_pre( '3.0' ) ) {
$hooks = array(
'woocommerce_order_add_product' => 'woocommerce_new_order_item',
@ -105,8 +103,6 @@ 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 );
}
/**
@ -116,7 +112,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 void
* @return null
*/
public function set_reports_to_update() {
if ( isset( $this->update_events_and_classes[ current_filter() ] ) ) {
@ -140,38 +136,45 @@ class WCS_Report_Cache_Manager {
*/
public function schedule_cache_updates() {
if ( empty( $this->reports_to_update ) ) {
return;
}
if ( ! empty( $this->reports_to_update ) ) {
// On large sites, we want to run the cache update once at 4am in the site's timezone
if ( $this->use_large_site_cache() ) {
// On large sites, we want to run the cache update once at 4am in the site's timezone
if ( $this->use_large_site_cache() ) {
$cache_update_timestamp = $this->get_large_site_cache_update_timestamp();
$four_am_site_time = new WC_DateTime( '4 am', wcs_get_sites_timezone() );
// 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 ) {
// Convert to a UTC timestamp for scheduling
$cache_update_timestamp = $four_am_site_time->getTimestamp();
$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 );
// 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;
}
// 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 );
// 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 );
}
}
}
}
@ -180,6 +183,7 @@ 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 ) {
/**
@ -211,29 +215,23 @@ class WCS_Report_Cache_Manager {
// Load report class dependencies
require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
require_once( WC()->plugin_path() . '/includes/admin/reports/class-wc-admin-report.php' );
$within_ci_environment = getenv( 'CI' );
$wc_core_dir_from_env = getenv( 'WC_CORE_DIR' );
$report_name = strtolower( str_replace( '_', '-', str_replace( 'WC_Report_', '', $report_class ) ) );
$report_path = WCS_Admin_Reports::initialize_reports_path( '', $report_name, $report_class );
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' );
require_once( $report_path );
$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 ) {
@ -256,7 +254,23 @@ class WCS_Report_Cache_Manager {
protected function use_large_site_cache() {
if ( null === $this->use_large_site_cache ) {
$this->use_large_site_cache = wcs_is_large_site();
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;
}
}
return apply_filters( 'wcs_report_use_large_site_cache', $this->use_large_site_cache );
@ -332,7 +346,7 @@ class WCS_Report_Cache_Manager {
'label' => 'Cache Update Failures',
/* translators: %d refers to the number of times we have detected cache update failures */
'note' => sprintf( _n( '%d failures', '%d failure', $failures, 'woocommerce-subscriptions' ), $failures ),
'success' => 0 === (int)$failures,
'success' => 0 === $failures,
),
);
@ -340,41 +354,6 @@ class WCS_Report_Cache_Manager {
return $data;
}
/**
* 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' );
}
}
}
return new WCS_Report_Cache_Manager();

514
includes/admin/reports/class-wcs-report-dashboard.php Normal file → Executable file
View File

@ -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,20 +16,6 @@ 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
@ -43,92 +29,62 @@ 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() {
$report_data = self::get_data();
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 ) );
?>
<li class="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 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>
</li>
<li class="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 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>
</li>
<?php
}
/**
@ -137,380 +93,8 @@ class WCS_Report_Dashboard {
* @since 2.1
*/
public static function dashboard_scripts() {
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;
wp_enqueue_style( 'wcs-dashboard-report', plugin_dir_url( WC_Subscriptions::$plugin_file ) . 'assets/css/dashboard.css', array(), WC_Subscriptions::$version );
}
}
return new WCS_Report_Dashboard();

Some files were not shown because too many files have changed in this diff Show More