Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
d8b83cd
fix mod savepoint
May 28, 2026
fd4bd35
Sync with Learnweb's centralized pull request template
github-actions[bot] Jul 31, 2026
caee5c6
Activity information for Moodle 5.2 activity chooser
lucaboesch Jan 11, 2026
a0a4a78
Support course overview, resolves #146.
lucaboesch Dec 28, 2025
3067d80
Tint monologo icon with dark grey as standard.
lucaboesch Apr 14, 2025
4ad7d22
Make some Scheduler layouts more beautiful.
lucaboesch Jul 26, 2026
f5037fb
Keep attended appointments in calendar events
jochen-hanisch Aug 5, 2026
678e466
Merge branch 'pr-80' into dev-branch-merge-prs
Aug 14, 2026
8c05024
Merge pr-82: Make some scheduler layouts more beautiful
Aug 14, 2026
74dccf1
Merge PR 76: Add activity information
Aug 14, 2026
6284a9a
Minimal adjustment of wording to be broader
Aug 14, 2026
b4d9c0a
Remove the limited width again, because it makes the tab placement ju…
Aug 14, 2026
0d54526
more space between checkbox and student name
Aug 16, 2026
175ded3
remove stale var from PR #62
Aug 16, 2026
fae0893
add new line. Resolves PR #66
Aug 17, 2026
249cda5
Merge PR #63: Monologo icon change
Aug 17, 2026
59ecf8a
Merge PR #83: Keep attended appointments in calendar events- #83
Aug 18, 2026
5b16216
Making the change from PR #83 optional in settings
Aug 18, 2026
8b7ead0
Merge PR 70: Support course overview
Aug 18, 2026
c220aa5
fix the course overview for scheduler. Use bookingrouping instead of …
Aug 18, 2026
357b226
Add test for overview fix
Aug 18, 2026
e0b90c1
Revert the changes for the styling because they will be addressed in …
Aug 18, 2026
8b8c031
Merge PR 73
Aug 18, 2026
034f391
codechecker
Aug 18, 2026
3e7f5d6
Make some Scheduler layouts more beautiful.
lucaboesch Jul 26, 2026
e18cd9b
Merge changes from #82
Aug 26, 2026
38cd7af
Update the db. Add field bookinggroupid.
Sep 23, 2026
8005d89
Store the booking group on appointments
Sep 23, 2026
9b5e393
Handle overlapping group booking independently
Sep 23, 2026
e0e4fd2
Backup preservation
Sep 23, 2026
96bc149
Testing
Sep 23, 2026
77f8c91
Fix indentation.
Sep 23, 2026
001c90f
phpcs fixes
Oct 1, 2026
7b47efa
db update and mod form
Oct 1, 2026
7906740
add automatic course group creation and membership synch
Oct 8, 2026
052f1e4
test
Oct 8, 2026
4c1a3d9
backup
Oct 8, 2026
4c8eeeb
phpcs
Oct 8, 2026
ed3b903
update parameter list
Oct 8, 2026
bdf456c
fix
Oct 9, 2026
30dc405
fix docblock for tests
Oct 9, 2026
538923b
additional tests
Oct 9, 2026
41c8bbb
make notes part of repeated slots form and ensure comments to be save…
Oct 9, 2026
764f2f7
Behat test
Oct 9, 2026
a5086ba
codesniffer fix
Oct 9, 2026
f559612
fix test
Oct 11, 2026
42b6b15
remove unnessary tag for testing and fix timing issue
Oct 15, 2026
8900996
Add the form
Oct 15, 2026
f7aa4d7
db update and logic adjustment
Oct 15, 2026
69eb217
backup and restore
Oct 15, 2026
449fe97
fix
Oct 15, 2026
5d883c5
behat testing
Oct 16, 2026
cb963a9
add validation to not allow contradictory settings
Oct 16, 2026
0b29469
missing docblock
Oct 16, 2026
c4a9669
fixes
Oct 16, 2026
088fd4c
slot form field addition for meeting link
Oct 16, 2026
f431b33
DB update
Oct 16, 2026
f82cbfb
save url
Oct 16, 2026
3712e5d
db change
Oct 16, 2026
a03afcd
add button
Oct 16, 2026
d2744cd
backup and restore
Oct 16, 2026
e47a7d3
behat testing
Oct 16, 2026
cdbbd32
remove temporary tag for behat
Oct 16, 2026
3a04058
codesniffer fix
Oct 16, 2026
c9582ed
db upgrade
Oct 23, 2026
3aef7aa
add mod form element
Oct 23, 2026
4fa692e
fix
Oct 23, 2026
4710695
logic and help text fix
Oct 23, 2026
8e6ff50
tests
Oct 23, 2026
eb7d0fe
add line
Oct 23, 2026
5dd1f3e
phpcs
Oct 23, 2026
5c653f7
coverage information
Oct 23, 2026
55c49d4
fix
Oct 23, 2026
82899bd
move the tabs and preserve the subtabs
Oct 31, 2026
c09e832
adjust tests
Nov 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
> **Note:** Please fill out all relevant sections and remove irrelevant ones.
### 🔀 Purpose of this PR:

