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
 
1441 ariadna 21
use core\lang_string;
1 efrain 22
use core_text;
1441 ariadna 23
use core_reportbuilder\local\filters\{boolean_select, date, select, text};
24
use core_reportbuilder\local\report\{column, filter};
1 efrain 25
use profile_field_base;
26
use stdClass;
27
 
28
defined('MOODLE_INTERNAL') || die();
29
 
30
global $CFG;
31
require_once($CFG->dirroot.'/user/profile/lib.php');
32
 
33
/**
34
 * Helper class for user profile fields.
35
 *
36
 * @package   core_reportbuilder
37
 * @copyright 2021 David Matamoros <davidmc@moodle.com>
38
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39
 */
40
class user_profile_fields {
41
 
1441 ariadna 42
    use join_trait;
1 efrain 43
 
1441 ariadna 44
    /** @var profile_field_base[] User profile fields */
45
    private array $userprofilefields;
1 efrain 46
 
47
    /**
1441 ariadna 48
     * Constructor
1 efrain 49
     *
1441 ariadna 50
     * @param string $usertablefieldalias The table/field alias to match the user ID when adding columns and filters.
1 efrain 51
     * @param string $entityname The entity name used when adding columns and filters.
52
     */
1441 ariadna 53
    public function __construct(
54
        /** @var string The table/field alias to match the user ID when adding columns and filters */
55
        private readonly string $usertablefieldalias,
56
        /** @var string The entity name used when adding columns and filters */
57
        private readonly string $entityname,
58
    ) {
59
        $this->userprofilefields = profile_get_user_fields_with_data(0);
1 efrain 60
    }
61
 
62
    /**
63
     * Get table alias for given profile field
64
     *
65
     * The entity name is used to ensure the alias differs when the entity is used multiple times within the same report, each
66
     * having their own table alias/join
67
     *
68
     * @param profile_field_base $profilefield
69
     * @return string
70
     */
71
    private function get_table_alias(profile_field_base $profilefield): string {
72
        static $aliases = [];
73
 
74
        $aliaskey = "{$this->entityname}_{$profilefield->fieldid}";
75
        if (!array_key_exists($aliaskey, $aliases)) {
76
            $aliases[$aliaskey] = database::generate_alias();
77
        }
78
 
79
        return $aliases[$aliaskey];
80
    }
81
 
82
    /**
83
     * Get table join for given profile field
84
     *
85
     * @param profile_field_base $profilefield
86
     * @return string
87
     */
88
    private function get_table_join(profile_field_base $profilefield): string {
89
        $userinfotablealias = $this->get_table_alias($profilefield);
90
 
91
        return "LEFT JOIN {user_info_data} {$userinfotablealias}
92
                       ON {$userinfotablealias}.userid = {$this->usertablefieldalias}
93
                      AND {$userinfotablealias}.fieldid = {$profilefield->fieldid}";
94
    }
95
 
96
    /**
97
     * Return the user profile fields visible columns.
98
     *
99
     * @return column[]
100
     */
101
    public function get_columns(): array {
102
        global $DB;
103
 
104
        $columns = [];
1441 ariadna 105
 
1 efrain 106
        foreach ($this->userprofilefields as $profilefield) {
1441 ariadna 107
            $userinfotablealias = $this->get_table_alias($profilefield);
108
            $userinfosql = "{$userinfotablealias}.data";
109
 
110
            // Numeric column (non-text) should coalesce with default, for aggregation.
1 efrain 111
            $columntype = $this->get_user_field_type($profilefield->field->datatype);
1441 ariadna 112
            if (!in_array($columntype, [column::TYPE_TEXT, column::TYPE_LONGTEXT])) {
1 efrain 113
 
1441 ariadna 114
                // See MDL-78783 regarding no bound parameters, and SQL Server limitations of GROUP BY.
115
                $userinfosql = "
116
                    CASE WHEN {$this->usertablefieldalias} IS NOT NULL
117
                         THEN " .
118
                            $DB->sql_cast_char2int("COALESCE({$userinfosql}, '" . (float) $profilefield->field->defaultdata . "')")
119
                            . "
120
                         ELSE NULL
121
                    END";
1 efrain 122
            }
123
 
1441 ariadna 124
            $columnname = 'profilefield_' . core_text::strtolower($profilefield->field->shortname);
125
            $columns[$columnname] = (new column(
126
                $columnname,
127
                new lang_string('customfieldcolumn', 'core_reportbuilder', $profilefield->display_name(false)),
1 efrain 128
                $this->entityname
129
            ))
130
                ->add_joins($this->get_joins())
131
                ->add_join($this->get_table_join($profilefield))
132
                ->set_type($columntype)
1441 ariadna 133
                ->add_field($userinfosql, 'data')
134
                ->add_field("{$userinfotablealias}.dataformat")
135
                ->add_field($this->usertablefieldalias, 'userid')
136
                ->set_is_sortable(true)
1 efrain 137
                ->add_callback(static function($value, stdClass $row, profile_field_base $field): string {
1441 ariadna 138
                    if ($row->userid === null && $value === null) {
1 efrain 139
                        return '';
140
                    }
141
 
1441 ariadna 142
                    $field->set_user_data(
143
                        $row->data ?? $field->field->defaultdata,
144
                        $row->dataformat ?? $field->field->defaultdataformat,
145
                    );
146
 
147
                    return $field->display_data();
148
                }, $profilefield)
149
                ->set_is_available($profilefield->is_visible());
1 efrain 150
        }
151
 
1441 ariadna 152
        return array_values($columns);
1 efrain 153
    }
154
 
155
    /**
156
     * Get custom user profile fields filters.
157
     *
158
     * @return filter[]
159
     */
160
    public function get_filters(): array {
161
        global $DB;
162
 
163
        $filters = [];
1441 ariadna 164
 
1 efrain 165
        foreach ($this->userprofilefields as $profilefield) {
1441 ariadna 166
            $userinfotablealias = $this->get_table_alias($profilefield);
167
            $userinfosql = "{$userinfotablealias}.data";
168
            $userinfoparams = [];
1 efrain 169
 
1441 ariadna 170
            // Perform casts where necessary, as this is a text DB field.
1 efrain 171
            switch ($profilefield->field->datatype) {
172
                case 'checkbox':
173
                    $classname = boolean_select::class;
1441 ariadna 174
                    $userinfosql = $DB->sql_cast_char2int($userinfosql, true);
1 efrain 175
                    break;
176
                case 'datetime':
177
                    $classname = date::class;
1441 ariadna 178
                    $userinfosql = $DB->sql_cast_char2int($userinfosql, true);
1 efrain 179
                    break;
180
                case 'menu':
181
                    $classname = select::class;
182
                    break;
183
                case 'text':
184
                case 'textarea':
185
                default:
186
                    $classname = text::class;
187
                    break;
188
            }
189
 
1441 ariadna 190
            // Account for field default value, when joined to the user table.
191
            if (($fielddefault = $profilefield->field->defaultdata) !== null) {
192
                $paramdefault = database::generate_param_name();
193
                $userinfosql = "
194
                        CASE WHEN {$this->usertablefieldalias} IS NOT NULL
195
                             THEN COALESCE({$userinfosql}, :{$paramdefault})
196
                             ELSE NULL
197
                        END";
198
                $userinfoparams[$paramdefault] = $fielddefault;
199
            }
200
 
201
            $filtername = 'profilefield_' . core_text::strtolower($profilefield->field->shortname);
1 efrain 202
            $filter = (new filter(
203
                $classname,
1441 ariadna 204
                $filtername,
205
                new lang_string('customfieldcolumn', 'core_reportbuilder', $profilefield->display_name(false)),
1 efrain 206
                $this->entityname,
1441 ariadna 207
                $userinfosql,
208
                $userinfoparams,
1 efrain 209
            ))
210
                ->add_joins($this->get_joins())
1441 ariadna 211
                ->add_join($this->get_table_join($profilefield))
212
                ->set_is_available($profilefield->is_visible());
1 efrain 213
 
1441 ariadna 214
            // If using a select filter, then populate the options.
215
            if ($filter->get_filter_class() === select::class) {
216
                $filter->set_options_callback(fn(): array => $profilefield->options);
1 efrain 217
            }
218
 
1441 ariadna 219
            $filters[$filtername] = $filter;
1 efrain 220
        }
221
 
1441 ariadna 222
        return array_values($filters);
1 efrain 223
    }
224
 
225
    /**
226
     * Get user profile field type for report.
227
     *
228
     * @param string $userfield user field.
229
     * @return int the constant equivalent to this custom field type.
230
     */
231
    protected function get_user_field_type(string $userfield): int {
232
        switch ($userfield) {
233
            case 'checkbox':
234
                $customfieldtype = column::TYPE_BOOLEAN;
235
                break;
236
            case 'datetime':
237
                $customfieldtype = column::TYPE_TIMESTAMP;
238
                break;
239
            case 'textarea':
240
                $customfieldtype = column::TYPE_LONGTEXT;
241
                break;
242
            case 'menu':
243
            case 'text':
244
            default:
245
                $customfieldtype = column::TYPE_TEXT;
246
                break;
247
        }
248
        return $customfieldtype;
249
    }
250
}