From 954d4e992b516a04f3249b9d03e3856b0918989f Mon Sep 17 00:00:00 2001 From: Riedler Date: Mon, 20 Jan 2025 22:27:36 +0100 Subject: [PATCH 1/9] feat: crude first impl of module model --- lbplanner/classes/helpers/modules_helper.php | 191 +++------- lbplanner/classes/model/module.php | 333 ++++++++++++++++++ .../modules/get_all_course_modules.php | 8 +- .../services/modules/get_all_modules.php | 15 +- lbplanner/services/modules/get_module.php | 15 +- 5 files changed, 397 insertions(+), 165 deletions(-) create mode 100644 lbplanner/classes/model/module.php diff --git a/lbplanner/classes/helpers/modules_helper.php b/lbplanner/classes/helpers/modules_helper.php index 33241482..284f3e10 100644 --- a/lbplanner/classes/helpers/modules_helper.php +++ b/lbplanner/classes/helpers/modules_helper.php @@ -26,10 +26,9 @@ namespace local_lbplanner\helpers; use core_customfield\category_controller; -use core_external\{external_single_structure, external_value}; -use moodle_url; +use DateTimeImmutable; use local_lbplanner\enums\{MODULE_STATUS, MODULE_GRADE, MODULE_TYPE}; -use local_modcustomfields\customfield\mod_handler; +use local_lbplanner\model\module; /** * Contains helper functions for working with modules. @@ -71,218 +70,116 @@ class modules_helper { */ const SUBMISSION_STATUS_SUBMITTED = 'submitted'; - /** - * The return structure of a module. - * - * @return external_single_structure The structure of a module. - */ - public static function structure(): external_single_structure { - return new external_single_structure( - [ - 'moduleid' => new external_value(PARAM_INT, 'Module ID'), - 'name' => new external_value(PARAM_TEXT, 'Shortened module name (max. 5 chars)'), - 'courseid' => new external_value(PARAM_INT, 'Course ID'), - 'status' => new external_value(PARAM_INT, 'Module status '.MODULE_STATUS::format()), - 'type' => new external_value(PARAM_INT, 'Module type '.MODULE_TYPE::format()), - 'url' => new external_value(PARAM_TEXT, 'URL to moodle page for module'), - 'grade' => new external_value(PARAM_INT, 'The grade of the module '.MODULE_GRADE::format()), - 'deadline' => new external_value(PARAM_INT, 'The deadline of the module set by the teacher'), - ] - ); - } - /** * Determins the enum value for a grade. + * TODO: this is bullshit. * * @param int $grade The grade of the module. * @param int $maxgrade The max. grade of the module. - * @param int $mingrade The min. grade of the module. * @param int $gradepass The grade to pass the module. * @return integer The enum value for the grade. */ - public static function determin_uinified_grade(int $grade, int $maxgrade, int $mingrade, int $gradepass): int { - if ($grade < $gradepass) { - return MODULE_GRADE::RIP; - } - - $maxgrade = $maxgrade - $mingrade; - - $p = $grade / $maxgrade; + public static function determine_uinified_grade(int $grade, int $maxgrade, int $gradepass): int { + $p = ($grade - $gradepass) / ($maxgrade - $gradepass); - if ($p >= 0.9) { + if ($p >= 0.75) { return MODULE_GRADE::EKV; - } else if ($p >= 0.8) { + } else if ($p >= 0.50) { return MODULE_GRADE::EK; - } else if ($p >= 0.7) { + } else if ($p >= 0.25) { return MODULE_GRADE::GKV; - } else if ($p >= 0.4) { + } else if ($p >= 0) { return MODULE_GRADE::GK; } else { return MODULE_GRADE::RIP; } } - /** - * Maps the given info to a module status. - * - * @param bool $submitted Whether the module is submitted. - * @param bool $done Whether the module is completed. - * @param bool $late Whether the module is late. - * @return integer The enum value for the module status. - */ - public static function map_status(bool $submitted, bool $done, bool $late): int { - if ($done) { - return MODULE_STATUS::DONE; - } else if ($submitted) { - return MODULE_STATUS::UPLOADED; - } else if ($late) { - return MODULE_STATUS::LATE; - } else { - return MODULE_STATUS::PENDING; - } - } - /** * Checks what type the module is. * - * @param int $moduleid The ID of the module. + * @param int $cmid The course module ID associated with the module. * @return int The enum value for the module type. * @throws \moodle_exception */ - public static function determine_type(int $moduleid): int { + public static function determine_type(int $cmid): int { $catid = config_helper::get_category_id(); if ($catid === -1) { throw new \moodle_exception('couldn\'t find custom fields category ID'); } $categorycontroller = category_controller::create($catid); - $instancedata = $categorycontroller->get_handler()->get_instance_data($moduleid); + $instancedata = $categorycontroller->get_handler()->get_instance_data($cmid); if (count($instancedata) === 0) { - throw new \moodle_exception("couldn't find any instance data for module ID {$moduleid} in category ID {$catid}"); + throw new \moodle_exception("couldn't find any instance data for module ID {$cmid} in category ID {$catid}"); + } else if (count($instancedata) > 1) { + throw new \moodle_exception("found multiple data for module ID {$cmid} in category ID {$catid}"); } - $type = intval($instancedata[1]->get('value')); // NOTE: why the hell is this on index one? + $type = intval($instancedata[1]->get_value()); // NOTE: why the hell is this on index one? MODULE_TYPE::name_from($type); // Basically asserting that this value exists as a module type. return $type; } - /** - * Returns the url of the module. - * - * @param int $moduleid The id of the module. - * @param int $courseid The id of the course. - * @return string The url of the module. - */ - public static function get_module_url(int $moduleid, int $courseid): string { + public static function get_module_status(module $module, int $userid, ?int $planid = null): int { global $DB; - $view = $DB->get_record( - self::COURSE_MODULES_TABLE, - ['course' => $courseid, 'instance' => $moduleid, 'module' => 1] - ); - - return strval(new moodle_url('/mod/assign/view.php?id='.$view->id)); - } - - /** - * Retrieves a module of the given id for the given user. - * - * @param int $moduleid The id of the module. - * @param int $userid The id of the user. - * @return array The module. - */ - public static function get_module(int $moduleid, int $userid): array { - global $DB; - date_default_timezone_set('UTC'); - - // Get module data. - $module = $DB->get_record(self::ASSIGN_TABLE, ['id' => $moduleid]); + if ($planid === null) { + $planid = plan_helper::get_plan_id($userid); + } - // Determine module type. - $type = self::determine_type($moduleid); + // Getting some necessary data. + $assignid = $module->get_assignid(); // Check if there are any submissions or feedbacks for this module. - $submitted = false; - - if ($DB->record_exists(self::SUBMISSIONS_TABLE, ['assignment' => $moduleid, 'userid' => $userid])) { + if ($DB->record_exists(self::SUBMISSIONS_TABLE, ['assignment' => $assignid, 'userid' => $userid])) { $submission = $DB->get_record( self::SUBMISSIONS_TABLE, - ['assignment' => $moduleid, 'userid' => $userid] + ['assignment' => $assignid, 'userid' => $userid] ); - $submitted = strval($submission->status) == self::SUBMISSION_STATUS_SUBMITTED; + if (strval($submission->status) === self::SUBMISSION_STATUS_SUBMITTED) { + return MODULE_STATUS::UPLOADED; + } } - $done = false; - $grade = null; - - if ($DB->record_exists(self::GRADES_TABLE, ['assignment' => $moduleid, 'userid' => $userid])) { - $moduleboundaries = $DB->get_record(self::GRADE_ITEMS_TABLE, ['iteminstance' => $moduleid]); + $grade = $module->get_grade($userid); - $mdlgrades = $DB->get_records( - self::GRADES_TABLE, - ['assignment' => $moduleid, 'userid' => $userid] - ); - - $mdlgrade = end($mdlgrades); - - if ($mdlgrade->grade > 0) { - $done = true; - - $grade = self::determin_uinified_grade( - $mdlgrade->grade, $moduleboundaries->grademax, - $moduleboundaries->grademin, - $moduleboundaries->gradepass - ); - - $done = $grade != MODULE_GRADE::RIP; - } + if ($grade !== null && $grade !== MODULE_GRADE::RIP) { + return MODULE_STATUS::DONE; } // Check if the module is late. - $late = false; - $planid = plan_helper::get_plan_id($userid); + $deadline = $module->get_deadline($planid); - if ($DB->record_exists(plan_helper::DEADLINES_TABLE, ['planid' => $planid, 'moduleid' => $moduleid])) { - $deadline = $DB->get_record(plan_helper::DEADLINES_TABLE, ['planid' => $planid, 'moduleid' => $moduleid]); - $late = intval(date("Ymd", $deadline->deadlineend)) < intval(date("Ymd")) && !$done; + if ($deadline !== null) { + $now = (new DateTimeImmutable())->getTimestamp(); + if ($deadline->deadlineend < $now) { + return MODULE_STATUS::LATE; + } } - $status = self::map_status($submitted, $done, $late); - - // Return the appropriate data. - - return [ - 'moduleid' => $moduleid, - 'name' => $module->name, - 'courseid' => $module->course, - 'status' => $status, - 'type' => $type, - 'url' => self::get_module_url($moduleid, $module->course), - 'grade' => $grade, - 'deadline' => $module->duedate > 0 ? $module->duedate : null, - ]; + return MODULE_STATUS::PENDING; } /** - * Reteruns all modules for the given course id. + * Returns all modules for the given course id. * * @param int $courseid The id of the course. - * @param int $userid The id of the user. * @param bool $ekenabled Whether EK modules should be included. - * @return array The modules. + * @return module[] The modules. */ - public static function get_all_course_modules(int $courseid, int $userid, bool $ekenabled): array { + public static function get_all_modules_by_course(int $courseid, bool $ekenabled): array { global $DB; - $mdlmodules = $DB->get_records(self::ASSIGN_TABLE, ['course' => $courseid]); + $assignments = $DB->get_records(self::ASSIGN_TABLE, ['course' => $courseid]); $modules = []; - foreach ($mdlmodules as $mdlmodule) { - if (!$ekenabled && self::determine_type($mdlmodule->id) == MODULE_TYPE::EK) { + foreach ($assignments as $assign) { + $module = module::from_assignobj($assign); + if (!$ekenabled && self::determine_type($module->get_assignid()) == MODULE_TYPE::EK) { continue; } - $module = self::get_module($mdlmodule->id, $userid); if ($module != null) { $modules[] = $module; } diff --git a/lbplanner/classes/model/module.php b/lbplanner/classes/model/module.php new file mode 100644 index 00000000..20ff98b8 --- /dev/null +++ b/lbplanner/classes/model/module.php @@ -0,0 +1,333 @@ +. + +/** + * Model for a module + * + * @package local_lbplanner + * @subpackage model + * @copyright 2025 necodeIT + * @license https://creativecommons.org/licenses/by-nc-sa/4.0/ CC-BY-NC-SA 4.0 International or later + */ + +namespace local_lbplanner\model; + +use core_external\{external_single_structure, external_value}; +use local_lbplanner\enums\{MODULE_GRADE, MODULE_STATUS, MODULE_TYPE}; +use local_lbplanner\helpers\modules_helper; +use local_lbplanner\helpers\plan_helper; + +/** + * Model class for model + */ +class module { + /** + * @var ?int $cmid the course module ID + */ + private ?int $cmid; + /** + * @var int $assignid the assignment ID. Old code might refer to this ID as the "module ID", but this terminology is deprecated. + */ + private ?int $assignid; + /** + * @var ?int $type the module type + */ + private ?int $type; + /** + * @var ?\stdClass $assignobj the DB object for the associated assignment + */ + private ?\stdClass $assignobj; + /** + * @var ?\stdClass $cmobj the DB object for the associated course module + */ + private ?\stdClass $cmobj; + /** + * @var int[int] $status map of the status of the module for a specific user's ID + * @see \local_lbplanner\enums\MODULE_STATUS + */ + private array $status; + /** + * @var \stdClass[int] $deadline cached deadlines per-planid + */ + private array $deadlines; + /** + * @var int[int] $grades cached grades per-userid + */ + private array $grades; + + /** + * Constructs a new course + * This is an internal function that prepares the object for initialization via a ::from_*() function. + */ + private function __construct() { + $this->cmid = null; + $this->assignid = null; + $this->type = null; + $this->assignobj = null; + $this->cmobj = null; + $this->status = []; + $this->deadlines = []; + $this->grades = []; + } + + /** + * Creates a module object from the assignment ID. + * @return module a module object with filled-in assignment ID + */ + public static function from_assignid(int $id): self { + $obj = new self(); + $obj->assignid = $id; + return $obj; + } + + /** + * Creates a module object from the assignment ID. + * @return module a module object with filled-in assignment ID + */ + public static function from_assignobj(\stdClass $assignobj): self { + $obj = new self(); + $obj->assignobj = $assignobj; + $obj->assignid = $assignobj->id; + return $obj; + } + + /** + * Fetches the necessary caches and returns the assignment ID + * @return int assign ID + */ + public function get_assignid(): int { + if ($this->assignid === null) { + if ($this->cmid !== null) { + $this->assignid = $this->get_cmobj()['instance']; + } else { + throw new \coding_exception('requested assignid, but no assignid'); + } + } + return $this->assignid; + } + + /** + * Fetches the necessary caches and returns the assignment ID + * @return int assign ID + */ + public function get_cmid(): int { + if ($this->cmid === null) { + $cm = $this->get_cmobj(); + $this->cmid = $cm->id; + } + return $this->cmid; + } + + /** + * Fetches the necessary caches and returns the assignment object + * @return \stdClass assignobj + */ + public function get_assignobj(): \stdClass { + global $DB; + if ($this->assignobj === null) { + $this->assignobj = $DB->get_record( + modules_helper::ASSIGN_TABLE, + ['id' => $this->get_assignid()] + ); + } + return $this->assignobj; + } + + /** + * Fetches the necessary caches and returns the course module object + * @return \stdClass cmobj + */ + public function get_cmobj(): \stdClass { + global $DB; + if ($this->cmobj === null) { + if ($this->cmid !== null) { + $this->cmobj = $DB->get_record( + modules_helper::COURSE_MODULES_TABLE, + ['id' => $this->cmid] + ); + } else { + assert($this->assignid !== null); + $this->cmobj = $DB->get_record( + modules_helper::COURSE_MODULES_TABLE, + [ + 'course' => $this->get_courseid(), + 'instance' => $this->assignid, + 'module' => 1 + ] + ); + } + } + return $this->cmobj; + } + + /** + * Fetches the necessary caches and returns the shortname. + * @return string shortname + */ + public function get_shortname(): string { + $name = $this->get_assignobj()->name; + return substr($name, 0, 5); + } + + /** + * Fetches the necessary caches and returns the teacher-defined due date for this module. + * If the module doesn't have any duedate, returns null. + * @return ?int duedate + */ + public function get_duedate(): ?int { + $assignobj = $this->get_assignobj(); + return $assignobj->duedate > 0 ? $assignobj->duedate : null; + } + + /** + * Fetches the necessary caches and returns the course ID. + * @return int course ID + */ + public function get_courseid(): int { + $via_cm = false; + // try to take path of least cache misses to get course ID + if ($this->assignobj === null) { + if ($this->cmobj !== null) { + $via_cm = true; + } else { + if ($this->cmid !== null) { + $via_cm = true; + } else if ($this->cmid === null) { + throw new \coding_exception('invalid module model: neither cmid nor assignid defined'); + } + } + } + if ($via_cm) { + return intval($this->get_cmobj()->course); + } else { + return intval($this->get_assignobj()->course); + } + } + + /** + * Fetches the necessary caches and returns the module status. + * @param int $userid ID of user to request status for + * @return int status + * @see \local_lbplanner\enums\MODULE_STATUS + */ + public function get_status(int $userid, ?int $planid = null): int { + if (!array_key_exists($userid, $this->status)) { + $this->status[$userid] = modules_helper::get_module_status($this, $userid, $planid); + } + return $this->status[$userid]; + } + + /** + * Fetches the necessary caches and returns the module type. + * @return int module type + */ + public function get_type(): int { + if ($this->type === null) { + $this->type = modules_helper::determine_type($this->get_cmid()); + } + return $this->type; + } + + /** + * Fetches the necessary caches and returns the deadline. + * @param int $planid ID of plan to request deadline for + * @return ?\stdClass deadline object + */ + public function get_deadline(int $planid): ?\stdClass { + global $DB; + if (!array_key_exists($planid, $this->deadlines)) { + $deadline = $DB->get_record(plan_helper::DEADLINES_TABLE, ['planid' => $planid, 'moduleid' => $this->get_assignid()]); + $this->deadlines[$planid] = $deadline !== false ? $deadline : null; + } + return $this->deadlines[$planid]; + } + + /** + * Fetches the necessary caches and returns the grade. + * @param int $userid ID of the user to request grade for + * @return ?int grade + * @see \local_lbplanner\enums\MODULE_GRADE + */ + public function get_grade(int $userid): ?int { + global $DB; + if (!array_key_exists($userid, $this->grades)) { + $grade = null; + $assignid = $this->get_assignid(); + + if ($DB->record_exists(modules_helper::GRADES_TABLE, ['assignment' => $assignid, 'userid' => $userid])) { + $moduleboundaries = $DB->get_record(modules_helper::GRADE_ITEMS_TABLE, ['iteminstance' => $assignid]); + + $mdlgrades = $DB->get_records( + modules_helper::GRADES_TABLE, + ['assignment' => $assignid, 'userid' => $userid] + ); + + $mdlgrade = end($mdlgrades); + + if ($mdlgrade->grade > 0) { + + $grade = modules_helper::determine_uinified_grade( + $mdlgrade->grade, + $moduleboundaries->grademax, + $moduleboundaries->gradepass + ); + } + } + + $this->grades[$userid] = $grade; + } + + return $this->grades[$userid]; + } + + /** + * Prepares full user-specific data for the API endpoint. + * + * @return array a shortened representation of this user and its data + */ + public function prepare_for_api_personal(int $userid, ?int $planid = null): array { + return [ + 'assignid' => $this->get_assignid(), + 'cmid' => $this->get_cmid(), + 'shortname' => $this->get_shortname(), + 'courseid' => $this->get_courseid(), + 'status' => $this->get_status($userid, $planid), + 'type' => $this->get_type(), + 'grade' => $this->get_grade($userid), + 'duedate' => $this->get_duedate(), + ]; + } + + /** + * Returns the full user-specific data structure for the API. + * + * @return external_single_structure The full user-specific data structure for the API. + */ + public static function api_structure_personal(): external_single_structure { + return new external_single_structure( + [ + 'assignid' => new external_value(PARAM_INT, 'Assignment ID (formerly "module ID")'), + 'cmid' => new external_value(PARAM_INT, 'Course module ID'), + 'shortname' => new external_value(PARAM_TEXT, 'Shortened module name (max. 5 chars)'), + 'courseid' => new external_value(PARAM_INT, 'Course ID'), + 'status' => new external_value(PARAM_INT, 'Module status '.MODULE_STATUS::format()), + 'type' => new external_value(PARAM_INT, 'Module type '.MODULE_TYPE::format()), + 'grade' => new external_value(PARAM_INT, 'The grade of the module '.MODULE_GRADE::format()), + 'duedate' => new external_value(PARAM_INT, 'The deadline of the module set by the teacher'), + ] + ); + } +} diff --git a/lbplanner/services/modules/get_all_course_modules.php b/lbplanner/services/modules/get_all_course_modules.php index 82c01b69..95196423 100644 --- a/lbplanner/services/modules/get_all_course_modules.php +++ b/lbplanner/services/modules/get_all_course_modules.php @@ -18,6 +18,8 @@ use core_external\{external_api, external_function_parameters, external_multiple_structure, external_value}; use local_lbplanner\helpers\modules_helper; +use local_lbplanner\helpers\plan_helper; +use local_lbplanner\model\module; /** * Get all the modules of the given course. @@ -59,7 +61,9 @@ public static function get_all_course_modules(int $courseid, bool $ekenabled): a ['courseid' => $courseid, 'ekenabled' => $ekenabled] ); - return modules_helper::get_all_course_modules($courseid, $USER->id, $ekenabled); + $planid = plan_helper::get_plan_id($USER->id); + $modules = modules_helper::get_all_modules_by_course($courseid, $ekenabled); + return array_map(fn(module $m) => $m->prepare_for_api_personal($USER->id, $planid), $modules); } /** @@ -68,7 +72,7 @@ public static function get_all_course_modules(int $courseid, bool $ekenabled): a */ public static function get_all_course_modules_returns(): external_multiple_structure { return new external_multiple_structure( - modules_helper::structure(), + module::api_structure_personal(), ); } } diff --git a/lbplanner/services/modules/get_all_modules.php b/lbplanner/services/modules/get_all_modules.php index f53a9f61..ae429eac 100644 --- a/lbplanner/services/modules/get_all_modules.php +++ b/lbplanner/services/modules/get_all_modules.php @@ -17,10 +17,8 @@ namespace local_lbplanner_services; use core_external\{external_api, external_function_parameters, external_multiple_structure}; -use local_lbplanner\helpers\course_helper; -use local_lbplanner\helpers\modules_helper; -use local_lbplanner\helpers\plan_helper; - +use local_lbplanner\helpers\{modules_helper, plan_helper, course_helper}; +use local_lbplanner\model\module; /** * Get all the modules of the current year. @@ -50,7 +48,8 @@ public static function get_all_modules(): array { $modules = []; $courses = course_helper::get_all_lbplanner_courses(); - $plan = plan_helper::get_plan(plan_helper::get_plan_id($USER->id)); + $planid = plan_helper::get_plan_id($USER->id); + $plan = plan_helper::get_plan($planid); $ekenabled = $plan["enableek"]; foreach ($courses as $course) { @@ -58,11 +57,11 @@ public static function get_all_modules(): array { continue; } $modules = array_merge( - modules_helper::get_all_course_modules($course->courseid, $USER->id, $ekenabled), + modules_helper::get_all_modules_by_course($course->courseid, $ekenabled), $modules ); } - return $modules; + return array_map(fn(module $m) => $m->prepare_for_api_personal($USER->id, $planid), $modules); } /** @@ -71,7 +70,7 @@ public static function get_all_modules(): array { */ public static function get_all_modules_returns(): external_multiple_structure { return new external_multiple_structure( - modules_helper::structure(), + module::api_structure_personal(), ); } } diff --git a/lbplanner/services/modules/get_module.php b/lbplanner/services/modules/get_module.php index a976e0e2..1383d6ff 100644 --- a/lbplanner/services/modules/get_module.php +++ b/lbplanner/services/modules/get_module.php @@ -17,8 +17,7 @@ namespace local_lbplanner_services; use core_external\{external_api, external_function_parameters, external_single_structure, external_value}; -use local_lbplanner\helpers\modules_helper; -use local_lbplanner\helpers\user_helper; +use local_lbplanner\model\module; /** * Get the data for a module. @@ -35,22 +34,22 @@ class modules_get_module extends external_api { */ public static function get_module_parameters(): external_function_parameters { return new external_function_parameters([ - 'moduleid' => new external_value(PARAM_INT, 'The id of the module', VALUE_REQUIRED, null, NULL_NOT_ALLOWED), + 'assignid' => new external_value(PARAM_INT, 'The assignment ID of the module', VALUE_REQUIRED, null, NULL_NOT_ALLOWED), ]); } /** * Returns the data for a module * - * @param int $moduleid The ID of the course + * @param int $assignid The assignment ID of the module * @return array the module */ - public static function get_module(int $moduleid): array { + public static function get_module(int $assignid): array { global $USER; - self::validate_parameters(self::get_module_parameters(), ['moduleid' => $moduleid, 'userid' => $USER->id]); + self::validate_parameters(self::get_module_parameters(), ['moduleid' => $assignid]); - return modules_helper::get_module($moduleid, $USER->id); + return module::from_assignid($assignid)->prepare_for_api_personal($USER->id); } /** @@ -58,6 +57,6 @@ public static function get_module(int $moduleid): array { * @return external_single_structure */ public static function get_module_returns(): external_single_structure { - return modules_helper::structure(); + return module::api_structure_personal(); } } From 8208219d6e25d0f903bfda3557456dfbf36e2cac Mon Sep 17 00:00:00 2001 From: Riedler Date: Mon, 20 Jan 2025 22:37:09 +0100 Subject: [PATCH 2/9] fix: wrote docblock for modules_helper::get_module_status --- lbplanner/classes/helpers/modules_helper.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lbplanner/classes/helpers/modules_helper.php b/lbplanner/classes/helpers/modules_helper.php index 284f3e10..700a779e 100644 --- a/lbplanner/classes/helpers/modules_helper.php +++ b/lbplanner/classes/helpers/modules_helper.php @@ -119,6 +119,14 @@ public static function determine_type(int $cmid): int { return $type; } + /** + * Returns status of a module + * @param module $module the module to query for + * @param int $userid the userid to see this in context of + * @param ?int $planid the planid of the user or null (param exists purely to deduplicate DB calls) + * @return int the module status + * @see \local_lbplanner\enums\MODULE_STATUS + */ public static function get_module_status(module $module, int $userid, ?int $planid = null): int { global $DB; From f0384ddafc5d3acb608c1a7b8936c6f8d2ee2254 Mon Sep 17 00:00:00 2001 From: Riedler Date: Mon, 20 Jan 2025 22:37:22 +0100 Subject: [PATCH 3/9] fix: made code checker happy --- lbplanner/classes/model/module.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lbplanner/classes/model/module.php b/lbplanner/classes/model/module.php index 20ff98b8..193bb403 100644 --- a/lbplanner/classes/model/module.php +++ b/lbplanner/classes/model/module.php @@ -197,20 +197,20 @@ public function get_duedate(): ?int { * @return int course ID */ public function get_courseid(): int { - $via_cm = false; + $viacm = false; // try to take path of least cache misses to get course ID if ($this->assignobj === null) { if ($this->cmobj !== null) { - $via_cm = true; + $viacm = true; } else { if ($this->cmid !== null) { - $via_cm = true; + $viacm = true; } else if ($this->cmid === null) { throw new \coding_exception('invalid module model: neither cmid nor assignid defined'); } } } - if ($via_cm) { + if ($viacm) { return intval($this->get_cmobj()->course); } else { return intval($this->get_assignobj()->course); From ddcdd2999e0a73ff65425d22f8a4fb80816a5a18 Mon Sep 17 00:00:00 2001 From: Riedler Date: Mon, 20 Jan 2025 22:40:54 +0100 Subject: [PATCH 4/9] fix: filled in some incomplete docblocks --- lbplanner/classes/model/module.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lbplanner/classes/model/module.php b/lbplanner/classes/model/module.php index 193bb403..02e98083 100644 --- a/lbplanner/classes/model/module.php +++ b/lbplanner/classes/model/module.php @@ -85,6 +85,7 @@ private function __construct() { /** * Creates a module object from the assignment ID. + * @param int $id the assignment ID * @return module a module object with filled-in assignment ID */ public static function from_assignid(int $id): self { @@ -95,6 +96,7 @@ public static function from_assignid(int $id): self { /** * Creates a module object from the assignment ID. + * @param \stdClass $assignobj the assignment object from moodle's DB * @return module a module object with filled-in assignment ID */ public static function from_assignobj(\stdClass $assignobj): self { @@ -220,6 +222,7 @@ public function get_courseid(): int { /** * Fetches the necessary caches and returns the module status. * @param int $userid ID of user to request status for + * @param ?int $planid the planid of the user or null (param exists purely to deduplicate DB calls) * @return int status * @see \local_lbplanner\enums\MODULE_STATUS */ @@ -295,7 +298,8 @@ public function get_grade(int $userid): ?int { /** * Prepares full user-specific data for the API endpoint. - * + * @param int $userid ID of the user to see this module in context of + * @param ?int $planid the planid of the user or null (param exists purely to deduplicate DB calls) * @return array a shortened representation of this user and its data */ public function prepare_for_api_personal(int $userid, ?int $planid = null): array { From 0fd066b97ae256aa0907804b381528c27f4a982c Mon Sep 17 00:00:00 2001 From: Riedler Date: Mon, 20 Jan 2025 22:45:30 +0100 Subject: [PATCH 5/9] fix: make code checker happy --- lbplanner/classes/model/module.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lbplanner/classes/model/module.php b/lbplanner/classes/model/module.php index 02e98083..56d2e480 100644 --- a/lbplanner/classes/model/module.php +++ b/lbplanner/classes/model/module.php @@ -167,7 +167,7 @@ public function get_cmobj(): \stdClass { [ 'course' => $this->get_courseid(), 'instance' => $this->assignid, - 'module' => 1 + 'module' => 1, ] ); } @@ -200,7 +200,7 @@ public function get_duedate(): ?int { */ public function get_courseid(): int { $viacm = false; - // try to take path of least cache misses to get course ID + // Try to take path of least cache misses to get course ID. if ($this->assignobj === null) { if ($this->cmobj !== null) { $viacm = true; From 150b3256766d66cccfe151469ec458ed499ee1bb Mon Sep 17 00:00:00 2001 From: Riedler Date: Mon, 20 Jan 2025 22:49:42 +0100 Subject: [PATCH 6/9] fix: incorrect drop-down selection ID readout at module type --- lbplanner/classes/helpers/modules_helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbplanner/classes/helpers/modules_helper.php b/lbplanner/classes/helpers/modules_helper.php index 700a779e..2df2d0f6 100644 --- a/lbplanner/classes/helpers/modules_helper.php +++ b/lbplanner/classes/helpers/modules_helper.php @@ -114,7 +114,7 @@ public static function determine_type(int $cmid): int { } else if (count($instancedata) > 1) { throw new \moodle_exception("found multiple data for module ID {$cmid} in category ID {$catid}"); } - $type = intval($instancedata[1]->get_value()); // NOTE: why the hell is this on index one? + $type = intval($instancedata[1]->get_value()) - 1; // NOTE: why the hell is this on index one? MODULE_TYPE::name_from($type); // Basically asserting that this value exists as a module type. return $type; } From 1da24643c5e6bc1b6a175961a075da532785944d Mon Sep 17 00:00:00 2001 From: Riedler Date: Tue, 21 Jan 2025 15:00:22 +0100 Subject: [PATCH 7/9] fix: return module fullname instead of shortname --- lbplanner/classes/model/module.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lbplanner/classes/model/module.php b/lbplanner/classes/model/module.php index 56d2e480..b0ba0acb 100644 --- a/lbplanner/classes/model/module.php +++ b/lbplanner/classes/model/module.php @@ -176,12 +176,11 @@ public function get_cmobj(): \stdClass { } /** - * Fetches the necessary caches and returns the shortname. - * @return string shortname + * Fetches the necessary caches and returns the name. + * @return string name */ - public function get_shortname(): string { - $name = $this->get_assignobj()->name; - return substr($name, 0, 5); + public function get_name(): string { + return $this->get_assignobj()->name; } /** @@ -306,7 +305,7 @@ public function prepare_for_api_personal(int $userid, ?int $planid = null): arra return [ 'assignid' => $this->get_assignid(), 'cmid' => $this->get_cmid(), - 'shortname' => $this->get_shortname(), + 'name' => $this->get_name(), 'courseid' => $this->get_courseid(), 'status' => $this->get_status($userid, $planid), 'type' => $this->get_type(), @@ -325,7 +324,7 @@ public static function api_structure_personal(): external_single_structure { [ 'assignid' => new external_value(PARAM_INT, 'Assignment ID (formerly "module ID")'), 'cmid' => new external_value(PARAM_INT, 'Course module ID'), - 'shortname' => new external_value(PARAM_TEXT, 'Shortened module name (max. 5 chars)'), + 'name' => new external_value(PARAM_TEXT, 'Shortened module name (max. 5 chars)'), 'courseid' => new external_value(PARAM_INT, 'Course ID'), 'status' => new external_value(PARAM_INT, 'Module status '.MODULE_STATUS::format()), 'type' => new external_value(PARAM_INT, 'Module type '.MODULE_TYPE::format()), From 8b89024660b8807e12172e7fb7efb7c1c24287aa Mon Sep 17 00:00:00 2001 From: Riedler Date: Tue, 21 Jan 2025 17:06:25 +0100 Subject: [PATCH 8/9] fix: timezone bullshittery --- lbplanner/classes/helpers/modules_helper.php | 8 ++++-- lbplanner/classes/helpers/plan_helper.php | 28 ++++++++++++++++++++ lbplanner/services/plan/set_deadline.php | 4 +-- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/lbplanner/classes/helpers/modules_helper.php b/lbplanner/classes/helpers/modules_helper.php index 2df2d0f6..026c67a7 100644 --- a/lbplanner/classes/helpers/modules_helper.php +++ b/lbplanner/classes/helpers/modules_helper.php @@ -27,6 +27,7 @@ use core_customfield\category_controller; use DateTimeImmutable; +use DateTimeZone; use local_lbplanner\enums\{MODULE_STATUS, MODULE_GRADE, MODULE_TYPE}; use local_lbplanner\model\module; @@ -160,8 +161,11 @@ public static function get_module_status(module $module, int $userid, ?int $plan $deadline = $module->get_deadline($planid); if ($deadline !== null) { - $now = (new DateTimeImmutable())->getTimestamp(); - if ($deadline->deadlineend < $now) { + $UTCTZ = new DateTimeZone('UTC'); + $now = (new DateTimeImmutable('yesterday', $UTCTZ)); + // take timestamp and remove time from it + $deadlineend = $now->setTimestamp(intval($deadline->deadlineend))->setTime(0, 0, 0, 0); + if ($now->diff($deadlineend)->invert === 1) { return MODULE_STATUS::LATE; } } diff --git a/lbplanner/classes/helpers/plan_helper.php b/lbplanner/classes/helpers/plan_helper.php index 6e6beb8f..7f3966f4 100644 --- a/lbplanner/classes/helpers/plan_helper.php +++ b/lbplanner/classes/helpers/plan_helper.php @@ -26,6 +26,8 @@ namespace local_lbplanner\helpers; use core_external\{external_value, external_single_structure, external_multiple_structure}; +use DateTimeImmutable; +use DateTimeZone; use local_lbplanner\enums\PLAN_ACCESS_TYPE; /** @@ -315,5 +317,31 @@ public static function get_invites_received(int $userid): array { $invites = $DB->get_records(self::INVITES_TABLE, ['inviteeid' => $userid]); return $invites; } + + /** + * normalizes timestamp from UTC to server's localtime + * @param int $utcts UNIX timestamp in UTC+0 + * @return int UNIX timestamp in localtime + */ + public static function timestamp_utc_to_localtime(int $utcts) { + return (new DateTimeImmutable()) + ->setTimezone(new DateTimeZone('UTC+0')) + ->setTimestamp($utcts) + ->setTimezone(new DateTimeZone(date_default_timezone_get())) + ->getTimestamp(); + } + + /** + * normalizes timestamp from server's localtime to UTC + * @param int $localts UNIX timestamp in localtime + * @return int UNIX timestamp in UTC+0 + */ + public static function timestamp_localtime_to_utc(int $localts) { + return (new DateTimeImmutable()) + ->setTimezone(new DateTimeZone(date_default_timezone_get())) + ->setTimestamp($localts) + ->setTimezone(new DateTimeZone('UTC+0')) + ->getTimestamp(); + } } diff --git a/lbplanner/services/plan/set_deadline.php b/lbplanner/services/plan/set_deadline.php index 4c05c8b4..225ade8f 100644 --- a/lbplanner/services/plan/set_deadline.php +++ b/lbplanner/services/plan/set_deadline.php @@ -43,14 +43,14 @@ public static function set_deadline_parameters(): external_function_parameters { ), 'deadlinestart' => new external_value( PARAM_INT, - 'Start of the deadline', + 'Start of the deadline as a UTC+0 UNIX timestamp', VALUE_REQUIRED, null, NULL_NOT_ALLOWED ), 'deadlineend' => new external_value( PARAM_INT, - 'End of the deadline', + 'End of the deadline as a UTC+0 UNIX timestamp', VALUE_REQUIRED, null, NULL_NOT_ALLOWED From 50dcde978730e04df181b7bbf69998e4caaf93f9 Mon Sep 17 00:00:00 2001 From: Riedler Date: Tue, 21 Jan 2025 17:10:33 +0100 Subject: [PATCH 9/9] fix: made codechecker happy --- lbplanner/classes/helpers/modules_helper.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lbplanner/classes/helpers/modules_helper.php b/lbplanner/classes/helpers/modules_helper.php index 026c67a7..1e3cc2f2 100644 --- a/lbplanner/classes/helpers/modules_helper.php +++ b/lbplanner/classes/helpers/modules_helper.php @@ -161,9 +161,9 @@ public static function get_module_status(module $module, int $userid, ?int $plan $deadline = $module->get_deadline($planid); if ($deadline !== null) { - $UTCTZ = new DateTimeZone('UTC'); - $now = (new DateTimeImmutable('yesterday', $UTCTZ)); - // take timestamp and remove time from it + $utctz = new DateTimeZone('UTC'); + $now = (new DateTimeImmutable('yesterday', $utctz)); + // Take timestamp and remove time from it. $deadlineend = $now->setTimestamp(intval($deadline->deadlineend))->setTime(0, 0, 0, 0); if ($now->diff($deadlineend)->invert === 1) { return MODULE_STATUS::LATE;