Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
declare(strict_types=1);
18
 
19
namespace core_reportbuilder\table;
20
 
21
use action_menu;
22
use action_menu_filler;
23
use core_table\local\filter\filterset;
24
use html_writer;
25
use moodle_exception;
26
use stdClass;
27
use core_reportbuilder\{manager, system_report};
28
use core_reportbuilder\local\models\report;
1441 ariadna 29
use core_reportbuilder\local\report\column;
1 efrain 30
 
31
/**
32
 * System report dynamic table class
33
 *
34
 * @package     core_reportbuilder
35
 * @copyright   2020 Paul Holden <paulh@moodle.com>
36
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37
 */
38
class system_report_table extends base_report_table {
39
 
40
    /** @var system_report $report */
41
    protected $report;
42
 
43
    /** @var string Unique ID prefix for the table */
44
    private const UNIQUEID_PREFIX = 'system-report-table-';
45
 
46
    /**
47
     * Table constructor. Note that the passed unique ID value must match the pattern "system-report-table-(\d+)" so that
48
     * dynamic updates continue to load the same report
49
     *
50
     * @param string $uniqueid
51
     * @param array $parameters
52
     * @throws moodle_exception For invalid unique ID
53
     */
54
    public function __construct(string $uniqueid, array $parameters = []) {
55
        if (!preg_match('/^' . self::UNIQUEID_PREFIX . '(?<id>\d+)$/', $uniqueid, $matches)) {
56
            throw new moodle_exception('invalidsystemreportid', 'core_reportbuilder', '', null, $uniqueid);
57
        }
58
 
59
        parent::__construct($uniqueid);
60
 
61
        // If we are loading via a dynamic table AJAX request, defer the report loading until the filterset is added to
62
        // the table, as it is used to populate the report $parameters during construction.
63
        $serviceinfo = optional_param('info', null, PARAM_RAW);
64
        if ($serviceinfo !== 'core_table_get_dynamic_table_content') {
65
            $this->load_report_instance((int) $matches['id'], $parameters);
66
        }
67
    }
68
 
69
    /**
70
     * Load the report persistent, and accompanying system report instance.
71
     *
72
     * @param int $reportid
73
     * @param array $parameters
74
     */
75
    private function load_report_instance(int $reportid, array $parameters): void {
76
        global $PAGE;
77
 
78
        $this->persistent = new report($reportid);
79
        $this->report = manager::get_report_from_persistent($this->persistent, $parameters);
80
 
81
        // TODO: can probably be removed pending MDL-72974.
82
        $PAGE->set_context($this->persistent->get_context());
83
 
84
        $fields = $this->report->get_base_fields();
1441 ariadna 85
        $groupby = [];
1 efrain 86
        $maintable = $this->report->get_main_table();
87
        $maintablealias = $this->report->get_main_table_alias();
88
        $joins = $this->report->get_joins();
89
        [$where, $params] = $this->report->get_base_condition();
90
 
91
        $this->set_attribute('data-region', 'reportbuilder-table');
92
        $this->set_attribute('class', $this->attributes['class'] . ' reportbuilder-table');
93
 
94
        // Download options.
95
        $this->showdownloadbuttonsat = [TABLE_P_BOTTOM];
96
        $this->is_downloading($parameters['download'] ?? null, $this->report->get_downloadfilename());
97
 
98
        // Retrieve all report columns. If we are downloading the report, remove as required.
99
        $columns = $this->report->get_active_columns();
100
        if ($this->is_downloading()) {
101
            $columns = array_diff_key($columns,
102
                array_flip($this->report->get_exclude_columns_for_download()));
103
        }
104
 
1441 ariadna 105
        // If we are aggregating any columns, we should group by the remaining ones.
106
        $aggregatedcolumns = array_filter($columns, fn(column $column): bool => !empty($column->get_aggregation()));
107
        $hasaggregatedcolumns = !empty($aggregatedcolumns);
108
        if ($hasaggregatedcolumns) {
109
            $groupby = $fields;
110
        }
1 efrain 111
 
1441 ariadna 112
        $columnheaders = $columnattributes = $columnicons = [];
113
 
1 efrain 114
        // Check whether report has checkbox toggle defined, note that select all is excluded during download.
115
        if (($checkbox = $this->report->get_checkbox_toggleall(true)) && !$this->is_downloading()) {
116
            $columnheaders['selectall'] = $PAGE->get_renderer('core')->render($checkbox);
117
            $this->no_sorting('selectall');
118
        }
119
 
120
        $columnindex = 1;
121
        foreach ($columns as $identifier => $column) {
122
            $column->set_index($columnindex++);
123
 
124
            $columnheaders[$column->get_column_alias()] = $column->get_title();
125
 
126
            // Specify whether column should behave as a user fullname column unless the column has a custom title set.
127
            if (preg_match('/^user:fullname.*$/', $column->get_unique_identifier()) && !$column->has_custom_title()) {
128
                $this->userfullnamecolumns[] = $column->get_column_alias();
129
            }
130
 
1441 ariadna 131
            // We need to determine for each column whether we should group by its fields, to support aggregation.
132
            $columnaggregation = $column->get_aggregation();
133
            if ($hasaggregatedcolumns && (empty($columnaggregation) || $columnaggregation::column_groupby())) {
134
                $groupby = array_merge($groupby, $column->get_groupby_sql());
135
            }
136
 
1 efrain 137
            // Add each columns fields, joins and params to our report.
138
            $fields = array_merge($fields, $column->get_fields());
139
            $joins = array_merge($joins, $column->get_joins());
140
            $params = array_merge($params, $column->get_params());
141
 
142
            // Disable sorting for some columns.
143
            if (!$column->get_is_sortable()) {
144
                $this->no_sorting($column->get_column_alias());
145
            }
146
 
1441 ariadna 147
            // Generate column attributes/icons for the table.
148
            $columnattributes[$column->get_column_alias()] = $column->get_attributes();
149
            $columnicons[] = $column->get_help_icon();
1 efrain 150
        }
151
 
152
        // If the report has any actions then append appropriate column, note that actions are excluded during download.
153
        if ($this->report->has_actions() && !$this->is_downloading()) {
154
            $columnheaders['actions'] = html_writer::tag('span', get_string('actions', 'core_reportbuilder'), [
1441 ariadna 155
                'class' => 'visually-hidden',
1 efrain 156
            ]);
157
            $this->no_sorting('actions');
158
        }
159
 
160
        $this->define_columns(array_keys($columnheaders));
161
        $this->define_headers(array_values($columnheaders));
162
 
1441 ariadna 163
        // Add column attributes/icons to the table.
164
        $this->set_columnsattributes($columnattributes);
165
        $this->define_help_for_headers($columnicons);
1 efrain 166
 
167
        // Initial table sort column.
168
        if ($sortcolumn = $this->report->get_initial_sort_column()) {
169
            $this->sortable(true, $sortcolumn->get_column_alias(), $this->report->get_initial_sort_direction());
170
        }
171
 
172
        // Table configuration.
173
        $this->initialbars(false);
174
        $this->collapsible(false);
175
        $this->pageable(true);
176
        $this->set_default_per_page($this->report->get_default_per_page());
177
 
178
        // Initialise table SQL properties.
179
        $fieldsql = implode(', ', $fields);
1441 ariadna 180
        $this->init_sql($fieldsql, "{{$maintable}} {$maintablealias}", $joins, $where, $params, $groupby);
1 efrain 181
    }
182
 
183
    /**
184
     * Return a new instance of the class for given report ID. We include report parameters here so they are present during
185
     * initialisation
186
     *
187
     * @param int $reportid
188
     * @param array $parameters
189
     * @return static
190
     */
191
    public static function create(int $reportid, array $parameters): self {
192
        return new static(self::UNIQUEID_PREFIX . $reportid, $parameters);
193
    }
194
 
195
    /**
196
     * Set the filterset in the table class. We set the report parameters here so that they are persisted while paging
197
     *
198
     * @param filterset $filterset
199
     */
200
    public function set_filterset(filterset $filterset): void {
201
        $reportid = $filterset->get_filter('reportid')->current();
202
        $parameters = $filterset->get_filter('parameters')->current();
203
 
204
        $this->load_report_instance($reportid, json_decode($parameters, true));
205
 
206
        parent::set_filterset($filterset);
207
    }
208
 
209
    /**
210
     * Override parent method for retrieving row class with that defined by the system report
211
     *
212
     * @param array|stdClass $row
213
     * @return string
214
     */
215
    public function get_row_class($row) {
216
        return $this->report->get_row_class((object) $row);
217
    }
218
 
219
    /**
220
     * Format each row of returned data, executing defined callbacks for the row and each column
221
     *
222
     * @param array|stdClass $row
223
     * @return array
224
     */
225
    public function format_row($row) {
226
        global $PAGE;
227
 
228
        $this->report->row_callback((object) $row);
229
 
230
        // Walk over the row, and for any key that matches one of our column aliases, call that columns format method.
231
        $columnsbyalias = $this->report->get_active_columns_by_alias();
232
        $row = (array) $row;
233
        array_walk($row, static function(&$value, $key) use ($columnsbyalias, $row): void {
234
            if (array_key_exists($key, $columnsbyalias)) {
235
                $value = $columnsbyalias[$key]->format_value($row);
236
            }
237
        });
238
 
239
        // Check whether report has checkbox toggle defined.
240
        if ($checkbox = $this->report->get_checkbox_toggleall(false, (object) $row)) {
241
            $row['selectall'] = $PAGE->get_renderer('core')->render($checkbox);
242
        }
243
 
244
        // Now check for any actions.
245
        if ($this->report->has_actions()) {
246
            $row['actions'] = $this->format_row_actions((object) $row);
247
        }
248
 
249
        return $row;
250
    }
251
 
252
    /**
253
     * Return formatted actions column for the row
254
     *
255
     * @param stdClass $row
256
     * @return string
257
     */
258
    private function format_row_actions(stdClass $row): string {
259
        global $OUTPUT;
260
 
261
        $menu = new action_menu();
1441 ariadna 262
        $menu->set_menu_trigger(
263
            $OUTPUT->pix_icon('i/menu', get_string('actions', 'core_reportbuilder')),
264
            'btn btn-icon d-flex no-caret',
265
        );
1 efrain 266
 
267
        $actions = array_filter($this->report->get_actions(), function($action) use ($row) {
268
            // Only return dividers and action items who can be displayed for current users.
269
            return $action instanceof action_menu_filler || $action->get_action_link($row);
270
        });
271
 
272
        $totalactions = count($actions);
273
        $actionvalues = array_values($actions);
274
        foreach ($actionvalues as $position => $action) {
275
            if ($action instanceof action_menu_filler) {
276
                $ispreviousdivider = array_key_exists($position - 1, $actionvalues) &&
277
                    ($actionvalues[$position - 1] instanceof action_menu_filler);
278
                $isnextdivider = array_key_exists($position + 1, $actionvalues) &&
279
                    ($actionvalues[$position + 1] instanceof action_menu_filler);
280
                $isfirstdivider = ($position === 0);
281
                $islastdivider = ($position === $totalactions - 1);
282
 
283
                // Avoid add divider at last/first position and having multiple fillers in a row.
284
                if ($ispreviousdivider || $isnextdivider || $isfirstdivider || $islastdivider) {
285
                    continue;
286
                }
287
                $actionlink = $action;
288
            } else {
289
                // Ensure the action link can be displayed for the current row.
290
                $actionlink = $action->get_action_link($row);
291
            }
292
 
293
            if ($actionlink) {
294
                $menu->add($actionlink);
295
            }
296
        }
297
        return $OUTPUT->render($menu);
298
    }
299
 
300
    /**
301
     * Get the html for the download buttons
302
     *
303
     * @return string
304
     */
305
    public function download_buttons(): string {
306
        global $OUTPUT;
307
 
308
        if ($this->report->can_be_downloaded() && !$this->is_downloading()) {
309
            return $OUTPUT->download_dataformat_selector(
310
                get_string('downloadas', 'table'),
311
                new \moodle_url('/reportbuilder/download.php'),
312
                'download',
313
                [
314
                    'id' => $this->persistent->get('id'),
315
                    'parameters' => json_encode($this->report->get_parameters()),
316
                ]
317
            );
318
        }
319
 
320
        return '';
321
    }
1441 ariadna 322
 
323
    /**
324
     * Check if the user has the capability to access this table.
325
     *
326
     * @return bool Return true if capability check passed.
327
     */
328
    public function has_capability(): bool {
329
        try {
330
            $this->report->require_can_view();
331
            return true;
332
        } catch (\core_reportbuilder\exception\report_access_exception $e) {
333
            return false;
334
        }
335
    }
1 efrain 336
}