PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 10.9.1
WooCommerce v10.9.1
10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.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 / src / Admin / API / Reports / Segmenter.php
woocommerce / src / Admin / API / Reports Last commit date
Categories 4 weeks ago Coupons 4 weeks ago Customers 2 months ago Downloads 1 year ago Export 3 years ago Import 3 years ago Orders 4 weeks ago PerformanceIndicators 9 months ago Products 4 weeks ago Revenue 3 months ago Stock 4 months ago Taxes 4 weeks ago Variations 4 weeks ago Cache.php 4 years ago Controller.php 11 months ago DataStore.php 4 weeks ago DataStoreInterface.php 4 years ago ExportableInterface.php 4 years ago ExportableTraits.php 4 years ago FilteredGetDataTrait.php 1 year ago GenericController.php 1 year ago GenericQuery.php 1 year ago GenericStatsController.php 1 year ago OrderAwareControllerTrait.php 1 year ago ParameterException.php 4 years ago Query.php 1 year ago Segmenter.php 1 year ago SqlQuery.php 3 years ago StatsDataStoreTrait.php 1 year ago TimeInterval.php 2 years ago
Segmenter.php
726 lines
1 <?php
2 /**
3 * Class for adding segmenting support without cluttering the data stores.
4 */
5
6 namespace Automattic\WooCommerce\Admin\API\Reports;
7
8 defined( 'ABSPATH' ) || exit;
9
10 use Automattic\WooCommerce\Admin\API\Reports\Coupons\DataStore as CouponsDataStore;
11 use Automattic\WooCommerce\Admin\API\Reports\Taxes\Stats\DataStore as TaxesStatsDataStore;
12 use Automattic\WooCommerce\Enums\ProductType;
13
14 /**
15 * Date & time interval and numeric range handling class for Reporting API.
16 */
17 class Segmenter {
18
19 /**
20 * Array of all segment ids.
21 *
22 * @var array|bool
23 */
24 protected $all_segment_ids = false;
25
26 /**
27 * Array of all segment labels.
28 *
29 * @var array
30 */
31 protected $segment_labels = array();
32
33 /**
34 * Query arguments supplied by the user for data store.
35 *
36 * @var array
37 */
38 protected $query_args = '';
39
40 /**
41 * SQL definition for each column.
42 *
43 * @var array
44 */
45 protected $report_columns = array();
46
47 /**
48 * Constructor.
49 *
50 * @param array $query_args Query arguments supplied by the user for data store.
51 * @param array $report_columns Report columns lookup from data store.
52 */
53 public function __construct( $query_args, $report_columns ) {
54 $this->query_args = $query_args;
55 $this->report_columns = $report_columns;
56 }
57
58 /**
59 * Filters definitions for SELECT clauses based on query_args and joins them into one string usable in SELECT clause.
60 *
61 * @param array $columns_mapping Column name -> SQL statememt mapping.
62 *
63 * @return string to be used in SELECT clause statements.
64 */
65 protected function prepare_selections( $columns_mapping ) {
66 if ( isset( $this->query_args['fields'] ) && is_array( $this->query_args['fields'] ) ) {
67 $keep = array();
68 foreach ( $this->query_args['fields'] as $field ) {
69 if ( isset( $columns_mapping[ $field ] ) ) {
70 $keep[ $field ] = $columns_mapping[ $field ];
71 }
72 }
73 $selections = implode( ', ', $keep );
74 } else {
75 $selections = implode( ', ', $columns_mapping );
76 }
77
78 if ( $selections ) {
79 $selections = ',' . $selections;
80 }
81
82 return $selections;
83 }
84
85 /**
86 * Update row-level db result for segments in 'totals' section to the format used for output.
87 *
88 * @param array $segments_db_result Results from the SQL db query for segmenting.
89 * @param string $segment_dimension Name of column used for grouping the result.
90 *
91 * @return array Reformatted array.
92 */
93 protected function reformat_totals_segments( $segments_db_result, $segment_dimension ) {
94 $segment_result = array();
95
96 if ( strpos( $segment_dimension, '.' ) ) {
97 $segment_dimension = substr( strstr( $segment_dimension, '.' ), 1 );
98 }
99
100 $segment_labels = $this->get_segment_labels();
101 foreach ( $segments_db_result as $segment_data ) {
102 $segment_id = $segment_data[ $segment_dimension ];
103 if ( ! isset( $segment_labels[ $segment_id ] ) ) {
104 continue;
105 }
106
107 unset( $segment_data[ $segment_dimension ] );
108 $segment_datum = array(
109 'segment_id' => $segment_id,
110 'segment_label' => $segment_labels[ $segment_id ],
111 'subtotals' => $segment_data,
112 );
113 $segment_result[ $segment_id ] = $segment_datum;
114 }
115
116 return $segment_result;
117 }
118
119 /**
120 * Merges segmented results for totals response part.
121 *
122 * E.g. $r1 = array(
123 * 0 => array(
124 * 'product_id' => 3,
125 * 'net_amount' => 15,
126 * ),
127 * );
128 * $r2 = array(
129 * 0 => array(
130 * 'product_id' => 3,
131 * 'avg_order_value' => 25,
132 * ),
133 * );
134 *
135 * $merged = array(
136 * 3 => array(
137 * 'segment_id' => 3,
138 * 'subtotals' => array(
139 * 'net_amount' => 15,
140 * 'avg_order_value' => 25,
141 * )
142 * ),
143 * );
144 *
145 * @param string $segment_dimension Name of the segment dimension=key in the result arrays used to match records from result sets.
146 * @param array $result1 Array 1 of segmented figures.
147 * @param array $result2 Array 2 of segmented figures.
148 *
149 * @return array
150 */
151 protected function merge_segment_totals_results( $segment_dimension, $result1, $result2 ) {
152 $result_segments = array();
153 $segment_labels = $this->get_segment_labels();
154
155 foreach ( $result1 as $segment_data ) {
156 $segment_id = $segment_data[ $segment_dimension ];
157 if ( ! isset( $segment_labels[ $segment_id ] ) ) {
158 continue;
159 }
160
161 unset( $segment_data[ $segment_dimension ] );
162 $result_segments[ $segment_id ] = array(
163 'segment_label' => $segment_labels[ $segment_id ],
164 'segment_id' => $segment_id,
165 'subtotals' => $segment_data,
166 );
167 }
168
169 foreach ( $result2 as $segment_data ) {
170 $segment_id = $segment_data[ $segment_dimension ];
171 if ( ! isset( $segment_labels[ $segment_id ] ) ) {
172 continue;
173 }
174
175 unset( $segment_data[ $segment_dimension ] );
176 if ( ! isset( $result_segments[ $segment_id ] ) ) {
177 $result_segments[ $segment_id ] = array(
178 'segment_label' => $segment_labels[ $segment_id ],
179 'segment_id' => $segment_id,
180 'subtotals' => array(),
181 );
182 }
183 $result_segments[ $segment_id ]['subtotals'] = array_merge( $result_segments[ $segment_id ]['subtotals'], $segment_data );
184 }
185 return $result_segments;
186 }
187 /**
188 * Merges segmented results for intervals response part.
189 *
190 * E.g. $r1 = array(
191 * 0 => array(
192 * 'product_id' => 3,
193 * 'time_interval' => '2018-12'
194 * 'net_amount' => 15,
195 * ),
196 * );
197 * $r2 = array(
198 * 0 => array(
199 * 'product_id' => 3,
200 * 'time_interval' => '2018-12'
201 * 'avg_order_value' => 25,
202 * ),
203 * );
204 *
205 * $merged = array(
206 * '2018-12' => array(
207 * 'segments' => array(
208 * 3 => array(
209 * 'segment_id' => 3,
210 * 'subtotals' => array(
211 * 'net_amount' => 15,
212 * 'avg_order_value' => 25,
213 * ),
214 * ),
215 * ),
216 * ),
217 * );
218 *
219 * @param string $segment_dimension Name of the segment dimension=key in the result arrays used to match records from result sets.
220 * @param array $result1 Array 1 of segmented figures.
221 * @param array $result2 Array 2 of segmented figures.
222 *
223 * @return array
224 */
225 protected function merge_segment_intervals_results( $segment_dimension, $result1, $result2 ) {
226 $result_segments = array();
227 $segment_labels = $this->get_segment_labels();
228
229 foreach ( $result1 as $segment_data ) {
230 $segment_id = $segment_data[ $segment_dimension ];
231 if ( ! isset( $segment_labels[ $segment_id ] ) ) {
232 continue;
233 }
234
235 $time_interval = $segment_data['time_interval'];
236 if ( ! isset( $result_segments[ $time_interval ] ) ) {
237 $result_segments[ $time_interval ] = array();
238 $result_segments[ $time_interval ]['segments'] = array();
239 }
240
241 unset( $segment_data['time_interval'] );
242 unset( $segment_data['datetime_anchor'] );
243 unset( $segment_data[ $segment_dimension ] );
244 $segment_datum = array(
245 'segment_label' => $segment_labels[ $segment_id ],
246 'segment_id' => $segment_id,
247 'subtotals' => $segment_data,
248 );
249 $result_segments[ $time_interval ]['segments'][ $segment_id ] = $segment_datum;
250 }
251
252 foreach ( $result2 as $segment_data ) {
253 $segment_id = $segment_data[ $segment_dimension ];
254 if ( ! isset( $segment_labels[ $segment_id ] ) ) {
255 continue;
256 }
257
258 $time_interval = $segment_data['time_interval'];
259 if ( ! isset( $result_segments[ $time_interval ] ) ) {
260 $result_segments[ $time_interval ] = array();
261 $result_segments[ $time_interval ]['segments'] = array();
262 }
263
264 unset( $segment_data['time_interval'] );
265 unset( $segment_data['datetime_anchor'] );
266 unset( $segment_data[ $segment_dimension ] );
267
268 if ( ! isset( $result_segments[ $time_interval ]['segments'][ $segment_id ] ) ) {
269 $result_segments[ $time_interval ]['segments'][ $segment_id ] = array(
270 'segment_label' => $segment_labels[ $segment_id ],
271 'segment_id' => $segment_id,
272 'subtotals' => array(),
273 );
274 }
275 $result_segments[ $time_interval ]['segments'][ $segment_id ]['subtotals'] = array_merge( $result_segments[ $time_interval ]['segments'][ $segment_id ]['subtotals'], $segment_data );
276 }
277 return $result_segments;
278 }
279
280 /**
281 * Update row-level db result for segments in 'intervals' section to the format used for output.
282 *
283 * @param array $segments_db_result Results from the SQL db query for segmenting.
284 * @param string $segment_dimension Name of column used for grouping the result.
285 *
286 * @return array Reformatted array.
287 */
288 protected function reformat_intervals_segments( $segments_db_result, $segment_dimension ) {
289 $aggregated_segment_result = array();
290
291 if ( strpos( $segment_dimension, '.' ) ) {
292 $segment_dimension = substr( strstr( $segment_dimension, '.' ), 1 );
293 }
294
295 $segment_labels = $this->get_segment_labels();
296
297 foreach ( $segments_db_result as $segment_data ) {
298 $segment_id = $segment_data[ $segment_dimension ];
299 if ( ! isset( $segment_labels[ $segment_id ] ) ) {
300 continue;
301 }
302
303 $time_interval = $segment_data['time_interval'];
304 if ( ! isset( $aggregated_segment_result[ $time_interval ] ) ) {
305 $aggregated_segment_result[ $time_interval ] = array();
306 $aggregated_segment_result[ $time_interval ]['segments'] = array();
307 }
308 unset( $segment_data['time_interval'] );
309 unset( $segment_data['datetime_anchor'] );
310 unset( $segment_data[ $segment_dimension ] );
311 $segment_datum = array(
312 'segment_label' => $segment_labels[ $segment_id ],
313 'segment_id' => $segment_id,
314 'subtotals' => $segment_data,
315 );
316 $aggregated_segment_result[ $time_interval ]['segments'][ $segment_id ] = $segment_datum;
317 }
318
319 return $aggregated_segment_result;
320 }
321
322 /**
323 * Fetches all segment ids from db and stores it for later use.
324 *
325 * @return void
326 */
327 protected function set_all_segments() {
328 global $wpdb;
329
330 if ( ! isset( $this->query_args['segmentby'] ) || '' === $this->query_args['segmentby'] ) {
331 $this->all_segment_ids = array();
332 return;
333 }
334
335 $segments = array();
336 $segment_labels = array();
337
338 if ( 'product' === $this->query_args['segmentby'] ) {
339 $args = array(
340 'return' => 'objects',
341 'limit' => -1,
342 );
343
344 if ( isset( $this->query_args['product_includes'] ) ) {
345 $args['include'] = $this->query_args['product_includes'];
346 }
347
348 if ( isset( $this->query_args['category_includes'] ) ) {
349 $categories = $this->query_args['category_includes'];
350 $args['category'] = array();
351 foreach ( $categories as $category_id ) {
352 $terms = get_term_by( 'id', $category_id, 'product_cat' );
353 $args['category'][] = $terms->slug;
354 }
355 }
356
357 $segment_objects = wc_get_products( $args );
358 foreach ( $segment_objects as $segment ) {
359 $id = $segment->get_id();
360 $segments[] = $id;
361 $segment_labels[ $id ] = $segment->get_name();
362 }
363 } elseif ( 'variation' === $this->query_args['segmentby'] ) {
364 $args = array(
365 'return' => 'objects',
366 'limit' => -1,
367 'type' => ProductType::VARIATION,
368 );
369
370 if (
371 isset( $this->query_args['product_includes'] ) &&
372 is_array( $this->query_args['product_includes'] ) &&
373 count( $this->query_args['product_includes'] ) === 1
374 ) {
375 $args['parent'] = $this->query_args['product_includes'][0];
376 }
377
378 if ( isset( $this->query_args['variation_includes'] ) ) {
379 $args['include'] = $this->query_args['variation_includes'];
380 }
381
382 $segment_objects = wc_get_products( $args );
383
384 foreach ( $segment_objects as $segment ) {
385 $id = $segment->get_id();
386 $segments[] = $id;
387 $product_name = $segment->get_name();
388 $separator = apply_filters( 'woocommerce_product_variation_title_attributes_separator', ' - ', $segment );
389 $attributes = wc_get_formatted_variation( $segment, true, false );
390
391 $segment_labels[ $id ] = $product_name . $separator . $attributes;
392 }
393
394 // If no variations were specified, add a segment for the parent product (variation = 0).
395 // This is to catch simple products with prior sales converted into variable products.
396 // See: https://github.com/woocommerce/woocommerce-admin/issues/2719.
397 if ( isset( $args['parent'] ) && empty( $args['include'] ) ) {
398 $parent_object = wc_get_product( $args['parent'] );
399 $segments[] = 0;
400 $segment_labels[0] = $parent_object->get_name();
401 }
402 } elseif ( 'category' === $this->query_args['segmentby'] ) {
403 $args = array(
404 'taxonomy' => 'product_cat',
405 );
406
407 if ( isset( $this->query_args['category_includes'] ) ) {
408 $args['include'] = $this->query_args['category_includes'];
409 }
410
411 // @todo: Look into `wc_get_products` or data store methods and not directly touching the database or post types.
412 $categories = get_categories( $args );
413
414 $segments = wp_list_pluck( $categories, 'cat_ID' );
415 $segment_labels = wp_list_pluck( $categories, 'name', 'cat_ID' );
416
417 } elseif ( 'coupon' === $this->query_args['segmentby'] ) {
418 $args = array();
419 if ( isset( $this->query_args['coupons'] ) ) {
420 $args['include'] = $this->query_args['coupons'];
421 }
422 $coupons_store = new CouponsDataStore();
423 $coupons = $coupons_store->get_coupons( $args );
424 $segments = wp_list_pluck( $coupons, 'ID' );
425 $segment_labels = wp_list_pluck( $coupons, 'post_title', 'ID' );
426 $segment_labels = array_map( 'wc_format_coupon_code', $segment_labels );
427 } elseif ( 'customer_type' === $this->query_args['segmentby'] ) {
428 // 0 -- new customer
429 // 1 -- returning customer
430 $segments = array( 0, 1 );
431 } elseif ( 'tax_rate_id' === $this->query_args['segmentby'] ) {
432 $args = array();
433 if ( isset( $this->query_args['taxes'] ) ) {
434 $args['include'] = $this->query_args['taxes'];
435 }
436 $taxes = TaxesStatsDataStore::get_taxes( $args );
437
438 foreach ( $taxes as $tax ) {
439 $id = $tax['tax_rate_id'];
440 $segments[] = $id;
441 $segment_labels[ $id ] = \WC_Tax::get_rate_code( (object) $tax );
442 }
443 } else {
444 // Catch all default.
445 $segments = array();
446 }
447
448 $this->all_segment_ids = $segments;
449 $this->segment_labels = $segment_labels;
450 }
451
452 /**
453 * Return all segment ids for given segmentby query parameter.
454 *
455 * @return array
456 */
457 protected function get_all_segments() {
458 if ( ! is_array( $this->all_segment_ids ) ) {
459 $this->set_all_segments();
460 }
461
462 return $this->all_segment_ids;
463 }
464
465 /**
466 * Return all segment labels for given segmentby query parameter.
467 *
468 * @return array
469 */
470 protected function get_segment_labels() {
471 if ( ! is_array( $this->all_segment_ids ) ) {
472 $this->set_all_segments();
473 }
474
475 return $this->segment_labels;
476 }
477
478 /**
479 * Compares two report data objects by pre-defined object property and ASC/DESC ordering.
480 *
481 * @param stdClass $a Object a.
482 * @param stdClass $b Object b.
483 * @return string
484 */
485 private function segment_cmp( $a, $b ) {
486 if ( $a['segment_id'] === $b['segment_id'] ) {
487 return 0;
488 } elseif ( $a['segment_id'] > $b['segment_id'] ) {
489 return 1;
490 } elseif ( $a['segment_id'] < $b['segment_id'] ) {
491 return - 1;
492 }
493 }
494
495 /**
496 * Adds zeroes for segments not present in the data selection.
497 *
498 * @param array $segments Array of segments from the database for given data points.
499 *
500 * @return array
501 */
502 protected function fill_in_missing_segments( $segments ) {
503 $segment_subtotals = array();
504 if ( isset( $this->query_args['fields'] ) && is_array( $this->query_args['fields'] ) ) {
505 foreach ( $this->query_args['fields'] as $field ) {
506 if ( isset( $this->report_columns[ $field ] ) ) {
507 $segment_subtotals[ $field ] = 0;
508 }
509 }
510 } else {
511 foreach ( $this->report_columns as $field => $sql_clause ) {
512 $segment_subtotals[ $field ] = 0;
513 }
514 }
515 if ( ! is_array( $segments ) ) {
516 $segments = array();
517 }
518 $all_segment_ids = $this->get_all_segments();
519 $segment_labels = $this->get_segment_labels();
520 foreach ( $all_segment_ids as $segment_id ) {
521 if ( ! isset( $segments[ $segment_id ] ) ) {
522 $segments[ $segment_id ] = array(
523 'segment_id' => $segment_id,
524 'segment_label' => $segment_labels[ $segment_id ],
525 'subtotals' => $segment_subtotals,
526 );
527 }
528 }
529
530 // Using array_values to remove custom keys, so that it gets later converted to JSON as an array.
531 $segments_no_keys = array_values( $segments );
532 usort( $segments_no_keys, array( $this, 'segment_cmp' ) );
533 return $segments_no_keys;
534 }
535
536 /**
537 * Adds missing segments to intervals, modifies $data.
538 *
539 * @param stdClass $data Response data.
540 */
541 protected function fill_in_missing_interval_segments( &$data ) {
542 foreach ( $data->intervals as $order_id => $interval_data ) {
543 $data->intervals[ $order_id ]['segments'] = $this->fill_in_missing_segments( $data->intervals[ $order_id ]['segments'] );
544 }
545 }
546
547 /**
548 * Calculate segments for totals where the segmenting property is bound to product (e.g. category, product_id, variation_id).
549 *
550 * @param array $segmenting_selections SELECT part of segmenting SQL query--one for 'product_level' and one for 'order_level'.
551 * @param string $segmenting_from FROM part of segmenting SQL query.
552 * @param string $segmenting_where WHERE part of segmenting SQL query.
553 * @param string $segmenting_groupby GROUP BY part of segmenting SQL query.
554 * @param string $segmenting_dimension_name Name of the segmenting dimension.
555 * @param string $table_name Name of SQL table which is the stats table for orders.
556 * @param array $totals_query Array of SQL clauses for totals query.
557 * @param string $unique_orders_table Name of temporary SQL table that holds unique orders.
558 *
559 * @return array
560 */
561 protected function get_product_related_totals_segments( $segmenting_selections, $segmenting_from, $segmenting_where, $segmenting_groupby, $segmenting_dimension_name, $table_name, $totals_query, $unique_orders_table ) {
562 return array();
563 }
564
565 /**
566 * Calculate segments for intervals where the segmenting property is bound to product (e.g. category, product_id, variation_id).
567 *
568 * @param array $segmenting_selections SELECT part of segmenting SQL query--one for 'product_level' and one for 'order_level'.
569 * @param string $segmenting_from FROM part of segmenting SQL query.
570 * @param string $segmenting_where WHERE part of segmenting SQL query.
571 * @param string $segmenting_groupby GROUP BY part of segmenting SQL query.
572 * @param string $segmenting_dimension_name Name of the segmenting dimension.
573 * @param string $table_name Name of SQL table which is the stats table for orders.
574 * @param array $intervals_query Array of SQL clauses for intervals query.
575 * @param string $unique_orders_table Name of temporary SQL table that holds unique orders.
576 *
577 * @return array
578 */
579 protected function get_product_related_intervals_segments( $segmenting_selections, $segmenting_from, $segmenting_where, $segmenting_groupby, $segmenting_dimension_name, $table_name, $intervals_query, $unique_orders_table ) {
580 return array();
581 }
582
583 /**
584 * Calculate segments for totals query where the segmenting property is bound to order (e.g. coupon or customer type).
585 *
586 * @param string $segmenting_select SELECT part of segmenting SQL query.
587 * @param string $segmenting_from FROM part of segmenting SQL query.
588 * @param string $segmenting_where WHERE part of segmenting SQL query.
589 * @param string $segmenting_groupby GROUP BY part of segmenting SQL query.
590 * @param string $table_name Name of SQL table which is the stats table for orders.
591 * @param array $totals_query Array of SQL clauses for intervals query.
592 *
593 * @return array
594 */
595 protected function get_order_related_totals_segments( $segmenting_select, $segmenting_from, $segmenting_where, $segmenting_groupby, $table_name, $totals_query ) {
596 return array();
597 }
598
599 /**
600 * Calculate segments for intervals query where the segmenting property is bound to order (e.g. coupon or customer type).
601 *
602 * @param string $segmenting_select SELECT part of segmenting SQL query.
603 * @param string $segmenting_from FROM part of segmenting SQL query.
604 * @param string $segmenting_where WHERE part of segmenting SQL query.
605 * @param string $segmenting_groupby GROUP BY part of segmenting SQL query.
606 * @param string $table_name Name of SQL table which is the stats table for orders.
607 * @param array $intervals_query Array of SQL clauses for intervals query.
608 *
609 * @return array
610 */
611 protected function get_order_related_intervals_segments( $segmenting_select, $segmenting_from, $segmenting_where, $segmenting_groupby, $table_name, $intervals_query ) {
612 return array();
613 }
614
615 /**
616 * Return array of segments formatted for REST response.
617 *
618 * @param string $type Type of segments to return--'totals' or 'intervals'.
619 * @param array $query_params SQL query parameter array.
620 * @param string $table_name Name of main SQL table for the data store (used as basis for JOINS).
621 *
622 * @return array
623 */
624 protected function get_segments( $type, $query_params, $table_name ) {
625 return array();
626 }
627
628 /**
629 * Calculate segments for segmenting property bound to product (e.g. category, product_id, variation_id).
630 *
631 * @param string $type Type of segments to return--'totals' or 'intervals'.
632 * @param array $segmenting_selections SELECT part of segmenting SQL query--one for 'product_level' and one for 'order_level'.
633 * @param string $segmenting_from FROM part of segmenting SQL query.
634 * @param string $segmenting_where WHERE part of segmenting SQL query.
635 * @param string $segmenting_groupby GROUP BY part of segmenting SQL query.
636 * @param string $segmenting_dimension_name Name of the segmenting dimension.
637 * @param string $table_name Name of SQL table which is the stats table for orders.
638 * @param array $query_params Array of SQL clauses for intervals/totals query.
639 * @param string $unique_orders_table Name of temporary SQL table that holds unique orders.
640 *
641 * @return array
642 */
643 protected function get_product_related_segments( $type, $segmenting_selections, $segmenting_from, $segmenting_where, $segmenting_groupby, $segmenting_dimension_name, $table_name, $query_params, $unique_orders_table ) {
644 if ( 'totals' === $type ) {
645 return $this->get_product_related_totals_segments( $segmenting_selections, $segmenting_from, $segmenting_where, $segmenting_groupby, $segmenting_dimension_name, $table_name, $query_params, $unique_orders_table );
646 } elseif ( 'intervals' === $type ) {
647 return $this->get_product_related_intervals_segments( $segmenting_selections, $segmenting_from, $segmenting_where, $segmenting_groupby, $segmenting_dimension_name, $table_name, $query_params, $unique_orders_table );
648 }
649 }
650
651 /**
652 * Calculate segments for segmenting property bound to order (e.g. coupon or customer type).
653 *
654 * @param string $type Type of segments to return--'totals' or 'intervals'.
655 * @param string $segmenting_select SELECT part of segmenting SQL query.
656 * @param string $segmenting_from FROM part of segmenting SQL query.
657 * @param string $segmenting_where WHERE part of segmenting SQL query.
658 * @param string $segmenting_groupby GROUP BY part of segmenting SQL query.
659 * @param string $table_name Name of SQL table which is the stats table for orders.
660 * @param array $query_params Array of SQL clauses for intervals/totals query.
661 *
662 * @return array
663 */
664 protected function get_order_related_segments( $type, $segmenting_select, $segmenting_from, $segmenting_where, $segmenting_groupby, $table_name, $query_params ) {
665 if ( 'totals' === $type ) {
666 return $this->get_order_related_totals_segments( $segmenting_select, $segmenting_from, $segmenting_where, $segmenting_groupby, $table_name, $query_params );
667 } elseif ( 'intervals' === $type ) {
668 return $this->get_order_related_intervals_segments( $segmenting_select, $segmenting_from, $segmenting_where, $segmenting_groupby, $table_name, $query_params );
669 }
670 }
671
672 /**
673 * Assign segments to time intervals by updating original $intervals array.
674 *
675 * @param array $intervals Result array from intervals SQL query.
676 * @param array $intervals_segments Result array from interval segments SQL query.
677 */
678 protected function assign_segments_to_intervals( &$intervals, $intervals_segments ) {
679 $old_keys = array_keys( $intervals );
680 foreach ( $intervals as $interval ) {
681 $intervals[ $interval['time_interval'] ] = $interval;
682 $intervals[ $interval['time_interval'] ]['segments'] = array();
683 }
684 foreach ( $old_keys as $key ) {
685 unset( $intervals[ $key ] );
686 }
687
688 foreach ( $intervals_segments as $time_interval => $segment ) {
689 if ( isset( $intervals[ $time_interval ] ) ) {
690 $intervals[ $time_interval ]['segments'] = $segment['segments'];
691 }
692 }
693 // To remove time interval keys (so that REST response is formatted correctly).
694 $intervals = array_values( $intervals );
695 }
696
697 /**
698 * Returns an array of segments for totals part of REST response.
699 *
700 * @param array $query_params Totals SQL query parameters.
701 * @param string $table_name Name of the SQL table that is the main order stats table.
702 *
703 * @return array
704 */
705 public function get_totals_segments( $query_params, $table_name ) {
706 $segments = $this->get_segments( 'totals', $query_params, $table_name );
707 $segments = $this->fill_in_missing_segments( $segments );
708
709 return $segments;
710 }
711
712 /**
713 * Adds an array of segments to data->intervals object.
714 *
715 * @param stdClass $data Data object representing the REST response.
716 * @param array $intervals_query Intervals SQL query parameters.
717 * @param string $table_name Name of the SQL table that is the main order stats table.
718 */
719 public function add_intervals_segments( &$data, $intervals_query, $table_name ) {
720 $intervals_segments = $this->get_segments( 'intervals', $intervals_query, $table_name );
721
722 $this->assign_segments_to_intervals( $data->intervals, $intervals_segments );
723 $this->fill_in_missing_interval_segments( $data );
724 }
725 }
726