Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions classes/model/scheduler.php
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,11 @@ public function create_slot() {
public function count_bookable_appointments($studentid, $includechangeable = true) {
global $DB;

// Note: cannot use empty() here on the model if we use magic __get. But using $this->data is a stdClass, so property_exists is safest.
if (property_exists($this->data, 'bulkbook') && $this->data->bulkbook) {
return -1;
}

// Find how many slots have already been booked.
$sql = 'SELECT COUNT(*) FROM {scheduler_slots} s'
.' JOIN {scheduler_appointment} a ON s.id = a.slotid'
Expand Down
1 change: 1 addition & 0 deletions db/install.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<FIELD NAME="intro" TYPE="text" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="introformat" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="schedulermode" TYPE="char" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="bulkbook" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="whether students can book all available slots at once"/>
<FIELD NAME="maxbookings" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="Maximum number of bookings for each student (depends on scheduler mode)"/>
<FIELD NAME="guardtime" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="reuseguardtime" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="legacy"/>
Expand Down
17 changes: 17 additions & 0 deletions db/upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -367,5 +367,22 @@ function xmldb_scheduler_upgrade($oldversion=0) {
upgrade_mod_savepoint(true, 2022120200, 'scheduler');
}

/* ******************* New upgrade line ********************** */

if ($oldversion < 2026040300) {

// Define field bulkbook to be added to scheduler.
$table = new xmldb_table('scheduler');
$field = new xmldb_field('bulkbook', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'schedulermode');

// Conditionally launch add field bulkbook.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}

// Scheduler savepoint reached.
upgrade_mod_savepoint(true, 2026040300, 'scheduler');
}

return true;
}
5 changes: 5 additions & 0 deletions lang/en/scheduler.php
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,11 @@
$string['yourstudentnote'] = 'Your message';
$string['yourtotalgrade'] = 'Your total grade in this activity is <strong>{$a}</strong>.';

$string['bulkbook'] = 'Enable bulk booking';
$string['bulkbook_help'] = 'If enabled, students can book all available slots at once with a single click. The mode (appointment limit) setting will be disabled when this option is active.';
$string['bookallslots'] = 'Book all available slots';
$string['bulkbook_message'] = 'Bulk booking is enabled. You can book all available slots at once by clicking the button below.';


/* *********** Help strings from here on ************ */

Expand Down
5 changes: 5 additions & 0 deletions mod_form.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ public function definition() {
$mform->addRule('staffrolename', get_string('error'), 'maxlength', 255);
$mform->addHelpButton('staffrolename', 'staffrolename', 'scheduler');

$mform->addElement('selectyesno', 'bulkbook', get_string('bulkbook', 'scheduler'));
$mform->addHelpButton('bulkbook', 'bulkbook', 'scheduler');
$mform->setDefault('bulkbook', 0);

$modegroup = array();
$modegroup[] = $mform->createElement('static', 'modeintro', '', get_string('modeintro', 'scheduler'));

Expand All @@ -89,6 +93,7 @@ public function definition() {

$mform->addGroup($modegroup, 'modegrp', get_string('mode', 'scheduler'), ' ', false);
$mform->addHelpButton('modegrp', 'appointmentmode', 'scheduler');
$mform->disabledIf('modegrp', 'bulkbook', 'eq', 1);

if (get_config('mod_scheduler', 'groupscheduling')) {
$selopt = array(
Expand Down
61 changes: 61 additions & 0 deletions studentview.controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,67 @@ function scheduler_book_slot($scheduler, $slotid, $userid, $groupid, $mform, $fo
scheduler_book_slot($scheduler, $slotid, $USER->id, $appointgroup, null, null, $returnurl);
}

/************************************************ Book all slots ************************************************/

if ($action == 'bookallslots') {

require_sesskey();
require_capability('mod/scheduler:appoint', $context);

// Reject this request if the user is required to go through a booking form.
if ($scheduler->uses_bookingform()) {
throw new moodle_exception('error');
}

if (!$scheduler->bulkbook) {
throw new moodle_exception('nopermissions');
}

$bookableslots = $scheduler->get_slots_available_to_student($USER->id, false);
$bookedcount = 0;
foreach ($bookableslots as $slot) {
if (!$slot->is_in_bookable_period()) {
continue;
}
$remaining = $slot->count_remaining_appointments();
if ($remaining >= 0 && $remaining < 1) {
continue;
}
// Check the student is not already booked in this slot.
$existingapp = $slot->get_student_appointment($USER->id);
if ($existingapp) {
continue;
}

// Create new appointment.
$appointment = $slot->create_appointment();
$appointment->studentid = $USER->id;
$appointment->attended = 0;
$appointment->timecreated = time();
$appointment->timemodified = time();
$appointment->save();

\mod_scheduler\event\booking_added::create_from_slot($slot)->trigger();

// Notify the teacher.
if ($scheduler->allownotifications) {
$student = $DB->get_record('user', array('id' => $USER->id), '*', MUST_EXIST);
$teacher = $DB->get_record('user', array('id' => $slot->teacherid), '*', MUST_EXIST);
scheduler_messenger::send_slot_notification($slot, 'bookingnotification', 'applied',
$student, $teacher, $teacher, $student, $COURSE);
}
$slot->save();
$bookedcount++;
}

if ($bookedcount > 0) {
\core\notification::success(get_string('slotsadded', 'scheduler', $bookedcount));
} else {
\core\notification::warning(get_string('noslotsavailable', 'scheduler'));
}
redirect($returnurl);
}

/******************************************** Show details of booking *******************************************/

if ($action == 'viewbooking') {
Expand Down
19 changes: 16 additions & 3 deletions studentview.php
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,14 @@
$end = $total;
}

// When bulk booking is enabled, hide individual "Book slot" buttons.
// Note: cannot use empty() here as the model uses __get magic without __isset.
$bulkbookenabled = (bool)$scheduler->bulkbook;

for ($idx = $start; $idx < $end; $idx++) {
$slot = $bookableslots[$idx];
$canbookthisslot = $canbook && ($bookablecnt != 0);
// Disable individual booking buttons when bulk booking is on.
$canbookthisslot = $bulkbookenabled ? false : ($canbook && ($bookablecnt != 0));

if (has_capability('mod/scheduler:seeotherstudentsbooking', $context)) {
$others = new scheduler_student_list($scheduler, false);
Expand Down Expand Up @@ -236,8 +241,16 @@

echo $output->heading(get_string('availableslots', 'scheduler'), 3);
if ($canbook) {
echo html_writer::div($bookingmsg1, 'studentbookingmessage');
echo html_writer::div($bookingmsg2, 'studentbookingmessage');
if ($bulkbookenabled) {
$bulkbookurl = new moodle_url($actionurl, array('what' => 'bookallslots', 'sesskey' => sesskey()));
echo html_writer::start_div('bulkbook-container', array('style' => 'background:#f0f7ff; border:1px solid #0073aa; border-radius:8px; padding:15px; margin-bottom:15px; text-align:center;'));
echo html_writer::tag('p', get_string('bulkbook_message', 'scheduler'), array('style' => 'margin:0 0 10px 0; font-size:1.1em;'));
echo $output->single_button($bulkbookurl, get_string('bookallslots', 'scheduler'), 'post', array('class' => 'btn-lg'));
echo html_writer::end_div();
} else {
echo html_writer::div($bookingmsg1, 'studentbookingmessage');
echo html_writer::div($bookingmsg2, 'studentbookingmessage');
}
}
if ($total > $pagesize) {
echo $output->paging_bar($total, $offset, $pagesize, $actionurl, 'offset');
Expand Down
2 changes: 1 addition & 1 deletion teacherview.php
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ function scheduler_print_schedulebox(scheduler $scheduler, $studentid, $groupid
$PAGE->requires->js_call_amd('mod_scheduler/delselected', 'init', [$delselectedurl->out(false)]);
$delselected = $commandbar->action_menu_link($delselectedurl, 'deleteselection', 't/delete',
'confirmdelete-selected', 'delselected');
$delselected->formid = 'delselected';
$delselected->attributes['formid'] = 'delselected';
$delbuttons[] = $delselected;

if ($permissions->can_edit_all_slots() && $subpage == 'allappointments') {
Expand Down
1 change: 1 addition & 0 deletions templates/action_menu_trigger.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
id="action-menu-toggle-{{instance}}"
aria-label="{{title}}"
data-toggle="dropdown"
data-bs-toggle="dropdown"
role="{{triggerrole}}"
aria-haspopup="true"
aria-expanded="false"
Expand Down
2 changes: 1 addition & 1 deletion version.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
*/

$plugin->component = 'mod_scheduler'; // Full name of the plugin (used for diagnostics).
$plugin->version = 2024080103; // The current module version (Date: YYYYMMDDXX).
$plugin->version = 2026040300; // The current module version (Date: YYYYMMDDXX).
$plugin->release = '4.x dev'; // Human-friendly version name.
$plugin->requires = 2023100905; // Requires Moodle 4.3.
$plugin->maturity = MATURITY_ALPHA; // Development release - not for production use.