Merge branch 'master' of github.com:woocommerce/wc-admin into fix/1352-backspace-remove-item
This commit is contained in:
commit
f28e74daf9
|
@ -26,6 +26,7 @@ wp-cli.local.yml
|
|||
yarn.lock
|
||||
tests
|
||||
vendor
|
||||
config
|
||||
node_modules
|
||||
*.sql
|
||||
*.tar.gz
|
||||
|
|
|
@ -8,6 +8,7 @@ build-style
|
|||
languages/*
|
||||
!languages/README.md
|
||||
wc-admin.zip
|
||||
includes/feature-config.php
|
||||
|
||||
# Directories/files that may appear in your environment
|
||||
.DS_Store
|
||||
|
|
|
@ -28,6 +28,10 @@ There are also some helper scripts:
|
|||
- `npm run i18n` : A multi-step process, used to create a pot file from both the JS and PHP gettext calls. First it runs `i18n:js`, which creates a temporary `.pot` file from the JS files. Next it runs `i18n:php`, which converts that `.pot` file to a PHP file. Lastly, it runs `i18n:pot`, which creates the final `.pot` file from all the PHP files in the plugin (including the generated one with the JS strings).
|
||||
- `npm test` : Run the JS test suite
|
||||
|
||||
To debug synced lookup information in the database, you can bypass the action scheduler and immediately sync order and customer information by using the `woocommerce_disable_order_scheduling` hook.
|
||||
|
||||
`add_filter( 'woocommerce_disable_order_scheduling', '__return_true' );`
|
||||
|
||||
## Privacy
|
||||
|
||||
If you have enabled WooCommerce usage tracking ( option `woocommerce_allow_tracking` ) then, in addition to the tracking described in https://woocommerce.com/usage-tracking/, this plugin also sends information about the actions that site administrators perform to Automattic - see https://automattic.com/privacy/#information-we-collect-automatically for more information.
|
||||
|
|
|
@ -63,6 +63,7 @@ fi
|
|||
|
||||
# Run the build.
|
||||
status "Generating build... 👷♀️"
|
||||
npm run build:feature-config
|
||||
npm run build
|
||||
npm run docs
|
||||
|
||||
|
@ -79,6 +80,6 @@ zip -r wc-admin.zip \
|
|||
$build_files \
|
||||
languages/wc-admin.pot \
|
||||
languages/wc-admin.php \
|
||||
README.md
|
||||
readme.txt
|
||||
|
||||
success "Done. You've built WooCommerce Admin! 🎉 "
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
/**
|
||||
* Generates an array of feature flags, based on the config used by the client application.
|
||||
*
|
||||
* @package WooCommerce Admin
|
||||
*/
|
||||
|
||||
$phase = isset( $_SERVER['WC_ADMIN_PHASE'] ) ? $_SERVER['WC_ADMIN_PHASE'] : ''; // WPCS: sanitization ok.
|
||||
if ( ! in_array( $phase, array( 'development', 'plugin', 'core' ) ) ) {
|
||||
$phase = 'core';
|
||||
}
|
||||
$config_json = file_get_contents( 'config/' . $phase . '.json' );
|
||||
$config = json_decode( $config_json );
|
||||
|
||||
$write = "<?php\n";
|
||||
$write .= "// WARNING: Do not directly edit this file.\n";
|
||||
$write .= "// This file is auto-generated as part of the build process and things may break.\n";
|
||||
$write .= "function wc_admin_get_feature_config() {\n";
|
||||
$write .= "\treturn array(\n";
|
||||
foreach ( $config->features as $feature => $bool ) {
|
||||
$write .= "\t\t'{$feature}' => " . ( $bool ? 'true' : 'false' ) . ",\n";
|
||||
}
|
||||
$write .= "\t);\n";
|
||||
$write .= "}\n";
|
||||
|
||||
$config_file = fopen( 'includes/feature-config.php', 'w' );
|
||||
fwrite( $config_file, $write );
|
||||
fclose( $config_file );
|
|
@ -2,6 +2,7 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Component } from '@wordpress/element';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { format as formatDate } from '@wordpress/date';
|
||||
|
@ -89,6 +90,7 @@ export class ReportChart extends Component {
|
|||
|
||||
renderChart( mode, isRequesting, chartData ) {
|
||||
const {
|
||||
emptySearchResults,
|
||||
interactiveLegend,
|
||||
itemsLabel,
|
||||
legendPosition,
|
||||
|
@ -101,11 +103,15 @@ export class ReportChart extends Component {
|
|||
const currentInterval = getIntervalForQuery( query );
|
||||
const allowedIntervals = getAllowedIntervalsForQuery( query );
|
||||
const formats = getDateFormatsForInterval( currentInterval, primaryData.data.intervals.length );
|
||||
const emptyMessage = emptySearchResults
|
||||
? __( 'No data for the current search', 'wc-admin' )
|
||||
: __( 'No data for the selected date range', 'wc-admin' );
|
||||
return (
|
||||
<Chart
|
||||
allowedIntervals={ allowedIntervals }
|
||||
data={ chartData }
|
||||
dateParser={ '%Y-%m-%dT%H:%M:%S' }
|
||||
emptyMessage={ emptyMessage }
|
||||
interactiveLegend={ interactiveLegend }
|
||||
interval={ currentInterval }
|
||||
isRequesting={ isRequesting }
|
||||
|
@ -119,7 +125,7 @@ export class ReportChart extends Component {
|
|||
tooltipLabelFormat={ formats.tooltipLabelFormat }
|
||||
tooltipTitle={ ( 'time-comparison' === mode && selectedChart.label ) || null }
|
||||
tooltipValueFormat={ getTooltipValueFormat( selectedChart.type ) }
|
||||
type={ getChartTypeForQuery( query ) }
|
||||
chartType={ getChartTypeForQuery( query ) }
|
||||
valueType={ selectedChart.type }
|
||||
xFormat={ formats.xFormat }
|
||||
x2Format={ formats.x2Format }
|
||||
|
@ -222,6 +228,7 @@ export default compose(
|
|||
|
||||
if ( query.search && ! ( query[ endpoint ] && query[ endpoint ].length ) ) {
|
||||
return {
|
||||
emptySearchResults: true,
|
||||
mode: chartMode,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -61,8 +61,15 @@ export class ReportSummary extends Component {
|
|||
|
||||
const renderSummaryNumbers = ( { onToggle } ) =>
|
||||
charts.map( chart => {
|
||||
const { key, label, type } = chart;
|
||||
const href = getNewPath( { chart: key } );
|
||||
const { key, order, orderby, label, type } = chart;
|
||||
const newPath = { chart: key };
|
||||
if ( orderby ) {
|
||||
newPath.orderby = orderby;
|
||||
}
|
||||
if ( order ) {
|
||||
newPath.order = order;
|
||||
}
|
||||
const href = getNewPath( newPath );
|
||||
const isSelected = selectedChart.key === key;
|
||||
const { delta, prevValue, value } = this.getValues( key, type );
|
||||
|
||||
|
|
|
@ -194,7 +194,9 @@ export default compose(
|
|||
if ( query.search && ! ( query[ endpoint ] && query[ endpoint ].length ) ) {
|
||||
return {};
|
||||
}
|
||||
const chartEndpoint = 'variations' === endpoint ? 'products' : endpoint;
|
||||
const chartEndpoint = [ 'variations', 'categories' ].includes( endpoint )
|
||||
? 'products'
|
||||
: endpoint;
|
||||
const primaryData = getSummary
|
||||
? getReportChartData( chartEndpoint, 'primary', query, select )
|
||||
: {};
|
||||
|
|
|
@ -13,16 +13,22 @@ export const charts = [
|
|||
{
|
||||
key: 'items_sold',
|
||||
label: __( 'Items Sold', 'wc-admin' ),
|
||||
order: 'desc',
|
||||
orderby: 'items_sold',
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
key: 'net_revenue',
|
||||
label: __( 'Net Revenue', 'wc-admin' ),
|
||||
order: 'desc',
|
||||
orderby: 'net_revenue',
|
||||
type: 'currency',
|
||||
},
|
||||
{
|
||||
key: 'orders_count',
|
||||
label: __( 'Orders Count', 'wc-admin' ),
|
||||
order: 'desc',
|
||||
orderby: 'orders_count',
|
||||
type: 'number',
|
||||
},
|
||||
];
|
||||
|
@ -30,7 +36,7 @@ export const charts = [
|
|||
export const filters = [
|
||||
{
|
||||
label: __( 'Show', 'wc-admin' ),
|
||||
staticParams: [ 'chart' ],
|
||||
staticParams: [],
|
||||
param: 'filter',
|
||||
showFilters: () => true,
|
||||
filters: [
|
||||
|
@ -77,13 +83,13 @@ export const filters = [
|
|||
label: __( 'Top Categories by Items Sold', 'wc-admin' ),
|
||||
value: 'top_items',
|
||||
chartMode: 'item-comparison',
|
||||
query: { orderby: 'items_sold', order: 'desc' },
|
||||
query: { orderby: 'items_sold', order: 'desc', chart: 'items_sold' },
|
||||
},
|
||||
{
|
||||
label: __( 'Top Categories by Net Revenue', 'wc-admin' ),
|
||||
value: 'top_revenue',
|
||||
chartMode: 'item-comparison',
|
||||
query: { orderby: 'net_revenue', order: 'desc' },
|
||||
query: { orderby: 'net_revenue', order: 'desc', chart: 'net_revenue' },
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
*/
|
||||
import { Component, Fragment } from '@wordpress/element';
|
||||
import PropTypes from 'prop-types';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
|
@ -20,23 +21,50 @@ import ReportChart from 'analytics/components/report-chart';
|
|||
import ReportSummary from 'analytics/components/report-summary';
|
||||
|
||||
export default class CategoriesReport extends Component {
|
||||
getChartMeta() {
|
||||
const { query } = this.props;
|
||||
|
||||
const isCategoryDetailsView = [ 'top_items', 'top_revenue', 'compare-categories' ].includes(
|
||||
query.filter
|
||||
);
|
||||
const mode = isCategoryDetailsView ? 'item-comparison' : 'time-comparison';
|
||||
const itemsLabel = __( '%d categories', 'wc-admin' );
|
||||
|
||||
return {
|
||||
itemsLabel,
|
||||
mode,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { query, path } = this.props;
|
||||
const { mode, itemsLabel } = this.getChartMeta();
|
||||
|
||||
const chartQuery = {
|
||||
...query,
|
||||
};
|
||||
|
||||
if ( 'item-comparison' === mode ) {
|
||||
chartQuery.segmentby = 'category';
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<ReportFilters query={ query } path={ path } filters={ filters } />
|
||||
<ReportSummary
|
||||
charts={ charts }
|
||||
endpoint="categories"
|
||||
query={ query }
|
||||
endpoint="products"
|
||||
query={ chartQuery }
|
||||
selectedChart={ getSelectedChart( query.chart, charts ) }
|
||||
/>
|
||||
<ReportChart
|
||||
filters={ filters }
|
||||
charts={ charts }
|
||||
endpoint="categories"
|
||||
mode={ mode }
|
||||
endpoint="products"
|
||||
path={ path }
|
||||
query={ query }
|
||||
query={ chartQuery }
|
||||
itemsLabel={ itemsLabel }
|
||||
selectedChart={ getSelectedChart( query.chart, charts ) }
|
||||
/>
|
||||
<CategoriesReportTable query={ query } />
|
||||
|
@ -47,4 +75,5 @@ export default class CategoriesReport extends Component {
|
|||
|
||||
CategoriesReport.propTypes = {
|
||||
query: PropTypes.object.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
};
|
||||
|
|
|
@ -13,11 +13,15 @@ export const charts = [
|
|||
{
|
||||
key: 'orders_count',
|
||||
label: __( 'Discounted Orders', 'wc-admin' ),
|
||||
order: 'desc',
|
||||
orderby: 'orders_count',
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
key: 'amount',
|
||||
label: __( 'Amount', 'wc-admin' ),
|
||||
order: 'desc',
|
||||
orderby: 'amount',
|
||||
type: 'currency',
|
||||
},
|
||||
];
|
||||
|
@ -66,8 +70,18 @@ export const filters = [
|
|||
},
|
||||
},
|
||||
},
|
||||
{ label: __( 'Top Coupons by Discounted Orders', 'wc-admin' ), value: 'top_orders' },
|
||||
{ label: __( 'Top Coupons by Amount Discounted', 'wc-admin' ), value: 'top_discount' },
|
||||
{
|
||||
label: __( 'Top Coupons by Discounted Orders', 'wc-admin' ),
|
||||
value: 'top_orders',
|
||||
chartMode: 'item-comparison',
|
||||
query: { orderby: 'orders_count', order: 'desc', chart: 'orders_count' },
|
||||
},
|
||||
{
|
||||
label: __( 'Top Coupons by Amount Discounted', 'wc-admin' ),
|
||||
value: 'top_discount',
|
||||
chartMode: 'item-comparison',
|
||||
query: { orderby: 'amount', order: 'desc', chart: 'amount' },
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
*/
|
||||
import { Component, Fragment } from '@wordpress/element';
|
||||
import PropTypes from 'prop-types';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
|
@ -20,8 +21,32 @@ import ReportChart from 'analytics/components/report-chart';
|
|||
import ReportSummary from 'analytics/components/report-summary';
|
||||
|
||||
export default class CouponsReport extends Component {
|
||||
getChartMeta() {
|
||||
const { query } = this.props;
|
||||
const isCompareView = [ 'top_orders', 'top_discount', 'compare-coupons' ].includes(
|
||||
query.filter
|
||||
);
|
||||
|
||||
const mode = isCompareView ? 'item-comparison' : 'time-comparison';
|
||||
const itemsLabel = __( '%d coupons', 'wc-admin' );
|
||||
|
||||
return {
|
||||
itemsLabel,
|
||||
mode,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { query, path } = this.props;
|
||||
const { mode, itemsLabel } = this.getChartMeta();
|
||||
|
||||
const chartQuery = {
|
||||
...query,
|
||||
};
|
||||
|
||||
if ( 'item-comparison' === mode ) {
|
||||
chartQuery.segmentby = 'coupon';
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
|
@ -29,14 +54,17 @@ export default class CouponsReport extends Component {
|
|||
<ReportSummary
|
||||
charts={ charts }
|
||||
endpoint="coupons"
|
||||
query={ query }
|
||||
query={ chartQuery }
|
||||
selectedChart={ getSelectedChart( query.chart, charts ) }
|
||||
/>
|
||||
<ReportChart
|
||||
filters={ filters }
|
||||
charts={ charts }
|
||||
mode={ mode }
|
||||
endpoint="coupons"
|
||||
path={ path }
|
||||
query={ query }
|
||||
query={ chartQuery }
|
||||
itemsLabel={ itemsLabel }
|
||||
selectedChart={ getSelectedChart( query.chart, charts ) }
|
||||
/>
|
||||
<CouponsReportTable query={ query } />
|
||||
|
|
|
@ -39,7 +39,7 @@ export const advancedFilters = {
|
|||
remove: __( 'Remove customer name filter', 'wc-admin' ),
|
||||
rule: __( 'Select a customer name filter match', 'wc-admin' ),
|
||||
/* translators: A sentence describing a Product filter. See screen shot for context: https://cloudup.com/cCsm3GeXJbE */
|
||||
title: __( 'Name {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
title: __( '{{title}}Name{{/title}} {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
filter: __( 'Select customer name', 'wc-admin' ),
|
||||
},
|
||||
rules: [
|
||||
|
@ -59,7 +59,7 @@ export const advancedFilters = {
|
|||
type: 'customers',
|
||||
getLabels: getRequestByIdString( NAMESPACE + '/customers', customer => ( {
|
||||
id: customer.id,
|
||||
label: [ customer.first_name, customer.last_name ].filter( Boolean ).join( ' ' ),
|
||||
label: customer.name,
|
||||
} ) ),
|
||||
},
|
||||
},
|
||||
|
@ -70,7 +70,7 @@ export const advancedFilters = {
|
|||
remove: __( 'Remove country filter', 'wc-admin' ),
|
||||
rule: __( 'Select a country filter match', 'wc-admin' ),
|
||||
/* translators: A sentence describing a Product filter. See screen shot for context: https://cloudup.com/cCsm3GeXJbE */
|
||||
title: __( 'Country {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
title: __( '{{title}}Country{{/title}} {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
filter: __( 'Select country', 'wc-admin' ),
|
||||
},
|
||||
rules: [
|
||||
|
@ -111,7 +111,7 @@ export const advancedFilters = {
|
|||
remove: __( 'Remove customer username filter', 'wc-admin' ),
|
||||
rule: __( 'Select a customer username filter match', 'wc-admin' ),
|
||||
/* translators: A sentence describing a customer username filter. See screen shot for context: https://cloudup.com/cCsm3GeXJbE */
|
||||
title: __( 'Username {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
title: __( '{{title}}Username{{/title}} {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
filter: __( 'Select customer username', 'wc-admin' ),
|
||||
},
|
||||
rules: [
|
||||
|
@ -139,7 +139,7 @@ export const advancedFilters = {
|
|||
remove: __( 'Remove customer email filter', 'wc-admin' ),
|
||||
rule: __( 'Select a customer email filter match', 'wc-admin' ),
|
||||
/* translators: A sentence describing a customer email filter. See screen shot for context: https://cloudup.com/cCsm3GeXJbE */
|
||||
title: __( 'Email {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
title: __( '{{title}}Email{{/title}} {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
filter: __( 'Select customer email', 'wc-admin' ),
|
||||
},
|
||||
rules: [
|
||||
|
@ -168,7 +168,7 @@ export const advancedFilters = {
|
|||
add: __( 'No. of Orders', 'wc-admin' ),
|
||||
remove: __( 'Remove order filter', 'wc-admin' ),
|
||||
rule: __( 'Select an order count filter match', 'wc-admin' ),
|
||||
title: __( 'No. of Orders {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
title: __( '{{title}}No. of Orders{{/title}} {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
|
@ -196,7 +196,7 @@ export const advancedFilters = {
|
|||
add: __( 'Total Spend', 'wc-admin' ),
|
||||
remove: __( 'Remove total spend filter', 'wc-admin' ),
|
||||
rule: __( 'Select a total spend filter match', 'wc-admin' ),
|
||||
title: __( 'Total Spend {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
title: __( '{{title}}Total Spend{{/title}} {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
|
@ -224,7 +224,7 @@ export const advancedFilters = {
|
|||
add: __( 'AOV', 'wc-admin' ),
|
||||
remove: __( 'Remove average older value filter', 'wc-admin' ),
|
||||
rule: __( 'Select an average order value filter match', 'wc-admin' ),
|
||||
title: __( 'AOV {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
title: __( '{{title}}AOV{{/title}} {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
|
@ -254,7 +254,7 @@ export const advancedFilters = {
|
|||
remove: __( 'Remove registered filter', 'wc-admin' ),
|
||||
rule: __( 'Select a registered filter match', 'wc-admin' ),
|
||||
/* translators: A sentence describing a Product filter. See screen shot for context: https://cloudup.com/cCsm3GeXJbE */
|
||||
title: __( 'Registered {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
title: __( '{{title}}Registered{{/title}} {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
filter: __( 'Select registered date', 'wc-admin' ),
|
||||
},
|
||||
rules: [
|
||||
|
@ -284,7 +284,7 @@ export const advancedFilters = {
|
|||
remove: __( 'Remove last active filter', 'wc-admin' ),
|
||||
rule: __( 'Select a last active filter match', 'wc-admin' ),
|
||||
/* translators: A sentence describing a Product filter. See screen shot for context: https://cloudup.com/cCsm3GeXJbE */
|
||||
title: __( 'Last active {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
title: __( '{{title}}Last active{{/title}} {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
filter: __( 'Select registered date', 'wc-admin' ),
|
||||
},
|
||||
rules: [
|
||||
|
|
|
@ -45,7 +45,7 @@ export const advancedFilters = {
|
|||
remove: __( 'Remove product filter', 'wc-admin' ),
|
||||
rule: __( 'Select a product filter match', 'wc-admin' ),
|
||||
/* translators: A sentence describing a Product filter. See screen shot for context: https://cloudup.com/ccxhyH2mEDg */
|
||||
title: __( 'Product {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
title: __( '{{title}}Product{{/title}} {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
filter: __( 'Select product', 'wc-admin' ),
|
||||
},
|
||||
rules: [
|
||||
|
@ -66,14 +66,14 @@ export const advancedFilters = {
|
|||
getLabels: getProductLabels,
|
||||
},
|
||||
},
|
||||
user: {
|
||||
customer: {
|
||||
labels: {
|
||||
add: __( 'Username', 'wc-admin' ),
|
||||
placeholder: __( 'Search customer username', 'wc-admin' ),
|
||||
remove: __( 'Remove customer username filter', 'wc-admin' ),
|
||||
rule: __( 'Select a customer username filter match', 'wc-admin' ),
|
||||
/* translators: A sentence describing a customer username filter. See screen shot for context: https://cloudup.com/ccxhyH2mEDg */
|
||||
title: __( 'Username {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
title: __( '{{title}}Username{{/title}} {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
filter: __( 'Select customer username', 'wc-admin' ),
|
||||
},
|
||||
rules: [
|
||||
|
@ -101,7 +101,7 @@ export const advancedFilters = {
|
|||
remove: __( 'Remove order number filter', 'wc-admin' ),
|
||||
rule: __( 'Select a order number filter match', 'wc-admin' ),
|
||||
/* translators: A sentence describing a order number filter. See screen shot for context: https://cloudup.com/ccxhyH2mEDg */
|
||||
title: __( 'Order number {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
title: __( '{{title}}Order number{{/title}} {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
filter: __( 'Select order number', 'wc-admin' ),
|
||||
},
|
||||
rules: [
|
||||
|
@ -135,7 +135,7 @@ export const advancedFilters = {
|
|||
remove: __( 'Remove IP address filter', 'wc-admin' ),
|
||||
rule: __( 'Select an IP address filter match', 'wc-admin' ),
|
||||
/* translators: A sentence describing a order number filter. See screen shot for context: https://cloudup.com/ccxhyH2mEDg */
|
||||
title: __( 'IP Address {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
title: __( '{{title}}IP Address{{/title}} {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
filter: __( 'Select IP address', 'wc-admin' ),
|
||||
},
|
||||
rules: [
|
||||
|
|
|
@ -70,9 +70,17 @@ export default class CouponsReportTable extends Component {
|
|||
const persistedQuery = getPersistedQuery( query );
|
||||
|
||||
return map( downloads, download => {
|
||||
const { _embedded, date, file_name, file_path, ip_address, order_id, product_id } = download;
|
||||
const {
|
||||
_embedded,
|
||||
date,
|
||||
file_name,
|
||||
file_path,
|
||||
ip_address,
|
||||
order_id,
|
||||
product_id,
|
||||
username,
|
||||
} = download;
|
||||
const { name: productName } = _embedded.product[ 0 ];
|
||||
const { name: userName } = _embedded.user[ 0 ];
|
||||
|
||||
const productLink = getNewPath( persistedQuery, 'products', {
|
||||
filter: 'single_product',
|
||||
|
@ -109,8 +117,8 @@ export default class CouponsReportTable extends Component {
|
|||
value: order_id,
|
||||
},
|
||||
{
|
||||
display: userName,
|
||||
value: userName,
|
||||
display: username,
|
||||
value: username,
|
||||
},
|
||||
{
|
||||
display: ip_address,
|
||||
|
|
|
@ -35,7 +35,7 @@ import withSelect from 'wc-api/with-select';
|
|||
const REPORTS_FILTER = 'woocommerce-reports-list';
|
||||
|
||||
const getReports = () => {
|
||||
const reports = applyFilters( REPORTS_FILTER, [
|
||||
const reports = [
|
||||
{
|
||||
report: 'revenue',
|
||||
title: __( 'Revenue', 'wc-admin' ),
|
||||
|
@ -86,9 +86,9 @@ const getReports = () => {
|
|||
title: __( 'Downloads', 'wc-admin' ),
|
||||
component: DownloadsReport,
|
||||
},
|
||||
] );
|
||||
];
|
||||
|
||||
return reports;
|
||||
return applyFilters( REPORTS_FILTER, reports );
|
||||
};
|
||||
|
||||
class Report extends Component {
|
||||
|
@ -145,15 +145,22 @@ export default compose(
|
|||
}
|
||||
|
||||
const { report } = props.params;
|
||||
const items = searchItemsByString( select, report, search );
|
||||
const searchWords = search.split( ',' ).map( searchWord => searchWord.replace( '%2C', ',' ) );
|
||||
const items = searchItemsByString( select, report, searchWords );
|
||||
const ids = Object.keys( items );
|
||||
if ( ! ids.length ) {
|
||||
return {};
|
||||
return {
|
||||
query: {
|
||||
...props.query,
|
||||
search: searchWords,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
query: {
|
||||
...props.query,
|
||||
search: searchWords,
|
||||
[ report ]: ids.join( ',' ),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -20,6 +20,8 @@ export const charts = [
|
|||
{
|
||||
key: 'net_revenue',
|
||||
label: __( 'Net Revenue', 'wc-admin' ),
|
||||
order: 'desc',
|
||||
orderby: 'net_total',
|
||||
type: 'currency',
|
||||
},
|
||||
{
|
||||
|
@ -30,6 +32,8 @@ export const charts = [
|
|||
{
|
||||
key: 'avg_items_per_order',
|
||||
label: __( 'Average Items Per Order', 'wc-admin' ),
|
||||
order: 'desc',
|
||||
orderby: 'num_items_sold',
|
||||
type: 'average',
|
||||
},
|
||||
];
|
||||
|
@ -61,7 +65,7 @@ export const advancedFilters = {
|
|||
remove: __( 'Remove order status filter', 'wc-admin' ),
|
||||
rule: __( 'Select an order status filter match', 'wc-admin' ),
|
||||
/* translators: A sentence describing an Order Status filter. See screen shot for context: https://cloudup.com/cSsUY9VeCVJ */
|
||||
title: __( 'Order Status {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
title: __( '{{title}}Order Status{{/title}} {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
filter: __( 'Select an order status', 'wc-admin' ),
|
||||
},
|
||||
rules: [
|
||||
|
@ -91,7 +95,7 @@ export const advancedFilters = {
|
|||
remove: __( 'Remove products filter', 'wc-admin' ),
|
||||
rule: __( 'Select a product filter match', 'wc-admin' ),
|
||||
/* translators: A sentence describing a Product filter. See screen shot for context: https://cloudup.com/cSsUY9VeCVJ */
|
||||
title: __( 'Product {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
title: __( '{{title}}Product{{/title}} {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
filter: __( 'Select products', 'wc-admin' ),
|
||||
},
|
||||
rules: [
|
||||
|
@ -119,7 +123,7 @@ export const advancedFilters = {
|
|||
remove: __( 'Remove coupon filter', 'wc-admin' ),
|
||||
rule: __( 'Select a coupon filter match', 'wc-admin' ),
|
||||
/* translators: A sentence describing a Coupon filter. See screen shot for context: https://cloudup.com/cSsUY9VeCVJ */
|
||||
title: __( 'Coupon Code {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
title: __( '{{title}}Coupon Code{{/title}} {{rule /}} {{filter /}}', 'wc-admin' ),
|
||||
filter: __( 'Select coupon codes', 'wc-admin' ),
|
||||
},
|
||||
rules: [
|
||||
|
@ -146,7 +150,7 @@ export const advancedFilters = {
|
|||
remove: __( 'Remove customer filter', 'wc-admin' ),
|
||||
rule: __( 'Select a customer filter match', 'wc-admin' ),
|
||||
/* translators: A sentence describing a Customer filter. See screen shot for context: https://cloudup.com/cSsUY9VeCVJ */
|
||||
title: __( 'Customer is {{filter /}}', 'wc-admin' ),
|
||||
title: __( '{{title}}Customer is{{/title}} {{filter /}}', 'wc-admin' ),
|
||||
filter: __( 'Select a customer type', 'wc-admin' ),
|
||||
},
|
||||
input: {
|
||||
|
|
|
@ -45,7 +45,6 @@ export default class OrdersReportTable extends Component {
|
|||
screenReaderLabel: __( 'Order ID', 'wc-admin' ),
|
||||
key: 'id',
|
||||
required: true,
|
||||
isSortable: true,
|
||||
},
|
||||
{
|
||||
label: __( 'Status', 'wc-admin' ),
|
||||
|
@ -68,9 +67,9 @@ export default class OrdersReportTable extends Component {
|
|||
},
|
||||
{
|
||||
label: __( 'Items Sold', 'wc-admin' ),
|
||||
key: 'items_sold',
|
||||
key: 'num_items_sold',
|
||||
required: false,
|
||||
isSortable: false,
|
||||
isSortable: true,
|
||||
isNumeric: true,
|
||||
},
|
||||
{
|
||||
|
@ -83,9 +82,9 @@ export default class OrdersReportTable extends Component {
|
|||
{
|
||||
label: __( 'N. Revenue', 'wc-admin' ),
|
||||
screenReaderLabel: __( 'Net Revenue', 'wc-admin' ),
|
||||
key: 'net_revenue',
|
||||
key: 'net_total',
|
||||
required: true,
|
||||
isSortable: false,
|
||||
isSortable: true,
|
||||
isNumeric: true,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -13,16 +13,22 @@ export const charts = [
|
|||
{
|
||||
key: 'items_sold',
|
||||
label: __( 'Items Sold', 'wc-admin' ),
|
||||
order: 'desc',
|
||||
orderby: 'items_sold',
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
key: 'net_revenue',
|
||||
label: __( 'Net Revenue', 'wc-admin' ),
|
||||
order: 'desc',
|
||||
orderby: 'net_revenue',
|
||||
type: 'currency',
|
||||
},
|
||||
{
|
||||
key: 'orders_count',
|
||||
label: __( 'Orders Count', 'wc-admin' ),
|
||||
order: 'desc',
|
||||
orderby: 'orders_count',
|
||||
type: 'number',
|
||||
},
|
||||
];
|
||||
|
|
|
@ -28,10 +28,13 @@ class ProductsReport extends Component {
|
|||
getChartMeta() {
|
||||
const { query, isSingleProductView, isSingleProductVariable } = this.props;
|
||||
|
||||
const isProductDetailsView =
|
||||
'top_items' === query.filter ||
|
||||
'top_sales' === query.filter ||
|
||||
'compare-products' === query.filter;
|
||||
const isProductDetailsView = [
|
||||
'top_items',
|
||||
'top_sales',
|
||||
'compare-products',
|
||||
'single_category',
|
||||
'compare-categories',
|
||||
].includes( query.filter );
|
||||
|
||||
const mode =
|
||||
isProductDetailsView || ( isSingleProductView && isSingleProductVariable )
|
||||
|
@ -41,8 +44,8 @@ class ProductsReport extends Component {
|
|||
isSingleProductView && isSingleProductVariable ? 'variations' : 'products';
|
||||
const label =
|
||||
isSingleProductView && isSingleProductVariable
|
||||
? __( '%s variations', 'wc-admin' )
|
||||
: __( '%s products', 'wc-admin' );
|
||||
? __( '%d variations', 'wc-admin' )
|
||||
: __( '%d products', 'wc-admin' );
|
||||
|
||||
return {
|
||||
compareObject,
|
||||
|
@ -130,7 +133,8 @@ export default compose(
|
|||
withSelect( ( select, props ) => {
|
||||
const { query } = props;
|
||||
const { getItems, isGetItemsRequesting, getItemsError } = select( 'wc-api' );
|
||||
const isSingleProductView = query.products && 1 === query.products.split( ',' ).length;
|
||||
const isSingleProductView =
|
||||
! query.search && query.products && 1 === query.products.split( ',' ).length;
|
||||
if ( isSingleProductView ) {
|
||||
const productId = parseInt( query.products );
|
||||
const includeArgs = { include: productId };
|
||||
|
|
|
@ -84,7 +84,7 @@ export default class VariationsReportTable extends Component {
|
|||
return map( data, row => {
|
||||
const { items_sold, net_revenue, orders_count, extended_info, product_id } = row;
|
||||
const { stock_status, stock_quantity, low_stock_amount, sku } = extended_info;
|
||||
const name = get( row, [ 'extended_info', 'name' ], '' ).replace( ' - ', ' / ' );
|
||||
const name = get( row, [ 'extended_info', 'name' ], '' );
|
||||
const ordersLink = getNewPath( persistedQuery, 'orders', {
|
||||
filter: 'advanced',
|
||||
product_includes: query.products,
|
||||
|
|
|
@ -8,31 +8,41 @@ export const charts = [
|
|||
{
|
||||
key: 'gross_revenue',
|
||||
label: __( 'Gross Revenue', 'wc-admin' ),
|
||||
order: 'desc',
|
||||
orderby: 'gross_revenue',
|
||||
type: 'currency',
|
||||
},
|
||||
{
|
||||
key: 'refunds',
|
||||
label: __( 'Refunds', 'wc-admin' ),
|
||||
order: 'desc',
|
||||
orderby: 'refunds',
|
||||
type: 'currency',
|
||||
},
|
||||
{
|
||||
key: 'coupons',
|
||||
label: __( 'Coupons', 'wc-admin' ),
|
||||
order: 'desc',
|
||||
orderby: 'coupons',
|
||||
type: 'currency',
|
||||
},
|
||||
{
|
||||
key: 'taxes',
|
||||
label: __( 'Taxes', 'wc-admin' ),
|
||||
order: 'desc',
|
||||
orderby: 'taxes',
|
||||
type: 'currency',
|
||||
},
|
||||
{
|
||||
key: 'shipping',
|
||||
label: __( 'Shipping', 'wc-admin' ),
|
||||
orderby: 'shipping',
|
||||
type: 'currency',
|
||||
},
|
||||
{
|
||||
key: 'net_revenue',
|
||||
label: __( 'Net Revenue', 'wc-admin' ),
|
||||
orderby: 'net_revenue',
|
||||
type: 'currency',
|
||||
},
|
||||
];
|
||||
|
|
|
@ -240,8 +240,8 @@ export default compose(
|
|||
return {
|
||||
tableData: {
|
||||
items: {
|
||||
data: get( revenueData, [ 'data', 'intervals' ] ),
|
||||
totalResults: get( revenueData, [ 'totalResults' ] ),
|
||||
data: get( revenueData, [ 'data', 'intervals' ], [] ),
|
||||
totalResults: get( revenueData, [ 'totalResults' ], 0 ),
|
||||
},
|
||||
isError,
|
||||
isRequesting,
|
||||
|
|
|
@ -17,6 +17,7 @@ export const filters = [
|
|||
{ label: __( 'Out of Stock', 'wc-admin' ), value: 'outofstock' },
|
||||
{ label: __( 'Low Stock', 'wc-admin' ), value: 'lowstock' },
|
||||
{ label: __( 'In Stock', 'wc-admin' ), value: 'instock' },
|
||||
{ label: __( 'On Backorder', 'wc-admin' ), value: 'onbackorder' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
@ -67,16 +67,14 @@ export default class StockReportTable extends Component {
|
|||
products: parent_id || id,
|
||||
} );
|
||||
|
||||
const formattedName = name.replace( ' - ', ' / ' );
|
||||
|
||||
const nameLink = (
|
||||
<Link href={ productDetailLink } type="wc-admin">
|
||||
{ formattedName }
|
||||
{ name }
|
||||
</Link>
|
||||
);
|
||||
|
||||
const stockStatusLink = (
|
||||
<Link href={ 'post.php?action=edit&post=' + parent_id || id } type="wp-admin">
|
||||
<Link href={ 'post.php?action=edit&post=' + ( parent_id || id ) } type="wp-admin">
|
||||
{ stockStatuses[ stock_status ] }
|
||||
</Link>
|
||||
);
|
||||
|
@ -84,7 +82,7 @@ export default class StockReportTable extends Component {
|
|||
return [
|
||||
{
|
||||
display: nameLink,
|
||||
value: formattedName,
|
||||
value: name,
|
||||
},
|
||||
{
|
||||
display: sku,
|
||||
|
@ -103,23 +101,27 @@ export default class StockReportTable extends Component {
|
|||
}
|
||||
|
||||
getSummary( totals ) {
|
||||
const { products = 0, out_of_stock = 0, low_stock = 0, in_stock = 0 } = totals;
|
||||
const { products = 0, outofstock = 0, lowstock = 0, instock = 0, onbackorder = 0 } = totals;
|
||||
return [
|
||||
{
|
||||
label: _n( 'product', 'products', products, 'wc-admin' ),
|
||||
value: numberFormat( products ),
|
||||
},
|
||||
{
|
||||
label: __( 'out of stock', out_of_stock, 'wc-admin' ),
|
||||
value: numberFormat( out_of_stock ),
|
||||
label: __( 'out of stock', outofstock, 'wc-admin' ),
|
||||
value: numberFormat( outofstock ),
|
||||
},
|
||||
{
|
||||
label: __( 'low stock', low_stock, 'wc-admin' ),
|
||||
value: numberFormat( low_stock ),
|
||||
label: __( 'low stock', lowstock, 'wc-admin' ),
|
||||
value: numberFormat( lowstock ),
|
||||
},
|
||||
{
|
||||
label: __( 'in stock', in_stock, 'wc-admin' ),
|
||||
value: numberFormat( in_stock ),
|
||||
label: __( 'on backorder', onbackorder, 'wc-admin' ),
|
||||
value: numberFormat( onbackorder ),
|
||||
},
|
||||
{
|
||||
label: __( 'in stock', instock, 'wc-admin' ),
|
||||
value: numberFormat( instock ),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
@ -132,7 +134,7 @@ export default class StockReportTable extends Component {
|
|||
endpoint="stock"
|
||||
getHeadersContent={ this.getHeadersContent }
|
||||
getRowsContent={ this.getRowsContent }
|
||||
// getSummary={ this.getSummary }
|
||||
getSummary={ this.getSummary }
|
||||
query={ query }
|
||||
tableQuery={ {
|
||||
orderby: query.orderby || 'stock_status',
|
||||
|
|
|
@ -15,21 +15,29 @@ export const charts = [
|
|||
{
|
||||
key: 'total_tax',
|
||||
label: __( 'Total Tax', 'wc-admin' ),
|
||||
order: 'desc',
|
||||
orderby: 'total_tax',
|
||||
type: 'currency',
|
||||
},
|
||||
{
|
||||
key: 'order_tax',
|
||||
label: __( 'Order Tax', 'wc-admin' ),
|
||||
order: 'desc',
|
||||
orderby: 'order_tax',
|
||||
type: 'currency',
|
||||
},
|
||||
{
|
||||
key: 'shipping_tax',
|
||||
label: __( 'Shipping Tax', 'wc-admin' ),
|
||||
order: 'desc',
|
||||
orderby: 'shipping_tax',
|
||||
type: 'currency',
|
||||
},
|
||||
{
|
||||
key: 'orders_count',
|
||||
label: __( 'Orders Count', 'wc-admin' ),
|
||||
order: 'desc',
|
||||
orderby: 'orders_count',
|
||||
type: 'number',
|
||||
},
|
||||
];
|
||||
|
|
|
@ -21,6 +21,7 @@ import './index.scss';
|
|||
import { analyticsSettings } from './config';
|
||||
import Header from 'header';
|
||||
import Setting from './setting';
|
||||
import withSelect from 'wc-api/with-select';
|
||||
|
||||
const SETTINGS_FILTER = 'woocommerce_admin_analytics_settings';
|
||||
|
||||
|
@ -33,6 +34,7 @@ class Settings extends Component {
|
|||
|
||||
this.state = {
|
||||
settings: settings,
|
||||
saving: false,
|
||||
};
|
||||
|
||||
this.handleInputChange = this.handleInputChange.bind( this );
|
||||
|
@ -59,9 +61,31 @@ class Settings extends Component {
|
|||
}
|
||||
};
|
||||
|
||||
componentDidUpdate() {
|
||||
const { addNotice, isError, isRequesting } = this.props;
|
||||
const { saving } = this.state;
|
||||
|
||||
if ( saving && ! isRequesting ) {
|
||||
if ( ! isError ) {
|
||||
addNotice( {
|
||||
status: 'success',
|
||||
message: __( 'Your settings have been successfully saved.', 'wc-admin' ),
|
||||
} );
|
||||
} else {
|
||||
addNotice( {
|
||||
status: 'error',
|
||||
message: __( 'There was an error saving your settings. Please try again.', 'wc-admin' ),
|
||||
} );
|
||||
}
|
||||
/* eslint-disable react/no-did-update-set-state */
|
||||
this.setState( { saving: false } );
|
||||
/* eslint-enable react/no-did-update-set-state */
|
||||
}
|
||||
}
|
||||
|
||||
saveChanges = () => {
|
||||
this.props.updateSettings( this.state.settings );
|
||||
// @todo Need a confirmation on successful update.
|
||||
this.setState( { saving: true } );
|
||||
};
|
||||
|
||||
handleInputChange( e ) {
|
||||
|
@ -124,10 +148,21 @@ class Settings extends Component {
|
|||
}
|
||||
|
||||
export default compose(
|
||||
withSelect( select => {
|
||||
const { getSettings, getSettingsError, isGetSettingsRequesting } = select( 'wc-api' );
|
||||
|
||||
const settings = getSettings();
|
||||
const isError = Boolean( getSettingsError() );
|
||||
const isRequesting = isGetSettingsRequesting();
|
||||
|
||||
return { getSettings, isError, isRequesting, settings };
|
||||
} ),
|
||||
withDispatch( dispatch => {
|
||||
const { addNotice } = dispatch( 'wc-admin' );
|
||||
const { updateSettings } = dispatch( 'wc-api' );
|
||||
|
||||
return {
|
||||
addNotice,
|
||||
updateSettings,
|
||||
};
|
||||
} )
|
||||
|
|
|
@ -78,11 +78,11 @@ class DashboardCharts extends Component {
|
|||
};
|
||||
}
|
||||
|
||||
handleTypeToggle( type ) {
|
||||
handleTypeToggle( chartType ) {
|
||||
return () => {
|
||||
this.setState( { chartType: type } );
|
||||
this.setState( { chartType } );
|
||||
const userDataFields = {
|
||||
[ 'dashboard_chart_type' ]: type,
|
||||
[ 'dashboard_chart_type' ]: chartType,
|
||||
};
|
||||
this.props.updateCurrentUserData( userDataFields );
|
||||
};
|
||||
|
@ -146,7 +146,7 @@ class DashboardCharts extends Component {
|
|||
render() {
|
||||
const { path } = this.props;
|
||||
const { chartType, hiddenChartKeys, interval } = this.state;
|
||||
const query = { ...this.props.query, type: chartType, interval };
|
||||
const query = { ...this.props.query, chartType, interval };
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="woocommerce-dashboard__dashboard-charts">
|
||||
|
@ -163,24 +163,25 @@ class DashboardCharts extends Component {
|
|||
>
|
||||
<IconButton
|
||||
className={ classNames( 'woocommerce-chart__type-button', {
|
||||
'woocommerce-chart__type-button-selected': ! query.type || query.type === 'line',
|
||||
'woocommerce-chart__type-button-selected':
|
||||
! query.chartType || query.chartType === 'line',
|
||||
} ) }
|
||||
icon={ <Gridicon icon="line-graph" /> }
|
||||
title={ __( 'Line chart', 'wc-admin' ) }
|
||||
aria-checked={ query.type === 'line' }
|
||||
aria-checked={ query.chartType === 'line' }
|
||||
role="menuitemradio"
|
||||
tabIndex={ query.type === 'line' ? 0 : -1 }
|
||||
tabIndex={ query.chartType === 'line' ? 0 : -1 }
|
||||
onClick={ this.handleTypeToggle( 'line' ) }
|
||||
/>
|
||||
<IconButton
|
||||
className={ classNames( 'woocommerce-chart__type-button', {
|
||||
'woocommerce-chart__type-button-selected': query.type === 'bar',
|
||||
'woocommerce-chart__type-button-selected': query.chartType === 'bar',
|
||||
} ) }
|
||||
icon={ <Gridicon icon="stats-alt" /> }
|
||||
title={ __( 'Bar chart', 'wc-admin' ) }
|
||||
aria-checked={ query.type === 'bar' }
|
||||
aria-checked={ query.chartType === 'bar' }
|
||||
role="menuitemradio"
|
||||
tabIndex={ query.type === 'bar' ? 0 : -1 }
|
||||
tabIndex={ query.chartType === 'bar' ? 0 : -1 }
|
||||
onClick={ this.handleTypeToggle( 'bar' ) }
|
||||
/>
|
||||
</NavigableMenu>
|
||||
|
|
|
@ -22,7 +22,6 @@ import { formatCurrency } from '@woocommerce/currency';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
Card,
|
||||
EllipsisMenu,
|
||||
MenuItem,
|
||||
MenuTitle,
|
||||
|
@ -160,7 +159,7 @@ class StorePerformance extends Component {
|
|||
return (
|
||||
<Fragment>
|
||||
<SectionHeader title={ __( 'Store Performance', 'wc-admin' ) } menu={ this.renderMenu() } />
|
||||
<Card className="woocommerce-dashboard__store-performance">{ this.renderList() }</Card>
|
||||
<div className="woocommerce-dashboard__store-performance">{ this.renderList() }</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,18 +1,40 @@
|
|||
/** @format */
|
||||
|
||||
.woocommerce-dashboard__store-performance {
|
||||
border-bottom: 0;
|
||||
border-right: 0;
|
||||
margin-bottom: $gap-large;
|
||||
|
||||
.woocommerce-card__header {
|
||||
border-right: 1px solid $core-grey-light-700;
|
||||
}
|
||||
|
||||
.woocommerce-card__body {
|
||||
padding: 0;
|
||||
@include breakpoint( '<782px' ) {
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.woocommerce-summary {
|
||||
background-color: $core-grey-light-100;
|
||||
margin: 0;
|
||||
|
||||
@include breakpoint( '<782px' ) {
|
||||
&.is-placeholder {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
&:not(.is-placeholder) {
|
||||
.woocommerce-summary__item-container:first-child {
|
||||
.woocommerce-summary__item {
|
||||
border-top: 1px solid $core-grey-light-700;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-summary__item {
|
||||
background-color: $white;
|
||||
|
||||
&:hover {
|
||||
background-color: $core-grey-light-200;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: $core-grey-light-300;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
{ "component": "ProductImage" },
|
||||
{ "component": "Rating" },
|
||||
{ "component": "Search" },
|
||||
{ "component": "SearchListControl" },
|
||||
{ "component": "Section" },
|
||||
{ "component": "SegmentedSelection" },
|
||||
{ "component": "SplitButton" },
|
||||
|
|
|
@ -10,6 +10,7 @@ import { Provider as SlotFillProvider } from 'react-slot-fill';
|
|||
*/
|
||||
import './stylesheets/_embedded.scss';
|
||||
import { EmbedLayout } from './layout';
|
||||
import 'store';
|
||||
import 'wc-api/wp-data-store';
|
||||
|
||||
render(
|
||||
|
|
|
@ -24,7 +24,7 @@ import {
|
|||
Section,
|
||||
} from '@woocommerce/components';
|
||||
import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency';
|
||||
import { getAdminLink } from '@woocommerce/navigation';
|
||||
import { getAdminLink, getNewPath } from '@woocommerce/navigation';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -32,7 +32,6 @@ import { getAdminLink } from '@woocommerce/navigation';
|
|||
import { ActivityCard, ActivityCardPlaceholder } from '../activity-card';
|
||||
import ActivityHeader from '../activity-header';
|
||||
import ActivityOutboundLink from '../activity-outbound-link';
|
||||
import { getOrderRefundTotal } from 'lib/order-values';
|
||||
import { QUERY_DEFAULTS } from 'wc-api/constants';
|
||||
import withSelect from 'wc-api/with-select';
|
||||
|
||||
|
@ -64,28 +63,56 @@ function OrdersPanel( { orders, isRequesting, isError } ) {
|
|||
</EllipsisMenu>
|
||||
);
|
||||
|
||||
const orderCardTitle = ( order, address ) => {
|
||||
const name = `${ address.first_name } ${ address.last_name }`;
|
||||
const getCustomerString = order => {
|
||||
const extended_info = order.extended_info || {};
|
||||
const { first_name, last_name } = extended_info.customer || {};
|
||||
|
||||
if ( ! first_name && ! last_name ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const name = [ first_name, last_name ].join( ' ' );
|
||||
return sprintf(
|
||||
__(
|
||||
/* translators: describes who placed an order, e.g. Order #123 placed by John Doe */
|
||||
'placed by {{customerLink}}%(customerName)s{{/customerLink}}',
|
||||
'wc-admin'
|
||||
),
|
||||
{
|
||||
customerName: name,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const orderCardTitle = order => {
|
||||
const { extended_info, order_id } = order;
|
||||
const { customer } = extended_info || {};
|
||||
const customerUrl = customer.customer_id
|
||||
? getNewPath( {}, '/analytics/customers', {
|
||||
filter: 'single_customer',
|
||||
customer_id: customer.customer_id,
|
||||
} )
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{ interpolateComponents( {
|
||||
mixedString: sprintf(
|
||||
__(
|
||||
/* eslint-disable-next-line max-len */
|
||||
'Order {{orderLink}}#%(orderNumber)s{{/orderLink}} placed by {{customerLink}}%(customerName)s{{/customerLink}} {{destinationFlag/}}',
|
||||
'Order {{orderLink}}#%(orderNumber)s{{/orderLink}} %(customerString)s {{destinationFlag/}}',
|
||||
'wc-admin'
|
||||
),
|
||||
{
|
||||
orderNumber: order.number,
|
||||
customerName: name,
|
||||
orderNumber: order_id,
|
||||
customerString: getCustomerString( order ),
|
||||
}
|
||||
),
|
||||
components: {
|
||||
orderLink: <Link href={ 'post.php?action=edit&post=' + order.id } type="wp-admin" />,
|
||||
// @todo Hook up customer name link
|
||||
customerLink: <Link href={ '#' } type="wp-admin" />,
|
||||
destinationFlag: <Flag order={ order } round={ false } />,
|
||||
orderLink: <Link href={ 'post.php?action=edit&post=' + order_id } type="wp-admin" />,
|
||||
destinationFlag: customer.country ? (
|
||||
<Flag code={ customer.country } round={ false } />
|
||||
) : null,
|
||||
customerLink: customerUrl ? <Link href={ customerUrl } type="wc-admin" /> : <span />,
|
||||
},
|
||||
} ) }
|
||||
</Fragment>
|
||||
|
@ -93,20 +120,20 @@ function OrdersPanel( { orders, isRequesting, isError } ) {
|
|||
};
|
||||
|
||||
const cards = [];
|
||||
orders.forEach( ( order, id ) => {
|
||||
// We want the billing address, but shipping can be used as a fallback.
|
||||
const address = { ...order.shipping, ...order.billing };
|
||||
const productsCount = order.line_items.reduce( ( total, line ) => total + line.quantity, 0 );
|
||||
orders.forEach( order => {
|
||||
const extended_info = order.extended_info || {};
|
||||
const productsCount =
|
||||
extended_info && extended_info.products ? extended_info.products.length : 0;
|
||||
|
||||
const total = order.total;
|
||||
const refundValue = getOrderRefundTotal( order );
|
||||
const remainingTotal = getCurrencyFormatDecimal( order.total ) + refundValue;
|
||||
const total = order.gross_total;
|
||||
const refundValue = order.refund_total;
|
||||
const remainingTotal = getCurrencyFormatDecimal( total ) + refundValue;
|
||||
|
||||
cards.push(
|
||||
<ActivityCard
|
||||
key={ id }
|
||||
key={ order.order_id }
|
||||
className="woocommerce-order-activity-card"
|
||||
title={ orderCardTitle( order, address ) }
|
||||
title={ orderCardTitle( order ) }
|
||||
date={ order.date_created }
|
||||
subtitle={
|
||||
<div>
|
||||
|
@ -118,16 +145,15 @@ function OrdersPanel( { orders, isRequesting, isError } ) {
|
|||
</span>
|
||||
{ refundValue ? (
|
||||
<span>
|
||||
<s>{ formatCurrency( total, order.currency_symbol ) }</s>{' '}
|
||||
{ formatCurrency( remainingTotal, order.currency_symbol ) }
|
||||
<s>{ formatCurrency( total ) }</s> { formatCurrency( remainingTotal ) }
|
||||
</span>
|
||||
) : (
|
||||
<span>{ formatCurrency( total, order.currency_symbol ) }</span>
|
||||
<span>{ formatCurrency( total ) }</span>
|
||||
) }
|
||||
</div>
|
||||
}
|
||||
actions={
|
||||
<Button isDefault href={ getAdminLink( 'post.php?action=edit&post=' + order.id ) }>
|
||||
<Button isDefault href={ getAdminLink( 'post.php?action=edit&post=' + order.order_id ) }>
|
||||
{ __( 'Begin fulfillment' ) }
|
||||
</Button>
|
||||
}
|
||||
|
@ -162,29 +188,30 @@ function OrdersPanel( { orders, isRequesting, isError } ) {
|
|||
}
|
||||
|
||||
OrdersPanel.propTypes = {
|
||||
orders: PropTypes.instanceOf( Map ).isRequired,
|
||||
orders: PropTypes.array.isRequired,
|
||||
isError: PropTypes.bool,
|
||||
isRequesting: PropTypes.bool,
|
||||
};
|
||||
|
||||
OrdersPanel.defaultProps = {
|
||||
orders: new Map(),
|
||||
orders: [],
|
||||
isError: false,
|
||||
isRequesting: false,
|
||||
};
|
||||
|
||||
export default compose(
|
||||
withSelect( select => {
|
||||
const { getItems, getItemsError, isGetItemsRequesting } = select( 'wc-api' );
|
||||
const { getReportItems, getReportItemsError, isReportItemsRequesting } = select( 'wc-api' );
|
||||
const ordersQuery = {
|
||||
page: 1,
|
||||
per_page: QUERY_DEFAULTS.pageSize,
|
||||
status: 'processing',
|
||||
status_is: [ 'processing', 'on-hold' ],
|
||||
extended_info: true,
|
||||
};
|
||||
|
||||
const orders = getItems( 'orders', ordersQuery );
|
||||
const isError = Boolean( getItemsError( 'orders', ordersQuery ) );
|
||||
const isRequesting = isGetItemsRequesting( 'orders', ordersQuery );
|
||||
const orders = getReportItems( 'orders', ordersQuery ).data;
|
||||
const isError = Boolean( getReportItemsError( 'orders', ordersQuery ) );
|
||||
const isRequesting = isReportItemsRequesting( 'orders', ordersQuery );
|
||||
|
||||
return { orders, isError, isRequesting };
|
||||
} )
|
||||
|
|
|
@ -12,7 +12,7 @@ import PropTypes from 'prop-types';
|
|||
/**
|
||||
* WooCommerce dependencies
|
||||
*/
|
||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||
import { getNewPath } from '@woocommerce/navigation';
|
||||
import { Link } from '@woocommerce/components';
|
||||
|
||||
/**
|
||||
|
@ -89,7 +89,7 @@ class Header extends Component {
|
|||
{ _sections.map( ( section, i ) => {
|
||||
const sectionPiece = Array.isArray( section ) ? (
|
||||
<Link
|
||||
href={ getNewPath( getPersistedQuery(), section[ 0 ], {} ) }
|
||||
href={ getNewPath( {}, section[ 0 ], {} ) }
|
||||
type={ isEmbedded ? 'wp-admin' : 'wc-admin' }
|
||||
>
|
||||
{ section[ 1 ] }
|
||||
|
|
|
@ -10,6 +10,7 @@ import { Provider as SlotFillProvider } from 'react-slot-fill';
|
|||
*/
|
||||
import './stylesheets/_index.scss';
|
||||
import { PageLayout } from './layout';
|
||||
import 'store';
|
||||
import 'wc-api/wp-data-store';
|
||||
|
||||
render(
|
||||
|
|
|
@ -21,44 +21,52 @@ import Dashboard from 'dashboard';
|
|||
import DevDocs from 'devdocs';
|
||||
|
||||
const getPages = () => {
|
||||
const pages = [
|
||||
{
|
||||
container: Dashboard,
|
||||
path: '/',
|
||||
wpOpenMenu: 'toplevel_page_woocommerce',
|
||||
wpClosedMenu: 'toplevel_page_wc-admin--analytics-revenue',
|
||||
},
|
||||
{
|
||||
container: Analytics,
|
||||
path: '/analytics',
|
||||
wpOpenMenu: 'toplevel_page_wc-admin--analytics-revenue',
|
||||
wpClosedMenu: 'toplevel_page_woocommerce',
|
||||
},
|
||||
{
|
||||
container: AnalyticsSettings,
|
||||
path: '/analytics/settings',
|
||||
wpOpenMenu: 'toplevel_page_wc-admin--analytics-revenue',
|
||||
wpClosedMenu: 'toplevel_page_woocommerce',
|
||||
},
|
||||
{
|
||||
container: AnalyticsReport,
|
||||
path: '/analytics/:report',
|
||||
wpOpenMenu: 'toplevel_page_wc-admin--analytics-revenue',
|
||||
wpClosedMenu: 'toplevel_page_woocommerce',
|
||||
},
|
||||
{
|
||||
const pages = [];
|
||||
|
||||
if ( window.wcAdminFeatures.devdocs ) {
|
||||
pages.push( {
|
||||
container: DevDocs,
|
||||
path: '/devdocs',
|
||||
wpOpenMenu: 'toplevel_page_woocommerce',
|
||||
wpClosedMenu: 'toplevel_page_wc-admin--analytics-revenue',
|
||||
},
|
||||
{
|
||||
} );
|
||||
pages.push( {
|
||||
container: DevDocs,
|
||||
path: '/devdocs/:component',
|
||||
wpOpenMenu: 'toplevel_page_woocommerce',
|
||||
wpClosedMenu: 'toplevel_page_wc-admin--analytics-revenue',
|
||||
},
|
||||
];
|
||||
} );
|
||||
}
|
||||
|
||||
if ( window.wcAdminFeatures.dashboard ) {
|
||||
pages.push( {
|
||||
container: Dashboard,
|
||||
path: '/',
|
||||
wpOpenMenu: 'toplevel_page_woocommerce',
|
||||
wpClosedMenu: 'toplevel_page_wc-admin--analytics-revenue',
|
||||
} );
|
||||
}
|
||||
|
||||
if ( window.wcAdminFeatures.analytics ) {
|
||||
pages.push( {
|
||||
container: Analytics,
|
||||
path: '/analytics',
|
||||
wpOpenMenu: 'toplevel_page_wc-admin--analytics-revenue',
|
||||
wpClosedMenu: 'toplevel_page_woocommerce',
|
||||
} );
|
||||
pages.push( {
|
||||
container: AnalyticsSettings,
|
||||
path: '/analytics/settings',
|
||||
wpOpenMenu: 'toplevel_page_wc-admin--analytics-revenue',
|
||||
wpClosedMenu: 'toplevel_page_woocommerce',
|
||||
} );
|
||||
pages.push( {
|
||||
container: AnalyticsReport,
|
||||
path: '/analytics/:report',
|
||||
wpOpenMenu: 'toplevel_page_wc-admin--analytics-revenue',
|
||||
wpClosedMenu: 'toplevel_page_woocommerce',
|
||||
} );
|
||||
}
|
||||
|
||||
return pages;
|
||||
};
|
||||
|
@ -161,6 +169,9 @@ window.wpNavMenuClassChange = function( page ) {
|
|||
closedMenu.classList.remove( 'wp-has-current-submenu' );
|
||||
closedMenu.classList.remove( 'wp-menu-open' );
|
||||
closedMenu.classList.add( 'wp-not-current-submenu' );
|
||||
|
||||
const wpWrap = document.querySelector( '#wpwrap' );
|
||||
wpWrap.classList.remove( 'wp-responsive-open' );
|
||||
};
|
||||
|
||||
export { Controller, getPages };
|
||||
|
|
|
@ -21,6 +21,7 @@ import { Controller, getPages } from './controller';
|
|||
import Header from 'header';
|
||||
import Notices from './notices';
|
||||
import { recordPageView } from 'lib/tracks';
|
||||
import TransientNotices from './transient-notices';
|
||||
|
||||
class Layout extends Component {
|
||||
componentDidMount() {
|
||||
|
@ -61,7 +62,8 @@ class Layout extends Component {
|
|||
const { isEmbeded, ...restProps } = this.props;
|
||||
return (
|
||||
<div className="woocommerce-layout">
|
||||
<Slot name="header" />
|
||||
{ window.wcAdminFeatures[ 'activity-panels' ] && <Slot name="header" /> }
|
||||
<TransientNotices />
|
||||
|
||||
<div className="woocommerce-layout__primary" id="woocommerce-layout__primary">
|
||||
<Notices />
|
||||
|
|
|
@ -17,6 +17,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.woocommerce-feature-disabled-activity-panels .woocommerce-layout__primary {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.woocommerce-layout .woocommerce-layout__main {
|
||||
padding-right: $fallback-gutter-large;
|
||||
padding-right: $gutter-large;
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import { Component } from '@wordpress/element';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
import TransientNotice from './transient-notice';
|
||||
import withSelect from 'wc-api/with-select';
|
||||
|
||||
class TransientNotices extends Component {
|
||||
render() {
|
||||
const { className, notices } = this.props;
|
||||
const classes = classnames( 'woocommerce-transient-notices', className );
|
||||
|
||||
return (
|
||||
<section className={ classes }>
|
||||
{ notices && notices.map( ( notice, i ) => <TransientNotice key={ i } { ...notice } /> ) }
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TransientNotices.propTypes = {
|
||||
/**
|
||||
* Additional class name to style the component.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
/**
|
||||
* Array of notices to be displayed.
|
||||
*/
|
||||
notices: PropTypes.array,
|
||||
};
|
||||
|
||||
export default compose(
|
||||
withSelect( select => {
|
||||
const { getNotices } = select( 'wc-admin' );
|
||||
const notices = getNotices();
|
||||
|
||||
return { notices };
|
||||
} )
|
||||
)( TransientNotices );
|
|
@ -0,0 +1,38 @@
|
|||
/** @format */
|
||||
|
||||
.woocommerce-transient-notices {
|
||||
position: fixed;
|
||||
bottom: $gap-small;
|
||||
left: 0;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.woocommerce-transient-notice {
|
||||
transform: translateX(calc(-100% - #{$gap}));
|
||||
transition: all 300ms cubic-bezier(0.42, 0, 0.58, 1);
|
||||
max-height: 300px; // Used to animate sliding down when multiple notices exist on exit.
|
||||
|
||||
@media screen and (prefers-reduced-motion: reduce) {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
&.slide-enter-active,
|
||||
&.slide-enter-done {
|
||||
transform: translateX(0%);
|
||||
}
|
||||
|
||||
&.slide-exit-active {
|
||||
transform: translateX(calc(-100% - #{$gap}));
|
||||
}
|
||||
|
||||
&.slide-exit-done {
|
||||
max-height: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.components-notice {
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import { Component } from '@wordpress/element';
|
||||
import { CSSTransition } from 'react-transition-group';
|
||||
import { noop } from 'lodash';
|
||||
import { Notice } from '@wordpress/components';
|
||||
import PropTypes from 'prop-types';
|
||||
import { speak } from '@wordpress/a11y';
|
||||
|
||||
class TransientNotice extends Component {
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
|
||||
this.state = {
|
||||
visible: false,
|
||||
timeout: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const exitTime = this.props.exitTime;
|
||||
const timeout = setTimeout(
|
||||
() => {
|
||||
this.setState( { visible: false } );
|
||||
},
|
||||
exitTime,
|
||||
name,
|
||||
exitTime
|
||||
);
|
||||
/* eslint-disable react/no-did-mount-set-state */
|
||||
this.setState( { visible: true, timeout } );
|
||||
/* eslint-enable react/no-did-mount-set-state */
|
||||
speak( this.props.message );
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout( this.state.timeout );
|
||||
}
|
||||
|
||||
render() {
|
||||
const { actions, className, isDismissible, message, onRemove, status } = this.props;
|
||||
const classes = classnames( 'woocommerce-transient-notice', className );
|
||||
|
||||
return (
|
||||
<CSSTransition in={ this.state.visible } timeout={ 300 } classNames="slide">
|
||||
<div className={ classes }>
|
||||
<Notice
|
||||
status={ status }
|
||||
isDismissible={ isDismissible }
|
||||
onRemove={ onRemove }
|
||||
actions={ actions }
|
||||
>
|
||||
{ message }
|
||||
</Notice>
|
||||
</div>
|
||||
</CSSTransition>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TransientNotice.propTypes = {
|
||||
/**
|
||||
* Array of action objects.
|
||||
* See https://wordpress.org/gutenberg/handbook/designers-developers/developers/components/notice/
|
||||
*/
|
||||
actions: PropTypes.array,
|
||||
/**
|
||||
* Additional class name to style the component.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
/**
|
||||
* Determines if the notice dimiss button should be shown.
|
||||
* See https://wordpress.org/gutenberg/handbook/designers-developers/developers/components/notice/
|
||||
*/
|
||||
isDismissible: PropTypes.bool,
|
||||
/**
|
||||
* Function called when dismissing the notice.
|
||||
* See https://wordpress.org/gutenberg/handbook/designers-developers/developers/components/notice/
|
||||
*/
|
||||
onRemove: PropTypes.func,
|
||||
/**
|
||||
* Type of notice to display.
|
||||
* See https://wordpress.org/gutenberg/handbook/designers-developers/developers/components/notice/
|
||||
*/
|
||||
status: PropTypes.oneOf( [ 'success', 'error', 'warning' ] ),
|
||||
/**
|
||||
* Time in milliseconds until exit.
|
||||
*/
|
||||
exitTime: PropTypes.number,
|
||||
};
|
||||
|
||||
TransientNotice.defaultProps = {
|
||||
actions: [],
|
||||
className: '',
|
||||
exitTime: 7000,
|
||||
isDismissible: false,
|
||||
onRemove: noop,
|
||||
status: 'warning',
|
||||
};
|
||||
|
||||
export default TransientNotice;
|
|
@ -0,0 +1,22 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { combineReducers, registerStore } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import notices from './notices';
|
||||
|
||||
registerStore( 'wc-admin', {
|
||||
reducer: combineReducers( {
|
||||
...notices.reducers,
|
||||
} ),
|
||||
actions: {
|
||||
...notices.actions,
|
||||
},
|
||||
selectors: {
|
||||
...notices.selectors,
|
||||
},
|
||||
} );
|
|
@ -0,0 +1,9 @@
|
|||
/** @format */
|
||||
|
||||
const addNotice = notice => {
|
||||
return { type: 'ADD_NOTICE', notice };
|
||||
};
|
||||
|
||||
export default {
|
||||
addNotice,
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
/** @format */
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import reducers from './reducers';
|
||||
import actions from './actions';
|
||||
import selectors from './selectors';
|
||||
|
||||
export default {
|
||||
reducers,
|
||||
actions,
|
||||
selectors,
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
/** @format */
|
||||
|
||||
const DEFAULT_STATE = [];
|
||||
|
||||
const notices = ( state = DEFAULT_STATE, action ) => {
|
||||
if ( action.type === 'ADD_NOTICE' ) {
|
||||
return [ ...state, action.notice ];
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
export default {
|
||||
notices,
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
/** @format */
|
||||
|
||||
const getNotices = state => {
|
||||
return state.notices;
|
||||
};
|
||||
|
||||
export default {
|
||||
getNotices,
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
/** @format */
|
||||
|
||||
export const DEFAULT_STATE = {
|
||||
notices: [],
|
||||
};
|
||||
|
||||
export const testNotice = { message: 'Test notice' };
|
|
@ -0,0 +1,55 @@
|
|||
/** @format */
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import actions from '../actions';
|
||||
import { DEFAULT_STATE, testNotice } from './fixtures';
|
||||
import reducers from '../reducers';
|
||||
import selectors from '../selectors';
|
||||
|
||||
describe( 'actions', () => {
|
||||
test( 'should create an add notice action', () => {
|
||||
const expectedAction = {
|
||||
type: 'ADD_NOTICE',
|
||||
notice: testNotice,
|
||||
};
|
||||
|
||||
expect( actions.addNotice( testNotice ) ).toEqual( expectedAction );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'selectors', () => {
|
||||
const notices = [ testNotice ];
|
||||
const updatedState = { ...DEFAULT_STATE, ...{ notices } };
|
||||
|
||||
test( 'should return an emtpy initial state', () => {
|
||||
expect( selectors.getNotices( DEFAULT_STATE ) ).toEqual( [] );
|
||||
} );
|
||||
|
||||
test( 'should have an array length matching number of notices', () => {
|
||||
expect( selectors.getNotices( updatedState ).length ).toEqual( 1 );
|
||||
} );
|
||||
|
||||
test( 'should return the message content', () => {
|
||||
expect( selectors.getNotices( updatedState )[ 0 ].message ).toEqual( 'Test notice' );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'reducers', () => {
|
||||
test( 'should return an emtpy initial state', () => {
|
||||
expect( reducers.notices( DEFAULT_STATE.notices, {} ) ).toEqual( [] );
|
||||
} );
|
||||
|
||||
test( 'should return the added notice', () => {
|
||||
expect(
|
||||
reducers.notices( DEFAULT_STATE.notices, { type: 'ADD_NOTICE', notice: testNotice } )
|
||||
).toEqual( [ testNotice ] );
|
||||
} );
|
||||
|
||||
const initialNotices = [ { message: 'Initial notice' } ];
|
||||
test( 'should return the initial notice and the added notice', () => {
|
||||
expect(
|
||||
reducers.notices( initialNotices, { type: 'ADD_NOTICE', notice: testNotice } )
|
||||
).toEqual( [ ...initialNotices, testNotice ] );
|
||||
} );
|
||||
} );
|
|
@ -59,3 +59,38 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hide an element from sighted users, but availble to screen reader users.
|
||||
@mixin visually-hidden() {
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
clip-path: inset(50%);
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
/* Many screen reader and browser combinations announce broken words as they would appear visually. */
|
||||
overflow-wrap: normal !important;
|
||||
word-wrap: normal !important;
|
||||
}
|
||||
|
||||
// Unhide a visually hidden element
|
||||
@mixin visually-shown() {
|
||||
clip: auto;
|
||||
clip-path: none;
|
||||
height: auto;
|
||||
width: auto;
|
||||
margin: unset;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// Create a string-repeat function
|
||||
@function str-repeat($character, $n) {
|
||||
@if $n == 0 {
|
||||
@return '';
|
||||
}
|
||||
$c: '';
|
||||
@for $i from 1 through $n {
|
||||
$c: $c + $character;
|
||||
}
|
||||
@return $c;
|
||||
}
|
||||
|
|
|
@ -56,11 +56,13 @@
|
|||
|
||||
.components-button.is-button.is-primary {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.components-button.is-button.is-primary:hover,
|
||||
.components-button.is-button.is-primary:active,
|
||||
.components-button.is-button.is-primary:focus {
|
||||
&:not(:disabled) {
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,12 +6,9 @@ import { MINUTE } from '@fresh-data/framework';
|
|||
|
||||
export const NAMESPACE = '/wc/v4';
|
||||
|
||||
// @todo Remove once swagger endpoints are phased out.
|
||||
export const SWAGGERNAMESPACE = 'https://virtserver.swaggerhub.com/peterfabian/wc-v3-api/1.0.0/';
|
||||
|
||||
export const DEFAULT_REQUIREMENT = {
|
||||
timeout: 1 * MINUTE,
|
||||
freshness: 5 * MINUTE,
|
||||
freshness: 30 * MINUTE,
|
||||
};
|
||||
|
||||
// WordPress & WooCommerce both set a hard limit of 100 for the per_page parameter
|
||||
|
|
|
@ -9,15 +9,14 @@
|
|||
*
|
||||
* @param {Object} select Instance of @wordpress/select
|
||||
* @param {String} endpoint Report API Endpoint
|
||||
* @param {String} search Search strings separated by commas.
|
||||
* @return {Object} Object Object containing the matching items.
|
||||
* @param {String[]} search Array of search strings.
|
||||
* @return {Object} Object containing the matching items.
|
||||
*/
|
||||
export function searchItemsByString( select, endpoint, search ) {
|
||||
const { getItems } = select( 'wc-api' );
|
||||
const searchWords = search.split( ',' );
|
||||
|
||||
const items = {};
|
||||
searchWords.forEach( searchWord => {
|
||||
search.forEach( searchWord => {
|
||||
const newItems = getItems( endpoint, {
|
||||
search: searchWord,
|
||||
per_page: 10,
|
||||
|
|
|
@ -13,7 +13,7 @@ import { stringifyQuery } from '@woocommerce/navigation';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { getResourceIdentifier, getResourcePrefix } from 'wc-api/utils';
|
||||
import { NAMESPACE, SWAGGERNAMESPACE } from 'wc-api/constants';
|
||||
import { NAMESPACE } from 'wc-api/constants';
|
||||
|
||||
const statEndpoints = [
|
||||
'coupons',
|
||||
|
@ -21,11 +21,10 @@ const statEndpoints = [
|
|||
'orders',
|
||||
'products',
|
||||
'revenue',
|
||||
'stock',
|
||||
'taxes',
|
||||
'customers',
|
||||
];
|
||||
// @todo Remove once swagger endpoints are phased out.
|
||||
const swaggerEndpoints = [ 'categories' ];
|
||||
|
||||
const typeEndpointMap = {
|
||||
'report-stats-query-orders': 'orders',
|
||||
|
@ -34,6 +33,7 @@ const typeEndpointMap = {
|
|||
'report-stats-query-categories': 'categories',
|
||||
'report-stats-query-downloads': 'downloads',
|
||||
'report-stats-query-coupons': 'coupons',
|
||||
'report-stats-query-stock': 'stock',
|
||||
'report-stats-query-taxes': 'taxes',
|
||||
'report-stats-query-customers': 'customers',
|
||||
};
|
||||
|
@ -53,9 +53,7 @@ function read( resourceNames, fetch = apiFetch ) {
|
|||
parse: false,
|
||||
};
|
||||
|
||||
if ( swaggerEndpoints.indexOf( endpoint ) >= 0 ) {
|
||||
fetchArgs.url = SWAGGERNAMESPACE + 'reports/' + endpoint + '/stats' + stringifyQuery( query );
|
||||
} else if ( statEndpoints.indexOf( endpoint ) >= 0 ) {
|
||||
if ( statEndpoints.indexOf( endpoint ) >= 0 ) {
|
||||
fetchArgs.path = NAMESPACE + '/reports/' + endpoint + '/stats' + stringifyQuery( query );
|
||||
} else {
|
||||
fetchArgs.path = endpoint + stringifyQuery( query );
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { find, forEach, isNull, get } from 'lodash';
|
||||
import { find, forEach, isNull, get, includes } from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
||||
/**
|
||||
|
@ -52,6 +52,9 @@ export function getFilterQuery( endpoint, query ) {
|
|||
return {};
|
||||
}
|
||||
|
||||
// Some stats endpoints don't have interval data, so they can ignore after/before params and omit that part of the response.
|
||||
const noIntervalEndpoints = [ 'stock', 'customers' ];
|
||||
|
||||
/**
|
||||
* Add timestamp to advanced filter parameters involving date. The api
|
||||
* expects a timestamp for these values similar to `before` and `after`.
|
||||
|
@ -137,9 +140,10 @@ export function getQueryFromConfig( config, advancedFilters, query ) {
|
|||
* Returns true if a report object is empty.
|
||||
*
|
||||
* @param {Object} report Report to check
|
||||
* @param {String} endpoint Endpoint slug
|
||||
* @return {Boolean} True if report is data is empty.
|
||||
*/
|
||||
export function isReportDataEmpty( report ) {
|
||||
export function isReportDataEmpty( report, endpoint ) {
|
||||
if ( ! report ) {
|
||||
return true;
|
||||
}
|
||||
|
@ -149,7 +153,9 @@ export function isReportDataEmpty( report ) {
|
|||
if ( ! report.data.totals || isNull( report.data.totals ) ) {
|
||||
return true;
|
||||
}
|
||||
if ( ! report.data.intervals || 0 === report.data.intervals.length ) {
|
||||
|
||||
const checkIntervals = ! includes( noIntervalEndpoints, endpoint );
|
||||
if ( checkIntervals && ( ! report.data.intervals || 0 === report.data.intervals.length ) ) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -168,7 +174,11 @@ function getRequestQuery( endpoint, dataType, query ) {
|
|||
const interval = getIntervalForQuery( query );
|
||||
const filterQuery = getFilterQuery( endpoint, query );
|
||||
const end = datesFromQuery[ dataType ].before;
|
||||
return {
|
||||
|
||||
const noIntervals = includes( noIntervalEndpoints, endpoint );
|
||||
return noIntervals
|
||||
? { ...filterQuery }
|
||||
: {
|
||||
order: 'asc',
|
||||
interval,
|
||||
per_page: MAX_PER_PAGE,
|
||||
|
@ -250,7 +260,7 @@ export function getReportChartData( endpoint, dataType, query, select ) {
|
|||
return { ...response, isRequesting: true };
|
||||
} else if ( getReportStatsError( endpoint, requestQuery ) ) {
|
||||
return { ...response, isError: true };
|
||||
} else if ( isReportDataEmpty( stats ) ) {
|
||||
} else if ( isReportDataEmpty( stats, endpoint ) ) {
|
||||
return { ...response, isEmpty: true };
|
||||
}
|
||||
|
||||
|
|
|
@ -48,13 +48,13 @@ function updateSettings( resourceNames, data, fetch ) {
|
|||
method: 'POST',
|
||||
data: { value: settingsData[ setting ] },
|
||||
} )
|
||||
.then( settingsToSettingsResource )
|
||||
.then( settingToSettingsResource.bind( null, data.settings ) )
|
||||
.catch( error => {
|
||||
return { [ resourceName ]: { error } };
|
||||
} );
|
||||
} );
|
||||
|
||||
return [ promises ];
|
||||
return promises;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
@ -65,6 +65,11 @@ function settingsToSettingsResource( settings ) {
|
|||
return { [ 'settings' ]: { data: settingsData } };
|
||||
}
|
||||
|
||||
function settingToSettingsResource( settings, setting ) {
|
||||
settings[ setting.id ] = setting.value;
|
||||
return { [ 'settings' ]: { data: settings } };
|
||||
}
|
||||
|
||||
export default {
|
||||
read,
|
||||
update,
|
||||
|
|
|
@ -1,14 +1,34 @@
|
|||
/** @format */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { isNil } from 'lodash';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { DEFAULT_REQUIREMENT } from '../constants';
|
||||
|
||||
const getSettings = ( getResource, requireResource ) => ( requirement = DEFAULT_REQUIREMENT ) => {
|
||||
return requireResource( requirement, 'settings' ).data;
|
||||
return requireResource( requirement, 'settings' ).data || {};
|
||||
};
|
||||
|
||||
const getSettingsError = getResource => () => {
|
||||
return getResource( 'settings' ).error;
|
||||
};
|
||||
|
||||
const isGetSettingsRequesting = getResource => () => {
|
||||
const { lastRequested, lastReceived } = getResource( 'settings' );
|
||||
if ( isNil( lastRequested ) || isNil( lastReceived ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return lastRequested > lastReceived;
|
||||
};
|
||||
|
||||
export default {
|
||||
getSettings,
|
||||
getSettingsError,
|
||||
isGetSettingsRequesting,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"features": {
|
||||
"activity-panels": false,
|
||||
"analytics": false,
|
||||
"dashboard": false,
|
||||
"devdocs": false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"features": {
|
||||
"activity-panels": true,
|
||||
"analytics": true,
|
||||
"dashboard": true,
|
||||
"devdocs": true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"features": {
|
||||
"activity-panels": false,
|
||||
"analytics": true,
|
||||
"dashboard": true,
|
||||
"devdocs": false
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
* [Overview](/)
|
||||
* [Components](components/)
|
||||
* [Feature Flags](feature-flags)
|
||||
* [Data](data)
|
||||
* [Documentation](documentation)
|
||||
* [Layout](layout)
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
* [Pagination](components/packages/pagination.md)
|
||||
* [ProductImage](components/packages/product-image.md)
|
||||
* [Rating](components/packages/rating.md)
|
||||
* [SearchListControl](components/packages/search-list-control.md)
|
||||
* [Search](components/packages/search.md)
|
||||
* [SectionHeader](components/packages/section-header.md)
|
||||
* [Section](components/packages/section.md)
|
||||
|
|
|
@ -38,9 +38,14 @@ Current path
|
|||
|
||||
### `primaryData`
|
||||
|
||||
- **Required**
|
||||
- Type: Object
|
||||
- Default: null
|
||||
- Default: `{
|
||||
data: {
|
||||
intervals: [],
|
||||
},
|
||||
isError: false,
|
||||
isRequesting: false,
|
||||
}`
|
||||
|
||||
Primary data to display in the chart.
|
||||
|
||||
|
@ -54,9 +59,14 @@ The query string represented in object form.
|
|||
|
||||
### `secondaryData`
|
||||
|
||||
- **Required**
|
||||
- Type: Object
|
||||
- Default: null
|
||||
- Default: `{
|
||||
data: {
|
||||
intervals: [],
|
||||
},
|
||||
isError: false,
|
||||
isRequesting: false,
|
||||
}`
|
||||
|
||||
Secondary data to display in the chart.
|
||||
|
||||
|
|
|
@ -42,3 +42,17 @@ The query string represented in object form.
|
|||
|
||||
Properties of the selected chart.
|
||||
|
||||
### `summaryData`
|
||||
|
||||
- Type: Object
|
||||
- Default: `{
|
||||
totals: {
|
||||
primary: {},
|
||||
secondary: {},
|
||||
},
|
||||
isError: false,
|
||||
isRequesting: false,
|
||||
}`
|
||||
|
||||
Data to display in the SummaryNumbers.
|
||||
|
||||
|
|
|
@ -68,9 +68,8 @@ The name of the property in the item object which contains the id.
|
|||
|
||||
### `primaryData`
|
||||
|
||||
- **Required**
|
||||
- Type: Object
|
||||
- Default: null
|
||||
- Default: `{}`
|
||||
|
||||
Primary data of that report. If it's not provided, it will be automatically
|
||||
loaded via the provided `endpoint`.
|
||||
|
@ -78,7 +77,13 @@ loaded via the provided `endpoint`.
|
|||
### `tableData`
|
||||
|
||||
- Type: Object
|
||||
- Default: `{}`
|
||||
- Default: `{
|
||||
items: {
|
||||
data: [],
|
||||
totalResults: 0,
|
||||
},
|
||||
query: {},
|
||||
}`
|
||||
|
||||
Table data of that report. If it's not provided, it will be automatically
|
||||
loaded via the provided `endpoint`.
|
||||
|
|
|
@ -13,6 +13,21 @@ Props
|
|||
|
||||
Allowed intervals to show in a dropdown.
|
||||
|
||||
### `baseValue`
|
||||
|
||||
- Type: Number
|
||||
- Default: `0`
|
||||
|
||||
Base chart value. If no data value is different than the baseValue, the
|
||||
`emptyMessage` will be displayed if provided.
|
||||
|
||||
### `chartType`
|
||||
|
||||
- Type: One of: 'bar', 'line'
|
||||
- Default: `'line'`
|
||||
|
||||
Chart type of either `line` or `bar`.
|
||||
|
||||
### `data`
|
||||
|
||||
- Type: Array
|
||||
|
@ -27,6 +42,14 @@ An array of data.
|
|||
|
||||
Format to parse dates into d3 time format
|
||||
|
||||
### `emptyMessage`
|
||||
|
||||
- Type: String
|
||||
- Default: null
|
||||
|
||||
The message to be displayed if there is no data to render. If no message is provided,
|
||||
nothing will be displayed.
|
||||
|
||||
### `itemsLabel`
|
||||
|
||||
- Type: String
|
||||
|
@ -127,13 +150,6 @@ A number formatting string or function to format the value displayed in the tool
|
|||
|
||||
A string to use as a title for the tooltip. Takes preference over `tooltipLabelFormat`.
|
||||
|
||||
### `type`
|
||||
|
||||
- Type: One of: 'bar', 'line'
|
||||
- Default: `'line'`
|
||||
|
||||
Chart type of either `line` or `bar`.
|
||||
|
||||
### `valueType`
|
||||
|
||||
- Type: String
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
`SearchListControl` (component)
|
||||
===============================
|
||||
|
||||
Component to display a searchable, selectable list of items.
|
||||
|
||||
Props
|
||||
-----
|
||||
|
||||
### `className`
|
||||
|
||||
- Type: String
|
||||
- Default: null
|
||||
|
||||
Additional CSS classes.
|
||||
|
||||
### `isHierarchical`
|
||||
|
||||
- Type: Boolean
|
||||
- Default: null
|
||||
|
||||
Whether the list of items is hierarchical or not. If true, each list item is expected to
|
||||
have a parent property.
|
||||
|
||||
### `isLoading`
|
||||
|
||||
- Type: Boolean
|
||||
- Default: null
|
||||
|
||||
Whether the list of items is still loading.
|
||||
|
||||
### `isSingle`
|
||||
|
||||
- Type: Boolean
|
||||
- Default: null
|
||||
|
||||
Restrict selections to one item.
|
||||
|
||||
### `list`
|
||||
|
||||
- Type: Array
|
||||
- id: Number
|
||||
- name: String
|
||||
- Default: null
|
||||
|
||||
A complete list of item objects, each with id, name properties. This is displayed as a
|
||||
clickable/keyboard-able list, and possibly filtered by the search term (searches name).
|
||||
|
||||
### `messages`
|
||||
|
||||
- Type: Object
|
||||
- clear: String - A more detailed label for the "Clear all" button, read to screen reader users.
|
||||
- list: String - Label for the list of selectable items, only read to screen reader users.
|
||||
- noItems: String - Message to display when the list is empty (implies nothing loaded from the server
|
||||
or parent component).
|
||||
- noResults: String - Message to display when no matching results are found. %s is the search term.
|
||||
- search: String - Label for the search input
|
||||
- selected: Function - Label for the selected items. This is actually a function, so that we can pass
|
||||
through the count of currently selected items.
|
||||
- updated: String - Label indicating that search results have changed, read to screen reader users.
|
||||
- Default: null
|
||||
|
||||
Messages displayed or read to the user. Configure these to reflect your object type.
|
||||
See `defaultMessages` above for examples.
|
||||
|
||||
### `onChange`
|
||||
|
||||
- **Required**
|
||||
- Type: Function
|
||||
- Default: null
|
||||
|
||||
Callback fired when selected items change, whether added, cleared, or removed.
|
||||
Passed an array of item objects (as passed in via props.list).
|
||||
|
||||
### `renderItem`
|
||||
|
||||
- Type: Function
|
||||
- Default: null
|
||||
|
||||
Callback to render each item in the selection list, allows any custom object-type rendering.
|
||||
|
||||
### `selected`
|
||||
|
||||
- **Required**
|
||||
- Type: Array
|
||||
- Default: null
|
||||
|
||||
The list of currently selected items.
|
||||
|
||||
### `search`
|
||||
|
||||
- Type: String
|
||||
- Default: null
|
||||
|
||||
|
||||
### `setState`
|
||||
|
||||
- Type: Function
|
||||
- Default: null
|
||||
|
||||
|
||||
### `debouncedSpeak`
|
||||
|
||||
- Type: Function
|
||||
- Default: null
|
||||
|
||||
|
||||
### `instanceId`
|
||||
|
||||
- Type: Number
|
||||
- Default: null
|
||||
|
||||
|
||||
`SearchListItem` (component)
|
||||
============================
|
||||
|
||||
|
||||
|
||||
Props
|
||||
-----
|
||||
|
||||
### `className`
|
||||
|
||||
- Type: String
|
||||
- Default: null
|
||||
|
||||
Additional CSS classes.
|
||||
|
||||
### `depth`
|
||||
|
||||
- Type: Number
|
||||
- Default: `0`
|
||||
|
||||
Depth, non-zero if the list is hierarchical.
|
||||
|
||||
### `item`
|
||||
|
||||
- Type: Object
|
||||
- Default: null
|
||||
|
||||
Current item to display.
|
||||
|
||||
### `isSelected`
|
||||
|
||||
- Type: Boolean
|
||||
- Default: null
|
||||
|
||||
Whether this item is selected.
|
||||
|
||||
### `isSingle`
|
||||
|
||||
- Type: Boolean
|
||||
- Default: null
|
||||
|
||||
Whether this should only display a single item (controls radio vs checkbox icon).
|
||||
|
||||
### `onSelect`
|
||||
|
||||
- Type: Function
|
||||
- Default: null
|
||||
|
||||
Callback for selecting the item.
|
||||
|
||||
### `search`
|
||||
|
||||
- Type: String
|
||||
- Default: `''`
|
||||
|
||||
Search string, used to highlight the substring in the item name.
|
||||
|
||||
### `showCount`
|
||||
|
||||
- Type: Boolean
|
||||
- Default: `false`
|
||||
|
||||
Toggles the "count" bubble on/off.
|
||||
|
|
@ -7,6 +7,13 @@ A search box which autocompletes results while typing, allowing for the user to
|
|||
Props
|
||||
-----
|
||||
|
||||
### `allowFreeTextSearch`
|
||||
|
||||
- Type: Boolean
|
||||
- Default: `false`
|
||||
|
||||
Render additional options in the autocompleter to allow free text entering depending on the type.
|
||||
|
||||
### `className`
|
||||
|
||||
- Type: String
|
||||
|
@ -54,6 +61,13 @@ search box.
|
|||
|
||||
Render tags inside input, otherwise render below input.
|
||||
|
||||
### `showClearButton`
|
||||
|
||||
- Type: Boolean
|
||||
- Default: `false`
|
||||
|
||||
Render a 'Clear' button next to the input box to remove its contents.
|
||||
|
||||
### `staticResults`
|
||||
|
||||
- Type: Boolean
|
||||
|
|
|
@ -132,13 +132,6 @@ The total number of rows to display per page.
|
|||
|
||||
The string to use as a query parameter when searching row items.
|
||||
|
||||
### `searchParam`
|
||||
|
||||
- Type: String
|
||||
- Default: null
|
||||
|
||||
Url query parameter search function operates on
|
||||
|
||||
### `showMenu`
|
||||
|
||||
- Type: Boolean
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
# Feature Flags
|
||||
|
||||
Features inside the `wc-admin` repository can be in various states of completeness. In addition to the development copy of `wc-admin`, feature plugin versions are bundled, and code is merged to WooCommerce core. To provide a way for improved control over how these features are released in these different environments, `wc-admin` has a system for feature flags.
|
||||
|
||||
We currently support the following environments:
|
||||
|
||||
| Environment | Description |
|
||||
|-------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| development | Development - All features should be enabled in development. These flags are also used in both JS and PHP tests. Ran using `npm start`. |
|
||||
| plugin | Plugin - A packaged release of the featured plugin, for GitHub WordPress.org. Ran using `npm run-script build:release`. | |
|
||||
| core | Core - assets/files ready and stable enough for core merge. @todo No build process exists yet.
|
||||
|
||||
|
||||
## Adding a new flag
|
||||
|
||||
Flags can be added to the files located in the `config/` directory. Make sure to add a flag for each environment and explicitly set the flag to false.
|
||||
Please add new feature flags alphabetically so they are easy to find.
|
||||
|
||||
## Basic Use - Client
|
||||
|
||||
The `window.wcAdminFeatures` constant is a global variable containing the feature flags.
|
||||
|
||||
Instances of `window.wcAdminFeatures` are replaced during the webpack build process by using webpack's [define plugin](https://webpack.js.org/plugins/define-plugin/). Using `webpack` for this allows us to eliminate dead code when making minified/production builds (`plugin`, or `core` environments).
|
||||
|
||||
To check if a feature is enabled, you can simplify check the boolean value of a feature:
|
||||
|
||||
```
|
||||
{ window.wcAdminFeatures[ 'activity-panels' ] && <ActivityPanel /> }
|
||||
```
|
||||
|
||||
We also expose CSS classes on the `body` tag, so that you can target specific feature states:
|
||||
|
||||
```
|
||||
<body class="wp-admin woocommerce-page woocommerce-feature-disabled-analytics woocommerce-feature-enabled-activity-panels ....">
|
||||
```
|
||||
|
||||
## Basic Use - Server
|
||||
|
||||
Feature flags are also available via PHP. To ensure these are consistent with the built client assets, `includes/feature-flags.php` is generated by the plugin build process or `npm start`. Do not edit `includes/feature-flags.php` directly.
|
||||
|
||||
To check if a feature is enabled, you can use the `wc_admin_is_feature_enabled()`:
|
||||
|
||||
```
|
||||
if ( wc_admin_is_feature_enabled( 'activity-panels' ) ) {
|
||||
add_action( 'admin_header', 'wc_admin_activity_panel' );
|
||||
}
|
||||
```
|
|
@ -13,32 +13,27 @@ defined( 'ABSPATH' ) || exit;
|
|||
* Customers controller.
|
||||
*
|
||||
* @package WooCommerce Admin/API
|
||||
* @extends WC_REST_Customers_Controller
|
||||
* @extends WC_Admin_REST_Reports_Customers_Controller
|
||||
*/
|
||||
class WC_Admin_REST_Customers_Controller extends WC_REST_Customers_Controller {
|
||||
|
||||
// @todo Add support for guests here. See https://wp.me/p7bje6-1dM.
|
||||
class WC_Admin_REST_Customers_Controller extends WC_Admin_REST_Reports_Customers_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
* Route base.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v4';
|
||||
protected $rest_base = 'customers';
|
||||
|
||||
/**
|
||||
* Searches emails by partial search instead of a strict match.
|
||||
* See "search parameters" under https://codex.wordpress.org/Class_Reference/WP_User_Query.
|
||||
* Maps query arguments from the REST request.
|
||||
*
|
||||
* @param array $prepared_args Prepared search filter args from the customer endpoint.
|
||||
* @param array $request Request/query arguments.
|
||||
* @param array $request Request array.
|
||||
* @return array
|
||||
*/
|
||||
public static function update_search_filters( $prepared_args, $request ) {
|
||||
if ( ! empty( $request['email'] ) ) {
|
||||
$prepared_args['search'] = '*' . $prepared_args['search'] . '*';
|
||||
}
|
||||
return $prepared_args;
|
||||
protected function prepare_reports_query( $request ) {
|
||||
$args = parent::prepare_reports_query( $request );
|
||||
$args['customers'] = $request['include'];
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -48,11 +43,8 @@ class WC_Admin_REST_Customers_Controller extends WC_REST_Customers_Controller {
|
|||
*/
|
||||
public function get_collection_params() {
|
||||
$params = parent::get_collection_params();
|
||||
// Allow partial email matches. Previously, this was of format 'email' which required a strict "test@example.com" format.
|
||||
// This, in combination with `update_search_filters` allows us to do partial searches.
|
||||
$params['email']['format'] = '';
|
||||
$params['include'] = $params['customers'];
|
||||
unset( $params['customers'] );
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
|
||||
add_filter( 'woocommerce_rest_customer_query', array( 'WC_Admin_REST_Customers_Controller', 'update_search_filters' ), 10, 2 );
|
||||
|
|
|
@ -46,7 +46,8 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control
|
|||
$args['order'] = $request['order'];
|
||||
$args['orderby'] = $request['orderby'];
|
||||
$args['match'] = $request['match'];
|
||||
$args['name'] = $request['name'];
|
||||
$args['search'] = $request['search'];
|
||||
$args['searchby'] = $request['searchby'];
|
||||
$args['username'] = $request['username'];
|
||||
$args['email'] = $request['email'];
|
||||
$args['country'] = $request['country'];
|
||||
|
@ -60,6 +61,7 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control
|
|||
$args['avg_order_value_max'] = $request['avg_order_value_max'];
|
||||
$args['last_order_before'] = $request['last_order_before'];
|
||||
$args['last_order_after'] = $request['last_order_after'];
|
||||
$args['customers'] = $request['customers'];
|
||||
|
||||
$between_params_numeric = array( 'orders_count', 'total_spend', 'avg_order_value' );
|
||||
$normalized_params_numeric = WC_Admin_Reports_Interval::normalize_between_params( $request, $between_params_numeric, false );
|
||||
|
@ -172,7 +174,7 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control
|
|||
'title' => 'report_customers',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'customer_id' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'Customer ID.', 'wc-admin' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
|
@ -333,11 +335,21 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control
|
|||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['name'] = array(
|
||||
'description' => __( 'Limit response to objects with a specfic customer name.', 'wc-admin' ),
|
||||
$params['search'] = array(
|
||||
'description' => __( 'Limit response to objects with a customer field containing the search term. Searches the field provided by `searchby`.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['searchby'] = array(
|
||||
'description' => 'Limit results with `search` and `searchby` to specific fields containing the search term.',
|
||||
'type' => 'string',
|
||||
'default' => 'name',
|
||||
'enum' => array(
|
||||
'name',
|
||||
'username',
|
||||
'email',
|
||||
),
|
||||
);
|
||||
$params['username'] = array(
|
||||
'description' => __( 'Limit response to objects with a specfic username.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
|
@ -446,6 +458,16 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control
|
|||
'format' => 'date-time',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['customers'] = array(
|
||||
'description' => __( 'Limit result to items with specified customer ids.', 'wc-admin' ),
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
'items' => array(
|
||||
'type' => 'integer',
|
||||
),
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ class WC_Admin_REST_Reports_Customers_Stats_Controller extends WC_REST_Reports_C
|
|||
$args['registered_before'] = $request['registered_before'];
|
||||
$args['registered_after'] = $request['registered_after'];
|
||||
$args['match'] = $request['match'];
|
||||
$args['name'] = $request['name'];
|
||||
$args['search'] = $request['search'];
|
||||
$args['username'] = $request['username'];
|
||||
$args['email'] = $request['email'];
|
||||
$args['country'] = $request['country'];
|
||||
|
@ -55,6 +55,7 @@ class WC_Admin_REST_Reports_Customers_Stats_Controller extends WC_REST_Reports_C
|
|||
$args['avg_order_value_max'] = $request['avg_order_value_max'];
|
||||
$args['last_order_before'] = $request['last_order_before'];
|
||||
$args['last_order_after'] = $request['last_order_after'];
|
||||
$args['customers'] = $request['customers'];
|
||||
|
||||
$between_params_numeric = array( 'orders_count', 'total_spend', 'avg_order_value' );
|
||||
$normalized_params_numeric = WC_Admin_Reports_Interval::normalize_between_params( $request, $between_params_numeric, false );
|
||||
|
@ -77,8 +78,6 @@ class WC_Admin_REST_Reports_Customers_Stats_Controller extends WC_REST_Reports_C
|
|||
$report_data = $customers_query->get_data();
|
||||
$out_data = array(
|
||||
'totals' => $report_data,
|
||||
// @todo Is this needed? the single element array tricks the isReportDataEmpty() selector.
|
||||
'intervals' => array( (object) array() ),
|
||||
);
|
||||
|
||||
return rest_ensure_response( $out_data );
|
||||
|
@ -161,55 +160,6 @@ class WC_Admin_REST_Reports_Customers_Stats_Controller extends WC_REST_Reports_C
|
|||
'readonly' => true,
|
||||
'properties' => $totals,
|
||||
),
|
||||
'intervals' => array( // @todo Remove this?
|
||||
'description' => __( 'Reports data grouped by intervals.', 'wc-admin' ),
|
||||
'type' => 'array',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'interval' => array(
|
||||
'description' => __( 'Type of interval.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'enum' => array( 'day', 'week', 'month', 'year' ),
|
||||
),
|
||||
'date_start' => array(
|
||||
'description' => __( "The date the report start, in the site's timezone.", 'wc-admin' ),
|
||||
'type' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'date_start_gmt' => array(
|
||||
'description' => __( 'The date the report start, as GMT.', 'wc-admin' ),
|
||||
'type' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'date_end' => array(
|
||||
'description' => __( "The date the report end, in the site's timezone.", 'wc-admin' ),
|
||||
'type' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'date_end_gmt' => array(
|
||||
'description' => __( 'The date the report end, as GMT.', 'wc-admin' ),
|
||||
'type' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'subtotals' => array(
|
||||
'description' => __( 'Interval subtotals.', 'wc-admin' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'properties' => $totals,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -246,11 +196,21 @@ class WC_Admin_REST_Reports_Customers_Stats_Controller extends WC_REST_Reports_C
|
|||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['name'] = array(
|
||||
'description' => __( 'Limit response to objects with a specfic customer name.', 'wc-admin' ),
|
||||
$params['search'] = array(
|
||||
'description' => __( 'Limit response to objects with a customer field containing the search term. Searches the field provided by `searchby`.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['searchby'] = array(
|
||||
'description' => 'Limit results with `search` and `searchby` to specific fields containing the search term.',
|
||||
'type' => 'string',
|
||||
'default' => 'name',
|
||||
'enum' => array(
|
||||
'name',
|
||||
'username',
|
||||
'email',
|
||||
),
|
||||
);
|
||||
$params['username'] = array(
|
||||
'description' => __( 'Limit response to objects with a specfic username.', 'wc-admin' ),
|
||||
'type' => 'string',
|
||||
|
@ -359,6 +319,15 @@ class WC_Admin_REST_Reports_Customers_Stats_Controller extends WC_REST_Reports_C
|
|||
'format' => 'date-time',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['customers'] = array(
|
||||
'description' => __( 'Limit result to items with specified customer ids.', 'wc-admin' ),
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
'items' => array(
|
||||
'type' => 'integer',
|
||||
),
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
|
|
@ -35,7 +35,6 @@ class WC_Admin_REST_Reports_Downloads_Controller extends WC_REST_Reports_Control
|
|||
* Get items.
|
||||
*
|
||||
* @param WP_REST_Request $request Request data.
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
|
@ -111,6 +110,8 @@ class WC_Admin_REST_Reports_Downloads_Controller extends WC_REST_Reports_Control
|
|||
$filename = basename( $file_path );
|
||||
$response->data['file_name'] = apply_filters( 'woocommerce_file_download_filename', $filename, $product_id );
|
||||
$response->data['file_path'] = $file_path;
|
||||
$customer = new WC_Customer( $data['user_id'] );
|
||||
$response->data['username'] = $customer->get_username();
|
||||
|
||||
/**
|
||||
* Filter a report returned from the API.
|
||||
|
@ -136,10 +137,6 @@ class WC_Admin_REST_Reports_Downloads_Controller extends WC_REST_Reports_Control
|
|||
'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, 'products', $object['product_id'] ) ),
|
||||
'embeddable' => true,
|
||||
),
|
||||
'user' => array(
|
||||
'href' => rest_url( 'wp/v2/users/' . $object['user_id'] ),
|
||||
'embeddable' => true,
|
||||
),
|
||||
);
|
||||
|
||||
return $links;
|
||||
|
@ -216,6 +213,12 @@ class WC_Admin_REST_Reports_Downloads_Controller extends WC_REST_Reports_Control
|
|||
'context' => array( 'view', 'edit' ),
|
||||
'description' => __( 'User ID for the downloader.', 'wc-admin' ),
|
||||
),
|
||||
'username' => array(
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'description' => __( 'User name of the downloader.', 'wc-admin' ),
|
||||
),
|
||||
'ip_address' => array(
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
|
@ -330,7 +333,7 @@ class WC_Admin_REST_Reports_Downloads_Controller extends WC_REST_Reports_Control
|
|||
'type' => 'integer',
|
||||
),
|
||||
);
|
||||
$params['user_includes'] = array(
|
||||
$params['customer_includes'] = array(
|
||||
'description' => __( 'Limit response to objects that have the specified user ids.', 'wc-admin' ),
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
|
@ -339,7 +342,7 @@ class WC_Admin_REST_Reports_Downloads_Controller extends WC_REST_Reports_Control
|
|||
'type' => 'integer',
|
||||
),
|
||||
);
|
||||
$params['user_excludes'] = array(
|
||||
$params['customer_excludes'] = array(
|
||||
'description' => __( 'Limit response to objects that don\'t have the specified user ids.', 'wc-admin' ),
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
|
|
|
@ -49,6 +49,8 @@ class WC_Admin_REST_Reports_Downloads_Stats_Controller extends WC_REST_Reports_C
|
|||
$args['match'] = $request['match'];
|
||||
$args['product_includes'] = (array) $request['product_includes'];
|
||||
$args['product_excludes'] = (array) $request['product_excludes'];
|
||||
$args['customer_includes'] = (array) $request['customer_includes'];
|
||||
$args['customer_excludes'] = (array) $request['customer_excludes'];
|
||||
$args['order_includes'] = (array) $request['order_includes'];
|
||||
$args['order_excludes'] = (array) $request['order_excludes'];
|
||||
$args['ip_address_includes'] = (array) $request['ip_address_includes'];
|
||||
|
@ -329,8 +331,8 @@ class WC_Admin_REST_Reports_Downloads_Stats_Controller extends WC_REST_Reports_C
|
|||
'type' => 'integer',
|
||||
),
|
||||
);
|
||||
$params['user_includes'] = array(
|
||||
'description' => __( 'Limit response to objects that have the specified user ids.', 'wc-admin' ),
|
||||
$params['customer_includes'] = array(
|
||||
'description' => __( 'Limit response to objects that have the specified customer ids.', 'wc-admin' ),
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
|
@ -338,8 +340,8 @@ class WC_Admin_REST_Reports_Downloads_Stats_Controller extends WC_REST_Reports_C
|
|||
'type' => 'integer',
|
||||
),
|
||||
);
|
||||
$params['user_excludes'] = array(
|
||||
'description' => __( 'Limit response to objects that don\'t have the specified user ids.', 'wc-admin' ),
|
||||
$params['customer_excludes'] = array(
|
||||
'description' => __( 'Limit response to objects that don\'t have the specified customer ids.', 'wc-admin' ),
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
|
|
|
@ -170,7 +170,7 @@ class WC_Admin_REST_Reports_Orders_Stats_Controller extends WC_Admin_REST_Report
|
|||
),
|
||||
'avg_items_per_order' => array(
|
||||
'description' => __( 'Average items per order', 'wc-admin' ),
|
||||
'type' => 'integer',
|
||||
'type' => 'number',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API Reports stock stats controller
|
||||
*
|
||||
* Handles requests to the /reports/stock/stats endpoint.
|
||||
*
|
||||
* @package WooCommerce Admin/API
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* REST API Reports stock stats controller class.
|
||||
*
|
||||
* @package WooCommerce/API
|
||||
* @extends WC_REST_Reports_Controller
|
||||
*/
|
||||
class WC_Admin_REST_Reports_Stock_Stats_Controller extends WC_REST_Reports_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v4';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'reports/stock/stats';
|
||||
|
||||
/**
|
||||
* Get Stock Status Totals.
|
||||
*
|
||||
* @param WP_REST_Request $request Request data.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$stock_query = new WC_Admin_Reports_Stock_Stats_Query();
|
||||
$report_data = $stock_query->get_data();
|
||||
$out_data = array(
|
||||
'totals' => $report_data,
|
||||
);
|
||||
return rest_ensure_response( $out_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a report object for serialization.
|
||||
*
|
||||
* @param WC_Product $report Report data.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function prepare_item_for_response( $report, $request ) {
|
||||
$data = $report;
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
|
||||
// Wrap the data in a response object.
|
||||
$response = rest_ensure_response( $data );
|
||||
|
||||
/**
|
||||
* Filter a report returned from the API.
|
||||
*
|
||||
* Allows modification of the report data right before it is returned.
|
||||
*
|
||||
* @param WP_REST_Response $response The response object.
|
||||
* @param WC_Product $product The original bject.
|
||||
* @param WP_REST_Request $request Request used to generate the response.
|
||||
*/
|
||||
return apply_filters( 'woocommerce_rest_prepare_report_stock_stats', $response, $product, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Report's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
$totals = array(
|
||||
'products' => array(
|
||||
'description' => __( 'Number of products.', 'wc-admin' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'lowstock' => array(
|
||||
'description' => __( 'Number of low stock products.', 'wc-admin' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
);
|
||||
|
||||
$status_options = wc_get_product_stock_status_options();
|
||||
foreach ( $status_options as $status => $label ) {
|
||||
$totals[ $status ] = array(
|
||||
/* translators: Stock status. Example: "Number of low stock products */
|
||||
'description' => sprintf( __( 'Number of %s products.', 'wc-admin' ), $label ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
);
|
||||
}
|
||||
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'report_customers_stats',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'totals' => array(
|
||||
'description' => __( 'Totals data.', 'wc-admin' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
'properties' => $totals,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query params for collections.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$params = array();
|
||||
$params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
|
||||
return $params;
|
||||
}
|
||||
}
|
|
@ -12,43 +12,6 @@ defined( 'ABSPATH' ) || exit;
|
|||
*/
|
||||
class WC_Admin_Api_Init {
|
||||
|
||||
/**
|
||||
* Action hook for reducing a range of batches down to single actions.
|
||||
*/
|
||||
const QUEUE_BATCH_ACTION = 'wc-admin_queue_batches';
|
||||
|
||||
/**
|
||||
* Action hook for queuing an action after another is complete.
|
||||
*/
|
||||
const QUEUE_DEPEDENT_ACTION = 'wc-admin_queue_dependent_action';
|
||||
|
||||
/**
|
||||
* Action hook for processing a batch of customers.
|
||||
*/
|
||||
const CUSTOMERS_BATCH_ACTION = 'wc-admin_process_customers_batch';
|
||||
|
||||
/**
|
||||
* Action hook for processing a batch of orders.
|
||||
*/
|
||||
const ORDERS_BATCH_ACTION = 'wc-admin_process_orders_batch';
|
||||
|
||||
/**
|
||||
* Action hook for initializing the orders lookup batch creation.
|
||||
*/
|
||||
const ORDERS_LOOKUP_BATCH_INIT = 'wc-admin_orders_lookup_batch_init';
|
||||
|
||||
/**
|
||||
* Action hook for processing a batch of orders.
|
||||
*/
|
||||
const SINGLE_ORDER_ACTION = 'wc-admin_process_order';
|
||||
|
||||
/**
|
||||
* Queue instance.
|
||||
*
|
||||
* @var WC_Queue_Interface
|
||||
*/
|
||||
protected static $queue = null;
|
||||
|
||||
/**
|
||||
* Boostrap REST API.
|
||||
*/
|
||||
|
@ -57,154 +20,121 @@ class WC_Admin_Api_Init {
|
|||
add_action( 'plugins_loaded', array( $this, 'init_classes' ), 19 );
|
||||
// Hook in data stores.
|
||||
add_filter( 'woocommerce_data_stores', array( 'WC_Admin_Api_Init', 'add_data_stores' ) );
|
||||
// Add wc-admin report tables to list of WooCommerce tables.
|
||||
add_filter( 'woocommerce_install_get_tables', array( 'WC_Admin_Api_Init', 'add_tables' ) );
|
||||
// REST API extensions init.
|
||||
add_action( 'rest_api_init', array( $this, 'rest_api_init' ) );
|
||||
add_filter( 'rest_endpoints', array( 'WC_Admin_Api_Init', 'filter_rest_endpoints' ), 10, 1 );
|
||||
add_filter( 'woocommerce_debug_tools', array( 'WC_Admin_Api_Init', 'add_regenerate_tool' ) );
|
||||
|
||||
// Initialize syncing hooks.
|
||||
add_action( 'wp_loaded', array( __CLASS__, 'orders_lookup_update_init' ) );
|
||||
|
||||
// Initialize scheduled action handlers.
|
||||
add_action( self::QUEUE_BATCH_ACTION, array( __CLASS__, 'queue_batches' ), 10, 3 );
|
||||
add_action( self::QUEUE_DEPEDENT_ACTION, array( __CLASS__, 'queue_dependent_action' ), 10, 3 );
|
||||
add_action( self::CUSTOMERS_BATCH_ACTION, array( __CLASS__, 'customer_lookup_process_batch' ) );
|
||||
add_action( self::ORDERS_BATCH_ACTION, array( __CLASS__, 'orders_lookup_process_batch' ) );
|
||||
add_action( self::ORDERS_LOOKUP_BATCH_INIT, array( __CLASS__, 'orders_lookup_batch_init' ) );
|
||||
add_action( self::SINGLE_ORDER_ACTION, array( __CLASS__, 'orders_lookup_process_order' ) );
|
||||
|
||||
// Add currency symbol to orders endpoint response.
|
||||
add_filter( 'woocommerce_rest_prepare_shop_order_object', array( __CLASS__, 'add_currency_symbol_to_order_response' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queue instance.
|
||||
*
|
||||
* @return WC_Queue_Interface
|
||||
*/
|
||||
public static function queue() {
|
||||
if ( is_null( self::$queue ) ) {
|
||||
self::$queue = WC()->queue();
|
||||
}
|
||||
|
||||
return self::$queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set queue instance.
|
||||
*
|
||||
* @param WC_Queue_Interface $queue Queue instance.
|
||||
*/
|
||||
public static function set_queue( $queue ) {
|
||||
self::$queue = $queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init classes.
|
||||
*/
|
||||
public function init_classes() {
|
||||
// Interfaces.
|
||||
require_once dirname( __FILE__ ) . '/interfaces/class-wc-admin-reports-data-store-interface.php';
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-reports-query.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/interfaces/class-wc-admin-reports-data-store-interface.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-query.php';
|
||||
|
||||
// Common date time code.
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-reports-interval.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-interval.php';
|
||||
|
||||
// Exceptions.
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-reports-parameter-exception.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-parameter-exception.php';
|
||||
|
||||
// WC Class extensions.
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-order.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-order.php';
|
||||
|
||||
// Segmentation.
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-reports-segmenting.php';
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-reports-orders-stats-segmenting.php';
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-reports-products-stats-segmenting.php';
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-reports-coupons-stats-segmenting.php';
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-reports-taxes-stats-segmenting.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-segmenting.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-orders-stats-segmenting.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-products-stats-segmenting.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-coupons-stats-segmenting.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-taxes-stats-segmenting.php';
|
||||
|
||||
// Query classes for reports.
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-reports-revenue-query.php';
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-reports-orders-query.php';
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-reports-orders-stats-query.php';
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-reports-products-query.php';
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-reports-variations-query.php';
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-reports-products-stats-query.php';
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-reports-categories-query.php';
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-reports-taxes-query.php';
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-reports-taxes-stats-query.php';
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-reports-coupons-query.php';
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-reports-coupons-stats-query.php';
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-reports-downloads-query.php';
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-reports-downloads-stats-query.php';
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-reports-customers-query.php';
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-reports-customers-stats-query.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-revenue-query.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-orders-query.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-orders-stats-query.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-products-query.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-variations-query.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-products-stats-query.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-categories-query.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-taxes-query.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-taxes-stats-query.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-coupons-query.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-coupons-stats-query.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-downloads-query.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-downloads-stats-query.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-customers-query.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-customers-stats-query.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-reports-stock-stats-query.php';
|
||||
|
||||
// Data stores.
|
||||
require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-data-store.php';
|
||||
require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-orders-data-store.php';
|
||||
require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-orders-stats-data-store.php';
|
||||
require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-products-data-store.php';
|
||||
require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-variations-data-store.php';
|
||||
require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-products-stats-data-store.php';
|
||||
require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-categories-data-store.php';
|
||||
require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-taxes-data-store.php';
|
||||
require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-taxes-stats-data-store.php';
|
||||
require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-coupons-data-store.php';
|
||||
require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-coupons-stats-data-store.php';
|
||||
require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-downloads-data-store.php';
|
||||
require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-downloads-stats-data-store.php';
|
||||
require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-customers-data-store.php';
|
||||
require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-customers-stats-data-store.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/data-stores/class-wc-admin-reports-data-store.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/data-stores/class-wc-admin-reports-orders-data-store.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/data-stores/class-wc-admin-reports-orders-stats-data-store.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/data-stores/class-wc-admin-reports-products-data-store.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/data-stores/class-wc-admin-reports-variations-data-store.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/data-stores/class-wc-admin-reports-products-stats-data-store.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/data-stores/class-wc-admin-reports-categories-data-store.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/data-stores/class-wc-admin-reports-taxes-data-store.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/data-stores/class-wc-admin-reports-taxes-stats-data-store.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/data-stores/class-wc-admin-reports-coupons-data-store.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/data-stores/class-wc-admin-reports-coupons-stats-data-store.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/data-stores/class-wc-admin-reports-downloads-data-store.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/data-stores/class-wc-admin-reports-downloads-stats-data-store.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/data-stores/class-wc-admin-reports-customers-data-store.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/data-stores/class-wc-admin-reports-customers-stats-data-store.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/data-stores/class-wc-admin-reports-stock-stats-data-store.php';
|
||||
|
||||
// Data triggers.
|
||||
require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-notes-data-store.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/data-stores/class-wc-admin-notes-data-store.php';
|
||||
|
||||
// CRUD classes.
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-note.php';
|
||||
require_once dirname( __FILE__ ) . '/class-wc-admin-notes.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-note.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-notes.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Init REST API.
|
||||
*/
|
||||
public function rest_api_init() {
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-admin-notes-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-coupons-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-customers-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-data-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-data-countries-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-data-download-ips-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-orders-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-products-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-product-categories-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-product-variations-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-product-reviews-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-product-variations-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-setting-options-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-system-status-tools-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-categories-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-coupons-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-coupons-stats-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-customers-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-customers-stats-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-downloads-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-downloads-files-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-downloads-stats-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-orders-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-orders-stats-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-products-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-variations-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-products-stats-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-performance-indicators-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-revenue-stats-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-taxes-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-taxes-stats-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-stock-controller.php';
|
||||
require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-taxes-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-admin-notes-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-coupons-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-data-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-data-countries-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-data-download-ips-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-orders-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-products-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-product-categories-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-product-variations-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-product-reviews-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-product-variations-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-reports-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-setting-options-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-system-status-tools-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-reports-categories-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-reports-coupons-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-reports-coupons-stats-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-reports-customers-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-reports-customers-stats-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-reports-downloads-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-reports-downloads-files-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-reports-downloads-stats-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-reports-orders-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-reports-orders-stats-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-reports-products-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-reports-variations-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-reports-products-stats-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-reports-performance-indicators-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-reports-revenue-stats-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-reports-taxes-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-reports-taxes-stats-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-reports-stock-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-reports-stock-stats-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-taxes-controller.php';
|
||||
require_once WC_ADMIN_ABSPATH . '/includes/api/class-wc-admin-rest-customers-controller.php';
|
||||
|
||||
$controllers = apply_filters(
|
||||
'woocommerce_admin_rest_controllers',
|
||||
|
@ -236,6 +166,7 @@ class WC_Admin_Api_Init {
|
|||
'WC_Admin_REST_Reports_Coupons_Controller',
|
||||
'WC_Admin_REST_Reports_Coupons_Stats_Controller',
|
||||
'WC_Admin_REST_Reports_Stock_Controller',
|
||||
'WC_Admin_REST_Reports_Stock_Stats_Controller',
|
||||
'WC_Admin_REST_Reports_Downloads_Controller',
|
||||
'WC_Admin_REST_Reports_Downloads_Stats_Controller',
|
||||
'WC_Admin_REST_Reports_Customers_Controller',
|
||||
|
@ -429,323 +360,6 @@ class WC_Admin_Api_Init {
|
|||
return $endpoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate data for reports.
|
||||
*/
|
||||
public static function regenerate_report_data() {
|
||||
// Add registered customers to the lookup table before updating order stats
|
||||
// so that the orders can be associated with the `customer_id` column.
|
||||
self::customer_lookup_batch_init();
|
||||
// Queue orders lookup to occur after customers lookup generation is done.
|
||||
self::queue_dependent_action( self::ORDERS_LOOKUP_BATCH_INIT, array(), self::CUSTOMERS_BATCH_ACTION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds regenerate tool.
|
||||
*
|
||||
* @param array $tools List of tools.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_regenerate_tool( $tools ) {
|
||||
return array_merge(
|
||||
$tools,
|
||||
array(
|
||||
'rebuild_stats' => array(
|
||||
'name' => __( 'Rebuild reports data', 'wc-admin' ),
|
||||
'button' => __( 'Rebuild reports', 'wc-admin' ),
|
||||
'desc' => __( 'This tool will rebuild all of the information used by the reports.', 'wc-admin' ),
|
||||
'callback' => array( 'WC_Admin_Api_Init', 'regenerate_report_data' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule an action to process a single Order.
|
||||
*
|
||||
* @param int $order_id Order ID.
|
||||
* @return void
|
||||
*/
|
||||
public static function schedule_single_order_process( $order_id ) {
|
||||
if ( 'shop_order' !== get_post_type( $order_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This can get called multiple times for a single order, so we look
|
||||
// for existing pending jobs for the same order to avoid duplicating efforts.
|
||||
$existing_jobs = self::queue()->search(
|
||||
array(
|
||||
'status' => 'pending',
|
||||
'per_page' => 1,
|
||||
'claimed' => false,
|
||||
'search' => "[{$order_id}]",
|
||||
)
|
||||
);
|
||||
|
||||
if ( $existing_jobs ) {
|
||||
$existing_job = current( $existing_jobs );
|
||||
|
||||
// Bail out if there's a pending single order action, or a pending dependent action.
|
||||
if (
|
||||
( self::SINGLE_ORDER_ACTION === $existing_job->get_hook() ) ||
|
||||
(
|
||||
self::QUEUE_DEPEDENT_ACTION === $existing_job->get_hook() &&
|
||||
in_array( self::SINGLE_ORDER_ACTION, $existing_job->get_args() )
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We want to ensure that customer lookup updates are scheduled before order updates.
|
||||
self::queue_dependent_action( self::SINGLE_ORDER_ACTION, array( $order_id ), self::CUSTOMERS_BATCH_ACTION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach order lookup update hooks.
|
||||
*/
|
||||
public static function orders_lookup_update_init() {
|
||||
// Activate WC_Order extension.
|
||||
WC_Admin_Order::add_filters();
|
||||
|
||||
add_action( 'save_post_shop_order', array( __CLASS__, 'schedule_single_order_process' ) );
|
||||
add_action( 'woocommerce_order_refunded', array( __CLASS__, 'schedule_single_order_process' ) );
|
||||
|
||||
WC_Admin_Reports_Orders_Stats_Data_Store::init();
|
||||
WC_Admin_Reports_Customers_Data_Store::init();
|
||||
WC_Admin_Reports_Coupons_Data_Store::init();
|
||||
WC_Admin_Reports_Products_Data_Store::init();
|
||||
WC_Admin_Reports_Taxes_Data_Store::init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init order/product lookup tables update (in batches).
|
||||
*/
|
||||
public static function orders_lookup_batch_init() {
|
||||
$batch_size = self::get_batch_size( self::ORDERS_BATCH_ACTION );
|
||||
$order_query = new WC_Order_Query(
|
||||
array(
|
||||
'return' => 'ids',
|
||||
'limit' => 1,
|
||||
'paginate' => true,
|
||||
)
|
||||
);
|
||||
$result = $order_query->get_orders();
|
||||
|
||||
if ( 0 === $result->total ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$num_batches = ceil( $result->total / $batch_size );
|
||||
|
||||
self::queue_batches( 1, $num_batches, self::ORDERS_BATCH_ACTION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a batch of orders to update (stats and products).
|
||||
*
|
||||
* @param int $batch_number Batch number to process (essentially a query page number).
|
||||
* @return void
|
||||
*/
|
||||
public static function orders_lookup_process_batch( $batch_number ) {
|
||||
$batch_size = self::get_batch_size( self::ORDERS_BATCH_ACTION );
|
||||
$order_query = new WC_Order_Query(
|
||||
array(
|
||||
'return' => 'ids',
|
||||
'limit' => $batch_size,
|
||||
'page' => $batch_number,
|
||||
'orderby' => 'ID',
|
||||
'order' => 'ASC',
|
||||
)
|
||||
);
|
||||
$order_ids = $order_query->get_orders();
|
||||
|
||||
foreach ( $order_ids as $order_id ) {
|
||||
self::orders_lookup_process_order( $order_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a single order to update lookup tables for.
|
||||
* If an error is encountered in one of the updates, a retry action is scheduled.
|
||||
*
|
||||
* @param int $order_id Order ID.
|
||||
* @return void
|
||||
*/
|
||||
public static function orders_lookup_process_order( $order_id ) {
|
||||
$result = array_sum(
|
||||
array(
|
||||
WC_Admin_Reports_Orders_Stats_Data_Store::sync_order( $order_id ),
|
||||
WC_Admin_Reports_Products_Data_Store::sync_order_products( $order_id ),
|
||||
WC_Admin_Reports_Coupons_Data_Store::sync_order_coupons( $order_id ),
|
||||
WC_Admin_Reports_Taxes_Data_Store::sync_order_taxes( $order_id ),
|
||||
)
|
||||
);
|
||||
|
||||
// If all updates were either skipped or successful, we're done.
|
||||
// The update methods return -1 for skip, or a boolean success indicator.
|
||||
if ( 4 === absint( $result ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise assume an error occurred and reschedule.
|
||||
self::schedule_single_order_process( $order_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the batch size for regenerating reports.
|
||||
* Note: can differ per batch action.
|
||||
*
|
||||
* @param string $action Single batch action name.
|
||||
* @return int Batch size.
|
||||
*/
|
||||
public static function get_batch_size( $action ) {
|
||||
$batch_sizes = array(
|
||||
self::QUEUE_BATCH_ACTION => 100,
|
||||
self::CUSTOMERS_BATCH_ACTION => 25,
|
||||
self::ORDERS_BATCH_ACTION => 10,
|
||||
);
|
||||
$batch_size = isset( $batch_sizes[ $action ] ) ? $batch_sizes[ $action ] : 25;
|
||||
|
||||
/**
|
||||
* Filter the batch size for regenerating a report table.
|
||||
*
|
||||
* @param int $batch_size Batch size.
|
||||
* @param string $action Batch action name.
|
||||
*/
|
||||
return apply_filters( 'wc_admin_report_regenerate_batch_size', $batch_size, $action );
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a large number of batch jobs, respecting the batch size limit.
|
||||
* Reduces a range of batches down to "single batch" jobs.
|
||||
*
|
||||
* @param int $range_start Starting batch number.
|
||||
* @param int $range_end Ending batch number.
|
||||
* @param string $single_batch_action Action to schedule for a single batch.
|
||||
* @return void
|
||||
*/
|
||||
public static function queue_batches( $range_start, $range_end, $single_batch_action ) {
|
||||
$batch_size = self::get_batch_size( self::QUEUE_BATCH_ACTION );
|
||||
$range_size = 1 + ( $range_end - $range_start );
|
||||
$action_timestamp = time() + 5;
|
||||
|
||||
if ( $range_size > $batch_size ) {
|
||||
// If the current batch range is larger than a single batch,
|
||||
// split the range into $queue_batch_size chunks.
|
||||
$chunk_size = ceil( $range_size / $batch_size );
|
||||
|
||||
for ( $i = 0; $i < $batch_size; $i++ ) {
|
||||
$batch_start = $range_start + ( $i * $chunk_size );
|
||||
$batch_end = min( $range_end, $range_start + ( $chunk_size * ( $i + 1 ) ) - 1 );
|
||||
|
||||
self::queue()->schedule_single(
|
||||
$action_timestamp,
|
||||
self::QUEUE_BATCH_ACTION,
|
||||
array( $batch_start, $batch_end, $single_batch_action )
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Otherwise, queue the single batches.
|
||||
for ( $i = $range_start; $i <= $range_end; $i++ ) {
|
||||
self::queue()->schedule_single( $action_timestamp, $single_batch_action, array( $i ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue an action to run after another.
|
||||
*
|
||||
* @param string $action Action to run after prerequisite.
|
||||
* @param array $action_args Action arguments.
|
||||
* @param string $prerequisite_action Prerequisite action.
|
||||
*/
|
||||
public static function queue_dependent_action( $action, $action_args, $prerequisite_action ) {
|
||||
$blocking_jobs = self::queue()->search(
|
||||
array(
|
||||
'status' => 'pending',
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'per_page' => 1,
|
||||
'claimed' => false,
|
||||
'search' => $prerequisite_action, // search is used instead of hook to find queued batch creation.
|
||||
)
|
||||
);
|
||||
|
||||
$next_job_schedule = null;
|
||||
$blocking_job_hook = null;
|
||||
|
||||
if ( $blocking_jobs ) {
|
||||
$blocking_job = current( $blocking_jobs );
|
||||
$blocking_job_hook = $blocking_job->get_hook();
|
||||
$next_job_schedule = $blocking_job->get_schedule()->next();
|
||||
}
|
||||
|
||||
// Eliminate the false positive scenario where the blocking job is
|
||||
// actually another queued dependent action awaiting the same prerequisite.
|
||||
// Also, ensure that the next schedule is a DateTime (it can be null).
|
||||
if (
|
||||
is_a( $next_job_schedule, 'DateTime' ) &&
|
||||
( self::QUEUE_DEPEDENT_ACTION !== $blocking_job_hook )
|
||||
) {
|
||||
self::queue()->schedule_single(
|
||||
$next_job_schedule->getTimestamp() + 5,
|
||||
self::QUEUE_DEPEDENT_ACTION,
|
||||
array( $action, $action_args, $prerequisite_action )
|
||||
);
|
||||
} else {
|
||||
self::queue()->schedule_single( time() + 5, $action, $action_args );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Init customer lookup table update (in batches).
|
||||
*/
|
||||
public static function customer_lookup_batch_init() {
|
||||
$batch_size = self::get_batch_size( self::CUSTOMERS_BATCH_ACTION );
|
||||
$customer_query = new WP_User_Query(
|
||||
array(
|
||||
'fields' => 'ID',
|
||||
'number' => 1,
|
||||
)
|
||||
);
|
||||
$total_customers = $customer_query->get_total();
|
||||
|
||||
if ( 0 === $total_customers ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$num_batches = ceil( $total_customers / $batch_size );
|
||||
|
||||
self::queue_batches( 1, $num_batches, self::CUSTOMERS_BATCH_ACTION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a batch of customers to update.
|
||||
*
|
||||
* @param int $batch_number Batch number to process (essentially a query page number).
|
||||
* @return void
|
||||
*/
|
||||
public static function customer_lookup_process_batch( $batch_number ) {
|
||||
$batch_size = self::get_batch_size( self::CUSTOMERS_BATCH_ACTION );
|
||||
$customer_query = new WP_User_Query(
|
||||
array(
|
||||
'fields' => 'ID',
|
||||
'orderby' => 'ID',
|
||||
'order' => 'ASC',
|
||||
'number' => $batch_size,
|
||||
'paged' => $batch_number,
|
||||
)
|
||||
);
|
||||
|
||||
$customer_ids = $customer_query->get_results();
|
||||
|
||||
foreach ( $customer_ids as $customer_id ) {
|
||||
// @todo Schedule single customer update if this fails?
|
||||
WC_Admin_Reports_Customers_Data_Store::update_registered_customer( $customer_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds data stores.
|
||||
*
|
||||
|
@ -772,171 +386,11 @@ class WC_Admin_Api_Init {
|
|||
'admin-note' => 'WC_Admin_Notes_Data_Store',
|
||||
'report-customers' => 'WC_Admin_Reports_Customers_Data_Store',
|
||||
'report-customers-stats' => 'WC_Admin_Reports_Customers_Stats_Data_Store',
|
||||
'report-stock-stats' => 'WC_Admin_Reports_Stock_Stats_Data_Store',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new tables.
|
||||
*
|
||||
* @param array $wc_tables List of WooCommerce tables.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_tables( $wc_tables ) {
|
||||
global $wpdb;
|
||||
|
||||
return array_merge(
|
||||
$wc_tables,
|
||||
array(
|
||||
// @todo Will this work on multisite?
|
||||
"{$wpdb->prefix}wc_order_stats",
|
||||
"{$wpdb->prefix}wc_order_product_lookup",
|
||||
"{$wpdb->prefix}wc_order_tax_lookup",
|
||||
"{$wpdb->prefix}wc_order_coupon_lookup",
|
||||
"{$wpdb->prefix}wc_admin_notes",
|
||||
"{$wpdb->prefix}wc_admin_note_actions",
|
||||
"{$wpdb->prefix}wc_customer_lookup",
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database schema.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function get_schema() {
|
||||
global $wpdb;
|
||||
|
||||
if ( $wpdb->has_cap( 'collation' ) ) {
|
||||
$collate = $wpdb->get_charset_collate();
|
||||
}
|
||||
|
||||
$tables = "
|
||||
CREATE TABLE {$wpdb->prefix}wc_order_stats (
|
||||
order_id bigint(20) unsigned NOT NULL,
|
||||
date_created timestamp DEFAULT '0000-00-00 00:00:00' NOT NULL,
|
||||
num_items_sold int(11) UNSIGNED DEFAULT 0 NOT NULL,
|
||||
gross_total double DEFAULT 0 NOT NULL,
|
||||
coupon_total double DEFAULT 0 NOT NULL,
|
||||
refund_total double DEFAULT 0 NOT NULL,
|
||||
tax_total double DEFAULT 0 NOT NULL,
|
||||
shipping_total double DEFAULT 0 NOT NULL,
|
||||
net_total double DEFAULT 0 NOT NULL,
|
||||
returning_customer boolean DEFAULT 0 NOT NULL,
|
||||
status varchar(200) NOT NULL,
|
||||
customer_id BIGINT UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (order_id),
|
||||
KEY date_created (date_created),
|
||||
KEY customer_id (customer_id),
|
||||
KEY status (status)
|
||||
) $collate;
|
||||
CREATE TABLE {$wpdb->prefix}wc_order_product_lookup (
|
||||
order_item_id BIGINT UNSIGNED NOT NULL,
|
||||
order_id BIGINT UNSIGNED NOT NULL,
|
||||
product_id BIGINT UNSIGNED NOT NULL,
|
||||
variation_id BIGINT UNSIGNED NOT NULL,
|
||||
customer_id BIGINT UNSIGNED NULL,
|
||||
date_created timestamp DEFAULT '0000-00-00 00:00:00' NOT NULL,
|
||||
product_qty INT UNSIGNED NOT NULL,
|
||||
product_net_revenue double DEFAULT 0 NOT NULL,
|
||||
product_gross_revenue double DEFAULT 0 NOT NULL,
|
||||
coupon_amount double DEFAULT 0 NOT NULL,
|
||||
tax_amount double DEFAULT 0 NOT NULL,
|
||||
shipping_amount double DEFAULT 0 NOT NULL,
|
||||
shipping_tax_amount double DEFAULT 0 NOT NULL,
|
||||
refund_amount double DEFAULT 0 NOT NULL,
|
||||
PRIMARY KEY (order_item_id),
|
||||
KEY order_id (order_id),
|
||||
KEY product_id (product_id),
|
||||
KEY customer_id (customer_id),
|
||||
KEY date_created (date_created)
|
||||
) $collate;
|
||||
CREATE TABLE {$wpdb->prefix}wc_order_tax_lookup (
|
||||
order_id BIGINT UNSIGNED NOT NULL,
|
||||
tax_rate_id BIGINT UNSIGNED NOT NULL,
|
||||
date_created timestamp DEFAULT '0000-00-00 00:00:00' NOT NULL,
|
||||
shipping_tax double DEFAULT 0 NOT NULL,
|
||||
order_tax double DEFAULT 0 NOT NULL,
|
||||
total_tax double DEFAULT 0 NOT NULL,
|
||||
PRIMARY KEY (order_id, tax_rate_id),
|
||||
KEY tax_rate_id (tax_rate_id),
|
||||
KEY date_created (date_created)
|
||||
) $collate;
|
||||
CREATE TABLE {$wpdb->prefix}wc_order_coupon_lookup (
|
||||
order_id BIGINT UNSIGNED NOT NULL,
|
||||
coupon_id BIGINT UNSIGNED NOT NULL,
|
||||
date_created timestamp DEFAULT '0000-00-00 00:00:00' NOT NULL,
|
||||
discount_amount double DEFAULT 0 NOT NULL,
|
||||
PRIMARY KEY (order_id, coupon_id),
|
||||
KEY coupon_id (coupon_id),
|
||||
KEY date_created (date_created)
|
||||
) $collate;
|
||||
CREATE TABLE {$wpdb->prefix}wc_admin_notes (
|
||||
note_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
name varchar(255) NOT NULL,
|
||||
type varchar(20) NOT NULL,
|
||||
locale varchar(20) NOT NULL,
|
||||
title longtext NOT NULL,
|
||||
content longtext NOT NULL,
|
||||
icon varchar(200) NOT NULL,
|
||||
content_data longtext NULL default null,
|
||||
status varchar(200) NOT NULL,
|
||||
source varchar(200) NOT NULL,
|
||||
date_created datetime NOT NULL default '0000-00-00 00:00:00',
|
||||
date_reminder datetime NULL default null,
|
||||
PRIMARY KEY (note_id)
|
||||
) $collate;
|
||||
CREATE TABLE {$wpdb->prefix}wc_admin_note_actions (
|
||||
action_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
note_id BIGINT UNSIGNED NOT NULL,
|
||||
name varchar(255) NOT NULL,
|
||||
label varchar(255) NOT NULL,
|
||||
query longtext NOT NULL,
|
||||
PRIMARY KEY (action_id),
|
||||
KEY note_id (note_id)
|
||||
) $collate;
|
||||
CREATE TABLE {$wpdb->prefix}wc_customer_lookup (
|
||||
customer_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
user_id BIGINT UNSIGNED DEFAULT NULL,
|
||||
username varchar(60) DEFAULT '' NOT NULL,
|
||||
first_name varchar(255) NOT NULL,
|
||||
last_name varchar(255) NOT NULL,
|
||||
email varchar(100) NOT NULL,
|
||||
date_last_active timestamp NULL default null,
|
||||
date_registered timestamp NULL default null,
|
||||
country char(2) DEFAULT '' NOT NULL,
|
||||
postcode varchar(20) DEFAULT '' NOT NULL,
|
||||
city varchar(100) DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (customer_id),
|
||||
UNIQUE KEY user_id (user_id),
|
||||
KEY email (email)
|
||||
) $collate;
|
||||
";
|
||||
|
||||
return $tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create database tables.
|
||||
*/
|
||||
public static function create_db_tables() {
|
||||
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||
|
||||
dbDelta( self::get_schema() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Install plugin.
|
||||
*/
|
||||
public static function install() {
|
||||
// Create tables.
|
||||
self::create_db_tables();
|
||||
|
||||
// Initialize report tables.
|
||||
add_action( 'woocommerce_after_register_post_type', array( __CLASS__, 'regenerate_report_data' ), 20 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the currency symbol (in addition to currency code) to each Order
|
||||
* object in REST API responses. For use in formatCurrency().
|
||||
|
|
|
@ -0,0 +1,264 @@
|
|||
<?php
|
||||
/**
|
||||
* Installation related functions and actions.
|
||||
*
|
||||
* @package WooCommerce Admin/Classes
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* WC_Admin_Install Class.
|
||||
*/
|
||||
class WC_Admin_Install {
|
||||
/**
|
||||
* Plugin version.
|
||||
*
|
||||
* @TODO: get this dynamically?
|
||||
*/
|
||||
const VERSION_NUMBER = '0.6.0';
|
||||
|
||||
/**
|
||||
* Plugin version option name.
|
||||
*/
|
||||
const VERSION_OPTION = 'wc_admin_version';
|
||||
|
||||
/**
|
||||
* DB updates and callbacks that need to be run per version.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $db_updates = array();
|
||||
|
||||
/**
|
||||
* Hook in tabs.
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'init', array( __CLASS__, 'check_version' ), 5 );
|
||||
add_filter( 'wpmu_drop_tables', array( __CLASS__, 'wpmu_drop_tables' ) );
|
||||
|
||||
// Add wc-admin report tables to list of WooCommerce tables.
|
||||
add_filter( 'woocommerce_install_get_tables', array( __CLASS__, 'add_tables' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check WC Admin version and run the updater is required.
|
||||
*
|
||||
* This check is done on all requests and runs if the versions do not match.
|
||||
*/
|
||||
public static function check_version() {
|
||||
if (
|
||||
! defined( 'IFRAME_REQUEST' ) &&
|
||||
version_compare( get_option( self::VERSION_OPTION ), self::VERSION_NUMBER, '<' )
|
||||
) {
|
||||
self::install();
|
||||
do_action( 'wc_admin_updated' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install WC Admin.
|
||||
*/
|
||||
public static function install() {
|
||||
if ( ! is_blog_installed() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we are not already running this routine.
|
||||
if ( 'yes' === get_transient( 'wc_admin_installing' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we made it till here nothing is running yet, lets set the transient now.
|
||||
set_transient( 'wc_admin_installing', 'yes', MINUTE_IN_SECONDS * 10 );
|
||||
wc_maybe_define_constant( 'WC_ADMIN_INSTALLING', true );
|
||||
|
||||
self::create_tables();
|
||||
WC_Admin_Reports_Sync::regenerate_report_data();
|
||||
self::update_wc_admin_version();
|
||||
|
||||
delete_transient( 'wc_admin_installing' );
|
||||
|
||||
do_action( 'wc_admin_installed' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database schema.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function get_schema() {
|
||||
global $wpdb;
|
||||
|
||||
if ( $wpdb->has_cap( 'collation' ) ) {
|
||||
$collate = $wpdb->get_charset_collate();
|
||||
}
|
||||
|
||||
$tables = "
|
||||
CREATE TABLE {$wpdb->prefix}wc_order_stats (
|
||||
order_id bigint(20) unsigned NOT NULL,
|
||||
date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
|
||||
num_items_sold int(11) UNSIGNED DEFAULT 0 NOT NULL,
|
||||
gross_total double DEFAULT 0 NOT NULL,
|
||||
coupon_total double DEFAULT 0 NOT NULL,
|
||||
refund_total double DEFAULT 0 NOT NULL,
|
||||
tax_total double DEFAULT 0 NOT NULL,
|
||||
shipping_total double DEFAULT 0 NOT NULL,
|
||||
net_total double DEFAULT 0 NOT NULL,
|
||||
returning_customer boolean DEFAULT 0 NOT NULL,
|
||||
status varchar(200) NOT NULL,
|
||||
customer_id BIGINT UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (order_id),
|
||||
KEY date_created (date_created),
|
||||
KEY customer_id (customer_id),
|
||||
KEY status (status)
|
||||
) $collate;
|
||||
CREATE TABLE {$wpdb->prefix}wc_order_product_lookup (
|
||||
order_item_id BIGINT UNSIGNED NOT NULL,
|
||||
order_id BIGINT UNSIGNED NOT NULL,
|
||||
product_id BIGINT UNSIGNED NOT NULL,
|
||||
variation_id BIGINT UNSIGNED NOT NULL,
|
||||
customer_id BIGINT UNSIGNED NULL,
|
||||
date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
|
||||
product_qty INT UNSIGNED NOT NULL,
|
||||
product_net_revenue double DEFAULT 0 NOT NULL,
|
||||
product_gross_revenue double DEFAULT 0 NOT NULL,
|
||||
coupon_amount double DEFAULT 0 NOT NULL,
|
||||
tax_amount double DEFAULT 0 NOT NULL,
|
||||
shipping_amount double DEFAULT 0 NOT NULL,
|
||||
shipping_tax_amount double DEFAULT 0 NOT NULL,
|
||||
refund_amount double DEFAULT 0 NOT NULL,
|
||||
PRIMARY KEY (order_item_id),
|
||||
KEY order_id (order_id),
|
||||
KEY product_id (product_id),
|
||||
KEY customer_id (customer_id),
|
||||
KEY date_created (date_created)
|
||||
) $collate;
|
||||
CREATE TABLE {$wpdb->prefix}wc_order_tax_lookup (
|
||||
order_id BIGINT UNSIGNED NOT NULL,
|
||||
tax_rate_id BIGINT UNSIGNED NOT NULL,
|
||||
date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
|
||||
shipping_tax double DEFAULT 0 NOT NULL,
|
||||
order_tax double DEFAULT 0 NOT NULL,
|
||||
total_tax double DEFAULT 0 NOT NULL,
|
||||
PRIMARY KEY (order_id, tax_rate_id),
|
||||
KEY tax_rate_id (tax_rate_id),
|
||||
KEY date_created (date_created)
|
||||
) $collate;
|
||||
CREATE TABLE {$wpdb->prefix}wc_order_coupon_lookup (
|
||||
order_id BIGINT UNSIGNED NOT NULL,
|
||||
coupon_id BIGINT UNSIGNED NOT NULL,
|
||||
date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
|
||||
discount_amount double DEFAULT 0 NOT NULL,
|
||||
PRIMARY KEY (order_id, coupon_id),
|
||||
KEY coupon_id (coupon_id),
|
||||
KEY date_created (date_created)
|
||||
) $collate;
|
||||
CREATE TABLE {$wpdb->prefix}wc_admin_notes (
|
||||
note_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
name varchar(255) NOT NULL,
|
||||
type varchar(20) NOT NULL,
|
||||
locale varchar(20) NOT NULL,
|
||||
title longtext NOT NULL,
|
||||
content longtext NOT NULL,
|
||||
icon varchar(200) NOT NULL,
|
||||
content_data longtext NULL default null,
|
||||
status varchar(200) NOT NULL,
|
||||
source varchar(200) NOT NULL,
|
||||
date_created datetime NOT NULL default '0000-00-00 00:00:00',
|
||||
date_reminder datetime NULL default null,
|
||||
PRIMARY KEY (note_id)
|
||||
) $collate;
|
||||
CREATE TABLE {$wpdb->prefix}wc_admin_note_actions (
|
||||
action_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
note_id BIGINT UNSIGNED NOT NULL,
|
||||
name varchar(255) NOT NULL,
|
||||
label varchar(255) NOT NULL,
|
||||
query longtext NOT NULL,
|
||||
PRIMARY KEY (action_id),
|
||||
KEY note_id (note_id)
|
||||
) $collate;
|
||||
CREATE TABLE {$wpdb->prefix}wc_customer_lookup (
|
||||
customer_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
user_id BIGINT UNSIGNED DEFAULT NULL,
|
||||
username varchar(60) DEFAULT '' NOT NULL,
|
||||
first_name varchar(255) NOT NULL,
|
||||
last_name varchar(255) NOT NULL,
|
||||
email varchar(100) NULL default NULL,
|
||||
date_last_active timestamp NULL default null,
|
||||
date_registered timestamp NULL default null,
|
||||
country char(2) DEFAULT '' NOT NULL,
|
||||
postcode varchar(20) DEFAULT '' NOT NULL,
|
||||
city varchar(100) DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (customer_id),
|
||||
UNIQUE KEY user_id (user_id),
|
||||
KEY email (email)
|
||||
) $collate;
|
||||
";
|
||||
|
||||
return $tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create database tables.
|
||||
*/
|
||||
public static function create_tables() {
|
||||
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||
|
||||
dbDelta( self::get_schema() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of tables. Used to make sure all WC Admin tables are dropped
|
||||
* when uninstalling the plugin in a single site or multi site environment.
|
||||
*
|
||||
* @return array WC tables.
|
||||
*/
|
||||
public static function get_tables() {
|
||||
global $wpdb;
|
||||
|
||||
return array(
|
||||
"{$wpdb->prefix}wc_order_stats",
|
||||
"{$wpdb->prefix}wc_order_product_lookup",
|
||||
"{$wpdb->prefix}wc_order_tax_lookup",
|
||||
"{$wpdb->prefix}wc_order_coupon_lookup",
|
||||
"{$wpdb->prefix}wc_admin_notes",
|
||||
"{$wpdb->prefix}wc_admin_note_actions",
|
||||
"{$wpdb->prefix}wc_customer_lookup",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new tables.
|
||||
*
|
||||
* @param array $wc_tables List of WooCommerce tables.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_tables( $wc_tables ) {
|
||||
return array_merge(
|
||||
$wc_tables,
|
||||
self::get_tables()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstall tables when MU blog is deleted.
|
||||
*
|
||||
* @param array $tables List of tables that will be deleted by WP.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function wpmu_drop_tables( $tables ) {
|
||||
return array_merge( $tables, self::get_tables() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update WC Admin version to current.
|
||||
*/
|
||||
private static function update_wc_admin_version() {
|
||||
delete_option( self::VERSION_OPTION );
|
||||
add_option( self::VERSION_OPTION, self::VERSION_NUMBER );
|
||||
}
|
||||
}
|
||||
|
||||
WC_Admin_Install::init();
|
|
@ -26,6 +26,44 @@ class WC_Admin_Reports_Interval {
|
|||
*/
|
||||
public static $sql_datetime_format = 'Y-m-d H:i:s';
|
||||
|
||||
/**
|
||||
* Converts local datetime to GMT/UTC time.
|
||||
*
|
||||
* @param string $datetime_string String representation of local datetime.
|
||||
* @return DateTime
|
||||
*/
|
||||
public static function convert_local_datetime_to_gmt( $datetime_string ) {
|
||||
$datetime = new DateTime( $datetime_string, new DateTimeZone( wc_timezone_string() ) );
|
||||
$datetime->setTimezone( new DateTimeZone( 'GMT' ) );
|
||||
return $datetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns default 'before' parameter for the reports.
|
||||
*
|
||||
* @return DateTime
|
||||
*/
|
||||
public static function default_before() {
|
||||
$datetime = new DateTime();
|
||||
$datetime->setTimezone( new DateTimeZone( wc_timezone_string() ) );
|
||||
return $datetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns default 'after' parameter for the reports.
|
||||
*
|
||||
* @return DateTime
|
||||
*/
|
||||
public static function default_after() {
|
||||
$now = time();
|
||||
$week_back = $now - WEEK_IN_SECONDS;
|
||||
|
||||
$datetime = new DateTime();
|
||||
$datetime->setTimestamp( $week_back );
|
||||
$datetime->setTimezone( new DateTimeZone( wc_timezone_string() ) );
|
||||
return $datetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns date format to be used as grouping clause in SQL.
|
||||
*
|
||||
|
@ -62,7 +100,7 @@ class WC_Admin_Reports_Interval {
|
|||
/**
|
||||
* Returns quarter for the DateTime.
|
||||
*
|
||||
* @param DateTime $datetime Date & time.
|
||||
* @param DateTime $datetime Local date & time.
|
||||
* @return int|null
|
||||
*/
|
||||
public static function quarter( $datetime ) {
|
||||
|
@ -94,7 +132,7 @@ class WC_Admin_Reports_Interval {
|
|||
* The first week of the year is considered to be the week containing January 1.
|
||||
* The second week starts on the next $first_day_of_week.
|
||||
*
|
||||
* @param DateTime $datetime Date for which the week number is to be calculated.
|
||||
* @param DateTime $datetime Local date for which the week number is to be calculated.
|
||||
* @param int $first_day_of_week 0 for Sunday to 6 for Saturday.
|
||||
* @return int
|
||||
*/
|
||||
|
@ -112,7 +150,7 @@ class WC_Admin_Reports_Interval {
|
|||
*
|
||||
* @see WC_Admin_Reports_Interval::simple_week_number()
|
||||
*
|
||||
* @param DateTime $datetime Date for which the week number is to be calculated.
|
||||
* @param DateTime $datetime Local date for which the week number is to be calculated.
|
||||
* @param int $first_day_of_week 0 for Sunday to 6 for Saturday.
|
||||
* @return int
|
||||
*/
|
||||
|
@ -159,22 +197,21 @@ class WC_Admin_Reports_Interval {
|
|||
/**
|
||||
* Calculates number of time intervals between two dates, closed interval on both sides.
|
||||
*
|
||||
* @param string $start Start date & time.
|
||||
* @param string $end End date & time.
|
||||
* @param DateTime $start_datetime Start date & time.
|
||||
* @param DateTime $end_datetime End date & time.
|
||||
* @param string $interval Time interval increment, e.g. hour, day, week.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function intervals_between( $start, $end, $interval ) {
|
||||
$start_datetime = new DateTime( $start );
|
||||
$end_datetime = new DateTime( $end );
|
||||
|
||||
public static function intervals_between( $start_datetime, $end_datetime, $interval ) {
|
||||
switch ( $interval ) {
|
||||
case 'hour':
|
||||
$end_timestamp = (int) $end_datetime->format( 'U' );
|
||||
$start_timestamp = (int) $start_datetime->format( 'U' );
|
||||
$addendum = 0;
|
||||
$end_min_sec = $end_timestamp % HOUR_IN_SECONDS;
|
||||
$start_min_sec = $start_timestamp % HOUR_IN_SECONDS;
|
||||
// modulo HOUR_IN_SECONDS would normally work, but there are non-full hour timezones, e.g. Nepal.
|
||||
$start_min_sec = (int) $start_datetime->format( 'i' ) * MINUTE_IN_SECONDS + (int) $start_datetime->format( 's' );
|
||||
$end_min_sec = (int) $end_datetime->format( 'i' ) * MINUTE_IN_SECONDS + (int) $end_datetime->format( 's' );
|
||||
if ( $end_min_sec < $start_min_sec ) {
|
||||
$addendum = 1;
|
||||
}
|
||||
|
@ -185,8 +222,8 @@ class WC_Admin_Reports_Interval {
|
|||
$end_timestamp = (int) $end_datetime->format( 'U' );
|
||||
$start_timestamp = (int) $start_datetime->format( 'U' );
|
||||
$addendum = 0;
|
||||
$end_hour_min_sec = $end_timestamp % DAY_IN_SECONDS;
|
||||
$start_hour_min_sec = $start_timestamp % DAY_IN_SECONDS;
|
||||
$end_hour_min_sec = (int) $end_datetime->format( 'H' ) * HOUR_IN_SECONDS + (int) $end_datetime->format( 'i' ) * MINUTE_IN_SECONDS + (int) $end_datetime->format( 's' );
|
||||
$start_hour_min_sec = (int) $start_datetime->format( 'H' ) * HOUR_IN_SECONDS + (int) $start_datetime->format( 'i' ) * MINUTE_IN_SECONDS + (int) $start_datetime->format( 's' );
|
||||
if ( $end_hour_min_sec < $start_hour_min_sec ) {
|
||||
$addendum = 1;
|
||||
}
|
||||
|
@ -234,7 +271,8 @@ class WC_Admin_Reports_Interval {
|
|||
public static function next_hour_start( $datetime, $reversed = false ) {
|
||||
$hour_increment = $reversed ? 0 : 1;
|
||||
$timestamp = (int) $datetime->format( 'U' );
|
||||
$hours_offset_timestamp = ( floor( $timestamp / HOUR_IN_SECONDS ) + $hour_increment ) * HOUR_IN_SECONDS;
|
||||
$seconds_into_hour = (int) $datetime->format( 'i' ) * MINUTE_IN_SECONDS + (int) $datetime->format( 's' );
|
||||
$hours_offset_timestamp = $timestamp + ( $hour_increment * HOUR_IN_SECONDS - $seconds_into_hour );
|
||||
|
||||
if ( $reversed ) {
|
||||
$hours_offset_timestamp --;
|
||||
|
@ -242,6 +280,7 @@ class WC_Admin_Reports_Interval {
|
|||
|
||||
$hours_offset_time = new DateTime();
|
||||
$hours_offset_time->setTimestamp( $hours_offset_timestamp );
|
||||
$hours_offset_time->setTimezone( new DateTimeZone( wc_timezone_string() ) );
|
||||
return $hours_offset_time;
|
||||
}
|
||||
|
||||
|
@ -253,19 +292,19 @@ class WC_Admin_Reports_Interval {
|
|||
* @return DateTime
|
||||
*/
|
||||
public static function next_day_start( $datetime, $reversed = false ) {
|
||||
$day_increment = $reversed ? -1 : 1;
|
||||
$day_increment = $reversed ? 0 : 1;
|
||||
$timestamp = (int) $datetime->format( 'U' );
|
||||
$next_day_timestamp = ( floor( $timestamp / DAY_IN_SECONDS ) + $day_increment ) * DAY_IN_SECONDS;
|
||||
$next_day = new DateTime();
|
||||
$next_day->setTimestamp( $next_day_timestamp );
|
||||
$seconds_into_day = (int) $datetime->format( 'H' ) * HOUR_IN_SECONDS + (int) $datetime->format( 'i' ) * MINUTE_IN_SECONDS + (int) $datetime->format( 's' );
|
||||
$next_day_timestamp = $timestamp + ( $day_increment * DAY_IN_SECONDS - $seconds_into_day );
|
||||
|
||||
// The day boundary is actually next midnight when going in reverse, so set it to day -1 at 23:59:59.
|
||||
if ( $reversed ) {
|
||||
$timestamp = (int) $next_day->format( 'U' );
|
||||
$end_of_day_timestamp = floor( $timestamp / DAY_IN_SECONDS ) * DAY_IN_SECONDS + DAY_IN_SECONDS - 1;
|
||||
$next_day->setTimestamp( $end_of_day_timestamp );
|
||||
$next_day_timestamp --;
|
||||
}
|
||||
|
||||
$next_day = new DateTime();
|
||||
$next_day->setTimestamp( $next_day_timestamp );
|
||||
$next_day->setTimezone( new DateTimeZone( wc_timezone_string() ) );
|
||||
return $next_day;
|
||||
}
|
||||
|
||||
|
@ -308,7 +347,7 @@ class WC_Admin_Reports_Interval {
|
|||
$month = (int) $datetime->format( 'm' );
|
||||
|
||||
if ( $reversed ) {
|
||||
$beg_of_month_datetime = new DateTime( "$year-$month-01 00:00:00" );
|
||||
$beg_of_month_datetime = new DateTime( "$year-$month-01 00:00:00", new DateTimeZone( wc_timezone_string() ) );
|
||||
$timestamp = (int) $beg_of_month_datetime->format( 'U' );
|
||||
$end_of_prev_month_timestamp = $timestamp - 1;
|
||||
$datetime->setTimestamp( $end_of_prev_month_timestamp );
|
||||
|
@ -319,7 +358,7 @@ class WC_Admin_Reports_Interval {
|
|||
$year ++;
|
||||
}
|
||||
$day = '01';
|
||||
$datetime = new DateTime( "$year-$month-$day 00:00:00" );
|
||||
$datetime = new DateTime( "$year-$month-$day 00:00:00", new DateTimeZone( wc_timezone_string() ) );
|
||||
}
|
||||
|
||||
return $datetime;
|
||||
|
@ -375,7 +414,7 @@ class WC_Admin_Reports_Interval {
|
|||
}
|
||||
break;
|
||||
}
|
||||
$datetime = new DateTime( "$year-$month-01 00:00:00" );
|
||||
$datetime = new DateTime( "$year-$month-01 00:00:00", new DateTimeZone( wc_timezone_string() ) );
|
||||
if ( $reversed ) {
|
||||
$timestamp = (int) $datetime->format( 'U' );
|
||||
$end_of_prev_month_timestamp = $timestamp - 1;
|
||||
|
@ -399,13 +438,13 @@ class WC_Admin_Reports_Interval {
|
|||
$day = '01';
|
||||
|
||||
if ( $reversed ) {
|
||||
$datetime = new DateTime( "$year-$month-$day 00:00:00" );
|
||||
$datetime = new DateTime( "$year-$month-$day 00:00:00", new DateTimeZone( wc_timezone_string() ) );
|
||||
$timestamp = (int) $datetime->format( 'U' );
|
||||
$end_of_prev_year_timestamp = $timestamp - 1;
|
||||
$datetime->setTimestamp( $end_of_prev_year_timestamp );
|
||||
} else {
|
||||
$year += $year_increment;
|
||||
$datetime = new DateTime( "$year-$month-$day 00:00:00" );
|
||||
$datetime = new DateTime( "$year-$month-$day 00:00:00", new DateTimeZone( wc_timezone_string() ) );
|
||||
}
|
||||
|
||||
return $datetime;
|
||||
|
@ -422,9 +461,6 @@ class WC_Admin_Reports_Interval {
|
|||
* @return DateTime
|
||||
*/
|
||||
public static function iterate( $datetime, $time_interval, $reversed = false ) {
|
||||
// $result_datetime =
|
||||
// $result_timestamp_adjusted = $result_datetime->format( 'U' ) - 1;
|
||||
// $result_datetime->setTimestamp( $result_timestamp_adjusted );
|
||||
return call_user_func( array( __CLASS__, "next_{$time_interval}_start" ), $datetime, $reversed );
|
||||
}
|
||||
|
||||
|
|
|
@ -347,17 +347,28 @@ class WC_Admin_Reports_Segmenting {
|
|||
$segment_labels[ $id ] = $segment->get_name();
|
||||
}
|
||||
} elseif ( 'category' === $this->query_args['segmentby'] ) {
|
||||
$categories = get_categories(
|
||||
array(
|
||||
$args = array(
|
||||
'taxonomy' => 'product_cat',
|
||||
)
|
||||
);
|
||||
|
||||
if ( isset( $this->query_args['categories'] ) ) {
|
||||
$args['include'] = $this->query_args['categories'];
|
||||
}
|
||||
|
||||
$categories = get_categories( $args );
|
||||
|
||||
$segments = wp_list_pluck( $categories, 'cat_ID' );
|
||||
$segment_labels = wp_list_pluck( $categories, 'name', 'cat_ID' );
|
||||
|
||||
} elseif ( 'coupon' === $this->query_args['segmentby'] ) {
|
||||
// @todo Switch to a non-direct-SQL way to get all coupons?
|
||||
// @todo These are only currently existing coupons, but we should add also deleted ones, if they have been used at least once.
|
||||
$coupon_ids = $wpdb->get_results( "SELECT ID FROM {$wpdb->prefix}posts WHERE post_type='shop_coupon' AND post_status='publish'", ARRAY_A ); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
$segments = wp_list_pluck( $coupon_ids, 'ID' );
|
||||
$args = array();
|
||||
if ( isset( $this->query_args['coupons'] ) ) {
|
||||
$args['include'] = $this->query_args['coupons'];
|
||||
}
|
||||
$coupons = WC_Admin_Reports_Coupons_Data_Store::get_coupons( $args );
|
||||
$segments = wp_list_pluck( $coupons, 'ID' );
|
||||
$segment_labels = wp_list_pluck( $coupons, 'post_title', 'ID' );
|
||||
$segment_labels = array_map( 'wc_format_coupon_code', $segment_labels );
|
||||
} elseif ( 'customer_type' === $this->query_args['segmentby'] ) {
|
||||
// 0 -- new customer
|
||||
// 1 -- returning customer
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
/**
|
||||
* Class for stock stats report querying
|
||||
*
|
||||
* $report = new WC_Admin_Reports_Stock__Stats_Query();
|
||||
* $mydata = $report->get_data();
|
||||
*
|
||||
* @package WooCommerce Admin/Classes
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* WC_Admin_Reports_Stock_Stats_Query
|
||||
*/
|
||||
class WC_Admin_Reports_Stock_Stats_Query extends WC_Admin_Reports_Query {
|
||||
|
||||
/**
|
||||
* Get product data based on the current query vars.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_data() {
|
||||
$data_store = WC_Data_Store::load( 'report-stock-stats' );
|
||||
$results = $data_store->get_data();
|
||||
return apply_filters( 'woocommerce_reports_stock_stats_query', $results );
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,416 @@
|
|||
<?php
|
||||
/**
|
||||
* Report table sync related functions and actions.
|
||||
*
|
||||
* @package WooCommerce Admin/Classes
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* WC_Admin_Reports_Sync Class.
|
||||
*/
|
||||
class WC_Admin_Reports_Sync {
|
||||
/**
|
||||
* Action hook for reducing a range of batches down to single actions.
|
||||
*/
|
||||
const QUEUE_BATCH_ACTION = 'wc-admin_queue_batches';
|
||||
|
||||
/**
|
||||
* Action hook for queuing an action after another is complete.
|
||||
*/
|
||||
const QUEUE_DEPEDENT_ACTION = 'wc-admin_queue_dependent_action';
|
||||
|
||||
/**
|
||||
* Action hook for processing a batch of customers.
|
||||
*/
|
||||
const CUSTOMERS_BATCH_ACTION = 'wc-admin_process_customers_batch';
|
||||
|
||||
/**
|
||||
* Action hook for processing a batch of orders.
|
||||
*/
|
||||
const ORDERS_BATCH_ACTION = 'wc-admin_process_orders_batch';
|
||||
|
||||
/**
|
||||
* Action hook for initializing the orders lookup batch creation.
|
||||
*/
|
||||
const ORDERS_LOOKUP_BATCH_INIT = 'wc-admin_orders_lookup_batch_init';
|
||||
|
||||
/**
|
||||
* Action hook for processing a batch of orders.
|
||||
*/
|
||||
const SINGLE_ORDER_ACTION = 'wc-admin_process_order';
|
||||
|
||||
/**
|
||||
* Queue instance.
|
||||
*
|
||||
* @var WC_Queue_Interface
|
||||
*/
|
||||
protected static $queue = null;
|
||||
|
||||
/**
|
||||
* Get queue instance.
|
||||
*
|
||||
* @return WC_Queue_Interface
|
||||
*/
|
||||
public static function queue() {
|
||||
if ( is_null( self::$queue ) ) {
|
||||
self::$queue = WC()->queue();
|
||||
}
|
||||
|
||||
return self::$queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set queue instance.
|
||||
*
|
||||
* @param WC_Queue_Interface $queue Queue instance.
|
||||
*/
|
||||
public static function set_queue( $queue ) {
|
||||
self::$queue = $queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook in sync methods.
|
||||
*/
|
||||
public static function init() {
|
||||
// Add report regeneration to tools menu.
|
||||
add_filter( 'woocommerce_debug_tools', array( __CLASS__, 'add_regenerate_tool' ) );
|
||||
|
||||
// Initialize syncing hooks.
|
||||
add_action( 'wp_loaded', array( __CLASS__, 'orders_lookup_update_init' ) );
|
||||
|
||||
// Initialize scheduled action handlers.
|
||||
add_action( self::QUEUE_BATCH_ACTION, array( __CLASS__, 'queue_batches' ), 10, 3 );
|
||||
add_action( self::QUEUE_DEPEDENT_ACTION, array( __CLASS__, 'queue_dependent_action' ), 10, 3 );
|
||||
add_action( self::CUSTOMERS_BATCH_ACTION, array( __CLASS__, 'customer_lookup_process_batch' ) );
|
||||
add_action( self::ORDERS_BATCH_ACTION, array( __CLASS__, 'orders_lookup_process_batch' ) );
|
||||
add_action( self::ORDERS_LOOKUP_BATCH_INIT, array( __CLASS__, 'orders_lookup_batch_init' ) );
|
||||
add_action( self::SINGLE_ORDER_ACTION, array( __CLASS__, 'orders_lookup_process_order' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate data for reports.
|
||||
*/
|
||||
public static function regenerate_report_data() {
|
||||
// Add registered customers to the lookup table before updating order stats
|
||||
// so that the orders can be associated with the `customer_id` column.
|
||||
self::customer_lookup_batch_init();
|
||||
// Queue orders lookup to occur after customers lookup generation is done.
|
||||
self::queue_dependent_action( self::ORDERS_LOOKUP_BATCH_INIT, array(), self::CUSTOMERS_BATCH_ACTION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds regenerate tool.
|
||||
*
|
||||
* @param array $tools List of tools.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_regenerate_tool( $tools ) {
|
||||
return array_merge(
|
||||
$tools,
|
||||
array(
|
||||
'rebuild_stats' => array(
|
||||
'name' => __( 'Rebuild reports data', 'wc-admin' ),
|
||||
'button' => __( 'Rebuild reports', 'wc-admin' ),
|
||||
'desc' => __( 'This tool will rebuild all of the information used by the reports.', 'wc-admin' ),
|
||||
'callback' => array( __CLASS__, 'regenerate_report_data' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Schedule an action to process a single Order.
|
||||
*
|
||||
* @param int $order_id Order ID.
|
||||
* @return void
|
||||
*/
|
||||
public static function schedule_single_order_process( $order_id ) {
|
||||
if ( 'shop_order' !== get_post_type( $order_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( apply_filters( 'woocommerce_disable_order_scheduling', false ) ) {
|
||||
self::orders_lookup_process_order( $order_id );
|
||||
return;
|
||||
}
|
||||
|
||||
// This can get called multiple times for a single order, so we look
|
||||
// for existing pending jobs for the same order to avoid duplicating efforts.
|
||||
$existing_jobs = self::queue()->search(
|
||||
array(
|
||||
'status' => 'pending',
|
||||
'per_page' => 1,
|
||||
'claimed' => false,
|
||||
'search' => "[{$order_id}]",
|
||||
)
|
||||
);
|
||||
|
||||
if ( $existing_jobs ) {
|
||||
$existing_job = current( $existing_jobs );
|
||||
|
||||
// Bail out if there's a pending single order action, or a pending dependent action.
|
||||
if (
|
||||
( self::SINGLE_ORDER_ACTION === $existing_job->get_hook() ) ||
|
||||
(
|
||||
self::QUEUE_DEPEDENT_ACTION === $existing_job->get_hook() &&
|
||||
in_array( self::SINGLE_ORDER_ACTION, $existing_job->get_args() )
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We want to ensure that customer lookup updates are scheduled before order updates.
|
||||
self::queue_dependent_action( self::SINGLE_ORDER_ACTION, array( $order_id ), self::CUSTOMERS_BATCH_ACTION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach order lookup update hooks.
|
||||
*/
|
||||
public static function orders_lookup_update_init() {
|
||||
// Activate WC_Order extension.
|
||||
WC_Admin_Order::add_filters();
|
||||
|
||||
add_action( 'save_post', array( __CLASS__, 'schedule_single_order_process' ) );
|
||||
add_action( 'woocommerce_order_refunded', array( __CLASS__, 'schedule_single_order_process' ) );
|
||||
|
||||
WC_Admin_Reports_Orders_Stats_Data_Store::init();
|
||||
WC_Admin_Reports_Customers_Data_Store::init();
|
||||
WC_Admin_Reports_Coupons_Data_Store::init();
|
||||
WC_Admin_Reports_Products_Data_Store::init();
|
||||
WC_Admin_Reports_Taxes_Data_Store::init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init order/product lookup tables update (in batches).
|
||||
*/
|
||||
public static function orders_lookup_batch_init() {
|
||||
$batch_size = self::get_batch_size( self::ORDERS_BATCH_ACTION );
|
||||
$order_query = new WC_Order_Query(
|
||||
array(
|
||||
'return' => 'ids',
|
||||
'limit' => 1,
|
||||
'paginate' => true,
|
||||
)
|
||||
);
|
||||
$result = $order_query->get_orders();
|
||||
|
||||
if ( 0 === $result->total ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$num_batches = ceil( $result->total / $batch_size );
|
||||
|
||||
self::queue_batches( 1, $num_batches, self::ORDERS_BATCH_ACTION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a batch of orders to update (stats and products).
|
||||
*
|
||||
* @param int $batch_number Batch number to process (essentially a query page number).
|
||||
* @return void
|
||||
*/
|
||||
public static function orders_lookup_process_batch( $batch_number ) {
|
||||
$batch_size = self::get_batch_size( self::ORDERS_BATCH_ACTION );
|
||||
$order_query = new WC_Order_Query(
|
||||
array(
|
||||
'return' => 'ids',
|
||||
'limit' => $batch_size,
|
||||
'page' => $batch_number,
|
||||
'orderby' => 'ID',
|
||||
'order' => 'ASC',
|
||||
)
|
||||
);
|
||||
$order_ids = $order_query->get_orders();
|
||||
|
||||
foreach ( $order_ids as $order_id ) {
|
||||
self::orders_lookup_process_order( $order_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a single order to update lookup tables for.
|
||||
* If an error is encountered in one of the updates, a retry action is scheduled.
|
||||
*
|
||||
* @param int $order_id Order ID.
|
||||
* @return void
|
||||
*/
|
||||
public static function orders_lookup_process_order( $order_id ) {
|
||||
$result = array_sum(
|
||||
array(
|
||||
WC_Admin_Reports_Orders_Stats_Data_Store::sync_order( $order_id ),
|
||||
WC_Admin_Reports_Products_Data_Store::sync_order_products( $order_id ),
|
||||
WC_Admin_Reports_Coupons_Data_Store::sync_order_coupons( $order_id ),
|
||||
WC_Admin_Reports_Taxes_Data_Store::sync_order_taxes( $order_id ),
|
||||
)
|
||||
);
|
||||
|
||||
// If all updates were either skipped or successful, we're done.
|
||||
// The update methods return -1 for skip, or a boolean success indicator.
|
||||
if ( 4 === absint( $result ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise assume an error occurred and reschedule.
|
||||
self::schedule_single_order_process( $order_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the batch size for regenerating reports.
|
||||
* Note: can differ per batch action.
|
||||
*
|
||||
* @param string $action Single batch action name.
|
||||
* @return int Batch size.
|
||||
*/
|
||||
public static function get_batch_size( $action ) {
|
||||
$batch_sizes = array(
|
||||
self::QUEUE_BATCH_ACTION => 100,
|
||||
self::CUSTOMERS_BATCH_ACTION => 25,
|
||||
self::ORDERS_BATCH_ACTION => 10,
|
||||
);
|
||||
$batch_size = isset( $batch_sizes[ $action ] ) ? $batch_sizes[ $action ] : 25;
|
||||
|
||||
/**
|
||||
* Filter the batch size for regenerating a report table.
|
||||
*
|
||||
* @param int $batch_size Batch size.
|
||||
* @param string $action Batch action name.
|
||||
*/
|
||||
return apply_filters( 'wc_admin_report_regenerate_batch_size', $batch_size, $action );
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a large number of batch jobs, respecting the batch size limit.
|
||||
* Reduces a range of batches down to "single batch" jobs.
|
||||
*
|
||||
* @param int $range_start Starting batch number.
|
||||
* @param int $range_end Ending batch number.
|
||||
* @param string $single_batch_action Action to schedule for a single batch.
|
||||
* @return void
|
||||
*/
|
||||
public static function queue_batches( $range_start, $range_end, $single_batch_action ) {
|
||||
$batch_size = self::get_batch_size( self::QUEUE_BATCH_ACTION );
|
||||
$range_size = 1 + ( $range_end - $range_start );
|
||||
$action_timestamp = time() + 5;
|
||||
|
||||
if ( $range_size > $batch_size ) {
|
||||
// If the current batch range is larger than a single batch,
|
||||
// split the range into $queue_batch_size chunks.
|
||||
$chunk_size = ceil( $range_size / $batch_size );
|
||||
|
||||
for ( $i = 0; $i < $batch_size; $i++ ) {
|
||||
$batch_start = $range_start + ( $i * $chunk_size );
|
||||
$batch_end = min( $range_end, $range_start + ( $chunk_size * ( $i + 1 ) ) - 1 );
|
||||
|
||||
self::queue()->schedule_single(
|
||||
$action_timestamp,
|
||||
self::QUEUE_BATCH_ACTION,
|
||||
array( $batch_start, $batch_end, $single_batch_action )
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Otherwise, queue the single batches.
|
||||
for ( $i = $range_start; $i <= $range_end; $i++ ) {
|
||||
self::queue()->schedule_single( $action_timestamp, $single_batch_action, array( $i ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue an action to run after another.
|
||||
*
|
||||
* @param string $action Action to run after prerequisite.
|
||||
* @param array $action_args Action arguments.
|
||||
* @param string $prerequisite_action Prerequisite action.
|
||||
*/
|
||||
public static function queue_dependent_action( $action, $action_args, $prerequisite_action ) {
|
||||
$blocking_jobs = self::queue()->search(
|
||||
array(
|
||||
'status' => 'pending',
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'per_page' => 1,
|
||||
'claimed' => false,
|
||||
'search' => $prerequisite_action, // search is used instead of hook to find queued batch creation.
|
||||
)
|
||||
);
|
||||
|
||||
$next_job_schedule = null;
|
||||
$blocking_job_hook = null;
|
||||
|
||||
if ( $blocking_jobs ) {
|
||||
$blocking_job = current( $blocking_jobs );
|
||||
$blocking_job_hook = $blocking_job->get_hook();
|
||||
$next_job_schedule = $blocking_job->get_schedule()->next();
|
||||
}
|
||||
|
||||
// Eliminate the false positive scenario where the blocking job is
|
||||
// actually another queued dependent action awaiting the same prerequisite.
|
||||
// Also, ensure that the next schedule is a DateTime (it can be null).
|
||||
if (
|
||||
is_a( $next_job_schedule, 'DateTime' ) &&
|
||||
( self::QUEUE_DEPEDENT_ACTION !== $blocking_job_hook )
|
||||
) {
|
||||
self::queue()->schedule_single(
|
||||
$next_job_schedule->getTimestamp() + 5,
|
||||
self::QUEUE_DEPEDENT_ACTION,
|
||||
array( $action, $action_args, $prerequisite_action )
|
||||
);
|
||||
} else {
|
||||
self::queue()->schedule_single( time() + 5, $action, $action_args );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Init customer lookup table update (in batches).
|
||||
*/
|
||||
public static function customer_lookup_batch_init() {
|
||||
$batch_size = self::get_batch_size( self::CUSTOMERS_BATCH_ACTION );
|
||||
$customer_query = new WP_User_Query(
|
||||
array(
|
||||
'fields' => 'ID',
|
||||
'number' => 1,
|
||||
)
|
||||
);
|
||||
$total_customers = $customer_query->get_total();
|
||||
|
||||
if ( 0 === $total_customers ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$num_batches = ceil( $total_customers / $batch_size );
|
||||
|
||||
self::queue_batches( 1, $num_batches, self::CUSTOMERS_BATCH_ACTION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a batch of customers to update.
|
||||
*
|
||||
* @param int $batch_number Batch number to process (essentially a query page number).
|
||||
* @return void
|
||||
*/
|
||||
public static function customer_lookup_process_batch( $batch_number ) {
|
||||
$batch_size = self::get_batch_size( self::CUSTOMERS_BATCH_ACTION );
|
||||
$customer_query = new WP_User_Query(
|
||||
array(
|
||||
'fields' => 'ID',
|
||||
'orderby' => 'ID',
|
||||
'order' => 'ASC',
|
||||
'number' => $batch_size,
|
||||
'paged' => $batch_number,
|
||||
)
|
||||
);
|
||||
|
||||
$customer_ids = $customer_query->get_results();
|
||||
|
||||
foreach ( $customer_ids as $customer_id ) {
|
||||
// @todo Schedule single customer update if this fails?
|
||||
WC_Admin_Reports_Customers_Data_Store::update_registered_customer( $customer_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WC_Admin_Reports_Sync::init();
|
|
@ -212,8 +212,6 @@ class WC_Admin_Reports_Categories_Data_Store extends WC_Admin_Reports_Data_Store
|
|||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
$now = time();
|
||||
$week_back = $now - WEEK_IN_SECONDS;
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
|
@ -221,13 +219,14 @@ class WC_Admin_Reports_Categories_Data_Store extends WC_Admin_Reports_Data_Store
|
|||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'date',
|
||||
'before' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $now ),
|
||||
'after' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ),
|
||||
'before' => WC_Admin_Reports_Interval::default_before(),
|
||||
'after' => WC_Admin_Reports_Interval::default_after(),
|
||||
'fields' => '*',
|
||||
'categories' => array(),
|
||||
'extended_info' => false,
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = wp_cache_get( $cache_key, $this->cache_group );
|
||||
|
|
|
@ -207,8 +207,6 @@ class WC_Admin_Reports_Coupons_Data_Store extends WC_Admin_Reports_Data_Store im
|
|||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
$now = time();
|
||||
$week_back = $now - WEEK_IN_SECONDS;
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
|
@ -216,13 +214,14 @@ class WC_Admin_Reports_Coupons_Data_Store extends WC_Admin_Reports_Data_Store im
|
|||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'coupon_id',
|
||||
'before' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $now ),
|
||||
'after' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ),
|
||||
'before' => WC_Admin_Reports_Interval::default_before(),
|
||||
'after' => WC_Admin_Reports_Interval::default_after(),
|
||||
'fields' => '*',
|
||||
'coupons' => array(),
|
||||
'extended_info' => false,
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = wp_cache_get( $cache_key, $this->cache_group );
|
||||
|
@ -336,7 +335,7 @@ class WC_Admin_Reports_Coupons_Data_Store extends WC_Admin_Reports_Data_Store im
|
|||
'order_id' => $order_id,
|
||||
'coupon_id' => $coupon_id,
|
||||
'discount_amount' => $coupon_item->get_discount(),
|
||||
'date_created' => date( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ),
|
||||
'date_created' => $order->get_date_created( 'edit' )->date( WC_Admin_Reports_Interval::$sql_datetime_format ),
|
||||
),
|
||||
array(
|
||||
'%d',
|
||||
|
@ -386,4 +385,22 @@ class WC_Admin_Reports_Coupons_Data_Store extends WC_Admin_Reports_Data_Store im
|
|||
do_action( 'woocommerce_reports_delete_coupon', 0, $order_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets coupons based on the provided arguments.
|
||||
*
|
||||
* @todo Upon core merge, including this in core's `class-wc-coupon-data-store-cpt.php` might make more sense.
|
||||
* @param array $args Array of args to filter the query by. Supports `include`.
|
||||
* @return array Array of results.
|
||||
*/
|
||||
public static function get_coupons( $args ) {
|
||||
global $wpdb;
|
||||
$query = "SELECT ID, post_title FROM {$wpdb->prefix}posts WHERE post_type='shop_coupon'";
|
||||
|
||||
if ( ! empty( $args['include'] ) ) {
|
||||
$included_coupons = implode( ',', $args['include'] );
|
||||
$query .= " AND ID IN ({$included_coupons})";
|
||||
}
|
||||
|
||||
return $wpdb->get_results( $query ); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,8 +95,6 @@ class WC_Admin_Reports_Coupons_Stats_Data_Store extends WC_Admin_Reports_Coupons
|
|||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
$now = time();
|
||||
$week_back = $now - WEEK_IN_SECONDS;
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
|
@ -104,13 +102,14 @@ class WC_Admin_Reports_Coupons_Stats_Data_Store extends WC_Admin_Reports_Coupons
|
|||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'date',
|
||||
'before' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $now ),
|
||||
'after' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ),
|
||||
'before' => WC_Admin_Reports_Interval::default_before(),
|
||||
'after' => WC_Admin_Reports_Interval::default_after(),
|
||||
'fields' => '*',
|
||||
'interval' => 'week',
|
||||
'coupons' => array(),
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = wp_cache_get( $cache_key, $this->cache_group );
|
||||
|
|
|
@ -25,7 +25,7 @@ class WC_Admin_Reports_Customers_Data_Store extends WC_Admin_Reports_Data_Store
|
|||
* @var array
|
||||
*/
|
||||
protected $column_types = array(
|
||||
'customer_id' => 'intval',
|
||||
'id' => 'intval',
|
||||
'user_id' => 'intval',
|
||||
'orders_count' => 'intval',
|
||||
'total_spend' => 'floatval',
|
||||
|
@ -38,7 +38,7 @@ class WC_Admin_Reports_Customers_Data_Store extends WC_Admin_Reports_Data_Store
|
|||
* @var array
|
||||
*/
|
||||
protected $report_columns = array(
|
||||
'customer_id' => 'customer_id',
|
||||
'id' => 'customer_id as id',
|
||||
'user_id' => 'user_id',
|
||||
'username' => 'username',
|
||||
'name' => "CONCAT_WS( ' ', first_name, last_name ) as name", // @todo What does this mean for RTL?
|
||||
|
@ -60,7 +60,7 @@ class WC_Admin_Reports_Customers_Data_Store extends WC_Admin_Reports_Data_Store
|
|||
global $wpdb;
|
||||
|
||||
// Initialize some report columns that need disambiguation.
|
||||
$this->report_columns['customer_id'] = $wpdb->prefix . self::TABLE_NAME . '.customer_id';
|
||||
$this->report_columns['id'] = $wpdb->prefix . self::TABLE_NAME . '.customer_id as id';
|
||||
$this->report_columns['date_last_order'] = "MAX( {$wpdb->prefix}wc_order_stats.date_created ) as date_last_order";
|
||||
}
|
||||
|
||||
|
@ -230,8 +230,28 @@ class WC_Admin_Reports_Customers_Data_Store extends WC_Admin_Reports_Data_Store
|
|||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $query_args['name'] ) ) {
|
||||
$where_clauses[] = $wpdb->prepare( "CONCAT_WS( ' ', first_name, last_name ) = %s", $query_args['name'] );
|
||||
$search_params = array(
|
||||
'name',
|
||||
'username',
|
||||
'email',
|
||||
);
|
||||
|
||||
if ( ! empty( $query_args['search'] ) ) {
|
||||
$name_like = '%' . $wpdb->esc_like( $query_args['search'] ) . '%';
|
||||
|
||||
if ( empty( $query_args['searchby'] ) || 'name' === $query_args['searchby'] || ! in_array( $query_args['searchby'], $search_params ) ) {
|
||||
$searchby = "CONCAT_WS( ' ', first_name, last_name )";
|
||||
} else {
|
||||
$searchby = $query_args['searchby'];
|
||||
}
|
||||
|
||||
$where_clauses[] = $wpdb->prepare( "{$searchby} LIKE %s", $name_like ); // WPCS: unprepared SQL ok.
|
||||
}
|
||||
|
||||
// Allow a list of customer IDs to be specified.
|
||||
if ( ! empty( $query_args['customers'] ) ) {
|
||||
$included_customers = implode( ',', $query_args['customers'] );
|
||||
$where_clauses[] = "{$customer_lookup_table}.customer_id IN ({$included_customers})";
|
||||
}
|
||||
|
||||
$numeric_params = array(
|
||||
|
@ -253,17 +273,18 @@ class WC_Admin_Reports_Customers_Data_Store extends WC_Admin_Reports_Data_Store
|
|||
$subclauses = array();
|
||||
$min_param = $numeric_param . '_min';
|
||||
$max_param = $numeric_param . '_max';
|
||||
$or_equal = isset( $query_args[ $min_param ] ) && isset( $query_args[ $max_param ] ) ? '=' : '';
|
||||
|
||||
if ( isset( $query_args[ $min_param ] ) ) {
|
||||
$subclauses[] = $wpdb->prepare(
|
||||
"{$param_info['column']} >= {$param_info['format']}",
|
||||
"{$param_info['column']} >{$or_equal} {$param_info['format']}",
|
||||
$query_args[ $min_param ]
|
||||
); // WPCS: unprepared SQL ok.
|
||||
}
|
||||
|
||||
if ( isset( $query_args[ $max_param ] ) ) {
|
||||
$subclauses[] = $wpdb->prepare(
|
||||
"{$param_info['column']} <= {$param_info['format']}",
|
||||
"{$param_info['column']} <{$or_equal} {$param_info['format']}",
|
||||
$query_args[ $max_param ]
|
||||
); // WPCS: unprepared SQL ok.
|
||||
}
|
||||
|
@ -312,6 +333,7 @@ class WC_Admin_Reports_Customers_Data_Store extends WC_Admin_Reports_Data_Store
|
|||
'fields' => '*',
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = wp_cache_get( $cache_key, $this->cache_group );
|
||||
|
@ -392,52 +414,77 @@ class WC_Admin_Reports_Customers_Data_Store extends WC_Admin_Reports_Data_Store
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets the guest (no user_id) customer ID or creates a new one for
|
||||
* the corresponding billing email in the provided WC_Order
|
||||
* Returns an existing customer ID for an order if one exists.
|
||||
*
|
||||
* @param WC_Order $order Order to get/create guest customer data with.
|
||||
* @return int|false The ID of the retrieved/created customer, or false on error.
|
||||
* @param object $order WC Order.
|
||||
* @return int|bool
|
||||
*/
|
||||
public function get_or_create_guest_customer_from_order( $order ) {
|
||||
global $wpdb;
|
||||
public static function get_existing_customer_id_from_order( $order ) {
|
||||
$user_id = $order->get_customer_id();
|
||||
|
||||
if ( 0 === $user_id ) {
|
||||
$email = $order->get_billing_email( 'edit' );
|
||||
|
||||
if ( empty( $email ) ) {
|
||||
if ( $email ) {
|
||||
return self::get_guest_id_by_email( $email );
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
$existing_guest = $this->get_guest_by_email( $email );
|
||||
|
||||
if ( $existing_guest ) {
|
||||
return $existing_guest['customer_id'];
|
||||
} else {
|
||||
return self::get_customer_id_by_user_id( $user_id );
|
||||
}
|
||||
}
|
||||
|
||||
$result = $wpdb->insert(
|
||||
$wpdb->prefix . self::TABLE_NAME,
|
||||
array(
|
||||
/**
|
||||
* Get or create a customer from a given order.
|
||||
*
|
||||
* @param object $order WC Order.
|
||||
* @return int|bool
|
||||
*/
|
||||
public static function get_or_create_customer_from_order( $order ) {
|
||||
global $wpdb;
|
||||
$returning_customer_id = self::get_existing_customer_id_from_order( $order );
|
||||
|
||||
if ( $returning_customer_id ) {
|
||||
return $returning_customer_id;
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'first_name' => $order->get_billing_first_name( 'edit' ),
|
||||
'last_name' => $order->get_billing_last_name( 'edit' ),
|
||||
'email' => $email,
|
||||
'email' => $order->get_billing_email( 'edit' ),
|
||||
'city' => $order->get_billing_city( 'edit' ),
|
||||
'postcode' => $order->get_billing_postcode( 'edit' ),
|
||||
'country' => $order->get_billing_country( 'edit' ),
|
||||
'date_last_active' => date( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ),
|
||||
),
|
||||
array(
|
||||
'%s',
|
||||
'%s',
|
||||
'%s',
|
||||
'%s',
|
||||
'%s',
|
||||
'%s',
|
||||
'%s',
|
||||
)
|
||||
);
|
||||
$format = array(
|
||||
'%s',
|
||||
'%s',
|
||||
'%s',
|
||||
'%s',
|
||||
'%s',
|
||||
'%s',
|
||||
'%s',
|
||||
);
|
||||
|
||||
// Add registered customer data.
|
||||
if ( 0 !== $order->get_user_id() ) {
|
||||
$user_id = $order->get_user_id();
|
||||
$customer = new WC_Customer( $user_id );
|
||||
$data['user_id'] = $user_id;
|
||||
$data['username'] = $customer->get_username( 'edit' );
|
||||
$data['date_registered'] = $customer->get_date_created( 'edit' )->date( WC_Admin_Reports_Interval::$sql_datetime_format );
|
||||
$format[] = '%d';
|
||||
$format[] = '%s';
|
||||
$format[] = '%s';
|
||||
}
|
||||
|
||||
$result = $wpdb->insert( $wpdb->prefix . self::TABLE_NAME, $data, $format );
|
||||
$customer_id = $wpdb->insert_id;
|
||||
|
||||
/**
|
||||
* Fires when customser's reports are created.
|
||||
* Fires when a new report customer is created.
|
||||
*
|
||||
* @param int $customer_id Customer ID.
|
||||
*/
|
||||
|
@ -447,53 +494,23 @@ class WC_Admin_Reports_Customers_Data_Store extends WC_Admin_Reports_Data_Store
|
|||
}
|
||||
|
||||
/**
|
||||
* Retrieve a guest (no user_id) customer row by email.
|
||||
* Retrieve a guest ID (when user_id is null) by email.
|
||||
*
|
||||
* @param string $email Email address.
|
||||
* @return false|array Customer array if found, boolean false if not.
|
||||
*/
|
||||
public function get_guest_by_email( $email ) {
|
||||
public static function get_guest_id_by_email( $email ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
$guest_row = $wpdb->get_row(
|
||||
$customer_id = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT * FROM {$table_name} WHERE email = %s AND user_id IS NULL LIMIT 1",
|
||||
"SELECT customer_id FROM {$table_name} WHERE email = %s AND user_id IS NULL LIMIT 1",
|
||||
$email
|
||||
),
|
||||
ARRAY_A
|
||||
)
|
||||
); // WPCS: unprepared SQL ok.
|
||||
|
||||
if ( $guest_row ) {
|
||||
return $this->cast_numbers( $guest_row );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a registered customer row by user_id.
|
||||
*
|
||||
* @param string|int $user_id User ID.
|
||||
* @return false|array Customer array if found, boolean false if not.
|
||||
*/
|
||||
public function get_customer_by_user_id( $user_id ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
$customer = $wpdb->get_row(
|
||||
$wpdb->prepare(
|
||||
"SELECT * FROM {$table_name} WHERE user_id = %d LIMIT 1",
|
||||
$user_id
|
||||
),
|
||||
ARRAY_A
|
||||
); // WPCS: unprepared SQL ok.
|
||||
|
||||
if ( $customer ) {
|
||||
return $this->cast_numbers( $customer );
|
||||
}
|
||||
|
||||
return false;
|
||||
return $customer_id ? (int) $customer_id : false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -516,6 +533,30 @@ class WC_Admin_Reports_Customers_Data_Store extends WC_Admin_Reports_Data_Store
|
|||
return $customer_id ? (int) $customer_id : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the oldest orders made by a customer.
|
||||
*
|
||||
* @param int $customer_id Customer ID.
|
||||
* @return array Orders.
|
||||
*/
|
||||
public static function get_oldest_orders( $customer_id ) {
|
||||
global $wpdb;
|
||||
$orders_table = $wpdb->prefix . 'wc_order_stats';
|
||||
$excluded_statuses = array_map( array( __CLASS__, 'normalize_order_status' ), self::get_excluded_report_order_statuses() );
|
||||
$excluded_statuses_condition = '';
|
||||
if ( ! empty( $excluded_statuses ) ) {
|
||||
$excluded_statuses_str = implode( "','", $excluded_statuses );
|
||||
$excluded_statuses_condition = "AND status NOT IN ('{$excluded_statuses_str}')";
|
||||
}
|
||||
|
||||
return $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT order_id, date_created FROM {$orders_table} WHERE customer_id = %d {$excluded_statuses_condition} ORDER BY date_created, order_id ASC LIMIT 2",
|
||||
$customer_id
|
||||
)
|
||||
); // WPCS: unprepared SQL ok.
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the database with customer data.
|
||||
*
|
||||
|
@ -527,7 +568,7 @@ class WC_Admin_Reports_Customers_Data_Store extends WC_Admin_Reports_Data_Store
|
|||
|
||||
$customer = new WC_Customer( $user_id );
|
||||
|
||||
if ( $customer->get_id() != $user_id ) {
|
||||
if ( ! self::is_valid_customer( $user_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -541,7 +582,7 @@ class WC_Admin_Reports_Customers_Data_Store extends WC_Admin_Reports_Data_Store
|
|||
'city' => $customer->get_billing_city( 'edit' ),
|
||||
'postcode' => $customer->get_billing_postcode( 'edit' ),
|
||||
'country' => $customer->get_billing_country( 'edit' ),
|
||||
'date_registered' => date( 'Y-m-d H:i:s', $customer->get_date_created( 'edit' )->getTimestamp() ),
|
||||
'date_registered' => $customer->get_date_created( 'edit' )->date( WC_Admin_Reports_Interval::$sql_datetime_format ),
|
||||
'date_last_active' => $last_active ? date( 'Y-m-d H:i:s', $last_active ) : null,
|
||||
);
|
||||
$format = array(
|
||||
|
@ -576,6 +617,26 @@ class WC_Admin_Reports_Customers_Data_Store extends WC_Admin_Reports_Data_Store
|
|||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user ID is a valid customer or other user role with past orders.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @return bool
|
||||
*/
|
||||
protected static function is_valid_customer( $user_id ) {
|
||||
$customer = new WC_Customer( $user_id );
|
||||
|
||||
if ( $customer->get_id() !== $user_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $customer->get_order_count() < 1 && 'customer' !== $customer->get_role() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns string to be used as cache key for the data.
|
||||
*
|
||||
|
|
|
@ -62,6 +62,8 @@ class WC_Admin_Reports_Customers_Stats_Data_Store extends WC_Admin_Reports_Custo
|
|||
'fields' => '*',
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = wp_cache_get( $cache_key, $this->cache_group );
|
||||
|
||||
|
|
|
@ -122,19 +122,18 @@ class WC_Admin_Reports_Data_Store {
|
|||
* Fills in interval gaps from DB with 0-filled objects.
|
||||
*
|
||||
* @param array $db_intervals Array of all intervals present in the db.
|
||||
* @param DateTime $datetime_start Start date.
|
||||
* @param DateTime $datetime_end End date.
|
||||
* @param DateTime $start_datetime Start date.
|
||||
* @param DateTime $end_datetime End date.
|
||||
* @param string $time_interval Time interval, e.g. day, week, month.
|
||||
* @param stdClass $data Data with SQL extracted intervals.
|
||||
* @return stdClass
|
||||
*/
|
||||
protected function fill_in_missing_intervals( $db_intervals, $datetime_start, $datetime_end, $time_interval, &$data ) {
|
||||
protected function fill_in_missing_intervals( $db_intervals, $start_datetime, $end_datetime, $time_interval, &$data ) {
|
||||
// @todo This is ugly and messy.
|
||||
$local_tz = new DateTimeZone( wc_timezone_string() );
|
||||
// At this point, we don't know when we can stop iterating, as the ordering can be based on any value.
|
||||
$end_datetime = new DateTime( $datetime_end );
|
||||
$time_ids = array_flip( wp_list_pluck( $data->intervals, 'time_interval' ) );
|
||||
$db_intervals = array_flip( $db_intervals );
|
||||
$datetime = new DateTime( $datetime_start );
|
||||
// Totals object used to get all needed properties.
|
||||
$totals_arr = get_object_vars( $data->totals );
|
||||
foreach ( $totals_arr as $key => $val ) {
|
||||
|
@ -142,9 +141,9 @@ class WC_Admin_Reports_Data_Store {
|
|||
}
|
||||
// @todo Should 'products' be in intervals?
|
||||
unset( $totals_arr['products'] );
|
||||
while ( $datetime <= $end_datetime ) {
|
||||
$next_start = WC_Admin_Reports_Interval::iterate( $datetime, $time_interval );
|
||||
$time_id = WC_Admin_Reports_Interval::time_interval_id( $time_interval, $datetime );
|
||||
while ( $start_datetime <= $end_datetime ) {
|
||||
$next_start = WC_Admin_Reports_Interval::iterate( $start_datetime, $time_interval );
|
||||
$time_id = WC_Admin_Reports_Interval::time_interval_id( $time_interval, $start_datetime );
|
||||
// Either create fill-zero interval or use data from db.
|
||||
if ( $next_start > $end_datetime ) {
|
||||
$interval_end = $end_datetime->format( 'Y-m-d H:i:s' );
|
||||
|
@ -152,27 +151,53 @@ class WC_Admin_Reports_Data_Store {
|
|||
$prev_end_timestamp = (int) $next_start->format( 'U' ) - 1;
|
||||
$prev_end = new DateTime();
|
||||
$prev_end->setTimestamp( $prev_end_timestamp );
|
||||
$prev_end->setTimezone( $local_tz );
|
||||
$interval_end = $prev_end->format( 'Y-m-d H:i:s' );
|
||||
}
|
||||
if ( array_key_exists( $time_id, $time_ids ) ) {
|
||||
// For interval present in the db for this time frame, just fill in dates.
|
||||
$record = &$data->intervals[ $time_ids[ $time_id ] ];
|
||||
$record['date_start'] = $datetime->format( 'Y-m-d H:i:s' );
|
||||
$record['date_start'] = $start_datetime->format( 'Y-m-d H:i:s' );
|
||||
$record['date_end'] = $interval_end;
|
||||
} elseif ( ! array_key_exists( $time_id, $db_intervals ) ) {
|
||||
// For intervals present in the db outside of this time frame, do nothing.
|
||||
// For intervals not present in the db, fabricate it.
|
||||
$record_arr = array();
|
||||
$record_arr['time_interval'] = $time_id;
|
||||
$record_arr['date_start'] = $datetime->format( 'Y-m-d H:i:s' );
|
||||
$record_arr['date_start'] = $start_datetime->format( 'Y-m-d H:i:s' );
|
||||
$record_arr['date_end'] = $interval_end;
|
||||
$data->intervals[] = array_merge( $record_arr, $totals_arr );
|
||||
}
|
||||
$datetime = $next_start;
|
||||
$start_datetime = $next_start;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts input datetime parameters to local timezone. If there are no inputs from the user in query_args,
|
||||
* uses default from $defaults.
|
||||
*
|
||||
* @param array $query_args Array of query arguments.
|
||||
* @param array $defaults Array of default values.
|
||||
*/
|
||||
protected function normalize_timezones( &$query_args, $defaults ) {
|
||||
$local_tz = new DateTimeZone( wc_timezone_string() );
|
||||
foreach ( array( 'before', 'after' ) as $query_arg_key ) {
|
||||
if ( isset( $query_args[ $query_arg_key ] ) && is_string( $query_args[ $query_arg_key ] ) ) {
|
||||
// Assume that unspecified timezone is a local timezone.
|
||||
$datetime = new DateTime( $query_args[ $query_arg_key ], $local_tz );
|
||||
// In case timezone was forced by using +HH:MM, convert to local timezone.
|
||||
$datetime->setTimezone( $local_tz );
|
||||
$query_args[ $query_arg_key ] = $datetime;
|
||||
} elseif ( isset( $query_args[ $query_arg_key ] ) && is_a( $query_args[ $query_arg_key ], 'DateTime' ) ) {
|
||||
// In case timezone is in other timezone, convert to local timezone.
|
||||
$query_args[ $query_arg_key ]->setTimezone( $local_tz );
|
||||
} else {
|
||||
$query_args[ $query_arg_key ] = isset( $defaults[ $query_arg_key ] ) ? $defaults[ $query_arg_key ] : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes extra records from intervals so that only requested number of records get returned.
|
||||
*
|
||||
|
@ -285,6 +310,7 @@ class WC_Admin_Reports_Data_Store {
|
|||
if ( $db_interval_count === $expected_interval_count ) {
|
||||
return;
|
||||
}
|
||||
$local_tz = new DateTimeZone( wc_timezone_string() );
|
||||
if ( 'date' === strtolower( $query_args['orderby'] ) ) {
|
||||
// page X in request translates to slightly different dates in the db, in case some
|
||||
// records are missing from the db.
|
||||
|
@ -292,9 +318,9 @@ class WC_Admin_Reports_Data_Store {
|
|||
$end_iteration = 0;
|
||||
if ( 'asc' === strtolower( $query_args['order'] ) ) {
|
||||
// ORDER BY date ASC.
|
||||
$new_start_date = new DateTime( $query_args['after'] );
|
||||
$new_start_date = $query_args['after'];
|
||||
$intervals_to_skip = ( $query_args['page'] - 1 ) * $intervals_query['per_page'];
|
||||
$latest_end_date = new DateTime( $query_args['before'] );
|
||||
$latest_end_date = $query_args['before'];
|
||||
for ( $i = 0; $i < $intervals_to_skip; $i++ ) {
|
||||
if ( $new_start_date > $latest_end_date ) {
|
||||
$new_start_date = $latest_end_date;
|
||||
|
@ -323,9 +349,9 @@ class WC_Admin_Reports_Data_Store {
|
|||
}
|
||||
} else {
|
||||
// ORDER BY date DESC.
|
||||
$new_end_date = new DateTime( $query_args['before'] );
|
||||
$new_end_date = $query_args['before'];
|
||||
$intervals_to_skip = ( $query_args['page'] - 1 ) * $intervals_query['per_page'];
|
||||
$earliest_start_date = new DateTime( $query_args['after'] );
|
||||
$earliest_start_date = $query_args['after'];
|
||||
for ( $i = 0; $i < $intervals_to_skip; $i++ ) {
|
||||
if ( $new_end_date < $earliest_start_date ) {
|
||||
$new_end_date = $earliest_start_date;
|
||||
|
@ -354,11 +380,13 @@ class WC_Admin_Reports_Data_Store {
|
|||
$new_start_date->setTimestamp( $new_start_date_timestamp );
|
||||
}
|
||||
}
|
||||
$query_args['adj_after'] = $new_start_date->format( WC_Admin_Reports_Interval::$iso_datetime_format );
|
||||
$query_args['adj_before'] = $new_end_date->format( WC_Admin_Reports_Interval::$iso_datetime_format );
|
||||
$query_args['adj_after'] = $new_start_date;
|
||||
$query_args['adj_before'] = $new_end_date;
|
||||
$adj_after = $new_start_date->format( WC_Admin_Reports_Interval::$sql_datetime_format );
|
||||
$adj_before = $new_end_date->format( WC_Admin_Reports_Interval::$sql_datetime_format );
|
||||
$intervals_query['where_time_clause'] = '';
|
||||
$intervals_query['where_time_clause'] .= " AND {$table_name}.date_created <= '{$query_args['adj_before']}'";
|
||||
$intervals_query['where_time_clause'] .= " AND {$table_name}.date_created >= '{$query_args['adj_after']}'";
|
||||
$intervals_query['where_time_clause'] .= " AND {$table_name}.date_created <= '$adj_before'";
|
||||
$intervals_query['where_time_clause'] .= " AND {$table_name}.date_created >= '$adj_after'";
|
||||
$intervals_query['limit'] = 'LIMIT 0,' . $intervals_query['per_page'];
|
||||
} else {
|
||||
if ( 'asc' === $query_args['order'] ) {
|
||||
|
@ -465,21 +493,21 @@ class WC_Admin_Reports_Data_Store {
|
|||
*
|
||||
* E.g. if there are db records for only Tuesday and Thursday this week, the actual week interval is [Mon, Sun], not [Tue, Thu].
|
||||
*
|
||||
* @param DateTime $datetime_start Start date.
|
||||
* @param DateTime $datetime_end End date.
|
||||
* @param DateTime $start_datetime Start date.
|
||||
* @param DateTime $end_datetime End date.
|
||||
* @param string $time_interval Time interval, e.g. day, week, month.
|
||||
* @param array $intervals Array of intervals extracted from SQL db.
|
||||
*/
|
||||
protected function update_interval_boundary_dates( $datetime_start, $datetime_end, $time_interval, &$intervals ) {
|
||||
protected function update_interval_boundary_dates( $start_datetime, $end_datetime, $time_interval, &$intervals ) {
|
||||
$local_tz = new DateTimeZone( wc_timezone_string() );
|
||||
foreach ( $intervals as $key => $interval ) {
|
||||
$datetime = new DateTime( $interval['datetime_anchor'] );
|
||||
$datetime = new DateTime( $interval['datetime_anchor'], $local_tz );
|
||||
|
||||
$prev_start = WC_Admin_Reports_Interval::iterate( $datetime, $time_interval, true );
|
||||
// @todo Not sure if the +1/-1 here are correct, especially as they are applied before the ?: below.
|
||||
$prev_start_timestamp = (int) $prev_start->format( 'U' ) + 1;
|
||||
$prev_start->setTimestamp( $prev_start_timestamp );
|
||||
if ( $datetime_start ) {
|
||||
$start_datetime = new DateTime( $datetime_start );
|
||||
if ( $start_datetime ) {
|
||||
$date_start = $prev_start < $start_datetime ? $start_datetime : $prev_start;
|
||||
$intervals[ $key ]['date_start'] = $date_start->format( 'Y-m-d H:i:s' );
|
||||
} else {
|
||||
|
@ -489,8 +517,7 @@ class WC_Admin_Reports_Data_Store {
|
|||
$next_end = WC_Admin_Reports_Interval::iterate( $datetime, $time_interval );
|
||||
$next_end_timestamp = (int) $next_end->format( 'U' ) - 1;
|
||||
$next_end->setTimestamp( $next_end_timestamp );
|
||||
if ( $datetime_end ) {
|
||||
$end_datetime = new DateTime( $datetime_end );
|
||||
if ( $end_datetime ) {
|
||||
$date_end = $next_end > $end_datetime ? $end_datetime : $next_end;
|
||||
$intervals[ $key ]['date_end'] = $date_end->format( 'Y-m-d H:i:s' );
|
||||
} else {
|
||||
|
@ -504,17 +531,21 @@ class WC_Admin_Reports_Data_Store {
|
|||
/**
|
||||
* Change structure of intervals to form a correct response.
|
||||
*
|
||||
* Also converts local datetimes to GMT and adds them to the intervals.
|
||||
*
|
||||
* @param array $intervals Time interval, e.g. day, week, month.
|
||||
*/
|
||||
protected function create_interval_subtotals( &$intervals ) {
|
||||
foreach ( $intervals as $key => $interval ) {
|
||||
$start_gmt = WC_Admin_Reports_Interval::convert_local_datetime_to_gmt( $interval['date_start'] );
|
||||
$end_gmt = WC_Admin_Reports_Interval::convert_local_datetime_to_gmt( $interval['date_end'] );
|
||||
// Move intervals result to subtotals object.
|
||||
$intervals[ $key ] = array(
|
||||
'interval' => $interval['time_interval'],
|
||||
'date_start' => $interval['date_start'],
|
||||
'date_start_gmt' => $interval['date_start'],
|
||||
'date_start_gmt' => $start_gmt->format( WC_Admin_Reports_Interval::$sql_datetime_format ),
|
||||
'date_end' => $interval['date_end'],
|
||||
'date_end_gmt' => $interval['date_end'],
|
||||
'date_end_gmt' => $end_gmt->format( WC_Admin_Reports_Interval::$sql_datetime_format ),
|
||||
);
|
||||
|
||||
unset( $interval['interval'] );
|
||||
|
@ -541,15 +572,13 @@ class WC_Admin_Reports_Data_Store {
|
|||
);
|
||||
|
||||
if ( isset( $query_args['before'] ) && '' !== $query_args['before'] ) {
|
||||
$datetime = new DateTime( $query_args['before'] );
|
||||
$datetime_str = $datetime->format( WC_Admin_Reports_Interval::$sql_datetime_format );
|
||||
$datetime_str = $query_args['before']->format( WC_Admin_Reports_Interval::$sql_datetime_format );
|
||||
$sql_query['where_time_clause'] .= " AND {$table_name}.date_created <= '$datetime_str'";
|
||||
|
||||
}
|
||||
|
||||
if ( isset( $query_args['after'] ) && '' !== $query_args['after'] ) {
|
||||
$datetime = new DateTime( $query_args['after'] );
|
||||
$datetime_str = $datetime->format( WC_Admin_Reports_Interval::$sql_datetime_format );
|
||||
$datetime_str = $query_args['after']->format( WC_Admin_Reports_Interval::$sql_datetime_format );
|
||||
$sql_query['where_time_clause'] .= " AND {$table_name}.date_created >= '$datetime_str'";
|
||||
}
|
||||
|
||||
|
|
|
@ -122,27 +122,32 @@ class WC_Admin_Reports_Downloads_Data_Store extends WC_Admin_Reports_Data_Store
|
|||
)";
|
||||
}
|
||||
|
||||
$included_users = $this->get_included_users( $query_args );
|
||||
$excluded_users = $this->get_excluded_users( $query_args );
|
||||
if ( $included_users ) {
|
||||
$customer_lookup_table = $wpdb->prefix . 'wc_customer_lookup';
|
||||
$included_customers = $this->get_included_customers( $query_args );
|
||||
$excluded_customers = $this->get_excluded_customers( $query_args );
|
||||
if ( $included_customers ) {
|
||||
$where_filters[] = " {$lookup_table}.permission_id IN (
|
||||
SELECT
|
||||
DISTINCT {$wpdb->prefix}woocommerce_downloadable_product_permissions.permission_id
|
||||
FROM
|
||||
{$wpdb->prefix}woocommerce_downloadable_product_permissions
|
||||
WHERE
|
||||
{$wpdb->prefix}woocommerce_downloadable_product_permissions.user_id IN ({$included_users})
|
||||
{$wpdb->prefix}woocommerce_downloadable_product_permissions.user_id IN (
|
||||
SELECT {$customer_lookup_table}.user_id FROM {$customer_lookup_table} WHERE {$customer_lookup_table}.customer_id IN ({$included_customers})
|
||||
)
|
||||
)";
|
||||
}
|
||||
|
||||
if ( $excluded_users ) {
|
||||
if ( $excluded_customers ) {
|
||||
$where_filters[] = " {$lookup_table}.permission_id NOT IN (
|
||||
SELECT
|
||||
DISTINCT {$wpdb->prefix}woocommerce_downloadable_product_permissions.permission_id
|
||||
FROM
|
||||
{$wpdb->prefix}woocommerce_downloadable_product_permissions
|
||||
WHERE
|
||||
{$wpdb->prefix}woocommerce_downloadable_product_permissions.user_id IN ({$excluded_users})
|
||||
{$wpdb->prefix}woocommerce_downloadable_product_permissions.user_id IN (
|
||||
SELECT {$customer_lookup_table}.user_id FROM {$customer_lookup_table} WHERE {$customer_lookup_table}.customer_id IN ({$excluded_customers})
|
||||
)
|
||||
)";
|
||||
}
|
||||
|
||||
|
@ -205,6 +210,35 @@ class WC_Admin_Reports_Downloads_Data_Store extends WC_Admin_Reports_Data_Store
|
|||
return $excluded_ips_str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns comma separated ids of included customers, based on query arguments from the user.
|
||||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @return string
|
||||
*/
|
||||
protected function get_included_customers( $query_args ) {
|
||||
$included_customers_str = '';
|
||||
|
||||
if ( isset( $query_args['customer_includes'] ) && is_array( $query_args['customer_includes'] ) && count( $query_args['customer_includes'] ) > 0 ) {
|
||||
$included_customers_str = implode( ',', $query_args['customer_includes'] );
|
||||
}
|
||||
return $included_customers_str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns comma separated ids of excluded customers, based on query arguments from the user.
|
||||
*
|
||||
* @param array $query_args Parameters supplied by the user.
|
||||
* @return string
|
||||
*/
|
||||
protected function get_excluded_customers( $query_args ) {
|
||||
$excluded_customer_str = '';
|
||||
|
||||
if ( isset( $query_args['customer_excludes'] ) && is_array( $query_args['customer_excludes'] ) && count( $query_args['customer_excludes'] ) > 0 ) {
|
||||
$excluded_customer_str = implode( ',', $query_args['customer_excludes'] );
|
||||
}
|
||||
return $excluded_customer_str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills WHERE clause of SQL request with date-related constraints.
|
||||
|
@ -220,16 +254,14 @@ class WC_Admin_Reports_Downloads_Data_Store extends WC_Admin_Reports_Data_Store
|
|||
'where_clause' => '',
|
||||
);
|
||||
|
||||
if ( isset( $query_args['before'] ) && '' !== $query_args['before'] ) {
|
||||
$datetime = new DateTime( $query_args['before'] );
|
||||
$datetime_str = $datetime->format( WC_Admin_Reports_Interval::$sql_datetime_format );
|
||||
if ( $query_args['before'] ) {
|
||||
$datetime_str = $query_args['before']->format( WC_Admin_Reports_Interval::$sql_datetime_format );
|
||||
$sql_query['where_time_clause'] .= " AND {$table_name}.timestamp <= '$datetime_str'";
|
||||
|
||||
}
|
||||
|
||||
if ( isset( $query_args['after'] ) && '' !== $query_args['after'] ) {
|
||||
$datetime = new DateTime( $query_args['after'] );
|
||||
$datetime_str = $datetime->format( WC_Admin_Reports_Interval::$sql_datetime_format );
|
||||
if ( $query_args['after'] ) {
|
||||
$datetime_str = $query_args['after']->format( WC_Admin_Reports_Interval::$sql_datetime_format );
|
||||
$sql_query['where_time_clause'] .= " AND {$table_name}.timestamp >= '$datetime_str'";
|
||||
}
|
||||
|
||||
|
@ -273,8 +305,6 @@ class WC_Admin_Reports_Downloads_Data_Store extends WC_Admin_Reports_Data_Store
|
|||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
$now = time();
|
||||
$week_back = $now - WEEK_IN_SECONDS;
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
|
@ -282,11 +312,12 @@ class WC_Admin_Reports_Downloads_Data_Store extends WC_Admin_Reports_Data_Store
|
|||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'timestamp',
|
||||
'before' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $now ),
|
||||
'after' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ),
|
||||
'before' => WC_Admin_Reports_Interval::default_before(),
|
||||
'after' => WC_Admin_Reports_Interval::default_after(),
|
||||
'fields' => '*',
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = wp_cache_get( $cache_key, $this->cache_group );
|
||||
|
|
|
@ -48,8 +48,6 @@ class WC_Admin_Reports_Downloads_Stats_Data_Store extends WC_Admin_Reports_Downl
|
|||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
$now = time();
|
||||
$week_back = $now - WEEK_IN_SECONDS;
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
|
@ -59,15 +57,11 @@ class WC_Admin_Reports_Downloads_Stats_Data_Store extends WC_Admin_Reports_Downl
|
|||
'orderby' => 'date',
|
||||
'fields' => '*',
|
||||
'interval' => 'week',
|
||||
'before' => WC_Admin_Reports_Interval::default_before(),
|
||||
'after' => WC_Admin_Reports_Interval::default_after(),
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
|
||||
if ( empty( $query_args['before'] ) ) {
|
||||
$query_args['before'] = date( WC_Admin_Reports_Interval::$iso_datetime_format, $now );
|
||||
}
|
||||
if ( empty( $query_args['after'] ) ) {
|
||||
$query_args['after'] = date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back );
|
||||
}
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = wp_cache_get( $cache_key, $this->cache_group );
|
||||
|
@ -190,43 +184,17 @@ class WC_Admin_Reports_Downloads_Stats_Data_Store extends WC_Admin_Reports_Downl
|
|||
}
|
||||
|
||||
/**
|
||||
* Sorts intervals according to user's request.
|
||||
* Normalizes order_by clause to match to SQL query.
|
||||
*
|
||||
* They are pre-sorted in SQL, but after adding gaps, they need to be sorted including the added ones.
|
||||
*
|
||||
* @param stdClass $data Data object, must contain an array under $data->intervals.
|
||||
* @param string $sort_by Ordering property.
|
||||
* @param string $direction DESC/ASC.
|
||||
*/
|
||||
protected function sort_intervals( &$data, $sort_by, $direction ) {
|
||||
if ( 'date' === $sort_by ) {
|
||||
$this->order_by = 'time_interval';
|
||||
} else {
|
||||
$this->order_by = $sort_by;
|
||||
}
|
||||
|
||||
$this->order = $direction;
|
||||
usort( $data->intervals, array( $this, 'interval_cmp' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two report data objects by pre-defined object property and ASC/DESC ordering.
|
||||
*
|
||||
* @param stdClass $a Object a.
|
||||
* @param stdClass $b Object b.
|
||||
* @param string $order_by Order by option requeste by user.
|
||||
* @return string
|
||||
*/
|
||||
protected function interval_cmp( $a, $b ) {
|
||||
if ( '' === $this->order_by || '' === $this->order ) {
|
||||
return 0;
|
||||
}
|
||||
if ( $a[ $this->order_by ] === $b[ $this->order_by ] ) {
|
||||
return 0;
|
||||
} elseif ( $a[ $this->order_by ] > $b[ $this->order_by ] ) {
|
||||
return strtolower( $this->order ) === 'desc' ? -1 : 1;
|
||||
} elseif ( $a[ $this->order_by ] < $b[ $this->order_by ] ) {
|
||||
return strtolower( $this->order ) === 'desc' ? 1 : -1;
|
||||
protected function normalize_order_by( $order_by ) {
|
||||
if ( 'date' === $order_by ) {
|
||||
return 'time_interval';
|
||||
}
|
||||
|
||||
return $order_by;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,6 +30,8 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
|||
'status' => 'strval',
|
||||
'customer_id' => 'intval',
|
||||
'net_total' => 'floatval',
|
||||
'gross_total' => 'floatval',
|
||||
'refund_total' => 'floatval',
|
||||
'num_items_sold' => 'intval',
|
||||
'customer_type' => 'strval',
|
||||
);
|
||||
|
@ -45,6 +47,8 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
|||
'status' => 'REPLACE(status, "wc-", "") as status',
|
||||
'customer_id' => 'customer_id',
|
||||
'net_total' => 'net_total',
|
||||
'gross_total' => 'gross_total',
|
||||
'refund_total' => 'refund_total',
|
||||
'num_items_sold' => 'num_items_sold',
|
||||
'customer_type' => '(CASE WHEN returning_customer <> 0 THEN "returning" ELSE "new" END) as customer_type',
|
||||
);
|
||||
|
@ -124,8 +128,6 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
|||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
$now = time();
|
||||
$week_back = $now - WEEK_IN_SECONDS;
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
|
@ -133,8 +135,8 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
|||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'date_created',
|
||||
'before' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $now ),
|
||||
'after' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ),
|
||||
'before' => WC_Admin_Reports_Interval::default_before(),
|
||||
'after' => WC_Admin_Reports_Interval::default_after(),
|
||||
'fields' => '*',
|
||||
'product_includes' => array(),
|
||||
'product_excludes' => array(),
|
||||
|
@ -145,6 +147,7 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
|||
'extended_info' => false,
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = wp_cache_get( $cache_key, $this->cache_group );
|
||||
|
@ -228,6 +231,8 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
|||
if ( 'date' === $order_by ) {
|
||||
return 'date_created';
|
||||
}
|
||||
|
||||
return $order_by;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -242,6 +247,8 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
|||
$mapped_products = $this->map_array_by_key( $products, 'product_id' );
|
||||
$coupons = $this->get_coupons_by_order_ids( array_keys( $mapped_orders ) );
|
||||
$product_categories = $this->get_product_categories_by_product_ids( array_keys( $mapped_products ) );
|
||||
$customers = $this->get_customers_by_orders( $orders_data );
|
||||
$mapped_customers = $this->map_array_by_key( $customers, 'customer_id' );
|
||||
|
||||
$mapped_data = array();
|
||||
foreach ( $products as $product ) {
|
||||
|
@ -279,8 +286,12 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
|||
'products' => array(),
|
||||
'categories' => array(),
|
||||
'coupons' => array(),
|
||||
'customer' => array(),
|
||||
);
|
||||
$orders_data[ $key ]['extended_info'] = isset( $mapped_data[ $order_data['order_id'] ] ) ? array_merge( $defaults, $mapped_data[ $order_data['order_id'] ] ) : $defaults;
|
||||
if ( $order_data['customer_id'] && isset( $mapped_customers[ $order_data['customer_id'] ] ) ) {
|
||||
$orders_data[ $key ]['extended_info']['customer'] = $mapped_customers[ $order_data['customer_id'] ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -323,6 +334,32 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
|||
return $products;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get customer data from order IDs.
|
||||
*
|
||||
* @param array $orders Array of orders.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_customers_by_orders( $orders ) {
|
||||
global $wpdb;
|
||||
$customer_lookup_table = $wpdb->prefix . 'wc_customer_lookup';
|
||||
|
||||
$customer_ids = array();
|
||||
foreach ( $orders as $order ) {
|
||||
if ( $order['customer_id'] ) {
|
||||
$customer_ids[] = $order['customer_id'];
|
||||
}
|
||||
}
|
||||
$customer_ids = implode( ',', $customer_ids );
|
||||
|
||||
$customers = $wpdb->get_results(
|
||||
"SELECT * FROM {$customer_lookup_table} WHERE customer_id IN ({$customer_ids})",
|
||||
ARRAY_A
|
||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||
|
||||
return $customers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get coupon information from order IDs.
|
||||
*
|
||||
|
|
|
@ -180,8 +180,6 @@ class WC_Admin_Reports_Orders_Stats_Data_Store extends WC_Admin_Reports_Data_Sto
|
|||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
$now = time();
|
||||
$week_back = $now - WEEK_IN_SECONDS;
|
||||
|
||||
// These defaults are only applied when not using REST API, as the API has its own defaults that overwrite these for most values (except before, after, etc).
|
||||
$defaults = array(
|
||||
|
@ -189,8 +187,8 @@ class WC_Admin_Reports_Orders_Stats_Data_Store extends WC_Admin_Reports_Data_Sto
|
|||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'date',
|
||||
'before' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $now ),
|
||||
'after' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ),
|
||||
'before' => WC_Admin_Reports_Interval::default_before(),
|
||||
'after' => WC_Admin_Reports_Interval::default_after(),
|
||||
'interval' => 'week',
|
||||
'fields' => '*',
|
||||
'segmentby' => '',
|
||||
|
@ -206,6 +204,7 @@ class WC_Admin_Reports_Orders_Stats_Data_Store extends WC_Admin_Reports_Data_Sto
|
|||
'categories' => array(),
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = wp_cache_get( $cache_key, $this->cache_group );
|
||||
|
@ -402,9 +401,10 @@ class WC_Admin_Reports_Orders_Stats_Data_Store extends WC_Admin_Reports_Data_Sto
|
|||
'refund_total' => $order->get_total_refunded(),
|
||||
'tax_total' => $order->get_total_tax(),
|
||||
'shipping_total' => $order->get_shipping_total(),
|
||||
'net_total' => (float) $order->get_total() - (float) $order->get_total_tax() - (float) $order->get_shipping_total(),
|
||||
'net_total' => self::get_net_total( $order ),
|
||||
'returning_customer' => self::is_returning_customer( $order ),
|
||||
'status' => self::normalize_order_status( $order->get_status() ),
|
||||
'customer_id' => WC_Admin_Reports_Customers_Data_Store::get_or_create_customer_from_order( $order ),
|
||||
);
|
||||
$format = array(
|
||||
'%d',
|
||||
|
@ -418,32 +418,9 @@ class WC_Admin_Reports_Orders_Stats_Data_Store extends WC_Admin_Reports_Data_Sto
|
|||
'%f',
|
||||
'%d',
|
||||
'%s',
|
||||
'%d',
|
||||
);
|
||||
|
||||
// Ensure we're associating this order with a Customer in the lookup table.
|
||||
$order_user_id = $order->get_customer_id();
|
||||
$customers_data_store = new WC_Admin_Reports_Customers_Data_Store();
|
||||
|
||||
if ( 0 === $order_user_id ) {
|
||||
$email = $order->get_billing_email( 'edit' );
|
||||
|
||||
if ( $email ) {
|
||||
$customer_id = $customers_data_store->get_or_create_guest_customer_from_order( $order );
|
||||
|
||||
if ( $customer_id ) {
|
||||
$data['customer_id'] = $customer_id;
|
||||
$format[] = '%d';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$customer = $customers_data_store->get_customer_by_user_id( $order_user_id );
|
||||
|
||||
if ( $customer && $customer['customer_id'] ) {
|
||||
$data['customer_id'] = $customer['customer_id'];
|
||||
$format[] = '%d';
|
||||
}
|
||||
}
|
||||
|
||||
// Update or add the information to the DB.
|
||||
$result = $wpdb->replace( $table_name, $data, $format );
|
||||
|
||||
|
@ -508,6 +485,23 @@ class WC_Admin_Reports_Orders_Stats_Data_Store extends WC_Admin_Reports_Data_Sto
|
|||
return $num_items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the net amount from an order without shipping, tax, or refunds.
|
||||
*
|
||||
* @param array $order WC_Order object.
|
||||
* @return float
|
||||
*/
|
||||
protected static function get_net_total( $order ) {
|
||||
$net_total = $order->get_total() - $order->get_total_tax() - $order->get_shipping_total();
|
||||
|
||||
$refunds = $order->get_refunds();
|
||||
foreach ( $refunds as $refund ) {
|
||||
$net_total += $refund->get_total() - $refund->get_total_tax() - $refund->get_shipping_total();
|
||||
}
|
||||
|
||||
return $net_total > 0 ? (float) $net_total : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if an order's customer has made previous orders or not
|
||||
*
|
||||
|
@ -515,24 +509,64 @@ class WC_Admin_Reports_Orders_Stats_Data_Store extends WC_Admin_Reports_Data_Sto
|
|||
* @return bool
|
||||
*/
|
||||
protected static function is_returning_customer( $order ) {
|
||||
global $wpdb;
|
||||
$customer_id = WC_Admin_Reports_Customers_Data_Store::get_customer_id_by_user_id( $order->get_user_id() );
|
||||
$orders_stats_table = $wpdb->prefix . self::TABLE_NAME;
|
||||
|
||||
if ( ! $customer_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$customer_orders = $wpdb->get_var(
|
||||
$oldest_orders = WC_Admin_Reports_Customers_Data_Store::get_oldest_orders( $customer_id );
|
||||
|
||||
if ( empty( $oldest_orders ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$first_order = $oldest_orders[0];
|
||||
$second_order = isset( $oldest_orders[1] ) ? $oldest_orders[1] : false;
|
||||
$excluded_statuses = self::get_excluded_report_order_statuses();
|
||||
|
||||
// Order is older than previous first order.
|
||||
if ( $order->get_date_created() < new WC_DateTime( $first_order->date_created ) &&
|
||||
! in_array( $order->get_status(), $excluded_statuses, true )
|
||||
) {
|
||||
self::set_customer_first_order( $customer_id, $order->get_id() );
|
||||
return false;
|
||||
}
|
||||
|
||||
// The current order is the oldest known order.
|
||||
$is_first_order = (int) $order->get_id() === (int) $first_order->order_id;
|
||||
// Order date has changed and next oldest is now the first order.
|
||||
$date_change = $second_order &&
|
||||
$order->get_date_created() > new WC_DateTime( $first_order->date_created ) &&
|
||||
new WC_DateTime( $second_order->date_created ) < $order->get_date_created();
|
||||
// Status has changed to an excluded status and next oldest order is now the first order.
|
||||
$status_change = $second_order &&
|
||||
in_array( $order->get_status(), $excluded_statuses, true );
|
||||
if ( $is_first_order && ( $date_change || $status_change ) ) {
|
||||
self::set_customer_first_order( $customer_id, $second_order->order_id );
|
||||
return true;
|
||||
}
|
||||
|
||||
return (int) $order->get_id() !== (int) $first_order->order_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a customer's first order and all others to returning.
|
||||
*
|
||||
* @param int $customer_id Customer ID.
|
||||
* @param int $order_id Order ID.
|
||||
*/
|
||||
protected static function set_customer_first_order( $customer_id, $order_id ) {
|
||||
global $wpdb;
|
||||
$orders_stats_table = $wpdb->prefix . self::TABLE_NAME;
|
||||
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM ${orders_stats_table} WHERE customer_id = %d AND date_created < %s AND order_id != %d",
|
||||
$customer_id,
|
||||
date( 'Y-m-d H:i:s', $order->get_date_created()->getTimestamp() ),
|
||||
$order->get_id()
|
||||
"UPDATE ${orders_stats_table} SET returning_customer = CASE WHEN order_id = %d THEN false ELSE true END WHERE customer_id = %d",
|
||||
$order_id,
|
||||
$customer_id
|
||||
)
|
||||
);
|
||||
|
||||
return $customer_orders >= 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -229,8 +229,6 @@ class WC_Admin_Reports_Products_Data_Store extends WC_Admin_Reports_Data_Store i
|
|||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
$now = time();
|
||||
$week_back = $now - WEEK_IN_SECONDS;
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
|
@ -238,14 +236,15 @@ class WC_Admin_Reports_Products_Data_Store extends WC_Admin_Reports_Data_Store i
|
|||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'date',
|
||||
'before' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $now ),
|
||||
'after' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ),
|
||||
'before' => WC_Admin_Reports_Interval::default_before(),
|
||||
'after' => WC_Admin_Reports_Interval::default_after(),
|
||||
'fields' => '*',
|
||||
'categories' => array(),
|
||||
'product_includes' => array(),
|
||||
'extended_info' => false,
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = wp_cache_get( $cache_key, $this->cache_group );
|
||||
|
@ -402,7 +401,7 @@ class WC_Admin_Reports_Products_Data_Store extends WC_Admin_Reports_Data_Store i
|
|||
'customer_id' => ( 0 < $order->get_customer_id( 'edit' ) ) ? $order->get_customer_id( 'edit' ) : null,
|
||||
'product_qty' => $product_qty,
|
||||
'product_net_revenue' => $net_revenue,
|
||||
'date_created' => date( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ),
|
||||
'date_created' => $order->get_date_created( 'edit' )->date( WC_Admin_Reports_Interval::$sql_datetime_format ),
|
||||
'coupon_amount' => $coupon_amount,
|
||||
'tax_amount' => $tax_amount,
|
||||
'shipping_amount' => $shipping_amount,
|
||||
|
|
|
@ -103,8 +103,6 @@ class WC_Admin_Reports_Products_Stats_Data_Store extends WC_Admin_Reports_Produc
|
|||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
$now = time();
|
||||
$week_back = $now - WEEK_IN_SECONDS;
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
|
@ -112,14 +110,15 @@ class WC_Admin_Reports_Products_Stats_Data_Store extends WC_Admin_Reports_Produc
|
|||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'date',
|
||||
'before' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $now ),
|
||||
'after' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ),
|
||||
'before' => WC_Admin_Reports_Interval::default_before(),
|
||||
'after' => WC_Admin_Reports_Interval::default_after(),
|
||||
'fields' => '*',
|
||||
'categories' => array(),
|
||||
'interval' => 'week',
|
||||
'product_includes' => array(),
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$product_data = wp_cache_get( $cache_key, $this->cache_group );
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
<?php
|
||||
/**
|
||||
* WC_Admin_Reports_Stock_Stats_Data_Store class file.
|
||||
*
|
||||
* @package WooCommerce Admin/Classes
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* WC_Reports_Stock_Stats_Data_Store.
|
||||
*/
|
||||
class WC_Admin_Reports_Stock_Stats_Data_Store extends WC_Admin_Reports_Data_Store implements WC_Admin_Reports_Data_Store_Interface {
|
||||
|
||||
/**
|
||||
* Get stock counts for the whole store.
|
||||
*
|
||||
* @param array $query Not used for the stock stats data store, but needed for the interface.
|
||||
* @return array Array of counts.
|
||||
*/
|
||||
public function get_data( $query ) {
|
||||
$report_data = array();
|
||||
$cache_expire = DAY_IN_SECONDS * 30;
|
||||
$low_stock_transient_name = 'wc_admin_stock_count_lowstock';
|
||||
$low_stock_count = get_transient( $low_stock_transient_name );
|
||||
if ( false === $low_stock_count ) {
|
||||
$low_stock_count = $this->get_low_stock_count();
|
||||
set_transient( $low_stock_transient_name, $low_stock_count, $cache_expire );
|
||||
}
|
||||
$report_data['lowstock'] = $low_stock_count;
|
||||
|
||||
$status_options = wc_get_product_stock_status_options();
|
||||
foreach ( $status_options as $status => $label ) {
|
||||
$transient_name = 'wc_admin_stock_count_' . $status;
|
||||
$count = get_transient( $transient_name );
|
||||
if ( false === $count ) {
|
||||
$count = $this->get_count( $status );
|
||||
set_transient( $transient_name, $count, $cache_expire );
|
||||
}
|
||||
$report_data[ $status ] = $count;
|
||||
}
|
||||
|
||||
$product_count_transient_name = 'wc_admin_product_count';
|
||||
$product_count = get_transient( $product_count_transient_name );
|
||||
if ( false === $product_count ) {
|
||||
$product_count = $this->get_product_count();
|
||||
set_transient( $product_count_transient_name, $product_count, $cache_expire );
|
||||
}
|
||||
$report_data['products'] = $product_count;
|
||||
return $report_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get low stock count.
|
||||
*
|
||||
* @return int Low stock count.
|
||||
*/
|
||||
private function get_low_stock_count() {
|
||||
$query_args = array();
|
||||
$query_args['post_type'] = array( 'product', 'product_variation' );
|
||||
$low_stock = absint( max( get_option( 'woocommerce_notify_low_stock_amount' ), 1 ) );
|
||||
$no_stock = absint( max( get_option( 'woocommerce_notify_no_stock_amount' ), 0 ) );
|
||||
$query_args['meta_query'] = array( // WPCS: slow query ok.
|
||||
array(
|
||||
'key' => '_manage_stock',
|
||||
'value' => 'yes',
|
||||
),
|
||||
array(
|
||||
'key' => '_stock',
|
||||
'value' => array( $no_stock, $low_stock ),
|
||||
'compare' => 'BETWEEN',
|
||||
'type' => 'NUMERIC',
|
||||
),
|
||||
array(
|
||||
'key' => '_stock_status',
|
||||
'value' => 'instock',
|
||||
),
|
||||
);
|
||||
|
||||
$query = new WP_Query();
|
||||
$query->query( $query_args );
|
||||
return intval( $query->found_posts );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count for the passed in stock status.
|
||||
*
|
||||
* @param string $status Status slug.
|
||||
* @return int Count.
|
||||
*/
|
||||
private function get_count( $status ) {
|
||||
$query_args = array();
|
||||
$query_args['post_type'] = array( 'product', 'product_variation' );
|
||||
$query_args['meta_query'] = array( // WPCS: slow query ok.
|
||||
array(
|
||||
'key' => '_stock_status',
|
||||
'value' => $status,
|
||||
),
|
||||
);
|
||||
|
||||
$query = new WP_Query();
|
||||
$query->query( $query_args );
|
||||
return intval( $query->found_posts );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get product count for the store.
|
||||
*
|
||||
* @return int Product count.
|
||||
*/
|
||||
private function get_product_count() {
|
||||
$query_args = array();
|
||||
$query_args['post_type'] = array( 'product', 'product_variation' );
|
||||
$query = new WP_Query();
|
||||
$query->query( $query_args );
|
||||
return intval( $query->found_posts );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the count cache when products are added or updated, or when
|
||||
* the no/low stock options are changed.
|
||||
*
|
||||
* @param int $id Post/product ID.
|
||||
*/
|
||||
function wc_admin_clear_stock_count_cache( $id ) {
|
||||
delete_transient( 'wc_admin_stock_count_lowstock' );
|
||||
delete_transient( 'wc_admin_product_count' );
|
||||
$status_options = wc_get_product_stock_status_options();
|
||||
foreach ( $status_options as $status => $label ) {
|
||||
delete_transient( 'wc_admin_stock_count_' . $status );
|
||||
}
|
||||
}
|
||||
|
||||
add_action( 'woocommerce_update_product', 'wc_admin_clear_stock_count_cache' );
|
||||
add_action( 'woocommerce_new_product', 'wc_admin_clear_stock_count_cache' );
|
||||
add_action( 'update_option_woocommerce_notify_low_stock_amount', 'wc_admin_clear_stock_count_cache' );
|
||||
add_action( 'update_option_woocommerce_notify_no_stock_amount', 'wc_admin_clear_stock_count_cache' );
|
|
@ -71,6 +71,7 @@ class WC_Admin_Reports_Taxes_Data_Store extends WC_Admin_Reports_Data_Store impl
|
|||
*/
|
||||
public static function init() {
|
||||
add_action( 'woocommerce_reports_delete_order_stats', array( __CLASS__, 'sync_on_order_delete' ), 15 );
|
||||
add_action( 'woocommerce_refund_deleted', array( __CLASS__, 'sync_on_refund_delete' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -135,8 +136,6 @@ class WC_Admin_Reports_Taxes_Data_Store extends WC_Admin_Reports_Data_Store impl
|
|||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
$now = time();
|
||||
$week_back = $now - WEEK_IN_SECONDS;
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
|
@ -144,12 +143,13 @@ class WC_Admin_Reports_Taxes_Data_Store extends WC_Admin_Reports_Data_Store impl
|
|||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'tax_rate_id',
|
||||
'before' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $now ),
|
||||
'after' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ),
|
||||
'before' => WC_Admin_Reports_Interval::default_before(),
|
||||
'after' => WC_Admin_Reports_Interval::default_after(),
|
||||
'fields' => '*',
|
||||
'taxes' => array(),
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = wp_cache_get( $cache_key, $this->cache_group );
|
||||
|
@ -268,15 +268,24 @@ class WC_Admin_Reports_Taxes_Data_Store extends WC_Admin_Reports_Data_Store impl
|
|||
$num_updated = 0;
|
||||
|
||||
foreach ( $tax_items as $tax_item ) {
|
||||
$item_refunds = 0;
|
||||
$shipping_refunds = 0;
|
||||
foreach ( $order->get_items() as $item_id => $item ) {
|
||||
$item_refunds += $order->get_tax_refunded_for_item( $item_id, $tax_item->get_rate_id() );
|
||||
}
|
||||
foreach ( $order->get_items( 'shipping' ) as $item_id => $item ) {
|
||||
$shipping_refunds += $order->get_tax_refunded_for_item( $item_id, $tax_item->get_rate_id(), 'shipping' );
|
||||
}
|
||||
|
||||
$result = $wpdb->replace(
|
||||
$wpdb->prefix . self::TABLE_NAME,
|
||||
array(
|
||||
'order_id' => $order->get_id(),
|
||||
'date_created' => date( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ),
|
||||
'date_created' => $order->get_date_created( 'edit' )->date( WC_Admin_Reports_Interval::$sql_datetime_format ),
|
||||
'tax_rate_id' => $tax_item->get_rate_id(),
|
||||
'shipping_tax' => $tax_item->get_shipping_tax_total(),
|
||||
'order_tax' => $tax_item->get_tax_total(),
|
||||
'total_tax' => $tax_item->get_tax_total() + $tax_item->get_shipping_tax_total(),
|
||||
'shipping_tax' => $tax_item->get_shipping_tax_total() - $shipping_refunds,
|
||||
'order_tax' => $tax_item->get_tax_total() - $item_refunds,
|
||||
'total_tax' => $tax_item->get_tax_total() + $tax_item->get_shipping_tax_total() - $item_refunds - $shipping_refunds,
|
||||
),
|
||||
array(
|
||||
'%d',
|
||||
|
@ -327,4 +336,14 @@ class WC_Admin_Reports_Taxes_Data_Store extends WC_Admin_Reports_Data_Store impl
|
|||
*/
|
||||
do_action( 'woocommerce_reports_delete_tax', 0, $order_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs tax information when a refund is deleted.
|
||||
*
|
||||
* @param int $refund_id Refund ID.
|
||||
* @param int $order_id Order ID.
|
||||
*/
|
||||
public static function sync_on_refund_delete( $refund_id, $order_id ) {
|
||||
self::sync_order_taxes( $order_id );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,8 +112,6 @@ class WC_Admin_Reports_Taxes_Stats_Data_Store extends WC_Admin_Reports_Data_Stor
|
|||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
$now = time();
|
||||
$week_back = $now - WEEK_IN_SECONDS;
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
|
@ -121,12 +119,13 @@ class WC_Admin_Reports_Taxes_Stats_Data_Store extends WC_Admin_Reports_Data_Stor
|
|||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'tax_rate_id',
|
||||
'before' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $now ),
|
||||
'after' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ),
|
||||
'before' => WC_Admin_Reports_Interval::default_before(),
|
||||
'after' => WC_Admin_Reports_Interval::default_after(),
|
||||
'fields' => '*',
|
||||
'taxes' => array(),
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = wp_cache_get( $cache_key, $this->cache_group );
|
||||
|
|
|
@ -188,8 +188,6 @@ class WC_Admin_Reports_Variations_Data_Store extends WC_Admin_Reports_Data_Store
|
|||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||
$now = time();
|
||||
$week_back = $now - WEEK_IN_SECONDS;
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
|
@ -197,14 +195,15 @@ class WC_Admin_Reports_Variations_Data_Store extends WC_Admin_Reports_Data_Store
|
|||
'page' => 1,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'date',
|
||||
'before' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $now ),
|
||||
'after' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ),
|
||||
'before' => WC_Admin_Reports_Interval::default_before(),
|
||||
'after' => WC_Admin_Reports_Interval::default_after(),
|
||||
'fields' => '*',
|
||||
'products' => array(),
|
||||
'variations' => array(),
|
||||
'extended_info' => false,
|
||||
);
|
||||
$query_args = wp_parse_args( $query_args, $defaults );
|
||||
$this->normalize_timezones( $query_args, $defaults );
|
||||
|
||||
$cache_key = $this->get_cache_key( $query_args );
|
||||
$data = wp_cache_get( $cache_key, $this->cache_group );
|
||||
|
|
|
@ -21,6 +21,10 @@ function wc_admin_is_admin_page() {
|
|||
* `wc_get_screen_ids` will also return IDs for extensions that have properly registered themselves.
|
||||
*/
|
||||
function wc_admin_is_embed_enabled_wc_page() {
|
||||
if ( ! wc_admin_is_feature_enabled( 'activity-panels' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$screen_id = wc_admin_get_current_screen_id();
|
||||
if ( ! $screen_id ) {
|
||||
return false;
|
||||
|
@ -66,6 +70,7 @@ function wc_admin_register_page( $options ) {
|
|||
function wc_admin_register_pages() {
|
||||
global $menu, $submenu;
|
||||
|
||||
if ( wc_admin_is_feature_enabled( 'dashboard' ) ) {
|
||||
add_submenu_page(
|
||||
'woocommerce',
|
||||
__( 'WooCommerce Dashboard', 'wc-admin' ),
|
||||
|
@ -74,7 +79,9 @@ function wc_admin_register_pages() {
|
|||
'wc-admin',
|
||||
'wc_admin_page'
|
||||
);
|
||||
}
|
||||
|
||||
if ( wc_admin_is_feature_enabled( 'analytics' ) ) {
|
||||
add_menu_page(
|
||||
__( 'WooCommerce Analytics', 'wc-admin' ),
|
||||
__( 'Analytics', 'wc-admin' ),
|
||||
|
@ -164,8 +171,9 @@ function wc_admin_register_pages() {
|
|||
'path' => '/analytics/settings',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG && defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
|
||||
if ( wc_admin_is_feature_enabled( 'devdocs' ) && defined( 'WP_DEBUG' ) && WP_DEBUG && defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
|
||||
wc_admin_register_page(
|
||||
array(
|
||||
'title' => 'DevDocs',
|
||||
|
@ -236,7 +244,7 @@ function wc_admin_enqueue_script() {
|
|||
add_action( 'admin_enqueue_scripts', 'wc_admin_enqueue_script' );
|
||||
|
||||
/**
|
||||
* Adds an admin body class.
|
||||
* Adds body classes to the main wp-admin wrapper, allowing us to better target elements in specific scenarios.
|
||||
*
|
||||
* @param string $admin_body_class Body class to add.
|
||||
*/
|
||||
|
@ -252,6 +260,18 @@ function wc_admin_admin_body_class( $admin_body_class = '' ) {
|
|||
if ( wc_admin_is_embed_enabled_wc_page() ) {
|
||||
$classes[] = 'woocommerce-embed-page';
|
||||
}
|
||||
|
||||
if ( function_exists( 'wc_admin_get_feature_config' ) ) {
|
||||
$features = wc_admin_get_feature_config();
|
||||
foreach ( $features as $feature_key => $bool ) {
|
||||
if ( true === $bool ) {
|
||||
$classes[] = sanitize_html_class( 'woocommerce-feature-enabled-' . $feature_key );
|
||||
} else {
|
||||
$classes[] = sanitize_html_class( 'woocommerce-feature-disabled-' . $feature_key );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$admin_body_class = implode( ' ', array_unique( $classes ) );
|
||||
return " $admin_body_class ";
|
||||
}
|
||||
|
@ -261,7 +281,7 @@ add_filter( 'admin_body_class', 'wc_admin_admin_body_class' );
|
|||
* Runs before admin notices action and hides them.
|
||||
*/
|
||||
function wc_admin_admin_before_notices() {
|
||||
if ( ! wc_admin_is_admin_page() && ! wc_admin_is_embed_enabled_wc_page() ) {
|
||||
if ( ( ! wc_admin_is_admin_page() && ! wc_admin_is_embed_enabled_wc_page() ) || ! wc_admin_is_feature_enabled( 'activity-panels' ) ) {
|
||||
return;
|
||||
}
|
||||
echo '<div class="woocommerce-layout__notice-list-hide" id="wp__notice-list">';
|
||||
|
@ -273,7 +293,7 @@ add_action( 'admin_notices', 'wc_admin_admin_before_notices', 0 );
|
|||
* Runs after admin notices and closes div.
|
||||
*/
|
||||
function wc_admin_admin_after_notices() {
|
||||
if ( ! wc_admin_is_admin_page() && ! wc_admin_is_embed_enabled_wc_page() ) {
|
||||
if ( ( ! wc_admin_is_admin_page() && ! wc_admin_is_embed_enabled_wc_page() ) || ! wc_admin_is_feature_enabled( 'activity-panels' ) ) {
|
||||
return;
|
||||
}
|
||||
echo '</div>';
|
||||
|
|
|
@ -280,3 +280,18 @@ function wc_admin_currency_settings() {
|
|||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if a specific wc-admin feature is enabled.
|
||||
*
|
||||
* @param string $feature Feature slug.
|
||||
* @return bool Returns true if the feature is enabled.
|
||||
* }
|
||||
*/
|
||||
function wc_admin_is_feature_enabled( $feature ) {
|
||||
if ( ! function_exists( 'wc_admin_get_feature_config' ) ) {
|
||||
return false;
|
||||
}
|
||||
$features = wc_admin_get_feature_config();
|
||||
return isset( $features[ $feature ] ) && true === $features[ $feature ];
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue