예제 #1
0
 /**
  * Fetch the answer data and clean it up based on the type of question
  * @param Array $potentialAnswers The potential answers that need checking.
  * @return Array A list of the results.
  */
 function check_quizzes_canWeContinue_extractAnswerData($potentialAnswers)
 {
     $resultsList = array('answer_list' => array(), 'wrong_answer_list' => array(), 'error_answer_list' => array());
     // #### 1 - Extract a list of actual answers from the potential answers. There will
     // be some noise in that data. Check the raw data to see what questions we have.
     foreach ($potentialAnswers as $key => $value) {
         // Only considering answers to questions. Format of answer field is:
         // question_16_truefalse_48 (first ID is quiz, 2nd ID is question, middle string
         // is the question type.
         if (preg_match('/^question_(\\d+)_([a-z]+)_(\\d+)$/', $key, $matches)) {
             $quizID = $matches[1];
             $questionID = $matches[3];
             $questionType = $matches[2];
             // Again, check that answer matches quiz we're expecting.
             // Probably a little paranoid, but it's worth checking
             // to ensure there's nothing naughty going on.
             if ($quizID != $this->unitQuizDetails->quiz_id) {
                 continue;
             }
             // Clean up the submitted data based on the type of quiz using the static checks in each
             // of the questions (to save loading whole class). If the data is valid, add the valid
             // answer to the list of fully validate danswers.
             switch ($questionType) {
                 case 'multi':
                     $resultsList['answer_list'][$questionID] = WPCW_quiz_MultipleChoice::sanitizeAnswerData($value);
                     break;
                 case 'truefalse':
                     $resultsList['answer_list'][$questionID] = WPCW_quiz_TrueFalse::sanitizeAnswerData($value);
                     break;
                 case 'open':
                     $resultsList['answer_list'][$questionID] = WPCW_quiz_OpenEntry::sanitizeAnswerData($value);
                     break;
                     // Ignore uploads as a $_POST field, simply because the files should be stored in $_FILES
                     // not in $_POST. So if we have a file in $_FILES, that's clearly an issue.
                 // Ignore uploads as a $_POST field, simply because the files should be stored in $_FILES
                 // not in $_POST. So if we have a file in $_FILES, that's clearly an issue.
                 case 'upload':
                     break;
             }
         }
         // end of question check
     }
     // end of potential answers loop
     // ### 2 - Check for file uploads if the quiz requires them. Only check for uploads
     // if the quiz details say there should be some uploads.
     if ($this->unitQuizDetails->want_uploads) {
         $uploadResultList = WPCW_quiz_FileUpload::validateFiles($_FILES, $this->unitQuizDetails);
         // Merge the valid results.
         // Basically if a file has been uploaded correctly, that answer is marked as being set.
         if (count($uploadResultList['upload_valid']) > 0) {
             $resultsList['answer_list'] = $resultsList['answer_list'] + $uploadResultList['upload_valid'];
         }
         // Merge the error results
         if (count($uploadResultList['upload_errors']) > 0) {
             $resultsList['error_answer_list'] = $resultsList['error_answer_list'] + $uploadResultList['upload_errors'];
         }
     }
     return $resultsList;
 }
예제 #2
0
/**
 * Having entered some details into the quiz, may the user progress to the next unit? If
 * there are any problems with the quiz, then they are dealt with via AJAX.
 * 
 * @param Object $quizDetails The potential quiz details.
 * @param Array $potentialAnswers The potential answers that need checking. 
 * @param Integer $userID The ID of the user that we're saving progress for.
 * 
 * @return Boolean True if the user may progress, false otherwise.
 */
