Esempio n. 1
0
/**
 * Function called when adding a question to a quiz from the thickbox.
 */
function WPCW_AJAX_handleThickboxAction_QuestionPool_addQuestion()
{
    $questionID = WPCW_arrays_getValue($_POST, 'questionnum');
    $questionDetails = WPCW_questions_getQuestionDetails($questionID);
    // Convert the question to HTML based on type.
    if ($questionDetails) {
        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;
            case 'random_selection':
                $quizObj = new WPCW_quiz_RandomSelection($questionDetails);
                break;
            default:
                die(__('Unknown quiz type: ', 'wp_courseware') . $questionDetails->question_type);
                break;
        }
        $quizObj->showErrors = true;
        $quizObj->needCorrectAnswers = true;
        echo $quizObj->editForm_toString();
    }
    die;
}
/**
 * Show the forms where the quiz answers can be edited.
 * 
 * @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_showQuestionEntryForms($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, false, false);
    $needCorrectAnswers = 'survey' != $quizDetails->quiz_type;
    // 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 = WPCW_quizzes_calculateActualQuestionCount($quizID);
        $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.', 'wp_courseware'));
        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;
                case 'random_selection':
                    $quizObj = new WPCW_quiz_RandomSelection($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('<a name="new_question"></a>');
    // The empty forms for adding a new question
    $quizItemDummy = new stdClass();
    $quizItemDummy->question_question = '';
    $quizItemDummy->question_correct_answer = false;
    $quizItemDummy->question_order = 0;
    $quizItemDummy->question_answer_type = false;
    $quizItemDummy->question_answer_hint = false;
    $quizItemDummy->question_answer_explanation = false;
    $quizItemDummy->question_answer_file_types = 'doc, pdf, jpg, png, jpeg, gif';
    $quizItemDummy->question_image = false;
    $quizItemDummy->question_usage_count = 0;
    $quizItemDummy->question_multi_random_enable = 0;
    $quizItemDummy->question_multi_random_count = 5;
    // Create some dummy answers.
    $quizItemDummy->question_data_answers = serialize(array(1 => array('answer' => ''), 2 => array('answer' => ''), 3 => array('answer' => '')));
    $quizFormsToCreate = array('new_multi' => 'WPCW_quiz_MultipleChoice', 'new_tf' => 'WPCW_quiz_TrueFalse', 'new_open' => 'WPCW_quiz_OpenEntry', 'new_upload' => 'WPCW_quiz_FileUpload', 'new_random_selection' => 'WPCW_quiz_RandomSelection');
    // 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;
        $quizObj->editForm_questionNotSavedYet = true;
        echo $quizObj->editForm_toString();
    }
}
Esempio n. 3
0
 /**
  * Handle exporting the XML for a quiz within a single unit.
  * 
  * @param Object $unitObj The unit with a quiz to export.
  * @param String $unitParentPath The parent path for the unit.
  * 
  * @param String The XML for the quiz in this unit.
  */
 function export_content_handleQuizzes($unitObj, $unitParentPath)
 {
     $xml = false;
     global $fieldsToProcess_quizzes, $fieldsToProcess_quiz_questions;
     // Show the quiz for this unit here... (if there are any).
     if (isset($unitObj->extradata_quiz_details) && !empty($unitObj->extradata_quiz_details)) {
         $quizObj = $unitObj->extradata_quiz_details;
         // ###ÊQuizzes - Start
         // Expecting just 1 quiz per unit, no more.
         $quizzesParentPath = $unitParentPath . '/quizzes';
         $xml .= $this->export_startBlock($quizzesParentPath, 'quizzes');
         $quizParentPath = $quizzesParentPath . '/quiz';
         // ###ÊQuiz - Start
         $xml .= $this->export_objectToXML('quiz', false, $quizObj, $fieldsToProcess_quizzes, $quizParentPath, false);
         // ### Quiz Detail - show_answers_settings (serialized data)
         $quizObj->show_answers_settings = maybe_unserialize($quizObj->show_answers_settings);
         if (!empty($quizObj->show_answers_settings) && is_array($quizObj->show_answers_settings)) {
             $innerPath = $quizParentPath . '/show_answers_settings';
             $xml .= $this->export_arrayToXML('show_answers_settings', false, $quizObj->show_answers_settings, false, $innerPath, '/show_answers_settings');
         }
         // ### Quiz Detail - quiz_paginate_questions_settings (serialized data)
         $quizObj->quiz_paginate_questions_settings = maybe_unserialize($quizObj->quiz_paginate_questions_settings);
         if (!empty($quizObj->quiz_paginate_questions_settings) && is_array($quizObj->quiz_paginate_questions_settings)) {
             $innerPath = $quizParentPath . '/quiz_paginate_questions_settings';
             $xml .= $this->export_arrayToXML('quiz_paginate_questions_settings', false, $quizObj->quiz_paginate_questions_settings, false, $innerPath, '/quiz_paginate_questions_settings');
         }
         // ### Questions
         $questionsParentPath = $quizParentPath . '/questions';
         $xml .= $this->export_startBlock($questionsParentPath, 'questions');
         $questionParentPath = $questionsParentPath . '/question';
         $tagSelectionsPath = $questionParentPath . '/tag_selections/';
         $tagSelectionPath = $tagSelectionsPath . '/tag_selection';
         if (!empty($quizObj->questions)) {
             $questionOrder = 1;
             foreach ($quizObj->questions as $singleQuestion) {
                 if ('random_selection' == $singleQuestion->question_type) {
                     // Get the hash and the order of the question
                     $questionDetailsByHash = array('question_type' => 'random_selection', 'question_order' => $questionOrder++);
                     // Render the details as a single question.
                     $xml .= $this->export_arrayToXML('question', false, $questionDetailsByHash, false, $questionsParentPath . '/', false);
                     // ### Start tag selections
                     $xml .= $this->export_startBlock($questionParentPath . '/', 'tag_selections');
                     // Decode the tags and add them
                     $decodedTags = WPCW_quiz_RandomSelection::decodeTagSelection($singleQuestion->question_question);
                     if (!empty($decodedTags)) {
                         foreach ($decodedTags as $tagType => $tagDetails) {
                             // Whole pool - use this as a single string.
                             if ('whole_pool' == $tagType) {
                                 $xml .= $this->export_textDataWithAttributes('tag_selection', 'whole_pool', $tagSelectionPath, array('count' => $tagDetails['count']));
                             } else {
                                 $xml .= $this->export_textDataWithAttributes('tag_selection', $tagDetails['name'], $tagSelectionPath, array('count' => $tagDetails['count']));
                             }
                         }
                     }
                     // ### End tag selections
                     $xml .= $this->export_endBlock($questionParentPath . '/', 'tag_selections');
                     $xml .= $this->export_endBlock($questionsParentPath . '/', 'question');
                 } else {
                     if (isset($this->questionList[$singleQuestion->question_id])) {
                         // Get the hash details for the question so we can use the hash rather than quiz details.
                         $storedQuestionDetails = $this->questionList[$singleQuestion->question_id];
                         // Get the hash and the order of the question
                         $questionDetailsByHash = array('question_type' => 'fixed', 'question_hash' => $storedQuestionDetails->question_hash, 'question_order' => $questionOrder++);
                         // Render the details as a single question.
                         $xml .= $this->export_arrayToXML('question', false, $questionDetailsByHash, false, $questionParentPath, '/question');
                     } else {
                         error_log(__('Error exporting question in quiz.', 'wp_courseware') . '(' . print_r($singleQuestion, true) . ')');
                     }
                 }
                 // end question type
             }
             // end foreach
         }
         // ###ÊQuestions
         // End the 'Quizzes' wrapper
         $xml .= $this->export_endBlock($questionsParentPath, 'questions');
         $xml .= $this->export_content_handleQuizzes_customFeedbackMessages($quizObj, $quizParentPath);
         // ### Quiz - End
         $xml .= $this->export_endBlock($quizParentPath, 'quiz');
         // ###ÊQuizzes - End
         $xml .= $this->export_endBlock($quizzesParentPath, 'quizzes');
         //error_log(print_r($quizObj, true));
     }
     // end of check for quiz data.
     return $xml;
 }
