Exemple #1
0
/**
 * Update the quiz results in the database. Assume that the data exists when doing this update.
 * @param Object $quizResultsSoFar The updated list of results data.
 */
function WPCW_quizzes_updateQuizResults($quizResultsSoFar)
{
    global $wpcwdb, $wpdb;
    $wpdb->show_errors();
    $markinglistCount = 0;
    $markinglist = false;
    // Relabel variables for clarity.
    $needsMarkingList = $quizResultsSoFar->quiz_needs_marking_list;
    $newQuizData = $quizResultsSoFar->quiz_data;
    // Got items that need marking.
    if (!empty($needsMarkingList)) {
        $markinglist = serialize($needsMarkingList);
        $markinglistCount = count($needsMarkingList);
    }
    $dataToUpdate = array('user_id' => $quizResultsSoFar->user_id, 'unit_id' => $quizResultsSoFar->unit_id, 'quiz_id' => $quizResultsSoFar->quiz_id, 'quiz_needs_marking_list' => $markinglist, 'quiz_needs_marking' => $markinglistCount, 'quiz_data' => serialize($newQuizData), 'quiz_grade' => -1, 'quiz_attempt_id' => $quizResultsSoFar->quiz_attempt_id);
    // Update with the quiz grade
    $dataToUpdate['quiz_grade'] = WPCW_quizzes_calculateGradeForQuiz($newQuizData, $markinglistCount);
    $SQL = arrayToSQLUpdate($wpcwdb->user_progress_quiz, $dataToUpdate, array('user_id', 'unit_id', 'quiz_id', 'quiz_attempt_id'));
    $wpdb->query($SQL);
}
/**
 * Marks a unit as complete for the specified user. No error checking is made to check that the user
 * is allowed to update the record, it's assumed that the permission checking has been done before this step.
 * 
 * @param Integer $userID The ID of the user that's completed the unit.
 * @param Integer $unitID The ID of the unit that's been completed.
 */
function WPCW_units_saveUserProgress_Complete($userID, $unitID)
{
    global $wpdb, $wpcwdb;
    $wpdb->show_errors();
    $keyColumns = array('user_id', 'unit_id');
    $data = array();
    $data['unit_completed_status'] = 'complete';
    $data['unit_completed_date'] = current_time('mysql');
    $data['user_id'] = $userID;
    $data['unit_id'] = $unitID;
    $progress = doesRecordExistAlready($wpcwdb->user_progress, $keyColumns, array($userID, $unitID));
    if ($progress) {
        // Has it been marked as complete? If so, we don't want to do that again to preserve the date.
        // We generally shouldn't get here, but protect anyway.
        if ($progress->unit_completed_status == 'complete') {
            return false;
        }
        $SQL = arrayToSQLUpdate($wpcwdb->user_progress, $data, $keyColumns);
    } else {
        $SQL = arrayToSQLInsert($wpcwdb->user_progress, $data);
    }
    $wpdb->query($SQL);
}
/**
 * Handle saving a feedback message to the database.
 * 
 * @param Integer $quizID The quiz for which the questions apply to. 
 */
