Quiz


Source

File: classes/Quiz.php

class Quiz {

	public function __construct() {

		add_action( 'wp_ajax_load_quiz_editor', [ $this, 'load_quiz_editor' ] );
		add_action( 'wp_ajax_dozent_save_quiz', [ $this, 'dozent_save_quiz' ] );
		add_action( 'wp_ajax_sort_quiz_questions', [ $this, 'sort_quiz_questions' ] );

		add_action( 'wp_ajax_quiz_load_edit', [ $this, 'quiz_load_edit' ] );
		add_action( 'wp_ajax_dozent_quiz_add_question', [ $this, 'dozent_quiz_add_question' ] );
		add_action( 'wp_ajax_load_edit_question_modal', [ $this, 'load_edit_question_modal' ] );
		add_action( 'wp_ajax_dozent_quiz_update_question', [ $this, 'dozent_quiz_update_question' ] );
		add_action( 'wp_ajax_quiz_question_delete', [ $this, 'quiz_question_delete' ] );
		add_action( 'wp_ajax_question_option_delete', [ $this, 'question_option_delete' ] );

		//Frontend Stuff
		add_dozent_action( 'start_quiz', [ $this, 'start_quiz' ] );
		add_filter( 'dozent_curriculum_template', [ $this, 'quiz_attempting' ] );
		add_filter( 'wp_ajax_dozent_quiz_submit_question', [ $this, 'submit_question' ] );

		//Attempts Table Filter

		add_filter( 'dozent_html_table_quiz_attempts_column', [ $this, 'table_quiz_attempts_column' ], 10, 3 );
		//View Attempt by Student page
		add_filter( 'dozent_curriculum_content_template', [ $this, 'attempt_view' ], 10, 2 );

		/**
		 * Table Column
		 * Dashboard Attemtps Review
		 */

		add_filter( 'dozent_html_table_quiz_attempts_courses_column', [ $this, 'table_quiz_attempts_courses_column' ],
			10, 3 );
		add_filter( 'dozent_html_table_dozent_course_has_quizzes_column',
			[ $this, 'table_dozent_course_has_quizzes_column' ], 10, 3 );
		add_filter( 'dozent_html_table_attempts_for_review_column', [ $this, 'table_attempts_for_review_column' ], 10,
			3 );

		/**
		 * Review Quiz Attempt
		 */

		add_dozent_action( 'dozent_review_attempt', [ $this, 'review_attempt' ] );

	}

	public function load_quiz_editor() {
		ob_start();
		wp_editor( '', 'quiz_content', [ 'textarea_rows' => 4 ] );
		$editor_html = ob_get_clean();

		wp_send_json_success( [ 'editor_html' => $editor_html ] );
	}

	/**
	 * Add Lecture Action
	 *
	 * @since DozentLMS 1.0.0
	 */
	public function dozent_save_quiz() {
		$course_id    = (int) dozent_input_text( 'course_id' );
		$item_id      = (int) dozent_input_text( 'item_id' );
		$title        = dozent_input_text( 'title' );
		$quiz_content = dozent_input_array_field( 'quiz_content' );
		$section_id   = dozent_input_array_field( 'section_id' );

		$post_arr = [
			'post_type'    => 'dozent_quiz',
			'post_title'   => $title,
			'post_content' => $quiz_content,
			'post_status'  => 'publish',
			'post_author'  => get_current_user_id(),
			'post_parent'  => $section_id,
		];

		if ( $item_id ) {
			$item                   = get_post( $item_id );
			$post_arr['ID']         = $item_id;
			$post_arr['menu_order'] = $item->menu_order;
		} else {
			$menu_order_id          = dozent_get_next_curriculum_item_order_id( $course_id );
			$post_arr['menu_order'] = $menu_order_id;
		}

		$quiz_id = wp_insert_post( $post_arr );
		if ( $quiz_id ) {
			update_post_meta( $quiz_id, '_dozent_course_id', $course_id );

			//Register Action Hooks
			do_action( 'dozent/quiz/saved', $quiz_id, $course_id, $post_arr );
			if ( $item_id ) {
				do_action( 'dozent/quiz/updated', $quiz_id, $course_id, $post_arr );
			} else {
				do_action( 'dozent/quiz/created', $quiz_id, $course_id, $post_arr );
			}

			$quiz_options = dozent_input_array_field( 'quiz_option' );
			update_post_meta( $quiz_id, '_quiz_option', $quiz_options );
		}

		ob_start();
		include DOZENT_ABSPATH . 'views/course_builder/curriculum.php';
		$course_contents = ob_get_clean();

		wp_send_json_success( [ 'quiz_id' => $quiz_id, 'curriculum' => $course_contents ] );
	}