function WPCW_quizzes_handleQuizRendering_canUserContinueAfterQuiz($quizDetails, $potentialAnswers, $userID)
{
    $resultsList = array('answer_list' => array(), 'wrong_answer_list' => array(), 'error_answer_list' => array());
    $resultDetails = array('correct' => array(), 'wrong' => array());
    // #### 1A Extract a list of actual answers from the potential answers. There will
    // be some noise in that data.
    foreach ($potentialAnswers as $key => $value) {
        // Only considering answers to questions. Format of answer field is:
        // question_16_truefalse_48 (first ID is quiz, 2nd ID is question, middle string
        // is the question type.
        if (preg_match('/^question_(\\d+)_([a-z]+)_(\\d+)$/', $key, $matches)) {
            $quizID = $matches[1];
            $questionID = $matches[3];
            $questionType = $matches[2];
            // Again, check that answer matches quiz we're expecting.
            // Probably a little paranoid, but it's worth checking
            // to ensure there's nothing naughty going on.
            if ($quizID != $quizDetails->quiz_id) {
                continue;
            }
            // Clean up the submitted data based on the type of quiz using the static checks in each
            // of the questions (to save loading whole class). If the data is valid, add the valid
            // answer to the list of fully validate danswers.
            switch ($questionType) {
                case 'multi':
                    $resultsList['answer_list'][$questionID] = WPCW_quiz_MultipleChoice::sanitizeAnswerData($value);
                    break;
                case 'truefalse':
                    $resultsList['answer_list'][$questionID] = WPCW_quiz_TrueFalse::sanitizeAnswerData($value);
                    break;
                case 'open':
                    $resultsList['answer_list'][$questionID] = WPCW_quiz_OpenEntry::sanitizeAnswerData($value);
                    break;
                    // Ignore uploads as a $_POST field, simply because the files should be stored in $_FILES
                    // not in $_POST. So if we have a file in $_FILES, that's clearly an issue.
                // Ignore uploads as a $_POST field, simply because the files should be stored in $_FILES
                // not in $_POST. So if we have a file in $_FILES, that's clearly an issue.
                case 'upload':
                    break;
            }
        }
        // end of question check
    }
    // end of potential answers loop
    // ### 1B Check for file uploads if the quiz requires them. Only check for uploads
    // if the quiz details say there should be some uploads.
    if ($quizDetails->want_uploads) {
        $uploadResultList = WPCW_quiz_FileUpload::validateFiles($_FILES, $quizDetails);
        // Merge the valid results.
        // Basically if a file has been uploaded correctly, that answer is marked as being set.
        if (count($uploadResultList['upload_valid']) > 0) {
            $resultsList['answer_list'] = $resultsList['answer_list'] + $uploadResultList['upload_valid'];
        }
        // Merge the error results
        if (count($uploadResultList['upload_errors']) > 0) {
            $resultsList['error_answer_list'] = $resultsList['error_answer_list'] + $uploadResultList['upload_errors'];
        }
    }
    // ### 2 - Check that we have enough answers given how many questions there are.
    // If there are not enough answers, then re-render the form with the answered questions
    // marked, and highlight the fields that have errors.
    if ($quizDetails->questions && count($resultsList['answer_list']) < count($quizDetails->questions)) {
        // Error - not all questions are answered
        echo WPCW_units_createErrorMessage(__('Please provide an answer for all of the questions.', 'wp_courseware'));
        // Show the form with the questions that have been completed already.
        echo WPCW_quizzes_handleQuizRendering($quizDetails->parent_unit_id, $quizDetails, $resultsList);
        // User may not continue - as quiz is not complete.
        return false;
    }
    // Flag to indicate if grading is needed before the user continues.
    $gradingNeeded = false;
    $gradingNeededBeforeContinue = false;
    // ### 3 - Do we need to check for correct answers?
    if ('survey' == $quizDetails->quiz_type) {
        // Never try to show answers. There aren't any.
        $quizDetails->quiz_show_answers = 'hide_answers';
        // No answers to check. Say thanks
        echo WPCW_units_createSuccessMessage(__('Thank you for your responses. This unit is now complete.', 'wp_courseware'));
    } else {
        $resultDetails = WPCW_quizzes_checkForCorrectAnswers($quizDetails, $resultsList['answer_list']);
        // #### Step A - have open-ended questions that need marking.
        if (!empty($resultDetails['needs_marking'])) {
            $gradingNeeded = true;
            $courseDetails = WPCW_courses_getCourseDetails($quizDetails->parent_course_id);
            // Non-blocking quiz - so allowed to continue, but will be graded later.
            if ('quiz_noblock' == $quizDetails->quiz_type) {
                echo WPCW_units_createSuccessMessage($courseDetails->course_message_quiz_open_grading_non_blocking);
            } else {
                echo WPCW_units_createSuccessMessage($courseDetails->course_message_quiz_open_grading_blocking);
                // Grading is needed before they continue, but don't want them to re-take the quiz.
                $gradingNeededBeforeContinue = true;
            }
        } else {
            // Copy over the wrong answers.
            $resultsList['wrong_answer_list'] = $resultDetails['wrong'];
            // Some statistics
            $correctCount = count($resultDetails['correct']);
            $totalQuestions = count($quizDetails->questions);
            $correctPercentage = number_format($correctCount / $totalQuestions * 100, 1);
            // Non-blocking quiz.
            if ('quiz_noblock' == $quizDetails->quiz_type) {
                // Store user quiz results
                echo WPCW_units_createSuccessMessage(sprintf(__('You got <b>%d out of %d (%d%%)</b> questions correct! This unit is now complete.', 'wp_courseware'), $correctCount, $totalQuestions, $correctPercentage));
                // Notify the user of their grade.
                do_action('wpcw_quiz_graded', $userID, $quizDetails, $correctPercentage, false);
            } else {
                $minPassQuestions = $totalQuestions * ($quizDetails->quiz_pass_mark / 100);
                // They've passed. Report how many they got right.
                if ($correctPercentage >= $quizDetails->quiz_pass_mark) {
                    echo WPCW_units_createSuccessMessage(sprintf(__('You got <b>%d out of %d (%d%%)</b> questions correct! This unit is now complete.', 'wp_courseware'), $correctCount, $totalQuestions, $correctPercentage));
                    // Notify the user of their grade.
                    do_action('wpcw_quiz_graded', $userID, $quizDetails, $correctPercentage, false);
                } else {
                    echo WPCW_units_createErrorMessage(sprintf(__('Unfortunately, you only got <b>%d out of %d (%d%%)</b> questions correct. You need to correctly answer <b>at least %d questions (%d%%)</b>.', 'wp_courseware'), $correctCount, $totalQuestions, $correctPercentage, $minPassQuestions, $quizDetails->quiz_pass_mark));
                    // Show form with error answers.
                    echo WPCW_quizzes_handleQuizRendering($quizDetails->parent_unit_id, $quizDetails, $resultsList);
                    // Errors, so the user cannot progress yet.
                    return false;
                }
            }
            // end of blocking quiz check
        }
    }
    // end of survey check
    // ### 4 - Show the correct answers to the user?
    if ('show_answers' == $quizDetails->quiz_show_answers) {
        echo WPCW_quizzes_showAllCorrectAnswers($quizDetails);
    }
    // ### 5 - Save the user progress
    WPCW_quizzes_saveUserProgress($userID, $quizDetails, $resultDetails, $resultsList['answer_list']);
    // Questions need grading, notify the admin
    if ($gradingNeeded) {
        // Notify the admin that questions need answering.
        do_action('wpcw_quiz_needs_grading', $userID, $quizDetails);
    }
    // Questions need grading, so don't allow user to continue
    if ($gradingNeededBeforeContinue) {
        return false;
    }
    // If we get this far, the user may progress to next unit
    return true;
}
예제 #3
0
/**
 * Function that allows a question to be edited.
 */