function WPCW_showPage_customFeedback_processSave($quizID)
{
    global $wpdb, $wpcwdb;
    $wpdb->show_errors();
    $msgToSave = array();
    $msgToSave_New = array();
    // Check $_POST data for the
    foreach ($_POST as $key => $value) {
        // ### 1) - Check if we're deleting a custom feedback message
        if (preg_match('/^delete_wpcw_qcfm_sgl_wrapper_([0-9]+)$/', $key, $matches)) {
            // Delete the message from the message table
            $SQL = $wpdb->prepare("\n\t\t\t\tDELETE FROM {$wpcwdb->quiz_feedback}\n\t\t\t\tWHERE qfeedback_id = %d\n\t\t\t", $matches[1]);
            $wpdb->query($SQL);
        }
        // #### 2 - See if we have a custom feedback message to add or update
        // Checking for wpcw_qcfm_sgl_wrapper_1 or wpcw_qcfm_sgl_wrapper_new_message_1
        if (preg_match('/^wpcw_qcfm_sgl_summary(_new_message)?_([0-9]+)$/', $key, $matches)) {
            // Got the ID of the message we're updating or adding.
            $messageID = $matches[2];
            // Store the extra string if we're adding a new message.
            $newMessagePrefix = $matches[1];
            $fieldSuffix = $newMessagePrefix . '_' . $messageID;
            // Fetch each field we need that will be saved
            $messageFields = array('qfeedback_quiz_id' => $quizID, 'qfeedback_summary' => stripslashes(WPCW_arrays_getValue($_POST, 'wpcw_qcfm_sgl_' . 'summary' . $fieldSuffix)), 'qfeedback_message' => stripslashes(WPCW_arrays_getValue($_POST, 'wpcw_qcfm_sgl_' . 'message' . $fieldSuffix)), 'qfeedback_tag_id' => intval(WPCW_arrays_getValue($_POST, 'wpcw_qcfm_sgl_' . 'tag' . $fieldSuffix)), 'qfeedback_score_grade' => intval(WPCW_arrays_getValue($_POST, 'wpcw_qcfm_sgl_' . 'score_grade' . $fieldSuffix)), 'qfeedback_score_type' => WPCW_arrays_getValue($_POST, 'wpcw_qcfm_sgl_' . 'score_type' . $fieldSuffix));
            // Check we have a valid score type.
            if ('below' != $messageFields['qfeedback_score_type'] && 'above' != $messageFields['qfeedback_score_type']) {
                $messageFields['qfeedback_score_type'] = 'below';
            }
            // #### 3) - Not a new message - so add to list of new messages to add.
            if ($newMessagePrefix) {
                $msgToSave_New[] = $messageFields;
            } else {
                $messageFields['qfeedback_id'] = $messageID;
                $msgToSave[] = $messageFields;
            }
        }
        // end of preg_match check
    }
    // each of $_POST foreach.
    // #### 4) Add new messages
    if (!empty($msgToSave_New)) {
        foreach ($msgToSave_New as $messageDetails) {
            $wpdb->query(arrayToSQLInsert($wpcwdb->quiz_feedback, $messageDetails));
        }
    }
    // #### 5) Update existing messages
    if (!empty($msgToSave)) {
        foreach ($msgToSave as $messageDetails) {
            $wpdb->query(arrayToSQLUpdate($wpcwdb->quiz_feedback, $messageDetails, 'qfeedback_id'));
        }
    }
}
 /**
  * Method called when form details are being saved to a normal table.
  * @param Array $formValues The list of details being saved.
  * @return Integer The ID of the newly saved record, or false if something went wrong.
  */
 protected function handleSaveMainDetails($formValues)
 {
     if (!$this->primaryKey || !$this->tableName) {
         $this->messages = $this->showMessage('handleSave(): Nothing could be saved, as the table name and primary key details are invalid.', true);
         return false;
     }
     global $wpdb;
     $wpdb->show_errors();
     // Have we already got this entry in the database?
     $this->alreadyExists = false;
     // Check #1 - See if we've got a primary key for it?
     $this->primaryKeyValue = $this->formObj->getArrayValue($formValues, $this->primaryKey);
     if ($this->primaryKeyValue) {
         //ÊCheck #2 - See if the record exists already
         $details = getRecordDetails($this->tableName, $this->primaryKey, $this->primaryKeyValue);
         if (!empty($details)) {
             $this->alreadyExists = true;
         }
     }
     // Record already exists, so updated it...
     if ($this->alreadyExists) {
         $SQL = arrayToSQLUpdate($this->tableName, $formValues, $this->primaryKey);
         $wpdb->query($SQL);
         $this->messages = $this->showMessage($this->msg_record_updated);
         // Update the locally stored details
         $this->recordDetails = getRecordDetails($this->tableName, $this->primaryKey, $this->primaryKeyValue, ARRAY_A);
         // Function called when record has been updated.
         if ($this->fn_record_updated && function_exists($this->fn_record_updated)) {
             call_user_func($this->fn_record_updated, $this->recordDetails);
         }
         // ID of existign record
         return $this->primaryKeyValue;
     } else {
         // Ensure that primary key is not set when creating a new one, to ensure non-dup
         // of IDs
         unset($formValues[$this->primaryKey]);
         // Create new record...
         $SQL = arrayToSQLInsert($this->tableName, $formValues);
         $wpdb->query($SQL);
         // ...then get the newly inserted ID so that we can update, without inserting again.
         $this->primaryKeyValue = $wpdb->insert_id;
         // DJH 2013-12-03 - Moved to below
         //$this->formObj->setDefaultValues(array($this->primaryKey => $this->primaryKeyValue));
         $this->messages = $this->showMessage($this->msg_record_created);
         // Update the locally stored details
         $this->recordDetails = getRecordDetails($this->tableName, $this->primaryKey, $this->primaryKeyValue, ARRAY_A);
         // DJH 2013-12-03 - Added to use the stored details in the form.
         $this->formObj->setDefaultValues($this->recordDetails);
         // Function called when record has been created.
         if ($this->fn_record_created && function_exists($this->fn_record_created)) {
             call_user_func($this->fn_record_created, $this->recordDetails);
         }
         // Newly inserted ID.
         return $this->primaryKeyValue;
     }
 }
 /**
  * In paging mode, check the answers that have been provided and update the status in the 
  * database if we're still progressing through getting answers to all questions.
  * 
  * @param Array $potentialAnswers The potential answers that need checking. 
  * 
  * @return Boolean Returns true when we have all of our answers, and we're allowed to carry on for grading.
  */
 function check_quizzes_canWeContinue_checkAnswersFromPaging($potentialAnswers)
 {
     global $wpdb, $wpcwdb;
     $wpdb->show_errors();
     // Check the raw data to see what questions we have.
     $resultsList = $this->check_quizzes_canWeContinue_extractAnswerData($potentialAnswers);
     // Need to build the data for the quiz progress, which should show that the quiz hasn't been graded as yet.
     $data = array();
     $data['user_id'] = $this->currentUserID;
     $data['unit_id'] = $this->unitPost->ID;
     $data['quiz_id'] = $this->unitQuizDetails->quiz_id;
     $data['quiz_completed_date'] = current_time('mysql');
     $data['quiz_correct_questions'] = 0;
     $data['quiz_question_total'] = count($this->unitQuizDetails->questions);
     // Total number of incomplete questions matches one less than question count (-1 to remove this Q).
     $data['quiz_paging_incomplete'] = $data['quiz_question_total'] - 1;
     // Showing the next question if we've got here.
     $data['quiz_paging_next_q'] = 1;
     // We're still working on this quiz, so it's incomplete.
     $data['quiz_paging_status'] = 'incomplete';
     // Set to false, we'll fill it in later.
     $data['quiz_data'] = false;
     // Do we have a progress item already? Such as a prior attempt or previous question (when paging).
     $SQL = $wpdb->prepare("\n\t\t\tSELECT * \n\t\t\tFROM {$wpcwdb->user_progress_quiz}\n\t\t\tWHERE user_id = %d\n\t\t\t  AND unit_id = %d\n\t\t\t  AND quiz_id = %d\n\t\t\tORDER BY quiz_attempt_id DESC \n\t\t\tLIMIT 1\n\t\t", $this->currentUserID, $this->unitPost->ID, $this->unitQuizDetails->quiz_id);
     $updateExistingProgress = false;
     // Already exists, so we just need to update the progress with where we're at.
     // If it doesn't exist, we'll just use the database default of 0.
     if ($existingProgress = $wpdb->get_row($SQL)) {
         // Got an existing incomplete paging progress, carry on working with it.
         if ('incomplete' == $existingProgress->quiz_paging_status) {
             $updateExistingProgress = true;
             // Need to progress to next item - BUT If the index is goes to the end of the list,
             // then don't change it. This allows to detect if we're running through skipped questions.
             if ($existingProgress->quiz_paging_next_q < $existingProgress->quiz_question_total) {
                 $data['quiz_paging_next_q'] = $existingProgress->quiz_paging_next_q + 1;
             } else {
                 $data['quiz_paging_next_q'] = $existingProgress->quiz_paging_next_q;
             }
             // Migrate data from the existing progress that's important.
             foreach ($existingProgress as $fieldName => $fieldDetails) {
                 if (!isset($data[$fieldName])) {
                     $data[$fieldName] = $fieldDetails;
                 }
             }
         } else {
             // Mark all previous progress items as not being the latest.
             $SQL = $wpdb->prepare("\n\t\t\t\t\tUPDATE {$wpcwdb->user_progress_quiz}\n\t\t\t\t\tSET quiz_is_latest = ''\n\t\t\t\t\tWHERE user_id = %d\n\t\t\t\t\t  AND unit_id = %d\n\t\t\t\t\t  AND quiz_id = %d\n\t\t\t\t", $this->currentUserID, $this->unitPost->ID, $this->unitQuizDetails->quiz_id);
             $wpdb->query($SQL);
             // Ensure this new progress marked as the latest.
             $data['quiz_is_latest'] = 'latest';
             $data['quiz_attempt_id'] = $existingProgress->quiz_attempt_id + 1;
         }
     }
     // Do Insert or Update as needed.
     if ($updateExistingProgress) {
         // Fetch the existing quiz data, as we need to merge the new complete data with
         // the existing data.
         $data['quiz_data'] = maybe_unserialize($existingProgress->quiz_data);
         if (!empty($data['quiz_data'])) {
             $newData = $this->fetch_quizzes_extractQuizStatusFromAnswers($resultsList['answer_list']);
             if (!empty($newData)) {
                 // We're assuming that if the new data is complete, then we overwrite the old
                 // data, as that should be incomplete.
                 foreach ($newData as $thisQuestionID => $thisAnswerDetails) {
                     // Got a new complete item, so overwrite details
                     if ($thisAnswerDetails['is_incomplete'] == 0) {
                         $data['quiz_data'][$thisQuestionID] = $thisAnswerDetails;
                     }
                 }
             }
             // Count up the total number of incomplete questions left.
             $data['quiz_paging_incomplete'] = 0;
             foreach ($data['quiz_data'] as $thisQuestionID => $thisAnswerDetails) {
                 // Found another question that's not complete, so add 1 to count of incomplete questions.
                 if ($thisAnswerDetails['is_incomplete'] == 1) {
                     $data['quiz_paging_incomplete']++;
                 }
             }
         } else {
             $data['quiz_data'] = $this->fetch_quizzes_extractQuizStatusFromAnswers($resultsList['answer_list']);
         }
         // Need to reserialize the data
         $data['quiz_data'] = serialize($data['quiz_data']);
         $SQL = arrayToSQLUpdate($wpcwdb->user_progress_quiz, $data, array('user_id', 'unit_id', 'quiz_id', 'quiz_attempt_id'));
     } else {
         // Extract all quiz data including placeholders for questions not yet answered.
         $data['quiz_data'] = serialize($this->fetch_quizzes_extractQuizStatusFromAnswers($resultsList['answer_list']));
         $SQL = arrayToSQLInsert($wpcwdb->user_progress_quiz, $data);
     }
     $wpdb->query($SQL);
     // Update internal store of results once saved to database.
     $this->unitQuizProgress = WPCW_quizzes_getUserResultsForQuiz($this->currentUserID, $this->unitPost->ID, $this->unitQuizDetails->quiz_id);
     // Try to work out which questions are still incomplete.
     $this->updateInternalVariable_quizzes_getIncompleteQuestions();
     // Reload any raw answers that we have into the internal raw answer object.
     $this->fetch_quizzes_loadRawAnswersSoFarForThisQuiz($data['quiz_data']);
     // We can only continue if we no longer have any incomplete questions.
     return $this->unitQuizProgress->quiz_paging_incomplete == 0 && !$this->check_paging_shouldWeShowReviewPage();
 }
