diff --git a/.eslintignore b/.eslintignore index c671566a14978..0622612311021 100644 --- a/.eslintignore +++ b/.eslintignore @@ -81,4 +81,4 @@ theme/boost/scss/fontawesome/ theme/bootstrapbase/less/bootstrap/ theme/bootstrapbase/javascript/html5shiv.js theme/bootstrapbase/amd/src/bootstrap.js -theme/bootstrapbase/less/fontawesome/ +theme/bootstrapbase/less/fontawesome/ \ No newline at end of file diff --git a/.stylelintignore b/.stylelintignore index d8ad8e62b85b2..cfcf7025b0d9f 100644 --- a/.stylelintignore +++ b/.stylelintignore @@ -82,4 +82,4 @@ theme/boost/scss/fontawesome/ theme/bootstrapbase/less/bootstrap/ theme/bootstrapbase/javascript/html5shiv.js theme/bootstrapbase/amd/src/bootstrap.js -theme/bootstrapbase/less/fontawesome/ +theme/bootstrapbase/less/fontawesome/ \ No newline at end of file diff --git a/admin/tool/models/amd/src/log_info.js b/admin/tool/models/amd/src/log_info.js index 5af6d6aaa4bfb..6f9a114734be6 100644 --- a/admin/tool/models/amd/src/log_info.js +++ b/admin/tool/models/amd/src/log_info.js @@ -13,11 +13,12 @@ define(['jquery', 'core/str', 'core/modal_factory', 'core/notification'], functi /** * Prepares a modal info for a log's results. + * * @access public * @param {int} id - * @return {String} HTML info + * @param {string[]} info */ - loadInfo : function(id, info) { + loadInfo: function(id, info) { var link = $('[data-model-log-id="' + id + '"]'); str.get_string('loginfo', 'tool_models').then(function(langString) { diff --git a/admin/tool/models/classes/analytics/target/course_dropout.php b/admin/tool/models/classes/analytics/target/course_dropout.php index 1f6554f224edf..6ce111b1ef96d 100644 --- a/admin/tool/models/classes/analytics/target/course_dropout.php +++ b/admin/tool/models/classes/analytics/target/course_dropout.php @@ -40,12 +40,22 @@ */ class course_dropout extends \core_analytics\local\target\binary { - protected $coursegradeitem = null; - + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('target:coursedropout', 'tool_models'); } + /** + * prediction_actions + * + * @param \core_analytics\prediction $prediction + * @param bool $includedetailsaction + * @return \core_analytics\prediction_action[] + */ public function prediction_actions(\core_analytics\prediction $prediction, $includedetailsaction = false) { global $USER; @@ -57,17 +67,24 @@ public function prediction_actions(\core_analytics\prediction $prediction, $incl // Send a message. $url = new \moodle_url('/message/index.php', array('user' => $USER->id, 'id' => $studentid)); $pix = new \pix_icon('t/message', get_string('sendmessage', 'message')); - $actions['studentmessage'] = new \core_analytics\prediction_action('studentmessage', $prediction, $url, $pix, get_string('sendmessage', 'message')); + $actions['studentmessage'] = new \core_analytics\prediction_action('studentmessage', $prediction, $url, $pix, + get_string('sendmessage', 'message')); // 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['viewoutlinereport'] = new \core_analytics\prediction_action('viewoutlinereport', $prediction, $url, $pix, get_string('outlinereport')); + $actions['viewoutlinereport'] = new \core_analytics\prediction_action('viewoutlinereport', $prediction, $url, $pix, + get_string('outlinereport')); return $actions; } + /** + * classes_description + * + * @return string[] + */ protected static function classes_description() { return array( get_string('labelstudentdropoutno', 'tool_models'), @@ -86,10 +103,22 @@ protected function ignored_predicted_classes() { return array(); } + /** + * get_analyser_class + * + * @return string + */ public function get_analyser_class() { return '\\core_analytics\\local\\analyser\\student_enrolments'; } + /** + * is_valid_analysable + * + * @param \core_analytics\analysable $course + * @param bool $fortraining + * @return true|string + */ public function is_valid_analysable(\core_analytics\analysable $course, $fortraining = true) { global $DB; @@ -145,7 +174,7 @@ public function is_valid_analysable(\core_analytics\analysable $course, $fortrai * @param int $sampleid * @param \core_analytics\analysable $course * @param bool $fortraining - * @return bool + * @return true|string */ public function is_valid_sample($sampleid, \core_analytics\analysable $course, $fortraining = true) { return true; @@ -160,7 +189,9 @@ public function is_valid_sample($sampleid, \core_analytics\analysable $course, $ * * @param int $sampleid * @param \core_analytics\analysable $course - * @return void + * @param int $starttime + * @param int $endtime + * @return float */ protected function calculate_sample($sampleid, \core_analytics\analysable $course, $starttime = false, $endtime = false) { diff --git a/admin/tool/models/classes/analytics/target/no_teaching.php b/admin/tool/models/classes/analytics/target/no_teaching.php index 8c0194c2cc1f1..e35bb90bcd803 100644 --- a/admin/tool/models/classes/analytics/target/no_teaching.php +++ b/admin/tool/models/classes/analytics/target/no_teaching.php @@ -44,10 +44,22 @@ public static function based_on_assumptions() { return true; } + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('target:noteachingactivity', 'tool_models'); } + /** + * prediction_actions + * + * @param \core_analytics\prediction $prediction + * @param mixed $includedetailsaction + * @return \core_analytics\prediction_action[] + */ public function prediction_actions(\core_analytics\prediction $prediction, $includedetailsaction = false) { global $USER; @@ -79,6 +91,11 @@ public function prediction_actions(\core_analytics\prediction $prediction, $incl return $actions; } + /** + * classes_description + * + * @return string[] + */ protected static function classes_description() { return array( get_string('labelteachingyes', 'tool_models'), @@ -96,10 +113,22 @@ protected function ignored_predicted_classes() { return array(0); } + /** + * get_analyser_class + * + * @return string + */ public function get_analyser_class() { return '\\core_analytics\\local\\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; @@ -108,10 +137,10 @@ public function is_valid_analysable(\core_analytics\analysable $analysable, $for /** * Only process samples which start date is getting close. * - * @param mixed $sampleid + * @param int $sampleid * @param \core_analytics\analysable $analysable * @param bool $fortraining - * @return void + * @return true|string */ public function is_valid_sample($sampleid, \core_analytics\analysable $analysable, $fortraining = true) { @@ -132,7 +161,9 @@ public function is_valid_sample($sampleid, \core_analytics\analysable $analysabl * * @param int $sampleid * @param \core_analytics\analysable $analysable - * @return void + * @param int $starttime + * @param int $endtime + * @return float */ protected function calculate_sample($sampleid, \core_analytics\analysable $analysable, $starttime = false, $endtime = false) { diff --git a/admin/tool/models/classes/output/form/edit_model.php b/admin/tool/models/classes/output/form/edit_model.php index 86a3b6de6b893..095c0b67db0b1 100644 --- a/admin/tool/models/classes/output/form/edit_model.php +++ b/admin/tool/models/classes/output/form/edit_model.php @@ -24,6 +24,8 @@ namespace tool_models\output\form; +defined('MOODLE_INTERNAL') || die(); + require_once($CFG->dirroot.'/lib/formslib.php'); /** diff --git a/admin/tool/models/classes/output/helper.php b/admin/tool/models/classes/output/helper.php index 8420b1a8fc8ab..c091f468779ee 100644 --- a/admin/tool/models/classes/output/helper.php +++ b/admin/tool/models/classes/output/helper.php @@ -24,8 +24,10 @@ namespace tool_models\output; +defined('MOODLE_INTERNAL') || die(); + /** - * Typical crappy helper class with tiny functions. + * Helper class with general purpose tiny functions. * * @package tool_models * @copyright 2017 David Monllao {@link http://www.davidmonllao.com} @@ -33,6 +35,12 @@ */ class helper { + /** + * Converts a class full name to a select option key + * + * @param string $class + * @return string + */ public static function class_to_option($class) { // Form field is PARAM_ALPHANUMEXT and we are sending fully qualified class names // as option names, but replacing the backslash for a string that is really unlikely @@ -40,6 +48,12 @@ public static function class_to_option($class) { return str_replace('\\', '2015102400ouuu', $class); } + /** + * option_to_class + * + * @param string $option + * @return string + */ public static function option_to_class($option) { // Really unlikely but yeah, I'm a bad booyyy. return str_replace('2015102400ouuu', '\\', $option); diff --git a/admin/tool/models/classes/output/models_list.php b/admin/tool/models/classes/output/models_list.php index 338a0c7f43c24..2894e762fd2d3 100644 --- a/admin/tool/models/classes/output/models_list.php +++ b/admin/tool/models/classes/output/models_list.php @@ -35,8 +35,19 @@ */ class models_list implements \renderable, \templatable { + /** + * models + * + * @var \core_analytics\model[] + */ protected $models = array(); + /** + * __construct + * + * @param \core_analytics\model[] $models + * @return void + */ public function __construct($models) { $this->models = $models; } diff --git a/admin/tool/models/classes/output/renderer.php b/admin/tool/models/classes/output/renderer.php index 9f06357e41d85..6110aeecf61be 100644 --- a/admin/tool/models/classes/output/renderer.php +++ b/admin/tool/models/classes/output/renderer.php @@ -106,7 +106,8 @@ public function render_evaluate_results($results, $logs = array()) { if (isset($result->score)) { // Score. - $output .= $OUTPUT->heading(get_string('accuracy', 'tool_models') . ': ' . round(floatval($result->score), 4) * 100 . '%', 4); + $output .= $OUTPUT->heading(get_string('accuracy', 'tool_models') . ': ' . + round(floatval($result->score), 4) * 100 . '%', 4); } if (!empty($result->info)) { diff --git a/admin/tool/models/classes/task/predict_models.php b/admin/tool/models/classes/task/predict_models.php index 366b8b6c8b873..9f4d3db1d2ba9 100644 --- a/admin/tool/models/classes/task/predict_models.php +++ b/admin/tool/models/classes/task/predict_models.php @@ -1,5 +1,4 @@ close(); +/** + * tool_models_calculate_course_dates + * + * @param stdClass $course + * @param array $options CLI options + * @return void + */ function tool_models_calculate_course_dates($course, $options) { global $DB, $OUTPUT; @@ -153,7 +160,8 @@ function tool_models_calculate_course_dates($course, $options) { if ($options['update']) { format_weeks::update_end_date($course->id); $course->enddate = $DB->get_field('course', 'enddate', array('id' => $course->id)); - $notification .= PHP_EOL . ' ' . get_string('weeksenddateautomaticallyset', 'tool_models') . ': ' . userdate($course->enddate); + $notification .= PHP_EOL . ' ' . get_string('weeksenddateautomaticallyset', 'tool_models') . ': ' . + userdate($course->enddate); } else { // We can't provide more info without actually updating it in db. $notification .= PHP_EOL . ' ' . get_string('weeksenddatedefault', 'tool_models'); @@ -194,4 +202,6 @@ function tool_models_calculate_course_dates($course, $options) { echo mtrace($notification); } +echo mtrace(get_string('success')); + exit(0); diff --git a/admin/tool/models/db/tasks.php b/admin/tool/models/db/tasks.php index 276a2775328b3..13e21f62a1061 100644 --- a/admin/tool/models/db/tasks.php +++ b/admin/tool/models/db/tasks.php @@ -17,11 +17,13 @@ /** * This file defines tasks performed by the tool. * - * @package tool_monitor - * @copyright 2014 Mark Nelson + * @package tool_models + * @copyright 2017 David Monllao {@link http://www.davidmonllao.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +defined('MOODLE_INTERNAL') || die(); + // List of tasks. $tasks = array( array( diff --git a/admin/tool/models/db/uninstall.php b/admin/tool/models/db/uninstall.php index d1db7817a8724..2eb1e931efd9b 100644 --- a/admin/tool/models/db/uninstall.php +++ b/admin/tool/models/db/uninstall.php @@ -24,6 +24,13 @@ defined('MOODLE_INTERNAL') || die(); +/** + * Deletes models created by tool_models + * + * @package tool_models + * @copyright 2017 David Monllao {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ function xmldb_tool_models_uninstall() { global $DB; diff --git a/admin/tool/models/index.php b/admin/tool/models/index.php index 76176fc232e90..415a4885ecceb 100644 --- a/admin/tool/models/index.php +++ b/admin/tool/models/index.php @@ -25,7 +25,7 @@ require_once(__DIR__ . '/../../../config.php'); require_once($CFG->libdir . '/adminlib.php'); -admin_externalpage_setup('analyticmodels', '', null, '', array('pagelayout'=>'report')); +admin_externalpage_setup('analyticmodels', '', null, '', array('pagelayout' => 'report')); $models = \core_analytics\manager::get_all_models(); diff --git a/admin/tool/models/model.php b/admin/tool/models/model.php index 676585b8b1303..26c3fb201a191 100644 --- a/admin/tool/models/model.php +++ b/admin/tool/models/model.php @@ -17,7 +17,7 @@ /** * Model-related actions. * - * @package tool_model + * @package tool_models * @copyright 2017 David Monllao {@link http://www.davidmonllao.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/analytics/classes/admin_setting_predictor.php b/analytics/classes/admin_setting_predictor.php index e1b0b6aace309..203ad5542e619 100644 --- a/analytics/classes/admin_setting_predictor.php +++ b/analytics/classes/admin_setting_predictor.php @@ -15,6 +15,7 @@ // along with Moodle. If not, see . /** + * Extension to show an error message if the selected predictor is not available. * * @package core_analytics * @copyright 2017 David Monllao {@link http://www.davidmonllao.com} @@ -27,6 +28,13 @@ require_once(__DIR__ . '/../../lib/adminlib.php'); +/** + * Extension to show an error message if the selected predictor is not available. + * + * @package core_analytics + * @copyright 2017 David Monllao {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class admin_setting_predictor extends \admin_setting_configselect { /** @@ -40,7 +48,7 @@ public function write_setting($data) { return ''; } if (!array_key_exists($data, $this->choices)) { - return ''; // ignore it + return ''; } // Calling it here without checking if it is ready because we check it below and show it as a controlled case. diff --git a/analytics/classes/analysable.php b/analytics/classes/analysable.php index a5187b12915f9..88d10a6bf96ee 100644 --- a/analytics/classes/analysable.php +++ b/analytics/classes/analysable.php @@ -15,6 +15,7 @@ // along with Moodle. If not, see . /** + * Any element analysers can analyse. * * @package core_analytics * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} @@ -26,6 +27,7 @@ defined('MOODLE_INTERNAL') || die(); /** + * Any element analysers can analyse. * * @package core_analytics * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} @@ -33,13 +35,36 @@ */ interface analysable { + /** + * Max timestamp. + */ const MAX_TIME = 9999999999; + /** + * The analysable unique identifier in the site. + * + * @return int. + */ public function get_id(); + /** + * The analysable context. + * + * @return \context + */ public function get_context(); + /** + * The start of the analysable if there is one. + * + * @return int|false + */ public function get_start(); + /** + * The end of the analysable if there is one. + * + * @return int|false + */ public function get_end(); } diff --git a/analytics/classes/calculable.php b/analytics/classes/calculable.php index 29147b76f8971..7d4597bd01795 100644 --- a/analytics/classes/calculable.php +++ b/analytics/classes/calculable.php @@ -35,10 +35,29 @@ */ abstract class calculable { + /** + * Neutral calculation outcome. + */ const OUTCOME_NEUTRAL = 0; + + /** + * Very positive calculation outcome. + */ const OUTCOME_VERY_POSITIVE = 1; + + /** + * Positive calculation outcome. + */ const OUTCOME_OK = 2; + + /** + * Negative calculation outcome. + */ const OUTCOME_NEGATIVE = 3; + + /** + * Very negative calculation outcome. + */ const OUTCOME_VERY_NEGATIVE = 4; /** @@ -243,7 +262,6 @@ protected function classify_value($value, $ranges) { * Merges arrays recursively keeping the same keys the original arrays have. * * @link http://php.net/manual/es/function.array-merge-recursive.php#114818 - * @param array * @return array */ private function array_merge_recursive_keep_keys() { @@ -256,8 +274,8 @@ private function array_merge_recursive_keep_keys() { if (is_array($value) && !empty($base[$key]) && is_array($base[$key])) { $base[$key] = $this->array_merge_recursive_keep_keys($base[$key], $value); } else { - if(isset($base[$key]) && is_int($key)) { - $key++; + if (isset($base[$key]) && is_int($key)) { + $key++; } $base[$key] = $value; } diff --git a/analytics/classes/course.php b/analytics/classes/course.php index 2d8ee540dc751..6dee6cf8d632d 100644 --- a/analytics/classes/course.php +++ b/analytics/classes/course.php @@ -15,6 +15,7 @@ // along with Moodle. If not, see . /** + * Moodle course analysable * * @package core_analytics * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} @@ -30,6 +31,7 @@ require_once($CFG->dirroot . '/lib/enrollib.php'); /** + * Moodle course analysable * * @package core_analytics * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} @@ -37,24 +39,81 @@ */ class course implements \core_analytics\analysable { + /** + * @var \core_analytics\course[] $instances + */ protected static $instances = array(); - protected static $studentroles = []; - protected static $teacherroles = []; - + /** + * Course object + * + * @var \stdClass + */ protected $course = null; + + /** + * The course context. + * + * @var \context_course + */ protected $coursecontext = null; + /** + * The course activities organized by activity type. + * + * @var array + */ protected $courseactivities = array(); + /** + * Course start time. + * + * @var int + */ protected $starttime = null; + + + /** + * Has the course already started? + * + * @var bool + */ protected $started = null; + + /** + * Course end time. + * + * @var int + */ protected $endtime = null; + + /** + * Is the course finished? + * + * @var bool + */ protected $finished = null; + /** + * Course students ids. + * + * @var int[] + */ protected $studentids = []; + + + /** + * Course teachers ids + * + * @var int[] + */ protected $teacherids = []; + /** + * Cached copy of the total number of logs in the course. + * + * @var int + */ protected $ntotallogs = null; /** @@ -78,18 +137,14 @@ public function __construct($course) { $this->coursecontext = \context_course::instance($this->course->id); - if (empty(self::$studentroles)) { - self::$studentroles = array_keys(get_archetype_roles('student')); - } - if (empty(self::$teacherroles)) { - self::$teacherroles = array_keys(get_archetype_roles('editingteacher') + get_archetype_roles('teacher')); - } - $this->now = time(); // Get the course users, including users assigned to student and teacher roles at an higher context. - $this->studentids = $this->get_user_ids(self::$studentroles); - $this->teacherids = $this->get_user_ids(self::$teacherroles); + $studentroles = array_keys(get_archetype_roles('student')); + $this->studentids = $this->get_user_ids($studentroles); + + $teacherroles = array_keys(get_archetype_roles('editingteacher') + get_archetype_roles('teacher')); + $this->teacherids = $this->get_user_ids($teacherroles); } /** @@ -114,10 +169,20 @@ public static function instance($course) { return self::$instances[$courseid]; } + /** + * get_id + * + * @return int + */ public function get_id() { return $this->course->id; } + /** + * get_context + * + * @return \context + */ public function get_context() { if ($this->coursecontext === null) { $this->coursecontext = \context_course::instance($this->course->id); @@ -147,6 +212,11 @@ public function get_start() { return $this->starttime; } + /** + * Guesses the start of the course based on students' activity and enrolment start dates. + * + * @return int + */ public function guess_start() { global $DB; @@ -168,8 +238,10 @@ public function guess_start() { $select = "userid = :userid AND contextlevel = :contextlevel AND contextinstanceid = :contextinstanceid"; $params = array('userid' => $studentid, 'contextlevel' => CONTEXT_COURSE, 'contextinstanceid' => $this->get_id()); $events = $logstore->get_events_select($select, $params, 'timecreated ASC', 0, 1); - $event = reset($events); - $firstlogs = $event->timecreated; + if ($events) { + $event = reset($events); + $firstlogs[] = $event->timecreated; + } } if (empty($firstlogs)) { // Can't guess if no student accesses. @@ -257,6 +329,11 @@ public function guess_end() { return $this->median($studentlastaccesses); } + /** + * Returns a course plain object. + * + * @return \stdClass + */ public function get_course_data() { return $this->course; } @@ -362,6 +439,12 @@ public function get_total_logs() { return $this->ntotallogs; } + /** + * Returns all the activities of the provided type the course has. + * + * @param string $activitytype + * @return array + */ public function get_all_activities($activitytype) { // Using is set because we set it to false if there are no activities. @@ -383,6 +466,12 @@ public function get_all_activities($activitytype) { return $this->courseactivities[$activitytype]; } + /** + * Returns the course students grades. + * + * @param array $courseactivities + * @return array + */ public function get_student_grades($courseactivities) { if (empty($courseactivities)) { @@ -410,9 +499,18 @@ public function get_student_grades($courseactivities) { return $grades; } + /** + * Guesses all activities that were available during a period of time. + * + * @param string $activitytype + * @param int $starttime + * @param int $endtime + * @param \stdClass $student + * @return array + */ public function get_activities($activitytype, $starttime, $endtime, $student = false) { - // $student may not be available, default to not calculating dynamic data. + // Var $student may not be available, default to not calculating dynamic data. $studentid = -1; if ($student) { $studentid = $student->id; @@ -432,6 +530,14 @@ public function get_activities($activitytype, $starttime, $endtime, $student = f return $timerangeactivities; } + /** + * Was the activity supposed to be completed during the provided time range?. + * + * @param \cm_info $activity + * @param int $starttime + * @param int $endtime + * @return bool + */ protected function completed_by(\cm_info $activity, $starttime, $endtime) { // We can't check uservisible because: @@ -455,7 +561,7 @@ protected function completed_by(\cm_info $activity, $starttime, $endtime) { } } - //// We skip activities in sections that were not yet visible or their 'until' was not in this $starttime - $endtime range. + // We skip activities in sections that were not yet visible or their 'until' was not in this $starttime - $endtime range. $section = $activity->get_modinfo()->get_section_info($activity->sectionnum); if ($section->availability) { $info = new \core_availability\info_section($section); @@ -524,6 +630,14 @@ protected function completed_by(\cm_info $activity, $starttime, $endtime) { return false; } + /** + * Check if the activity/section should have been completed during the provided period according to its availability rules. + * + * @param \core_availability\info $info + * @param int $starttime + * @param int $endtime + * @return bool|null + */ protected function availability_completed_by(\core_availability\info $info, $starttime, $endtime) { $dateconditions = $info->get_availability_tree()->get_all_children('\availability_date\condition'); @@ -576,6 +690,7 @@ protected function update_loop_times($start, $end) { /** * Returns the query and params used to filter the logstore by this course students. * + * @param string $prefix * @return array */ protected function course_students_query_filter($prefix = false) { diff --git a/analytics/classes/dataset_manager.php b/analytics/classes/dataset_manager.php index 115f1cbd824ea..fe34cbb897348 100644 --- a/analytics/classes/dataset_manager.php +++ b/analytics/classes/dataset_manager.php @@ -15,6 +15,7 @@ // along with Moodle. If not, see . /** + * Datasets manager. * * @package core_analytics * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} @@ -26,6 +27,7 @@ defined('MOODLE_INTERNAL') || die(); /** + * Datasets manager. * * @package core_analytics * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} @@ -33,8 +35,19 @@ */ class dataset_manager { + /** + * File area for labelled datasets. + */ const LABELLED_FILEAREA = 'labelled'; + + /** + * File area for unlabelled datasets. + */ const UNLABELLED_FILEAREA = 'unlabelled'; + + /** + * Evaluation file file name. + */ const EVALUATION_FILENAME = 'evaluation.csv'; /** @@ -71,8 +84,13 @@ class dataset_manager { protected $includetarget; /** - * Simple constructor. + * Constructor method. * + * @param int $modelid + * @param int $analysableid + * @param string $timesplittingid + * @param bool $evaluation + * @param bool $includetarget * @return void */ public function __construct($modelid, $analysableid, $timesplittingid, $evaluation = false, $includetarget = false) { @@ -90,7 +108,8 @@ public function __construct($modelid, $analysableid, $timesplittingid, $evaluati */ public function init_process() { $lockkey = 'modelid:' . $this->modelid . '-analysableid:' . $this->analysableid . - '-timesplitting:' . self::clean_time_splitting_id($this->timesplittingid) . '-includetarget:' . (int)$this->includetarget; + '-timesplitting:' . self::clean_time_splitting_id($this->timesplittingid) . + '-includetarget:' . (int)$this->includetarget; // Large timeout as processes may be quite long. $lockfactory = \core\lock\lock_config::get_lock_factory('core_analytics'); @@ -168,6 +187,13 @@ public static function get_previous_evaluation_file($modelid, $timesplittingid) '/timesplitting/' . self::clean_time_splitting_id($timesplittingid) . '/', self::EVALUATION_FILENAME); } + /** + * Deletes previous evaluation files of this model. + * + * @param int $modelid + * @param string $timesplittingid + * @return bool + */ public static function delete_previous_evaluation_file($modelid, $timesplittingid) { $fs = get_file_storage(); if ($file = self::get_previous_evaluation_file($modelid, $timesplittingid)) { @@ -178,6 +204,14 @@ public static function delete_previous_evaluation_file($modelid, $timesplittingi return false; } + /** + * Returns this (model + analysable + time splitting) file. + * + * @param int $modelid + * @param int $analysableid + * @param string $timesplittingid + * @return \stored_file + */ public static function get_evaluation_analysable_file($modelid, $analysableid, $timesplittingid) { // Delete previous file if it exists. @@ -196,7 +230,6 @@ public static function get_evaluation_analysable_file($modelid, $analysableid, $ * Important! It is the caller responsability to ensure that the datasets are compatible. * * @param array $files - * @param string $filename * @param int $modelid * @param string $timesplittingid * @param bool $evaluation @@ -277,6 +310,12 @@ public static function merge_datasets(array $files, $modelid, $timesplittingid, return $fs->create_file_from_pathname($filerecord, $tmpfilepath); } + /** + * Returns the dataset file data structured by sampleids using the indicators and target column names. + * + * @param \stored_file $dataset + * @return array + */ public static function get_structured_data(\stored_file $dataset) { if ($dataset->get_filearea() !== 'unlabelled') { @@ -315,6 +354,12 @@ public static function get_structured_data(\stored_file $dataset) { return $calculations; } + /** + * Delete all files of a model. + * + * @param int $modelid + * @return bool + */ public static function clear_model_files($modelid) { $fs = get_file_storage(); return $fs->delete_area_files(\context_system::instance()->id, 'analytics', false, $modelid); @@ -331,6 +376,12 @@ protected static function clean_time_splitting_id($timesplittingid) { return clean_param($timesplittingid, PARAM_ALPHANUMEXT); } + /** + * Returns the file name to be used. + * + * @param strinbool $evaluation + * @return string + */ protected static function get_filename($evaluation) { if ($evaluation === true) { @@ -343,6 +394,12 @@ protected static function get_filename($evaluation) { return $filename; } + /** + * Returns the file area to be used. + * + * @param bool $includetarget + * @return string + */ protected static function get_filearea($includetarget) { if ($includetarget === true) { diff --git a/analytics/classes/local/analyser/base.php b/analytics/classes/local/analyser/base.php index 96ba73208e5ef..f2dc76ba0c7b1 100644 --- a/analytics/classes/local/analyser/base.php +++ b/analytics/classes/local/analyser/base.php @@ -15,6 +15,7 @@ // along with Moodle. If not, see . /** + * Analysers base class. * * @package core_analytics * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} @@ -26,6 +27,7 @@ defined('MOODLE_INTERNAL') || die(); /** + * Analysers base class. * * @package core_analytics * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} @@ -33,16 +35,59 @@ */ abstract class base { + /** + * @var int + */ protected $modelid; + /** + * The model target. + * + * @var \core_analytics\local\target\base + */ protected $target; + + /** + * The model indicators. + * + * @var \core_analytics\local\indicator\base[] + */ protected $indicators; + + /** + * Time splitting methods to use. + * + * Multiple time splitting methods during evaluation and 1 single + * time splitting method once the model is enabled. + * + * @var \core_analytics\local\time_splitting\base[] + */ protected $timesplittings; + /** + * Execution options. + * + * @var array + */ protected $options; + /** + * Simple log array. + * + * @var string[] + */ protected $log; + /** + * Constructor method. + * + * @param int $modelid + * @param \core_analytics\local\target\base $target + * @param \core_analytics\local\indicator\base[] $indicators + * @param \core_analytics\local\time_splitting\base[] $timesplittings + * @param array $options + * @return void + */ public function __construct($modelid, \core_analytics\local\target\base $target, $indicators, $timesplittings, $options) { $this->modelid = $modelid; $this->target = $target; @@ -61,7 +106,7 @@ public function __construct($modelid, \core_analytics\local\target\base $target, } /** - * This function returns the list of samples that can be calculated. + * This function returns this analysable list of samples. * * @param \core_analytics\analysable $analysable * @return array array[0] = int[] (sampleids) and array[1] = array (samplesdata) @@ -69,7 +114,7 @@ public function __construct($modelid, \core_analytics\local\target\base $target, abstract protected function get_all_samples(\core_analytics\analysable $analysable); /** - * get_samples + * This function returns the samples data from a list of sample ids. * * @param int[] $sampleids * @return array array[0] = int[] (sampleids) and array[1] = array (samplesdata) @@ -77,7 +122,7 @@ abstract protected function get_all_samples(\core_analytics\analysable $analysab abstract public function get_samples($sampleids); /** - * get_sample_analysable + * Returns the analysable of a sample. * * @param int $sampleid * @return \core_analytics\analysable @@ -85,13 +130,15 @@ abstract public function get_samples($sampleids); abstract public function get_sample_analysable($sampleid); /** - * get_samples_origin + * Returns the sample's origin in moodle database. * * @return string */ abstract protected function get_samples_origin(); /** + * Returns the context of a sample. + * * moodle/analytics:listinsights will be required at this level to access the sample predictions. * * @param int $sampleid @@ -100,7 +147,7 @@ abstract protected function get_samples_origin(); abstract public function sample_access_context($sampleid); /** - * sample_description + * Describes a sample with a description summary and a \renderable (an image for example) * * @param int $sampleid * @param int $contextid @@ -109,10 +156,6 @@ abstract public function sample_access_context($sampleid); */ abstract public function sample_description($sampleid, $contextid, $sampledata); - protected function provided_sample_data() { - return array($this->get_samples_origin()); - } - /** * Main analyser method which processes the site analysables. * @@ -121,14 +164,34 @@ protected function provided_sample_data() { * In most of the cases you should have enough extending from one of these classes so you don't need * to reimplement this method. * + * @param bool $includetarget * @return \stored_file[] */ abstract public function get_analysable_data($includetarget); + /** + * Samples data this analyser provides. + * + * @return string[] + */ + protected function provided_sample_data() { + return array($this->get_samples_origin()); + } + + /** + * Returns labelled data (training and evaluation). + * + * @return array + */ public function get_labelled_data() { return $this->get_analysable_data(true); } + /** + * Returns unlabelled data (prediction). + * + * @return array + */ public function get_unlabelled_data() { return $this->get_analysable_data(false); } @@ -151,7 +214,7 @@ protected function check_indicators_requirements() { } /** - * check_indicator_requirements + * Checks that this analyser satisfies the provided indicator requirements. * * @param \core_analytics\local\indicator\base $indicator * @return true|string[] True if all good, missing requirements list otherwise @@ -248,7 +311,7 @@ public function process_analysable($analysable, $includetarget) { $a = new \stdClass(); $a->analysableid = $analysable->get_id(); - $a->errors = implode(', ', $errors); + $a->errors = implode(', ', $errors); $this->add_log(get_string('analysablenotused', 'analytics', $a)); } @@ -256,7 +319,7 @@ public function process_analysable($analysable, $includetarget) { } /** - * add_log + * Adds a register to the analysis log. * * @param string $string * @return void @@ -266,7 +329,7 @@ public function add_log($string) { } /** - * get_logs + * Returns the analysis logs. * * @return string[] */ @@ -274,12 +337,20 @@ public function get_logs() { return $this->log; } + /** + * Processes the analysable samples using the provided time splitting method. + * + * @param \core_analytics\local\time_splitting\base $timesplitting + * @param \core_analytics\analysable $analysable + * @param \core_analytics\local\target\base|false $target + * @return \stdClass Results object. + */ protected function process_time_splitting($timesplitting, $analysable, $target = false) { $result = new \stdClass(); if (!$timesplitting->is_valid_analysable($analysable)) { - $result->status = \core_analytics\model::ANALYSE_REJECTED_RANGE_PROCESSOR; + $result->status = \core_analytics\model::ANALYSABLE_REJECTED_TIME_SPLITTING_METHOD; $result->message = get_string('invalidanalysablefortimesplitting', 'analytics', $timesplitting->get_name()); return $result; @@ -287,7 +358,8 @@ protected function process_time_splitting($timesplitting, $analysable, $target = $timesplitting->set_analysable($analysable); if (CLI_SCRIPT && !PHPUNIT_TEST) { - mtrace('Analysing id "' . $analysable->get_id() . '" with "' . $timesplitting->get_name() . '" time splitting method...'); + mtrace('Analysing id "' . $analysable->get_id() . '" with "' . $timesplitting->get_name() . + '" time splitting method...'); } // What is a sample is defined by the analyser, it can be an enrolment, a course, a user, a question @@ -295,7 +367,7 @@ protected function process_time_splitting($timesplitting, $analysable, $target = list($sampleids, $samplesdata) = $this->get_all_samples($analysable); if (count($sampleids) === 0) { - $result->status = \core_analytics\model::ANALYSE_REJECTED_RANGE_PROCESSOR; + $result->status = \core_analytics\model::ANALYSABLE_REJECTED_TIME_SPLITTING_METHOD; $result->message = get_string('nodata', 'analytics'); return $result; } @@ -312,7 +384,7 @@ protected function process_time_splitting($timesplitting, $analysable, $target = if ($this->options['evaluation'] === false) { if (empty($ranges)) { - $result->status = \core_analytics\model::ANALYSE_REJECTED_RANGE_PROCESSOR; + $result->status = \core_analytics\model::ANALYSABLE_REJECTED_TIME_SPLITTING_METHOD; $result->message = get_string('nonewdata', 'analytics'); return $result; } @@ -321,7 +393,7 @@ protected function process_time_splitting($timesplitting, $analysable, $target = $sampleids = $this->filter_out_train_samples($sampleids, $timesplitting); if (count($sampleids) === 0) { - $result->status = \core_analytics\model::ANALYSE_REJECTED_RANGE_PROCESSOR; + $result->status = \core_analytics\model::ANALYSABLE_REJECTED_TIME_SPLITTING_METHOD; $result->message = get_string('nonewdata', 'analytics'); return $result; } @@ -335,7 +407,7 @@ protected function process_time_splitting($timesplitting, $analysable, $target = } if (count($ranges) === 0) { - $result->status = \core_analytics\model::ANALYSE_REJECTED_RANGE_PROCESSOR; + $result->status = \core_analytics\model::ANALYSABLE_REJECTED_TIME_SPLITTING_METHOD; $result->message = get_string('nonewtimeranges', 'analytics'); return $result; } @@ -375,13 +447,12 @@ protected function process_time_splitting($timesplitting, $analysable, $target = $target->add_sample_data($samplesdata); } - // Here we start the memory intensive process that will last until $data var is // unset (until the method is finished basically). $data = $timesplitting->calculate($sampleids, $this->get_samples_origin(), $this->indicators, $ranges, $target); if (!$data) { - $result->status = \core_analytics\model::ANALYSE_REJECTED_RANGE_PROCESSOR; + $result->status = \core_analytics\model::ANALYSABLE_REJECTED_TIME_SPLITTING_METHOD; $result->message = get_string('novaliddata', 'analytics'); $dataset->close_process(); return $result; @@ -410,6 +481,12 @@ protected function process_time_splitting($timesplitting, $analysable, $target = return $result; } + /** + * Returns the ranges of a time splitting that can be used to predict. + * + * @param \core_analytics\local\time_splitting\base $timesplitting + * @return array + */ protected function get_prediction_ranges($timesplitting) { $now = time(); @@ -426,6 +503,13 @@ protected function get_prediction_ranges($timesplitting) { return $predictionranges; } + /** + * Filters out samples that have already been used for training. + * + * @param int[] $sampleids + * @param \core_analytics\local\time_splitting\base $timesplitting + * @return int[] + */ protected function filter_out_train_samples($sampleids, $timesplitting) { global $DB; @@ -448,6 +532,13 @@ protected function filter_out_train_samples($sampleids, $timesplitting) { return $sampleids; } + /** + * Filters out samples that have already been used for prediction. + * + * @param array $ranges + * @param \core_analytics\local\time_splitting\base $timesplitting + * @return int[] + */ protected function filter_out_prediction_ranges($ranges, $timesplitting) { global $DB; @@ -465,6 +556,14 @@ protected function filter_out_prediction_ranges($ranges, $timesplitting) { } + /** + * Saves samples that have just been used for training. + * + * @param int[] $sampleids + * @param \core_analytics\local\time_splitting\base $timesplitting + * @param \stored_file $file + * @return bool + */ protected function save_train_samples($sampleids, $timesplitting, $file) { global $DB; @@ -481,6 +580,13 @@ protected function save_train_samples($sampleids, $timesplitting, $file) { return $DB->insert_record('analytics_train_samples', $trainingsamples); } + /** + * Saves samples that have just been used for prediction. + * + * @param array $ranges + * @param \core_analytics\local\time_splitting\base $timesplitting + * @return void + */ protected function save_prediction_ranges($ranges, $timesplitting) { global $DB; diff --git a/analytics/classes/local/analyser/by_course.php b/analytics/classes/local/analyser/by_course.php index 51c2ffaa82b18..e4feb14532d9c 100644 --- a/analytics/classes/local/analyser/by_course.php +++ b/analytics/classes/local/analyser/by_course.php @@ -15,6 +15,7 @@ // along with Moodle. If not, see . /** + * Abstract analyser in course basis. * * @package core_analytics * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} @@ -26,6 +27,7 @@ defined('MOODLE_INTERNAL') || die(); /** + * Abstract analyser in course basis. * * @package core_analytics * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} @@ -33,6 +35,11 @@ */ abstract class by_course extends base { + /** + * Return the list of courses to analyse. + * + * @return \core_analytics\course[] + */ public function get_courses() { // Default to all system courses. @@ -58,6 +65,12 @@ public function get_courses() { return $analysables; } + /** + * Returns the analysed data + * + * @param bool $includetarget + * @return \stored_file[] + */ public function get_analysable_data($includetarget) { $status = array(); @@ -82,6 +95,13 @@ public function get_analysable_data($includetarget) { return $timesplittingfiles; } + /** + * Merges analysable dataset files into 1. + * + * @param array $filesbytimesplitting + * @param bool $includetarget + * @return \stored_file[] + */ protected function merge_analysable_files($filesbytimesplitting, $includetarget) { $timesplittingfiles = array(); diff --git a/analytics/classes/local/analyser/courses.php b/analytics/classes/local/analyser/courses.php index d87fea9b2e57a..fa33bd572edb7 100644 --- a/analytics/classes/local/analyser/courses.php +++ b/analytics/classes/local/analyser/courses.php @@ -35,12 +35,17 @@ */ class courses extends by_course { + /** + * Samples origin is course table. + * + * @return string + */ public function get_samples_origin() { return 'course'; } /** - * get_sample_analysable + * Returns the analysable of a sample * * @param int $sampleid * @return \core_analytics\analysable @@ -49,16 +54,27 @@ public function get_sample_analysable($sampleid) { return \core_analytics\course::instance($sampleid); } + /** + * This provides samples' course and context. + * + * @return string[] + */ protected function provided_sample_data() { return array('course', 'context'); } + /** + * Returns the context of a sample. + * + * @param int $sampleid + * @return \context + */ public function sample_access_context($sampleid) { return \context_course::instance($sampleid); } /** - * get_all_samples + * This will return just one course as we analyse 'by_course'. * * @param \core_analytics\analysable $course * @return array @@ -76,7 +92,7 @@ protected function get_all_samples(\core_analytics\analysable $course) { } /** - * get_samples + * Returns samples data from sample ids. * * @param int[] $sampleids * @return array @@ -99,12 +115,12 @@ public function get_samples($sampleids) { } /** - * sample_description + * Returns the sample description * * @param int $sampleid * @param int $contextid * @param array $sampledata - * @return array + * @return array array(string, \renderable) */ public function sample_description($sampleid, $contextid, $sampledata) { $description = format_string($sampledata['course']->fullname, true, array('context' => $sampledata['context'])); diff --git a/analytics/classes/local/analyser/site_courses.php b/analytics/classes/local/analyser/site_courses.php index a9e781f29a369..a3b1278ea78dc 100644 --- a/analytics/classes/local/analyser/site_courses.php +++ b/analytics/classes/local/analyser/site_courses.php @@ -35,12 +35,17 @@ */ class site_courses extends sitewide { + /** + * Samples origin is course table. + * + * @return string + */ public function get_samples_origin() { return 'course'; } /** - * get_sample_analysable + * Returns the sample analysable * * @param int $sampleid * @return \core_analytics\analysable @@ -49,14 +54,31 @@ public function get_sample_analysable($sampleid) { return new \core_analytics\site(); } + /** + * Data this analyer samples provide. + * + * @return string[] + */ protected function provided_sample_data() { return array('course', 'context'); } + /** + * Returns the sample context. + * + * @param int $sampleid + * @return \context + */ public function sample_access_context($sampleid) { return \context_system::instance(); } + /** + * Returns all site courses. + * + * @param \core_analytics\analysable $site + * @return array + */ protected function get_all_samples(\core_analytics\analysable $site) { global $DB; @@ -77,6 +99,12 @@ protected function get_all_samples(\core_analytics\analysable $site) { return array($sampleids, $courses); } + /** + * Return all complete samples data from sample ids. + * + * @param int[] $sampleids + * @return array + */ public function get_samples($sampleids) { global $DB; @@ -95,12 +123,12 @@ public function get_samples($sampleids) { } /** - * sample_description + * Returns the description of a sample. * * @param int $sampleid * @param int $contextid * @param array $sampledata - * @return void + * @return array array(string, \renderable) */ public function sample_description($sampleid, $contextid, $sampledata) { $description = format_string($sampledata['course']->fullname, true, array('context' => $sampledata['context'])); diff --git a/analytics/classes/local/analyser/sitewide.php b/analytics/classes/local/analyser/sitewide.php index ed91ce5cb05f9..1cffcc06570c3 100644 --- a/analytics/classes/local/analyser/sitewide.php +++ b/analytics/classes/local/analyser/sitewide.php @@ -15,6 +15,7 @@ // along with Moodle. If not, see . /** + * Site-level contents abstract analysable. * * @package core_analytics * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} @@ -26,6 +27,7 @@ defined('MOODLE_INTERNAL') || die(); /** + * Site-level contents abstract analysable. * * @package core_analytics * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} @@ -33,14 +35,16 @@ */ abstract class sitewide extends base { - protected function get_site() { - return new \core_analytics\site(); - } - + /** + * Returns the analysable data. + * + * @param bool $includetarget + * @return \stored_file[] One file for each time splitting method. + */ public function get_analysable_data($includetarget) { // Here there is a single analysable and it is the system. - $analysable = $this->get_site(); + $analysable = new \core_analytics\site(); $return = array(); @@ -54,7 +58,7 @@ public function get_analysable_data($includetarget) { \core_analytics\dataset_manager::delete_previous_evaluation_file($this->modelid, $timesplittingid); } - // We use merge but it is just a copy + // We use merge but it is just a copy. $files[$timesplittingid] = \core_analytics\dataset_manager::merge_datasets(array($file), $this->modelid, $timesplittingid, $this->options['evaluation'], $includetarget); } diff --git a/analytics/classes/local/analyser/student_enrolments.php b/analytics/classes/local/analyser/student_enrolments.php index 970cabafc0aab..dbf26e6aa819e 100644 --- a/analytics/classes/local/analyser/student_enrolments.php +++ b/analytics/classes/local/analyser/student_enrolments.php @@ -15,6 +15,7 @@ // along with Moodle. If not, see . /** + * Student enrolments analyser. * * @package core_analytics * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} @@ -28,6 +29,9 @@ require_once($CFG->dirroot . '/lib/enrollib.php'); /** + * Student enrolments analyser. + * + * It does return all student enrolments including the suspended ones. * * @package core_analytics * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} @@ -40,28 +44,52 @@ class student_enrolments extends by_course { */ protected $samplecourses = array(); + /** + * Defines the origin of the samples in the database. + * + * @return string + */ protected function get_samples_origin() { return 'user_enrolments'; } + /** + * Returns the student enrolment course context. + * + * @param int $sampleid + * @return \context + */ public function sample_access_context($sampleid) { return \context_course::instance($this->get_sample_courseid($sampleid)); } + /** + * Returns the student enrolment course. + * + * @param int $sampleid + * @return \core_analytics\analysable + */ public function get_sample_analysable($sampleid) { $course = enrol_get_course_by_user_enrolment_id($sampleid); return \core_analytics\course::instance($course); } + /** + * Data provided by get_all_samples & get_samples. + * + * @return string[] + */ protected function provided_sample_data() { return array('user_enrolments', 'context', 'course', 'user'); } /** - * All course enrolments. + * All course student enrolments. + * + * It does return all student enrolments including the suspended ones. * * @param \core_analytics\analysable $course - * @return void + * @return array */ protected function get_all_samples(\core_analytics\analysable $course) { @@ -111,6 +139,12 @@ protected function get_all_samples(\core_analytics\analysable $course) { return array(array_combine($enrolids, $enrolids), $samplesdata); } + /** + * Returns all samples from the samples ids. + * + * @param int[] $sampleids + * @return array + */ public function get_samples($sampleids) { global $DB; @@ -189,7 +223,7 @@ protected function get_sample_courseid($sampleid) { * @param int $sampleid * @param int $contextid * @param array $sampledata - * @return array + * @return array array(string, \renderable) */ public function sample_description($sampleid, $contextid, $sampledata) { $description = fullname($sampledata['user'], true, array('context' => $contextid)); diff --git a/analytics/classes/local/indicator/any_access_after_end.php b/analytics/classes/local/indicator/any_access_after_end.php index 4143817c1348b..8a7b39b5e3b8d 100644 --- a/analytics/classes/local/indicator/any_access_after_end.php +++ b/analytics/classes/local/indicator/any_access_after_end.php @@ -35,14 +35,33 @@ */ class any_access_after_end extends binary { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:accessesafterend', 'analytics'); } + /** + * required_sample_data + * + * @return string[] + */ public static function required_sample_data() { return array('user', 'course', 'context'); } + /** + * calculate_sample + * + * @param int $sampleid + * @param string $samplesorigin + * @param int $starttime + * @param int $endtime + * @return float + */ protected function calculate_sample($sampleid, $samplesorigin, $starttime = false, $endtime = false) { global $DB; diff --git a/analytics/classes/local/indicator/any_access_before_start.php b/analytics/classes/local/indicator/any_access_before_start.php index dd5647ab48f3d..ef5fae335e646 100644 --- a/analytics/classes/local/indicator/any_access_before_start.php +++ b/analytics/classes/local/indicator/any_access_before_start.php @@ -35,14 +35,33 @@ */ class any_access_before_start extends binary { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:accessesbeforestart', 'analytics'); } + /** + * required_sample_data + * + * @return string[] + */ public static function required_sample_data() { return array('user', 'course', 'context'); } + /** + * calculate_sample + * + * @param int $sampleid + * @param string $samplesorigin + * @param int $starttime + * @param int $endtime + * @return float + */ protected function calculate_sample($sampleid, $samplesorigin, $starttime = false, $endtime = false) { global $DB; diff --git a/analytics/classes/local/indicator/any_write_action.php b/analytics/classes/local/indicator/any_write_action.php index 77b06506da68d..e97927161ccd8 100644 --- a/analytics/classes/local/indicator/any_write_action.php +++ b/analytics/classes/local/indicator/any_write_action.php @@ -35,15 +35,34 @@ */ class any_write_action extends binary { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:anywrite', 'analytics'); } + /** + * required_sample_data + * + * @return string[] + */ public static function required_sample_data() { // User is not required, calculate_sample can handle its absence. return array('context'); } + /** + * 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) { global $DB; diff --git a/analytics/classes/local/indicator/base.php b/analytics/classes/local/indicator/base.php index f08c310d4b3fc..310dc55fb7c5b 100644 --- a/analytics/classes/local/indicator/base.php +++ b/analytics/classes/local/indicator/base.php @@ -35,12 +35,18 @@ */ abstract class base extends \core_analytics\calculable { + /** + * Min value an indicator can return. + */ const MIN_VALUE = -1; + /** + * Max value an indicator can return. + */ const MAX_VALUE = 1; /** - * Converts the calculated indicators to feature/s. + * Converts the calculated indicators to dataset feature/s. * * @param float|int[] $calculatedvalues * @return array @@ -60,26 +66,57 @@ abstract protected function to_features($calculatedvalues); */ abstract protected function calculate_sample($sampleid, $sampleorigin, $starttime, $endtime); + /** + * Should this value be displayed? + * + * Indicators providing multiple features can be used this method to discard some of them. + * + * @param float $value + * @param string $subtype + * @return bool + */ public function should_be_displayed($value, $subtype) { // We should everything by default. return true; } /** - * @return null|string + * Allows indicators to specify data they need. + * + * e.g. A model using courses as samples will not provide users data, but an indicator like + * "user is hungry" needs user data. + * + * @return null|string[] Name of the required elements (use the database tablename) */ public static function required_sample_data() { return null; } + /** + * Returns an instance of the indicator. + * + * Useful to reset cached data. + * + * @return \core_analytics\local\indicator\base + */ public static function instance() { return new static(); } + /** + * Returns the maximum value an indicator calculation can return. + * + * @return float + */ public static function get_max_value() { return self::MAX_VALUE; } + /** + * Returns the minimum value an indicator calculation can return. + * + * @return float + */ public static function get_min_value() { return self::MIN_VALUE; } @@ -89,7 +126,7 @@ public static function get_min_value() { * * Returns an array of values which size matches $sampleids size. * - * @param array $sampleids + * @param int[] $sampleids * @param string $samplesorigin * @param integer $starttime Limit the calculation to this timestart * @param integer $endtime Limit the calculation to this timeend @@ -118,17 +155,4 @@ public function calculate($sampleids, $samplesorigin, $starttime = false, $endti return $calculations; } - - protected static function add_samples_averages() { - return false; - } - - protected static function get_average_columns() { - return array('mean'); - } - - protected function calculate_averages($values) { - $mean = array_sum($values) / count($values); - return array($mean); - } } diff --git a/analytics/classes/local/indicator/binary.php b/analytics/classes/local/indicator/binary.php index a44e669f547ac..b642475d9016f 100644 --- a/analytics/classes/local/indicator/binary.php +++ b/analytics/classes/local/indicator/binary.php @@ -35,6 +35,11 @@ */ abstract class binary extends discrete { + /** + * get_classes + * + * @return array + */ public static final function get_classes() { // It does not really matter, all \core_analytics\local\indicator\discrete get_classes calls have been overwriten as we // only need 1 column here. @@ -79,11 +84,22 @@ public function get_calculation_outcome($value, $subtype = false) { } } + /** + * get_feature_headers + * + * @return array + */ public static function get_feature_headers() { // Just 1 single feature obtained from the calculated value. return array(get_called_class()); } + /** + * to_features + * + * @param array $calculatedvalues + * @return array + */ protected function to_features($calculatedvalues) { // Indicators with binary values have only 1 feature for indicator, here we do nothing else // than converting each sample scalar value to an array of scalars with 1 element. diff --git a/analytics/classes/local/indicator/community_of_inquiry_activity.php b/analytics/classes/local/indicator/community_of_inquiry_activity.php index e79b297ccda90..367ae2ec92675 100644 --- a/analytics/classes/local/indicator/community_of_inquiry_activity.php +++ b/analytics/classes/local/indicator/community_of_inquiry_activity.php @@ -35,10 +35,12 @@ */ abstract class community_of_inquiry_activity extends linear { + /** + * @var \core_analytics\course + */ protected $course = null; + /** - * TODO This should ideally be reused by cognitive depth and social breadth. - * * @var array Array of logs by [contextid][userid] */ protected $activitylogs = null; @@ -49,12 +51,12 @@ abstract class community_of_inquiry_activity extends linear { protected $grades = null; /** - * @const Constant cognitive indicator type. + * Constant cognitive indicator type. */ const INDICATOR_COGNITIVE = "cognitve"; /** - * @const Constant social indicator type. + * Constant social indicator type. */ const INDICATOR_SOCIAL = "social"; @@ -63,7 +65,7 @@ abstract class community_of_inquiry_activity extends linear { * * @var string The activity name (e.g. assign or quiz) */ - final protected function get_activity_type() { + protected final function get_activity_type() { $class = get_class($this); $package = stristr($class, "\\", true); $type = str_replace("mod_", "", $package); @@ -73,21 +75,47 @@ final protected function get_activity_type() { return $type; } + /** + * Returns the potential level of cognitive depth. + * + * @param \cm_info $cm + * @return int + */ protected function get_cognitive_depth_level(\cm_info $cm) { throw new \coding_exception('Overwrite get_cognitive_depth_level method to set your activity potential cognitive ' . 'depth level'); } + /** + * Returns the potential level of social breadth. + * + * @param \cm_info $cm + * @return int + */ protected function get_social_breadth_level(\cm_info $cm) { throw new \coding_exception('Overwrite get_social_breadth_level method to set your activity potential social ' . 'breadth level'); } + /** + * required_sample_data + * + * @return string[] + */ public static function required_sample_data() { // Only course because the indicator is valid even without students. return array('course'); } + /** + * Do activity logs contain any log of user in this context? + * + * If user is empty we look for any log in this context. + * + * @param int $contextid + * @param \stdClass|false $user + * @return bool + */ protected final function any_log($contextid, $user) { if (empty($this->activitylogs[$contextid])) { return false; @@ -103,6 +131,15 @@ protected final function any_log($contextid, $user) { return false; } + /** + * Do activity logs contain any write log of user in this context? + * + * If user is empty we look for any write log in this context. + * + * @param int $contextid + * @param \stdClass|false $user + * @return bool + */ protected final function any_write_log($contextid, $user) { if (empty($this->activitylogs[$contextid])) { return false; @@ -127,6 +164,17 @@ protected final function any_write_log($contextid, $user) { return false; } + /** + * Is there any feedback activity log for this user in this context? + * + * This method returns true if $user is empty and there is any feedback activity logs. + * + * @param string $action + * @param \cm_info $cm + * @param int $contextid + * @param \stdClass|false $user + * @return bool + */ protected function any_feedback($action, \cm_info $cm, $contextid, $user) { if (!in_array($action, 'submitted', 'replied', 'viewed')) { @@ -175,29 +223,76 @@ protected function feedback_viewed(\cm_info $cm, $contextid, $userid, $after = n return $this->feedback_post_action($cm, $contextid, $userid, $this->feedback_viewed_events(), $after); } + /** + * $cm is used for this method overrides. + * + * This function must be fast. + * + * @param \cm_info $cm + * @param mixed $contextid + * @param mixed $userid + * @param int $after Timestamp, defaults to the graded date or false if we don't check the date. + * @return bool + */ protected function feedback_replied(\cm_info $cm, $contextid, $userid, $after = null) { return $this->feedback_post_action($cm, $contextid, $userid, $this->feedback_replied_events(), $after); } + /** + * $cm is used for this method overrides. + * + * This function must be fast. + * + * @param \cm_info $cm + * @param mixed $contextid + * @param mixed $userid + * @param int $after Timestamp, defaults to the graded date or false if we don't check the date. + * @return bool + */ protected function feedback_submitted(\cm_info $cm, $contextid, $userid, $after = null) { return $this->feedback_post_action($cm, $contextid, $userid, $this->feedback_submitted_events(), $after); } + /** + * Returns the list of events that involve viewing feedback from other users. + * + * @return string[] + */ protected function feedback_viewed_events() { throw new \coding_exception('Activities with a potential cognitive or social level that include viewing feedback ' . 'should define "feedback_viewed_events" method or should override feedback_viewed method.'); } + /** + * Returns the list of events that involve replying to feedback from other users. + * + * @return string[] + */ protected function feedback_replied_events() { throw new \coding_exception('Activities with a potential cognitive or social level that include replying to feedback ' . 'should define "feedback_replied_events" method or should override feedback_replied method.'); } + /** + * Returns the list of events that involve submitting something after receiving feedback from other users. + * + * @return string[] + */ protected function feedback_submitted_events() { throw new \coding_exception('Activities with a potential cognitive or social level that include viewing feedback ' . 'should define "feedback_submitted_events" method or should override feedback_submitted method.'); } + /** + * Whether this user in this context did any of the provided actions (events) + * + * @param \cm_info $cm + * @param int $contextid + * @param int $userid + * @param string[] $eventnames + * @param int|false $after + * @return bool + */ protected function feedback_post_action(\cm_info $cm, $contextid, $userid, $eventnames, $after = null) { if ($after === null) { if ($this->feedback_check_grades()) { @@ -237,7 +332,7 @@ protected function feedback_post_action(\cm_info $cm, $contextid, $userid, $even } /** - * get_graded_date + * Returns the date a user was graded. * * @param int $contextid * @param int $userid @@ -268,6 +363,15 @@ protected function get_graded_date($contextid, $userid, $checkfeedback = false) return $after; } + /** + * Returns the activities the user had access to between a time period. + * + * @param int $sampleid + * @param string $tablename + * @param int $starttime + * @param int $endtime + * @return array + */ protected function get_student_activities($sampleid, $tablename, $starttime, $endtime) { // May not be available. @@ -307,6 +411,14 @@ protected function get_student_activities($sampleid, $tablename, $starttime, $en return $useractivities; } + /** + * Fetch acitivity logs from database + * + * @param array $activities + * @param int $starttime + * @param int $endtime + * @return array + */ protected function fetch_activity_logs($activities, $starttime = false, $endtime = false) { global $DB; @@ -330,7 +442,7 @@ protected function fetch_activity_logs($activities, $starttime = false, $endtime $processedevents[$event->contextid][$event->userid] = array(); } - // contextid and userid have already been used to index the events, the next field to index by is eventname: + // Contextid and userid have already been used to index the events, the next field to index by is eventname: // crud is unique per eventname, courseid is the same for all records and we append timecreated. if (!isset($processedevents[$event->contextid][$event->userid][$event->eventname])) { @@ -358,7 +470,7 @@ protected function fetch_activity_logs($activities, $starttime = false, $endtime /** * Whether grades should be checked or not when looking for feedback. * - * @return void + * @return bool */ protected function feedback_check_grades() { return true; @@ -367,10 +479,10 @@ protected function feedback_check_grades() { /** * cognitive_calculate_sample * - * @param $sampleid - * @param $tablename - * @param bool $starttime - * @param bool $endtime + * @param int $sampleid + * @param string $tablename + * @param int $starttime + * @param int $endtime * @return float|int|null * @throws \coding_exception */ @@ -404,7 +516,7 @@ protected function cognitive_calculate_sample($sampleid, $tablename, $starttime $score += $scoreperlevel * 5; break; } - // The user didn't reach the activity max cognitive depth, continue with level 2. + // The user didn't reach the activity max cognitive depth, continue with level 2. case 4: // Cognitive level 4 is to comment on feedback. @@ -412,7 +524,7 @@ protected function cognitive_calculate_sample($sampleid, $tablename, $starttime $score += $scoreperlevel * 4; break; } - // The user didn't reach the activity max cognitive depth, continue with level 2. + // The user didn't reach the activity max cognitive depth, continue with level 2. case 3: // Cognitive level 3 is to view feedback. @@ -422,7 +534,7 @@ protected function cognitive_calculate_sample($sampleid, $tablename, $starttime $score += $scoreperlevel * 3; break; } - // The user didn't reach the activity max cognitive depth, continue with level 2. + // The user didn't reach the activity max cognitive depth, continue with level 2. case 2: // Cognitive depth level 2 is to submit content. @@ -431,7 +543,7 @@ protected function cognitive_calculate_sample($sampleid, $tablename, $starttime $score += $scoreperlevel * 2; break; } - // The user didn't reach the activity max cognitive depth, continue with level 1. + // The user didn't reach the activity max cognitive depth, continue with level 1. case 1: // Cognitive depth level 1 is just accessing the activity. @@ -456,10 +568,10 @@ protected function cognitive_calculate_sample($sampleid, $tablename, $starttime /** * social_calculate_sample * - * @param $sampleid - * @param $tablename - * @param bool $starttime - * @param bool $endtime + * @param int $sampleid + * @param string $tablename + * @param int $starttime + * @param int $endtime * @return float|int|null */ protected function social_calculate_sample($sampleid, $tablename, $starttime = false, $endtime = false) { @@ -486,14 +598,15 @@ protected function social_calculate_sample($sampleid, $tablename, $starttime = f // TODO Add support for other levels than 2. switch ($potentiallevel) { case 2: - // Social breadth level 2 is to view feedback. (Same as cognitive level 3) + // Social breadth level 2 is to view feedback. (Same as cognitive level 3). if ($this->any_feedback('viewed', $cm, $contextid, $user)) { // Max score for level 2. $score += $scoreperlevel * 2; break; } - // The user didn't reach the activity max social breadth, continue with level 1. + // The user didn't reach the activity max social breadth, continue with level 1. + case 1: // Social breadth level 1 is just accessing the activity. if ($this->any_log($contextid, $user)) { @@ -515,12 +628,12 @@ protected function social_calculate_sample($sampleid, $tablename, $starttime = f /** * calculate_sample * + * @throws \coding_exception * @param int $sampleid * @param string $tablename - * @param bool $starttime - * @param bool $endtime + * @param int $starttime + * @param int $endtime * @return float|int|null - * @throws \coding_exception */ protected function calculate_sample($sampleid, $tablename, $starttime = false, $endtime = false) { if ($this->get_indicator_type() == self::INDICATOR_COGNITIVE) { @@ -534,7 +647,7 @@ protected function calculate_sample($sampleid, $tablename, $starttime = false, $ /** * Defines indicator type. * - * @return mixed + * @return string */ abstract protected function get_indicator_type(); } diff --git a/analytics/classes/local/indicator/discrete.php b/analytics/classes/local/indicator/discrete.php index 7f63e9104f9f6..a557fb40c4a1d 100644 --- a/analytics/classes/local/indicator/discrete.php +++ b/analytics/classes/local/indicator/discrete.php @@ -44,6 +44,11 @@ protected static function get_classes() { throw new \coding_exception('Please overwrite get_classes() specifying your discrete-values\' indicator classes'); } + /** + * Returns 1 feature header for each of the classes. + * + * @return string[] + */ public static function get_feature_headers() { $fullclassname = get_called_class(); @@ -55,6 +60,13 @@ public static function get_feature_headers() { return $headers; } + /** + * Whether the value should be displayed or not. + * + * @param float $value + * @param string $subtype + * @return bool + */ public function should_be_displayed($value, $subtype) { if ($value != static::get_max_value()) { // Discrete values indicators are converted internally to 1 feature per indicator, we are only interested @@ -94,6 +106,14 @@ public function get_display_style($ignoredvalue, $ignoredsubtype) { return \core_analytics\calculable::OUTCOME_NEUTRAL; } + /** + * From calculated values to dataset features. + * + * One column for each class. + * + * @param float[] $calculatedvalues + * @return float[] + */ protected function to_features($calculatedvalues) { $classes = self::get_classes(); diff --git a/analytics/classes/local/indicator/linear.php b/analytics/classes/local/indicator/linear.php index bdc9f6f6909f8..df2f646742cf7 100644 --- a/analytics/classes/local/indicator/linear.php +++ b/analytics/classes/local/indicator/linear.php @@ -44,6 +44,11 @@ protected static function include_averages() { return true; } + /** + * get_feature_headers + * + * @return array + */ public static function get_feature_headers() { $fullclassname = get_called_class(); @@ -57,6 +62,13 @@ public static function get_feature_headers() { return $headers; } + /** + * should_be_displayed + * + * @param float $value + * @param string $subtype + * @return bool + */ public function should_be_displayed($value, $subtype) { if ($subtype != false) { return false; @@ -91,6 +103,12 @@ public function get_calculation_outcome($value, $subtype = false) { } } + /** + * Converts the calculated values to a list of features for the dataset. + * + * @param array $calculatedvalues + * @return array + */ protected function to_features($calculatedvalues) { // Null mean if all calculated values are null. diff --git a/analytics/classes/local/indicator/read_actions.php b/analytics/classes/local/indicator/read_actions.php index 825b155d2baba..1d6099f019131 100644 --- a/analytics/classes/local/indicator/read_actions.php +++ b/analytics/classes/local/indicator/read_actions.php @@ -35,15 +35,34 @@ */ class read_actions extends linear { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:readactions', 'analytics'); } + /** + * required_sample_data + * + * @return string[] + */ public static function required_sample_data() { // User is not required, calculate_sample can handle its absence. return array('context'); } + /** + * 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) { $select = ''; @@ -67,7 +86,7 @@ protected function calculate_sample($sampleid, $sampleorigin, $starttime = false // # Done absolutely nothing // # Not much really, just accessing the course once a week // # More than just accessing the course, some interaction - // # Significant contribution, will depend on the course anyway + // # Significant contribution, will depend on the course anyway. // We need to adapt the limits to the time range duration. $nweeks = $this->get_time_range_weeks_number($starttime, $endtime); diff --git a/analytics/classes/local/indicator/user_profile_set.php b/analytics/classes/local/indicator/user_profile_set.php index b55c92e7d3bf9..3a2ad7c4f996f 100644 --- a/analytics/classes/local/indicator/user_profile_set.php +++ b/analytics/classes/local/indicator/user_profile_set.php @@ -35,15 +35,33 @@ */ class user_profile_set extends linear { - + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:completeduserprofile', 'analytics'); } + /** + * 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) { global $CFG; diff --git a/analytics/classes/local/indicator/user_track_forums.php b/analytics/classes/local/indicator/user_track_forums.php index 07702cd0ece73..4d1daa84b4b74 100644 --- a/analytics/classes/local/indicator/user_track_forums.php +++ b/analytics/classes/local/indicator/user_track_forums.php @@ -35,14 +35,33 @@ */ class user_track_forums extends binary { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:userforumstracking', 'analytics'); } + /** + * required_sample_data + * + * @return string[] + */ public static function required_sample_data() { return array('user'); } + /** + * calculate_sample + * + * @param int $sampleid + * @param string $samplesorigin + * @param int $starttime + * @param int $endtime + * @return float + */ protected function calculate_sample($sampleid, $samplesorigin, $starttime = false, $endtime = false) { $user = $this->retrieve('user', $sampleid); return ($user->trackforums) ? self::get_max_value() : self::get_min_value(); diff --git a/analytics/classes/local/target/base.php b/analytics/classes/local/target/base.php index d25f05f0b6360..8144220b5a876 100644 --- a/analytics/classes/local/target/base.php +++ b/analytics/classes/local/target/base.php @@ -186,7 +186,6 @@ public function generate_insight_notifications($modelid, $samplecontexts) { $message->smallmessage = get_string('insightinfomessage', 'analytics', $insighturl->out()); $message->contexturl = $insighturl->out(false); - message_send($message); } } @@ -240,7 +239,7 @@ protected function min_prediction_score() { * Should the model callback be triggered? * * @param mixed $predictedvalue - * @param float $predictedscore + * @param float $predictionscore * @return bool */ public function triggers_callback($predictedvalue, $predictionscore) { @@ -292,7 +291,8 @@ public function calculate($sampleids, \core_analytics\analysable $analysable, $s $calculatedvalue = $this->calculate_sample($sampleid, $analysable, $starttime, $endtime); if (!is_null($calculatedvalue)) { - if ($this->is_linear() && ($calculatedvalue > static::get_max_value() || $calculatedvalue < static::get_min_value())) { + if ($this->is_linear() && + ($calculatedvalue > static::get_max_value() || $calculatedvalue < static::get_min_value())) { throw new \coding_exception('Calculated values should be higher than ' . static::get_min_value() . ' and lower than ' . static::get_max_value() . '. ' . $calculatedvalue . ' received'); } else if (!$this->is_linear() && static::is_a_class($calculatedvalue) === false) { @@ -310,6 +310,7 @@ public function calculate($sampleids, \core_analytics\analysable $analysable, $s * * @param int[] $sampleids * @param \core_analytics\analysable $analysable + * @param bool $fortraining * @return void */ public function filter_out_invalid_samples(&$sampleids, \core_analytics\analysable $analysable, $fortraining = true) { diff --git a/analytics/classes/local/target/binary.php b/analytics/classes/local/target/binary.php index 055e28d0148de..ea04410f3dfda 100644 --- a/analytics/classes/local/target/binary.php +++ b/analytics/classes/local/target/binary.php @@ -35,9 +35,12 @@ */ abstract class binary extends discrete { + /** + * is_linear + * + * @return bool + */ public function is_linear() { - // TODO Remove this discrete override once prediction processors support - // multiclass classifiers; this method will be moved to discrete. return false; } @@ -62,6 +65,13 @@ protected function ignored_predicted_classes() { return array(0); } + /** + * Is the calculated value a positive outcome of this target? + * + * @param string $value + * @param string $ignoredsubtype + * @return int + */ public function get_calculation_outcome($value, $ignoredsubtype = false) { if (!self::is_a_class($value)) { @@ -81,6 +91,11 @@ public function get_calculation_outcome($value, $ignoredsubtype = false) { return self::OUTCOME_VERY_POSITIVE; } + /** + * classes_description + * + * @return string[] + */ protected static function classes_description() { return array( get_string('yes'), diff --git a/analytics/classes/local/target/discrete.php b/analytics/classes/local/target/discrete.php index e05b673d83565..5d3dcb4a878dd 100644 --- a/analytics/classes/local/target/discrete.php +++ b/analytics/classes/local/target/discrete.php @@ -35,11 +35,22 @@ */ abstract class discrete extends base { + /** + * Are this target calculations linear values? + * + * @return bool + */ public function is_linear() { // Not supported yet. throw new \coding_exception('Sorry, this version\'s prediction processors only support targets with binary values.'); } + /** + * Is the provided class one of this target valid classes? + * + * @param string $class + * @return bool + */ protected static function is_a_class($class) { return (in_array($class, static::get_classes())); } @@ -48,7 +59,7 @@ protected static function is_a_class($class) { * get_display_value * * @param float $value - * @param string $subtype + * @param string $ignoredsubtype * @return string */ public function get_display_value($value, $ignoredsubtype = false) { @@ -57,17 +68,19 @@ public function get_display_value($value, $ignoredsubtype = false) { throw new \moodle_exception('errorpredictionformat', 'analytics'); } - // array_values to discard any possible weird keys devs used. + // To discard any possible weird keys devs used. $classes = array_values(static::get_classes()); $descriptions = array_values(static::classes_description()); if (count($classes) !== count($descriptions)) { - throw new \coding_exception('You need to describe all your classes (' . json_encode($classes) . ') in self::classes_description'); + throw new \coding_exception('You need to describe all your classes (' . json_encode($classes) . + ') in self::classes_description'); } $key = array_search($value, $classes); if ($key === false) { - throw new \coding_exception('You need to describe all your classes (' . json_encode($classes) . ') in self::classes_description'); + throw new \coding_exception('You need to describe all your classes (' . json_encode($classes) . + ') in self::classes_description'); } return $descriptions[$key]; @@ -91,8 +104,8 @@ public function get_calculation_outcome($value, $ignoredsubtype = false) { return self::OUTCOME_OK; } - debugging('Please overwrite \core_analytics\local\target\discrete::get_calculation_outcome, all your target classes are styled ' . - 'the same way otherwise', DEBUG_DEVELOPER); + debugging('Please overwrite \core_analytics\local\target\discrete::get_calculation_outcome, all your target ' . + 'classes are styled the same way otherwise', DEBUG_DEVELOPER); return self::OUTCOME_OK; } diff --git a/analytics/classes/local/target/linear.php b/analytics/classes/local/target/linear.php index 76b6ea75f1562..8d8e258fe7d68 100644 --- a/analytics/classes/local/target/linear.php +++ b/analytics/classes/local/target/linear.php @@ -35,11 +35,23 @@ */ abstract class linear extends base { + /** + * Are the calculated values this target returns linear values? + * + * @return bool + */ public function is_linear() { // Not supported yet. throw new \coding_exception('Sorry, this version\'s prediction processors only support targets with binary values.'); } + /** + * How positive is this calculated value? + * + * @param float $value + * @param string $ignoredsubtype + * @return int + */ public function get_calculated_outcome($value, $ignoredsubtype = false) { // This is very generic, targets will probably be interested in overwriting this. diff --git a/analytics/classes/local/time_splitting/base.php b/analytics/classes/local/time_splitting/base.php index a828d1466d921..4992799eb5d86 100644 --- a/analytics/classes/local/time_splitting/base.php +++ b/analytics/classes/local/time_splitting/base.php @@ -26,6 +26,13 @@ defined('MOODLE_INTERNAL') || die(); +/** + * Base time splitting method. + * + * @package core_analytics + * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ abstract class base { /** @@ -59,8 +66,22 @@ abstract class base { */ protected static $indicators = []; + /** + * Define the time splitting methods ranges. + * + * 'time' value defines when predictions are executed, their values will be compared with + * the current time in ready_to_predict + * + * @return array('start' => time(), 'end' => time(), 'time' => time()) + */ abstract protected function define_ranges(); + /** + * The time splitting method name. + * + * It is very recommendable to overwrite this method as this name appears in the UI. + * @return string + */ public function get_name() { return $this->get_id(); } @@ -86,6 +107,11 @@ public function set_analysable(\core_analytics\analysable $analysable) { $this->validate_ranges(); } + /** + * get_analysable + * + * @return \core_analytics\analysable + */ public function get_analysable() { return $this->analysable; } @@ -93,12 +119,19 @@ public function get_analysable() { /** * Returns whether the course can be processed by this time splitting method or not. * + * @param \core_analytics\analysable $analysable * @return bool */ public function is_valid_analysable(\core_analytics\analysable $analysable) { return true; } + /** + * Should we predict this time range now? + * + * @param array $range + * @return bool + */ public function ready_to_predict($range) { if ($range['time'] <= time()) { return true; @@ -111,7 +144,7 @@ public function ready_to_predict($range) { * * @param array $sampleids * @param string $samplesorigin - * @param \core_analytics\local\indicator\base $indicators + * @param \core_analytics\local\indicator\base[] $indicators * @param array $ranges * @param \core_analytics\local\target\base $target * @return array @@ -155,6 +188,10 @@ public function calculate(&$sampleids, $samplesorigin, $indicators, $ranges, $ta /** * Calculates indicators. * + * @param array $sampleids + * @param string $samplesorigin + * @param \core_analytics\local\indicator\base[] $indicators + * @param array $ranges * @return array */ protected function calculate_indicators($sampleids, $samplesorigin, $indicators, $ranges) { @@ -197,6 +234,8 @@ protected function calculate_indicators($sampleids, $samplesorigin, $indicators, * * This will identify the sample as belonging to a specific range. * + * @param array $dataset + * @param array $calculatedtarget * @return void */ protected function fill_dataset(&$dataset, $calculatedtarget = false) { @@ -244,6 +283,9 @@ protected function fill_dataset(&$dataset, $calculatedtarget = false) { * ..... * ---------------------------------------------------- * + * @param array $dataset + * @param \core_analytics\local\indicator\base[] $indicators + * @param \core_analytics\local\target\base|false $target * @return void */ protected function add_metadata(&$dataset, $indicators, $target = false) { @@ -284,6 +326,7 @@ public function get_all_ranges() { /** * get_range_by_index * + * @param int $rangeindex * @return array */ public function get_range_by_index($rangeindex) { @@ -293,14 +336,34 @@ public function get_range_by_index($rangeindex) { return $this->ranges[$rangeindex]; } + /** + * Generates a unique sample id (sample in a range index). + * + * @param int $sampleid + * @param int $rangeindex + * @return string + */ public function append_rangeindex($sampleid, $rangeindex) { return $sampleid . '-' . $rangeindex; } - public function infer_sample_info($sampleid) { - return explode('-', $sampleid); + /** + * Returns the sample id and the range index from a uniquesampleid. + * + * @param string $uniquesampleid + * @return array array($sampleid, $rangeindex) + */ + public function infer_sample_info($uniquesampleid) { + return explode('-', $uniquesampleid); } + /** + * Returns the headers for the csv file based on the indicators and the target. + * + * @param \core_analytics\local\indicator\base[] $indicators + * @param \core_analytics\local\target\base|false $target + * @return string[] + */ protected function get_headers($indicators, $target = false) { // 3rd column will contain the indicator ids. $headers = array(); @@ -335,7 +398,7 @@ protected function get_headers($indicators, $target = false) { /** * Validates the time splitting method ranges. * - * @throw \coding_exception + * @throws \coding_exception * @return void */ protected function validate_ranges() { diff --git a/analytics/classes/local/time_splitting/deciles.php b/analytics/classes/local/time_splitting/deciles.php index bf6c3eb9c4cd5..dbfca557c2985 100644 --- a/analytics/classes/local/time_splitting/deciles.php +++ b/analytics/classes/local/time_splitting/deciles.php @@ -35,10 +35,20 @@ */ class deciles extends base { + /** + * get_name + * + * @return string + */ public function get_name() { return get_string('timesplitting:deciles', 'analytics'); } + /** + * define_ranges + * + * @return array + */ protected function define_ranges() { $rangeduration = floor(($this->analysable->get_end() - $this->analysable->get_start()) / 10); diff --git a/analytics/classes/local/time_splitting/deciles_accum.php b/analytics/classes/local/time_splitting/deciles_accum.php index ff9340fe681d5..486be855334a0 100644 --- a/analytics/classes/local/time_splitting/deciles_accum.php +++ b/analytics/classes/local/time_splitting/deciles_accum.php @@ -35,10 +35,20 @@ */ class deciles_accum extends base { + /** + * get_name + * + * @return string + */ public function get_name() { return get_string('timesplitting:decilesaccum', 'analytics'); } + /** + * define_ranges + * + * @return array + */ protected function define_ranges() { $rangeduration = floor(($this->analysable->get_end() - $this->analysable->get_start()) / 10); diff --git a/analytics/classes/local/time_splitting/no_splitting.php b/analytics/classes/local/time_splitting/no_splitting.php index 54a784ec6c97f..0252a584f9e05 100644 --- a/analytics/classes/local/time_splitting/no_splitting.php +++ b/analytics/classes/local/time_splitting/no_splitting.php @@ -28,16 +28,41 @@ defined('MOODLE_INTERNAL') || die(); +/** + * No time splitting method. + * + * Used when time is not a factor to consider into the equation. + * + * @package core_analytics + * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class no_splitting extends base { + /** + * get_name + * + * @return string + */ public function get_name() { return get_string('timesplitting:nosplitting', 'analytics'); } + /** + * ready_to_predict + * + * @param array $range + * @return true + */ public function ready_to_predict($range) { return true; } + /** + * define_ranges + * + * @return array + */ protected function define_ranges() { return [ [ diff --git a/analytics/classes/local/time_splitting/quarters.php b/analytics/classes/local/time_splitting/quarters.php index 2307dacab233d..4ad1b5f36810c 100644 --- a/analytics/classes/local/time_splitting/quarters.php +++ b/analytics/classes/local/time_splitting/quarters.php @@ -35,10 +35,20 @@ */ class quarters extends base { + /** + * get_name + * + * @return string + */ public function get_name() { return get_string('timesplitting:quarters', 'analytics'); } + /** + * define_ranges + * + * @return array + */ protected function define_ranges() { $duration = floor(($this->analysable->get_end() - $this->analysable->get_start()) / 4); return [ diff --git a/analytics/classes/local/time_splitting/quarters_accum.php b/analytics/classes/local/time_splitting/quarters_accum.php index a84d546b95d1a..6b7e0fc692b9e 100644 --- a/analytics/classes/local/time_splitting/quarters_accum.php +++ b/analytics/classes/local/time_splitting/quarters_accum.php @@ -35,10 +35,20 @@ */ class quarters_accum extends base { + /** + * get_name + * + * @return string + */ public function get_name() { return get_string('timesplitting:quartersaccum', 'analytics'); } + /** + * define_ranges + * + * @return array + */ protected function define_ranges() { $duration = floor(($this->analysable->get_end() - $this->analysable->get_start()) / 4); return [ diff --git a/analytics/classes/local/time_splitting/single_range.php b/analytics/classes/local/time_splitting/single_range.php index cf55c946c8859..0386f27737e0d 100644 --- a/analytics/classes/local/time_splitting/single_range.php +++ b/analytics/classes/local/time_splitting/single_range.php @@ -26,14 +26,31 @@ defined('MOODLE_INTERNAL') || die(); +/** + * Single time splitting method. + * + * @package core_analytics + * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class single_range extends base { + /** + * get_name + * + * @return string + */ public function get_name() { return get_string('timesplitting:singlerange', 'analytics'); } + /** + * One single range covering all analysable duration. + * + * @return array + */ protected function define_ranges() { - // time == 0 because we want it to start predicting from the beginning. + // Key 'time' == 0 because we want it to start predicting from the beginning. return [ [ 'start' => $this->analysable->get_start(), diff --git a/analytics/classes/local/time_splitting/weekly.php b/analytics/classes/local/time_splitting/weekly.php deleted file mode 100644 index 72d2dd81754e6..0000000000000 --- a/analytics/classes/local/time_splitting/weekly.php +++ /dev/null @@ -1,80 +0,0 @@ -. - -/** - * Weekly time splitting method. - * - * @package core_analytics - * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_analytics\local\time_splitting; - -defined('MOODLE_INTERNAL') || die(); - -abstract class weekly extends base { - - public function get_name() { - return get_string('timesplitting:weekly', 'analytics'); - } - - public function is_valid_analysable(\core_analytics\analysable $analysable) { - $diff = $analysable->get_end() - $analysable->get_start(); - $nweeks = round($diff / WEEKSECS); - if ($nweeks > 520) { - // More than 10 years... - return get_string('coursetoolong', 'analytics'); - } - return parent::is_valid_analysable($analysable); - } - - protected function define_ranges() { - - $ranges = array(); - - // It is more important to work with a proper end date than the start date. - $i = 0; - do { - - $dt = new \DateTime(); - $dt->setTimestamp($this->analysable->get_end()); - $dt->modify('-' . $i . ' weeks'); - $rangeend = $dt->getTimestamp(); - - $dt->modify('-1 weeks'); - $rangestart = $dt->getTimestamp(); - - $ranges[] = array( - 'start' => $rangestart, - 'end' => $rangeend, - 'time' => $rangeend - ); - - $i++; - - } while ($this->analysable->get_start() < $rangestart); - - $ranges = array_reverse($ranges, false); - - // Is not worth trying to predict during the first weeks. - array_shift($ranges); - array_shift($ranges); - - return $ranges; - } - -} diff --git a/analytics/classes/local/time_splitting/weekly_accum.php b/analytics/classes/local/time_splitting/weekly_accum.php deleted file mode 100644 index 99a0c49b852a3..0000000000000 --- a/analytics/classes/local/time_splitting/weekly_accum.php +++ /dev/null @@ -1,82 +0,0 @@ -. - -/** - * Weekly time splitting method. - * - * @package core_analytics - * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace core_analytics\local\time_splitting; - -defined('MOODLE_INTERNAL') || die(); - -abstract class weekly_accum extends base { - - public function get_name() { - return get_string('timesplitting:weeklyaccum', 'analytics'); - } - - public function is_valid_analysable(\core_analytics\analysable $analysable) { - $diff = $analysable->get_end() - $analysable->get_start(); - $nweeks = round($diff / WEEKSECS); - if ($nweeks > 520) { - // More than 10 years... - return false; - } - return parent::is_valid_analysable($analysable); - } - - protected function define_ranges() { - - $ranges = array(); - - // It is more important to work with a proper end date than start date. - $i = 0; - do { - - $dt = new \DateTime(); - $dt->setTimestamp($this->analysable->get_end()); - $dt->modify('-' . $i . ' weeks'); - $rangeend = $dt->getTimestamp(); - - // Used to calculate when we are done creating new ranges. - $dt->modify('-1 weeks'); - $rangestart = $dt->getTimestamp(); - - // Accumulative, always from the course start. - $ranges[] = array( - 'start' => $this->analysable->get_start(), - 'end' => $rangeend, - 'time' => $rangeend - ); - - $i++; - - } while ($this->analysable->get_start() < $rangestart); - - $ranges = array_reverse($ranges, false); - - // Is not worth trying to predict during the first weeks. - array_shift($ranges); - array_shift($ranges); - - return $ranges; - } - -} diff --git a/analytics/classes/manager.php b/analytics/classes/manager.php index 5b158d4b8d772..02325a6b9d5ef 100644 --- a/analytics/classes/manager.php +++ b/analytics/classes/manager.php @@ -335,7 +335,7 @@ public static function get_models_with_insights(\context $context) { self::check_can_list_insights($context); - $models = \core_analytics\manager::get_all_models(true, true, $context); + $models = self::get_all_models(true, true, $context); foreach ($models as $key => $model) { // Check that it not only have predictions but also generates insights from them. if (!$model->uses_insights()) { @@ -366,7 +366,7 @@ public static function get_prediction($predictionid, $requirelogin = false) { $context = \context::instance_by_id($predictionobj->contextid); } - \core_analytics\manager::check_can_list_insights($context); + self::check_can_list_insights($context); $model = new \core_analytics\model($predictionobj->modelid); $sampledata = $model->prediction_sample_data($predictionobj); diff --git a/analytics/classes/model.php b/analytics/classes/model.php index 6838644242648..f1e25ddcf0801 100644 --- a/analytics/classes/model.php +++ b/analytics/classes/model.php @@ -35,19 +35,59 @@ */ class model { + /** + * All as expected. + */ const OK = 0; + + /** + * There was a problem. + */ const GENERAL_ERROR = 1; + + /** + * No dataset to analyse. + */ const NO_DATASET = 2; + /** + * Model with low prediction accuracy. + */ const EVALUATE_LOW_SCORE = 4; + + /** + * Not enough data to evaluate the model properly. + */ const EVALUATE_NOT_ENOUGH_DATA = 8; - const ANALYSE_REJECTED_RANGE_PROCESSOR = 4; + /** + * Invalid analysable for the time splitting method. + */ + const ANALYSABLE_REJECTED_TIME_SPLITTING_METHOD = 4; + + /** + * Invalid analysable for all time splitting methods. + */ const ANALYSABLE_STATUS_INVALID_FOR_RANGEPROCESSORS = 8; + + /** + * Invalid analysable for the target + */ const ANALYSABLE_STATUS_INVALID_FOR_TARGET = 16; + /** + * Minimum score to consider a non-static prediction model as good. + */ const MIN_SCORE = 0.7; + + /** + * Maximum standard deviation between different evaluation repetitions to consider that evaluation results are stable. + */ const ACCEPTED_DEVIATION = 0.05; + + /** + * Number of evaluation repetitions. + */ const EVALUATION_ITERATIONS = 10; /** @@ -552,7 +592,7 @@ public function predict() { if ($this->is_static()) { // Prediction based on assumptions. - $result->status = \core_analytics\model::OK; + $result->status = self::OK; $result->info = []; $result->predictions = $this->get_static_predictions($indicatorcalculations); @@ -589,7 +629,7 @@ private function format_predictor_predictions($predictorresult) { if ($predictorresult->predictions) { foreach ($predictorresult->predictions as $sampleinfo) { - // We parse each prediction + // We parse each prediction. switch (count($sampleinfo)) { case 1: // For whatever reason the predictions processor could not process this sample, we @@ -620,7 +660,7 @@ private function format_predictor_predictions($predictorresult) { * Execute the prediction callbacks defined by the target. * * @param \stdClass[] $predictions - * @param array $predictions + * @param array $indicatorcalculations * @return array */ protected function execute_prediction_callbacks($predictions, $indicatorcalculations) { @@ -636,8 +676,8 @@ protected function execute_prediction_callbacks($predictions, $indicatorcalculat list($sampleid, $rangeindex) = $this->get_time_splitting()->infer_sample_info($uniquesampleid); // Store the predicted values. - $samplecontext = $this->save_prediction($sampleid, $rangeindex, $prediction->prediction, $prediction->predictionscore, - json_encode($indicatorcalculations[$uniquesampleid])); + $samplecontext = $this->save_prediction($sampleid, $rangeindex, $prediction->prediction, + $prediction->predictionscore, json_encode($indicatorcalculations[$uniquesampleid])); // Also store all samples context to later generate insights or whatever action the target wants to perform. $samplecontexts[$samplecontext->id] = $samplecontext; diff --git a/analytics/classes/prediction.php b/analytics/classes/prediction.php index d81c46566a2d5..f738f693dadec 100644 --- a/analytics/classes/prediction.php +++ b/analytics/classes/prediction.php @@ -15,6 +15,7 @@ // along with Moodle. If not, see . /** + * Representation of a prediction. * * @package core_analytics * @copyright 2017 David Monllao {@link http://www.davidmonllao.com} @@ -26,6 +27,7 @@ defined('MOODLE_INTERNAL') || die(); /** + * Representation of a prediction. * * @package core_analytics * @copyright 2017 David Monllao {@link http://www.davidmonllao.com} @@ -33,12 +35,28 @@ */ class prediction { + /** + * @var \stdClass + */ private $prediction; + /** + * @var array + */ private $sampledata; + /** + * @var array + */ private $calculations = array(); + /** + * Constructor + * + * @param \stdClass $prediction + * @param array $sampledata + * @return void + */ public function __construct($prediction, $sampledata) { global $DB; @@ -52,18 +70,38 @@ public function __construct($prediction, $sampledata) { $this->format_calculations(); } + /** + * Get prediction object data. + * + * @return \stdClass + */ public function get_prediction_data() { return $this->prediction; } + /** + * Get prediction sample data. + * + * @return array + */ public function get_sample_data() { return $this->sampledata; } + /** + * Gets the prediction calculations + * + * @return array + */ public function get_calculations() { return $this->calculations; } + /** + * format_calculations + * + * @return \stdClass[] + */ private function format_calculations() { $calculations = json_decode($this->prediction->calculations, true); @@ -86,6 +124,12 @@ private function format_calculations() { } } + /** + * parse_feature_name + * + * @param string $featurename + * @return string[] + */ private function parse_feature_name($featurename) { $indicatorclass = $featurename; @@ -100,5 +144,4 @@ private function parse_feature_name($featurename) { return array($indicatorclass, $subtype); } - } diff --git a/analytics/classes/prediction_action.php b/analytics/classes/prediction_action.php index 6a6e9ca365af2..db7e50ab46abf 100644 --- a/analytics/classes/prediction_action.php +++ b/analytics/classes/prediction_action.php @@ -15,6 +15,7 @@ // along with Moodle. If not, see . /** + * Representation of a suggested action associated with a prediction. * * @package core_analytics * @copyright 2017 David Monllao {@link http://www.davidmonllao.com} @@ -26,6 +27,7 @@ defined('MOODLE_INTERNAL') || die(); /** + * Representation of a suggested action associated with a prediction. * * @package core_analytics * @copyright 2017 David Monllao {@link http://www.davidmonllao.com} @@ -52,7 +54,8 @@ class prediction_action { public function __construct($actionname, \core_analytics\prediction $prediction, \moodle_url $actionurl, \pix_icon $icon, $text, $primary = false) { // We want to track how effective are our suggested actions, we pass users through a script that will log these actions. - $params = array('action' => $actionname, 'predictionid' => $prediction->get_prediction_data()->id, 'forwardurl' => $actionurl->out(false)); + $params = array('action' => $actionname, 'predictionid' => $prediction->get_prediction_data()->id, + 'forwardurl' => $actionurl->out(false)); $url = new \moodle_url('/report/insights/action.php', $params); if ($primary === false) { @@ -63,6 +66,8 @@ public function __construct($actionname, \core_analytics\prediction $prediction, } /** + * Returns the link to the action. + * * @return \action_menu_link */ public function get_action_link() { diff --git a/analytics/classes/predictor.php b/analytics/classes/predictor.php index ed46ec3ea4e8d..944c76eeb2d50 100644 --- a/analytics/classes/predictor.php +++ b/analytics/classes/predictor.php @@ -35,11 +35,42 @@ */ interface predictor { + /** + * Is it ready to predict? + * + * @return bool + */ public function is_ready(); + /** + * Train the provided dataset. + * + * @param int $modelid + * @param \stored_file $dataset + * @param string $outputdir + * @return \stdClass + */ public function train($modelid, \stored_file $dataset, $outputdir); + /** + * Predict the provided dataset samples. + * + * @param int $modelid + * @param \stored_file $dataset + * @param string $outputdir + * @return \stdClass + */ public function predict($modelid, \stored_file $dataset, $outputdir); + /** + * evaluate + * + * @param int $modelid + * @param float $maxdeviation + * @param int $niterations + * @param \stored_file $dataset + * @param string $outputdir + * @return \stdClass + */ public function evaluate($modelid, $maxdeviation, $niterations, \stored_file $dataset, $outputdir); } diff --git a/analytics/classes/site.php b/analytics/classes/site.php index f8a8396ff2953..e84c00a10432e 100644 --- a/analytics/classes/site.php +++ b/analytics/classes/site.php @@ -15,6 +15,7 @@ // along with Moodle. If not, see . /** + * Moodle site analysable. * * @package core_analytics * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} @@ -26,6 +27,7 @@ defined('MOODLE_INTERNAL') || die(); /** + * Moodle site analysable. * * @package core_analytics * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} diff --git a/analytics/tests/course_activities_test.php b/analytics/tests/course_activities_test.php index 2d70b1b9e68d1..8ba3a34b067c0 100644 --- a/analytics/tests/course_activities_test.php +++ b/analytics/tests/course_activities_test.php @@ -33,6 +33,11 @@ */ class core_analytics_course_activities_testcase extends advanced_testcase { + /** + * availability_levels + * + * @return array + */ public function availability_levels() { return array( 'activity' => array('activity'), @@ -51,7 +56,7 @@ public function test_get_activities_with_availability($availabilitylevel) { list($course, $stu1) = $this->setup_course(); - // forum1 is ignored as section 0 does not count. + // Forum1 is ignored as section 0 does not count. $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); $courseman = new \core_analytics\course($course); @@ -89,18 +94,27 @@ public function test_get_activities_with_availability($availabilitylevel) { $cm = $modinfo->get_cm($forum->cmid); // Condition from after provided end time. - $this->assertCount(0, $courseman->get_activities('forum', strtotime('2015-10-20 00:00:00 GMT'), strtotime('2015-10-21 00:00:00 GMT'), $stu1)); + $this->assertCount(0, $courseman->get_activities('forum', strtotime('2015-10-20 00:00:00 GMT'), + strtotime('2015-10-21 00:00:00 GMT'), $stu1)); // Condition until before provided start time - $this->assertCount(0, $courseman->get_activities('forum', strtotime('2015-10-25 00:00:00 GMT'), strtotime('2015-10-26 00:00:00 GMT'), $stu1)); + $this->assertCount(0, $courseman->get_activities('forum', strtotime('2015-10-25 00:00:00 GMT'), + strtotime('2015-10-26 00:00:00 GMT'), $stu1)); // Condition until after provided end time. - $this->assertCount(0, $courseman->get_activities('forum', strtotime('2015-10-22 00:00:00 GMT'), strtotime('2015-10-23 00:00:00 GMT'), $stu1)); + $this->assertCount(0, $courseman->get_activities('forum', strtotime('2015-10-22 00:00:00 GMT'), + strtotime('2015-10-23 00:00:00 GMT'), $stu1)); // Condition until after provided start time and before provided end time. - $this->assertCount(1, $courseman->get_activities('forum', strtotime('2015-10-22 00:00:00 GMT'), strtotime('2015-10-25 00:00:00 GMT'), $stu1)); + $this->assertCount(1, $courseman->get_activities('forum', strtotime('2015-10-22 00:00:00 GMT'), + strtotime('2015-10-25 00:00:00 GMT'), $stu1)); } + /** + * test_get_activities_with_weeks + * + * @return void + */ public function test_get_activities_with_weeks() { $startdate = gmmktime('0', '0', '0', 10, 24, 2015); @@ -112,7 +126,7 @@ public function test_get_activities_with_weeks() { list($course, $stu1) = $this->setup_course($record); - // forum1 is ignored as section 0 does not count. + // Forum1 is ignored as section 0 does not count. $forum1 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), array('section' => 0)); $forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), @@ -138,9 +152,14 @@ public function test_get_activities_with_weeks() { $this->assertCount(2, $courseman->get_activities('forum', $forth, $forth + WEEKSECS, $stu1)); } + /** + * test_get_activities_by_section + * + * @return void + */ public function test_get_activities_by_section() { - // This makes debugging easier, sorry WA's +8 :) + // This makes debugging easier, sorry WA's +8 :). $this->setTimezone('UTC'); // 1 year. @@ -156,7 +175,7 @@ public function test_get_activities_by_section() { list($course, $stu1) = $this->setup_course($record); - // forum1 is ignored as section 0 does not count. + // Forum1 is ignored as section 0 does not count. $forum1 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), array('section' => 0)); $forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), @@ -187,8 +206,8 @@ public function test_get_activities_by_section() { // Split the course in as many parts as sections. $duration = ($enddate - $startdate) / $numsections; - for($i = 1; $i <= $numsections; $i++) { - // -1 because section 1 start represents the course start. + for ($i = 1; $i <= $numsections; $i++) { + // The -1 because section 1 start represents the course start. $timeranges[$i] = $startdate + ($duration * ($i - 1)); } $this->assertCount(1, $courseman->get_activities('forum', $timeranges[1], $timeranges[1] + $duration, $stu1)); @@ -202,6 +221,12 @@ public function test_get_activities_by_section() { $this->assertCount(0, $courseman->get_activities('forum', $timeranges[3], $timeranges[3] + $duration, $stu1)); } + /** + * setup_course + * + * @param stdClass $courserecord + * @return array + */ protected function setup_course($courserecord = null) { global $CFG; diff --git a/analytics/tests/course_test.php b/analytics/tests/course_test.php index ba8e4c54a3c3b..f96bcb7fa6f66 100644 --- a/analytics/tests/course_test.php +++ b/analytics/tests/course_test.php @@ -47,7 +47,6 @@ public function setUp() { $this->editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher')); $this->teacherroleid = $DB->get_field('role', 'id', array('shortname' => 'teacher')); - $this->getDataGenerator()->enrol_user($this->stu1->id, $this->course->id, $this->studentroleid); $this->getDataGenerator()->enrol_user($this->stu2->id, $this->course->id, $this->studentroleid); $this->getDataGenerator()->enrol_user($this->both->id, $this->course->id, $this->studentroleid); @@ -69,7 +68,7 @@ public function test_users() { $this->assertCount(2, $courseman->get_user_ids(array($this->editingteacherroleid))); $this->assertCount(1, $courseman->get_user_ids(array($this->teacherroleid))); - // Distinct is applied + // Distinct is applied. $this->assertCount(3, $courseman->get_user_ids(array($this->editingteacherroleid, $this->teacherroleid))); $this->assertCount(4, $courseman->get_user_ids(array($this->editingteacherroleid, $this->studentroleid))); } diff --git a/analytics/tests/fixtures/test_indicator_fullname.php b/analytics/tests/fixtures/test_indicator_fullname.php index 7d52ad7f013ef..6c47bb5859b58 100644 --- a/analytics/tests/fixtures/test_indicator_fullname.php +++ b/analytics/tests/fixtures/test_indicator_fullname.php @@ -1,15 +1,65 @@ . +/** + * Test indicator. + * + * @package core_analytics + * @copyright 2017 David Monllaó {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Test indicator. + * + * @package core_analytics + * @copyright 2017 David Monllaó {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class test_indicator_fullname extends \core_analytics\local\indicator\linear { + /** + * include_averages + * + * @return bool + */ protected static function include_averages() { return false; } + /** + * required_sample_data + * + * @return string[] + */ public static function required_sample_data() { return array('course'); } + /** + * calculate_sample + * + * @param int $sampleid + * @param string $samplesorigin + * @param int $starttime + * @param int $endtime + * @return float + */ protected function calculate_sample($sampleid, $samplesorigin, $starttime, $endtime) { global $DB; @@ -26,5 +76,4 @@ protected function calculate_sample($sampleid, $samplesorigin, $starttime, $endt return self::MAX_VALUE; } } - } diff --git a/analytics/tests/fixtures/test_indicator_max.php b/analytics/tests/fixtures/test_indicator_max.php index 752e843775a3c..60102fdc77bc0 100644 --- a/analytics/tests/fixtures/test_indicator_max.php +++ b/analytics/tests/fixtures/test_indicator_max.php @@ -1,6 +1,47 @@ . +/** + * Test indicator. + * + * @package core_analytics + * @copyright 2017 David Monllaó {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Test indicator. + * + * @package core_analytics + * @copyright 2017 David Monllaó {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class test_indicator_max extends \core_analytics\local\indicator\binary { + + /** + * calculate_sample + * + * @param int $sampleid + * @param string $samplesorigin + * @param int $starttime + * @param int $endtime + * @return float + */ protected function calculate_sample($sampleid, $samplesorigin, $starttime, $endtime) { return self::MAX_VALUE; } diff --git a/analytics/tests/fixtures/test_indicator_min.php b/analytics/tests/fixtures/test_indicator_min.php index 19e19f8eb4ee2..1d11f59fffacf 100644 --- a/analytics/tests/fixtures/test_indicator_min.php +++ b/analytics/tests/fixtures/test_indicator_min.php @@ -1,6 +1,47 @@ . +/** + * Test indicator. + * + * @package core_analytics + * @copyright 2017 David Monllaó {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Test indicator. + * + * @package core_analytics + * @copyright 2017 David Monllaó {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class test_indicator_min extends \core_analytics\local\indicator\binary { + + /** + * calculate_sample + * + * @param int $sampleid + * @param string $samplesorigin + * @param int $starttime + * @param int $endtime + * @return float + */ protected function calculate_sample($sampleid, $samplesorigin, $starttime, $endtime) { return self::MIN_VALUE; } diff --git a/analytics/tests/fixtures/test_indicator_random.php b/analytics/tests/fixtures/test_indicator_random.php index 113b352e3d875..a94be7424be82 100644 --- a/analytics/tests/fixtures/test_indicator_random.php +++ b/analytics/tests/fixtures/test_indicator_random.php @@ -1,10 +1,48 @@ . +/** + * Test indicator. + * + * @package core_analytics + * @copyright 2017 David Monllaó {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Test indicator. + * + * @package core_analytics + * @copyright 2017 David Monllaó {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class test_indicator_random extends \core_analytics\local\indicator\binary { + /** + * calculate_sample + * + * @param int $sampleid + * @param string $samplesorigin + * @param int $starttime + * @param int $endtime + * @return float + */ protected function calculate_sample($sampleid, $samplesorigin, $starttime, $endtime) { - global $DB; - return mt_rand(-1, 1); } } diff --git a/analytics/tests/fixtures/test_static_target_shortname.php b/analytics/tests/fixtures/test_static_target_shortname.php index 0f36d517a8964..fe2e65cb75aca 100644 --- a/analytics/tests/fixtures/test_static_target_shortname.php +++ b/analytics/tests/fixtures/test_static_target_shortname.php @@ -1,13 +1,59 @@ . +/** + * Test static target. + * + * @package core_analytics + * @copyright 2017 David Monllaó {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Test static target. + * + * @package core_analytics + * @copyright 2017 David Monllaó {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class test_static_target_shortname extends \core_analytics\local\target\binary { + /** + * predictions + * + * @var array + */ protected $predictions = array(); + /** + * get_analyser_class + * + * @return string + */ public function get_analyser_class() { return '\core_analytics\local\analyser\site_courses'; } + /** + * classes_description + * + * @return string[] + */ public static function classes_description() { return array( 'Course fullname first char is A', @@ -17,7 +63,7 @@ public static function classes_description() { /** * We don't want to discard results. - * @return float + * @return null */ protected function min_prediction_score() { return null; @@ -31,23 +77,52 @@ protected function ignored_predicted_classes() { return array(); } + /** + * is_valid_analysable + * + * @param \core_analytics\analysable $analysable + * @param bool $fortraining + * @return bool + */ public function is_valid_analysable(\core_analytics\analysable $analysable, $fortraining = true) { // This is testing, let's make things easy. return true; } - protected function calculate_sample($sampleid, \core_analytics\analysable $analysable, $starttime = false, $endtime = false) { - global $DB; - - $sample = $DB->get_record('course', array('id' => $sampleid)); + /** + * is_valid_sample + * + * @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) { + // We skip not-visible courses during training as a way to emulate the training data / prediction data difference. + // In normal circumstances is_valid_sample will return false when they receive a sample that can not be + // processed. + if (!$fortraining) { + return true; + } + $sample = $this->retrieve('course', $sampleid); if ($sample->visible == 0) { - // We skip not-visible courses as a way to emulate the training data / prediction data difference. - // In normal circumstances targets will return null when they receive a sample that can not be - // processed, that same sample may be used for prediction. - // We can not do this in is_valid_analysable because the analysable there is the site not the course. - return null; + 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) { + $sample = $this->retrieve('course', $sampleid); $firstchar = substr($sample->shortname, 0, 1); if ($firstchar === 'a') { diff --git a/analytics/tests/fixtures/test_target_shortname.php b/analytics/tests/fixtures/test_target_shortname.php index 890e7eb5eceb0..507499fe69321 100644 --- a/analytics/tests/fixtures/test_target_shortname.php +++ b/analytics/tests/fixtures/test_target_shortname.php @@ -1,13 +1,59 @@ . +/** + * Test target. + * + * @package core_analytics + * @copyright 2017 David Monllaó {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Test target. + * + * @package core_analytics + * @copyright 2017 David Monllaó {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class test_target_shortname extends \core_analytics\local\target\binary { + /** + * predictions + * + * @var array + */ protected $predictions = array(); + /** + * get_analyser_class + * + * @return string + */ public function get_analyser_class() { return '\core_analytics\local\analyser\site_courses'; } + /** + * classes_description + * + * @return string[] + */ public static function classes_description() { return array( 'Course fullname first char is A', @@ -31,11 +77,26 @@ protected function ignored_predicted_classes() { return array(); } + /** + * is_valid_analysable + * + * @param \core_analytics\analysable $analysable + * @param bool $fortraining + * @return bool + */ public function is_valid_analysable(\core_analytics\analysable $analysable, $fortraining = true) { // This is testing, let's make things easy. return true; } + /** + * is_valid_sample + * + * @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) { // We skip not-visible courses during training as a way to emulate the training data / prediction data difference. // In normal circumstances is_valid_sample will return false when they receive a sample that can not be @@ -51,6 +112,15 @@ public function is_valid_sample($sampleid, \core_analytics\analysable $analysabl 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) { $sample = $this->retrieve('course', $sampleid); diff --git a/analytics/tests/model_test.php b/analytics/tests/model_test.php index 36c74696ed91c..ad351f8317b9a 100644 --- a/analytics/tests/model_test.php +++ b/analytics/tests/model_test.php @@ -17,7 +17,7 @@ /** * Unit tests for the model. * - * @package analytics + * @package core_analytics * @copyright 2017 David Monllaó {@link http://www.davidmonllao.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -32,7 +32,7 @@ /** * Unit tests for the model. * - * @package analytics + * @package core_analytics * @copyright 2017 David Monllaó {@link http://www.davidmonllao.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -142,12 +142,32 @@ public function test_unique_id() { } } +/** + * Testable version to change methods' visibility. + * + * @package core_analytics + * @copyright 2017 David Monllaó {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class testable_model extends \core_analytics\model { + + /** + * get_output_dir + * + * @param array $subdirs + * @return string + */ public function get_output_dir($subdirs = array()) { return parent::get_output_dir($subdirs); } + /** + * init_analyser + * + * @param array $options + * @return void + */ public function init_analyser($options = array()) { - return parent::init_analyser($options); + parent::init_analyser($options); } } diff --git a/analytics/tests/prediction_test.php b/analytics/tests/prediction_test.php index ad4a64116387b..6a41a0883a49b 100644 --- a/analytics/tests/prediction_test.php +++ b/analytics/tests/prediction_test.php @@ -40,9 +40,12 @@ class core_analytics_prediction_testcase extends advanced_testcase { /** + * test_ml_training_and_prediction + * * @dataProvider provider_ml_training_and_prediction * @param string $timesplittingid * @param int $npredictedranges + * @param string $predictionsprocessorclass * @return void */ public function test_ml_training_and_prediction($timesplittingid, $npredictedranges, $predictionsprocessorclass) { @@ -112,7 +115,7 @@ public function test_ml_training_and_prediction($timesplittingid, $npredictedran // They will not be skipped for prediction though. $result = $model->predict(); - // $course1 predictions should be 1 == 'a', $course2 predictions should be 0 == 'b'. + // Var $course1 predictions should be 1 == 'a', $course2 predictions should be 0 == 'b'. $correct = array($course1->id => 1, $course2->id => 0); foreach ($result->predictions as $uniquesampleid => $predictiondata) { list($sampleid, $rangeindex) = $model->get_time_splitting()->infer_sample_info($uniquesampleid); @@ -127,7 +130,8 @@ public function test_ml_training_and_prediction($timesplittingid, $npredictedran $this->assertEquals(1, $DB->count_records('analytics_used_files', array('modelid' => $model->get_id(), 'action' => 'predicted'))); // 2 predictions for each range. - $this->assertEquals(2 * $npredictedranges, $DB->count_records('analytics_predictions', array('modelid' => $model->get_id()))); + $this->assertEquals(2 * $npredictedranges, $DB->count_records('analytics_predictions', + array('modelid' => $model->get_id()))); // No new generated files nor records as there are no new courses available. $model->predict(); @@ -135,9 +139,15 @@ public function test_ml_training_and_prediction($timesplittingid, $npredictedran $this->assertEquals($npredictedranges, count($trainedsamples)); $this->assertEquals(1, $DB->count_records('analytics_used_files', array('modelid' => $model->get_id(), 'action' => 'predicted'))); - $this->assertEquals(2 * $npredictedranges, $DB->count_records('analytics_predictions', array('modelid' => $model->get_id()))); + $this->assertEquals(2 * $npredictedranges, $DB->count_records('analytics_predictions', + array('modelid' => $model->get_id()))); } + /** + * provider_ml_training_and_prediction + * + * @return array + */ public function provider_ml_training_and_prediction() { $cases = array( 'no_splitting' => array('\core_analytics\local\time_splitting\no_splitting', 1), @@ -153,6 +163,11 @@ public function provider_ml_training_and_prediction() { * Basic test to check that prediction processors work as expected. * * @dataProvider provider_ml_test_evaluation + * @param string $modelquality + * @param int $ncourses + * @param array $expected + * @param string $predictionsprocessorclass + * @return void */ public function test_ml_evaluation($modelquality, $ncourses, $expected, $predictionsprocessorclass) { $this->resetAfterTest(true); @@ -172,7 +187,6 @@ public function test_ml_evaluation($modelquality, $ncourses, $expected, $predict throw new \coding_exception('Only perfect and random accepted as $modelquality values'); } - // Generate training data. $params = array( 'startdate' => mktime(0, 0, 0, 10, 24, 2015), @@ -202,12 +216,19 @@ public function test_ml_evaluation($modelquality, $ncourses, $expected, $predict // We check that the returned status includes at least $expectedcode code. foreach ($results as $timesplitting => $result) { $message = 'The returned status code ' . $result->status . ' should include ' . $expected[$timesplitting]; - $this->assertEquals($expected[$timesplitting], $result->status & $expected[$timesplitting], $message); + $filtered = $result->status & $expected[$timesplitting]; + $this->assertEquals($expected[$timesplitting], $filtered, $message); } } + /** + * provider_ml_test_evaluation + * + * @return array + */ public function provider_ml_test_evaluation() { + $notenoughandlowscore = \core_analytics\model::EVALUATE_NOT_ENOUGH_DATA + \core_analytics\model::EVALUATE_LOW_SCORE; $cases = array( 'bad-and-no-enough-data' => array( 'modelquality' => 'random', @@ -216,8 +237,8 @@ public function provider_ml_test_evaluation() { // The course duration is too much to be processed by in weekly basis. '\core_analytics\local\time_splitting\weekly' => \core_analytics\model::NO_DATASET, // 10 samples is not enough to process anything. - '\core_analytics\local\time_splitting\single_range' => \core_analytics\model::EVALUATE_NOT_ENOUGH_DATA + \core_analytics\model::EVALUATE_LOW_SCORE, - '\core_analytics\local\time_splitting\quarters' => \core_analytics\model::EVALUATE_NOT_ENOUGH_DATA + \core_analytics\model::EVALUATE_LOW_SCORE, + '\core_analytics\local\time_splitting\single_range' => $notenoughandlowscore, + '\core_analytics\local\time_splitting\quarters' => $notenoughandlowscore, ) ), 'bad' => array( @@ -244,6 +265,11 @@ public function provider_ml_test_evaluation() { return $this->add_prediction_processors($cases); } + /** + * add_random_model + * + * @return \core_analytics\model + */ protected function add_random_model() { $target = \core_analytics\manager::get_target('test_target_shortname'); @@ -258,6 +284,11 @@ protected function add_random_model() { return new \core_analytics\model($model->get_id()); } + /** + * add_perfect_model + * + * @return \core_analytics\model + */ protected function add_perfect_model() { $target = \core_analytics\manager::get_target('test_target_shortname'); @@ -272,6 +303,12 @@ protected function add_perfect_model() { return new \core_analytics\model($model->get_id()); } + /** + * add_prediction_processors + * + * @param array $cases + * @return array + */ protected function add_prediction_processors($cases) { $return = array(); diff --git a/course/classes/analytics/indicator/no_teacher.php b/course/classes/analytics/indicator/no_teacher.php index 5372c51e8c704..54687cd24930d 100644 --- a/course/classes/analytics/indicator/no_teacher.php +++ b/course/classes/analytics/indicator/no_teacher.php @@ -35,10 +35,20 @@ */ class no_teacher extends \core_analytics\local\indicator\binary { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_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. diff --git a/lib/classes/component.php b/lib/classes/component.php index b4eee454e696f..277d7db6942d7 100644 --- a/lib/classes/component.php +++ b/lib/classes/component.php @@ -83,6 +83,7 @@ class core_component { 'MatthiasMullie\\Minify' => 'lib/minify/matthiasmullie-minify/src/', 'MatthiasMullie\\PathConverter' => 'lib/minify/matthiasmullie-pathconverter/src/', 'IMSGlobal\LTI' => 'lib/ltiprovider/src', + 'Phpml' => 'lib/mlbackend/php/phpml/src/Phpml', ); /** diff --git a/lib/classes/event/prediction_action_started.php b/lib/classes/event/prediction_action_started.php index 2b25e300557b7..6cd4705a0268a 100644 --- a/lib/classes/event/prediction_action_started.php +++ b/lib/classes/event/prediction_action_started.php @@ -70,7 +70,8 @@ public static function get_name() { * @return string */ public function get_description() { - return "The user with id '$this->userid' has started '{$this->other['actionname']}' action for the prediction with id '".$this->objectid."'."; + return "The user with id '$this->userid' has started '{$this->other['actionname']}' action for the prediction with id '" . + $this->objectid . "'."; } /** @@ -95,6 +96,11 @@ protected function validate_data() { } } + /** + * get_objectid_mapping + * + * @return array + */ public static function get_objectid_mapping() { return array('db' => 'analytics_predictions'); } diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index 876ac445c55b0..30220900c9504 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -2084,7 +2084,8 @@ function xmldb_main_upgrade($oldversion) { $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); // Adding indexes to table analytics_train_samples. - $table->add_index('modelidandanalysableidandtimesplitting', XMLDB_INDEX_NOTUNIQUE, array('modelid', 'analysableid', 'timesplitting')); + $table->add_index('modelidandanalysableidandtimesplitting', XMLDB_INDEX_NOTUNIQUE, + array('modelid', 'analysableid', 'timesplitting')); // Conditionally launch create table for analytics_train_samples. if (!$dbman->table_exists($table)) { @@ -2106,7 +2107,8 @@ function xmldb_main_upgrade($oldversion) { $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); // Adding indexes to table analytics_predict_ranges. - $table->add_index('modelidandanalysableidandtimesplitting', XMLDB_INDEX_NOTUNIQUE, array('modelid', 'analysableid', 'timesplitting')); + $table->add_index('modelidandanalysableidandtimesplitting', XMLDB_INDEX_NOTUNIQUE, + array('modelid', 'analysableid', 'timesplitting')); // Conditionally launch create table for analytics_predict_ranges. if (!$dbman->table_exists($table)) { diff --git a/lib/enrollib.php b/lib/enrollib.php index c9cc8ee477917..01c31401588e2 100644 --- a/lib/enrollib.php +++ b/lib/enrollib.php @@ -1434,9 +1434,9 @@ function enrol_get_course_by_user_enrolment_id($ueid) { * * @param int $courseid Course id or false if using $uefilter (user enrolment ids may belong to different courses) * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions - * @param array usersfilter Limit the results obtained to this list of user ids. $uefilter compatibility not guaranteed. - * @param array uefilter Limit the results obtained to this list of user enrolment ids. $usersfilter compatibility not guaranteed. - * @return stdClass + * @param array $usersfilter Limit the results obtained to this list of user ids. $uefilter compatibility not guaranteed. + * @param array $uefilter Limit the results obtained to this list of user enrolment ids. $usersfilter compatibility not guaranteed. + * @return stdClass[] */ function enrol_get_course_users($courseid = false, $onlyactive = false, $usersfilter = array(), $uefilter = array()) { global $DB; @@ -1460,8 +1460,10 @@ function enrol_get_course_users($courseid = false, $onlyactive = false, $usersfi } if ($onlyactive) { - $conditions[] = "ue.status = :active AND e.status = :enabled AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)"; - $params['now1'] = round(time(), -2); // improves db caching + $conditions[] = "ue.status = :active AND e.status = :enabled AND ue.timestart < :now1 AND " . + "(ue.timeend = 0 OR ue.timeend > :now2)"; + // Improves db caching. + $params['now1'] = round(time(), -2); $params['now2'] = $params['now1']; $params['active'] = ENROL_USER_ACTIVE; $params['enabled'] = ENROL_INSTANCE_ENABLED; @@ -1483,8 +1485,13 @@ function enrol_get_course_users($courseid = false, $onlyactive = false, $usersfi } /** + * Enrolment plugins abstract class. + * * All enrol plugins should be based on this class, * this is also the main source of documentation. + * + * @copyright 2010 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class enrol_plugin { protected $config = null; diff --git a/lib/mlbackend/php/classes/processor.php b/lib/mlbackend/php/classes/processor.php index 891d1d8eedce1..294f8cdffd355 100644 --- a/lib/mlbackend/php/classes/processor.php +++ b/lib/mlbackend/php/classes/processor.php @@ -24,20 +24,12 @@ namespace mlbackend_php; -// TODO No support for 3rd party plugins psr4?? -spl_autoload_register(function($class) { - // Autoload Phpml classes. - $path = __DIR__ . '/../phpml/src/' . str_replace('\\', '/', $class) . '.php'; - if (file_exists($path)) { - require_once($path); - } -}); +defined('MOODLE_INTERNAL') || die(); use Phpml\Preprocessing\Normalizer; use Phpml\CrossValidation\RandomSplit; use Phpml\Dataset\ArrayDataset; - -defined('MOODLE_INTERNAL') || die(); +use Phpml\ModelManager; /** * PHP predictions processor. @@ -48,12 +40,31 @@ */ class processor implements \core_analytics\predictor { + /** + * Size of training / prediction batches. + */ const BATCH_SIZE = 5000; + + /** + * Number of train iterations. + */ const TRAIN_ITERATIONS = 500; + + /** + * File name of the serialised model. + */ const MODEL_FILENAME = 'model.ser'; + /** + * @var bool + */ protected $limitedsize = false; + /** + * Checks if the processor is ready to use. + * + * @return bool + */ public function is_ready() { if (version_compare(phpversion(), '7.0.0') < 0) { return get_string('errorphp7required', 'mlbackend_php'); @@ -61,12 +72,20 @@ public function is_ready() { return true; } + /** + * Trains a machine learning algorithm with the provided training set. + * + * @param string $uniqueid + * @param \stored_file $dataset + * @param string $outputdir + * @return \stdClass + */ public function train($uniqueid, \stored_file $dataset, $outputdir) { // Output directory is already unique to the model. $modelfilepath = $outputdir . DIRECTORY_SEPARATOR . self::MODEL_FILENAME; - $modelmanager = new \Phpml\ModelManager(); + $modelmanager = new ModelManager(); if (file_exists($modelfilepath)) { $classifier = $modelmanager->restoreFromFile($modelfilepath); @@ -114,6 +133,14 @@ public function train($uniqueid, \stored_file $dataset, $outputdir) { return $resultobj; } + /** + * Predicts the provided samples + * + * @param string $uniqueid + * @param \stored_file $dataset + * @param string $outputdir + * @return \stdClass + */ public function predict($uniqueid, \stored_file $dataset, $outputdir) { // Output directory is already unique to the model. @@ -123,7 +150,7 @@ public function predict($uniqueid, \stored_file $dataset, $outputdir) { throw new \moodle_exception('errorcantloadmodel', 'analytics', '', $modelfilepath); } - $modelmanager = new \Phpml\ModelManager(); + $modelmanager = new ModelManager(); $classifier = $modelmanager->restoreFromFile($modelfilepath); $fh = $dataset->get_content_file_handle(); @@ -204,7 +231,7 @@ public function evaluate($uniqueid, $maxdeviation, $niterations, \stored_file $d // Just an approximation, will depend on PHP version, compile options... // Double size + zval struct (6 bytes + 8 bytes + 16 bytes) + array bucket (96 bytes) - // https://nikic.github.io/2011/12/12/How-big-are-PHP-arrays-really-Hint-BIG.html + // https://nikic.github.io/2011/12/12/How-big-are-PHP-arrays-really-Hint-BIG.html. $floatsize = (PHP_INT_SIZE * 2) + 6 + 8 + 16 + 96; } @@ -219,7 +246,7 @@ public function evaluate($uniqueid, $maxdeviation, $niterations, \stored_file $d if (empty($CFG->mlbackend_php_no_evaluation_limits)) { // We allow admins to disable evaluation memory usage limits by modifying config.php. - // We will have plenty of missing values in the dataset so it should be a conservative approximation: + // We will have plenty of missing values in the dataset so it should be a conservative approximation. $samplessize = $samplessize + (count($sampledata) * $floatsize); // Stop fetching more samples. @@ -251,8 +278,17 @@ public function evaluate($uniqueid, $maxdeviation, $niterations, \stored_file $d return $this->get_evaluation_result_object($dataset, $phis, $maxdeviation); } + /** + * Returns the results objects from all evaluations. + * + * @param \stored_file $dataset + * @param array $phis + * @param float $maxdeviation + * @return \stdClass + */ protected function get_evaluation_result_object(\stored_file $dataset, $phis, $maxdeviation) { + // Average phi of all evaluations as final score. if (count($phis) === 1) { $avgphi = reset($phis); } else { @@ -300,6 +336,13 @@ protected function get_evaluation_result_object(\stored_file $dataset, $phis, $m return $resultobj; } + /** + * Returns the Phi correlation coefficient. + * + * @param array $testlabels + * @param array $predictedlabels + * @return float + */ protected function get_phi($testlabels, $predictedlabels) { // Binary here only as well. @@ -320,6 +363,14 @@ protected function get_phi($testlabels, $predictedlabels) { return $phi; } + /** + * Extracts metadata from the dataset file. + * + * The file poiter should be located at the top of the file. + * + * @param resource $fh + * @return array + */ protected function extract_metadata($fh) { $metadata = fgetcsv($fh); return array_combine($metadata, fgetcsv($fh)); diff --git a/lib/mlbackend/php/lang/en/mlbackend_php.php b/lib/mlbackend/php/lang/en/mlbackend_php.php index 2262879f941b2..2a821a40b36cb 100644 --- a/lib/mlbackend/php/lang/en/mlbackend_php.php +++ b/lib/mlbackend/php/lang/en/mlbackend_php.php @@ -1,4 +1,26 @@ . + +/** + * Strings for component 'mlbackend_php' + * + * @package mlbackend_php + * @copyright 2017 David Monllao {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ $string['datasetsizelimited'] = 'Only a part of the evaluation dataset has been evaluated due to its size. Set $CFG->mlbackend_php_no_memory_limit if you are confident that your system can cope a {$a} dataset'; $string['errorcantloadmodel'] = 'Model file {$a} does not exist, ensure the model has been trained before using it to predict.'; diff --git a/lib/mlbackend/python/classes/processor.php b/lib/mlbackend/python/classes/processor.php index 5bb40e8823bc7..e0e042d101b55 100644 --- a/lib/mlbackend/python/classes/processor.php +++ b/lib/mlbackend/python/classes/processor.php @@ -35,11 +35,19 @@ */ class processor implements \core_analytics\predictor { + /** + * The required version of the python package that performs all calculations. + */ const REQUIRED_PIP_PACKAGE_VERSION = '0.0.9'; + /** + * Is the plugin ready to be used?. + * + * @return bool + */ public function is_ready() { - # Check the installed pip package version. + // Check the installed pip package version. $cmd = 'python -m moodleinspire.version'; $output = null; @@ -63,8 +71,17 @@ public function is_ready() { return get_string('pythonpackagenotinstalled', 'mlbackend_python', $cmd); } + /** + * Trains a machine learning algorithm with the provided dataset. + * + * @param string $uniqueid + * @param \stored_file $dataset + * @param string $outputdir + * @return \stdClass + */ public function train($uniqueid, \stored_file $dataset, $outputdir) { + // Obtain the physical route to the file. $datasetpath = $this->get_file_path($dataset); $cmd = 'python -m moodleinspire.training ' . @@ -95,8 +112,17 @@ public function train($uniqueid, \stored_file $dataset, $outputdir) { return $resultobj; } + /** + * Returns predictions for the provided dataset samples. + * + * @param string $uniqueid + * @param \stored_file $dataset + * @param string $outputdir + * @return \stdClass + */ public function predict($uniqueid, \stored_file $dataset, $outputdir) { + // Obtain the physical route to the file. $datasetpath = $this->get_file_path($dataset); $cmd = 'python -m moodleinspire.prediction ' . @@ -127,8 +153,19 @@ public function predict($uniqueid, \stored_file $dataset, $outputdir) { return $resultobj; } + /** + * Evaluates the provided dataset. + * + * @param string $uniqueid + * @param float $maxdeviation + * @param int $niterations + * @param \stored_file $dataset + * @param string $outputdir + * @return \stdClass + */ public function evaluate($uniqueid, $maxdeviation, $niterations, \stored_file $dataset, $outputdir) { + // Obtain the physical route to the file. $datasetpath = $this->get_file_path($dataset); $cmd = 'python -m moodleinspire.evaluation ' . @@ -158,6 +195,12 @@ public function evaluate($uniqueid, $maxdeviation, $niterations, \stored_file $d return $resultobj; } + /** + * Returns the path to the dataset file. + * + * @param \stored_file $file + * @return string + */ protected function get_file_path(\stored_file $file) { // From moodle filesystem to the local file system. // This is not ideal, but there is no read access to moodle filesystem files. diff --git a/lib/mlbackend/python/lang/en/mlbackend_python.php b/lib/mlbackend/python/lang/en/mlbackend_python.php index 1a2889a4b56c5..e201ddee98571 100644 --- a/lib/mlbackend/python/lang/en/mlbackend_python.php +++ b/lib/mlbackend/python/lang/en/mlbackend_python.php @@ -1,4 +1,26 @@ . + +/** + * Strings for component 'mlbackend_python' + * + * @package mlbackend_python + * @copyright 2017 David Monllao {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ $string['packageinstalledshouldbe'] = '"moodleinspire" python package should be updated. The required version is "{$a->required}" and the installed version is "{$a->installed}"'; $string['pluginname'] = 'Python machine learning backend'; diff --git a/lib/tests/component_test.php b/lib/tests/component_test.php index 30852a69b2a3a..24b0b6d840424 100644 --- a/lib/tests/component_test.php +++ b/lib/tests/component_test.php @@ -31,9 +31,11 @@ */ class core_component_testcase extends advanced_testcase { - // To be changed if number of subsystems increases/decreases, - // this is defined here to annoy devs that try to add more without any thinking, - // always verify that it does not collide with any existing add-on modules and subplugins!!! + /** + * To be changed if number of subsystems increases/decreases, + * this is defined here to annoy devs that try to add more without any thinking, + * always verify that it does not collide with any existing add-on modules and subplugins!!! + */ const SUBSYSTEMCOUNT = 67; public function setUp() { diff --git a/mod/assign/classes/analytics/indicator/activity_base.php b/mod/assign/classes/analytics/indicator/activity_base.php index a73360c5567c2..f5a5203f98423 100644 --- a/mod/assign/classes/analytics/indicator/activity_base.php +++ b/mod/assign/classes/analytics/indicator/activity_base.php @@ -35,10 +35,20 @@ */ abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity { + /** + * feedback_viewed_events + * + * @return string[] + */ protected function feedback_viewed_events() { return array('\mod_assign\event\feedback_viewed'); } + /** + * feedback_check_grades + * + * @return bool + */ protected function feedback_check_grades() { // We need the grade to be released to the student to consider that feedback has been provided. return true; diff --git a/mod/assign/classes/analytics/indicator/cognitive_depth.php b/mod/assign/classes/analytics/indicator/cognitive_depth.php index c95de32f999da..a499bd62a3b63 100644 --- a/mod/assign/classes/analytics/indicator/cognitive_depth.php +++ b/mod/assign/classes/analytics/indicator/cognitive_depth.php @@ -35,22 +35,52 @@ */ class cognitive_depth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthassign', 'mod_assign'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_COGNITIVE; } + /** + * get_cognitive_depth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_cognitive_depth_level(\cm_info $cm) { return 5; } + /** + * feedback_submitted_events + * + * @return string[] + */ protected function feedback_submitted_events() { return array('\mod_assign\event\assessable_submitted'); } + /** + * feedback_replied + * + * @param \cm_info $cm + * @param int $contextid + * @param int $userid + * @param int $after + * @return bool + */ protected function feedback_replied(\cm_info $cm, $contextid, $userid, $after = false) { // No level 4. return false; diff --git a/mod/assign/classes/analytics/indicator/social_breadth.php b/mod/assign/classes/analytics/indicator/social_breadth.php index ac9d8ec9ac81e..1ff1fda3e8f8d 100644 --- a/mod/assign/classes/analytics/indicator/social_breadth.php +++ b/mod/assign/classes/analytics/indicator/social_breadth.php @@ -35,14 +35,30 @@ */ class social_breadth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:socialbreadthassign', 'mod_assign'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_SOCIAL; } + /** + * get_social_breadth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_social_breadth_level(\cm_info $cm) { return 2; } diff --git a/mod/book/classes/analytics/indicator/cognitive_depth.php b/mod/book/classes/analytics/indicator/cognitive_depth.php index 209467673dce3..37d3461b03dc4 100644 --- a/mod/book/classes/analytics/indicator/cognitive_depth.php +++ b/mod/book/classes/analytics/indicator/cognitive_depth.php @@ -35,14 +35,30 @@ */ class cognitive_depth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthbook', 'mod_book'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_COGNITIVE; } + /** + * get_cognitive_depth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_cognitive_depth_level(\cm_info $cm) { return 1; } diff --git a/mod/book/classes/analytics/indicator/social_breadth.php b/mod/book/classes/analytics/indicator/social_breadth.php index f6d1fe3d00e0a..bbe6772b43686 100644 --- a/mod/book/classes/analytics/indicator/social_breadth.php +++ b/mod/book/classes/analytics/indicator/social_breadth.php @@ -35,14 +35,30 @@ */ class social_breadth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:socialbreadthbook', 'mod_book'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_SOCIAL; } + /** + * get_social_breadth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_social_breadth_level(\cm_info $cm) { return 1; } diff --git a/mod/chat/classes/analytics/indicator/activity_base.php b/mod/chat/classes/analytics/indicator/activity_base.php index 8970773dedf66..1264e17175527 100644 --- a/mod/chat/classes/analytics/indicator/activity_base.php +++ b/mod/chat/classes/analytics/indicator/activity_base.php @@ -35,11 +35,35 @@ */ abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity { + /** + * feedback_viewed_events + * + * @return string[] + */ protected function feedback_viewed_events() { return array('\mod_chat\event\course_module_viewed', '\mod_chat\event\message_sent', '\mod_chat\event\sessions_viewed'); } + /** + * feedback_replied_events + * + * @return string[] + */ + protected function feedback_replied_events() { + return array('\mod_chat\event\message_sent'); + } + + /** + * feedback_post_action + * + * @param \cm_info $cm + * @param int $contextid + * @param int $userid + * @param string[] $eventnames + * @param int $after + * @return bool + */ protected function feedback_post_action(\cm_info $cm, $contextid, $userid, $eventnames, $after = false) { if (empty($this->activitylogs[$contextid][$userid])) { @@ -75,6 +99,11 @@ protected function feedback_post_action(\cm_info $cm, $contextid, $userid, $even return false; } + /** + * feedback_check_grades + * + * @return bool + */ protected function feedback_check_grades() { // Chat's feedback is not contained in grades. return false; diff --git a/mod/chat/classes/analytics/indicator/cognitive_depth.php b/mod/chat/classes/analytics/indicator/cognitive_depth.php index 9d3d354f3eaa5..aa74afd273f7f 100644 --- a/mod/chat/classes/analytics/indicator/cognitive_depth.php +++ b/mod/chat/classes/analytics/indicator/cognitive_depth.php @@ -35,20 +35,31 @@ */ class cognitive_depth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthchat', 'mod_chat'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_COGNITIVE; } + /** + * get_cognitive_depth_level + * + * @param \cm_info $cm + * @return int + */ public function get_cognitive_depth_level(\cm_info $cm) { return 4; } - - protected function feedback_replied_events() { - return array('\mod_chat\event\message_sent'); - } - } diff --git a/mod/chat/classes/analytics/indicator/social_breadth.php b/mod/chat/classes/analytics/indicator/social_breadth.php index 3261668994cdc..e751c4aaeda3c 100644 --- a/mod/chat/classes/analytics/indicator/social_breadth.php +++ b/mod/chat/classes/analytics/indicator/social_breadth.php @@ -35,14 +35,30 @@ */ class social_breadth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:socialbreadthchat', 'mod_chat'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_SOCIAL; } + /** + * get_social_breadth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_social_breadth_level(\cm_info $cm) { return 2; } diff --git a/mod/choice/classes/analytics/indicator/activity_base.php b/mod/choice/classes/analytics/indicator/activity_base.php index f82a31763f769..4395c6fa93003 100644 --- a/mod/choice/classes/analytics/indicator/activity_base.php +++ b/mod/choice/classes/analytics/indicator/activity_base.php @@ -35,8 +35,18 @@ */ abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity { + /** + * choicedata + * + * @var array + */ protected $choicedata = array(); + /** + * feedback_viewed_events + * + * @return string[] + */ protected function feedback_viewed_events() { return array('\mod_choice\event\course_module_viewed', '\mod_choice\event\answer_updated'); } @@ -51,10 +61,20 @@ protected function fill_choice_data(\cm_info $cm) { global $DB; if (!isset($this->choicedata[$cm->instance])) { - $this->choicedata[$cm->instance] = $DB->get_record('choice', array('id' => $cm->instance), 'id, showresults, timeclose', MUST_EXIST); + $this->choicedata[$cm->instance] = $DB->get_record('choice', array('id' => $cm->instance), + 'id, showresults, timeclose', MUST_EXIST); } } + /** + * feedback_viewed + * + * @param \cm_info $cm + * @param int $contextid + * @param int $userid + * @param int $after + * @return bool + */ protected function feedback_viewed(\cm_info $cm, $contextid, $userid, $after = null) { // If results are shown after they answer a write action counts as feedback viewed. diff --git a/mod/choice/classes/analytics/indicator/cognitive_depth.php b/mod/choice/classes/analytics/indicator/cognitive_depth.php index 75adb841a7a8f..050f71ef2643b 100644 --- a/mod/choice/classes/analytics/indicator/cognitive_depth.php +++ b/mod/choice/classes/analytics/indicator/cognitive_depth.php @@ -35,14 +35,30 @@ */ class cognitive_depth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthchoice', 'mod_choice'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_COGNITIVE; } + /** + * get_cognitive_depth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_cognitive_depth_level(\cm_info $cm) { $this->fill_choice_data($cm); diff --git a/mod/choice/classes/analytics/indicator/social_breadth.php b/mod/choice/classes/analytics/indicator/social_breadth.php index 518c52ef51f55..ff48e80331713 100644 --- a/mod/choice/classes/analytics/indicator/social_breadth.php +++ b/mod/choice/classes/analytics/indicator/social_breadth.php @@ -35,14 +35,30 @@ */ class social_breadth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:socialbreadthchoice', 'mod_choice'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_SOCIAL; } + /** + * get_social_breadth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_social_breadth_level(\cm_info $cm) { $this->fill_choice_data($cm); return 2; diff --git a/mod/data/classes/analytics/indicator/cognitive_depth.php b/mod/data/classes/analytics/indicator/cognitive_depth.php index 29fd0202d39da..3380ce0c13f74 100644 --- a/mod/data/classes/analytics/indicator/cognitive_depth.php +++ b/mod/data/classes/analytics/indicator/cognitive_depth.php @@ -35,14 +35,30 @@ */ class cognitive_depth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthdata', 'mod_data'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_COGNITIVE; } + /** + * get_cognitive_depth_level + * + * @param \cm_info $cm + * @return int + */ public function get_cognitive_depth_level(\cm_info $cm) { return 2; } diff --git a/mod/data/classes/analytics/indicator/social_breadth.php b/mod/data/classes/analytics/indicator/social_breadth.php index f52305a12e9bb..3156cfea9547a 100644 --- a/mod/data/classes/analytics/indicator/social_breadth.php +++ b/mod/data/classes/analytics/indicator/social_breadth.php @@ -35,14 +35,30 @@ */ class social_breadth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:socialbreadthdata', 'mod_data'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_SOCIAL; } + /** + * get_social_breadth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_social_breadth_level(\cm_info $cm) { return 1; } diff --git a/mod/feedback/classes/analytics/indicator/activity_base.php b/mod/feedback/classes/analytics/indicator/activity_base.php index b52ef325e70c0..72f31c1d0e1b8 100644 --- a/mod/feedback/classes/analytics/indicator/activity_base.php +++ b/mod/feedback/classes/analytics/indicator/activity_base.php @@ -40,6 +40,12 @@ abstract class activity_base extends \core_analytics\local\indicator\community_o */ protected $publishstats = array(); + /** + * fill_publishstats + * + * @param \cm_info $cm + * @return void + */ protected function fill_publishstats(\cm_info $cm) { global $DB; @@ -55,7 +61,7 @@ protected function fill_publishstats(\cm_info $cm) { * @param int $contextid * @param int $userid * @param int $after - * @return int + * @return bool */ protected function feedback_viewed(\cm_info $cm, $contextid, $userid, $after = null) { // If stats are published any write action counts as viewed feedback. diff --git a/mod/feedback/classes/analytics/indicator/cognitive_depth.php b/mod/feedback/classes/analytics/indicator/cognitive_depth.php index 36fb88ea5f45b..7e39604f09f9d 100644 --- a/mod/feedback/classes/analytics/indicator/cognitive_depth.php +++ b/mod/feedback/classes/analytics/indicator/cognitive_depth.php @@ -35,14 +35,30 @@ */ class cognitive_depth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthfeedback', 'mod_feedback'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_COGNITIVE; } + /** + * get_cognitive_depth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_cognitive_depth_level(\cm_info $cm) { $this->fill_publishstats($cm); diff --git a/mod/feedback/classes/analytics/indicator/social_breadth.php b/mod/feedback/classes/analytics/indicator/social_breadth.php index 358f87abae44f..67e8f60b4eade 100644 --- a/mod/feedback/classes/analytics/indicator/social_breadth.php +++ b/mod/feedback/classes/analytics/indicator/social_breadth.php @@ -35,14 +35,30 @@ */ class social_breadth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:socialbreadthfeedback', 'mod_feedback'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_SOCIAL; } + /** + * get_social_breadth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_social_breadth_level(\cm_info $cm) { $this->fill_publishstats($cm); diff --git a/mod/folder/classes/analytics/indicator/cognitive_depth.php b/mod/folder/classes/analytics/indicator/cognitive_depth.php index 5a3c8b113bd66..ba814188399de 100644 --- a/mod/folder/classes/analytics/indicator/cognitive_depth.php +++ b/mod/folder/classes/analytics/indicator/cognitive_depth.php @@ -35,14 +35,30 @@ */ class cognitive_depth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthfolder', 'mod_folder'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_COGNITIVE; } + /** + * get_cognitive_depth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_cognitive_depth_level(\cm_info $cm) { return 1; } diff --git a/mod/folder/classes/analytics/indicator/social_breadth.php b/mod/folder/classes/analytics/indicator/social_breadth.php index 5de069edd8236..9d5d821af39e5 100644 --- a/mod/folder/classes/analytics/indicator/social_breadth.php +++ b/mod/folder/classes/analytics/indicator/social_breadth.php @@ -35,14 +35,30 @@ */ class social_breadth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:socialbreadthfolder', 'mod_folder'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_SOCIAL; } + /** + * get_social_breadth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_social_breadth_level(\cm_info $cm) { return 1; } diff --git a/mod/forum/classes/analytics/indicator/activity_base.php b/mod/forum/classes/analytics/indicator/activity_base.php index 5d42dbce0002f..4772261c89165 100644 --- a/mod/forum/classes/analytics/indicator/activity_base.php +++ b/mod/forum/classes/analytics/indicator/activity_base.php @@ -35,11 +35,27 @@ */ abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity { + /** + * feedback_viewed_events + * + * @return string[] + */ protected function feedback_viewed_events() { - // We could add any forum event, but it will make feedback_post_action slower - return array('\mod_forum\event\assessable_uploaded', '\mod_forum\event\course_module_viewed', '\mod_forum\event\discussion_viewed'); + // We could add any forum event, but it will make feedback_post_action slower. + return array('\mod_forum\event\assessable_uploaded', '\mod_forum\event\course_module_viewed', + '\mod_forum\event\discussion_viewed'); } + /** + * feedback_post_action + * + * @param \cm_info $cm + * @param int $contextid + * @param int $userid + * @param string[] $eventnames + * @param int $after + * @return bool + */ protected function feedback_post_action(\cm_info $cm, $contextid, $userid, $eventnames, $after = false) { if (empty($this->activitylogs[$contextid][$userid])) { diff --git a/mod/forum/classes/analytics/indicator/cognitive_depth.php b/mod/forum/classes/analytics/indicator/cognitive_depth.php index 573aeb506b957..026f4a783aa8a 100644 --- a/mod/forum/classes/analytics/indicator/cognitive_depth.php +++ b/mod/forum/classes/analytics/indicator/cognitive_depth.php @@ -35,22 +35,48 @@ */ class cognitive_depth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthforum', 'mod_forum'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_COGNITIVE; } + /** + * get_cognitive_depth_level + * + * @param \cm_info $cm + * @return int + */ public function get_cognitive_depth_level(\cm_info $cm) { return 4; } + /** + * feedback_check_grades + * + * @return bool + */ protected function feedback_check_grades() { return false; } + /** + * feedback_replied_events + * + * @return string[] + */ protected function feedback_replied_events() { return array('\mod_forum\event\assessable_uploaded'); } diff --git a/mod/forum/classes/analytics/indicator/social_breadth.php b/mod/forum/classes/analytics/indicator/social_breadth.php index 382b498b5100f..7cac14afc5154 100644 --- a/mod/forum/classes/analytics/indicator/social_breadth.php +++ b/mod/forum/classes/analytics/indicator/social_breadth.php @@ -35,14 +35,30 @@ */ class social_breadth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:socialbreadthforum', 'mod_forum'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_SOCIAL; } + /** + * get_social_breadth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_social_breadth_level(\cm_info $cm) { return 2; } diff --git a/mod/glossary/classes/analytics/indicator/cognitive_depth.php b/mod/glossary/classes/analytics/indicator/cognitive_depth.php index 618326a81ff91..8aa13a278620a 100644 --- a/mod/glossary/classes/analytics/indicator/cognitive_depth.php +++ b/mod/glossary/classes/analytics/indicator/cognitive_depth.php @@ -35,14 +35,30 @@ */ class cognitive_depth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthglossary', 'mod_glossary'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_COGNITIVE; } + /** + * get_cognitive_depth_level + * + * @param \cm_info $cm + * @return int + */ public function get_cognitive_depth_level(\cm_info $cm) { return 2; } diff --git a/mod/glossary/classes/analytics/indicator/social_breadth.php b/mod/glossary/classes/analytics/indicator/social_breadth.php index dbd1998e56ef0..95d0571333930 100644 --- a/mod/glossary/classes/analytics/indicator/social_breadth.php +++ b/mod/glossary/classes/analytics/indicator/social_breadth.php @@ -35,14 +35,30 @@ */ class social_breadth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:socialbreadthglossary', 'mod_glossary'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_SOCIAL; } + /** + * get_social_breadth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_social_breadth_level(\cm_info $cm) { return 1; } diff --git a/mod/imscp/classes/analytics/indicator/cognitive_depth.php b/mod/imscp/classes/analytics/indicator/cognitive_depth.php index 5485b4385aa3d..bf720343fa797 100644 --- a/mod/imscp/classes/analytics/indicator/cognitive_depth.php +++ b/mod/imscp/classes/analytics/indicator/cognitive_depth.php @@ -35,14 +35,30 @@ */ class cognitive_depth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthimscp', 'mod_imscp'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_COGNITIVE; } + /** + * get_cognitive_depth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_cognitive_depth_level(\cm_info $cm) { return 1; } diff --git a/mod/imscp/classes/analytics/indicator/social_breadth.php b/mod/imscp/classes/analytics/indicator/social_breadth.php index 00468f34f714c..7b6a788e585e5 100644 --- a/mod/imscp/classes/analytics/indicator/social_breadth.php +++ b/mod/imscp/classes/analytics/indicator/social_breadth.php @@ -35,14 +35,30 @@ */ class social_breadth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:socialbreadthimscp', 'mod_imscp'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_SOCIAL; } + /** + * get_social_breadth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_social_breadth_level(\cm_info $cm) { return 1; } diff --git a/mod/label/classes/analytics/indicator/cognitive_depth.php b/mod/label/classes/analytics/indicator/cognitive_depth.php index df2d46694db38..0c1d365faa1dc 100644 --- a/mod/label/classes/analytics/indicator/cognitive_depth.php +++ b/mod/label/classes/analytics/indicator/cognitive_depth.php @@ -35,14 +35,30 @@ */ class cognitive_depth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthlabel', 'mod_label'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_COGNITIVE; } + /** + * get_cognitive_depth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_cognitive_depth_level(\cm_info $cm) { return 1; } diff --git a/mod/label/classes/analytics/indicator/social_breadth.php b/mod/label/classes/analytics/indicator/social_breadth.php index 8950e87119b49..d2a0955e77718 100644 --- a/mod/label/classes/analytics/indicator/social_breadth.php +++ b/mod/label/classes/analytics/indicator/social_breadth.php @@ -35,14 +35,30 @@ */ class social_breadth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:socialbreadthlabel', 'mod_label'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_SOCIAL; } + /** + * get_social_breadth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_social_breadth_level(\cm_info $cm) { return 1; } diff --git a/mod/lesson/classes/analytics/indicator/activity_base.php b/mod/lesson/classes/analytics/indicator/activity_base.php index db0fb653574eb..f87612672c7a3 100644 --- a/mod/lesson/classes/analytics/indicator/activity_base.php +++ b/mod/lesson/classes/analytics/indicator/activity_base.php @@ -35,10 +35,20 @@ */ abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity { + /** + * feedback_viewed_events + * + * @return string + */ protected function feedback_viewed_events() { return array('\mod_lesson\event\lesson_ended'); } + /** + * feedback_check_grades + * + * @return bool + */ protected function feedback_check_grades() { // We don't need to check grades as we get the feedback while completing the activity. return false; diff --git a/mod/lesson/classes/analytics/indicator/cognitive_depth.php b/mod/lesson/classes/analytics/indicator/cognitive_depth.php index c2e709182a6f9..17f7122fdd0cb 100644 --- a/mod/lesson/classes/analytics/indicator/cognitive_depth.php +++ b/mod/lesson/classes/analytics/indicator/cognitive_depth.php @@ -35,18 +35,43 @@ */ class cognitive_depth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthlesson', 'mod_lesson'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_COGNITIVE; } + /** + * get_cognitive_depth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_cognitive_depth_level(\cm_info $cm) { return 5; } + /** + * feedback_submitted + * + * @param \cm_info $cm + * @param int $contextid + * @param int $userid + * @param int $after + * @return bool + */ protected function feedback_submitted(\cm_info $cm, $contextid, $userid, $after = false) { if (empty($this->activitylogs[$contextid][$userid]) || empty($this->activitylogs[$contextid][$userid]['\mod_lesson\event\lesson_ended'])) { @@ -57,6 +82,15 @@ protected function feedback_submitted(\cm_info $cm, $contextid, $userid, $after return (2 >= count($this->activitylogs[$contextid][$userid]['\mod_lesson\event\lesson_ended'])); } + /** + * feedback_replied + * + * @param \cm_info $cm + * @param int $contextid + * @param int $userid + * @param int $after + * @return bool + */ protected function feedback_replied(\cm_info $cm, $contextid, $userid, $after = false) { // No level 4. return false; diff --git a/mod/lesson/classes/analytics/indicator/social_breadth.php b/mod/lesson/classes/analytics/indicator/social_breadth.php index bffa1500facec..0f1294c44a150 100644 --- a/mod/lesson/classes/analytics/indicator/social_breadth.php +++ b/mod/lesson/classes/analytics/indicator/social_breadth.php @@ -35,14 +35,30 @@ */ class social_breadth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:socialbreadthlesson', 'mod_lesson'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_SOCIAL; } + /** + * get_social_breadth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_social_breadth_level(\cm_info $cm) { return 2; } diff --git a/mod/lti/classes/analytics/indicator/activity_base.php b/mod/lti/classes/analytics/indicator/activity_base.php index cec7537fe0783..06d379a4d0195 100644 --- a/mod/lti/classes/analytics/indicator/activity_base.php +++ b/mod/lti/classes/analytics/indicator/activity_base.php @@ -35,6 +35,11 @@ */ abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity { + /** + * feedback_viewed_events + * + * @return string[] + */ protected function feedback_viewed_events() { // Any view after the data graded counts as feedback viewed. return array('\mod_lti\event\course_module_viewed'); diff --git a/mod/lti/classes/analytics/indicator/cognitive_depth.php b/mod/lti/classes/analytics/indicator/cognitive_depth.php index e37c536fb6305..247d0d8e6dc61 100644 --- a/mod/lti/classes/analytics/indicator/cognitive_depth.php +++ b/mod/lti/classes/analytics/indicator/cognitive_depth.php @@ -35,14 +35,30 @@ */ class cognitive_depth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthlti', 'mod_lti'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_COGNITIVE; } + /** + * get_cognitive_depth_level + * + * @param \cm_info $cm + * @return int + */ public function get_cognitive_depth_level(\cm_info $cm) { return 3; } diff --git a/mod/lti/classes/analytics/indicator/social_breadth.php b/mod/lti/classes/analytics/indicator/social_breadth.php index 1bafb2f864a2f..f7b80d93efde9 100644 --- a/mod/lti/classes/analytics/indicator/social_breadth.php +++ b/mod/lti/classes/analytics/indicator/social_breadth.php @@ -35,14 +35,30 @@ */ class social_breadth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:socialbreadthlti', 'mod_lti'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_SOCIAL; } + /** + * get_social_breadth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_social_breadth_level(\cm_info $cm) { return 2; } diff --git a/mod/page/classes/analytics/indicator/cognitive_depth.php b/mod/page/classes/analytics/indicator/cognitive_depth.php index 4fa02221be327..c5c0ba62bc7b4 100644 --- a/mod/page/classes/analytics/indicator/cognitive_depth.php +++ b/mod/page/classes/analytics/indicator/cognitive_depth.php @@ -35,14 +35,30 @@ */ class cognitive_depth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthpage', 'mod_page'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_COGNITIVE; } + /** + * get_cognitive_depth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_cognitive_depth_level(\cm_info $cm) { return 1; } diff --git a/mod/page/classes/analytics/indicator/social_breadth.php b/mod/page/classes/analytics/indicator/social_breadth.php index fefc87becd08d..3fccfe6adeb3d 100644 --- a/mod/page/classes/analytics/indicator/social_breadth.php +++ b/mod/page/classes/analytics/indicator/social_breadth.php @@ -35,14 +35,30 @@ */ class social_breadth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:socialbreadthpage', 'mod_page'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_SOCIAL; } + /** + * get_social_breadth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_social_breadth_level(\cm_info $cm) { return 1; } diff --git a/mod/quiz/classes/analytics/indicator/activity_base.php b/mod/quiz/classes/analytics/indicator/activity_base.php index 60f18493a86c7..4f3804cbeba4c 100644 --- a/mod/quiz/classes/analytics/indicator/activity_base.php +++ b/mod/quiz/classes/analytics/indicator/activity_base.php @@ -35,11 +35,21 @@ */ abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity { + /** + * feedback_check_grades + * + * @return bool + */ protected function feedback_check_grades() { // We need the grade to be released to the student to consider that feedback has been provided. return true; } + /** + * feedback_viewed_events + * + * @return string[] + */ protected function feedback_viewed_events() { return array('\mod_quiz\event\course_module_viewed'); } diff --git a/mod/quiz/classes/analytics/indicator/cognitive_depth.php b/mod/quiz/classes/analytics/indicator/cognitive_depth.php index 6c31c779727b1..e47ffdb26627b 100644 --- a/mod/quiz/classes/analytics/indicator/cognitive_depth.php +++ b/mod/quiz/classes/analytics/indicator/cognitive_depth.php @@ -35,22 +35,52 @@ */ class cognitive_depth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthquiz', 'mod_quiz'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_COGNITIVE; } + /** + * get_cognitive_depth_level + * + * @param \cm_info $cm + * @return int + */ public function get_cognitive_depth_level(\cm_info $cm) { return 5; } + /** + * feedback_submitted_events + * + * @return string[] + */ protected function feedback_submitted_events() { return array('\mod_quiz\event\attempt_submitted'); } + /** + * feedback_replied + * + * @param \cm_info $cm + * @param int $contextid + * @param int $userid + * @param int $after + * @return bool + */ protected function feedback_replied(\cm_info $cm, $contextid, $userid, $after = false) { // No level 4. return false; diff --git a/mod/quiz/classes/analytics/indicator/social_breadth.php b/mod/quiz/classes/analytics/indicator/social_breadth.php index dd323fb33564f..6259a4e832ed3 100644 --- a/mod/quiz/classes/analytics/indicator/social_breadth.php +++ b/mod/quiz/classes/analytics/indicator/social_breadth.php @@ -35,14 +35,30 @@ */ class social_breadth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthquiz', 'mod_quiz'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_SOCIAL; } + /** + * get_social_breadth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_social_breadth_level(\cm_info $cm) { return 2; } diff --git a/mod/resource/classes/analytics/indicator/activity_base.php b/mod/resource/classes/analytics/indicator/activity_base.php index 074cf866717b7..8e8627a0a37a2 100644 --- a/mod/resource/classes/analytics/indicator/activity_base.php +++ b/mod/resource/classes/analytics/indicator/activity_base.php @@ -34,5 +34,4 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity { - } diff --git a/mod/resource/classes/analytics/indicator/cognitive_depth.php b/mod/resource/classes/analytics/indicator/cognitive_depth.php index 3358f681f2df9..25bc096c54a27 100644 --- a/mod/resource/classes/analytics/indicator/cognitive_depth.php +++ b/mod/resource/classes/analytics/indicator/cognitive_depth.php @@ -35,14 +35,30 @@ */ class cognitive_depth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthresource', 'mod_resource'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_COGNITIVE; } + /** + * get_cognitive_depth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_cognitive_depth_level(\cm_info $cm) { return 1; } diff --git a/mod/resource/classes/analytics/indicator/social_breadth.php b/mod/resource/classes/analytics/indicator/social_breadth.php index 93149e6f9a645..9637e46681ccc 100644 --- a/mod/resource/classes/analytics/indicator/social_breadth.php +++ b/mod/resource/classes/analytics/indicator/social_breadth.php @@ -35,14 +35,30 @@ */ class social_breadth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthresource', 'mod_resource'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_SOCIAL; } + /** + * get_social_breadth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_social_breadth_level(\cm_info $cm) { return 1; } diff --git a/mod/scorm/classes/analytics/indicator/activity_base.php b/mod/scorm/classes/analytics/indicator/activity_base.php index 5f146a0d8e796..75a6ace56c218 100644 --- a/mod/scorm/classes/analytics/indicator/activity_base.php +++ b/mod/scorm/classes/analytics/indicator/activity_base.php @@ -35,6 +35,11 @@ */ abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity { + /** + * feedback_viewed_events + * + * @return string[] + */ protected function feedback_viewed_events() { // Any view after the data graded counts as feedback viewed. return array('\mod_scorm\event\course_module_viewed'); diff --git a/mod/scorm/classes/analytics/indicator/cognitive_depth.php b/mod/scorm/classes/analytics/indicator/cognitive_depth.php index f46947ff3f43d..82d43b5bdab8c 100644 --- a/mod/scorm/classes/analytics/indicator/cognitive_depth.php +++ b/mod/scorm/classes/analytics/indicator/cognitive_depth.php @@ -35,14 +35,30 @@ */ class cognitive_depth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthscorm', 'mod_scorm'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_COGNITIVE; } + /** + * get_cognitive_depth_level + * + * @param \cm_info $cm + * @return int + */ public function get_cognitive_depth_level(\cm_info $cm) { return 3; } diff --git a/mod/scorm/classes/analytics/indicator/social_breadth.php b/mod/scorm/classes/analytics/indicator/social_breadth.php index 34247f55ae066..d431652a22805 100644 --- a/mod/scorm/classes/analytics/indicator/social_breadth.php +++ b/mod/scorm/classes/analytics/indicator/social_breadth.php @@ -35,14 +35,30 @@ */ class social_breadth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthscorm', 'mod_scorm'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_SOCIAL; } + /** + * get_social_breadth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_social_breadth_level(\cm_info $cm) { return 2; } diff --git a/mod/survey/classes/analytics/indicator/activity_base.php b/mod/survey/classes/analytics/indicator/activity_base.php index 0bbe5870bef7d..d6f39d484b293 100644 --- a/mod/survey/classes/analytics/indicator/activity_base.php +++ b/mod/survey/classes/analytics/indicator/activity_base.php @@ -34,5 +34,4 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity { - } diff --git a/mod/survey/classes/analytics/indicator/cognitive_depth.php b/mod/survey/classes/analytics/indicator/cognitive_depth.php index bb648458218d5..9e8e5e122c088 100644 --- a/mod/survey/classes/analytics/indicator/cognitive_depth.php +++ b/mod/survey/classes/analytics/indicator/cognitive_depth.php @@ -35,14 +35,30 @@ */ class cognitive_depth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthsurvey', 'mod_survey'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_COGNITIVE; } + /** + * get_cognitive_depth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_cognitive_depth_level(\cm_info $cm) { return 2; } diff --git a/mod/survey/classes/analytics/indicator/social_breadth.php b/mod/survey/classes/analytics/indicator/social_breadth.php index 4becdd381638f..483458b1395fc 100644 --- a/mod/survey/classes/analytics/indicator/social_breadth.php +++ b/mod/survey/classes/analytics/indicator/social_breadth.php @@ -35,14 +35,30 @@ */ class social_breadth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:socialbreadthsurvey', 'mod_survey'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_SOCIAL; } + /** + * get_social_breadth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_social_breadth_level(\cm_info $cm) { return 1; } diff --git a/mod/url/classes/analytics/indicator/activity_base.php b/mod/url/classes/analytics/indicator/activity_base.php index e0b3b93423387..82bc6b9c199f2 100644 --- a/mod/url/classes/analytics/indicator/activity_base.php +++ b/mod/url/classes/analytics/indicator/activity_base.php @@ -34,5 +34,4 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity { - } diff --git a/mod/url/classes/analytics/indicator/cognitive_depth.php b/mod/url/classes/analytics/indicator/cognitive_depth.php index ee11328c1103b..9c68162475f1b 100644 --- a/mod/url/classes/analytics/indicator/cognitive_depth.php +++ b/mod/url/classes/analytics/indicator/cognitive_depth.php @@ -35,14 +35,30 @@ */ class cognitive_depth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthurl', 'mod_url'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_COGNITIVE; } + /** + * get_cognitive_depth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_cognitive_depth_level(\cm_info $cm) { return 1; } diff --git a/mod/url/classes/analytics/indicator/social_breadth.php b/mod/url/classes/analytics/indicator/social_breadth.php index 109bd06c40f63..48c6f0b88490d 100644 --- a/mod/url/classes/analytics/indicator/social_breadth.php +++ b/mod/url/classes/analytics/indicator/social_breadth.php @@ -35,14 +35,30 @@ */ class social_breadth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:socialbreadthurl', 'mod_url'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_SOCIAL; } + /** + * get_social_breadth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_social_breadth_level(\cm_info $cm) { return 1; } diff --git a/mod/wiki/classes/analytics/indicator/activity_base.php b/mod/wiki/classes/analytics/indicator/activity_base.php index 2a740a72fbf78..cd262464efb8d 100644 --- a/mod/wiki/classes/analytics/indicator/activity_base.php +++ b/mod/wiki/classes/analytics/indicator/activity_base.php @@ -34,5 +34,4 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity { - } diff --git a/mod/wiki/classes/analytics/indicator/cognitive_depth.php b/mod/wiki/classes/analytics/indicator/cognitive_depth.php index 3fae42032e7bd..fd4a47a7b492f 100644 --- a/mod/wiki/classes/analytics/indicator/cognitive_depth.php +++ b/mod/wiki/classes/analytics/indicator/cognitive_depth.php @@ -35,14 +35,30 @@ */ class cognitive_depth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthwiki', 'mod_wiki'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_COGNITIVE; } + /** + * get_cognitive_depth_level + * + * @param \cm_info $cm + * @return int + */ public function get_cognitive_depth_level(\cm_info $cm) { return 2; } diff --git a/mod/wiki/classes/analytics/indicator/social_breadth.php b/mod/wiki/classes/analytics/indicator/social_breadth.php index 44670ab03e995..a02018d97ed54 100644 --- a/mod/wiki/classes/analytics/indicator/social_breadth.php +++ b/mod/wiki/classes/analytics/indicator/social_breadth.php @@ -35,14 +35,30 @@ */ class social_breadth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:socialbreadthwiki', 'mod_wiki'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_SOCIAL; } + /** + * get_social_breadth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_social_breadth_level(\cm_info $cm) { return 1; } diff --git a/mod/workshop/classes/analytics/indicator/activity_base.php b/mod/workshop/classes/analytics/indicator/activity_base.php index 1816fa514b5ac..1774d6357ed62 100644 --- a/mod/workshop/classes/analytics/indicator/activity_base.php +++ b/mod/workshop/classes/analytics/indicator/activity_base.php @@ -35,10 +35,20 @@ */ abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity { + /** + * feedback_check_grades + * + * @return bool + */ protected function feedback_check_grades() { return true; } + /** + * feedback_viewed_events + * + * @return string[] + */ protected function feedback_viewed_events() { return array('\mod_workshop\event\course_module_viewed', '\mod_workshop\event\submission_viewed'); } diff --git a/mod/workshop/classes/analytics/indicator/cognitive_depth.php b/mod/workshop/classes/analytics/indicator/cognitive_depth.php index d84150d5de314..e55d0a5379a38 100644 --- a/mod/workshop/classes/analytics/indicator/cognitive_depth.php +++ b/mod/workshop/classes/analytics/indicator/cognitive_depth.php @@ -35,24 +35,50 @@ */ class cognitive_depth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:cognitivedepthworkshop', 'mod_workshop'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_COGNITIVE; } + /** + * get_cognitive_depth_level + * + * @param \cm_info $cm + * @return int + */ public function get_cognitive_depth_level(\cm_info $cm) { return 5; } + /** + * feedback_replied_events + * + * @return string[] + */ protected function feedback_replied_events() { return array('\mod_workshop\event\submission_assessed', '\mod_workshop\event\submission_reassessed'); } + /** + * feedback_submitted_events + * + * @return string[] + */ protected function feedback_submitted_events() { - // Can't use assessable_uploaded instead of submission_* as mod_workshop only triggers it during submission_updated + // Can't use assessable_uploaded instead of submission_* as mod_workshop only triggers it during submission_updated. return array('\mod_workshop\event\submission_updated', '\mod_workshop\event\submission_created', '\mod_workshop\event\submission_reassessed'); } diff --git a/mod/workshop/classes/analytics/indicator/social_breadth.php b/mod/workshop/classes/analytics/indicator/social_breadth.php index 8c7de48464abd..dca70fd7aee18 100644 --- a/mod/workshop/classes/analytics/indicator/social_breadth.php +++ b/mod/workshop/classes/analytics/indicator/social_breadth.php @@ -35,14 +35,30 @@ */ class social_breadth extends activity_base { + /** + * get_name + * + * @return string + */ public static function get_name() { return get_string('indicator:socialbreadthworkshop', 'mod_workshop'); } + /** + * get_indicator_type + * + * @return string + */ protected function get_indicator_type() { return self::INDICATOR_SOCIAL; } + /** + * get_social_breadth_level + * + * @param \cm_info $cm + * @return int + */ protected function get_social_breadth_level(\cm_info $cm) { return 2; } diff --git a/report/insights/classes/output/insight.php b/report/insights/classes/output/insight.php index 20ad9203fb0f8..51b7fe652b883 100644 --- a/report/insights/classes/output/insight.php +++ b/report/insights/classes/output/insight.php @@ -50,6 +50,14 @@ class insight implements \renderable, \templatable { */ protected $includedetailsaction = false; + /** + * Constructor + * + * @param \core_analytics\prediction $prediction + * @param \core_analytics\model $model + * @param bool $includedetailsaction + * @return void + */ public function __construct(\core_analytics\prediction $prediction, \core_analytics\model $model, $includedetailsaction = false) { $this->prediction = $prediction; $this->model = $model; diff --git a/report/insights/classes/output/insights_list.php b/report/insights/classes/output/insights_list.php index 97e311c196082..8968f0d8e594d 100644 --- a/report/insights/classes/output/insights_list.php +++ b/report/insights/classes/output/insights_list.php @@ -46,10 +46,18 @@ class insights_list implements \renderable, \templatable { protected $context; /** - * @var \core_analytics\model + * @var \core_analytics\model[] */ protected $othermodels; + /** + * Constructor + * + * @param \core_analytics\model $model + * @param \context $context + * @param \core_analytics\model[] $othermodels + * @return void + */ public function __construct(\core_analytics\model $model, \context $context, $othermodels) { $this->model = $model; $this->context = $context; diff --git a/report/insights/lib.php b/report/insights/lib.php index c6e3b83ba62e3..aee031f1afe1e 100644 --- a/report/insights/lib.php +++ b/report/insights/lib.php @@ -30,6 +30,7 @@ * @param navigation_node $navigation The navigation node to extend * @param stdClass $course The course to object for the tool * @param context $context The context of the course + * @return void */ function report_insights_extend_navigation_course($navigation, $course, $context) { global $DB; diff --git a/user/classes/analytics/indicator/user_profile_set.php b/user/classes/analytics/indicator/user_profile_set.php new file mode 100644 index 0000000000000..f9a6aa75d72ce --- /dev/null +++ b/user/classes/analytics/indicator/user_profile_set.php @@ -0,0 +1,99 @@ +. + +/** + * User profile set indicator. + * + * @package core_user + * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_user\analytics\indicator; + +defined('MOODLE_INTERNAL') || die(); + +/** + * User profile set indicator. + * + * @package core_user + * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class user_profile_set extends \core_analytics\local\indicator\linear { + + /** + * get_name + * + * @return string + */ + public static function get_name() { + return get_string('indicator:completeduserprofile'); + } + + /** + * 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) { + global $CFG; + + $user = $this->retrieve('user', $sampleid); + + // Nothing set results in -1. + $calculatedvalue = self::MIN_VALUE; + + if (!empty($CFG->sitepolicy) && !$user->policyagreed) { + return self::MIN_VALUE; + } + + if (!$user->confirmed) { + return self::MIN_VALUE; + } + + if ($user->description != '') { + $calculatedvalue += 1; + } + + if ($user->picture != '') { + $calculatedvalue += 1; + } + + // 0.2 for any of the following fields being set (some of them may even be compulsory or have a default). + $fields = array('institution', 'department', 'address', 'city', 'country', 'url'); + foreach ($fields as $fieldname) { + if ($user->{$fieldname} != '') { + $calculatedvalue += 0.2; + } + } + + return $this->limit_value($calculatedvalue); + } +} diff --git a/user/classes/analytics/indicator/user_track_forums.php b/user/classes/analytics/indicator/user_track_forums.php new file mode 100644 index 0000000000000..29b4e76dee648 --- /dev/null +++ b/user/classes/analytics/indicator/user_track_forums.php @@ -0,0 +1,69 @@ +. + +/** + * User tracks forums indicator. + * + * @package core_user + * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_user\analytics\indicator; + +defined('MOODLE_INTERNAL') || die(); + +/** + * User tracks forums indicator. + * + * @package core_user + * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class user_track_forums extends \core_analytics\local\indicator\binary { + + /** + * get_name + * + * @return string + */ + public static function get_name() { + return get_string('indicator:userforumstracking'); + } + + /** + * required_sample_data + * + * @return string[] + */ + public static function required_sample_data() { + return array('user'); + } + + /** + * calculate_sample + * + * @param int $sampleid + * @param string $samplesorigin + * @param int $starttime + * @param int $endtime + * @return float + */ + protected function calculate_sample($sampleid, $samplesorigin, $starttime = false, $endtime = false) { + $user = $this->retrieve('user', $sampleid); + return ($user->trackforums) ? self::get_max_value() : self::get_min_value(); + } +}