	/**
	 * Sort quiz question
	 */

	public function sort_quiz_questions() {
		global $wpdb;
		$questions = dozent_input_array_field( 'questions' );

		if ( dozent_count( $questions ) ) {
			foreach ( $questions as $short => $question ) {
				$wpdb->update( $wpdb->dozent_quiz_questions, [ 'sort_order' => $short ],
					[ 'question_id' => $question ] );
			}
		}
	}


	/**
	 * Load Quiz Edit Form
	 *
	 *
	 * @since DozentLMS 1.0.0
	 *
	 */

	public function quiz_load_edit() {
		$item_id = (int) dozent_input_text( 'item_id' );
		$item    = get_post( $item_id );

		ob_start();
		include DOZENT_ABSPATH . 'views/course_builder/edit_quiz.php';
		$html = ob_get_clean();

		wp_send_json_success( [ 'html' => $html ] );
	}

	/**
	 * Quiz Add Question
	 *
	 *
	 * @since DozentLMS 1.0.0
	 *
	 * @see dozent_quiz_add_question();
	 *
	 */

	public function dozent_quiz_add_question() {
		global $wpdb;

		$question_type  = dozent_input_text( 'question_type' );
		$question_title = dozent_input_text( 'question_title' );
		$quiz_id        = dozent_input_text( 'quiz_id' );
		$score          = dozent_input_text( 'score' );
		$image_id       = dozent_input_text( 'image_id' );

		//For the question type true false
		$is_true = dozent_input_text( 'question_type_is_true' );

		if ( empty( $question_title ) ) {
			$error_text    = __( 'The question title field is required.', 'dozent' );
			$error_message = "<p class='dozent-text-danger'> <i class='dicon-notification'></i> {$error_text} </p>";

			wp_send_json_error( [ 'message' => $error_message ] );
		}

		$user_id = get_current_user_id();
		$question_order
		         = $wpdb->get_var( "SELECT MAX(sort_order) FROM {$wpdb->dozent_quiz_questions} WHERE quiz_id = {$quiz_id} " );
		$question_order ++;

		$data = apply_filters( 'dozent/quiz/add_question/data', [
			'user_id'       => $user_id,
			'quiz_id'       => $quiz_id,
			'title'         => $question_title,
			'image_id'      => $image_id,
			'question_type' => $question_type,
			'score'         => $score,
			'sort_order'    => $question_order,
		] );

		if ( $question_type === 'true_false' ) {
			$data['mixed_value'] = $is_true;
		}

		do_action( 'dozent/quiz/add_question/before', $data );

		$wpdb->insert( $wpdb->dozent_quiz_questions, $data );
		$question_id = (int) $wpdb->insert_id;

		$question_options = (array) dozent_input_array_field( 'options' );
		$question_options = dozent_array_except( $question_options, '{index}' );

		if ( dozent_count( $question_options ) ) {
			$sort = 0;
			foreach ( $question_options as $option ) {
				$sort ++;

				$optionData = [
					'question_id'        => $question_id,
					'title'              => dozent_array_get( 'title', $option ),
					'image_id'           => dozent_array_get( 'image_id', $option ),
					'display_preference' => dozent_array_get( 'd_pref', $option ),
					'is_correct'         => (int) dozent_array_get( 'is_correct', $option ),
					'sort_order'         => $sort,
				];

				$wpdb->insert( $wpdb->dozent_quiz_question_options, $optionData );
			}
		}

		do_action( 'dozent/quiz/add_question/after', $data, $question_id );

		ob_start();
		include DOZENT_ABSPATH . 'views/course_builder/quiz_questions.php';
		$questions_html = ob_get_clean();

		wp_send_json_success( [ 'questions_html' => $questions_html ] );
	}


