Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 200
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
driprelease
0.00% covered (danger)
0.00%
0 / 200
0.00% covered (danger)
0.00%
0 / 11
2162
0.00% covered (danger)
0.00%
0 / 1
 update
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
6
 get_table_data
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
42
 update_availability
0.00% covered (danger)
0.00%
0 / 51
0.00% covered (danger)
0.00%
0 / 1
156
 row_fill
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 get_course_module_types
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 add_header
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 get_availability
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
42
 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
 manage_selections
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
42
 get_modules
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
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
17namespace tool_driprelease;
18use 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 */
26class 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}