- [ ] Fixes a bug
- [ ] Updates for a new Moodle version
- [ ] Adds a new feature of functionality
- [ ] Improves or enhances existing features
- [ ] Refactoring: restructures code for better performance or maintainability
- [ ] Testing: add missing or improve existing tests
- [ ] Miscellaneous: code cleaning (without functional changes), documentation, configuration, ...

---

### 📝 Description:

Please describe the purpose of this PR in a few sentences.

- What feature or bug does it address?
- Why is this change or addition necessary?
- What is the expected behavior after the change?

---

### 📋 Checklist

Please confirm the following (check all that apply):

- [ ] I have `phpunit` and/or `behat` tests that cover my changes or additions.
- [ ] Code passes the code checker without errors and warnings.
- [ ] Code passes the moodle-ci/cd pipeline on all supported Moodle versions or the ones the plugin supports.
- [ ] Code does not have `var_dump()` or `var_export` or any other debugging statements (or commented out code) that
should not appear on the productive branch.
- [ ] Code only uses language strings instead of hard-coded strings.
- [ ] If there are changes in the database: I updated/created the necessary upgrade steps in `db/upgrade.php` and
updated the `version.php`.
- [ ] If there are changes in javascript: I build new `.min` files with the `grunt amd` command.
- [ ] If it is a Moodle update PR: I read the release notes, updated the `version.php` and the `CHANGES.md`.
I ran all tests thoroughly checking for errors. I checked if bootstrap had any changes/deprecations that require
changes in the plugins UI.

---

### 🔍 Related Issues

- Related to #[IssueNumber]

---

### 🧾📸🌐 Additional Information (like screenshots, documentation, links, etc.)

Any other relevant information.

---
2 changes: 1 addition & 1 deletion amd/build/delselected.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion amd/build/delselected.min.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion amd/src/delselected.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
*/
export const SELECTORS = {
DELACTION: 'div.commandbar a#delselected',
SELECTBOX: 'table#slotmanager input.slotselect',
SELECTBOX: 'table#slotmanager input.slotselect.form-check-input.mt-0',
};

