diff --git a/.kateproject b/.kateproject new file mode 100644 index 00000000..b9ffc1c6 --- /dev/null +++ b/.kateproject @@ -0,0 +1,9 @@ +{ + "lspclient": { + "servers": { + "php" : { + "root": "../moodle/" + } + } + } +} \ No newline at end of file diff --git a/lbplanner/classes/enums/WEEKDAY.php b/lbplanner/classes/enums/WEEKDAY.php new file mode 100644 index 00000000..7d5a5b3c --- /dev/null +++ b/lbplanner/classes/enums/WEEKDAY.php @@ -0,0 +1,66 @@ +. +/** + * enum for weekdays + * (cringe, ik, but we need these defined concretely) + * + * @package local_lbplanner + * @subpackage enums + * @copyright 2024 NecodeIT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_lbplanner\enums; + +// TODO: revert to native enums once we migrate to php8. + +use local_lbplanner\polyfill\Enum; + +/** + * All the days of the week. + * All seven of them. + * Yup. + */ +class WEEKDAY extends Enum { + /** + * monday + */ + const MONDAY = 1; + /** + * tuesday + */ + const TUESDAY = 2; + /** + * wednesday + */ + const WEDNESDAY = 3; + /** + * thursday + */ + const THURSDAY = 4; + /** + * friday + */ + const FRIDAY = 5; + /** + * saturday + */ + const SATURDAY = 6; + /** + * sunday + */ + const SUNDAY = 7; +} diff --git a/lbplanner/classes/helpers/slot_helper.php b/lbplanner/classes/helpers/slot_helper.php new file mode 100644 index 00000000..f487d4b2 --- /dev/null +++ b/lbplanner/classes/helpers/slot_helper.php @@ -0,0 +1,270 @@ +. +/** + * Provides helper classes for any tables related with the slot booking function of the app + * + * @package local_lbplanner + * @subpackage helpers + * @copyright 2024 NecodeIT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_lbplanner\helpers; + +use DateInterval; +use DateTime; +use DateTimeImmutable; +use DateTimeInterface; +use external_api; +use local_lbplanner\enums\WEEKDAY; +use local_lbplanner\model\{slot, reservation, slot_filter}; + +/** + * Provides helper methods for any tables related with the planning function of the app + */ +class slot_helper { + /** + * how far into the future a user can reserve a slot + */ + const RESERVATION_RANGE_USER = 3; + /** + * how far into the future a supervisor can reserve a slot for a user + */ + const RESERVATION_RANGE_SUPERVISOR = 7; + /** + * school units according to untis, in H:i format + */ + const SCHOOL_UNITS = [ + null, + '08:00', + '08:50', + '09:50', + '10:40', + '11:30', + '12:30', + '13:20', + '14:10', + '15:10', + '16:00', + '17:00', // All units after this point are 45min long instead of the usual 50. + '17:45', // We will assume 50min anyway because it's easier that way. + '18:45', + '19:30', + '20:15', + '21:00', + ]; + /** + * local_lbplanner_slots table. + */ + const TABLE_SLOTS = 'local_lbplanner_slots'; + /** + * local_lbplanner_reservations table. + */ + const TABLE_RESERVATIONS = 'local_lbplanner_reservations'; + /** + * local_lbplanner_slot_courses table. + */ + const TABLE_SLOT_FILTERS = 'local_lbplanner_slot_courses'; + /** + * local_lbplanner_supervisors table. + */ + const TABLE_SUPERVISORS = 'local_lbplanner_supervisors'; + + /** + * Returns a list of all slots. + * + * @return slot[] An array of the slots. + */ + public static function get_all_slots(): array { + global $DB; + $slots = $DB->get_records(self::TABLE_SLOTS, []); + + $slotsobj = []; + foreach ($slots as $slot) { + array_push($slotsobj, new slot(...$slot)); + } + + return $slotsobj; + } + + /** + * Returns a list of all slots belonging to a supervisor. + * @param int $supervisorid userid of the supervisor in question + * + * @return slot[] An array of the slots. + */ + public static function get_supervisor_slots(int $supervisorid): array { + global $DB; + + $slots = $DB->get_records_sql( + 'SELECT slot.* FROM {'.self::TABLE_SLOTS.'} as slot'. + 'INNER JOIN '.self::TABLE_SUPERVISORS.' as supervisor ON supervisor.slotid=slot.id'. + 'WHERE supervisor.userid=?', + [$supervisorid] + ); + + $slotsobj = []; + foreach ($slots as $slot) { + array_push($slotsobj, new slot(...$slot)); + } + + return $slotsobj; + } + + /** + * Returns a singular slot. + * @param int $slotid ID of the slot + * + * @return slot the requested slot + */ + public static function get_slot(int $slotid): slot { + global $DB; + $slot = $DB->get_record(self::TABLE_SLOTS, ['id' => $slotid]); + + return new slot(...$slot); + } + + /** + * Returns reservations for a slot. + * @param int $slotid ID of the slot + * + * @return reservation[] the requested reservations + */ + public static function get_reservations_for_slot(int $slotid): array { + global $DB; + $reservations = $DB->get_records(self::TABLE_RESERVATIONS, ['slotid' => $slotid]); + + $reservationsobj = []; + foreach ($reservations as $reservation) { + $reservation['date'] = new DateTimeImmutable($reservation['date']); + array_push($reservationsobj, new reservation(...$reservation)); + } + + return $reservationsobj; + } + + /** + * Returns filters for a slot. + * @param int $slotid ID of the slot + * + * @return slot_filter[] the requested filters + */ + public static function get_filters_for_slot(int $slotid): array { + global $DB; + $filters = $DB->get_records(self::TABLE_SLOT_FILTERS, ['slotid' => $slotid]); + + $filtersobj = []; + foreach ($filters as $filter) { + array_push($filtersobj, new slot_filter(...$filter)); + } + + return $filtersobj; + } + + /** + * Filters an array of slots for the slots that the user can theoretically reserve + * NOTE: not taking into account time or fullness, only filters i.e. users' class and courses + * @param slot[] $allslots the slots to filter + * @param mixed $user a user object - e.g. $USER or a user object from the database + * @return slot[] the filtered slot array + */ + public static function filter_slots_for_user(array $allslots, mixed $user): array { + $mycourses = external_api::call_external_function('local_lbplanner_courses_get_all_courses', ['userid' => $user->id]); + $mycourseids = []; + foreach ($mycourses as $course) { + array_push($mycourseids, $course->courseid); + } + + $slots = []; + foreach ($allslots as $slot) { + $filters = self::get_filters_for_slot($slot->id); + foreach ($filters as $filter) { + // Checking for course ID. + if (!is_null($filter->courseid) && !in_array($filter->courseid, $mycourseids)) { + continue; + } + // TODO: replace address with cohorts. + // Checking for vintage. + if (!is_null($filter->vintage) && $user->address !== $filter->vintage) { + continue; + } + // If all filters passed, add slot to my slots and break. + array_push($slots, $slot); + break; + } + } + return $slots; + } + + /** + * Filters an array of slots for a timerange around now. + * @param slot[] $allslots the slots to filter + * @param int $range how many days in the future the slot is allowed to be + * @return slot[] the filtered slot array + */ + public static function filter_slots_for_time(array $allslots, int $range): array { + $now = new DateTimeImmutable(); + $slots = []; + // Calculate date and time each slot happens next, and add it to the return list if within reach from today. + foreach ($allslots as $slot) { + $slotdatetime = self::calculate_slot_datetime($slot, $now); + + if ($now->diff($slotdatetime)->days <= $range) { + array_push($slots, $slot->prepare_for_api()); + } + } + return $slots; + } + + /** + * calculates when a slot is to happen next + * @param slot $slot the slot + * @param DateTimeInterface $now the point in time representing now + * @return DateTimeImmutable the next time this slot will occur + */ + public static function calculate_slot_datetime(slot $slot, DateTimeInterface $now): DateTimeImmutable { + $slotdaytime = self::SCHOOL_UNITS[$slot->startunit]; + // NOTE: format and fromFormat use different date formatting conventions + $slotdatetime = DateTime::createFromFormat('YY-MM-DD tHH:MM', $now->format('Y-m-d ').$slotdaytime); + // Move to next day this weekday occurs (doesn't move if it's the same as today). + $slotdatetime->modify('this '.WEEKDAY::name_from($slot->weekday)); + + // Check if slot is before now (because time of day and such) and move it a week into the future if so. + if ($now->diff($slotdatetime)->invert === 1) { + $slotdatetime->add(new DateInterval('P1W')); + } + + return new DateTimeImmutable($slotdatetime); + } + + /** + * Returns a list of all slots belonging to a supervisor. + * @param int $supervisorid userid of the supervisor in question + * + * @return slot[] An array of the slots. + */ + public static function check_slot_supervisor(int $supervisorid, int $slotid): bool { + global $DB; + + $result = $DB->get_record_sql( + 'SELECT supervisor.userid FROM '.self::TABLE_SUPERVISORS.' as supervisor'. + 'WHERE supervisor.userid=? AND supervisor.slotid=?', + [$supervisorid, $slotid] + ); + + return $result !== false; + } +} diff --git a/lbplanner/classes/model/reservation.php b/lbplanner/classes/model/reservation.php new file mode 100644 index 00000000..681fc3e4 --- /dev/null +++ b/lbplanner/classes/model/reservation.php @@ -0,0 +1,146 @@ +. +/** + * Model for a reservation + * + * @package local_lbplanner + * @subpackage helpers + * @copyright 2024 NecodeIT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_lbplanner\model; + +use DateTimeImmutable; + +use external_single_structure; +use external_value; + +use local_lbplanner\model\slot; +use local_lbplanner\helpers\slot_helper; + +/** + * Model class for reservation + */ +class reservation { + /** + * @var int $id ID of reservation + */ + public int $id; + /** + * @var int $slotid ID of the linked slot + */ + public int $slotid; + /** + * @var DateTimeImmutable $date date this reservation is on (time will be ignored) + */ + public DateTimeImmutable $date; + /** + * @var int $userid ID of the user this reservation is for + */ + public int $userid; + /** + * @var int $reserverid ID of the user who submitted this reservation (either pupil or supervisor) + */ + public int $reserverid; + /** + * @var ?slot $slot the linked slot (gets filled in by helper functions) + */ + private ?slot $slot; + /** + * @var ?DateTimeImmutable $datetime the date this reservation is for, with time filled in + */ + private ?DateTimeImmutable $datetime; + + /** + * Constructs a reservation + * @param int $id ID of reservation + * @param int $slotid ID of the linked slot + * @param DateTimeImmutable $date date this reservation is on (time will be ignored) + * @param int $userid ID of the user this reservation is for + * @param int $reserverid ID of the user who submitted this reservation (either pupil or supervisor) + * @link slot + */ + public function __construct(int $id, int $slotid, DateTimeImmutable $date, int $userid, int $reserverid) { + $this->id = $id; + $this->slotid = $slotid; + $this->date = $date; + $this->userid = $userid; + $this->reserverid = $reserverid; + $this->slot = null; + } + + /** + * Returns the associated slot. + * + * @return slot the associated slot + */ + public function get_slot(): slot { + if (is_null($this->slot)) { + $this->slot = slot_helper::get_slot($this->slotid); + } + + return $this->slot; + } + + /** + * Prepares data for the DB endpoint. + * + * @return object a representation of this reservation and its data + */ + public function prepare_for_db(): object { + $obj = new \stdClass(); + + $obj->slotid = $this->slotid; + $obj->date = $this->date; + $obj->userid = $this->userid; + $obj->reserverid = $this->reserverid; + + 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, + 'slotid' => $this->slotid, + 'datetime' => $this->date->format('Y-m-d'), + 'userid' => $this->userid, + 'reserverid' => $this->reserverid, + ]; + } + + /** + * Returns the data structure of a reservation for the API. + * + * @return external_single_structure The data structure of a reservation for the API. + */ + public static function api_structure(): external_single_structure { + return new external_single_structure( + [ + 'id' => new external_value(PARAM_INT, 'reservation ID'), + 'slotid' => new external_value(PARAM_INT, 'ID of associated slot'), + 'date' => new external_value(PARAM_TEXT, 'date of the reservation in YYYY-MM-DD (as per ISO-8601)'), + 'userid' => new external_value(PARAM_INT, 'ID of the user this reservation is for'), + 'reserverid' => new external_value(PARAM_INT, 'ID of the user who submitted this reservation'), + ] + ); + } +} diff --git a/lbplanner/classes/model/slot.php b/lbplanner/classes/model/slot.php new file mode 100644 index 00000000..8e2fa1e0 --- /dev/null +++ b/lbplanner/classes/model/slot.php @@ -0,0 +1,177 @@ +. +/** + * Model for a slot + * + * @package local_lbplanner + * @subpackage helpers + * @copyright 2024 NecodeIT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_lbplanner\model; + +use local_lbplanner\enums\WEEKDAY; +use local_lbplanner\helpers\slot_helper; + +use external_single_structure; +use external_value; + +/** + * Model class for slot + */ +class slot { + /** + * @var int $id ID of slot + */ + public int $id; + /** + * @var int $startunit Unit this slot starts in + */ + public int $startunit; + /** + * @var int $duration duration of slot in units + */ + public int $duration; + /** + * @var int $weekday weekday this slot occurs in + */ + public int $weekday; + /** + * @var string $room room this slot is for + */ + public string $room; + /** + * @var int $size how many pupils fit in this slot + */ + public int $size; + /** + * @var ?int $fullness how many pupils have already reserved this slot (gets filled in by helper functions) + */ + private ?int $fullness; + /** + * @var ?bool $forcuruser whether the current user has reserved this slot (gets filled in by helper functions) + */ + private ?bool $forcuruser; + + /** + * Constructs a new Slot + * @param int $id ID of slot + * @param int $startunit Unit this slot starts in + * @param int $duration duration of slot in units + * @param int $weekday weekday this slot occurs in + * @param string $room room this slot is for + * @param int $size how many pupils fit in this slot + * @link slot_helper::SCHOOL_UNITS + * @link WEEKDAY + */ + public function __construct(int $id, int $startunit, int $duration, int $weekday, string $room, int $size) { + $this->id = $id; + assert($startunit > 0); + $this->startunit = $startunit; + assert($duration > 0); + $this->duration = $duration; + $this->weekday = WEEKDAY::from($weekday); + assert(strlen($room) > 0 && strlen($room) <= 7); + $this->room = $room; + assert($size >= 0); // Make it technically possible to not allow any students in a room to temporarily disable the slot. + $this->size = $size; + $this->fullness = null; + $this->forcuruser = null; + } + + /** + * Returns how many reservations there are for this slot. + * + * @return int fullness + */ + public function get_fullness(): int { + if (is_null($this->fullness)) { + $this->check_reservations(); + } + + return $this->fullness; + } + + /** + * Returns whether the current user has a reservation for this slot. + * + * @return bool forcuruser + */ + public function get_forcuruser(): bool { + if (is_null($this->forcuruser)) { + $this->check_reservations(); + } + + return $this->forcuruser; + } + + /** + * Prepares data for the API endpoint. + * + * @return array a representation of this slot and its data + */ + public function prepare_for_api(): array { + return [ + 'id' => $this->id, + 'startunit' => $this->startunit, + 'duration' => $this->duration, + 'weekday' => $this->weekday, + 'room' => $this->room, + 'size' => $this->size, + 'fullness' => $this->get_fullness(), + 'forcuruser' => $this->get_forcuruser(), + ]; + } + + /** + * Returns the data structure of a slot for the API. + * + * @return external_single_structure The data structure of a slot for the API. + */ + public static function api_structure(): external_single_structure { + return new external_single_structure( + [ + 'id' => new external_value(PARAM_INT, 'slot ID'), + 'startunit' => new external_value(PARAM_INT, 'unit this slot starts in (8:00 is unit 1)'), + 'duration' => new external_value(PARAM_INT, 'duration of the slot in units'), + 'weekday' => new external_value(PARAM_INT, 'The day this unit repeats weekly: '.WEEKDAY::format()), + 'room' => new external_value(PARAM_TEXT, 'The room this slot is for'), + 'size' => new external_value(PARAM_INT, 'total capacity of the slot'), + 'fullness' => new external_value(PARAM_INT, 'how many people have already reserved this slot'), + 'forcuruser' => new external_value(PARAM_BOOL, 'whether the current user has reserved this slot'), + ] + ); + } + + /** + * Queries reservations for this slot and fills in internal data with that info. + */ + private function check_reservations(): void { + global $USER; + $reservations = slot_helper::get_reservations_for_slot($this->id); + + $this->fullness = count($reservations); + + foreach ($reservations as $reservation) { + if ($reservation->userid === $USER['id']) { + $this->forcuruser = true; + return; + } + } + $this->forcuruser = false; + } +} diff --git a/lbplanner/classes/model/slot_filter.php b/lbplanner/classes/model/slot_filter.php new file mode 100644 index 00000000..d798f2c7 --- /dev/null +++ b/lbplanner/classes/model/slot_filter.php @@ -0,0 +1,64 @@ +. +/** + * Model for a filter for slots + * + * @package local_lbplanner + * @subpackage helpers + * @copyright 2024 NecodeIT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_lbplanner\model; + +/** + * Model class for a filter for slots + */ +class slot_filter { + /** + * @var int $id ID of filter + */ + public int $id; + /** + * @var int $id ID of linked slot + */ + public int $slotid; + /** + * @var ?int $id ID of linked course or null if any + */ + public ?int $courseid; + /** + * @var ?string $vintage linked class or null if any + */ + public ?string $vintage; + + /** + * Constructs new slot_filter + * @param int $id ID of filter + * @param int $slotid ID of linked slot + * @param ?int $courseid ID of linked course or null if any + * @param ?string $vintage linked class or null if any + */ + public function __construct(int $id, int $slotid, ?int $courseid, ?string $vintage) { + $this->id = $id; + $this->slotid = $slotid; + $this->courseid = $courseid; + if (!is_null($vintage)) { + assert(strlen($vintage) <= 7); + } + $this->vintage = $vintage; + } +} diff --git a/lbplanner/classes/polyfill/Enum.php b/lbplanner/classes/polyfill/Enum.php index 1d8b7b19..e5c4d2a3 100644 --- a/lbplanner/classes/polyfill/Enum.php +++ b/lbplanner/classes/polyfill/Enum.php @@ -35,15 +35,36 @@ class Enum { /** * tries to match the passed value to one of the enum values * @param mixed $value the value to be matched - * @return mixed either the matching enum value or null if not found + * @param bool $try whether to return null (true) or throw an error (false) if not found + * @return ?EnumCase the matching enum case or null if not found and $try==true + * @throws ValueError if not found and $try==false */ - public static function try_from(mixed $value): mixed { + private static function find(mixed $value, bool $try): ?EnumCase { foreach (static::cases() as $case) { if ($case->value === $value) { - return $value; + return $case; } } - return null; + + if ($try) { + return null; + } else { + throw new ValueError("value {$value} cannot be represented as a value in enum ".static::class); + } + } + /** + * tries to match the passed value to one of the enum values + * @param mixed $value the value to be matched + * @return mixed either the matching enum value or null if not found + */ + public static function try_from(mixed $value): mixed { + // TODO: replace with nullsafe operator in php8. + $case = static::find($value, true); + if (is_null($case)) { + return null; + } else { + return $case->value; + } } /** * tries to match the passed value to one of the enum values @@ -52,13 +73,31 @@ public static function try_from(mixed $value): mixed { * @throws ValueError if not found */ public static function from(mixed $value): mixed { - foreach (static::cases() as $case) { - if ($case->value === $value) { - return $value; - } + return static::find($value, false)->value; + } + /** + * tries to match the passed value to one of the enum values + * @param mixed $value the value to be matched + * @return string the matching enum case name + * @throws mixed either the matching enum case name or null if not found + */ + public static function try_name_from(mixed $value): ?string { + // TODO: replace with nullsafe operator in php8. + $case = static::find($value, true); + if (is_null($case)) { + return null; + } else { + return $case->name; } - - throw new ValueError("value {$value} cannot be represented as a value in enum ".static::class); + } + /** + * tries to match the passed value to one of the enum values + * @param mixed $value the value to be matched + * @return string the matching enum case name + * @throws ValueError if not found + */ + public static function name_from(mixed $value): string { + return static::find($value, false)->name; } /** * Returns an array of all the cases that exist in this enum @@ -76,7 +115,7 @@ public static function cases(): array { /** * Formats all possible enum values into a string * Example: - * (31=>RED,32=>GREEN,33=>YELLOW) + * [31=>RED,32=>GREEN,33=>YELLOW] * @return string the resulting string */ public static function format(): string { diff --git a/lbplanner/db/install.xml b/lbplanner/db/install.xml index f0d274fb..13c855e2 100644 --- a/lbplanner/db/install.xml +++ b/lbplanner/db/install.xml @@ -114,5 +114,58 @@ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+ + + + + + + + + + + +
diff --git a/lbplanner/db/services.php b/lbplanner/db/services.php index 2bf60516..1c5c5859 100644 --- a/lbplanner/db/services.php +++ b/lbplanner/db/services.php @@ -286,6 +286,42 @@ 'capabilities' => '', 'ajax' => true, ], + 'local_lbplanner_slots_get_my_slot' => [ + 'classname' => 'local_lbplanner_services\slots_get_my_slot', + 'methodname' => 'get_my_slot', + 'classpath' => 'local/lbplanner/services/slots/get_my_slot.php', + 'description' => 'Get all slots the user can theoretically reserve.', + 'type' => 'read', + 'capabilities' => 'local/lb_planner:student', + 'ajax' => true, + ], + 'local_lbplanner_slots_get_student_slots' => [ + 'classname' => 'local_lbplanner_services\slots_get_student_slots', + 'methodname' => 'get_student_slots', + 'classpath' => 'local/lbplanner/services/slots/get_student_slots.php', + 'description' => 'Get all slots a supervisor can theoretically reserve for a student.', + 'type' => 'read', + 'capabilities' => 'local/lb_planner:student', + 'ajax' => true, + ], + 'local_lbplanner_slots_get_supervisor_slots' => [ + 'classname' => 'local_lbplanner_services\slots_get_supervisor_slots', + 'methodname' => 'get_supervisor_slots', + 'classpath' => 'local/lbplanner/services/slots/get_supervisor_slots.php', + 'description' => 'Get all slots belonging to the supervisor.', + 'type' => 'read', + 'capabilities' => 'local/lb_planner:student', + 'ajax' => true, + ], + 'local_lbplanner_slots_book_reservation' => [ + 'classname' => 'local_lbplanner_services\slots_book_reservation', + 'methodname' => 'book_reservation', + 'classpath' => 'local/lbplanner/services/slots/book_reservation.php', + 'description' => 'Book a reservation', + 'type' => 'write', + 'capabilities' => 'local/lb_planner:student', + 'ajax' => true, + ], ]; $services = [ @@ -321,6 +357,10 @@ 'local_lbplanner_plan_accept_invite', 'local_lbplanner_plan_decline_invite', 'local_lbplanner_config_get_version', + 'local_lbplanner_slots_get_my_slot', + 'local_lbplanner_slots_get_student_slots', + 'local_lbplanner_slots_get_supervisor_slots', + 'local_lbplanner_slots_book_reservation', ], 'restrictedusers' => 0, 'enabled' => 1, diff --git a/lbplanner/services/plan/set_deadline.php b/lbplanner/services/plan/set_deadline.php index 9ad07a8f..aac4d86d 100644 --- a/lbplanner/services/plan/set_deadline.php +++ b/lbplanner/services/plan/set_deadline.php @@ -34,7 +34,7 @@ class plan_set_deadline extends external_api { * Parameters for set_deadline. * @return external_function_parameters */ - public static function set_deadline_parameters() { + public static function set_deadline_parameters(): external_function_parameters { return new external_function_parameters([ 'moduleid' => new external_value( PARAM_INT, @@ -69,7 +69,7 @@ public static function set_deadline_parameters() { * @return void * @throws \moodle_exception when access denied */ - public static function set_deadline(int $moduleid, int $deadlinestart, int $deadlineend): external_function_parameters { + public static function set_deadline(int $moduleid, int $deadlinestart, int $deadlineend) { global $DB, $USER; self::validate_parameters( diff --git a/lbplanner/services/slots/book_reservation.php b/lbplanner/services/slots/book_reservation.php new file mode 100644 index 00000000..dab33ab3 --- /dev/null +++ b/lbplanner/services/slots/book_reservation.php @@ -0,0 +1,142 @@ +. + +namespace local_lbplanner_services; + +use DateTimeImmutable; + +use core_user; +use external_api; +use external_function_parameters; +use external_single_structure; +use external_value; + +use local_lbplanner\helpers\slot_helper; +use local_lbplanner\model\reservation; + +/** + * Returns all slots the user can theoretically reserve. + * This does not include times the user has already reserved a slot for. + * + * @package local_lbplanner + * @subpackage services_plan + * @copyright 2024 necodeIT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class slots_book_reservation extends external_api { + /** + * Parameters for book_reservation. + * @return external_function_parameters + */ + public static function book_reservation_parameters(): external_function_parameters { + global $USER; + return new external_function_parameters([ + 'slotid' => new external_value( + PARAM_INT, + 'ID of the slot for which a reservation is being requested', + VALUE_REQUIRED, + null, + NULL_NOT_ALLOWED + ), + 'date' => new external_value( + PARAM_TEXT, + 'date of the reservation in YYYY-MM-DD (as per ISO-8601)', + VALUE_REQUIRED, + null, + NULL_NOT_ALLOWED + ), + 'userid' => new external_value( + PARAM_INT, + 'the user to reserve this slot for', + VALUE_OPTIONAL, + $USER->id, + NULL_NOT_ALLOWED + ), + ]); + } + + /** + * Returns slots the current user is supposed to see + */ + public static function book_reservation(int $slotid, string $date, int $userid): array { + global $USER, $DB; + + $now = new DateTimeImmutable(); + $dateobj = DateTimeImmutable::createFromFormat("YY-MM-DD", $date); + $td = $dateobj->diff($now); + + if($td->invert){ + throw new \moodle_exception('Can\'t reserve date in the past'); + } + + $maxdays = null; + $student = null; + + if ($userid === $USER->id) { + // student reserving slot for themself + + $maxdays = slot_helper::RESERVATION_RANGE_USER; + $student = $USER; + } else { + // supervisor reserving slot for student + + if (!slot_helper::check_slot_supervisor($USER->id, $slotid)) { + throw new \moodle_exception('Forbidden: you\'re not a supervisor of this slot'); + } + + $maxdays = slot_helper::RESERVATION_RANGE_USER; + $student = core_user::get_user($userid, '*', MUST_EXIST); + } + + if ($td->days > $maxdays) { + throw new \moodle_exception("Date is past allowed date ({$maxdays} days in the future)"); + } + + $slot = slot_helper::get_slot($slotid); + + // check if user has access to slot + if (sizeof(slot_helper::filter_slots_for_user([$slot], $student)) === 0) { + throw new \moodle_exception('Student does not have access to this slot'); + } + + // check if user is already in slot + foreach (slot_helper::get_reservations_for_slot($slotid) as $_reservation) { + if ($_reservation->userid === $userid){ + throw new \moodle_exception('Student is already in slot'); + } + } + + // check if slot is full + if ($slot->get_fullness() > $slot->size){ + throw new \moodle_exception('Slot is already full'); + } + + $reservation = new reservation(0, $slotid, $dateobj, $userid, $USER->id); + + $id = $DB->insert_record(slot_helper::TABLE_RESERVATIONS, $reservation->prepare_for_db()); + $reservation->id = $id; + + return $reservation->prepare_for_api(); + } + + /** + * Returns the structure of the slot array + * @return external_multiple_structure + */ + public static function book_reservation_returns(): external_single_structure { + return reservation::api_structure(); + } +} diff --git a/lbplanner/services/slots/get_my_slots.php b/lbplanner/services/slots/get_my_slots.php new file mode 100644 index 00000000..5198fc84 --- /dev/null +++ b/lbplanner/services/slots/get_my_slots.php @@ -0,0 +1,67 @@ +. + +namespace local_lbplanner_services; + +use external_api; +use external_function_parameters; +use external_multiple_structure; +use local_lbplanner\helpers\slot_helper; +use local_lbplanner\model\slot; + +/** + * Returns all slots the user can theoretically reserve. + * This does not include times the user has already reserved a slot for. + * + * @package local_lbplanner + * @subpackage services_plan + * @copyright 2024 necodeIT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class slots_get_my_slots extends external_api { + /** + * Parameters for get_my_slots. + * @return external_function_parameters + */ + public static function get_my_slots_parameters(): external_function_parameters { + return new external_function_parameters([]); + } + + /** + * Returns slots the current user is supposed to see + */ + public static function get_my_slots(): array { + global $USER; + + $allslots = slot_helper::get_all_slots(); + + $myslots = slot_helper::filter_slots_for_user($allslots, $USER); + + $returnslots = slot_helper::filter_slots_for_time($myslots, slot_helper::RESERVATION_RANGE_USER); + + return $returnslots; + } + + /** + * Returns the structure of the slot array + * @return external_multiple_structure + */ + public static function get_my_slots_returns(): external_multiple_structure { + return new external_multiple_structure( + slot::api_structure() + ); + } +} diff --git a/lbplanner/services/slots/get_student_slots.php b/lbplanner/services/slots/get_student_slots.php new file mode 100644 index 00000000..2a9df0e1 --- /dev/null +++ b/lbplanner/services/slots/get_student_slots.php @@ -0,0 +1,75 @@ +. + +namespace local_lbplanner_services; + +use external_api; +use external_function_parameters; +use external_multiple_structure; +use external_value; +use local_lbplanner\helpers\slot_helper; +use local_lbplanner\model\slot; + +/** + * Returns all slots a supervisor can theoretically reserve for a user. + * This does not include times the user has already reserved a slot for. + * + * @package local_lbplanner + * @subpackage services_plan + * @copyright 2024 necodeIT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class slots_get_student_slots extends external_api { + /** + * Parameters for get_student_slots. + * @return external_function_parameters + */ + public static function get_student_slots_parameters(): external_function_parameters { + return new external_function_parameters([ + 'userid' => new external_value(PARAM_INT, 'ID of the user to query for', VALUE_REQUIRED, null, NULL_NOT_ALLOWED), + ]); + } + + /** + * Returns slots of a user the supervisor can see. + * @param int $userid ID of the user in question (NOT the supervisor) + */ + public static function get_student_slots(int $userid): array { + global $USER; + self::validate_parameters( + self::get_student_slots_parameters(), + ['userid' => $userid] + ); + + $superslots = slot_helper::get_supervisor_slots($USER->id); + + $myslots = slot_helper::filter_slots_for_user($superslots, $userid); + + $returnslots = slot_helper::filter_slots_for_time($myslots, slot_helper::RESERVATION_RANGE_SUPERVISOR); + + return $returnslots; + } + + /** + * Returns the structure of the slot array + * @return external_multiple_structure + */ + public static function get_student_slots_returns(): external_multiple_structure { + return new external_multiple_structure( + slot::api_structure() + ); + } +} diff --git a/lbplanner/services/slots/get_supervisor_slots.php b/lbplanner/services/slots/get_supervisor_slots.php new file mode 100644 index 00000000..2a50347a --- /dev/null +++ b/lbplanner/services/slots/get_supervisor_slots.php @@ -0,0 +1,65 @@ +. + +namespace local_lbplanner_services; + +use external_api; +use external_function_parameters; +use external_multiple_structure; +use local_lbplanner\helpers\slot_helper; +use local_lbplanner\model\slot; + +/** + * Returns all slots a supervisor can see. + * + * @package local_lbplanner + * @subpackage services_plan + * @copyright 2024 necodeIT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class slots_get_supervisor_slots extends external_api { + /** + * Parameters for get_supervisor_slots. + * @return external_function_parameters + */ + public static function get_supervisor_slots_parameters(): external_function_parameters { + return new external_function_parameters([]); + } + + /** + * Returns all slots a supervisor controls. + * @param int $userid ID of the user in question (NOT the supervisor) + */ + public static function get_supervisor_slots(int $userid): array { + global $USER; + self::validate_parameters( + self::get_supervisor_slots_parameters(), + ['userid' => $userid] + ); + + return slot_helper::get_supervisor_slots($USER->id); + } + + /** + * Returns the structure of the slot array + * @return external_multiple_structure + */ + public static function get_supervisor_slots_returns(): external_multiple_structure { + return new external_multiple_structure( + slot::api_structure() + ); + } +}