/** * Show the forms where the quiz answers can be edited. * * @param Integer $quizID the ID of the quiz to be edited. * @param Object $page The associated page object for showing messages. */ function WPCW_showPage_ModifyQuiz_showQuestionEntryForms($quizID, $page) { global $wpdb, $wpcwdb; $wpdb->show_errors(); // Work out if we need correct answers or not. And what the pass mark is. $quizDetails = WPCW_quizzes_getQuizDetails($quizID, true, false, false); $needCorrectAnswers = 'survey' != $quizDetails->quiz_type; // Show the existing quiz questions as a series of forms. $quizItems = WPCW_quizzes_getListOfQuestions($quizID); // Show the number of correct answers the user must get in order to pass. if ('quiz_block' == $quizDetails->quiz_type) { $totalQs = WPCW_quizzes_calculateActualQuestionCount($quizID); $passQs = ceil($quizDetails->quiz_pass_mark / 100 * $totalQs); printf('<div class="wpcw_msg wpcw_msg_info">'); printf(__('The trainee will be required to correctly answer at least <b>%d of the %d</b> following questions (<b>at least %d%%</b>) to progress.', 'wp_courseware'), $passQs, $totalQs, $quizDetails->quiz_pass_mark); printf('</div>'); } // Got a quiz, and trainer is requiring to show answers. Tell them we can't show answers // as this quiz contains open-ended questions that need grading. if ($needCorrectAnswers && 'show_answers' == $quizDetails->quiz_show_answers && WPCW_quizzes_containsQuestionsNeedingManualGrading($quizItems)) { printf('<div class="wpcw_msg wpcw_msg_error">'); printf(__('This quiz contains questions that need <b>manual grading</b>, and you\'ve selected \'<b>Show Answers</b>\' when the user completes this quiz. ', 'wp_courseware') . '<br/><br/>' . __('Since answers cannot be shown to the user because they are not known at that stage, <b>answers cannot be shown</b>. To hide this message, select \'<b>No Answers</b>\' above.', 'wp_courseware')); printf('</div>'); } $errorCount = 0; global $errorCount; // Wrapper for questions printf('<ol class="wpcw_dragable_question_holder">'); if ($quizItems) { // Render edit form for each of the quizzes that already exist foreach ($quizItems as $quizItem) { switch ($quizItem->question_type) { case 'multi': $quizObj = new WPCW_quiz_MultipleChoice($quizItem); break; case 'truefalse': $quizObj = new WPCW_quiz_TrueFalse($quizItem); break; case 'open': $quizObj = new WPCW_quiz_OpenEntry($quizItem); break; case 'upload': $quizObj = new WPCW_quiz_FileUpload($quizItem); break; case 'random_selection': $quizObj = new WPCW_quiz_RandomSelection($quizItem); break; default: die(__('Unknown quiz type: ', 'wp_courseware') . $quizItem->question_type); break; } $quizObj->showErrors = true; $quizObj->needCorrectAnswers = $needCorrectAnswers; // Keep track of errors if ($quizObj && $quizObj->gotError) { $errorCount++; } echo $quizObj->editForm_toString(); } } printf('</ol>'); // Do any of the questions have residual errors? Tell the user. if ($errorCount > 0) { $page->showMessage(sprintf(__('%d of the questions below have errors. Please make corrections and then save the changes.', 'wp_courseware'), $errorCount), true); } $page->showPageMiddle('35%'); // Show the menu for saving and adding new items. WPCW_showPage_ModifyQuiz_FloatMenu($page); // Flag to indicate that questions have been updated. printf('<input type="hidden" name="survey_updated" value="survey_updated" />'); printf('<a name="new_question"></a>'); // The empty forms for adding a new question $quizItemDummy = new stdClass(); $quizItemDummy->question_question = ''; $quizItemDummy->question_correct_answer = false; $quizItemDummy->question_order = 0; $quizItemDummy->question_answer_type = false; $quizItemDummy->question_answer_hint = false; $quizItemDummy->question_answer_explanation = false; $quizItemDummy->question_answer_file_types = 'doc, pdf, jpg, png, jpeg, gif'; $quizItemDummy->question_image = false; $quizItemDummy->question_usage_count = 0; $quizItemDummy->question_multi_random_enable = 0; $quizItemDummy->question_multi_random_count = 5; // Create some dummy answers. $quizItemDummy->question_data_answers = serialize(array(1 => array('answer' => ''), 2 => array('answer' => ''), 3 => array('answer' => ''))); $quizFormsToCreate = array('new_multi' => 'WPCW_quiz_MultipleChoice', 'new_tf' => 'WPCW_quiz_TrueFalse', 'new_open' => 'WPCW_quiz_OpenEntry', 'new_upload' => 'WPCW_quiz_FileUpload', 'new_random_selection' => 'WPCW_quiz_RandomSelection'); // Create the dummy quiz objects foreach ($quizFormsToCreate as $dummyid => $objClass) { // Set placeholder class $quizItemDummy->question_id = $dummyid; // Create new object and set it up with defaults $quizObj = new $objClass($quizItemDummy); $quizObj->cssClasses .= ' wpcw_question_template'; $quizObj->showErrors = false; $quizObj->needCorrectAnswers = $needCorrectAnswers; $quizObj->editForm_questionNotSavedYet = true; echo $quizObj->editForm_toString(); } }
/** * Function called when a user is submitting quiz answers via * the frontend form. */ function WPCW_AJAX_units_handleQuizResponse() { // Security check if (!wp_verify_nonce(WPCW_arrays_getValue($_POST, 'progress_nonce'), 'wpcw-progress-nonce')) { die(__('Security check failed!', 'wp_courseware')); } // Quiz ID and Unit ID are combined in the single CSS ID for validation. // So validate both are correct and that user is allowed to access quiz. $quizAndUnitID = WPCW_arrays_getValue($_POST, 'id'); // e.g. quiz_complete_69_1 or quiz_complete_17_2 (first ID is unit, 2nd ID is quiz) if (!preg_match('/quiz_complete_(\\d+)_(\\d+)/', $quizAndUnitID, $matches)) { echo WPCW_units_getCompletionBox_error(); die; } // Use the extracted data for further validation $unitID = $matches[1]; $quizID = $matches[2]; $user_id = get_current_user_id(); // #### Get associated data for this unit. No course/module data, not a unit $parentData = WPCW_units_getAssociatedParentData($unitID); if (!$parentData) { // No error, as not a valid unit. die; } // #### User not allowed access to content, so certainly can't say they've done this unit. if (!WPCW_courses_canUserAccessCourse($parentData->course_id, $user_id)) { // No error, as not a valid unit. die; } // #### Check that the quiz is valid and belongs to this unit $quizDetails = WPCW_quizzes_getQuizDetails($quizID, true); if (!($quizDetails && $quizDetails->parent_unit_id == $unitID)) { die; } // Validate the quiz answers... which means we might have to // send back the form to be re-filled. $canContinue = WPCW_quizzes_handleQuizRendering_canUserContinueAfterQuiz($quizDetails, $_POST, $user_id); // Check that user is allowed to progress. if ($canContinue) { WPCW_units_saveUserProgress_Complete($user_id, $unitID, 'complete'); // Unit complete, check if course/module is complete too. do_action('wpcw_user_completed_unit', $user_id, $unitID, $parentData); // Only complete if allowed to continue. echo WPCW_units_getCompletionBox_complete($parentData, $unitID, $user_id); } die; }
/** * Function that handles the export of the survey responses for a specified survey. */ function WPCW_data_export_quizSurveyData() { $quizID = trim(WPCW_arrays_getValue($_GET, 'quiz_id')) + 0; $quizDetails = WPCW_quizzes_getQuizDetails($quizID, true, false, false); // Check that we can find the survey. if (!$quizDetails) { printf('<div class="error updated"><p>%s</p></div>', __('Sorry, could not find that survey to export the response data.', 'wp_courseware')); return; } // Ensure it's a survey if ('survey' != $quizDetails->quiz_type) { printf('<div class="error updated"><p>%s</p></div>', __('Sorry, but the selected item is not a survey, it\'s a quiz.', 'wp_courseware')); return; } // Does this survey contain random questions? If so, then we need to get the full question data // of all possible questions if (WPCW_quizzes_doesQuizContainRandomQuestions($quizDetails)) { $quizDetails->questions = WPCW_quizzes_randomQuestions_fullyExpand($quizDetails); } global $wpcwdb, $wpdb; $wpdb->show_errors(); // Create a URL-safe version of the filename. $csvFileName = WPCW_urls_createCleanURL('survey-' . $quizDetails->quiz_id . '-' . $quizDetails->quiz_title) . '.csv'; WPCW_data_export_sendHeaders_CSV($csvFileName); // The headings $headings = array(__('Trainee WP ID', 'wp_courseware'), __('Trainee Name', 'wp_courseware'), __('Trainee Email Address', 'wp_courseware')); // Extract the questions to use as headings. $questionListForColumns = array(); // See if we have any questions in the list. if (!empty($quizDetails->questions)) { foreach ($quizDetails->questions as $questionID => $questionDetails) { $questionListForColumns[$questionID] = $questionDetails->question_question; // Add this question to the headings. $headings[] = $questionDetails->question_question; } } // Start CSV $out = fopen('php://output', 'w'); // Push out the question headings. fputcsv($out, $headings); // The responses to the questions $answers = $wpdb->get_results($wpdb->prepare("\n\t\tSELECT * \n\t\tFROM {$wpcwdb->user_progress_quiz}\n\t\tWHERE quiz_id = %d\n\t", $quizDetails->quiz_id)); // Process eacy response from the user, extracting their details too. if (!empty($answers)) { foreach ($answers as $answerDetails) { $resultData = array(); // We've definitely got the ID $resultData[] = $answerDetails->user_id; // See if we can get the name and email address. $userDetails = get_userdata($answerDetails->user_id); if ($userDetails) { $resultData[] = $userDetails->display_name; $resultData[] = $userDetails->user_email; } else { $resultData[] = __('User no longer on system.', 'wp_courseware'); $resultData[] = __('n/a', 'wp_courseware'); } // Extract their responses into an array $theirResponses = maybe_unserialize($answerDetails->quiz_data); // Go through answers logically now if (!empty($questionListForColumns)) { foreach ($questionListForColumns as $questionID => $questionTitle) { if (isset($theirResponses[$questionID]) && isset($theirResponses[$questionID]['their_answer'])) { $resultData[] = $theirResponses[$questionID]['their_answer']; } else { $resultData[] = __('No answer for this question.', 'wp_courseware'); } } } // end of !empty check fputcsv($out, $resultData); } // end foreach } // end of if (!empty($answers)) // All done fclose($out); die; }
/** * Shows a detailed summary of the user's quiz or survey answers. */ function WPCW_showPage_UserProgess_quizAnswers_load() { global $wpcwdb, $wpdb; $wpdb->show_errors(); $page = new PageBuilder(false); $page->showPageHeader(__('Detailed User Quiz/Survey Results', 'wp_courseware'), '75%', WPCW_icon_getPageIconURL()); $userID = WPCW_arrays_getValue($_GET, 'user_id') + 0; $unitID = WPCW_arrays_getValue($_GET, 'unit_id') + 0; $quizID = WPCW_arrays_getValue($_GET, 'quiz_id') + 0; // Create a link back to the detailed user progress, and back to all users. printf('<div class="wpcw_button_group">'); // Link back to all user summary printf('<a href="%s" class="button-secondary">%s</a> ', admin_url('users.php'), __('« Return to User Summary', 'wp_courseware')); if ($userDetails = get_userdata($userID)) { // Link back to user's personal summary printf('<a href="%s&user_id=%d" class="button-secondary">%s</a> ', admin_url('users.php?page=WPCW_showPage_UserProgess'), $userDetails->ID, sprintf(__('« Return to <b>%s\'s</b> Progress Report', 'wp_courseware'), $userDetails->display_name)); } // Try to get the full detailed results. $results = WPCW_quizzes_getUserResultsForQuiz($userID, $unitID, $quizID); // No results, so abort. if (!$results) { // Close the button wrapper for above early printf('</div>'); // .wpcw_button_group $page->showMessage(__('Sorry, but no results could be found.', 'wp_courseware'), true); $page->showPageFooter(); return; } // Could potentially have an issue where the quiz has been deleted // but the data exists.. small chance though. $quizDetails = WPCW_quizzes_getQuizDetails($quizID, true, true, $userID); // Extra button - return to gradebook printf('<a href="%s&course_id=%d" class="button-secondary">%s</a> ', admin_url('admin.php?page=WPCW_showPage_GradeBook'), $quizDetails->parent_course_id, __("« Return to Gradebook", 'wp_courseware')); printf('</div>'); // .wpcw_button_group // #### 1 - Handle grades being updated $results = WPCW_showPage_UserProgess_quizAnswers_handingGrading($quizDetails, $results, $page, $userID, $unitID); // #### 2A - Check if next action for user has been triggered by the admin. $results = WPCW_showPage_UserProgess_quizAnswers_whatsNext_savePreferences($quizDetails, $results, $page, $userID, $unitID); // #### 2B - Handle telling admin what's next WPCW_showPage_UserProgess_quizAnswers_whatsNext($quizDetails, $results, $page, $userID, $unitID); //Ê#### 3 - Handle sending emails if something has changed. if (isset($results->sendOutEmails) && $results->sendOutEmails) { $extraDetail = isset($results->extraEmailDetail) ? $results->extraEmailDetail : ''; // Only called if the quiz was graded. if (isset($results->quiz_has_just_been_graded) && $results->quiz_has_just_been_graded) { // Need to call the action anyway, but any functions hanging off this // should check if the admin wants users to have notifications or not. do_action('wpcw_quiz_graded', $userID, $quizDetails, number_format($results->quiz_grade, 1), $extraDetail); } $courseDetails = WPCW_courses_getCourseDetails($quizDetails->parent_course_id); if ($courseDetails->email_quiz_grade_option == 'send_email') { // Message is only if quiz has been graded. if (isset($results->quiz_has_just_been_graded) && $results->quiz_has_just_been_graded) { $page->showMessage(__('The user has been sent an email with their grade for this course.', 'wp_courseware')); } } } // #### - Table 1 - Overview printf('<h3>%s</h3>', __('Quiz/Survey Overview', 'wp_courseware')); $tbl = new TableBuilder(); $tbl->attributes = array('id' => 'wpcw_tbl_progress_quiz_info', 'class' => 'widefat wpcw_tbl'); $tblCol = new TableColumn(false, 'quiz_label'); $tblCol->cellClass = 'wpcw_tbl_label'; $tbl->addColumn($tblCol); $tblCol = new TableColumn(false, 'quiz_detail'); $tbl->addColumn($tblCol); // These are the base details for the quiz to show. $summaryData = array(__('Quiz Title', 'wp_courseware') => $quizDetails->quiz_title, __('Quiz Description', 'wp_courseware') => $quizDetails->quiz_desc, __('Quiz Type', 'wp_courseware') => WPCW_quizzes_getQuizTypeName($quizDetails->quiz_type), __('No. of Questions', 'wp_courseware') => $results->quiz_question_total, __('Completed Date', 'wp_courseware') => __('About', 'wp_courseware') . ' ' . human_time_diff($results->quiz_completed_date_ts) . ' ' . __('ago', 'wp_courseware') . '<br/><small>(' . date('D jS M Y \\a\\t H:i:s', $results->quiz_completed_date_ts) . ')</small>', __('Number of Quiz Attempts', 'wp_courseware') => $results->attempt_count, __('Permitted Quiz Attempts', 'wp_courseware') => -1 == $quizDetails->quiz_attempts_allowed ? __('Unlimited', 'wp_courseware') : $quizDetails->quiz_attempts_allowed); // Quiz details relating to score, etc. if ('survey' != $quizDetails->quiz_type) { $summaryData[__('Pass Mark', 'wp_courseware')] = $quizDetails->quiz_pass_mark . '%'; // Still got items to grade if ($results->quiz_needs_marking > 0) { $summaryData[__('No. of Questions to Grade', 'wp_courseware')] = '<span class="wpcw_status_info wpcw_icon_pending">' . $results->quiz_needs_marking . '</span>'; $summaryData[__('Overall Grade', 'wp_courseware')] = '<span class="wpcw_status_info wpcw_icon_pending">' . __('Awaiting Final Grading', 'wp_courseware') . '</span>'; } else { $summaryData[__('No. of Question to Grade', 'wp_courseware')] = '-'; // Show if PASSED or FAILED with the overall grade. $gradeData = false; if ($results->quiz_grade >= $quizDetails->quiz_pass_mark) { $gradeData = sprintf('<span class="wpcw_tbl_progress_quiz_overall wpcw_question_yesno_status wpcw_question_yes">%s%% %s</span>', number_format($results->quiz_grade, 1), __('Passed', 'wp_courseware')); } else { $gradeData = sprintf('<span class="wpcw_tbl_progress_quiz_overall wpcw_question_yesno_status wpcw_question_no">%s%% %s</span>', number_format($results->quiz_grade, 1), __('Failed', 'wp_courseware')); } $summaryData[__('Overall Grade', 'wp_courseware')] = $gradeData; } } foreach ($summaryData as $label => $data) { $tbl->addRow(array('quiz_label' => $label . ':', 'quiz_detail' => $data)); } echo $tbl->toString(); // ### 4 - Form Code - to allow instructor to send data back to printf('<form method="POST" id="wpcw_tbl_progress_quiz_grading_form">'); printf('<input type="hidden" name="grade_answers_submitted" value="true">'); // ### 5 - Table 2 - Each Specific Quiz $questionNumber = 0; if ($results->quiz_data && count($results->quiz_data) > 0) { foreach ($results->quiz_data as $questionID => $answer) { $data = $answer; // Get the question type if (isset($quizDetails->questions[$questionID])) { // Store as object for easy reference. $quObj = $quizDetails->questions[$questionID]; // Render the question as a table. printf('<h3>%s #%d - %s</h3>', __('Question', 'wp_courseware'), ++$questionNumber, $quObj->question_question); $tbl = new TableBuilder(); $tbl->attributes = array('id' => 'wpcw_tbl_progress_quiz_info', 'class' => 'widefat wpcw_tbl wpcw_tbl_progress_quiz_answers_' . $quObj->question_type); $tblCol = new TableColumn(false, 'quiz_label'); $tblCol->cellClass = 'wpcw_tbl_label'; $tbl->addColumn($tblCol); $tblCol = new TableColumn(false, 'quiz_detail'); $tbl->addColumn($tblCol); $theirAnswer = false; switch ($quObj->question_type) { case 'truefalse': case 'multi': $theirAnswer = $answer['their_answer']; break; // File Upload - create a download link // File Upload - create a download link case 'upload': $theirAnswer = sprintf('<a href="%s%s" target="_blank" class="button-primary">%s .%s %s (%s)</a>', WP_CONTENT_URL, $answer['their_answer'], __('Open', 'wp_courseware'), pathinfo($answer['their_answer'], PATHINFO_EXTENSION), __('File', 'wp_courseware'), WPCW_files_getFileSize_human($answer['their_answer'])); break; // Open Ended - Wrap in span tags, to cap the size of the field, and format new lines. // Open Ended - Wrap in span tags, to cap the size of the field, and format new lines. case 'open': $theirAnswer = '<span class="wpcw_q_answer_open_wrap"><textarea readonly>' . $data['their_answer'] . '</textarea></span>'; break; } // end of $theirAnswer check $summaryData = array(__('Type', 'wp_courseware') => array('data' => WPCW_quizzes_getQuestionTypeName($quObj->question_type), 'cssclass' => ''), __('Their Answer', 'wp_courseware') => array('data' => $theirAnswer, 'cssclass' => '')); // Just for quizzes - show answers/grade if ('survey' != $quizDetails->quiz_type) { switch ($quObj->question_type) { case 'truefalse': case 'multi': // The right answer... $summaryData[__('Correct Answer', 'wp_courseware')] = array('data' => $answer['correct'], 'cssclass' => ''); // Did they get it right? $getItRight = sprintf('<span class="wpcw_question_yesno_status wpcw_question_%s">%s</span>', $answer['got_right'], 'yes' == $answer['got_right'] ? __('Yes', 'wp_courseware') : __('No', 'wp_courseware')); $summaryData[__('Did they get it right?', 'wp_courseware')] = array('data' => $getItRight, 'cssclass' => ''); break; case 'upload': case 'open': $gradeHTML = false; $theirGrade = WPCW_arrays_getValue($answer, 'their_grade'); // Not graded - show select box. if ($theirGrade == 0) { $cssClass = 'wpcw_grade_needs_grading'; } else { $cssClass = 'wpcw_grade_already_graded'; $gradeHTML = sprintf('<span class="wpcw_grade_view">%d%% <a href="#">(%s)</a></span>', $theirGrade, __('Click to edit', 'wp_courseware')); } // Not graded yet, allow admin to grade the quiz, or change // the grading later if they want to. $gradeHTML .= WPCW_forms_createDropdown('grade_quiz_' . $quObj->question_id, WPCW_quizzes_getPercentageList(__('-- Select a grade --', 'wp_courseware')), $theirGrade, false, 'wpcw_tbl_progress_quiz_answers_grade'); $summaryData[__('Their Grade', 'wp_courseware')] = array('data' => $gradeHTML, 'cssclass' => $cssClass); break; } } // Check of showing the right answer. foreach ($summaryData as $label => $data) { $tbl->addRow(array('quiz_label' => $label . ':', 'quiz_detail' => $data['data']), $data['cssclass']); } echo $tbl->toString(); } // end if (isset($quizDetails->questions[$questionID])) } // foreach ($results->quiz_data as $questionID => $answer) } printf('</form>'); // Shows a bar that pops up, allowing the user to easily save all grades that have changed. ?> <div id="wpcw_sticky_bar" style="display: none"> <div id="wpcw_sticky_bar_inner"> <a href="#" id="wpcw_tbl_progress_quiz_grading_updated" class="button-primary"><?php _e('Save Changes to Grades', 'wp_courseware'); ?> </a> <span id="wpcw_sticky_bar_status" title="<?php _e('Grades have been changed. Ready to save changes?', 'wp_courseware'); ?> "></span> </div> </div> <br/><br/><br/><br/> <?php $page->showPageFooter(); }
/** * Get the associated quiz for a unit. * * @param Integer $unitID The ID of the unit to get the associated quiz for. * @param Boolean $resolveRandomQuestions If true, convert any randomised questions to live questions. * @param Integer $userID The ID of the user to resolve the questions to. * * @return Object The Object of the associated quiz, or false if no quiz found. */ function WPCW_quizzes_getAssociatedQuizForUnit($unitID, $resolveRandomQuestions = false, $userID) { if (!$unitID) { return false; } global $wpcwdb, $wpdb; $wpdb->show_errors(); $SQL = $wpdb->prepare("\n\t\t\tSELECT quiz_id \n\t\t\tFROM {$wpcwdb->quiz}\t\n\t\t\tWHERE parent_unit_id = %d \n\t\t\t", $unitID); $quizObj = $wpdb->get_row($SQL); // Nothing found if (!$quizObj) { return false; } // Return full details for this quiz return WPCW_quizzes_getQuizDetails($quizObj->quiz_id, true, $resolveRandomQuestions, $userID); }
/** * Handle the quiz deletion from the summary page. * @param PageBuilder $page The page rendering object. */ function WPCW_quizzes_handleQuizDeletion($page) { global $wpcwdb, $wpdb; $wpdb->show_errors(); // Check that the quiz exists and deletion has been requested if (isset($_GET['action']) && $_GET['action'] == 'delete' && isset($_GET['quiz_id'])) { $quizID = $_GET['quiz_id']; $quizDetails = WPCW_quizzes_getQuizDetails($quizID, false, false, false); // Only do deletion if quiz details are valid. if ($quizDetails) { // Delete quiz questions from question map $wpdb->query($wpdb->prepare("\n\t\t\t\tDELETE FROM {$wpcwdb->quiz_qs_mapping}\n\t\t\t\tWHERE parent_quiz_id = %d\n\t\t\t", $quizDetails->quiz_id)); // Delete user progress $wpdb->query($wpdb->prepare("\n\t\t\t\tDELETE FROM {$wpcwdb->user_progress_quiz} \n\t\t\t\tWHERE quiz_id = %d\n\t\t\t", $quizDetails->quiz_id)); // Finally delete quiz itself $wpdb->query($wpdb->prepare("\n\t\t\t\tDELETE FROM {$wpcwdb->quiz} \n\t\t\t\tWHERE quiz_id = %d\n\t\t\t", $quizDetails->quiz_id)); $page->showMessage(sprintf(__('The quiz \'%s\' was successfully deleted.', 'wp_courseware'), $quizDetails->quiz_title)); } // end of if $quizDetails } // end of check for deletion action }