/** * Create the associations of the questions * @param Object $singleQuiz The quiz details to check for questions. */ function loadQuizData_addQuestionsAssociations($singleQuiz) { // No questons to worry about if (!isset($singleQuiz['questions']) || empty($singleQuiz['questions'])) { return false; } // ### 1 - Ensure the questions are ordered correctly. usort($singleQuiz['questions'], array($this, 'sortQuestionsByOrder')); global $wpdb, $wpcwdb; $wpdb->show_errors(); // ### 2 - Need to now do the question mapping for this quiz. $questionOrder = 0; foreach ($singleQuiz['questions'] as $singleQuestionToAdd) { // ### 3 - Fixed - questions are a simple association. if ('fixed' == $singleQuestionToAdd['question_type']) { // Only do association if ID is valid if ($singleQuestionToAdd['question_real_id']) { $wpdb->query($wpdb->prepare("\n\t\t\t\t\t\tINSERT INTO {$wpcwdb->quiz_qs_mapping} \n\t\t\t\t\t\t(question_id, parent_quiz_id, question_order)\n\t\t\t\t\t\tVALUES (%d, %d, %d)\n\t\t\t\t\t", $singleQuestionToAdd['question_real_id'], $singleQuiz['quiz_id'], $questionOrder++)); // ### 4 - Update the mapping counts now that we've added all of the questions. WPCW_questions_updateUsageCount($singleQuestionToAdd['question_real_id']); // DEFERRED - Report an error here if real ID is invalid? } } else { $toSaveList = array(); if (!empty($singleQuestionToAdd['tag_selections'])) { $totalNumberOfSelections = 0; // ### 5 - Check each selection foreach ($singleQuestionToAdd['tag_selections'] as $tagName => $selectionCount) { // This is the simple one. Assume that selection counts are valid. if ('whole_pool' == $tagName) { $toSaveList[$tagName] = $selectionCount; // Need to keep track of how many questions there might be. $totalNumberOfSelections += $selectionCount; } else { // Effectively we'll validate the tags here. $tagDetails = WPCW_questions_tags_getTagDetails_byName($tagName); if ($tagDetails) { // Need to create tag_11 => count $toSaveList['tag_' . $tagDetails->question_tag_id] = $selectionCount; // Need to keep track of how many questions there might be. $totalNumberOfSelections += $selectionCount; } } // end of selection type } // end of loop for tag selections // ### 6 - Now add the question to the database. Usage count is always 1, as we're only // creating this for this quiz. $wpdb->query($wpdb->prepare("\n\t\t\t\t\t\tINSERT INTO {$wpcwdb->quiz_qs} \n\t\t\t\t\t\t(question_type, question_question, question_usage_count, question_expanded_count)\n\t\t\t\t\t\tVALUES ('random_selection', %s, 1, %d)\n\t\t\t\t\t", json_encode($toSaveList), $totalNumberOfSelections)); $newSelectionQuestionID = $wpdb->insert_id; // ### 7 - Now associate with the parent quiz. $wpdb->query($wpdb->prepare("\n\t\t\t\t\t\tINSERT INTO {$wpcwdb->quiz_qs_mapping} \n\t\t\t\t\t\t(question_id, parent_quiz_id, question_order)\n\t\t\t\t\t\tVALUES (%d, %d, %d)\n\t\t\t\t\t", $newSelectionQuestionID, $singleQuiz['quiz_id'], $questionOrder++)); } // end of check for tag selections } // end check of question type. } }
/** * Delete a course, its modules and disassociating of the units that it contains. * * @param Object $courseDetails The details of the course to delete. * @param String $deleteMethod The deletion method. Either 'complete' or 'course_and_module'. */ function WPCW_modules_deleteCourse($courseDetails, $deleteMethod) { if (!$courseDetails) { return; } global $wpdb, $wpcwdb; $wpdb->show_errors(); // Get a list of units for this course. $unitList = $wpdb->get_col($wpdb->prepare("\n \tSELECT unit_id\n \tFROM {$wpcwdb->units_meta}\n \tWHERE parent_course_id = %d\n ", $courseDetails->course_id)); switch ($deleteMethod) { // Disassociate quizzes, units, etc. case 'course_and_module': if ($unitList) { foreach ($unitList as $unitID) { // Unit Meta // Remove course info. Update database with new association and ordering. $SQL = $wpdb->prepare("\n\t\t\t\t\t\tUPDATE {$wpcwdb->units_meta}\n\t\t\t\t\t\t SET unit_order = 0, parent_module_id = 0, parent_course_id = 0, unit_number = 0\n\t\t\t\t\t\tWHERE unit_id = %d\n\t\t\t\t\t", $unitID); $wpdb->query($SQL); // Unit Post Meta // Update post meta to remove associated module detail update_post_meta($unitID, 'wpcw_associated_module', 0); // User Progress // Remove progress for this unit, since unit is now unassociated. $SQL = $wpdb->prepare("\n\t\t\t\t\t\tDELETE FROM {$wpcwdb->user_progress}\n\t\t\t\t\t\tWHERE unit_id = %d\n\t\t\t\t\t", $unitID); $wpdb->query($SQL); // User Quiz Progress // Progress is linked to a course, hence wanting to remove it. $SQL = $wpdb->prepare("\n\t\t\t\t\t\tDELETE FROM {$wpcwdb->user_progress_quiz}\n\t\t\t\t\t\tWHERE unit_id = %d\n\t\t\t\t\t", $unitID); $wpdb->query($SQL); } } // Quiz Association // Remove course info for all quizzes $SQL = $wpdb->prepare("\n\t\t\t\tUPDATE {$wpcwdb->quiz}\n\t\t\t\t SET parent_course_id = 0\n\t\t\t\tWHERE parent_course_id = %d\n\t\t\t", $courseDetails->course_id); $wpdb->query($SQL); break; // Complete delete - delete absolutely everything... // Complete delete - delete absolutely everything... default: if ($unitList) { // Reduce SQL queries - do an ARRAY search on the WHERE. $unitIDList = implode(",", $unitList); // Unit Meta $wpdb->query("\n\t\t\t\t\tDELETE FROM {$wpcwdb->units_meta}\n\t\t\t\t\tWHERE unit_id IN ({$unitIDList})\n\t\t\t\t"); // User Progress $wpdb->query("\n\t\t\t\t\tDELETE FROM {$wpcwdb->user_progress}\n\t\t\t\t\tWHERE unit_id IN ({$unitIDList})\n\t\t\t\t"); // User Quiz Progress $wpdb->query("\n\t\t\t\t\tDELETE FROM {$wpcwdb->user_progress_quiz}\n\t\t\t\t\tWHERE unit_id IN ({$unitIDList})\n\t\t\t\t"); // Use WordPress delete for deleting the unit. foreach ($unitList as $unitID) { wp_delete_post($unitID, true); } } // end unit check // Quiz Deletion - need a list of quiz IDs to delete the // question mappings $quizList = $wpdb->get_col($wpdb->prepare("\n\t\t \tSELECT quiz_id\n\t\t \tFROM {$wpcwdb->quiz}\n\t\t \tWHERE parent_course_id = %d\n\t\t ", $courseDetails->course_id)); if ($quizList) { // Reduce SQL queries - do an ARRAY search on the WHERE. $quizIDList = implode(",", $quizList); // Get a list of question IDs first $questionIDList = $wpdb->get_col("\n\t\t\t\t\tSELECT question_id \n\t\t\t\t\tFROM {$wpcwdb->quiz_qs_mapping}\n\t\t\t\t\tWHERE parent_quiz_id IN ({$quizIDList})\n\t\t\t\t\tGROUP BY question_id\n\t\t\t\t"); // Remove the mappings between Quiz and their Questions $wpdb->query("\n\t\t\t\t\tDELETE FROM {$wpcwdb->quiz_qs_mapping}\n\t\t\t\t\tWHERE parent_quiz_id IN ({$quizIDList})\n\t\t\t\t"); // Update usage count for questions if (!empty($questionIDList)) { foreach ($questionIDList as $questionID) { WPCW_questions_updateUsageCount($questionID); } } } // Quizzes themselves $SQL = $wpdb->prepare("\n\t\t\t\tDELETE FROM {$wpcwdb->quiz}\n\t\t\t\tWHERE parent_course_id = %d\n\t\t\t", $courseDetails->course_id); $wpdb->query($SQL); break; } // Module deletion here. $wpdb->query($SQL = $wpdb->prepare("\n\t\t\t\tDELETE FROM {$wpcwdb->modules}\n\t\t\t\tWHERE parent_course_id = %d\n\t\t\t", $courseDetails->course_id)); // Certificate deletion for this course $wpdb->query($SQL = $wpdb->prepare("\n\t\t\t\tDELETE FROM {$wpcwdb->certificates}\n\t\t\t\tWHERE cert_course_id = %d\n\t\t\t", $courseDetails->course_id)); // Perform course deletion here. $wpdb->query($SQL = $wpdb->prepare("\n\t\t\t\tDELETE FROM {$wpcwdb->courses}\n\t\t\t\tWHERE course_id = %d\n\t\t\t", $courseDetails->course_id)); // Course progress summary for each user needs to be removed. $wpdb->query($SQL = $wpdb->prepare("\n\t\t\t\tDELETE FROM {$wpcwdb->user_courses}\n\t\t\t\tWHERE course_id = %d\n\t\t\t", $courseDetails->course_id)); // Trigger event that course has been deleted do_action('wpcw_course_deleted', $courseDetails); }
/** * 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); } } } }