/**
Expand Down
48 changes: 41 additions & 7 deletions backup/moodle2/backup_scheduler_stepslib.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,30 +36,56 @@ class backup_scheduler_activity_structure_step extends backup_activity_structure
* @return backup_nested_element
*/
protected function define_structure() {
global $DB;

// To know if we are including userinfo.
$userinfo = $this->get_setting_value('userinfo');

$task = $this->get_task();

$backupinfo = $DB->get_record(
'backup_controllers',
['backupid' => $task->get_backupid()],
'type, purpose, interactive',
MUST_EXIST
);

$isduplicate = $backupinfo->type === backup::TYPE_1ACTIVITY
&& (int) $backupinfo->purpose === backup::MODE_IMPORT
&& (bool) $backupinfo->interactive === backup::INTERACTIVE_NO;

$copyslots = false;
if ($isduplicate) {
$copyslots = (bool) $DB->get_field(
'scheduler',
'copyslotsonduplicate',
['id' => $task->get_activityid()],
MUST_EXIST
);
}

$includeslots = $userinfo || $copyslots;

// Define each element separated.
$scheduler = new backup_nested_element('scheduler', ['id'], [
'name', 'intro', 'introformat', 'schedulermode', 'maxbookings',
'guardtime', 'defaultslotduration', 'allownotifications', 'staffrolename',
'scale', 'gradingstrategy', 'bookingrouping', 'usenotes',
'scale', 'gradingstrategy', 'bookingrouping', 'bookingtype', 'usenotes',
'usebookingform', 'bookinginstructions', 'bookinginstructionsformat',
'usestudentnotes', 'requireupload', 'uploadmaxfiles', 'uploadmaxsize',
'usecaptcha', 'timemodified', ]);
'usecaptcha', 'timemodified', 'groupcreation', 'copyslotsonduplicate', ]);

$slots = new backup_nested_element('slots');

$slot = new backup_nested_element('slot', ['id'], [
'starttime', 'duration', 'teacherid', 'appointmentlocation',
'starttime', 'duration', 'teacherid', 'appointmentlocation', 'meetingurl',
'timemodified', 'notes', 'notesformat', 'exclusivity',
'emaildate', 'hideuntil', ]);
'emaildate', 'hideuntil', 'coursegroupid', ]);

$appointments = new backup_nested_element('appointments');

$appointment = new backup_nested_element('appointment', ['id'], [
'studentid', 'attended', 'grade',
'studentid', 'bookinggroupid', 'attended', 'grade',
'appointmentnote', 'appointmentnoteformat', 'teachernote', 'teachernoteformat',
'studentnote', 'studentnoteformat', 'timecreated', 'timemodified', ]);

