/** * 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(); } }
/** * 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; }
/** * 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; }
/** * 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); } } } }