/** * 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')); } } }
/** * 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(); }
/** * 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; } }
/** * Updates the database to generate a certificate entry. If a certificate already exists for the user/course ID, * then no new entry is created. * * @param Integer $userID The ID of the user that the certificate is being generated for. * @param Integer $courseID The ID of the associated course. */ function WPCW_certificate_generateCertificateEntry($userID, $courseID) { if (!$userID || !$courseID) { return; } global $wpdb, $wpcwdb; $wpdb->show_errors(); // Already have a record for this certificate. if ($certificateDetails = doesRecordExistAlready($wpcwdb->certificates, array('cert_user_id', 'cert_course_id'), array($userID, $courseID))) { return $certificateDetails; } // Create anonymous entry to allow users to access a certificate when they've completed a course. Means that certificates // stay existing even if units are added to a course. $data = array(); $data['cert_user_id'] = $userID; $data['cert_course_id'] = $courseID; $data['cert_generated'] = current_time('mysql'); $data['cert_access_key'] = md5(serialize($data)); // Unique key based on data we've just added $SQL = arrayToSQLInsert($wpcwdb->certificates, $data); $wpdb->query($SQL); // Return details of the added certificate return getRecordDetails($wpcwdb->certificates, array('cert_user_id', 'cert_course_id'), array($userID, $courseID)); }
/** * 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 = '• ' . __("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/>' . '• ' . __('e.g. Groups (A, B, C, D) with ordering (50, 100, 0, 50) will be rendered as (C, A, D, B).', 'wp-portfolio') . '<br/>' . '• ' . __("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 }
/** * Function that performs the actual course import. * @return Integer The ID of the newly imported course. */ public function importCourseIntoDatabase() { if (!current_user_can('manage_options')) { return $this->returnErrorList(__('Sorry, you\'re not allowed to import courses into WordPress.', 'wp_courseware')); } // ### 1) Extract XML data into a clean array of information. // The functions below may handle detailed verification of data in // future releases. $courseData = $this->loadCourseData(); $moduleData = $this->loadModuleData(); // ### 2) Turn the course into an actual database entry if (count($courseData) == 0) { return $this->returnErrorList(__('There was no course data to import.', 'wp_courseware')); } global $wpdb, $wpcwdb; $wpdb->show_errors(); $queryResult = $wpdb->query(arrayToSQLInsert($wpcwdb->courses, $courseData)); // Check query succeeded. if ($queryResult === FALSE) { return $this->returnErrorList(__('Could not create course in database.', 'wp_courseware')); } $this->course_id = $wpdb->insert_id; // Track how many units we add $unitCount = 0; $this->unit_order = 0; // ### 3) Check for the module data, and then try to add this if ($moduleData) { $moduleCount = 0; foreach ($moduleData as $moduleItem) { // Extract Unit Data from module info, so it doesn't interfere with database add $unitData = $moduleItem['units']; unset($moduleItem['units']); $moduleCount++; // Add parent course details, plus order details. $moduleItem['parent_course_id'] = $this->course_id; $moduleItem['module_order'] = $moduleCount; $moduleItem['module_number'] = $moduleCount; $queryResult = $wpdb->query(arrayToSQLInsert($wpcwdb->modules, $moduleItem)); // Check query succeeded. if ($queryResult === FALSE) { return $this->returnErrorList(__('There was a problem inserting the module into the database.', 'wp_courseware')); } $currentModuleID = $wpdb->insert_id; // ### 4) Check for any units $unitCount += $this->addUnitsToDatabase($unitData, $currentModuleID); } } // end if $moduleData // Update unit counts // 31st May 2013 - V1.26 - Fix - Incorrectly referring to $course_id - which is empty. // Changed to $this->course_id to fix issue. $courseDetails = WPCW_courses_getCourseDetails($this->course_id); do_action('wpcw_course_details_updated', $courseDetails); }
/** * Save the user's quiz progress to the database using their verbose answers. * * @param Integer $userID The ID of the user completing the quiz. * @param Array $quizDetails The details of the quiz that's been completed. * @param Array $resultDetails The full result details for the user who's completed the quiz. * @param Array $checkedAnswerList The full list of checked answers. */ function WPCW_quizzes_saveUserProgress($userID, $quizDetails, $resultDetails, $checkedAnswerList) { global $wpdb, $wpcwdb; $wpdb->show_errors(); $data = array(); $data['quiz_correct_questions'] = count($resultDetails['correct']); $data['quiz_question_total'] = count($quizDetails->questions); $data['quiz_completed_date'] = current_time('mysql'); $data['user_id'] = $userID; $data['unit_id'] = $quizDetails->parent_unit_id; $data['quiz_id'] = $quizDetails->quiz_id; // Store which questions need marking $data['quiz_needs_marking'] = false; $data['quiz_needs_marking_list'] = false; // Generate a full list of the quiz and the answers given. $fullDetails = array(); foreach ($quizDetails->questions as $singleQuestion) { $qItem = array(); $qItem['title'] = $singleQuestion->question_question; $openEndedQuestion = false; // We know we have enough answers at this point, so know switch ($singleQuestion->question_type) { // There's definitely a right or wrong answer, so determine that now. case 'truefalse': case 'multi': $qItem['their_answer'] = WPCW_quizzes_getCorrectAnswer($singleQuestion, $checkedAnswerList[$singleQuestion->question_id]); break; // Uploaded files and open-ended questions need marking, so it's just their raw answer. // Uploaded files and open-ended questions need marking, so it's just their raw answer. case 'upload': case 'open': $openEndedQuestion = true; $qItem['their_answer'] = $checkedAnswerList[$singleQuestion->question_id]; break; } // If a survey, there are no correct answers. if ('survey' == $quizDetails->quiz_type || $openEndedQuestion) { $qItem['correct'] = false; $qItem['got_right'] = false; } else { // Get the correct answer $qItem['correct'] = WPCW_quizzes_getCorrectAnswer($singleQuestion); // Did the answers match? $qItem['got_right'] = $qItem['their_answer'] == $qItem['correct'] ? 'yes' : 'no'; } $fullDetails[$singleQuestion->question_id] = $qItem; } // Track how many questions need marking $data['quiz_needs_marking'] = count($resultDetails['needs_marking']); $data['quiz_needs_marking_list'] = serialize(array_keys($resultDetails['needs_marking'])); // Only store the IDs. // Use serialised data for storing full results. $data['quiz_data'] = serialize($fullDetails); $data['quiz_grade'] = WPCW_quizzes_calculateGradeForQuiz($fullDetails, $data['quiz_needs_marking']); $SQL = $wpdb->prepare("\n\t\tSELECT * \n\t\tFROM {$wpcwdb->user_progress_quiz}\n\t\tWHERE user_id = %d\n\t\t AND unit_id = %d\n\t\t AND quiz_id = %d\n\t\tORDER BY quiz_attempt_id DESC \n\t\tLIMIT 1\n\t", $userID, $quizDetails->parent_unit_id, $quizDetails->quiz_id); // Already exists, so increment the quiz_attempt_id // If it doesn't exist, we'll just use the database default of 0. if ($existingProgress = $wpdb->get_row($SQL)) { // Mark all previous progress items as not being the latest. $SQL = $wpdb->prepare("\n\t\t\tUPDATE {$wpcwdb->user_progress_quiz}\n\t\t\tSET quiz_is_latest = ''\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", $userID, $quizDetails->parent_unit_id, $quizDetails->quiz_id); $wpdb->query($SQL); // Ensure this is marked as the latest. $data['quiz_is_latest'] = 'latest'; $data['quiz_attempt_id'] = $existingProgress->quiz_attempt_id + 1; } $SQL = arrayToSQLInsert($wpcwdb->user_progress_quiz, $data); $wpdb->query($SQL); }
/** * Try to add this specific question to the database (to the pool). * @param Array $singleQuestionData Specific question data to add. * @return Integer The ID of the newly inserted question ID, or false if it wasn't added. */ private function loadQuestionData_addQuestionToDatabase($singleQuestionData) { if (!$singleQuestionData || empty($singleQuestionData)) { return false; } global $wpdb, $wpcwdb; $wpdb->show_errors(); // ### 1 - Initialise data to be added for question. $questionDetailsToAdd = $singleQuestionData; // ### 2 - Need to strip out question order, as there is no question_order field // in the database, so we need to use it in the mappings data. $question_order = WPCW_arrays_getValue($singleQuestionData, 'question_order'); unset($questionDetailsToAdd['question_order']); // ### 3 - Handle the insert of the special arrays that need serialising. We know these exist // as we have specifically validated them. $questionDetailsToAdd['question_data_answers'] = false; if (!empty($singleQuestionData['question_data_answers'])) { $questionDetailsToAdd['question_data_answers'] = serialize($singleQuestionData['question_data_answers']); } // ### 4 - Extract tags from the question data so that we can insert the question into // the database without any errors. $tagList = array(); if (isset($questionDetailsToAdd['tags'])) { $tagList = $questionDetailsToAdd['tags']; } unset($questionDetailsToAdd['tags']); // ### 5 - Insert details of this particular question into the database. $queryResult = $wpdb->query(arrayToSQLInsert($wpcwdb->quiz_qs, $questionDetailsToAdd)); // ### 6 - Store the ID of the newly inserted ID, we'll need it for associating tags. $currentQuestionID = $wpdb->insert_id; // ### 7 - Check query succeeded. if ($queryResult === FALSE) { $this->errorList[] = __('There was a problem adding the question into the database.', 'wp_courseware'); return false; } // ### 8 - Now we create the tag associations. WPCW_questions_tags_addTags($currentQuestionID, $tagList); return $currentQuestionID; }
/** * 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); } } } }