Differences Between: [Versions 400 and 401] [Versions 400 and 402] [Versions 400 and 403]
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\entities; 20 21 use context_system; 22 use html_writer; 23 use lang_string; 24 use moodle_url; 25 use stdClass; 26 use core_user\fields; 27 use core_reportbuilder\local\filters\boolean_select; 28 use core_reportbuilder\local\filters\date; 29 use core_reportbuilder\local\filters\select; 30 use core_reportbuilder\local\filters\text; 31 use core_reportbuilder\local\filters\user as user_filter; 32 use core_reportbuilder\local\helpers\user_profile_fields; 33 use core_reportbuilder\local\helpers\format; 34 use core_reportbuilder\local\report\column; 35 use core_reportbuilder\local\report\filter; 36 37 /** 38 * User entity class implementation. 39 * 40 * This entity defines all the user columns and filters to be used in any report. 41 * 42 * @package core_reportbuilder 43 * @copyright 2020 Sara Arjona <sara@moodle.com> based on Marina Glancy code. 44 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 45 */ 46 class user extends base { 47 48 /** 49 * Database tables that this entity uses and their default aliases 50 * 51 * @return array 52 */ 53 protected function get_default_table_aliases(): array { 54 return ['user' => 'u']; 55 } 56 57 /** 58 * The default title for this entity 59 * 60 * @return lang_string 61 */ 62 protected function get_default_entity_title(): lang_string { 63 return new lang_string('entityuser', 'core_reportbuilder'); 64 } 65 66 /** 67 * Initialise the entity, add all user fields and all 'visible' user profile fields 68 * 69 * @return base 70 */ 71 public function initialise(): base { 72 $userprofilefields = $this->get_user_profile_fields(); 73 74 $columns = array_merge($this->get_all_columns(), $userprofilefields->get_columns()); 75 foreach ($columns as $column) { 76 $this->add_column($column); 77 } 78 79 $filters = array_merge($this->get_all_filters(), $userprofilefields->get_filters()); 80 foreach ($filters as $filter) { 81 $this->add_filter($filter); 82 } 83 84 $conditions = array_merge($this->get_all_filters(), $userprofilefields->get_filters()); 85 foreach ($conditions as $condition) { 86 $this->add_condition($condition); 87 } 88 89 return $this; 90 } 91 92 /** 93 * Get user profile fields helper instance 94 * 95 * @return user_profile_fields 96 */ 97 protected function get_user_profile_fields(): user_profile_fields { 98 $userprofilefields = new user_profile_fields($this->get_table_alias('user') . '.id', $this->get_entity_name()); 99 $userprofilefields->add_joins($this->get_joins()); 100 return $userprofilefields; 101 } 102 103 /** 104 * Returns list of all available columns 105 * 106 * These are all the columns available to use in any report that uses this entity. 107 * 108 * @return column[] 109 */ 110 protected function get_all_columns(): array { 111 global $DB; 112 113 $usertablealias = $this->get_table_alias('user'); 114 115 $fullnameselect = self::get_name_fields_select($usertablealias); 116 $fullnamesort = explode(', ', $fullnameselect); 117 118 $userpictureselect = fields::for_userpic()->get_sql($usertablealias, false, '', '', false)->selects; 119 $viewfullnames = has_capability('moodle/site:viewfullnames', context_system::instance()); 120 121 // Fullname column. 122 $columns[] = (new column( 123 'fullname', 124 new lang_string('fullname'), 125 $this->get_entity_name() 126 )) 127 ->add_joins($this->get_joins()) 128 ->add_fields($fullnameselect) 129 ->set_type(column::TYPE_TEXT) 130 ->set_is_sortable($this->is_sortable('fullname'), $fullnamesort) 131 ->add_callback(static function(?string $value, stdClass $row) use ($viewfullnames): string { 132 if ($value === null) { 133 return ''; 134 } 135 136 // Ensure we populate all required name properties. 137 $namefields = fields::get_name_fields(); 138 foreach ($namefields as $namefield) { 139 $row->{$namefield} = $row->{$namefield} ?? ''; 140 } 141 142 return fullname($row, $viewfullnames); 143 }); 144 145 // Formatted fullname columns (with link, picture or both). 146 $fullnamefields = [ 147 'fullnamewithlink' => new lang_string('userfullnamewithlink', 'core_reportbuilder'), 148 'fullnamewithpicture' => new lang_string('userfullnamewithpicture', 'core_reportbuilder'), 149 'fullnamewithpicturelink' => new lang_string('userfullnamewithpicturelink', 'core_reportbuilder'), 150 ]; 151 foreach ($fullnamefields as $fullnamefield => $fullnamelang) { 152 $column = (new column( 153 $fullnamefield, 154 $fullnamelang, 155 $this->get_entity_name() 156 )) 157 ->add_joins($this->get_joins()) 158 ->add_fields($fullnameselect) 159 ->add_field("{$usertablealias}.id") 160 ->set_type(column::TYPE_TEXT) 161 ->set_is_sortable($this->is_sortable($fullnamefield), $fullnamesort) 162 ->add_callback(static function(?string $value, stdClass $row) use ($fullnamefield, $viewfullnames): string { 163 global $OUTPUT; 164 165 if ($value === null) { 166 return ''; 167 } 168 169 // Ensure we populate all required name properties. 170 $namefields = fields::get_name_fields(); 171 foreach ($namefields as $namefield) { 172 $row->{$namefield} = $row->{$namefield} ?? ''; 173 } 174 175 if ($fullnamefield === 'fullnamewithlink') { 176 return html_writer::link(new moodle_url('/user/profile.php', ['id' => $row->id]), 177 fullname($row, $viewfullnames)); 178 } 179 if ($fullnamefield === 'fullnamewithpicture') { 180 return $OUTPUT->user_picture($row, ['link' => false, 'alttext' => false]) . 181 fullname($row, $viewfullnames); 182 } 183 if ($fullnamefield === 'fullnamewithpicturelink') { 184 return html_writer::link(new moodle_url('/user/profile.php', ['id' => $row->id]), 185 $OUTPUT->user_picture($row, ['link' => false, 'alttext' => false]) . 186 fullname($row, $viewfullnames)); 187 } 188 189 return $value; 190 }); 191 192 // Picture fields need some more data. 193 if (strpos($fullnamefield, 'picture') !== false) { 194 $column->add_fields($userpictureselect); 195 } 196 197 $columns[] = $column; 198 } 199 200 // Picture column. 201 $columns[] = (new column( 202 'picture', 203 new lang_string('userpicture', 'core_reportbuilder'), 204 $this->get_entity_name() 205 )) 206 ->add_joins($this->get_joins()) 207 ->add_fields($userpictureselect) 208 ->set_type(column::TYPE_INTEGER) 209 ->set_is_sortable($this->is_sortable('picture')) 210 // It doesn't make sense to offer integer aggregation methods for this column. 211 ->set_disabled_aggregation(['avg', 'max', 'min', 'sum']) 212 ->add_callback(static function ($value, stdClass $row): string { 213 global $OUTPUT; 214 215 return !empty($row->id) ? $OUTPUT->user_picture($row, ['link' => false, 'alttext' => false]) : ''; 216 }); 217 218 // Add all other user fields. 219 $userfields = $this->get_user_fields(); 220 foreach ($userfields as $userfield => $userfieldlang) { 221 $columntype = $this->get_user_field_type($userfield); 222 223 $columnfieldsql = "{$usertablealias}.{$userfield}"; 224 if ($columntype === column::TYPE_LONGTEXT && $DB->get_dbfamily() === 'oracle') { 225 $columnfieldsql = $DB->sql_order_by_text($columnfieldsql, 1024); 226 } 227 228 $column = (new column( 229 $userfield, 230 $userfieldlang, 231 $this->get_entity_name() 232 )) 233 ->add_joins($this->get_joins()) 234 ->set_type($columntype) 235 ->add_field($columnfieldsql, $userfield) 236 ->set_is_sortable($this->is_sortable($userfield)) 237 ->add_callback([$this, 'format'], $userfield); 238 239 // Some columns also have specific format callbacks. 240 if ($userfield === 'country') { 241 $column->add_callback(static function(string $country): string { 242 $countries = get_string_manager()->get_list_of_countries(true); 243 return $countries[$country] ?? ''; 244 }); 245 } 246 247 $columns[] = $column; 248 } 249 250 return $columns; 251 } 252 253 /** 254 * Check if this field is sortable 255 * 256 * @param string $fieldname 257 * @return bool 258 */ 259 protected function is_sortable(string $fieldname): bool { 260 // Some columns can't be sorted, like longtext or images. 261 $nonsortable = [ 262 'picture', 263 ]; 264 265 return !in_array($fieldname, $nonsortable); 266 } 267 268 /** 269 * Formats the user field for display. 270 * 271 * @param mixed $value Current field value. 272 * @param stdClass $row Complete row. 273 * @param string $fieldname Name of the field to format. 274 * @return string 275 */ 276 public function format($value, stdClass $row, string $fieldname): string { 277 if ($this->get_user_field_type($fieldname) === column::TYPE_BOOLEAN) { 278 return format::boolean_as_text($value); 279 } 280 281 if ($this->get_user_field_type($fieldname) === column::TYPE_TIMESTAMP) { 282 return format::userdate($value, $row); 283 } 284 285 return s($value); 286 } 287 288 /** 289 * Returns a SQL statement to select all user fields necessary for fullname() function 290 * 291 * Note the implementation here is similar to {@see fields::get_sql_fullname} but without concatenation 292 * 293 * @param string $usertablealias 294 * @return string 295 */ 296 public static function get_name_fields_select(string $usertablealias = 'u'): string { 297 298 $namefields = fields::get_name_fields(true); 299 300 // Create a dummy user object containing all name fields. 301 $dummyuser = (object) array_combine($namefields, $namefields); 302 $dummyfullname = fullname($dummyuser, true); 303 304 // Extract any name fields from the fullname format in the order that they appear. 305 $matchednames = array_values(order_in_string($namefields, $dummyfullname)); 306 307 $userfields = array_map(static function(string $userfield) use ($usertablealias): string { 308 if (!empty($usertablealias)) { 309 $userfield = "{$usertablealias}.{$userfield}"; 310 } 311 312 return $userfield; 313 }, $matchednames); 314 315 return implode(', ', $userfields); 316 } 317 318 /** 319 * User fields 320 * 321 * @return lang_string[] 322 */ 323 protected function get_user_fields(): array { 324 return [ 325 'firstname' => new lang_string('firstname'), 326 'lastname' => new lang_string('lastname'), 327 'email' => new lang_string('email'), 328 'city' => new lang_string('city'), 329 'country' => new lang_string('country'), 330 'firstnamephonetic' => new lang_string('firstnamephonetic'), 331 'lastnamephonetic' => new lang_string('lastnamephonetic'), 332 'middlename' => new lang_string('middlename'), 333 'alternatename' => new lang_string('alternatename'), 334 'idnumber' => new lang_string('idnumber'), 335 'institution' => new lang_string('institution'), 336 'department' => new lang_string('department'), 337 'phone1' => new lang_string('phone1'), 338 'phone2' => new lang_string('phone2'), 339 'address' => new lang_string('address'), 340 'lastaccess' => new lang_string('lastaccess'), 341 'suspended' => new lang_string('suspended'), 342 'confirmed' => new lang_string('confirmed', 'admin'), 343 'username' => new lang_string('username'), 344 'moodlenetprofile' => new lang_string('moodlenetprofile', 'user'), 345 ]; 346 } 347 348 /** 349 * Return appropriate column type for given user field 350 * 351 * @param string $userfield 352 * @return int 353 */ 354 protected function get_user_field_type(string $userfield): int { 355 switch ($userfield) { 356 case 'confirmed': 357 case 'suspended': 358 $fieldtype = column::TYPE_BOOLEAN; 359 break; 360 case 'lastaccess': 361 $fieldtype = column::TYPE_TIMESTAMP; 362 break; 363 default: 364 $fieldtype = column::TYPE_TEXT; 365 break; 366 } 367 368 return $fieldtype; 369 } 370 371 /** 372 * Return list of all available filters 373 * 374 * @return filter[] 375 */ 376 protected function get_all_filters(): array { 377 $filters = []; 378 $tablealias = $this->get_table_alias('user'); 379 380 // Fullname filter. 381 $canviewfullnames = has_capability('moodle/site:viewfullnames', context_system::instance()); 382 [$fullnamesql, $fullnameparams] = fields::get_sql_fullname($tablealias, $canviewfullnames); 383 $filters[] = (new filter( 384 text::class, 385 'fullname', 386 new lang_string('fullname'), 387 $this->get_entity_name(), 388 $fullnamesql, 389 $fullnameparams 390 )) 391 ->add_joins($this->get_joins()); 392 393 // User fields filters. 394 $fields = $this->get_user_fields(); 395 foreach ($fields as $field => $name) { 396 $optionscallback = [static::class, 'get_options_for_' . $field]; 397 if (is_callable($optionscallback)) { 398 $classname = select::class; 399 } else if ($this->get_user_field_type($field) === column::TYPE_BOOLEAN) { 400 $classname = boolean_select::class; 401 } else if ($this->get_user_field_type($field) === column::TYPE_TIMESTAMP) { 402 $classname = date::class; 403 } else { 404 $classname = text::class; 405 } 406 407 $filter = (new filter( 408 $classname, 409 $field, 410 $name, 411 $this->get_entity_name(), 412 $tablealias . '.' . $field 413 )) 414 ->add_joins($this->get_joins()); 415 416 // Populate filter options by callback, if available. 417 if (is_callable($optionscallback)) { 418 $filter->set_options_callback($optionscallback); 419 } 420 421 $filters[] = $filter; 422 } 423 424 // User select filter. 425 $filters[] = (new filter( 426 user_filter::class, 427 'userselect', 428 new lang_string('userselect', 'core_reportbuilder'), 429 $this->get_entity_name(), 430 "{$tablealias}.id" 431 )) 432 ->add_joins($this->get_joins()); 433 434 return $filters; 435 } 436 437 /** 438 * List of options for the field country. 439 * 440 * @return string[] 441 */ 442 public static function get_options_for_country(): array { 443 return array_map('shorten_text', get_string_manager()->get_list_of_countries()); 444 } 445 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body