PluginProbe
Tutor LMS – eLearning and online course solution / 3.9.2
Tutor LMS – eLearning and online course solution v3.9.2
4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 4.0.2 4.0.1 4.0.0 3.9.15 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 All 191 releases
← All changes | models/CourseModel.php +30 -462 4.0.23.9.2 View file →
@@ -9,16 +9,11 @@
9 9 */
10 10
11 11 namespace Tutor\Models;
12 12
13 -use Tutor\Helpers\DateTimeHelper;
14 -use TUTOR\Icon;
15 13 use TUTOR\Course;
16 -use TUTOR\Lesson;
17 14 use Tutor\Ecommerce\Tax;
18 -use InvalidArgumentException;
19 15 use Tutor\Helpers\QueryHelper;
20 -use TUTOR\User;
21 16 use TUTOR_ASSIGNMENTS\Assignments;
22 17
23 18 /**
24 19 * CourseModel Class
@@ -91,34 +86,8 @@
91 86 );
92 87 }
93 88
94 89 /**
95 - * Check if a course is available for enrollment or purchase.
96 - *
97 - * @since 4.0.0
98 - *
99 - * @param integer $course_id the course id.
100 - *
101 - * @return bool
102 - */
103 - public static function is_course_accessible( $course_id = 0 ) {
104 - $course = get_post( $course_id );
105 - if ( ! $course || ! is_object( $course ) ) {
106 - return false;
107 - }
108 -
109 - if ( ! in_array( $course->post_type, array( tutor()->course_post_type, tutor()->bundle_post_type ), true ) ) {
110 - return false;
111 - }
112 -
113 - if ( ! in_array( $course->post_status, array( self::STATUS_PUBLISH, self::STATUS_PRIVATE ), true ) ) {
114 - return false;
115 - }
116 -
117 - return true;
118 - }
119 -
120 - /**
121 90 * Course record count
122 91 *
123 92 * @since 2.0.7
124 93 *
@@ -272,12 +241,25 @@
272 241 * @since 1.0.0
273 242 *
274 243 * @param int $quiz_id quiz id.
275 244 *
276 - * @return mixed WP_Post|null
245 + * @return array|bool|null|object|void
277 246 */
278 247 public static function get_course_by_quiz( $quiz_id ) {
279 - return get_post_parent( get_post_parent( $quiz_id ) );
248 + $quiz_id = tutils()->get_post_id( $quiz_id );
249 + $post = get_post( $quiz_id );
250 +
251 + if ( $post ) {
252 + $course = get_post( $post->post_parent );
253 + if ( $course ) {
254 + if ( tutor()->course_post_type !== $course->post_type ) {
255 + $course = get_post( $course->post_parent );
256 + }
257 + return $course;
258 + }
259 + }
260 +
261 + return false;
280 262 }
281 263
282 264 /**
283 265 * Get courses by a instructor
@@ -283,9 +265,8 @@
283 265 * Get courses by a instructor
284 266 *
285 267 * @since 1.0.0
286 268 * @since 3.5.0 param $post_types added.
287 - * @since 4.0.0 params $search and $order added.
288 269 *
289 270 * @param integer $instructor_id instructor id.
290 271 * @param array|string $post_status post status.
291 272 * @param integer $offset offset.
@@ -291,23 +272,12 @@
291 272 * @param integer $offset offset.
292 273 * @param integer $limit limit.
293 274 * @param boolean $count_only count or not.
294 275 * @param array $post_types array of post types.
295 - * @param string $search search keyword.
296 - * @param string $order order.
297 276 *
298 277 * @return array|null|object
299 278 */
300 - public static function get_courses_by_instructor(
301 - $instructor_id = 0,
302 - $post_status = array( 'publish' ),
303 - int $offset = 0,
304 - int $limit = PHP_INT_MAX,
305 - $count_only = false,
306 - $post_types = array(),
307 - $search = '',
308 - $order = 'DESC'
309 - ) {
279 + public static function get_courses_by_instructor( $instructor_id = 0, $post_status = array( 'publish' ), int $offset = 0, int $limit = PHP_INT_MAX, $count_only = false, $post_types = array() ) {
310 280 global $wpdb;
311 281 $offset = sanitize_text_field( $offset );
312 282 $limit = sanitize_text_field( $limit );
313 283 $instructor_id = tutils()->get_user_id( $instructor_id );
@@ -325,21 +295,8 @@
325 295 $statuses = "'" . implode( "','", $post_status ) . "'";
326 296 $where_post_status = "AND $wpdb->posts.post_status IN({$statuses}) ";
327 297 }
328 298
329 - $search_sql = '';
330 - if ( ! empty( $search ) ) {
331 - $like = '%' . $wpdb->esc_like( $search ) . '%';
332 - $search_sql = $wpdb->prepare(
333 - " AND ( {$wpdb->posts}.post_title LIKE %s OR {$wpdb->posts}.post_excerpt LIKE %s ) ",
334 - $like,
335 - $like
336 - );
337 - }
338 -
339 - $order = strtoupper( $order ) === 'ASC' ? 'ASC' : 'DESC';
340 - $order_sql = " ORDER BY $wpdb->posts.post_date {$order} ";
341 -
342 299 $select_col = $count_only ? " COUNT(DISTINCT $wpdb->posts.ID) " : " $wpdb->posts.* ";
343 300 $limit_offset = $count_only ? '' : " LIMIT $offset, $limit ";
344 301
345 302 //phpcs:disable
@@ -344,18 +301,17 @@
344 301
345 302 //phpcs:disable
346 303 $query = $wpdb->prepare(
347 304 "SELECT $select_col
348 - FROM $wpdb->posts
305 + FROM $wpdb->posts
349 306 LEFT JOIN {$wpdb->usermeta}
350 - ON $wpdb->usermeta.user_id = %d
351 - AND $wpdb->usermeta.meta_key = %s
352 - AND $wpdb->usermeta.meta_value = $wpdb->posts.ID
353 - WHERE 1 = 1 {$where_post_status}
307 + ON $wpdb->usermeta.user_id = %d
308 + AND $wpdb->usermeta.meta_key = %s
309 + AND $wpdb->usermeta.meta_value = $wpdb->posts.ID
310 + WHERE 1 = 1 {$where_post_status}
354 311 AND $wpdb->posts.post_type IN ({$post_types})
355 312 AND ($wpdb->posts.post_author = %d OR $wpdb->usermeta.user_id = %d)
356 - {$search_sql}
357 - {$order_sql} {$limit_offset}",
313 + ORDER BY $wpdb->posts.post_date DESC $limit_offset",
358 314 $instructor_id,
359 315 '_tutor_instructor_course_id',
360 316 $instructor_id,
361 317 $instructor_id
@@ -541,49 +497,21 @@
541 497 foreach ( $topic_content_ids as $content_id ) {
542 498 /**
543 499 * Delete Quiz data
544 500 */
545 - if ( get_post_type( $content_id ) === $quiz_post_type ) {
546 - // Collect file paths from all question types that store files before deleting rows (files deleted after DB for safety).
547 - $attempts_for_quiz = QueryHelper::get_all( 'tutor_quiz_attempts', array( 'quiz_id' => $content_id ), 'attempt_id', -1 );
548 - $attempt_file_paths = array();
549 - if ( ! empty( $attempts_for_quiz ) ) {
550 - $attempt_ids = array_map(
551 - function ( $row ) {
552 - return (int) $row->attempt_id;
553 - },
554 - $attempts_for_quiz
555 - );
556 - $attempt_file_paths = apply_filters( 'tutor_quiz/attempt_file_paths_for_deletion', array(), $attempt_ids );
557 - $attempt_file_paths = is_array( $attempt_file_paths ) ? array_values( array_filter( array_unique( $attempt_file_paths ) ) ) : array();
558 - }
559 -
501 + if ( get_post_type( $content_id ) === 'tutor_quiz' ) {
560 502 $wpdb->delete( $wpdb->prefix . 'tutor_quiz_attempts', array( 'quiz_id' => $content_id ) );
561 503 $wpdb->delete( $wpdb->prefix . 'tutor_quiz_attempt_answers', array( 'quiz_id' => $content_id ) );
562 504
563 - QuizModel::delete_files_by_paths( $attempt_file_paths );
564 -
565 505 do_action( 'tutor_before_delete_quiz_content', $content_id, null );
566 506
567 - // Collect instructor file paths before deleting question data (e.g. draw_image / pin_image masks).
568 - /**
569 - * Filter to get file paths for quiz deletion.
570 - * Pro and other add-ons register their question types via this filter.
571 - *
572 - * @param string[] $file_paths Paths collected so far.
573 - * @param int $quiz_id Quiz post ID.
574 - */
575 - $quiz_file_paths = apply_filters( 'tutor_quiz_quiz_file_paths_for_deletion', array(), $content_id );
576 -
577 507 $questions_ids = $wpdb->get_col( $wpdb->prepare( "SELECT question_id FROM {$wpdb->prefix}tutor_quiz_questions WHERE quiz_id = %d ", $content_id ) );
578 508 if ( is_array( $questions_ids ) && count( $questions_ids ) ) {
579 - $in_clause = QueryHelper::prepare_in_clause( $questions_ids );
509 + $in_question_ids = "'" . implode( "','", $questions_ids ) . "'";
580 510 //phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
581 - $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}tutor_quiz_question_answers WHERE belongs_question_id IN({$in_clause}) " ) );
511 + $wpdb->query( "DELETE FROM {$wpdb->prefix}tutor_quiz_question_answers WHERE belongs_question_id IN({$in_question_ids}) " );
582 512 }
583 513 $wpdb->delete( $wpdb->prefix . 'tutor_quiz_questions', array( 'quiz_id' => $content_id ) );
584 -
585 - QuizModel::delete_files_by_paths( $quiz_file_paths );
586 514 }
587 515
588 516 /**
589 517 * Delete assignment data ( Assignments, Assignment Submit, Assignment Evalutation )
@@ -1189,9 +1117,9 @@
1189 1117 );
1190 1118 }
1191 1119 }
1192 1120
1193 - return apply_filters( 'tutor_course_dropdown_options', $course_options );
1121 + return $course_options;
1194 1122 }
1195 1123
1196 1124 /**
1197 1125 * Get category dropdown options
@@ -1277,9 +1205,9 @@
1277 1205 * @return bool|\WP_Query
1278 1206 */
1279 1207 public static function get_completed_courses_by_user( $user_id = 0, $offset = 0, $posts_per_page = -1, $args = array() ) {
1280 1208 $user_id = tutor_utils()->get_user_id( $user_id );
1281 - $course_ids = tutor_utils()->get_completed_courses_ids_by_user( $user_id, false );
1209 + $course_ids = tutor_utils()->get_completed_courses_ids_by_user( $user_id );
1282 1210
1283 1211 if ( count( $course_ids ) ) {
1284 1212 $course_post_type = tutor()->course_post_type;
1285 1213 $course_args = array(
@@ -1287,9 +1215,8 @@
1287 1215 'post_status' => 'publish',
1288 1216 'post__in' => $course_ids,
1289 1217 'posts_per_page' => $posts_per_page,
1290 1218 'offset' => $offset,
1291 - 'orderby' => 'post__in',
1292 1219 );
1293 1220
1294 1221 $args = apply_filters( 'tutor_get_completed_courses_by_user', $args, $user_id, $course_post_type );
1295 1222 $course_args = wp_parse_args( $args, $course_args );
@@ -1313,10 +1240,10 @@
1313 1240 * @return bool|\WP_Query
1314 1241 */
1315 1242 public static function get_active_courses_by_user( $user_id = 0, $offset = 0, $posts_per_page = -1, $args = array() ) {
1316 1243 $user_id = tutor_utils()->get_user_id( $user_id );
1317 - $course_ids = tutor_utils()->get_completed_courses_ids_by_user( $user_id, false );
1318 - $enrolled_course_ids = tutor_utils()->get_enrolled_courses_ids_by_user( $user_id, false );
1244 + $course_ids = tutor_utils()->get_completed_courses_ids_by_user( $user_id );
1245 + $enrolled_course_ids = tutor_utils()->get_enrolled_courses_ids_by_user( $user_id );
1319 1246 $active_courses = array_diff( $enrolled_course_ids, $course_ids );
1320 1247
1321 1248 if ( count( $active_courses ) ) {
1322 1249 $course_post_type = tutor()->course_post_type;
@@ -1325,9 +1252,8 @@
1325 1252 'post_status' => 'publish',
1326 1253 'post__in' => $active_courses,
1327 1254 'posts_per_page' => $posts_per_page,
1328 1255 'offset' => $offset,
1329 - 'orderby' => 'post__in',
1330 1256 );
1331 1257
1332 1258 $args = apply_filters( 'tutor_get_active_courses_by_user', $args, $user_id, $course_post_type );
1333 1259 $course_args = wp_parse_args( $args, $course_args );
@@ -1355,9 +1281,9 @@
1355 1281 * @return bool|\WP_Query
1356 1282 */
1357 1283 public static function get_enrolled_courses_by_user( $user_id = 0, $post_status = 'publish', $offset = 0, $posts_per_page = -1, $args = array() ) {
1358 1284 $user_id = tutor_utils()->get_user_id( $user_id );
1359 - $course_ids = array_unique( tutor_utils()->get_enrolled_courses_ids_by_user( $user_id, false ) );
1285 + $course_ids = array_unique( tutor_utils()->get_enrolled_courses_ids_by_user( $user_id ) );
1360 1286
1361 1287 if ( count( $course_ids ) ) {
1362 1288 $course_post_type = tutor()->course_post_type;
1363 1289 $course_args = array(
@@ -1365,9 +1291,8 @@
1365 1291 'post_status' => $post_status,
1366 1292 'post__in' => $course_ids,
1367 1293 'offset' => $offset,
1368 1294 'posts_per_page' => $posts_per_page,
1369 - 'orderby' => 'post__in',
1370 1295 );
1371 1296
1372 1297 $args = apply_filters( 'tutor_get_enrolled_courses_by_user', $args, $user_id, $course_post_type );
1373 1298 $course_args = wp_parse_args( $args, $course_args );
@@ -1391,363 +1316,6 @@
1391 1316 return $result;
1392 1317 }
1393 1318
1394 1319 return false;
1395 - }
1396 -
1397 - /**
1398 - * Get the number of courses created by an instructor within a given date range.
1399 - *
1400 - * @since 4.0.0
1401 - *
1402 - * If no date range is provided, returns the total course count for the instructor.
1403 - * Course creation date is determined using the `_wp_old_date` meta value when available; otherwise, the course
1404 - * post date is used.
1405 - *
1406 - * @param string|null $start_date Start date in Y-m-d format.
1407 - * @param string|null $end_date End date in Y-m-d format.
1408 - * @param int $user_id Course author (user) ID.
1409 - *
1410 - * @return int Total number of matching courses.
1411 - */
1412 - public static function get_course_count_by_date( $start_date, $end_date, $user_id ) {
1413 -
1414 - $user_id = tutor_utils()->get_user_id( $user_id );
1415 -
1416 - if ( empty( $start_date ) && empty( $end_date ) ) {
1417 - return (int) self::get_courses_by_instructor( $user_id, array( 'publish', 'private' ), 0, PHP_INT_MAX, true );
1418 - }
1419 -
1420 - $courses = self::get_courses_by_instructor( $user_id, array( 'publish', 'private' ), 0, PHP_INT_MAX );
1421 -
1422 - if ( empty( $courses ) ) {
1423 - return 0;
1424 - }
1425 -
1426 - $start_date_timestamp = strtotime( $start_date . ' 00:00:00' );
1427 - $end_date_timestamp = strtotime( $end_date . ' 23:59:59' );
1428 -
1429 - $filtered = array_filter(
1430 - $courses,
1431 - function ( $course ) use ( $start_date_timestamp, $end_date_timestamp ) {
1432 - $creation_date = get_post_meta( $course->ID, '_wp_old_date', true );
1433 -
1434 - $course_creation_date = empty( $creation_date ) ? strtotime( $course->post_date_gmt ) : strtotime( $creation_date );
1435 -
1436 - return $start_date_timestamp <= $course_creation_date && $end_date_timestamp >= $course_creation_date;
1437 - }
1438 - );
1439 -
1440 - return count( $filtered );
1441 - }
1442 -
1443 - /**
1444 - * Get topic-wise progress data for a course and a student.
1445 - *
1446 - * @since 4.0.0
1447 - *
1448 - * @param int $course_id Course ID.
1449 - * @param int $student_id Student ID.
1450 - *
1451 - * @return array[] Topic progress data.
1452 - */
1453 - public static function get_course_progress_details( $course_id, $student_id ) {
1454 -
1455 - $topics_query = tutor_utils()->get_topics( $course_id );
1456 - $topic_list = array();
1457 -
1458 - if ( empty( $topics_query ) || ! $topics_query->have_posts() ) {
1459 - return $topic_list;
1460 - }
1461 -
1462 - foreach ( $topics_query->posts as $topic_post ) {
1463 - $topic_id = (int) $topic_post->ID;
1464 - $items = array();
1465 - $completion_percentage = tutor_utils()->count_completed_contents_by_topic( $topic_id, $student_id );
1466 - $contents_query = tutor_utils()->get_course_contents_by_topic( $topic_id, -1 );
1467 -
1468 - if ( ! empty( $contents_query ) && $contents_query->have_posts() ) {
1469 - foreach ( $contents_query->posts as $content_post ) {
1470 - $items[] = ( new self() )->build_course_progress_item( $content_post, $student_id, $course_id );
1471 - }
1472 - }
1473 -
1474 - $topic_list[] = array(
1475 - 'id' => $topic_id,
1476 - 'summary' => $topic_post->post_content,
1477 - 'title' => get_the_title( $topic_id ),
1478 - 'items' => $items,
1479 - 'completion_percentage' => $completion_percentage['percentage'] ?? 0,
1480 - );
1481 - }
1482 -
1483 - wp_reset_postdata();
1484 -
1485 - return $topic_list;
1486 - }
1487 -
1488 - /**
1489 - * Check if the current user has course content access
1490 - *
1491 - * @since 4.0.0
1492 - *
1493 - * @throws InvalidArgumentException If args are invalid.
1494 - *
1495 - * @param array $args Array of arguments.
1496 - *
1497 - * `$defaults = array(
1498 - * 'current_post_type' => '',
1499 - * 'current_post_id' => 0,
1500 - * 'course_id' => 0,
1501 - * 'is_public' => false,
1502 - * 'is_enrolled' => false,
1503 - * );`.
1504 - *
1505 - * @return bool
1506 - */
1507 - public static function has_course_content_access( array $args = array() ): bool {
1508 - if ( User::is_admin() ) {
1509 - return true;
1510 - }
1511 -
1512 - $defaults = array(
1513 - 'current_post_type' => '',
1514 - 'current_post_id' => 0,
1515 - 'course_id' => 0,
1516 - 'is_public' => false,
1517 - 'is_enrolled' => false,
1518 - );
1519 -
1520 - $args = wp_parse_args( $args, $defaults );
1521 -
1522 - if ( ! $args['course_id'] || ! $args['current_post_id'] || ! $args['current_post_type'] ) {
1523 - throw new InvalidArgumentException( __( 'Invalid argument passed', 'tutor' ) );
1524 - }
1525 -
1526 - $has_access = false;
1527 - $user_id = get_current_user_id();
1528 - switch ( $args['current_post_type'] ) {
1529 - case tutor()->lesson_post_type:
1530 - $is_preview = (int) get_post_meta( $args['current_post_id'], Lesson::PREVIEW_META_KEY, true );
1531 - if ( $args['is_public'] || $is_preview ) {
1532 - $has_access = true;
1533 - } elseif ( $args['is_enrolled'] || tutor_utils()->has_enrolled_content_access( 'lesson' ) ) {
1534 - $has_access = true;
1535 - }
1536 - break;
1537 - case tutor()->quiz_post_type:
1538 - case tutor()->assignment_post_type:
1539 - if ( $user_id ) {
1540 - $content_type = tutor()->quiz_post_type === $args['current_post_type'] ? 'quiz' : 'assignment';
1541 - if ( $args['is_enrolled'] || $args['is_public'] || tutor_utils()->has_enrolled_content_access( $content_type ) ) {
1542 - $has_access = true;
1543 - }
1544 - }
1545 - break;
1546 - default:
1547 - if ( $args['is_enrolled'] ) {
1548 - $has_access = true;
1549 - }
1550 - break;
1551 - }
1552 -
1553 - return apply_filters( 'tutor_course_content_access', $has_access, $args );
1554 - }
1555 -
1556 - /**
1557 - * Build course progress item data for a specific lesson, quiz, assignment, or meeting.
1558 - *
1559 - * @since 4.0.0
1560 - *
1561 - * @param \WP_Post $post The course content post object.
1562 - * @param int $user_id The user ID for whom progress is being calculated.
1563 - * @param int $course_id The course ID to which the content belongs.
1564 - *
1565 - * @return array Associative array of progress item data
1566 - */
1567 - private function build_course_progress_item( \WP_Post $post, $user_id, $course_id ) {
1568 -
1569 - $base_items = array(
1570 - 'id' => $post->ID,
1571 - 'type' => $post->post_type,
1572 - 'link' => esc_url_raw( get_permalink( $post->ID ) ),
1573 - 'title' => $post->post_title,
1574 - );
1575 -
1576 - switch ( $post->post_type ) {
1577 - case tutor()->quiz_post_type:
1578 - $quiz_status = '';
1579 - $icon_name = Icon::QUIZ_2;
1580 - $icon_class = 'tutor-text-subdued';
1581 - $last_attempt = ( new QuizModel() )->get_first_or_last_attempt( $post->ID, $user_id );
1582 -
1583 - if ( is_object( $last_attempt ) && QuizModel::ATTEMPT_STARTED !== $last_attempt->attempt_status ) {
1584 - $quiz_status = QuizModel::get_quiz_result( $post->ID, $user_id );
1585 -
1586 - $quiz_icon_map = array(
1587 - QuizModel::RESULT_FAIL => array( Icon::CROSS_COLORIZE, 'tutor-icon-critical' ),
1588 - QuizModel::RESULT_PENDING => array( Icon::INFO_COLORIZE, 'tutor-icon-warning-secondary' ),
1589 - );
1590 -
1591 - if ( isset( $quiz_icon_map[ $quiz_status ] ) ) {
1592 - list( $icon_name, $icon_class ) = $quiz_icon_map[ $quiz_status ];
1593 - }
1594 - }
1595 -
1596 - return array_merge(
1597 - $base_items,
1598 - array(
1599 - 'is_completed' => QuizModel::RESULT_PASS === $quiz_status,
1600 - 'label' => __( 'Quiz', 'tutor' ),
1601 - 'icon' => $icon_name,
1602 - 'icon_class' => $icon_class,
1603 - )
1604 - );
1605 -
1606 - case tutor()->assignment_post_type:
1607 - $status = '';
1608 -
1609 - $is_submitted = tutor_utils()->is_assignment_submitted( $post->ID, $user_id );
1610 - $status = $is_submitted ? Assignments::get_assignment_result( $post->ID, $user_id ) : $status;
1611 -
1612 - if ( ! $is_submitted && Assignments::is_expired( $post->ID, $user_id, $course_id ) ) {
1613 - $status = 'fail';
1614 - }
1615 -
1616 - $icon = Icon::BOOK_2;
1617 - $icon_class = 'tutor-text-subdued';
1618 -
1619 - $status_map = array(
1620 - 'pending' => array( Icon::INFO_COLORIZE, 'tutor-icon-warning-secondary' ),
1621 - 'fail' => array( Icon::CROSS_COLORIZE, 'tutor-icon-critical' ),
1622 - );
1623 -
1624 - if ( isset( $status_map[ $status ] ) ) {
1625 - list( $icon, $icon_class ) = $status_map[ $status ];
1626 - }
1627 -
1628 - return array_merge(
1629 - $base_items,
1630 - array(
1631 - 'is_completed' => 'pass' === $status,
1632 - 'label' => __( 'Assignment', 'tutor' ),
1633 - 'icon' => $icon,
1634 - 'icon_class' => $icon_class,
1635 - )
1636 - );
1637 -
1638 - case tutor()->zoom_post_type:
1639 - if ( ! tutor_utils()->is_addon_enabled( 'tutor-zoom' ) ) {
1640 - return $base_items;
1641 - }
1642 -
1643 - return array_merge(
1644 - $base_items,
1645 - array(
1646 - 'label' => __( 'Zoom', 'tutor' ),
1647 - 'icon' => Icon::ZOOM,
1648 - 'is_completed' => \TUTOR_ZOOM\Zoom::is_zoom_lesson_done( '', $post->ID, $user_id ),
1649 - )
1650 - );
1651 -
1652 - case tutor()->meet_post_type:
1653 - if ( ! tutor_utils()->is_addon_enabled( 'google-meet' ) ) {
1654 - return $base_items;
1655 - }
1656 -
1657 - return array_merge(
1658 - $base_items,
1659 - array(
1660 - 'label' => __( 'Google Meet', 'tutor' ),
1661 - 'icon' => Icon::GOOGLE_MEET,
1662 - 'is_completed' => \TutorPro\GoogleMeet\Frontend\Frontend::is_lesson_completed( false, $post->ID, $user_id ),
1663 - )
1664 - );
1665 -
1666 - default:
1667 - $video = tutor_utils()->get_video_info( $post->ID );
1668 -
1669 - return array_merge(
1670 - $base_items,
1671 - array(
1672 - 'video' => $video,
1673 - 'video_play_time' => isset( $video->playtime ) ? $video->playtime : '',
1674 - 'is_completed' => (bool) tutor_utils()->is_completed_lesson( $post->ID, $user_id ),
1675 - 'label' => empty( $video ) ? __( 'Reading', 'tutor' ) : __( 'Video', 'tutor' ),
1676 - 'icon' => empty( $video ) ? Icon::COURSES : Icon::VIDEO_CAMERA,
1677 - )
1678 - );
1679 - }
1680 - }
1681 -
1682 - /**
1683 - * Calculate the total course duration for a set of courses and returns the total time in hours, minutes, and
1684 - * seconds.
1685 - *
1686 - * @since 4.0.0
1687 - *
1688 - * @param array<int> | int $course_ids List of course IDs or a single course ID.
1689 - *
1690 - * @return array{
1691 - * hours: int,
1692 - * minutes: int,
1693 - * seconds: int
1694 - * } Total accumulated duration from all given courses.
1695 - */
1696 - public static function get_total_course_duration( $course_ids ): array {
1697 - $total_seconds = 0;
1698 - $course_ids = is_array( $course_ids ) ? $course_ids : array( $course_ids );
1699 - foreach ( $course_ids as $id ) {
1700 - $total_seconds += self::get_course_duration_in_seconds( $id );
1701 - }
1702 -
1703 - return DateTimeHelper::split_seconds_into_time_units( $total_seconds );
1704 - }
1705 -
1706 - /**
1707 - * Calculate the estimated time spent on courses based on completion progress.
1708 - *
1709 - * @since 4.0.0
1710 - *
1711 - * @param array<int> $course_ids List of course IDs.
1712 - *
1713 - * @return array{
1714 - * hours: int,
1715 - * minutes: int,
1716 - * seconds: int
1717 - * } Total accumulated duration from all given courses.
1718 - */
1719 - public static function get_total_estimated_time_spent( $course_ids ): array {
1720 - $total_seconds = 0;
1721 - foreach ( $course_ids as $id ) {
1722 - $completion_percentage = tutor_utils()->is_completed_course( $id )
1723 - ? 100
1724 - : (int) tutor_utils()->get_course_completed_percent( (int) $id );
1725 -
1726 - if ( 0 === $completion_percentage ) {
1727 - continue;
1728 - }
1729 - $course_duration_in_seconds = self::get_course_duration_in_seconds( $id );
1730 - // Calculate the time spent by a user based on the course completion percentage.
1731 - $total_seconds += (int) round( $course_duration_in_seconds * ( $completion_percentage / 100 ) );
1732 - }
1733 -
1734 - return DateTimeHelper::split_seconds_into_time_units( $total_seconds );
1735 - }
1736 -
1737 - /**
1738 - * Get the total duration of a course in seconds.
1739 - *
1740 - * @since 4.0.0
1741 - *
1742 - * @param int $course_id Course ID.
1743 - *
1744 - * @return int Course duration in seconds.
1745 - */
1746 - public static function get_course_duration_in_seconds( int $course_id ): int {
1747 - $duration = tutor_utils()->get_course_duration( $course_id, true );
1748 -
1749 - return ( $duration['durationHours'] * HOUR_IN_SECONDS )
1750 - + ( $duration['durationMinutes'] * MINUTE_IN_SECONDS )
1751 - + ( $duration['durationSeconds'] );
1752 1320 }
1753 1321 }