Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/quiz-master-next/mlw_quizmaster2.php
+++ b/quiz-master-next/mlw_quizmaster2.php
@@ -2,7 +2,7 @@
/**
* Plugin Name: Quiz And Survey Master
* Description: Easily and quickly add quizzes and surveys to your website.
- * Version: 11.1.2
+ * Version: 11.1.3
* Author: ExpressTech
* Author URI: https://quizandsurveymaster.com/
* Plugin URI: https://expresstech.io/
@@ -43,7 +43,7 @@
* @var string
* @since 4.0.0
*/
- public $version = '11.1.2';
+ public $version = '11.1.3';
/**
* QSM Alert Manager Object
--- a/quiz-master-next/php/admin/admin-results-page.php
+++ b/quiz-master-next/php/admin/admin-results-page.php
@@ -560,13 +560,15 @@
$co = ! empty( $quiz_infos ) ? count( $quiz_infos ) : 0;
if ( $co > 0 ) {
for ( $x = 0; $x < $co; $x++ ) {
+ $qsm_row_class = apply_filters( 'qsm_admin_results_table_row_class', '', $quiz_infos[ $x ] );
?>
- <tr>
+ <tr<?php echo '' !== $qsm_row_class ? ' class="' . esc_attr( $qsm_row_class ) . '"' : ''; ?>>
<td style="text-align: center;">
<input type="checkbox" class="qmn_delete_checkbox" name="delete_results[]" value="<?php echo esc_attr( $quiz_infos[ $x ]->result_id ); ?>" />
</td>
<td class="<?php echo apply_filters( 'qsm_results_quiz_name_class', '', $quiz_infos[ $x ]->result_id ); ?>">
<strong><a class="row-title" href="admin.php?page=qsm_quiz_result_details&result_id=<?php echo esc_attr( $quiz_infos[ $x ]->result_id ); ?>"><?php echo esc_html( $quiz_infos[ $x ]->quiz_name ); ?></a></strong>
+ <?php do_action( 'qsm_admin_results_quiz_name_after', $quiz_infos[ $x ] ); ?>
<div class="row-actions">
<span class="qsm-row-actions-links" style="color: green;">
<?php do_action('qsm_admin_quiz_results_page_rowactions_before', $quiz_infos[ $x ]);
--- a/quiz-master-next/php/admin/options-page-questions-tab.php
+++ b/quiz-master-next/php/admin/options-page-questions-tab.php
@@ -96,6 +96,7 @@
'qsm_user_ve' => get_user_meta( $user_id, 'rich_editing', true ),
'saveNonce' => wp_create_nonce( 'ajax-nonce-sandy-page' ),
'unlinkNonce' => wp_create_nonce( 'ajax-nonce-unlink-question' ),
+ 'loadAllQuestionsNonce' => wp_create_nonce( 'qsm_load_all_quiz_questions' ),
'categories' => $question_categories,
'form_type' => $form_type,
'quiz_system' => $quiz_system,
@@ -1118,7 +1119,14 @@
*/
function qsm_load_all_quiz_questions_ajax() {
global $wpdb;
- global $mlwQuizMasterNext;
+
+ if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'qsm_load_all_quiz_questions' ) ) {
+ wp_send_json_error( __( 'Nonce verification failed.', 'quiz-master-next' ) );
+ }
+
+ if ( ! current_user_can( 'edit_qsm_quizzes' ) ) {
+ wp_send_json_error( __( 'You do not have permission to view questions.', 'quiz-master-next' ) );
+ }
// Loads questions.
$questions = $wpdb->get_results( "SELECT question_id, question_name FROM {$wpdb->prefix}mlw_questions WHERE deleted = '0' ORDER BY question_id DESC" );
@@ -1133,8 +1141,7 @@
}
// Echos JSON and dies.
- echo wp_json_encode( $question_json );
- wp_die();
+ wp_send_json( $question_json );
}
add_action( 'wp_ajax_qsm_send_data_sendy', 'qsm_send_data_sendy' );
--- a/quiz-master-next/php/classes/class-qmn-quiz-manager.php
+++ b/quiz-master-next/php/classes/class-qmn-quiz-manager.php
@@ -246,6 +246,10 @@
* ajax login function
*/
public function qsm_ajax_login() {
+ if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'qsm_ajax_login_nonce' ) ) {
+ wp_send_json_error( array( 'message' => __( 'Incorrect username or password! Please try again.', 'quiz-master-next' ) ) );
+ }
+
$username = ! empty( $_POST['username'] ) ? sanitize_user( wp_unslash( $_POST['username'] ) ) : '';
$password = ! empty( $_POST['password'] ) ? sanitize_text_field( wp_unslash( $_POST['password'] ) ) : '';
@@ -254,7 +258,7 @@
if ( ! $user ) {
$user = get_user_by( 'email', $username );
if ( ! $user ) {
- wp_send_json_error( array( 'message' => __( 'User not found! Please try again.', 'quiz-master-next' ) ) );
+ wp_send_json_error( array( 'message' => __( 'Incorrect username or password! Please try again.', 'quiz-master-next' ) ) );
}
}
@@ -379,7 +383,7 @@
$result_id = $result['result_id'];
$return_display = do_shortcode( '[qsm_result id="' . $result_id . '"]' );
- $return_display = str_replace( '%FB_RESULT_ID%', $result_unique_id, $return_display );
+ $return_display = str_replace( '%FB_RESULT_ID%', esc_js( esc_attr( $result_unique_id ) ), $return_display );
} else {
$return_display = esc_html__( 'Result id is wrong!', 'quiz-master-next' );
}
@@ -2738,7 +2742,7 @@
$result_display .= apply_filters( 'qmn_captcha_varification_failed_msg', __( 'Captcha verification failed.', 'quiz-master-next' ), $qmn_quiz_options, $qmn_array_for_variables );
}
- $result_display = str_replace( '%FB_RESULT_ID%', $unique_id, $result_display );
+ $result_display = str_replace( '%FB_RESULT_ID%', esc_js( esc_attr( $unique_id ) ), $result_display );
// Prepares data to be sent back to front-end.
$return_array = array(
@@ -3552,6 +3556,7 @@
'qmn_common_ajax_object',
array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
+ 'nonce' => wp_create_nonce( 'qsm_ajax_login_nonce' ),
)
);
}
--- a/quiz-master-next/php/classes/class-qsm-install.php
+++ b/quiz-master-next/php/classes/class-qsm-install.php
@@ -1731,7 +1731,8 @@
global $wpdb, $mlwQuizMasterNext;
$results_table_name = $wpdb->prefix . 'mlw_results';
$data = $mlwQuizMasterNext->version;
- if ( ! get_option( 'qmn_original_version' ) ) {
+ $is_first_install = ! get_option( 'qmn_original_version' );
+ if ( $is_first_install ) {
add_option( 'qmn_original_version', $data );
}
if ( get_option( 'mlw_quiz_master_version' ) != $data ) {
@@ -2279,6 +2280,21 @@
if ( ! get_option( 'mlw_advert_shows' ) ) {
add_option( 'mlw_advert_shows', 'true' );
}
+
+ /**
+ * Always redirect option last for new installation
+ */
+ if (
+ $is_first_install
+ && ! isset( $_GET['activate-multi'] )
+ && ! wp_doing_ajax()
+ && ! is_network_admin()
+ && current_user_can( 'manage_options' )
+ && ! headers_sent()
+ ) {
+ wp_safe_redirect( admin_url( 'admin.php?page=qsm_dashboard' ) );
+ exit;
+ }
}
/**
--- a/quiz-master-next/php/template-variables.php
+++ b/quiz-master-next/php/template-variables.php
@@ -144,11 +144,11 @@
}
}elseif ( 'rich' === $question_settings['answerEditor'] ) {
foreach ( $answers['user_answer'] as $answer ) {
- $answerstr .= htmlspecialchars_decode($answer);
+ $answerstr .= wp_kses_post( htmlspecialchars_decode($answer) );
}
}elseif ( 'image' === $question_settings['answerEditor'] ) {
foreach ( $answers['user_answer'] as $answer ) {
- $answerstr .= '<span class="qmn_image_option" ><img src="' . htmlspecialchars_decode($answer, ENT_QUOTES ) . '"/></span>';
+ $answerstr .= '<span class="qmn_image_option" ><img src="' . esc_url( htmlspecialchars_decode($answer, ENT_QUOTES ) ) . '"/></span>';
}
}else {
$answerstr .= implode(", ",$answers['user_answer']);
@@ -271,9 +271,9 @@
*/
function qsm_answers_type_evaluated( $answer, $question_settings ) {
if ( 'rich' === $question_settings['answerEditor'] ) {
- $answer = htmlspecialchars_decode( $answer );
+ $answer = wp_kses_post( htmlspecialchars_decode( $answer ) );
} elseif ( 'image' === $question_settings['answerEditor'] ) {
- $answer = '<span class="qmn_image_option" ><img src="' . htmlspecialchars_decode( $answer, ENT_QUOTES ) . '"/></span>';
+ $answer = '<span class="qmn_image_option" ><img src="' . esc_url( htmlspecialchars_decode( $answer, ENT_QUOTES ) ) . '"/></span>';
}
return $answer;
}
@@ -1214,16 +1214,32 @@
if ( isset( $answer['id'] ) && isset( $questions[ $answer['id'] ] ) && ! empty( $questions[ $answer['id'] ] ) ) {
$total_answers = isset( $questions[ $answer['id'] ]['answers'] ) ? $questions[ $answer['id'] ]['answers'] : array();
$total_answers = ! empty( $answer['answer_limit_keys'] ) ? $mlwQuizMasterNext->pluginHelper->qsm_get_limited_options_by_keys( $total_answers, $answer['answer_limit_keys'] ) : $total_answers;
+ $random_ids_for_question = array();
if ( ! empty( $_POST['quiz_answer_random_ids'] ) ) {
- $answers_random = array();
- $quiz_answer_random_ids = sanitize_text_field( wp_unslash( $_POST['quiz_answer_random_ids'] ) );
- $quiz_answer_random_ids = qsm_safe_unserialize( $quiz_answer_random_ids );
+ $quiz_answer_random_ids_raw = sanitize_text_field( wp_unslash( $_POST['quiz_answer_random_ids'] ) );
+ $quiz_answer_random_ids = qsm_safe_unserialize( $quiz_answer_random_ids_raw );
if ( ! empty( $quiz_answer_random_ids[ $answer['id'] ] ) && is_array( $quiz_answer_random_ids[ $answer['id'] ] ) ) {
- foreach ( $quiz_answer_random_ids[ $answer['id'] ] as $key ) {
+ $random_ids_for_question = $quiz_answer_random_ids[ $answer['id'] ];
+ }
+ }
+ // Lazy-loaded pages append their shuffled order here so the result
+ // page can render answers in the order the user saw.
+ if ( empty( $random_ids_for_question ) && ! empty( $_POST['qsm_lazy_answer_random_ids'][ $answer['id'] ] ) ) {
+ $lazy_keys = wp_unslash( $_POST['qsm_lazy_answer_random_ids'][ $answer['id'] ] );
+ if ( is_array( $lazy_keys ) ) {
+ $random_ids_for_question = array_map( 'intval', $lazy_keys );
+ }
+ }
+ if ( ! empty( $random_ids_for_question ) ) {
+ $answers_random = array();
+ foreach ( $random_ids_for_question as $key ) {
+ if ( isset( $total_answers[ $key ] ) ) {
$answers_random[ $key ] = $total_answers[ $key ];
}
}
- $total_answers = $answers_random;
+ if ( ! empty( $answers_random ) ) {
+ $total_answers = $answers_random;
+ }
}
if ( $total_answers ) {
if ( isset( $answer['question_type'] ) && in_array( intval( $answer['question_type'] ), $question_with_text_input, true ) ) {
--- a/quiz-master-next/renderer/frontend/class-qsm-ajax-handler.php
+++ b/quiz-master-next/renderer/frontend/class-qsm-ajax-handler.php
@@ -98,10 +98,21 @@
$question_start_number
);
+ global $quiz_answer_random_ids;
+ $response_random_ids = array();
+ if ( in_array( 'answers', $randomness_order, true ) && ! empty( $quiz_answer_random_ids ) ) {
+ foreach ( $question_ids as $qid ) {
+ if ( isset( $quiz_answer_random_ids[ $qid ] ) ) {
+ $response_random_ids[ $qid ] = array_map( 'intval', (array) $quiz_answer_random_ids[ $qid ] );
+ }
+ }
+ }
+
wp_send_json_success( array(
- 'html' => $html,
- 'page_number' => $page_number,
- 'question_count' => count( $question_ids ),
+ 'html' => $html,
+ 'page_number' => $page_number,
+ 'question_count' => count( $question_ids ),
+ 'quiz_answer_random_ids' => $response_random_ids,
) );
} catch ( Exception $e ) {
@@ -147,7 +158,11 @@
// Get quiz settings
$quiz_settings = maybe_unserialize( $quiz_options->quiz_settings );
- $quiz_options_settings = (object) maybe_unserialize( $quiz_settings['quiz_options'] ?? '[]' );
+ $quiz_options_arr = maybe_unserialize( $quiz_settings['quiz_options'] ?? '[]' );
+ $quiz_text_arr = maybe_unserialize( $quiz_settings['quiz_text'] ?? '[]' );
+ $quiz_options_arr = is_array( $quiz_options_arr ) ? $quiz_options_arr : array();
+ $quiz_text_arr = is_array( $quiz_text_arr ) ? $quiz_text_arr : array();
+ $quiz_options_settings = (object) array_merge( $quiz_options_arr, $quiz_text_arr );
// Create renderer instance for template methods (pass class_object to templates)
$quiz_data = array( 'quiz_id' => $quiz_id );
@@ -223,6 +238,11 @@
echo $question_template; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
+ if ( ! empty( $question['hints'] ) ) {
+ $hint_data = wp_kses_post( $mlwQuizMasterNext->pluginHelper->qsm_language_support( $question['hints'], "hint-{$question_id}" ) );
+ QSM_New_Renderer::renderHintToggle( $question_id, $hint_data );
+ }
+
do_action( 'qsm_after_question', $question );
?>
</div>
--- a/quiz-master-next/renderer/frontend/class-qsm-new-renderer.php
+++ b/quiz-master-next/renderer/frontend/class-qsm-new-renderer.php
@@ -494,6 +494,44 @@
return __( 'Unknown', 'quiz-master-next' );
}
}
+
+ /**
+ * Render inline Show/Hide Hint toggle markup.
+ *
+ * @param int $question_id Question ID.
+ * @param string $hint_data HTML pre-sanitized via wp_kses_post().
+ */
+ public static function renderHintToggle( $question_id, $hint_data ) {
+ if ( '' === trim( (string) $hint_data ) ) {
+ return;
+ }
+ $question_id = intval( $question_id );
+ $hint_show = __( 'Show Hint', 'quiz-master-next' );
+ $hint_hide = __( 'Hide Hint', 'quiz-master-next' );
+ $panel_id = 'qsm-hint-panel-' . $question_id;
+ ?>
+ <div class="qsm-hint-wrapper" data-question-id="<?php echo esc_attr( $question_id ); ?>">
+ <button type="button" class="qsm-hint-toggle" aria-expanded="false" aria-controls="<?php echo esc_attr( $panel_id ); ?>">
+ <span class="qsm-hint-icon" aria-hidden="true">
+ <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" focusable="false">
+ <path d="M9 18h6"></path>
+ <path d="M10 22h4"></path>
+ <path d="M12 2a7 7 0 0 0-4 12.7c.7.6 1 1.4 1 2.3v1h6v-1c0-.9.3-1.7 1-2.3A7 7 0 0 0 12 2z"></path>
+ </svg>
+ </span>
+ <span class="qsm-hint-label" data-show="<?php echo esc_attr( $hint_show ); ?>" data-hide="<?php echo esc_attr( $hint_hide ); ?>"><?php echo esc_html( $hint_show ); ?></span>
+ <span class="qsm-hint-chevron" aria-hidden="true">
+ <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" focusable="false">
+ <polyline points="6 9 12 15 18 9"></polyline>
+ </svg>
+ </span>
+ </button>
+ <section id="<?php echo esc_attr( $panel_id ); ?>" class="qsm-hint-panel" aria-label="<?php esc_attr_e( 'Hint', 'quiz-master-next' ); ?>" aria-hidden="true" hidden>
+ <div class="qsm-hint-content"><?php echo $hint_data; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already wp_kses_post above ?></div>
+ </section>
+ </div>
+ <?php
+ }
}
// Initialize the new renderer
--- a/quiz-master-next/renderer/frontend/class-qsm-render-pagination.php
+++ b/quiz-master-next/renderer/frontend/class-qsm-render-pagination.php
@@ -872,7 +872,7 @@
public function render_quiz_timer() {
ob_start();
// Only render timer if timer limit is set
- $timer_limit = isset( $this->quiz_options->timer_limit ) ? intval( $this->quiz_options->timer_limit ) : 0;
+ $timer_limit = isset( $this->quiz_options->timer_limit ) ? floatval( $this->quiz_options->timer_limit ) : 0;
if ( $timer_limit > 0 ) {
?>
<div class="qsm-timer qsm-quiz-timer-<?php echo esc_attr( $this->options->quiz_id ); ?>">
@@ -1069,7 +1069,13 @@
<textarea class="qsm-question-comment qsm-question-comment-large mlw_qmn_question_comment" id="mlwComment<?php echo esc_attr( $question_id ); ?>" name="mlwComment<?php echo esc_attr( $question_id ); ?>" placeholder="<?php echo esc_attr( $comment_placeholder ); ?>" onclick="qmnClearField(this)" ></textarea>
<?php
}
-
+
+ if ( ! empty( $question['hints'] ) ) {
+ global $mlwQuizMasterNext;
+ $hint_data = wp_kses_post( $mlwQuizMasterNext->pluginHelper->qsm_language_support( $question['hints'], "hint-{$question_id}" ) );
+ QSM_New_Renderer::renderHintToggle( $question_id, $hint_data );
+ }
+
do_action('qsm_after_question', $question);
?>
</div>
@@ -1547,8 +1553,8 @@
'progress_bar' => $this->quiz_options->progress_bar ?? 0,
'contact_info_location' => $this->quiz_options->contact_info_location ?? 0,
'skip_validation_time_expire' => $this->quiz_options->skip_validation_time_expire ?? 0,
- 'timer_limit' => intval( $this->options->timer_limit ?? 0 ),
- 'timer_limit_val' => intval( $this->options->timer_limit ?? 0 ),
+ 'timer_limit' => floatval( $this->options->timer_limit ?? 0 ),
+ 'timer_limit_val' => floatval( $this->options->timer_limit ?? 0 ),
'disable_scroll_next_previous_click' => $this->quiz_options->disable_scroll_next_previous_click ?? 0,
'disable_scroll_on_result' => $this->quiz_options->disable_scroll_on_result ?? 0,
'disable_first_page' => $this->quiz_options->disable_first_page ?? 0,