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\local\helpers;
20
 
21
use core_reportbuilder\local\filters\boolean_select;
22
use core_reportbuilder\local\filters\date;
23
use core_reportbuilder\local\filters\number;
24
use core_reportbuilder\local\filters\select;
25
use core_reportbuilder\local\filters\text;
26
use core_reportbuilder\local\report\column;
27
use core_reportbuilder\local\report\filter;
28
use lang_string;
29
use stdClass;
30
use core_customfield\data_controller;
31
use core_customfield\field_controller;
32
use core_customfield\handler;
33
 
34
/**
35
 * Helper class for course custom fields.
36
 *
37
 * @package   core_reportbuilder
38
 * @copyright 2021 Sara Arjona <sara@moodle.com> based on David Matamoros <davidmc@moodle.com> code.
39
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40
 */
41
class custom_fields {
42
 
43
    /** @var string $entityname Name of the entity */
44
    private $entityname;
45
 
46
    /** @var handler $handler The handler for the customfields */
47
    private $handler;
48
 
49
    /** @var int $tablefieldalias The table alias and the field name (table.field) that matches the customfield instanceid. */
50
    private $tablefieldalias;
51
 
52
    /** @var array additional joins */
53
    private $joins = [];
54
 
55
    /**
56
     * Class customfields constructor.
57
     *
58
     * @param string $tablefieldalias table alias and the field name (table.field) that matches the customfield instanceid.
59
     * @param string $entityname name of the entity in the report where we add custom fields.
60
     * @param string $component component name of full frankenstyle plugin name.
61
     * @param string $area name of the area (each component/plugin may define handlers for multiple areas).
62
     * @param int $itemid item id if the area uses them (usually not used).
63
     */
64
    public function __construct(string $tablefieldalias, string $entityname, string $component, string $area, int $itemid = 0) {
65
        $this->tablefieldalias = $tablefieldalias;
66
        $this->entityname = $entityname;
67
        $this->handler = handler::get_handler($component, $area, $itemid);
68
    }
69
 
70
    /**
71
     * Additional join that is needed.
72
     *
73
     * @param string $join
74
     * @return self
75
     */
76
    public function add_join(string $join): self {
77
        $this->joins[trim($join)] = trim($join);
78
        return $this;
79
    }
80
 
81
    /**
82
     * Additional joins that are needed.
83
     *
84
     * @param array $joins
85
     * @return self
86
     */
87
    public function add_joins(array $joins): self {
88
        foreach ($joins as $join) {
89
            $this->add_join($join);
90
        }
91
        return $this;
92
    }
93
 
94
    /**
95
     * Return joins
96
     *
97
     * @return string[]
98
     */
99
    private function get_joins(): array {
100
        return array_values($this->joins);
101
    }
102
 
103
    /**
104
     * Get table alias for given custom field
105
     *
106
     * The entity name is used to ensure the alias differs when the entity is used multiple times within the same report, each
107
     * having their own table alias/join
108
     *
109
     * @param field_controller $field
110
     * @return string
111
     */
112
    private function get_table_alias(field_controller $field): string {
113
        static $aliases = [];
114
 
115
        $aliaskey = "{$this->entityname}_{$field->get('id')}";
116
        if (!array_key_exists($aliaskey, $aliases)) {
117
            $aliases[$aliaskey] = database::generate_alias();
118
        }
119
 
120
        return $aliases[$aliaskey];
121
    }
122
 
123
    /**
124
     * Get table join for given custom field
125
     *
126
     * @param field_controller $field
127
     * @return string
128
     */
129
    private function get_table_join(field_controller $field): string {
130
        $customdatatablealias = $this->get_table_alias($field);
131
 
132
        return "LEFT JOIN {customfield_data} {$customdatatablealias}
133
                       ON {$customdatatablealias}.fieldid = {$field->get('id')}
134
                      AND {$customdatatablealias}.instanceid = {$this->tablefieldalias}";
135
    }
136
 
137
    /**
138
     * Gets the custom fields columns for the report.
139
     *
140
     * Column will be named as 'customfield_' + customfield shortname.
141
     *
142
     * @return column[]
143
     */
144
    public function get_columns(): array {
145
        global $DB;
146
 
147
        $columns = [];
148
 
149
        $categorieswithfields = $this->handler->get_categories_with_fields();
150
        foreach ($categorieswithfields as $fieldcategory) {
151
            $categoryfields = $fieldcategory->get_fields();
152
            foreach ($categoryfields as $field) {
153
                $customdatatablealias = $this->get_table_alias($field);
154
 
155
                $datacontroller = data_controller::create(0, null, $field);
156
 
157
                $datafield = $datacontroller->datafield();
158
                $datafieldsql = "{$customdatatablealias}.{$datafield}";
159
 
160
                // Long text fields should be cast for Oracle, for aggregation support.
161
                $columntype = $this->get_column_type($field, $datafield);
162
                if ($columntype === column::TYPE_LONGTEXT && $DB->get_dbfamily() === 'oracle') {
163
                    $datafieldsql = $DB->sql_order_by_text($datafieldsql, 1024);
164
                }
165
 
166
                // Select enough fields to re-create and format each custom field instance value.
167
                $selectfields = "{$customdatatablealias}.id, {$customdatatablealias}.contextid";
168
                if ($datafield === 'value') {
169
                    // We will take the format into account when displaying the individual values.
170
                    $selectfields .= ", {$customdatatablealias}.valueformat, {$customdatatablealias}.valuetrust";
171
                }
172
 
173
                $columns[] = (new column(
174
                    'customfield_' . $field->get('shortname'),
175
                    new lang_string('customfieldcolumn', 'core_reportbuilder', $field->get_formatted_name()),
176
                    $this->entityname
177
                ))
178
                    ->add_joins($this->get_joins())
179
                    ->add_join($this->get_table_join($field))
180
                    ->add_field($datafieldsql, $datafield)
181
                    ->add_fields($selectfields)
182
                    ->add_field($this->tablefieldalias, 'tablefieldalias')
183
                    ->set_type($columntype)
184
                    ->set_is_sortable($columntype !== column::TYPE_LONGTEXT)
185
                    ->add_callback(static function($value, stdClass $row, field_controller $field): string {
186
                        if ($row->tablefieldalias === null) {
187
                            return '';
188
                        }
189
                        return (string) data_controller::create(0, $row, $field)->export_value();
190
                    }, $field)
191
                    // Important. If the handler implements can_view() function, it will be called with parameter $instanceid=0.
192
                    // This means that per-instance access validation will be ignored.
193
                    ->set_is_available($this->handler->can_view($field, 0));
194
            }
195
        }
196
        return $columns;
197
    }
198
 
199
    /**
200
     * Returns the column type
201
     *
202
     * @param field_controller $field
203
     * @param string $datafield
204
     * @return int
205
     */
206
    private function get_column_type(field_controller $field, string $datafield): int {
207
        if ($field->get('type') === 'checkbox') {
208
            return column::TYPE_BOOLEAN;
209
        }
210
 
211
        if ($field->get('type') === 'date') {
212
            return column::TYPE_TIMESTAMP;
213
        }
214
 
215
        if ($field->get('type') === 'select') {
216
            return column::TYPE_TEXT;
217
        }
218
 
219
        if ($datafield === 'intvalue') {
220
            return column::TYPE_INTEGER;
221
        }
222
 
223
        if ($datafield === 'decvalue') {
224
            return column::TYPE_FLOAT;
225
        }
226
 
227
        if ($datafield === 'value') {
228
            return column::TYPE_LONGTEXT;
229
        }
230
 
231
        return column::TYPE_TEXT;
232
    }
233
 
234
    /**
235
     * Returns all available filters on custom fields.
236
     *
237
     * Filter will be named as 'customfield_' + customfield shortname.
238
     *
239
     * @return filter[]
240
     */
241
    public function get_filters(): array {
242
        global $DB;
243
 
244
        $filters = [];
245
 
246
        $categorieswithfields = $this->handler->get_categories_with_fields();
247
        foreach ($categorieswithfields as $fieldcategory) {
248
            $categoryfields = $fieldcategory->get_fields();
249
            foreach ($categoryfields as $field) {
250
                $customdatatablealias = $this->get_table_alias($field);
251
 
252
                $datacontroller = data_controller::create(0, null, $field);
253
 
254
                $datafield = $datacontroller->datafield();
255
                $datafieldsql = "{$customdatatablealias}.{$datafield}";
256
                if ($datafield === 'value') {
257
                    $datafieldsql = $DB->sql_cast_to_char($datafieldsql);
258
                }
259
 
260
                $typeclass = $this->get_filter_class_type($datacontroller);
261
                $filter = (new filter(
262
                    $typeclass,
263
                    'customfield_' . $field->get('shortname'),
264
                    new lang_string('customfieldcolumn', 'core_reportbuilder', $field->get_formatted_name()),
265
                    $this->entityname,
266
                    $datafieldsql
267
                ))
268
                    ->add_joins($this->get_joins())
269
                    ->add_join($this->get_table_join($field));
270
 
271
                // Options are stored inside configdata json string and we need to convert it to array.
272
                if ($field->get('type') === 'select') {
273
                    $filter->set_options_callback(static function() use ($field): array {
11 efrain 274
                        return $field->get_options();
1 efrain 275
                    });
276
                }
277
 
278
                $filters[] = $filter;
279
            }
280
        }
281
        return $filters;
282
    }
283
 
284
    /**
285
     * Returns class for the filter element that should be used for the field
286
     *
287
     * In some situation we can assume what kind of data is stored in the customfield plugin and we can
288
     * display appropriate filter form element. For all others assume text filter.
289
     *
290
     * @param data_controller $datacontroller
291
     * @return string
292
     */
293
    private function get_filter_class_type(data_controller $datacontroller): string {
294
        $type = $datacontroller->get_field()->get('type');
295
 
296
        switch ($type) {
297
            case 'checkbox':
298
                $classtype = boolean_select::class;
299
                break;
300
            case 'date':
301
                $classtype = date::class;
302
                break;
303
            case 'select':
304
                $classtype = select::class;
305
                break;
306
            default:
307
                // To support third party field type we need to account for stored numbers.
308
                $datafield = $datacontroller->datafield();
309
                if ($datafield === 'intvalue' || $datafield === 'decvalue') {
310
                    $classtype = number::class;
311
                } else {
312
                    $classtype = text::class;
313
                }
314
                break;
315
        }
316
 
317
        return $classtype;
318
    }
319
}