	public function load_edit_question_modal() {
		$question_id = (int) dozent_input_text( 'question_id' );
		$question    = dozent_get_question( $question_id );

		ob_start();
		include DOZENT_ABSPATH . 'views/course_builder/edit_question.php';
		$course_contents = ob_get_clean();

		wp_send_json_success( [ 'modal_html' => $course_contents ] );
	}

	/**
	 * Update quiz question
	 *
	 *
	 * @since DozentLMS 1.0.0
	 *
	 */

	public function dozent_quiz_update_question() {
		global $wpdb;

		$question_id    = dozent_input_text( 'question_id' );
		$question       = dozent_get_question( $question_id );
		$question_title = dozent_input_text( 'question_title' );
		$score          = dozent_input_text( 'score' );
		$image_id       = dozent_input_text( 'image_id' );

		//For the question type true false
		$is_true = dozent_input_text( 'question_type_is_true' );

		if ( empty( $question_title ) ) {
			$error_text    = __( 'The question title field is required.', 'dozent' );
			$error_message = "<p class='dozent-text-danger'> <i class='dicon-notification'></i> {$error_text} </p>";

			wp_send_json_error( [ 'message' => $error_message ] );
		}

		$user_id = get_current_user_id();

		$data = apply_filters( 'dozent/quiz/update_question/data', [
			'user_id'  => $user_id,
			'title'    => $question_title,
			'image_id' => $image_id,
			'score'    => $score,
		] );

		if ( $question->question_type === 'true_false' ) {
			$data['mixed_value'] = $is_true;
		}

		do_action( 'dozent/quiz/update_question/before', $data );

		$wpdb->update( $wpdb->dozent_quiz_questions, $data, [ 'question_id' => $question_id ] );

		$question_options = (array) dozent_input_array_field( 'options' );
		$question_options = dozent_array_except( $question_options, '{index}' );

		if ( dozent_count( $question_options ) ) {
			$sort = 0;
			foreach ( $question_options as $option ) {
				$sort ++;

				$option_id  = (int) dozent_array_get( 'option_id', $option );
				$optionData = [
					'question_id'        => $question_id,
					'title'              => dozent_array_get( 'title', $option ),
					'image_id'           => dozent_array_get( 'image_id', $option ),
					'display_preference' => dozent_array_get( 'd_pref', $option ),
					'is_correct'         => (int) dozent_array_get( 'is_correct', $option ),
					'sort_order'         => $sort,
				];

				if ( $option_id ) {
					$wpdb->update( $wpdb->dozent_quiz_question_options, $optionData, [ 'option_id' => $option_id ] );
				} else {
					$wpdb->insert( $wpdb->dozent_quiz_question_options, $optionData );
				}
			}
		}

		do_action( 'dozent/quiz/update_question/after', $data, $question_id );

		ob_start();
		$quiz_id = $question->quiz_id;
		include DOZENT_ABSPATH . 'views/course_builder/quiz_questions.php';
		$questions_html = ob_get_clean();

		wp_send_json_success( [ 'questions_html' => $questions_html ] );
	}

	public function quiz_question_delete() {
		global $wpdb;
		$question_id = dozent_input_text( 'question_id' );

		$wpdb->delete( $wpdb->dozent_quiz_questions, [ 'question_id' => $question_id ] );
		$wpdb->delete( $wpdb->dozent_quiz_question_options, [ 'question_id' => $question_id ] );

		wp_send_json_success();
	}

	/**
	 * Delete Quiz Question Option
	 *
	 *
	 * @since DozentLMS 1.0.0
	 */
	public function question_option_delete() {
		global $wpdb;
		$option_id = dozent_input_text( 'option_id' );
		$wpdb->delete( $wpdb->dozent_quiz_question_options, [ 'option_id' => $option_id ] );
		wp_send_json_success();
	}

	/**
	 * Start Quiz
	 *
	 * @since DozentLMS 1.0.0
	 */

