PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 4.0.1
Tutor LMS – eLearning and online course solution v4.0.1
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 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 / Quiz_Attempts_List.php
tutor / classes Last commit date
Addons.php 4 days ago Admin.php 4 days ago Ajax.php 4 days ago Announcements.php 4 days ago Assets.php 1 day ago Backend_Page_Trait.php 1 year ago BaseController.php 1 year ago Config.php 4 days ago Container.php 11 months ago Course.php 4 days ago Course_Embed.php 3 years ago Course_Filter.php 4 days ago Course_List.php 4 days ago Course_Settings_Tabs.php 4 days ago Course_Widget.php 1 year ago Custom_Validation.php 4 days ago Dashboard.php 4 days ago Earnings.php 9 months ago FormHandler.php 4 days ago Frontend.php 4 days ago Gutenberg.php 1 year ago Icon.php 4 days ago Input.php 4 days ago Instructor.php 4 days ago Instructors_List.php 4 days ago Lesson.php 4 days ago Options_V2.php 4 days ago Permalink.php 4 days ago Post_types.php 5 days ago Private_Course_Access.php 4 days ago Q_And_A.php 4 days ago Question_Answers_List.php 11 months ago Quiz.php 1 day ago QuizBuilder.php 4 days ago Quiz_Attempts_List.php 1 day ago RestAPI.php 1 day ago Reviews.php 4 days ago Rewrite_Rules.php 2 years ago SampleCourse.php 4 days ago Shortcode.php 4 days ago Singleton.php 1 year ago Student.php 4 days ago Students_List.php 1 year ago Taxonomies.php 1 year ago Template.php 4 days ago Theme_Compatibility.php 3 years ago Tools.php 1 year ago Tools_V2.php 1 month ago Tutor.php 4 days ago TutorEDD.php 4 days ago Tutor_Base.php 2 years ago Tutor_Setup.php 4 days ago Upgrader.php 4 days ago User.php 4 days ago UserPreference.php 4 days ago Utils.php 1 day ago Video_Stream.php 3 years ago WhatsNew.php 1 day ago Withdraw.php 4 days ago Withdraw_Requests_List.php 11 months ago WooCommerce.php 4 days ago
Quiz_Attempts_List.php
722 lines
1 <?php
2 /**
3 * Quiz attempt list management
4 *
5 * @package Tutor\QuestionAnswer
6 * @author Themeum <support@themeum.com>
7 * @link https://themeum.com
8 * @since 1.0.0
9 */
10
11 namespace TUTOR;
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 use TUTOR\User;
18 use Tutor\Cache\QuizAttempts;
19 use Tutor\Components\Badge;
20 use Tutor\Components\Button;
21 use Tutor\Components\Constants\Size;
22 use Tutor\Components\Constants\Positions;
23 use Tutor\Components\Constants\Variant;
24 use Tutor\Components\Popover;
25 use Tutor\Helpers\UrlHelper;
26 use Tutor\Models\QuizModel;
27 use Tutor\Components\SvgIcon;
28 use Tutor\Components\Progress;
29
30 /**
31 * Quiz attempt class
32 *
33 * @since 1.0.0
34 */
35 class Quiz_Attempts_List {
36
37 const QUIZ_ATTEMPT_PAGE = 'tutor_quiz_attempts';
38
39 /**
40 * Trait for utilities
41 *
42 * @var $page_title
43 */
44
45 use Backend_Page_Trait;
46
47 /**
48 * Bulk Action
49 *
50 * @var $bulk_action
51 */
52 public $bulk_action = true;
53
54 /**
55 * Handle dependencies
56 *
57 * @since 1.0.0
58 *
59 * @param boolean $register_hook should register hook or not.
60 */
61 public function __construct( $register_hook = true ) {
62 if ( ! $register_hook ) {
63 return;
64 }
65
66 /**
67 * Handle bulk action
68 *
69 * @since 2.0.0
70 */
71 add_action( 'wp_ajax_tutor_quiz_attempts_bulk_action', array( $this, 'quiz_attempts_bulk_action' ) );
72 add_action( 'wp_ajax_tutor_quiz_attempts_count', array( $this, 'get_quiz_attempts_stat' ) );
73
74 /**
75 * Delete quiz attempt cache
76 *
77 * @since 2.1.0
78 */
79 add_action( 'tutor_quiz/attempt_ended', array( new QuizAttempts(), 'delete_cache' ) );
80 add_action( 'tutor_quiz/attempt_deleted', array( new QuizAttempts(), 'delete_cache' ) );
81 add_action( 'tutor_quiz/answer/review/after', array( new QuizAttempts(), 'delete_cache' ) );
82 }
83
84 /**
85 * Page title fallback
86 *
87 * @since 3.5.0
88 *
89 * @param string $name Property name.
90 *
91 * @return string
92 */
93 public function __get( $name ) {
94 if ( 'page_title' === $name ) {
95 return esc_html__( 'Quiz Attempts', 'tutor' );
96 }
97 }
98
99 /**
100 * Get the attempts stat from specific instructor context
101 *
102 * @since 2.0.0
103 * @since 3.8.0 refactor and query optimize.
104 *
105 * @return array
106 */
107 public function get_quiz_attempts_stat() {
108 global $wpdb;
109
110 if ( wp_doing_ajax() ) {
111 tutor_utils()->checking_nonce();
112 }
113
114 $count_obj = (object) array(
115 'pass' => 0,
116 'fail' => 0,
117 'pending' => 0,
118 );
119
120 $is_ajax_action = 'tutor_quiz_attempts_count' === Input::post( 'action' );
121 $user_id = get_current_user_id();
122 $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
123 $date = Input::post( 'date', '' );
124 $search = Input::post( 'search', '' );
125
126 if ( $is_ajax_action ) {
127 $current_params = compact( 'course_id', 'date', 'search' );
128 $attempt_cache = new QuizAttempts( $current_params );
129
130 $cached_attempts = $attempt_cache->get_cache();
131 if ( $attempt_cache->has_cache() && $attempt_cache->is_same_query() && isset( $cached_attempts['result'] ) ) {
132 $count_obj = $cached_attempts['result'];
133 } else {
134
135 $course_filter = $course_id ? $wpdb->prepare( ' AND quiz_attempts.course_id = %d', $course_id ) : '';
136 $date_filter = empty( $date ) ? '' : $wpdb->prepare( ' AND DATE(quiz_attempts.attempt_started_at) = %s ', $date );
137 $user_clause = User::is_admin() ? '' : $wpdb->prepare( ' AND quiz.post_author = %d', $user_id );
138
139 $search_term_raw = $search;
140 $search_filter = '%' . $wpdb->esc_like( $search ) . '%';
141
142 //phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
143 $results = $wpdb->get_results(
144 $wpdb->prepare(
145 "SELECT result, COUNT( DISTINCT attempt_id) AS total
146 FROM {$wpdb->prefix}tutor_quiz_attempts quiz_attempts
147 INNER JOIN {$wpdb->posts} quiz ON quiz_attempts.quiz_id = quiz.ID
148 INNER JOIN {$wpdb->users} AS users ON quiz_attempts.user_id = users.ID
149 INNER JOIN {$wpdb->posts} AS course ON course.ID = quiz_attempts.course_id
150 WHERE result IS NOT NULL
151 AND (
152 users.user_email = %s
153 OR users.display_name LIKE %s
154 OR quiz.post_title LIKE %s
155 OR course.post_title LIKE %s
156 )
157 {$user_clause}
158 {$course_filter}
159 {$date_filter}
160 GROUP BY result",
161 $search_term_raw,
162 $search_filter,
163 $search_filter,
164 $search_filter
165 )
166 );
167 //phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
168
169 foreach ( $results as $row ) {
170 if ( isset( $count_obj->{$row->result} ) ) {
171 $count_obj->{$row->result} = (int) $row->total;
172 }
173 }
174
175 $attempt_cache->data = $count_obj;
176 $attempt_cache->set_cache();
177 }
178 }
179
180 $all = $count_obj->pass + $count_obj->fail + $count_obj->pending;
181 $pass = $count_obj->pass;
182 $fail = $count_obj->fail;
183 $pending = $count_obj->pending;
184 $response = compact( 'all', 'pass', 'fail', 'pending' );
185
186 return $is_ajax_action ? wp_send_json_success( $response ) : $response;
187 }
188
189 /**
190 * Available tabs that will visible on the right side of page navbar
191 *
192 * @since 2.0.0
193 *
194 * @param string $user_id selected quiz_attempts id | optional.
195 * @param int $course_id selected quiz_attempts id | optional.
196 * @param string $date selected date | optional.
197 * @param string $search search by user name or email | optional.
198 *
199 * @return array
200 */
201 public function tabs_key_value( $user_id, $course_id, $date, $search ): array {
202 $url = apply_filters( 'tutor_data_tab_base_url', get_pagenum_link() );
203 $stats = $this->get_quiz_attempts_stat();
204
205 $tabs = array(
206 array(
207 'key' => '',
208 'title' => __( 'All', 'tutor' ),
209 'value' => $stats['all'],
210 'url' => $url . '&data=all',
211 ),
212 array(
213 'key' => 'pass',
214 'title' => __( 'Pass', 'tutor' ),
215 'value' => $stats['pass'],
216 'url' => $url . '&data=pass',
217 ),
218 array(
219 'key' => 'fail',
220 'title' => __( 'Fail', 'tutor' ),
221 'value' => $stats['fail'],
222 'url' => $url . '&data=fail',
223 ),
224 array(
225 'key' => 'pending',
226 'title' => __( 'Pending', 'tutor' ),
227 'value' => $stats['pending'],
228 'url' => $url . '&data=pending',
229 ),
230 );
231
232 return $tabs;
233 }
234
235 /**
236 * Obtain nav data for quiz attempts.
237 *
238 * @since 4.0.0
239 *
240 * @param int $quiz_attempts_count the quiz attempt count.
241 * @param string $url the url.
242 * @param string $result_filter the current result state.
243 * @param string $search_filter the search filter.
244 * @param string $course_filter the course id filter.
245 * @param string $start_date range start date (Y-m-d).
246 * @param string $end_date range end date (Y-m-d).
247 * @param string $order_filter the order filter.
248 * @param array $all_quizzes all quiz data for student.
249 *
250 * @return array
251 */
252 public function get_quiz_attempts_nav_data( $quiz_attempts_count = 0, $url = '', $result_filter = '', $search_filter = '', $course_filter = 0, $start_date = '', $end_date = '', $order_filter = 'DESC', $all_quizzes = array() ): array {
253 $quiz_model = new QuizModel();
254
255 if ( tutor_utils()->count( $all_quizzes ) ) {
256 $results = isset( $all_quizzes['results'] ) ? $all_quizzes['results'] : array();
257 $all_attempts = isset( $all_quizzes['total_count'] ) ? $all_quizzes['total_count'] : 0;
258 $quiz_attempts_count = $all_attempts;
259 $passed_attempts = count( $quiz_model->get_formatted_quiz_attempt_list_by_quiz_id( $results, QuizModel::RESULT_PASS ) );
260 $failed_attempts = count( $quiz_model->get_formatted_quiz_attempt_list_by_quiz_id( $results, QuizModel::RESULT_FAIL ) );
261 $pending_attempts = count( $quiz_model->get_formatted_quiz_attempt_list_by_quiz_id( $results, QuizModel::RESULT_PENDING ) );
262 } else {
263 $all_attempts = QuizModel::get_quiz_attempts( 0, 0, $search_filter, $course_filter > 0 ? $course_filter : '', $start_date, $end_date, $order_filter, '', true, true );
264 $pending_attempts = QuizModel::get_quiz_attempts( 0, 0, $search_filter, $course_filter > 0 ? $course_filter : '', $start_date, $end_date, $order_filter, QuizModel::RESULT_PENDING, true, true );
265 $passed_attempts = QuizModel::get_quiz_attempts( 0, 0, $search_filter, $course_filter > 0 ? $course_filter : '', $start_date, $end_date, $order_filter, QuizModel::RESULT_PASS, true, true );
266 $failed_attempts = QuizModel::get_quiz_attempts( 0, 0, $search_filter, $course_filter > 0 ? $course_filter : '', $start_date, $end_date, $order_filter, QuizModel::RESULT_FAIL, true, true );
267 }
268
269 $filter_url = remove_query_arg( 'current_page', $url );
270
271 $nav_links = array(
272 'type' => 'dropdown',
273 'active' => true,
274 'count' => $quiz_attempts_count,
275 'options' => array(
276 array(
277 'label' => __( 'All', 'tutor' ),
278 'count' => $all_attempts,
279 'url' => remove_query_arg( 'result', $filter_url ),
280 'active' => '' === $result_filter,
281 ),
282 array(
283 'label' => __( 'Pending', 'tutor' ),
284 'url' => UrlHelper::add_query_params( $filter_url, array( 'result' => QuizModel::RESULT_PENDING ) ),
285 'count' => $pending_attempts,
286 'active' => QuizModel::RESULT_PENDING === $result_filter,
287 ),
288 array(
289 'label' => __( 'Failed', 'tutor' ),
290 'url' => UrlHelper::add_query_params( $filter_url, array( 'result' => QuizModel::RESULT_FAIL ) ),
291 'count' => $failed_attempts,
292 'active' => QuizModel::RESULT_FAIL === $result_filter,
293 ),
294 array(
295 'label' => __( 'Passed', 'tutor' ),
296 'url' => UrlHelper::add_query_params( $filter_url, array( 'result' => QuizModel::RESULT_PASS ) ),
297 'count' => $passed_attempts,
298 'active' => QuizModel::RESULT_PASS === $result_filter,
299 ),
300 ),
301 );
302
303 return $nav_links;
304 }
305
306 /**
307 * Prepare bulk actions that will show on dropdown options
308 *
309 * @since 2.0.0
310 *
311 * @return array
312 */
313 public function prepare_bulk_actions(): array {
314 $actions = array(
315 $this->bulk_action_default(),
316 $this->bulk_action_delete(),
317 );
318 return $actions;
319 }
320
321
322 /**
323 * Handle bulk action for instructor delete
324 *
325 * @since 2.0.0
326 *
327 * @return void send wp_json response
328 */
329 public function quiz_attempts_bulk_action() {
330 // check nonce.
331 tutor_utils()->checking_nonce();
332
333 // Check if user is privileged.
334 if ( ! User::has_any_role( array( User::ADMIN, User::INSTRUCTOR ) ) ) {
335 wp_send_json_error( tutor_utils()->error_message() );
336 }
337
338 $bulk_action = Input::post( 'bulk-action', '' );
339 $bulk_ids = Input::post( 'bulk-ids', '' );
340 $bulk_ids = explode( ',', $bulk_ids );
341 $bulk_ids = array_map(
342 function ( $id ) {
343 return (int) trim( $id );
344 },
345 $bulk_ids
346 );
347
348 // prevent instructor to remove quiz attempt from admin.
349 $bulk_ids = array_filter(
350 $bulk_ids,
351 function ( $attempt_id ) {
352 $attempt = tutor_utils()->get_attempt( $attempt_id );
353 $user_id = get_current_user_id();
354 $course_id = $attempt && is_object( $attempt ) ? $attempt->course_id : 0;
355 return $course_id && tutor_utils()->can_user_edit_course( $user_id, $course_id );
356 }
357 );
358
359 switch ( $bulk_action ) {
360 case 'delete':
361 QuizModel::delete_quiz_attempt( $bulk_ids );
362 break;
363 }
364
365 wp_send_json_success();
366 }
367
368 /**
369 * Get bulk action as an array
370 *
371 * @since 2.0.0
372 *
373 * @return array
374 */
375 public function get_bulk_actions() {
376 $actions = array(
377 'delete' => 'Delete',
378 );
379 return $actions;
380 }
381
382 /**
383 * Check if attempt details are hidden.
384 *
385 * @since 4.0.0
386 *
387 * @return bool
388 */
389 public static function is_attempt_details_hidden(): bool {
390 $is_student_view = User::is_student_view();
391 $is_quiz_details_hidden = $is_student_view && tutor_utils()->get_option( 'hide_quiz_details' );
392 return $is_quiz_details_hidden;
393 }
394
395 /**
396 * Get retry button attributes.
397 *
398 * @since 4.0.0
399 *
400 * @param integer $quiz_id the quiz id.
401 *
402 * @return string
403 */
404 private function get_retry_attribute( $quiz_id = 0 ): string {
405 $retry_attr = sprintf(
406 'TutorCore.modal.showModal("tutor-retry-modal", { data: %s });',
407 wp_json_encode(
408 array(
409 'quizID' => $quiz_id,
410 'redirectURL' => get_post_permalink( $quiz_id ),
411 )
412 )
413 );
414
415 return $retry_attr;
416 }
417
418 /**
419 * Get the quiz attempt review url.
420 *
421 * @since 4.0.0
422 *
423 * @param array $attempt Quiz attempt.
424 * @param array $query_param Query param to add with the URL.
425 *
426 * @return string
427 */
428 public function get_review_url( $attempt = array(), $query_param = array() ): string {
429 $default = array( 'attempt_id' => $attempt['attempt_id'] ?? 0 );
430 $params = wp_parse_args( $query_param, $default );
431
432 return UrlHelper::add_query_params( get_pagenum_link(), $params );
433 }
434
435 /**
436 * Render student quiz attempt retry button.
437 *
438 * @since 4.0.0
439 *
440 * @param integer $course_id the course id.
441 * @param integer $quiz_id the quiz id.
442 * @param array $attempt the quiz attempt.
443 * @param integer $attempts_count the quiz attempt count.
444 *
445 * @return void
446 */
447 public function render_retry_button( $course_id = 0, $quiz_id = 0, $attempt = array(), $attempts_count = 0 ) {
448 $quiz_settings = tutor_utils()->get_quiz_option( $quiz_id, '', array() );
449 $limit_attempts_allowed = '1' === (string) ( $quiz_settings['limit_attempts_allowed'] ?? '0' );
450 $attempts_allowed = (int) ( $quiz_settings['attempts_allowed'] ?? 0 );
451 $can_retry = Quiz::can_retry_quiz( $limit_attempts_allowed, $attempts_allowed, $attempts_count );
452
453 if ( User::is_student_view() && $can_retry ) {
454 Button::make()
455 ->label( __( 'Retry', 'tutor' ) )
456 ->icon( Icon::RELOAD )
457 ->size( Size::MEDIUM )
458 ->variant( 'primary' )
459 ->attr( '@click', $this->get_retry_attribute( $quiz_id ) )
460 ->render();
461 }
462 }
463
464 /**
465 * Render quiz attempt details button.
466 *
467 * @since 4.0.0
468 *
469 * @param array $attempt the quiz attempt.
470 * @param bool $is_learning_area is learning area list item.
471 *
472 * @return void
473 */
474 public function render_details_button( $attempt, $is_learning_area = false ) {
475 if ( ! User::is_student_view() ) {
476 return;
477 }
478
479 $args = $is_learning_area ? array( 'action' => 'view_details' ) : array();
480 $url = $this->get_review_url( $attempt, $args );
481
482 Button::make()
483 ->label( __( 'Details', 'tutor' ) )
484 ->icon( Icon::RESOURCES, 'left', 20 )
485 ->size( Size::MEDIUM )
486 ->tag( 'a' )
487 ->attr( 'href', $url )
488 ->variant( 'primary' )
489 ->render();
490 }
491
492 /**
493 * Get kebab button for quiz attempt popover.
494 *
495 * @since 4.0.0
496 *
497 * @param string $size the size of the button.
498 *
499 * @return string
500 */
501 private function get_kebab_button( $size = Size::X_SMALL ) {
502 $kebab_button = Button::make()
503 ->label( __( 'More options', 'tutor' ) )
504 ->icon( Icon::ELLIPSES )
505 ->icon_only()
506 ->attr( 'x-ref', 'trigger' )
507 ->attr( '@click', 'toggle()' )
508 ->attr( 'class', 'tutor-quiz-item-result-more' )
509 ->variant( Variant::SECONDARY )
510 ->size( $size )
511 ->get();
512 return $kebab_button;
513 }
514
515 /**
516 * Get quiz detail item for quiz attempt popover.
517 *
518 * @since 4.0.0
519 *
520 * @param array $attempt the quiz attempt.
521 *
522 * @return array
523 */
524 private function get_details_item( $attempt = array() ) {
525 $query_param = array( 'action' => 'view_details' );
526
527 $url = $this->get_review_url( $attempt, $query_param );
528
529 $details_item = array(
530 'tag' => 'a',
531 'content' => __( 'Details', 'tutor' ),
532 'icon' => SvgIcon::make()->name( Icon::RESOURCES )->size( 20 )->get(),
533 'attr' => array( 'href' => $url ),
534 );
535 return $details_item;
536 }
537
538 /**
539 * Render student quiz attempt popover.
540 *
541 * @since 4.0.0
542 *
543 * @param array $attempt the quiz attempt.
544 * @param integer $attempts_count the quiz attempt count.
545 * @param integer $quiz_id the quiz id.
546 * @param bool $is_learning_area is learning area list item.
547 * @param bool $show_details whether to show details.
548 *
549 * @return void
550 */
551 public function render_student_attempt_popover( $attempt = array(), $attempts_count = 0, $quiz_id = 0, $is_learning_area = false, $show_details = true ) {
552 $is_quiz_details_hidden = $this->is_attempt_details_hidden();
553 $quiz_settings = tutor_utils()->get_quiz_option( $quiz_id, '', array() );
554 $limit_attempts_allowed = '1' === (string) ( $quiz_settings['limit_attempts_allowed'] ?? '0' );
555 $attempts_allowed = (int) ( $quiz_settings['attempts_allowed'] ?? 0 );
556
557 $can_retry = ! $is_learning_area && Quiz::can_retry_quiz( $limit_attempts_allowed, $attempts_allowed, $attempts_count );
558 $show_retry = $can_retry && $attempts_count > 0;
559
560 if ( ! $show_retry && $is_quiz_details_hidden ) {
561 return;
562 }
563
564 if ( $is_learning_area && ! $is_quiz_details_hidden ) {
565
566 $query_param = array( 'action' => 'view_details' );
567
568 $url = $this->get_review_url( $attempt, $query_param );
569
570 $button_html = Button::make()
571 ->tag( 'a' )
572 ->label( __( 'Details', 'tutor' ) )
573 ->size( Size::X_SMALL )
574 ->variant( Variant::PRIMARY )
575 ->attr( 'href', $url )
576 ->attr( 'class', 'tutor-quiz-item-result-more tutor-quiz-details-btn' )
577 ->get();
578
579 echo '<div class="tutor-flex">' . wp_kses_post( $button_html ) . '</div>';
580 return;
581 }
582
583 $popover = Popover::make()
584 ->trigger( $this->get_kebab_button( $show_details ? Size::X_SMALL : Size::MEDIUM ) )
585 ->placement( 'bottom' )
586 ->menu_min_width( '110px' );
587
588 if ( $show_retry ) {
589 $popover->menu_item(
590 array(
591 'tag' => 'button',
592 'content' => __( 'Retry', 'tutor' ),
593 'icon' => SvgIcon::make()->name( Icon::RELOAD_3 )->size( 20 )->get(),
594 'attr' => array(
595 '@click' => $this->get_retry_attribute( $quiz_id ),
596 ),
597 )
598 );
599 }
600
601 if ( ! $is_quiz_details_hidden && $show_details ) {
602 $popover->menu_item( $this->get_details_item( $attempt ) );
603 }
604
605 $popover->render();
606 }
607
608 /**
609 * Render quiz attempt marks percentage.
610 *
611 * @since 4.0.0
612 *
613 * @param string $attempt_result the quiz attempt `QuizModel::RESULT_PASS | QuizModel::RESULT_PENDING | QuizModel::RESULT_FAIL`.
614 * @param int $earned_percentage the earned percentage.
615 * @param string $size the size of the component.
616 * @param string $wrapper_class the wrapper class of the component.
617 *
618 * @return void
619 */
620 public static function render_quiz_attempt_marks_percentage( $attempt_result = '', $earned_percentage = 0, $size = 'small', $wrapper_class = '' ) {
621 $statics_stroke_color = 'var(--tutor-icon-critical)';
622
623 if ( QuizModel::RESULT_PASS === $attempt_result ) {
624 $statics_stroke_color = 'var(--tutor-icon-success-secondary)';
625
626 if ( 100 === (int) $earned_percentage ) {
627 $statics_stroke_color = 'var(--tutor-icon-success-primary)';
628 }
629 } elseif ( QuizModel::RESULT_PENDING === $attempt_result ) {
630 $statics_stroke_color = 'var(--tutor-icon-warning-secondary)';
631 }
632
633 Progress::make()
634 ->type( 'circle' )
635 ->value( $earned_percentage )
636 ->size( $size )
637 ->stroke_color( 'var(--tutor-border-idle2)' )
638 ->progress_stroke_color( $statics_stroke_color )
639 ->animated()
640 ->attr( 'class', $wrapper_class )
641 ->render();
642 }
643
644 /**
645 * Render List Badge for quiz attempts.
646 *
647 * @since 4.0.0
648 *
649 * @param array $attempt the quiz attempt.
650 *
651 * @return void
652 */
653 public function render_quiz_attempt_list_badge( $attempt = array() ) {
654 if ( QuizModel::RESULT_PASS === $attempt['result'] ) {
655 Badge::make()->label( __( 'Passed', 'tutor' ) )->variant( Badge::SUCCESS )->rounded()->render();
656 } elseif ( QuizModel::RESULT_PENDING === $attempt['result'] ) {
657 Badge::make()->label( __( 'Pending', 'tutor' ) )->variant( Badge::WARNING )->rounded()->render();
658 } else {
659 Badge::make()->label( 'Failed' )->variant( Badge::ERROR )->rounded()->render();
660 }
661 }
662
663 /**
664 * Render quiz attempt mobile view buttons.
665 *
666 * @since 4.0.0
667 *
668 * @param array $attempt the quiz attempt.
669 *
670 * @return void
671 */
672 public function render_quiz_attempt_buttons( $attempt = array() ) {
673 Button::make()
674 ->label( __( 'Details', 'tutor' ) )
675 ->icon( Icon::RESOURCES, 'left', 20 )
676 ->size( Size::MEDIUM )
677 ->tag( 'a' )
678 ->attr( 'href', $this->get_review_url( $attempt ) )
679 ->variant( 'primary' )
680 ->render();
681
682 Button::make()
683 ->label( __( 'Delete', 'tutor' ) )
684 ->icon( Icon::DELETE_2, 'left', 20 )
685 ->size( Size::MEDIUM )
686 ->attr( '@click', sprintf( 'TutorCore.modal.showModal("tutor-quiz-attempt-delete-modal", { attemptID: %d });', $attempt['attempt_id'] ?? 0 ) )
687 ->variant( 'secondary' )
688 ->render();
689 }
690
691 /**
692 * Render quiz attempt popover for instructor quiz attempt list.
693 *
694 * @since 4.0.0
695 *
696 * @param array $attempt the quiz attempt.
697 *
698 * @return void
699 */
700 public function render_quiz_attempt_popover( $attempt = array() ) {
701 Popover::make()
702 ->trigger( $this->get_kebab_button() )
703 ->placement( Positions::BOTTOM )
704 ->menu_min_width( '110px' )
705 ->menu_item( $this->get_details_item( $attempt ) )
706 ->menu_item(
707 array(
708 'tag' => 'button',
709 'content' => __( 'Delete', 'tutor' ),
710 'icon' => SvgIcon::make()->name( Icon::DELETE_2 )->size( 20 )->get(),
711 'attr' => array(
712 '@click' => sprintf(
713 'hide(); TutorCore.modal.showModal("tutor-quiz-attempt-delete-modal", { attemptID: %d });',
714 $attempt['attempt_id'] ?? 0
715 ),
716 ),
717 )
718 )
719 ->render();
720 }
721 }
722