From 5ab703bffd4f06273a3fb5691b32e452d77ab173 Mon Sep 17 00:00:00 2001 From: Riedler Date: Mon, 1 Sep 2025 18:06:27 +0200 Subject: [PATCH 01/14] feat: defined kanban column type as enum --- lbplanner/classes/enums/KANBANCOL_TYPE.php | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 lbplanner/classes/enums/KANBANCOL_TYPE.php diff --git a/lbplanner/classes/enums/KANBANCOL_TYPE.php b/lbplanner/classes/enums/KANBANCOL_TYPE.php new file mode 100644 index 00000000..fb15ff0e --- /dev/null +++ b/lbplanner/classes/enums/KANBANCOL_TYPE.php @@ -0,0 +1,52 @@ +. + +/** + * enum for columns on the kanban board + * + * @package local_lbplanner + * @subpackage enums + * @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\enums; + +// TODO: revert to native enums once we migrate to php8. + +use local_lbplanner\polyfill\Enum; + +/** + * The types of columns in the kanban board + */ +class KANBANCOL_TYPE extends Enum { + /** + * column "backlog" + */ + const BACKLOG = 'backlog'; + /** + * column "in progress" + */ + const INPROGRESS = 'inprogress'; + /** + * column "todo" + */ + const TODO = 'todo'; + /** + * column "done" + */ + const DONE = 'done'; +} From 02f8d3909a6c8556cbb16683ed519896ec8cd9cc Mon Sep 17 00:00:00 2001 From: Riedler Date: Tue, 2 Sep 2025 01:32:27 +0200 Subject: [PATCH 02/14] feat: added kanban data to user model --- lbplanner/classes/helpers/user_helper.php | 2 +- lbplanner/classes/model/user.php | 80 +++++++++++++++++++---- lbplanner/classes/polyfill/Enum.php | 10 ++- lbplanner/db/install.xml | 4 ++ lbplanner/db/services.php | 4 +- lbplanner/services/user/update_user.php | 52 ++++++++++++++- lbplanner/version.php | 2 +- 7 files changed, 134 insertions(+), 20 deletions(-) diff --git a/lbplanner/classes/helpers/user_helper.php b/lbplanner/classes/helpers/user_helper.php index 23f7c2b4..ca10e824 100644 --- a/lbplanner/classes/helpers/user_helper.php +++ b/lbplanner/classes/helpers/user_helper.php @@ -76,7 +76,7 @@ public static function get_user(int $userid): user { } // Register user if not found. - $eduplanneruser = new user(0, $userid, 'default', 'none', 1, false); + $eduplanneruser = new user(0, $userid, 'default', 'none', 1, false, true, null, null, null); $epid = $DB->insert_record(self::EDUPLANNER_USER_TABLE, $eduplanneruser->prepare_for_db()); $eduplanneruser->set_fresh($epid); diff --git a/lbplanner/classes/model/user.php b/lbplanner/classes/model/user.php index 0621a4c7..60f7cf50 100644 --- a/lbplanner/classes/model/user.php +++ b/lbplanner/classes/model/user.php @@ -29,8 +29,7 @@ use core_external\{external_single_structure, external_value}; use user_picture; -use local_lbplanner\enums\CAPABILITY; -use local_lbplanner\enums\CAPABILITY_FLAG; +use local_lbplanner\enums\{CAPABILITY, CAPABILITY_FLAG, KANBANCOL_TYPE, KANBANCOL_TYPE_ORNONE}; use local_lbplanner\helpers\plan_helper; use local_lbplanner\helpers\user_helper; @@ -68,6 +67,29 @@ class user { */ public bool $ekenabled; + /** + * @var bool $show_column_colors Whether column colors should show in kanban board. + */ + public bool $show_column_colors; + + /** + * @var ?string $auto_move_completed_tasks what kanban column to move completed tasks to (null → don't move) + * @see KANBANCOL_TYPE + */ + public ?string $auto_move_completed_tasks; + + /** + * @var ?string $auto_move_submitted_tasks what kanban column to move submitted tasks to (null → don't move) + * @see KANBANCOL_TYPE + */ + public ?string $auto_move_submitted_tasks; + + /** + * @var ?string $auto_move_overdue_tasks what kanban column to move overdue tasks to (null → don't move) + * @see KANBANCOL_TYPE + */ + public ?string $auto_move_overdue_tasks; + /** * @var ?\stdClass $mdluser the cached moodle user */ @@ -88,6 +110,20 @@ class user { */ private ?int $capabilitybitmask; + /** + * @var string[] dbmirrorprops properties that are mirrored 1:1 between the DB and this object + */ + private const dbmirrorprops = [ + 'theme', + 'colorblindness', + 'displaytaskcount', + 'ekenabled', + 'show_column_colors', + 'auto_move_completed_tasks', + 'auto_move_submitted_tasks', + 'auto_move_overdue_tasks' + ]; + /** * Constructs a new course * @param int $lbpid ID of the Eduplanner user @@ -103,15 +139,18 @@ public function __construct( string $theme, string $colorblindness, bool $displaytaskcount, - bool $ekenabled + bool $ekenabled, + bool $show_column_colors, + ?string $auto_move_completed_tasks, + ?string $auto_move_submitted_tasks, + ?string $auto_move_overdue_tasks, ) { global $USER; $this->lbpid = $lbpid; $this->mdlid = $mdlid; - $this->set_theme($theme); - $this->set_colorblindness($colorblindness); - $this->displaytaskcount = $displaytaskcount; - $this->ekenabled = $ekenabled; + foreach (self::dbmirrorprops as $propname) { + $this->$propname = $$propname; + } $this->planid = null; $this->pfp = null; $this->capabilitybitmask = null; @@ -130,7 +169,14 @@ public function __construct( * @return user a representation of this user and its data */ public static function from_db(object $obj): self { - return new self($obj->id, $obj->userid, $obj->theme, $obj->colorblindness, $obj->displaytaskcount, $obj->ekenabled); + $vars = get_object_vars($obj); + // Rename the two properties that are different in the DB from in this object. + $vars['lbpid'] = $vars['id']; + $vars['mdlid'] = $vars['userid']; + unset($vars['id']); + unset($vars['userid']); + // Just throw the whole assarr in the constructor. Surely nothing bad will happen. + return new self(...$vars); } /** @@ -161,6 +207,7 @@ public function set_fresh(int $lbpid): void { * @param string $cbn colorblindness */ public function set_colorblindness(string $cbn): void { + // TODO: remove in favour of setting member directly. $this->colorblindness = $cbn; } @@ -169,6 +216,7 @@ public function set_colorblindness(string $cbn): void { * @param string $theme theme */ public function set_theme(string $theme): void { + // TODO: remove in favour of setting member directly. $this->theme = $theme; } @@ -264,10 +312,10 @@ public function prepare_for_db(): object { $obj = new \stdClass(); $obj->userid = $this->mdlid; - $obj->theme = $this->theme; - $obj->colorblindness = $this->colorblindness; - $obj->displaytaskcount = $this->displaytaskcount; - $obj->ekenabled = $this->ekenabled; + + foreach (self::dbmirrorprops as $propname) { + $obj->$propname = $this->$propname; + } if ($this->lbpid !== 0) { $obj->id = $this->lbpid; @@ -335,6 +383,10 @@ public function prepare_for_api(): array { 'planid' => $this->get_planid(), 'colorblindness' => $this->colorblindness, 'displaytaskcount' => $this->displaytaskcount, + 'show_column_colors' => $this->show_column_colors, + 'auto_move_completed_tasks' => $this->auto_move_completed_tasks ?? KANBANCOL_TYPE_ORNONE::NONE, + 'auto_move_submitted_tasks' => $this->auto_move_submitted_tasks ?? KANBANCOL_TYPE_ORNONE::NONE, + 'auto_move_overdue_tasks' => $this->auto_move_overdue_tasks ?? KANBANCOL_TYPE_ORNONE::NONE, 'email' => $mdluser->email, ] ); @@ -358,6 +410,10 @@ public static function api_structure(): external_single_structure { 'planid' => new external_value(PARAM_INT, 'The id of the plan the user is assigned to'), 'colorblindness' => new external_value(PARAM_TEXT, 'The colorblindness of the user'), 'displaytaskcount' => new external_value(PARAM_BOOL, 'Whether the user has the taskcount enabled'), + 'show_column_colors' => new external_value(PARAM_BOOL, 'Whether column colors should show in kanban board'), + 'auto_move_completed_tasks' => new external_value(PARAM_TEXT, 'The kanban column to move a task to if completed '.KANBANCOL_TYPE_ORNONE::format()), + 'auto_move_submitted_tasks' => new external_value(PARAM_TEXT, 'The kanban column to move a task to if submitted '.KANBANCOL_TYPE_ORNONE::format()), + 'auto_move_overdue_tasks' => new external_value(PARAM_TEXT, 'The kanban column to move a task to if overdue '.KANBANCOL_TYPE_ORNONE::format()), 'capabilities' => new external_value(PARAM_INT, 'The capabilities of the user represented as a bitmask value'), 'vintage' => new external_value(PARAM_TEXT, 'The vintage of the user', VALUE_DEFAULT), 'email' => new external_value(PARAM_TEXT, 'The email address of the user'), diff --git a/lbplanner/classes/polyfill/Enum.php b/lbplanner/classes/polyfill/Enum.php index a0bce32e..82a5d244 100644 --- a/lbplanner/classes/polyfill/Enum.php +++ b/lbplanner/classes/polyfill/Enum.php @@ -27,6 +27,7 @@ use ReflectionClass; use local_lbplanner\polyfill\EnumCase; +use moodle_exception; /** * Class which is meant to serve as a substitute for native enums. @@ -157,7 +158,14 @@ public static function cases(): array { public static function format(): string { $result = "["; foreach (static::cases() as $case) { - $result .= "{$case->value}=>{$case->name},"; + if (is_string($case->value)) { + $formattedval = "\"{$case->value}\""; + } else if (is_int($case->value)) { + $formattedval = $case->value; + } else { + throw new moodle_exception('unimplemented case value type for Enum::format()'); + } + $result .= "{$formattedval}=>{$case->name},"; } $result[-1] = ']'; return $result; diff --git a/lbplanner/db/install.xml b/lbplanner/db/install.xml index 6585b39f..ea6fae9f 100644 --- a/lbplanner/db/install.xml +++ b/lbplanner/db/install.xml @@ -8,6 +8,10 @@ + + + + diff --git a/lbplanner/db/services.php b/lbplanner/db/services.php index 3433ee50..db951a40 100644 --- a/lbplanner/db/services.php +++ b/lbplanner/db/services.php @@ -19,7 +19,7 @@ * * @package local_lbplanner * @subpackage db - * @copyright 2024 NecodeIT + * @copyright 2025 NecodeIT * @license https://creativecommons.org/licenses/by-nc-sa/4.0/ CC-BY-NC-SA 4.0 International or later */ @@ -48,7 +48,7 @@ 'classname' => 'local_lbplanner_services\user_update_user', 'methodname' => 'update_user', 'classpath' => 'local/lbplanner/services/user/update_user.php', - 'description' => 'Update the data for a user', + 'description' => 'Update the data for a user. null values or unset parameters are left unmodified', 'type' => 'write', 'capabilities' => 'local/lb_planner:student', 'ajax' => true, diff --git a/lbplanner/services/user/update_user.php b/lbplanner/services/user/update_user.php index 81dbd286..d9f1c4c4 100644 --- a/lbplanner/services/user/update_user.php +++ b/lbplanner/services/user/update_user.php @@ -23,13 +23,14 @@ use local_lbplanner\helpers\user_helper; use local_lbplanner\model\user; +use local_lbplanner\enums\KANBANCOL_TYPE_ORNONE; /** - * Update the data for a user. + * Update the data for a user. null values or unset parameters are left unmodified. * * @package local_lbplanner * @subpackage services_user - * @copyright 2024 necodeIT + * @copyright 2025 necodeIT * @license https://creativecommons.org/licenses/by-nc-sa/4.0/ CC-BY-NC-SA 4.0 International or later */ class user_update_user extends external_api { @@ -55,6 +56,26 @@ public static function update_user_parameters(): external_function_parameters { 'Whether the user wants to see EK modules', VALUE_DEFAULT, null), + 'show_column_colors' => new external_value( + PARAM_BOOL, + 'Whether column colors should show in kanban board', + VALUE_DEFAULT, + null), + 'auto_move_completed_tasks' => new external_value( + PARAM_TEXT, + 'The kanban column to move a task to if completed '.KANBANCOL_TYPE_ORNONE::format(), + VALUE_DEFAULT, + null), + 'auto_move_submitted_tasks' => new external_value( + PARAM_TEXT, + 'The kanban column to move a task to if submitted '.KANBANCOL_TYPE_ORNONE::format(), + VALUE_DEFAULT, + null), + 'auto_move_overdue_tasks' => new external_value( + PARAM_TEXT, + 'The kanban column to move a task to if overdue '.KANBANCOL_TYPE_ORNONE::format(), + VALUE_DEFAULT, + null), ]); } @@ -69,7 +90,16 @@ public static function update_user_parameters(): external_function_parameters { * @throws dml_exception * @throws invalid_parameter_exception */ - public static function update_user(?string $theme, ?string $colorblindness, ?bool $displaytaskcount, ?bool $ekenabled): array { + public static function update_user( + ?string $theme, + ?string $colorblindness, + ?bool $displaytaskcount, + ?bool $ekenabled, + ?bool $show_column_colors, + ?string $auto_move_completed_tasks, + ?string $auto_move_submitted_tasks, + ?string $auto_move_overdue_tasks, + ): array { global $DB, $USER; self::validate_parameters( @@ -79,6 +109,10 @@ public static function update_user(?string $theme, ?string $colorblindness, ?boo 'colorblindness' => $colorblindness, 'displaytaskcount' => $displaytaskcount, 'ekenabled' => $ekenabled, + 'show_column_colors' => $show_column_colors, + 'auto_move_completed_tasks' => $auto_move_completed_tasks, + 'auto_move_submitted_tasks' => $auto_move_submitted_tasks, + 'auto_move_overdue_tasks' => $auto_move_overdue_tasks, ] ); @@ -99,6 +133,18 @@ public static function update_user(?string $theme, ?string $colorblindness, ?boo if ($ekenabled !== null) { $user->ekenabled = $ekenabled; } + if ($show_column_colors !== null) { + $user->show_column_colors = $show_column_colors; + } + foreach (['auto_move_completed_tasks', 'auto_move_submitted_tasks', 'auto_move_overdue_tasks'] as $propname) { + if ($$propname !== null) { + if ($$propname === KANBANCOL_TYPE_ORNONE::NONE) { + $user->$propname = null; + } else { + $user->$propname = $$propname; + } + } + } $DB->update_record(user_helper::EDUPLANNER_USER_TABLE, $user->prepare_for_db()); diff --git a/lbplanner/version.php b/lbplanner/version.php index ee33c580..3754f714 100644 --- a/lbplanner/version.php +++ b/lbplanner/version.php @@ -28,7 +28,7 @@ $plugin->maturity = MATURITY_BETA; $plugin->component = 'local_lbplanner'; $plugin->release = '1.0.2'; -$plugin->version = 202502200000; +$plugin->version = 202509010000; $plugin->dependencies = [ // Depend upon version 2023110600 of local_modcustomfields. 'local_modcustomfields' => 2023110600, From e9130aab72c8bca003a4b632f26b24d11eaee3c3 Mon Sep 17 00:00:00 2001 From: Riedler Date: Tue, 2 Sep 2025 05:47:40 +0200 Subject: [PATCH 03/14] feat: implemented service kanban_get_board --- lbplanner/classes/enums/KANBANCOL_TYPE.php | 11 ++ .../classes/enums/KANBANCOL_TYPE_NUMERIC.php | 63 ++++++++ .../classes/enums/KANBANCOL_TYPE_ORNONE.php | 39 +++++ lbplanner/classes/helpers/kanban_helper.php | 52 +++++++ lbplanner/classes/model/kanbanentry.php | 135 ++++++++++++++++++ lbplanner/classes/model/module.php | 11 ++ lbplanner/db/install.xml | 17 ++- lbplanner/db/services.php | 10 ++ lbplanner/services/kanban/get_board.php | 88 ++++++++++++ 9 files changed, 425 insertions(+), 1 deletion(-) create mode 100644 lbplanner/classes/enums/KANBANCOL_TYPE_NUMERIC.php create mode 100644 lbplanner/classes/enums/KANBANCOL_TYPE_ORNONE.php create mode 100644 lbplanner/classes/helpers/kanban_helper.php create mode 100644 lbplanner/classes/model/kanbanentry.php create mode 100644 lbplanner/services/kanban/get_board.php diff --git a/lbplanner/classes/enums/KANBANCOL_TYPE.php b/lbplanner/classes/enums/KANBANCOL_TYPE.php index fb15ff0e..886db554 100644 --- a/lbplanner/classes/enums/KANBANCOL_TYPE.php +++ b/lbplanner/classes/enums/KANBANCOL_TYPE.php @@ -28,6 +28,7 @@ // TODO: revert to native enums once we migrate to php8. use local_lbplanner\polyfill\Enum; +use local_lbplanner\enums\KANBANCOL_TYPE_NUMERIC; /** * The types of columns in the kanban board @@ -49,4 +50,14 @@ class KANBANCOL_TYPE extends Enum { * column "done" */ const DONE = 'done'; + + /** + * Converts from column name to column ID + * @param string $str the column name + * @return int the column ID + * @link KANBANCOL_TYPE_NUMERIC + */ + public static function to_numeric(string $str): int { + return KANBANCOL_TYPE_NUMERIC::get(self::name_from($str)); + } } diff --git a/lbplanner/classes/enums/KANBANCOL_TYPE_NUMERIC.php b/lbplanner/classes/enums/KANBANCOL_TYPE_NUMERIC.php new file mode 100644 index 00000000..5b0b7133 --- /dev/null +++ b/lbplanner/classes/enums/KANBANCOL_TYPE_NUMERIC.php @@ -0,0 +1,63 @@ +. + +/** + * enum for columns on the kanban board + * + * @package local_lbplanner + * @subpackage enums + * @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\enums; + +// TODO: revert to native enums once we migrate to php8. + +use local_lbplanner\polyfill\Enum; +use local_lbplanner\enums\KANBANCOL_TYPE; + +/** + * The types of columns in the kanban board + */ +class KANBANCOL_TYPE_NUMERIC extends Enum { + /** + * column "backlog" + */ + const BACKLOG = 0; + /** + * column "todo" + */ + const TODO = 1; + /** + * column "in progress" + */ + const INPROGRESS = 2; + /** + * column "done" + */ + const DONE = 3; + + /** + * converts numeric repr to named repr + * @param int $num the column number + * @return string the column name + * @link KANBANCOL_TYPE + */ + public static function to_named(int $num): string { + return KANBANCOL_TYPE::get(self::name_from($num)); + } +} diff --git a/lbplanner/classes/enums/KANBANCOL_TYPE_ORNONE.php b/lbplanner/classes/enums/KANBANCOL_TYPE_ORNONE.php new file mode 100644 index 00000000..e54f4832 --- /dev/null +++ b/lbplanner/classes/enums/KANBANCOL_TYPE_ORNONE.php @@ -0,0 +1,39 @@ +. + +/** + * enum for columns on the kanban board + * + * @package local_lbplanner + * @subpackage enums + * @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\enums; +use local_lbplanner\enums\KANBANCOL_TYPE; + +// TODO: revert to native enums once we migrate to php8. + +/** + * The types of columns in the kanban board + */ +class KANBANCOL_TYPE_ORNONE extends KANBANCOL_TYPE { + /** + * nonexistant column - for user setting "don't move to any column" + */ + const NONE = ''; +} diff --git a/lbplanner/classes/helpers/kanban_helper.php b/lbplanner/classes/helpers/kanban_helper.php new file mode 100644 index 00000000..614f2552 --- /dev/null +++ b/lbplanner/classes/helpers/kanban_helper.php @@ -0,0 +1,52 @@ +. + +namespace local_lbplanner\helpers; + +use local_lbplanner\model\kanbanentry; + +/** + * Helper class for the kanban board + * + * @package local_lbplanner + * @subpackage helpers + * @copyright 2025 NecodeIT + * @license https://creativecommons.org/licenses/by-nc-sa/4.0/ CC-BY-NC-SA 4.0 International or later + */ +class kanban_helper { + + /** + * Table for storing kanban board entries. + */ + const TABLE = 'local_lbplanner_kanbanentries'; + + /** + * Gets all kanban entries for a user. + * @param int $userid ID of the user to look entries up for + * @return kanbanentry[] all the kanban entries for this user + */ + public static function get_all_entries_by_user(int $userid): array { + global $DB; + + $res = $DB->get_records(self::TABLE, ['userid'=>$userid]); + $entries = []; + foreach ($res as $obj) { + array_push($entries, kanbanentry::from_obj($obj)); + } + + return $entries; + } +} diff --git a/lbplanner/classes/model/kanbanentry.php b/lbplanner/classes/model/kanbanentry.php new file mode 100644 index 00000000..737106f3 --- /dev/null +++ b/lbplanner/classes/model/kanbanentry.php @@ -0,0 +1,135 @@ +. + +/** + * Model for a reservation + * + * @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\{KANBANCOL_TYPE, KANBANCOL_TYPE_NUMERIC}; + +/** + * Model class for a kanban board entry + */ +class kanbanentry { + /** + * @var int $id ID of reservation + */ + public int $id; + /** + * @var int $userid ID of user + */ + public int $userid; + /** + * @var int $cmid ID of course-module + */ + public int $cmid; + /** + * @var int $column column number + */ + public int $column; + + /** + * Constructs a kbbe + * @param int $id ID of reservation + * @param int $userid ID of user + * @param int $cmid ID of course-module + * @param int $column column number + */ + public function __construct(int $id, int $userid, int $cmid, int $column) { + $this->id = $id; + $this->userid = $userid; + $this->cmid = $cmid; + $this->column = $column; + } + + /** + * Initializes object from a DB object + * @param \stdClass $obj the DB obj + * @return kanbanentry the kanbanentry obj + */ + public static function from_obj(\stdClass $obj): self { + return new self($obj->id, $obj->userid, $obj->cmid, $obj->column); + } + + /** + * Mark the object as freshly created and sets the new ID + * @param int $id the new ID after insertint into the DB + */ + public function set_fresh(int $id) { + assert($this->id === 0); + assert($id !== 0); + $this->id = $id; + } + + /** + * Prepares data for the DB endpoint. + * doesn't set ID if it's 0 + * + * @return object a representation of this reservation and its data + */ + public function prepare_for_db(): object { + $obj = new \stdClass(); + + $obj->userid = $this->userid; + $obj->cmid = $this->cmid; + $obj->column = $this->column; + + if ($this->id !== 0) { + $obj->id = $this->id; + } + + return $obj; + } + + /** + * Prepares data for the API endpoint. + * + * @return array a representation of this reservation and its data + */ + public function prepare_for_api(): array { + return [ + 'id' => $this->id, + 'userid' => $this->userid, + 'cmid' => $this->cmid, + 'column' => KANBANCOL_TYPE_NUMERIC::to_named($this->column), + ]; + } + + /** + * Returns the data structure of an kanban board entry. + * + * @return external_single_structure The data structure of a kbbe. + */ + public static function api_structure(): external_single_structure { + return new external_single_structure( + [ + 'id' => new external_value(PARAM_INT, 'kanban board ID'), + 'userid' => new external_value(PARAM_INT, 'ID of the owner of this entry'), + 'cmid' => new external_value(PARAM_INT, 'ID of the course-module'), + 'column' => new external_value(PARAM_TEXT, 'which column this module is in '.KANBANCOL_TYPE::format()), + ] + ); + } +} diff --git a/lbplanner/classes/model/module.php b/lbplanner/classes/model/module.php index 6e7c77ec..98dd6a18 100644 --- a/lbplanner/classes/model/module.php +++ b/lbplanner/classes/model/module.php @@ -106,6 +106,17 @@ public static function from_assignobj(\stdClass $assignobj): self { return $obj; } + /** + * Creates a module object from the course-module ID. + * @param int $id the course-module ID + * @return module a module object + */ + public static function from_cmid(int $id): self { + $obj = new self(); + $obj->cmid = $id; + return $obj; + } + /** * Fetches the necessary caches and returns the assignment ID * @return int assign ID diff --git a/lbplanner/db/install.xml b/lbplanner/db/install.xml index ea6fae9f..69ed0e66 100644 --- a/lbplanner/db/install.xml +++ b/lbplanner/db/install.xml @@ -91,7 +91,7 @@ - + @@ -150,5 +150,20 @@ + + + + + + + + + + + + + + +
diff --git a/lbplanner/db/services.php b/lbplanner/db/services.php index db951a40..a497ea31 100644 --- a/lbplanner/db/services.php +++ b/lbplanner/db/services.php @@ -395,6 +395,15 @@ 'capabilities' => 'local/lb_planner:slotmaster', 'ajax' => true, ], + 'local_lbplanner_kanban_get_board' => [ + 'classname' => 'local_lbplanner_services\kanban_get_board', + 'methodname' => 'get_board', + 'classpath' => 'local/lbplanner/services/kanban/get_board.php', + 'description' => 'Returns all entries in the kanban board for the current user', + 'type' => 'read', + 'capabilities' => 'local/lb_planner:student', + 'ajax' => true, + ], ]; $services = [ @@ -440,6 +449,7 @@ 'local_lbplanner_slots_get_supervisor_slots', 'local_lbplanner_slots_remove_slot_supervisor', 'local_lbplanner_slots_update_slot', + 'local_lbplanner_kanban_get_board', ], 'restrictedusers' => 0, 'enabled' => 1, diff --git a/lbplanner/services/kanban/get_board.php b/lbplanner/services/kanban/get_board.php new file mode 100644 index 00000000..e6459bea --- /dev/null +++ b/lbplanner/services/kanban/get_board.php @@ -0,0 +1,88 @@ +. + +namespace local_lbplanner_services; + +use core_external\{external_api, external_function_parameters, external_multiple_structure, external_single_structure, external_value}; +use local_lbplanner\enums\KANBANCOL_TYPE; +use local_lbplanner\enums\KANBANCOL_TYPE_NUMERIC; +use local_lbplanner\enums\MODULE_TYPE; +use local_lbplanner\helpers\kanban_helper; +use local_lbplanner\model\module; +use local_lbplanner\model\user; + +/** + * Returns all entries in the kanban board for the current user. + * + * @package local_lbplanner + * @subpackage services_kanban + * @copyright 2025 necodeIT + * @license https://creativecommons.org/licenses/by-nc-sa/4.0/ CC-BY-NC-SA 4.0 International or later + */ +class kanban_get_board extends external_api { + /** + * Parameters for kanban_get_board. + * @return external_function_parameters + */ + public static function get_board_parameters(): external_function_parameters { + return new external_function_parameters([]); + } + + /** + * Gets all the entries on this user's board. + */ + public static function get_board(): array { + global $USER; + + $entries = kanban_helper::get_all_entries_by_user($USER->id); + + $sorted = [ + KANBANCOL_TYPE_NUMERIC::TODO => [], + KANBANCOL_TYPE_NUMERIC::INPROGRESS => [], + KANBANCOL_TYPE_NUMERIC::DONE => [], + ]; + + $ekenabled = user::from_mdlobj($USER)->ekenabled; + + foreach ($entries as $entry) { + if (!$ekenabled) { + $module = module::from_cmid($entry->cmid); + if($module->get_type() === MODULE_TYPE::EK) { + continue; + } + } + $sorted[$entry->column] = $entry->cmid; + } + + return [ + KANBANCOL_TYPE::TODO => $sorted[KANBANCOL_TYPE_NUMERIC::TODO], + KANBANCOL_TYPE::INPROGRESS => $sorted[KANBANCOL_TYPE_NUMERIC::INPROGRESS], + KANBANCOL_TYPE::DONE => $sorted[KANBANCOL_TYPE_NUMERIC::DONE], + ]; + } + + /** + * Return structure of kanban_get_board + * @return external_multiple_structure + */ + public static function get_board_returns() { + return new external_single_structure([ + KANBANCOL_TYPE::TODO => new external_multiple_structure(new external_value(PARAM_INT, 'course-module ID')), + KANBANCOL_TYPE::INPROGRESS => new external_multiple_structure(new external_value(PARAM_INT, 'course-module ID')), + KANBANCOL_TYPE::DONE => new external_multiple_structure(new external_value(PARAM_INT, 'course-module ID')), + ]); + } +} From 8df7d691eba1958eaedf38a0fe2a2133056b8196 Mon Sep 17 00:00:00 2001 From: Riedler Date: Tue, 2 Sep 2025 05:48:47 +0200 Subject: [PATCH 04/14] fix: implemented newly needed stuff for php parser py --- document_services.py | 103 +++++++++++++++++++++++++++++++------------ 1 file changed, 76 insertions(+), 27 deletions(-) diff --git a/document_services.py b/document_services.py index de073ad4..39b50531 100644 --- a/document_services.py +++ b/document_services.py @@ -202,30 +202,71 @@ def resolve(self) -> PHPExpression: def __str__(self) -> str: return f"{self.classname}::{self.funcname}()" -class PHPEnumFormat(PHPClassMemberFunction, PHPString): - def resolve(self) -> PHPString: +class PHPEnum(): + @classmethod + def getcases(cls, classname: str) -> dict[str, str]: # https://regex101.com/r/p5FzCh casepattern = r"const (\w+) = (\d+|true|false|(['\"]).*?\3)" - fullbody_pattern = f"class {self.classname} extends Enum {{.*?}}" + fullbody_pattern = f"class {classname} extends (Enum|\\w+) {{(.*?)}}" + + cases = {} - fp = f"lbplanner/classes/enums/{self.classname}.php" + fp = f"lbplanner/classes/enums/{classname}.php" if not path.exists(fp): warn(f"Couldn't find enum file {fp}") - return PHPStringLiteral("") + return {} with open(fp, "r") as f: - matches: list[str] = re.findall(fullbody_pattern, f.read(), re.DOTALL) + matches: list[list[str]] = re.findall(fullbody_pattern, f.read(), re.DOTALL) if len(matches) == 1: - body = matches[0] + if matches[0][0] != 'Enum': + cases = cls.getcases(matches[0][0]) + body = matches[0][1] else: - warn(f"couldn't parse enum {self.classname}", matches) + warn(f"couldn't parse enum {classname}", matches) - cases = {} - matches = re.findall(casepattern, body) - for match in matches: - # capitalizing first letter, if exists - name = "".join([match[0][0].upper(), match[0][1:].lower()]) - cases[name] = match[1].replace("'", '"') + matches2: list[str] = re.findall(casepattern, body) + for match in matches2: + val = match[1].replace("'", '"') + cases[match[0]] = val + + return cases + +class PHPEnumCase(PHPEnum, PHPString): + __slots__ = ('classname', 'casename', 'fp') + classname: str + casename: str + fp: str + + def __init__(self, classname: str, casename: str, fp: str): + self.classname = classname + self.casename = casename + self.fp = fp + + def resolve(self) -> PHPString: + cases = self.getcases(self.classname) + if self.casename not in cases.keys(): + warn(f"enum member {self.classname}::{self.casename} not found", cases) + return PHPStringLiteral("?") + + val = cases[self.casename] + + if val.startswith('"') and val.endswith('"'): + val = val[1:-1] + + return PHPStringLiteral(val) + + def get_value(self) -> str: + return self.resolve().get_value() + + def __str__(self) -> str: + return f"{self.classname}::{self.casename}" + +class PHPEnumFormat(PHPEnum, PHPClassMemberFunction, PHPString): + def resolve(self) -> PHPString: + cases = self.getcases(self.classname) + # capitalizing first letter of each key + cases = {"".join([name[0].upper(), name[1:].lower()]): case for name, case in cases.items()} return PHPStringLiteral("{ " + ", ".join([f"{name} = {value}" for name, value in cases.items()]) + " }") @@ -506,22 +547,30 @@ def parse_expression(code: str, nr: PHPNameResolution) -> tuple[int, PHPExpressi assert len(buf) > 0 assert expr is None i += 2 - iplus = code[i:].index('(') - funcname = code[i:i + iplus] + iplus = 1 + while 95 <= ord(code[i + iplus].lower()) <= 122: # until it hits non-word character + iplus += 1 + is_func = code[i + iplus] == '(' + membername = code[i:i + iplus] classname = "".join(buf) i += iplus - assert code[i:i + 2] == '()' - i += 2 - C: type[PHPClassMemberFunction] - fp_import: str | None - if funcname == 'format': - C = PHPEnumFormat - fp_import = path.join(path.dirname(__file__), "lbplanner", "enums", f"{classname}.php") + if is_func: + assert code[i:i + 2] == '()' + i += 2 + C: type[PHPClassMemberFunction] + fp_import: str | None + if membername == 'format': + C = PHPEnumFormat + fp_import = path.join(path.dirname(__file__), "lbplanner", "enums", f"{classname}.php") + else: + C = PHPClassMemberFunction + fp_import = find_import(nr, classname) + expr = C(classname, membername, fp_import).resolve() + buf = [] else: - C = PHPClassMemberFunction - fp_import = find_import(nr, classname) - expr = C(classname, funcname, fp_import).resolve() - buf = [] + fp_import = path.join(path.dirname(__file__), "lbplanner", "enums", f"{classname}.php") + expr = PHPEnumCase(classname, membername, fp_import).resolve() + buf = [] else: # unkown character? simply bail if len(buf) > 0: From f0bd2196e5b76385ec4676d4f123d125d37cbb71 Mon Sep 17 00:00:00 2001 From: Riedler Date: Tue, 2 Sep 2025 06:14:51 +0200 Subject: [PATCH 05/14] fix: made moodle codechecker happy fucking terrible, considering I had to rename a GOOD chunk of vars --- lbplanner/classes/helpers/kanban_helper.php | 2 +- lbplanner/classes/model/kanbanentry.php | 10 ++-- lbplanner/classes/model/user.php | 64 +++++++++++---------- lbplanner/db/install.xml | 8 +-- lbplanner/services/kanban/get_board.php | 2 +- lbplanner/services/user/update_user.php | 34 ++++++----- 6 files changed, 65 insertions(+), 55 deletions(-) diff --git a/lbplanner/classes/helpers/kanban_helper.php b/lbplanner/classes/helpers/kanban_helper.php index 614f2552..558fdd35 100644 --- a/lbplanner/classes/helpers/kanban_helper.php +++ b/lbplanner/classes/helpers/kanban_helper.php @@ -41,7 +41,7 @@ class kanban_helper { public static function get_all_entries_by_user(int $userid): array { global $DB; - $res = $DB->get_records(self::TABLE, ['userid'=>$userid]); + $res = $DB->get_records(self::TABLE, ['userid' => $userid]); $entries = []; foreach ($res as $obj) { array_push($entries, kanbanentry::from_obj($obj)); diff --git a/lbplanner/classes/model/kanbanentry.php b/lbplanner/classes/model/kanbanentry.php index 737106f3..ba7d99f5 100644 --- a/lbplanner/classes/model/kanbanentry.php +++ b/lbplanner/classes/model/kanbanentry.php @@ -15,7 +15,7 @@ // along with Moodle. If not, see . /** - * Model for a reservation + * Model for a kanban entry * * @package local_lbplanner * @subpackage model @@ -34,7 +34,7 @@ */ class kanbanentry { /** - * @var int $id ID of reservation + * @var int $id ID of kbbe */ public int $id; /** @@ -52,7 +52,7 @@ class kanbanentry { /** * Constructs a kbbe - * @param int $id ID of reservation + * @param int $id ID of kbbe * @param int $userid ID of user * @param int $cmid ID of course-module * @param int $column column number @@ -87,7 +87,7 @@ public function set_fresh(int $id) { * Prepares data for the DB endpoint. * doesn't set ID if it's 0 * - * @return object a representation of this reservation and its data + * @return object a representation of this kbbe and its data */ public function prepare_for_db(): object { $obj = new \stdClass(); @@ -106,7 +106,7 @@ public function prepare_for_db(): object { /** * Prepares data for the API endpoint. * - * @return array a representation of this reservation and its data + * @return array a representation of this kbbe and its data */ public function prepare_for_api(): array { return [ diff --git a/lbplanner/classes/model/user.php b/lbplanner/classes/model/user.php index 60f7cf50..e0dfb866 100644 --- a/lbplanner/classes/model/user.php +++ b/lbplanner/classes/model/user.php @@ -68,27 +68,27 @@ class user { public bool $ekenabled; /** - * @var bool $show_column_colors Whether column colors should show in kanban board. + * @var bool $showcolumncolors Whether column colors should show in kanban board. */ - public bool $show_column_colors; + public bool $showcolumncolors; /** - * @var ?string $auto_move_completed_tasks what kanban column to move completed tasks to (null → don't move) + * @var ?string $automovecompletedtasks what kanban column to move completed tasks to (null → don't move) * @see KANBANCOL_TYPE */ - public ?string $auto_move_completed_tasks; + public ?string $automovecompletedtasks; /** - * @var ?string $auto_move_submitted_tasks what kanban column to move submitted tasks to (null → don't move) + * @var ?string $automovesubmittedtasks what kanban column to move submitted tasks to (null → don't move) * @see KANBANCOL_TYPE */ - public ?string $auto_move_submitted_tasks; + public ?string $automovesubmittedtasks; /** - * @var ?string $auto_move_overdue_tasks what kanban column to move overdue tasks to (null → don't move) + * @var ?string $automoveoverduetasks what kanban column to move overdue tasks to (null → don't move) * @see KANBANCOL_TYPE */ - public ?string $auto_move_overdue_tasks; + public ?string $automoveoverduetasks; /** * @var ?\stdClass $mdluser the cached moodle user @@ -111,17 +111,17 @@ class user { private ?int $capabilitybitmask; /** - * @var string[] dbmirrorprops properties that are mirrored 1:1 between the DB and this object + * @var string[] DBMIRRORPROPS properties that are mirrored 1:1 between the DB and this object */ - private const dbmirrorprops = [ + private const DBMIRRORPROPS = [ 'theme', 'colorblindness', 'displaytaskcount', 'ekenabled', - 'show_column_colors', - 'auto_move_completed_tasks', - 'auto_move_submitted_tasks', - 'auto_move_overdue_tasks' + 'showcolumncolors', + 'automovecompletedtasks', + 'automovesubmittedtasks', + 'automoveoverduetasks', ]; /** @@ -132,6 +132,10 @@ class user { * @param string $colorblindness user's colorblindness * @param bool $displaytaskcount user's display task count * @param bool $ekenabled whether the user wants to see EK modules + * @param bool $showcolumncolors whether column colors should show in kanban board + * @param ?string $automovecompletedtasks what kanban column to move completed tasks to (null → don't move) + * @param ?string $automovesubmittedtasks what kanban column to move submitted tasks to (null → don't move) + * @param ?string $automoveoverduetasks what kanban column to move overdue tasks to (null → don't move) */ public function __construct( int $lbpid, @@ -140,15 +144,16 @@ public function __construct( string $colorblindness, bool $displaytaskcount, bool $ekenabled, - bool $show_column_colors, - ?string $auto_move_completed_tasks, - ?string $auto_move_submitted_tasks, - ?string $auto_move_overdue_tasks, + bool $showcolumncolors, + ?string $automovecompletedtasks, + ?string $automovesubmittedtasks, + ?string $automoveoverduetasks, ) { global $USER; $this->lbpid = $lbpid; $this->mdlid = $mdlid; - foreach (self::dbmirrorprops as $propname) { + foreach (self::DBMIRRORPROPS as $propname) { + $propname = str_replace('_', '', $propname); $this->$propname = $$propname; } $this->planid = null; @@ -313,8 +318,9 @@ public function prepare_for_db(): object { $obj->userid = $this->mdlid; - foreach (self::dbmirrorprops as $propname) { - $obj->$propname = $this->$propname; + foreach (self::DBMIRRORPROPS as $propname) { + $phppropname = str_replace('_', '', $propname); + $obj->$propname = $this->$phppropname; } if ($this->lbpid !== 0) { @@ -383,10 +389,10 @@ public function prepare_for_api(): array { 'planid' => $this->get_planid(), 'colorblindness' => $this->colorblindness, 'displaytaskcount' => $this->displaytaskcount, - 'show_column_colors' => $this->show_column_colors, - 'auto_move_completed_tasks' => $this->auto_move_completed_tasks ?? KANBANCOL_TYPE_ORNONE::NONE, - 'auto_move_submitted_tasks' => $this->auto_move_submitted_tasks ?? KANBANCOL_TYPE_ORNONE::NONE, - 'auto_move_overdue_tasks' => $this->auto_move_overdue_tasks ?? KANBANCOL_TYPE_ORNONE::NONE, + 'showcolumncolors' => $this->showcolumncolors, + 'automovecompletedtasks' => $this->automovecompletedtasks ?? KANBANCOL_TYPE_ORNONE::NONE, + 'automovesubmittedtasks' => $this->automovesubmittedtasks ?? KANBANCOL_TYPE_ORNONE::NONE, + 'automoveoverduetasks' => $this->automoveoverduetasks ?? KANBANCOL_TYPE_ORNONE::NONE, 'email' => $mdluser->email, ] ); @@ -410,10 +416,10 @@ public static function api_structure(): external_single_structure { 'planid' => new external_value(PARAM_INT, 'The id of the plan the user is assigned to'), 'colorblindness' => new external_value(PARAM_TEXT, 'The colorblindness of the user'), 'displaytaskcount' => new external_value(PARAM_BOOL, 'Whether the user has the taskcount enabled'), - 'show_column_colors' => new external_value(PARAM_BOOL, 'Whether column colors should show in kanban board'), - 'auto_move_completed_tasks' => new external_value(PARAM_TEXT, 'The kanban column to move a task to if completed '.KANBANCOL_TYPE_ORNONE::format()), - 'auto_move_submitted_tasks' => new external_value(PARAM_TEXT, 'The kanban column to move a task to if submitted '.KANBANCOL_TYPE_ORNONE::format()), - 'auto_move_overdue_tasks' => new external_value(PARAM_TEXT, 'The kanban column to move a task to if overdue '.KANBANCOL_TYPE_ORNONE::format()), + 'showcolumncolors' => new external_value(PARAM_BOOL, 'Whether column colors should show in kanban board'), + 'automovecompletedtasks' => new external_value(PARAM_TEXT, 'The kanban column to move a task to if completed '.KANBANCOL_TYPE_ORNONE::format()), + 'automovesubmittedtasks' => new external_value(PARAM_TEXT, 'The kanban column to move a task to if submitted '.KANBANCOL_TYPE_ORNONE::format()), + 'automoveoverduetasks' => new external_value(PARAM_TEXT, 'The kanban column to move a task to if overdue '.KANBANCOL_TYPE_ORNONE::format()), 'capabilities' => new external_value(PARAM_INT, 'The capabilities of the user represented as a bitmask value'), 'vintage' => new external_value(PARAM_TEXT, 'The vintage of the user', VALUE_DEFAULT), 'email' => new external_value(PARAM_TEXT, 'The email address of the user'), diff --git a/lbplanner/db/install.xml b/lbplanner/db/install.xml index 69ed0e66..43c02073 100644 --- a/lbplanner/db/install.xml +++ b/lbplanner/db/install.xml @@ -8,10 +8,10 @@ - - - - + + + + diff --git a/lbplanner/services/kanban/get_board.php b/lbplanner/services/kanban/get_board.php index e6459bea..032596df 100644 --- a/lbplanner/services/kanban/get_board.php +++ b/lbplanner/services/kanban/get_board.php @@ -60,7 +60,7 @@ public static function get_board(): array { foreach ($entries as $entry) { if (!$ekenabled) { $module = module::from_cmid($entry->cmid); - if($module->get_type() === MODULE_TYPE::EK) { + if ($module->get_type() === MODULE_TYPE::EK) { continue; } } diff --git a/lbplanner/services/user/update_user.php b/lbplanner/services/user/update_user.php index d9f1c4c4..89eb5750 100644 --- a/lbplanner/services/user/update_user.php +++ b/lbplanner/services/user/update_user.php @@ -56,22 +56,22 @@ public static function update_user_parameters(): external_function_parameters { 'Whether the user wants to see EK modules', VALUE_DEFAULT, null), - 'show_column_colors' => new external_value( + 'showcolumncolors' => new external_value( PARAM_BOOL, 'Whether column colors should show in kanban board', VALUE_DEFAULT, null), - 'auto_move_completed_tasks' => new external_value( + 'automovecompletedtasks' => new external_value( PARAM_TEXT, 'The kanban column to move a task to if completed '.KANBANCOL_TYPE_ORNONE::format(), VALUE_DEFAULT, null), - 'auto_move_submitted_tasks' => new external_value( + 'automovesubmittedtasks' => new external_value( PARAM_TEXT, 'The kanban column to move a task to if submitted '.KANBANCOL_TYPE_ORNONE::format(), VALUE_DEFAULT, null), - 'auto_move_overdue_tasks' => new external_value( + 'automoveoverduetasks' => new external_value( PARAM_TEXT, 'The kanban column to move a task to if overdue '.KANBANCOL_TYPE_ORNONE::format(), VALUE_DEFAULT, @@ -85,6 +85,10 @@ public static function update_user_parameters(): external_function_parameters { * @param ?string $colorblindness The colorblindness the user has selected * @param ?bool $displaytaskcount The displaytaskcount the user has selected * @param ?bool $ekenabled whether the user wants to see EK modules + * @param ?bool $showcolumncolors whether column colors should show in kanban board + * @param ?string $automovecompletedtasks what kanban column to move completed tasks to ("" → don't move) + * @param ?string $automovesubmittedtasks what kanban column to move submitted tasks to ("" → don't move) + * @param ?string $automoveoverduetasks what kanban column to move overdue tasks to ("" → don't move) * @return array The updated user * @throws moodle_exception * @throws dml_exception @@ -95,10 +99,10 @@ public static function update_user( ?string $colorblindness, ?bool $displaytaskcount, ?bool $ekenabled, - ?bool $show_column_colors, - ?string $auto_move_completed_tasks, - ?string $auto_move_submitted_tasks, - ?string $auto_move_overdue_tasks, + ?bool $showcolumncolors, + ?string $automovecompletedtasks, + ?string $automovesubmittedtasks, + ?string $automoveoverduetasks, ): array { global $DB, $USER; @@ -109,10 +113,10 @@ public static function update_user( 'colorblindness' => $colorblindness, 'displaytaskcount' => $displaytaskcount, 'ekenabled' => $ekenabled, - 'show_column_colors' => $show_column_colors, - 'auto_move_completed_tasks' => $auto_move_completed_tasks, - 'auto_move_submitted_tasks' => $auto_move_submitted_tasks, - 'auto_move_overdue_tasks' => $auto_move_overdue_tasks, + 'showcolumncolors' => $showcolumncolors, + 'automovecompletedtasks' => $automovecompletedtasks, + 'automovesubmittedtasks' => $automovesubmittedtasks, + 'automoveoverduetasks' => $automoveoverduetasks, ] ); @@ -133,10 +137,10 @@ public static function update_user( if ($ekenabled !== null) { $user->ekenabled = $ekenabled; } - if ($show_column_colors !== null) { - $user->show_column_colors = $show_column_colors; + if ($showcolumncolors !== null) { + $user->showcolumncolors = $showcolumncolors; } - foreach (['auto_move_completed_tasks', 'auto_move_submitted_tasks', 'auto_move_overdue_tasks'] as $propname) { + foreach (['automovecompletedtasks', 'automovesubmittedtasks', 'automoveoverduetasks'] as $propname) { if ($$propname !== null) { if ($$propname === KANBANCOL_TYPE_ORNONE::NONE) { $user->$propname = null; From 917e0dcc8254033a7731c2f311b5626f378e7eb2 Mon Sep 17 00:00:00 2001 From: Riedler Date: Tue, 2 Sep 2025 06:27:15 +0200 Subject: [PATCH 06/14] fix: shortened some overlong lines --- lbplanner/classes/model/user.php | 15 ++++++++++++--- lbplanner/services/kanban/get_board.php | 15 +++++++++------ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/lbplanner/classes/model/user.php b/lbplanner/classes/model/user.php index e0dfb866..7bedd26b 100644 --- a/lbplanner/classes/model/user.php +++ b/lbplanner/classes/model/user.php @@ -417,9 +417,18 @@ public static function api_structure(): external_single_structure { 'colorblindness' => new external_value(PARAM_TEXT, 'The colorblindness of the user'), 'displaytaskcount' => new external_value(PARAM_BOOL, 'Whether the user has the taskcount enabled'), 'showcolumncolors' => new external_value(PARAM_BOOL, 'Whether column colors should show in kanban board'), - 'automovecompletedtasks' => new external_value(PARAM_TEXT, 'The kanban column to move a task to if completed '.KANBANCOL_TYPE_ORNONE::format()), - 'automovesubmittedtasks' => new external_value(PARAM_TEXT, 'The kanban column to move a task to if submitted '.KANBANCOL_TYPE_ORNONE::format()), - 'automoveoverduetasks' => new external_value(PARAM_TEXT, 'The kanban column to move a task to if overdue '.KANBANCOL_TYPE_ORNONE::format()), + 'automovecompletedtasks' => new external_value( + PARAM_TEXT, + 'The kanban column to move a task to if completed '.KANBANCOL_TYPE_ORNONE::format() + ), + 'automovesubmittedtasks' => new external_value( + PARAM_TEXT, + 'The kanban column to move a task to if submitted '.KANBANCOL_TYPE_ORNONE::format() + ), + 'automoveoverduetasks' => new external_value( + PARAM_TEXT, + 'The kanban column to move a task to if overdue '.KANBANCOL_TYPE_ORNONE::format() + ), 'capabilities' => new external_value(PARAM_INT, 'The capabilities of the user represented as a bitmask value'), 'vintage' => new external_value(PARAM_TEXT, 'The vintage of the user', VALUE_DEFAULT), 'email' => new external_value(PARAM_TEXT, 'The email address of the user'), diff --git a/lbplanner/services/kanban/get_board.php b/lbplanner/services/kanban/get_board.php index 032596df..81f24764 100644 --- a/lbplanner/services/kanban/get_board.php +++ b/lbplanner/services/kanban/get_board.php @@ -16,13 +16,16 @@ namespace local_lbplanner_services; -use core_external\{external_api, external_function_parameters, external_multiple_structure, external_single_structure, external_value}; -use local_lbplanner\enums\KANBANCOL_TYPE; -use local_lbplanner\enums\KANBANCOL_TYPE_NUMERIC; -use local_lbplanner\enums\MODULE_TYPE; +use core_external\{ + external_api, + external_function_parameters, + external_multiple_structure, + external_single_structure, + external_value, +}; +use local_lbplanner\enums\{KANBANCOL_TYPE,KANBANCOL_TYPE_NUMERIC,MODULE_TYPE}; use local_lbplanner\helpers\kanban_helper; -use local_lbplanner\model\module; -use local_lbplanner\model\user; +use local_lbplanner\model\{module,user}; /** * Returns all entries in the kanban board for the current user. From 0bc5b30682aa287293618c8175a8c15aca959b9a Mon Sep 17 00:00:00 2001 From: Riedler Date: Tue, 2 Sep 2025 06:40:14 +0200 Subject: [PATCH 07/14] =?UTF-8?q?fix:=20kurwa=20ma=C4=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lbplanner/services/kanban/get_board.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lbplanner/services/kanban/get_board.php b/lbplanner/services/kanban/get_board.php index 81f24764..d1a2c6c2 100644 --- a/lbplanner/services/kanban/get_board.php +++ b/lbplanner/services/kanban/get_board.php @@ -23,9 +23,9 @@ external_single_structure, external_value, }; -use local_lbplanner\enums\{KANBANCOL_TYPE,KANBANCOL_TYPE_NUMERIC,MODULE_TYPE}; +use local_lbplanner\enums\{KANBANCOL_TYPE, KANBANCOL_TYPE_NUMERIC, MODULE_TYPE}; use local_lbplanner\helpers\kanban_helper; -use local_lbplanner\model\{module,user}; +use local_lbplanner\model\{module, user}; /** * Returns all entries in the kanban board for the current user. From 19c7855f44b93a55fd55194ace9aaef2742f4868 Mon Sep 17 00:00:00 2001 From: Riedler Date: Tue, 2 Sep 2025 08:17:26 +0200 Subject: [PATCH 08/14] feat: impl kanban_move_module --- lbplanner/classes/helpers/kanban_helper.php | 26 +++++++ lbplanner/db/services.php | 10 +++ lbplanner/services/kanban/move_module.php | 82 +++++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 lbplanner/services/kanban/move_module.php diff --git a/lbplanner/classes/helpers/kanban_helper.php b/lbplanner/classes/helpers/kanban_helper.php index 558fdd35..1f167baa 100644 --- a/lbplanner/classes/helpers/kanban_helper.php +++ b/lbplanner/classes/helpers/kanban_helper.php @@ -49,4 +49,30 @@ public static function get_all_entries_by_user(int $userid): array { return $entries; } + + /** + * Gets specific kanban entry. + * @param int $userid ID of the user to look entries up for + * @param int $cmid ID of the content-module + * @return kanbanentry the kanban entry matching this selection or null if not found + */ + public static function get_entry(int $userid, int $cmid): ?kanbanentry { + global $DB; + + $res = $DB->get_record(self::TABLE, ['userid' => $userid, 'cmid' => $cmid]); + + return $res !== false ? kanbanentry::from_obj($res) : null; + } + + /** + * Sets specific kanban entry. + * @param kanbanentry $entry the entry to set + */ + public static function set_entry(kanbanentry $entry): void { + global $DB; + + $DB->delete_records(self::TABLE, ['userid' => $entry->userid, 'cmid' => $entry->cmid]); + $newid = $DB->insert_record(self::TABLE, $entry->prepare_for_db()); + $entry->set_fresh($newid); + } } diff --git a/lbplanner/db/services.php b/lbplanner/db/services.php index a497ea31..850a4c37 100644 --- a/lbplanner/db/services.php +++ b/lbplanner/db/services.php @@ -404,6 +404,15 @@ 'capabilities' => 'local/lb_planner:student', 'ajax' => true, ], + 'local_lbplanner_kanban_move_module' => [ + 'classname' => 'local_lbplanner_services\kanban_move_module', + 'methodname' => 'move_module', + 'classpath' => 'local/lbplanner/services/kanban/move_module.php', + 'description' => 'Moves a module to a different column on the kanban board', + 'type' => 'write', + 'capabilities' => 'local/lb_planner:student', + 'ajax' => true, + ], ]; $services = [ @@ -450,6 +459,7 @@ 'local_lbplanner_slots_remove_slot_supervisor', 'local_lbplanner_slots_update_slot', 'local_lbplanner_kanban_get_board', + 'local_lbplanner_kanban_move_module', ], 'restrictedusers' => 0, 'enabled' => 1, diff --git a/lbplanner/services/kanban/move_module.php b/lbplanner/services/kanban/move_module.php new file mode 100644 index 00000000..f33b9c29 --- /dev/null +++ b/lbplanner/services/kanban/move_module.php @@ -0,0 +1,82 @@ +. + +namespace local_lbplanner_services; + +use core_external\{external_api, external_function_parameters, external_value}; +use local_lbplanner\enums\KANBANCOL_TYPE; +use local_lbplanner\helpers\kanban_helper; +use local_lbplanner\model\kanbanentry; + +/** + * Moves a module to a different column on the kanban board. + * + * @package local_lbplanner + * @subpackage services_kanban + * @copyright 2025 necodeIT + * @license https://creativecommons.org/licenses/by-nc-sa/4.0/ CC-BY-NC-SA 4.0 International or later + */ +class kanban_move_module extends external_api { + /** + * Parameters for move_module. + * @return external_function_parameters + */ + public static function move_module_parameters(): external_function_parameters { + return new external_function_parameters([ + 'cmid' => new external_value( + PARAM_INT, + 'ID of the module to move', + VALUE_REQUIRED, + null, + NULL_NOT_ALLOWED + ), + 'column' => new external_value( + PARAM_TEXT, + 'name of the target column', + VALUE_REQUIRED, + null, + NULL_NOT_ALLOWED + ), + ]); + } + + /** + * Moves a module to a different column on the kanban board. + * @param int $cmid content-module ID + * @param string $column name of the target column + */ + public static function move_module(int $cmid, string $column): void { + global $USER; + self::validate_parameters( + self::move_module_parameters(), + [ + 'cmid' => $cmid, + 'column' => $column, + ] + ); + + $colnr = KANBANCOL_TYPE::to_numeric($column); + kanban_helper::set_entry(new kanbanentry(0, $USER->id, $cmid, $colnr)); + } + + /** + * Return structure of move_module + * @return null + */ + public static function move_module_returns() { + return null; + } +} From 8b4fd4622c7ff3fc401cf03e70a8dc5b32d6bf95 Mon Sep 17 00:00:00 2001 From: Riedler Date: Tue, 2 Sep 2025 21:08:54 +0200 Subject: [PATCH 09/14] fix: upgrade.php --- lbplanner/db/upgrade.php | 18 +++++++++++++++++- lbplanner/version.php | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lbplanner/db/upgrade.php b/lbplanner/db/upgrade.php index 9eab1bc5..e8294b7f 100644 --- a/lbplanner/db/upgrade.php +++ b/lbplanner/db/upgrade.php @@ -19,7 +19,7 @@ * * @package local_lbplanner * @subpackage db - * @copyright 2024 NecodeIT + * @copyright 2025 NecodeIT * @license https://creativecommons.org/licenses/by-nc-sa/4.0/ CC-BY-NC-SA 4.0 International or later */ @@ -32,9 +32,25 @@ * @return bool true */ function xmldb_local_lbplanner_upgrade($oldversion): bool { + global $DB; if ($oldversion < 202502110011) { config_helper::remove_customfield(); config_helper::add_customfield(); } + if ($oldversion < 202509020000) { + $dbman = $DB->get_manager(); + + $table = new xmldb_table('local_lbplanner_users'); + $f1 = new xmldb_field('showcolumncolors', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, false, 1, 'ekenabled'); + $f2 = new xmldb_field('automovecompletedtasks', XMLDB_TYPE_TEXT, null, null, null, null, null, 'showcolumncolors'); + $f3 = new xmldb_field('automovesubmittedtasks', XMLDB_TYPE_TEXT, null, null, null, null, null, 'automovecompletedtasks'); + $f4 = new xmldb_field('automoveoverduetasks', XMLDB_TYPE_TEXT, null, null, null, null, null, 'automovesubmittedtasks'); + + $dbman->add_field($table, $f1); + $dbman->add_field($table, $f2); + $dbman->add_field($table, $f3); + $dbman->add_field($table, $f4); + upgrade_plugin_savepoint(true, 202509020000, 'local', 'lbplanner'); + } return true; } diff --git a/lbplanner/version.php b/lbplanner/version.php index 3754f714..dbff048f 100644 --- a/lbplanner/version.php +++ b/lbplanner/version.php @@ -28,7 +28,7 @@ $plugin->maturity = MATURITY_BETA; $plugin->component = 'local_lbplanner'; $plugin->release = '1.0.2'; -$plugin->version = 202509010000; +$plugin->version = 202509020000; $plugin->dependencies = [ // Depend upon version 2023110600 of local_modcustomfields. 'local_modcustomfields' => 2023110600, From 46a3ba8e3ea0573aa820b3eac09835ae7067f988 Mon Sep 17 00:00:00 2001 From: Riedler Date: Tue, 2 Sep 2025 23:22:47 +0200 Subject: [PATCH 10/14] fix: fixed upgrade logic --- lbplanner/db/upgrade.php | 22 ++++++++++++++++++++-- lbplanner/version.php | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lbplanner/db/upgrade.php b/lbplanner/db/upgrade.php index e8294b7f..75fcee5e 100644 --- a/lbplanner/db/upgrade.php +++ b/lbplanner/db/upgrade.php @@ -33,13 +33,12 @@ */ function xmldb_local_lbplanner_upgrade($oldversion): bool { global $DB; + $dbman = $DB->get_manager(); if ($oldversion < 202502110011) { config_helper::remove_customfield(); config_helper::add_customfield(); } if ($oldversion < 202509020000) { - $dbman = $DB->get_manager(); - $table = new xmldb_table('local_lbplanner_users'); $f1 = new xmldb_field('showcolumncolors', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, false, 1, 'ekenabled'); $f2 = new xmldb_field('automovecompletedtasks', XMLDB_TYPE_TEXT, null, null, null, null, null, 'showcolumncolors'); @@ -52,5 +51,24 @@ function xmldb_local_lbplanner_upgrade($oldversion): bool { $dbman->add_field($table, $f4); upgrade_plugin_savepoint(true, 202509020000, 'local', 'lbplanner'); } + if ($oldversion < 202509020001) { + $table = new xmldb_table('local_lbplanner_kanbanentries'); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('cmid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('column', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, null); + + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); + $table->add_key('userid', XMLDB_KEY_FOREIGN, ['userid'], 'local_lbplanner_users', ['userid']); + + $table->add_index('uniqueentry', XMLDB_INDEX_UNIQUE, ['userid', 'cmid']); + + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + upgrade_plugin_savepoint(true, 202509020001, 'local', 'lbplanner'); + } return true; } diff --git a/lbplanner/version.php b/lbplanner/version.php index dbff048f..01b79d29 100644 --- a/lbplanner/version.php +++ b/lbplanner/version.php @@ -28,7 +28,7 @@ $plugin->maturity = MATURITY_BETA; $plugin->component = 'local_lbplanner'; $plugin->release = '1.0.2'; -$plugin->version = 202509020000; +$plugin->version = 202509020001; $plugin->dependencies = [ // Depend upon version 2023110600 of local_modcustomfields. 'local_modcustomfields' => 2023110600, From 8d7bebd889524bb9ea4b2f585fdc62c293ef3b7c Mon Sep 17 00:00:00 2001 From: Riedler Date: Wed, 3 Sep 2025 00:46:58 +0200 Subject: [PATCH 11/14] fix: workaround for moodle bug --- lbplanner/classes/helpers/kanban_helper.php | 6 ++++-- lbplanner/classes/model/kanbanentry.php | 20 -------------------- lbplanner/version.php | 2 +- 3 files changed, 5 insertions(+), 23 deletions(-) diff --git a/lbplanner/classes/helpers/kanban_helper.php b/lbplanner/classes/helpers/kanban_helper.php index 1f167baa..9119f9a6 100644 --- a/lbplanner/classes/helpers/kanban_helper.php +++ b/lbplanner/classes/helpers/kanban_helper.php @@ -69,10 +69,12 @@ public static function get_entry(int $userid, int $cmid): ?kanbanentry { * @param kanbanentry $entry the entry to set */ public static function set_entry(kanbanentry $entry): void { - global $DB; + global $DB, $CFG; $DB->delete_records(self::TABLE, ['userid' => $entry->userid, 'cmid' => $entry->cmid]); - $newid = $DB->insert_record(self::TABLE, $entry->prepare_for_db()); + $table = $CFG->prefix . self::TABLE; + // moodle is too stupid to compensate for 'column' being a keyword so I need to shit my own ass manually + $newid = $DB->execute("INSERT INTO {$table} VALUES (null,?,?,?)", [$entry->userid, $entry->cmid, $entry->column]); $entry->set_fresh($newid); } } diff --git a/lbplanner/classes/model/kanbanentry.php b/lbplanner/classes/model/kanbanentry.php index ba7d99f5..f6f270a7 100644 --- a/lbplanner/classes/model/kanbanentry.php +++ b/lbplanner/classes/model/kanbanentry.php @@ -83,26 +83,6 @@ public function set_fresh(int $id) { $this->id = $id; } - /** - * Prepares data for the DB endpoint. - * doesn't set ID if it's 0 - * - * @return object a representation of this kbbe and its data - */ - public function prepare_for_db(): object { - $obj = new \stdClass(); - - $obj->userid = $this->userid; - $obj->cmid = $this->cmid; - $obj->column = $this->column; - - if ($this->id !== 0) { - $obj->id = $this->id; - } - - return $obj; - } - /** * Prepares data for the API endpoint. * diff --git a/lbplanner/version.php b/lbplanner/version.php index 01b79d29..b4b138a3 100644 --- a/lbplanner/version.php +++ b/lbplanner/version.php @@ -28,7 +28,7 @@ $plugin->maturity = MATURITY_BETA; $plugin->component = 'local_lbplanner'; $plugin->release = '1.0.2'; -$plugin->version = 202509020001; +$plugin->version = 202509020003; $plugin->dependencies = [ // Depend upon version 2023110600 of local_modcustomfields. 'local_modcustomfields' => 2023110600, From d19bc11335331ef7f2525098cd8eb1d90a1cfbd2 Mon Sep 17 00:00:00 2001 From: Riedler Date: Wed, 3 Sep 2025 00:47:18 +0200 Subject: [PATCH 12/14] fix: embarassing coding mistake --- lbplanner/services/kanban/get_board.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbplanner/services/kanban/get_board.php b/lbplanner/services/kanban/get_board.php index d1a2c6c2..ae275e82 100644 --- a/lbplanner/services/kanban/get_board.php +++ b/lbplanner/services/kanban/get_board.php @@ -67,7 +67,7 @@ public static function get_board(): array { continue; } } - $sorted[$entry->column] = $entry->cmid; + array_push($sorted[$entry->column], $entry->cmid); } return [ From bd37e258e367f5a35790b9e48ef24867306a1383 Mon Sep 17 00:00:00 2001 From: Riedler Date: Wed, 3 Sep 2025 00:53:03 +0200 Subject: [PATCH 13/14] fix: don't add kanban entry to table if backlog --- lbplanner/classes/helpers/kanban_helper.php | 11 +++++++---- lbplanner/version.php | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lbplanner/classes/helpers/kanban_helper.php b/lbplanner/classes/helpers/kanban_helper.php index 9119f9a6..c55ebcff 100644 --- a/lbplanner/classes/helpers/kanban_helper.php +++ b/lbplanner/classes/helpers/kanban_helper.php @@ -16,6 +16,7 @@ namespace local_lbplanner\helpers; +use local_lbplanner\enums\KANBANCOL_TYPE_NUMERIC; use local_lbplanner\model\kanbanentry; /** @@ -72,9 +73,11 @@ public static function set_entry(kanbanentry $entry): void { global $DB, $CFG; $DB->delete_records(self::TABLE, ['userid' => $entry->userid, 'cmid' => $entry->cmid]); - $table = $CFG->prefix . self::TABLE; - // moodle is too stupid to compensate for 'column' being a keyword so I need to shit my own ass manually - $newid = $DB->execute("INSERT INTO {$table} VALUES (null,?,?,?)", [$entry->userid, $entry->cmid, $entry->column]); - $entry->set_fresh($newid); + if ($entry->column !== KANBANCOL_TYPE_NUMERIC::BACKLOG) { + $table = $CFG->prefix . self::TABLE; + // moodle is too stupid to compensate for 'column' being a keyword so I need to shit my own ass manually + $newid = $DB->execute("INSERT INTO {$table} VALUES (null,?,?,?)", [$entry->userid, $entry->cmid, $entry->column]); + $entry->set_fresh($newid); + } } } diff --git a/lbplanner/version.php b/lbplanner/version.php index b4b138a3..88cb9940 100644 --- a/lbplanner/version.php +++ b/lbplanner/version.php @@ -28,7 +28,7 @@ $plugin->maturity = MATURITY_BETA; $plugin->component = 'local_lbplanner'; $plugin->release = '1.0.2'; -$plugin->version = 202509020003; +$plugin->version = 202509020004; $plugin->dependencies = [ // Depend upon version 2023110600 of local_modcustomfields. 'local_modcustomfields' => 2023110600, From 3cc1a32db7b26d958f23868fcfba145f3a043917 Mon Sep 17 00:00:00 2001 From: Riedler Date: Wed, 3 Sep 2025 01:05:29 +0200 Subject: [PATCH 14/14] fix: made moodle codechecker happy --- lbplanner/classes/helpers/kanban_helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbplanner/classes/helpers/kanban_helper.php b/lbplanner/classes/helpers/kanban_helper.php index c55ebcff..3999067c 100644 --- a/lbplanner/classes/helpers/kanban_helper.php +++ b/lbplanner/classes/helpers/kanban_helper.php @@ -75,7 +75,7 @@ public static function set_entry(kanbanentry $entry): void { $DB->delete_records(self::TABLE, ['userid' => $entry->userid, 'cmid' => $entry->cmid]); if ($entry->column !== KANBANCOL_TYPE_NUMERIC::BACKLOG) { $table = $CFG->prefix . self::TABLE; - // moodle is too stupid to compensate for 'column' being a keyword so I need to shit my own ass manually + // Moodle is too stupid to compensate for 'column' being a keyword so I need to shit my own ass manually. $newid = $DB->execute("INSERT INTO {$table} VALUES (null,?,?,?)", [$entry->userid, $entry->cmid, $entry->column]); $entry->set_fresh($newid); }