	public function start_quiz() {
		dozent_checking_nonce();

		global $wpdb;

		$quiz_id   = (int) dozent_input_text( 'quiz_id' );
		$user_id   = get_current_user_id();
		$course_id = dozent_get_course_id_by_content( $quiz_id );

		$has_started_attempt = dozent_get_attempts( [
			'quiz_id' => $quiz_id,
			'user_id' => $user_id,
			'status'  => 'started',
		] );

		if ( ! $has_started_attempt->count ) {
			$quiz_option     = dozent_get_quiz_option( $quiz_id );
			$passing_percent = (int) dozent_array_get( 'passing_score', $quiz_option );

			$attempt_data = [
				'course_id'       => $course_id,
				'quiz_id'         => $quiz_id,
				'user_id'         => $user_id,
				'questions_limit' => dozent_array_get( 'questions_limit', $quiz_option ),
				'status'          => 'started',
				'quiz_gradable'   => dozent_array_get( 'gradable', $quiz_option ),
				'passing_percent' => $passing_percent,
				'quiz_option'     => json_encode( $quiz_option ),
				'created_at'      => dozent_mysql_time(),
				'updated_at'      => dozent_mysql_time(),
			];

			$wpdb->insert( $wpdb->dozent_quiz_attempts, $attempt_data );
		}

		dozent_redirect( get_permalink( $quiz_id ) );
	}

	/**
	 * Check if user attempting to any question right now.
	 *
	 * @since DozentLMS 1.0.0
	 *
	 * @param $template
	 *
	 * @return string
	 */

	public function quiz_attempting( $template ) {
		global $wpdb;

		if ( is_dozent_single_quiz() && is_user_logged_in() ) {
			$user_id = get_current_user_id();

			$quiz_id = get_the_ID();

			$has_started_attempt = dozent_get_attempts( [
				'quiz_id' => $quiz_id,
				'user_id' => $user_id,
				'status'  => 'started',
			], true );

			if ( $has_started_attempt->count ) {

				$attempt = $has_started_attempt->results;

				$answered = dozent_get_quiz_answers( [
					'quiz_id'    => $quiz_id,
					'user_id'    => $user_id,
					'attempt_id' => $attempt->id
				] );

				$answered_question_ids = [];
				if ( dozent_count( $answered->results ) ) {
					$answered_question_ids = wp_list_pluck( $answered->results, 'question_id' );
				}

				$questions = dozent_get_questions( [ 'quiz_id' => $quiz_id ] );

				$questions_limit = (int) ( $questions->count >= $attempt->questions_limit ) ? $attempt->questions_limit
					: $questions->count;

				if ( $answered->count >= $questions_limit ) {
					//Finished Quiz

					$reviewRequired = dozent_get_quiz_answers( [
						'quiz_id'    => $quiz_id,
						'user_id'    => $user_id,
						'attempt_id' => $attempt->id,
						'where_sql'  => " AND (q_type = 'text' OR q_type = 'textarea') ",
					] );


					$total_scores = array_sum( array_map( function ( $item ) {
						return $item->q_score;
					}, $answered->results ) );

					$earned_scores = array_sum( array_map( function ( $item ) {
						return $item->earned_scores;
					}, $answered->results ) );

					$earned_percentage = ( $earned_scores > 0 && $total_scores > 0 ) ? ( number_format( ( $earned_scores
					                                                                                      * 100 )
					                                                                                    / $total_scores ) )
						: 0;

					$attempt_data = [
						'total_scores'   => $total_scores,
						'total_answered' => $answered->count,
						'earned_scores'  => $earned_scores,
						'earned_percent' => $earned_percentage,
					];

					if ( $reviewRequired->count ) {
						$attempt_data['status'] = 'in_review';
					} else {
						$attempt_data['status'] = 'finished';
					}
					$attempt_data['ended_at'] = dozent_mysql_time();

					$wpdb->update( $wpdb->dozent_quiz_attempts, $attempt_data, [ 'id' => $attempt->id ] );
					dozent_complete_content( $quiz_id );

					dozent_redirect( get_the_permalink( $quiz_id ) );
				}

				$question_number = $answered->count + 1;
				$quiz_title      = get_the_title( $quiz_id );


				/**
				 * Query single random question
				 */

				$question_query_args = [ 'quiz_id' => $quiz_id, 'order_by' => 'rand' ];
				if ( dozent_count( $answered_question_ids ) ) {
					$question_query_args['not_in'] = [ 'question_id' => $answered_question_ids ];
				}

				$question = dozent_get_questions( $question_query_args, true );

				$attempt_option = apply_filters( 'dozent_quiz_attempt_options', [
					'started_at'    => $attempt->created_at,
					'date_time_now' => dozent_mysql_time(),
					'quiz_option'   => json_decode( $attempt->quiz_option, true ),
				] );

				global $question_data;

				$question_data = [
					'quiz_id'         => $quiz_id,
					'quiz_title'      => $quiz_title,
					'question_number' => $question_number,
					'questions_limit' => $questions_limit,
					'question'        => $question,
					'attempt'         => $attempt,
					'attempt_option'  => $attempt_option,
				];

				$template = dozent_get_template_path( 'quiz-attempt' );
			}
		}

		return $template;
	}

