PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 3.7.2
Tutor LMS – eLearning and online course solution v3.7.2
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 11 months ago Ajax.php 1 year ago Announcements.php 1 year ago Assets.php 11 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 10 months ago Course_Embed.php 3 years ago Course_Filter.php 1 year ago Course_List.php 10 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 1 year ago FormHandler.php 2 years ago Frontend.php 1 year ago Gutenberg.php 1 year ago Icon.php 10 months ago Input.php 1 year ago Instructor.php 1 year ago Instructors_List.php 11 months ago Lesson.php 10 months ago Options_V2.php 11 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 10 months ago QuizBuilder.php 11 months ago Quiz_Attempts_List.php 11 months ago RestAPI.php 2 years ago Reviews.php 11 months ago Rewrite_Rules.php 2 years ago Shortcode.php 1 year ago Singleton.php 1 year ago Student.php 1 year ago Students_List.php 1 year ago Taxonomies.php 1 year ago Template.php 11 months ago Theme_Compatibility.php 3 years ago Tools.php 1 year ago Tools_V2.php 1 year ago Tutor.php 10 months ago TutorEDD.php 1 year ago Tutor_Base.php 2 years ago Tutor_Setup.php 1 year ago Upgrader.php 10 months ago User.php 1 year ago Utils.php 10 months ago Video_Stream.php 3 years ago WhatsNew.php 2 years ago Withdraw.php 1 year ago Withdraw_Requests_List.php 11 months ago WooCommerce.php 1 year ago
Course_List.php
549 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 /**
280 * Handle bulk action for enrollment cancel | delete
281 *
282 * @return void
283 * @since 2.0.0
284 */
285 public function course_list_bulk_action() {
286
287 tutor_utils()->checking_nonce();
288
289 $action = Input::post( 'bulk-action', '' );
290 $bulk_ids = Input::post( 'bulk-ids', '' );
291
292 // Check if user is privileged.
293 if ( ! current_user_can( 'administrator' ) ) {
294 if ( current_user_can( 'edit_tutor_course' ) ) {
295 $can_publish_course = tutor_utils()->get_option( 'instructor_can_publish_course' );
296
297 if ( 'publish' === $action && ! $can_publish_course ) {
298 wp_send_json_error( tutor_utils()->error_message() );
299 }
300 } else {
301 wp_send_json_error( tutor_utils()->error_message() );
302 }
303 }
304
305 if ( '' === $action || '' === $bulk_ids ) {
306 wp_send_json_error( array( 'message' => __( 'Please select appropriate action', 'tutor' ) ) );
307 exit;
308 }
309
310 if ( 'delete' === $action ) {
311 // Do action before delete.
312 do_action( 'before_tutor_course_bulk_action_delete', $bulk_ids );
313
314 $delete_courses = self::bulk_delete_course( $bulk_ids );
315
316 do_action( 'after_tutor_course_bulk_action_delete', $bulk_ids );
317 $delete_courses ? wp_send_json_success() : wp_send_json_error( array( 'message' => __( 'Could not delete selected courses', 'tutor' ) ) );
318 exit;
319 }
320
321 /**
322 * Do action before course update
323 *
324 * @param string $action (publish | pending | draft | trash).
325 * @param array $bulk_ids, course id.
326 */
327 do_action( 'before_tutor_course_bulk_action_update', $action, $bulk_ids );
328
329 $update_status = self::update_course_status( $action, $bulk_ids );
330
331 do_action( 'after_tutor_course_bulk_action_update', $action, $bulk_ids );
332
333 $update_status ? wp_send_json_success() : wp_send_json_error( array( 'message' => __( 'Could not update course status', 'tutor' ) ) );
334
335 exit;
336 }
337
338 /**
339 * Handle ajax request for updating course status
340 *
341 * @return void
342 * @since 2.0.0
343 */
344 public static function tutor_change_course_status() {
345 tutor_utils()->checking_nonce();
346
347 $status = Input::post( 'status' );
348 $id = Input::post( 'id' );
349 $course = get_post( $id );
350
351 // Check if user is privileged.
352 if ( ! current_user_can( 'administrator' ) ) {
353
354 if ( ! tutor_utils()->can_user_edit_course( get_current_user_id(), $course->ID ) ) {
355 wp_send_json_error( tutor_utils()->error_message() );
356 }
357
358 $can_delete_course = tutor_utils()->get_option( 'instructor_can_delete_course' );
359 $can_publish_course = tutor_utils()->get_option( 'instructor_can_publish_course' );
360
361 if ( 'publish' === $status && ! $can_publish_course ) {
362 wp_send_json_error( tutor_utils()->error_message() );
363 }
364
365 if ( 'trash' === $status && $can_delete_course ) {
366 $args = array(
367 'ID' => $id,
368 'post_status' => $status,
369 );
370 $trash_post = wp_update_post( $args );
371
372 if ( $trash_post ) {
373 wp_send_json_success( __( 'Course trashed successfully', 'tutor' ) );
374 }
375 }
376 }
377
378 if ( ! CourseModel::get_post_types( $course ) ) {
379 wp_send_json_error( tutor_utils()->error_message() );
380 }
381
382 $args = array(
383 'ID' => $id,
384 'post_status' => $status,
385 );
386
387 if ( CourseModel::STATUS_FUTURE === $course->post_status && CourseModel::STATUS_PUBLISH === $status ) {
388 $args['post_status'] = CourseModel::STATUS_PUBLISH;
389 $args['post_date'] = current_time( 'mysql' );
390 $args['post_date_gmt'] = current_time( 'mysql', 1 );
391 }
392
393 wp_update_post( $args );
394 wp_send_json_success();
395 exit;
396 }
397
398 /**
399 * Handle ajax request for deleting course
400 *
401 * @since 2.0.0
402 *
403 * @return void JSON response
404 */
405 public static function tutor_course_delete() {
406 tutor_utils()->checking_nonce();
407
408 $user_id = get_current_user_id();
409 $course_id = Input::post( 'id', 0, Input::TYPE_INT );
410
411 // Check if user is privileged.
412 if ( ! tutor_utils()->can_user_edit_course( $user_id, $course_id ) ) {
413 wp_send_json_error( tutor_utils()->error_message() );
414 }
415
416 $delete = CourseModel::delete_course( $course_id );
417
418 if ( $delete ) {
419 wp_send_json_success( __( 'Course has been deleted ', 'tutor' ) );
420 } else {
421 wp_send_json_error( __( 'Course delete failed ', 'tutor' ) );
422 }
423
424 exit;
425 }
426
427 /**
428 * Execute bulk delete action
429 *
430 * @param string $bulk_ids ids that need to update.
431 * @return bool
432 * @since 2.0.0
433 */
434 public static function bulk_delete_course( $bulk_ids ): bool {
435 $bulk_ids = explode( ',', sanitize_text_field( $bulk_ids ) );
436
437 foreach ( $bulk_ids as $post_id ) {
438 CourseModel::delete_course( $post_id );
439 }
440
441 return true;
442 }
443
444 /**
445 * Update course status
446 *
447 * @param string $status for updating course status.
448 * @param string $bulk_ids comma separated ids.
449 *
450 * @return bool
451 *
452 * @since 2.0.0
453 */
454 public static function update_course_status( string $status, $bulk_ids ): bool {
455 global $wpdb;
456 $post_table = $wpdb->posts;
457 $status = sanitize_text_field( $status );
458 $bulk_ids = sanitize_text_field( $bulk_ids );
459
460 $ids = array_map( 'intval', explode( ',', $bulk_ids ) );
461 $in_clause = QueryHelper::prepare_in_clause( $ids );
462
463 $update = $wpdb->query(
464 $wpdb->prepare(
465 "UPDATE {$post_table} SET post_status = %s WHERE ID IN ($in_clause)", //phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
466 $status
467 )
468 );
469
470 return true;
471 }
472
473 /**
474 * Get course enrollment list with student info
475 *
476 * @param int $course_id int | required.
477 * @return array
478 * @since 2.0.0
479 */
480 public static function course_enrollments_with_student_details( int $course_id ) {
481 global $wpdb;
482 $course_id = sanitize_text_field( $course_id );
483 $course_completed = 0;
484 $course_inprogress = 0;
485
486 $enrollments = $wpdb->get_results(
487 $wpdb->prepare(
488 "SELECT enroll.ID AS enroll_id, enroll.post_author AS enroll_author, user.*, course.ID AS course_id
489 FROM {$wpdb->posts} AS enroll
490 LEFT JOIN {$wpdb->users} AS user ON user.ID = enroll.post_author
491 LEFT JOIN {$wpdb->posts} AS course ON course.ID = enroll.post_parent
492 WHERE enroll.post_type = %s
493 AND enroll.post_status = %s
494 AND enroll.post_parent = %d
495 ",
496 'tutor_enrolled',
497 'completed',
498 $course_id
499 )
500 );
501
502 foreach ( $enrollments as $enrollment ) {
503 $course_progress = tutor_utils()->get_course_completed_percent( $course_id, $enrollment->enroll_author );
504 if ( 100 == $course_progress ) {
505 $course_completed++;
506 } else {
507 $course_inprogress++;
508 }
509 }
510
511 return array(
512 'enrollments' => $enrollments,
513 'total_completed' => $course_completed,
514 'total_inprogress' => $course_inprogress,
515 'total_enrollments' => count( $enrollments ),
516 );
517 }
518
519 /**
520 * Check wheather course is public or not
521 *
522 * @param integer $course_id course id to check with.
523 * @return boolean true if public otherwise false.
524 * @since 1.0.0
525 */
526 public static function is_public( int $course_id ): bool {
527 $is_public = get_post_meta( $course_id, '_tutor_is_public_course', true );
528 return 'yes' === $is_public ? true : false;
529 }
530
531 /**
532 * Query for obtaining course list.
533 *
534 * @since 3.4.0
535 *
536 * @param array $args the query args.
537 * @param int $user_id the user id.
538 * @param string $status the post status.
539 * @param bool $all_post_types should keep all post types.
540 *
541 * @return \WP_Query
542 */
543 public static function course_list_query( $args, $user_id, $status, $all_post_types = false ) {
544
545 $course_list_query = new \WP_Query( apply_filters( 'tutor_admin_course_list', $args, $user_id, $status, $all_post_types ) );
546 return $course_list_query;
547 }
548 }
549