WC_Report_Sales_By_Product

WC_Report_Sales_By_Product.

Defined (1)

The class is defined in the following location(s).

/includes/admin/reports/class-wc-report-sales-by-product.php  
  1. class WC_Report_Sales_By_Product extends WC_Admin_Report { 
  2.  
  3. /** 
  4. * Chart colors. 
  5. * @var array 
  6. */ 
  7. public $chart_colours = array(); 
  8.  
  9. /** 
  10. * Product ids. 
  11. * @var array 
  12. */ 
  13. public $product_ids = array(); 
  14.  
  15. /** 
  16. * Product ids with titles. 
  17. * @var array 
  18. */ 
  19. public $product_ids_titles = array(); 
  20.  
  21. /** 
  22. * Constructor. 
  23. */ 
  24. public function __construct() { 
  25. if ( isset( $_GET['product_ids'] ) && is_array( $_GET['product_ids'] ) ) { 
  26. $this->product_ids = array_filter( array_map( 'absint', $_GET['product_ids'] ) ); 
  27. } elseif ( isset( $_GET['product_ids'] ) ) { 
  28. $this->product_ids = array_filter( array( absint( $_GET['product_ids'] ) ) ); 
  29.  
  30. /** 
  31. * Get the legend for the main chart sidebar. 
  32. * @return array 
  33. */ 
  34. public function get_chart_legend() { 
  35.  
  36. if ( empty( $this->product_ids ) ) { 
  37. return array(); 
  38.  
  39. $legend = array(); 
  40.  
  41. $total_sales = $this->get_order_report_data( array( 
  42. 'data' => array( 
  43. '_line_total' => array( 
  44. 'type' => 'order_item_meta',  
  45. 'order_item_type' => 'line_item',  
  46. 'function' => 'SUM',  
  47. 'name' => 'order_item_amount',  
  48. ),  
  49. ),  
  50. 'where_meta' => array( 
  51. 'relation' => 'OR',  
  52. array( 
  53. 'type' => 'order_item_meta',  
  54. 'meta_key' => array( '_product_id', '_variation_id' ),  
  55. 'meta_value' => $this->product_ids,  
  56. 'operator' => 'IN',  
  57. ),  
  58. 'query_type' => 'get_var',  
  59. 'filter_range' => true,  
  60. ) ); 
  61.  
  62. $total_items = absint( $this->get_order_report_data( array( 
  63. 'data' => array( 
  64. '_qty' => array( 
  65. 'type' => 'order_item_meta',  
  66. 'order_item_type' => 'line_item',  
  67. 'function' => 'SUM',  
  68. 'name' => 'order_item_count',  
  69. ),  
  70. ),  
  71. 'where_meta' => array( 
  72. 'relation' => 'OR',  
  73. array( 
  74. 'type' => 'order_item_meta',  
  75. 'meta_key' => array( '_product_id', '_variation_id' ),  
  76. 'meta_value' => $this->product_ids,  
  77. 'operator' => 'IN',  
  78. ),  
  79. 'query_type' => 'get_var',  
  80. 'filter_range' => true,  
  81. ) ) ); 
  82.  
  83. $legend[] = array( 
  84. /** translators: %s: total items sold */ 
  85. 'title' => sprintf( __( '%s sales for the selected items', 'woocommerce' ), '<strong>' . wc_price( $total_sales ) . '</strong>' ),  
  86. 'color' => $this->chart_colours['sales_amount'],  
  87. 'highlight_series' => 1,  
  88. ); 
  89.  
  90. $legend[] = array( 
  91. /** translators: %s: total items purchased */ 
  92. 'title' => sprintf( __( '%s purchases for the selected items', 'woocommerce' ), '<strong>' . ( $total_items ) . '</strong>' ),  
  93. 'color' => $this->chart_colours['item_count'],  
  94. 'highlight_series' => 0,  
  95. ); 
  96.  
  97. return $legend; 
  98.  
  99. /** 
  100. * Output the report. 
  101. */ 
  102. public function output_report() { 
  103.  
  104. $ranges = array( 
  105. 'year' => __( 'Year', 'woocommerce' ),  
  106. 'last_month' => __( 'Last month', 'woocommerce' ),  
  107. 'month' => __( 'This month', 'woocommerce' ),  
  108. '7day' => __( 'Last 7 days', 'woocommerce' ),  
  109. ); 
  110.  
  111. $this->chart_colours = array( 
  112. 'sales_amount' => '#3498db',  
  113. 'item_count' => '#d4d9dc',  
  114. ); 
  115.  
  116. $current_range = ! empty( $_GET['range'] ) ? sanitize_text_field( $_GET['range'] ) : '7day'; 
  117.  
  118. if ( ! in_array( $current_range, array( 'custom', 'year', 'last_month', 'month', '7day' ) ) ) { 
  119. $current_range = '7day'; 
  120.  
  121. $this->check_current_range_nonce( $current_range ); 
  122. $this->calculate_current_range( $current_range ); 
  123.  
  124. include( WC()->plugin_path() . '/includes/admin/views/html-report-by-date.php' ); 
  125.  
  126. /** 
  127. * Get chart widgets. 
  128. * @return array 
  129. */ 
  130. public function get_chart_widgets() { 
  131.  
  132. $widgets = array(); 
  133.  
  134. if ( ! empty( $this->product_ids ) ) { 
  135. $widgets[] = array( 
  136. 'title' => __( 'Showing reports for:', 'woocommerce' ),  
  137. 'callback' => array( $this, 'current_filters' ),  
  138. ); 
  139.  
  140. $widgets[] = array( 
  141. 'title' => '',  
  142. 'callback' => array( $this, 'products_widget' ),  
  143. ); 
  144.  
  145. return $widgets; 
  146.  
  147. /** 
  148. * Output current filters. 
  149. */ 
  150. public function current_filters() { 
  151.  
  152. $this->product_ids_titles = array(); 
  153.  
  154. foreach ( $this->product_ids as $product_id ) { 
  155.  
  156. $product = wc_get_product( $product_id ); 
  157.  
  158. if ( $product ) { 
  159. $this->product_ids_titles[] = $product->get_formatted_name(); 
  160. } else { 
  161. $this->product_ids_titles[] = '#' . $product_id; 
  162.  
  163. echo '<p>' . ' <strong>' . esc_html( implode( ', ', $this->product_ids_titles ) ) . '</strong></p>'; 
  164. echo '<p><a class="button" href="' . esc_url( remove_query_arg( 'product_ids' ) ) . '">' . __( 'Reset', 'woocommerce' ) . '</a></p>'; 
  165.  
  166. /** 
  167. * Output products widget. 
  168. */ 
  169. public function products_widget() { 
  170. ?> 
  171. <h4 class="section_title"><span><?php _e( 'Product search', 'woocommerce' ); ?></span></h4> 
  172. <div class="section"> 
  173. <form method="GET"> 
  174. <div> 
  175. <select class="wc-product-search" style="width:203px;" multiple="multiple" id="product_ids" name="product_ids[]" data-placeholder="<?php esc_attr_e( 'Search for a product…', 'woocommerce' ); ?>" data-action="woocommerce_json_search_products_and_variations"></select> 
  176. <input type="submit" class="submit button" value="<?php esc_attr_e( 'Show', 'woocommerce' ); ?>" /> 
  177. <input type="hidden" name="range" value="<?php if ( ! empty( $_GET['range'] ) ) echo esc_attr( $_GET['range'] ) ?>" /> 
  178. <input type="hidden" name="start_date" value="<?php if ( ! empty( $_GET['start_date'] ) ) echo esc_attr( $_GET['start_date'] ) ?>" /> 
  179. <input type="hidden" name="end_date" value="<?php if ( ! empty( $_GET['end_date'] ) ) echo esc_attr( $_GET['end_date'] ) ?>" /> 
  180. <input type="hidden" name="page" value="<?php if ( ! empty( $_GET['page'] ) ) echo esc_attr( $_GET['page'] ) ?>" /> 
  181. <input type="hidden" name="tab" value="<?php if ( ! empty( $_GET['tab'] ) ) echo esc_attr( $_GET['tab'] ) ?>" /> 
  182. <input type="hidden" name="report" value="<?php if ( ! empty( $_GET['report'] ) ) echo esc_attr( $_GET['report'] ) ?>" /> 
  183. </div> 
  184. </form> 
  185. </div> 
  186. <h4 class="section_title"><span><?php _e( 'Top sellers', 'woocommerce' ); ?></span></h4> 
  187. <div class="section"> 
  188. <table cellspacing="0"> 
  189. <?php 
  190. $top_sellers = $this->get_order_report_data( array( 
  191. 'data' => array( 
  192. '_product_id' => array( 
  193. 'type' => 'order_item_meta',  
  194. 'order_item_type' => 'line_item',  
  195. 'function' => '',  
  196. 'name' => 'product_id',  
  197. ),  
  198. '_qty' => array( 
  199. 'type' => 'order_item_meta',  
  200. 'order_item_type' => 'line_item',  
  201. 'function' => 'SUM',  
  202. 'name' => 'order_item_qty',  
  203. ),  
  204. ),  
  205. 'order_by' => 'order_item_qty DESC',  
  206. 'group_by' => 'product_id',  
  207. 'limit' => 12,  
  208. 'query_type' => 'get_results',  
  209. 'filter_range' => true,  
  210. ) ); 
  211.  
  212. if ( $top_sellers ) { 
  213. foreach ( $top_sellers as $product ) { 
  214. echo '<tr class="' . ( in_array( $product->product_id, $this->product_ids ) ? 'active' : '' ) . '"> 
  215. <td class="count">' . $product->order_item_qty . '</td> 
  216. <td class="name"><a href="' . esc_url( add_query_arg( 'product_ids', $product->product_id ) ) . '">' . get_the_title( $product->product_id ) . '</a></td> 
  217. <td class="sparkline">' . $this->sales_sparkline( $product->product_id, 7, 'count' ) . '</td> 
  218. </tr>'; 
  219. } else { 
  220. echo '<tr><td colspan="3">' . __( 'No products found in range', 'woocommerce' ) . '</td></tr>'; 
  221. ?> 
  222. </table> 
  223. </div> 
  224. <h4 class="section_title"><span><?php _e( 'Top freebies', 'woocommerce' ); ?></span></h4> 
  225. <div class="section"> 
  226. <table cellspacing="0"> 
  227. <?php 
  228. $top_freebies = $this->get_order_report_data( array( 
  229. 'data' => array( 
  230. '_product_id' => array( 
  231. 'type' => 'order_item_meta',  
  232. 'order_item_type' => 'line_item',  
  233. 'function' => '',  
  234. 'name' => 'product_id',  
  235. ),  
  236. '_qty' => array( 
  237. 'type' => 'order_item_meta',  
  238. 'order_item_type' => 'line_item',  
  239. 'function' => 'SUM',  
  240. 'name' => 'order_item_qty',  
  241. ),  
  242. ),  
  243. 'where_meta' => array( 
  244. array( 
  245. 'type' => 'order_item_meta',  
  246. 'meta_key' => '_line_subtotal',  
  247. 'meta_value' => '0',  
  248. 'operator' => '=',  
  249. ),  
  250. ),  
  251. 'order_by' => 'order_item_qty DESC',  
  252. 'group_by' => 'product_id',  
  253. 'limit' => 12,  
  254. 'query_type' => 'get_results',  
  255. 'filter_range' => true,  
  256. ) ); 
  257.  
  258. if ( $top_freebies ) { 
  259. foreach ( $top_freebies as $product ) { 
  260. echo '<tr class="' . ( in_array( $product->product_id, $this->product_ids ) ? 'active' : '' ) . '"> 
  261. <td class="count">' . $product->order_item_qty . '</td> 
  262. <td class="name"><a href="' . esc_url( add_query_arg( 'product_ids', $product->product_id ) ) . '">' . get_the_title( $product->product_id ) . '</a></td> 
  263. <td class="sparkline">' . $this->sales_sparkline( $product->product_id, 7, 'count' ) . '</td> 
  264. </tr>'; 
  265. } else { 
  266. echo '<tr><td colspan="3">' . __( 'No products found in range', 'woocommerce' ) . '</td></tr>'; 
  267. ?> 
  268. </table> 
  269. </div> 
  270. <h4 class="section_title"><span><?php _e( 'Top earners', 'woocommerce' ); ?></span></h4> 
  271. <div class="section"> 
  272. <table cellspacing="0"> 
  273. <?php 
  274. $top_earners = $this->get_order_report_data( array( 
  275. 'data' => array( 
  276. '_product_id' => array( 
  277. 'type' => 'order_item_meta',  
  278. 'order_item_type' => 'line_item',  
  279. 'function' => '',  
  280. 'name' => 'product_id',  
  281. ),  
  282. '_line_total' => array( 
  283. 'type' => 'order_item_meta',  
  284. 'order_item_type' => 'line_item',  
  285. 'function' => 'SUM',  
  286. 'name' => 'order_item_total',  
  287. ),  
  288. ),  
  289. 'order_by' => 'order_item_total DESC',  
  290. 'group_by' => 'product_id',  
  291. 'limit' => 12,  
  292. 'query_type' => 'get_results',  
  293. 'filter_range' => true,  
  294. ) ); 
  295.  
  296. if ( $top_earners ) { 
  297. foreach ( $top_earners as $product ) { 
  298. echo '<tr class="' . ( in_array( $product->product_id, $this->product_ids ) ? 'active' : '' ) . '"> 
  299. <td class="count">' . wc_price( $product->order_item_total ) . '</td> 
  300. <td class="name"><a href="' . esc_url( add_query_arg( 'product_ids', $product->product_id ) ) . '">' . get_the_title( $product->product_id ) . '</a></td> 
  301. <td class="sparkline">' . $this->sales_sparkline( $product->product_id, 7, 'sales' ) . '</td> 
  302. </tr>'; 
  303. } else { 
  304. echo '<tr><td colspan="3">' . __( 'No products found in range', 'woocommerce' ) . '</td></tr>'; 
  305. ?> 
  306. </table> 
  307. </div> 
  308. <script type="text/javascript"> 
  309. jQuery('.section_title').click(function() { 
  310. var next_section = jQuery(this).next('.section'); 
  311.  
  312. if ( jQuery(next_section).is(':visible') ) 
  313. return false; 
  314.  
  315. jQuery('.section:visible').slideUp(); 
  316. jQuery('.section_title').removeClass('open'); 
  317. jQuery(this).addClass('open').next('.section').slideDown(); 
  318.  
  319. return false; 
  320. }); 
  321. jQuery('.section').slideUp( 100, function() { 
  322. <?php if ( empty( $this->product_ids ) ) : ?> 
  323. jQuery('.section_title:eq(1)').click(); 
  324. <?php endif; ?> 
  325. }); 
  326. </script> 
  327. <?php 
  328.  
  329. /** 
  330. * Output an export link. 
  331. */ 
  332. public function get_export_button() { 
  333.  
  334. $current_range = ! empty( $_GET['range'] ) ? sanitize_text_field( $_GET['range'] ) : '7day'; 
  335. ?> 
  336. <a 
  337. href="#" 
  338. download="report-<?php echo esc_attr( $current_range ); ?>-<?php echo date_i18n( 'Y-m-d', current_time( 'timestamp' ) ); ?>.csv" 
  339. class="export_csv" 
  340. data-export="chart" 
  341. data-xaxes="<?php esc_attr_e( 'Date', 'woocommerce' ); ?>" 
  342. data-groupby="<?php echo $this->chart_groupby; ?>" 
  343. <?php _e( 'Export CSV', 'woocommerce' ); ?> 
  344. </a> 
  345. <?php 
  346.  
  347. /** 
  348. * Get the main chart. 
  349. * @return string 
  350. */ 
  351. public function get_main_chart() { 
  352. global $wp_locale; 
  353.  
  354. if ( empty( $this->product_ids ) ) { 
  355. ?> 
  356. <div class="chart-container"> 
  357. <p class="chart-prompt"><?php _e( 'Choose a product to view stats', 'woocommerce' ); ?></p> 
  358. </div> 
  359. <?php 
  360. } else { 
  361. // Get orders and dates in range - we want the SUM of order totals, COUNT of order items, COUNT of orders, and the date 
  362. $order_item_counts = $this->get_order_report_data( array( 
  363. 'data' => array( 
  364. '_qty' => array( 
  365. 'type' => 'order_item_meta',  
  366. 'order_item_type' => 'line_item',  
  367. 'function' => 'SUM',  
  368. 'name' => 'order_item_count',  
  369. ),  
  370. 'post_date' => array( 
  371. 'type' => 'post_data',  
  372. 'function' => '',  
  373. 'name' => 'post_date',  
  374. ),  
  375. '_product_id' => array( 
  376. 'type' => 'order_item_meta',  
  377. 'order_item_type' => 'line_item',  
  378. 'function' => '',  
  379. 'name' => 'product_id',  
  380. ),  
  381. ),  
  382. 'where_meta' => array( 
  383. 'relation' => 'OR',  
  384. array( 
  385. 'type' => 'order_item_meta',  
  386. 'meta_key' => array( '_product_id', '_variation_id' ),  
  387. 'meta_value' => $this->product_ids,  
  388. 'operator' => 'IN',  
  389. ),  
  390. ),  
  391. 'group_by' => 'product_id, ' . $this->group_by_query,  
  392. 'order_by' => 'post_date ASC',  
  393. 'query_type' => 'get_results',  
  394. 'filter_range' => true,  
  395. ) ); 
  396.  
  397. $order_item_amounts = $this->get_order_report_data( array( 
  398. 'data' => array( 
  399. '_line_total' => array( 
  400. 'type' => 'order_item_meta',  
  401. 'order_item_type' => 'line_item',  
  402. 'function' => 'SUM',  
  403. 'name' => 'order_item_amount',  
  404. ),  
  405. 'post_date' => array( 
  406. 'type' => 'post_data',  
  407. 'function' => '',  
  408. 'name' => 'post_date',  
  409. ),  
  410. '_product_id' => array( 
  411. 'type' => 'order_item_meta',  
  412. 'order_item_type' => 'line_item',  
  413. 'function' => '',  
  414. 'name' => 'product_id',  
  415. ),  
  416. ),  
  417. 'where_meta' => array( 
  418. 'relation' => 'OR',  
  419. array( 
  420. 'type' => 'order_item_meta',  
  421. 'meta_key' => array( '_product_id', '_variation_id' ),  
  422. 'meta_value' => $this->product_ids,  
  423. 'operator' => 'IN',  
  424. ),  
  425. ),  
  426. 'group_by' => 'product_id, ' . $this->group_by_query,  
  427. 'order_by' => 'post_date ASC',  
  428. 'query_type' => 'get_results',  
  429. 'filter_range' => true,  
  430. ) ); 
  431.  
  432. // Prepare data for report 
  433. $order_item_counts = $this->prepare_chart_data( $order_item_counts, 'post_date', 'order_item_count', $this->chart_interval, $this->start_date, $this->chart_groupby ); 
  434. $order_item_amounts = $this->prepare_chart_data( $order_item_amounts, 'post_date', 'order_item_amount', $this->chart_interval, $this->start_date, $this->chart_groupby ); 
  435.  
  436. // Encode in json format 
  437. $chart_data = json_encode( array( 
  438. 'order_item_counts' => array_values( $order_item_counts ),  
  439. 'order_item_amounts' => array_values( $order_item_amounts ),  
  440. ) ); 
  441. ?> 
  442. <div class="chart-container"> 
  443. <div class="chart-placeholder main"></div> 
  444. </div> 
  445. <script type="text/javascript"> 
  446. var main_chart; 
  447.  
  448. jQuery(function() { 
  449. var order_data = jQuery.parseJSON( '<?php echo $chart_data; ?>' ); 
  450.  
  451. var drawGraph = function( highlight ) { 
  452.  
  453. var series = [ 
  454. label: "<?php echo esc_js( __( 'Number of items sold', 'woocommerce' ) ) ?>",  
  455. data: order_data.order_item_counts,  
  456. color: '<?php echo $this->chart_colours['item_count']; ?>',  
  457. bars: { fillColor: '<?php echo $this->chart_colours['item_count']; ?>', fill: true, show: true, lineWidth: 0, barWidth: <?php echo $this->barwidth; ?> * 0.5, align: 'center' },  
  458. shadowSize: 0,  
  459. hoverable: false 
  460. },  
  461. label: "<?php echo esc_js( __( 'Sales amount', 'woocommerce' ) ) ?>",  
  462. data: order_data.order_item_amounts,  
  463. yaxis: 2,  
  464. color: '<?php echo $this->chart_colours['sales_amount']; ?>',  
  465. points: { show: true, radius: 5, lineWidth: 3, fillColor: '#fff', fill: true },  
  466. lines: { show: true, lineWidth: 4, fill: false },  
  467. shadowSize: 0,  
  468. <?php echo $this->get_currency_tooltip(); ?> 
  469. ]; 
  470.  
  471. if ( highlight !== 'undefined' && series[ highlight ] ) { 
  472. highlight_series = series[ highlight ]; 
  473.  
  474. highlight_series.color = '#9c5d90'; 
  475.  
  476. if ( highlight_series.bars ) 
  477. highlight_series.bars.fillColor = '#9c5d90'; 
  478.  
  479. if ( highlight_series.lines ) { 
  480. highlight_series.lines.lineWidth = 5; 
  481.  
  482. main_chart = jQuery.plot( 
  483. jQuery('.chart-placeholder.main'),  
  484. series,  
  485. legend: { 
  486. show: false 
  487. },  
  488. grid: { 
  489. color: '#aaa',  
  490. borderColor: 'transparent',  
  491. borderWidth: 0,  
  492. hoverable: true 
  493. },  
  494. xaxes: [ { 
  495. color: '#aaa',  
  496. position: "bottom",  
  497. tickColor: 'transparent',  
  498. mode: "time",  
  499. timeformat: "<?php echo ( 'day' === $this->chart_groupby ) ? '%d %b' : '%b'; ?>",  
  500. monthNames: <?php echo json_encode( array_values( $wp_locale->month_abbrev ) ) ?>,  
  501. tickLength: 1,  
  502. minTickSize: [1, "<?php echo $this->chart_groupby; ?>"],  
  503. font: { 
  504. color: "#aaa" 
  505. } ],  
  506. yaxes: [ 
  507. min: 0,  
  508. minTickSize: 1,  
  509. tickDecimals: 0,  
  510. color: '#ecf0f1',  
  511. font: { color: "#aaa" } 
  512. },  
  513. position: "right",  
  514. min: 0,  
  515. tickDecimals: 2,  
  516. alignTicksWithAxis: 1,  
  517. color: 'transparent',  
  518. font: { color: "#aaa" } 
  519. ],  
  520. ); 
  521.  
  522. jQuery('.chart-placeholder').resize(); 
  523.  
  524. drawGraph(); 
  525.  
  526. jQuery('.highlight_series').hover( 
  527. function() { 
  528. drawGraph( jQuery(this).data('series') ); 
  529. },  
  530. function() { 
  531. drawGraph(); 
  532. ); 
  533. }); 
  534. </script> 
  535. <?php