/** * Selects the next question base appropriate for the quiz attempt passed * @param Model_Quiz_QuizAttempt $vQuizAttempt * @param boolean $debug Being Phased out TODO * @throws Exception * @return Model_Quiz_QuestionBase|NULL NULL Should only be returned in exceptional circumstances */ public static function select_next_question($vQuizAttempt, $debug = false) { //Get all the concepts this quiz tests $vQuiz = $vQuizAttempt->getQuiz(); $vTestedConcepts = $vQuiz->getTestedConcepts(); if (sizeof($vTestedConcepts) < 1) { throw new Exception("This quiz is not ready yet - reason: No testedConcepts specified."); } //Foreach concept tested, see how many questions we've attempted foreach ($vTestedConcepts as $vTestedConcept) { Model_Shell_Debug::getInstance()->log("Looking at TestedConcept: " . $vTestedConcept->getConcept()->getConcept_name()); $vQuestionAttempts = Model_Quiz_QuestionAttempt::getAllFromQuizAttemptAndConcept($vQuizAttempt, $vTestedConcept->getConcept()); Model_Shell_Debug::getInstance()->log("Done " . sizeof($vQuestionAttempts) . "/" . $vTestedConcept->getNumber_tested()); //If we haven't attempted enough in this concept, we need to choose a question difficulty if (sizeof($vQuestionAttempts) < $vTestedConcept->getNumber_tested()) { //What difficulties are there being tested within this concept? $vLowest = $vTestedConcept->getLower_difficulty(); $vHighest = $vTestedConcept->getHigher_difficulty(); //Find out how many questions I should allocate for each difficulty $mQuestionsPerDifficultyLevel = floor(0.8 / ($vHighest + 1 - $vLowest) * $vTestedConcept->getNumber_tested()); $mBuffer = $vTestedConcept->getNumber_tested() - ($vHighest + 1 - $vLowest) * $mQuestionsPerDifficultyLevel; //If there's only one difficulty, that settles that if ($vLowest == $vHighest) { return Model_Shell_QuestionChooser::select_next_question_2($vQuizAttempt, $vTestedConcept->getConcept(), $vLowest); } //Were there any previous attempts at this concept? If not, we need to start a Q at the lowest specified difficulty $vQuestionAttempts = $vQuizAttempt->getQuestionAttempts($vTestedConcept->getConcept()); //echo "Size of vQuestionAttempts:".sizeof($vQuestionAttempts)."<br/>"; if (sizeof($vQuestionAttempts) == 0) { return Model_Shell_QuestionChooser::select_next_question_2($vQuizAttempt, $vTestedConcept->getConcept(), $vLowest); } //Find the highest attempted difficulty done so far $vHighestSoFar = $vQuizAttempt->getHighestDifficultyTestedSoFar(); //echo "The highest difficulty so far: $vHighestSoFar<br/>"; if ($vHighestSoFar >= $vHighest) { //Already at the highest difficulty, the next question will be at this difficulty too return Model_Shell_QuestionChooser::select_next_question_2($vQuizAttempt, $vTestedConcept->getConcept(), $vHighest); } //Look at the last attempts in this difficulty. Were they below-par (use a percentage?)? $vTotal = 0; $vTotalRight = 0; Model_Shell_Debug::getInstance()->log("Checking your previous attempts...Size of vQuestionAttempts:" . sizeof($vQuestionAttempts)); foreach ($vQuestionAttempts as $vQA) { $vQB = $vQA->getQuestion_base(); //echo "Looking at " . $vQB->getDifficulty() . " vs $vHighestSoFar<br/>"; if ($vQB->getDifficulty() == $vHighestSoFar) { $vTotal++; if ($vQB->getEstimated_time() > $vQA->getTime_finished() - $vQA->getTime_started()) { //You did this question in a reasonable time... did you get it right first time? if ($vQA->getInitial_result() == "1") { $vTotalRight++; } } } } //Note, a divide by 0 happened here before... if ($vTotal == 0 || $vTotalRight / $vTotal > 0.8) { //OK. If they WERE good attempts, I should move on if I've hit my quota for this difficulty if ($vTotal >= $mQuestionsPerDifficultyLevel) { //Reached our minumum quota for this difficulty level. NEXT! return Model_Shell_QuestionChooser::select_next_question_2($vQuizAttempt, $vTestedConcept->getConcept(), $vHighestSoFar + 1); } else { //We've done well so far, but we need to do more in this difficulty return Model_Shell_QuestionChooser::select_next_question_2($vQuizAttempt, $vTestedConcept->getConcept(), $vHighestSoFar); } } else { //Your previous attempts weren't all that good. //Figure out how much buffer we have left. Can we afford to give you a question at $vHighestSoFar? Or do we have to move onto something harder? $mBuffer = $vTestedConcept->getNumber_tested() - ($vHighest - $vHighestSoFar) * $mQuestionsPerDifficultyLevel - sizeof($vQuestionAttempts); if ($mBuffer > 1) { //We can afford to give you another easier question return Model_Shell_QuestionChooser::select_next_question_2($vQuizAttempt, $vTestedConcept->getConcept(), $vHighestSoFar); } else { //Sorry.. buffer is dry. You weren't going too well, but you gotta do the harder stuff return Model_Shell_QuestionChooser::select_next_question_2($vQuizAttempt, $vTestedConcept->getConcept(), $vHighestSoFar + 1); } } } //End If } //End Foreach testedConcept //We shouldn't really ever get here, but in case we do -> null return null; }
/** * Shows Pass/Fail for a given class [Group] */ public function passfailAction() { $group = $this->_getParam("group"); $this->view->group = $group; // Pass ALL the groups to the view (to select) $all_groups = array(); $all_quizzes = Model_Quiz_Quiz::getAll(); foreach ($all_quizzes as $quiz) { $all_groups[] = $quiz->getPermissions_group(); } $all_groups = array_unique($all_groups); sort($all_groups); $this->view->all_groups = $all_groups; // If we've SELECTED a group... if (!is_null($group)) { $group = strtolower($group); // Group Members $members = Model_Auth_ActiveDirectory::getUsersFromGroup($group); $keyed_members = array(); foreach ($members as $member) { $username = $member; $member = Model_Auth_ActiveDirectory::getUserDetails($username); $member['username'] = strtolower($username); $keyed_members[$username] = $member; } // Find all Quizzes that are part of this group $all_quizzes = Model_Quiz_Quiz::getAll(); $valid_quizzes = array(); foreach ($all_quizzes as $quiz) { if (strtolower($quiz->getPermissions_group()) == $group) { $valid_quizzes[] = $quiz; } } // Now go and find all the results for each quiz $quiz_results = array(); // Key is the quiz ID foreach ($valid_quizzes as $quiz) { $set_result = array(); foreach ($keyed_members as $member) { //Did they pass? $highest_result = Model_Quiz_QuizAttempt::getHighestMarkQuiz($member['username'], $quiz); if (is_null($highest_result)) { $set_result[$member['username']] = "NA"; } else { if ($highest_result->getTotal_score() / $quiz->getTotalQuestions() * 100 >= $quiz->getPercentage_pass()) { $set_result[$member['username']] = "<span class='green'>P</span>"; } else { $set_result[$member['username']] = "<span class='red'>F</span>"; } } } $quiz_results[$quiz->getID()] = $set_result; } // Pass all info to the view $this->view->members = $keyed_members; $this->view->quizzes = $valid_quizzes; $this->view->quiz_results = $quiz_results; } }
/** * This action is the quiz shell. * It ensures permissions, creates new attempts etc etc. * * @author Ben Evans */ public function attemptAction() { Model_Shell_Debug::getInstance()->log("User Entered the Attempt Action"); $identity = Zend_Auth::getInstance()->getIdentity(); $username = $identity->username; $auth_model = Model_Auth_General::getAuthModel(); /* Before we do anything, test to make sure we've passed a VALID QUIZ which WE ARE ENTITLED to sit. */ $quiz_id = $this->_getParam("quiz"); if (is_null($quiz_id)) { throw new Exception("No quiz was passed. Cannot continue."); } $vQuiz = Model_Quiz_Quiz::fromID($quiz_id); if ($vQuiz == null) { throw new Exception("Quiz ID passed was invalid. Cannot continue."); } $mFinished = false; $mMarking = false; //Permissions if ($auth_model->userInGroup($username, $vQuiz->getPermissions_group()) && $vQuiz->getOpen_date() <= strtotime("now")) { //Have we run out of attempts? $vAttempts = Model_Quiz_QuizAttempt::getAllFromUser($username, $vQuiz); if (sizeof($vAttempts) >= $vQuiz->getMax_attempts()) { //It is possible that we're on our last attempt, and that it's "in progress"...check $bInProgress = false; foreach ($vAttempts as $vAttempt) { if ($vAttempt->getDate_finished() == null) { $bInProgress = true; } } if (!$bInProgress) { throw new Exception("You've exceeded your maximum attempts for this quiz. Cannot continue"); } } } else { if (!$this->view->is_admin) { throw new Exception("Insufficient Permissions to take this quiz / Quiz not open yet"); } $vAttempts = Model_Quiz_QuizAttempt::getAllFromUser($username, $vQuiz); } /* Ok. We're allowed to TAKE the quiz. Are we resuming, or starting a new one? */ $mQuizAttempt = null; if (is_array($vAttempts)) { foreach ($vAttempts as $vAttempt) { if ($vAttempt->getDate_finished() == null) { $mQuizAttempt = $vAttempt; break; } } //End Foreach } //End If if ($mQuizAttempt == null) { $mQuizAttempt = Model_Quiz_QuizAttempt::fromScratch(strtotime("now"), $vQuiz, $username); } /* Calculate the total questions needed for this quiz */ $vTCs = $vQuiz->getTestedConcepts(); $vTotalQuestions = 0; foreach ($vTCs as $vTC) { $vTotalQuestions = $vTotalQuestions + $vTC->getNumber_tested(); } /* We have our quizAttempt ready to go. Now we look to see if we're resuming a question or not */ $mQuestionAttempt = $mQuizAttempt->getLastIncompleteQuestion(); if (is_object($mQuestionAttempt) && !$mQuestionAttempt->isValid()) { $mQuestionAttempt->destroy(); // Remove the Question attempt (Database was reinitialised or something) $mQuestionAttempt = null; } if ($mQuestionAttempt != null) { /* Are we getting an ANSWER for this question? */ if (array_key_exists("marking", $_POST) && $_POST['marking'] == "1") { /* Mark it */ $mMarking = true; } /* If we reach here, the page has probably been refreshed. We just re-display the last question */ } else { /* Have we finished this quiz? */ if ($mQuizAttempt->getQuestionAttemptCount() >= $vTotalQuestions) { //Close this attempt and display a result later on down the page $mQuizAttempt->setDate_finished(strtotime("now")); //Calculate and store the final score $mQuizAttempt->setTotal_score($mQuizAttempt->getTotal_score()); $mFinished = true; } else { /* QuizAttempt isn't finished... Fetch a questionBase */ $vQuestionBase = Model_Shell_QuestionChooser::select_next_question($mQuizAttempt, true); /* Make a GeneratedQuestion */ $vCounter = 0; //Make sure we don't get any fluke no-text answers while ($vCounter < 3) { Model_Shell_Debug::getInstance()->log("vQuestionBase: " . isset($vQuestionBase)); Model_Shell_Debug::getInstance()->log("Generating... from " . $vQuestionBase->getXml()); $vGen = Model_Quiz_GeneratedQuestion::fromQuestionBase($vQuestionBase); if ($vGen->getCorrect_answer() != "" && $vGen->getCorrect_answer() != "\r\n") { break; } else { $vGen->remove(); } $vCounter++; } if ($vGen->getCorrect_answer() == "" || $vGen->getCorrect_answer() == "\r\n") { throw new Exception("Error. While generating a question for you, blank answers appeared > 3 times. This should never happen. Either try to refresh this page, or consult your lecturer..."); } /* Make a QuestionAttempt */ $mQuestionAttempt = Model_Quiz_QuestionAttempt::fromScratch($vQuestionBase, strtotime("now"), strtotime("now"), $mQuizAttempt, $vGen); } //End-if_finished_quizAttempt } // Pass all relevant information to the view $this->view->quiz = $vQuiz; $this->view->question_attempt = $mQuestionAttempt; $this->view->finished = $mFinished; $this->view->marking = $mMarking; $this->view->mQuizAttempt = $mQuizAttempt; $this->view->vTotalQuestions = $vTotalQuestions; }
/** * This action is the quiz shell. * It ensures permissions, creates new attempts etc etc. * * @author Ben Evans */ public function attemptAction() { Model_Shell_Debug::getInstance()->log("User Entered the Attempt Action"); $identity = Zend_Auth::getInstance()->getIdentity(); $username = $identity->username; $auth_model = Model_Auth_General::getAuthModel(); /* Before we do anything, test to make sure we've passed a VALID QUIZ which WE ARE ENTITLED to sit. */ $quiz = $this->findQuiz($this->_getParam("quiz")); $finished = false; $marking = false; $now = strtotime("now"); // Permissions $is_open = $quiz->getOpen_date() <= $now; if ($auth_model->userInGroup($username, $quiz->getPermissions_group()) && $is_open && !$quiz->hasPendingPrerequisite($username)) { // Have we run out of attempts? $quizAttempts = Model_Quiz_QuizAttempt::getAllFromUser($username, $quiz); if (sizeof($quizAttempts) >= $quiz->getMax_attempts()) { // It is possible that we're on our last attempt, and that it's "in progress"...check $quizAttempt = $this->findQuizAttemptInProgress($quizAttempts); if (!$quizAttempt) { throw new Exception("You've exceeded your maximum attempts for this quiz. Cannot continue"); } } } else { if (!$this->view->is_admin) { throw new Exception("Insufficient Permissions to take this quiz / Quiz not open yet / Prerequisites incomplete."); } $quizAttempts = Model_Quiz_QuizAttempt::getAllFromUser($username, $quiz); } /* Ok. We're allowed to TAKE the quiz. Are we resuming, or starting a new one? */ $quizAttempt = $this->findQuizAttemptInProgress($quizAttempts); if ($quizAttempt == null) { $quizAttempt = Model_Quiz_QuizAttempt::fromScratch($now, $quiz, $username); } $total_questions = $quiz->getTotalQuestions(); /* We have our quizAttempt ready to go. Now we look to see if we're resuming a question or not */ $questionAttempt = $quizAttempt->getLastIncompleteQuestion(); if (is_object($questionAttempt) && !$questionAttempt->isValid()) { $questionAttempt->destroy(); // Remove the Question attempt (Database was reinitialised or something) $questionAttempt = null; } if ($questionAttempt != null) { /* Are we getting an ANSWER for this question? */ //if (array_key_exists("marking", $_POST) && $_POST['marking'] == "1") { if (1 == $this->getRequest()->getPost('marking')) { /* Mark it */ $marking = true; } My_Logger::log("Marking is {$marking}"); /* If we reach here, the page has probably been refreshed. We just re-display the last question */ } else { /* Have we finished this quiz? */ if ($quizAttempt->getQuestionAttemptCount() >= $total_questions) { // Close this attempt and display a result later on down the page $quizAttempt->setDate_finished($now); // Calculate and store the final score $quizAttempt->setTotal_score($quizAttempt->getTotal_score()); $finished = true; } else { /* Make a QuestionAttempt */ $questionAttempt = $this->makeQuestionAttempt($quizAttempt, $now); } } // Pass all relevant information to the view $this->view->setEscape('htmlentities'); $this->view->quiz = $quiz; $this->view->question_attempt = $questionAttempt; $this->view->finished = $finished; $this->view->marking = $marking; $this->view->mQuizAttempt = $quizAttempt; $this->view->vTotalQuestions = $total_questions; $this->view->setEscape('htmlspecialchars_decode'); }
/** * Creates a new Question Attempt from the parameters passed * @param Model_Quiz_QuestionBase $vQuestionBase * @param int $attempted_on UNIX Timestamp Int * @param int $time_started UNIX Timestamp Int * @param Model_Quiz_QuizAttempt $vQuizAttempt * @param Model_Quiz_GeneratedQuestion $vGeneratedQuestion * @return Model_Quiz_QuestionAttempt|NULL */ public static function fromScratch($vQuestionBase, $attempted_on, $time_started, $vQuizAttempt, $vGeneratedQuestion) { $db = Zend_Registry::get("db"); $sql = "INSERT INTO question_attempt(attempt_id,question_basequestion_id,attempted_on,time_started,quiz_attemptquiz_attempt_id,generated_questionsgenerated_id) VALUES(NULL, " . $db->quote($vQuestionBase->getID()) . ",'" . date("Y-m-d H:i:s", $attempted_on) . "','" . date("Y-m-d H:i:s", $time_started) . "'," . $db->quote($vQuizAttempt->getID()) . "," . $db->quote($vGeneratedQuestion->getID()) . ")"; $db->query($sql); //Now find the appropriate entry in the database // A safe (default) assumption for this is a query that looks for everything you just put in. $sql = "SELECT attempt_id FROM question_attempt WHERE question_basequestion_id=" . $db->quote($vQuestionBase->getID()) . " AND attempted_on='" . date("Y-m-d H:i:s", $attempted_on) . "' AND time_started='" . date("Y-m-d H:i:s", $time_started) . "' AND quiz_attemptquiz_attempt_id=" . $db->quote($vQuizAttempt->getID()) . " AND generated_questionsgenerated_id=" . $db->quote($vGeneratedQuestion->getID()); $result = $db->query($sql); $row = $result->fetch(); if ($row['attempt_id'] != null) { return Model_Quiz_QuestionAttempt::fromID($row['attempt_id']); } else { return null; //Something didn't happen } }
function hasPendingPrerequisite($username) { $prereq = $this->getPrerequisite(); if (!$prereq) { return false; } $prereq_question = Model_Quiz_Quiz::fromID($prereq); // have the user attempted the prerequisites? $attempt = Model_Quiz_QuizAttempt::fromQuizAndUser($prereq, $username); if (!$attempt) { return true; //prerequisite has not even been attempted } // if yes, of all the attempts, has the best score passed the quiz requirement? if ($attempt->getDate_finished()) { $best_attempt = Model_Quiz_QuizAttempt::getHighestMarkQuiz($username, $prereq_question); return !$best_attempt->hasPassedQuiz(); } return true; /* if ($attempt->getDate_finished()==null){ $vQuizStatus = Model_Quiz_Quiz::QUIZ_INPROGRESS; if($vQuiz->getClose_date() < strtotime("now")){ echo "\t\t<td>In Progress (Late)</td>\n"; } else{ echo "\t\t<td>In Progress</td>\n"; } }else{ $vQuizStatus = Model_Quiz_Quiz::QUIZ_COMPLETED; echo "\t\t<td>Completed</td>\n"; } */ }
/** * This shows the results of an individual quiz, * It works by going through all the People in the Quizzes * primary Active Directory group, and then seeing if their * account has an attempt associated with it. */ public function resultsquizAction() { $quiz_id = $this->_getParam("quiz_id"); if (!isset($quiz_id)) { throw new Exception("No Quiz Identifier Passed", 3000); } $quiz = Model_Quiz_Quiz::fromID($quiz_id); if (is_null($quiz) || $quiz === false) { throw new Exception("Invalid Quiz Identifier", 3000); } // Pass the quiz (for general information) $this->view->quiz = $quiz; // Start By Populating an array with the Group information $results = array(); $group_members = Model_Auth_ActiveDirectory::getUsersFromGroup($quiz->getPermissions_group()); foreach ($group_members as $gm) { $results[$gm] = array(); } unset($group_members); // At this point, we have an array with keys being the username foreach ($results as $name => &$result) { // Get the User's First and Last Name $details = Model_Auth_ActiveDirectory::getUserDetails($name); $result['first_name'] = $details['first_name']; $result['last_name'] = $details['last_name']; $result['username'] = $name; //Get the verdict / best score... $vHighest = Model_Quiz_QuizAttempt::getHighestMarkQuiz($name, $quiz); // Will be null if not completed, Model_Quiz_QuizAttempt otherwise if (!is_null($vHighest)) { // Get their finish date $result['completion_date'] = $vHighest->getDate_finished(); //Is this 'highest' attempt still in progress? if ($vHighest->getDate_finished() == null) { $result['verdict'] = "<span class='orange'>In Progress</span>"; } else { // Completed //Did they pass/fail? if ($vHighest->getTotal_score() / $quiz->getTotalQuestions() * 100 >= $quiz->getPercentage_pass()) { $result['verdict'] = "<span class='green'>PASS</span>"; } else { $result['verdict'] = "<span class='red'>FAIL</span>"; } } // Best Score $result['best_score'] = $vHighest->getTotal_score(); // Attempts $result['attempts'] = sizeof(Model_Quiz_QuizAttempt::getAllFromUser($name, $quiz)); } } $this->view->results = $results; }
public static function getHighestMarkQuiz($vUser, $vQuiz) { My_Logger::log("In " . __METHOD__ . " quiz id is " . $vQuiz->getID()); $db = Zend_Registry::get("db"); $result = $db->query("SELECT * FROM quiz_attempt WHERE ad_user_cachesamaccountname=" . $db->quote($vUser) . " AND quizquiz_id=" . $vQuiz->getID() . " ORDER BY total_score DESC"); $row = $result->fetch(); return Model_Quiz_QuizAttempt::fromID($row['quiz_attempt_id']); }
/** * Shows Available Quizzes for the logged in User * * @return void * @author Ben Evans */ public function availableAction() { $this->view->headTitle("Available Quizzes"); $outstanding = $this->_getParam("outstanding"); $vQuizzes = Model_Quiz_Quiz::getAll(true); $vAvailable = array(); $auth_model = Model_Auth_General::getAuthModel(); $identity = Zend_Auth::getInstance()->getIdentity(); /* Make sure you have permission */ foreach ($vQuizzes as $vQuiz) { if ($this->view->is_admin) { $vAvailable[] = $vQuiz; } else { if ($auth_model->userInGroup($this->view->username, $vQuiz->getPermissions_group()) && $vQuiz->getOpen_date() <= strtotime("now")) { $vAvailable[] = $vQuiz; } } } if (isset($outstanding)) { $this->view->title = "Outstanding Quizzes"; $vOutstanding = array(); foreach ($vAvailable as $vQuiz) { $vQuizAttempt = Model_Quiz_QuizAttempt::fromQuizAndUser($vQuiz, $identity->username); if ($vQuizAttempt == null) { $vOutstanding[] = $vQuiz; } else { //Have an end time? if ($vQuizAttempt->getDate_finished() == null) { $vOutstanding[] = $vQuiz; } } } //Make the new 'available quizzes' the quizzes that aren't complete yet $vAvailable = $vOutstanding; } else { $this->view->title = "Available Quizzes"; } $this->view->available = $vAvailable; }
public function getQuizAttempts() { $db = Zend_Registry::get("db"); $vReturn = array(); $result = $db->query("SELECT * FROM quiz_attempt WHERE quizquiz_id=" . $db->quote($this->quiz_id)); $rows = $result->fetchAll(); foreach ($rows as $row) { $vReturn[] = Model_Quiz_QuizAttempt::fromID($row['quiz_attempt_id']); } return $vReturn; }