Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
68.47% |
139 / 203 |
|
41.67% |
5 / 12 |
CRAP | n/a |
0 / 0 |
|
tool_driprelease_extend_navigation_course | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
driprelease_update | |
100.00% |
37 / 37 |
|
100.00% |
1 / 1 |
2 | |||
manage_selections | |
86.36% |
19 / 22 |
|
0.00% |
0 / 1 |
6.09 | |||
get_course_module_types | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
get_modules | |
88.89% |
8 / 9 |
|
0.00% |
0 / 1 |
4.02 | |||
add_header | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
get_table_data | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
6 | |||
row_fill | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
6 | |||
update_availability | |
66.00% |
33 / 50 |
|
0.00% |
0 / 1 |
17.66 | |||
get_availability | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
7 | |||
driprelease_calculate_availability | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
2 | |||
get_sequence | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
20 |
1 | <?php |
2 | // This file is part of Moodle - https://moodle.org/ |
3 | // |
4 | // Moodle is free software: you can redistribute it and/or modify |
5 | // it under the terms of the GNU General Public License as published by |
6 | // the Free Software Foundation, either version 3 of the License, or |
7 | // (at your option) any later version. |
8 | // |
9 | // Moodle is distributed in the hope that it will be useful, |
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | // GNU General Public License for more details. |
13 | // |
14 | // You should have received a copy of the GNU General Public License |
15 | // along with Moodle. If not, see <https://www.gnu.org/licenses/>. |
16 | |
17 | /** |
18 | * Library of interface functions and constants. |
19 | * |
20 | * @package tool_driprelease |
21 | * @copyright 2022 Marcus Green |
22 | * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later |
23 | */ |
24 | use core_availability\tree; |
25 | |
26 | /** |
27 | * A core callback to make ther plugin appear in the "more" dropdown of courses |
28 | * |
29 | * @param navigation_node $navigation |
30 | * @param stdClass $course |
31 | * @param context_course $context |
32 | * @return void |
33 | */ |
34 | function tool_driprelease_extend_navigation_course(navigation_node $navigation, stdClass $course, context_course $context) { |
35 | if (has_capability('moodle/course:update', $context)) { |
36 | $url = new moodle_url('/admin/tool/driprelease/view.php', ['courseid' => $course->id]); |
37 | $name = get_string('pluginname', 'tool_driprelease'); |
38 | $navigation->add($name, $url, navigation_node::TYPE_SETTING, null, null, new pix_icon('icon', 'Driprelease', |
39 | 'tool_driprelease')); |
40 | } |
41 | } |
42 | |
43 | /** |
44 | * Adds/updates an instance of driprelease and its related |
45 | * |
46 | * @param \stdClass $fromform |
47 | * @param int $courseid |
48 | * @return array |
49 | */ |
50 | function driprelease_update(\stdClass $fromform , int $courseid): array { |
51 | global $DB; |
52 | $dripreleaseid = $DB->get_field('tool_driprelease', 'id', ['courseid' => $courseid]); |
53 | |
54 | if ($dripreleaseid) { |
55 | $driprelease = (object) [ |
56 | 'id' => $dripreleaseid, |
57 | 'modtype' => $fromform->modtype, |
58 | 'courseid' => $courseid, |
59 | 'activitiespersession' => $fromform->activitiespersession, |
60 | 'sessionlength' => $fromform->sessiongroup['sessionlength'] ?? $fromform->sessionlength, |
61 | 'schedulestart' => $fromform->schedulestart, |
62 | 'schedulefinish' => $fromform->schedulefinish, |
63 | 'coursegroup' => $fromform->coursegroup, |
64 | 'stayavailable' => $fromform->stayavailable, |
65 | 'hideunselected' => $fromform->hideunselected, |
66 | 'resetunselected' => $fromform->resetunselected, |
67 | 'displaydisabled' => $fromform->displaydisabled, |
68 | ]; |
69 | $DB->update_record('tool_driprelease', $driprelease); |
70 | manage_selections($fromform, $dripreleaseid); |
71 | } else { |
72 | $driprelease = (object) [ |
73 | 'courseid' => $courseid, |
74 | 'modtype' => $fromform->modtype, |
75 | 'activitiespersession' => $fromform->activitiespersession, |
76 | 'sessionlength' => $fromform->sessionlength, |
77 | 'schedulestart' => $fromform->schedulestart, |
78 | 'schedulefinish' => $fromform->schedulefinish, |
79 | 'coursegroup' => $fromform->coursegroup, |
80 | 'stayavailable' => $fromform->stayavailable, |
81 | 'hideunselected' => $fromform->hideunselected, |
82 | 'resetunselected' => $fromform->resetunselected, |
83 | 'displaydisabled' => $fromform->displaydisabled, |
84 | ]; |
85 | $dripreleaseid = $DB->insert_record('tool_driprelease', $driprelease); |
86 | $driprelease->id = $dripreleaseid; |
87 | } |
88 | manage_selections($fromform, $dripreleaseid); |
89 | $selections = $DB->get_records_menu('tool_driprelease_cmids', ['driprelease' => $dripreleaseid], null, 'id,coursemoduleid'); |
90 | return [$selections, $driprelease]; |
91 | } |
92 | |
93 | /** |
94 | * Process the checkbox selections and upsert the database records |
95 | * |
96 | * @param \stdClass $fromform |
97 | * @param int $dripreleaseid |
98 | * @return int $insertedcount // For future testing purposes. |
99 | */ |
100 | function manage_selections(\stdClass $fromform, int $dripreleaseid): int { |
101 | global $DB; |
102 | if (!isset($fromform->activitygroup)) { |
103 | return 0; |
104 | } |
105 | $moduleids = []; |
106 | foreach ($fromform->activitygroup as $key => $value) { |
107 | if ($value == 1) { |
108 | $moduleids[] = explode('_', $key)[1]; |
109 | } |
110 | } |
111 | $selections = $DB->get_records_menu('tool_driprelease_cmids', ['driprelease' => $dripreleaseid], null, 'id,coursemoduleid'); |
112 | |
113 | $todelete = array_diff($selections, $moduleids); |
114 | if ($todelete) { |
115 | list($insql, $inparams) = $DB->get_in_or_equal($todelete); |
116 | $DB->delete_records_select("tool_driprelease_cmids", "coursemoduleid $insql", $inparams); |
117 | } |
118 | $toinsert = array_diff($moduleids, $selections); |
119 | $insertedcount = 0; |
120 | foreach ($toinsert as $moduleid) { |
121 | $dataobject = (object) [ |
122 | 'driprelease' => $dripreleaseid, |
123 | 'coursemoduleid' => $moduleid, |
124 | ]; |
125 | $DB->insert_record('tool_driprelease_cmids', $dataobject); |
126 | $insertedcount ++; |
127 | } |
128 | return $insertedcount; |
129 | } |
130 | /** |
131 | * Get names of modules on course for showing |
132 | * in the select element on the form. |
133 | * |
134 | * @param int $courseid |
135 | * @return array |
136 | */ |
137 | function get_course_module_types(int $courseid): array { |
138 | $modinfo = get_fast_modinfo($courseid); |
139 | $modtypes = []; |
140 | foreach ($modinfo->cms as $cm) { |
141 | $modtypes[$cm->modname] = get_string('pluginname', $cm->modname); |
142 | } |
143 | return $modtypes; |
144 | } |
145 | |
146 | /** |
147 | * Get course modules given an instance of driprelease |
148 | * |
149 | * @param \stdClass $driprelease |
150 | * @return array |
151 | */ |
152 | function get_modules(\stdClass $driprelease): array { |
153 | global $DB; |
154 | $course = $DB->get_record('course', ['id' => $driprelease->courseid]); |
155 | $modinfo = get_fast_modinfo($course); |
156 | if (!$modinfo->instances || (!array_key_exists($driprelease->modtype, $modinfo->instances))) { |
157 | return []; |
158 | }; |
159 | $modules = []; |
160 | foreach ($modinfo->instances[$driprelease->modtype] as $cm) { |
161 | $modules[$cm->id] = $cm; |
162 | } |
163 | return $modules; |
164 | } |
165 | /** |
166 | * Add another session header row in the display/preview of scheduled modules |
167 | * |
168 | * @param array $row |
169 | * @return array |
170 | */ |
171 | function add_header(array $row): array { |
172 | $header = $row; |
173 | $header['isheader'] = true; |
174 | $header['cm'] = (object) ['id' => -1]; |
175 | $header['name'] = 'Session'; |
176 | return $header; |
177 | } |
178 | /** |
179 | * Get the rows to be displayed in the schedule of dripped out modules |
180 | * |
181 | * @param \stdClass $driprelease |
182 | * @return array |
183 | */ |
184 | function get_table_data(\stdClass $driprelease): array { |
185 | global $DB; |
186 | $modules = get_modules($driprelease); |
187 | $contentcounter = 0; |
188 | $sessioncounter = 0; |
189 | $selections = []; |
190 | if (isset($driprelease->id)) { |
191 | $selections = $DB->get_records_menu('tool_driprelease_cmids', ['driprelease' => $driprelease->id], |
192 | null, 'id,coursemoduleid'); |
193 | } |
194 | foreach ($modules as $cm) { |
195 | $row['selected'] = in_array($cm->id, $selections) ? 'checked' : ""; |
196 | |
197 | if ($contentcounter % ($driprelease->activitiespersession) == 0) { |
198 | if ($row['selected'] > "") { |
199 | $row['calculatedavailability'] = driprelease_calculate_availability($driprelease, $sessioncounter); |
200 | $sessioncounter++; |
201 | } |
202 | $data[] = add_header($row); |
203 | } |
204 | $contentcounter++; |
205 | $row['modtype'] = $driprelease->modtype; |
206 | $data[] = row_fill($row, $cm); |
207 | } |
208 | return $data ?? []; |
209 | } |
210 | |
211 | /** |
212 | * Simplify get_table_data |
213 | * |
214 | * @param array $row |
215 | * @param cm_info $cm |
216 | * @return array |
217 | */ |
218 | function row_fill(array $row, cm_info $cm): array { |
219 | global $DB; |
220 | |
221 | $details = $DB->get_record($row['modtype'], ['id' => $cm->instance]); |
222 | $row['cm'] = $cm; |
223 | $row['intro'] = strip_tags($details->intro); |
224 | if ($cm->modname == 'quiz') { |
225 | $questions = $DB->get_records('quiz_slots', ['quizid' => $cm->instance]); |
226 | $row['questioncount'] = count($questions); |
227 | } |
228 | $row['isheader'] = false; |
229 | $row['id'] = $cm->id; |
230 | $row['name'] = $cm->name; |
231 | $row['moduleavailability'] = get_availability($cm->availability); |
232 | return $row; |
233 | } |
234 | |
235 | /** |
236 | * Write the availability back to the course_modules table |
237 | * See https://moodledev.io/docs/apis/subsystems/availability/ |
238 | * @param array $tabledata |
239 | * @param \stdClass $driprelease |
240 | * @return void |
241 | */ |
242 | function update_availability(array $tabledata, \stdClass $driprelease) { |
243 | global $DB, $COURSE; |
244 | $updatecount = 0; |
245 | |
246 | foreach ($tabledata as $module) { |
247 | if (!$module['isheader']) { |
248 | if (!$module['selected'] == "checked") { |
249 | $cm = $module['cm']; |
250 | if ($driprelease->hideunselected) { |
251 | set_coursemodule_visible($cm->id, false, false); |
252 | \core\event\course_module_updated::create_from_cm($cm)->trigger(); |
253 | } else { |
254 | set_coursemodule_visible($cm->id, true, true); |
255 | } |
256 | if ($driprelease->resetunselected) { |
257 | $DB->set_field( |
258 | 'course_modules', |
259 | 'availability', |
260 | '', |
261 | ['id' => $module['cm']->id] |
262 | ); |
263 | } |
264 | continue; |
265 | } |
266 | if (!array_key_exists('calculatedavailability', $module)) { |
267 | continue; |
268 | } |
269 | // Don't write any availability restrictions after the end of the schedule. |
270 | if ($module['calculatedavailability']['start'] > $driprelease->schedulefinish) { |
271 | continue; |
272 | } |
273 | $cm = $module['cm']; |
274 | set_coursemodule_visible($cm->id, true, true); |
275 | \core\event\course_module_updated::create_from_cm($cm)->trigger(); |
276 | |
277 | $availability = $module['calculatedavailability']; |
278 | $restrictions = []; |
279 | if ($driprelease->coursegroup) { |
280 | $restrictions[] = \availability_group\condition::get_json($driprelease->coursegroup); |
281 | } |
282 | $restrictions[] = \availability_date\condition::get_json(">=", $availability['start']); |
283 | if (!$driprelease->stayavailable) { |
284 | $restrictions[] = \availability_date\condition::get_json("<", $availability['end']); |
285 | } |
286 | $showvalue = false; |
287 | if ($driprelease->displaydisabled) { |
288 | $showvalue = true; |
289 | } |
290 | |
291 | $showc = array_fill(0, count($restrictions), $showvalue); |
292 | $restrictionsclass = tree::get_root_json($restrictions, tree::OP_AND, $showc); |
293 | |
294 | $DB->set_field( |
295 | 'course_modules', |
296 | 'availability', |
297 | json_encode($restrictionsclass), |
298 | ['id' => $module['cm']->id] |
299 | ); |
300 | $updatecount++; |
301 | } |
302 | } |
303 | $modulenameplural = get_string('modulenameplural', $driprelease->modtype); |
304 | $msg = get_string('updated', 'moodle', $updatecount). " ".$modulenameplural; |
305 | $refresh = optional_param('refresh', 0, PARAM_RAW); |
306 | if (! $refresh) { |
307 | \core\notification::add($msg, \core\notification::SUCCESS); |
308 | rebuild_course_cache($COURSE->id); |
309 | } |
310 | } |
311 | |
312 | |
313 | /** |
314 | * Get the date related availability for an activity |
315 | * |
316 | * @param string $json |
317 | * @return array |
318 | */ |
319 | function get_availability(?string $json): array { |
320 | global $USER; |
321 | $availability = []; |
322 | |
323 | if ($json > "" && $json <> '{}') { |
324 | $decoded = json_decode($json); |
325 | foreach ($decoded->c as $restriction) { |
326 | if (property_exists($restriction, 'type') && $restriction->type == "date") { |
327 | $operator = $restriction->d; |
328 | if ($operator == ">=") { |
329 | $datetime = $restriction->t; |
330 | $availability['from'] = userdate($datetime, '%a %d %b %Y %H:%M'); |
331 | } else { |
332 | $datetime = $restriction->t; |
333 | $availability['to'] = userdate($datetime, '%a %d %b %Y %H:%M'); |
334 | } |
335 | } |
336 | } |
337 | } |
338 | return $availability; |
339 | } |
340 | |
341 | /** |
342 | * Calculate the dripping out of availability and format the dates for the session labels |
343 | * |
344 | * @param \stdClass $driprelease |
345 | * @param int $sessioncounter |
346 | * @return array |
347 | */ |
348 | function driprelease_calculate_availability(\stdClass $driprelease, int $sessioncounter): array { |
349 | $row = []; |
350 | $daysrepeat = $sessioncounter * $driprelease->sessionlength; |
351 | $daysoffset = " + $daysrepeat day"; |
352 | $start = strtotime(' + ' . $daysrepeat . ' day', $driprelease->schedulestart); |
353 | $daysoffset = " + " . (($daysrepeat - 1) + $driprelease->sessionlength) . " day "; |
354 | $end = strtotime($daysoffset, $driprelease->schedulestart); |
355 | |
356 | $midnight = strtotime('today midnight', $driprelease->schedulefinish); |
357 | $endminutes = $driprelease->schedulefinish - $midnight; |
358 | $end = strtotime('today midnight', $end ); |
359 | $end += $endminutes; |
360 | |
361 | $row['sessioncounter'] = $sessioncounter + 1; |
362 | $row['start'] = $start; |
363 | $row['end'] = $end; |
364 | return $row; |
365 | } |
366 | |
367 | /** |
368 | * This is designed to return the coursemods in the order they are displayed on the course |
369 | * It is currently not used and may be deleted at some point, or the need for it may be obscured |
370 | * by the way test data means course activities are always in the order they were created. |
371 | * |
372 | * @param \stdClass $data |
373 | * @return array |
374 | */ |
375 | function get_sequence(\stdClass $data): array { |
376 | global $DB; |
377 | $sql = 'SELECT sequence FROM {course_sections} WHERE course = :course AND sequence > "" ORDER BY section'; |
378 | $coursesequence = $DB->get_records_sql($sql, ['course' => $data->course]); |
379 | $activitiesordered = []; |
380 | $i = 0; |
381 | foreach ($coursesequence as $item) { |
382 | $temp = explode(',', $item->sequence); |
383 | foreach ($temp as $t) { |
384 | if (array_key_exists($t, $data->activities)) { |
385 | $activitiesordered[$i] = $t; |
386 | $i++; |
387 | } |
388 | } |
389 | } |
390 | return $activitiesordered; |
391 | } |