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-course-db.php +862 -862 4.3.94.4.8 View file →
@@ -1,862 +1,862 @@
1 -<?php
2 -/**
3 - * Class LP_Course_DB
4 - *
5 - * @author tungnx
6 - * @since 3.2.7.5
7 - */
8 -
9 -use LearnPress\Models\CourseModel;
10 -
11 -defined( 'ABSPATH' ) || exit();
12 -
13 -class LP_Course_DB extends LP_Database {
14 - private static $_instance;
15 -
16 - protected function __construct() {
17 - parent::__construct();
18 - }
19 -
20 - public static function getInstance() {
21 - if ( is_null( self::$_instance ) ) {
22 - self::$_instance = new self();
23 - }
24 -
25 - return self::$_instance;
26 - }
27 -
28 - /**
29 - * Get course_id of item
30 - *
31 - * item type lp_lesson, lp_quiz
32 - *
33 - * @param int $item_id
34 - *
35 - * @return int
36 - * @throws Exception
37 - */
38 - public function get_course_by_item_id( $item_id = 0 ): int {
39 - // Get cache
40 - $lp_course_cache = LP_Course_Cache::instance();
41 - $key_cache = "$item_id/course_id_of_item_id";
42 - $course_id = $lp_course_cache->get_cache( $key_cache );
43 -
44 - if ( ! $course_id ) {
45 - $query = $this->wpdb->prepare(
46 - "
47 - SELECT section_course_id
48 - FROM {$this->tb_lp_sections} AS s
49 - INNER JOIN {$this->tb_lp_section_items} AS si
50 - ON si.section_id = s.section_id
51 - WHERE si.item_id = %d",
52 - $item_id
53 - );
54 -
55 - $course_id = (int) $this->wpdb->get_var( $query );
56 -
57 - $this->check_execute_has_error();
58 -
59 - // Set cache
60 - $lp_course_cache->set_cache( $key_cache, $course_id );
61 - }
62 -
63 - return $course_id;
64 - }
65 -
66 - /**
67 - * Get all item ids' course
68 - *
69 - * @param int $course_id
70 - *
71 - * @return array|object|stdClass[]|null
72 - * @throws Exception
73 - * @since 4.1.6.9
74 - * @version 1.0.0
75 - */
76 - public function get_full_sections_and_items_course( int $course_id = 0 ) {
77 - $method_called_to = debug_backtrace()[1]['function'];
78 -
79 - // Check accept call from function 'get_sections_and_items_course_from_db_and_sort'
80 - if ( 'get_sections_and_items_course_from_db_and_sort' !== $method_called_to ) {
81 - error_log( 'You can not call direct this function' );
82 -
83 - return [];
84 - }
85 -
86 - $query = $this->wpdb->prepare(
87 - "SELECT *
88 - FROM {$this->tb_lp_section_items} AS si
89 - INNER JOIN {$this->tb_lp_sections} AS s
90 - ON si.section_id = s.section_id
91 - INNER JOIN {$this->tb_posts} AS p
92 - ON si.item_id = p.ID AND p.post_status = 'publish'
93 - WHERE section_course_id = %d
94 - ORDER BY s.section_order",
95 - $course_id
96 - );
97 -
98 - $sections_items = $this->wpdb->get_results( $query );
99 -
100 - $this->check_execute_has_error();
101 -
102 - return $sections_items;
103 - }
104 -
105 - /**
106 - * Get all sections' course
107 - *
108 - * @param int $course_id
109 - *
110 - * @return array|object|stdClass[]|null
111 - * @throws Exception
112 - * @since 4.1.6.9
113 - * @version 1.0.0
114 - */
115 - public function get_sections( int $course_id = 0 ) {
116 - $method_called_to = debug_backtrace()[1]['function'];
117 -
118 - // Check accept call from function 'get_sections_and_items_course_from_db_and_sort'
119 - if ( 'get_sections_and_items_course_from_db_and_sort' !== $method_called_to ) {
120 - error_log( 'You can not call direct this function' );
121 -
122 - return [];
123 - }
124 -
125 - $query = $this->wpdb->prepare(
126 - "SELECT * FROM {$this->tb_lp_sections}
127 - WHERE section_course_id = %d
128 - ORDER BY section_order",
129 - $course_id
130 - );
131 -
132 - $sections_items = $this->wpdb->get_results( $query, OBJECT_K );
133 -
134 - $this->check_execute_has_error();
135 -
136 - return $sections_items;
137 - }
138 -
139 - /**
140 - * Get user_item_id by order_id, course_id, user_id
141 - *
142 - * @param int $order_id
143 - * @param int $course_id
144 - * @param int $user_id
145 - *
146 - * @return int
147 - * @deprecated 4.2.6.6 not use anywhere
148 - */
149 - public function get_user_item_id( $order_id = 0, $course_id = 0, $user_id = 0 ): int {
150 - _deprecated_function( __METHOD__, '4.2.6.6' );
151 - $query = $this->wpdb->prepare(
152 - "
153 - SELECT user_item_id
154 - FROM {$this->tb_lp_user_items}
155 - WHERE ref_type = %s
156 - AND ref_id = %d
157 - AND item_type = %s
158 - AND item_id = %d
159 - AND user_id = %d
160 - ",
161 - LP_ORDER_CPT,
162 - $order_id,
163 - LP_COURSE_CPT,
164 - $course_id,
165 - $user_id
166 - );
167 -
168 - return (int) $this->wpdb->get_var( $query );
169 - }
170 -
171 - /**
172 - * Get first item id of course
173 - *
174 - * @param int $course_id .
175 - *
176 - * @return int
177 - * @throws Exception
178 - * @since 4.0.0
179 - * @version 1.0.4
180 - * @modify 4.1.3
181 - * @author tungnx
182 - */
183 - public function get_first_item_id( int $course_id = 0 ): int {
184 - $query = $this->wpdb->prepare(
185 - "
186 - SELECT item_id FROM $this->tb_lp_section_items AS si
187 - INNER JOIN $this->tb_lp_sections AS sections
188 - ON si.section_id = sections.section_id
189 - AND sections.section_course_id = %d
190 - INNER JOIN $this->tb_posts AS p
191 - ON si.item_id = p.ID
192 - AND p.post_status = 'publish'
193 - ORDER BY sections.section_order ASC, si.item_order ASC
194 - LIMIT %d
195 - ",
196 - $course_id,
197 - 1
198 - );
199 -
200 - $first_item_id = (int) $this->wpdb->get_var( $query );
201 -
202 - $this->check_execute_has_error();
203 -
204 - return $first_item_id;
205 - }
206 -
207 - public function get_recent_courses( LP_Course_Filter $filter ): array {
208 - global $wpdb;
209 -
210 - $limit = $filter->limit ?? - 1;
211 - $order = ! empty( $filter->order ) ? $filter->order : 'DESC';
212 -
213 - if ( $limit <= 0 ) {
214 - $limit = 0;
215 - }
216 -
217 - $query = apply_filters(
218 - 'learnpress/databases/widgets/recent_courses',
219 - $wpdb->prepare(
220 - "SELECT DISTINCT p.ID
221 - FROM $wpdb->posts AS p
222 - WHERE p.post_type = %s
223 - AND p.post_status = %s
224 - ORDER BY p.post_date {$order}
225 - LIMIT %d",
226 - LP_COURSE_CPT,
227 - 'publish',
228 - $limit
229 - )
230 - );
231 -
232 - return $wpdb->get_col( $query );
233 - }
234 -
235 - public function get_featured_courses( LP_Course_Filter $filter ): array {
236 - global $wpdb;
237 -
238 - $limit = ! empty( $filter->limit ) ? $filter->limit : - 1;
239 - $order_by = ! empty( $filter->order_by ) ? $filter->order_by : 'post_date';
240 - $order = ! empty( $filter->order ) ? $filter->order : 'DESC';
241 -
242 - if ( $limit < 0 ) {
243 - $limit = 0;
244 - }
245 -
246 - $query = apply_filters(
247 - 'learnpress/databases/widgets/featured_courses',
248 - $wpdb->prepare(
249 - "SELECT DISTINCT p.ID
250 - FROM {$wpdb->posts} p
251 - LEFT JOIN {$wpdb->postmeta} as pmeta ON p.ID=pmeta.post_id AND pmeta.meta_key = %s
252 - WHERE p.post_type = %s
253 - AND p.post_status = %s
254 - AND pmeta.meta_value = %s
255 - ORDER BY p.{$order_by} {$order}
256 - LIMIT %d",
257 - '_lp_featured',
258 - LP_COURSE_CPT,
259 - 'publish',
260 - 'yes',
261 - $limit
262 - )
263 - );
264 -
265 - return $wpdb->get_col( $query );
266 - }
267 -
268 - /**
269 - * Get list user ids enrolled by course
270 - *
271 - * @return array|object|null
272 - * @throws Exception
273 - * @version 1.0.0
274 - * @author tungnx
275 - * @since 4.1.3.1
276 - */
277 - public function get_user_ids_enrolled( int $course_id ) {
278 - $query = $this->wpdb->prepare(
279 - "
280 - SELECT DISTINCT user_id FROM {$this->tb_lp_user_items}
281 - WHERE item_id = %d
282 - AND item_type = %s
283 - AND (status = %s OR status = %s )
284 - ",
285 - $course_id,
286 - LP_COURSE_CPT,
287 - 'enrolled',
288 - 'finished'
289 - );
290 -
291 - $result = $this->wpdb->get_results( $query, OBJECT_K );
292 -
293 - $this->check_execute_has_error();
294 -
295 - return $result;
296 - }
297 -
298 - /**
299 - * Count total user enrolled by course
300 - *
301 - * @param int $course_id
302 - *
303 - * @return int
304 - * @throws Exception
305 - * @author tungnx
306 - * @since 4.1.4
307 - * @version 1.0.0
308 - */
309 - public function get_total_user_enrolled( int $course_id ): int {
310 - $filter = new LP_User_Items_Filter();
311 - $filter->only_fields = [ 'DISTINCT(user_id)' ];
312 - $filter->item_id = $course_id;
313 - $filter->item_type = LP_COURSE_CPT;
314 - $filter->field_count = 'ui.user_id';
315 - $filter->join[] = "INNER JOIN {$this->tb_users} AS u ON ui.user_id = u.ID";
316 - $filter->where[] = 'AND ui.user_id > 0';
317 - $filter->where[] = $this->wpdb->prepare( 'AND ( ui.status = %s OR ui.status = %s )', LP_COURSE_ENROLLED, LP_COURSE_FINISHED );
318 - $filter->query_count = true;
319 -
320 - $total = 0;
321 - $lp_user_items_db = LP_User_Items_DB::getInstance();
322 - $lp_user_items_db->get_user_items( $filter, $total );
323 -
324 - return $total;
325 - }
326 -
327 - /**
328 - * Count total user enrolled or purchase by course
329 - *
330 - * @param int $course_id
331 - *
332 - * @return int
333 - * @throws Exception
334 - * @author tungnx
335 - * @since 4.1.4
336 - * @version 1.0.2
337 - */
338 - public function get_total_user_enrolled_or_purchased( int $course_id ): int {
339 - $filter = new LP_User_Items_Filter();
340 - $filter->only_fields = [ 'DISTINCT(user_id) AS user_id' ];
341 - $filter->item_id = $course_id;
342 - $filter->item_type = LP_COURSE_CPT;
343 - $filter->field_count = 'ui.user_id';
344 - $filter->join[] = "INNER JOIN {$this->tb_users} AS u ON ui.user_id = u.ID";
345 - $filter->where[] = 'AND ui.user_id > 0';
346 - $filter->where[] = $this->wpdb->prepare(
347 - 'AND ( ui.status = %s OR ui.status = %s OR ui.status = %s )',
348 - LP_COURSE_ENROLLED,
349 - LP_COURSE_FINISHED,
350 - LP_COURSE_PURCHASED
351 - );
352 - $filter->query_count = true;
353 -
354 - $total = 0;
355 - $lp_user_items_db = LP_User_Items_DB::getInstance();
356 - $lp_user_items_db->get_user_items( $filter, $total );
357 -
358 - return $total;
359 - }
360 -
361 - /**
362 - * Get total items of course
363 - *
364 - * @param int $course_id
365 - *
366 - * @return null|object
367 - * @throws Exception
368 - * @version 1.0.1
369 - * @author tungnx
370 - * @since 4.1.4.1
371 - */
372 - public function get_total_items( int $course_id = 0 ) {
373 - $item_types = CourseModel::item_types_support();
374 - $count_item_types = count( $item_types );
375 - $i = 0;
376 -
377 - $query_count = $this->wpdb->prepare( 'SUM(s.section_course_id = %d) AS count_items,', $course_id );
378 -
379 - foreach ( $item_types as $item_type ) {
380 - ++ $i;
381 - if ( $i == $count_item_types ) {
382 - $query_count .= $this->wpdb->prepare( 'SUM(s.section_course_id = %d AND si.item_type = %s) AS %s', $course_id, $item_type, $item_type );
383 - } else {
384 - $query_count .= $this->wpdb->prepare( 'SUM(s.section_course_id = %d AND si.item_type = %s) AS %s,', $course_id, $item_type, $item_type );
385 - }
386 - }
387 -
388 - $query = "
389 - SELECT $query_count
390 - FROM $this->tb_lp_section_items si
391 - INNER JOIN $this->tb_lp_sections s ON s.section_id = si.section_id
392 - INNER JOIN $this->tb_posts p ON si.item_id = p.ID
393 - AND p.post_status = 'publish'
394 - ";
395 -
396 - $total_items = $this->wpdb->get_row( $query );
397 -
398 - $this->check_execute_has_error();
399 -
400 - return $total_items;
401 - }
402 -
403 - /**
404 - * Count all item are unassigned to any courses.
405 - *
406 - * @param string $item_type (type item Lesson, Quiz, Assignment, H5P ...)
407 - *
408 - * @return int
409 - * @throws Exception
410 - * @author tungnx
411 - * @version 1.0.1
412 - * @since 4.1.4.1
413 - */
414 - public function get_total_item_unassigned( string $item_type ): int {
415 - $filter = new LP_Post_Type_Filter();
416 - $filter->post_type = $item_type;
417 - $filter->query_count = true;
418 - $filter->post_status = array();
419 - $filter->field_count = 'p.ID';
420 -
421 - return $this->get_item_ids_unassigned( $filter );
422 - }
423 -
424 - /**
425 - * list id item are unassigned to any courses.
426 - *
427 - * @param LP_Post_Type_Filter|mixed $filter
428 - *
429 - * @return array|int|string|null
430 - * @throws Exception
431 - * @author tungnx
432 - * @version 1.0.0
433 - * @since 4.1.6
434 - */
435 - public function get_item_ids_unassigned( $filter = null ) {
436 - if ( is_null( $filter ) ) {
437 - $filter = new LP_Post_Type_Filter();
438 - }
439 -
440 - if ( ! $filter instanceof LP_Post_Type_Filter ) {
441 - throw new Exception( 'Filter must be instance of LP_Post_Type_Filter' );
442 - }
443 -
444 - $filter_section_items = new LP_Section_Items_Filter();
445 - $filter_section_items->return_string_query = true;
446 - $filter_section_items->only_fields = array( 'si.item_id' );
447 - $filter_section_items->where[] = $this->wpdb->prepare( 'AND si.item_type = %s', $filter->post_type );
448 - $query_item_ids_assigned = LP_Section_Items_DB::getInstance()->get_section_items( $filter_section_items );
449 -
450 - $filter->only_fields = array( 'p.ID' );
451 - $filter->collection = $this->tb_posts;
452 - $filter->collection_alias = 'p';
453 - $filter->where[] = $this->wpdb->prepare( 'AND p.post_type = %s', $filter->post_type );
454 - $filter->where[] = 'AND ID NOT IN(' . $query_item_ids_assigned . ')';
455 - $filter->where[] = $this->wpdb->prepare( 'AND p.post_status not IN(%s, %s)', 'trash', 'auto-draft' );
456 -
457 - return $this->execute( $filter );
458 - }
459 -
460 - /**
461 - * Get Courses
462 - *
463 - * @param LP_Course_Filter $filter
464 - * @param int $total_rows return total_rows
465 - *
466 - * @return array|object|null|int|string
467 - * @throws Exception
468 - * @author tungnx
469 - * @version 1.0.2
470 - * @since 4.1.5
471 - */
472 - public function get_courses( $filter, int &$total_rows = 0 ) {
473 - $default_fields = $filter->all_fields;
474 - $filter->fields = array_merge( $default_fields, $filter->fields );
475 -
476 - if ( empty( $filter->collection ) ) {
477 - $filter->collection = $this->tb_posts;
478 - }
479 -
480 - if ( empty( $filter->collection_alias ) ) {
481 - $filter->collection_alias = 'p';
482 - }
483 -
484 - // Where
485 - $filter->where[] = $this->wpdb->prepare( 'AND p.post_type = %s', $filter->post_type );
486 -
487 - // Status
488 - $filter->post_status = (array) $filter->post_status;
489 - if ( ! empty( $filter->post_status ) ) {
490 - $post_status_format = LP_Helper::db_format_array( $filter->post_status, '%s' );
491 - $filter->where[] = $this->wpdb->prepare( 'AND p.post_status IN (' . $post_status_format . ')', $filter->post_status );
492 - }
493 -
494 - // Exclude status auto-draft, because Admin of WP not show auto-draft in list post, so all front-end and back-end of LP also not show auto-draft
495 - if ( ! in_array( 'auto-draft', $filter->post_status, true ) ) {
496 - $filter->where[] = $this->wpdb->prepare( 'AND p.post_status != %s', 'auto-draft' );
497 - }
498 -
499 - // Term ids
500 - if ( ! empty( $filter->term_ids ) ) {
501 - // Sanitize term ids
502 - $filter->term_ids = array_map( 'absint', $filter->term_ids );
503 - $filter->join[] = "INNER JOIN $this->tb_term_relationships AS r_term ON p.ID = r_term.object_id";
504 - $filter->join[] = "INNER JOIN $this->tb_term_taxonomy AS tx ON r_term.term_taxonomy_id = tx.term_taxonomy_id";
505 -
506 - if ( LP_Settings::get_option( 'get_courses_of_subcategory' ) !== 'yes' ) {
507 - $term_ids_format = join( ',', $filter->term_ids );
508 - $filter->where[] = "AND tx.term_id IN ($term_ids_format)";
509 - } else {
510 - $term_all = $this->recursion_sub_categories( $filter->term_ids );
511 - $term_ids_format = join( ',', $term_all );
512 - $filter->where[] = "AND tx.term_id IN ($term_ids_format)";
513 - }
514 - $filter->where[] = $this->wpdb->prepare( 'AND tx.taxonomy = %s', LP_COURSE_CATEGORY_TAX );
515 - }
516 -
517 - // Tag ids
518 - if ( ! empty( $filter->tag_ids ) ) {
519 - // Sanitize tag ids
520 - $filter->tag_ids = array_map( 'absint', $filter->tag_ids );
521 - $filter->join[] = "INNER JOIN $this->tb_term_relationships AS r_tag ON p.ID = r_tag.object_id";
522 - $filter->join[] = "INNER JOIN $this->tb_term_taxonomy AS tag ON r_tag.term_taxonomy_id = tag.term_taxonomy_id";
523 -
524 - $tag_ids_format = join( ',', $filter->tag_ids );
525 - $filter->where[] = "AND tag.term_id IN ($tag_ids_format)";
526 - $filter->where[] = $this->wpdb->prepare( 'AND tag.taxonomy = %s', LP_COURSE_TAXONOMY_TAG );
527 - }
528 -
529 - // Level
530 - if ( ! empty( $filter->levels ) ) {
531 - $filter->join[] = "INNER JOIN $this->tb_postmeta AS pml ON p.ID = pml.post_id";
532 - $filter->where[] = $this->wpdb->prepare( 'AND pml.meta_key = %s', '_lp_level' );
533 - $levels_format = LP_Helper::db_format_array( $filter->levels, '%s' );
534 - $filter->where[] = $this->wpdb->prepare( 'AND pml.meta_value IN (' . $levels_format . ')', $filter->levels );
535 - }
536 -
537 - // Course type
538 - if ( ! empty( $filter->type ) && $filter->type !== 'all' ) {
539 - if ( $filter->type === 'offline' ) {
540 - $filter->join[] = "INNER JOIN $this->tb_postmeta AS pm_off ON p.ID = pm_off.post_id";
541 - $filter->where[] = $this->wpdb->prepare( 'AND pm_off.meta_key = %s', '_lp_offline_course' );
542 - $filter->where[] = $this->wpdb->prepare( 'AND pm_off.meta_value = %s', 'yes' );
543 - } else {
544 - $filter->where[] = $this->wpdb->prepare(
545 - "AND p.ID NOT IN
546 - ( SELECT id FROM $this->tb_posts as p1
547 - INNER JOIN $this->tb_postmeta as pm_ol on p1.ID = pm_ol.post_id
548 - WHERE pm_ol.meta_key = %s AND pm_ol.meta_value = %s )",
549 - '_lp_offline_course',
550 - 'yes'
551 - );
552 - }
553 - }
554 -
555 - // course ids
556 - if ( ! empty( $filter->post_ids ) ) {
557 - $list_ids_format = LP_Helper::db_format_array( $filter->post_ids, '%d' );
558 - $filter->where[] = $this->wpdb->prepare( 'AND p.ID IN (' . $list_ids_format . ')', $filter->post_ids );
559 - }
560 -
561 - // Title, do not distinguish diacritics
562 - if ( $filter->post_title ) {
563 - //$filter->where[] = $this->wpdb->prepare( 'AND p.post_title COLLATE utf8mb4_unicode_ci LIKE %s', '%' . $filter->post_title . '%' );
564 - $filter->where[] = $this->wpdb->prepare( 'AND p.post_title LIKE %s', '%' .$filter->post_title . '%' );
565 - }
566 -
567 - // Slug
568 - if ( $filter->post_name ) {
569 - $filter->where[] = $this->wpdb->prepare( 'AND p.post_name = %s', $filter->post_name );
570 - }
571 -
572 - // Author
573 - if ( isset( $filter->post_author ) ) {
574 - $filter->where[] = $this->wpdb->prepare( 'AND p.post_author = %d', $filter->post_author );
575 - }
576 - // Authors
577 - if ( ! empty( $filter->post_authors ) ) {
578 - $post_authors_format = LP_Helper::db_format_array( $filter->post_authors, '%d' );
579 - $filter->where[] = $this->wpdb->prepare( 'AND p.post_author IN (' . $post_authors_format . ')', $filter->post_authors );
580 - }
581 -
582 - $filter = apply_filters( 'lp/course/query/filter', $filter );
583 -
584 - return $this->execute( $filter, $total_rows );
585 - }
586 -
587 - /**
588 - * Get list courses sort by price
589 - *
590 - * @param LP_Course_Filter $filter
591 - *
592 - * @return LP_Course_Filter
593 - * @since 4.1.5
594 - * @author tungnx
595 - * @version 1.0.0
596 - */
597 - public function get_courses_order_by_price( LP_Course_Filter &$filter ): LP_Course_Filter {
598 - $filter->join[] = "INNER JOIN $this->tb_postmeta AS pm ON p.ID = pm.post_id";
599 - $filter->where[] = $this->wpdb->prepare( 'AND pm.meta_key = %s', '_lp_price' );
600 - $filter->order_by = 'CAST( pm.meta_value AS UNSIGNED )';
601 -
602 - return $filter;
603 - }
604 -
605 - /**
606 - * Get list courses is on sale
607 - *
608 - * @param LP_Course_Filter $filter
609 - *
610 - * @return LP_Course_Filter
611 - * @since 4.1.5
612 - * @author tungnx
613 - * @version 1.0.0
614 - */
615 - public function get_courses_sort_by_sale( LP_Course_Filter &$filter ): LP_Course_Filter {
616 - $filter->join[] = "INNER JOIN $this->tb_postmeta AS pm ON p.ID = pm.post_id";
617 - $filter->where[] = $this->wpdb->prepare( 'AND pm.meta_key = %s', '_lp_course_is_sale' );
618 -
619 - return $filter;
620 - }
621 -
622 - /**
623 - * Get list courses is Free
624 - *
625 - * @param LP_Course_Filter $filter
626 - *
627 - * @return LP_Course_Filter
628 - * @throws Exception
629 - * @version 1.0.0
630 - * @since 4.2.3.2
631 - */
632 - public function get_courses_sort_by_free( LP_Course_Filter &$filter ): LP_Course_Filter {
633 - $filter_course_price = new LP_Course_Filter();
634 - $filter_course_price->only_fields = [ 'DISTINCT(ID)' ];
635 - $filter_course_price = $this->get_courses_sort_by_paid( $filter_course_price );
636 - $filter_course_price->return_string_query = true;
637 - $courses_price = $this->get_courses( $filter_course_price );
638 -
639 - $filter->join[] = "INNER JOIN $this->tb_postmeta AS pmfr ON p.ID = pmfr.post_id";
640 - $filter->where[] = 'AND ID NOT IN( ' . $courses_price . ' )';
641 -
642 - return $filter;
643 - }
644 -
645 - /**
646 - * Get list courses has price
647 - *
648 - * @param LP_Course_Filter $filter
649 - *
650 - * @return LP_Course_Filter
651 - * @version 1.0.0
652 - * @since 4.2.3.2
653 - */
654 - public function get_courses_sort_by_paid( LP_Course_Filter $filter ): LP_Course_Filter {
655 - $filter->join[] = "INNER JOIN $this->tb_postmeta AS pmp ON p.ID = pmp.post_id";
656 - $filter->where[] = $this->wpdb->prepare( 'AND pmp.meta_key = %s AND pmp.meta_value > %d', '_lp_price', 0 );
657 -
658 - return $filter;
659 - }
660 -
661 - /**
662 - * Get list courses is on feature
663 - *
664 - * @param LP_Course_Filter $filter
665 - *
666 - * @return LP_Course_Filter
667 - * @author tungnx
668 - * @version 1.0.0
669 - * @since 4.1.5
670 - */
671 - public function get_courses_sort_by_feature( LP_Course_Filter &$filter ): LP_Course_Filter {
672 - $filter->join[] = "INNER JOIN $this->tb_postmeta AS pmf ON p.ID = pmf.post_id";
673 - $filter->where[] = $this->wpdb->prepare( 'AND pmf.meta_key = %s', '_lp_featured' );
674 - $filter->where[] = $this->wpdb->prepare( 'AND pmf.meta_value = %s', 'yes' );
675 -
676 - return $filter;
677 - }
678 -
679 - /**
680 - * Count total courses free on category
681 - *
682 - * @param LP_Course_Filter $filter
683 - *
684 - * @return int
685 - * @since 4.2.5.4
686 - * @version 1.0.1
687 - */
688 - public function count_course_free( LP_Course_Filter $filter ): int {
689 - $count = 0;
690 -
691 - try {
692 - $filter->only_fields = [ 'COUNT( DISTINCT(ID) )' ];
693 - $filter->order_by = ''; // Must set to empty to avoid error from param Request
694 - $this->get_courses_sort_by_free( $filter );
695 - $filter->return_string_query = true;
696 - $query_count = $this->get_courses( $filter, $count );
697 - $count = $this->wpdb->get_var( $query_count );
698 - } catch ( Throwable $e ) {
699 - LP_Debug::error_log( $e );
700 - }
701 -
702 - return (int) $count;
703 - }
704 -
705 - /**
706 - * Get list courses is on popular
707 - * Use "UNION" to merge 2 query
708 - *
709 - * @param LP_Course_Filter $filter
710 - *
711 - * @return LP_Course_Filter
712 - * @throws Exception
713 - * @version 1.0.1
714 - * @since 4.1.6
715 - * @author minhpd
716 - */
717 - public function get_courses_order_by_popular( LP_Course_Filter &$filter ): LP_Course_Filter {
718 - // Set list name columns get
719 - //$columns_table_posts = $this->get_cols_of_table( $this->tb_posts );
720 - //$filter->fields = array_merge( $columns_table_posts, $filter->fields );
721 -
722 - $filter_user_course = clone $filter;
723 - $filter_course_not_attend = clone $filter;
724 -
725 - // Query get users total attend courses
726 - $fields_user_course_require = [ 'ID', 'COUNT(ID) AS total' ];
727 - $filter_user_course->fields = array( 'ID', 'COUNT(ID) AS total' );
728 - if ( ! empty( $filter_user_course->only_fields ) ) {
729 - $pattern = '#ID.*#';
730 - foreach ( $filter_user_course->only_fields as $k => $field ) {
731 - if ( preg_match( $pattern, $field ) ) {
732 - unset( $filter_user_course->only_fields[ $k ] );
733 - }
734 - }
735 - $filter_user_course->only_fields = array_unique( array_merge( $filter_user_course->only_fields, $fields_user_course_require ) );
736 - }
737 -
738 - $filter_user_course->join[] = "INNER JOIN {$this->tb_lp_user_items} AS ui ON p.ID = ui.item_id";
739 - $filter_user_course->where[] = $this->wpdb->prepare( 'AND ui.item_type = %s', LP_COURSE_CPT );
740 - $filter_user_course->where[] = $this->wpdb->prepare(
741 - 'AND (status = %s OR status = %s OR status = %s)',
742 - LP_COURSE_ENROLLED,
743 - LP_COURSE_PURCHASED,
744 - LP_COURSE_FINISHED
745 - );
746 - $filter_user_course->group_by = 'p.ID';
747 - $filter_user_course->order_by = '';
748 - $filter_user_course->return_string_query = true;
749 - $query_user_course = LP_Course_DB::getInstance()->get_courses( $filter_user_course );
750 -
751 - // Query get courses not attend
752 - $filter_user_course_cl = clone $filter_user_course;
753 - $filter_user_course_cl->only_fields = array( 'ID' );
754 - $query_user_course_for_not_in = LP_Course_DB::getInstance()->get_courses( $filter_user_course_cl );
755 -
756 - $fields_user_course_not_attend_require = [ 'ID', '0 AS total' ];
757 - $filter_course_not_attend->fields = [ 'ID', '0 AS total' ];
758 - if ( ! empty( $filter_course_not_attend->only_fields ) ) {
759 - $pattern = '#ID.*#';
760 - foreach ( $filter_course_not_attend->only_fields as $k => $field ) {
761 - if ( preg_match( $pattern, $field ) ) {
762 - unset( $filter_course_not_attend->only_fields[ $k ] );
763 - }
764 - }
765 - $filter_course_not_attend->only_fields = array_unique( array_merge( $filter_course_not_attend->only_fields, $fields_user_course_not_attend_require ) );
766 - }
767 - $filter_course_not_attend->where[] = 'AND p.ID NOT IN(' . $query_user_course_for_not_in . ')';
768 -
769 - $filter_course_not_attend->order_by = '';
770 - $filter_course_not_attend->return_string_query = true;
771 - $query_course_not_attend = LP_Course_DB::getInstance()->get_courses( $filter_course_not_attend );
772 -
773 - $filter->union[] = $query_user_course;
774 - $filter->union[] = $query_course_not_attend;
775 - $filter->order_by = 'total';
776 - $filter->order = 'DESC';
777 -
778 - return $filter;
779 - }
780 -
781 - /**
782 - * Get total courses of Author
783 - *
784 - * @param int $author_id
785 - *
786 - * @return LP_Course_Filter
787 - * @throws Exception
788 - * @version 1.0.0
789 - * @since 4.1.6
790 - * @deprecated 4.2.6.6 not use anywhere
791 - */
792 - public function count_courses_publish_of_author( int $author_id ): LP_Course_Filter {
793 - _deprecated_function( __METHOD__, '4.2.6.6' );
794 - $filter_course = new LP_Course_Filter();
795 - $filter_course->only_fields = array( 'ID' );
796 - $filter_course->post_author = $author_id;
797 - $filter_course->post_status = 'publish';
798 - $filter_course->field_count = 'ID';
799 - $filter_course->query_count = true;
800 -
801 - return apply_filters( 'lp/user/course/query/filter/count-users-attend-courses-of-author', $filter_course );
802 - }
803 -
804 - /**
805 - * Get total courses of Author
806 - *
807 - * @param int $author_id
808 - * @param array $status
809 - *
810 - * @return LP_Course_Filter
811 - * @since 4.2.3
812 - * @version 1.0.0
813 - */
814 - public function count_courses_of_author( int $author_id, array $status = [] ): LP_Course_Filter {
815 - $filter_course = new LP_Course_Filter();
816 - $filter_course->only_fields = array( 'ID' );
817 - $filter_course->post_author = $author_id;
818 - $filter_course->post_status = $status;
819 - if ( empty( $status ) ) {
820 - $filter_course->post_status = [];
821 - }
822 - $filter_course->field_count = 'ID';
823 - $filter_course->query_count = true;
824 -
825 - return apply_filters( 'lp/user/course/query/filter/count-courses-of-author', $filter_course );
826 - }
827 -
828 - /**
829 - * Get child categories of category and add to query OR
830 - *
831 - * @param array $term_ids
832 - *
833 - * @return array
834 - * @throws Exception
835 - * @version 1.0.1
836 - * @since 4.2.6.5
837 - */
838 - public function recursion_sub_categories( array $term_ids ): array {
839 - $total_found = 0;
840 - $term_ids_format = join( ',', $term_ids );
841 - $filter_sub_category = new LP_Filter();
842 - $filter_sub_category->collection = $this->tb_term_taxonomy;
843 - $filter_sub_category->collection_alias = 'tx';
844 - $filter_sub_category->field_count = 'tx.term_id';
845 - $filter_sub_category->only_fields = [ 'term_id' ];
846 - $filter_sub_category->where[] = "AND tx.parent IN ($term_ids_format)";
847 - $filter_sub_category->limit = -1;
848 - $query_sub_category = $this->execute( $filter_sub_category, $total_found );
849 - $term_sub_ids = [];
850 -
851 - if ( $total_found > 0 ) {
852 - foreach ( $query_sub_category as $term_id ) {
853 - $term_sub_ids[] = $term_id->term_id;
854 - }
855 -
856 - $term_sub_idss = $this->recursion_sub_categories( $term_sub_ids );
857 - $term_sub_ids = array_merge( $term_sub_ids, $term_sub_idss );
858 - }
859 -
860 - return array_merge( $term_ids, $term_sub_ids );
861 - }
862 -}
1 +<?php
2 +/**
3 + * Class LP_Course_DB
4 + *
5 + * @author tungnx
6 + * @since 3.2.7.5
7 + */
8 +
9 +use LearnPress\Models\CourseModel;
10 +
11 +defined( 'ABSPATH' ) || exit();
12 +
13 +class LP_Course_DB extends LP_Database {
14 + private static $_instance;
15 +
16 + protected function __construct() {
17 + parent::__construct();
18 + }
19 +
20 + public static function getInstance() {
21 + if ( is_null( self::$_instance ) ) {
22 + self::$_instance = new self();
23 + }
24 +
25 + return self::$_instance;
26 + }
27 +
28 + /**
29 + * Get course_id of item
30 + *
31 + * item type lp_lesson, lp_quiz
32 + *
33 + * @param int $item_id
34 + *
35 + * @return int
36 + * @throws Exception
37 + */
38 + public function get_course_by_item_id( $item_id = 0 ): int {
39 + // Get cache
40 + $lp_course_cache = LP_Course_Cache::instance();
41 + $key_cache = "$item_id/course_id_of_item_id";
42 + $course_id = $lp_course_cache->get_cache( $key_cache );
43 +
44 + if ( ! $course_id ) {
45 + $query = $this->wpdb->prepare(
46 + "
47 + SELECT section_course_id
48 + FROM {$this->tb_lp_sections} AS s
49 + INNER JOIN {$this->tb_lp_section_items} AS si
50 + ON si.section_id = s.section_id
51 + WHERE si.item_id = %d",
52 + $item_id
53 + );
54 +
55 + $course_id = (int) $this->wpdb->get_var( $query );
56 +
57 + $this->check_execute_has_error();
58 +
59 + // Set cache
60 + $lp_course_cache->set_cache( $key_cache, $course_id );
61 + }
62 +
63 + return $course_id;
64 + }
65 +
66 + /**
67 + * Get all item ids' course
68 + *
69 + * @param int $course_id
70 + *
71 + * @return array|object|stdClass[]|null
72 + * @throws Exception
73 + * @since 4.1.6.9
74 + * @version 1.0.0
75 + */
76 + public function get_full_sections_and_items_course( int $course_id = 0 ) {
77 + $method_called_to = debug_backtrace()[1]['function'];
78 +
79 + // Check accept call from function 'get_sections_and_items_course_from_db_and_sort'
80 + if ( 'get_sections_and_items_course_from_db_and_sort' !== $method_called_to ) {
81 + error_log( 'You can not call direct this function' );
82 +
83 + return [];
84 + }
85 +
86 + $query = $this->wpdb->prepare(
87 + "SELECT *
88 + FROM {$this->tb_lp_section_items} AS si
89 + INNER JOIN {$this->tb_lp_sections} AS s
90 + ON si.section_id = s.section_id
91 + INNER JOIN {$this->tb_posts} AS p
92 + ON si.item_id = p.ID AND p.post_status = 'publish'
93 + WHERE section_course_id = %d
94 + ORDER BY s.section_order",
95 + $course_id
96 + );
97 +
98 + $sections_items = $this->wpdb->get_results( $query );
99 +
100 + $this->check_execute_has_error();
101 +
102 + return $sections_items;
103 + }
104 +
105 + /**
106 + * Get all sections' course
107 + *
108 + * @param int $course_id
109 + *
110 + * @return array|object|stdClass[]|null
111 + * @throws Exception
112 + * @since 4.1.6.9
113 + * @version 1.0.0
114 + */
115 + public function get_sections( int $course_id = 0 ) {
116 + $method_called_to = debug_backtrace()[1]['function'];
117 +
118 + // Check accept call from function 'get_sections_and_items_course_from_db_and_sort'
119 + if ( 'get_sections_and_items_course_from_db_and_sort' !== $method_called_to ) {
120 + error_log( 'You can not call direct this function' );
121 +
122 + return [];
123 + }
124 +
125 + $query = $this->wpdb->prepare(
126 + "SELECT * FROM {$this->tb_lp_sections}
127 + WHERE section_course_id = %d
128 + ORDER BY section_order",
129 + $course_id
130 + );
131 +
132 + $sections_items = $this->wpdb->get_results( $query, OBJECT_K );
133 +
134 + $this->check_execute_has_error();
135 +
136 + return $sections_items;
137 + }
138 +
139 + /**
140 + * Get user_item_id by order_id, course_id, user_id
141 + *
142 + * @param int $order_id
143 + * @param int $course_id
144 + * @param int $user_id
145 + *
146 + * @return int
147 + * @deprecated 4.2.6.6 not use anywhere
148 + */
149 + public function get_user_item_id( $order_id = 0, $course_id = 0, $user_id = 0 ): int {
150 + _deprecated_function( __METHOD__, '4.2.6.6' );
151 + $query = $this->wpdb->prepare(
152 + "
153 + SELECT user_item_id
154 + FROM {$this->tb_lp_user_items}
155 + WHERE ref_type = %s
156 + AND ref_id = %d
157 + AND item_type = %s
158 + AND item_id = %d
159 + AND user_id = %d
160 + ",
161 + LP_ORDER_CPT,
162 + $order_id,
163 + LP_COURSE_CPT,
164 + $course_id,
165 + $user_id
166 + );
167 +
168 + return (int) $this->wpdb->get_var( $query );
169 + }
170 +
171 + /**
172 + * Get first item id of course
173 + *
174 + * @param int $course_id .
175 + *
176 + * @return int
177 + * @throws Exception
178 + * @since 4.0.0
179 + * @version 1.0.4
180 + * @modify 4.1.3
181 + * @author tungnx
182 + */
183 + public function get_first_item_id( int $course_id = 0 ): int {
184 + $query = $this->wpdb->prepare(
185 + "
186 + SELECT item_id FROM $this->tb_lp_section_items AS si
187 + INNER JOIN $this->tb_lp_sections AS sections
188 + ON si.section_id = sections.section_id
189 + AND sections.section_course_id = %d
190 + INNER JOIN $this->tb_posts AS p
191 + ON si.item_id = p.ID
192 + AND p.post_status = 'publish'
193 + ORDER BY sections.section_order ASC, si.item_order ASC
194 + LIMIT %d
195 + ",
196 + $course_id,
197 + 1
198 + );
199 +
200 + $first_item_id = (int) $this->wpdb->get_var( $query );
201 +
202 + $this->check_execute_has_error();
203 +
204 + return $first_item_id;
205 + }
206 +
207 + public function get_recent_courses( LP_Course_Filter $filter ): array {
208 + global $wpdb;
209 +
210 + $limit = $filter->limit ?? - 1;
211 + $order = ! empty( $filter->order ) ? $filter->order : 'DESC';
212 +
213 + if ( $limit <= 0 ) {
214 + $limit = 0;
215 + }
216 +
217 + $query = apply_filters(
218 + 'learnpress/databases/widgets/recent_courses',
219 + $wpdb->prepare(
220 + "SELECT DISTINCT p.ID
221 + FROM $wpdb->posts AS p
222 + WHERE p.post_type = %s
223 + AND p.post_status = %s
224 + ORDER BY p.post_date {$order}
225 + LIMIT %d",
226 + LP_COURSE_CPT,
227 + 'publish',
228 + $limit
229 + )
230 + );
231 +
232 + return $wpdb->get_col( $query );
233 + }
234 +
235 + public function get_featured_courses( LP_Course_Filter $filter ): array {
236 + global $wpdb;
237 +
238 + $limit = ! empty( $filter->limit ) ? $filter->limit : - 1;
239 + $order_by = ! empty( $filter->order_by ) ? $filter->order_by : 'post_date';
240 + $order = ! empty( $filter->order ) ? $filter->order : 'DESC';
241 +
242 + if ( $limit < 0 ) {
243 + $limit = 0;
244 + }
245 +
246 + $query = apply_filters(
247 + 'learnpress/databases/widgets/featured_courses',
248 + $wpdb->prepare(
249 + "SELECT DISTINCT p.ID
250 + FROM {$wpdb->posts} p
251 + LEFT JOIN {$wpdb->postmeta} as pmeta ON p.ID=pmeta.post_id AND pmeta.meta_key = %s
252 + WHERE p.post_type = %s
253 + AND p.post_status = %s
254 + AND pmeta.meta_value = %s
255 + ORDER BY p.{$order_by} {$order}
256 + LIMIT %d",
257 + '_lp_featured',
258 + LP_COURSE_CPT,
259 + 'publish',
260 + 'yes',
261 + $limit
262 + )
263 + );
264 +
265 + return $wpdb->get_col( $query );
266 + }
267 +
268 + /**
269 + * Get list user ids enrolled by course
270 + *
271 + * @return array|object|null
272 + * @throws Exception
273 + * @version 1.0.0
274 + * @author tungnx
275 + * @since 4.1.3.1
276 + */
277 + public function get_user_ids_enrolled( int $course_id ) {
278 + $query = $this->wpdb->prepare(
279 + "
280 + SELECT DISTINCT user_id FROM {$this->tb_lp_user_items}
281 + WHERE item_id = %d
282 + AND item_type = %s
283 + AND (status = %s OR status = %s )
284 + ",
285 + $course_id,
286 + LP_COURSE_CPT,
287 + 'enrolled',
288 + 'finished'
289 + );
290 +
291 + $result = $this->wpdb->get_results( $query, OBJECT_K );
292 +
293 + $this->check_execute_has_error();
294 +
295 + return $result;
296 + }
297 +
298 + /**
299 + * Count total user enrolled by course
300 + *
301 + * @param int $course_id
302 + *
303 + * @return int
304 + * @throws Exception
305 + * @author tungnx
306 + * @since 4.1.4
307 + * @version 1.0.0
308 + */
309 + public function get_total_user_enrolled( int $course_id ): int {
310 + $filter = new LP_User_Items_Filter();
311 + $filter->only_fields = [ 'DISTINCT(user_id)' ];
312 + $filter->item_id = $course_id;
313 + $filter->item_type = LP_COURSE_CPT;
314 + $filter->field_count = 'ui.user_id';
315 + $filter->join[] = "INNER JOIN {$this->tb_users} AS u ON ui.user_id = u.ID";
316 + $filter->where[] = 'AND ui.user_id > 0';
317 + $filter->where[] = $this->wpdb->prepare( 'AND ( ui.status = %s OR ui.status = %s )', LP_COURSE_ENROLLED, LP_COURSE_FINISHED );
318 + $filter->query_count = true;
319 +
320 + $total = 0;
321 + $lp_user_items_db = LP_User_Items_DB::getInstance();
322 + $lp_user_items_db->get_user_items( $filter, $total );
323 +
324 + return $total;
325 + }
326 +
327 + /**
328 + * Count total user enrolled or purchase by course
329 + *
330 + * @param int $course_id
331 + *
332 + * @return int
333 + * @throws Exception
334 + * @author tungnx
335 + * @since 4.1.4
336 + * @version 1.0.2
337 + */
338 + public function get_total_user_enrolled_or_purchased( int $course_id ): int {
339 + $filter = new LP_User_Items_Filter();
340 + $filter->only_fields = [ 'DISTINCT(user_id) AS user_id' ];
341 + $filter->item_id = $course_id;
342 + $filter->item_type = LP_COURSE_CPT;
343 + $filter->field_count = 'ui.user_id';
344 + $filter->join[] = "INNER JOIN {$this->tb_users} AS u ON ui.user_id = u.ID";
345 + $filter->where[] = 'AND ui.user_id > 0';
346 + $filter->where[] = $this->wpdb->prepare(
347 + 'AND ( ui.status = %s OR ui.status = %s OR ui.status = %s )',
348 + LP_COURSE_ENROLLED,
349 + LP_COURSE_FINISHED,
350 + LP_COURSE_PURCHASED
351 + );
352 + $filter->query_count = true;
353 +
354 + $total = 0;
355 + $lp_user_items_db = LP_User_Items_DB::getInstance();
356 + $lp_user_items_db->get_user_items( $filter, $total );
357 +
358 + return $total;
359 + }
360 +
361 + /**
362 + * Get total items of course
363 + *
364 + * @param int $course_id
365 + *
366 + * @return null|object
367 + * @throws Exception
368 + * @version 1.0.1
369 + * @author tungnx
370 + * @since 4.1.4.1
371 + */
372 + public function get_total_items( int $course_id = 0 ) {
373 + $item_types = CourseModel::item_types_support();
374 + $count_item_types = count( $item_types );
375 + $i = 0;
376 +
377 + $query_count = $this->wpdb->prepare( 'SUM(s.section_course_id = %d) AS count_items,', $course_id );
378 +
379 + foreach ( $item_types as $item_type ) {
380 + ++ $i;
381 + if ( $i == $count_item_types ) {
382 + $query_count .= $this->wpdb->prepare( 'SUM(s.section_course_id = %d AND si.item_type = %s) AS %s', $course_id, $item_type, $item_type );
383 + } else {
384 + $query_count .= $this->wpdb->prepare( 'SUM(s.section_course_id = %d AND si.item_type = %s) AS %s,', $course_id, $item_type, $item_type );
385 + }
386 + }
387 +
388 + $query = "
389 + SELECT $query_count
390 + FROM $this->tb_lp_section_items si
391 + INNER JOIN $this->tb_lp_sections s ON s.section_id = si.section_id
392 + INNER JOIN $this->tb_posts p ON si.item_id = p.ID
393 + AND p.post_status = 'publish'
394 + ";
395 +
396 + $total_items = $this->wpdb->get_row( $query );
397 +
398 + $this->check_execute_has_error();
399 +
400 + return $total_items;
401 + }
402 +
403 + /**
404 + * Count all item are unassigned to any courses.
405 + *
406 + * @param string $item_type (type item Lesson, Quiz, Assignment, H5P ...)
407 + *
408 + * @return int
409 + * @throws Exception
410 + * @author tungnx
411 + * @version 1.0.1
412 + * @since 4.1.4.1
413 + */
414 + public function get_total_item_unassigned( string $item_type ): int {
415 + $filter = new LP_Post_Type_Filter();
416 + $filter->post_type = $item_type;
417 + $filter->query_count = true;
418 + $filter->post_status = array();
419 + $filter->field_count = 'p.ID';
420 +
421 + return $this->get_item_ids_unassigned( $filter );
422 + }
423 +
424 + /**
425 + * list id item are unassigned to any courses.
426 + *
427 + * @param LP_Post_Type_Filter|mixed $filter
428 + *
429 + * @return array|int|string|null
430 + * @throws Exception
431 + * @author tungnx
432 + * @version 1.0.0
433 + * @since 4.1.6
434 + */
435 + public function get_item_ids_unassigned( $filter = null ) {
436 + if ( is_null( $filter ) ) {
437 + $filter = new LP_Post_Type_Filter();
438 + }
439 +
440 + if ( ! $filter instanceof LP_Post_Type_Filter ) {
441 + throw new Exception( 'Filter must be instance of LP_Post_Type_Filter' );
442 + }
443 +
444 + $filter_section_items = new LP_Section_Items_Filter();
445 + $filter_section_items->return_string_query = true;
446 + $filter_section_items->only_fields = array( 'si.item_id' );
447 + $filter_section_items->where[] = $this->wpdb->prepare( 'AND si.item_type = %s', $filter->post_type );
448 + $query_item_ids_assigned = LP_Section_Items_DB::getInstance()->get_section_items( $filter_section_items );
449 +
450 + $filter->only_fields = array( 'p.ID' );
451 + $filter->collection = $this->tb_posts;
452 + $filter->collection_alias = 'p';
453 + $filter->where[] = $this->wpdb->prepare( 'AND p.post_type = %s', $filter->post_type );
454 + $filter->where[] = 'AND ID NOT IN(' . $query_item_ids_assigned . ')';
455 + $filter->where[] = $this->wpdb->prepare( 'AND p.post_status not IN(%s, %s)', 'trash', 'auto-draft' );
456 +
457 + return $this->execute( $filter );
458 + }
459 +
460 + /**
461 + * Get Courses
462 + *
463 + * @param LP_Course_Filter $filter
464 + * @param int $total_rows return total_rows
465 + *
466 + * @return array|object|null|int|string
467 + * @throws Exception
468 + * @author tungnx
469 + * @version 1.0.2
470 + * @since 4.1.5
471 + */
472 + public function get_courses( $filter, int &$total_rows = 0 ) {
473 + $default_fields = $filter->all_fields;
474 + $filter->fields = array_merge( $default_fields, $filter->fields );
475 +
476 + if ( empty( $filter->collection ) ) {
477 + $filter->collection = $this->tb_posts;
478 + }
479 +
480 + if ( empty( $filter->collection_alias ) ) {
481 + $filter->collection_alias = 'p';
482 + }
483 +
484 + // Where
485 + $filter->where[] = $this->wpdb->prepare( 'AND p.post_type = %s', $filter->post_type );
486 +
487 + // Status
488 + $filter->post_status = (array) $filter->post_status;
489 + if ( ! empty( $filter->post_status ) ) {
490 + $post_status_format = LP_Helper::db_format_array( $filter->post_status, '%s' );
491 + $filter->where[] = $this->wpdb->prepare( 'AND p.post_status IN (' . $post_status_format . ')', $filter->post_status );
492 + }
493 +
494 + // Exclude status auto-draft, because Admin of WP not show auto-draft in list post, so all front-end and back-end of LP also not show auto-draft
495 + if ( ! in_array( 'auto-draft', $filter->post_status, true ) ) {
496 + $filter->where[] = $this->wpdb->prepare( 'AND p.post_status != %s', 'auto-draft' );
497 + }
498 +
499 + // Term ids
500 + if ( ! empty( $filter->term_ids ) ) {
501 + // Sanitize term ids
502 + $filter->term_ids = array_map( 'absint', $filter->term_ids );
503 + $filter->join[] = "INNER JOIN $this->tb_term_relationships AS r_term ON p.ID = r_term.object_id";
504 + $filter->join[] = "INNER JOIN $this->tb_term_taxonomy AS tx ON r_term.term_taxonomy_id = tx.term_taxonomy_id";
505 +
506 + if ( LP_Settings::get_option( 'get_courses_of_subcategory' ) !== 'yes' ) {
507 + $term_ids_format = join( ',', $filter->term_ids );
508 + $filter->where[] = "AND tx.term_id IN ($term_ids_format)";
509 + } else {
510 + $term_all = $this->recursion_sub_categories( $filter->term_ids );
511 + $term_ids_format = join( ',', $term_all );
512 + $filter->where[] = "AND tx.term_id IN ($term_ids_format)";
513 + }
514 + $filter->where[] = $this->wpdb->prepare( 'AND tx.taxonomy = %s', LP_COURSE_CATEGORY_TAX );
515 + }
516 +
517 + // Tag ids
518 + if ( ! empty( $filter->tag_ids ) ) {
519 + // Sanitize tag ids
520 + $filter->tag_ids = array_map( 'absint', $filter->tag_ids );
521 + $filter->join[] = "INNER JOIN $this->tb_term_relationships AS r_tag ON p.ID = r_tag.object_id";
522 + $filter->join[] = "INNER JOIN $this->tb_term_taxonomy AS tag ON r_tag.term_taxonomy_id = tag.term_taxonomy_id";
523 +
524 + $tag_ids_format = join( ',', $filter->tag_ids );
525 + $filter->where[] = "AND tag.term_id IN ($tag_ids_format)";
526 + $filter->where[] = $this->wpdb->prepare( 'AND tag.taxonomy = %s', LP_COURSE_TAXONOMY_TAG );
527 + }
528 +
529 + // Level
530 + if ( ! empty( $filter->levels ) ) {
531 + $filter->join[] = "INNER JOIN $this->tb_postmeta AS pml ON p.ID = pml.post_id";
532 + $filter->where[] = $this->wpdb->prepare( 'AND pml.meta_key = %s', '_lp_level' );
533 + $levels_format = LP_Helper::db_format_array( $filter->levels, '%s' );
534 + $filter->where[] = $this->wpdb->prepare( 'AND pml.meta_value IN (' . $levels_format . ')', $filter->levels );
535 + }
536 +
537 + // Course type
538 + if ( ! empty( $filter->type ) && $filter->type !== 'all' ) {
539 + if ( $filter->type === 'offline' ) {
540 + $filter->join[] = "INNER JOIN $this->tb_postmeta AS pm_off ON p.ID = pm_off.post_id";
541 + $filter->where[] = $this->wpdb->prepare( 'AND pm_off.meta_key = %s', '_lp_offline_course' );
542 + $filter->where[] = $this->wpdb->prepare( 'AND pm_off.meta_value = %s', 'yes' );
543 + } else {
544 + $filter->where[] = $this->wpdb->prepare(
545 + "AND p.ID NOT IN
546 + ( SELECT id FROM $this->tb_posts as p1
547 + INNER JOIN $this->tb_postmeta as pm_ol on p1.ID = pm_ol.post_id
548 + WHERE pm_ol.meta_key = %s AND pm_ol.meta_value = %s )",
549 + '_lp_offline_course',
550 + 'yes'
551 + );
552 + }
553 + }
554 +
555 + // course ids
556 + if ( ! empty( $filter->post_ids ) ) {
557 + $list_ids_format = LP_Helper::db_format_array( $filter->post_ids, '%d' );
558 + $filter->where[] = $this->wpdb->prepare( 'AND p.ID IN (' . $list_ids_format . ')', $filter->post_ids );
559 + }
560 +
561 + // Title, do not distinguish diacritics
562 + if ( $filter->post_title ) {
563 + //$filter->where[] = $this->wpdb->prepare( 'AND p.post_title COLLATE utf8mb4_unicode_ci LIKE %s', '%' . $filter->post_title . '%' );
564 + $filter->where[] = $this->wpdb->prepare( 'AND p.post_title LIKE %s', '%' .$filter->post_title . '%' );
565 + }
566 +
567 + // Slug
568 + if ( $filter->post_name ) {
569 + $filter->where[] = $this->wpdb->prepare( 'AND p.post_name = %s', $filter->post_name );
570 + }
571 +
572 + // Author
573 + if ( isset( $filter->post_author ) ) {
574 + $filter->where[] = $this->wpdb->prepare( 'AND p.post_author = %d', $filter->post_author );
575 + }
576 + // Authors
577 + if ( ! empty( $filter->post_authors ) ) {
578 + $post_authors_format = LP_Helper::db_format_array( $filter->post_authors, '%d' );
579 + $filter->where[] = $this->wpdb->prepare( 'AND p.post_author IN (' . $post_authors_format . ')', $filter->post_authors );
580 + }
581 +
582 + $filter = apply_filters( 'lp/course/query/filter', $filter );
583 +
584 + return $this->execute( $filter, $total_rows );
585 + }
586 +
587 + /**
588 + * Get list courses sort by price
589 + *
590 + * @param LP_Course_Filter $filter
591 + *
592 + * @return LP_Course_Filter
593 + * @since 4.1.5
594 + * @author tungnx
595 + * @version 1.0.0
596 + */
597 + public function get_courses_order_by_price( LP_Course_Filter &$filter ): LP_Course_Filter {
598 + $filter->join[] = "INNER JOIN $this->tb_postmeta AS pm ON p.ID = pm.post_id";
599 + $filter->where[] = $this->wpdb->prepare( 'AND pm.meta_key = %s', '_lp_price' );
600 + $filter->order_by = 'CAST( pm.meta_value AS UNSIGNED )';
601 +
602 + return $filter;
603 + }
604 +
605 + /**
606 + * Get list courses is on sale
607 + *
608 + * @param LP_Course_Filter $filter
609 + *
610 + * @return LP_Course_Filter
611 + * @since 4.1.5
612 + * @author tungnx
613 + * @version 1.0.0
614 + */
615 + public function get_courses_sort_by_sale( LP_Course_Filter &$filter ): LP_Course_Filter {
616 + $filter->join[] = "INNER JOIN $this->tb_postmeta AS pm ON p.ID = pm.post_id";
617 + $filter->where[] = $this->wpdb->prepare( 'AND pm.meta_key = %s', '_lp_course_is_sale' );
618 +
619 + return $filter;
620 + }
621 +
622 + /**
623 + * Get list courses is Free
624 + *
625 + * @param LP_Course_Filter $filter
626 + *
627 + * @return LP_Course_Filter
628 + * @throws Exception
629 + * @version 1.0.0
630 + * @since 4.2.3.2
631 + */
632 + public function get_courses_sort_by_free( LP_Course_Filter &$filter ): LP_Course_Filter {
633 + $filter_course_price = new LP_Course_Filter();
634 + $filter_course_price->only_fields = [ 'DISTINCT(ID)' ];
635 + $filter_course_price = $this->get_courses_sort_by_paid( $filter_course_price );
636 + $filter_course_price->return_string_query = true;
637 + $courses_price = $this->get_courses( $filter_course_price );
638 +
639 + $filter->join[] = "INNER JOIN $this->tb_postmeta AS pmfr ON p.ID = pmfr.post_id";
640 + $filter->where[] = 'AND ID NOT IN( ' . $courses_price . ' )';
641 +
642 + return $filter;
643 + }
644 +
645 + /**
646 + * Get list courses has price
647 + *
648 + * @param LP_Course_Filter $filter
649 + *
650 + * @return LP_Course_Filter
651 + * @version 1.0.0
652 + * @since 4.2.3.2
653 + */
654 + public function get_courses_sort_by_paid( LP_Course_Filter $filter ): LP_Course_Filter {
655 + $filter->join[] = "INNER JOIN $this->tb_postmeta AS pmp ON p.ID = pmp.post_id";
656 + $filter->where[] = $this->wpdb->prepare( 'AND pmp.meta_key = %s AND pmp.meta_value > %d', '_lp_price', 0 );
657 +
658 + return $filter;
659 + }
660 +
661 + /**
662 + * Get list courses is on feature
663 + *
664 + * @param LP_Course_Filter $filter
665 + *
666 + * @return LP_Course_Filter
667 + * @author tungnx
668 + * @version 1.0.0
669 + * @since 4.1.5
670 + */
671 + public function get_courses_sort_by_feature( LP_Course_Filter &$filter ): LP_Course_Filter {
672 + $filter->join[] = "INNER JOIN $this->tb_postmeta AS pmf ON p.ID = pmf.post_id";
673 + $filter->where[] = $this->wpdb->prepare( 'AND pmf.meta_key = %s', '_lp_featured' );
674 + $filter->where[] = $this->wpdb->prepare( 'AND pmf.meta_value = %s', 'yes' );
675 +
676 + return $filter;
677 + }
678 +
679 + /**
680 + * Count total courses free on category
681 + *
682 + * @param LP_Course_Filter $filter
683 + *
684 + * @return int
685 + * @since 4.2.5.4
686 + * @version 1.0.1
687 + */
688 + public function count_course_free( LP_Course_Filter $filter ): int {
689 + $count = 0;
690 +
691 + try {
692 + $filter->only_fields = [ 'COUNT( DISTINCT(ID) )' ];
693 + $filter->order_by = ''; // Must set to empty to avoid error from param Request
694 + $this->get_courses_sort_by_free( $filter );
695 + $filter->return_string_query = true;
696 + $query_count = $this->get_courses( $filter, $count );
697 + $count = $this->wpdb->get_var( $query_count );
698 + } catch ( Throwable $e ) {
699 + LP_Debug::error_log( $e );
700 + }
701 +
702 + return (int) $count;
703 + }
704 +
705 + /**
706 + * Get list courses is on popular
707 + * Use "UNION" to merge 2 query
708 + *
709 + * @param LP_Course_Filter $filter
710 + *
711 + * @return LP_Course_Filter
712 + * @throws Exception
713 + * @version 1.0.1
714 + * @since 4.1.6
715 + * @author minhpd
716 + */
717 + public function get_courses_order_by_popular( LP_Course_Filter &$filter ): LP_Course_Filter {
718 + // Set list name columns get
719 + //$columns_table_posts = $this->get_cols_of_table( $this->tb_posts );
720 + //$filter->fields = array_merge( $columns_table_posts, $filter->fields );
721 +
722 + $filter_user_course = clone $filter;
723 + $filter_course_not_attend = clone $filter;
724 +
725 + // Query get users total attend courses
726 + $fields_user_course_require = [ 'ID', 'COUNT(ID) AS total' ];
727 + $filter_user_course->fields = array( 'ID', 'COUNT(ID) AS total' );
728 + if ( ! empty( $filter_user_course->only_fields ) ) {
729 + $pattern = '#ID.*#';
730 + foreach ( $filter_user_course->only_fields as $k => $field ) {
731 + if ( preg_match( $pattern, $field ) ) {
732 + unset( $filter_user_course->only_fields[ $k ] );
733 + }
734 + }
735 + $filter_user_course->only_fields = array_unique( array_merge( $filter_user_course->only_fields, $fields_user_course_require ) );
736 + }
737 +
738 + $filter_user_course->join[] = "INNER JOIN {$this->tb_lp_user_items} AS ui ON p.ID = ui.item_id";
739 + $filter_user_course->where[] = $this->wpdb->prepare( 'AND ui.item_type = %s', LP_COURSE_CPT );
740 + $filter_user_course->where[] = $this->wpdb->prepare(
741 + 'AND (status = %s OR status = %s OR status = %s)',
742 + LP_COURSE_ENROLLED,
743 + LP_COURSE_PURCHASED,
744 + LP_COURSE_FINISHED
745 + );
746 + $filter_user_course->group_by = 'p.ID';
747 + $filter_user_course->order_by = '';
748 + $filter_user_course->return_string_query = true;
749 + $query_user_course = LP_Course_DB::getInstance()->get_courses( $filter_user_course );
750 +
751 + // Query get courses not attend
752 + $filter_user_course_cl = clone $filter_user_course;
753 + $filter_user_course_cl->only_fields = array( 'ID' );
754 + $query_user_course_for_not_in = LP_Course_DB::getInstance()->get_courses( $filter_user_course_cl );
755 +
756 + $fields_user_course_not_attend_require = [ 'ID', '0 AS total' ];
757 + $filter_course_not_attend->fields = [ 'ID', '0 AS total' ];
758 + if ( ! empty( $filter_course_not_attend->only_fields ) ) {
759 + $pattern = '#ID.*#';
760 + foreach ( $filter_course_not_attend->only_fields as $k => $field ) {
761 + if ( preg_match( $pattern, $field ) ) {
762 + unset( $filter_course_not_attend->only_fields[ $k ] );
763 + }
764 + }
765 + $filter_course_not_attend->only_fields = array_unique( array_merge( $filter_course_not_attend->only_fields, $fields_user_course_not_attend_require ) );
766 + }
767 + $filter_course_not_attend->where[] = 'AND p.ID NOT IN(' . $query_user_course_for_not_in . ')';
768 +
769 + $filter_course_not_attend->order_by = '';
770 + $filter_course_not_attend->return_string_query = true;
771 + $query_course_not_attend = LP_Course_DB::getInstance()->get_courses( $filter_course_not_attend );
772 +
773 + $filter->union[] = $query_user_course;
774 + $filter->union[] = $query_course_not_attend;
775 + $filter->order_by = 'total';
776 + $filter->order = 'DESC';
777 +
778 + return $filter;
779 + }
780 +
781 + /**
782 + * Get total courses of Author
783 + *
784 + * @param int $author_id
785 + *
786 + * @return LP_Course_Filter
787 + * @throws Exception
788 + * @version 1.0.0
789 + * @since 4.1.6
790 + * @deprecated 4.2.6.6 not use anywhere
791 + */
792 + public function count_courses_publish_of_author( int $author_id ): LP_Course_Filter {
793 + _deprecated_function( __METHOD__, '4.2.6.6' );
794 + $filter_course = new LP_Course_Filter();
795 + $filter_course->only_fields = array( 'ID' );
796 + $filter_course->post_author = $author_id;
797 + $filter_course->post_status = 'publish';
798 + $filter_course->field_count = 'ID';
799 + $filter_course->query_count = true;
800 +
801 + return apply_filters( 'lp/user/course/query/filter/count-users-attend-courses-of-author', $filter_course );
802 + }
803 +
804 + /**
805 + * Get total courses of Author
806 + *
807 + * @param int $author_id
808 + * @param array $status
809 + *
810 + * @return LP_Course_Filter
811 + * @since 4.2.3
812 + * @version 1.0.0
813 + */
814 + public function count_courses_of_author( int $author_id, array $status = [] ): LP_Course_Filter {
815 + $filter_course = new LP_Course_Filter();
816 + $filter_course->only_fields = array( 'ID' );
817 + $filter_course->post_author = $author_id;
818 + $filter_course->post_status = $status;
819 + if ( empty( $status ) ) {
820 + $filter_course->post_status = [];
821 + }
822 + $filter_course->field_count = 'ID';
823 + $filter_course->query_count = true;
824 +
825 + return apply_filters( 'lp/user/course/query/filter/count-courses-of-author', $filter_course );
826 + }
827 +
828 + /**
829 + * Get child categories of category and add to query OR
830 + *
831 + * @param array $term_ids
832 + *
833 + * @return array
834 + * @throws Exception
835 + * @version 1.0.1
836 + * @since 4.2.6.5
837 + */
838 + public function recursion_sub_categories( array $term_ids ): array {
839 + $total_found = 0;
840 + $term_ids_format = join( ',', $term_ids );
841 + $filter_sub_category = new LP_Filter();
842 + $filter_sub_category->collection = $this->tb_term_taxonomy;
843 + $filter_sub_category->collection_alias = 'tx';
844 + $filter_sub_category->field_count = 'tx.term_id';
845 + $filter_sub_category->only_fields = [ 'term_id' ];
846 + $filter_sub_category->where[] = "AND tx.parent IN ($term_ids_format)";
847 + $filter_sub_category->limit = -1;
848 + $query_sub_category = $this->execute( $filter_sub_category, $total_found );
849 + $term_sub_ids = [];
850 +
851 + if ( $total_found > 0 ) {
852 + foreach ( $query_sub_category as $term_id ) {
853 + $term_sub_ids[] = $term_id->term_id;
854 + }
855 +
856 + $term_sub_idss = $this->recursion_sub_categories( $term_sub_ids );
857 + $term_sub_ids = array_merge( $term_sub_ids, $term_sub_idss );
858 + }
859 +
860 + return array_merge( $term_ids, $term_sub_ids );
861 + }
862 +}