86 |
87 |
--------------------------------------------------------------------------------
/exportform.php:
--------------------------------------------------------------------------------
1 | libdir.'/formslib.php');
14 | require_once($CFG->dirroot.'/mod/scheduler/exportlib.php');
15 |
16 | /**
17 | * Export settings form
18 | * (using Moodle formslib)
19 | *
20 | * @package mod_scheduler
21 | * @copyright 2015 Henning Bostelmann and others (see README.txt)
22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 | */
24 | class scheduler_export_form extends moodleform {
25 |
26 | /**
27 | * @var scheduler_instance the scheduler to be exported
28 | */
29 | protected $scheduler;
30 |
31 | /**
32 | * Create a new export settings form.
33 | *
34 | * @param string $action
35 | * @param scheduler_instance $scheduler the scheduler to export
36 | * @param object $customdata
37 | */
38 | public function __construct($action, scheduler_instance $scheduler, $customdata=null) {
39 | $this->scheduler = $scheduler;
40 | parent::__construct($action, $customdata);
41 | }
42 |
43 | protected function definition() {
44 |
45 | $mform = $this->_form;
46 |
47 | // General introduction.
48 | $mform->addElement('header', 'general', get_string('general', 'form'));
49 |
50 | $radios = array();
51 | $radios[] = $mform->createElement('radio', 'content', '',
52 | get_string('onelineperslot', 'scheduler'), 'onelineperslot');
53 | $radios[] = $mform->createElement('radio', 'content', '',
54 | get_string('onelineperappointment', 'scheduler'), 'onelineperappointment');
55 | $radios[] = $mform->createElement('radio', 'content', '',
56 | get_string('appointmentsgrouped', 'scheduler'), 'appointmentsgrouped');
57 | $mform->addGroup($radios, 'contentgroup',
58 | get_string('contentformat', 'scheduler'), null, false);
59 | $mform->setDefault('content', 'onelineperappointment');
60 | $mform->addHelpButton('contentgroup', 'contentformat', 'scheduler');
61 |
62 | if (has_capability('mod/scheduler:canseeotherteachersbooking', $this->scheduler->get_context())) {
63 | $selopt = array('me' => get_string('myself', 'scheduler'),
64 | 'all' => get_string ('everyone', 'scheduler'));
65 | $mform->addElement('select', 'includewhom', get_string('includeslotsfor', 'scheduler'), $selopt);
66 | $mform->setDefault('includewhom', 'all');
67 |
68 | $selopt = array('all' => get_string('allononepage', 'scheduler'),
69 | 'perteacher' => get_string('pageperteacher', 'scheduler', $this->scheduler->get_teacher_name()) );
70 | $mform->addElement('select', 'paging', get_string('pagination', 'scheduler'), $selopt);
71 | $mform->addHelpButton('paging', 'pagination', 'scheduler');
72 |
73 | }
74 |
75 | $mform->addElement('selectyesno', 'includeemptyslots', get_string('includeemptyslots', 'scheduler'));
76 | $mform->setDefault('includeemptyslots', 1);
77 |
78 | // Select data to export.
79 | $mform->addElement('header', 'datafieldhdr', get_string('datatoinclude', 'scheduler'));
80 | $mform->addHelpButton('datafieldhdr', 'datatoinclude', 'scheduler');
81 |
82 | $this->add_exportfield_group('slot', 'slot');
83 | $this->add_exportfield_group('student', 'student');
84 | $this->add_exportfield_group('appointment', 'appointment');
85 |
86 | $mform->setDefault('field-date', 1);
87 | $mform->setDefault('field-starttime', 1);
88 | $mform->setDefault('field-endtime', 1);
89 | $mform->setDefault('field-teachername', 1);
90 | $mform->setDefault('field-studentfullname', 1);
91 | $mform->setDefault('field-attended', 1);
92 |
93 | // Output file format.
94 | $mform->addElement('header', 'fileformathdr', get_string('fileformat', 'scheduler'));
95 | $mform->addHelpButton('fileformathdr', 'fileformat', 'scheduler');
96 |
97 | $radios = array();
98 | $radios[] = $mform->createElement('radio', 'outputformat', '', get_string('csvformat', 'scheduler'), 'csv');
99 | $radios[] = $mform->createElement('radio', 'outputformat', '', get_string('excelformat', 'scheduler'), 'xls');
100 | $radios[] = $mform->createElement('radio', 'outputformat', '', get_string('odsformat', 'scheduler'), 'ods');
101 | $radios[] = $mform->createElement('radio', 'outputformat', '', get_string('htmlformat', 'scheduler'), 'html');
102 | $radios[] = $mform->createElement('radio', 'outputformat', '', get_string('pdfformat', 'scheduler'), 'pdf');
103 | $mform->addGroup($radios, 'outputformatgroup', get_string('fileformat', 'scheduler'), null, false);
104 | $mform->setDefault('outputformat', 'csv');
105 |
106 | $selopt = array('comma' => get_string('sepcomma', 'scheduler'),
107 | 'colon' => get_string('sepcolon', 'scheduler'),
108 | 'semicolon' => get_string('sepsemicolon', 'scheduler'),
109 | 'tab' => get_string('septab', 'scheduler'));
110 | $mform->addElement('select', 'csvseparator', get_string('csvfieldseparator', 'scheduler'), $selopt);
111 | $mform->setDefault('csvseparator', 'comma');
112 | $mform->disabledIf('csvseparator', 'outputformat', 'neq', 'csv');
113 |
114 | $selopt = array('P' => get_string('portrait', 'scheduler'),
115 | 'L' => get_string('landscape', 'scheduler'));
116 | $mform->addElement('select', 'pdforientation', get_string('pdforientation', 'scheduler'), $selopt);
117 | $mform->disabledIf('pdforientation', 'outputformat', 'neq', 'pdf');
118 |
119 | $buttonarray = array();
120 | $buttonarray[] = $mform->createElement('submit', 'preview', get_string('preview', 'scheduler'));
121 | $buttonarray[] = $mform->createElement('submit', 'submitbutton', get_string('createexport', 'scheduler'));
122 | $buttonarray[] = $mform->createElement('cancel');
123 | $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
124 | $mform->closeHeaderBefore('buttonar');
125 |
126 | }
127 |
128 | /**
129 | * Add a group of export fields to the form.
130 | *
131 | * @param string $groupid id of the group in the list of fields
132 | * @param string $labelid language string id for the group label
133 | */
134 | private function add_exportfield_group($groupid, $labelid) {
135 |
136 | $mform = $this->_form;
137 | $fields = scheduler_get_export_fields($this->scheduler);
138 | $checkboxes = array();
139 |
140 | foreach ($fields as $field) {
141 | if ($field->get_group() == $groupid && $field->is_available($this->scheduler)) {
142 | $inputid = 'field-'.$field->get_id();
143 | $label = $field->get_formlabel($this->scheduler);
144 | $checkboxes[] = $mform->createElement('checkbox', $inputid, '', $label);
145 | }
146 | }
147 | $grouplabel = get_string($labelid, 'scheduler');
148 | $mform->addGroup($checkboxes, 'fields-'.$groupid, $grouplabel, null, false);
149 | }
150 |
151 | public function validation($data, $files) {
152 | $errors = parent::validation($data, $files);
153 |
154 | return $errors;
155 | }
156 |
157 | }
158 |
--------------------------------------------------------------------------------
/tests/behat/groupscheduling.feature:
--------------------------------------------------------------------------------
1 | @mod_scheduler
2 | Feature: Entire groups can be booked into slots at once
3 | In order to allow booking of entire groups
4 | As a teacher
5 | I need to use a scheduler with group bookings
6 |
7 | Background:
8 | Given the following "users" exist:
9 | | username | firstname | lastname | email |
10 | | edteacher1 | Editingteacher | 1 | edteacher1@example.com |
11 | | neteacher1 | Nonedteacher | 1 | neteacher1@example.com |
12 | | student1 | Student | 1 | student1@example.com |
13 | | student2 | Student | 2 | student2@example.com |
14 | | student3 | Student | 3 | student3@example.com |
15 | | student4 | Student | 4 | student4@example.com |
16 | And the following "courses" exist:
17 | | fullname | shortname | category |
18 | | Course 1 | C1 | 0 |
19 | And the following "course enrolments" exist:
20 | | user | course | role |
21 | | edteacher1 | C1 | editingteacher |
22 | | neteacher1 | C1 | teacher |
23 | | student1 | C1 | student |
24 | | student2 | C1 | student |
25 | | student3 | C1 | student |
26 | | student4 | C1 | student |
27 | And the following "groups" exist:
28 | | name | course | idnumber |
29 | | Group A1 | C1 | GA1 |
30 | | Group A2 | C1 | GA2 |
31 | | Group B1 | C1 | GB1 |
32 | | Group B2 | C1 | GB2 |
33 | And the following "groupings" exist:
34 | | name | course | idnumber |
35 | | Grouping A | C1 | GROUPINGA |
36 | | Grouping B | C1 | GROUPINGB |
37 | And the following "group members" exist:
38 | | user | group |
39 | | neteacher1 | GB1 |
40 | | neteacher1 | GA1 |
41 | | student1 | GA1 |
42 | | student2 | GA1 |
43 | | student3 | GA2 |
44 | | student4 | GA2 |
45 | | student1 | GB1 |
46 | | student2 | GB2 |
47 | | student3 | GB1 |
48 | | student4 | GB2 |
49 | And the following "grouping groups" exist:
50 | | grouping | group |
51 | | GROUPINGA | GA1 |
52 | | GROUPINGA | GA2 |
53 | | GROUPINGB | GB1 |
54 | | GROUPINGB | GB2 |
55 | And the following "activities" exist:
56 | | activity | name | intro | course | idnumber |
57 | | scheduler | Test scheduler no grouping | n | C1 | schedulern |
58 | | scheduler | Test scheduler grouping A | n | C1 | schedulera |
59 | | scheduler | Test scheduler grouping B | n | C1 | schedulerb |
60 | And I log in as "edteacher1"
61 | And I am on "Course 1" course homepage
62 | And I follow "Test scheduler no grouping"
63 | And I navigate to "Edit settings" in current page administration
64 | And I set the following fields to these values:
65 | | Booking in groups | Yes, for all groups |
66 | And I click on "Save and return to course" "button"
67 | And I follow "Test scheduler grouping A"
68 | And I navigate to "Edit settings" in current page administration
69 | And I set the following fields to these values:
70 | | Booking in groups | Yes, in grouping Grouping A |
71 | And I click on "Save and return to course" "button"
72 | And I follow "Test scheduler grouping B"
73 | And I navigate to "Edit settings" in current page administration
74 | And I set the following fields to these values:
75 | | Booking in groups | Yes, in grouping Grouping B |
76 | And I click on "Save and return to course" "button"
77 | And I log out
78 |
79 | @javascript
80 | Scenario: Editing teachers can see and schedule relevant groups
81 | Given I log in as "edteacher1"
82 | And I am on "Course 1" course homepage
83 |
84 | When I am on "Course 1" course homepage
85 | And I follow "Test scheduler no grouping"
86 | Then I should see "Group A1" in the "groupstoschedule" "table"
87 | And I should see "Group A2" in the "groupstoschedule" "table"
88 | And I should see "Group B1" in the "groupstoschedule" "table"
89 | And I should see "Group B2" in the "groupstoschedule" "table"
90 |
91 | When I am on "Course 1" course homepage
92 | And I follow "Test scheduler grouping A"
93 | Then I should see "Group A1" in the "groupstoschedule" "table"
94 | And I should see "Group A2" in the "groupstoschedule" "table"
95 | And I should not see "Group B" in the "groupstoschedule" "table"
96 |
97 | When I am on "Course 1" course homepage
98 | And I follow "Test scheduler grouping B"
99 | Then I should not see "Group A" in the "groupstoschedule" "table"
100 | And I should see "Group B1" in the "groupstoschedule" "table"
101 | And I should see "Group B2" in the "groupstoschedule" "table"
102 |
103 | When I am on "Course 1" course homepage
104 | And I follow "Test scheduler no grouping"
105 | And I click on "Schedule" "link_or_button" in the "Group A1" "table_row"
106 | And I click on "Schedule in slot" "text" in the "Group A1" "table_row"
107 | And I click on "Save changes" "button"
108 | Then I should see "Student 1" in the "slotmanager" "table"
109 | And I should see "Student 2" in the "slotmanager" "table"
110 | And I should see "2 students still need to make an appointment"
111 | And I should not see "Group A1" in the "groupstoschedule" "table"
112 | And I should see "Group A2" in the "groupstoschedule" "table"
113 | And I should not see "Group B1" in the "groupstoschedule" "table"
114 | And I should not see "Group B2" in the "groupstoschedule" "table"
115 |
116 | @javascript
117 | Scenario: Students can book their entire group into a slot
118 | Given I log in as "edteacher1"
119 | And I am on "Course 1" course homepage
120 | And I follow "Test scheduler no grouping"
121 | And I add 8 slots 5 days ahead in "Test scheduler" scheduler and I fill the form with:
122 | | Location | Large office |
123 | | exclusivity | 5 |
124 | And I add 5 slots 6 days ahead in "Test scheduler" scheduler and I fill the form with:
125 | | Location | Small office |
126 | | exclusivity | 1 |
127 | And I log out
128 |
129 | When I log in as "student1"
130 | And I am on "Course 1" course homepage
131 | And I follow "Test scheduler no grouping"
132 | Then the "appointgroup" select box should contain "Myself"
133 | And the "appointgroup" select box should contain "Group A1"
134 | And the "appointgroup" select box should contain "Group B1"
135 | And the "appointgroup" select box should not contain "Group A2"
136 | And the "appointgroup" select box should not contain "Group B2"
137 |
138 | When I set the field "appointgroup" to "Group A1"
139 | And I click on "Book slot" "button" in the "8:00 AM" "table_row"
140 | Then I should see "8:00 AM" in the "Large office" "table_row"
141 | And I log out
142 |
143 | When I log in as "edteacher1"
144 | And I am on "Course 1" course homepage
145 | And I follow "Test scheduler no grouping"
146 | Then I should see "Student 1" in the "8:00 AM" "table_row"
147 | And I should see "Student 2" in the "8:00 AM" "table_row"
148 | And I should see "2 students still need to make an appointment"
149 | And I should not see "Group A1" in the "groupstoschedule" "table"
150 | And I should see "Group A2" in the "groupstoschedule" "table"
151 | And I should not see "Group B1" in the "groupstoschedule" "table"
152 | And I should not see "Group B2" in the "groupstoschedule" "table"
153 | And I log out
154 |
155 |
--------------------------------------------------------------------------------
/tests/behat/notes.feature:
--------------------------------------------------------------------------------
1 | @mod_scheduler
2 | Feature: Teachers can write notes on slots and appointments
3 | In order to record details about a meeting
4 | As a teacher
5 | I need to enter notes for the appointment
6 |
7 | Background:
8 | Given the following "users" exist:
9 | | username | firstname | lastname | email |
10 | | edteacher1 | Editingteacher | 1 | edteacher1@example.com |
11 | | neteacher1 | Nonedteacher | 1 | neteacher1@example.com |
12 | | student1 | Student | 1 | student1@example.com |
13 | And the following "courses" exist:
14 | | fullname | shortname | category |
15 | | Course 1 | C1 | 0 |
16 | And the following "course enrolments" exist:
17 | | user | course | role |
18 | | edteacher1 | C1 | editingteacher |
19 | | neteacher1 | C1 | teacher |
20 | | student1 | C1 | student |
21 | And the following "activities" exist:
22 | | activity | name | intro | course | idnumber | usenotes |
23 | | scheduler | Test scheduler | n | C1 | schedulern | 3 |
24 | And I log in as "edteacher1"
25 | And I am on "Course 1" course homepage
26 | And I add 5 slots 10 days ahead in "Test scheduler" scheduler and I fill the form with:
27 | | Location | Here |
28 | And I log out
29 |
30 | @javascript
31 | Scenario: Teachers can enter slot notes and appointment notes for others to see
32 | When I log in as "edteacher1"
33 | And I am on "Course 1" course homepage
34 | And I follow "Test scheduler"
35 | And I follow "Statistics"
36 | And I follow "All appointments"
37 | And I click on "Edit" "link" in the "4:00 AM" "table_row"
38 | And I set the following fields to these values:
39 | | Comments | Note-for-slot |
40 | And I click on "Save" "button"
41 | Then I should see "slot updated"
42 | When I click on "Edit" "link" in the "4:00 AM" "table_row"
43 | Then I should see "Note-for-slot"
44 | And I log out
45 |
46 | When I log in as "student1"
47 | And I am on "Course 1" course homepage
48 | And I follow "Test scheduler"
49 | Then I should see "Note-for-slot" in the "4:00 AM" "table_row"
50 | When I click on "Book slot" "button" in the "4:00 AM" "table_row"
51 | Then I should see "Note-for-slot"
52 | And I log out
53 |
54 | When I log in as "edteacher1"
55 | And I am on "Course 1" course homepage
56 | And I follow "Test scheduler"
57 | And I follow "Statistics"
58 | And I follow "All appointments"
59 | And I click on "//a[text()='Student 1']" "xpath_element" in the "4:00 AM" "table_row"
60 | Then I should see ", 4:00 AM" in the "Date and time" "table_row"
61 | And I should see "4:45 AM" in the "Date and time" "table_row"
62 | And I should see "Editingteacher 1" in the "Teacher" "table_row"
63 | And I set the following fields to these values:
64 | | Attended | 1 |
65 | | Notes for appointment (visible to student) | note-for-appointment |
66 | | Confidential notes (visible to teacher only) | note-confidential |
67 | And I click on "Save changes" "button"
68 | Then I should see "note-for-appointment"
69 | And I should see "note-confidential"
70 | And I log out
71 |
72 | When I log in as "student1"
73 | And I am on "Course 1" course homepage
74 | And I follow "Test scheduler"
75 | Then I should see "Attended slots"
76 | And I should see "note-for-appointment"
77 | And I should not see "note-confidential"
78 | And I log out
79 |
80 | @javascript
81 | Scenario: Teachers see only the comments fields specified in the configuration
82 |
83 | When I log in as "student1"
84 | And I am on "Course 1" course homepage
85 | And I follow "Test scheduler"
86 | And I click on "Book slot" "button" in the "4:00 AM" "table_row"
87 | Then I should see "Upcoming slots"
88 | And I log out
89 |
90 | When I log in as "edteacher1"
91 | And I am on "Course 1" course homepage
92 | And I follow "Test scheduler"
93 | And I follow "Statistics"
94 | And I follow "All appointments"
95 | And I click on "//a[text()='Student 1']" "xpath_element" in the "4:00 AM" "table_row"
96 | And I set the following fields to these values:
97 | | Notes for appointment (visible to student) | note-for-appointment |
98 | | Confidential notes (visible to teacher only) | note-confidential |
99 | And I click on "Save changes" "button"
100 | Then I should see "note-for-appointment"
101 | And I should see "note-confidential"
102 |
103 | When I follow "Test scheduler"
104 | And I navigate to "Edit settings" in current page administration
105 | And I set the field "Use notes for appointments" to "0"
106 | And I click on "Save and display" "button"
107 | And I click on "//a[text()='Student 1']" "xpath_element" in the "4:00 AM" "table_row"
108 | Then I should not see "Notes for appointment"
109 | And I should not see "note-for-appointment"
110 | And I should not see "Confidential notes"
111 | And I should not see "note-confidential"
112 | And I click on "Save changes" "button"
113 | And I log out
114 |
115 | When I log in as "student1"
116 | And I am on "Course 1" course homepage
117 | And I follow "Test scheduler"
118 | Then I should not see "note-for-appointment"
119 | And I should not see "note-confidential"
120 | And I log out
121 |
122 | When I log in as "edteacher1"
123 | And I am on "Course 1" course homepage
124 | And I follow "Test scheduler"
125 | And I navigate to "Edit settings" in current page administration
126 | And I set the field "Use notes for appointments" to "1"
127 | And I click on "Save and display" "button"
128 | And I click on "//a[text()='Student 1']" "xpath_element" in the "4:00 AM" "table_row"
129 | Then I should see "Notes for appointment"
130 | And I should see "note-for-appointment"
131 | And I should not see "Confidential notes"
132 | And I should not see "note-confidential"
133 | And I click on "Save changes" "button"
134 | And I log out
135 |
136 | When I log in as "student1"
137 | And I am on "Course 1" course homepage
138 | And I follow "Test scheduler"
139 | Then I should see "note-for-appointment"
140 | And I should not see "note-confidential"
141 | And I log out
142 |
143 | When I log in as "edteacher1"
144 | And I am on "Course 1" course homepage
145 | And I follow "Test scheduler"
146 | And I navigate to "Edit settings" in current page administration
147 | And I set the field "Use notes for appointments" to "2"
148 | And I click on "Save and display" "button"
149 | And I click on "//a[text()='Student 1']" "xpath_element" in the "4:00 AM" "table_row"
150 | Then I should not see "Notes for appointment"
151 | And I should not see "note-for-appointment"
152 | And I should see "Confidential notes"
153 | And I should see "note-confidential"
154 | And I click on "Save changes" "button"
155 | And I log out
156 |
157 | When I log in as "student1"
158 | And I am on "Course 1" course homepage
159 | And I follow "Test scheduler"
160 | Then I should not see "note-for-appointment"
161 | And I should not see "note-confidential"
162 | And I log out
163 |
164 | When I log in as "edteacher1"
165 | And I am on "Course 1" course homepage
166 | And I follow "Test scheduler"
167 | And I navigate to "Edit settings" in current page administration
168 | And I set the field "Use notes for appointments" to "3"
169 | And I click on "Save and display" "button"
170 | And I click on "//a[text()='Student 1']" "xpath_element" in the "4:00 AM" "table_row"
171 | Then I should see "Notes for appointment"
172 | And I should see "note-for-appointment"
173 | And I should see "Confidential notes"
174 | And I should see "note-confidential"
175 | And I log out
--------------------------------------------------------------------------------
/README.txt:
--------------------------------------------------------------------------------
1 | Appointment Scheduler for Moodle
2 |
3 | This program is free software; you can redistribute it and/or modify
4 | it under the terms of the GNU General Public License as published by
5 | the Free Software Foundation; either version 3 of the License, or
6 | (at your option) any later version.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU General Public License for more details:
12 |
13 | http://www.gnu.org/copyleft/gpl.html
14 |
15 |
16 | === Description ===
17 |
18 | The Scheduler module helps you to schedule appointments with your students.
19 | Teachers specify time slots for meetings, students then choose one of them on Moodle.
20 | Teacher in turn can record the outcome of the meeting - and optionally a grade -
21 | within the scheduler.
22 |
23 | For further information, please see:
24 | http://docs.moodle.org/33/en/Scheduler_module
25 |
26 | (Note that the information there may refer to a previous version of the module.)
27 |
28 |
29 | === Installation instructions ===
30 |
31 | Place the code of the module into the mod/scheduler directory of your Moodle
32 | directory root. That is, the present file should be located at:
33 | mod/scheduler/README.txt
34 |
35 | For further installation instructions please see:
36 | http://docs.moodle.org/en/Installing_contributed_modules_or_plugins
37 |
38 | This module is intended for Moodle 3.3 and above.
39 |
40 |
41 | === Authors ===
42 |
43 | Current maintainer:
44 | Henning Bostelmann, University of York
45 |
46 | Based on previous work by:
47 |
48 | * Gustav Delius (until Moodle 1.7)
49 | * Valery Fremaux (Moodle 1.8 - Moodle 1.9)
50 |
51 | With further contributions taken from:
52 |
53 | * Vivek Arora (independent migration of the module to 2.0)
54 | * Andriy Semenets (Russian and Ukrainian localization)
55 | * Gaël Mifsud (French localization)
56 | * Various authors of the core Moodle code
57 |
58 |
59 | === Release notes ===
60 |
61 | --- Version 3.3 ---
62 |
63 | Intended for Moodle 3.3 and later.
64 |
65 | New features / improvements:
66 |
67 | Optionally, before making an appointment, students now see a booking screen
68 | in which they need to enter text, upload a file, and/or solve a captcha.
69 |
70 | Filter strings (e.g., multilang syntax) are now processed in course shortname,
71 | course fullname, and location fields.
72 |
73 | Export files can now include custom profile fields of students.
74 |
75 | Feature changes:
76 |
77 | For booking in groups, students now need to select explicitly which group
78 | they are booking for, or whether they want to make an individual booking.
79 | Individual bookings can be disabled via a global configuration setting.
80 |
81 | For viewing student's email addresses, the capability
82 | moodle/site:viewuseridentity is now required.
83 |
84 | When allowing an "unlimited" number of appointments, students will no longer
85 | be included in reminder e-mails if they have booked at least one slot.
86 |
87 | Refactoring / API changes:
88 |
89 | The function scheduler_get_user_fields() in customlib.php has changed
90 | signature. If you have customized it in an earlier version, you will want
91 | to edit your code.
92 |
93 | --- Version 3.1 ---
94 |
95 | Intended for Moodle 3.1 and later.
96 |
97 | New features / improvements:
98 |
99 | An additional "confidential note" field is supplied for appointments;
100 | the contents can be read by teachers only.
101 |
102 | Slot notes and appointment notes can now contain attachments.
103 |
104 | Students can now be allowed to see existing bookings of other students.
105 | See https://docs.moodle.org/31/en/Scheduler_Module_capabilities#Student_side
106 |
107 | Feature changes:
108 |
109 | Sending of invitations and reminders is no longer handled via a "mailto" link
110 | but rather via a webform, using Moodle's messaging system.
111 |
112 | The conflict detection feature (when creating new slots) has been reworked slightly.
113 | See https://docs.moodle.org/31/en/Scheduler:_Conflicts
114 |
115 | Refactoring / API changes:
116 |
117 | All email-related features now use the Messaging API.
118 |
119 | Appointment reminders and deletion of past unused slots are now handled via
120 | the Scheduled Tasks API.
121 |
122 | The new Search API is supported for the activity description only.
123 |
124 | --- Version 2.9 ---
125 |
126 | Intended for Moodle 2.9 and later.
127 |
128 | New features / improvements:
129 |
130 | The export screen now allows users to choose the format of the output file,
131 | as well as the data fields to include in the export. File format may
132 | slightly differ from previous versions.
133 |
134 | Improved gradebook integration: Grades overridden in the gradebook will now
135 | show up as such in the scheduler.
136 |
137 | Lists of students to be scheduled now take availability conditions
138 | (groups and groupings) into account.
139 |
140 | Feature changes:
141 |
142 | The handling of "group mode" in Scheduler has changed. The feature of "booking
143 | entire groups into a slot" is now controlled by a setting "Booking in groups"
144 | at the level of each scheduler. The setting "Group mode" in "Common module
145 | settings" is now used in line with usual Moodle conventions - setting it to,
146 | e.g., "Separate groups" will mean that students can only book slots with
147 | teachers in the same group. The old "Group mode" settings are automatically
148 | migrated to "Booking in groups" and the "Group mode" set to "None".
149 | If you have used group scheduling in previous versions, please check your data
150 | after migration.
151 |
152 | The student view has been redesigned. Bookable appointments are now displayed
153 | in pages of 25, and student select a slot by clicking a button "Book slot"
154 | rather then selecting with a radio button and clicking "Save choice".
155 |
156 | For using the Overview screen outside the current scheduler, e.g., for displaying
157 | all slots of a user across the site, users will now need extra permissions;
158 | see CONTRIB-5750 for details.
159 |
160 | Refactoring / API changes:
161 |
162 | Config settings have been migrated to the config_plugins table.
163 |
164 | --- Version 2.7 ---
165 |
166 | Intended for Moodle 2.7 and later.
167 |
168 | New features:
169 |
170 | Students can now be allowed to book several slots at a time.
171 | "Volatile slots" replaced with "guard time" - students cannot change their booking
172 | for slots closer than this time to the current time.
173 |
174 | Feature changes:
175 |
176 | "Notes" field will now be shown to students at booking time.
177 |
178 | Refactoring / API changes:
179 |
180 | Major refactoring of teacher view (slot list), student view (booking screen),
181 | teacher view of individual appointments, as well as of the backend.
182 | Security enhancements (sessionid parameter now used throughout).
183 | Adapted to changes in core API and to the new logging/event system (Event 2).
184 |
185 | --- Version 2.5 ---
186 |
187 | Intended for Moodle 2.5 and later.
188 |
189 | Module adapted to API changes Moodle core.
190 | "Add slot" and "Edit slot" forms refactored, now based on Moodle Forms.
191 | Language packs migrated to AMOS, removed from plugin codebase.
192 |
193 | --- Version 2.3 ---
194 |
195 | Intended for Moodle 2.3 and later; no major functional changes, but API adapted and minor enhancements.
196 |
197 | --- Version 2.0 ---
198 |
199 | No major functional changes over 1.9; bug fixes and API migration only. Requires 1.9 for database upgrades.
200 |
201 |
202 | === Technical notes ===
203 |
204 | The code of this module is rather old, some of it still predates even Moodle 1.9.
205 | It has now largely, but not completely, been adapted to the new APIs.
206 | The following aspects have been migrated, that is, malfunction in this respect
207 | should be considered a bug:
208 |
209 | * Gradebook integration
210 | * Moodle 2 backup
211 | * New rich text editor and file API
212 | * Localization / language packs
213 | * Logging / event system
214 | * Scheduler tasks API
215 | * Messaging API
216 |
217 | The module does not use any deprecated API as of Moodle 3.3.
218 |
--------------------------------------------------------------------------------
/tests/behat/conflicts.feature:
--------------------------------------------------------------------------------
1 | @mod_scheduler
2 | Feature: Teachers are warned about scheduling conflicts
3 | In order to create useful slots
4 | As a teacher
5 | I need to take care not to create conflicting schedules.
6 |
7 | Background:
8 | Given the following "users" exist:
9 | | username | firstname | lastname | email |
10 | | manager1 | Manager | 1 | manager1@example.com |
11 | | teacher1 | Teacher | 1 | teacher1@example.com |
12 | | teacher2 | Teacher | 2 | teacher2@example.com |
13 | | student1 | Student | 1 | student1@example.com |
14 | And the following "courses" exist:
15 | | fullname | shortname | category |
16 | | Course 1 | C1 | 0 |
17 | And the following "course enrolments" exist:
18 | | user | course | role |
19 | | teacher1 | C1 | editingteacher |
20 | | teacher2 | C1 | editingteacher |
21 | | student1 | C1 | student |
22 | And the following "system role assigns" exist:
23 | | user | role |
24 | | manager1 | manager |
25 | And the following "activities" exist:
26 | | activity | name | intro | course | idnumber | groupmode | schedulermode | maxbookings |
27 | | scheduler | Test scheduler A | n | C1 | schedulerA | 0 | oneonly | 1 |
28 | | scheduler | Test scheduler B | n | C1 | schedulerB | 0 | oneonly | 1 |
29 |
30 | @javascript
31 | Scenario: A teacher edits a single slot and is warned about conflicts
32 |
33 | Given I log in as "teacher1"
34 | And I am on "Course 1" course homepage
35 | And I add 5 slots 5 days ahead in "Test scheduler A" scheduler and I fill the form with:
36 | | Location | My office |
37 | And I am on "Course 1" course homepage
38 | And I add a slot 5 days ahead at 1000 in "Test scheduler B" scheduler and I fill the form with:
39 | | Location | My office |
40 |
41 | When I am on "Course 1" course homepage
42 | And I follow "Test scheduler A"
43 | And I click on "Edit" "link" in the "2:00 AM" "table_row"
44 | And I set the following fields to these values:
45 | | starttime[minute] | 40 |
46 | And I click on "Save changes" "button"
47 | Then I should see "conflict"
48 | And "Save changes" "button" should exist
49 | And I should see "3:00 AM"
50 | And I should not see "2:00 AM"
51 |
52 | When I set the following fields to these values:
53 | | starttime[hour] | 09 |
54 | | starttime[minute] | 55 |
55 | And I click on "Save changes" "button"
56 | Then I should see "conflict"
57 | And I should see "in course C1, scheduler Test scheduler B"
58 | And I should see "10:00 AM"
59 | And I should not see "2:00 AM"
60 | And "Save changes" "button" should exist
61 |
62 | When I set the following fields to these values:
63 | | starttime[hour] | 09 |
64 | | starttime[minute] | 55 |
65 | | Ignore scheduling conflicts | 1 |
66 | And I click on "Save changes" "button"
67 | Then I should see "slot updated"
68 | And "9:55 AM" "table_row" should exist
69 | And I log out
70 |
71 | @javascript
72 | Scenario: A manager edits slots for several teachers, creating conflicts
73 |
74 | Given I log in as "manager1"
75 | And I follow "Site home"
76 | And I navigate to "Turn editing on" in current page administration
77 | And I add the "Navigation" block if not present
78 | And I click on "Courses" "link" in the "Navigation" "block"
79 | And I am on "Course 1" course homepage
80 | And I add 6 slots 5 days ahead in "Test scheduler A" scheduler and I fill the form with:
81 | | Location | Office T1 |
82 | | Teacher | Teacher 1 |
83 | And I am on "Course 1" course homepage
84 | And I add 5 slots 5 days ahead in "Test scheduler B" scheduler and I fill the form with:
85 | | Location | Office T2 |
86 | | Teacher | Teacher 2 |
87 |
88 | When I am on "Course 1" course homepage
89 | And I follow "Test scheduler A"
90 | And I click on "Edit" "link" in the "3:00 AM" "table_row"
91 | And I set the following fields to these values:
92 | | starttime[hour] | 6 |
93 | | starttime[minute] | 40 |
94 | | duration | 5 |
95 | And I click on "Save changes" "button"
96 | Then I should see "conflict"
97 | And I should see "6:00 AM"
98 | And I should see "in this scheduler"
99 | And I should not see "3:00 AM"
100 | And "Save changes" "button" should exist
101 |
102 | When I set the following fields to these values:
103 | | starttime[hour] | 5 |
104 | | starttime[minute] | 40 |
105 | | duration | 5 |
106 | | Teacher | Teacher 2 |
107 | And I click on "Save changes" "button"
108 | Then I should see "conflict"
109 | And I should see "5:00 AM"
110 | And I should see "in course C1, scheduler Test scheduler B"
111 | And I should not see "3:00 AM"
112 | And "Save changes" "button" should exist
113 |
114 | When I set the following fields to these values:
115 | | starttime[hour] | 6 |
116 | | starttime[minute] | 40 |
117 | | duration | 5 |
118 | | Teacher | Teacher 2 |
119 | And I click on "Save changes" "button"
120 | Then I should not see "conflict"
121 | And I should see "slot updated"
122 | And "6:40 AM" "table_row" should exist
123 | And "Save changes" "button" should not exist
124 | And I log out
125 |
126 |
127 | @javascript
128 | Scenario: A teacher adds a series of slots, creating conflicts
129 |
130 | Given I log in as "teacher1"
131 | And I am on "Course 1" course homepage
132 | And I add a slot 5 days ahead at 0125 in "Test scheduler A" scheduler and I fill the form with:
133 | | Location | My office |
134 | | duration | 15 |
135 | # Blocks 3 other slots on a 1-hour grid
136 | And I am on "Course 1" course homepage
137 | And I add a slot 5 days ahead at 0225 in "Test scheduler A" scheduler and I fill the form with:
138 | | Location | My office |
139 | | duration | 100 |
140 | # Booked slot - must not be deleted as conflict
141 | And I am on "Course 1" course homepage
142 | And I add a slot 5 days ahead at 0855 in "Test scheduler A" scheduler and I fill the form with:
143 | | Location | My office |
144 | | duration | 10 |
145 | | studentid[0] | Student 1 |
146 | # Slot in other scheduler - must not be deleted as conflict
147 | And I am on "Course 1" course homepage
148 | And I add a slot 5 days ahead at 0605 in "Test scheduler B" scheduler and I fill the form with:
149 | | Location | My office |
150 | | duration | 20 |
151 |
152 | When I am on "Course 1" course homepage
153 | And I add 10 slots 5 days ahead in "Test scheduler A" scheduler and I fill the form with:
154 | | Location | Lecture hall |
155 | Then I should see "conflicting slots"
156 | And I should not see "deleted"
157 | And I should see "4 slots have been added"
158 | And "1:25 AM" "table_row" should exist
159 | And "2:25 AM" "table_row" should exist
160 | And "8:55 AM" "table_row" should exist
161 | And "1:00 AM" "table_row" should not exist
162 | And "2:00 AM" "table_row" should not exist
163 | And "3:00 AM" "table_row" should not exist
164 | And "4:00 AM" "table_row" should not exist
165 | And "5:00 AM" "table_row" should exist
166 | And "6:00 AM" "table_row" should not exist
167 | And "7:00 AM" "table_row" should exist
168 | And "8:00 AM" "table_row" should exist
169 | And "9:00 AM" "table_row" should not exist
170 | And "10:00 AM" "table_row" should exist
171 | And I am on "Course 1" course homepage
172 | And I follow "Test scheduler B"
173 | And "6:05 AM" "table_row" should exist
174 |
175 | When I am on "Course 1" course homepage
176 | And I add 10 slots 5 days ahead in "Test scheduler A" scheduler and I fill the form with:
177 | | Location | Lecture hall |
178 | | Force when overlap | 1 |
179 | Then I should see "conflicting slots"
180 | And I should see "deleted"
181 | And I should see "8 slots have been added"
182 | And "1:25 AM" "table_row" should not exist
183 | And "2:25 AM" "table_row" should not exist
184 | And "9:55 AM" "table_row" should not exist
185 | And "1:00 AM" "table_row" should exist
186 | And "2:00 AM" "table_row" should exist
187 | And "3:00 AM" "table_row" should exist
188 | And "4:00 AM" "table_row" should exist
189 | And "5:00 AM" "table_row" should exist
190 | And "6:00 AM" "table_row" should not exist
191 | And "7:00 AM" "table_row" should exist
192 | And "8:00 AM" "table_row" should exist
193 | And "9:00 AM" "table_row" should not exist
194 | And "10:00 AM" "table_row" should exist
195 | And I am on "Course 1" course homepage
196 | And I follow "Test scheduler B"
197 | And "6:05 AM" "table_row" should exist
198 |
199 | And I log out
200 |
--------------------------------------------------------------------------------
/tests/privacy_test.php:
--------------------------------------------------------------------------------
1 | .
16 |
17 | /**
18 | * Data provider tests.
19 | *
20 | * @package mod_scheduler
21 | * @category test
22 | * @copyright 2018 Henning Bostelmann
23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 | */
25 |
26 | defined('MOODLE_INTERNAL') || die();
27 | global $CFG;
28 |
29 | use core_privacy\tests\provider_testcase;
30 | use mod_scheduler\privacy\provider;
31 | use core_privacy\local\request\approved_contextlist;
32 | use core_privacy\local\request\approved_userlist;
33 | use core_privacy\local\request\writer;
34 |
35 | require_once($CFG->dirroot.'/mod/scheduler/locallib.php');
36 |
37 | /**
38 | * Data provider testcase class.
39 | *
40 | * @group mod_scheduler
41 | * @copyright 2018 Henning Bostelmann
42 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
43 | */
44 | class mod_scheduler_privacy_testcase extends provider_testcase {
45 |
46 | /**
47 | * @var int course_module id used for testing
48 | */
49 | protected $moduleid;
50 |
51 | /**
52 | * @var the module context used for testing
53 | */
54 | protected $context;
55 |
56 | /**
57 | * @var int Course id used for testing
58 | */
59 | protected $courseid;
60 |
61 | /**
62 | * @var int Scheduler id used for testing
63 | */
64 | protected $schedulerid;
65 |
66 | /**
67 | * @var int One of the slots used for testing
68 | */
69 | protected $slotid;
70 |
71 | /**
72 | * @var int first student used in testing - a student that has an appointment
73 | */
74 | protected $student1;
75 |
76 | /**
77 | * @var int second student used in testing - a student that has an appointment
78 | */
79 | protected $student2;
80 |
81 | /**
82 | * @var array all students (only id) involved in the scheduler
83 | */
84 | protected $allstudents;
85 |
86 | protected function setUp() {
87 | global $DB, $CFG;
88 |
89 | $this->resetAfterTest(true);
90 |
91 | $course = $this->getDataGenerator()->create_course();
92 | $this->courseid = $course->id;
93 |
94 |
95 | $this->student1 = $this->getDataGenerator()->create_user();
96 | $this->student2 = $this->getDataGenerator()->create_user();
97 | $this->allstudents = [$this->student1->id, $this->student2->id];
98 |
99 | $options = array();
100 | $options['slottimes'] = array();
101 | $options['slotstudents'] = array();
102 | for ($c = 0; $c < 4; $c++) {
103 | $options['slottimes'][$c] = time() + ($c + 1) * DAYSECS;
104 | $stud = $this->getDataGenerator()->create_user()->id;
105 | $this->allstudents[] = $stud;
106 | $options['slotstudents'][$c] = array($stud);
107 | }
108 | $options['slottimes'][4] = time() + 10 * DAYSECS;
109 | $options['slottimes'][5] = time() + 11 * DAYSECS;
110 | $options['slotstudents'][5] = array(
111 | $this->student1->id,
112 | $this->student2->id
113 | );
114 |
115 | $scheduler = $this->getDataGenerator()->create_module('scheduler', array('course' => $course->id), $options);
116 | $coursemodule = $DB->get_record('course_modules', array('id' => $scheduler->cmid));
117 |
118 | $this->schedulerid = $scheduler->id;
119 | $this->moduleid = $coursemodule->id;
120 | $this->context = context_module::instance($scheduler->cmid);
121 |
122 | $recs = $DB->get_records('scheduler_slots', array('schedulerid' => $scheduler->id), 'id DESC');
123 | $this->slotid = array_keys($recs)[0];
124 | $this->appointmentids = array_keys($DB->get_records('scheduler_appointment', array('slotid' => $this->slotid)));
125 | }
126 |
127 | /**
128 | * Asserts whether or not an appointment exists in a scheduler for a certian student.
129 | *
130 | * @param int $schedulerid the id of the scheduler to test
131 | * @param int $studentid the user id of the student to test
132 | * @param boolean $expected whether an appointment is expected to exist or not
133 | */
134 | private function assert_appointment_status($schedulerid, $studentid, $expected) {
135 | global $DB;
136 |
137 | $sql = "SELECT * FROM {scheduler} s
138 | JOIN {scheduler_slots} t ON t.schedulerid = s.id
139 | JOIN {scheduler_appointment} a ON a.slotid = t.id
140 | WHERE s.id = :schedulerid AND a.studentid = :studentid";
141 |
142 | $params = array('schedulerid' => $schedulerid, 'studentid' => $studentid);
143 | $actual = $DB->record_exists_sql($sql, $params);
144 | $this->assertEquals($expected, $actual, "Checking whether student $studentid has appointment in scheduler $schedulerid");
145 | }
146 |
147 | /**
148 | * Test getting the contexts for a user.
149 | */
150 | public function test_get_contexts_for_userid() {
151 |
152 | // Get contexts for the first user.
153 | $contextids = provider::get_contexts_for_userid($this->student1->id)->get_contextids();
154 | $this->assertEquals([$this->context->id], $contextids, '', 0.0, 10, true);
155 | }
156 |
157 | /**
158 | * Test getting the users within a context.
159 | */
160 | public function test_get_users_in_context() {
161 | global $DB;
162 | $component = 'mod_scheduler';
163 |
164 | // Ensure userlist for context contains all users.
165 | $userlist = new \core_privacy\local\request\userlist($this->context, $component);
166 | provider::get_users_in_context($userlist);
167 |
168 | $expected = $this->allstudents;
169 | $expected[] = 2; // The teacher involved.
170 | $actual = $userlist->get_userids();
171 | sort($expected);
172 | sort($actual);
173 | $this->assertEquals($expected, $actual);
174 | }
175 |
176 |
177 | /**
178 | * Export test for teacher data.
179 | */
180 | public function test_export_teacher_data() {
181 | global $DB;
182 |
183 | // Export all contexts for the teacher.
184 | $contextids = [$this->context->id];
185 | $teacher = $DB->get_record('user', array('id' => 2));
186 | $appctx = new approved_contextlist($teacher, 'mod_scheduler', $contextids);
187 | provider::export_user_data($appctx);
188 | $data = writer::with_context($this->context)->get_data([]);
189 | $this->assertNotEmpty($data);
190 | }
191 |
192 | /**
193 | * Export test for student1's data.
194 | */
195 | public function test_export_user_data1() {
196 |
197 | // Export all contexts for the first user.
198 | $contextids = [$this->context->id];
199 | $appctx = new approved_contextlist($this->student1, 'mod_scheduler', $contextids);
200 | provider::export_user_data($appctx);
201 | $data = writer::with_context($this->context)->get_data([]);
202 | $this->assertNotEmpty($data);
203 | }
204 |
205 | /**
206 | * Test for delete_data_for_all_users_in_context().
207 | */
208 | public function test_delete_data_for_all_users_in_context() {
209 | provider::delete_data_for_all_users_in_context($this->context);
210 |
211 | foreach($this->allstudents as $u) {
212 | $this->assert_appointment_status($this->schedulerid, $u, false);
213 | }
214 | }
215 |
216 | /**
217 | * Test for delete_data_for_user().
218 | */
219 | public function test_delete_data_for_user() {
220 | $appctx = new approved_contextlist($this->student1, 'mod_scheduler', [$this->context->id]);
221 | provider::delete_data_for_user($appctx);
222 |
223 | $this->assert_appointment_status($this->schedulerid, $this->student1->id, false);
224 | $this->assert_appointment_status($this->schedulerid, $this->student2->id, true);
225 |
226 | }
227 |
228 | /**
229 | * Test for delete_data_for_users().
230 | */
231 | public function test_delete_data_for_users() {
232 | $component = 'mod_scheduler';
233 |
234 | $approveduserids = [$this->student1->id, $this->student2->id];
235 | $approvedlist = new approved_userlist($this->context, $component, $approveduserids);
236 | provider::delete_data_for_users($approvedlist);
237 |
238 | $this->assert_appointment_status($this->schedulerid, $this->student1->id, false);
239 | $this->assert_appointment_status($this->schedulerid, $this->student2->id, false);
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/datelist.php:
--------------------------------------------------------------------------------
1 | libdir.'/tablelib.php');
14 |
15 | $PAGE->set_docs_path('mod/scheduler/datelist');
16 |
17 | $scope = optional_param('scope', 'activity', PARAM_TEXT);
18 | if (!in_array($scope, array('activity', 'course', 'site'))) {
19 | $scope = 'activity';
20 | }
21 | $teacherid = optional_param('teacherid', 0, PARAM_INT);
22 |
23 | if ($scope == 'site') {
24 | $scopecontext = context_system::instance();
25 | } else if ($scope == 'course') {
26 | $scopecontext = context_course::instance($scheduler->courseid);
27 | } else {
28 | $scopecontext = $context;
29 | }
30 |
31 | if (!has_capability('mod/scheduler:seeoverviewoutsideactivity', $context)) {
32 | $scope = 'activity';
33 | }
34 | if (!has_capability('mod/scheduler:canseeotherteachersbooking', $scopecontext)) {
35 | $teacherid = 0;
36 | }
37 |
38 | $taburl = new moodle_url('/mod/scheduler/view.php',
39 | array('id' => $scheduler->cmid, 'what' => 'datelist', 'scope' => $scope, 'teacherid' => $teacherid));
40 | $returnurl = new moodle_url('/mod/scheduler/view.php', array('id' => $scheduler->cmid));
41 |
42 | $PAGE->set_url($taburl);
43 |
44 | echo $output->header();
45 |
46 | // Print top tabs.
47 |
48 | echo $output->teacherview_tabs($scheduler, $taburl, 'datelist');
49 |
50 |
51 | // Find active group in case that group mode is in use.
52 | $currentgroupid = 0;
53 | $groupmode = groups_get_activity_groupmode($scheduler->cm);
54 | if ($groupmode) {
55 | $currentgroupid = groups_get_activity_group($scheduler->cm, true);
56 |
57 | echo html_writer::start_div('dropdownmenu');
58 | groups_print_activity_menu($scheduler->cm, $taburl);
59 | echo html_writer::end_div();
60 | }
61 |
62 | $scopemenukey = 'scopemenuself';
63 | if (has_capability('mod/scheduler:canseeotherteachersbooking', $scopecontext)) {
64 | $teachers = $scheduler->get_available_teachers($currentgroupid);
65 | $teachermenu = array();
66 | foreach ($teachers as $teacher) {
67 | $teachermenu[$teacher->id] = fullname($teacher);
68 | }
69 | $select = $output->single_select($taburl, 'teacherid', $teachermenu, $teacherid,
70 | array(0 => get_string('myself', 'scheduler')), 'teacheridform');
71 | echo html_writer::div(get_string('teachersmenu', 'scheduler', $select), 'dropdownmenu');
72 | $scopemenukey = 'scopemenu';
73 | }
74 | if (has_capability('mod/scheduler:seeoverviewoutsideactivity', $context)) {
75 | $scopemenu = array('activity' => get_string('thisscheduler', 'scheduler'),
76 | 'course' => get_string('thiscourse', 'scheduler'),
77 | 'site' => get_string('thissite', 'scheduler'));
78 | $select = $output->single_select($taburl, 'scope', $scopemenu, $scope, null, 'scopeform');
79 | echo html_writer::div(get_string($scopemenukey, 'scheduler', $select), 'dropdownmenu');
80 | }
81 |
82 | // Getting date list.
83 |
84 | $params = array();
85 | $params['teacherid'] = $teacherid == 0 ? $USER->id : $teacherid;
86 | $params['courseid'] = $scheduler->courseid;
87 | $params['schedulerid'] = $scheduler->id;
88 |
89 | $scopecond = '';
90 | if ($scope == 'activity') {
91 | $scopecond = ' AND sc.id = :schedulerid';
92 | } else if ($scope == 'course') {
93 | $scopecond = ' AND c.id = :courseid';
94 | }
95 |
96 | $sql = "SELECT a.id AS id, ".
97 | user_picture::fields('u1', array('email', 'department'), 'studentid', 'student').", ".
98 | $DB->sql_fullname('u1.firstname', 'u1.lastname')." AS studentfullname,
99 | a.appointmentnote,
100 | a.appointmentnoteformat,
101 | a.teachernote,
102 | a.teachernoteformat,
103 | a.grade,
104 | sc.name,
105 | sc.id AS schedulerid,
106 | sc.scale,
107 | c.shortname AS courseshort,
108 | c.id AS courseid, ".
109 | user_picture::fields('u2', null, 'teacherid').",
110 | s.id AS sid,
111 | s.starttime,
112 | s.duration,
113 | s.appointmentlocation,
114 | s.notes,
115 | s.notesformat
116 | FROM {course} c,
117 | {scheduler} sc,
118 | {scheduler_appointment} a,
119 | {scheduler_slots} s,
120 | {user} u1,
121 | {user} u2
122 | WHERE c.id = sc.course AND
123 | sc.id = s.schedulerid AND
124 | a.slotid = s.id AND
125 | u1.id = a.studentid AND
126 | u2.id = s.teacherid AND
127 | s.teacherid = :teacherid ".
128 | $scopecond;
129 |
130 | $sqlcount =
131 | "SELECT COUNT(*)
132 | FROM {course} c,
133 | {scheduler} sc,
134 | {scheduler_appointment} a,
135 | {scheduler_slots} s
136 | WHERE c.id = sc.course AND
137 | sc.id = s.schedulerid AND
138 | a.slotid = s.id AND
139 | s.teacherid = :teacherid ".
140 | $scopecond;
141 |
142 | $numrecords = $DB->count_records_sql($sqlcount, $params);
143 |
144 |
145 | $limit = 30;
146 |
147 | if ($numrecords) {
148 |
149 | // Make the table of results.
150 |
151 | $coursestr = get_string('course', 'scheduler');
152 | $schedulerstr = get_string('scheduler', 'scheduler');
153 | $whenstr = get_string('when', 'scheduler');
154 | $wherestr = get_string('where', 'scheduler');
155 | $whostr = get_string('who', 'scheduler');
156 | $wherefromstr = get_string('department', 'scheduler');
157 | $whatstr = get_string('what', 'scheduler');
158 | $whatresultedstr = get_string('whatresulted', 'scheduler');
159 | $whathappenedstr = get_string('whathappened', 'scheduler');
160 |
161 | $tablecolumns = array('courseshort', 'schedulerid', 'starttime', 'appointmentlocation',
162 | 'studentfullname', 'studentdepartment', 'notes', 'grade', 'appointmentnote');
163 | $tableheaders = array($coursestr, $schedulerstr, $whenstr, $wherestr,
164 | $whostr, $wherefromstr, $whatstr, $whatresultedstr, $whathappenedstr);
165 |
166 | $table = new flexible_table('mod-scheduler-datelist');
167 | $table->define_columns($tablecolumns);
168 | $table->define_headers($tableheaders);
169 |
170 | $table->define_baseurl($taburl);
171 |
172 | $table->sortable(true, 'when'); // Sorted by date by default.
173 | $table->collapsible(true); // Allow column hiding.
174 | $table->initialbars(true);
175 |
176 | $table->column_suppress('courseshort');
177 | $table->column_suppress('schedulerid');
178 | $table->column_suppress('starttime');
179 | $table->column_suppress('studentfullname');
180 | $table->column_suppress('notes');
181 |
182 | $table->set_attribute('id', 'dates');
183 | $table->set_attribute('class', 'datelist');
184 |
185 | $table->column_class('course', 'datelist_course');
186 | $table->column_class('scheduler', 'datelist_scheduler');
187 |
188 | $table->setup();
189 |
190 | // Get extra query parameters from flexible_table behaviour.
191 | $where = $table->get_sql_where();
192 | $sort = $table->get_sql_sort();
193 | $table->pagesize($limit, $numrecords);
194 |
195 | if (!empty($sort)) {
196 | $sql .= " ORDER BY $sort";
197 | }
198 |
199 | $results = $DB->get_records_sql($sql, $params);
200 |
201 | foreach ($results as $id => $row) {
202 | $courseurl = new moodle_url('/course/view.php', array('id' => $row->courseid));
203 | $coursedata = html_writer::link($courseurl, format_string($row->courseshort));
204 | $schedulerurl = new moodle_url('/mod/scheduler/view.php', array('a' => $row->schedulerid));
205 | $schedulerdata = html_writer::link($schedulerurl, format_string($row->name));
206 | $a = mod_scheduler_renderer::slotdatetime($row->starttime, $row->duration);
207 | $whendata = get_string('slotdatetime', 'scheduler', $a);
208 | $whourl = new moodle_url('/mod/scheduler/view.php',
209 | array('what' => 'viewstudent', 'a' => $row->schedulerid, 'appointmentid' => $row->id));
210 | $whodata = html_writer::link($whourl, $row->studentfullname);
211 | $whatdata = $output->format_notes($row->notes, $row->notesformat, $context, 'slotnote', $row->sid);
212 | $gradedata = $row->scale == 0 ? '' : $output->format_grade($row->scale, $row->grade);
213 |
214 | $dataset = array(
215 | $coursedata,
216 | $schedulerdata,
217 | $whendata,
218 | format_string($row->appointmentlocation),
219 | $whodata,
220 | $row->studentdepartment,
221 | $whatdata,
222 | $gradedata,
223 | $output->format_appointment_notes($scheduler, $row) );
224 | $table->add_data($dataset);
225 | }
226 | $table->print_html();
227 | echo $output->continue_button($returnurl);
228 | } else {
229 | notice(get_string('noresults', 'scheduler'), $returnurl);
230 | }
231 |
232 | echo $output->footer();
--------------------------------------------------------------------------------
/mailtemplatelib.php:
--------------------------------------------------------------------------------
1 | id) and $course->id != SITEID and !empty($course->lang)) {
32 | // Course language overrides user language.
33 | $return = $course->lang;
34 | } else if (!empty($user->lang)) {
35 | $return = $user->lang;
36 | } else if (isset ($CFG->lang)) {
37 | $return = $CFG->lang;
38 | } else {
39 | $return = 'en';
40 | }
41 |
42 | return $return;
43 | }
44 |
45 | /**
46 | * Gets the content of an e-mail from language strings.
47 | *
48 | * Looks for the language string email_$template_$format and replaces the parameter values.
49 | *
50 | * @param string $template the template's identified
51 | * @param string $format the mail format ('subject', 'html' or 'plain')
52 | * @param array $parameters an array ontaining pairs of parm => data to replace in template
53 | * @param string $module module to use language strings from
54 | * @param string $lang language to use
55 | * @return a fully resolved template where all data has been injected
56 | *
57 | */
58 | public static function compile_mail_template($template, $format, $parameters, $module = 'scheduler', $lang = null) {
59 | $params = array ();
60 | foreach ($parameters as $key => $value) {
61 | $params [strtolower($key)] = $value;
62 | }
63 | $mailstr = get_string_manager()->get_string("email_{$template}_{$format}", $module, $params, $lang);
64 | return $mailstr;
65 | }
66 |
67 | /**
68 | * Sends a message based on a template.
69 | * Several template substitution values are automatically filled by this routine.
70 | *
71 | * @uses $CFG
72 | * @uses $SITE
73 | * @param string $modulename
74 | * name of the module that sends the message
75 | * @param string $messagename
76 | * name of the message in messages.php
77 | * @param int $isnotification
78 | * 1 for notifications, 0 for personal messages
79 | * @param user $sender
80 | * A {@link $USER} object describing the sender
81 | * @param user $recipient
82 | * A {@link $USER} object describing the recipient
83 | * @param object $course
84 | * The course that the activity is in. Can be null.
85 | * @param string $template
86 | * the mail template name as in language config file (without "_html" part)
87 | * @param array $parameters
88 | * a hash containing pairs of parm => data to replace in template
89 | * @return bool|int Returns message id if message was sent OK, "false" if there was another sort of error.
90 | */
91 | public static function send_message_from_template($modulename, $messagename, $isnotification,
92 | stdClass $sender, stdClass $recipient, $course,
93 | $template, array $parameters) {
94 | global $CFG;
95 | global $SITE;
96 |
97 | $lang = self::get_message_language($recipient, $course);
98 |
99 | $defaultvars = array (
100 | 'SITE' => $SITE->fullname,
101 | 'SITE_SHORT' => $SITE->shortname,
102 | 'SITE_URL' => $CFG->wwwroot,
103 | 'SENDER' => fullname ( $sender ),
104 | 'RECIPIENT' => fullname ( $recipient )
105 | );
106 |
107 | if ($course) {
108 | $defaultvars['COURSE_SHORT'] = format_string($course->shortname);
109 | $defaultvars['COURSE'] = format_string($course->fullname);
110 | $defaultvars['COURSE_URL'] = $CFG->wwwroot . '/course/view.php?id=' . $course->id;
111 | }
112 |
113 | $vars = array_merge($defaultvars, $parameters);
114 |
115 | $message = new \core\message\message();
116 | $message->component = $modulename;
117 | $message->name = $messagename;
118 | $message->userfrom = $sender;
119 | $message->userto = $recipient;
120 | $message->subject = self::compile_mail_template($template, 'subject', $vars, $modulename, $lang);
121 | $message->fullmessage = self::compile_mail_template($template, 'plain', $vars, $modulename, $lang);
122 | $message->fullmessageformat = FORMAT_PLAIN;
123 | $message->fullmessagehtml = self::compile_mail_template ( $template, 'html', $vars, $modulename, $lang );
124 | $message->notification = '1';
125 | $message->courseid = $course->id;
126 | $message->contexturl = $defaultvars['COURSE_URL'];
127 | $message->contexturlname = $course->fullname;
128 |
129 | $msgid = message_send($message);
130 | return $msgid;
131 | }
132 |
133 | /**
134 | * Construct an array with subtitution rules for mail templates, relating to
135 | * a single appointment. Any of the parameters can be null.
136 | *
137 | * @param scheduler_instance $scheduler The scheduler instance
138 | * @param scheduler_slot $slot The slot data as an MVC object, may be null
139 | * @param user $teacher A {@link $USER} object describing the attendant (teacher)
140 | * @param user $student A {@link $USER} object describing the attendee (student)
141 | * @param object $course A course object relating to the ontext of the message
142 | * @param object $recipient A {@link $USER} object describing the recipient of the message
143 | * (used for determining the message language)
144 | * @return array A hash with mail template substitutions
145 | */
146 | public static function get_scheduler_variables(scheduler_instance $scheduler, $slot,
147 | $teacher, $student, $course, $recipient) {
148 |
149 | global $CFG;
150 |
151 | $lang = self::get_message_language($recipient, $course);
152 | // Force any string formatting to happen in the target language.
153 | $oldlang = force_current_language($lang);
154 |
155 | $tz = core_date::get_user_timezone($recipient);
156 |
157 | $vars = array();
158 |
159 | if ($scheduler) {
160 | $vars['MODULE'] = format_string($scheduler->name);
161 | $vars['STAFFROLE'] = $scheduler->get_teacher_name();
162 | $vars['SCHEDULER_URL'] = $CFG->wwwroot.'/mod/scheduler/view.php?id='.$scheduler->cmid;
163 | }
164 | if ($slot) {
165 | $vars ['DATE'] = userdate($slot->starttime, get_string('strftimedate'), $tz);
166 | $vars ['TIME'] = userdate($slot->starttime, get_string('strftimetime'), $tz);
167 | $vars ['ENDTIME'] = userdate($slot->endtime, get_string('strftimetime'), $tz);
168 | $vars ['LOCATION'] = format_string($slot->appointmentlocation);
169 | }
170 | if ($teacher) {
171 | $vars['ATTENDANT'] = fullname($teacher);
172 | $vars['ATTENDANT_URL'] = $CFG->wwwroot.'/user/view.php?id='.$teacher->id.'&course='.$scheduler->course;
173 | }
174 | if ($student) {
175 | $vars['ATTENDEE'] = fullname($student);
176 | $vars['ATTENDEE_URL'] = $CFG->wwwroot.'/user/view.php?id='.$student->id.'&course='.$scheduler->course;
177 | }
178 |
179 | // Reset language settings.
180 | force_current_language($oldlang);
181 |
182 | return $vars;
183 |
184 | }
185 |
186 |
187 | /**
188 | * Send a notification message about a scheduler slot.
189 | *
190 | * @param scheduler_slot $slot the slot that the notification relates to
191 | * @param string $messagename name of message as in db/message.php
192 | * @param string $template template name to use (language string up to prefix/postfix)
193 | * @param stdClass $sender user record for sender
194 | * @param stdClass $recipient user record for recipient
195 | * @param stdClass $teacher user record for teacher
196 | * @param stdClass $student user record for student
197 | * @param stdClass $course course record
198 | */
199 | public static function send_slot_notification(scheduler_slot $slot, $messagename, $template,
200 | stdClass $sender, stdClass $recipient,
201 | stdClass $teacher, stdClass $student, stdClass $course) {
202 | $vars = self::get_scheduler_variables($slot->get_scheduler(), $slot, $teacher, $student, $course, $recipient);
203 | self::send_message_from_template('mod_scheduler', $messagename, 1, $sender, $recipient, $course, $template, $vars);
204 | }
205 |
206 | }
--------------------------------------------------------------------------------