	public function submit_question() {
		global $wpdb;

		$user_id    = get_current_user_id();
		$questions  = dozent_input_array_field( 'questions' );
		$attempt_id = dozent_input_text( 'attempt_id' );

		if ( dozent_count( $questions ) ) {

			foreach ( $questions as $question_id => $answer ) {

				$question = dozent_get_question( $question_id );
				$answer   = is_string( $answer ) ? $answer : json_encode( $answer );

				$is_correct = 0;
				$r_score    = 0;

				if ( $question->question_type === 'true_false' ) {
					if ( $question->mixed_value == $answer ) {
						$is_correct = 1;
						$r_score    = $question->score;
					}
				} elseif ( $question->question_type === 'radio' ) {
					$option = dozent_get_question_correct_options( $question_id, true );
					if ( $option && $option->option_id == $answer ) {
						$is_correct = 1;
						$r_score    = $question->score;
					}
				} elseif ( $question->question_type === 'checkbox' ) {
					$options    = (array) dozent_get_question_correct_options( $question_id );
					$option_ids = wp_list_pluck( $options, 'option_id' );

					if ( ! count( array_diff( $option_ids, json_decode( $answer, true ) ) ) ) {
						$is_correct = 1;
						$r_score    = $question->score;
					}
				}

				$answerData = apply_filters( 'dozent_quiz_attempt_answer_data', [
					'quiz_id'       => $question->quiz_id,
					'question_id'   => $question_id,
					'user_id'       => $user_id,
					'attempt_id'    => $attempt_id,
					'answer'        => $answer,
					'q_type'        => $question->question_type,
					'q_score'       => $question->score,
					'earned_scores' => $r_score,
					'is_correct'    => $is_correct,
				] );

				$wpdb->insert( $wpdb->dozent_quiz_answers, $answerData );
			}
		}

		wp_send_json_success( [ 'quiz_url' => get_the_permalink( $question->quiz_id ) ] );
	}

