Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
68.47% covered (warning)
68.47%
139 / 203
41.67% covered (danger)
41.67%
5 / 12
CRAP
n/a
0 / 0
tool_driprelease_extend_navigation_course
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
driprelease_update
100.00% covered (success)
100.00%
37 / 37
100.00% covered (success)
100.00%
1 / 1
2
manage_selections
86.36% covered (warning)
86.36%
19 / 22
0.00% covered (danger)
0.00%
0 / 1
6.09
get_course_module_types
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
get_modules
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
4.02
add_header
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
get_table_data
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
6
row_fill
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
update_availability
66.00% covered (warning)
66.00%
33 / 50
0.00% covered (danger)
0.00%
0 / 1
17.66
get_availability
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
7
driprelease_calculate_availability
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
2
get_sequence
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
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 */
24use 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 */
34function 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 */
50function 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 */
100function 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 */
137function 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 */
152function 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 */
171function 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 */
184function 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 */
218function 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 */
242function 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 */
319function 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   */
348function 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 */
375function 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}