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