/** * Creates a box to show that a unit is currently pending. * * @param Object $parentData A copy of the parent course and module details for this unit. * @param Integer $unitID The ID of the unit that's currently pending. */ function WPCW_units_getCompletionBox_pending($parentData, $unitID) { // See if we have a quiz for this unit? If so, render it and allow the trainee to complete it. $quizDetails = WPCW_quizzes_getAssociatedQuizForUnit($unitID); $html = false; // See if we have a quiz for this unit, if we do, see if the user has completed it or not. if ($quizDetails && $quizDetails->questions && count($quizDetails) > 0) { // Got the user progress, determine if it's pending marking or not. $quizProgress = WPCW_quizzes_getUserResultsForQuiz(get_current_user_id(), $unitID, $quizDetails->quiz_id); if ($quizProgress && 'retake_quiz' == $quizProgress->quiz_next_step_type) { // Show a generic message that the quiz needs to be re-taken. $messageToShow = wpautop(__('The course instructor has required that you retake this quiz.')); $messageToShow .= '<p>' . sprintf(__('Your previous grade was: <b>%s</b>'), number_format($quizProgress->quiz_grade, 1) . '%') . '</p>'; // Add the custom message if there was one, which is personalised from the instructor. if ($quizProgress->quiz_next_step_msg) { $messageToShow .= wpautop($quizProgress->quiz_next_step_msg); } $html .= WPCW_units_createWarningMessage($messageToShow); } // User has completed this quiz, so we need to indicate if it's been marked or not. If it's not been marked // we show a message saying they are blocked until it's marked. if ($quizProgress && $quizProgress->quiz_needs_marking > 0) { // Blocking quiz - show a status message saying that they can't continue until the quiz is graded. if ('quiz_block' == $quizDetails->quiz_type) { $html .= WPCW_units_createSuccessMessage($parentData->course_message_quiz_open_grading_blocking); } } else { $html .= WPCW_quizzes_handleQuizRendering($unitID, $quizDetails); } } else { // Render the message $html .= sprintf(' <div class="wpcw_fe_progress_box_wrap" id="wpcw_fe_unit_complete_%d"> <div class="wpcw_fe_progress_box wpcw_fe_progress_box_pending wpcw_fe_progress_box_updating"> <div class="wpcw_fe_progress_box_mark"> <img src="%s/ajax_loader.gif" class="wpcw_loader" style="display: none;" /> <a href="#" class="fe_btn fe_btn_completion btn_completion" id="unit_complete_%d">%s</a> </div> %s</div> </div>', $unitID, WPCW_plugin_getPluginPath() . 'img', $unitID, __('Mark as Completed', 'wp_courseware'), $parentData->course_message_unit_pending); } return $html; }
/** * Function that shows details to the admin telling them what to do next. */ function WPCW_showPage_UserProgess_quizAnswers_whatsNext($quizDetails, $results, $page, $userID, $unitID) { // Tell admin still questions that need marking if ($results->quiz_needs_marking > 0) { printf('<div id="message" class="wpcw_msg wpcw_msg_info"><span class="wpcw_icon_pending"><b>%s</b></span></div>', __('This quiz has questions that need grading.', 'wp_courseware')); } else { // Show the form only if the quiz is blocking and they've failed. if ('quiz_block' == $quizDetails->quiz_type && $results->quiz_grade < $quizDetails->quiz_pass_mark) { $showAdminProgressForm = true; $showAdminMessageCustom = false; // Show admin which method was selected. if ($results->quiz_next_step_type) { switch ($results->quiz_next_step_type) { case 'progress_anyway': printf('<div id="message" class="wpcw_msg wpcw_msg_info">%s</span></div>', __('You have allowed the user to <b>progress anyway</b>, despite failing the quiz.', 'wp_courseware')); $showAdminProgressForm = false; break; case 'retake_quiz': printf('<div id="message" class="wpcw_msg wpcw_msg_info">%s</span></div>', __('You have requested that the user <b>re-takes the quiz</b>.', 'wp_courseware')); $showAdminProgressForm = false; break; case 'retake_waiting': printf('<div id="message" class="wpcw_msg wpcw_msg_info">%s</span></div>', __('The user has requested a retake, but they have not yet completed the quiz.', 'wp_courseware')); $showAdminProgressForm = false; break; case 'quiz_fail_no_retakes': $showAdminMessageCustom = __('The user has <b>exhausted all of their retakes</b>.', 'wp_courseware'); $showAdminProgressForm = true; break; } } // Next step has not been specified, allow the admin to choose one. if ($showAdminProgressForm) { $attempts_taken = WPCW_quizzes_getUserResultsForQuiz($userID, $unitID, $quizDetails->quiz_id); $unitQuizDetails = WPCW_quizzes_getAssociatedQuizForUnit($unitID, true, $userID); printf('<div class="wpcw_user_progress_failed"><form method="POST">'); // Show the main message or a custom message from above. printf('<div id="message" class="wpcw_msg wpcw_msg_error">%s %s</span></div>', $showAdminMessageCustom, __('Since this is a <b>blocking quiz</b>, and the user has <b>failed</b>, what would you like to do?', 'wp_courseware')); printf(' <div class="wpcw_user_progress_failed_next_action"> <label><input type="radio" name="wpcw_user_continue_action" class="wpcw_next_action_progress_anyway" value="progress_anyway" checked="checked" /> <span><b>%s</b> %s</span></label><br/> ', __('Allow the user to continue anyway.', 'wp_courseware'), __(' (User is emailed saying they can continue)', 'wp_courseware')); //if ($results->quiz_next_step_type == 'quiz_fail_no_retakes'){ if ($attempts_taken->attempt_count >= $unitQuizDetails->quiz_attempts_allowed && $unitQuizDetails->quiz_attempts_allowed != -1) { printf(' <label><input type="radio" name="wpcw_user_continue_action" class="wpcw_next_action_retake_quiz" value="retake_quiz" /> <span><b>%s</b> %s</span></label> ', __('Allow the user one more attempt.', 'wp_courseware'), __(' (User is emailed saying they need to re-take the quiz)', 'wp_courseware')); } else { printf(' <label><input type="radio" name="wpcw_user_continue_action" class="wpcw_next_action_retake_quiz" value="retake_quiz" /> <span><b>%s</b> %s</span></label> ', __('Require the user to re-take the quiz.', 'wp_courseware'), __(' (User is emailed saying they need to re-take the quiz)', 'wp_courseware')); } printf(' </div> <div class="wpcw_user_progress_failed_reason" style="display: none;"> <label><b>%s</b></label><br/> <textarea name="wpcw_user_progress_failed_reason"></textarea><br/> <small>%s</small> </div> <div class="wpcw_user_progress_failed_btns"> <input type="submit" name="failed_quiz_next_action" value="%s" class="button-primary" /> </div> ', __('Require the user to re-take the quiz.', 'wp_courseware'), __(' (User is emailed saying they need to re-take the quiz)', 'wp_courseware'), __('Custom Message:', 'wp_courseware'), __('Custom message for the user that\'s sent to the user when asking them to retake the quiz.', 'wp_courseware'), __('Save Preference', 'wp_courseware')); printf('</form></div>'); } } } }
/** * 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(); }
/** * Handles the grading of the quiz questions. */ function WPCW_showPage_UserProgess_quizAnswers_handingGrading($quizDetails, $results, $page, $userID, $unitID) { if (isset($_POST['grade_answers_submitted']) && 'true' == $_POST['grade_answers_submitted']) { $listOfQuestionsToMark = $results->quiz_needs_marking_list; // Switch array so values become keys. if (!empty($listOfQuestionsToMark)) { $listOfQuestionsToMark = array_flip($listOfQuestionsToMark); } else { $listOfQuestionsToMark = array(); } // Check $_POST keys for the graded results. foreach ($_POST as $key => $value) { // Check that we have a question ID and a matching grade for the quiz. Only want grades that are greater than 0. if (preg_match('/^grade_quiz_([0-9]+)$/', $key, $keyMatches) && preg_match('/^[0-9]+$/', $value) && $value > 0) { $questionID = $keyMatches[1]; // Remove from list to be marked, if found unset($listOfQuestionsToMark[$questionID]); // Add the grade information to the quiz if (isset($results->quiz_data[$questionID])) { $results->quiz_data[$questionID]['their_grade'] = $value; } } } // Update the database with the list of questions to mark, plus the updated quiz grading information. // Return to a simple list again, hence using array flip (ID => index) becomes (index => ID) $results->quiz_needs_marking_list = array_flip($listOfQuestionsToMark); // Update the results in the database. WPCW_quizzes_updateQuizResults($results); // Success message $page->showMessage(__('Grades have been successfully updated for this user.', 'wp_courseware')); // Refresh the results - now that we've made changes $results = WPCW_quizzes_getUserResultsForQuiz($userID, $unitID, $quizDetails->quiz_id); // All items are marked, so email user, and tell admin that user has been notified. if ($results->quiz_needs_marking == 0) { // Send out email only if not a blocking test, or blocking and passed. if ('quiz_block' == $quizDetails->quiz_type && $results->quiz_grade < $quizDetails->quiz_pass_mark) { $results->sendOutEmails = false; } else { $results->sendOutEmails = true; } // Check if the user has passed or not to indicate what to do next. if ($results->quiz_grade >= $quizDetails->quiz_pass_mark) { // Just a little note to mark as complete $results->extraEmailDetail = __('You have passed the quiz.', 'wp_courseware'); printf('<div id="message" class="wpcw_msg wpcw_msg_success">%s</span></div>', __('The user has <b>PASSED</b> this quiz, and the associated unit has been marked as complete.', 'wp_courseware')); WPCW_units_saveUserProgress_Complete($userID, $unitID); // Unit complete, check if course/module is complete too. do_action('wpcw_user_completed_unit', $userID, $unitID, WPCW_units_getAssociatedParentData($unitID)); } } // Set flag that the quiz has just literally been graded for use in code around this. // Doing this after the results have been updated above. $results->quiz_has_just_been_graded = true; } return $results; }
/** * Handles saving what the admin wants to do for the user next. */ function WPCW_showPage_UserProgess_quizAnswers_whatsNext_savePreferences($quizDetails, $results, $page, $userID, $unitID) { // Admin wants to save the next action to this progress. if (isset($_POST['failed_quiz_next_action']) && $_POST['failed_quiz_next_action']) { global $wpdb, $wpcwdb; $wpdb->show_errors(); $userNextAction = WPCW_arrays_getValue($_POST, 'wpcw_user_continue_action'); $userRetakeMsg = filter_var(WPCW_arrays_getValue($_POST, 'wpcw_user_progress_failed_reason'), FILTER_SANITIZE_STRING); // Check action is valid. Abort if not if (!in_array($userNextAction, array('retake_quiz', 'progress_anyway'))) { return $results; } // Sort out the SQL statement for what to update switch ($userNextAction) { // User needs to retake the course. case 'retake_quiz': break; // User is allowed to progress // User is allowed to progress case 'progress_anyway': $userRetakeMsg = false; // Mark the unit as completed. WPCW_units_saveUserProgress_Complete($userID, $unitID); // Unit complete, check if course/module is complete too. do_action('wpcw_user_completed_unit', $userID, $unitID, WPCW_units_getAssociatedParentData($unitID)); break; } // Update the progress item $SQL = $wpdb->prepare("\n\t\t \tUPDATE {$wpcwdb->user_progress_quiz}\n\t\t \t SET quiz_next_step_type = '%s', \n\t\t \t quiz_next_step_msg = %s \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\t", $userNextAction, $userRetakeMsg, $userID, $unitID, $quizDetails->quiz_id); $wpdb->query($SQL); // Need to update the results object for use later. $results = WPCW_quizzes_getUserResultsForQuiz($userID, $unitID, $quizDetails->quiz_id); switch ($userNextAction) { // User needs to retake the course. case 'retake_quiz': $results->extraEmailDetail = __('Since you didn\'t pass the quiz, the instructor has asked that you re-take this quiz.', 'wp_courseware'); if ($userRetakeMsg) { $results->extraEmailDetail .= "\n\n" . $userRetakeMsg; } break; // User is allowed to progress // User is allowed to progress case 'progress_anyway': $results->extraEmailDetail = __('Although you didn\'t pass the quiz, the instructor is allowing you to continue on to the next unit.', WPCW_showPage_UserProgess_quizAnswers_handingGrading); // Mark the unit as completed. WPCW_units_saveUserProgress_Complete($userID, $unitID); // Unit complete, check if course/module is complete too. do_action('wpcw_user_completed_unit', $userID, $unitID, WPCW_units_getAssociatedParentData($unitID)); break; } // Tell code to send out emails $results->sendOutEmails = true; } return $results; }