MDL-59256 analytics: Move get_activities method to CoI scope
authorDavid Monllao <davidm@moodle.com>
Fri, 27 Oct 2017 14:58:56 +0000 (16:58 +0200)
committerDavid Monllao <davidm@moodle.com>
Tue, 7 Nov 2017 07:14:49 +0000 (08:14 +0100)
CoI = community of inquiry model

admin/tool/analytics/cli/guess_course_start_and_end.php
analytics/classes/course.php
analytics/classes/local/indicator/community_of_inquiry_activity.php

index 404b010..4e5ea9b 100644 (file)
@@ -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);
index fed39db..b723abf 100644 (file)
@@ -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.
      *
index 1e7da1d..5deaeb6 100644 (file)
@@ -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.
      *