	/**
	 * Filter default column data for Quiz Attempts Table
	 *
	 *
	 * @since DozentLMS 1.0.0
	 *
	 * @param $cell_data
	 * @param $column
	 * @param $row_data
	 *
	 * @return string
	 */
	public function table_quiz_attempts_column( $cell_data, $column, $row_data ) {

		$attempt_id = dozent_array_get( 'id', $row_data );
		$quiz_id    = dozent_array_get( 'quiz_id', $row_data );
		$course_id  = dozent_array_get( 'course_id', $row_data );

		if ( $column === 'info' ) {

			$output = "";

			$quiz_title   = "<a href=' " . get_the_permalink( $quiz_id ) . " '> " . get_the_title( $quiz_id ) . " </a>";
			$course_title = "<a href=' " . get_the_permalink( $course_id ) . " '> " . get_the_title( $course_id )
			                . " </a>";

			$output .= "<p> <strong> <i class='dicon-quiz'></i> " . __( 'Quiz', 'dozent' ) . " : </strong> "
			           . $quiz_title . " </p>";
			$output .= "<p> <strong> <i class='dicon-graduation-cap'></i> " . __( 'Quiz', 'dozent' ) . " : </strong> "
			           . $course_title . " </p>";


			return $output;
		}

		if ( $column === 'time' ) {
			$output = "";

			$created_at = dozent_array_get( 'created_at', $row_data );
			$ended_at   = dozent_array_get( 'ended_at', $row_data );

			$output .= "<p> " . __( 'Started at', 'dozent' ) . " : <span class='dozent-text-muted'> "
			           . dozent_datetime_format( strtotime( $created_at ) ) . " </span> </p>";
			$output .= "<p> " . __( 'Started at', 'dozent' ) . " : <span class='dozent-text-muted'> "
			           . dozent_datetime_format( strtotime( $ended_at ) ) . " </span> </p>";

			return $output;
		}

		if ( $column === 'stats' ) {

			$output = "";

			$total_scores    = dozent_array_get( 'total_scores', $row_data );
			$earned_scores   = dozent_array_get( 'earned_scores', $row_data );
			$attempt_status  = dozent_array_get( 'status', $row_data );
			$passing_percent = dozent_array_get( 'passing_percent', $row_data );

			$earned_percentage = $earned_scores > 0 ? ( number_format( ( $earned_scores * 100 ) / $total_scores ) ) : 0;

			$result = '';
			if ( $attempt_status === 'in_review' ) {
				$result = '<span class="dozent-pill">' . __( 'Under Review', 'dozent' ) . '</span>';
			} else {
				if ( $earned_percentage >= $passing_percent ) {
					$result = '<span class="dozent-pill dozent-pill-result-pass">' . __( 'Pass', 'dozent' ) . '</span>';
				} else {
					$result = '<span class="dozent-pill dozent-pill-result-fail">' . __( 'Fail', 'dozent' ) . '</span>';
				}
			}

			$output .= "<p> " . __( 'Total Scores', 'dozent' ) . " : <span class='dozent-text-muted'> " . $total_scores
			           . " </span> </p>";
			$output .= "<p> " . __( 'Earned Scores', 'dozent' ) . " : <span class='dozent-text-muted'> "
			           . $earned_scores . " ({$earned_percentage}%)" . " </span> </p>";

			$output .= "<p> " . __( 'Result', 'dozent' ) . " : {$result} </p>";

			return $output;
		}

		if ( $column === 'status' ) {
			$status = dozent_array_get( $cell_data, dozent_attempt_statuses() );
			$output = "<span class='dozent-pill dozent-pill-status-{$cell_data}'> {$status} </span>";

			return $output;
		}

		if ( $column === 'actions' ) {
			$attempt_id = dozent_array_get( 'id', $row_data );
			$url        = add_query_arg( [ 'view_attempt_id' => $attempt_id ] );

			$output = "<a href='{$url}'> " . __( 'View', 'dozent' ) . " </a>";

			return $output;
		}

		return $cell_data;
	}

	/**
	 * View attempt detail
	 *
	 * This page will see by student, when any student click to view attempt from the previous attempt list.
	 *
	 *
	 * @since DozentLMS 1.0.0
	 *
	 *
	 * @param $template
	 * @param $post_type
	 *
	 * @return mixed
	 */

	public function attempt_view( $template, $post_type ) {

		if ( $template === 'single/quiz/content' ) {
			$attempt_id = (int) dozent_input_text( 'view_attempt_id' );
			if ( $attempt_id ) {
				return "single/quiz/attempt_view";
			}
		}

		return $template;
	}

