/** * Test update_activity_completion_status_manually */ public function test_update_activity_completion_status_manually() { global $DB, $CFG; $this->resetAfterTest(true); $CFG->enablecompletion = true; $user = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1)); $data = $this->getDataGenerator()->create_module('data', array('course' => $course->id), array('completion' => 1)); $cm = get_coursemodule_from_id('data', $data->cmid); $studentrole = $DB->get_record('role', array('shortname' => 'student')); $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id); $this->setUser($user); $result = core_completion_external::update_activity_completion_status_manually($data->cmid, true); // We need to execute the return values cleaning process to simulate the web service server. $result = external_api::clean_returnvalue(core_completion_external::update_activity_completion_status_manually_returns(), $result); // Check in DB. $this->assertEquals(1, $DB->get_field('course_modules_completion', 'completionstate', array('coursemoduleid' => $data->cmid))); // Check using the API. $completion = new completion_info($course); $completiondata = $completion->get_data($cm); $this->assertEquals(1, $completiondata->completionstate); $this->assertTrue($result['status']); $result = core_completion_external::update_activity_completion_status_manually($data->cmid, false); // We need to execute the return values cleaning process to simulate the web service server. $result = external_api::clean_returnvalue(core_completion_external::update_activity_completion_status_manually_returns(), $result); $this->assertEquals(0, $DB->get_field('course_modules_completion', 'completionstate', array('coursemoduleid' => $data->cmid))); $completiondata = $completion->get_data($cm); $this->assertEquals(0, $completiondata->completionstate); $this->assertTrue($result['status']); }
/** * Taken from /format/renderer.php * Generate a summary of the activites in a section * * @param stdClass $section The course_section entry from DB * @param stdClass $course the course record from DB * @param array $mods (argument not used) * @return string HTML to output. */ public static function section_activity_summary($section, $course, $mods) { global $CFG; require_once $CFG->libdir . '/completionlib.php'; $modinfo = get_fast_modinfo($course); if (empty($modinfo->sections[$section->section])) { return ''; } // Generate array with count of activities in this section. $sectionmods = array(); $total = 0; $complete = 0; $cancomplete = isloggedin() && !isguestuser(); $completioninfo = new completion_info($course); foreach ($modinfo->sections[$section->section] as $cmid) { $thismod = $modinfo->cms[$cmid]; if ($thismod->uservisible) { if (isset($sectionmods[$thismod->modname])) { $sectionmods[$thismod->modname]['name'] = $thismod->modplural; $sectionmods[$thismod->modname]['count']++; } else { $sectionmods[$thismod->modname]['name'] = $thismod->modfullname; $sectionmods[$thismod->modname]['count'] = 1; } if ($cancomplete && $completioninfo->is_enabled($thismod) != COMPLETION_TRACKING_NONE) { $total++; $completiondata = $completioninfo->get_data($thismod, true); if ($completiondata->completionstate == COMPLETION_COMPLETE || $completiondata->completionstate == COMPLETION_COMPLETE_PASS) { $complete++; } } } } if (empty($sectionmods)) { // No sections. return ''; } // Output section activities summary. $o = ''; $o .= "<div class='section-summary-activities mdl-right'>"; foreach ($sectionmods as $mod) { $o .= "<span class='activity-count'>"; $o .= $mod['name'] . ': ' . $mod['count']; $o .= "</span>"; } $o .= "</div>"; $a = false; // Output section completion data. if ($total > 0) { $a = new stdClass(); $a->complete = $complete; $a->total = $total; $a->percentage = $complete / $total * 100; $o .= "<div class='section-summary-activities mdl-right'>"; $o .= "<span class='activity-count'>" . get_string('progresstotal', 'completion', $a) . "</span>"; $o .= "</div>"; } $retobj = (object) array('output' => $o, 'progress' => $a, 'complete' => $complete, 'total' => $total); return $retobj; }
public function is_available($not, \core_availability\info $info, $grabthelot, $userid) { $modinfo = $info->get_modinfo(); $completion = new \completion_info($modinfo->get_course()); if (!array_key_exists($this->cmid, $modinfo->cms)) { // If the cmid cannot be found, always return false regardless // of the condition or $not state. (Will be displayed in the // information message.) $allow = false; } else { // The completion system caches its own data so no caching needed here. $completiondata = $completion->get_data((object) array('id' => $this->cmid), $grabthelot, $userid, $modinfo); $allow = true; if ($this->expectedcompletion == COMPLETION_COMPLETE) { // Complete also allows the pass, fail states. switch ($completiondata->completionstate) { case COMPLETION_COMPLETE: case COMPLETION_COMPLETE_FAIL: case COMPLETION_COMPLETE_PASS: break; default: $allow = false; } } else { // Other values require exact match. if ($completiondata->completionstate != $this->expectedcompletion) { $allow = false; } } if ($not) { $allow = !$allow; } } return $allow; }
/** * Check if an activity is configured to only show navbuttons when * complete and then check if the activity is complete * @param cm_info $cm the course module for the activity * @return boolean true if the navbuttons should be shown */ function navbuttons_activity_showbuttons($cm) { $modname = $cm->modname; $show = get_config('block_navbuttons', 'activity' . $modname); if ($show === false || $show == NAVBUTTONS_ACTIVITY_ALWAYS) { return true; // No config or 'always show' } if ($show == NAVBUTTONS_ACTIVITY_NEVER) { return false; } if ($show == NAVBUTTONS_ACTIVITY_COMPLETE) { $completion = new completion_info($cm->get_course()); if (!$completion->is_enabled($cm)) { return true; // No completion tracking - show the buttons } $cmcompletion = $completion->get_data($cm); if ($cmcompletion->completionstate == COMPLETION_INCOMPLETE) { return false; } return true; } if (!isloggedin() || isguestuser()) { return true; // Always show the buttons if not logged in } // NAVBUTTONS_ACTIVITY_CUSTOM $funcname = 'navbuttons_mod_' . $modname . '_showbuttons'; if (!function_exists($funcname)) { return true; // Shouldn't have got to here, but allow the buttons anyway } return $funcname($cm); }
/** * Test survey_view * @return void */ public function test_survey_view() { global $CFG; $CFG->enablecompletion = 1; $this->resetAfterTest(); $this->setAdminUser(); // Setup test data. $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1)); $survey = $this->getDataGenerator()->create_module('survey', array('course' => $course->id), array('completion' => 2, 'completionview' => 1)); $context = context_module::instance($survey->cmid); $cm = get_coursemodule_from_instance('survey', $survey->id); // Trigger and capture the event. $sink = $this->redirectEvents(); survey_view($survey, $course, $cm, $context, 'form'); $events = $sink->get_events(); // 2 additional events thanks to completion. $this->assertCount(3, $events); $event = array_shift($events); // Checking that the event contains the expected values. $this->assertInstanceOf('\\mod_survey\\event\\course_module_viewed', $event); $this->assertEquals($context, $event->get_context()); $moodleurl = new \moodle_url('/mod/survey/view.php', array('id' => $cm->id)); $this->assertEquals($moodleurl, $event->get_url()); $this->assertEquals('form', $event->other['viewed']); $this->assertEventContextNotUsed($event); $this->assertNotEmpty($event->get_name()); // Check completion status. $completion = new completion_info($course); $completiondata = $completion->get_data($cm); $this->assertEquals(1, $completiondata->completionstate); }
public function test_glossary_view() { global $CFG; $origcompletion = $CFG->enablecompletion; $CFG->enablecompletion = true; $this->resetAfterTest(true); // Generate all the things. $c1 = $this->getDataGenerator()->create_course(array('enablecompletion' => 1)); $g1 = $this->getDataGenerator()->create_module('glossary', array('course' => $c1->id, 'completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1)); $g2 = $this->getDataGenerator()->create_module('glossary', array('course' => $c1->id, 'completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1)); $u1 = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($u1->id, $c1->id); $modinfo = course_modinfo::instance($c1->id); $cm1 = $modinfo->get_cm($g1->cmid); $cm2 = $modinfo->get_cm($g2->cmid); $ctx1 = $cm1->context; $completion = new completion_info($c1); $this->setUser($u1); // Confirm what we've set up. $this->assertEquals(COMPLETION_NOT_VIEWED, $completion->get_data($cm1, false, $u1->id)->viewed); $this->assertEquals(COMPLETION_INCOMPLETE, $completion->get_data($cm1, false, $u1->id)->completionstate); $this->assertEquals(COMPLETION_NOT_VIEWED, $completion->get_data($cm2, false, $u1->id)->viewed); $this->assertEquals(COMPLETION_INCOMPLETE, $completion->get_data($cm2, false, $u1->id)->completionstate); // Simulate the view call. $sink = $this->redirectEvents(); glossary_view($g1, $c1, $cm1, $ctx1, 'letter'); $events = $sink->get_events(); // Assertions. $this->assertCount(3, $events); $this->assertEquals('\\core\\event\\course_module_completion_updated', $events[0]->eventname); $this->assertEquals('\\core\\event\\course_module_completion_updated', $events[1]->eventname); $this->assertEquals('\\mod_glossary\\event\\course_module_viewed', $events[2]->eventname); $this->assertEquals($g1->id, $events[2]->objectid); $this->assertEquals('letter', $events[2]->other['mode']); $this->assertEquals(COMPLETION_VIEWED, $completion->get_data($cm1, false, $u1->id)->viewed); $this->assertEquals(COMPLETION_COMPLETE, $completion->get_data($cm1, false, $u1->id)->completionstate); $this->assertEquals(COMPLETION_NOT_VIEWED, $completion->get_data($cm2, false, $u1->id)->viewed); $this->assertEquals(COMPLETION_INCOMPLETE, $completion->get_data($cm2, false, $u1->id)->completionstate); // Tear down. $sink->close(); $CFG->enablecompletion = $origcompletion; }
/** * get_activity_tree :: course_modinfo -> integer -> context -> array * @param \course_modinfo $modinfo * @param integer $section_number * @param \context $context * @return array */ function get_activity_tree(\course_modinfo $modinfo, $section_number, \context $context) { $completion_info = new \completion_info($modinfo->get_course()); return F\map(F\filter($modinfo->get_section_info_all(), function (\section_info $section_info) { return $section_info->visible; }), function (\section_info $section_info) use($completion_info, $section_number, $context) { $mods = F\map(F\filter($section_info->modinfo->cms, function (\cm_info $cm_info) use($section_info) { global $CFG; return ($cm_info->uservisible || $CFG->enableavailability && !empty($cm_info->availableinfo)) && (int) $cm_info->sectionnum === (int) $section_info->section; }), function (\cm_info $cm_info) use($completion_info, $context) { global $CFG; $canComplete = $CFG->enablecompletion && isloggedin() && !isguestuser() && (int) $completion_info->is_enabled($cm_info) === COMPLETION_TRACKING_MANUAL; $hasCompleted = false; if ($canComplete) { $completion_data = $completion_info->get_data($cm_info, true); $hasCompleted = F\contains([COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS], (int) $completion_data->completionstate); } return (object) ['id' => (int) $cm_info->id, 'name' => $cm_info->name, 'modname' => $cm_info->modname, 'current' => is_current_mod($cm_info, $context), 'available' => $cm_info->available, 'canComplete' => $canComplete, 'hasCompleted' => $hasCompleted]; }); return (object) ['id' => (int) $section_info->id, 'section' => (int) $section_info->section, 'name' => \get_section_name($section_info->modinfo->courseid, $section_info->section), 'current' => is_current_section($section_info, $section_number, $context), 'activities' => array_values($mods)]; }); }
/** * Renders html for completion box on course page * * If completion is disabled, returns empty string * If completion is automatic, returns an icon of the current completion state * If completion is manual, returns a form (with an icon inside) that allows user to * toggle completion * * @param stdClass $course course object * @param completion_info $completioninfo completion info for the course, it is recommended * to fetch once for all modules in course/section for performance * @param cm_info $mod module to show completion for * @param array $displayoptions display options, not used in core * @return string */ public function course_section_cm_completion($course, &$completioninfo, cm_info $mod, $displayoptions = array()) { global $CFG; $output = ''; if (!empty($displayoptions['hidecompletion']) || !isloggedin() || isguestuser() || !$mod->uservisible) { return $output; } if ($completioninfo === null) { $completioninfo = new completion_info($course); } $completion = $completioninfo->is_enabled($mod); if ($completion == COMPLETION_TRACKING_NONE) { if ($this->page->user_is_editing()) { $output .= html_writer::span(' ', 'filler'); } return $output; } $completiondata = $completioninfo->get_data($mod, true); $completionicon = ''; if ($this->page->user_is_editing()) { switch ($completion) { case COMPLETION_TRACKING_MANUAL: $completionicon = 'manual-enabled'; break; case COMPLETION_TRACKING_AUTOMATIC: $completionicon = 'auto-enabled'; break; } } else { if ($completion == COMPLETION_TRACKING_MANUAL) { switch ($completiondata->completionstate) { case COMPLETION_INCOMPLETE: $completionicon = 'manual-n'; break; case COMPLETION_COMPLETE: $completionicon = 'manual-y'; break; } } else { // Automatic switch ($completiondata->completionstate) { case COMPLETION_INCOMPLETE: $completionicon = 'auto-n'; break; case COMPLETION_COMPLETE: $completionicon = 'auto-y'; break; case COMPLETION_COMPLETE_PASS: $completionicon = 'auto-pass'; break; case COMPLETION_COMPLETE_FAIL: $completionicon = 'auto-fail'; break; } } } if ($completionicon) { $formattedname = $mod->get_formatted_name(); $imgalt = get_string('completion-alt-' . $completionicon, 'completion', $formattedname); if ($this->page->user_is_editing()) { // When editing, the icon is just an image. $completionpixicon = new pix_icon('i/completion-' . $completionicon, $imgalt, '', array('title' => $imgalt, 'class' => 'iconsmall')); $output .= html_writer::tag('span', $this->output->render($completionpixicon), array('class' => 'autocompletion')); } else { if ($completion == COMPLETION_TRACKING_MANUAL) { $imgtitle = get_string('completion-title-' . $completionicon, 'completion', $formattedname); $newstate = $completiondata->completionstate == COMPLETION_COMPLETE ? COMPLETION_INCOMPLETE : COMPLETION_COMPLETE; // In manual mode the icon is a toggle form... // If this completion state is used by the // conditional activities system, we need to turn // off the JS. $extraclass = ''; if (!empty($CFG->enableavailability) && core_availability\info::completion_value_used($course, $mod->id)) { $extraclass = ' preventjs'; } $output .= html_writer::start_tag('form', array('method' => 'post', 'action' => new moodle_url('/course/togglecompletion.php'), 'class' => 'togglecompletion' . $extraclass)); $output .= html_writer::start_tag('div'); $output .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'id', 'value' => $mod->id)); $output .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey())); $output .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'modulename', 'value' => $mod->name)); $output .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'completionstate', 'value' => $newstate)); $output .= html_writer::empty_tag('input', array('type' => 'image', 'src' => $this->output->pix_url('i/completion-' . $completionicon), 'alt' => $imgalt, 'title' => $imgtitle, 'aria-live' => 'polite')); $output .= html_writer::end_tag('div'); $output .= html_writer::end_tag('form'); } else { // In auto mode, the icon is just an image. $completionpixicon = new pix_icon('i/completion-' . $completionicon, $imgalt, '', array('title' => $imgalt)); $output .= html_writer::tag('span', $this->output->render($completionpixicon), array('class' => 'autocompletion')); } } } return $output; }
/** * Performs the synchronisation of grades. * * @return bool|void */ public function execute() { global $DB, $CFG; require_once $CFG->dirroot . '/enrol/lti/ims-blti/OAuth.php'; require_once $CFG->dirroot . '/enrol/lti/ims-blti/OAuthBody.php'; require_once $CFG->dirroot . '/lib/completionlib.php'; require_once $CFG->libdir . '/gradelib.php'; require_once $CFG->dirroot . '/grade/querylib.php'; // Check if the authentication plugin is disabled. if (!is_enabled_auth('lti')) { mtrace('Skipping task - ' . get_string('pluginnotenabled', 'auth', get_string('pluginname', 'auth_lti'))); return true; } // Check if the enrolment plugin is disabled - isn't really necessary as the task should not run if // the plugin is disabled, but there is no harm in making sure core hasn't done something wrong. if (!enrol_is_enabled('lti')) { mtrace('Skipping task - ' . get_string('enrolisdisabled', 'enrol_lti')); return true; } // Get all the enabled tools. if ($tools = \enrol_lti\helper::get_lti_tools(array('status' => ENROL_INSTANCE_ENABLED, 'gradesync' => 1))) { foreach ($tools as $tool) { mtrace("Starting - Grade sync for shared tool '{$tool->id}' for the course '{$tool->courseid}'."); // Variables to keep track of information to display later. $usercount = 0; $sendcount = 0; // We check for all the users - users can access the same tool from different consumers. if ($ltiusers = $DB->get_records('enrol_lti_users', array('toolid' => $tool->id), 'lastaccess DESC')) { $completion = new \completion_info(get_course($tool->courseid)); foreach ($ltiusers as $ltiuser) { $mtracecontent = "for the user '{$ltiuser->userid}' in the tool '{$tool->id}' for the course " . "'{$tool->courseid}'"; $usercount = $usercount + 1; // Check if we do not have a serviceurl - this can happen if the sync process has an unexpected error. if (empty($ltiuser->serviceurl)) { mtrace("Skipping - Empty serviceurl {$mtracecontent}."); continue; } // Check if we do not have a sourceid - this can happen if the sync process has an unexpected error. if (empty($ltiuser->sourceid)) { mtrace("Skipping - Empty sourceid {$mtracecontent}."); continue; } // Need a valid context to continue. if (!($context = \context::instance_by_id($tool->contextid))) { mtrace("Failed - Invalid contextid '{$tool->contextid}' for the tool '{$tool->id}'."); continue; } // Ok, let's get the grade. $grade = false; if ($context->contextlevel == CONTEXT_COURSE) { // Check if the user did not completed the course when it was required. if ($tool->gradesynccompletion && !$completion->is_course_complete($ltiuser->userid)) { mtrace("Skipping - Course not completed {$mtracecontent}."); continue; } // Get the grade. if ($grade = grade_get_course_grade($ltiuser->userid, $tool->courseid)) { $grademax = floatval($grade->item->grademax); $grade = $grade->grade; } } else { if ($context->contextlevel == CONTEXT_MODULE) { $cm = get_coursemodule_from_id(false, $context->instanceid, 0, false, MUST_EXIST); if ($tool->gradesynccompletion) { $data = $completion->get_data($cm, false, $ltiuser->userid); if ($data->completionstate != COMPLETION_COMPLETE_PASS && $data->completionstate != COMPLETION_COMPLETE) { mtrace("Skipping - Activity not completed {$mtracecontent}."); continue; } } $grades = grade_get_grades($cm->course, 'mod', $cm->modname, $cm->instance, $ltiuser->userid); if (!empty($grades->items[0]->grades)) { $grade = reset($grades->items[0]->grades); if (!empty($grade->item)) { $grademax = floatval($grade->item->grademax); } else { $grademax = floatval($grades->items[0]->grademax); } $grade = $grade->grade; } } } if ($grade === false || $grade === null || strlen($grade) < 1) { mtrace("Skipping - Invalid grade {$mtracecontent}."); continue; } // No need to be dividing by zero. if (empty($grademax)) { mtrace("Skipping - Invalid grade {$mtracecontent}."); continue; } // This can happen if the sync process has an unexpected error. if ($grade == $ltiuser->lastgrade) { mtrace("Not sent - The grade {$mtracecontent} was not sent as the grades are the same."); continue; } // Sync with the external system. $floatgrade = $grade / $grademax; $body = \enrol_lti\helper::create_service_body($ltiuser->sourceid, $floatgrade); try { $response = sendOAuthBodyPOST('POST', $ltiuser->serviceurl, $ltiuser->consumerkey, $ltiuser->consumersecret, 'application/xml', $body); } catch (\Exception $e) { mtrace("Failed - The grade '{$floatgrade}' {$mtracecontent} failed to send."); mtrace($e->getMessage()); continue; } if (strpos(strtolower($response), 'success') !== false) { $DB->set_field('enrol_lti_users', 'lastgrade', intval($grade), array('id' => $ltiuser->id)); mtrace("Success - The grade '{$floatgrade}' {$mtracecontent} was sent."); $sendcount = $sendcount + 1; } else { mtrace("Failed - The grade '{$floatgrade}' {$mtracecontent} failed to send."); } } } mtrace("Completed - Synced grades for tool '{$tool->id}' in the course '{$tool->courseid}'. " . "Processed {$usercount} users; sent {$sendcount} grades."); mtrace(""); } } }
/** * Get course completion progress for specific course. * NOTE: It is by design that even teachers get course completion progress, this is so that they see exactly the * same as a student would in the personal menu. * * @param $course * @return stdClass | null */ public static function course_completion_progress($course) { if (!isloggedin() || isguestuser()) { return null; // Can't get completion progress for users who aren't logged in. } // Security check - are they enrolled on course. $context = \context_course::instance($course->id); if (!is_enrolled($context, null, '', true)) { return null; } $completioninfo = new \completion_info($course); $trackcount = 0; $compcount = 0; if ($completioninfo->is_enabled()) { $modinfo = get_fast_modinfo($course); foreach ($modinfo->cms as $thismod) { if (!$thismod->uservisible) { // Skip when mod is not user visible. continue; } $completioninfo->get_data($thismod, true); if ($completioninfo->is_enabled($thismod) != COMPLETION_TRACKING_NONE) { $trackcount++; $completiondata = $completioninfo->get_data($thismod, true); if ($completiondata->completionstate == COMPLETION_COMPLETE || $completiondata->completionstate == COMPLETION_COMPLETE_PASS) { $compcount++; } } } } $compobj = (object) array('complete' => $compcount, 'total' => $trackcount, 'progresshtml' => ''); if ($trackcount > 0) { $progress = get_string('progresstotal', 'completion', $compobj); // TODO - we should be putting our HTML in a renderer. $progressinfo = '<div class="completionstatus outoftotal">' . $progress . '</div>'; $compobj->progresshtml = $progressinfo; } return $compobj; }
function test_get_data() { global $DB, $SESSION; $c = new completion_info((object)array('id'=>42)); $cm = (object)array('id'=>13, 'course'=>42); // 1. Not current user, record exists $sillyrecord = (object)array('frog'=>'kermit'); /** @var $DB PHPUnit_Framework_MockObject_MockObject */ $DB->expects($this->at(0)) ->method('get_record') ->with('course_modules_completion', array('coursemoduleid'=>13,'userid'=>123)) ->will($this->returnValue($sillyrecord)); $result = $c->get_data($cm,false,123); $this->assertEquals($sillyrecord, $result); $this->assertTrue(empty($SESSION->completioncache)); // 2. Not current user, default record, wholecourse (ignored) $DB->expects($this->at(0)) ->method('get_record') ->with('course_modules_completion', array('coursemoduleid'=>13,'userid'=>123)) ->will($this->returnValue(false)); $result=$c->get_data($cm,true,123); $this->assertEquals((object)array( 'id'=>'0','coursemoduleid'=>13,'userid'=>123,'completionstate'=>0, 'viewed'=>0,'timemodified'=>0),$result); $this->assertTrue(empty($SESSION->completioncache)); // 3. Current user, single record, not from cache $DB->expects($this->at(0)) ->method('get_record') ->with('course_modules_completion', array('coursemoduleid'=>13,'userid'=>314159)) ->will($this->returnValue($sillyrecord)); $result = $c->get_data($cm); $this->assertEquals($sillyrecord, $result); $this->assertEquals($sillyrecord, $SESSION->completioncache[42][13]); // When checking time(), allow for second overlaps $this->assertTrue(time()-$SESSION->completioncache[42]['updated']<2); // 4. Current user, 'whole course', but from cache $result = $c->get_data($cm, true); $this->assertEquals($sillyrecord, $result); // 5. Current user, single record, cache expired $SESSION->completioncache[42]['updated']=37; // Quite a long time ago $now = time(); $SESSION->completioncache[17]['updated']=$now; $SESSION->completioncache[39]['updated']=72; // Also a long time ago $DB->expects($this->at(0)) ->method('get_record') ->with('course_modules_completion', array('coursemoduleid'=>13,'userid'=>314159)) ->will($this->returnValue($sillyrecord)); $result = $c->get_data($cm, false); $this->assertEquals($sillyrecord, $result); // Check that updated value is right, then fudge it to make next compare // work $this->assertTrue(time()-$SESSION->completioncache[42]['updated']<2); $SESSION->completioncache[42]['updated']=$now; // Check things got expired from cache $this->assertEquals(array(42=>array(13=>$sillyrecord, 'updated'=>$now), 17=>array('updated'=>$now)), $SESSION->completioncache); // 6. Current user, 'whole course' and record not in cache unset($SESSION->completioncache); // Scenario: Completion data exists for one CMid $basicrecord = (object)array('coursemoduleid'=>13); $DB->expects($this->at(0)) ->method('get_records_sql') ->will($this->returnValue(array('1'=>$basicrecord))); /* $DB->expectAt(0,'get_records_sql',array(new IgnoreWhitespaceExpectation(" SELECT cmc.* FROM {course_modules} cm INNER JOIN {course_modules_completion} cmc ON cmc.coursemoduleid=cm.id WHERE cm.course=? AND cmc.userid=?"),array(42,314159))); */ // There are two CMids in total, the one we had data for and another one $modinfo = new stdClass(); $modinfo->cms = array((object)array('id'=>13), (object)array('id'=>14)); $result = $c->get_data($cm, true, 0, $modinfo); // Check result $this->assertEquals($basicrecord, $result); // Check the cache contents $this->assertTrue(time()-$SESSION->completioncache[42]['updated']<2); $SESSION->completioncache[42]['updated'] = $now; $this->assertEquals(array(42=>array(13=>$basicrecord, 14=>(object)array( 'id'=>'0', 'coursemoduleid'=>14, 'userid'=>314159, 'completionstate'=>0, 'viewed'=>0, 'timemodified'=>0), 'updated'=>$now)), $SESSION->completioncache); }
/** * Review this criteria and decide if the user has completed * * @param completion_completion $completion The user's completion record * @param bool $mark Optionally set false to not save changes to database * @return bool */ public function review($completion, $mark = true) { global $DB; $course = $DB->get_record('course', array('id' => $completion->course)); $cm = $DB->get_record('course_modules', array('id' => $this->moduleinstance)); $info = new completion_info($course); $data = $info->get_data($cm, false, $completion->userid); // If the activity is complete if (in_array($data->completionstate, array(COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS, COMPLETION_COMPLETE_FAIL))) { if ($mark) { $completion->mark_complete(); } return true; } return false; }
/** * Test wiki_page_view. * * @return void */ public function test_wiki_page_view() { global $CFG; $CFG->enablecompletion = COMPLETION_ENABLED; $this->resetAfterTest(); $this->setAdminUser(); // Setup test data. $course = $this->getDataGenerator()->create_course(array('enablecompletion' => COMPLETION_ENABLED)); $options = array('completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => COMPLETION_VIEW_REQUIRED); $wiki = $this->getDataGenerator()->create_module('wiki', array('course' => $course->id), $options); $context = context_module::instance($wiki->cmid); $cm = get_coursemodule_from_instance('wiki', $wiki->id); $firstpage = $this->getDataGenerator()->get_plugin_generator('mod_wiki')->create_first_page($wiki); // Trigger and capture the event. $sink = $this->redirectEvents(); wiki_page_view($wiki, $firstpage, $course, $cm, $context); $events = $sink->get_events(); // 2 additional events thanks to completion. $this->assertCount(3, $events); $event = array_shift($events); // Checking that the event contains the expected values. $this->assertInstanceOf('\\mod_wiki\\event\\page_viewed', $event); $this->assertEquals($context, $event->get_context()); $pageurl = new \moodle_url('/mod/wiki/view.php', array('pageid' => $firstpage->id)); $this->assertEquals($pageurl, $event->get_url()); $this->assertEventContextNotUsed($event); $this->assertNotEmpty($event->get_name()); // Check completion status. $completion = new completion_info($course); $completiondata = $completion->get_data($cm); $this->assertEquals(1, $completiondata->completionstate); }
public function test_course_completion() { global $DB; $this->resetAfterTest(); // Enable avaibility. // If not enabled all conditional fields will be ignored. set_config('enableavailability', 1); // Enable course completion. // If not enabled all completion settings will be ignored. set_config('enablecompletion', COMPLETION_ENABLED); $generator = $this->getDataGenerator(); // Create course with completion tracking enabled. $course = $generator->create_course(['enablecompletion' => 1, 'numsections' => 3], ['createsections' => true]); // Enrol user to completion tracking course. $sturole = $DB->get_record('role', array('shortname' => 'student')); $generator->enrol_user($this->user1->id, $course->id, $sturole->id); // Create page with completion marked on view. $page1 = $generator->create_module('page', array('course' => $course->id, 'name' => 'page1 complete on view'), array('completion' => 2, 'completionview' => 1)); $modinfo = get_fast_modinfo($course); $page1cm = $modinfo->get_cm($page1->cmid); // Create page restricted to only show when first page is viewed. $moduleinfo = (object) []; $moduleinfo->course = $course->id; $moduleinfo->name = 'page2 available after page1 viewed'; $moduleinfo->availability = json_encode(\core_availability\tree::get_root_json([\availability_completion\condition::get_json($page1->cmid, COMPLETION_COMPLETE)], '&')); $page2 = $generator->create_module('page', $moduleinfo); // Make section 2 restricted to only show when first page is viewed. $section = $modinfo->get_section_info(2); $sectionupdate = ['id' => $section->id, 'availability' => json_encode(\core_availability\tree::get_root_json([\availability_completion\condition::get_json($page1->cmid, COMPLETION_COMPLETE)], '&'))]; $DB->update_record('course_sections', $sectionupdate); // Check user1 has expected unavailable section and mod. $this->setUser($this->user1); // Dump cache and reget modinfo. get_fast_modinfo($course, 0, true); $modinfo = get_fast_modinfo($course); $page2cm = $modinfo->get_cm($page2->cmid); list($previouslyunavailablesections, $previouslyunavailablemods) = local::conditionally_unavailable_elements($course); $this->assertContains(2, $previouslyunavailablesections); $this->assertContains($page2cm->id, $previouslyunavailablemods); // View page1 to trigger completion $context = context_module::instance($page1->cmid); page_view($page1, $course, $page1cm, $context); $completion = new completion_info($course); $completiondata = $completion->get_data($page1cm); $this->assertEquals(COMPLETION_COMPLETE, $completiondata->completionstate); get_fast_modinfo($course, 0, true); // Reset modinfo. // Make sure that unavailable sections and mods no longer contain the ones requiring availabililty criteria // satisfying. list($unavailablesections, $unavailablemods) = local::conditionally_unavailable_elements($course); $this->assertNotContains($page2cm->id, $unavailablemods); $this->assertNotContains(2, $unavailablesections); $result = $this->courseservice->course_completion($course->shortname, $previouslyunavailablesections, $previouslyunavailablemods); // Make sure that the second page module (which is now newly available) appears in the list of newly available // module html. $this->assertTrue(isset($result['newlyavailablemodhtml'][$page2->cmid])); // Make sure that the second section (which is now wnely available) appears in the list of newly available // section html. $this->assertTrue(isset($result['newlyavailablesectionhtml'][2])); }
function slides_make_outline($course,$topics_info,$sections,$editing){ // TOPICS OUTLINE list echo '<li id="section--1" class="section outline clearfix" >'; echo "<ul id='section-outline'>\n"; // check activity completion and add to array where section id is key $completionbysection = array(); $completioninfo = new completion_info($course); $activitieswithcompletion = $completioninfo->get_activities(); // unknown > 0 = incomplete < complete foreach($activitieswithcompletion as $activity){ $completionbysection[$activity->section] = "complete"; $activity_status_data = $completioninfo->get_data($activity->id, $USER->id); if($activity_status_data->completionstate < 0) { $completionbysection[$activity->section] = "unknown"; } else if ($activity_status_data->completionstate === 0 && $completionbysection[$activity->section] != "unknown") { $completionbysection[$activity->section] = "incomplete"; } } //sort by id for($i=0; $i<=$course->numsections; $i++) { $section = $sections[$i]; $section_complete = isset($completionbysection[$section->id]) ? $completionbysection[$section->id] : "complete"; $sectionname = !empty($section->name) ? $section->name : "Topic " . $section->section; $style = "left:" . $topics_info[$section->id]->x_offset . "; top:" .$topics_info[$section->id]->y_offset; $css = $editing ? "$section_complete editing" : "$section_complete"; $css .= $section->visible ? "" : " hidden"; $css .= $course->marker == $i ? " highlight" : ""; echo '<li class="'. $css . '" id="topiclink' . $section->id . '" style="' .$style . ';">'; echo '<a href="view.php?id='.$course->id.'&topic='.$section->section.'" title="'.$sectionname.'" class="outline-link">' .$sectionname; // echo "</a> $complete</li>\n"; // TODO: Fix complete echo "</a></li>\n"; } echo "</ul>\n"; echo "</li>\n"; }
/** * Test book_view * @return void */ public function test_book_view() { global $CFG, $DB; $CFG->enablecompletion = 1; $this->resetAfterTest(); $this->setAdminUser(); // Setup test data. $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1)); $book = $this->getDataGenerator()->create_module('book', array('course' => $course->id), array('completion' => 2, 'completionview' => 1)); $bookgenerator = $this->getDataGenerator()->get_plugin_generator('mod_book'); $chapter = $bookgenerator->create_chapter(array('bookid' => $book->id)); $context = context_module::instance($book->cmid); $cm = get_coursemodule_from_instance('book', $book->id); // Trigger and capture the event. $sink = $this->redirectEvents(); // Check just opening the book. book_view($book, 0, false, $course, $cm, $context); $events = $sink->get_events(); $this->assertCount(1, $events); $event = array_shift($events); // Checking that the event contains the expected values. $this->assertInstanceOf('\\mod_book\\event\\course_module_viewed', $event); $this->assertEquals($context, $event->get_context()); $moodleurl = new \moodle_url('/mod/book/view.php', array('id' => $cm->id)); $this->assertEquals($moodleurl, $event->get_url()); $this->assertEventContextNotUsed($event); $this->assertNotEmpty($event->get_name()); // Check viewing one book chapter (the only one so it will be the first and last). book_view($book, $chapter, true, $course, $cm, $context); $events = $sink->get_events(); // We expect a total of 4 events. One for module viewed, one for chapter viewed and two belonging to completion. $this->assertCount(4, $events); // Check completion status. $completion = new completion_info($course); $completiondata = $completion->get_data($cm); $this->assertEquals(1, $completiondata->completionstate); }
/** * Determines whether this particular item is currently available * according to these criteria. * * - This does not include the 'visible' setting (i.e. this might return * true even if visible is false); visible is handled independently. * - This does not take account of the viewhiddenactivities capability. * That should apply later. * * @global stdClass $COURSE * @global moodle_database $DB * @uses COMPLETION_COMPLETE * @uses COMPLETION_COMPLETE_FAIL * @uses COMPLETION_COMPLETE_PASS * @param string $information If the item has availability restrictions, * a string that describes the conditions will be stored in this variable; * if this variable is set blank, that means don't display anything * @param bool $grabthelot Performance hint: if true, caches information * required for all course-modules, to make the front page and similar * pages work more quickly (works only for current user) * @param int $userid If set, specifies a different user ID to check availability for * @param object $modinfo Usually leave as null for default. Specify when * calling recursively from inside get_fast_modinfo. The value supplied * here must include list of all CMs with 'id' and 'name' * @return bool True if this item is available to the user, false otherwise */ public function is_available(&$information, $grabthelot = false, $userid = 0, $modinfo = null) { global $COURSE, $DB; $this->require_data(); $available = true; $information = ''; // Check each completion condition if (count($this->item->conditionscompletion) > 0) { if ($this->item->course == $COURSE->id) { $course = $COURSE; } else { $course = $DB->get_record('course', array('id' => $this->item->course), 'id, enablecompletion, modinfo, sectioncache', MUST_EXIST); } $completion = new completion_info($course); foreach ($this->item->conditionscompletion as $cmid => $expectedcompletion) { // If this depends on a deleted module, handle that situation // gracefully. if (!$modinfo) { $modinfo = get_fast_modinfo($course); } if (empty($modinfo->cms[$cmid])) { global $PAGE; if (isset($PAGE) && strpos($PAGE->pagetype, 'course-view-') === 0) { debugging("Warning: activity {$this->cm->id} '{$this->cm->name}' has condition " . "on deleted activity {$cmid} (to get rid of this message, edit the named activity)"); } continue; } // The completion system caches its own data $completiondata = $completion->get_data((object) array('id' => $cmid), $grabthelot, $userid, $modinfo); $thisisok = true; if ($expectedcompletion == COMPLETION_COMPLETE) { // 'Complete' also allows the pass, fail states switch ($completiondata->completionstate) { case COMPLETION_COMPLETE: case COMPLETION_COMPLETE_FAIL: case COMPLETION_COMPLETE_PASS: break; default: $thisisok = false; } } else { // Other values require exact match if ($completiondata->completionstate != $expectedcompletion) { $thisisok = false; } } if (!$thisisok) { $available = false; $information .= get_string('requires_completion_' . $expectedcompletion, 'condition', $modinfo->cms[$cmid]->name) . ' '; } } } // Check each grade condition if (count($this->item->conditionsgrade) > 0) { foreach ($this->item->conditionsgrade as $gradeitemid => $minmax) { $score = $this->get_cached_grade_score($gradeitemid, $grabthelot, $userid); if ($score === false || !is_null($minmax->min) && $score < $minmax->min || !is_null($minmax->max) && $score >= $minmax->max) { // Grade fail $available = false; // String depends on type of requirement. We are coy about // the actual numbers, in case grades aren't released to // students. if (is_null($minmax->min) && is_null($minmax->max)) { $string = 'any'; } else { if (is_null($minmax->max)) { $string = 'min'; } else { if (is_null($minmax->min)) { $string = 'max'; } else { $string = 'range'; } } } $information .= get_string('requires_grade_' . $string, 'condition', $minmax->name) . ' '; } } } // Check if user field condition if (count($this->item->conditionsfield) > 0) { foreach ($this->item->conditionsfield as $field => $details) { $uservalue = $this->get_cached_user_profile_field($userid, $field); if (!$this->is_field_condition_met($details->operator, $uservalue, $details->value)) { // Set available to false $available = false; $a = new stdClass(); $a->field = $details->fieldname; $a->value = $details->value; $information .= get_string('requires_user_field_' . $details->operator, 'condition', $a) . ' '; } } } // Test dates if ($this->item->availablefrom) { if (time() < $this->item->availablefrom) { $available = false; $information .= get_string('requires_date', 'condition', self::show_time($this->item->availablefrom, self::is_midnight($this->item->availablefrom))); } } if ($this->item->availableuntil) { if (time() >= $this->item->availableuntil) { $available = false; // But we don't display any information about this case. This is // because the only reason to set a 'disappear' date is usually // to get rid of outdated information/clutter in which case there // is no point in showing it... // Note it would be nice if we could make it so that the 'until' // date appears below the item while the item is still accessible, // unfortunately this is not possible in the current system. Maybe // later, or if somebody else wants to add it. } } // If the item is marked as 'not visible' then we don't change the available // flag (visible/available are treated distinctly), but we remove any // availability info. If the item is hidden with the eye icon, it doesn't // make sense to show 'Available from <date>' or similar, because even // when that date arrives it will still not be available unless somebody // toggles the eye icon. if (!$this->item->visible) { $information = ''; } $information = trim($information); return $available; }
/** * Test badges observer when course module completion event id fired. */ public function test_badges_observer_course_module_criteria_review() { $this->preventResetByRollback(); // Messaging is not compatible with transactions. $badge = new badge($this->coursebadge); $this->assertFalse($badge->is_issued($this->user->id)); $criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id)); $criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY)); $criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_ACTIVITY, 'badgeid' => $badge->id)); $criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY, 'module_' . $this->module->cmid => $this->module->cmid)); // Set completion for forum activity. $c = new completion_info($this->course); $activities = $c->get_activities(); $this->assertEquals(1, count($activities)); $this->assertTrue(isset($activities[$this->module->cmid])); $this->assertEquals($activities[$this->module->cmid]->name, $this->module->name); $current = $c->get_data($activities[$this->module->cmid], false, $this->user->id); $current->completionstate = COMPLETION_COMPLETE; $current->timemodified = time(); $sink = $this->redirectEmails(); $c->internal_set_data($activities[$this->module->cmid], $current); $this->assertCount(1, $sink->get_messages()); $sink->close(); // Check if badge is awarded. $this->assertDebuggingCalled('Error baking badge image!'); $this->assertTrue($badge->is_issued($this->user->id)); }
/** * Test updating activity completion when submitting an assessment. */ public function test_update_activity_completion_records_team_submission() { $assign = $this->create_instance(array('grade' => 100, 'completion' => COMPLETION_TRACKING_AUTOMATIC, 'teamsubmission' => 1)); $cm = $assign->get_course_module(); $student1 = $this->students[0]; $student2 = $this->students[1]; $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign'); // Put both users into a group. $group1 = $this->getDataGenerator()->create_group(array('courseid' => $this->course->id)); $this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $student1->id)); $this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $student2->id)); $this->setUser($student1); // Simulate a submission. $data = new stdClass(); $data->onlinetext_editor = array('itemid' => file_get_unused_draft_itemid(), 'text' => 'Student submission text', 'format' => FORMAT_MOODLE); $completion = new completion_info($this->course); $notices = array(); $assign->save_submission($data, $notices); $submission = $assign->get_user_submission($student1->id, true); $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; $submission->groupid = $group1->id; // Check that completion is not met yet. $completiondata = $completion->get_data($cm, false, $student1->id); $this->assertEquals(0, $completiondata->completionstate); $completiondata = $completion->get_data($cm, false, $student2->id); $this->assertEquals(0, $completiondata->completionstate); $assign->testable_update_activity_completion_records(1, 0, $submission, $student1->id, COMPLETION_COMPLETE, $completion); // Completion should now be met. $completiondata = $completion->get_data($cm, false, $student1->id); $this->assertEquals(1, $completiondata->completionstate); $completiondata = $completion->get_data($cm, false, $student2->id); $this->assertEquals(1, $completiondata->completionstate); }
/** * Retrieves the completion status of the LO for an specific user (100 if this is completed and 0 if not), returns NULL if completion tracking is not enabled for that LO (or the entire course) and if the LO corresponds to a section or course LO * @author elever * @param cm_info $coursemodule_info : Moodle object with information of a module in a course * @param int $userid : moodle identifier of the user * @return int $completion_status | null */ function block_intuitel_get_completion_status(\cm_info $coursemodule_info, $userid) { $completion = new \completion_info($coursemodule_info->get_course()); if ($completion->is_enabled($coursemodule_info) > 0) { //check if completion is enabled for a particular course and activity, returns 0 if it is not enabled, 1 if completion is enabled and is manual and 2 if automatic $completion_status = $completion->get_data($coursemodule_info, false, $userid); //this object has also information about viewed...is the row of the related table in the database $completion_status = $completion_status->completionstate; if ($completion_status > 0) { $completion_status = 100; // moodle completion system retrieves 0 when the activity is not completed, 1 when the activity is completed (regardless of mark), 2 when the activity is completed with a passed mark, 3 when the activity is completed but with a fail mark } } else { $completion_status = null; } return $completion_status; }
/** * Test course module completion update event. */ public function test_course_module_completion_updated_event() { global $USER, $CFG; $this->setup_data(); $this->setAdminUser(); $completionauto = array('completion' => COMPLETION_TRACKING_AUTOMATIC); $forum = $this->getDataGenerator()->create_module('forum', array('course' => $this->course->id), $completionauto); $c = new completion_info($this->course); $activities = $c->get_activities(); $this->assertEquals(1, count($activities)); $this->assertTrue(isset($activities[$forum->cmid])); $this->assertEquals($activities[$forum->cmid]->name, $forum->name); $current = $c->get_data($activities[$forum->cmid], false, $this->user->id); $current->completionstate = COMPLETION_COMPLETE; $current->timemodified = time(); $sink = $this->redirectEvents(); $c->internal_set_data($activities[$forum->cmid], $current); $events = $sink->get_events(); $event = reset($events); $this->assertInstanceOf('\\core\\event\\course_module_completion_updated', $event); $this->assertEquals($forum->cmid, $event->get_record_snapshot('course_modules_completion', $event->objectid)->coursemoduleid); $this->assertEquals($current, $event->get_record_snapshot('course_modules_completion', $event->objectid)); $this->assertEquals(context_module::instance($forum->cmid), $event->get_context()); $this->assertEquals($USER->id, $event->userid); $this->assertEquals($this->user->id, $event->relateduserid); $this->assertInstanceOf('moodle_url', $event->get_url()); $this->assertEventLegacyData($current, $event); }
/** * Test test_view_assign */ public function test_view_assign() { global $CFG; $CFG->enablecompletion = 1; $this->resetAfterTest(); $this->setAdminUser(); // Setup test data. $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1)); $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id), array('completion' => 2, 'completionview' => 1)); $context = context_module::instance($assign->cmid); $cm = get_coursemodule_from_instance('assign', $assign->id); $result = mod_assign_external::view_assign($assign->id); $result = external_api::clean_returnvalue(mod_assign_external::view_assign_returns(), $result); $this->assertTrue($result['status']); $this->assertEmpty($result['warnings']); // Check completion status. $completion = new completion_info($course); $completiondata = $completion->get_data($cm); $this->assertEquals(1, $completiondata->completionstate); }
/** * Prints a section full of activity modules */ function print_section($course, $section, $mods, $modnamesused, $absolute = false, $width = "100%", $hidecompletion = false) { global $CFG, $USER, $DB, $PAGE, $OUTPUT; static $initialised; static $groupbuttons; static $groupbuttonslink; static $isediting; static $ismoving; static $strmovehere; static $strmovefull; static $strunreadpostsone; static $usetracking; static $groupings; if (!isset($initialised)) { $groupbuttons = ($course->groupmode or !$course->groupmodeforce); $groupbuttonslink = !$course->groupmodeforce; $isediting = $PAGE->user_is_editing(); $ismoving = $isediting && ismoving($course->id); if ($ismoving) { $strmovehere = get_string("movehere"); $strmovefull = strip_tags(get_string("movefull", "", "'{$USER->activitycopyname}'")); } include_once $CFG->dirroot . '/mod/forum/lib.php'; if ($usetracking = forum_tp_can_track_forums()) { $strunreadpostsone = get_string('unreadpostsone', 'forum'); } $initialised = true; } $labelformatoptions = new stdClass(); $labelformatoptions->noclean = true; $labelformatoptions->overflowdiv = true; /// Casting $course->modinfo to string prevents one notice when the field is null $modinfo = get_fast_modinfo($course); $completioninfo = new completion_info($course); //Accessibility: replace table with list <ul>, but don't output empty list. if (!empty($section->sequence)) { // Fix bug #5027, don't want style=\"width:$width\". echo "<ul class=\"section img-text\">\n"; $sectionmods = explode(",", $section->sequence); foreach ($sectionmods as $modnumber) { if (empty($mods[$modnumber])) { continue; } $mod = $mods[$modnumber]; if ($ismoving and $mod->id == $USER->activitycopy) { // do not display moving mod continue; } if (isset($modinfo->cms[$modnumber])) { // We can continue (because it will not be displayed at all) // if: // 1) The activity is not visible to users // and // 2a) The 'showavailability' option is not set (if that is set, // we need to display the activity so we can show // availability info) // or // 2b) The 'availableinfo' is empty, i.e. the activity was // hidden in a way that leaves no info, such as using the // eye icon. if (!$modinfo->cms[$modnumber]->uservisible && (empty($modinfo->cms[$modnumber]->showavailability) || empty($modinfo->cms[$modnumber]->availableinfo))) { // visibility shortcut continue; } } else { if (!file_exists("{$CFG->dirroot}/mod/{$mod->modname}/lib.php")) { // module not installed continue; } if (!coursemodule_visible_for_user($mod) && empty($mod->showavailability)) { // full visibility check continue; } } // In some cases the activity is visible to user, but it is // dimmed. This is done if viewhiddenactivities is true and if: // 1. the activity is not visible, or // 2. the activity has dates set which do not include current, or // 3. the activity has any other conditions set (regardless of whether // current user meets them) $canviewhidden = has_capability('moodle/course:viewhiddenactivities', get_context_instance(CONTEXT_MODULE, $mod->id)); $accessiblebutdim = false; if ($canviewhidden) { $accessiblebutdim = !$mod->visible; if (!empty($CFG->enableavailability)) { $accessiblebutdim = $accessiblebutdim || $mod->availablefrom > time() || $mod->availableuntil && $mod->availableuntil < time() || count($mod->conditionsgrade) > 0 || count($mod->conditionscompletion) > 0; } } $liclasses = array(); $liclasses[] = 'activity'; $liclasses[] = $mod->modname; $liclasses[] = 'modtype_' . $mod->modname; echo html_writer::start_tag('li', array('class' => join(' ', $liclasses), 'id' => 'module-' . $modnumber)); if ($ismoving) { echo '<a title="' . $strmovefull . '"' . ' href="' . $CFG->wwwroot . '/course/mod.php?moveto=' . $mod->id . '&sesskey=' . sesskey() . '">' . '<img class="movetarget" src="' . $OUTPUT->pix_url('movehere') . '" ' . ' alt="' . $strmovehere . '" /></a><br /> '; } $classes = array('mod-indent'); if (!empty($mod->indent)) { $classes[] = 'mod-indent-' . $mod->indent; if ($mod->indent > 15) { $classes[] = 'mod-indent-huge'; } } echo html_writer::start_tag('div', array('class' => join(' ', $classes))); $extra = ''; if (!empty($modinfo->cms[$modnumber]->extra)) { $extra = $modinfo->cms[$modnumber]->extra; } if ($mod->modname == "label") { if ($accessiblebutdim || !$mod->uservisible) { echo '<div class="dimmed_text"><span class="accesshide">' . get_string('hiddenfromstudents') . '</span>'; } else { echo '<div>'; } echo format_text($extra, FORMAT_HTML, $labelformatoptions); echo "</div>"; if (!empty($mod->groupingid) && has_capability('moodle/course:managegroups', get_context_instance(CONTEXT_COURSE, $course->id))) { if (!isset($groupings)) { $groupings = groups_get_all_groupings($course->id); } echo " <span class=\"groupinglabel\">(" . format_string($groupings[$mod->groupingid]->name) . ')</span>'; } } else { // Normal activity $instancename = format_string($modinfo->cms[$modnumber]->name, true, $course->id); $customicon = $modinfo->cms[$modnumber]->icon; if (!empty($customicon)) { if (substr($customicon, 0, 4) === 'mod/') { list($modname, $iconname) = explode('/', substr($customicon, 4), 2); $icon = $OUTPUT->pix_url($iconname, $modname); } else { $icon = $OUTPUT->pix_url($customicon); } } else { $icon = $OUTPUT->pix_url('icon', $mod->modname); } //Accessibility: for files get description via icon, this is very ugly hack! $altname = ''; $altname = $mod->modfullname; if (!empty($customicon)) { $archetype = plugin_supports('mod', $mod->modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER); if ($archetype == MOD_ARCHETYPE_RESOURCE) { $mimetype = mimeinfo_from_icon('type', $customicon); $altname = get_mimetype_description($mimetype); } } // Avoid unnecessary duplication. if (false !== stripos($instancename, $altname)) { $altname = ''; } // File type after name, for alphabetic lists (screen reader). if ($altname) { $altname = get_accesshide(' ' . $altname); } // We may be displaying this just in order to show information // about visibility, without the actual link if ($mod->uservisible) { // Display normal module link if (!$accessiblebutdim) { $linkcss = ''; $accesstext = ''; } else { $linkcss = ' class="dimmed" '; $accesstext = '<span class="accesshide">' . get_string('hiddenfromstudents') . ': </span>'; } echo '<a ' . $linkcss . ' ' . $extra . ' href="' . $CFG->wwwroot . '/mod/' . $mod->modname . '/view.php?id=' . $mod->id . '">' . '<img src="' . $icon . '" class="activityicon" alt="' . get_string('modulename', $mod->modname) . '" /> ' . $accesstext . '<span class="instancename">' . $instancename . $altname . '</span></a>'; if (!empty($mod->groupingid) && has_capability('moodle/course:managegroups', get_context_instance(CONTEXT_COURSE, $course->id))) { if (!isset($groupings)) { $groupings = groups_get_all_groupings($course->id); } echo " <span class=\"groupinglabel\">(" . format_string($groupings[$mod->groupingid]->name) . ')</span>'; } } else { // Display greyed-out text of link echo '<span class="dimmed_text" ' . $extra . ' ><span class="accesshide">' . get_string('notavailableyet', 'condition') . ': </span>' . '<img src="' . $icon . '" class="activityicon" alt="' . get_string('modulename', $mod->modname) . '" /> <span>' . $instancename . $altname . '</span></span>'; } } if ($usetracking && $mod->modname == 'forum') { if ($unread = forum_tp_count_forum_unread_posts($mod, $course)) { echo '<span class="unread"> <a href="' . $CFG->wwwroot . '/mod/forum/view.php?id=' . $mod->id . '">'; if ($unread == 1) { echo $strunreadpostsone; } else { print_string('unreadpostsnumber', 'forum', $unread); } echo '</a></span>'; } } if ($isediting) { if ($groupbuttons and plugin_supports('mod', $mod->modname, FEATURE_GROUPS, 0)) { if (!($mod->groupmodelink = $groupbuttonslink)) { $mod->groupmode = $course->groupmode; } } else { $mod->groupmode = false; } echo ' '; echo make_editing_buttons($mod, $absolute, true, $mod->indent, $section->section); } // Completion $completion = $hidecompletion ? COMPLETION_TRACKING_NONE : $completioninfo->is_enabled($mod); if ($completion != COMPLETION_TRACKING_NONE && isloggedin() && !isguestuser() && $mod->uservisible) { $completiondata = $completioninfo->get_data($mod, true); $completionicon = ''; if ($isediting) { switch ($completion) { case COMPLETION_TRACKING_MANUAL: $completionicon = 'manual-enabled'; break; case COMPLETION_TRACKING_AUTOMATIC: $completionicon = 'auto-enabled'; break; default: // wtf } } else { if ($completion == COMPLETION_TRACKING_MANUAL) { switch ($completiondata->completionstate) { case COMPLETION_INCOMPLETE: $completionicon = 'manual-n'; break; case COMPLETION_COMPLETE: $completionicon = 'manual-y'; break; } } else { // Automatic switch ($completiondata->completionstate) { case COMPLETION_INCOMPLETE: $completionicon = 'auto-n'; break; case COMPLETION_COMPLETE: $completionicon = 'auto-y'; break; case COMPLETION_COMPLETE_PASS: $completionicon = 'auto-pass'; break; case COMPLETION_COMPLETE_FAIL: $completionicon = 'auto-fail'; break; } } } if ($completionicon) { $imgsrc = $OUTPUT->pix_url('i/completion-' . $completionicon); $imgalt = s(get_string('completion-alt-' . $completionicon, 'completion')); if ($completion == COMPLETION_TRACKING_MANUAL && !$isediting) { $imgtitle = s(get_string('completion-title-' . $completionicon, 'completion')); $newstate = $completiondata->completionstate == COMPLETION_COMPLETE ? COMPLETION_INCOMPLETE : COMPLETION_COMPLETE; // In manual mode the icon is a toggle form... // If this completion state is used by the // conditional activities system, we need to turn // off the JS. if (!empty($CFG->enableavailability) && condition_info::completion_value_used_as_condition($course, $mod)) { $extraclass = ' preventjs'; } else { $extraclass = ''; } echo "\n<form class='togglecompletion{$extraclass}' method='post' action='togglecompletion.php'><div>\n<input type='hidden' name='id' value='{$mod->id}' />\n<input type='hidden' name='sesskey' value='" . sesskey() . "' />\n<input type='hidden' name='completionstate' value='{$newstate}' />\n<input type='image' src='{$imgsrc}' alt='{$imgalt}' title='{$imgtitle}' />\n</div></form>"; } else { // In auto mode, or when editing, the icon is just an image echo "<span class='autocompletion'>"; echo "<img src='{$imgsrc}' alt='{$imgalt}' title='{$imgalt}' /></span>"; } } } // Show availability information (for someone who isn't allowed to // see the activity itself, or for staff) if (!$mod->uservisible) { echo '<div class="availabilityinfo">' . $mod->availableinfo . '</div>'; } else { if ($canviewhidden && !empty($CFG->enableavailability)) { $ci = new condition_info($mod); $fullinfo = $ci->get_full_information(); if ($fullinfo) { echo '<div class="availabilityinfo">' . get_string($mod->showavailability ? 'userrestriction_visible' : 'userrestriction_hidden', 'condition', $fullinfo) . '</div>'; } } } echo html_writer::end_tag('div'); echo html_writer::end_tag('li') . "\n"; } } elseif ($ismoving) { echo "<ul class=\"section\">\n"; } if ($ismoving) { echo '<li><a title="' . $strmovefull . '"' . ' href="' . $CFG->wwwroot . '/course/mod.php?movetosection=' . $section->id . '&sesskey=' . sesskey() . '">' . '<img class="movetarget" src="' . $OUTPUT->pix_url('movehere') . '" ' . ' alt="' . $strmovehere . '" /></a></li> '; } if (!empty($section->sequence) || $ismoving) { echo "</ul><!--class='section'-->\n\n"; } }
/** * Generate a summary of the activites in a section * * @param stdClass $section The course_section entry from DB * @param stdClass $course the course record from DB * @param array $mods (argument not used) * @return string HTML to output. */ protected function section_activity_summary($section, $course, $mods) { $modinfo = get_fast_modinfo($course); if (empty($modinfo->sections[$section->section])) { return ''; } // Generate array with count of activities in this section: $sectionmods = array(); $total = 0; $complete = 0; $cancomplete = isloggedin() && !isguestuser(); $completioninfo = new completion_info($course); foreach ($modinfo->sections[$section->section] as $cmid) { $thismod = $modinfo->cms[$cmid]; if ($thismod->modname == 'label') { // Labels are special (not interesting for students)! continue; } if ($thismod->uservisible) { if (isset($sectionmods[$thismod->modname])) { $sectionmods[$thismod->modname]['name'] = $thismod->modplural; $sectionmods[$thismod->modname]['count']++; } else { $sectionmods[$thismod->modname]['name'] = $thismod->modfullname; $sectionmods[$thismod->modname]['count'] = 1; } if ($cancomplete && $completioninfo->is_enabled($thismod) != COMPLETION_TRACKING_NONE) { $total++; $completiondata = $completioninfo->get_data($thismod, true); if ($completiondata->completionstate == COMPLETION_COMPLETE || $completiondata->completionstate == COMPLETION_COMPLETE_PASS) { $complete++; } } } } if (empty($sectionmods)) { // No sections return ''; } // Output section activities summary: $o = ''; $o .= html_writer::start_tag('div', array('class' => 'section-summary-activities mdl-right')); foreach ($sectionmods as $mod) { $o .= html_writer::start_tag('span', array('class' => 'activity-count')); $o .= $mod['name'] . ': ' . $mod['count']; $o .= html_writer::end_tag('span'); } $o .= html_writer::end_tag('div'); // Output section completion data if ($total > 0) { $a = new stdClass(); $a->complete = $complete; $a->total = $total; $o .= html_writer::start_tag('div', array('class' => 'section-summary-activities mdl-right')); $o .= html_writer::tag('span', get_string('progresstotal', 'completion', $a), array('class' => 'activity-count')); $o .= html_writer::end_tag('div'); } return $o; }
/** * Prints a section full of activity modules */ function print_section($course, $section, $mods, $modnamesused, $absolute = false, $width = "100%", $hidecompletion = false) { global $CFG, $USER, $DB, $PAGE, $OUTPUT; static $initialised; static $groupbuttons; static $groupbuttonslink; static $isediting; static $ismoving; static $strmovehere; static $strmovefull; static $strunreadpostsone; static $groupings; static $modulenames; if (!isset($initialised)) { $groupbuttons = ($course->groupmode or !$course->groupmodeforce); $groupbuttonslink = !$course->groupmodeforce; $isediting = $PAGE->user_is_editing(); $ismoving = $isediting && ismoving($course->id); if ($ismoving) { $strmovehere = get_string("movehere"); $strmovefull = strip_tags(get_string("movefull", "", "'{$USER->activitycopyname}'")); } $modulenames = array(); $initialised = true; } $modinfo = get_fast_modinfo($course); $completioninfo = new completion_info($course); //Accessibility: replace table with list <ul>, but don't output empty list. if (!empty($section->sequence)) { // Fix bug #5027, don't want style=\"width:$width\". echo "<ul class=\"section img-text\">\n"; $sectionmods = explode(",", $section->sequence); foreach ($sectionmods as $modnumber) { if (empty($mods[$modnumber])) { continue; } /** * @var cm_info */ $mod = $mods[$modnumber]; if ($ismoving and $mod->id == $USER->activitycopy) { // do not display moving mod continue; } if (isset($modinfo->cms[$modnumber])) { // We can continue (because it will not be displayed at all) // if: // 1) The activity is not visible to users // and // 2a) The 'showavailability' option is not set (if that is set, // we need to display the activity so we can show // availability info) // or // 2b) The 'availableinfo' is empty, i.e. the activity was // hidden in a way that leaves no info, such as using the // eye icon. if (!$modinfo->cms[$modnumber]->uservisible && (empty($modinfo->cms[$modnumber]->showavailability) || empty($modinfo->cms[$modnumber]->availableinfo))) { // visibility shortcut continue; } } else { if (!file_exists("{$CFG->dirroot}/mod/{$mod->modname}/lib.php")) { // module not installed continue; } if (!coursemodule_visible_for_user($mod) && empty($mod->showavailability)) { // full visibility check continue; } } if (!isset($modulenames[$mod->modname])) { $modulenames[$mod->modname] = get_string('modulename', $mod->modname); } $modulename = $modulenames[$mod->modname]; // In some cases the activity is visible to user, but it is // dimmed. This is done if viewhiddenactivities is true and if: // 1. the activity is not visible, or // 2. the activity has dates set which do not include current, or // 3. the activity has any other conditions set (regardless of whether // current user meets them) $canviewhidden = has_capability('moodle/course:viewhiddenactivities', get_context_instance(CONTEXT_MODULE, $mod->id)); $accessiblebutdim = false; if ($canviewhidden) { $accessiblebutdim = !$mod->visible; if (!empty($CFG->enableavailability)) { $accessiblebutdim = $accessiblebutdim || $mod->availablefrom > time() || $mod->availableuntil && $mod->availableuntil < time() || count($mod->conditionsgrade) > 0 || count($mod->conditionscompletion) > 0; } } $liclasses = array(); $liclasses[] = 'activity'; $liclasses[] = $mod->modname; $liclasses[] = 'modtype_' . $mod->modname; $extraclasses = $mod->get_extra_classes(); if ($extraclasses) { $liclasses = array_merge($liclasses, explode(' ', $extraclasses)); } echo html_writer::start_tag('li', array('class' => join(' ', $liclasses), 'id' => 'module-' . $modnumber)); if ($ismoving) { echo '<a title="' . $strmovefull . '"' . ' href="' . $CFG->wwwroot . '/course/mod.php?moveto=' . $mod->id . '&sesskey=' . sesskey() . '">' . '<img class="movetarget" src="' . $OUTPUT->pix_url('movehere') . '" ' . ' alt="' . $strmovehere . '" /></a><br /> '; } $classes = array('mod-indent'); if (!empty($mod->indent)) { $classes[] = 'mod-indent-' . $mod->indent; if ($mod->indent > 15) { $classes[] = 'mod-indent-huge'; } } echo html_writer::start_tag('div', array('class' => join(' ', $classes))); // Get data about this course-module list($content, $instancename) = get_print_section_cm_text($modinfo->cms[$modnumber], $course); //Accessibility: for files get description via icon, this is very ugly hack! $altname = ''; $altname = $mod->modfullname; if (!empty($customicon)) { $archetype = plugin_supports('mod', $mod->modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER); if ($archetype == MOD_ARCHETYPE_RESOURCE) { $mimetype = mimeinfo_from_icon('type', $customicon); $altname = get_mimetype_description($mimetype); } } // Avoid unnecessary duplication: if e.g. a forum name already // includes the word forum (or Forum, etc) then it is unhelpful // to include that in the accessible description that is added. if (false !== strpos(textlib::strtolower($instancename), textlib::strtolower($altname))) { $altname = ''; } // File type after name, for alphabetic lists (screen reader). if ($altname) { $altname = get_accesshide(' ' . $altname); } // We may be displaying this just in order to show information // about visibility, without the actual link $contentpart = ''; if ($mod->uservisible) { // Nope - in this case the link is fully working for user $linkclasses = ''; $textclasses = ''; if ($accessiblebutdim) { $linkclasses .= ' dimmed'; $textclasses .= ' dimmed_text'; $accesstext = '<span class="accesshide">' . get_string('hiddenfromstudents') . ': </span>'; } else { $accesstext = ''; } if ($linkclasses) { $linkcss = 'class="' . trim($linkclasses) . '" '; } else { $linkcss = ''; } if ($textclasses) { $textcss = 'class="' . trim($textclasses) . '" '; } else { $textcss = ''; } // Get on-click attribute value if specified $onclick = $mod->get_on_click(); if ($onclick) { $onclick = ' onclick="' . $onclick . '"'; } if ($url = $mod->get_url()) { // Display link itself echo '<a ' . $linkcss . $mod->extra . $onclick . ' href="' . $url . '"><img src="' . $mod->get_icon_url() . '" class="activityicon" alt="' . $modulename . '" /> ' . $accesstext . '<span class="instancename">' . $instancename . $altname . '</span></a>'; // If specified, display extra content after link if ($content) { $contentpart = '<div class="' . trim('contentafterlink' . $textclasses) . '">' . $content . '</div>'; } } else { // No link, so display only content $contentpart = '<div ' . $textcss . $mod->extra . '>' . $accesstext . $content . '</div>'; } if (!empty($mod->groupingid) && has_capability('moodle/course:managegroups', get_context_instance(CONTEXT_COURSE, $course->id))) { if (!isset($groupings)) { $groupings = groups_get_all_groupings($course->id); } echo " <span class=\"groupinglabel\">(" . format_string($groupings[$mod->groupingid]->name) . ')</span>'; } } else { $textclasses = $extraclasses; $textclasses .= ' dimmed_text'; if ($textclasses) { $textcss = 'class="' . trim($textclasses) . '" '; } else { $textcss = ''; } $accesstext = '<span class="accesshide">' . get_string('notavailableyet', 'condition') . ': </span>'; if ($url = $mod->get_url()) { // Display greyed-out text of link echo '<div ' . $textcss . $mod->extra . ' >' . '<img src="' . $mod->get_icon_url() . '" class="activityicon" alt="' . $modulename . '" /> <span>' . $instancename . $altname . '</span></div>'; // Do not display content after link when it is greyed out like this. } else { // No link, so display only content (also greyed) $contentpart = '<div ' . $textcss . $mod->extra . '>' . $accesstext . $content . '</div>'; } } // Module can put text after the link (e.g. forum unread) echo $mod->get_after_link(); // If there is content but NO link (eg label), then display the // content here (BEFORE any icons). In this case cons must be // displayed after the content so that it makes more sense visually // and for accessibility reasons, e.g. if you have a one-line label // it should work similarly (at least in terms of ordering) to an // activity. if (empty($url)) { echo $contentpart; } if ($isediting) { if ($groupbuttons and plugin_supports('mod', $mod->modname, FEATURE_GROUPS, 0)) { if (!($mod->groupmodelink = $groupbuttonslink)) { $mod->groupmode = $course->groupmode; } } else { $mod->groupmode = false; } echo ' '; echo make_editing_buttons($mod, $absolute, true, $mod->indent, $section->section); echo $mod->get_after_edit_icons(); } // Completion $completion = $hidecompletion ? COMPLETION_TRACKING_NONE : $completioninfo->is_enabled($mod); if ($completion != COMPLETION_TRACKING_NONE && isloggedin() && !isguestuser() && $mod->uservisible) { $completiondata = $completioninfo->get_data($mod, true); $completionicon = ''; if ($isediting) { switch ($completion) { case COMPLETION_TRACKING_MANUAL: $completionicon = 'manual-enabled'; break; case COMPLETION_TRACKING_AUTOMATIC: $completionicon = 'auto-enabled'; break; default: // wtf } } else { if ($completion == COMPLETION_TRACKING_MANUAL) { switch ($completiondata->completionstate) { case COMPLETION_INCOMPLETE: $completionicon = 'manual-n'; break; case COMPLETION_COMPLETE: $completionicon = 'manual-y'; break; } } else { // Automatic switch ($completiondata->completionstate) { case COMPLETION_INCOMPLETE: $completionicon = 'auto-n'; break; case COMPLETION_COMPLETE: $completionicon = 'auto-y'; break; case COMPLETION_COMPLETE_PASS: $completionicon = 'auto-pass'; break; case COMPLETION_COMPLETE_FAIL: $completionicon = 'auto-fail'; break; } } } if ($completionicon) { $imgsrc = $OUTPUT->pix_url('i/completion-' . $completionicon); $imgalt = s(get_string('completion-alt-' . $completionicon, 'completion', $mod->name)); if ($completion == COMPLETION_TRACKING_MANUAL && !$isediting) { $imgtitle = s(get_string('completion-title-' . $completionicon, 'completion', $mod->name)); $newstate = $completiondata->completionstate == COMPLETION_COMPLETE ? COMPLETION_INCOMPLETE : COMPLETION_COMPLETE; // In manual mode the icon is a toggle form... // If this completion state is used by the // conditional activities system, we need to turn // off the JS. if (!empty($CFG->enableavailability) && condition_info::completion_value_used_as_condition($course, $mod)) { $extraclass = ' preventjs'; } else { $extraclass = ''; } echo "\n<form class='togglecompletion{$extraclass}' method='post' action='" . $CFG->wwwroot . "/course/togglecompletion.php'><div>\n<input type='hidden' name='id' value='{$mod->id}' />\n<input type='hidden' name='modulename' value='" . s($mod->name) . "' />\n<input type='hidden' name='sesskey' value='" . sesskey() . "' />\n<input type='hidden' name='completionstate' value='{$newstate}' />\n<input type='image' src='{$imgsrc}' alt='{$imgalt}' title='{$imgtitle}' />\n</div></form>"; } else { // In auto mode, or when editing, the icon is just an image echo "<span class='autocompletion'>"; echo "<img src='{$imgsrc}' alt='{$imgalt}' title='{$imgalt}' /></span>"; } } } // If there is content AND a link, then display the content here // (AFTER any icons). Otherwise it was displayed before if (!empty($url)) { echo $contentpart; } // Show availability information (for someone who isn't allowed to // see the activity itself, or for staff) if (!$mod->uservisible) { echo '<div class="availabilityinfo">' . $mod->availableinfo . '</div>'; } else { if ($canviewhidden && !empty($CFG->enableavailability)) { $ci = new condition_info($mod); $fullinfo = $ci->get_full_information(); if ($fullinfo) { echo '<div class="availabilityinfo">' . get_string($mod->showavailability ? 'userrestriction_visible' : 'userrestriction_hidden', 'condition', $fullinfo) . '</div>'; } } } echo html_writer::end_tag('div'); echo html_writer::end_tag('li') . "\n"; } } elseif ($ismoving) { echo "<ul class=\"section\">\n"; } if ($ismoving) { echo '<li><a title="' . $strmovefull . '"' . ' href="' . $CFG->wwwroot . '/course/mod.php?movetosection=' . $section->id . '&sesskey=' . sesskey() . '">' . '<img class="movetarget" src="' . $OUTPUT->pix_url('movehere') . '" ' . ' alt="' . $strmovehere . '" /></a></li> '; } if (!empty($section->sequence) || $ismoving) { echo "</ul><!--class='section'-->\n\n"; } }
/** * Determines whether this particular course-module is currently available * according to these criteria. * * - This does not include the 'visible' setting (i.e. this might return * true even if visible is false); visible is handled independently. * - This does not take account of the viewhiddenactivities capability. * That should apply later. * * @global object * @global object * @uses COMPLETION_COMPLETE * @uses COMPLETION_COMPLETE_FAIL * @uses COMPLETION_COMPLETE_PASS * @param string $information If the item has availability restrictions, * a string that describes the conditions will be stored in this variable; * if this variable is set blank, that means don't display anything * @param bool $grabthelot Performance hint: if true, caches information * required for all course-modules, to make the front page and similar * pages work more quickly (works only for current user) * @param int $userid If set, specifies a different user ID to check availability for * @param object $modinfo Usually leave as null for default. Specify when * calling recursively from inside get_fast_modinfo. The value supplied * here must include list of all CMs with 'id' and 'name' * @return bool True if this item is available to the user, false otherwise */ public function is_available(&$information, $grabthelot = false, $userid = 0, $modinfo = null) { $this->require_data(); global $COURSE, $DB; $available = true; $information = ''; // Check each completion condition if (count($this->cm->conditionscompletion) > 0) { if ($this->cm->course == $COURSE->id) { $course = $COURSE; } else { $course = $DB->get_record('course', array('id' => $this->cm->course), 'id,enablecompletion,modinfo'); } $completion = new completion_info($course); foreach ($this->cm->conditionscompletion as $cmid => $expectedcompletion) { // The completion system caches its own data $completiondata = $completion->get_data((object) array('id' => $cmid), $grabthelot, $userid, $modinfo); $thisisok = true; if ($expectedcompletion == COMPLETION_COMPLETE) { // 'Complete' also allows the pass, fail states switch ($completiondata->completionstate) { case COMPLETION_COMPLETE: case COMPLETION_COMPLETE_FAIL: case COMPLETION_COMPLETE_PASS: break; default: $thisisok = false; } } else { // Other values require exact match if ($completiondata->completionstate != $expectedcompletion) { $thisisok = false; } } if (!$thisisok) { $available = false; if (!$modinfo) { $modinfo = get_fast_modinfo($course); } $information .= get_string('requires_completion_' . $expectedcompletion, 'condition', $modinfo->cms[$cmid]->name) . ' '; } } } // Check each grade condition if (count($this->cm->conditionsgrade) > 0) { foreach ($this->cm->conditionsgrade as $gradeitemid => $minmax) { $score = $this->get_cached_grade_score($gradeitemid, $grabthelot, $userid); if ($score === false || !is_null($minmax->min) && $score < $minmax->min || !is_null($minmax->max) && $score >= $minmax->max) { // Grade fail $available = false; // String depends on type of requirement. We are coy about // the actual numbers, in case grades aren't released to // students. if (is_null($minmax->min) && is_null($minmax->max)) { $string = 'any'; } else { if (is_null($minmax->max)) { $string = 'min'; } else { if (is_null($minmax->min)) { $string = 'max'; } else { $string = 'range'; } } } $information .= get_string('requires_grade_' . $string, 'condition', $minmax->name) . ' '; } } } // Test dates if ($this->cm->availablefrom) { if (time() < $this->cm->availablefrom) { $available = false; $information .= get_string('requires_date', 'condition', self::show_time($this->cm->availablefrom, false)); } } if ($this->cm->availableuntil) { if (time() >= $this->cm->availableuntil) { $available = false; // But we don't display any information about this case. This is // because the only reason to set a 'disappear' date is usually // to get rid of outdated information/clutter in which case there // is no point in showing it... // Note it would be nice if we could make it so that the 'until' // date appears below the item while the item is still accessible, // unfortunately this is not possible in the current system. Maybe // later, or if somebody else wants to add it. } } $information = trim($information); return $available; }
/** * Check whether a user or a component of a group has completed an activity * If the group identifier provided is not 0, the group is checked, else the user is checked. * * @param int $cmid The identifier of course module activity. * @param int $userid The identifier of user. * @param int $groupid The identifier of group. * @param stdClass $context The context object. * @return bool True/false. */ function treasurehunt_check_completion_activity($cmid, $userid, $groupid, $context) { global $COURSE; $users = array(); if ($cmid != 0) { $modinfo = get_fast_modinfo($COURSE); $cmactivitytoend = $modinfo->get_cm($cmid); } else { return true; } // Get all users. if ($groupid) { $users = get_enrolled_users($context, 'mod/treasurehunt:play', $groupid, 'u.id'); } else { $user = new stdClass(); $user->id = $userid; $users[] = $user; } foreach ($users as $user) { // Check if a user has complete that activity. $completioninfo = new completion_info($COURSE); $current = $completioninfo->get_data($cmactivitytoend, false, $user->id); if ($current->completionstate == 1) { return $user->id; } } return false; }
$grade = false; if ($context = $DB->get_record('context', array('id' => $tool->contextid))) { if ($context->contextlevel == CONTEXT_COURSE) { if ($tool->requirecompletion and !$completion->is_course_complete($user->userid)) { mtrace(" Skipping user {$user->userid} since he didn't complete the course"); continue; } if ($grade = grade_get_course_grade($user->userid, $tool->courseid)) { $grademax = floatval($grade->item->grademax); $grade = $grade->grade; } } else { if ($context->contextlevel == CONTEXT_MODULE) { $cm = get_coursemodule_from_id(false, $context->instanceid, 0, false, MUST_EXIST); if ($tool->requirecompletion) { $data = $completion->get_data($cm, false, $user->userid); if ($data->completionstate != COMPLETION_COMPLETE_PASS and $data->completionstate != COMPLETION_COMPLETE) { mtrace(" Skipping user {$user->userid} since he didn't complete the activity"); continue; } } $grades = grade_get_grades($cm->course, 'mod', $cm->modname, $cm->instance, $user->userid); if (empty($grades->items[0]->grades)) { $grade = false; } else { $grade = reset($grades->items[0]->grades); if (!empty($grade->item)) { $grademax = floatval($grade->item->grademax); } else { $grademax = floatval($grades->items[0]->grademax); }
/** * Return criteria progress details for display in reports * * @param completion_completion $completion The user's completion record * @return array An array with the following keys: * type, criteria, requirement, status */ public function get_details($completion) { global $DB, $CFG; // Get completion info $course = new stdClass(); $course->id = $completion->course; $info = new completion_info($course); $module = $DB->get_record('course_modules', array('id' => $this->moduleinstance)); $data = $info->get_data($module, false, $completion->userid); $activity = $DB->get_record($this->module, array('id' => $module->instance)); $details = array(); $details['type'] = $this->get_title(); $details['criteria'] = '<a href="' . $CFG->wwwroot . '/mod/' . $this->module . '/view.php?id=' . $this->moduleinstance . '">' . $activity->name . '</a>'; // Build requirements $details['requirement'] = array(); if ($module->completion == 1) { $details['requirement'][] = get_string('markingyourselfcomplete', 'completion'); } elseif ($module->completion == 2) { if ($module->completionview) { $details['requirement'][] = get_string('viewingactivity', 'completion', $this->module); } if (!is_null($module->completiongradeitemnumber)) { $details['requirement'][] = get_string('achievinggrade', 'completion'); } } $details['requirement'] = implode($details['requirement'], ', '); $details['status'] = ''; return $details; }
/** * Review this criteria and decide if it has been completed * * @param int $userid User whose criteria completion needs to be reviewed. * @param bool $filtered An additional parameter indicating that user list * has been reduced and some expensive checks can be skipped. * * @return bool Whether criteria is complete */ public function review($userid, $filtered = false) { $completionstates = array(COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS); $course = new stdClass(); $course->id = $this->courseid; if ($this->coursestartdate > time()) { return false; } $info = new completion_info($course); $overall = false; foreach ($this->params as $param) { $cm = new stdClass(); $cm->id = $param['module']; $data = $info->get_data($cm, false, $userid); $check_date = true; if (isset($param['bydate'])) { $date = $data->timemodified; $check_date = $date <= $param['bydate']; } if ($this->method == BADGE_CRITERIA_AGGREGATION_ALL) { if (in_array($data->completionstate, $completionstates) && $check_date) { $overall = true; continue; } else { return false; } } else { if (in_array($data->completionstate, $completionstates) && $check_date) { return true; } else { $overall = false; continue; } } } return $overall; }