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