	public function table_quiz_attempts_courses_column( $cell_data, $column, $data ) {
		$course_id          = dozent_array_get( 'ID', $data );
		$post_statuses      = get_post_statuses( $course_id );
		$course_status      = get_post_status( $course_id );
		$course_status_text = dozent_array_get( $course_status, $post_statuses );

		if ( $column === 'thumbnail' ) {
			return sprintf( '<div class="courses-thumbnail"> %s </div>', dozent_get_course_thumbnail( $course_id ) );
		}

		if ( $column === 'course_info' ) {
			$quiz_count         = dozent_get_total_quiz( $course_id );
			$attempt_count      = dozent_get_attempts_count_by_course( $course_id );
			$quiz_count_text    = sprintf( _n( '%s quiz', '%s quizzes', $quiz_count, 'dozent' ),
				number_format_i18n( $quiz_count ) );
			$attempt_count_text = sprintf( _n( '%s attempt', '%s attempts', $attempt_count, 'dozent' ),
				number_format_i18n( $attempt_count ) );

			$output = '';
			$output .= "<p class='dozent-mb-2'>
                           <a href='" . get_the_permalink( $course_id ) . "' target='_blank'><strong> "
			           . get_the_title( $course_id ) . " </strong></a> <span class='dozent-pill dozent-pill-{$course_status}'> {$course_status_text} </span>
                        </p>";

			$output .= "<p class='dozent-text-muted dozent-small'> {$quiz_count_text}, {$attempt_count_text} </p>";

			return $output;
		}

		if ( $column === 'buttons' ) {
			$quizzes_url = dozent_get_dashboard_permalink( 'quiz-attempts/quizzes' );
			$quizzes_url = add_query_arg( [ 'course_id' => $course_id ], $quizzes_url );

			return sprintf( "<a href='%s' class='dozent-btn dozent-btn-primary'> <i class='dicon-quiz'></i> "
			                . __( 'Quizzes', 'dozent' ) . " </a>", $quizzes_url );
		}

		return $cell_data;
	}

	public function table_dozent_course_has_quizzes_column( $cell_data, $column, $data ) {
		$quiz_id = dozent_array_get( 'ID', $data );

		if ( $column === 'quiz_info' ) {
			$attempt_count      = dozent_get_attempts_count_by_quiz( $quiz_id );
			$attempt_count_text = sprintf( _n( '%s attempt', '%s attempts', $attempt_count, 'dozent' ),
				number_format_i18n( $attempt_count ) );

			$output = '';
			$output .= "<p class='dozent-mb-2'>
                           <a href='" . get_the_permalink( $quiz_id ) . "' target='_blank'><strong> "
			           . get_the_title( $quiz_id ) . " </strong></a>
                        </p>";

			$output .= "<p class='dozent-text-muted dozent-small'> {$attempt_count_text} </p>";

			return $output;
		}

		if ( $column === 'buttons' ) {
			$quizzes_url = dozent_get_dashboard_permalink( 'quiz-attempts/attempts' );
			$quizzes_url = add_query_arg( [ 'quiz_id' => $quiz_id ], $quizzes_url );

			return sprintf( "<a href='%s' class='dozent-btn dozent-btn-primary'> <i class='dicon-quiz'></i> "
			                . __( 'Attempts', 'dozent' ) . " </a>", $quizzes_url );
		}

		return $cell_data;
	}

	public function table_attempts_for_review_column( $cell_data, $column, $data ) {
		$attempt_id = dozent_array_get( 'id', $data );
		$user_id    = dozent_array_get( 'user_id', $data );

		if ( $column === 'attempt_info' ) {

			$user        = get_userdata( $user_id );
			$avatar      = dozent_get_avatar( $user_id );
			$profile_url = dozent_user_url( $user, false );


			$total_scores    = dozent_array_get( 'total_scores', $data );
			$earned_scores   = dozent_array_get( 'earned_scores', $data );
			$attempt_status  = dozent_array_get( 'status', $data );
			$passing_percent = dozent_array_get( 'passing_percent', $data );
			$created_at      = dozent_array_get( 'created_at', $data );
			$ended_at        = dozent_array_get( 'ended_at', $data );

			$earned_percentage = $earned_scores > 0 ? ( number_format( ( $earned_scores * 100 ) / $total_scores ) ) : 0;
			$takes_time        = __( 'Time Takes', 'dozent' ) . ' - ' . dozent_datetime_diff( $created_at, $ended_at );

			if ( $attempt_status === 'in_review' ) {
				$result = '<span class="dozent-pill">' . __( 'Under Review', 'dozent' ) . '</span>';
			} else {
				if ( $earned_percentage >= $passing_percent ) {
					$result = '<span class="dozent-pill dozent-pill-result-pass">' . __( 'Pass', 'dozent' ) . '</span>';
				} else {
					$result = '<span class="dozent-pill dozent-pill-result-fail">' . __( 'Fail', 'dozent' ) . '</span>';
				}
			}

			$output
				= "<div class='student-name dozent-d-flex dozent-align-flex-start'> {$avatar} <strong class='flex-grow-1'> <a href='{$profile_url}' target='_blank'> {$user->display_name}  </a> </strong> {$result} </div>";

			$output .= "<p class='dozent-text-muted dozent-small'> {$takes_time} </p>";


			return $output;
		}

		if ( $column === 'status' ) {
			$status = dozent_array_get( $cell_data, dozent_attempt_statuses() );
			$output = "<span class='dozent-pill dozent-pill-status-{$cell_data}'> {$status} </span>";

			return $output;
		}

		if ( $column === 'buttons' ) {
			$review_url = dozent_get_dashboard_permalink( 'quiz-attempts/attempt-review' );
			$review_url = add_query_arg( [ 'attempt_id' => $attempt_id ], $review_url );

			return sprintf( "<a href='%s' class='dozent-btn dozent-btn-primary'> <i class='dicon-check'></i> "
			                . __( 'Review', 'dozent' ) . " </a>", $review_url );
		}

		return $cell_data;
	}