Esempio n. 4
0
/**
 * Does this quiz contain any random questions.
 * 
 * @param unknown_type $quizDetails The quiz details to check.
 * @return Boolean True if there are random questions, false otherwise.
 */
function WPCW_quizzes_randomQuestions_fullyExpand($quizDetails)
{
    if (empty($quizDetails->questions)) {
        return $quizDetails->questions;
    }
    // Just need the first question to confirm if this is the case.
    $newQuizList = array();
    foreach ($quizDetails->questions as $questionID => $singleQuestion) {
        // Got a random selection, so we need to get all question variations.
        if ('random_selection' == $singleQuestion->question_type) {
            // Need tags for this question
            $tagDetails = WPCW_quiz_RandomSelection::decodeTagSelection($singleQuestion->question_question);
            // Ignore limits, just get all questions
            $expandedQuestions = WPCW_quiz_RandomSelection::questionSelection_getRandomQuestionsFromTags($tagDetails);
            if (!empty($expandedQuestions)) {
                $newQuizList += $expandedQuestions;
            }
        } else {
            $newQuizList[$questionID] = $singleQuestion;
        }
    }
    return $newQuizList;
}
Esempio n. 5
0
/**
 * Function to get all of the quiz details.
 * 
 * @param Integer $quizID The ID of the quiz for which we want to get details.
 * @param Boolean $getQuestionsToo If true, get the questions for the quiz too. 
 * @param Boolean $resolveRandomQuestions If true, convert any randomised questions to live questions.
 * @param Integer $userID The ID of the user to resolve the questions to.
 * 
 * @return Object The details of the quiz as an object.
 */