Expand All @@ -75,18 +101,26 @@ protected function define_structure() {
$scheduler->set_source_table('scheduler', ['id' => backup::VAR_ACTIVITYID]);
$scheduler->annotate_ids('grouping', 'bookingrouping');

// Include appointments only if we back up user information.
if ($userinfo) {
// Include slots when backing up user data or explicitly copying slots.
if ($includeslots) {
$slot->set_source_table('scheduler_slots', ['schedulerid' => backup::VAR_PARENTID]);
}

// Include bookings and personal appointment data only with user data.
if ($userinfo) {
$appointment->set_source_table('scheduler_appointment', ['slotid' => backup::VAR_PARENTID]);
}

// Define id annotations.
$scheduler->annotate_ids('scale', 'scale');

if ($includeslots) {
$slot->annotate_ids('group', 'coursegroupid');
}
if ($userinfo) {
$slot->annotate_ids('user', 'teacherid');
$appointment->annotate_ids('user', 'studentid');
$appointment->annotate_ids('group', 'bookinggroupid');
}

// Define file annotations.
Expand Down
99 changes: 95 additions & 4 deletions backup/moodle2/restore_scheduler_stepslib.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,51 @@
*/
class restore_scheduler_activity_structure_step extends restore_activity_structure_step
{
/** @var bool Whether this is a duplication within the same course. */
protected $isduplicate = false;

/** @var bool Whether slots may be copied without appointment data. */
protected $copyslots = false;

/**
* define_structure
*
* @return array
*/
protected function define_structure() {
global $DB;

$userinfo = $this->get_setting_value('userinfo');
$task = $this->get_task();

$restoreinfo = $DB->get_record(
'backup_controllers',
['backupid' => $task->get_restoreid()],
'type, purpose, interactive',
MUST_EXIST
);

$this->isduplicate =
$restoreinfo->type === backup::TYPE_1ACTIVITY
&& (int) $restoreinfo->purpose === backup::MODE_IMPORT
&& (bool) $restoreinfo->interactive === backup::INTERACTIVE_NO
&& $task->is_samesite()
&& (int) $task->get_old_courseid() === (int) $this->get_courseid();

$paths = [new restore_path_element('scheduler', '/activity/scheduler')];
if ($userinfo || $this->isduplicate) {
$paths[] = new restore_path_element('scheduler_slot', '/activity/scheduler/slots/slot');
}

if ($userinfo) {
$paths[] = new restore_path_element(
'scheduler_appointment',
'/activity/scheduler/slots/slot/appointments/appointment'
);
}

return $this->prepare_activity_structure($paths);

$paths = [];
$userinfo = $this->get_setting_value('userinfo');

$scheduler = new restore_path_element('scheduler', '/activity/scheduler');
Expand Down Expand Up @@ -80,10 +117,27 @@ protected function process_scheduler($data) {
$data->gradingstrategy = 0;
}

if (!isset($data->bookingtype)) {
$data->bookingtype = get_config('mod_scheduler', 'mixindivgroup') ? 1 : 2;
}

if ($data->bookingrouping > 0) {
$data->bookingrouping = $this->get_mappingid('grouping', $data->bookingrouping);
if ($this->isduplicate) {
$DB->get_record(
'groupings',
['id' => $data->bookingrouping, 'courseid' => $this->get_courseid()],
'id',
MUST_EXIST
);
} else {
$data->bookingrouping = $this->get_mappingid('grouping', $data->bookingrouping);
}
}

$data->groupcreation = (int) ($data->groupcreation ?? 0);
$data->copyslotsonduplicate = (int) ($data->copyslotsonduplicate ?? 0);
$this->copyslots = $this->isduplicate && !empty($data->copyslotsonduplicate);

// Insert the scheduler record.
$newitemid = $DB->insert_record('scheduler', $data);
// Immediately after inserting "activity" record, call this.
Expand All @@ -98,16 +152,50 @@ protected function process_scheduler($data) {
protected function process_scheduler_slot($data) {
global $DB;

$userinfo = $this->get_setting_value('userinfo');
if (!$userinfo && !$this->copyslots) {
return self::SKIP_ALL_CHILDREN;
}
$data = (object)$data;
$data->meetingurl = $data->meetingurl ?? '';
$oldid = $data->id;

$data->schedulerid = $this->get_new_parentid('scheduler');
$data->starttime = $this->apply_date_offset($data->starttime);
$data->timemodified = $this->apply_date_offset($data->timemodified);
$data->emaildate = $this->apply_date_offset($data->emaildate);
$data->hideuntil = $this->apply_date_offset($data->hideuntil);

$data->teacherid = $this->get_mappingid('user', $data->teacherid);
if ($this->copyslots && !$userinfo) {
// Same-site, same-course duplication retains existing references.
if (!empty($data->teacherid)) {
$DB->get_record(
'user',
['id' => $data->teacherid, 'deleted' => 0],
'id',
MUST_EXIST
);
}

$data->coursegroupid = (int) ($data->coursegroupid ?? 0);

if ($data->coursegroupid > 0) {
$DB->get_record(
'groups',
[
'id' => $data->coursegroupid,
'courseid' => $this->get_courseid(),
],
'id',
MUST_EXIST
);
}
} else {
$data->teacherid = $this->get_mappingid('user', $data->teacherid);

$data->coursegroupid = !empty($data->coursegroupid)
? $this->get_mappingid('group', $data->coursegroupid, 0)
: 0;
}

$newitemid = $DB->insert_record('scheduler_slots', $data);
$this->set_mapping('scheduler_slot', $oldid, $newitemid, true);
Expand All @@ -131,6 +219,9 @@ protected function process_scheduler_appointment($data) {

$data->studentid = $this->get_mappingid('user', $data->studentid);

$data->bookinggroupid = !empty($data->bookinggroupid)
? $this->get_mappingid('group', $data->bookinggroupid, 0) : 0;

$newitemid = $DB->insert_record('scheduler_appointment', $data);
$this->set_mapping('scheduler_appointment', $oldid, $newitemid, true);
}
Expand Down
Loading
Loading