	/**
	 * Review Quiz Attempt
	 *
	 *
	 * @since DozentLMS 1.0.0
	 */

	public function review_attempt() {
		dozent_checking_nonce();

		global $wpdb;

		$review_btn = dozent_input_text( 'review_btn' );
		$attempt_id = (int) dozent_input_text( 'attempt_id' );
		$attempt = dozent_get_attempts( [ 'id' => $attempt_id ], true )->results;

		if ( $review_btn === 'review' ) {
			//Review Attempt

			$answers = dozent_input_array_field( 'answers' );
			$user_id = get_current_user_id();

			if ( dozent_count( $answers ) ) {
				foreach ( $answers as $answer_id => $answer ) {
					$data = [
						'earned_scores' => dozent_array_get( 'review_score', $answer ),
						'is_correct'    => (int) dozent_array_get( 'is_correct', $answer ),
					];

					$wpdb->update( $wpdb->dozent_quiz_answers, $data, [ 'id' => $answer_id ] );
				}

				/**
				 * Update attempt data
				 */

				$total_scores = $attempt->total_scores;

				$earned_scores = $wpdb->get_var( "SELECT SUM(earned_scores) AS earned_scores FROM {$wpdb->dozent_quiz_answers} WHERE attempt_id = {$attempt_id} " );

				$earned_percentage = ( $earned_scores > 0 && $total_scores > 0 ) ? ( ( $earned_scores * 100 ) / $total_scores ) : 0;
				$passed            = $earned_percentage >= $attempt->passing_percent ? 1 : 0;

				$review_data = [
					'reviewer_id'    => $user_id,
					'earned_scores'  => $earned_scores,
					'earned_percent' => $earned_percentage,
					'status'         => 'finished',
					'is_reviewed'    => 1,
					'reviewed_at'    => dozent_mysql_time(),
					'passed'         => $passed,
				];

				$wpdb->update( $wpdb->dozent_quiz_attempts, $review_data, [ 'id' => $attempt_id ] );

				/**
				 * Fire action after quiz attempt review finished
				 *
				 * @since DozentLMS 1.0.0
				 *
				 * @param int $attempt_id Attempt ID
				 */

				do_action( 'dozent_quiz_attempt_review_after', $attempt_id );

				dozent_set_flash_message( __( 'Attempt has been reviewed', 'dozent' ) );
			}
		}

		/**
		 * Delete Review and sync with quiz.
		 */
		if ( $review_btn === 'delete' ) {

			$wpdb->delete( $wpdb->dozent_quiz_attempts, [ 'id' => $attempt_id ] );
			$wpdb->delete( $wpdb->dozent_quiz_answers, [ 'attempt_id' => $attempt_id ] );
			dozent_complete_content( $attempt->quiz_id, $attempt->user_id, 'delete' );

			/**
			 * Fire action after quiz attempt deleted
			 *
			 * @since DozentLMS 1.0.0
			 *
			 * @param int $attempt_id Attempt ID
			 */

			do_action( 'dozent_quiz_attempt_deleted', $attempt_id );

			dozent_set_flash_message( __( 'Attempt has been deleted', 'dozent' ) );
			dozent_redirect( dozent_get_dashboard_permalink( "quiz-attempts/quizzes/?course_id={$attempt->course_id}" ) );
		}

		dozent_redirect();
	}


}

Methods