function WPCW_quizzes_getQuizDetails($quizID, $getQuestionsToo = false, $resolveRandomQuestions = false, $userID)
{
    if (!$quizID) {
        return false;
    }
    global $wpcwdb, $wpdb;
    $wpdb->show_errors();
    $SQL = $wpdb->prepare("SELECT * \n\t\t\tFROM {$wpcwdb->quiz}\t\n\t\t\tWHERE quiz_id = %d \n\t\t\t", $quizID);
    $quizObj = $wpdb->get_row($SQL);
    // Nothing found
    if (!$quizObj) {
        return false;
    }
    // Add flag indicating if random questions are resolved or not.
    $quizObj->resolved_random_questions = $resolveRandomQuestions;
    if ($getQuestionsToo) {
        // Something found, so return the questions for this quiz too.
        $quizObj->questions = WPCW_quizzes_getListOfQuestions($quizObj->quiz_id);
        // Check if we need to expand any random questions
        if ($resolveRandomQuestions && $userID > 0 && !empty($quizObj->questions)) {
            $questionListToRender = array();
            foreach ($quizObj->questions as $question) {
                switch ($question->question_type) {
                    // Got a random selection - extract these questions
                    case 'random_selection':
                        $quObj = new WPCW_quiz_RandomSelection($question);
                        $randList = $quObj->questionSelection_getLockedQuestionSelection($userID, $quizObj->parent_unit_id);
                        // Append the random questions.
                        if (!empty($randList)) {
                            $questionListToRender += $randList;
                        }
                        break;
                        // Got a standard question
                    // Got a standard question
                    case 'multi':
                    case 'open':
                    case 'upload':
                    case 'truefalse':
                        $questionListToRender[$question->question_id] = $question;
                        break;
                        // Not expecting anything here... so not handling the error case.
                    // Not expecting anything here... so not handling the error case.
                    default:
                        die(__('Unexpected question type, aborting.', 'wp_courseware'));
                        break;
                }
                // end switch
            }
            // end foreach
            // Overwrite existing questions
            $quizObj->questions = $questionListToRender;
        }
        // end if we want to expand random questions
    }
    // Simple flag to see if we have open questions or not.
    $quizObj->has_open_questions = false;
    // Are we expecting any uploads? If so, set a flag to make answer processing faster.
    $quizObj->want_uploads = false;
    if (!empty($quizObj->questions)) {
        foreach ($quizObj->questions as $quizID => $quizItem) {
            // We're searching for an upload anyway, so check for an open question
            if ('upload' == $quizItem->question_type) {
                $quizObj->want_uploads = true;
                $quizObj->has_open_questions = true;
                break;
            }
        }
        // Not found an open question yet even though we checked for uploads.
        if (!$quizObj->has_open_questions) {
            foreach ($quizObj->questions as $quizID => $quizItem) {
                // Look for an open question (already checked uploads). This s
                // saves some computation time.
                if ('open' == $quizItem->question_type) {
                    $quizObj->has_open_questions = true;
                    break;
                }
            }
        }
    }
    return $quizObj;
}
 /**
  * Given a user ID, get the selection of questions for this random question selection. This
  * will lock the selection to the user if the selection has not been locked already.
  * 
  * @param Integer $userID The ID of the user to get the selection of questions for.
  * @param Integer $parentUnitID The ID of the parent unit to show this random question on.
  * 
  * @return Array The list of questions that have been locked.
  */
 public function questionSelection_getLockedQuestionSelection($userID, $parentUnitID)
 {
     global $wpcwdb, $wpdb;
     $wpdb->show_errors();
     // #### 1 - See if the question is locked for a user? If so, return locked selection.
     $lockedSelection = $wpdb->get_var($wpdb->prepare("\n\t\t\t\tSELECT question_selection_list \n\t\t\t\tFROM {$wpcwdb->question_rand_lock}\n\t\t\t\tWHERE question_user_id = %d\t\t\t\t  \n\t\t\t\t  AND rand_question_id = %d\n\t\t\t\t  AND parent_unit_id   = %d \n\t\t\t", $userID, $this->quizItem->question_id, $parentUnitID));
     // #### 2 - Got a selection, turn this into question IDs.
     if ($lockedSelection) {
         // Quickly validate that it's just numbers and commas, then use directly in SQL
         // for an array search of question IDs that match the selection.  If the string
         // does not validate as a string of numbers and commas, then we'll generate a new
         // selection using the code below.
         if (preg_match('/^([0-9]+,?)+$/', $lockedSelection)) {
             $questionsToUseWithIDKey = array();
             // ORDER BY FIELD is a function that will sort the questions in the order they appear in
             // the comma-separated list.
             $questionsToUse = $wpdb->get_results("\n\t\t\t\t\tSELECT *\n\t\t\t\t\tFROM {$wpcwdb->quiz_qs}\n\t\t\t\t\tWHERE question_id IN ({$lockedSelection})\n\t\t\t\t\t  AND question_type != 'random_selection'\n\t\t\t\t\tORDER BY FIELD(question_id, {$lockedSelection})\n\t\t\t\t");
             if (!empty($questionsToUse)) {
                 // Need to remap to use ID => question
                 foreach ($questionsToUse as $questionsToUse_single) {
                     $questionsToUseWithIDKey[$questionsToUse_single->question_id] = $questionsToUse_single;
                 }
             }
             return $questionsToUseWithIDKey;
         }
     }
     // #### 3 - Selection is not locked, so we generate a new selection for the user and then lock/save it to the database.
     $tagDetails = WPCW_quiz_RandomSelection::decodeTagSelection($this->quizItem->question_question);
     $questionsToUse = WPCW_quiz_RandomSelection::questionSelection_getRandomQuestionsFromTags($tagDetails);
     if (!empty($questionsToUse)) {
         // Need to get the IDs of this question to lock it. Don't need any other data.
         $questionIDList = array();
         foreach ($questionsToUse as $questionDetails) {
             $questionIDList[] = $questionDetails->question_id;
         }
         // Now insert this list into the database to create the lock. Handling
         // duplicates elegantly here by using REPLACE INTO intentionally
         $wpdb->query($wpdb->prepare("\n\t\t\t\tREPLACE INTO {$wpcwdb->question_rand_lock}\n\t\t\t\t(question_user_id, rand_question_id, question_selection_list, parent_unit_id) \n\t\t\t\tVALUES (%d, %d, %s, %d)\n\t\t\t", $userID, $this->quizItem->question_id, implode(',', $questionIDList), $parentUnitID));
     }
     return $questionsToUse;
 }
