first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -0,0 +1,115 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Activities due indicator.
*
* @package core
* @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_course\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/calendar/externallib.php');
/**
* Activities due indicator.
*
* @package core
* @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class activities_due extends \core_analytics\local\indicator\binary {
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('indicator:activitiesdue');
}
/**
* required_sample_data
*
* @return string[]
*/
public static function required_sample_data() {
return array('user');
}
/**
* calculate_sample
*
* @param int $sampleid
* @param string $sampleorigin
* @param int $starttime
* @param int $endtime
* @return float
*/
protected function calculate_sample($sampleid, $sampleorigin, $starttime = false, $endtime = false) {
$user = $this->retrieve('user', $sampleid);
$actionevents = \core_calendar_external::get_calendar_action_events_by_timesort($starttime, $endtime, 0, 1,
true, $user->id);
$useractionevents = [];
if ($actionevents->events) {
// We first need to check that at least one of the core_calendar_provide_event_action
// callbacks has the $userid param.
foreach ($actionevents->events as $event) {
$nparams = $this->get_provide_event_action_num_params($event->modulename);
if ($nparams > 2) {
// Just the basic info for the insight as we want a low memory usage.
$useractionevents[$event->id] = (object)[
'name' => $event->name,
'url' => $event->url,
'time' => $event->timesort,
'coursename' => $event->course->fullnamedisplay,
'icon' => $event->icon,
];
}
}
if (!empty($useractionevents)) {
$this->add_shared_calculation_info($sampleid, $useractionevents);
return self::get_max_value();
}
}
return self::get_min_value();
}
/**
* Returns the number of params declared in core_calendar_provide_event_action's implementation.
*
* @param string $modulename The module name
* @return int
*/
private function get_provide_event_action_num_params(string $modulename) {
$functionname = 'mod_' . $modulename . '_core_calendar_provide_event_action';
$reflection = new \ReflectionFunction($functionname);
return $reflection->getNumberOfParameters();
}
}
@@ -0,0 +1,87 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Completion enabled set indicator.
*
* @package core_course
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_course\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/lib/completionlib.php');
/**
* Completion enabled set indicator.
*
* @package core_course
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class completion_enabled extends \core_analytics\local\indicator\binary {
/**
* get_name
*
* @return new \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('indicator:completionenabled', 'moodle');
}
/**
* required_sample_data
*
* @return string[]
*/
public static function required_sample_data() {
// Minimum course although it also accepts course_modules.
return array('course');
}
/**
* Is completion enabled? Work both with courses and activities.
*
* @param int $sampleid
* @param string $sampleorigin
* @param int|false $notusedstarttime
* @param int|false $notusedendtime
* @return float
*/
public function calculate_sample($sampleid, $sampleorigin, $notusedstarttime = false, $notusedendtime = false) {
$course = $this->retrieve('course', $sampleid);
// It may not be available, but if it is the indicator checks if completion is enabled for the cm.
$cm = $this->retrieve('course_modules', $sampleid);
$completion = new \completion_info($course);
if (!$completion->is_enabled($cm)) {
$value = self::get_min_value();
} else if (!$cm && !$completion->has_criteria()) {
// Course completion enabled with no criteria counts as nothing.
$value = self::get_min_value();
} else {
$value = self::get_max_value();
}
return $value;
}
}
@@ -0,0 +1,95 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* No student indicator.
*
* @package core_course
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_course\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* No student indicator.
*
* @package core_course
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class no_student extends \core_analytics\local\indicator\binary {
/**
* Student role ids.
*
* @var array|null
*/
protected $studentroleids = null;
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('indicator:nostudent', 'moodle');
}
/**
* required_sample_data
*
* @return string[]
*/
public static function required_sample_data() {
// We require course because, although calculate_sample only reads context, we need the context to be course
// or below.
return array('context', 'course');
}
/**
* calculate_sample
*
* @param int $sampleid
* @param string $sampleorigin
* @param int|false $notusedstarttime
* @param int|false $notusedendtime
* @return float
*/
public function calculate_sample($sampleid, $sampleorigin, $notusedstarttime = false, $notusedendtime = false) {
$context = $this->retrieve('context', $sampleid);
if (is_null($this->studentroleids)) {
$this->studentroleids = array_keys(get_archetype_roles('student'));
}
foreach ($this->studentroleids as $role) {
// We look for roles, not enrolments as a student assigned at category level is supposed to be a
// course student.
$students = get_role_users($role, $context, false, 'u.id', 'u.id');
if ($students) {
return self::get_max_value();
}
}
return self::get_min_value();
}
}
@@ -0,0 +1,95 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* No teacher indicator.
*
* @package core_course
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_course\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* No teacher indicator.
*
* @package core_course
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class no_teacher extends \core_analytics\local\indicator\binary {
/**
* Teacher role ids.
*
* @var array|null
*/
protected $teacherroleids = null;
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('indicator:noteacher', 'moodle');
}
/**
* required_sample_data
*
* @return string[]
*/
public static function required_sample_data() {
// We require course because, although calculate_sample only reads context, we need the context to be course
// or below.
return array('context', 'course');
}
/**
* calculate_sample
*
* @param int $sampleid
* @param string $sampleorigin
* @param int|false $notusedstarttime
* @param int|false $notusedendtime
* @return float
*/
public function calculate_sample($sampleid, $sampleorigin, $notusedstarttime = false, $notusedendtime = false) {
$context = $this->retrieve('context', $sampleid);
if (is_null($this->teacherroleids)) {
$this->teacherroleids = array_keys(get_archetype_roles('editingteacher') + get_archetype_roles('teacher'));
}
foreach ($this->teacherroleids as $role) {
// We look for roles, not enrolments as a teacher assigned at category level is supposed to be a
// course teacher.
$teachers = get_role_users($role, $context, false, 'u.id', 'u.id');
if ($teachers) {
return self::get_max_value();
}
}
return self::get_min_value();
}
}
@@ -0,0 +1,136 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Potential cognitive depth indicator.
*
* @package core_course
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_course\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
use \core_analytics\local\indicator\community_of_inquiry_activity;
/**
* Potential cognitive depth indicator.
*
* It extends linear instead of discrete as there is a linear relation between
* the different cognitive levels activities can reach.
*
* @package core_course
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class potential_cognitive_depth extends \core_analytics\local\indicator\linear {
/**
* get_name
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('indicator:potentialcognitive', 'moodle');
}
/**
* Specify the required data to process this indicator.
*
* @return string[]
*/
public static function required_sample_data() {
// We require course because, although this indicator can also work with course_modules we can't
// calculate anything without the course.
return array('course');
}
/**
* calculate_sample
*
* @throws \coding_exception
* @param int $sampleid
* @param string $sampleorigin
* @param int|false $notusedstarttime
* @param int|false $notusedendtime
* @return float
*/
public function calculate_sample($sampleid, $sampleorigin, $notusedstarttime = false, $notusedendtime = false) {
if ($sampleorigin === 'course_modules') {
$cm = $this->retrieve('course_modules', $sampleid);
$cminfo = \cm_info::create($cm);
$cognitivedepthindicator = $this->get_cognitive_indicator($cminfo->modname);
$potentiallevel = $cognitivedepthindicator->get_cognitive_depth_level($cminfo);
if ($potentiallevel > community_of_inquiry_activity::MAX_COGNITIVE_LEVEL) {
throw new \coding_exception('Maximum cognitive depth level is ' .
community_of_inquiry_activity::MAX_COGNITIVE_LEVEL . ', ' . $potentiallevel . ' provided by ' .
get_class($this));
}
} else {
$course = $this->retrieve('course', $sampleid);
$modinfo = get_fast_modinfo($course);
$cms = $modinfo->get_cms();
if (!$cms) {
return self::get_min_value();
}
$potentiallevel = 0;
foreach ($cms as $cm) {
if (!$cognitivedepthindicator = $this->get_cognitive_indicator($cm->modname)) {
continue;
}
$level = $cognitivedepthindicator->get_cognitive_depth_level($cm);
if ($level > community_of_inquiry_activity::MAX_COGNITIVE_LEVEL) {
throw new \coding_exception('Maximum cognitive depth level is ' .
community_of_inquiry_activity::MAX_COGNITIVE_LEVEL . ', ' . $level . ' provided by ' . get_class($this));
}
if ($level > $potentiallevel) {
$potentiallevel = $level;
}
}
}
// Values from -1 to 1 range split in 5 parts (the max cognitive depth level).
// Note that we divide by 4 because we start from -1.
$levelscore = round((self::get_max_value() - self::get_min_value()) / 4, 2);
// We substract $levelscore because we want to start from the lower score and there is no cognitive depth level 0.
return self::get_min_value() + ($levelscore * $potentiallevel) - $levelscore;
}
/**
* Returns the cognitive depth class of this indicator.
*
* @param string $modname
* @return \core_analytics\local\indicator\base|false
*/
protected function get_cognitive_indicator($modname) {
$indicators = \core_analytics\manager::get_all_indicators();
foreach ($indicators as $indicator) {
if ($indicator instanceof community_of_inquiry_activity &&
$indicator->get_indicator_type() === community_of_inquiry_activity::INDICATOR_COGNITIVE &&
$indicator->get_activity_type() === $modname) {
return $indicator;
}
}
return false;
}
}
@@ -0,0 +1,148 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Potential social breadth indicator.
*
* @package core_course
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_course\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
use \core_analytics\local\indicator\community_of_inquiry_activity;
/**
* Potential social breadth indicator.
*
* It extends linear instead of discrete as there is a linear relation between
* the different social levels activities can reach.
*
* @package core_course
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class potential_social_breadth extends \core_analytics\local\indicator\linear {
/**
* get_name
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('indicator:potentialsocial', 'moodle');
}
/**
* Specify the required data to process this indicator.
*
* @return string[]
*/
public static function required_sample_data() {
// We require course because, although this indicator can also work with course_modules we can't
// calculate anything without the course.
return array('course');
}
/**
* calculate_sample
*
* @param int $sampleid
* @param string $sampleorigin
* @param int|false $notusedstarttime
* @param int|false $notusedendtime
* @return float
*/
public function calculate_sample($sampleid, $sampleorigin, $notusedstarttime = false, $notusedendtime = false) {
if ($sampleorigin === 'course_modules') {
$cm = $this->retrieve('course_modules', $sampleid);
$cminfo = \cm_info::create($cm);
$socialbreadthindicator = $this->get_social_indicator($cminfo->modname);
$potentiallevel = $socialbreadthindicator->get_social_breadth_level($cminfo);
if ($potentiallevel > community_of_inquiry_activity::MAX_SOCIAL_LEVEL) {
$this->level_not_accepted($potentiallevel);
}
} else {
$course = $this->retrieve('course', $sampleid);
$modinfo = get_fast_modinfo($course);
$cms = $modinfo->get_cms();
if (!$cms) {
return self::get_min_value();
}
$potentiallevel = 0;
foreach ($cms as $cm) {
if (!$socialbreadthindicator = $this->get_social_indicator($cm->modname)) {
continue;
}
$level = $socialbreadthindicator->get_social_breadth_level($cm);
if ($level > community_of_inquiry_activity::MAX_SOCIAL_LEVEL) {
$this->level_not_accepted($level);
}
if ($level > $potentiallevel) {
$potentiallevel = $level;
}
}
}
// Core activities social breadth only reaches level 2, until core activities social
// breadth do not reach level 5 we limit it to what we currently support, which is level 2.
if ($potentiallevel > 2) {
$potentiallevel = 2;
}
// Supporting only social breadth level 1 and 2 the possible values are -1 or 1.
$levelscore = round(self::get_max_value() - self::get_min_value(), 2);
// We substract $levelscore because we want to start from the lower socre and there is no cognitive depth level 0.
return self::get_min_value() + ($levelscore * $potentiallevel) - $levelscore;
}
/**
* Returns the social breadth class of this indicator.
*
* @param string $modname
* @return \core_analytics\local\indicator\base|false
*/
protected function get_social_indicator($modname) {
$indicators = \core_analytics\manager::get_all_indicators();
foreach ($indicators as $indicator) {
if ($indicator instanceof community_of_inquiry_activity &&
$indicator->get_indicator_type() === community_of_inquiry_activity::INDICATOR_SOCIAL &&
$indicator->get_activity_type() === $modname) {
return $indicator;
}
}
return false;
}
/**
* Throw a \coding_exception.
*
* @param int $level
*/
protected function level_not_accepted($level) {
throw new \coding_exception('Activities\' potential social breadth go from 1 to ' .
community_of_inquiry_activity::MAX_SOCIAL_LEVEL . '.');
}
}
@@ -0,0 +1,141 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Course competencies achievement target.
*
* @package core_course
* @copyright 2019 Victor Deniz <victor@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_course\analytics\target;
defined('MOODLE_INTERNAL') || die();
/**
* Course competencies achievement target.
*
* @package core_course
* @copyright 2019 Victor Deniz <victor@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_competencies extends course_enrolments {
/**
* Number of competencies assigned per course.
* @var int[]
*/
protected $coursecompetencies = array();
/**
* Count the competencies in a course.
*
* Save the value in $coursecompetencies array to prevent new accesses to the database.
*
* @param int $courseid The course id.
* @return int Number of competencies assigned to the course.
*/
protected function get_num_competencies_in_course($courseid) {
if (!isset($this->coursecompetencies[$courseid])) {
$ccs = \core_competency\api::count_competencies_in_course($courseid);
// Save the number of competencies per course to avoid another database access in calculate_sample().
$this->coursecompetencies[$courseid] = $ccs;
} else {
$ccs = $this->coursecompetencies[$courseid];
}
return $ccs;
}
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('target:coursecompetencies', 'course');
}
/**
* Returns descriptions for each of the values the target calculation can return.
*
* @return string[]
*/
protected static function classes_description() {
return array(
get_string('targetlabelstudentcompetenciesno', 'course'),
get_string('targetlabelstudentcompetenciesyes', 'course'),
);
}
/**
* Discards courses that are not yet ready to be used for training or prediction.
*
* @param \core_analytics\analysable $course
* @param bool $fortraining
* @return true|string
*/
public function is_valid_analysable(\core_analytics\analysable $course, $fortraining = true) {
$isvalid = parent::is_valid_analysable($course, $fortraining);
if (is_string($isvalid)) {
return $isvalid;
}
$ccs = $this->get_num_competencies_in_course($course->get_id());
if (!$ccs) {
return get_string('nocompetenciesincourse', 'tool_lp');
}
return true;
}
/**
* To have the proficiency or not in each of the competencies assigned to the course sets the target value.
*
* @param int $sampleid
* @param \core_analytics\analysable $course
* @param int $starttime
* @param int $endtime
* @return float|null 0 -> competencies achieved, 1 -> competencies not achieved
*/
protected function calculate_sample($sampleid, \core_analytics\analysable $course, $starttime = false, $endtime = false) {
if (!$this->enrolment_active_during_analysis_time($sampleid, $starttime, $endtime)) {
// We should not use this sample as the analysis results could be misleading.
return null;
}
$userenrol = $this->retrieve('user_enrolments', $sampleid);
$key = $course->get_id();
// Number of competencies in the course.
$ccs = $this->get_num_competencies_in_course($key);
// Number of proficient competencies in the same course for the user.
$ucs = \core_competency\api::count_proficient_competencies_in_course_for_user($key, $userenrol->userid);
// If they are the equals, the user achieved all the competencies assigned to the course.
if ($ccs == $ucs) {
return 0;
}
return 1;
}
}
@@ -0,0 +1,114 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Course completion target.
*
* @package core_course
* @copyright 2019 Victor Deniz <victor@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_course\analytics\target;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/course/lib.php');
require_once($CFG->dirroot . '/lib/completionlib.php');
require_once($CFG->dirroot . '/completion/completion_completion.php');
/**
* Course completion target.
*
* @package core_course
* @copyright 2019 Victor Deniz <victor@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_completion extends course_enrolments {
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('target:coursecompletion', 'course');
}
/**
* Returns descriptions for each of the values the target calculation can return.
*
* @return string[]
*/
protected static function classes_description() {
return array(
get_string('targetlabelstudentcompletionno', 'course'),
get_string('targetlabelstudentcompletionyes', 'course')
);
}
/**
* Discards courses that are not yet ready to be used for training or prediction.
*
* @param \core_analytics\analysable $course
* @param bool $fortraining
* @return true|string
*/
public function is_valid_analysable(\core_analytics\analysable $course, $fortraining = true) {
$isvalid = parent::is_valid_analysable($course, $fortraining);
if (is_string($isvalid)) {
return $isvalid;
}
// Not a valid target if completion is not enabled or there are not completion criteria defined.
$completion = new \completion_info($course->get_course_data());
if (!$completion->is_enabled() || !$completion->has_criteria()) {
return get_string('completionnotenabledforcourse', 'completion');
}
return true;
}
/**
* Course completion sets the target value.
*
* @param int $sampleid
* @param \core_analytics\analysable $course
* @param int $starttime
* @param int $endtime
* @return float|null 0 -> course not completed, 1 -> course completed
*/
protected function calculate_sample($sampleid, \core_analytics\analysable $course, $starttime = false, $endtime = false) {
if (!$this->enrolment_active_during_analysis_time($sampleid, $starttime, $endtime)) {
// We should not use this sample as the analysis results could be misleading.
return null;
}
$userenrol = $this->retrieve('user_enrolments', $sampleid);
// We use completion as a success metric.
$ccompletion = new \completion_completion(array('userid' => $userenrol->userid, 'course' => $course->get_id()));
if ($ccompletion->is_complete()) {
return 0;
} else {
return 1;
}
}
}
@@ -0,0 +1,154 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Drop out course target.
*
* @package core_course
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_course\analytics\target;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/course/lib.php');
require_once($CFG->dirroot . '/lib/gradelib.php');
require_once($CFG->dirroot . '/lib/completionlib.php');
require_once($CFG->dirroot . '/completion/completion_completion.php');
/**
* Drop out course target.
*
* @package core_course
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_dropout extends course_enrolments {
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('target:coursedropout', 'course');
}
/**
* classes_description
*
* @return string[]
*/
protected static function classes_description() {
return array(
get_string('targetlabelstudentdropoutno', 'course'),
get_string('targetlabelstudentdropoutyes', 'course')
);
}
/**
* Discards courses that are not yet ready to be used for training or prediction.
*
* @param \core_analytics\analysable $course
* @param bool $fortraining
* @return true|string
*/
public function is_valid_analysable(\core_analytics\analysable $course, $fortraining = true) {
global $DB;
$isvalid = parent::is_valid_analysable($course, $fortraining);
if (is_string($isvalid)) {
return $isvalid;
}
if ($fortraining) {
// Not a valid target for training if there are not enough course accesses between the course start and end dates.
$params = array('courseid' => $course->get_id(), 'anonymous' => 0, 'start' => $course->get_start(),
'end' => $course->get_end());
list($studentssql, $studentparams) = $DB->get_in_or_equal($this->students, SQL_PARAMS_NAMED);
// Using anonymous to use the db index, not filtering by timecreated to speed it up.
$select = 'courseid = :courseid AND anonymous = :anonymous AND timecreated > :start AND timecreated < :end ' .
'AND userid ' . $studentssql;
if (!$logstore = \core_analytics\manager::get_analytics_logstore()) {
throw new \coding_exception('No available log stores');
}
$nlogs = $logstore->get_events_select_count($select, array_merge($params, $studentparams));
// At least a minimum of students activity.
$nstudents = count($this->students);
if ($nlogs / $nstudents < 10) {
return get_string('nocourseactivity', 'course');
}
}
return true;
}
/**
* calculate_sample
*
* The meaning of a drop out changes depending on the settings enabled in the course. Following these priorities order:
* 1.- Course completion
* 2.- No logs during the last quarter of the course
*
* @param int $sampleid
* @param \core_analytics\analysable $course
* @param int $starttime
* @param int $endtime
* @return float|null 0 -> not at risk, 1 -> at risk
*/
protected function calculate_sample($sampleid, \core_analytics\analysable $course, $starttime = false, $endtime = false) {
if (!$this->enrolment_active_during_analysis_time($sampleid, $starttime, $endtime)) {
// We should not use this sample as the analysis results could be misleading.
return null;
}
$userenrol = $this->retrieve('user_enrolments', $sampleid);
// We use completion as a success metric only when it is enabled.
$completion = new \completion_info($course->get_course_data());
if ($completion->is_enabled() && $completion->has_criteria()) {
$ccompletion = new \completion_completion(array('userid' => $userenrol->userid, 'course' => $course->get_id()));
if ($ccompletion->is_complete()) {
return 0;
} else {
return 1;
}
}
if (!$logstore = \core_analytics\manager::get_analytics_logstore()) {
throw new \coding_exception('No available log stores');
}
// No logs during the last quarter of the course.
$courseduration = $course->get_end() - $course->get_start();
$limit = intval($course->get_end() - ($courseduration / 4));
$select = "courseid = :courseid AND userid = :userid AND timecreated > :limit";
$params = array('userid' => $userenrol->userid, 'courseid' => $course->get_id(), 'limit' => $limit);
$nlogs = $logstore->get_events_select_count($select, $params);
if ($nlogs == 0) {
return 1;
}
return 0;
}
}
@@ -0,0 +1,393 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Base class for targets whose analysable is a course using user enrolments as samples.
*
* @package core_course
* @copyright 2019 Victor Deniz <victor@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_course\analytics\target;
defined('MOODLE_INTERNAL') || die();
/**
* Base class for targets whose analysable is a course using user enrolments as samples.
*
* @package core_course
* @copyright 2019 Victor Deniz <victor@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class course_enrolments extends \core_analytics\local\target\binary {
/**
* @var string
*/
const MESSAGE_ACTION_NAME = 'studentmessage';
/**
* @var float
*/
const ENROL_ACTIVE_PERCENT_REQUIRED = 0.7;
/**
* Students in the course.
* @var int[]
*/
protected $students;
/**
* Returns the analyser class that should be used along with this target.
*
* @return string The full class name as a string
*/
public function get_analyser_class() {
return '\core\analytics\analyser\student_enrolments';
}
/**
* Only past stuff.
*
* @param \core_analytics\local\time_splitting\base $timesplitting
* @return bool
*/
public function can_use_timesplitting(\core_analytics\local\time_splitting\base $timesplitting): bool {
return ($timesplitting instanceof \core_analytics\local\time_splitting\before_now);
}
/**
* Overwritten to show a simpler language string.
*
* @param int $modelid
* @param \context $context
* @return string
*/
public function get_insight_subject(int $modelid, \context $context) {
return get_string('studentsatriskincourse', 'course', $context->get_context_name(false));
}
/**
* Returns the body message for the insight.
*
* @param \context $context
* @param string $contextname
* @param \stdClass $user
* @param \moodle_url $insighturl
* @return string[] The plain text message and the HTML message
*/
public function get_insight_body(\context $context, string $contextname, \stdClass $user, \moodle_url $insighturl): array {
global $OUTPUT;
$a = (object)['coursename' => $contextname, 'userfirstname' => $user->firstname];
$fullmessage = get_string('studentsatriskinfomessage', 'course', $a) . PHP_EOL . PHP_EOL . $insighturl->out(false);
$fullmessagehtml = $OUTPUT->render_from_template('core_analytics/insight_info_message',
['url' => $insighturl->out(false), 'insightinfomessage' => get_string('studentsatriskinfomessage', 'course', $a)]
);
return [$fullmessage, $fullmessagehtml];
}
/**
* Discards courses that are not yet ready to be used for training or prediction.
*
* @param \core_analytics\analysable $course
* @param bool $fortraining
* @return true|string
*/
public function is_valid_analysable(\core_analytics\analysable $course, $fortraining = true) {
if (!$course->was_started()) {
return get_string('coursenotyetstarted', 'course');
}
if (!$fortraining && !$course->get_course_data()->visible) {
return get_string('hiddenfromstudents');
}
if (!$this->students = $course->get_students()) {
return get_string('nocoursestudents', 'course');
}
if (!course_format_uses_sections($course->get_course_data()->format)) {
// We can not split activities in time ranges.
return get_string('nocoursesections', 'course');
}
if ($course->get_end() == 0) {
// We require time end to be set.
return get_string('nocourseendtime', 'course');
}
if ($course->get_end() < $course->get_start()) {
return get_string('errorendbeforestart', 'course');
}
// A course that lasts longer than 1 year probably have wrong start or end dates.
if ($course->get_end() - $course->get_start() > (YEARSECS + (WEEKSECS * 4))) {
return get_string('coursetoolong', 'course');
}
// Finished courses can not be used to get predictions.
if (!$fortraining && $course->is_finished()) {
return get_string('coursealreadyfinished', 'course');
}
if ($fortraining) {
// Ongoing courses data can not be used to train.
if (!$course->is_finished()) {
return get_string('coursenotyetfinished', 'course');
}
}
return true;
}
/**
* Discard student enrolments that are invalid.
*
* Note that this method assumes that the target is only interested in enrolments that are/were active
* between the current course start and end times. Targets interested in predicting students at risk before
* their enrolment start and targets interested in getting predictions for students whose enrolment already
* finished should overwrite this method as these students are discarded by this method.
*
* @param int $sampleid
* @param \core_analytics\analysable $course
* @param bool $fortraining
* @return bool
*/
public function is_valid_sample($sampleid, \core_analytics\analysable $course, $fortraining = true) {
$now = time();
$userenrol = $this->retrieve('user_enrolments', $sampleid);
if ($userenrol->timeend && $course->get_start() > $userenrol->timeend) {
// Discard enrolments which time end is prior to the course start. This should get rid of
// old user enrolments that remain on the course.
return false;
}
$limit = $course->get_start() - (YEARSECS + (WEEKSECS * 4));
if (($userenrol->timestart && $userenrol->timestart < $limit) ||
(!$userenrol->timestart && $userenrol->timecreated < $limit)) {
// Following what we do in is_valid_analysable, we will discard enrolments that last more than 1 academic year
// because they have incorrect start and end dates or because they are reused along multiple years
// without removing previous academic years students. This may not be very accurate because some courses
// can last just some months, but it is better than nothing.
return false;
}
if ($course->get_end()) {
if (($userenrol->timestart && $userenrol->timestart > $course->get_end()) ||
(!$userenrol->timestart && $userenrol->timecreated > $course->get_end())) {
// Discard user enrolments that start after the analysable official end.
return false;
}
}
if ($now < $userenrol->timestart && $userenrol->timestart) {
// Discard enrolments whose start date is after now (no need to check timecreated > $now :P).
return false;
}
if (!$fortraining && $userenrol->timeend && $userenrol->timeend < $now) {
// We don't want to generate predictions for finished enrolments.
return false;
}
return true;
}
/**
* prediction_actions
*
* @param \core_analytics\prediction $prediction
* @param bool $includedetailsaction
* @param bool $isinsightuser
* @return \core_analytics\prediction_action[]
*/
public function prediction_actions(\core_analytics\prediction $prediction, $includedetailsaction = false,
$isinsightuser = false) {
$actions = array();
$sampledata = $prediction->get_sample_data();
$studentid = $sampledata['user']->id;
// View outline report.
$url = new \moodle_url('/report/outline/user.php', array('id' => $studentid, 'course' => $sampledata['course']->id,
'mode' => 'outline'));
$pix = new \pix_icon('i/report', get_string('outlinereport'));
$actions[] = new \core_analytics\prediction_action('viewoutlinereport', $prediction, $url, $pix,
get_string('outlinereport'), false, ['target' => '_blank']);
return array_merge(parent::prediction_actions($prediction, $includedetailsaction, $isinsightuser), $actions);
}
/**
* Suggested bulk actions for a user.
*
* @param \core_analytics\prediction[] $predictions List of predictions suitable for the bulk actions to use.
* @return \core_analytics\bulk_action[] The list of bulk actions.
*/
public function bulk_actions(array $predictions) {
$actions = [];
$userids = [];
foreach ($predictions as $prediction) {
$sampledata = $prediction->get_sample_data();
$userid = $sampledata['user']->id;
// Indexed by prediction id because we want the predictionid-userid
// mapping later when sending the message.
$userids[$prediction->get_prediction_data()->id] = $userid;
}
// Send a message for all the students.
$attrs = array(
'data-bulk-sendmessage' => '1',
'data-prediction-to-user-id' => json_encode($userids)
);
$actions[] = new \core_analytics\bulk_action(self::MESSAGE_ACTION_NAME, new \moodle_url(''),
new \pix_icon('t/message', get_string('sendmessage', 'message')),
get_string('sendmessage', 'message'), true, $attrs);
return array_merge($actions, parent::bulk_actions($predictions));
}
/**
* Adds the JS required to run the bulk actions.
*/
public function add_bulk_actions_js() {
global $PAGE;
$PAGE->requires->js_call_amd('report_insights/message_users', 'init',
['.insights-bulk-actions', self::MESSAGE_ACTION_NAME]);
parent::add_bulk_actions_js();
}
/**
* Is/was this user enrolment active during most of the analysis interval?
*
* This method discards enrolments that were not active during most of the analysis interval. It is
* important to discard these enrolments because the indicator calculations can lead to misleading
* results.
*
* Note that this method assumes that the target is interested in enrolments that are/were active
* during the analysis interval. Targets interested in predicting students at risk before
* their enrolment start should not call this method. Similarly, targets interested in getting
* predictions for students whose enrolment already finished should not call this method either.
*
* @param int $sampleid The id of the sample that is being calculated
* @param int $starttime The analysis interval start time
* @param int $endtime The analysis interval end time
* @return bool
*/
protected function enrolment_active_during_analysis_time(int $sampleid, int $starttime, int $endtime) {
$userenrol = $this->retrieve('user_enrolments', $sampleid);
if (!empty($userenrol->timestart)) {
$enrolstart = $userenrol->timestart;
} else {
// This is always set.
$enrolstart = $userenrol->timecreated;
}
if (!empty($userenrol->timeend)) {
$enrolend = $userenrol->timeend;
} else {
// Default to tre end of the world.
$enrolend = PHP_INT_MAX;
}
if ($endtime && $endtime < $enrolstart) {
/* The enrolment starts/ed after the analysis end time.
* |=========| |----------|
* A start A end E start E end
*/
return false;
}
if ($starttime && $enrolend < $starttime) {
/* The enrolment finishes/ed before the analysis start time.
* |---------| |==========|
* E start E end A start A end
*/
return false;
}
// Now we want to discard enrolments that were not active for most of the analysis interval. We
// need both a $starttime and an $endtime to calculate this.
if (!$starttime) {
// Early return. Nothing to discard if there is no start.
return true;
}
if (!$endtime) {
// We can not calculate in relative terms (percent) how far from the analysis start time
// this enrolment start is/was.
return true;
}
if ($enrolstart < $starttime && $endtime < $enrolend) {
/* The enrolment is active during all the analysis time.
* |-----------------------------|
* |========|
* E start A start A end E end
*/
return true;
}
// If we reach this point is because the enrolment is only active for a portion of the analysis interval.
// Therefore, we check that it was active for most of the analysis interval, a self::ENROL_ACTIVE_PERCENT_REQUIRED.
if ($starttime <= $enrolstart && $enrolend <= $endtime) {
/* |=============================|
* |--------|
* A start E start E end A end
*/
$activeenrolduration = $enrolend - $enrolstart;
} else if ($enrolstart <= $starttime && $enrolend <= $endtime) {
/* |===================|
* |------------------|
* E start A start E end A end
*/
$activeenrolduration = $enrolend - $starttime;
} else if ($starttime <= $enrolstart && $endtime <= $enrolend) {
/* |===================|
* |------------------|
* A start E start A end E end
*/
$activeenrolduration = $endtime - $enrolstart;
}
$analysisduration = $endtime - $starttime;
if (floatval($activeenrolduration) / floatval($analysisduration) < self::ENROL_ACTIVE_PERCENT_REQUIRED) {
// The student was not enroled in the course for most of the analysis interval.
return false;
}
// We happily return true if the enrolment was active for more than self::ENROL_ACTIVE_PERCENT_REQUIRED of
// the analysis interval.
return true;
}
}
@@ -0,0 +1,186 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Getting the minimum grade to pass target.
*
* @package core_course
* @copyright 2019 Victor Deniz <victor@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_course\analytics\target;
defined('MOODLE_INTERNAL') || die();
/**
* Getting the minimum grade to pass target.
*
* @package core_course
* @copyright 2019 Victor Deniz <victor@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_gradetopass extends course_enrolments {
/**
* Courses grades to pass.
* @var mixed[]
*/
protected $coursesgradetopass = array();
/**
* Courses grades.
* @var mixed[]
*/
protected $coursesgrades = array();
/**
* Returns the grade to pass a course.
*
* Save the value in $coursesgradetopass array to prevent new accesses to the database.
*
* @param int $courseid The course id.
* @return array The courseitem id and the required grade to pass the course.
*/
protected function get_course_gradetopass($courseid) {
if (!isset($this->coursesgradetopass[$courseid])) {
// Get course grade_item.
$courseitem = \grade_item::fetch_course_item($courseid);
$ci = array();
$ci['courseitemid'] = $courseitem->id;
if ($courseitem->gradetype == GRADE_TYPE_VALUE && grade_floats_different($courseitem->gradepass, 0.0)) {
$ci['gradetopass'] = $courseitem->gradepass;
} else {
$ci['gradetopass'] = null;
}
$this->coursesgradetopass[$courseid] = $ci;
}
return $this->coursesgradetopass[$courseid];
}
/**
* Returns the grade of a user in a course.
*
* Saves the grades of all course users in $coursesgrades array to prevent new accesses to the database.
*
* @param int $courseitemid The course item id.
* @param int $userid the user whose grade is requested.
* @return array The courseitem id and the required grade to pass the course.
*/
protected function get_user_grade($courseitemid, $userid) {
// If the user grade for this course is not available, get all the grades for the course.
if (!isset($this->coursesgrades[$courseitemid])) {
// Ony a course is cached to avoid high memory usage.
unset($this->coursesgrades);
$gg = new \grade_grade(null, false);
$usersgrades = $gg->fetch_all(array('itemid' => $courseitemid));
if ($usersgrades) {
foreach ($usersgrades as $ug) {
$this->coursesgrades[$courseitemid][$ug->userid] = $ug->finalgrade;
}
}
}
if (!isset($this->coursesgrades[$courseitemid][$userid])) {
$this->coursesgrades[$courseitemid][$userid] = null;
}
return $this->coursesgrades[$courseitemid][$userid];
}
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('target:coursegradetopass', 'course');
}
/**
* Returns descriptions for each of the values the target calculation can return.
*
* @return string[]
*/
protected static function classes_description() {
return array(
get_string('targetlabelstudentgradetopassno', 'course'),
get_string('targetlabelstudentgradetopassyes', 'course')
);
}
/**
* Discards courses that are not yet ready to be used for training or prediction.
*
* Only courses with "value" grade type and grade to pass set are valid.
*
* @param \core_analytics\analysable $course
* @param bool $fortraining
* @return true|string
*/
public function is_valid_analysable(\core_analytics\analysable $course, $fortraining = true) {
$isvalid = parent::is_valid_analysable($course, $fortraining);
if (is_string($isvalid)) {
return $isvalid;
}
$courseitem = $this->get_course_gradetopass ($course->get_id());
if (is_null($courseitem['gradetopass'])) {
return get_string('gradetopassnotset', 'course');
}
return true;
}
/**
* The user's grade in the course sets the target value.
*
* @param int $sampleid
* @param \core_analytics\analysable $course
* @param int $starttime
* @param int $endtime
* @return float|null 0 -> course grade to pass achieved, 1 -> course grade to pass not achieved
*/
protected function calculate_sample($sampleid, \core_analytics\analysable $course, $starttime = false, $endtime = false) {
if (!$this->enrolment_active_during_analysis_time($sampleid, $starttime, $endtime)) {
// We should not use this sample as the analysis results could be misleading.
return null;
}
$userenrol = $this->retrieve('user_enrolments', $sampleid);
// Get course grade to pass.
$courseitem = $this->get_course_gradetopass($course->get_id());
// Get the user grade.
$usergrade = $this->get_user_grade($courseitem['courseitemid'], $userenrol->userid);
if ($usergrade >= $courseitem['gradetopass']) {
return 0;
}
return 1;
}
}
@@ -0,0 +1,80 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* No accesses since the start of the course.
*
* @package core_course
* @copyright 2019 David Monllaó {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_course\analytics\target;
defined('MOODLE_INTERNAL') || die();
/**
* No accesses since the start of the course.
*
* @package core_course
* @copyright 2019 David Monllaó {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class no_access_since_course_start extends no_recent_accesses {
/**
* Only past stuff whose start matches the course start.
*
* @param \core_analytics\local\time_splitting\base $timesplitting
* @return bool
*/
public function can_use_timesplitting(\core_analytics\local\time_splitting\base $timesplitting): bool {
return ($timesplitting instanceof \core_analytics\local\time_splitting\after_start);
}
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('target:noaccesssincecoursestart', 'course');
}
/**
* Returns the body message for the insight.
*
* @param \context $context
* @param string $contextname
* @param \stdClass $user
* @param \moodle_url $insighturl
* @return array The plain text message and the HTML message
*/
public function get_insight_body(\context $context, string $contextname, \stdClass $user, \moodle_url $insighturl): array {
global $OUTPUT;
$a = (object)['coursename' => $contextname, 'userfirstname' => $user->firstname];
$fullmessage = get_string('noaccesssincestartinfomessage', 'course', $a) . PHP_EOL . PHP_EOL . $insighturl->out(false);
$fullmessagehtml = $OUTPUT->render_from_template('core_analytics/insight_info_message',
['url' => $insighturl->out(false), 'insightinfomessage' => get_string('noaccesssincestartinfomessage', 'course', $a)]
);
return [$fullmessage, $fullmessagehtml];
}
}
@@ -0,0 +1,144 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* No recent accesses.
*
* @package core_course
* @copyright 2019 David Monllaó {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_course\analytics\target;
defined('MOODLE_INTERNAL') || die();
/**
* No recent accesses.
*
* @package core_course
* @copyright 2019 David Monllaó {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class no_recent_accesses extends course_enrolments {
/**
* Machine learning backends are not required to predict.
*
* @return bool
*/
public static function based_on_assumptions() {
return true;
}
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('target:norecentaccesses', 'course');
}
/**
* Returns the body message for the insight.
*
* @param \context $context
* @param string $contextname
* @param \stdClass $user
* @param \moodle_url $insighturl
* @return array The plain text message and the HTML message
*/
public function get_insight_body(\context $context, string $contextname, \stdClass $user, \moodle_url $insighturl): array {
global $OUTPUT;
$a = (object)['coursename' => $contextname, 'userfirstname' => $user->firstname];
$fullmessage = get_string('norecentaccessesinfomessage', 'course', $a) . PHP_EOL . PHP_EOL . $insighturl->out(false);
$fullmessagehtml = $OUTPUT->render_from_template('core_analytics/insight_info_message',
['url' => $insighturl->out(false), 'insightinfomessage' => get_string('norecentaccessesinfomessage', 'course', $a)]
);
return [$fullmessage, $fullmessagehtml];
}
/**
* Only past stuff whose start matches the course start.
*
* @param \core_analytics\local\time_splitting\base $timesplitting
* @return bool
*/
public function can_use_timesplitting(\core_analytics\local\time_splitting\base $timesplitting): bool {
return ($timesplitting instanceof \core_analytics\local\time_splitting\past_periodic);
}
/**
* Discards courses that are not yet ready to be used for prediction.
*
* @param \core_analytics\analysable $course
* @param bool $fortraining
* @return true|string
*/
public function is_valid_analysable(\core_analytics\analysable $course, $fortraining = true) {
if (!$course->was_started()) {
return get_string('coursenotyetstarted', 'course');
}
if (!$this->students = $course->get_students()) {
return get_string('nocoursestudents', 'course');
}
if (!$fortraining && !$course->get_course_data()->visible) {
return get_string('hiddenfromstudents');
}
if ($course->get_end() && $course->get_end() < $course->get_start()) {
return get_string('errorendbeforestart', 'course');
}
// Finished courses can not be used to get predictions.
if (!$fortraining && $course->is_finished()) {
return get_string('coursealreadyfinished', 'course');
}
return true;
}
/**
* Do the user has any read action in the course?
*
* @param int $sampleid
* @param \core_analytics\analysable $analysable
* @param int $starttime
* @param int $endtime
* @return float|null 0 -> accesses, 1 -> no accesses.
*/
protected function calculate_sample($sampleid, \core_analytics\analysable $analysable, $starttime = false, $endtime = false) {
if (!$this->enrolment_active_during_analysis_time($sampleid, $starttime, $endtime)) {
// We should not use this sample as the analysis results could be misleading.
return null;
}
$readactions = $this->retrieve('\core\analytics\indicator\any_course_access', $sampleid);
if ($readactions == \core\analytics\indicator\any_course_access::get_min_value()) {
return 1;
}
return 0;
}
}
@@ -0,0 +1,212 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* No teaching target.
*
* @package core_course
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_course\analytics\target;
defined('MOODLE_INTERNAL') || die();
/**
* No teaching target.
*
* @package core_course
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class no_teaching extends \core_analytics\local\target\binary {
/**
* Machine learning backends are not required to predict.
*
* @return bool
*/
public static function based_on_assumptions() {
return true;
}
/**
* It requires a specific time-splitting method.
*
* @param \core_analytics\local\time_splitting\base $timesplitting
* @return bool
*/
public function can_use_timesplitting(\core_analytics\local\time_splitting\base $timesplitting): bool {
return (get_class($timesplitting) === \core\analytics\time_splitting\single_range::class);
}
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('target:noteachingactivity', 'course');
}
/**
* Overwritten to show a simpler language string.
*
* @param int $modelid
* @param \context $context
* @return string
*/
public function get_insight_subject(int $modelid, \context $context) {
return get_string('noteachingupcomingcourses');
}
/**
* Returns the body message for the insight.
*
* @param \context $context
* @param string $contextname
* @param \stdClass $user
* @param \moodle_url $insighturl
* @return string[] The plain text message and the HTML message
*/
public function get_insight_body(\context $context, string $contextname, \stdClass $user, \moodle_url $insighturl): array {
global $OUTPUT;
$a = (object)['userfirstname' => $user->firstname];
$fullmessage = get_string('noteachinginfomessage', 'course', $a) . PHP_EOL . PHP_EOL . $insighturl->out(false);
$fullmessagehtml = $OUTPUT->render_from_template('core_analytics/insight_info_message',
['url' => $insighturl->out(false), 'insightinfomessage' => get_string('noteachinginfomessage', 'course', $a)]
);
return [$fullmessage, $fullmessagehtml];
}
/**
* prediction_actions
*
* @param \core_analytics\prediction $prediction
* @param mixed $includedetailsaction
* @param bool $isinsightuser
* @return \core_analytics\prediction_action[]
*/
public function prediction_actions(\core_analytics\prediction $prediction, $includedetailsaction = false,
$isinsightuser = false) {
global $CFG;
require_once($CFG->dirroot . '/course/lib.php');
$sampledata = $prediction->get_sample_data();
$course = $sampledata['course'];
$actions = array();
$url = new \moodle_url('/course/view.php', array('id' => $course->id));
$pix = new \pix_icon('i/course', get_string('course'));
$actions[] = new \core_analytics\prediction_action('viewcourse', $prediction,
$url, $pix, get_string('view'));
if (course_can_view_participants($sampledata['context'])) {
$url = new \moodle_url('/user/index.php', array('id' => $course->id));
$pix = new \pix_icon('i/cohort', get_string('participants'));
$actions[] = new \core_analytics\prediction_action('viewparticipants', $prediction,
$url, $pix, get_string('participants'));
}
$parentactions = parent::prediction_actions($prediction, $includedetailsaction, $isinsightuser);
return array_merge($actions, $parentactions);
}
/**
* classes_description
*
* @return string[]
*/
protected static function classes_description() {
return array(
get_string('targetlabelteachingyes', 'course'),
get_string('targetlabelteachingno', 'course'),
);
}
/**
* get_analyser_class
*
* @return string
*/
public function get_analyser_class() {
return '\core\analytics\analyser\site_courses';
}
/**
* is_valid_analysable
*
* @param \core_analytics\analysable $analysable
* @param mixed $fortraining
* @return true|string
*/
public function is_valid_analysable(\core_analytics\analysable $analysable, $fortraining = true) {
// The analysable is the site, so yes, it is always valid.
return true;
}
/**
* Only process samples which start date is getting close.
*
* @param int $sampleid
* @param \core_analytics\analysable $analysable
* @param bool $fortraining
* @return bool
*/
public function is_valid_sample($sampleid, \core_analytics\analysable $analysable, $fortraining = true) {
$course = $this->retrieve('course', $sampleid);
$now = time();
// No courses without start date, no finished courses, no predictions before start - 1 week nor
// predictions for courses that started more than 1 week ago.
if (!$course->startdate || (!empty($course->enddate) && $course->enddate < $now) ||
$course->startdate - WEEKSECS > $now || $course->startdate + WEEKSECS < $now) {
return false;
}
return true;
}
/**
* calculate_sample
*
* @param int $sampleid
* @param \core_analytics\analysable $analysable
* @param int $starttime
* @param int $endtime
* @return float
*/
protected function calculate_sample($sampleid, \core_analytics\analysable $analysable, $starttime = false, $endtime = false) {
$noteachersindicator = $this->retrieve('\core_course\analytics\indicator\no_teacher', $sampleid);
$nostudentsindicator = $this->retrieve('\core_course\analytics\indicator\no_student', $sampleid);
if ($noteachersindicator == \core_course\analytics\indicator\no_teacher::get_min_value() ||
$nostudentsindicator == \core_course\analytics\indicator\no_student::get_min_value()) {
// No teachers or no students :(.
return 1;
}
return 0;
}
}