function WPCW_showPage_ModifyQuestion_load()
{
    $page = new PageBuilder(true);
    $page->showPageHeader(__('Edit Single Question', 'wp_courseware'), '70%', WPCW_icon_getPageIconURL());
    $questionID = false;
    // Check POST and GET
    if (isset($_GET['question_id'])) {
        $questionID = $_GET['question_id'] + 0;
    } else {
        if (isset($_POST['question_id'])) {
            $questionID = $_POST['question_id'] + 0;
        }
    }
    // Trying to edit a question
    $questionDetails = WPCW_questions_getQuestionDetails($questionID, true);
    // Abort if question not found.
    if (!$questionDetails) {
        $page->showMessage(__('Sorry, but that question could not be found.', 'wp_courseware'), true);
        $page->showPageFooter();
        return;
    }
    // See if the question has been submitted for saving.
    if ('true' == WPCW_arrays_getValue($_POST, 'question_save_mode')) {
        WPCW_handler_questions_processSave(false, true);
        $page->showMessage(__('Question successfully updated.', 'wp_courseware'));
        // Assume save has happened, so reload the settings.
        $questionDetails = WPCW_questions_getQuestionDetails($questionID, true);
    }
    // Manually set the order to zero, as not needed for ordering in this context.
    $questionDetails->question_order = 0;
    switch ($questionDetails->question_type) {
        case 'multi':
            $quizObj = new WPCW_quiz_MultipleChoice($questionDetails);
            break;
        case 'truefalse':
            $quizObj = new WPCW_quiz_TrueFalse($questionDetails);
            break;
        case 'open':
            $quizObj = new WPCW_quiz_OpenEntry($questionDetails);
            break;
        case 'upload':
            $quizObj = new WPCW_quiz_FileUpload($questionDetails);
            break;
        default:
            die(__('Unknown quiz type: ', 'wp_courseware') . $questionDetails->question_type);
            break;
    }
    $quizObj->showErrors = true;
    $quizObj->needCorrectAnswers = true;
    $quizObj->hideDragActions = true;
    // #wpcw_quiz_details_questions = needed for media uploader
    // .wpcw_question_holder_static = needed for wrapping the question using existing HTML.
    printf('<div id="wpcw_quiz_details_questions"><ul class="wpcw_question_holder_static">');
    // Create form wrapper, so that we can save this question.
    printf('<form method="POST" action="%s?page=WPCW_showPage_ModifyQuestion&question_id=%d" />', admin_url('admin.php'), $questionDetails->question_id);
    // Question hidden fields
    printf('<input name="question_id" type="hidden" value="%d" />', $questionDetails->question_id);
    printf('<input name="question_save_mode" type="hidden" value="true" />');
    // Show the quiz so that it can be edited. We're re-using the code we have for editing questions,
    // to save creating any special form edit code.
    echo $quizObj->editForm_toString();
    // Save and return buttons.
    printf('<div class="wpcw_button_group"><br/>');
    printf('<a href="%s?page=WPCW_showPage_QuestionPool" class="button-secondary">%s</a>&nbsp;&nbsp;', admin_url('admin.php'), __('&laquo; Return to Question Pool', 'wp_courseware'));
    printf('<input type="submit" class="button-primary" value="%s" />', __('Save Question Details', 'wp_courseware'));
    printf('</div>');
    printf('</form>');
    printf('</ul></div>');
    $page->showPageFooter();
}
예제 #4
0
/**
 * Show the forms where the quiz answers can be editied.
 * 
 * @param Integer $quizID the ID of the quiz to be edited.
 * @param Object $page The associated page object for showing messages.
 */