/**
 * Handle saving questions to the database.
 * 
 * @param Integer $quizID The quiz for which the questions apply to. 
 * @param Boolean $singleQuestionMode If true, then we're updating a single question, and we do things slightly differently.
 */
function WPCW_handler_questions_processSave($quizID, $singleQuestionMode = false)
{
    global $wpdb, $wpcwdb;
    $wpdb->show_errors();
    $questionsToSave = array();
    $questionsToSave_New = array();
    // Check $_POST data for the
    foreach ($_POST as $key => $value) {
        // #### 1 - Check if we're deleting a question from this quiz
        // We're not just deleting the question, just the association. This is because questions remain in the
        // pool now.
        if (preg_match('/^delete_wpcw_quiz_details_([0-9]+)$/', $key, $matches)) {
            // Remove mapping from the mapping table.
            $SQL = $wpdb->prepare("\n\t\t\t\tDELETE FROM {$wpcwdb->quiz_qs_mapping}\n\t\t\t\tWHERE question_id = %d\n\t\t\t\t  AND parent_quiz_id = %d\n\t\t\t", $matches[1], $quizID);
            $wpdb->query($SQL);
            // Update usage counts
            WPCW_questions_updateUsageCount($matches[1]);
            // Just a deletion - move on to next array item to save processing time.
            continue;
        }
        // #### 2 - See if we have a question to check for.
        if (preg_match('/^question_question_(new_question_)?([0-9]+)$/', $key, $matches)) {
            // Got the ID of the question, now get answers and correct answer.
            $questionID = $matches[2];
            // Store the extra string if we're adding a new question.
            $newQuestionPrefix = $matches[1];
            $fieldName_Answers = 'question_answer_' . $newQuestionPrefix . $questionID;
            $fieldName_Answers_Img = 'question_answer_image_' . $newQuestionPrefix . $questionID;
            $fieldName_Correct = 'question_answer_sel_' . $newQuestionPrefix . $questionID;
            $fieldName_Type = 'question_type_' . $newQuestionPrefix . $questionID;
            $fieldName_Order = 'question_order_' . $newQuestionPrefix . $questionID;
            $fieldName_AnswerType = 'question_answer_type_' . $newQuestionPrefix . $questionID;
            $fieldName_AnswerHint = 'question_answer_hint_' . $newQuestionPrefix . $questionID;
            $fieldName_Explanation = 'question_answer_explanation_' . $newQuestionPrefix . $questionID;
            $fieldName_Image = 'question_image_' . $newQuestionPrefix . $questionID;
            // For Multi-Choice - Answer randomization
            $fieldName_Multi_Random_Enable = 'question_multi_random_enable_' . $newQuestionPrefix . $questionID;
            $fieldName_Multi_Random_Count = 'question_multi_random_count_' . $newQuestionPrefix . $questionID;
            // Order should be a number
            $questionOrder = 0;
            if (isset($_POST[$fieldName_Order])) {
                $questionOrder = $_POST[$fieldName_Order] + 0;
            }
            // Default types
            $qAns = false;
            $qAnsCor = false;
            $qAnsType = false;
            // Just used for open question types.
            $qAnsFileTypes = false;
            // Just used for upload file types.
            // Get the hint - Just used for open and upload types. Allow HTML.
            $qAnsHint = trim(WPCW_arrays_getValue($_POST, $fieldName_AnswerHint));
            // Get the explanation - All questions. Allow HTML.
            $qAnsExplain = trim(WPCW_arrays_getValue($_POST, $fieldName_Explanation));
            // The image URL to use. No HTML. Table record is 300 chars, hence cropping.
            $qQuesImage = trim(substr(strip_tags(WPCW_arrays_getValue($_POST, $fieldName_Image)), 0, 300));
            // How many questions are there is this selection? 1 by default for non-random questions.
            $expandedQuestionCount = 1;
            // For Multi-Choice - Answer randomization
            $qMultiRandomEnable = false;
            $qMultiRandomCount = 5;
            // What type of question do we have?
            $questionType = WPCW_arrays_getValue($_POST, $fieldName_Type);
            switch ($questionType) {
                case 'multi':
                    $qAns = WPCW_quiz_MultipleChoice::editSave_extractAnswerList($fieldName_Answers, $fieldName_Answers_Img);
                    $qAnsCor = WPCW_quiz_MultipleChoice::editSave_extractCorrectAnswer($qAns, $fieldName_Correct);
                    // Provide the UI with at least once slot for an answer.
                    if (!$qAns) {
                        $qAns = array('1' => array('answer' => ''), '2' => array('answer' => ''));
                    }
                    // Check randomization values (boolean will be 'on' to enable, as it's a checkbox)
                    $qMultiRandomEnable = 'on' == WPCW_arrays_getValue($_POST, $fieldName_Multi_Random_Enable);
                    $qMultiRandomCount = intval(WPCW_arrays_getValue($_POST, $fieldName_Multi_Random_Count));
                    break;
                case 'open':
                    // See if there's a question type that's been sent back to the server.
                    $answerTypes = WPCW_quiz_OpenEntry::getValidAnswerTypes();
                    $thisAnswerType = WPCW_arrays_getValue($_POST, $fieldName_AnswerType);
                    // Validate the answer type is in the list. Don't create a default so that user must choose.
                    if (isset($answerTypes[$thisAnswerType])) {
                        $qAnsType = $thisAnswerType;
                    }
                    // There's no correct answer for an open question.
                    $qAnsCor = false;
                    break;
                case 'upload':
                    $fieldName_FileType = 'question_answer_file_types_' . $newQuestionPrefix . $questionID;
                    // Check new file extension types, parsing them.
                    $qAnsFileTypesRaw = WPCW_files_cleanFileExtensionList(WPCW_arrays_getValue($_POST, $fieldName_FileType));
                    $qAnsFileTypes = implode(',', $qAnsFileTypesRaw);
                    break;
                case 'truefalse':
                    $qAnsCor = WPCW_quiz_TrueFalse::editSave_extractCorrectAnswer($fieldName_Correct);
                    break;
                    // Validate the the JSON data here... ensure all the tags are valid (not worried about the counts).
                    // Then save back to database.
                // Validate the the JSON data here... ensure all the tags are valid (not worried about the counts).
                // Then save back to database.
                case 'random_selection':
                    // Reset to zero for counting below.
                    $expandedQuestionCount = 0;
                    $decodedTags = WPCW_quiz_RandomSelection::decodeTagSelection(stripslashes($value));
                    // Capture just ID and count and resave back to database.
                    $toSaveList = false;
                    if (!empty($decodedTags)) {
                        $toSaveList = array();
                        foreach ($decodedTags as $decodedKey => $decodedDetails) {
                            $toSaveList[$decodedKey] = $decodedDetails['count'];
                            // Track requested questions
                            $expandedQuestionCount += $decodedDetails['count'];
                        }
                    }
                    // Overwrite $value to use cleaned question
                    $value = json_encode($toSaveList);
                    break;
                    // Not expecting anything here... so not handling the error case.
                // Not expecting anything here... so not handling the error case.
                default:
                    break;
            }
            // ### 4a - Encode the answer data
            $encodedqAns = $qAns;
            if (!empty($qAns)) {
                foreach ($encodedqAns as $idx => $data) {
                    $encodedqAns[$idx]['answer'] = base64_encode($data['answer']);
                }
            }
            // ### 4b - Save new question data as a list ready for saving to the database.
            $quDataToSave = array('question_answers' => false, 'question_question' => stripslashes($value), 'question_data_answers' => serialize($encodedqAns), 'question_correct_answer' => $qAnsCor, 'question_type' => $questionType, 'question_order' => $questionOrder, 'question_answer_type' => $qAnsType, 'question_answer_hint' => stripslashes($qAnsHint), 'question_answer_explanation' => stripslashes($qAnsExplain), 'question_answer_file_types' => $qAnsFileTypes, 'question_image' => $qQuesImage, 'question_expanded_count' => $expandedQuestionCount, 'question_multi_random_enable' => $qMultiRandomEnable, 'question_multi_random_count' => $qMultiRandomCount, 'taglist' => array());
            // ### 5 - Check if there are any tags to save. Only happens for questions that
            // haven't been saved, so that we can save when we do a $_POST save.
            $tagFieldForNewQuestions = 'tags_to_add_' . $newQuestionPrefix . $questionID;
            if (isset($_POST[$tagFieldForNewQuestions])) {
                if (!empty($_POST[$tagFieldForNewQuestions])) {
                    // Validate each tag ID we have, add to list to be stored for this question later.
                    foreach ($_POST[$tagFieldForNewQuestions] as $idx => $tagText) {
                        $tagText = trim(stripslashes($tagText));
                        if ($tagText) {
                            $quDataToSave['taglist'][] = $tagText;
                        }
                    }
                }
            }
            // Not a new question - so not got question ID as yet
            if ($newQuestionPrefix) {
                $questionsToSave_New[] = $quDataToSave;
            } else {
                $quDataToSave['question_id'] = $questionID;
                $questionsToSave[$questionID] = $quDataToSave;
            }
        }
        // end if question found.
    }
    // Only need to adjust quiz settings when editing a quiz and not a single question.
    if (!$singleQuestionMode) {
        // #### 6 - Remove association of all questions for this quiz
        //          as we're going to re-add them.
        $wpdb->query($wpdb->prepare("\n\t\t\t\t\tDELETE FROM {$wpcwdb->quiz_qs_mapping}\n\t\t\t\t\tWHERE parent_quiz_id = %d\n\t\t\t\t", $quizID));
    }
    // #### 7 - Check we have existing questions to save
    if (count($questionsToSave)) {
        // Now save all data back to the database.
        foreach ($questionsToSave as $questionID => $questionDetails) {
            // Extract the question order, as can't save order with question in DB
            $questionOrder = $questionDetails['question_order'];
            unset($questionDetails['question_order']);
            // Tag list only used for new questions, so remove this field
            unset($questionDetails['taglist']);
            // Save question details back to database.
            $wpdb->query(arrayToSQLUpdate($wpcwdb->quiz_qs, $questionDetails, 'question_id'));
            // No need to update counts/associations when editing a single lone question
            if (!$singleQuestionMode) {
                // Create the association for this quiz/question.
                $wpdb->query($wpdb->prepare("\n\t\t\t\t\tINSERT INTO {$wpcwdb->quiz_qs_mapping} \n\t\t\t\t\t(question_id, parent_quiz_id, question_order)\n\t\t\t\t\tVALUES (%d, %d, %d)\n\t\t\t\t", $questionID, $quizID, $questionOrder));
                // Update usage count for question.
                WPCW_questions_updateUsageCount($questionID);
            }
        }
    }
    // #### 8 - Save the new questions we have
    if (count($questionsToSave_New)) {
        // Now save all data back to the database.
        foreach ($questionsToSave_New as $questionDetails) {
            // Extract the question order, as can't save order with question in DB
            $questionOrder = $questionDetails['question_order'];
            unset($questionDetails['question_order']);
            // Extract the tags added for this question - we'll save manually.
            $tagsToAddList = $questionDetails['taglist'];
            unset($questionDetails['taglist']);
            // Create question in database
            $wpdb->query(arrayToSQLInsert($wpcwdb->quiz_qs, $questionDetails));
            $newQuestionID = $wpdb->insert_id;
            // No need to update counts/associations when editing a single lone question
            if (!$singleQuestionMode) {
                // Create the association for this quiz/question.
                $wpdb->query($wpdb->prepare("\n\t\t\t\t\tINSERT INTO {$wpcwdb->quiz_qs_mapping} \n\t\t\t\t\t(question_id, parent_quiz_id, question_order)\n\t\t\t\t\tVALUES (%d, %d, %d)\n\t\t\t\t", $newQuestionID, $quizID, $questionOrder));
                // Update usage
                WPCW_questions_updateUsageCount($newQuestionID);
            }
            // Add associations for tags for this unsaved question now we finally have a question ID.
            if (!empty($tagsToAddList)) {
                WPCW_questions_tags_addTags($newQuestionID, $tagsToAddList);
            }
        }
    }
}