diff --git a/admin/tool/analytics/cli/guess_course_start_and_end.php b/admin/tool/analytics/cli/guess_course_start_and_end.php index 404b01085e7..4e5ea9b6a40 100644 --- a/admin/tool/analytics/cli/guess_course_start_and_end.php +++ b/admin/tool/analytics/cli/guess_course_start_and_end.php @@ -156,11 +156,12 @@ function tool_analytics_calculate_course_dates($course, $options) { $format = course_get_format($course); $formatoptions = $format->get_format_options(); - if ($course->format === 'weeks' && $formatoptions['automaticenddate']) { - // Special treatment for weeks with automatic end date. + // Change this for a course formats API level call in MDL-60702. + if (method_exists($format, 'update_end_date') && $formatoptions['automaticenddate']) { + // Special treatment for weeks-based formats with automatic end date. if ($options['update']) { - format_weeks::update_end_date($course->id); + $format::update_end_date($course->id); $course->enddate = $DB->get_field('course', 'enddate', array('id' => $course->id)); $notification .= PHP_EOL . ' ' . get_string('weeksenddateautomaticallyset', 'tool_analytics') . ': ' . userdate($course->enddate); diff --git a/analytics/classes/course.php b/analytics/classes/course.php index fed39db5c28..b723abfb0e7 100644 --- a/analytics/classes/course.php +++ b/analytics/classes/course.php @@ -547,172 +547,6 @@ class course implements \core_analytics\analysable { return $grades; } - /** - * Guesses all activities that were available during a period of time. - * - * @param string $activitytype - * @param int $starttime - * @param int $endtime - * @param \stdClass $student - * @return array - */ - public function get_activities($activitytype, $starttime, $endtime, $student = false) { - - // Var $student may not be available, default to not calculating dynamic data. - $studentid = -1; - if ($student) { - $studentid = $student->id; - } - $modinfo = get_fast_modinfo($this->get_course_data(), $studentid); - $activities = $modinfo->get_instances_of($activitytype); - - $timerangeactivities = array(); - foreach ($activities as $activity) { - if (!$this->completed_by($activity, $starttime, $endtime)) { - continue; - } - - $timerangeactivities[$activity->context->id] = $activity; - } - - return $timerangeactivities; - } - - /** - * Was the activity supposed to be completed during the provided time range?. - * - * @param \cm_info $activity - * @param int $starttime - * @param int $endtime - * @return bool - */ - protected function completed_by(\cm_info $activity, $starttime, $endtime) { - - // We can't check uservisible because: - // - Any activity with available until would not be counted. - // - Sites may block student's course view capabilities once the course is closed. - - // Students can not view hidden activities by default, this is not reliable 100% but accurate in most of the cases. - if ($activity->visible === false) { - return false; - } - - // We skip activities that were not yet visible or their 'until' was not in this $starttime - $endtime range. - if ($activity->availability) { - $info = new \core_availability\info_module($activity); - $activityavailability = $this->availability_completed_by($info, $starttime, $endtime); - if ($activityavailability === false) { - return false; - } else if ($activityavailability === true) { - // This activity belongs to this time range. - return true; - } - } - - // We skip activities in sections that were not yet visible or their 'until' was not in this $starttime - $endtime range. - $section = $activity->get_modinfo()->get_section_info($activity->sectionnum); - if ($section->availability) { - $info = new \core_availability\info_section($section); - $sectionavailability = $this->availability_completed_by($info, $starttime, $endtime); - if ($sectionavailability === false) { - return false; - } else if ($sectionavailability === true) { - // This activity belongs to this section time range. - return true; - } - } - - // When the course is using format weeks we use the week's end date. - $format = course_get_format($activity->get_modinfo()->get_course()); - if ($this->get_course_data()->format === 'weeks') { - $dates = $format->get_section_dates($section); - - // We need to consider the +2 hours added by get_section_dates. - // Avoid $starttime <= $dates->end because $starttime may be the start of the next week. - if ($starttime < ($dates->end - 7200) && $endtime >= ($dates->end - 7200)) { - return true; - } else { - return false; - } - } - - if ($activity->sectionnum == 0) { - return false; - } - - if (!$this->get_end() || !$this->get_start()) { - debugging('Activities which due date is in a time range can not be calculated ' . - 'if the course doesn\'t have start and end date', DEBUG_DEVELOPER); - return false; - } - - if (!course_format_uses_sections($this->get_course_data()->format)) { - // If it does not use sections and there are no availability conditions to access it it is available - // and we can not magically classify it into any other time range than this one. - return true; - } - - // Split the course duration in the number of sections and consider the end of each section the due - // date of all activities contained in that section. - $formatoptions = $format->get_format_options(); - if (!empty($formatoptions['numsections'])) { - $nsections = $formatoptions['numsections']; - } else { - // There are course format that use sections but without numsections, we fallback to the number - // of cached sections in get_section_info_all, not that accurate though. - $coursesections = $activity->get_modinfo()->get_section_info_all(); - $nsections = count($coursesections); - if (isset($coursesections[0])) { - // We don't count section 0 if it exists. - $nsections--; - } - } - - $courseduration = $this->get_end() - $this->get_start(); - $sectionduration = round($courseduration / $nsections); - $activitysectionenddate = $this->get_start() + ($sectionduration * $activity->sectionnum); - if ($activitysectionenddate > $starttime && $activitysectionenddate <= $endtime) { - return true; - } - - return false; - } - - /** - * Check if the activity/section should have been completed during the provided period according to its availability rules. - * - * @param \core_availability\info $info - * @param int $starttime - * @param int $endtime - * @return bool|null - */ - protected function availability_completed_by(\core_availability\info $info, $starttime, $endtime) { - - $dateconditions = $info->get_availability_tree()->get_all_children('\availability_date\condition'); - foreach ($dateconditions as $condition) { - // Availability API does not allow us to check from / to dates nicely, we need to be naughty. - $conditiondata = $condition->save(); - - if ($conditiondata->d === \availability_date\condition::DIRECTION_FROM && - $conditiondata->t > $endtime) { - // Skip this activity if any 'from' date is later than the end time. - return false; - - } else if ($conditiondata->d === \availability_date\condition::DIRECTION_UNTIL && - ($conditiondata->t < $starttime || $conditiondata->t > $endtime)) { - // Skip activity if any 'until' date is not in $starttime - $endtime range. - return false; - } else if ($conditiondata->d === \availability_date\condition::DIRECTION_UNTIL && - $conditiondata->t < $endtime && $conditiondata->t > $starttime) { - return true; - } - } - - // This can be interpreted as 'the activity was available but we don't know if its expected completion date - // was during this period. - return null; - } - /** * Used by get_user_ids to extract the user id. * diff --git a/analytics/classes/local/indicator/community_of_inquiry_activity.php b/analytics/classes/local/indicator/community_of_inquiry_activity.php index 1e7da1dbd03..5deaeb6049e 100644 --- a/analytics/classes/local/indicator/community_of_inquiry_activity.php +++ b/analytics/classes/local/indicator/community_of_inquiry_activity.php @@ -35,6 +35,13 @@ defined('MOODLE_INTERNAL') || die(); */ abstract class community_of_inquiry_activity extends linear { + /** + * instancedata + * + * @var array + */ + protected $instancedata = array(); + /** * @var \core_analytics\course */ @@ -478,8 +485,8 @@ abstract class community_of_inquiry_activity extends linear { // Samples are at cm level or below. $useractivities = array(\context_module::instance($cm->id)->id => $cm); } else { - // All course activities. - $useractivities = $this->course->get_activities($this->get_activity_type(), $starttime, $endtime, $user); + // Activities that should be completed during this time period. + $useractivities = $this->get_activities($starttime, $endtime, $user); } return $useractivities; @@ -741,6 +748,250 @@ abstract class community_of_inquiry_activity extends linear { $this->grades = $course->get_student_grades($courseactivities); } + /** + * Guesses all activities that were available during a period of time. + * + * @param int $starttime + * @param int $endtime + * @param \stdClass|false $student + * @return array + */ + protected function get_activities($starttime, $endtime, $student = false) { + + $activitytype = $this->get_activity_type(); + + // Var $student may not be available, default to not calculating dynamic data. + $studentid = -1; + if ($student) { + $studentid = $student->id; + } + $modinfo = get_fast_modinfo($this->course->get_course_data(), $studentid); + $activities = $modinfo->get_instances_of($activitytype); + + $timerangeactivities = array(); + foreach ($activities as $activity) { + + if (!$this->activity_completed_by($activity, $starttime, $endtime, $student)) { + continue; + } + + $timerangeactivities[$activity->context->id] = $activity; + } + + return $timerangeactivities; + } + + /** + * Was the activity supposed to be completed during the provided time range?. + * + * @param \cm_info $activity + * @param int $starttime + * @param int $endtime + * @param \stdClass|false $student + * @return bool + */ + protected function activity_completed_by(\cm_info $activity, $starttime, $endtime, $student = false) { + + // We can't check uservisible because: + // - Any activity with available until would not be counted. + // - Sites may block student's course view capabilities once the course is closed. + + // Students can not view hidden activities by default, this is not reliable 100% but accurate in most of the cases. + if ($activity->visible === false) { + return false; + } + + // Give priority to the different methods activities have to set a "due" date. + $return = $this->activity_type_completed_by($activity, $starttime, $endtime, $student); + if (!is_null($return)) { + // Method activity_type_completed_by returns null if there is no due date method or there is but it is not set. + return $return; + } + + // We skip activities that were not yet visible or their 'until' was not in this $starttime - $endtime range. + if ($activity->availability) { + $info = new \core_availability\info_module($activity); + $activityavailability = $this->availability_completed_by($info, $starttime, $endtime); + if ($activityavailability === false) { + return false; + } else if ($activityavailability === true) { + // This activity belongs to this time range. + return true; + } + } + + // We skip activities in sections that were not yet visible or their 'until' was not in this $starttime - $endtime range. + $section = $activity->get_modinfo()->get_section_info($activity->sectionnum); + if ($section->availability) { + $info = new \core_availability\info_section($section); + $sectionavailability = $this->availability_completed_by($info, $starttime, $endtime); + if ($sectionavailability === false) { + return false; + } else if ($sectionavailability === true) { + // This activity belongs to this section time range. + return true; + } + } + + // When the course is using format weeks we use the week's end date. + $format = course_get_format($activity->get_modinfo()->get_course()); + // We should change this in MDL-60702. + if (method_exists($format, 'get_section_dates')) { + $dates = $format->get_section_dates($section); + + // We need to consider the +2 hours added by get_section_dates. + // Avoid $starttime <= $dates->end because $starttime may be the start of the next week. + if ($starttime < ($dates->end - 7200) && $endtime >= ($dates->end - 7200)) { + return true; + } else { + return false; + } + } + + if ($activity->sectionnum == 0) { + return false; + } + + if (!$this->course->get_end() || !$this->course->get_start()) { + debugging('Activities which due date is in a time range can not be calculated ' . + 'if the course doesn\'t have start and end date', DEBUG_DEVELOPER); + return false; + } + + if (!course_format_uses_sections($this->course->get_course_data()->format)) { + // If it does not use sections and there are no availability conditions to access it it is available + // and we can not magically classify it into any other time range than this one. + return true; + } + + // Split the course duration in the number of sections and consider the end of each section the due + // date of all activities contained in that section. + $formatoptions = $format->get_format_options(); + if (!empty($formatoptions['numsections'])) { + $nsections = $formatoptions['numsections']; + } else { + // There are course format that use sections but without numsections, we fallback to the number + // of cached sections in get_section_info_all, not that accurate though. + $coursesections = $activity->get_modinfo()->get_section_info_all(); + $nsections = count($coursesections); + if (isset($coursesections[0])) { + // We don't count section 0 if it exists. + $nsections--; + } + } + + $courseduration = $this->course->get_end() - $this->course->get_start(); + $sectionduration = round($courseduration / $nsections); + $activitysectionenddate = $this->course->get_start() + ($sectionduration * $activity->sectionnum); + if ($activitysectionenddate > $starttime && $activitysectionenddate <= $endtime) { + return true; + } + + return false; + } + + /** + * True if the activity is due or it has been closed during this period, false if during another period, null if no due time. + * + * It can be overwritten by activities that allow teachers to set a due date or a time close separately + * from Moodle availability system. Note that in most of the cases overwriting get_timeclose_field should + * be enough. + * + * Returns true or false if the time close date falls into the provided time range. Null otherwise. + * + * @param \cm_info $activity + * @param int $starttime + * @param int $endtime + * @param \stdClass|false $student + * @return null + */ + protected function activity_type_completed_by(\cm_info $activity, $starttime, $endtime, $student = false) { + + $fieldname = $this->get_timeclose_field(); + if (!$fieldname) { + // This activity type do not have its own availability control. + return null; + } + + $this->fill_instance_data($activity); + $instance = $this->instancedata[$activity->instance]; + + if (!$instance->{$fieldname}) { + return null; + } + + if ($starttime < $instance->{$fieldname} && $endtime >= $instance->{$fieldname}) { + return true; + } + + return false; + } + + /** + * Returns the name of the field that controls activity availability. + * + * Should be overwritten by activities that allow teachers to set a due date or a time close separately + * from Moodle availability system. + * + * Just 1 field will not be enough for all cases, but for the most simple ones without + * overrides and stuff like that. + * + * @return null|string + */ + protected function get_timeclose_field() { + return null; + } + + /** + * Check if the activity/section should have been completed during the provided period according to its availability rules. + * + * @param \core_availability\info $info + * @param int $starttime + * @param int $endtime + * @return bool|null + */ + protected function availability_completed_by(\core_availability\info $info, $starttime, $endtime) { + + $dateconditions = $info->get_availability_tree()->get_all_children('\availability_date\condition'); + foreach ($dateconditions as $condition) { + // Availability API does not allow us to check from / to dates nicely, we need to be naughty. + $conditiondata = $condition->save(); + + if ($conditiondata->d === \availability_date\condition::DIRECTION_FROM && + $conditiondata->t > $endtime) { + // Skip this activity if any 'from' date is later than the end time. + return false; + + } else if ($conditiondata->d === \availability_date\condition::DIRECTION_UNTIL && + ($conditiondata->t < $starttime || $conditiondata->t > $endtime)) { + // Skip activity if any 'until' date is not in $starttime - $endtime range. + return false; + } else if ($conditiondata->d === \availability_date\condition::DIRECTION_UNTIL && + $conditiondata->t < $endtime && $conditiondata->t > $starttime) { + return true; + } + } + + // This can be interpreted as 'the activity was available but we don't know if its expected completion date + // was during this period. + return null; + } + + /** + * Fills in activity instance data. + * + * @param \cm_info $cm + * @return void + */ + protected function fill_instance_data(\cm_info $cm) { + global $DB; + + if (!isset($this->instancedata[$cm->instance])) { + $this->instancedata[$cm->instance] = $DB->get_record($this->get_activity_type(), array('id' => $cm->instance), + '*', MUST_EXIST); + } + } + /** * Defines indicator type. * diff --git a/analytics/tests/course_activities_test.php b/analytics/tests/community_of_inquiry_activities_completed_by.php similarity index 52% rename from analytics/tests/course_activities_test.php rename to analytics/tests/community_of_inquiry_activities_completed_by.php index 8ba3a34b067..cb76f02d8db 100644 --- a/analytics/tests/course_activities_test.php +++ b/analytics/tests/community_of_inquiry_activities_completed_by.php @@ -15,7 +15,7 @@ // along with Moodle. If not, see . /** - * Unit tests for course activities. + * Unit tests for activities completed by classification. * * @package core_analytics * @copyright 2017 David MonllaĆ³ {@link http://www.davidmonllao.com} @@ -25,13 +25,13 @@ defined('MOODLE_INTERNAL') || die(); /** - * Unit tests for course activities + * Unit tests for activities completed by classification. * * @package core_analytics * @copyright 2017 David MonllaĆ³ {@link http://www.davidmonllao.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class core_analytics_course_activities_testcase extends advanced_testcase { +class community_of_inquiry_activities_completed_by_testcase extends advanced_testcase { /** * availability_levels @@ -59,8 +59,6 @@ class core_analytics_course_activities_testcase extends advanced_testcase { // Forum1 is ignored as section 0 does not count. $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); - $courseman = new \core_analytics\course($course); - $modinfo = get_fast_modinfo($course, $stu1->id); $cm = $modinfo->get_cm($forum->cmid); @@ -93,20 +91,23 @@ class core_analytics_course_activities_testcase extends advanced_testcase { $cm = $modinfo->get_cm($forum->cmid); + $course = new \core_analytics\course($course); + list($indicator, $method) = $this->instantiate_indicator('mod_forum', $course); + // Condition from after provided end time. - $this->assertCount(0, $courseman->get_activities('forum', strtotime('2015-10-20 00:00:00 GMT'), + $this->assertCount(0, $method->invoke($indicator, strtotime('2015-10-20 00:00:00 GMT'), strtotime('2015-10-21 00:00:00 GMT'), $stu1)); // Condition until before provided start time - $this->assertCount(0, $courseman->get_activities('forum', strtotime('2015-10-25 00:00:00 GMT'), + $this->assertCount(0, $method->invoke($indicator, strtotime('2015-10-25 00:00:00 GMT'), strtotime('2015-10-26 00:00:00 GMT'), $stu1)); // Condition until after provided end time. - $this->assertCount(0, $courseman->get_activities('forum', strtotime('2015-10-22 00:00:00 GMT'), + $this->assertCount(0, $method->invoke($indicator, strtotime('2015-10-22 00:00:00 GMT'), strtotime('2015-10-23 00:00:00 GMT'), $stu1)); // Condition until after provided start time and before provided end time. - $this->assertCount(1, $courseman->get_activities('forum', strtotime('2015-10-22 00:00:00 GMT'), + $this->assertCount(1, $method->invoke($indicator, strtotime('2015-10-22 00:00:00 GMT'), strtotime('2015-10-25 00:00:00 GMT'), $stu1)); } @@ -138,7 +139,8 @@ class core_analytics_course_activities_testcase extends advanced_testcase { $forum5 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), array('section' => 4)); - $courseman = new \core_analytics\course($course); + $course = new \core_analytics\course($course); + list($indicator, $method) = $this->instantiate_indicator('mod_forum', $course); $this->setUser($stu1); @@ -146,10 +148,10 @@ class core_analytics_course_activities_testcase extends advanced_testcase { $second = $startdate + WEEKSECS; $third = $startdate + (WEEKSECS * 2); $forth = $startdate + (WEEKSECS * 3); - $this->assertCount(1, $courseman->get_activities('forum', $first, $first + WEEKSECS, $stu1)); - $this->assertCount(1, $courseman->get_activities('forum', $second, $second + WEEKSECS, $stu1)); - $this->assertCount(0, $courseman->get_activities('forum', $third, $third + WEEKSECS, $stu1)); - $this->assertCount(2, $courseman->get_activities('forum', $forth, $forth + WEEKSECS, $stu1)); + $this->assertCount(1, $method->invoke($indicator, $first, $first + WEEKSECS, $stu1)); + $this->assertCount(1, $method->invoke($indicator, $second, $second + WEEKSECS, $stu1)); + $this->assertCount(0, $method->invoke($indicator, $third, $third + WEEKSECS, $stu1)); + $this->assertCount(2, $method->invoke($indicator, $forth, $forth + WEEKSECS, $stu1)); } /** @@ -189,7 +191,8 @@ class core_analytics_course_activities_testcase extends advanced_testcase { $forum6 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), array('section' => 12)); - $courseman = new \core_analytics\course($course); + $course = new \core_analytics\course($course); + list($indicator, $method) = $this->instantiate_indicator('mod_forum', $course); $this->setUser($stu1); @@ -199,10 +202,10 @@ class core_analytics_course_activities_testcase extends advanced_testcase { $second = $startdate + $duration; $third = $startdate + ($duration * 2); $forth = $startdate + ($duration * 3); - $this->assertCount(1, $courseman->get_activities('forum', $first, $first + $duration, $stu1)); - $this->assertCount(1, $courseman->get_activities('forum', $second, $second + $duration, $stu1)); - $this->assertCount(1, $courseman->get_activities('forum', $third, $third + $duration, $stu1)); - $this->assertCount(2, $courseman->get_activities('forum', $forth, $forth + $duration, $stu1)); + $this->assertCount(1, $method->invoke($indicator, $first, $first + $duration, $stu1)); + $this->assertCount(1, $method->invoke($indicator, $second, $second + $duration, $stu1)); + $this->assertCount(1, $method->invoke($indicator, $third, $third + $duration, $stu1)); + $this->assertCount(2, $method->invoke($indicator, $forth, $forth + $duration, $stu1)); // Split the course in as many parts as sections. $duration = ($enddate - $startdate) / $numsections; @@ -210,15 +213,94 @@ class core_analytics_course_activities_testcase extends advanced_testcase { // The -1 because section 1 start represents the course start. $timeranges[$i] = $startdate + ($duration * ($i - 1)); } - $this->assertCount(1, $courseman->get_activities('forum', $timeranges[1], $timeranges[1] + $duration, $stu1)); - $this->assertCount(1, $courseman->get_activities('forum', $timeranges[4], $timeranges[4] + $duration, $stu1)); - $this->assertCount(1, $courseman->get_activities('forum', $timeranges[8], $timeranges[8] + $duration, $stu1)); - $this->assertCount(1, $courseman->get_activities('forum', $timeranges[10], $timeranges[10] + $duration, $stu1)); - $this->assertCount(1, $courseman->get_activities('forum', $timeranges[12], $timeranges[12] + $duration, $stu1)); + $this->assertCount(1, $method->invoke($indicator, $timeranges[1], $timeranges[1] + $duration, $stu1)); + $this->assertCount(1, $method->invoke($indicator, $timeranges[4], $timeranges[4] + $duration, $stu1)); + $this->assertCount(1, $method->invoke($indicator, $timeranges[8], $timeranges[8] + $duration, $stu1)); + $this->assertCount(1, $method->invoke($indicator, $timeranges[10], $timeranges[10] + $duration, $stu1)); + $this->assertCount(1, $method->invoke($indicator, $timeranges[12], $timeranges[12] + $duration, $stu1)); // Nothing here. - $this->assertCount(0, $courseman->get_activities('forum', $timeranges[2], $timeranges[2] + $duration, $stu1)); - $this->assertCount(0, $courseman->get_activities('forum', $timeranges[3], $timeranges[3] + $duration, $stu1)); + $this->assertCount(0, $method->invoke($indicator, $timeranges[2], $timeranges[2] + $duration, $stu1)); + $this->assertCount(0, $method->invoke($indicator, $timeranges[3], $timeranges[3] + $duration, $stu1)); + } + + /** + * test_get_activities_with_specific_restrictions + * + * @return void + */ + public function test_get_activities_with_specific_restrictions() { + + list($course, $stu1) = $this->setup_course(); + + $end = strtotime('2015-10-24 00:00:00 GMT'); + + // 1 with time close, one without. + $params = array('course' => $course->id); + $assign1 = $this->getDataGenerator()->create_module('assign', $params); + $params['duedate'] = $end; + $assign2 = $this->getDataGenerator()->create_module('assign', $params); + + // 1 with time close, one without. + $params = array('course' => $course->id); + $choice1 = $this->getDataGenerator()->create_module('choice', $params); + $params['timeclose'] = $end; + $choice1 = $this->getDataGenerator()->create_module('choice', $params); + + // 1 with time close, one without. + $params = array('course' => $course->id); + $data1 = $this->getDataGenerator()->create_module('data', $params); + $params['timeavailableto'] = $end; + $data1 = $this->getDataGenerator()->create_module('data', $params); + + // 1 with time close, one without. + $params = array('course' => $course->id); + $feedback1 = $this->getDataGenerator()->create_module('feedback', $params); + $params['timeclose'] = $end; + $feedback1 = $this->getDataGenerator()->create_module('feedback', $params); + + // 1 with time close, one without. + $params = array('course' => $course->id); + $lesson1 = $this->getDataGenerator()->create_module('lesson', $params); + $params['deadline'] = $end; + $lesson1 = $this->getDataGenerator()->create_module('lesson', $params); + + // 1 with time close, one without. + $params = array('course' => $course->id); + $quiz1 = $this->getDataGenerator()->create_module('quiz', $params); + $params['timeclose'] = $end; + $quiz1 = $this->getDataGenerator()->create_module('quiz', $params); + + // 1 with time close, one without. + $params = array('course' => $course->id); + $scorm1 = $this->getDataGenerator()->create_module('scorm', $params); + $params['timeclose'] = $end; + $scorm1 = $this->getDataGenerator()->create_module('scorm', $params); + + // 1 with time close, one without. + $params = array('course' => $course->id); + $workshop1 = $this->getDataGenerator()->create_module('workshop', $params); + $params['submissionend'] = $end; + $workshop1 = $this->getDataGenerator()->create_module('workshop', $params); + + $course = new \core_analytics\course($course); + + $activitytypes = array('mod_assign', 'mod_choice', 'mod_data', 'mod_feedback', 'mod_lesson', + 'mod_quiz', 'mod_scorm', 'mod_workshop'); + foreach ($activitytypes as $activitytype) { + + list($indicator, $method) = $this->instantiate_indicator($activitytype, $course); + + $message = $activitytype . ' activity type returned activities do not match expected size'; + $this->assertCount(0, $method->invoke($indicator, strtotime('2015-10-20 00:00:00 GMT'), + strtotime('2015-10-21 00:00:00 GMT'), $stu1), $message); + + $this->assertCount(0, $method->invoke($indicator, strtotime('2015-10-25 00:00:00 GMT'), + strtotime('2015-10-26 00:00:00 GMT'), $stu1), $message); + + $this->assertCount(1, $method->invoke($indicator, strtotime('2015-10-22 00:00:00 GMT'), + strtotime('2015-10-25 00:00:00 GMT'), $stu1), $message); + } } /** @@ -243,4 +325,27 @@ class core_analytics_course_activities_testcase extends advanced_testcase { return array($course, $stu1); } + /** + * Returns the module cognitive depth indicator and the reflection method. + * + * @param string $modulename + * @param \core_analytics\course $course + * @return array + */ + private function instantiate_indicator($modulename, \core_analytics\course $course) { + + $classname = '\\' . $modulename . '\analytics\indicator\cognitive_depth'; + $indicator = new $classname(); + $class = new ReflectionClass($indicator); + + $property = $class->getProperty('course'); + $property->setAccessible(true); + $property->setValue($indicator, $course); + + $method = new ReflectionMethod($indicator, 'get_activities'); + $method->setAccessible(true); + + return array($indicator, $method); + } + } diff --git a/mod/assign/classes/analytics/indicator/activity_base.php b/mod/assign/classes/analytics/indicator/activity_base.php index f5a5203f984..aea9067d6f1 100644 --- a/mod/assign/classes/analytics/indicator/activity_base.php +++ b/mod/assign/classes/analytics/indicator/activity_base.php @@ -53,4 +53,14 @@ abstract class activity_base extends \core_analytics\local\indicator\community_o // We need the grade to be released to the student to consider that feedback has been provided. return true; } + + /** + * Returns the name of the field that controls activity availability. + * + * @return null|string + */ + protected function get_timeclose_field() { + return 'duedate'; + } + } diff --git a/mod/choice/classes/analytics/indicator/activity_base.php b/mod/choice/classes/analytics/indicator/activity_base.php index 4395c6fa930..d28fedc12e5 100644 --- a/mod/choice/classes/analytics/indicator/activity_base.php +++ b/mod/choice/classes/analytics/indicator/activity_base.php @@ -35,13 +35,6 @@ defined('MOODLE_INTERNAL') || die(); */ abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity { - /** - * choicedata - * - * @var array - */ - protected $choicedata = array(); - /** * feedback_viewed_events * @@ -51,21 +44,6 @@ abstract class activity_base extends \core_analytics\local\indicator\community_o return array('\mod_choice\event\course_module_viewed', '\mod_choice\event\answer_updated'); } - /** - * Fills choice activities data. - * - * @param \cm_info $cm - * @return void - */ - protected function fill_choice_data(\cm_info $cm) { - global $DB; - - if (!isset($this->choicedata[$cm->instance])) { - $this->choicedata[$cm->instance] = $DB->get_record('choice', array('id' => $cm->instance), - 'id, showresults, timeclose', MUST_EXIST); - } - } - /** * feedback_viewed * @@ -78,17 +56,26 @@ abstract class activity_base extends \core_analytics\local\indicator\community_o protected function feedback_viewed(\cm_info $cm, $contextid, $userid, $after = null) { // If results are shown after they answer a write action counts as feedback viewed. - if ($this->choicedata[$cm->instance]->showresults == 1) { + if ($this->instancedata[$cm->instance]->showresults == 1) { // The user id will be enough for any_write_log. $user = (object)['id' => $userid]; return $this->any_write_log($contextid, $user); } $after = null; - if ($this->choicedata[$cm->instance]->timeclose) { - $after = $this->choicedata[$cm->instance]->timeclose; + if ($this->instancedata[$cm->instance]->timeclose) { + $after = $this->instancedata[$cm->instance]->timeclose; } return $this->feedback_post_action($cm, $contextid, $userid, $this->feedback_viewed_events(), $after); } + + /** + * Returns the name of the field that controls activity availability. + * + * @return null|string + */ + protected function get_timeclose_field() { + return 'timeclose'; + } } diff --git a/mod/choice/classes/analytics/indicator/cognitive_depth.php b/mod/choice/classes/analytics/indicator/cognitive_depth.php index 48cec079cfa..5dcbeb844a1 100644 --- a/mod/choice/classes/analytics/indicator/cognitive_depth.php +++ b/mod/choice/classes/analytics/indicator/cognitive_depth.php @@ -51,9 +51,9 @@ class cognitive_depth extends activity_base { } public function get_cognitive_depth_level(\cm_info $cm) { - $this->fill_choice_data($cm); + $this->fill_instance_data($cm); - if ($this->choicedata[$cm->instance]->showresults == 0 || $this->choicedata[$cm->instance]->showresults == 4) { + if ($this->instancedata[$cm->instance]->showresults == 0 || $this->instancedata[$cm->instance]->showresults == 4) { // Results are not shown to students or are always shown. return self::COGNITIVE_LEVEL_2; } diff --git a/mod/choice/classes/analytics/indicator/social_breadth.php b/mod/choice/classes/analytics/indicator/social_breadth.php index 26c0a7dd4bb..2ae86497b49 100644 --- a/mod/choice/classes/analytics/indicator/social_breadth.php +++ b/mod/choice/classes/analytics/indicator/social_breadth.php @@ -51,7 +51,7 @@ class social_breadth extends activity_base { } public function get_social_breadth_level(\cm_info $cm) { - $this->fill_choice_data($cm); + $this->fill_instance_data($cm); return self::SOCIAL_LEVEL_2; } } diff --git a/mod/data/classes/analytics/indicator/activity_base.php b/mod/data/classes/analytics/indicator/activity_base.php index 7d2a160fe84..6dd5c6f07d5 100644 --- a/mod/data/classes/analytics/indicator/activity_base.php +++ b/mod/data/classes/analytics/indicator/activity_base.php @@ -34,4 +34,13 @@ defined('MOODLE_INTERNAL') || die(); * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity { + + /** + * Returns the name of the field that controls activity availability. + * + * @return null|string + */ + protected function get_timeclose_field() { + return 'timeavailableto'; + } } diff --git a/mod/feedback/classes/analytics/indicator/activity_base.php b/mod/feedback/classes/analytics/indicator/activity_base.php index 72f31c1d0e1..706f9d7c9a8 100644 --- a/mod/feedback/classes/analytics/indicator/activity_base.php +++ b/mod/feedback/classes/analytics/indicator/activity_base.php @@ -35,25 +35,6 @@ defined('MOODLE_INTERNAL') || die(); */ abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity { - /** - * @var int[] Tiny cache to hold feedback instance - publish_stats field relation. - */ - protected $publishstats = array(); - - /** - * fill_publishstats - * - * @param \cm_info $cm - * @return void - */ - protected function fill_publishstats(\cm_info $cm) { - global $DB; - - if (!isset($this->publishstats[$cm->instance])) { - $this->publishstats[$cm->instance] = $DB->get_field('feedback', 'publish_stats', array('id' => $cm->instance)); - } - } - /** * Overwritten to mark as viewed if stats are published. * @@ -65,11 +46,20 @@ abstract class activity_base extends \core_analytics\local\indicator\community_o */ protected function feedback_viewed(\cm_info $cm, $contextid, $userid, $after = null) { // If stats are published any write action counts as viewed feedback. - if (!empty($this->publishstats[$cm->instance])) { + if (!empty($this->instancedata[$cm->instance]->publish_stats)) { $user = (object)['id' => $userid]; return $this->any_write_log($contextid, $user); } return parent::feedback_viewed($cm, $contextid, $userid, $after); } + + /** + * Returns the name of the field that controls activity availability. + * + * @return null|string + */ + protected function get_timeclose_field() { + return 'timeclose'; + } } diff --git a/mod/feedback/classes/analytics/indicator/cognitive_depth.php b/mod/feedback/classes/analytics/indicator/cognitive_depth.php index 0890982e329..5641005b2c3 100644 --- a/mod/feedback/classes/analytics/indicator/cognitive_depth.php +++ b/mod/feedback/classes/analytics/indicator/cognitive_depth.php @@ -51,9 +51,9 @@ class cognitive_depth extends activity_base { } public function get_cognitive_depth_level(\cm_info $cm) { - $this->fill_publishstats($cm); + $this->fill_instance_data($cm); - if (!empty($this->publishstats[$cm->instance])) { + if (!empty($this->instancedata[$cm->instance]->publish_stats)) { // If stats are published we count that the user viewed feedback. return self::COGNITIVE_LEVEL_3; } diff --git a/mod/feedback/classes/analytics/indicator/social_breadth.php b/mod/feedback/classes/analytics/indicator/social_breadth.php index d1c7e276f73..8d972ea0008 100644 --- a/mod/feedback/classes/analytics/indicator/social_breadth.php +++ b/mod/feedback/classes/analytics/indicator/social_breadth.php @@ -51,7 +51,7 @@ class social_breadth extends activity_base { } public function get_social_breadth_level(\cm_info $cm) { - $this->fill_publishstats($cm); + $this->fill_instance_data($cm); return self::SOCIAL_LEVEL_2; } diff --git a/mod/lesson/classes/analytics/indicator/activity_base.php b/mod/lesson/classes/analytics/indicator/activity_base.php index f87612672c7..4758344f3e6 100644 --- a/mod/lesson/classes/analytics/indicator/activity_base.php +++ b/mod/lesson/classes/analytics/indicator/activity_base.php @@ -53,4 +53,13 @@ abstract class activity_base extends \core_analytics\local\indicator\community_o // We don't need to check grades as we get the feedback while completing the activity. return false; } + + /** + * Returns the name of the field that controls activity availability. + * + * @return null|string + */ + protected function get_timeclose_field() { + return 'deadline'; + } } diff --git a/mod/quiz/classes/analytics/indicator/activity_base.php b/mod/quiz/classes/analytics/indicator/activity_base.php index 4f3804cbeba..03f12e0d523 100644 --- a/mod/quiz/classes/analytics/indicator/activity_base.php +++ b/mod/quiz/classes/analytics/indicator/activity_base.php @@ -53,4 +53,13 @@ abstract class activity_base extends \core_analytics\local\indicator\community_o protected function feedback_viewed_events() { return array('\mod_quiz\event\course_module_viewed'); } + + /** + * Returns the name of the field that controls activity availability. + * + * @return null|string + */ + protected function get_timeclose_field() { + return 'timeclose'; + } } diff --git a/mod/scorm/classes/analytics/indicator/activity_base.php b/mod/scorm/classes/analytics/indicator/activity_base.php index 75a6ace56c2..c11cdbcf4bf 100644 --- a/mod/scorm/classes/analytics/indicator/activity_base.php +++ b/mod/scorm/classes/analytics/indicator/activity_base.php @@ -44,4 +44,13 @@ abstract class activity_base extends \core_analytics\local\indicator\community_o // Any view after the data graded counts as feedback viewed. return array('\mod_scorm\event\course_module_viewed'); } + + /** + * Returns the name of the field that controls activity availability. + * + * @return null|string + */ + protected function get_timeclose_field() { + return 'timeclose'; + } } diff --git a/mod/workshop/classes/analytics/indicator/activity_base.php b/mod/workshop/classes/analytics/indicator/activity_base.php index 1774d6357ed..9c5489d204f 100644 --- a/mod/workshop/classes/analytics/indicator/activity_base.php +++ b/mod/workshop/classes/analytics/indicator/activity_base.php @@ -52,4 +52,13 @@ abstract class activity_base extends \core_analytics\local\indicator\community_o protected function feedback_viewed_events() { return array('\mod_workshop\event\course_module_viewed', '\mod_workshop\event\submission_viewed'); } + + /** + * Returns the name of the field that controls activity availability. + * + * @return null|string + */ + protected function get_timeclose_field() { + return 'submissionend'; + } }