function WPCW_showPage_ModifyQuiz_showQuizEntryForms($quizID, $page)
{
    // Handle the saving of quiz questions
    WPCW_showPage_ModifyQuiz_showQuizEntryForms_processSave($quizID, $page);
    global $wpdb, $wpcwdb;
    $wpdb->show_errors();
    // Work out if we need correct answers or not. And what the pass mark is.
    $quizDetails = WPCW_quizzes_getQuizDetails($quizID, true);
    $needCorrectAnswers = 'survey' != $quizDetails->quiz_type;
    // Got URL in action to ensure we go to top of page to see the success message.
    printf('<form method="post" id="wpcw_question_update_form" action="%s&quiz_id=%d">', admin_url('admin.php?page=WPCW_showPage_ModifyQuiz'), $quizID);
    // Show the existing quiz questions as a series of forms.
    $quizItems = WPCW_quizzes_getListOfQuestions($quizID);
    // Show the number of correct answers the user must get in order to pass.
    if ('quiz_block' == $quizDetails->quiz_type) {
        $totalQs = count($quizItems);
        $passQs = ceil($quizDetails->quiz_pass_mark / 100 * $totalQs);
        printf('<div class="wpcw_msg wpcw_msg_info">');
        printf(__('The trainee will be required to correctly answer at least <b>%d of the %d</b> following questions (<b>at least %d%%</b>) to progress.', 'wp_courseware'), $passQs, $totalQs, $quizDetails->quiz_pass_mark);
        printf('</div>');
    }
    // Got a  quiz, and trainer is requiring to show answers. Tell them we can't show answers
    // as this quiz contains open-ended questions that need grading.
    if ($needCorrectAnswers && 'show_answers' == $quizDetails->quiz_show_answers && WPCW_quizzes_containsQuestionsNeedingManualGrading($quizItems)) {
        printf('<div class="wpcw_msg wpcw_msg_error">');
        printf(__('This quiz contains questions that need <b>manual grading</b>, and you\'ve selected \'<b>Show Answers</b>\' when the user completes this quiz. ', 'wp_courseware') . '<br/><br/>' . __('Since answers cannot be shown to the user because they are not known at that stage, <b>answers cannot be shown</b>. To hide this message, select \'<b>No Answers</b>\' above.'));
        printf('</div>');
    }
    $errorCount = 0;
    global $errorCount;
    // Wrapper for questions
    printf('<ol class="wpcw_dragable_question_holder">');
    if ($quizItems) {
        // Render edit form for each of the quizzes that already exist
        foreach ($quizItems as $quizItem) {
            switch ($quizItem->question_type) {
                case 'multi':
                    $quizObj = new WPCW_quiz_MultipleChoice($quizItem);
                    break;
                case 'truefalse':
                    $quizObj = new WPCW_quiz_TrueFalse($quizItem);
                    break;
                case 'open':
                    $quizObj = new WPCW_quiz_OpenEntry($quizItem);
                    break;
                case 'upload':
                    $quizObj = new WPCW_quiz_FileUpload($quizItem);
                    break;
                default:
                    die(__('Unknown quiz type: ', 'wp_courseware') . $quizItem->question_type);
                    break;
            }
            $quizObj->showErrors = true;
            $quizObj->needCorrectAnswers = $needCorrectAnswers;
            // Keep track of errors
            if ($quizObj && $quizObj->gotError) {
                $errorCount++;
            }
            echo $quizObj->editForm_toString();
        }
    }
    printf('</ol>');
    // Do any of the questions have residual errors? Tell the user.
    if ($errorCount > 0) {
        $page->showMessage(sprintf(__('%d of the questions below have errors. Please make corrections and then save the changes.', 'wp_courseware'), $errorCount), true);
    }
    $page->showPageMiddle('35%');
    // Show the menu for saving and adding new items.
    WPCW_showPage_ModifyQuiz_FloatMenu($page);
    // Flag to indicate that questions have been updated.
    printf('<input type="hidden" name="survey_updated" value="survey_updated" />');
    printf('</form><a name="new_question"></a>');
    // The empty forms for adding a new question
    $quizItemDummy = new stdClass();
    $quizItemDummy->question_question = '';
    $quizItemDummy->question_answers = "\n\n\n";
    $quizItemDummy->question_correct_answer = false;
    $quizItemDummy->question_order = 0;
    $quizItemDummy->question_answer_type = false;
    $quizItemDummy->question_answer_hint = false;
    $quizItemDummy->question_answer_file_types = 'doc, pdf, jpg, png, jpeg, gif';
    $quizFormsToCreate = array('new_multi' => 'WPCW_quiz_MultipleChoice', 'new_tf' => 'WPCW_quiz_TrueFalse', 'new_open' => 'WPCW_quiz_OpenEntry', 'new_upload' => 'WPCW_quiz_FileUpload');
    // Create the dummy quiz objects
    foreach ($quizFormsToCreate as $dummyid => $objClass) {
        // Set placeholder class
        $quizItemDummy->question_id = $dummyid;
        // Create new object and set it up with defaults
        $quizObj = new $objClass($quizItemDummy);
        $quizObj->cssClasses = 'wpcw_question_template';
        $quizObj->showErrors = false;
        $quizObj->needCorrectAnswers = $needCorrectAnswers;
        echo $quizObj->editForm_toString();
    }
}