PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 3.9.10
Tutor LMS – eLearning and online course solution v3.9.10
3.9.14 3.9.13 3.9.12 3.9.11 trunk 1.0.0 1.0.0-alpha 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.2.0 1.2.1 1.2.11 1.2.12 1.2.13 1.2.20 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 1.8.10 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.15 1.9.16 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.4.0 2.5.0 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.1.0 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.7.0 3.7.1 3.7.2 3.7.3 3.7.4 3.8.0 3.8.1 3.8.2 3.8.3 3.9.0 3.9.1 3.9.10 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9
tutor / classes / Course_List.php
tutor / classes Last commit date
Addons.php 11 months ago Admin.php 2 months ago Ajax.php 9 months ago Announcements.php 1 year ago Assets.php 2 months ago Backend_Page_Trait.php 1 year ago BaseController.php 1 year ago Config.php 11 months ago Container.php 11 months ago Course.php 2 months ago Course_Embed.php 3 years ago Course_Filter.php 1 year ago Course_List.php 5 months ago Course_Settings_Tabs.php 1 year ago Course_Widget.php 1 year ago Custom_Validation.php 3 years ago Dashboard.php 1 year ago Earnings.php 9 months ago FormHandler.php 2 years ago Frontend.php 1 year ago Gutenberg.php 1 year ago Icon.php 8 months ago Input.php 1 year ago Instructor.php 2 months ago Instructors_List.php 2 months ago Lesson.php 8 months ago Options_V2.php 7 months ago Permalink.php 2 years ago Post_types.php 1 year ago Private_Course_Access.php 1 year ago Q_And_A.php 10 months ago Question_Answers_List.php 11 months ago Quiz.php 5 months ago QuizBuilder.php 3 months ago Quiz_Attempts_List.php 9 months ago RestAPI.php 2 years ago Reviews.php 9 months ago Rewrite_Rules.php 2 years ago Shortcode.php 9 months ago Singleton.php 1 year ago Student.php 2 months ago Students_List.php 1 year ago Taxonomies.php 1 year ago Template.php 9 months ago Theme_Compatibility.php 3 years ago Tools.php 1 year ago Tools_V2.php 1 year ago Tutor.php 2 months ago TutorEDD.php 1 year ago Tutor_Base.php 2 years ago Tutor_Setup.php 8 months ago Upgrader.php 9 months ago User.php 4 months ago Utils.php 2 months ago Video_Stream.php 3 years ago WhatsNew.php 9 months ago Withdraw.php 1 year ago Withdraw_Requests_List.php 11 months ago WooCommerce.php 7 months ago
Course_List.php
561 lines
1 <?php
2 /**
3 * Manage Course List
4 *
5 * @package Tutor
6 * @author Themeum <support@themeum.com>
7 * @link https://themeum.com
8 * @since 2.0.0
9 */
10
11 namespace TUTOR;
12
13 use Tutor\Helpers\QueryHelper;
14 use Tutor\Models\CourseModel;
15
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit;
18 }
19 /**
20 * Course List class
21 *
22 * @since 2.0.0
23 */
24 class Course_List {
25 /**
26 * Trait for utilities
27 *
28 * @var $page_title
29 */
30
31 use Backend_Page_Trait;
32
33 /**
34 * Bulk Action
35 *
36 * @var $bulk_action
37 */
38 public $bulk_action = true;
39
40 /**
41 * Constructor
42 *
43 * @return void
44 * @since 2.0.0
45 */
46 public function __construct() {
47 /**
48 * Handle bulk action
49 *
50 * @since v2.0.0
51 */
52 add_action( 'wp_ajax_tutor_course_list_bulk_action', array( $this, 'course_list_bulk_action' ) );
53 /**
54 * Handle ajax request for updating course status
55 *
56 * @since v2.0.0
57 */
58 add_action( 'wp_ajax_tutor_change_course_status', array( $this, 'tutor_change_course_status' ) );
59 /**
60 * Handle ajax request for delete course
61 *
62 * @since v2.0.0
63 */
64 add_action( 'wp_ajax_tutor_course_delete', array( $this, 'tutor_course_delete' ) );
65 }
66
67 /**
68 * Page title fallback
69 *
70 * @since 3.5.0
71 *
72 * @param string $name Property name.
73 *
74 * @return string
75 */
76 public function __get( $name ) {
77 if ( 'page_title' === $name ) {
78 return esc_html__( 'Courses', 'tutor' );
79 }
80 }
81
82 /**
83 * Prepare bulk actions that will show on dropdown options
84 *
85 * @return array
86 * @since 2.0.0
87 */
88 public function prepare_bulk_actions(): array {
89 $actions = array(
90 $this->bulk_action_default(),
91 $this->bulk_action_publish(),
92 $this->bulk_action_pending(),
93 $this->bulk_action_draft(),
94 );
95
96 $active_tab = Input::get( 'data', '' );
97
98 if ( 'trash' === $active_tab ) {
99 array_push( $actions, $this->bulk_action_delete() );
100 }
101 if ( 'trash' !== $active_tab ) {
102 array_push( $actions, $this->bulk_action_trash() );
103 }
104
105 if ( ! current_user_can( 'administrator' ) ) {
106 $can_trash_post = tutor_utils()->get_option( 'instructor_can_delete_course' ) && current_user_can( 'edit_tutor_course' );
107 if ( ! $can_trash_post ) {
108 $actions = array_filter(
109 $actions,
110 function ( $val ) {
111 return 'trash' !== $val['value'];
112 }
113 );
114 }
115 }
116 return apply_filters( 'tutor_course_bulk_actions', $actions );
117 }
118
119 /**
120 * Available tabs that will visible on the right side of page navbar
121 *
122 * @param string $category_slug category slug.
123 * @param integer $course_id course ID.
124 * @param string $date selected date | optional.
125 * @param string $search search by user name or email | optional.
126 *
127 * @return array
128 *
129 * @since v2.0.0
130 */
131 public function tabs_key_value( $category_slug, $course_id, $date, $search ): array {
132 $url = apply_filters( 'tutor_data_tab_base_url', get_pagenum_link() );
133
134 $all = self::count_course( 'all', $category_slug, $course_id, $date, $search );
135 $mine = self::count_course( 'mine', $category_slug, $course_id, $date, $search );
136 $published = self::count_course( 'publish', $category_slug, $course_id, $date, $search );
137 $draft = self::count_course( 'draft', $category_slug, $course_id, $date, $search );
138 $pending = self::count_course( 'pending', $category_slug, $course_id, $date, $search );
139 $trash = self::count_course( 'trash', $category_slug, $course_id, $date, $search );
140 $private = self::count_course( 'private', $category_slug, $course_id, $date, $search );
141 $future = self::count_course( 'future', $category_slug, $course_id, $date, $search );
142
143 $tabs = array(
144 array(
145 'key' => '',
146 'title' => __( 'All', 'tutor' ),
147 'value' => $all,
148 'url' => $url . '&data=all',
149 ),
150 array(
151 'key' => 'mine',
152 'title' => __( 'Mine', 'tutor' ),
153 'value' => $mine,
154 'url' => $url . '&data=mine',
155 ),
156 array(
157 'key' => 'published',
158 'title' => __( 'Published', 'tutor' ),
159 'value' => $published,
160 'url' => $url . '&data=published',
161 ),
162 array(
163 'key' => 'draft',
164 'title' => __( 'Draft', 'tutor' ),
165 'value' => $draft,
166 'url' => $url . '&data=draft',
167 ),
168 array(
169 'key' => 'pending',
170 'title' => __( 'Pending', 'tutor' ),
171 'value' => $pending,
172 'url' => $url . '&data=pending',
173 ),
174 array(
175 'key' => 'future',
176 'title' => __( 'Scheduled', 'tutor' ),
177 'value' => $future,
178 'url' => $url . '&data=future',
179 ),
180 array(
181 'key' => 'private',
182 'title' => __( 'Private', 'tutor' ),
183 'value' => $private,
184 'url' => $url . '&data=private',
185 ),
186 array(
187 'key' => 'trash',
188 'title' => __( 'Trash', 'tutor' ),
189 'value' => $trash,
190 'url' => $url . '&data=trash',
191 ),
192 );
193 if ( ! tutor_utils()->get_option( 'instructor_can_delete_course' ) && ! current_user_can( 'administrator' ) ) {
194 unset( $tabs[7] );
195 }
196 return apply_filters( 'tutor_course_tabs', $tabs );
197 }
198
199 /**
200 * Count courses by status & filters
201 * Count all | min | published | pending | draft
202 *
203 * @param string $status | required.
204 * @param string $category_slug course category | optional.
205 * @param string $course_id selected course id | optional.
206 * @param string $date selected date | optional.
207 * @param string $search_term search by user name or email | optional.
208 *
209 * @return int
210 *
211 * @since 2.0.0
212 */
213 protected static function count_course( string $status, $category_slug = '', $course_id = '', $date = '', $search_term = '' ): int {
214 $user_id = get_current_user_id();
215 $status = sanitize_text_field( $status );
216 $course_id = sanitize_text_field( $course_id );
217 $date = sanitize_text_field( $date );
218 $search_term = sanitize_text_field( $search_term );
219 $category_slug = sanitize_text_field( $category_slug );
220
221 $args = array(
222 'post_type' => tutor()->course_post_type,
223 );
224
225 if ( 'all' === $status || 'mine' === $status ) {
226 $args['post_status'] = array( 'publish', 'pending', 'draft', 'private', 'future' );
227 } else {
228 $args['post_status'] = array( $status );
229 }
230
231 // Author query.
232 if ( 'mine' === $status || ! current_user_can( 'administrator' ) ) {
233 $args['author'] = $user_id;
234 }
235
236 $date_filter = sanitize_text_field( $date );
237
238 $year = date( 'Y', strtotime( $date_filter ) );
239 $month = date( 'm', strtotime( $date_filter ) );
240 $day = date( 'd', strtotime( $date_filter ) );
241
242 // Add date query.
243 if ( '' !== $date_filter ) {
244 $args['date_query'] = array(
245 array(
246 'year' => $year,
247 'month' => $month,
248 'day' => $day,
249 ),
250 );
251 }
252
253 if ( '' !== $course_id ) {
254 $args['p'] = $course_id;
255 }
256
257 // Search filter.
258 if ( '' !== $search_term ) {
259 $args['s'] = $search_term;
260 }
261
262 // Category filter.
263 if ( '' !== $category_slug ) {
264 $args['tax_query'] = array(
265 array(
266 'taxonomy' => CourseModel::COURSE_CATEGORY,
267 'field' => 'slug',
268 'terms' => $category_slug,
269 ),
270 );
271 }
272
273 $the_query = self::course_list_query( $args, $user_id, $status );
274
275 return ! is_null( $the_query ) && isset( $the_query->found_posts ) ? $the_query->found_posts : $the_query;
276 }
277
278 /**
279 * Handle bulk action for enrollment cancel | delete
280 *
281 * @return void
282 * @since 2.0.0
283 */
284 public function course_list_bulk_action() {
285
286 tutor_utils()->checking_nonce();
287
288 $action = Input::post( 'bulk-action', '' );
289 $bulk_ids = Input::post( 'bulk-ids', '' );
290
291 // Check if user is privileged.
292 if ( ! current_user_can( 'administrator' ) ) {
293 $course_ids = explode( ',', $bulk_ids );
294
295 if ( current_user_can( 'edit_tutor_course' ) ) {
296 $can_publish_course = tutor_utils()->get_option( 'instructor_can_publish_course' );
297
298 if ( 'publish' === $action && ! $can_publish_course ) {
299 wp_send_json_error( tutor_utils()->error_message() );
300 }
301 } else {
302 wp_send_json_error( tutor_utils()->error_message() );
303 }
304
305 // Check if the course ids are instructors own course.
306 $course_ids = array_filter(
307 $course_ids,
308 function ( $course_id ) {
309 return tutor_utils()->is_instructor_of_this_course( get_current_user_id(), $course_id );
310 }
311 );
312
313 $bulk_ids = implode( ',', $course_ids );
314
315 }
316
317 if ( '' === $action || '' === $bulk_ids ) {
318 wp_send_json_error( array( 'message' => __( 'Please select appropriate action', 'tutor' ) ) );
319 exit;
320 }
321
322 if ( 'delete' === $action ) {
323 // Do action before delete.
324 do_action( 'before_tutor_course_bulk_action_delete', $bulk_ids );
325
326 $delete_courses = self::bulk_delete_course( $bulk_ids );
327
328 do_action( 'after_tutor_course_bulk_action_delete', $bulk_ids );
329 $delete_courses ? wp_send_json_success() : wp_send_json_error( array( 'message' => __( 'Could not delete selected courses', 'tutor' ) ) );
330 exit;
331 }
332
333 /**
334 * Do action before course update
335 *
336 * @param string $action (publish | pending | draft | trash).
337 * @param array $bulk_ids, course id.
338 */
339 do_action( 'before_tutor_course_bulk_action_update', $action, $bulk_ids );
340
341 $update_status = self::update_course_status( $action, $bulk_ids );
342
343 do_action( 'after_tutor_course_bulk_action_update', $action, $bulk_ids );
344
345 $update_status ? wp_send_json_success() : wp_send_json_error( array( 'message' => __( 'Could not update course status', 'tutor' ) ) );
346
347 exit;
348 }
349
350 /**
351 * Handle ajax request for updating course status
352 *
353 * @return void
354 * @since 2.0.0
355 */
356 public static function tutor_change_course_status() {
357 tutor_utils()->checking_nonce();
358
359 $status = Input::post( 'status' );
360 $id = Input::post( 'id' );
361 $course = get_post( $id );
362
363 // Check if user is privileged.
364 if ( ! current_user_can( 'administrator' ) ) {
365
366 if ( ! tutor_utils()->can_user_edit_course( get_current_user_id(), $course->ID ) ) {
367 wp_send_json_error( tutor_utils()->error_message() );
368 }
369
370 $can_delete_course = tutor_utils()->get_option( 'instructor_can_delete_course' );
371 $can_publish_course = tutor_utils()->get_option( 'instructor_can_publish_course' );
372
373 if ( 'publish' === $status && ! $can_publish_course ) {
374 wp_send_json_error( tutor_utils()->error_message() );
375 }
376
377 if ( 'trash' === $status && $can_delete_course ) {
378 $args = array(
379 'ID' => $id,
380 'post_status' => $status,
381 );
382 $trash_post = wp_update_post( $args );
383
384 if ( $trash_post ) {
385 wp_send_json_success( __( 'Course trashed successfully', 'tutor' ) );
386 }
387 }
388 }
389
390 if ( ! CourseModel::get_post_types( $course ) ) {
391 wp_send_json_error( tutor_utils()->error_message() );
392 }
393
394 $args = array(
395 'ID' => $id,
396 'post_status' => $status,
397 );
398
399 if ( CourseModel::STATUS_FUTURE === $course->post_status && CourseModel::STATUS_PUBLISH === $status ) {
400 $args['post_status'] = CourseModel::STATUS_PUBLISH;
401 $args['post_date'] = current_time( 'mysql' );
402 $args['post_date_gmt'] = current_time( 'mysql', 1 );
403 }
404
405 wp_update_post( $args );
406 wp_send_json_success();
407 exit;
408 }
409
410 /**
411 * Handle ajax request for deleting course
412 *
413 * @since 2.0.0
414 *
415 * @return void JSON response
416 */
417 public static function tutor_course_delete() {
418 tutor_utils()->checking_nonce();
419
420 $user_id = get_current_user_id();
421 $course_id = Input::post( 'id', 0, Input::TYPE_INT );
422
423 // Check if user is privileged.
424 if ( ! tutor_utils()->can_user_edit_course( $user_id, $course_id ) ) {
425 wp_send_json_error( tutor_utils()->error_message() );
426 }
427
428 $delete = CourseModel::delete_course( $course_id );
429
430 if ( $delete ) {
431 wp_send_json_success( __( 'Course has been deleted ', 'tutor' ) );
432 } else {
433 wp_send_json_error( __( 'Course delete failed ', 'tutor' ) );
434 }
435
436 exit;
437 }
438
439 /**
440 * Execute bulk delete action
441 *
442 * @param string $bulk_ids ids that need to update.
443 * @return bool
444 * @since 2.0.0
445 */
446 public static function bulk_delete_course( $bulk_ids ): bool {
447 $bulk_ids = explode( ',', sanitize_text_field( $bulk_ids ) );
448
449 foreach ( $bulk_ids as $post_id ) {
450 CourseModel::delete_course( $post_id );
451 }
452
453 return true;
454 }
455
456 /**
457 * Update course status
458 *
459 * @param string $status for updating course status.
460 * @param string $bulk_ids comma separated ids.
461 *
462 * @return bool
463 *
464 * @since 2.0.0
465 */
466 public static function update_course_status( string $status, $bulk_ids ): bool {
467 global $wpdb;
468 $post_table = $wpdb->posts;
469 $status = sanitize_text_field( $status );
470 $bulk_ids = sanitize_text_field( $bulk_ids );
471
472 $ids = array_map( 'intval', explode( ',', $bulk_ids ) );
473 $in_clause = QueryHelper::prepare_in_clause( $ids );
474
475 $update = $wpdb->query(
476 $wpdb->prepare(
477 "UPDATE {$post_table} SET post_status = %s WHERE ID IN ($in_clause)", //phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
478 $status
479 )
480 );
481
482 return true;
483 }
484
485 /**
486 * Get course enrollment list with student info
487 *
488 * @param int $course_id int | required.
489 * @return array
490 * @since 2.0.0
491 */
492 public static function course_enrollments_with_student_details( int $course_id ) {
493 global $wpdb;
494 $course_id = sanitize_text_field( $course_id );
495 $course_completed = 0;
496 $course_inprogress = 0;
497
498 $enrollments = $wpdb->get_results(
499 $wpdb->prepare(
500 "SELECT enroll.ID AS enroll_id, enroll.post_author AS enroll_author, user.*, course.ID AS course_id
501 FROM {$wpdb->posts} AS enroll
502 LEFT JOIN {$wpdb->users} AS user ON user.ID = enroll.post_author
503 LEFT JOIN {$wpdb->posts} AS course ON course.ID = enroll.post_parent
504 WHERE enroll.post_type = %s
505 AND enroll.post_status = %s
506 AND enroll.post_parent = %d
507 ",
508 'tutor_enrolled',
509 'completed',
510 $course_id
511 )
512 );
513
514 foreach ( $enrollments as $enrollment ) {
515 $course_progress = tutor_utils()->get_course_completed_percent( $course_id, $enrollment->enroll_author );
516 if ( 100 == $course_progress ) {
517 $course_completed++;
518 } else {
519 $course_inprogress++;
520 }
521 }
522
523 return array(
524 'enrollments' => $enrollments,
525 'total_completed' => $course_completed,
526 'total_inprogress' => $course_inprogress,
527 'total_enrollments' => count( $enrollments ),
528 );
529 }
530
531 /**
532 * Check wheather course is public or not
533 *
534 * @param integer $course_id course id to check with.
535 * @return boolean true if public otherwise false.
536 * @since 1.0.0
537 */
538 public static function is_public( int $course_id ): bool {
539 $is_public = get_post_meta( $course_id, '_tutor_is_public_course', true );
540 return 'yes' === $is_public ? true : false;
541 }
542
543 /**
544 * Query for obtaining course list.
545 *
546 * @since 3.4.0
547 *
548 * @param array $args the query args.
549 * @param int $user_id the user id.
550 * @param string $status the post status.
551 * @param bool $all_post_types should keep all post types.
552 *
553 * @return \WP_Query
554 */
555 public static function course_list_query( $args, $user_id, $status, $all_post_types = false ) {
556
557 $course_list_query = new \WP_Query( apply_filters( 'tutor_admin_course_list', $args, $user_id, $status, $all_post_types ) );
558 return $course_list_query;
559 }
560 }
561