PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 4.0.0
Tutor LMS – eLearning and online course solution v4.0.0
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 17 hours ago Admin.php 17 hours ago Ajax.php 17 hours ago Announcements.php 17 hours ago Assets.php 17 hours ago Backend_Page_Trait.php 1 year ago BaseController.php 1 year ago Config.php 17 hours ago Container.php 11 months ago Course.php 17 hours ago Course_Embed.php 3 years ago Course_Filter.php 17 hours ago Course_List.php 17 hours ago Course_Settings_Tabs.php 17 hours ago Course_Widget.php 1 year ago Custom_Validation.php 17 hours ago Dashboard.php 17 hours ago Earnings.php 9 months ago FormHandler.php 17 hours ago Frontend.php 17 hours ago Gutenberg.php 1 year ago Icon.php 17 hours ago Input.php 17 hours ago Instructor.php 17 hours ago Instructors_List.php 17 hours ago Lesson.php 17 hours ago Options_V2.php 17 hours ago Permalink.php 17 hours ago Post_types.php 1 day ago Private_Course_Access.php 17 hours ago Q_And_A.php 17 hours ago Question_Answers_List.php 11 months ago Quiz.php 17 hours ago QuizBuilder.php 17 hours ago Quiz_Attempts_List.php 17 hours ago RestAPI.php 2 years ago Reviews.php 17 hours ago Rewrite_Rules.php 2 years ago SampleCourse.php 17 hours ago Shortcode.php 17 hours ago Singleton.php 1 year ago Student.php 17 hours ago Students_List.php 1 year ago Taxonomies.php 1 year ago Template.php 17 hours ago Theme_Compatibility.php 3 years ago Tools.php 1 year ago Tools_V2.php 4 weeks ago Tutor.php 17 hours ago TutorEDD.php 17 hours ago Tutor_Base.php 2 years ago Tutor_Setup.php 17 hours ago Upgrader.php 17 hours ago User.php 17 hours ago UserPreference.php 17 hours ago Utils.php 17 hours ago Video_Stream.php 3 years ago WhatsNew.php 10 months ago Withdraw.php 17 hours ago Withdraw_Requests_List.php 11 months ago WooCommerce.php 17 hours ago
Quiz_Attempts_List.php
716 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 *
471 * @return void
472 */
473 public function render_details_button( $attempt ) {
474 if ( User::is_student_view() ) {
475 Button::make()
476 ->label( __( 'Details', 'tutor' ) )
477 ->icon( Icon::RESOURCES, 'left', 20 )
478 ->size( Size::MEDIUM )
479 ->tag( 'a' )
480 ->attr( 'href', $this->get_review_url( $attempt ) )
481 ->variant( 'primary' )
482 ->render();
483 }
484 }
485
486 /**
487 * Get kebab button for quiz attempt popover.
488 *
489 * @since 4.0.0
490 *
491 * @param string $size the size of the button.
492 *
493 * @return string
494 */
495 private function get_kebab_button( $size = Size::X_SMALL ) {
496 $kebab_button = Button::make()
497 ->label( __( 'More options', 'tutor' ) )
498 ->icon( Icon::ELLIPSES )
499 ->icon_only()
500 ->attr( 'x-ref', 'trigger' )
501 ->attr( '@click', 'toggle()' )
502 ->attr( 'class', 'tutor-quiz-item-result-more' )
503 ->variant( Variant::SECONDARY )
504 ->size( $size )
505 ->get();
506 return $kebab_button;
507 }
508
509 /**
510 * Get quiz detail item for quiz attempt popover.
511 *
512 * @since 4.0.0
513 *
514 * @param array $attempt the quiz attempt.
515 *
516 * @return array
517 */
518 private function get_details_item( $attempt = array() ) {
519 $query_param = array( 'action' => 'view_details' );
520
521 $url = $this->get_review_url( $attempt, $query_param );
522
523 $details_item = array(
524 'tag' => 'a',
525 'content' => __( 'Details', 'tutor' ),
526 'icon' => SvgIcon::make()->name( Icon::RESOURCES )->size( 20 )->get(),
527 'attr' => array( 'href' => $url ),
528 );
529 return $details_item;
530 }
531
532 /**
533 * Render student quiz attempt popover.
534 *
535 * @since 4.0.0
536 *
537 * @param array $attempt the quiz attempt.
538 * @param integer $attempts_count the quiz attempt count.
539 * @param integer $quiz_id the quiz id.
540 * @param bool $is_learning_area is learning area list item.
541 * @param bool $show_details whether to show details.
542 *
543 * @return void
544 */
545 public function render_student_attempt_popover( $attempt = array(), $attempts_count = 0, $quiz_id = 0, $is_learning_area = false, $show_details = true ) {
546 $is_quiz_details_hidden = $this->is_attempt_details_hidden();
547 $quiz_settings = tutor_utils()->get_quiz_option( $quiz_id, '', array() );
548 $limit_attempts_allowed = '1' === (string) ( $quiz_settings['limit_attempts_allowed'] ?? '0' );
549 $attempts_allowed = (int) ( $quiz_settings['attempts_allowed'] ?? 0 );
550
551 $can_retry = ! $is_learning_area && Quiz::can_retry_quiz( $limit_attempts_allowed, $attempts_allowed, $attempts_count );
552 $show_retry = $can_retry && $attempts_count > 0;
553
554 if ( ! $show_retry && $is_quiz_details_hidden ) {
555 return;
556 }
557
558 if ( $is_learning_area && ! $is_quiz_details_hidden ) {
559
560 $query_param = array( 'action' => 'view_details' );
561
562 $url = $this->get_review_url( $attempt, $query_param );
563
564 $button_html = Button::make()
565 ->tag( 'a' )
566 ->label( __( 'Details', 'tutor' ) )
567 ->size( Size::X_SMALL )
568 ->variant( Variant::PRIMARY )
569 ->attr( 'href', $url )
570 ->attr( 'class', 'tutor-quiz-item-result-more tutor-quiz-details-btn' )
571 ->get();
572
573 echo '<div class="tutor-flex">' . wp_kses_post( $button_html ) . '</div>';
574 return;
575 }
576
577 $popover = Popover::make()
578 ->trigger( $this->get_kebab_button( $show_details ? Size::X_SMALL : Size::MEDIUM ) )
579 ->placement( 'bottom' )
580 ->menu_min_width( '110px' );
581
582 if ( $show_retry ) {
583 $popover->menu_item(
584 array(
585 'tag' => 'button',
586 'content' => __( 'Retry', 'tutor' ),
587 'icon' => SvgIcon::make()->name( Icon::RELOAD_3 )->size( 20 )->get(),
588 'attr' => array(
589 '@click' => $this->get_retry_attribute( $quiz_id ),
590 ),
591 )
592 );
593 }
594
595 if ( ! $is_quiz_details_hidden && $show_details ) {
596 $popover->menu_item( $this->get_details_item( $attempt ) );
597 }
598
599 $popover->render();
600 }
601
602 /**
603 * Render quiz attempt marks percentage.
604 *
605 * @since 4.0.0
606 *
607 * @param string $attempt_result the quiz attempt `QuizModel::RESULT_PASS | QuizModel::RESULT_PENDING | QuizModel::RESULT_FAIL`.
608 * @param int $earned_percentage the earned percentage.
609 * @param string $size the size of the component.
610 * @param string $wrapper_class the wrapper class of the component.
611 *
612 * @return void
613 */
614 public static function render_quiz_attempt_marks_percentage( $attempt_result = '', $earned_percentage = 0, $size = 'small', $wrapper_class = '' ) {
615 $statics_stroke_color = 'var(--tutor-icon-critical)';
616
617 if ( QuizModel::RESULT_PASS === $attempt_result ) {
618 $statics_stroke_color = 'var(--tutor-icon-success-secondary)';
619
620 if ( 100 === (int) $earned_percentage ) {
621 $statics_stroke_color = 'var(--tutor-icon-success-primary)';
622 }
623 } elseif ( QuizModel::RESULT_PENDING === $attempt_result ) {
624 $statics_stroke_color = 'var(--tutor-icon-warning-secondary)';
625 }
626
627 Progress::make()
628 ->type( 'circle' )
629 ->value( $earned_percentage )
630 ->size( $size )
631 ->stroke_color( 'var(--tutor-border-idle2)' )
632 ->progress_stroke_color( $statics_stroke_color )
633 ->animated()
634 ->attr( 'class', $wrapper_class )
635 ->render();
636 }
637
638 /**
639 * Render List Badge for quiz attempts.
640 *
641 * @since 4.0.0
642 *
643 * @param array $attempt the quiz attempt.
644 *
645 * @return void
646 */
647 public function render_quiz_attempt_list_badge( $attempt = array() ) {
648 if ( QuizModel::RESULT_PASS === $attempt['result'] ) {
649 Badge::make()->label( __( 'Passed', 'tutor' ) )->variant( Badge::SUCCESS )->rounded()->render();
650 } elseif ( QuizModel::RESULT_PENDING === $attempt['result'] ) {
651 Badge::make()->label( __( 'Pending', 'tutor' ) )->variant( Badge::WARNING )->rounded()->render();
652 } else {
653 Badge::make()->label( 'Failed' )->variant( Badge::ERROR )->rounded()->render();
654 }
655 }
656
657 /**
658 * Render quiz attempt mobile view buttons.
659 *
660 * @since 4.0.0
661 *
662 * @param array $attempt the quiz attempt.
663 *
664 * @return void
665 */
666 public function render_quiz_attempt_buttons( $attempt = array() ) {
667 Button::make()
668 ->label( __( 'Details', 'tutor' ) )
669 ->icon( Icon::RESOURCES, 'left', 20 )
670 ->size( Size::MEDIUM )
671 ->tag( 'a' )
672 ->attr( 'href', $this->get_review_url( $attempt ) )
673 ->variant( 'primary' )
674 ->render();
675
676 Button::make()
677 ->label( __( 'Delete', 'tutor' ) )
678 ->icon( Icon::DELETE_2, 'left', 20 )
679 ->size( Size::MEDIUM )
680 ->attr( '@click', sprintf( 'TutorCore.modal.showModal("tutor-quiz-attempt-delete-modal", { attemptID: %d });', $attempt['attempt_id'] ?? 0 ) )
681 ->variant( 'secondary' )
682 ->render();
683 }
684
685 /**
686 * Render quiz attempt popover for instructor quiz attempt list.
687 *
688 * @since 4.0.0
689 *
690 * @param array $attempt the quiz attempt.
691 *
692 * @return void
693 */
694 public function render_quiz_attempt_popover( $attempt = array() ) {
695 Popover::make()
696 ->trigger( $this->get_kebab_button() )
697 ->placement( Positions::BOTTOM )
698 ->menu_min_width( '110px' )
699 ->menu_item( $this->get_details_item( $attempt ) )
700 ->menu_item(
701 array(
702 'tag' => 'button',
703 'content' => __( 'Delete', 'tutor' ),
704 'icon' => SvgIcon::make()->name( Icon::DELETE_2 )->size( 20 )->get(),
705 'attr' => array(
706 '@click' => sprintf(
707 'hide(); TutorCore.modal.showModal("tutor-quiz-attempt-delete-modal", { attemptID: %d });',
708 $attempt['attempt_id'] ?? 0
709 ),
710 ),
711 )
712 )
713 ->render();
714 }
715 }
716