| 1 | efrain | 1 | <?php
 | 
        
           |  |  | 2 | // This file is part of Moodle - http://moodle.org/
 | 
        
           |  |  | 3 | //
 | 
        
           |  |  | 4 | // Moodle is free software: you can redistribute it and/or modify
 | 
        
           |  |  | 5 | // it under the terms of the GNU General Public License as published by
 | 
        
           |  |  | 6 | // the Free Software Foundation, either version 3 of the License, or
 | 
        
           |  |  | 7 | // (at your option) any later version.
 | 
        
           |  |  | 8 | //
 | 
        
           |  |  | 9 | // Moodle is distributed in the hope that it will be useful,
 | 
        
           |  |  | 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
        
           |  |  | 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
        
           |  |  | 12 | // GNU General Public License for more details.
 | 
        
           |  |  | 13 | //
 | 
        
           |  |  | 14 | // You should have received a copy of the GNU General Public License
 | 
        
           |  |  | 15 | // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 | 
        
           |  |  | 16 |   | 
        
           |  |  | 17 | /**
 | 
        
           |  |  | 18 |  * Base class for targets whose analysable is a course using user enrolments as samples.
 | 
        
           |  |  | 19 |  *
 | 
        
           |  |  | 20 |  * @package   core_course
 | 
        
           |  |  | 21 |  * @copyright 2019 Victor Deniz <victor@moodle.com>
 | 
        
           |  |  | 22 |  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 23 |  */
 | 
        
           |  |  | 24 |   | 
        
           |  |  | 25 | namespace core_course\analytics\target;
 | 
        
           |  |  | 26 |   | 
        
           |  |  | 27 | defined('MOODLE_INTERNAL') || die();
 | 
        
           |  |  | 28 |   | 
        
           |  |  | 29 | /**
 | 
        
           |  |  | 30 |  * Base class for targets whose analysable is a course using user enrolments as samples.
 | 
        
           |  |  | 31 |  *
 | 
        
           |  |  | 32 |  * @package   core_course
 | 
        
           |  |  | 33 |  * @copyright 2019 Victor Deniz <victor@moodle.com>
 | 
        
           |  |  | 34 |  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 35 |  */
 | 
        
           |  |  | 36 | abstract class course_enrolments extends \core_analytics\local\target\binary {
 | 
        
           |  |  | 37 |   | 
        
           |  |  | 38 |     /**
 | 
        
           |  |  | 39 |      * @var string
 | 
        
           |  |  | 40 |      */
 | 
        
           |  |  | 41 |     const MESSAGE_ACTION_NAME = 'studentmessage';
 | 
        
           |  |  | 42 |   | 
        
           |  |  | 43 |     /**
 | 
        
           |  |  | 44 |      * @var float
 | 
        
           |  |  | 45 |      */
 | 
        
           |  |  | 46 |     const ENROL_ACTIVE_PERCENT_REQUIRED = 0.7;
 | 
        
           |  |  | 47 |   | 
        
           |  |  | 48 |     /**
 | 
        
           |  |  | 49 |      * Students in the course.
 | 
        
           |  |  | 50 |      * @var int[]
 | 
        
           |  |  | 51 |      */
 | 
        
           |  |  | 52 |     protected $students;
 | 
        
           |  |  | 53 |   | 
        
           |  |  | 54 |     /**
 | 
        
           |  |  | 55 |      * Returns the analyser class that should be used along with this target.
 | 
        
           |  |  | 56 |      *
 | 
        
           |  |  | 57 |      * @return string The full class name as a string
 | 
        
           |  |  | 58 |      */
 | 
        
           |  |  | 59 |     public function get_analyser_class() {
 | 
        
           |  |  | 60 |         return '\core\analytics\analyser\student_enrolments';
 | 
        
           |  |  | 61 |     }
 | 
        
           |  |  | 62 |   | 
        
           |  |  | 63 |     /**
 | 
        
           |  |  | 64 |      * Only past stuff.
 | 
        
           |  |  | 65 |      *
 | 
        
           |  |  | 66 |      * @param  \core_analytics\local\time_splitting\base $timesplitting
 | 
        
           |  |  | 67 |      * @return bool
 | 
        
           |  |  | 68 |      */
 | 
        
           |  |  | 69 |     public function can_use_timesplitting(\core_analytics\local\time_splitting\base $timesplitting): bool {
 | 
        
           |  |  | 70 |         return ($timesplitting instanceof \core_analytics\local\time_splitting\before_now);
 | 
        
           |  |  | 71 |     }
 | 
        
           |  |  | 72 |   | 
        
           |  |  | 73 |     /**
 | 
        
           |  |  | 74 |      * Overwritten to show a simpler language string.
 | 
        
           |  |  | 75 |      *
 | 
        
           |  |  | 76 |      * @param  int $modelid
 | 
        
           |  |  | 77 |      * @param  \context $context
 | 
        
           |  |  | 78 |      * @return string
 | 
        
           |  |  | 79 |      */
 | 
        
           |  |  | 80 |     public function get_insight_subject(int $modelid, \context $context) {
 | 
        
           |  |  | 81 |         return get_string('studentsatriskincourse', 'course', $context->get_context_name(false));
 | 
        
           |  |  | 82 |     }
 | 
        
           |  |  | 83 |   | 
        
           |  |  | 84 |     /**
 | 
        
           |  |  | 85 |      * Returns the body message for the insight.
 | 
        
           |  |  | 86 |      *
 | 
        
           |  |  | 87 |      * @param  \context     $context
 | 
        
           |  |  | 88 |      * @param  string       $contextname
 | 
        
           |  |  | 89 |      * @param  \stdClass    $user
 | 
        
           |  |  | 90 |      * @param  \moodle_url  $insighturl
 | 
        
           |  |  | 91 |      * @return string[]                     The plain text message and the HTML message
 | 
        
           |  |  | 92 |      */
 | 
        
           |  |  | 93 |     public function get_insight_body(\context $context, string $contextname, \stdClass $user, \moodle_url $insighturl): array {
 | 
        
           |  |  | 94 |         global $OUTPUT;
 | 
        
           |  |  | 95 |   | 
        
           |  |  | 96 |         $a = (object)['coursename' => $contextname, 'userfirstname' => $user->firstname];
 | 
        
           |  |  | 97 |         $fullmessage = get_string('studentsatriskinfomessage', 'course', $a) . PHP_EOL . PHP_EOL . $insighturl->out(false);
 | 
        
           |  |  | 98 |         $fullmessagehtml = $OUTPUT->render_from_template('core_analytics/insight_info_message',
 | 
        
           |  |  | 99 |             ['url' => $insighturl->out(false), 'insightinfomessage' => get_string('studentsatriskinfomessage', 'course', $a)]
 | 
        
           |  |  | 100 |         );
 | 
        
           |  |  | 101 |   | 
        
           |  |  | 102 |         return [$fullmessage, $fullmessagehtml];
 | 
        
           |  |  | 103 |     }
 | 
        
           |  |  | 104 |   | 
        
           |  |  | 105 |     /**
 | 
        
           |  |  | 106 |      * Discards courses that are not yet ready to be used for training or prediction.
 | 
        
           |  |  | 107 |      *
 | 
        
           |  |  | 108 |      * @param \core_analytics\analysable $course
 | 
        
           |  |  | 109 |      * @param bool $fortraining
 | 
        
           |  |  | 110 |      * @return true|string
 | 
        
           |  |  | 111 |      */
 | 
        
           |  |  | 112 |     public function is_valid_analysable(\core_analytics\analysable $course, $fortraining = true) {
 | 
        
           |  |  | 113 |   | 
        
           |  |  | 114 |         if (!$course->was_started()) {
 | 
        
           |  |  | 115 |             return get_string('coursenotyetstarted', 'course');
 | 
        
           |  |  | 116 |         }
 | 
        
           |  |  | 117 |   | 
        
           |  |  | 118 |         if (!$fortraining && !$course->get_course_data()->visible) {
 | 
        
           |  |  | 119 |             return get_string('hiddenfromstudents');
 | 
        
           |  |  | 120 |         }
 | 
        
           |  |  | 121 |   | 
        
           |  |  | 122 |         if (!$this->students = $course->get_students()) {
 | 
        
           |  |  | 123 |             return get_string('nocoursestudents', 'course');
 | 
        
           |  |  | 124 |         }
 | 
        
           |  |  | 125 |   | 
        
           |  |  | 126 |         if (!course_format_uses_sections($course->get_course_data()->format)) {
 | 
        
           |  |  | 127 |             // We can not split activities in time ranges.
 | 
        
           |  |  | 128 |             return get_string('nocoursesections', 'course');
 | 
        
           |  |  | 129 |         }
 | 
        
           |  |  | 130 |   | 
        
           |  |  | 131 |         if ($course->get_end() == 0) {
 | 
        
           |  |  | 132 |             // We require time end to be set.
 | 
        
           |  |  | 133 |             return get_string('nocourseendtime', 'course');
 | 
        
           |  |  | 134 |         }
 | 
        
           |  |  | 135 |   | 
        
           |  |  | 136 |         if ($course->get_end() < $course->get_start()) {
 | 
        
           |  |  | 137 |             return get_string('errorendbeforestart', 'course');
 | 
        
           |  |  | 138 |         }
 | 
        
           |  |  | 139 |   | 
        
           |  |  | 140 |         // A course that lasts longer than 1 year probably have wrong start or end dates.
 | 
        
           |  |  | 141 |         if ($course->get_end() - $course->get_start() > (YEARSECS + (WEEKSECS * 4))) {
 | 
        
           |  |  | 142 |             return get_string('coursetoolong', 'course');
 | 
        
           |  |  | 143 |         }
 | 
        
           |  |  | 144 |   | 
        
           |  |  | 145 |         // Finished courses can not be used to get predictions.
 | 
        
           |  |  | 146 |         if (!$fortraining && $course->is_finished()) {
 | 
        
           |  |  | 147 |             return get_string('coursealreadyfinished', 'course');
 | 
        
           |  |  | 148 |         }
 | 
        
           |  |  | 149 |   | 
        
           |  |  | 150 |         if ($fortraining) {
 | 
        
           |  |  | 151 |             // Ongoing courses data can not be used to train.
 | 
        
           |  |  | 152 |             if (!$course->is_finished()) {
 | 
        
           |  |  | 153 |                 return get_string('coursenotyetfinished', 'course');
 | 
        
           |  |  | 154 |             }
 | 
        
           |  |  | 155 |         }
 | 
        
           |  |  | 156 |   | 
        
           |  |  | 157 |         return true;
 | 
        
           |  |  | 158 |     }
 | 
        
           |  |  | 159 |   | 
        
           |  |  | 160 |     /**
 | 
        
           |  |  | 161 |      * Discard student enrolments that are invalid.
 | 
        
           |  |  | 162 |      *
 | 
        
           |  |  | 163 |      * Note that this method assumes that the target is only interested in enrolments that are/were active
 | 
        
           |  |  | 164 |      * between the current course start and end times. Targets interested in predicting students at risk before
 | 
        
           |  |  | 165 |      * their enrolment start and targets interested in getting predictions for students whose enrolment already
 | 
        
           |  |  | 166 |      * finished should overwrite this method as these students are discarded by this method.
 | 
        
           |  |  | 167 |      *
 | 
        
           |  |  | 168 |      * @param int $sampleid
 | 
        
           |  |  | 169 |      * @param \core_analytics\analysable $course
 | 
        
           |  |  | 170 |      * @param bool $fortraining
 | 
        
           |  |  | 171 |      * @return bool
 | 
        
           |  |  | 172 |      */
 | 
        
           |  |  | 173 |     public function is_valid_sample($sampleid, \core_analytics\analysable $course, $fortraining = true) {
 | 
        
           |  |  | 174 |   | 
        
           |  |  | 175 |         $now = time();
 | 
        
           |  |  | 176 |   | 
        
           |  |  | 177 |         $userenrol = $this->retrieve('user_enrolments', $sampleid);
 | 
        
           |  |  | 178 |         if ($userenrol->timeend && $course->get_start() > $userenrol->timeend) {
 | 
        
           |  |  | 179 |             // Discard enrolments which time end is prior to the course start. This should get rid of
 | 
        
           |  |  | 180 |             // old user enrolments that remain on the course.
 | 
        
           |  |  | 181 |             return false;
 | 
        
           |  |  | 182 |         }
 | 
        
           |  |  | 183 |   | 
        
           |  |  | 184 |         $limit = $course->get_start() - (YEARSECS + (WEEKSECS * 4));
 | 
        
           |  |  | 185 |         if (($userenrol->timestart && $userenrol->timestart < $limit) ||
 | 
        
           |  |  | 186 |                 (!$userenrol->timestart && $userenrol->timecreated < $limit)) {
 | 
        
           |  |  | 187 |             // Following what we do in is_valid_analysable, we will discard enrolments that last more than 1 academic year
 | 
        
           |  |  | 188 |             // because they have incorrect start and end dates or because they are reused along multiple years
 | 
        
           |  |  | 189 |             // without removing previous academic years students. This may not be very accurate because some courses
 | 
        
           |  |  | 190 |             // can last just some months, but it is better than nothing.
 | 
        
           |  |  | 191 |             return false;
 | 
        
           |  |  | 192 |         }
 | 
        
           |  |  | 193 |   | 
        
           |  |  | 194 |         if ($course->get_end()) {
 | 
        
           |  |  | 195 |             if (($userenrol->timestart && $userenrol->timestart > $course->get_end()) ||
 | 
        
           |  |  | 196 |                     (!$userenrol->timestart && $userenrol->timecreated > $course->get_end())) {
 | 
        
           |  |  | 197 |                 // Discard user enrolments that start after the analysable official end.
 | 
        
           |  |  | 198 |                 return false;
 | 
        
           |  |  | 199 |             }
 | 
        
           |  |  | 200 |   | 
        
           |  |  | 201 |         }
 | 
        
           |  |  | 202 |   | 
        
           |  |  | 203 |         if ($now < $userenrol->timestart && $userenrol->timestart) {
 | 
        
           |  |  | 204 |             // Discard enrolments whose start date is after now (no need to check timecreated > $now :P).
 | 
        
           |  |  | 205 |             return false;
 | 
        
           |  |  | 206 |         }
 | 
        
           |  |  | 207 |   | 
        
           |  |  | 208 |         if (!$fortraining && $userenrol->timeend && $userenrol->timeend < $now) {
 | 
        
           |  |  | 209 |             // We don't want to generate predictions for finished enrolments.
 | 
        
           |  |  | 210 |             return false;
 | 
        
           |  |  | 211 |         }
 | 
        
           |  |  | 212 |   | 
        
           |  |  | 213 |         return true;
 | 
        
           |  |  | 214 |     }
 | 
        
           |  |  | 215 |   | 
        
           |  |  | 216 |     /**
 | 
        
           |  |  | 217 |      * prediction_actions
 | 
        
           |  |  | 218 |      *
 | 
        
           |  |  | 219 |      * @param \core_analytics\prediction $prediction
 | 
        
           |  |  | 220 |      * @param bool $includedetailsaction
 | 
        
           |  |  | 221 |      * @param bool $isinsightuser
 | 
        
           |  |  | 222 |      * @return \core_analytics\prediction_action[]
 | 
        
           |  |  | 223 |      */
 | 
        
           |  |  | 224 |     public function prediction_actions(\core_analytics\prediction $prediction, $includedetailsaction = false,
 | 
        
           |  |  | 225 |             $isinsightuser = false) {
 | 
        
           |  |  | 226 |   | 
        
           |  |  | 227 |         $actions = array();
 | 
        
           |  |  | 228 |   | 
        
           |  |  | 229 |         $sampledata = $prediction->get_sample_data();
 | 
        
           |  |  | 230 |         $studentid = $sampledata['user']->id;
 | 
        
           |  |  | 231 |   | 
        
           |  |  | 232 |         // View outline report.
 | 
        
           |  |  | 233 |         $url = new \moodle_url('/report/outline/user.php', array('id' => $studentid, 'course' => $sampledata['course']->id,
 | 
        
           |  |  | 234 |                 'mode' => 'outline'));
 | 
        
           |  |  | 235 |         $pix = new \pix_icon('i/report', get_string('outlinereport'));
 | 
        
           |  |  | 236 |         $actions[] = new \core_analytics\prediction_action('viewoutlinereport', $prediction, $url, $pix,
 | 
        
           |  |  | 237 |                 get_string('outlinereport'), false, ['target' => '_blank']);
 | 
        
           |  |  | 238 |   | 
        
           |  |  | 239 |         return array_merge(parent::prediction_actions($prediction, $includedetailsaction, $isinsightuser), $actions);
 | 
        
           |  |  | 240 |     }
 | 
        
           |  |  | 241 |   | 
        
           |  |  | 242 |     /**
 | 
        
           |  |  | 243 |      * Suggested bulk actions for a user.
 | 
        
           |  |  | 244 |      *
 | 
        
           |  |  | 245 |      * @param  \core_analytics\prediction[]     $predictions List of predictions suitable for the bulk actions to use.
 | 
        
           |  |  | 246 |      * @return \core_analytics\bulk_action[]                 The list of bulk actions.
 | 
        
           |  |  | 247 |      */
 | 
        
           |  |  | 248 |     public function bulk_actions(array $predictions) {
 | 
        
           |  |  | 249 |   | 
        
           |  |  | 250 |         $actions = [];
 | 
        
           |  |  | 251 |   | 
        
           |  |  | 252 |         $userids = [];
 | 
        
           |  |  | 253 |         foreach ($predictions as $prediction) {
 | 
        
           |  |  | 254 |             $sampledata = $prediction->get_sample_data();
 | 
        
           |  |  | 255 |             $userid = $sampledata['user']->id;
 | 
        
           |  |  | 256 |   | 
        
           |  |  | 257 |             // Indexed by prediction id because we want the predictionid-userid
 | 
        
           |  |  | 258 |             // mapping later when sending the message.
 | 
        
           |  |  | 259 |             $userids[$prediction->get_prediction_data()->id] = $userid;
 | 
        
           |  |  | 260 |         }
 | 
        
           |  |  | 261 |   | 
        
           |  |  | 262 |         // Send a message for all the students.
 | 
        
           |  |  | 263 |         $attrs = array(
 | 
        
           |  |  | 264 |             'data-bulk-sendmessage' => '1',
 | 
        
           |  |  | 265 |             'data-prediction-to-user-id' => json_encode($userids)
 | 
        
           |  |  | 266 |         );
 | 
        
           |  |  | 267 |         $actions[] = new \core_analytics\bulk_action(self::MESSAGE_ACTION_NAME, new \moodle_url(''),
 | 
        
           |  |  | 268 |             new \pix_icon('t/message', get_string('sendmessage', 'message')),
 | 
        
           |  |  | 269 |             get_string('sendmessage', 'message'), true, $attrs);
 | 
        
           |  |  | 270 |   | 
        
           |  |  | 271 |         return array_merge($actions, parent::bulk_actions($predictions));
 | 
        
           |  |  | 272 |     }
 | 
        
           |  |  | 273 |   | 
        
           |  |  | 274 |     /**
 | 
        
           |  |  | 275 |      * Adds the JS required to run the bulk actions.
 | 
        
           |  |  | 276 |      */
 | 
        
           |  |  | 277 |     public function add_bulk_actions_js() {
 | 
        
           |  |  | 278 |         global $PAGE;
 | 
        
           |  |  | 279 |   | 
        
           |  |  | 280 |         $PAGE->requires->js_call_amd('report_insights/message_users', 'init',
 | 
        
           |  |  | 281 |             ['.insights-bulk-actions', self::MESSAGE_ACTION_NAME]);
 | 
        
           |  |  | 282 |         parent::add_bulk_actions_js();
 | 
        
           |  |  | 283 |     }
 | 
        
           |  |  | 284 |   | 
        
           |  |  | 285 |     /**
 | 
        
           |  |  | 286 |      * Is/was this user enrolment active during most of the analysis interval?
 | 
        
           |  |  | 287 |      *
 | 
        
           |  |  | 288 |      * This method discards enrolments that were not active during most of the analysis interval. It is
 | 
        
           |  |  | 289 |      * important to discard these enrolments because the indicator calculations can lead to misleading
 | 
        
           |  |  | 290 |      * results.
 | 
        
           |  |  | 291 |      *
 | 
        
           |  |  | 292 |      * Note that this method assumes that the target is interested in enrolments that are/were active
 | 
        
           |  |  | 293 |      * during the analysis interval. Targets interested in predicting students at risk before
 | 
        
           |  |  | 294 |      * their enrolment start should not call this method. Similarly, targets interested in getting
 | 
        
           |  |  | 295 |      * predictions for students whose enrolment already finished should not call this method either.
 | 
        
           |  |  | 296 |      *
 | 
        
           |  |  | 297 |      * @param  int    $sampleid     The id of the sample that is being calculated
 | 
        
           |  |  | 298 |      * @param  int    $starttime    The analysis interval start time
 | 
        
           |  |  | 299 |      * @param  int    $endtime      The analysis interval end time
 | 
        
           |  |  | 300 |      * @return bool
 | 
        
           |  |  | 301 |      */
 | 
        
           |  |  | 302 |     protected function enrolment_active_during_analysis_time(int $sampleid, int $starttime, int $endtime) {
 | 
        
           |  |  | 303 |   | 
        
           |  |  | 304 |         $userenrol = $this->retrieve('user_enrolments', $sampleid);
 | 
        
           |  |  | 305 |   | 
        
           |  |  | 306 |         if (!empty($userenrol->timestart)) {
 | 
        
           |  |  | 307 |             $enrolstart = $userenrol->timestart;
 | 
        
           |  |  | 308 |         } else {
 | 
        
           |  |  | 309 |             // This is always set.
 | 
        
           |  |  | 310 |             $enrolstart = $userenrol->timecreated;
 | 
        
           |  |  | 311 |         }
 | 
        
           |  |  | 312 |   | 
        
           |  |  | 313 |         if (!empty($userenrol->timeend)) {
 | 
        
           |  |  | 314 |             $enrolend = $userenrol->timeend;
 | 
        
           |  |  | 315 |         } else {
 | 
        
           |  |  | 316 |             // Default to tre end of the world.
 | 
        
           |  |  | 317 |             $enrolend = PHP_INT_MAX;
 | 
        
           |  |  | 318 |         }
 | 
        
           |  |  | 319 |   | 
        
           |  |  | 320 |         if ($endtime && $endtime < $enrolstart) {
 | 
        
           |  |  | 321 |             /* The enrolment starts/ed after the analysis end time.
 | 
        
           |  |  | 322 |              *   |=========|        |----------|
 | 
        
           |  |  | 323 |              * A start    A end   E start     E end
 | 
        
           |  |  | 324 |              */
 | 
        
           |  |  | 325 |             return false;
 | 
        
           |  |  | 326 |         }
 | 
        
           |  |  | 327 |   | 
        
           |  |  | 328 |         if ($starttime && $enrolend < $starttime) {
 | 
        
           |  |  | 329 |             /* The enrolment finishes/ed before the analysis start time.
 | 
        
           |  |  | 330 |              *    |---------|        |==========|
 | 
        
           |  |  | 331 |              * E start    E end   A start     A end
 | 
        
           |  |  | 332 |              */
 | 
        
           |  |  | 333 |             return false;
 | 
        
           |  |  | 334 |         }
 | 
        
           |  |  | 335 |   | 
        
           |  |  | 336 |         // Now we want to discard enrolments that were not active for most of the analysis interval. We
 | 
        
           |  |  | 337 |         // need both a $starttime and an $endtime to calculate this.
 | 
        
           |  |  | 338 |   | 
        
           |  |  | 339 |         if (!$starttime) {
 | 
        
           |  |  | 340 |             // Early return. Nothing to discard if there is no start.
 | 
        
           |  |  | 341 |             return true;
 | 
        
           |  |  | 342 |         }
 | 
        
           |  |  | 343 |   | 
        
           |  |  | 344 |         if (!$endtime) {
 | 
        
           |  |  | 345 |             // We can not calculate in relative terms (percent) how far from the analysis start time
 | 
        
           |  |  | 346 |             // this enrolment start is/was.
 | 
        
           |  |  | 347 |             return true;
 | 
        
           |  |  | 348 |         }
 | 
        
           |  |  | 349 |   | 
        
           |  |  | 350 |         if ($enrolstart < $starttime && $endtime < $enrolend) {
 | 
        
           |  |  | 351 |             /* The enrolment is active during all the analysis time.
 | 
        
           |  |  | 352 |              *    |-----------------------------|
 | 
        
           |  |  | 353 |              *               |========|
 | 
        
           |  |  | 354 |              * E start    A start   A end     E end
 | 
        
           |  |  | 355 |              */
 | 
        
           |  |  | 356 |             return true;
 | 
        
           |  |  | 357 |         }
 | 
        
           |  |  | 358 |   | 
        
           |  |  | 359 |         // If we reach this point is because the enrolment is only active for a portion of the analysis interval.
 | 
        
           |  |  | 360 |         // Therefore, we check that it was active for most of the analysis interval, a self::ENROL_ACTIVE_PERCENT_REQUIRED.
 | 
        
           |  |  | 361 |   | 
        
           |  |  | 362 |         if ($starttime <= $enrolstart && $enrolend <= $endtime) {
 | 
        
           |  |  | 363 |             /*    |=============================|
 | 
        
           |  |  | 364 |              *               |--------|
 | 
        
           |  |  | 365 |              * A start    E start   E end     A end
 | 
        
           |  |  | 366 |              */
 | 
        
           |  |  | 367 |             $activeenrolduration = $enrolend - $enrolstart;
 | 
        
           |  |  | 368 |         } else if ($enrolstart <= $starttime && $enrolend <= $endtime) {
 | 
        
           |  |  | 369 |             /*            |===================|
 | 
        
           |  |  | 370 |              *    |------------------|
 | 
        
           |  |  | 371 |              * E start  A start    E end    A end
 | 
        
           |  |  | 372 |              */
 | 
        
           |  |  | 373 |             $activeenrolduration = $enrolend - $starttime;
 | 
        
           |  |  | 374 |         } else if ($starttime <= $enrolstart && $endtime <= $enrolend) {
 | 
        
           |  |  | 375 |             /*   |===================|
 | 
        
           |  |  | 376 |              *               |------------------|
 | 
        
           |  |  | 377 |              * A start    E start  A end    E end
 | 
        
           |  |  | 378 |              */
 | 
        
           |  |  | 379 |             $activeenrolduration = $endtime - $enrolstart;
 | 
        
           |  |  | 380 |         }
 | 
        
           |  |  | 381 |   | 
        
           |  |  | 382 |         $analysisduration = $endtime - $starttime;
 | 
        
           |  |  | 383 |   | 
        
           |  |  | 384 |         if (floatval($activeenrolduration) / floatval($analysisduration) < self::ENROL_ACTIVE_PERCENT_REQUIRED) {
 | 
        
           |  |  | 385 |             // The student was not enroled in the course for most of the analysis interval.
 | 
        
           |  |  | 386 |             return false;
 | 
        
           |  |  | 387 |         }
 | 
        
           |  |  | 388 |   | 
        
           |  |  | 389 |         // We happily return true if the enrolment was active for more than self::ENROL_ACTIVE_PERCENT_REQUIRED of
 | 
        
           |  |  | 390 |         // the analysis interval.
 | 
        
           |  |  | 391 |         return true;
 | 
        
           |  |  | 392 |     }
 | 
        
           |  |  | 393 | }
 |