at path:
ROOT
/
wp-content
/
plugins
/
tutor
/
classes
/
QuizBuilder.php
run:
R
W
Run
Addons.php
11.79 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Admin.php
26.61 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Ajax.php
17.03 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Announcements.php
2.79 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Assets.php
24.27 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Backend_Page_Trait.php
4.39 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
BaseController.php
1.47 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Config.php
3.16 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Container.php
4.85 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Course.php
94.45 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Course_Embed.php
2.55 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Course_Filter.php
8.68 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Course_List.php
14.68 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Course_Settings_Tabs.php
1.16 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Course_Widget.php
8.2 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Custom_Validation.php
513 By
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Dashboard.php
1.23 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Earnings.php
10.43 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
FormHandler.php
7.16 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Frontend.php
2.94 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Gutenberg.php
4.62 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Icon.php
11.49 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Input.php
9.08 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Instructor.php
13.2 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Instructors_List.php
13.09 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Lesson.php
19.92 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Options_V2.php
67.77 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Permalink.php
2 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Post_types.php
18.3 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Private_Course_Access.php
2.52 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Q_And_A.php
10.78 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Question_Answers_List.php
2.55 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Quiz.php
35.16 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
QuizBuilder.php
12.53 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Quiz_Attempts_List.php
7.28 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
RestAPI.php
7.97 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Reviews.php
3.72 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Rewrite_Rules.php
5.18 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Shortcode.php
14.18 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Singleton.php
1.08 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Student.php
10.44 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Students_List.php
3.03 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Taxonomies.php
8.2 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Template.php
14.52 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Theme_Compatibility.php
683 By
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Tools.php
3.33 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Tools_V2.php
18.56 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Tutor.php
36.45 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
TutorEDD.php
4.69 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Tutor_Base.php
1.48 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Tutor_Setup.php
33.34 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Upgrader.php
12.49 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
User.php
14.9 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Utils.php
270.91 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Video_Stream.php
3.94 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
WhatsNew.php
4.83 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Withdraw.php
9.49 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
Withdraw_Requests_List.php
6.24 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
WooCommerce.php
24.76 KB
2026-04-14 05:34:26
R
W
Run
Delete
Rename
error_log
up
📄
QuizBuilder.php
Save
<?php /** * Quiz Builder * * @package Tutor\Classes * @author Themeum <support@themeum.com> * @link https://themeum.com * @since 3.0.0 */ namespace TUTOR; if ( ! defined( 'ABSPATH' ) ) { exit; } use Tutor\Helpers\HttpHelper; use Tutor\Helpers\QueryHelper; use Tutor\Helpers\ValidationHelper; use Tutor\Models\QuizModel; use Tutor\Traits\JsonResponse; /** * Class QuizBuilder * * @since 1.0.0 */ class QuizBuilder { use JsonResponse; const TRACKING_KEY = '_data_status'; const FLAG_NEW = 'new'; const FLAG_UPDATE = 'update'; const FLAG_NO_CHANGE = 'no_change'; /** * Register hooks and dependencies. * * @param boolean $register_hooks register hooks or not. */ public function __construct( $register_hooks = true ) { if ( ! $register_hooks ) { return; } add_action( 'wp_ajax_tutor_quiz_builder_save', array( $this, 'ajax_quiz_builder_save' ) ); } /** * Prepare question data. * * @since 3.0.0 * * @param int $quiz_id quiz id. * @param array $input question data. * * @return array */ public function prepare_question_data( $quiz_id, $input ) { $question_title = Input::sanitize( wp_slash( $input['question_title'] ), '' ); $question_description = Input::sanitize( wp_slash( $input['question_description'] ) ?? '', '', Input::TYPE_KSES_POST ); $question_type = Input::sanitize( $input['question_type'], '' ); $question_mark = Input::sanitize( $input['question_mark'], 1, Input::TYPE_INT ); $question_settings = Input::sanitize_array( $input['question_settings'] ); $data = array( 'quiz_id' => $quiz_id, 'question_title' => $question_title, 'question_description' => $question_description, 'question_type' => $question_type, 'question_mark' => $question_mark, 'question_settings' => maybe_serialize( $question_settings ), ); return apply_filters( 'tutor_quiz_question_data', $data, $input ); } /** * Prepare answer data. * * @param int $question_id question id. * @param string $question_type question type. * @param array $input answer data. * * @return array */ public function prepare_answer_data( $question_id, $question_type, $input ) { $answer_title = Input::sanitize( wp_slash( $input['answer_title'] ) ?? '', '' ); $is_correct = Input::sanitize( $input['is_correct'] ?? 0, 0, Input::TYPE_INT ); $image_id = Input::sanitize( $input['image_id'] ?? null ); $answer_two_gap_match = Input::sanitize( $input['answer_two_gap_match'] ?? '' ); $answer_view_format = Input::sanitize( $input['answer_view_format'] ?? '' ); $answer_settings = null; $answer_data = array( 'belongs_question_id' => $question_id, 'belongs_question_type' => $question_type, 'answer_title' => $answer_title, 'is_correct' => $is_correct, 'image_id' => $image_id, 'answer_two_gap_match' => $answer_two_gap_match, 'answer_view_format' => $answer_view_format, 'answer_settings' => $answer_settings, ); return $answer_data; } /** * Save question answers. * * @since 3.7.0 * * @param int $question_id question id. * @param string $question_type question type. * @param array $question_answers question answers. * * @return void */ public function save_question_answers( $question_id, $question_type, $question_answers ) { global $wpdb; $answers_table = $wpdb->prefix . 'tutor_quiz_question_answers'; $answer_order = 0; foreach ( $question_answers as $answer ) { $data_status = isset( $answer[ self::TRACKING_KEY ] ) ? $answer[ self::TRACKING_KEY ] : self::FLAG_NO_CHANGE; $answer_data = $this->prepare_answer_data( $question_id, $question_type, $answer ); // New answer. if ( self::FLAG_NEW === $data_status ) { $wpdb->insert( $answers_table, $answer_data ); $answer_id = $wpdb->insert_id; } // Update answer. if ( self::FLAG_UPDATE === $data_status ) { $answer_id = $answer['answer_id']; $wpdb->update( $answers_table, $answer_data, array( 'answer_id' => $answer_id ) ); } if ( self::FLAG_NO_CHANGE === $data_status ) { $answer_id = $answer['answer_id']; } // Save sort order. $answer_order++; $wpdb->update( $answers_table, array( 'answer_order' => $answer_order ), array( 'answer_id' => $answer_id ) ); } } /** * Save quiz questions. * * @since 3.0.0 * * @param int $quiz_id quiz id. * @param array $questions questions data. * * @return void */ public function save_questions( $quiz_id, $questions ) { global $wpdb; $questions_table = $wpdb->prefix . 'tutor_quiz_questions'; $question_order = 0; foreach ( $questions as $question ) { $data_status = isset( $question[ self::TRACKING_KEY ] ) ? $question[ self::TRACKING_KEY ] : self::FLAG_NO_CHANGE; $question_order++; if ( isset( $question['is_cb_question'], $question['cb_action'] ) && 'link' === $question['cb_action'] ) { $question['question_order'] = $question_order; do_action( 'tutor_content_bank_question_linked_to_quiz', $quiz_id, (object) $question ); continue; } $question_type = Input::sanitize( $question['question_type'] ); $question_data = $this->prepare_question_data( $quiz_id, $question ); $question_answers = isset( $question['question_answers'] ) ? $question['question_answers'] : array(); // New question. if ( self::FLAG_NEW === $data_status ) { $wpdb->insert( $questions_table, $question_data ); $question_id = $wpdb->insert_id; if ( isset( $question['is_cb_question'] ) ) { $question['question_order'] = $question_order; $question['new_question_id'] = $question_id; do_action( 'tutor_content_bank_question_added_to_quiz', $quiz_id, (object) $question ); } } // Update question. if ( self::FLAG_UPDATE === $data_status ) { $question_id = (int) $question['question_id']; $wpdb->update( $questions_table, $question_data, array( 'question_id' => $question_id ) ); } if ( self::FLAG_NO_CHANGE === $data_status ) { $question_id = $question['question_id']; } // Save sort order. $wpdb->update( $questions_table, array( 'question_order' => $question_order ), array( 'question_id' => $question_id ) ); // Save question's answers. $this->save_question_answers( $question_id, $question_type, $question_answers ); } } /** * Validate payload. * * @since 3.0.0 * * @param array $payload payload. * * @return object consist success, errors. */ public function validate_payload( $payload ) { $errors = array(); $success = true; if ( ! is_array( $payload ) ) { $success = false; $errors['payload'] = __( 'Invalid payload', 'tutor' ); } $rules = array( 'post_title' => 'required', 'quiz_option' => 'required|is_array', 'questions' => 'required|is_array', ); $validation = ValidationHelper::validate( $rules, $payload ); if ( ! $validation->success ) { $success = false; $errors = array_merge( $errors, $validation->errors ); } foreach ( $payload['questions'] as $question ) { if ( ! isset( $question[ self::TRACKING_KEY ] ) ) { $success = false; // translators: %s is the tracking key required for each question. $errors[ self::TRACKING_KEY ][] = sprintf( __( '%s is required for each question', 'tutor' ), self::TRACKING_KEY ); break; } if ( ! in_array( $question[ self::TRACKING_KEY ], array( self::FLAG_NEW, self::FLAG_UPDATE, self::FLAG_NO_CHANGE ), true ) ) { $success = false; // translators: %s is the tracking key for which the value is invalid. $errors[ self::TRACKING_KEY ][] = sprintf( __( 'Invalid value for %s', 'tutor' ), self::TRACKING_KEY ); break; } if ( ! isset( $question['question_settings'] ) || ! is_array( $question['question_settings'] ) ) { $success = false; $errors['question_settings'][] = __( 'Question settings is required with array data', 'tutor' ); break; } } return (object) array( 'success' => $success, 'errors' => $errors, ); } /** * Handle delete questions and answers. * * @since 3.0.0 * * @param array $deleted_question_ids question ids. * @param array $deleted_answer_ids answer ids. * * @return void */ public function handle_delete( $deleted_question_ids = array(), $deleted_answer_ids = array() ) { global $wpdb; $deleted_question_ids = array_filter( $deleted_question_ids, 'is_numeric' ); $deleted_answer_ids = array_filter( $deleted_answer_ids, 'is_numeric' ); if ( count( $deleted_question_ids ) ) { $in_clause = QueryHelper::prepare_in_clause( $deleted_question_ids ); //phpcs:ignore -- sanitized $in_clause. $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}tutor_quiz_questions WHERE content_id IS NULL AND question_id IN ({$in_clause})" ) ); do_action( 'tutor_deleted_quiz_question_ids', $deleted_question_ids ); } if ( count( $deleted_answer_ids ) ) { $in_clause = QueryHelper::prepare_in_clause( $deleted_answer_ids ); //phpcs:ignore -- sanitized $in_clause. $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}tutor_quiz_question_answers WHERE answer_id IN ({$in_clause})" ) ); } } /** * Create or update quiz. * * @since 3.0.0 * * @param int $topic_id topic id. * @param array $payload payload. * * @return object consist success, errors. */ public function save_quiz( $topic_id, $payload ) { $success = true; $data = null; $errors = array(); $validation = $this->validate_payload( $payload ); if ( ! $validation->success ) { return (object) array( 'success' => false, 'errors' => $validation->errors, ); } $is_update = isset( $payload['ID'] ); $quiz_id = $is_update ? $payload['ID'] : null; $questions = isset( $payload['questions'] ) ? $payload['questions'] : array(); $menu_order = (int) ( isset( $payload['menu_order'] ) ? $payload['menu_order'] : tutor_utils()->get_next_course_content_order_id( $topic_id, $quiz_id ) ); $quiz_data = array( 'post_type' => tutor()->quiz_post_type, 'post_title' => Input::sanitize( wp_slash( $payload['post_title'] ?? '' ) ), 'post_content' => Input::sanitize( wp_slash( $payload['post_content'] ?? '' ) ), 'post_status' => 'publish', 'post_author' => get_current_user_id(), 'post_parent' => $topic_id, 'menu_order' => $menu_order, ); global $wpdb; $wpdb->query( 'START TRANSACTION' ); try { // Add or update the quiz. if ( $is_update ) { $quiz_data['ID'] = $quiz_id; } $quiz_id = wp_insert_post( $quiz_data ); do_action( ( $is_update ? 'tutor_quiz_updated' : 'tutor_initial_quiz_created' ), $quiz_id ); // Save quiz settings. $quiz_option = Input::sanitize_array( $payload['quiz_option'] ?? array() ); //phpcs:ignore update_post_meta( $quiz_id, Quiz::META_QUIZ_OPTION, $quiz_option ); do_action( 'tutor_quiz_settings_updated', $quiz_id ); // Save quiz questions. if ( count( $questions ) ) { $this->save_questions( $quiz_id, $questions ); } // Delete questions and answers. $deleted_question_ids = Input::post( 'deleted_question_ids', array(), Input::TYPE_ARRAY ); $deleted_answer_ids = Input::post( 'deleted_answer_ids', array(), Input::TYPE_ARRAY ); $this->handle_delete( $deleted_question_ids, $deleted_answer_ids ); $wpdb->query( 'COMMIT' ); $data = $quiz_id; } catch ( \Throwable $th ) { $wpdb->query( 'ROLLBACK' ); $success = false; $errors['500'][] = $th->getMessage(); } return (object) array( 'success' => $success, 'data' => $data, 'errors' => $errors, ); } /** * Create or update quiz from new course builder. * * @since 3.0.0 * * @return void json response. */ public function ajax_quiz_builder_save() { tutor_utils()->check_nonce(); $payload = $_POST['payload'] ?? array(); //phpcs:ignore if ( is_string( $payload ) ) { $payload = json_decode( wp_unslash( $payload ), true ); } $course_id = Input::post( 'course_id', 0, Input::TYPE_INT ); $topic_id = Input::post( 'topic_id', 0, Input::TYPE_INT ); $course_cls = new Course( false ); $course_cls->check_access( $course_id ); $result = $this->save_quiz( $topic_id, wp_slash( $payload ) ); if ( $result->success ) { $quiz_id = $result->data; $quiz_details = QuizModel::get_quiz_details( $quiz_id ); $this->json_response( __( 'Quiz saved successfully', 'tutor' ), $quiz_details ); } else { $this->json_response( __( 'Error', 'tutor' ), $result->errors, HttpHelper::STATUS_BAD_REQUEST ); } } }