PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 7.5.1
WooCommerce v7.5.1
10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / includes / admin / reports / class-wc-admin-report.php
woocommerce / includes / admin / reports Last commit date
class-wc-admin-report.php 3 years ago class-wc-report-coupon-usage.php 5 years ago class-wc-report-customer-list.php 5 years ago class-wc-report-customers.php 5 years ago class-wc-report-downloads.php 5 years ago class-wc-report-low-in-stock.php 5 years ago class-wc-report-most-stocked.php 5 years ago class-wc-report-out-of-stock.php 5 years ago class-wc-report-sales-by-category.php 5 years ago class-wc-report-sales-by-date.php 4 years ago class-wc-report-sales-by-product.php 5 years ago class-wc-report-stock.php 5 years ago class-wc-report-taxes-by-code.php 5 years ago class-wc-report-taxes-by-date.php 4 years ago
class-wc-admin-report.php
801 lines
1 <?php
2 /**
3 * Admin report functionality.
4 *
5 * @package WooCommerce\Admin\Reports
6 */
7
8 use Automattic\WooCommerce\Utilities\ArrayUtil;
9
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit; // Exit if accessed directly.
12 }
13
14 /**
15 * Admin Report.
16 *
17 * Extended by reports to show charts and stats in admin.
18 *
19 * @package WooCommerce\Admin\Reports
20 * @version 2.1.0
21 */
22 class WC_Admin_Report {
23
24 /**
25 * List of transients name that have been updated and need persisting.
26 *
27 * @var array
28 */
29 protected static $transients_to_update = array();
30
31 /**
32 * The list of transients.
33 *
34 * @var array
35 */
36 protected static $cached_results = array();
37
38 /**
39 * The chart interval.
40 *
41 * @var int
42 */
43 public $chart_interval;
44
45 /**
46 * Group by SQL query.
47 *
48 * @var string
49 */
50 public $group_by_query;
51
52 /**
53 * The bar width.
54 *
55 * @var int
56 */
57 public $barwidth;
58
59 /**
60 * Group chart item by day or month.
61 *
62 * @var string
63 */
64 public $chart_groupby;
65
66 /**
67 * The start date of the report.
68 *
69 * @var int timestamp
70 */
71 public $start_date;
72
73 /**
74 * The end date of the report.
75 *
76 * @var int timestamp
77 */
78 public $end_date;
79
80 /**
81 * Get report totals such as order totals and discount amounts.
82 *
83 * Data example:
84 *
85 * '_order_total' => array(
86 * 'type' => 'meta',
87 * 'function' => 'SUM',
88 * 'name' => 'total_sales'
89 * )
90 *
91 * @param array $args arguments for the report.
92 * @return mixed depending on query_type
93 */
94 public function get_order_report_data( $args = array() ) {
95 global $wpdb;
96
97 $default_args = array(
98 'data' => array(),
99 'where' => array(),
100 'where_meta' => array(),
101 'query_type' => 'get_row',
102 'group_by' => '',
103 'order_by' => '',
104 'limit' => '',
105 'filter_range' => false,
106 'nocache' => false,
107 'debug' => false,
108 'order_types' => wc_get_order_types( 'reports' ),
109 'order_status' => array( 'completed', 'processing', 'on-hold' ),
110 'parent_order_status' => false,
111 );
112 $args = apply_filters( 'woocommerce_reports_get_order_report_data_args', $args );
113 $args = wp_parse_args( $args, $default_args );
114
115 // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
116 extract( $args );
117
118 if ( empty( $data ) ) {
119 return '';
120 }
121
122 $order_status = apply_filters( 'woocommerce_reports_order_statuses', $order_status );
123
124 $query = array();
125 $select = array();
126
127 foreach ( $data as $raw_key => $value ) {
128 $key = sanitize_key( $raw_key );
129 $distinct = '';
130
131 if ( isset( $value['distinct'] ) ) {
132 $distinct = 'DISTINCT';
133 }
134
135 switch ( $value['type'] ) {
136 case 'meta':
137 $get_key = "meta_{$key}.meta_value";
138 break;
139 case 'parent_meta':
140 $get_key = "parent_meta_{$key}.meta_value";
141 break;
142 case 'post_data':
143 $get_key = "posts.{$key}";
144 break;
145 case 'order_item_meta':
146 $get_key = "order_item_meta_{$key}.meta_value";
147 break;
148 case 'order_item':
149 $get_key = "order_items.{$key}";
150 break;
151 }
152
153 if ( empty( $get_key ) ) {
154 // Skip to the next foreach iteration else the query will be invalid.
155 continue;
156 }
157
158 if ( $value['function'] ) {
159 $get = "{$value['function']}({$distinct} {$get_key})";
160 } else {
161 $get = "{$distinct} {$get_key}";
162 }
163
164 $select[] = "{$get} as {$value['name']}";
165 }
166
167 $query['select'] = 'SELECT ' . implode( ',', $select );
168 $query['from'] = "FROM {$wpdb->posts} AS posts";
169
170 // Joins.
171 $joins = array();
172
173 foreach ( ( $data + $where ) as $raw_key => $value ) {
174 $join_type = isset( $value['join_type'] ) ? $value['join_type'] : 'INNER';
175 $type = isset( $value['type'] ) ? $value['type'] : false;
176 $key = sanitize_key( $raw_key );
177
178 switch ( $type ) {
179 case 'meta':
180 $joins[ "meta_{$key}" ] = "{$join_type} JOIN {$wpdb->postmeta} AS meta_{$key} ON ( posts.ID = meta_{$key}.post_id AND meta_{$key}.meta_key = '{$raw_key}' )";
181 break;
182 case 'parent_meta':
183 $joins[ "parent_meta_{$key}" ] = "{$join_type} JOIN {$wpdb->postmeta} AS parent_meta_{$key} ON (posts.post_parent = parent_meta_{$key}.post_id) AND (parent_meta_{$key}.meta_key = '{$raw_key}')";
184 break;
185 case 'order_item_meta':
186 $joins['order_items'] = "{$join_type} JOIN {$wpdb->prefix}woocommerce_order_items AS order_items ON (posts.ID = order_items.order_id)";
187
188 if ( ! empty( $value['order_item_type'] ) ) {
189 $joins['order_items'] .= " AND (order_items.order_item_type = '{$value['order_item_type']}')";
190 }
191
192 $joins[ "order_item_meta_{$key}" ] = "{$join_type} JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS order_item_meta_{$key} ON " .
193 "(order_items.order_item_id = order_item_meta_{$key}.order_item_id) " .
194 " AND (order_item_meta_{$key}.meta_key = '{$raw_key}')";
195 break;
196 case 'order_item':
197 $joins['order_items'] = "{$join_type} JOIN {$wpdb->prefix}woocommerce_order_items AS order_items ON posts.ID = order_items.order_id";
198 break;
199 }
200 }
201
202 if ( ! empty( $where_meta ) ) {
203 foreach ( $where_meta as $value ) {
204 if ( ! is_array( $value ) ) {
205 continue;
206 }
207 $join_type = isset( $value['join_type'] ) ? $value['join_type'] : 'INNER';
208 $type = isset( $value['type'] ) ? $value['type'] : false;
209 $key = sanitize_key( is_array( $value['meta_key'] ) ? $value['meta_key'][0] . '_array' : $value['meta_key'] );
210
211 if ( 'order_item_meta' === $type ) {
212
213 $joins['order_items'] = "{$join_type} JOIN {$wpdb->prefix}woocommerce_order_items AS order_items ON posts.ID = order_items.order_id";
214 $joins[ "order_item_meta_{$key}" ] = "{$join_type} JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS order_item_meta_{$key} ON order_items.order_item_id = order_item_meta_{$key}.order_item_id";
215
216 } else {
217 // If we have a where clause for meta, join the postmeta table.
218 $joins[ "meta_{$key}" ] = "{$join_type} JOIN {$wpdb->postmeta} AS meta_{$key} ON posts.ID = meta_{$key}.post_id";
219 }
220 }
221 }
222
223 if ( ! empty( $parent_order_status ) ) {
224 $joins['parent'] = "LEFT JOIN {$wpdb->posts} AS parent ON posts.post_parent = parent.ID";
225 }
226
227 $query['join'] = implode( ' ', $joins );
228
229 $query['where'] = "
230 WHERE posts.post_type IN ( '" . implode( "','", $order_types ) . "' )
231 ";
232
233 if ( ! empty( $order_status ) ) {
234 $query['where'] .= "
235 AND posts.post_status IN ( 'wc-" . implode( "','wc-", $order_status ) . "')
236 ";
237 }
238
239 if ( ! empty( $parent_order_status ) ) {
240 if ( ! empty( $order_status ) ) {
241 $query['where'] .= " AND ( parent.post_status IN ( 'wc-" . implode( "','wc-", $parent_order_status ) . "') OR parent.ID IS NULL ) ";
242 } else {
243 $query['where'] .= " AND parent.post_status IN ( 'wc-" . implode( "','wc-", $parent_order_status ) . "') ";
244 }
245 }
246
247 // phpcs:disable WordPress.DateTime.RestrictedFunctions.date_date
248 if ( $filter_range ) {
249 $query['where'] .= "
250 AND posts.post_date >= '" . date( 'Y-m-d H:i:s', $this->start_date ) . "'
251 AND posts.post_date < '" . date( 'Y-m-d H:i:s', strtotime( '+1 DAY', $this->end_date ) ) . "'
252 ";
253 }
254 // phpcs:enable WordPress.DateTime.RestrictedFunctions.date_date
255
256 if ( ! empty( $where_meta ) ) {
257
258 $relation = isset( $where_meta['relation'] ) ? $where_meta['relation'] : 'AND';
259
260 $query['where'] .= ' AND (';
261
262 foreach ( $where_meta as $index => $value ) {
263
264 if ( ! is_array( $value ) ) {
265 continue;
266 }
267
268 $key = sanitize_key( is_array( $value['meta_key'] ) ? $value['meta_key'][0] . '_array' : $value['meta_key'] );
269
270 if ( strtolower( $value['operator'] ) === 'in' || strtolower( $value['operator'] ) === 'not in' ) {
271
272 if ( is_array( $value['meta_value'] ) ) {
273 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
274 $value['meta_value'] = implode( "','", $value['meta_value'] );
275 }
276
277 if ( ! empty( $value['meta_value'] ) ) {
278 $where_value = "{$value['operator']} ('{$value['meta_value']}')";
279 }
280 } else {
281 $where_value = "{$value['operator']} '{$value['meta_value']}'";
282 }
283
284 if ( ! empty( $where_value ) ) {
285 if ( $index > 0 ) {
286 $query['where'] .= ' ' . $relation;
287 }
288
289 if ( isset( $value['type'] ) && 'order_item_meta' === $value['type'] ) {
290
291 if ( is_array( $value['meta_key'] ) ) {
292 $query['where'] .= " ( order_item_meta_{$key}.meta_key IN ('" . implode( "','", $value['meta_key'] ) . "')";
293 } else {
294 $query['where'] .= " ( order_item_meta_{$key}.meta_key = '{$value['meta_key']}'";
295 }
296
297 $query['where'] .= " AND order_item_meta_{$key}.meta_value {$where_value} )";
298 } else {
299
300 if ( is_array( $value['meta_key'] ) ) {
301 $query['where'] .= " ( meta_{$key}.meta_key IN ('" . implode( "','", $value['meta_key'] ) . "')";
302 } else {
303 $query['where'] .= " ( meta_{$key}.meta_key = '{$value['meta_key']}'";
304 }
305
306 $query['where'] .= " AND meta_{$key}.meta_value {$where_value} )";
307 }
308 }
309 }
310
311 $query['where'] .= ')';
312 }
313
314 if ( ! empty( $where ) ) {
315
316 foreach ( $where as $value ) {
317
318 if ( strtolower( $value['operator'] ) === 'in' || strtolower( $value['operator'] ) === 'not in' ) {
319
320 if ( is_array( $value['value'] ) ) {
321 $value['value'] = implode( "','", $value['value'] );
322 }
323
324 if ( ! empty( $value['value'] ) ) {
325 $where_value = "{$value['operator']} ('{$value['value']}')";
326 }
327 } else {
328 $where_value = "{$value['operator']} '{$value['value']}'";
329 }
330
331 if ( ! empty( $where_value ) ) {
332 $query['where'] .= " AND {$value['key']} {$where_value}";
333 }
334 }
335 }
336
337 if ( $group_by ) {
338 $query['group_by'] = "GROUP BY {$group_by}";
339 }
340
341 if ( $order_by ) {
342 $query['order_by'] = "ORDER BY {$order_by}";
343 }
344
345 if ( $limit ) {
346 $query['limit'] = "LIMIT {$limit}";
347 }
348
349 $query = apply_filters( 'woocommerce_reports_get_order_report_query', $query );
350 $query = implode( ' ', $query );
351
352 if ( $debug ) {
353 echo '<pre>';
354 wc_print_r( $query );
355 echo '</pre>';
356 }
357
358 if ( $debug || $nocache ) {
359 self::enable_big_selects();
360
361 $result = apply_filters( 'woocommerce_reports_get_order_report_data', $wpdb->$query_type( $query ), $data );
362 } else {
363 $query_hash = md5( $query_type . $query );
364 $result = $this->get_cached_query( $query_hash );
365 if ( null === $result ) {
366 self::enable_big_selects();
367
368 $result = apply_filters( 'woocommerce_reports_get_order_report_data', $wpdb->$query_type( $query ), $data );
369 }
370 $this->set_cached_query( $query_hash, $result );
371 }
372
373 return $result;
374 }
375
376 /**
377 * Init the static hooks of the class.
378 */
379 protected static function add_update_transients_hook() {
380 if ( ! has_action( 'shutdown', array( 'WC_Admin_Report', 'maybe_update_transients' ) ) ) {
381 add_action( 'shutdown', array( 'WC_Admin_Report', 'maybe_update_transients' ) );
382 }
383 }
384
385 /**
386 * Enables big mysql selects for reports, just once for this session.
387 */
388 protected static function enable_big_selects() {
389 static $big_selects = false;
390
391 global $wpdb;
392
393 if ( ! $big_selects ) {
394 $wpdb->query( 'SET SESSION SQL_BIG_SELECTS=1' );
395 $big_selects = true;
396 }
397 }
398
399 /**
400 * Get the cached query result or null if it's not in the cache.
401 *
402 * @param string $query_hash The query hash.
403 *
404 * @return mixed
405 */
406 protected function get_cached_query( $query_hash ) {
407 $class = strtolower( get_class( $this ) );
408
409 if ( ! isset( self::$cached_results[ $class ] ) ) {
410 self::$cached_results[ $class ] = get_transient( strtolower( get_class( $this ) ) );
411 }
412
413 if ( isset( self::$cached_results[ $class ][ $query_hash ] ) ) {
414 return self::$cached_results[ $class ][ $query_hash ];
415 }
416
417 return null;
418 }
419
420 /**
421 * Set the cached query result.
422 *
423 * @param string $query_hash The query hash.
424 * @param mixed $data The data to cache.
425 */
426 protected function set_cached_query( $query_hash, $data ) {
427 $class = strtolower( get_class( $this ) );
428
429 if ( ! isset( self::$cached_results[ $class ] ) ) {
430 self::$cached_results[ $class ] = get_transient( $class );
431 }
432
433 if ( false === self::$cached_results[ $class ] ) {
434 self::$cached_results[ $class ] = array();
435 }
436
437 self::add_update_transients_hook();
438
439 self::$transients_to_update[ $class ] = $class;
440 self::$cached_results[ $class ][ $query_hash ] = $data;
441 }
442
443 /**
444 * Function to update the modified transients at the end of the request.
445 */
446 public static function maybe_update_transients() {
447 foreach ( self::$transients_to_update as $key => $transient_name ) {
448 set_transient( $transient_name, self::$cached_results[ $transient_name ], DAY_IN_SECONDS );
449 }
450 // Transients have been updated reset the list.
451 self::$transients_to_update = array();
452 }
453
454 /**
455 * Put data with post_date's into an array of times.
456 *
457 * @param array $data array of your data.
458 * @param string $date_key key for the 'date' field. e.g. 'post_date'.
459 * @param string $data_key key for the data you are charting.
460 * @param int $interval interval to use.
461 * @param string $start_date start date.
462 * @param string $group_by group by.
463 * @return array
464 */
465 public function prepare_chart_data( $data, $date_key, $data_key, $interval, $start_date, $group_by ) {
466 // phpcs:disable WordPress.DateTime.RestrictedFunctions.date_date
467
468 $prepared_data = array();
469
470 // Ensure all days (or months) have values in this range.
471 if ( 'day' === $group_by ) {
472 for ( $i = 0; $i <= $interval; $i ++ ) {
473 $time = strtotime( date( 'Ymd', strtotime( "+{$i} DAY", $start_date ) ) ) . '000';
474
475 if ( ! isset( $prepared_data[ $time ] ) ) {
476 $prepared_data[ $time ] = array( esc_js( $time ), 0 );
477 }
478 }
479 } else {
480 $current_yearnum = date( 'Y', $start_date );
481 $current_monthnum = date( 'm', $start_date );
482
483 for ( $i = 0; $i <= $interval; $i ++ ) {
484 $time = strtotime( $current_yearnum . str_pad( $current_monthnum, 2, '0', STR_PAD_LEFT ) . '01' ) . '000';
485
486 if ( ! isset( $prepared_data[ $time ] ) ) {
487 $prepared_data[ $time ] = array( esc_js( $time ), 0 );
488 }
489
490 $current_monthnum ++;
491
492 if ( $current_monthnum > 12 ) {
493 $current_monthnum = 1;
494 $current_yearnum ++;
495 }
496 }
497 }
498
499 foreach ( $data as $d ) {
500 switch ( $group_by ) {
501 case 'day':
502 $time = strtotime( date( 'Ymd', strtotime( $d->$date_key ) ) ) . '000';
503 break;
504 case 'month':
505 default:
506 $time = strtotime( date( 'Ym', strtotime( $d->$date_key ) ) . '01' ) . '000';
507 break;
508 }
509
510 if ( ! isset( $prepared_data[ $time ] ) ) {
511 continue;
512 }
513
514 if ( $data_key ) {
515 $prepared_data[ $time ][1] += $d->$data_key;
516 } else {
517 $prepared_data[ $time ][1] ++;
518 }
519 }
520
521 return $prepared_data;
522
523 // phpcs:enable WordPress.DateTime.RestrictedFunctions.date_date
524 }
525
526 /**
527 * Prepares a sparkline to show sales in the last X days.
528 *
529 * @param int $id ID of the product to show. Blank to get all orders.
530 * @param int $days Days of stats to get.
531 * @param string $type Type of sparkline to get. Ignored if ID is not set.
532 * @return string
533 */
534 public function sales_sparkline( $id = '', $days = 7, $type = 'sales' ) {
535
536 // phpcs:disable WordPress.DateTime.RestrictedFunctions.date_date, WordPress.DateTime.CurrentTimeTimestamp.Requested
537
538 if ( $id ) {
539 $meta_key = ( 'sales' === $type ) ? '_line_total' : '_qty';
540
541 $data = $this->get_order_report_data(
542 array(
543 'data' => array(
544 '_product_id' => array(
545 'type' => 'order_item_meta',
546 'order_item_type' => 'line_item',
547 'function' => '',
548 'name' => 'product_id',
549 ),
550 $meta_key => array(
551 'type' => 'order_item_meta',
552 'order_item_type' => 'line_item',
553 'function' => 'SUM',
554 'name' => 'sparkline_value',
555 ),
556 'post_date' => array(
557 'type' => 'post_data',
558 'function' => '',
559 'name' => 'post_date',
560 ),
561 ),
562 'where' => array(
563 array(
564 'key' => 'post_date',
565 'value' => date( 'Y-m-d', strtotime( 'midnight -' . ( $days - 1 ) . ' days', current_time( 'timestamp' ) ) ),
566 'operator' => '>',
567 ),
568 array(
569 'key' => 'order_item_meta__product_id.meta_value',
570 'value' => $id,
571 'operator' => '=',
572 ),
573 ),
574 'group_by' => 'YEAR(posts.post_date), MONTH(posts.post_date), DAY(posts.post_date)',
575 'query_type' => 'get_results',
576 'filter_range' => false,
577 )
578 );
579 } else {
580
581 $data = $this->get_order_report_data(
582 array(
583 'data' => array(
584 '_order_total' => array(
585 'type' => 'meta',
586 'function' => 'SUM',
587 'name' => 'sparkline_value',
588 ),
589 'post_date' => array(
590 'type' => 'post_data',
591 'function' => '',
592 'name' => 'post_date',
593 ),
594 ),
595 'where' => array(
596 array(
597 'key' => 'post_date',
598 'value' => date( 'Y-m-d', strtotime( 'midnight -' . ( $days - 1 ) . ' days', current_time( 'timestamp' ) ) ),
599 'operator' => '>',
600 ),
601 ),
602 'group_by' => 'YEAR(posts.post_date), MONTH(posts.post_date), DAY(posts.post_date)',
603 'query_type' => 'get_results',
604 'filter_range' => false,
605 )
606 );
607 }
608
609 $total = 0;
610 foreach ( $data as $d ) {
611 $total += $d->sparkline_value;
612 }
613
614 if ( 'sales' === $type ) {
615 /* translators: 1: total income 2: days */
616 $tooltip = sprintf( __( 'Sold %1$s worth in the last %2$d days', 'woocommerce' ), wp_strip_all_tags( wc_price( $total ) ), $days );
617 } else {
618 /* translators: 1: total items sold 2: days */
619 $tooltip = sprintf( _n( 'Sold %1$d item in the last %2$d days', 'Sold %1$d items in the last %2$d days', $total, 'woocommerce' ), $total, $days );
620 }
621
622 $sparkline_data = array_values( $this->prepare_chart_data( $data, 'post_date', 'sparkline_value', $days - 1, strtotime( 'midnight -' . ( $days - 1 ) . ' days', current_time( 'timestamp' ) ), 'day' ) );
623
624 return '<span class="wc_sparkline ' . ( ( 'sales' === $type ) ? 'lines' : 'bars' ) . ' tips" data-color="#777" data-tip="' . esc_attr( $tooltip ) . '" data-barwidth="' . 60 * 60 * 16 * 1000 . '" data-sparkline="' . wc_esc_json( wp_json_encode( $sparkline_data ) ) . '"></span>';
625
626 // phpcs:enable WordPress.DateTime.RestrictedFunctions.date_date, WordPress.DateTime.CurrentTimeTimestamp.Requested
627 }
628
629 /**
630 * Get the current range and calculate the start and end dates.
631 *
632 * @param string $current_range Type of range.
633 */
634 public function calculate_current_range( $current_range ) {
635
636 // phpcs:disable WordPress.DateTime.RestrictedFunctions.date_date, WordPress.DateTime.CurrentTimeTimestamp.Requested
637 // phpcs:disable WordPress.Security.NonceVerification.Recommended
638
639 switch ( $current_range ) {
640
641 case 'custom':
642 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
643 $this->start_date = max( strtotime( '-20 years' ), strtotime( sanitize_text_field( wp_unslash( $_GET['start_date'] ) ) ) );
644
645 if ( empty( $_GET['end_date'] ) ) {
646 $this->end_date = strtotime( 'midnight', current_time( 'timestamp' ) );
647 } else {
648 $this->end_date = strtotime( 'midnight', strtotime( sanitize_text_field( wp_unslash( $_GET['end_date'] ) ) ) );
649 }
650
651 $interval = 0;
652 $min_date = $this->start_date;
653
654 // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
655 while ( ( $min_date = strtotime( '+1 MONTH', $min_date ) ) <= $this->end_date ) {
656 $interval ++;
657 }
658
659 // 3 months max for day view
660 if ( $interval > 3 ) {
661 $this->chart_groupby = 'month';
662 } else {
663 $this->chart_groupby = 'day';
664 }
665 break;
666
667 case 'year':
668 $this->start_date = strtotime( date( 'Y-01-01', current_time( 'timestamp' ) ) );
669 $this->end_date = strtotime( 'midnight', current_time( 'timestamp' ) );
670 $this->chart_groupby = 'month';
671 break;
672
673 case 'last_month':
674 $first_day_current_month = strtotime( date( 'Y-m-01', current_time( 'timestamp' ) ) );
675 $this->start_date = strtotime( date( 'Y-m-01', strtotime( '-1 DAY', $first_day_current_month ) ) );
676 $this->end_date = strtotime( date( 'Y-m-t', strtotime( '-1 DAY', $first_day_current_month ) ) );
677 $this->chart_groupby = 'day';
678 break;
679
680 case 'month':
681 $this->start_date = strtotime( date( 'Y-m-01', current_time( 'timestamp' ) ) );
682 $this->end_date = strtotime( 'midnight', current_time( 'timestamp' ) );
683 $this->chart_groupby = 'day';
684 break;
685
686 case '7day':
687 $this->start_date = strtotime( '-6 days', strtotime( 'midnight', current_time( 'timestamp' ) ) );
688 $this->end_date = strtotime( 'midnight', current_time( 'timestamp' ) );
689 $this->chart_groupby = 'day';
690 break;
691 }
692
693 // Group by.
694 switch ( $this->chart_groupby ) {
695
696 case 'day':
697 $this->group_by_query = 'YEAR(posts.post_date), MONTH(posts.post_date), DAY(posts.post_date)';
698 $this->chart_interval = absint( ceil( max( 0, ( $this->end_date - $this->start_date ) / ( 60 * 60 * 24 ) ) ) );
699 $this->barwidth = 60 * 60 * 24 * 1000;
700 break;
701
702 case 'month':
703 $this->group_by_query = 'YEAR(posts.post_date), MONTH(posts.post_date)';
704 $this->chart_interval = 0;
705 $min_date = strtotime( date( 'Y-m-01', $this->start_date ) );
706
707 // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
708 while ( ( $min_date = strtotime( '+1 MONTH', $min_date ) ) <= $this->end_date ) {
709 $this->chart_interval ++;
710 }
711
712 $this->barwidth = 60 * 60 * 24 * 7 * 4 * 1000;
713 break;
714 }
715
716 // phpcs:enable WordPress.Security.NonceVerification.Recommended
717 // phpcs:enable WordPress.DateTime.RestrictedFunctions.date_date, WordPress.DateTime.CurrentTimeTimestamp.Requested
718 }
719
720 /**
721 * Return currency tooltip JS based on WooCommerce currency position settings.
722 *
723 * @return string
724 */
725 public function get_currency_tooltip() {
726 switch ( get_option( 'woocommerce_currency_pos' ) ) {
727 case 'right':
728 $currency_tooltip = 'append_tooltip: "' . get_woocommerce_currency_symbol() . '"';
729 break;
730 case 'right_space':
731 $currency_tooltip = 'append_tooltip: "&nbsp;' . get_woocommerce_currency_symbol() . '"';
732 break;
733 case 'left':
734 $currency_tooltip = 'prepend_tooltip: "' . get_woocommerce_currency_symbol() . '"';
735 break;
736 case 'left_space':
737 default:
738 $currency_tooltip = 'prepend_tooltip: "' . get_woocommerce_currency_symbol() . '&nbsp;"';
739 break;
740 }
741
742 return $currency_tooltip;
743 }
744
745 /**
746 * Get the main chart.
747 */
748 public function get_main_chart() {}
749
750 /**
751 * Get the legend for the main chart sidebar.
752 *
753 * @return array
754 */
755 public function get_chart_legend() {
756 return array();
757 }
758
759 /**
760 * Get chart widgets.
761 *
762 * @return array
763 */
764 public function get_chart_widgets() {
765 return array();
766 }
767
768 /**
769 * Get an export link if needed.
770 */
771 public function get_export_button() {}
772
773 /**
774 * Output the report.
775 */
776 public function output_report() {}
777
778 /**
779 * Check nonce for current range.
780 *
781 * @since 3.0.4
782 * @param string $current_range Current range.
783 */
784 public function check_current_range_nonce( $current_range ) {
785 if ( 'custom' !== $current_range ) {
786 return;
787 }
788
789 if ( ! isset( $_GET['wc_reports_nonce'] ) || ! wp_verify_nonce( sanitize_key( $_GET['wc_reports_nonce'] ), 'custom_range' ) ) {
790 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotValidated
791 wp_die(
792 /* translators: %1$s: open link, %2$s: close link */
793 sprintf( esc_html__( 'This report link has expired. %1$sClick here to view the filtered report%2$s.', 'woocommerce' ), '<a href="' . esc_url( wp_nonce_url( esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ), 'custom_range', 'wc_reports_nonce' ) ) . '">', '</a>' ),
794 esc_attr__( 'Confirm navigation', 'woocommerce' )
795 );
796 // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotValidated
797 exit;
798 }
799 }
800 }
801