PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.8
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.8
4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 4.2.0 All 139 releases
← All changes | inc/Databases/class-lp-statistics-db.php +386 -238 4.3.74.4.8 View file →
@@ -5,8 +5,10 @@
5 5 * @author thimpress
6 6 * @since 4.2.6
7 7 */
8 8
9 +use LearnPress\Statistics\StatisticsScope;
10 +
9 11 defined( 'ABSPATH' ) || exit();
10 12
11 13 class LP_Statistics_DB extends LP_Database {
12 14 private static $_instance;
@@ -23,9 +25,39 @@
23 25 return self::$_instance;
24 26 }
25 27
26 28 /**
29 + * Apply a statistics-scoped filter to every LP_Filter query this class runs,
30 + * then defer to the core query builder.
31 + *
32 + * Single choke point for all statistics queries: the DTO ( collection /
33 + * only_fields / where / join / group_by / order_by ) is exposed for shaping,
34 + * and no raw SQL string is passed, so a handler customizes a specific query
35 + * by inspecting $filter ( e.g. $filter->collection, $filter->only_fields )
36 + * and returning a mutated LP_Filter. Note the generic lp/query/* filters
37 + * still fire downstream in the query builder as before.
38 + *
39 + * @param LP_Filter $filter Query DTO.
40 + * @param int $total_rows Total rows, by reference.
41 + * @return mixed
42 + * @since 4.4.2
43 + */
44 + public function execute( $filter, int &$total_rows = 0 ) {
45 + /**
46 + * Filter a statistics query DTO before execution.
47 + *
48 + * @param LP_Filter $filter The query DTO.
49 + * @param LP_Statistics_DB $db This DB instance.
50 + * @since 4.4.2
51 + */
52 + $filter = apply_filters( 'learn-press/statistics/query/filter', $filter, $this );
53 +
54 + return parent::execute( $filter, $total_rows );
55 + }
56 +
57 + /**
27 58 * filter to get data for chart of a day.
59 + *
28 60 * @param LP_Filter $filter
29 61 * @param string $time_field the column use to filter time
30 62 * @return LP_Filter
31 63 */
@@ -30,13 +62,14 @@
30 62 * @return LP_Filter
31 63 */
32 64 public function chart_filter_date_group_by( LP_Filter $filter, string $time_field ) {
33 65 $filter->only_fields[] = "HOUR($time_field) as x_data_label";
34 - $filter->group_by = "x_data_label";
66 + $filter->group_by = 'x_data_label';
35 67 return $filter;
36 68 }
37 69 /**
38 70 * filter to get data for chart of last some days. ex: last 7 days, last 30 days,...
71 + *
39 72 * @param LP_Filter $filter
40 73 * @param string $time_field the column use to filter time
41 74 * @return LP_Filter
42 75 */
@@ -41,13 +74,14 @@
41 74 * @return LP_Filter
42 75 */
43 76 public function chart_filter_previous_days_group_by( LP_Filter $filter, string $time_field ) {
44 77 $filter->only_fields[] = "CAST($time_field AS DATE) as x_data_label";
45 - $filter->group_by = "x_data_label";
78 + $filter->group_by = 'x_data_label';
46 79 return $filter;
47 80 }
48 81 /**
49 82 * filter to get data for chart of a month
83 + *
50 84 * @param LP_Filter $filter
51 85 * @param string $time_field the column use to filter time
52 86 * @return LP_Filter
53 87 */
@@ -52,13 +86,14 @@
52 86 * @return LP_Filter
53 87 */
54 88 public function chart_filter_month_group_by( LP_Filter $filter, string $time_field ) {
55 89 $filter->only_fields[] = "DAY($time_field) as x_data_label";
56 - $filter->group_by = "x_data_label";
90 + $filter->group_by = 'x_data_label';
57 91 return $filter;
58 92 }
59 93 /**
60 94 * filter to get data for chart of months. ex: last 3 months, 6 months, 9 months,...
95 + *
61 96 * @param LP_Filter $filter
62 97 * @param string $time_field the column use to filter time
63 98 * @return LP_Filter
64 99 */
@@ -63,13 +98,14 @@
63 98 * @return LP_Filter
64 99 */
65 100 public function chart_filter_previous_months_group_by( LP_Filter $filter, string $time_field ) {
66 101 $filter->only_fields[] = "DATE_FORMAT( $time_field , '%m-%Y') as x_data_label";
67 - $filter->group_by = "x_data_label";
102 + $filter->group_by = 'x_data_label';
68 103 return $filter;
69 104 }
70 105 /**
71 106 * filter to get data for chart of a year
107 + *
72 108 * @param LP_Filter $filter
73 109 * @param string $time_field the column use to filter time. ex: post_date with posts table, user_registered on users table
74 110 * @return LP_Filter
75 111 */
@@ -74,13 +110,14 @@
74 110 * @return LP_Filter
75 111 */
76 112 public function chart_filter_year_group_by( LP_Filter $filter, string $time_field ) {
77 113 $filter->only_fields[] = "MONTH($time_field) as x_data_label";
78 - $filter->group_by = "x_data_label";
114 + $filter->group_by = 'x_data_label';
79 115 return $filter;
80 116 }
81 117 /**
82 118 * filter to get data for chart of a custom date ranges
119 + *
83 120 * @param LP_Filter $filter
84 121 * @param array $dates array of date range use to filer
85 122 * @param string $time_field the column use to filter time. ex: post_date with posts table, user_registered on users table
86 123 * @return LP_Filter
@@ -88,9 +125,9 @@
88 125 public function chart_filter_custom_group_by( LP_Filter $filter, array $dates, string $time_field ) {
89 126 $diff1 = date_create( $dates[0] );
90 127 $diff2 = date_create( $dates[1] );
91 128 if ( ! $diff1 || ! $diff2 ) {
92 - throw new Exception( 'Custom filter date is invalid.', 'learnpress' );
129 + throw new Exception( __( 'Custom filter date is invalid.', 'learnpress' ) );
93 130 }
94 131 $diff = date_diff( $diff1, $diff2, true );
95 132 $y = $diff->y;
96 133 $m = $diff->m;
@@ -111,14 +148,14 @@
111 148 // less thans 2 years return data of year months
112 149 $filter = $this->chart_filter_previous_months_group_by( $filter, $time_field );
113 150 } elseif ( $y < 5 ) {
114 151 // from 2-5years return data of year quarters
115 - $filter->only_fields[] = $this->wpdb->prepare( "CONCAT( %s, QUARTER($time_field) ,%s, Year($time_field)) as x_data_label", [ 'q', '-' ] );
116 - $filter->group_by = "x_data_label";
152 + $filter->only_fields[] = $this->wpdb->prepare( "CONCAT( %s, QUARTER($time_field) ,%s, Year($time_field)) as x_data_label", array( 'q', '-' ) );
153 + $filter->group_by = 'x_data_label';
117 154 } else {
118 155 // more than 5 years, return data of years
119 156 $filter->only_fields[] = "YEAR($time_field) as x_data_label";
120 - $filter->group_by = "x_data_label";
157 + $filter->group_by = 'x_data_label';
121 158 }
122 159 return $filter;
123 160 }
124 161 /**
@@ -142,9 +179,9 @@
142 179 * @return LP_Filter
143 180 */
144 181 public function previous_days_filter( LP_Filter $filter, int $value, string $time_field, $is_until = false ) {
145 182 if ( $value < 2 ) {
146 - throw new Exception( 'Day must be greater than 2 days.', 'learnpress' );
183 + throw new Exception( __( 'Day must be greater than 2 days.', 'learnpress' ) );
147 184 }
148 185 if ( $is_until ) {
149 186 $filter->where[] = "AND $time_field <= CURDATE()";
150 187 } else {
@@ -174,9 +211,9 @@
174 211 * @return LP_Filter
175 212 */
176 213 public function previous_months_filter( LP_Filter $filter, int $value, string $time_field, $is_until = false ) {
177 214 if ( $value < 2 ) {
178 - throw new Exception( 'Values must be greater than 2 months.', 'learnpress' );
215 + throw new Exception( __( 'Values must be greater than 2 months.', 'learnpress' ) );
179 216 }
180 217 if ( $is_until ) {
181 218 $filter->where[] = "AND $time_field <= CURDATE()";
182 219 } else {
@@ -185,8 +222,9 @@
185 222 return $filter;
186 223 }
187 224 /**
188 225 * get data for each month in year
226 + *
189 227 * @param LP_Filter $filter
190 228 * @param string $date choose a date to query, format Y-m-d
191 229 * @param string $time_field $time_field the column use to filter time. ex: post_date with posts table, user_registered on
192 230 * @return LP_Filter
@@ -200,8 +238,9 @@
200 238 return $filter;
201 239 }
202 240 /**
203 241 * custom query with data range
242 + *
204 243 * @param LP_Filter $filter
205 244 * @param array $dates date ranges, array of 2 dates.
206 245 * @param string $time_field $time_field the column use to filter time. ex: post_date with posts table, user_registered on
207 246 * @return LP_Filter
@@ -207,9 +246,9 @@
207 246 * @return LP_Filter
208 247 */
209 248 public function custom_time_filter( LP_Filter $filter, array $dates, string $time_field, $is_until = false ) {
210 249 if ( empty( $dates ) ) {
211 - throw new Exception( 'Select date', 'learnpress' );
250 + throw new Exception( __( 'Select date', 'learnpress' ) );
212 251 }
213 252 sort( $dates );
214 253 if ( $is_until ) {
215 254 $filter->where[] = $this->wpdb->prepare( "AND cast( $time_field as DATE) <= cast(%s as DATE)", $dates[1] );
@@ -225,8 +264,9 @@
225 264 }
226 265
227 266 /**
228 267 * choose filter type foreach filter time
268 + *
229 269 * @param LP_Filter $filter
230 270 * @param string $type date|month|year|previous_days|custom
231 271 * @param string $time_field datetime colummn
232 272 * @param boolean $value value to query datetimes
@@ -234,9 +274,9 @@
234 274 * @return LP_Filter
235 275 */
236 276 public function filter_time( LP_Filter $filter, string $type, string $time_field, $value = false, $is_until = false ) {
237 277 if ( ! $value ) {
238 - throw new Exception( 'Empty statistic time', 'learnpress' );
278 + throw new Exception( __( 'Empty statistic time', 'learnpress' ) );
239 279 }
240 280 switch ( $type ) {
241 281 case 'date':
242 282 $filter = $this->date_filter( $filter, $value, $time_field, $is_until );
@@ -255,9 +295,9 @@
255 295 break;
256 296 case 'custom':
257 297 $value = explode( '+', $value );
258 298 if ( count( $value ) !== 2 ) {
259 - throw new Exception( 'Invalid custom time', 'learnpress' );
299 + throw new Exception( __( 'Invalid custom time', 'learnpress' ) );
260 300 }
261 301 $filter = $this->custom_time_filter( $filter, $value, $time_field, $is_until );
262 302 default:
263 303 // code...
@@ -266,15 +306,19 @@
266 306 return $filter;
267 307 }
268 308 /**
269 309 * format return data foreach type of filter
270 - * @param LP_Filter $filter
271 - * @param string $type date|month|year|previous_days|custom
272 - * @param string $time_field datetime colummn
273 - * @param boolean $value value to query datetimes
310 + *
311 + * @param LP_Filter $filter
312 + * @param string $type date|month|year|previous_days|custom
313 + * @param string $time_field datetime colummn
314 + * @param boolean $value value to query datetimes
315 + * @param string|null $granularity explicit chart resolution ( hour|day|month ) from PeriodResolver;
316 + * only honored for the custom type — named legacy types keep their
317 + * historical grouping so existing output never changes. @since 4.4.2
274 318 * @return LP_Filter
275 319 */
276 - public function chart_filter_group_by( LP_Filter $filter, string $type, string $time_field, $value = false ) {
320 + public function chart_filter_group_by( LP_Filter $filter, string $type, string $time_field, $value = false, ?string $granularity = null ) {
277 321 switch ( $type ) {
278 322 case 'date':
279 323 $filter = $this->chart_filter_date_group_by( $filter, $time_field );
280 324 break;
@@ -290,14 +334,18 @@
290 334 case 'previous_months':
291 335 $filter = $this->chart_filter_previous_months_group_by( $filter, $time_field );
292 336 break;
293 337 case 'custom':
338 + if ( $granularity ) {
339 + $filter = $this->chart_filter_granularity_group_by( $filter, $granularity, $time_field );
340 + break;
341 + }
294 342 if ( empty( $value ) ) {
295 - throw new Exception( 'Empty statistic time', 'learnpress' );
343 + throw new Exception( __( 'Empty statistic time', 'learnpress' ) );
296 344 }
297 345 $value = explode( '+', $value );
298 346 if ( count( $value ) !== 2 ) {
299 - throw new Exception( 'Invalid custom time', 'learnpress' );
347 + throw new Exception( __( 'Invalid custom time', 'learnpress' ) );
300 348 }
301 349 $filter = $this->chart_filter_custom_group_by( $filter, $value, $time_field );
302 350 default:
303 351 // code...
@@ -306,32 +354,65 @@
306 354 return $filter;
307 355 }
308 356
309 357 /**
358 + * Group a custom-range chart by an explicit granularity instead of the
359 + * span heuristics of chart_filter_custom_group_by().
360 + *
361 + * hour → HOUR( field ), day → CAST( field AS DATE ), month → 'mm-YYYY' —
362 + * the same label shapes the date/previous_days/previous_months groupings
363 + * produce, so the controller processors handle them unchanged.
364 + *
365 + * @param LP_Filter $filter
366 + * @param string $granularity hour|day|month
367 + * @param string $time_field datetime column
368 + * @return LP_Filter
369 + * @since 4.4.2
370 + */
371 + public function chart_filter_granularity_group_by( LP_Filter $filter, string $granularity, string $time_field ) {
372 + switch ( $granularity ) {
373 + case 'hour':
374 + return $this->chart_filter_date_group_by( $filter, $time_field );
375 + case 'month':
376 + return $this->chart_filter_previous_months_group_by( $filter, $time_field );
377 + case 'day':
378 + default:
379 + return $this->chart_filter_previous_days_group_by( $filter, $time_field );
380 + }
381 + }
382 +
383 + /**
310 384 * get_completed_order_data use this for complete order report chart
311 - * @param string $type time type filter: date|month|year|previous_days|custom
312 - * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom
385 + *
386 + * @param string $type time type filter: date|month|year|previous_days|custom
387 + * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom
388 + * @param StatisticsScope $scope optional instructor/category scope. @since 4.4.2
389 + * @param string|null $granularity explicit chart resolution ( hour|day|month ), custom type only. @since 4.4.2
313 390 * @return array completed order data
314 391 */
315 - public function get_completed_order_data( string $type, string $value ) {
392 + public function get_completed_order_data( string $type, string $value, ?StatisticsScope $scope = null, ?string $granularity = null ) {
316 393 if ( ! $type || ! $value ) {
317 - return [];
394 + return array();
318 395 }
319 396 $filter = new LP_Order_Filter();
320 397 $filter->collection = $this->tb_posts;
321 - $filter->collection_alias = "p";
322 - $time_field = "p.post_date";
398 + $filter->collection_alias = 'p';
399 + $time_field = 'p.post_date';
323 400
324 401 // count completed orders
325 - $filter->only_fields[] = "count( p.ID) as x_data";
402 + $filter->only_fields[] = 'count( p.ID) as x_data';
326 403 $filter = $this->filter_time( $filter, $type, $time_field, $value );
327 - $filter = $this->chart_filter_group_by( $filter, $type, $time_field, $value );
328 - $filter->where[] = $this->wpdb->prepare( "AND p.post_status=%s", LP_ORDER_COMPLETED_DB );
329 - $filter->where[] = $this->wpdb->prepare( "AND p.post_type=%s", $filter->post_type );
404 + $filter = $this->chart_filter_group_by( $filter, $type, $time_field, $value, $granularity );
405 + $filter->where[] = $this->wpdb->prepare( 'AND p.post_status=%s', LP_ORDER_COMPLETED_DB );
406 + $filter->where[] = $this->wpdb->prepare( 'AND p.post_type=%s', $filter->post_type );
330 407 $filter->limit = -1;
331 408 $filter->order_by = $time_field;
332 - $filter->order = "asc";
409 + $filter->order = 'asc';
333 410
411 + if ( $scope && ! $scope->is_empty() ) {
412 + $filter = $scope->apply_to_orders( $filter, 'p.ID' );
413 + }
414 +
334 415 $filter->run_query_count = false;
335 416 $result = $this->execute( $filter );
336 417
337 418 return $result;
@@ -338,18 +419,19 @@
338 419 }
339 420
340 421 /**
341 422 * query to count LP Orders with all statuses
423 + *
342 424 * @param LP_Order_Filter $filter
343 425 * @return LP_Order_Filter
344 426 */
345 427 public function filter_order_count_statics( LP_Order_Filter $filter ) {
346 428 // $filter->query_count = true;
347 - $filter->only_fields[] = "count( p.ID) as count_order";
429 + $filter->only_fields[] = 'count( p.ID) as count_order';
348 430 $filter->only_fields[] = "REPLACE(p.post_status,'lp-','') as order_status";
349 - $filter->group_by = "p.post_status";
431 + $filter->group_by = 'p.post_status';
350 432 $filter->where[] = $this->wpdb->prepare( "AND p.post_status LIKE CONCAT(%s,'%')", 'lp-' );
351 - $filter->where[] = $this->wpdb->prepare( "AND p.post_type=%s", $filter->post_type );
433 + $filter->where[] = $this->wpdb->prepare( 'AND p.post_type=%s', $filter->post_type );
352 434 $filter->run_query_count = false;
353 435
354 436 return $filter;
355 437 }
@@ -354,24 +436,29 @@
354 436 return $filter;
355 437 }
356 438 /**
357 439 * get LP Order count of a filter time
358 - * @param string $type date|month|year|previous_days|custom
359 - * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom
440 + *
441 + * @param string $type date|month|year|previous_days|custom
442 + * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom
443 + * @param StatisticsScope $scope optional instructor/category scope. @since 4.4.2
360 444 * @return array result of LP Order count foreach status
361 445 */
362 - public function get_order_statics( string $type, string $value ) {
446 + public function get_order_statics( string $type, string $value, ?StatisticsScope $scope = null ) {
363 447 if ( ! $type || ! $value ) {
364 448 return;
365 449 }
366 450 $filter = new LP_Order_Filter();
367 451 $filter->collection = $this->tb_posts;
368 - $filter->collection_alias = "p";
369 - $time_field = "p.post_date";
452 + $filter->collection_alias = 'p';
453 + $time_field = 'p.post_date';
370 454 $filter = $this->filter_time( $filter, $type, $time_field, $value );
371 455 $filter = $this->filter_order_count_statics( $filter );
372 - $filter->limit = -1;
373 - $result = $this->execute( $filter );
456 + if ( $scope && ! $scope->is_empty() ) {
457 + $filter = $scope->apply_to_orders( $filter, 'p.ID' );
458 + }
459 + $filter->limit = -1;
460 + $result = $this->execute( $filter );
374 461
375 462 return $result;
376 463 }
377 464 /*Overviews statistics*/
@@ -376,41 +463,47 @@
376 463 }
377 464 /*Overviews statistics*/
378 465 /**
379 466 * get sales amount of complete order
380 - * @param string $type [time type filter: date|month|year|previous_days|custom]
381 - * @param string $value [time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom ]
467 + *
468 + * @param string $type [time type filter: date|month|year|previous_days|custom]
469 + * @param string $value [time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom ]
470 + * @param StatisticsScope $scope optional instructor/category scope. @since 4.4.2
471 + * @param string|null $granularity explicit chart resolution ( hour|day|month ), custom type only. @since 4.4.2
382 472 * @return array completed order data
383 473 */
384 - public function get_net_sales_data( string $type, string $value ) {
474 + public function get_net_sales_data( string $type, string $value, ?StatisticsScope $scope = null, ?string $granularity = null ) {
385 475 if ( ! $type || ! $value ) {
386 - return [];
476 + return array();
387 477 }
388 478 $filter = new LP_Order_Filter();
389 479 $filter->collection = $this->tb_posts;
390 - $filter->collection_alias = "p";
480 + $filter->collection_alias = 'p';
391 481 $oi_table = $this->tb_lp_order_items;
392 482 $oim_table = $this->tb_lp_order_itemmeta;
393 483 // net sales summary
394 - $filter->only_fields[] = "SUM(CAST(oim.meta_value AS DECIMAL(10,2))) as x_data";
395 - $time_field = "p.post_date";
396 - $filter->join = [
484 + $filter->only_fields[] = 'SUM(CAST(oim.meta_value AS DECIMAL(10,2))) as x_data';
485 + $time_field = 'p.post_date';
486 + $filter->join = array(
397 487 "INNER JOIN $oi_table AS oi ON p.ID = oi.order_id",
398 488 "INNER JOIN $oim_table AS oim ON oi.order_item_id = oim.learnpress_order_item_id",
399 - ];
400 - $filter->limit = -1;
401 - $filter->where = [
402 - $this->wpdb->prepare( "AND p.post_type=%s", $filter->post_type ),
403 - $this->wpdb->prepare( "AND p.post_status=%s", LP_ORDER_COMPLETED_DB ),
404 - $this->wpdb->prepare( "AND oim.meta_key=%s", '_total' ),
405 - ];
406 - $filter = $this->filter_time( $filter, $type, $time_field, $value );
407 - $filter = $this->chart_filter_group_by( $filter, $type, $time_field, $value );
489 + );
490 + $filter->limit = -1;
491 + $filter->where = array(
492 + $this->wpdb->prepare( 'AND p.post_type=%s', $filter->post_type ),
493 + $this->wpdb->prepare( 'AND p.post_status=%s', LP_ORDER_COMPLETED_DB ),
494 + $this->wpdb->prepare( 'AND oim.meta_key=%s', '_total' ),
495 + );
496 + $filter = $this->filter_time( $filter, $type, $time_field, $value );
497 + $filter = $this->chart_filter_group_by( $filter, $type, $time_field, $value, $granularity );
498 + if ( $scope && ! $scope->is_empty() ) {
499 + $filter = $scope->apply( $filter, 'oi.item_id' );
500 + }
408 501 $filter->order_by = $time_field;
409 - $filter->order = "asc";
502 + $filter->order = 'asc';
410 503 $filter->run_query_count = false;
411 -
412 - $result = $this->execute( $filter );
504 +
505 + $result = $this->execute( $filter );
413 506 // error_log( $this->check_execute_has_error() );
414 507 return $result;
415 508 }
416 509
@@ -415,26 +508,28 @@
415 508 }
416 509
417 510 /**
418 511 * get top categories of sold course
419 - * @param string $type date|month|year|previous_days|custom
420 - * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom
421 - * @param integer $limit limit of query, default is 10
422 - * @param boolean $exclude_free_course exclude free course
512 + *
513 + * @param string $type date|month|year|previous_days|custom
514 + * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom
515 + * @param integer $limit limit of query, default is 10
516 + * @param boolean $exclude_free_course exclude free course
517 + * @param StatisticsScope $scope optional instructor/category scope. @since 4.4.2
423 518 * @return array return term_id and term_count
424 519 */
425 - public function get_top_sold_categories( string $type, string $value, $limit = 0, $exclude_free_course = false ) {
520 + public function get_top_sold_categories( string $type, string $value, $limit = 0, $exclude_free_course = false, ?StatisticsScope $scope = null ) {
426 521 if ( ! $type || ! $value ) {
427 522 return;
428 523 }
429 524 $filter = new LP_Order_Filter();
430 525 $filter->collection = $this->tb_posts;
431 - $filter->collection_alias = "p";
432 - $filter->only_fields[] = "r_term.term_taxonomy_id as term_id";
433 - $filter->only_fields[] = "SUM(CAST(oim_qty.meta_value AS UNSIGNED)) as term_count";
434 - $filter->only_fields[] = "terms.name as term_name";
526 + $filter->collection_alias = 'p';
527 + $filter->only_fields[] = 'r_term.term_taxonomy_id as term_id';
528 + $filter->only_fields[] = 'SUM(CAST(oim_qty.meta_value AS UNSIGNED)) as term_count';
529 + $filter->only_fields[] = 'terms.name as term_name';
435 530 $filter->limit = $limit > 0 ? $limit : 10;
436 - $time_field = "p.post_date";
531 + $time_field = 'p.post_date';
437 532 $tb_term_relationships = $this->tb_term_relationships;
438 533 $tb_term_taxonomy = $this->tb_term_taxonomy;
439 534 $tb_terms = $this->tb_terms;
440 535 $oi_table = $this->tb_lp_order_items;
@@ -439,29 +534,32 @@
439 534 $tb_terms = $this->tb_terms;
440 535 $oi_table = $this->tb_lp_order_items;
441 536 $oim_table = $this->tb_lp_order_itemmeta;
442 537
443 - $filter->join = [
538 + $filter->join = array(
444 539 "INNER JOIN $oi_table AS oi ON p.ID = oi.order_id",
445 540 "INNER JOIN $tb_term_relationships AS r_term ON oi.item_id = r_term.object_id",
446 541 "INNER JOIN $tb_term_taxonomy AS tax_term ON tax_term.term_taxonomy_id = r_term.term_taxonomy_id",
447 542 "INNER JOIN $tb_terms AS terms ON terms.term_id = r_term.term_taxonomy_id",
448 543 "INNER JOIN $oim_table AS oim_qty ON oi.order_item_id = oim_qty.learnpress_order_item_id AND oim_qty.meta_key = '_quantity'",
449 - ];
544 + );
450 545 if ( $exclude_free_course ) {
451 546 $filter->join[] = "INNER JOIN $oim_table AS oim_total ON oi.order_item_id = oim_total.learnpress_order_item_id AND oim_total.meta_key = '_total' AND CAST(oim_total.meta_value AS DECIMAL(10,2)) > 0";
452 547 }
453 548
454 549 $filter->where = array(
455 - $this->wpdb->prepare( "AND p.post_type=%s", $filter->post_type ),
456 - $this->wpdb->prepare( "AND p.post_status=%s", LP_ORDER_COMPLETED_DB ),
457 - $this->wpdb->prepare( "AND oi.item_type=%s", LP_COURSE_CPT ),
458 - $this->wpdb->prepare( "AND tax_term.taxonomy=%s", LP_COURSE_CATEGORY_TAX ),
550 + $this->wpdb->prepare( 'AND p.post_type=%s', $filter->post_type ),
551 + $this->wpdb->prepare( 'AND p.post_status=%s', LP_ORDER_COMPLETED_DB ),
552 + $this->wpdb->prepare( 'AND oi.item_type=%s', LP_COURSE_CPT ),
553 + $this->wpdb->prepare( 'AND tax_term.taxonomy=%s', LP_COURSE_CATEGORY_TAX ),
459 554 );
460 - $filter = $this->filter_time( $filter, $type, $time_field, $value );
461 - $filter->group_by = "term_id";
462 - $filter->order_by = "term_count";
463 - $filter->order = "DESC";
555 + $filter = $this->filter_time( $filter, $type, $time_field, $value );
556 + if ( $scope && ! $scope->is_empty() ) {
557 + $filter = $scope->apply( $filter, 'oi.item_id' );
558 + }
559 + $filter->group_by = 'term_id';
560 + $filter->order_by = 'term_count';
561 + $filter->order = 'DESC';
464 562 $filter->run_query_count = false;
465 563 $result = $this->execute( $filter );
466 564
467 565 return $result;
@@ -468,15 +566,17 @@
468 566 }
469 567
470 568 /**
471 569 * get top courses was sold in the filter
472 - * @param string $type date|month|year|previous_days|custom
473 - * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom
474 - * @param integer $limit limit of query, default 10
475 - * @param boolean $exclude_free_course exclude free course, get result only purchase course
570 + *
571 + * @param string $type date|month|year|previous_days|custom
572 + * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom
573 + * @param integer $limit limit of query, default 10
574 + * @param boolean $exclude_free_course exclude free course, get result only purchase course
575 + * @param StatisticsScope $scope optional instructor/category scope. @since 4.4.2
476 576 * @return array $result
477 577 */
478 - public function get_top_sold_courses( string $type, string $value, $limit = 0, $exclude_free_course = false ) {
578 + public function get_top_sold_courses( string $type, string $value, $limit = 0, $exclude_free_course = false, ?StatisticsScope $scope = null ) {
479 579 if ( ! $type || ! $value ) {
480 580 return;
481 581 }
482 582 $filter = new LP_Order_Filter();
@@ -481,22 +581,22 @@
481 581 }
482 582 $filter = new LP_Order_Filter();
483 583 $tb_posts = $this->tb_posts;
484 584 $filter->collection = $tb_posts;
485 - $filter->collection_alias = "p";
486 - $filter->only_fields[] = "oi.item_id as course_id";
487 - $filter->only_fields[] = "SUM(CAST(oim_qty.meta_value AS UNSIGNED)) as course_count";
488 - $filter->only_fields[] = "p2.post_title as course_name";
585 + $filter->collection_alias = 'p';
586 + $filter->only_fields[] = 'oi.item_id as course_id';
587 + $filter->only_fields[] = 'SUM(CAST(oim_qty.meta_value AS UNSIGNED)) as course_count';
588 + $filter->only_fields[] = 'p2.post_title as course_name';
489 589 $filter->limit = $limit > 0 ? $limit : 10;
490 - $time_field = "p.post_date";
590 + $time_field = 'p.post_date';
491 591 $oi_table = $this->tb_lp_order_items;
492 592 $oim_table = $this->tb_lp_order_itemmeta;
493 593
494 - $filter->join = [
594 + $filter->join = array(
495 595 "INNER JOIN $oi_table AS oi ON p.ID = oi.order_id",
496 596 "INNER JOIN $tb_posts AS p2 ON p2.ID = oi.item_id",
497 597 "INNER JOIN $oim_table AS oim_qty ON oi.order_item_id = oim_qty.learnpress_order_item_id AND oim_qty.meta_key = '_quantity'",
498 - ];
598 + );
499 599
500 600 if ( $exclude_free_course ) {
501 601 $filter->join[] = "INNER JOIN $oim_table AS oim_total ON oi.order_item_id = oim_total.learnpress_order_item_id AND oim_total.meta_key = '_total' AND CAST(oim_total.meta_value AS DECIMAL(10,2)) > 0";
502 602 }
@@ -501,17 +601,20 @@
501 601 $filter->join[] = "INNER JOIN $oim_table AS oim_total ON oi.order_item_id = oim_total.learnpress_order_item_id AND oim_total.meta_key = '_total' AND CAST(oim_total.meta_value AS DECIMAL(10,2)) > 0";
502 602 }
503 603
504 604 $filter->where = array(
505 - $this->wpdb->prepare( "AND p.post_type=%s", $filter->post_type ),
506 - $this->wpdb->prepare( "AND p.post_status=%s", LP_ORDER_COMPLETED_DB ),
507 - $this->wpdb->prepare( "AND oi.item_type=%s", LP_COURSE_CPT ),
605 + $this->wpdb->prepare( 'AND p.post_type=%s', $filter->post_type ),
606 + $this->wpdb->prepare( 'AND p.post_status=%s', LP_ORDER_COMPLETED_DB ),
607 + $this->wpdb->prepare( 'AND oi.item_type=%s', LP_COURSE_CPT ),
508 608 );
509 609
510 - $filter = $this->filter_time( $filter, $type, $time_field, $value );
511 - $filter->group_by = "course_id";
512 - $filter->order_by = "course_count";
513 - $filter->order = "DESC";
610 + $filter = $this->filter_time( $filter, $type, $time_field, $value );
611 + if ( $scope && ! $scope->is_empty() ) {
612 + $filter = $scope->apply( $filter, 'oi.item_id' );
613 + }
614 + $filter->group_by = 'course_id';
615 + $filter->order_by = 'course_count';
616 + $filter->order = 'DESC';
514 617 $filter->run_query_count = false;
515 618 $result = $this->execute( $filter );
516 619
517 620 return $result;
@@ -517,24 +620,28 @@
517 620 return $result;
518 621 }
519 622 /**
520 623 * Overviews get total courses was created ( all statuses )
521 - * @param string $type date|month|year|previous_days|custom
522 - * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string
624 + *
625 + * @param string $type date|month|year|previous_days|custom
626 + * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string
523 627 * @return int $result course count
524 628 */
525 - public function get_total_course_created( string $type, string $value ) {
629 + public function get_total_course_created( string $type, string $value, ?StatisticsScope $scope = null ) {
526 630 if ( ! $type || ! $value ) {
527 631 return;
528 632 }
529 633 $filter = new LP_Course_Filter();
530 634 $filter->collection = $this->tb_posts;
531 - $filter->collection_alias = "p";
532 - $filter->only_fields[] = "p.ID";
533 - $time_field = "p.post_date";
635 + $filter->collection_alias = 'p';
636 + $filter->only_fields[] = 'p.ID';
637 + $time_field = 'p.post_date';
534 638
535 - $filter->where[] = $this->wpdb->prepare( "AND p.post_type=%s", LP_COURSE_CPT );
536 - $filter = $this->filter_time( $filter, $type, $time_field, $value );
639 + $filter->where[] = $this->wpdb->prepare( 'AND p.post_type=%s', LP_COURSE_CPT );
640 + $filter = $this->filter_time( $filter, $type, $time_field, $value );
641 + if ( $scope && ! $scope->is_empty() ) {
642 + $filter = $scope->apply( $filter, 'p.ID' );
643 + }
537 644 $filter->query_count = true;
538 645 $result = $this->execute( $filter );
539 646 return $result;
540 647 }
@@ -539,25 +646,29 @@
539 646 return $result;
540 647 }
541 648 /**
542 649 * Overviews get total orders was created ( all statuses )
543 - * @param string $type date|month|year|previous_days|custom
544 - * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string
650 + *
651 + * @param string $type date|month|year|previous_days|custom
652 + * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string
545 653 * @return int $result order count
546 654 */
547 - public function get_total_order_created( string $type, string $value ) {
655 + public function get_total_order_created( string $type, string $value, ?StatisticsScope $scope = null ) {
548 656 if ( ! $type || ! $value ) {
549 657 return;
550 658 }
551 659 $filter = new LP_Course_Filter();
552 660 $filter->collection = $this->tb_posts;
553 - $filter->collection_alias = "p";
554 - $filter->only_fields[] = "p.ID";
555 - $time_field = "p.post_date";
661 + $filter->collection_alias = 'p';
662 + $filter->only_fields[] = 'p.ID';
663 + $time_field = 'p.post_date';
556 664
557 - $filter->where[] = $this->wpdb->prepare( "AND p.post_type = %s", LP_ORDER_CPT );
558 - $filter->where[] = $this->wpdb->prepare( "AND p.post_status != %s", 'auto-draft' );
559 - $filter = $this->filter_time( $filter, $type, $time_field, $value );
665 + $filter->where[] = $this->wpdb->prepare( 'AND p.post_type = %s', LP_ORDER_CPT );
666 + $filter->where[] = $this->wpdb->prepare( 'AND p.post_status != %s', 'auto-draft' );
667 + $filter = $this->filter_time( $filter, $type, $time_field, $value );
668 + if ( $scope && ! $scope->is_empty() ) {
669 + $filter = $scope->apply_to_orders( $filter, 'p.ID' );
670 + }
560 671 $filter->query_count = true;
561 672 $result = $this->execute( $filter );
562 673 return $result;
563 674 }
@@ -562,10 +673,11 @@
562 673 return $result;
563 674 }
564 675 /**
565 676 * Overviews get total instructors was created ( administrator and lp_teacher )
566 - * @param string $type date|month|year|previous_days|custom
567 - * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string
677 + *
678 + * @param string $type date|month|year|previous_days|custom
679 + * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string
568 680 * @return int $result user count
569 681 */
570 682 public function get_total_instructor_created( string $type, string $value ) {
571 683 if ( ! $type || ! $value ) {
@@ -572,16 +684,16 @@
572 684 return;
573 685 }
574 686 $filter = new LP_Filter();
575 687 $filter->collection = $this->wpdb->users;
576 - $filter->collection_alias = "u";
577 - $filter->only_fields[] = "u.ID";
688 + $filter->collection_alias = 'u';
689 + $filter->only_fields[] = 'u.ID';
578 690 $usermeta_table = $this->wpdb->usermeta;
579 691 $filter->join[] = "INNER JOIN $usermeta_table AS um ON um.user_id = u.ID";
580 - $time_field = "u.user_registered";
581 - $filter->where[] = $this->wpdb->prepare( "AND um.meta_key=%s", $this->wpdb->prefix . 'capabilities' );
582 - $filter->where[] = $this->wpdb->prepare( "AND um.meta_value LIKE %s", '%' . ADMIN_ROLE . '%' );
583 - $filter->where[] = $this->wpdb->prepare( "OR um.meta_value LIKE %s", '%' . LP_TEACHER_ROLE . '%' );
692 + $time_field = 'u.user_registered';
693 + $filter->where[] = $this->wpdb->prepare( 'AND um.meta_key=%s', $this->wpdb->prefix . 'capabilities' );
694 + $filter->where[] = $this->wpdb->prepare( 'AND um.meta_value LIKE %s', '%' . ADMIN_ROLE . '%' );
695 + $filter->where[] = $this->wpdb->prepare( 'OR um.meta_value LIKE %s', '%' . LP_TEACHER_ROLE . '%' );
584 696 $filter = $this->filter_time( $filter, $type, $time_field, $value, true );
585 697 $filter->query_count = true;
586 698 $result = $this->execute( $filter );
587 699 return $result;
@@ -587,10 +699,11 @@
587 699 return $result;
588 700 }
589 701 /**
590 702 * Overviews get total student was created ( subscriber )
591 - * @param string $type date|month|year|previous_days|custom
592 - * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string
703 + *
704 + * @param string $type date|month|year|previous_days|custom
705 + * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string
593 706 * @return int $result user count
594 707 */
595 708 public function get_total_student_created( string $type, string $value ) {
596 709 if ( ! $type || ! $value ) {
@@ -597,15 +710,15 @@
597 710 return;
598 711 }
599 712 $filter = new LP_Filter();
600 713 $filter->collection = $this->wpdb->users;
601 - $filter->collection_alias = "u";
602 - $filter->only_fields[] = "u.ID";
714 + $filter->collection_alias = 'u';
715 + $filter->only_fields[] = 'u.ID';
603 716 $usermeta_table = $this->wpdb->usermeta;
604 717 $filter->join[] = "INNER JOIN $usermeta_table AS um ON um.user_id = u.ID";
605 - $time_field = "u.user_registered";
606 - $filter->where[] = $this->wpdb->prepare( "AND um.meta_key=%s", $this->wpdb->prefix . 'capabilities' );
607 - $filter->where[] = $this->wpdb->prepare( "AND um.meta_value LIKE %s", '%subscriber%' );
718 + $time_field = 'u.user_registered';
719 + $filter->where[] = $this->wpdb->prepare( 'AND um.meta_key=%s', $this->wpdb->prefix . 'capabilities' );
720 + $filter->where[] = $this->wpdb->prepare( 'AND um.meta_value LIKE %s', '%subscriber%' );
608 721 $filter = $this->filter_time( $filter, $type, $time_field, $value, true );
609 722 $filter->query_count = true;
610 723 $result = $this->execute( $filter );
611 724 return $result;
@@ -613,30 +726,35 @@
613 726 /*Course statistics*/
614 727 /**
615 728 * Gets the published course data.
616 729 *
617 - * @param string $type date|month|year|previous_days|custom
618 - * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom
730 + * @param string $type date|month|year|previous_days|custom
731 + * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom
732 + * @param StatisticsScope $scope optional instructor/category scope. @since 4.4.2
733 + * @param string|null $granularity explicit chart resolution ( hour|day|month ), custom type only. @since 4.4.2
619 734 *
620 735 * @return array The published course data.
621 736 */
622 - public function get_published_course_data( string $type, string $value ) {
737 + public function get_published_course_data( string $type, string $value, ?StatisticsScope $scope = null, ?string $granularity = null ) {
623 738 if ( ! $type || ! $value ) {
624 - return [];
739 + return array();
625 740 }
626 741 $filter = new LP_Course_Filter();
627 742 $filter->collection = $this->tb_posts;
628 - $filter->collection_alias = "p";
629 - $time_field = "p.post_date";
743 + $filter->collection_alias = 'p';
744 + $time_field = 'p.post_date';
630 745 // count published course
631 - $filter->only_fields[] = "count( p.ID) as x_data";
746 + $filter->only_fields[] = 'count( p.ID) as x_data';
632 747 $filter = $this->filter_time( $filter, $type, $time_field, $value );
633 - $filter = $this->chart_filter_group_by( $filter, $type, $time_field, $value );
634 - $filter->where[] = $this->wpdb->prepare( "AND p.post_status=%s", 'publish' );
635 - $filter->where[] = $this->wpdb->prepare( "AND p.post_type=%s", $filter->post_type );
636 - $filter->limit = -1;
637 - $filter->order_by = $time_field;
638 - $filter->order = 'asc';
748 + $filter = $this->chart_filter_group_by( $filter, $type, $time_field, $value, $granularity );
749 + if ( $scope && ! $scope->is_empty() ) {
750 + $filter = $scope->apply( $filter, 'p.ID' );
751 + }
752 + $filter->where[] = $this->wpdb->prepare( 'AND p.post_status=%s', 'publish' );
753 + $filter->where[] = $this->wpdb->prepare( 'AND p.post_type=%s', $filter->post_type );
754 + $filter->limit = -1;
755 + $filter->order_by = $time_field;
756 + $filter->order = 'asc';
639 757
640 758 $filter->run_query_count = false;
641 759 $result = $this->execute( $filter );
642 760
@@ -644,37 +762,40 @@
644 762 }
645 763 /**
646 764 * Gets the course count by statuses.
647 765 *
648 - * @param string $type date|month|year|previous_days|custom
649 - * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom
766 + * @param string $type date|month|year|previous_days|custom
767 + * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom
650 768 *
651 769 * @return array $result The course count by statuses.
652 770 */
653 - public function get_course_count_by_statuses( string $type, string $value ) {
771 + public function get_course_count_by_statuses( string $type, string $value, ?StatisticsScope $scope = null ) {
654 772 if ( ! $type || ! $value ) {
655 - return [];
773 + return array();
656 774 }
657 775 $filter = new LP_Course_Filter();
658 776 $filter->collection = $this->tb_posts;
659 - $filter->collection_alias = "p";
660 - $filter->only_fields[] = "COUNT(p.ID) as course_count";
661 - $filter->only_fields[] = "p.post_status as course_status";
662 - $time_field = "p.post_date";
777 + $filter->collection_alias = 'p';
778 + $filter->only_fields[] = 'COUNT(p.ID) as course_count';
779 + $filter->only_fields[] = 'p.post_status as course_status';
780 + $time_field = 'p.post_date';
663 781 $filter = $this->filter_time( $filter, $type, $time_field, $value );
664 - $filter->where[] = $this->wpdb->prepare( "AND p.post_type=%s", $filter->post_type );
665 - $filter->where[] = $this->wpdb->prepare( 'AND p.post_status IN (%s, %s, %s)', 'publish', 'pending', 'future' );
666 - $filter->limit = -1;
667 - $filter->group_by = 'p.post_status';
668 - $filter->run_query_count = false;
669 - $result = $this->execute( $filter );
782 + if ( $scope && ! $scope->is_empty() ) {
783 + $filter = $scope->apply( $filter, 'p.ID' );
784 + }
785 + $filter->where[] = $this->wpdb->prepare( 'AND p.post_type=%s', $filter->post_type );
786 + $filter->where[] = $this->wpdb->prepare( 'AND p.post_status IN (%s, %s, %s)', 'publish', 'pending', 'future' );
787 + $filter->limit = -1;
788 + $filter->group_by = 'p.post_status';
789 + $filter->run_query_count = false;
790 + $result = $this->execute( $filter );
670 791 return $result;
671 792 }
672 793 /**
673 794 * Gets the course items count.
674 795 *
675 - * @param string $type date|month|year|previous_days|custom
676 - * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom
796 + * @param string $type date|month|year|previous_days|custom
797 + * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom
677 798 *
678 799 * @return int $result The course items count.
679 800 */
680 801 public function get_course_items_count( string $type, string $value ) {
@@ -682,12 +803,12 @@
682 803 return;
683 804 }
684 805 $filter = new LP_Filter();
685 806 $filter->collection = $this->tb_posts;
686 - $filter->collection_alias = "p";
687 - $filter->only_fields[] = "COUNT(p.ID) as item_count";
688 - $filter->only_fields[] = "p.post_type as item_type";
689 - $time_field = "p.post_date";
807 + $filter->collection_alias = 'p';
808 + $filter->only_fields[] = 'COUNT(p.ID) as item_count';
809 + $filter->only_fields[] = 'p.post_type as item_type';
810 + $time_field = 'p.post_date';
690 811 $filter = $this->filter_time( $filter, $type, $time_field, $value );
691 812 if ( class_exists( 'LP_Assignment' ) ) {
692 813 $filter->where[] = $this->wpdb->prepare( 'AND p.post_type IN (%s, %s, %s)', LP_LESSON_CPT, LP_QUIZ_CPT, LP_ASSIGNMENT_CPT );
693 814 } else {
@@ -703,28 +824,29 @@
703 824 /*User Statistics*/
704 825 /**
705 826 * Gets the user registered data.
706 827 *
707 - * @param string $type date|month|year|previous_days|custom
708 - * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom
828 + * @param string $type date|month|year|previous_days|custom
829 + * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom
830 + * @param string|null $granularity explicit chart resolution ( hour|day|month ), custom type only. @since 4.4.2
709 831 *
710 832 * @return array $result The user registered data.
711 833 */
712 - public function get_user_registered_data( string $type, string $value ) {
834 + public function get_user_registered_data( string $type, string $value, ?string $granularity = null ) {
713 835 if ( ! $type || ! $value ) {
714 - return [];
836 + return array();
715 837 }
716 838 $filter = new LP_Filter();
717 839 $filter->collection = $this->tb_users;
718 - $filter->collection_alias = "u";
719 - $time_field = "u.user_registered";
840 + $filter->collection_alias = 'u';
841 + $time_field = 'u.user_registered';
720 842 // count user_registered
721 - $filter->only_fields[] = "count( u.ID) as x_data";
843 + $filter->only_fields[] = 'count( u.ID) as x_data';
722 844 $filter = $this->filter_time( $filter, $type, $time_field, $value );
723 - $filter = $this->chart_filter_group_by( $filter, $type, $time_field, $value );
845 + $filter = $this->chart_filter_group_by( $filter, $type, $time_field, $value, $granularity );
724 846 $filter->limit = -1;
725 847 $filter->order_by = $time_field;
726 - $filter->order = "asc";
848 + $filter->order = 'asc';
727 849
728 850 $filter->run_query_count = false;
729 851 $result = $this->execute( $filter );
730 852 return $result;
@@ -731,34 +853,39 @@
731 853 }
732 854
733 855 /**
734 856 * Gets the users by user item graduation statuses.
735 - * @param string $type date|month|year|previous_days|custom
736 - * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom
857 + *
858 + * @param string $type date|month|year|previous_days|custom
859 + * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom
737 860 * @return int $result count users by graduation statuses.
738 861 */
739 - public function get_users_by_user_item_graduation_statuses( string $type, string $value ) {
862 + public function get_users_by_user_item_graduation_statuses( string $type, string $value, ?StatisticsScope $scope = null ) {
740 863 if ( ! $type || ! $value ) {
741 864 return;
742 865 }
743 866 $filter = new LP_Filter();
744 867 $filter->collection = $this->tb_lp_user_items;
745 - $filter->collection_alias = "ui";
746 - $filter->only_fields[] = "ui.graduation as graduation_status";
747 - $filter->only_fields[] = "COUNT(distinct(ui.user_id)) as user_count";
748 - $time_field = "ui.start_time";
868 + $filter->collection_alias = 'ui';
869 + $filter->only_fields[] = 'ui.graduation as graduation_status';
870 + $filter->only_fields[] = 'COUNT(distinct(ui.user_id)) as user_count';
871 + $time_field = 'ui.start_time';
749 872 $filter->limit = -1;
750 - $filter->where[] = $this->wpdb->prepare( "AND ui.item_type=%s", LP_COURSE_CPT );
873 + $filter->where[] = $this->wpdb->prepare( 'AND ui.item_type=%s', LP_COURSE_CPT );
751 874 $filter = $this->filter_time( $filter, $type, $time_field, $value );
752 - $filter->group_by = "graduation_status";
753 - $filter->run_query_count = false;
754 - $result = $this->execute( $filter );
875 + if ( $scope && ! $scope->is_empty() ) {
876 + $filter = $scope->apply( $filter, 'ui.item_id' );
877 + }
878 + $filter->group_by = 'graduation_status';
879 + $filter->run_query_count = false;
880 + $result = $this->execute( $filter );
755 881 return $result;
756 882 }
757 883 /**
758 884 * filter user dont study any course in the filter time
759 - * @param string $type date|month|year|previous_days|custom
760 - * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom
885 + *
886 + * @param string $type date|month|year|previous_days|custom
887 + * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom
761 888 * @return int $result count users
762 889 */
763 890 public function get_users_not_started_any_course( string $type, string $value ) {
764 891 if ( ! $type || ! $value ) {
@@ -766,17 +893,17 @@
766 893 }
767 894 $filter = new LP_Filter();
768 895 $table_useritems = $this->tb_lp_user_items;
769 896 $table_user = $this->tb_users;
770 - $time_filter = $this->filter_time( $filter, $type, "ui.start_time", $value );
897 + $time_filter = $this->filter_time( $filter, $type, 'ui.start_time', $value );
771 898 // get time_filter condition SQL
772 899 $time_condition = $time_filter->where[0];
773 900 // reset where
774 901 $filter->where = array();
775 902 $filter->collection = $table_user;
776 - $filter->collection_alias = "u";
777 - $filter->only_fields[] = "u.ID";
778 - $filter->where[] = $this->wpdb->prepare( "AND NOT EXISTS (SELECT * FROM $table_useritems as ui WHERE ui.user_id = u.ID $time_condition)" );
903 + $filter->collection_alias = 'u';
904 + $filter->only_fields[] = 'u.ID';
905 + $filter->where[] = "AND NOT EXISTS (SELECT * FROM $table_useritems as ui WHERE ui.user_id = u.ID $time_condition)";
779 906 $filter->limit = -1;
780 907 $filter->query_count = true;
781 908 // use this to see the sql query
782 909 // $filter->return_string_query= true;
@@ -784,37 +911,42 @@
784 911 return $result;
785 912 }
786 913 /**
787 914 * get top courses was enrolled by users
788 - * @param string $type date|month|year|previous_days|custom
789 - * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom
790 - * @param integer $limit limit of query, default 10
791 - * @param boolean $exclude_free_course exclude free course, get result only purchase course
915 + *
916 + * @param string $type date|month|year|previous_days|custom
917 + * @param string $value time value string "Y-m-d" for date|month|year, int for previous_days, string "Y-m-d+Y-m-d" for custom
918 + * @param integer $limit limit of query, default 10
919 + * @param boolean $exclude_free_course exclude free course, get result only purchase course
920 + * @param StatisticsScope $scope optional instructor/category scope. @since 4.4.2
792 921 * @return array $result
793 922 */
794 - public function get_top_enrolled_courses( string $type, string $value, $limit = 0, $exclude_free_course = false ) {
923 + public function get_top_enrolled_courses( string $type, string $value, $limit = 0, $exclude_free_course = false, ?StatisticsScope $scope = null ) {
795 924 if ( ! $type || ! $value ) {
796 925 return;
797 926 }
798 927 $filter = new LP_Filter();
799 928 $filter->collection = $this->tb_lp_user_items;
800 - $filter->collection_alias = "ui";
801 - $filter->only_fields[] = "ui.item_id as course_id";
802 - $filter->only_fields[] = "COUNT(ui.user_item_id) as enrolled_user";
803 - $filter->only_fields[] = "p.post_author as instructor_id";
804 - $filter->only_fields[] = "p.post_title as course_name";
805 - $filter->only_fields[] = "u.display_name as instructor_name";
929 + $filter->collection_alias = 'ui';
930 + $filter->only_fields[] = 'ui.item_id as course_id';
931 + $filter->only_fields[] = 'COUNT(ui.user_item_id) as enrolled_user';
932 + $filter->only_fields[] = 'p.post_author as instructor_id';
933 + $filter->only_fields[] = 'p.post_title as course_name';
934 + $filter->only_fields[] = 'u.display_name as instructor_name';
806 935 $filter->limit = ! $limit ? 10 : $limit;
807 - $time_field = "ui.start_time";
936 + $time_field = 'ui.start_time';
808 937 $filter->join[] = "INNER JOIN $this->tb_posts AS p ON p.ID = ui.item_id";
809 938 $filter->join[] = "INNER JOIN $this->tb_users AS u ON u.ID = p.post_author";
810 - $filter->where[] = $this->wpdb->prepare( "AND ui.item_type=%s", LP_COURSE_CPT );
939 + $filter->where[] = $this->wpdb->prepare( 'AND ui.item_type=%s', LP_COURSE_CPT );
811 940 $filter = $this->filter_time( $filter, $type, $time_field, $value );
812 - $filter->group_by = "course_id";
813 - $filter->order_by = "enrolled_user";
814 - $filter->order = "DESC";
815 - $filter->run_query_count = false;
816 - $result = $this->execute( $filter );
941 + if ( $scope && ! $scope->is_empty() ) {
942 + $filter = $scope->apply( $filter, 'ui.item_id' );
943 + }
944 + $filter->group_by = 'course_id';
945 + $filter->order_by = 'enrolled_user';
946 + $filter->order = 'DESC';
947 + $filter->run_query_count = false;
948 + $result = $this->execute( $filter );
817 949 return $result;
818 950 }
819 951
820 952 /**
@@ -825,9 +957,9 @@
825 957 *
826 958 * @return array Top sold courses for the instructor.
827 959 * @since 4.3.0
828 960 */
829 - public function get_top_sold_courses_by_instructor( int $instructor_id, int $limit = 5 ): array {
961 + public function get_top_sold_courses_by_instructor( int $instructor_id, int $limit = 5, string $search = '' ): array {
830 962 $tb_posts = $this->tb_posts;
831 963 $oi_table = $this->tb_lp_order_items;
832 964 $oim_table = $this->tb_lp_order_itemmeta;
833 965
@@ -840,15 +972,15 @@
840 972 $filter->only_fields[] = 'u.display_name as instructor_name';
841 973 $filter->only_fields[] = 'SUM(CAST(oim_total.meta_value AS DECIMAL(10,2))) as total_revenue';
842 974 $filter->limit = $limit > 0 ? $limit : 5;
843 975
844 - $filter->join = [
976 + $filter->join = array(
845 977 "INNER JOIN $oi_table AS oi ON p.ID = oi.order_id",
846 978 "INNER JOIN $tb_posts AS p2 ON p2.ID = oi.item_id",
847 979 "INNER JOIN {$this->tb_users} AS u ON u.ID = p2.post_author",
848 980 "INNER JOIN $oim_table AS oim_qty ON oi.order_item_id = oim_qty.learnpress_order_item_id AND oim_qty.meta_key = '_quantity'",
849 981 "INNER JOIN $oim_table AS oim_total ON oi.order_item_id = oim_total.learnpress_order_item_id AND oim_total.meta_key = '_total'",
850 - ];
982 + );
851 983
852 984 $filter->where = array(
853 985 $this->wpdb->prepare( 'AND p.post_type=%s', $filter->post_type ),
854 986 $this->wpdb->prepare( 'AND p.post_status=%s', LP_ORDER_COMPLETED_DB ),
@@ -858,8 +990,13 @@
858 990 if ( $instructor_id > 0 ) {
859 991 $filter->where[] = $this->wpdb->prepare( 'AND p2.post_author=%d', $instructor_id );
860 992 }
861 993
994 + $search = trim( $search );
995 + if ( '' !== $search ) {
996 + $filter->where[] = $this->wpdb->prepare( 'AND p2.post_title LIKE %s', '%' . $this->wpdb->esc_like( $search ) . '%' );
997 + }
998 +
862 999 $filter->group_by = 'course_id';
863 1000 $filter->order_by = 'course_count';
864 1001 $filter->order = 'DESC';
865 1002 $filter->run_query_count = false;
@@ -864,9 +1001,9 @@
864 1001 $filter->order = 'DESC';
865 1002 $filter->run_query_count = false;
866 1003 $result = $this->execute( $filter );
867 1004
868 - return is_array( $result ) ? $result : [];
1005 + return is_array( $result ) ? $result : array();
869 1006 }
870 1007
871 1008 /**
872 1009 * Get top enrolled courses by a specific instructor.
@@ -876,9 +1013,9 @@
876 1013 *
877 1014 * @return array Top enrolled courses for the instructor.
878 1015 * @since 4.3.0
879 1016 */
880 - public function get_top_enrolled_courses_by_instructor( int $instructor_id, int $limit = 5 ): array {
1017 + public function get_top_enrolled_courses_by_instructor( int $instructor_id, int $limit = 5, string $search = '' ): array {
881 1018 $filter = new \LP_Filter();
882 1019 $filter->collection = $this->tb_lp_user_items;
883 1020 $filter->collection_alias = 'ui';
884 1021 $filter->only_fields[] = 'ui.item_id as course_id';
@@ -895,8 +1032,13 @@
895 1032 if ( $instructor_id > 0 ) {
896 1033 $filter->where[] = $this->wpdb->prepare( 'AND p.post_author=%d', $instructor_id );
897 1034 }
898 1035
1036 + $search = trim( $search );
1037 + if ( '' !== $search ) {
1038 + $filter->where[] = $this->wpdb->prepare( 'AND p.post_title LIKE %s', '%' . $this->wpdb->esc_like( $search ) . '%' );
1039 + }
1040 +
899 1041 $filter->group_by = 'course_id';
900 1042 $filter->order_by = 'enrollment_count';
901 1043 $filter->order = 'DESC';
902 1044 $filter->run_query_count = false;
@@ -901,9 +1043,9 @@
901 1043 $filter->order = 'DESC';
902 1044 $filter->run_query_count = false;
903 1045 $result = $this->execute( $filter );
904 1046
905 - return is_array( $result ) ? $result : [];
1047 + return is_array( $result ) ? $result : array();
906 1048 }
907 1049
908 1050 /**
909 1051 * Get top instructors by course count and student count.
@@ -946,9 +1088,9 @@
946 1088 );
947 1089
948 1090 $results = $this->wpdb->get_results( $sql );
949 1091
950 - return is_array( $results ) ? $results : [];
1092 + return is_array( $results ) ? $results : array();
951 1093 }
952 1094
953 1095 /**
954 1096 * Get net sales chart data scoped by instructor.
@@ -961,9 +1103,9 @@
961 1103 * @since 4.3.0
962 1104 */
963 1105 public function get_net_sales_data_scoped( string $type, string $value, int $instructor_id = 0 ): array {
964 1106 if ( ! $type || ! $value ) {
965 - return [];
1107 + return array();
966 1108 }
967 1109
968 1110 $filter = new \LP_Order_Filter();
969 1111 $filter->collection = $this->tb_posts;
@@ -970,25 +1112,25 @@
970 1112 $filter->collection_alias = 'p';
971 1113 $oi_table = $this->tb_lp_order_items;
972 1114 $oim_table = $this->tb_lp_order_itemmeta;
973 1115
974 - $filter->only_fields[] = "SUM(CAST(oim.meta_value AS DECIMAL(10,2))) as x_data";
975 - $time_field = "p.post_date";
1116 + $filter->only_fields[] = 'SUM(CAST(oim.meta_value AS DECIMAL(10,2))) as x_data';
1117 + $time_field = 'p.post_date';
976 1118
977 - $filter->join = [
1119 + $filter->join = array(
978 1120 "INNER JOIN $oi_table AS oi ON p.ID = oi.order_id",
979 1121 "INNER JOIN $oim_table AS oim ON oi.order_item_id = oim.learnpress_order_item_id",
980 - ];
1122 + );
981 1123
982 - $filter->where = [
983 - $this->wpdb->prepare( "AND p.post_type=%s", $filter->post_type ),
984 - $this->wpdb->prepare( "AND p.post_status=%s", LP_ORDER_COMPLETED_DB ),
985 - $this->wpdb->prepare( "AND oim.meta_key=%s", '_total' ),
986 - ];
1124 + $filter->where = array(
1125 + $this->wpdb->prepare( 'AND p.post_type=%s', $filter->post_type ),
1126 + $this->wpdb->prepare( 'AND p.post_status=%s', LP_ORDER_COMPLETED_DB ),
1127 + $this->wpdb->prepare( 'AND oim.meta_key=%s', '_total' ),
1128 + );
987 1129
988 1130 if ( $instructor_id > 0 ) {
989 - $filter->join[] = "INNER JOIN {$this->tb_posts} AS p2 ON p2.ID = oi.item_id";
990 - $filter->where[] = $this->wpdb->prepare( "AND p2.post_author=%d", $instructor_id );
1131 + $filter->join[] = "INNER JOIN {$this->tb_posts} AS p2 ON p2.ID = oi.item_id";
1132 + $filter->where[] = $this->wpdb->prepare( 'AND p2.post_author=%d', $instructor_id );
991 1133 }
992 1134
993 1135 $filter->limit = -1;
994 1136 $filter = $this->filter_time( $filter, $type, $time_field, $value );
@@ -993,28 +1135,30 @@
993 1135 $filter->limit = -1;
994 1136 $filter = $this->filter_time( $filter, $type, $time_field, $value );
995 1137 $filter = $this->chart_filter_group_by( $filter, $type, $time_field, $value );
996 1138 $filter->order_by = $time_field;
997 - $filter->order = "asc";
1139 + $filter->order = 'asc';
998 1140 $filter->run_query_count = false;
999 1141
1000 1142 $result = $this->execute( $filter );
1001 - return is_array( $result ) ? $result : [];
1143 + return is_array( $result ) ? $result : array();
1002 1144 }
1003 1145
1004 1146 /**
1005 1147 * Get enrollment chart data scoped by instructor.
1006 1148 *
1007 - * @param string $type Time filter type.
1008 - * @param string $value Time filter value.
1009 - * @param int $instructor_id Instructor ID (0 for all).
1149 + * @param string $type Time filter type.
1150 + * @param string $value Time filter value.
1151 + * @param int $instructor_id Instructor ID (0 for all).
1152 + * @param StatisticsScope $scope optional instructor/category scope. @since 4.4.2
1153 + * @param string|null $granularity explicit chart resolution ( hour|day|month ), custom type only. @since 4.4.2
1010 1154 *
1011 1155 * @return array Enrollment chart data.
1012 1156 * @since 4.3.0
1013 1157 */
1014 - public function get_enrollment_chart_data( string $type, string $value, int $instructor_id = 0 ): array {
1158 + public function get_enrollment_chart_data( string $type, string $value, int $instructor_id = 0, ?StatisticsScope $scope = null, ?string $granularity = null ): array {
1015 1159 if ( ! $type || ! $value ) {
1016 - return [];
1160 + return array();
1017 1161 }
1018 1162
1019 1163 $filter = new \LP_Filter();
1020 1164 $filter->collection = $this->tb_lp_user_items;
@@ -1028,15 +1172,19 @@
1028 1172 $filter->join[] = "INNER JOIN {$this->tb_posts} AS p ON p.ID = ui.item_id";
1029 1173 $filter->where[] = $this->wpdb->prepare( 'AND p.post_author=%d', $instructor_id );
1030 1174 }
1031 1175
1176 + if ( $scope && ! $scope->is_empty() ) {
1177 + $filter = $scope->apply( $filter, 'ui.item_id' );
1178 + }
1179 +
1032 1180 $filter->limit = -1;
1033 1181 $filter = $this->filter_time( $filter, $type, $time_field, $value );
1034 - $filter = $this->chart_filter_group_by( $filter, $type, $time_field, $value );
1182 + $filter = $this->chart_filter_group_by( $filter, $type, $time_field, $value, $granularity );
1035 1183 $filter->order_by = $time_field;
1036 1184 $filter->order = 'asc';
1037 1185 $filter->run_query_count = false;
1038 1186
1039 1187 $result = $this->execute( $filter );
1040 - return is_array( $result ) ? $result : [];
1188 + return is_array( $result ) ? $result : array();
1041 1189 }
1042 1190 }