Exemple #6
0
/**
 * Shows the page that allows a group to be modified.
 */
function WPPortfolio_modify_group()
{
    // Determine if we're in edit mode. Ensure we get correct mode regardless of where it is.
    $editmode = false;
    if (isset($_POST['editmode'])) {
        $editmode = $_POST['editmode'] == 'edit';
    } else {
        if (isset($_GET['editmode'])) {
            $editmode = $_GET['editmode'] == 'edit';
        }
    }
    // Get the Group ID. Ensure we get ID regardless of where it is.
    $groupid = 0;
    if (isset($_POST['group_groupid'])) {
        $groupid = is_numeric($_POST['group_groupid']) ? $_POST['group_groupid'] + 0 : 0;
    } else {
        if (isset($_GET['groupid'])) {
            $groupid = is_numeric($_GET['groupid']) ? $_GET['groupid'] + 0 : 0;
        }
    }
    $verb = __("Add New", 'wp-portfolio');
    if ($editmode) {
        $verb = __("Modify", 'wp-portfolio');
    }
    // Show title to determine action
    ?>
	<div class="wrap">
	<div id="icon-edit" class="icon32">
	<br/>
	</div>
	<h2><?php 
    echo $verb . __(' Group Details', 'wp-portfolio');
    ?>
</h2>
	<?php 
    // Check id is a valid number if editing $editmode
    if ($editmode && $groupid == 0) {
        WPPortfolio_showMessage(sprintf(__('Sorry, but no group with that ID could be found. Please click <a href="%s">here</a> to return to the list of groups.', 'wp-portfolio'), WPP_GROUP_SUMMARY), true);
        return;
    }
    $groupdetails = false;
    // ### EDIT ### Check if we're adding or editing a group
    if ($editmode && $groupid > 0) {
        // Get details from the database
        $groupdetails = WPPortfolio_getGroupDetails($groupid);
        // False alarm, couldn't find it.
        if (count($groupdetails) == 0) {
            $editmode = false;
        }
    }
    // end of editing check
    // Check if group is being updated/added.
    if (isset($_POST) && isset($_POST['update'])) {
        // Grab specified details
        $data = array();
        $data['groupid'] = $groupid;
        $data['groupname'] = strip_tags($_POST['group_groupname']);
        $data['groupdescription'] = $_POST['group_groupdescription'];
        $data['grouporder'] = $_POST['group_grouporder'] + 0;
        // Add zero to convert to number
        // Keep track of errors for validation
        $errors = array();
        // Ensure all fields have been completed
        if (!($data['groupname'] && $data['groupdescription'])) {
            array_push($errors, __("Please check that you have completed the group name and description fields.", 'wp-portfolio'));
        }
        // Continue if there are no errors
        if (count($errors) == 0) {
            global $wpdb;
            $table_name = $wpdb->prefix . TABLE_WEBSITE_GROUPS;
            // Change query based on add or edit
            if ($editmode) {
                $query = arrayToSQLUpdate($table_name, $data, 'groupid');
            } else {
                unset($data['groupid']);
                // Don't need id for an insert
                $query = arrayToSQLInsert($table_name, $data);
            }
            // Try to put the data into the database
            $wpdb->show_errors();
            $wpdb->query($query);
            // When editing, show what we've just been editing.
            if ($editmode) {
                WPPortfolio_showMessage(__("Group details successfully updated.", 'wp-portfolio'));
                // Retrieve the details from the database again
                $groupdetails = WPPortfolio_getGroupDetails($groupid);
            } else {
                WPPortfolio_showMessage(__("Group details successfully added.", 'wp-portfolio'));
                $groupdetails['groupid'] = false;
                $groupdetails['groupname'] = false;
                $groupdetails['groupdescription'] = false;
                $groupdetails['grouporder'] = false;
            }
        } else {
            $message = __("Sorry, but unfortunately there were some errors. Please fix the errors and try again.", 'wp-portfolio') . '<br><br>';
            $message .= "<ul style=\"margin-left: 20px; list-style-type: square;\">";
            // Loop through all errors in the $error list
            foreach ($errors as $errormsg) {
                $message .= "<li>{$errormsg}</li>";
            }
            $message .= "</ul>";
            WPPortfolio_showMessage($message, true);
            $groupdetails = $data;
        }
    }
    $form = new FormBuilder();
    $formElem = new FormElement("group_groupname", __("Group Name", 'wp-portfolio'));
    $formElem->value = WPPortfolio_getArrayValue($groupdetails, 'groupname');
    $formElem->description = __("The name for this group of websites.", 'wp-portfolio');
    $form->addFormElement($formElem);
    $formElem = new FormElement("group_groupdescription", __("Group Description", 'wp-portfolio'));
    $formElem->value = WPPortfolio_getArrayValue($groupdetails, 'groupdescription');
    $formElem->description = __("The description of your group. HTML is permitted.", 'wp-portfolio');
    $formElem->setTypeAsTextArea(4, 70);
    $form->addFormElement($formElem);
    $formElem = new FormElement("group_grouporder", __("Group Order", 'wp-portfolio'));
    $formElem->value = WPPortfolio_getArrayValue($groupdetails, 'grouporder');
    $formElem->description = '&bull; ' . __("The number to use for ordering the groups. Groups are rendered in ascending order, first by this order value (lowest value first), then by group name.", 'wp-portfolio') . '<br/>' . '&bull; ' . __('e.g. Groups (A, B, C, D) with ordering (50, 100, 0, 50) will be rendered as (C, A, D, B).', 'wp-portfolio') . '<br/>' . '&bull; ' . __("If all groups have 0 for ordering, then the groups are rendered in alphabetical order.", 'wp-portfolio');
    $form->addFormElement($formElem);
    // Hidden Elements
    $formElem = new FormElement("group_groupid", false);
    $formElem->value = WPPortfolio_getArrayValue($groupdetails, 'groupid');
    $formElem->setTypeAsHidden();
    $form->addFormElement($formElem);
    $formElem = new FormElement("editmode", false);
    $formElem->value = $editmode ? "edit" : "add";
    $formElem->setTypeAsHidden();
    $form->addFormElement($formElem);
    $form->setSubmitLabel(($editmode ? __("Update", 'wp-portfolio') : __("Add", 'wp-portfolio')) . " " . __("Group Details", 'wp-portfolio'));
    echo $form->toString();
    ?>
		
	<br><br>
	</div><!-- wrap -->
	<?php 
}
 /**
  * Try to add the units to the database.
  * @param Array $unitData The list of units to add
  * @param Integer $moduleID The ID of the parent module
  * @return Integer The number of units added.
  */
 private function addUnitsToDatabase($unitData, $moduleID)
 {
     if (!$unitData || count($unitData) < 1) {
         return 0;
     }
     global $wpdb, $wpcwdb;
     $wpdb->show_errors();
     $unitCount = 0;
     foreach ($unitData as $singleUnit) {
         // ### 1 - Create unit as a WP Post
         $unitPost = array('post_title' => $singleUnit['post_title'], 'post_content' => $singleUnit['post_content'], 'post_name' => $singleUnit['post_name'], 'post_status' => 'publish', 'post_type' => 'course_unit');
         // Insert the post into the database
         $unitID = wp_insert_post($unitPost);
         if (!$unitID) {
             $this->errorList[] = sprintf(__('Could not create course unit "%s". So this was skipped.', 'wp_courseware'), $singleUnit['post_title']);
             continue;
         }
         // ### 2 - Update the post with the meta of the related module
         update_post_meta($unitID, 'wpcw_associated_module', $moduleID);
         // ### 3 - Create the meta data for WPCW for this unit
         $unitCount++;
         $this->unit_order += 10;
         $unitmeta = array();
         $unitmeta['unit_id'] = $unitID;
         $unitmeta['parent_module_id'] = $moduleID;
         $unitmeta['parent_course_id'] = $this->course_id;
         $unitmeta['unit_order'] = $this->unit_order;
         // The order overall in whole course
         $unitmeta['unit_number'] = $unitCount;
         // The number of the unit within module
         // This is an update, as wp_insert_post will create meta entry.
         $queryResult = $wpdb->query(arrayToSQLUpdate($wpcwdb->units_meta, $unitmeta, 'unit_id'));
         // Check query succeeded.
         if ($queryResult === FALSE) {
             return $this->returnErrorList(__('There was a problem adding unit meta data into the database.', 'wp_courseware'));
         }
     }
     return $unitCount;
 }
