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/**
18 * Forum summary report filters renderable.
19 *
20 * @package    forumreport_summary
21 * @copyright  2019 Michael Hawkins <michaelh@moodle.com>
22 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25namespace forumreport_summary\output;
26
27use moodle_url;
28use renderable;
29use renderer_base;
30use stdClass;
31use templatable;
32use forumreport_summary;
33
34defined('MOODLE_INTERNAL') || die();
35
36/**
37 * Forum summary report filters renderable.
38 *
39 * @copyright  2019 Michael Hawkins <michaelh@moodle.com>
40 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41 */
42class filters implements renderable, templatable {
43
44    /**
45     * Course modules the report relates to.
46     * Array of stdClass objects
47     *
48     * @var array $cms
49     */
50    protected $cms;
51
52    /**
53     * Course ID where the report is being generated.
54     *
55     * @var int $courseid
56     */
57    protected $courseid;
58
59    /**
60     * Moodle URL used as the form action on the generate button.
61     *
62     * @var moodle_url $actionurl
63     */
64    protected $actionurl;
65
66    /**
67     * Details of groups available for filtering.
68     * Stored in the format groupid => groupname.
69     *
70     * @var array $groupsavailable
71     */
72    protected $groupsavailable = [];
73
74    /**
75     * IDs of groups selected for filtering.
76     *
77     * @var array $groupsselected
78     */
79    protected $groupsselected = [];
80
81    /**
82     * IDs of discussions required for export links.
83     * If a subset of groups available are selected, this will include the discussion IDs
84     * within that group in the forum.
85     * If all groups are selected, or no groups mode is enabled, this will be empty as
86     * no discussion filtering is required in the export.
87     *
88     * @var array $discussionids
89     */
90    protected $discussionids = [];
91
92    /**
93     * HTML for dates filter.
94     *
95     * @var array $datesdata
96     */
97    protected $datesdata = [];
98
99    /**
100     * Text to display on the dates filter button.
101     *
102     * @var string $datesbuttontext
103     */
104    protected $datesbuttontext;
105
106    /**
107     * Builds renderable filter data.
108     *
109     * @param stdClass $course The course object.
110     * @param array $cms Array of course module objects.
111     * @param moodle_url $actionurl The form action URL.
112     * @param array $filterdata (optional) Associative array of data that has been set on available filters, if any,
113     *                                     in the format filtertype => [values]
114     */
115    public function __construct(stdClass $course, array $cms, moodle_url $actionurl, array $filterdata = []) {
116        $this->cms = $cms;
117        $this->courseid = $course->id;
118        $this->actionurl = $actionurl;
119
120        // Prepare groups filter data.
121        $groupsdata = $filterdata['groups'] ?? [];
122        $this->prepare_groups_data($groupsdata);
123
124        // Prepare dates filter data.
125        $datefromdata = $filterdata['datefrom'] ?? [];
126        $datetodata = $filterdata['dateto'] ?? [];
127        $this->prepare_dates_data($datefromdata, $datetodata);
128    }
129
130    /**
131     * Prepares groups data and sets relevant property values.
132     *
133     * @param array $groupsdata Groups selected for filtering.
134     * @return void.
135     */
136    protected function prepare_groups_data(array $groupsdata): void {
137        global $DB, $USER;
138
139        $groupsavailable = [];
140        $allowedgroupsobj = [];
141
142        $usergroups = groups_get_all_groups($this->courseid, $USER->id);
143        $coursegroups = groups_get_all_groups($this->courseid);
144        $forumids = [];
145        $allgroups = false;
146        $hasgroups = false;
147
148        // Check if any forum gives the user access to all groups and no groups.
149        foreach ($this->cms as $cm) {
150            $forumids[] = $cm->instance;
151
152            // Only need to check for all groups access if not confirmed by a previous check.
153            if (!$allgroups) {
154                $groupmode = groups_get_activity_groupmode($cm);
155
156                // If no groups mode enabled on the forum, nothing to prepare.
157                if (!in_array($groupmode, [VISIBLEGROUPS, SEPARATEGROUPS])) {
158                    continue;
159                }
160
161                $hasgroups = true;
162
163                // Fetch for the current cm's forum.
164                $context = \context_module::instance($cm->id);
165                $aag = has_capability('moodle/site:accessallgroups', $context);
166
167                if ($groupmode == VISIBLEGROUPS || $aag) {
168                    $allgroups = true;
169                }
170            }
171        }
172
173        // If no groups mode enabled, nothing to prepare.
174        if (!$hasgroups) {
175            return;
176        }
177
178        // Any groups, and no groups.
179        if ($allgroups) {
180            $nogroups = new stdClass();
181            $nogroups->id = -1;
182            $nogroups->name = get_string('groupsnone');
183
184            $allowedgroupsobj = $coursegroups + [$nogroups];
185        } else {
186            $allowedgroupsobj = $usergroups;
187        }
188
189        foreach ($allowedgroupsobj as $group) {
190            $groupsavailable[$group->id] = $group->name;
191        }
192
193        // Set valid groups selected.
194        $groupsselected = array_intersect($groupsdata, array_keys($groupsavailable));
195
196        // Overwrite groups properties.
197        $this->groupsavailable = $groupsavailable;
198        $this->groupsselected = $groupsselected;
199
200        $groupsselectedcount = count($groupsselected);
201        if ($groupsselectedcount > 0 && $groupsselectedcount < count($groupsavailable)) {
202            list($forumidin, $forumidparams) = $DB->get_in_or_equal($forumids, SQL_PARAMS_NAMED);
203            list($groupidin, $groupidparams) = $DB->get_in_or_equal($groupsselected, SQL_PARAMS_NAMED);
204
205            $discussionswhere = "course = :courseid AND forum {$forumidin} AND groupid {$groupidin}";
206            $discussionsparams = ['courseid' => $this->courseid];
207            $discussionsparams += $forumidparams + $groupidparams;
208
209            $discussionids = $DB->get_fieldset_select('forum_discussions', 'DISTINCT id', $discussionswhere, $discussionsparams);
210
211            foreach ($discussionids as $discussionid) {
212                $this->discussionids[] = ['discid' => $discussionid];
213            }
214        }
215    }
216
217    /**
218     * Prepares from date, to date and button text.
219     * Empty data will default to a disabled filter with today's date.
220     *
221     * @param array $datefromdata From date selected for filtering, and whether the filter is enabled.
222     * @param array $datetodata To date selected for filtering, and whether the filter is enabled.
223     * @return void.
224     */
225    private function prepare_dates_data(array $datefromdata, array $datetodata): void {
226        $timezone = \core_date::get_user_timezone_object();
227        $calendartype = \core_calendar\type_factory::get_calendar_instance();
228        $timestamptoday = time();
229        $datetoday  = $calendartype->timestamp_to_date_array($timestamptoday, $timezone);
230
231        // Prepare date/enabled data.
232        if (empty($datefromdata['enabled'])) {
233            $fromdate = $datetoday;
234            $fromtimestamp = $timestamptoday;
235            $fromenabled = false;
236        } else {
237            $fromdate = $calendartype->timestamp_to_date_array($datefromdata['timestamp'], $timezone);
238            $fromtimestamp = $datefromdata['timestamp'];
239            $fromenabled = true;
240        }
241
242        if (empty($datetodata['enabled'])) {
243            $todate = $datetoday;
244            $totimestamp = $timestamptoday;
245            $toenabled = false;
246        } else {
247            $todate = $calendartype->timestamp_to_date_array($datetodata['timestamp'], $timezone);
248            $totimestamp = $datetodata['timestamp'];
249            $toenabled = true;
250        }
251
252        $this->datesdata = [
253            'from' => [
254                'day'       => $fromdate['mday'],
255                'month'     => $fromdate['mon'],
256                'year'      => $fromdate['year'],
257                'timestamp' => $fromtimestamp,
258                'enabled'   => $fromenabled,
259            ],
260            'to' => [
261                'day'       => $todate['mday'],
262                'month'     => $todate['mon'],
263                'year'      => $todate['year'],
264                'timestamp' => $totimestamp,
265                'enabled'   => $toenabled,
266            ],
267        ];
268
269        // Prepare button string data.
270        $displayformat = get_string('strftimedatemonthabbr', 'langconfig');
271        $fromdatestring = $calendartype->timestamp_to_date_string($fromtimestamp, $displayformat, $timezone, true, true);
272        $todatestring = $calendartype->timestamp_to_date_string($totimestamp, $displayformat, $timezone, true, true);
273
274        if ($fromenabled && $toenabled) {
275            $datestrings = [
276                'datefrom' => $fromdatestring,
277                'dateto'   => $todatestring,
278            ];
279            $this->datesbuttontext = get_string('filter:datesfromto', 'forumreport_summary', $datestrings);
280        } else if ($fromenabled) {
281            $this->datesbuttontext = get_string('filter:datesfrom', 'forumreport_summary', $fromdatestring);
282        } else if ($toenabled) {
283            $this->datesbuttontext = get_string('filter:datesto', 'forumreport_summary', $todatestring);
284        } else {
285            $this->datesbuttontext = get_string('filter:datesname', 'forumreport_summary');
286        }
287    }
288
289    /**
290     * Export data for use as the context of a mustache template.
291     *
292     * @param renderer_base $renderer The renderer to be used to display report filters.
293     * @return array Data in a format compatible with a mustache template.
294     */
295    public function export_for_template(renderer_base $renderer): stdClass {
296        $output = new stdClass();
297
298        // Set formaction URL.
299        $output->actionurl = $this->actionurl->out(false);
300
301        // Set groups filter data.
302        if (!empty($this->groupsavailable)) {
303            $output->hasgroups = true;
304
305            $groupscount = count($this->groupsselected);
306
307            if (count($this->groupsavailable) <= $groupscount) {
308                $output->filtergroupsname = get_string('filter:groupscountall', 'forumreport_summary');
309            } else if (!empty($this->groupsselected)) {
310                $output->filtergroupsname = get_string('filter:groupscountnumber', 'forumreport_summary', $groupscount);
311            } else {
312                $output->filtergroupsname = get_string('filter:groupsname', 'forumreport_summary');
313            }
314
315            // Set groups filter.
316            $groupsdata = [];
317
318            foreach ($this->groupsavailable as $groupid => $groupname) {
319                $groupsdata[] = [
320                    'groupid' => $groupid,
321                    'groupname' => $groupname,
322                    'checked' => in_array($groupid, $this->groupsselected),
323                ];
324            }
325
326            $output->filtergroups = $groupsdata;
327        } else {
328            $output->hasgroups = false;
329        }
330
331        // Set discussion IDs for use by export links (always included, as it will be empty if not required).
332        $output->discussionids = $this->discussionids;
333
334        // Set date button and generate dates popover mform.
335        $datesformdata = [];
336
337        if ($this->datesdata['from']['enabled']) {
338            $datesformdata['filterdatefrompopover'] = $this->datesdata['from'];
339        }
340
341        if ($this->datesdata['to']['enabled']) {
342            $datesformdata['filterdatetopopover'] = $this->datesdata['to'];
343        }
344
345        $output->filterdatesname = $this->datesbuttontext;
346        $datesform = new forumreport_summary\form\dates_filter_form();
347        $datesform->set_data($datesformdata);
348        $output->filterdatesform = $datesform->render();
349
350         // Set dates filter data within filters form.
351        $disableddate = [
352            'day' => '',
353            'month' => '',
354            'year' => '',
355            'enabled' => '0',
356        ];
357        $datefromdata = ['type' => 'from'] + ($this->datesdata['from']['enabled'] ? $this->datesdata['from'] : $disableddate);
358        $datetodata = ['type' => 'to'] + ($this->datesdata['to']['enabled'] ? $this->datesdata['to'] : $disableddate);
359        $output->filterdatesdata = [$datefromdata, $datetodata];
360
361        return $output;
362    }
363}
364