/** * Test to ensure that event data is being stored correctly. */ public function test_answer_deleted() { global $DB, $USER; // Generate user data. $user = $this->getDataGenerator()->create_user(); $optionids = array_keys($DB->get_records('choice_options', array('choiceid' => $this->choice->id))); // Create the first answer. choice_user_submit_response($optionids[2], $this->choice, $user->id, $this->course, $this->cm); // Get the users response. $answer = $DB->get_record('choice_answers', array('userid' => $user->id, 'choiceid' => $this->choice->id), '*', $strictness = IGNORE_MULTIPLE); // Redirect event. $sink = $this->redirectEvents(); // Now delete the answer. choice_delete_responses(array($answer->id), $this->choice, $this->cm, $this->course); // Get our event event. $events = $sink->get_events(); $event = reset($events); // Data checking. $this->assertInstanceOf('\\mod_choice\\event\\answer_deleted', $event); $this->assertEquals($USER->id, $event->userid); $this->assertEquals($user->id, $event->relateduserid); $this->assertEquals(context_module::instance($this->choice->cmid), $event->get_context()); $this->assertEquals($this->choice->id, $event->other['choiceid']); $this->assertEquals($answer->optionid, $event->other['optionid']); $this->assertEventContextNotUsed($event); $sink->close(); }
choice_delete_responses($attemptids, $choice, $cm, $course); //delete responses. redirect("view.php?id={$cm->id}"); } // Redirection after all POSTs breaks block editing, we need to be more specific! if ($choice->allowmultiple) { $answer = optional_param_array('answer', array(), PARAM_INT); } else { $answer = optional_param('answer', '', PARAM_INT); } if (!$choiceavailable) { $reason = current(array_keys($warnings)); throw new moodle_exception($reason, 'choice', '', $warnings[$reason]); } if ($answer) { choice_user_submit_response($answer, $choice, $USER->id, $course, $cm); redirect(new moodle_url('/mod/choice/view.php', array('id' => $cm->id, 'notify' => 'choicesaved', 'sesskey' => sesskey()))); } else { if (empty($answer) and $action === 'makechoice') { // We cannot use the 'makechoice' alone because there might be some legacy renderers without it, // outdated renderers will not get the 'mustchoose' message - bad luck. redirect(new moodle_url('/mod/choice/view.php', array('id' => $cm->id, 'notify' => 'mustchooseone', 'sesskey' => sesskey()))); } } } // Completion and trigger events. choice_view($choice, $course, $cm, $context); echo $OUTPUT->header(); echo $OUTPUT->heading(format_string($choice->name), 2, null); if ($notify and confirm_sesskey()) { if ($notify === 'choicesaved') {
/** * Submit choice responses * * @param int $choiceid the choice instance id * @param array $responses the response ids * @return array answers information and warnings * @since Moodle 3.0 */ public static function submit_choice_response($choiceid, $responses) { global $USER; $warnings = array(); $params = self::validate_parameters(self::submit_choice_response_parameters(), array('choiceid' => $choiceid, 'responses' => $responses)); if (!($choice = choice_get_choice($params['choiceid']))) { throw new moodle_exception("invalidcoursemodule", "error"); } list($course, $cm) = get_course_and_cm_from_instance($choice, 'choice'); $context = context_module::instance($cm->id); self::validate_context($context); require_capability('mod/choice:choose', $context); $timenow = time(); if ($choice->timeclose != 0) { if ($choice->timeopen > $timenow) { throw new moodle_exception("notopenyet", "choice", '', userdate($choice->timeopen)); } else { if ($timenow > $choice->timeclose) { throw new moodle_exception("expired", "choice", '', userdate($choice->timeclose)); } } } if (!choice_get_my_response($choice) or $choice->allowupdate) { // When a single response is given, we convert the array to a simple variable // in order to avoid choice_user_submit_response to check with allowmultiple even // for a single response. if (count($params['responses']) == 1) { $params['responses'] = reset($params['responses']); } choice_user_submit_response($params['responses'], $choice, $USER->id, $course, $cm); } else { throw new moodle_exception('missingrequiredcapability', 'webservice', '', 'allowupdate'); } $answers = choice_get_my_response($choice); return array('answers' => $answers, 'warnings' => $warnings); }
/** * Test delete_choice_responses */ public function test_delete_choice_responses() { global $DB; $this->resetAfterTest(true); $course = self::getDataGenerator()->create_course(); $params = new stdClass(); $params->course = $course->id; $params->option = array('fried rice', 'spring rolls', 'sweet and sour pork', 'satay beef', 'gyouza'); $params->name = 'First Choice Activity'; $params->showresults = CHOICE_SHOWRESULTS_ALWAYS; $params->allowmultiple = 1; $params->showunanswered = 1; $choice = self::getDataGenerator()->create_module('choice', $params); $cm = get_coursemodule_from_id('choice', $choice->cmid); $choiceinstance = choice_get_choice($cm->instance); $options = array_keys($choiceinstance->option); $student = $this->getDataGenerator()->create_user(); $studentrole = $DB->get_record('role', array('shortname' => 'student')); // Enroll student in Course1. self::getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id); $this->setUser($student); $results = mod_choice_external::submit_choice_response($choice->id, array($options[1], $options[2])); $results = external_api::clean_returnvalue(mod_choice_external::submit_choice_response_returns(), $results); $myresponses = array_keys(choice_get_my_response($choice)); // Try to delete responses when allow update is false. try { mod_choice_external::delete_choice_responses($choice->id, array($myresponses[0], $myresponses[0])); $this->fail('Exception expected due to missing permissions.'); } catch (required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Set allow update to true, and a passed time close. $DB->set_field('choice', 'allowupdate', 1, array('id' => $choice->id)); $DB->set_field('choice', 'timeclose', time() - DAYSECS, array('id' => $choice->id)); try { mod_choice_external::delete_choice_responses($choice->id, array($myresponses[0], $myresponses[1])); $this->fail('Exception expected due to expired choice.'); } catch (moodle_exception $e) { $this->assertEquals('expired', $e->errorcode); } // Reset time close. We should be able now to delete all the responses. $DB->set_field('choice', 'timeclose', 0, array('id' => $choice->id)); $results = mod_choice_external::delete_choice_responses($choice->id, array($myresponses[0], $myresponses[1])); $results = external_api::clean_returnvalue(mod_choice_external::delete_choice_responses_returns(), $results); $this->assertTrue($results['status']); $this->assertCount(0, $results['warnings']); // Now, in the DB 0 responses. $this->assertCount(0, choice_get_my_response($choice)); // Submit again the responses. $results = mod_choice_external::submit_choice_response($choice->id, array($options[1], $options[2])); $results = external_api::clean_returnvalue(mod_choice_external::submit_choice_response_returns(), $results); $myresponses = array_keys(choice_get_my_response($choice)); // Delete only one response. $results = mod_choice_external::delete_choice_responses($choice->id, array($myresponses[0])); $results = external_api::clean_returnvalue(mod_choice_external::delete_choice_responses_returns(), $results); $this->assertTrue($results['status']); $this->assertCount(0, $results['warnings']); // Now, in the DB 1 response still. $this->assertCount(1, choice_get_my_response($choice)); // Delete the remaining response, passing 2 invalid responses ids. $results = mod_choice_external::delete_choice_responses($choice->id, array($myresponses[1], $myresponses[0] + 2, $myresponses[0] + 3)); $results = external_api::clean_returnvalue(mod_choice_external::delete_choice_responses_returns(), $results); $this->assertTrue($results['status']); // 2 warnings, 2 invalid responses. $this->assertCount(2, $results['warnings']); // Now, in the DB 0 responses. $this->assertCount(0, choice_get_my_response($choice)); // Now, as an admin we must be able to delete all the responses under any condition. // Submit again the responses. $results = mod_choice_external::submit_choice_response($choice->id, array($options[1], $options[2])); $results = external_api::clean_returnvalue(mod_choice_external::submit_choice_response_returns(), $results); $studentresponses = array_keys(choice_get_my_response($choice)); $this->setAdminUser(); $DB->set_field('choice', 'allowupdate', 0, array('id' => $choice->id)); $DB->set_field('choice', 'timeclose', time() - DAYSECS, array('id' => $choice->id)); $results = mod_choice_external::delete_choice_responses($choice->id, array($studentresponses[0], $studentresponses[1])); $results = external_api::clean_returnvalue(mod_choice_external::delete_choice_responses_returns(), $results); $this->assertTrue($results['status']); $this->assertCount(0, $results['warnings']); // Submit again the responses. $DB->set_field('choice', 'timeclose', 0, array('id' => $choice->id)); $results = mod_choice_external::submit_choice_response($choice->id, array($options[1], $options[2])); $results = external_api::clean_returnvalue(mod_choice_external::submit_choice_response_returns(), $results); // With other user account too, so we can test all the responses are deleted. choice_user_submit_response(array($options[1], $options[2]), $choice, $student->id, $course, $cm); // Test deleting all (not passing the answers ids), event not only mine. $results = mod_choice_external::delete_choice_responses($choice->id); $results = external_api::clean_returnvalue(mod_choice_external::delete_choice_responses_returns(), $results); $this->assertTrue($results['status']); $this->assertCount(0, $results['warnings']); $this->assertCount(0, choice_get_all_responses($choice)); // Now, in the DB 0 responses. $this->setUser($student); // Submit again respones. $DB->set_field('choice', 'allowupdate', 1, array('id' => $choice->id)); $DB->set_field('choice', 'timeclose', 0, array('id' => $choice->id)); $results = mod_choice_external::submit_choice_response($choice->id, array($options[1], $options[2])); $results = external_api::clean_returnvalue(mod_choice_external::submit_choice_response_returns(), $results); // Delete all responses. $results = mod_choice_external::delete_choice_responses($choice->id); $results = external_api::clean_returnvalue(mod_choice_external::delete_choice_responses_returns(), $results); $this->assertTrue($results['status']); $this->assertCount(0, $results['warnings']); $this->assertCount(0, choice_get_my_response($choice)); }
/** * Test get_choice_options */ public function test_get_choice_options() { global $DB; // Warningcodes. $notopenyet = 1; $previewonly = 2; $expired = 3; $this->resetAfterTest(true); $timenow = time(); $timeopen = $timenow + (60 * 60 * 24 * 2); $timeclose = $timenow + (60 * 60 * 24 * 7); $course = self::getDataGenerator()->create_course(); $possibleoptions = array('fried rice', 'spring rolls', 'sweet and sour pork', 'satay beef', 'gyouza'); $params = array(); $params['course'] = $course->id; $params['option'] = $possibleoptions; $params['name'] = 'First Choice Activity'; $params['showpreview'] = 0; $generator = $this->getDataGenerator()->get_plugin_generator('mod_choice'); $choice = $generator->create_instance($params); $student1 = $this->getDataGenerator()->create_user(); $studentrole = $DB->get_record('role', array('shortname' => 'student')); // Enroll Students in Course. self::getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id); $this->setUser($student1); $results = mod_choice_external::get_choice_options($choice->id); // We need to execute the return values cleaning process to simulate the web service server. $results = external_api::clean_returnvalue(mod_choice_external::get_choice_options_returns(), $results); // We should retrieve all options. $this->assertCount(count($possibleoptions), $results['options']); // Here we force timeopen/close in the future. $choice->timeopen = $timeopen; $choice->timeclose = $timeclose; $DB->update_record('choice', $choice); $results = mod_choice_external::get_choice_options($choice->id); // We need to execute the return values cleaning process to simulate the web service server. $results = external_api::clean_returnvalue(mod_choice_external::get_choice_options_returns(), $results); // We should retrieve no options. $this->assertCount(0, $results['options']); $this->assertEquals($notopenyet, $results['warnings'][0]['warningcode']); // Here we see the options because of preview! $choice->showpreview = 1; $DB->update_record('choice', $choice); $results = mod_choice_external::get_choice_options($choice->id); // We need to execute the return values cleaning process to simulate the web service server. $results = external_api::clean_returnvalue(mod_choice_external::get_choice_options_returns(), $results); // We should retrieve all options. $this->assertCount(count($possibleoptions), $results['options']); foreach ($results['options'] as $option) { // Each option is disabled as this is only the preview! $this->assertEquals(1, $option['disabled']); } $warnings = array(); foreach ($results['warnings'] as $warning) { $warnings[$warning['warningcode']] = $warning['message']; } $this->assertTrue(isset($warnings[$previewonly])); $this->assertTrue(isset($warnings[$notopenyet])); // Simulate activity as opened! $choice->timeopen = $timenow - (60 * 60 * 24 * 3); $choice->timeclose = $timenow + (60 * 60 * 24 * 2); $DB->update_record('choice', $choice); $cm = get_coursemodule_from_id('choice', $choice->cmid); $choiceinstance = choice_get_choice($cm->instance); $optionsids = array_keys($choiceinstance->option); $myanswerid = $optionsids[2]; choice_user_submit_response($myanswerid, $choice, $student1->id, $course, $cm); $results = mod_choice_external::get_choice_options($choice->id); // We need to execute the return values cleaning process to simulate the web service server. $results = external_api::clean_returnvalue(mod_choice_external::get_choice_options_returns(), $results); // We should retrieve all options. $this->assertCount(count($possibleoptions), $results['options']); foreach ($results['options'] as $option) { // When we answered and we cannot update our choice. if ($option['id'] == $myanswerid and !$choice->allowupdate) { $this->assertEquals(1, $option['disabled']); $this->assertEquals(1, $option['checked']); } else { $this->assertEquals(0, $option['disabled']); } } // Set timeopen and timeclose as older than today! // We simulate what happens when the activity is closed. $choice->timeopen = $timenow - (60 * 60 * 24 * 3); $choice->timeclose = $timenow - (60 * 60 * 24 * 2); $DB->update_record('choice', $choice); $results = mod_choice_external::get_choice_options($choice->id); // We need to execute the return values cleaning process to simulate the web service server. $results = external_api::clean_returnvalue(mod_choice_external::get_choice_options_returns(), $results); // We should retrieve no options. $this->assertCount(0, $results['options']); $this->assertEquals($expired, $results['warnings'][0]['warningcode']); }
/** * Test choice_get_availability_status * @return void */ public function test_choice_get_availability_status() { global $USER; $this->resetAfterTest(); $this->setAdminUser(); // Setup test data. $course = $this->getDataGenerator()->create_course(); $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id)); // No time restrictions and updates allowed. list($status, $warnings) = choice_get_availability_status($choice, false); $this->assertEquals(true, $status); $this->assertCount(0, $warnings); // No updates allowed, but haven't answered yet. $choice->allowupdate = false; list($status, $warnings) = choice_get_availability_status($choice, false); $this->assertEquals(true, $status); $this->assertCount(0, $warnings); // No updates allowed and have answered. $cm = get_coursemodule_from_instance('choice', $choice->id); $choicewithoptions = choice_get_choice($choice->id); $optionids = array_keys($choicewithoptions->option); choice_user_submit_response($optionids[0], $choice, $USER->id, $course, $cm); list($status, $warnings) = choice_get_availability_status($choice, false); $this->assertEquals(false, $status); $this->assertCount(1, $warnings); $this->assertEquals('choicesaved', array_keys($warnings)[0]); $choice->allowupdate = true; // With time restrictions, still open. $choice->timeopen = time() - DAYSECS; $choice->timeclose = time() + DAYSECS; list($status, $warnings) = choice_get_availability_status($choice, false); $this->assertEquals(true, $status); $this->assertCount(0, $warnings); // Choice not open yet. $choice->timeopen = time() + DAYSECS; $choice->timeclose = $choice->timeopen + DAYSECS; list($status, $warnings) = choice_get_availability_status($choice, false); $this->assertEquals(false, $status); $this->assertCount(1, $warnings); $this->assertEquals('notopenyet', array_keys($warnings)[0]); // Choice closed. $choice->timeopen = time() - DAYSECS; $choice->timeclose = time() - 1; list($status, $warnings) = choice_get_availability_status($choice, false); $this->assertEquals(false, $status); $this->assertCount(1, $warnings); $this->assertEquals('expired', array_keys($warnings)[0]); }
/** * Test to ensure that event data is being stored correctly. */ public function test_answer_updated() { // Generate user data. $user = $this->getDataGenerator()->create_user(); // Create the first answer. choice_user_submit_response(2, $this->choice, $user->id, $this->course, $this->cm); // Redirect event. $sink = $this->redirectEvents(); // Now choose a different answer. choice_user_submit_response(3, $this->choice, $user->id, $this->course, $this->cm); $events = $sink->get_events(); // Data checking. $this->assertCount(1, $events); $this->assertInstanceOf('\\mod_choice\\event\\answer_updated', $events[0]); $this->assertEquals($user->id, $events[0]->userid); $this->assertEquals(context_module::instance($this->choice->cmid), $events[0]->get_context()); $this->assertEquals($this->choice->id, $events[0]->other['choiceid']); $this->assertEquals(3, $events[0]->other['optionid']); $expected = array($this->course->id, "choice", "choose again", 'view.php?id=' . $this->cm->id, $this->choice->id, $this->cm->id); $this->assertEventLegacyLogData($expected, $events[0]); $this->assertEventContextNotUsed($events[0]); $sink->close(); }
/** * Test choice_get_my_response * @return void */ public function test_choice_get_my_response() { global $USER; $this->resetAfterTest(); $this->setAdminUser(); // Setup test data. $course = $this->getDataGenerator()->create_course(); $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id)); $context = context_module::instance($choice->cmid); $cm = get_coursemodule_from_instance('choice', $choice->id); $choicewithoptions = choice_get_choice($choice->id); $optionids = array_keys($choicewithoptions->option); choice_user_submit_response($optionids[0], $choice, $USER->id, $course, $cm); $responses = choice_get_my_response($choice, $course, $cm, $context); $this->assertCount(1, $responses); $response = array_shift($responses); $this->assertEquals($optionids[0], $response->optionid); // Multiple responses. $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id, 'allowmultiple' => 1)); $context = context_module::instance($choice->cmid); $cm = get_coursemodule_from_instance('choice', $choice->id); $choicewithoptions = choice_get_choice($choice->id); $optionids = array_keys($choicewithoptions->option); choice_user_submit_response($optionids, $choice, $USER->id, $course, $cm); $responses = choice_get_my_response($choice, $course, $cm, $context); $this->assertCount(count($optionids), $responses); foreach ($responses as $resp) { $this->assertContains($resp->optionid, $optionids); } }
/** * Modifies responses of other users adding the option $newoptionid to them * * @param array $userids list of users to add option to (must be users without any answers yet) * @param array $answerids list of existing attempt ids of users (will be either appended or * substituted with the newoptionid, depending on $choice->allowmultiple) * @param int $newoptionid * @param stdClass $choice choice object, result of {@link choice_get_choice()} * @param stdClass $cm * @param stdClass $course */ function choice_modify_responses($userids, $answerids, $newoptionid, $choice, $cm, $course) { // Get all existing responses and the list of non-respondents. $groupmode = groups_get_activity_groupmode($cm); $onlyactive = $choice->includeinactive ? false : true; $allresponses = choice_get_response_data($choice, $cm, $groupmode, $onlyactive); // Check that the option value is valid. if (!$newoptionid || !isset($choice->option[$newoptionid])) { return; } // First add responses for users who did not make any choice yet. foreach ($userids as $userid) { if (isset($allresponses[0][$userid])) { choice_user_submit_response($newoptionid, $choice, $userid, $course, $cm); } } // Create the list of all options already selected by each user. $optionsbyuser = []; // Mapping userid=>array of chosen choice options. $usersbyanswer = []; // Mapping answerid=>userid (which answer belongs to each user). foreach ($allresponses as $optionid => $responses) { if ($optionid > 0) { foreach ($responses as $userid => $userresponse) { $optionsbyuser += [$userid => []]; $optionsbyuser[$userid][] = $optionid; $usersbyanswer[$userresponse->answerid] = $userid; } } } // Go through the list of submitted attemptids and find which users answers need to be updated. foreach ($answerids as $answerid) { if (isset($usersbyanswer[$answerid])) { $userid = $usersbyanswer[$answerid]; if (!in_array($newoptionid, $optionsbyuser[$userid])) { $options = $choice->allowmultiple ? array_merge($optionsbyuser[$userid], [$newoptionid]) : $newoptionid; choice_user_submit_response($options, $choice, $userid, $course, $cm); } } } }