Exemple #8
0
/**
 * Handle saving questions to the database.
 * @param Integer $quizID The quiz for which the questions apply to.
 * @param Object $page The associated page object for showing messages. 
 */
function WPCW_showPage_ModifyQuiz_showQuizEntryForms_processSave($quizID, $page)
{
    // No updates have been requested, so exit.
    if (!isset($_POST['survey_updated'])) {
        return;
    }
    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?
        // Not interested in new questions that have been added and then deleted. Just the
        // ones that were added to the database first.
        if (preg_match('/^delete_wpcw_quiz_details_([0-9]+)$/', $key, $matches)) {
            $SQL = $wpdb->prepare("\n\t\t\t\tDELETE FROM {$wpcwdb->quiz_qs}\n\t\t\t\tWHERE question_id = %d\n\t\t\t", $matches[1]);
            $wpdb->query($SQL);
        } else {
            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_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;
                // 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));
                // 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);
                        $qAnsCor = WPCW_quiz_MultipleChoice::editSave_extractCorrectAnswer($qAns, $fieldName_Correct);
                        // Provide the UI with at least once slot for an answer.
                        if (!$qAns) {
                            $qAns = array('', '');
                        }
                        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;
                        // Not expecting anything here... so not handling the error case.
                    // Not expecting anything here... so not handling the error case.
                    default:
                        break;
                }
                // ### 4 - Save new question data as a list ready for saving to the database.
                // New question - so no question ID as yet
                if ($newQuestionPrefix) {
                    $questionsToSave_New[] = array('question_question' => stripslashes($value), 'question_answers' => $qAns ? implode("\n", $qAns) : $qAns, 'question_correct_answer' => $qAnsCor, 'parent_quiz_id' => $quizID, 'question_type' => $questionType, 'question_order' => $questionOrder, 'question_answer_type' => $qAnsType, 'question_answer_hint' => stripslashes($qAnsHint), 'question_answer_file_types' => $qAnsFileTypes);
                } else {
                    $questionsToSave[$questionID] = array('question_id' => $questionID, 'question_question' => stripslashes($value), 'question_answers' => $qAns ? implode("\n", $qAns) : $qAns, 'question_correct_answer' => $qAnsCor, 'parent_quiz_id' => $quizID, 'question_type' => $questionType, 'question_order' => $questionOrder, 'question_answer_type' => $qAnsType, 'question_answer_hint' => stripslashes($qAnsHint), 'question_answer_file_types' => $qAnsFileTypes);
                }
            }
        }
        // end if question found.
    }
    // #### 5 - Check we have existing questions to save
    if (count($questionsToSave)) {
        // Now save all data back to the database.
        foreach ($questionsToSave as $questionID => $questionDetails) {
            $wpdb->query(arrayToSQLUpdate($wpcwdb->quiz_qs, $questionDetails, 'question_id'));
        }
    }
    // #### 6 - Save the new questions we have
    if (count($questionsToSave_New)) {
        // Now save all data back to the database.
        foreach ($questionsToSave_New as $questionDetails) {
            $wpdb->query(arrayToSQLInsert($wpcwdb->quiz_qs, $questionDetails));
        }
    }
    // Show an error if questions are missing details.
    $page->showMessage(__('Questions were successfully updated.', 'wp_courseware'));
}
/**
 * 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);
            }
        }
    }
}