<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace core_reportbuilder\local\entities;
> use context_helper;
use context_system;
> use context_user;
use html_writer;
> use core_component;
use lang_string;
use moodle_url;
use stdClass;
use core_user\fields;
use core_reportbuilder\local\filters\boolean_select;
use core_reportbuilder\local\filters\date;
use core_reportbuilder\local\filters\select;
use core_reportbuilder\local\filters\text;
use core_reportbuilder\local\filters\user as user_filter;
use core_reportbuilder\local\helpers\user_profile_fields;
use core_reportbuilder\local\helpers\format;
use core_reportbuilder\local\report\column;
use core_reportbuilder\local\report\filter;
/**
* User entity class implementation.
*
* This entity defines all the user columns and filters to be used in any report.
*
* @package core_reportbuilder
* @copyright 2020 Sara Arjona <sara@moodle.com> based on Marina Glancy code.
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user extends base {
/**
* Database tables that this entity uses and their default aliases
*
* @return array
*/
protected function get_default_table_aliases(): array {
< return ['user' => 'u'];
> return [
> 'user' => 'u',
> 'context' => 'uctx',
> 'tag_instance' => 'uti',
> 'tag' => 'ut',
> ];
}
/**
* The default title for this entity
*
* @return lang_string
*/
protected function get_default_entity_title(): lang_string {
return new lang_string('entityuser', 'core_reportbuilder');
}
/**
* Initialise the entity, add all user fields and all 'visible' user profile fields
*
* @return base
*/
public function initialise(): base {
$userprofilefields = $this->get_user_profile_fields();
$columns = array_merge($this->get_all_columns(), $userprofilefields->get_columns());
foreach ($columns as $column) {
$this->add_column($column);
}
$filters = array_merge($this->get_all_filters(), $userprofilefields->get_filters());
foreach ($filters as $filter) {
$this->add_filter($filter);
}
$conditions = array_merge($this->get_all_filters(), $userprofilefields->get_filters());
foreach ($conditions as $condition) {
$this->add_condition($condition);
}
return $this;
}
/**
* Get user profile fields helper instance
*
* @return user_profile_fields
*/
protected function get_user_profile_fields(): user_profile_fields {
$userprofilefields = new user_profile_fields($this->get_table_alias('user') . '.id', $this->get_entity_name());
$userprofilefields->add_joins($this->get_joins());
return $userprofilefields;
}
/**
> * Returns column that corresponds to the given identity field, profile field identifiers will be converted to those
* Returns list of all available columns
> * used by the {@see user_profile_fields} helper
*
> *
* These are all the columns available to use in any report that uses this entity.
> * @param string $identityfield Field from the user table, or a custom profile field
*
> * @return column
* @return column[]
> */
*/
> public function get_identity_column(string $identityfield): column {
protected function get_all_columns(): array {
> if (preg_match(fields::PROFILE_FIELD_REGEX, $identityfield, $matches)) {
global $DB;
> $identityfield = 'profilefield_' . $matches[1];
> }
$usertablealias = $this->get_table_alias('user');
>
> return $this->get_column($identityfield);
$fullnameselect = self::get_name_fields_select($usertablealias);
> }
$fullnamesort = explode(', ', $fullnameselect);
>
> /**
$userpictureselect = fields::for_userpic()->get_sql($usertablealias, false, '', '', false)->selects;
> * Returns filter that corresponds to the given identity field, profile field identifiers will be converted to those
$viewfullnames = has_capability('moodle/site:viewfullnames', context_system::instance());
> * used by the {@see user_profile_fields} helper
> *
// Fullname column.
> * @param string $identityfield Field from the user table, or a custom profile field
$columns[] = (new column(
> * @return filter
'fullname',
> */
new lang_string('fullname'),
> public function get_identity_filter(string $identityfield): filter {
$this->get_entity_name()
> if (preg_match(fields::PROFILE_FIELD_REGEX, $identityfield, $matches)) {
))
> $identityfield = 'profilefield_' . $matches[1];
->add_joins($this->get_joins())
> }
->add_fields($fullnameselect)
>
->set_type(column::TYPE_TEXT)
> return $this->get_filter($identityfield);
->set_is_sortable($this->is_sortable('fullname'), $fullnamesort)
> }
->add_callback(static function(?string $value, stdClass $row) use ($viewfullnames): string {
>
if ($value === null) {
> /**
return '';
> * Return joins necessary for retrieving tags
}
> *
> * @return string[]
// Ensure we populate all required name properties.
> */
$namefields = fields::get_name_fields();
> public function get_tag_joins(): array {
foreach ($namefields as $namefield) {
> $user = $this->get_table_alias('user');
$row->{$namefield} = $row->{$namefield} ?? '';
> $taginstance = $this->get_table_alias('tag_instance');
}
> $tag = $this->get_table_alias('tag');
>
return fullname($row, $viewfullnames);
> return [
});
> "LEFT JOIN {tag_instance} {$taginstance}
> ON {$taginstance}.component = 'core'
// Formatted fullname columns (with link, picture or both).
> AND {$taginstance}.itemtype = 'user'
$fullnamefields = [
> AND {$taginstance}.itemid = {$user}.id",
'fullnamewithlink' => new lang_string('userfullnamewithlink', 'core_reportbuilder'),
> "LEFT JOIN {tag} {$tag}
'fullnamewithpicture' => new lang_string('userfullnamewithpicture', 'core_reportbuilder'),
> ON {$tag}.id = {$taginstance}.tagid",
'fullnamewithpicturelink' => new lang_string('userfullnamewithpicturelink', 'core_reportbuilder'),
> ];
];
> }
foreach ($fullnamefields as $fullnamefield => $fullnamelang) {
>
$column = (new column(
> /**
$fullnamefield,
> $contexttablealias = $this->get_table_alias('context');
$fullnamelang,
$this->get_entity_name()
))
->add_joins($this->get_joins())
->add_fields($fullnameselect)
->add_field("{$usertablealias}.id")
->set_type(column::TYPE_TEXT)
->set_is_sortable($this->is_sortable($fullnamefield), $fullnamesort)
->add_callback(static function(?string $value, stdClass $row) use ($fullnamefield, $viewfullnames): string {
global $OUTPUT;
if ($value === null) {
return '';
}
// Ensure we populate all required name properties.
$namefields = fields::get_name_fields();
foreach ($namefields as $namefield) {
$row->{$namefield} = $row->{$namefield} ?? '';
}
if ($fullnamefield === 'fullnamewithlink') {
return html_writer::link(new moodle_url('/user/profile.php', ['id' => $row->id]),
fullname($row, $viewfullnames));
}
if ($fullnamefield === 'fullnamewithpicture') {
return $OUTPUT->user_picture($row, ['link' => false, 'alttext' => false]) .
fullname($row, $viewfullnames);
}
if ($fullnamefield === 'fullnamewithpicturelink') {
return html_writer::link(new moodle_url('/user/profile.php', ['id' => $row->id]),
$OUTPUT->user_picture($row, ['link' => false, 'alttext' => false]) .
fullname($row, $viewfullnames));
}
return $value;
});
// Picture fields need some more data.
if (strpos($fullnamefield, 'picture') !== false) {
$column->add_fields($userpictureselect);
}
$columns[] = $column;
}
// Picture column.
$columns[] = (new column(
'picture',
new lang_string('userpicture', 'core_reportbuilder'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->add_fields($userpictureselect)
->set_type(column::TYPE_INTEGER)
->set_is_sortable($this->is_sortable('picture'))
// It doesn't make sense to offer integer aggregation methods for this column.
->set_disabled_aggregation(['avg', 'max', 'min', 'sum'])
->add_callback(static function ($value, stdClass $row): string {
global $OUTPUT;
return !empty($row->id) ? $OUTPUT->user_picture($row, ['link' => false, 'alttext' => false]) : '';
});
// Add all other user fields.
$userfields = $this->get_user_fields();
foreach ($userfields as $userfield => $userfieldlang) {
$columntype = $this->get_user_field_type($userfield);
$columnfieldsql = "{$usertablealias}.{$userfield}";
if ($columntype === column::TYPE_LONGTEXT && $DB->get_dbfamily() === 'oracle') {
$columnfieldsql = $DB->sql_order_by_text($columnfieldsql, 1024);
}
$column = (new column(
$userfield,
$userfieldlang,
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type($columntype)
->add_field($columnfieldsql, $userfield)
->set_is_sortable($this->is_sortable($userfield))
->add_callback([$this, 'format'], $userfield);
// Some columns also have specific format callbacks.
if ($userfield === 'country') {
$column->add_callback(static function(string $country): string {
$countries = get_string_manager()->get_list_of_countries(true);
return $countries[$country] ?? '';
});
> } else if ($userfield === 'description') {
}
> // Select enough fields in order to format the column.
> $column
$columns[] = $column;
> ->add_join("LEFT JOIN {context} {$contexttablealias}
}
> ON {$contexttablealias}.contextlevel = " . CONTEXT_USER . "
> AND {$contexttablealias}.instanceid = {$usertablealias}.id")
return $columns;
> ->add_fields("{$usertablealias}.descriptionformat, {$usertablealias}.id")
}
> ->add_fields(context_helper::get_preload_record_columns_sql($contexttablealias));
/**
* Check if this field is sortable
*
* @param string $fieldname
* @return bool
*/
protected function is_sortable(string $fieldname): bool {
// Some columns can't be sorted, like longtext or images.
$nonsortable = [
> 'description',
'picture',
];
return !in_array($fieldname, $nonsortable);
}
/**
* Formats the user field for display.
*
* @param mixed $value Current field value.
* @param stdClass $row Complete row.
* @param string $fieldname Name of the field to format.
* @return string
*/
public function format($value, stdClass $row, string $fieldname): string {
> global $CFG;
if ($this->get_user_field_type($fieldname) === column::TYPE_BOOLEAN) {
>
return format::boolean_as_text($value);
}
if ($this->get_user_field_type($fieldname) === column::TYPE_TIMESTAMP) {
return format::userdate($value, $row);
}
> if ($fieldname === 'description') {
return s($value);
> if (empty($row->id)) {
}
> return '';
> }
/**
>
* Returns a SQL statement to select all user fields necessary for fullname() function
> require_once("{$CFG->libdir}/filelib.php");
*
>
* Note the implementation here is similar to {@see fields::get_sql_fullname} but without concatenation
> context_helper::preload_from_record($row);
*
> $context = context_user::instance($row->id);
* @param string $usertablealias
>
* @return string
> $description = file_rewrite_pluginfile_urls($value, 'pluginfile.php', $context->id, 'user', 'profile', null);
*/
> return format_text($description, $row->descriptionformat, ['context' => $context->id]);
public static function get_name_fields_select(string $usertablealias = 'u'): string {
> }
>
$namefields = fields::get_name_fields(true);
// Create a dummy user object containing all name fields.
$dummyuser = (object) array_combine($namefields, $namefields);
$dummyfullname = fullname($dummyuser, true);
// Extract any name fields from the fullname format in the order that they appear.
$matchednames = array_values(order_in_string($namefields, $dummyfullname));
$userfields = array_map(static function(string $userfield) use ($usertablealias): string {
if (!empty($usertablealias)) {
$userfield = "{$usertablealias}.{$userfield}";
}
return $userfield;
}, $matchednames);
return implode(', ', $userfields);
}
/**
* User fields
*
* @return lang_string[]
*/
protected function get_user_fields(): array {
return [
'firstname' => new lang_string('firstname'),
'lastname' => new lang_string('lastname'),
'email' => new lang_string('email'),
'city' => new lang_string('city'),
'country' => new lang_string('country'),
> 'description' => new lang_string('description'),
'firstnamephonetic' => new lang_string('firstnamephonetic'),
'lastnamephonetic' => new lang_string('lastnamephonetic'),
'middlename' => new lang_string('middlename'),
'alternatename' => new lang_string('alternatename'),
'idnumber' => new lang_string('idnumber'),
'institution' => new lang_string('institution'),
'department' => new lang_string('department'),
'phone1' => new lang_string('phone1'),
'phone2' => new lang_string('phone2'),
'address' => new lang_string('address'),
'lastaccess' => new lang_string('lastaccess'),
'suspended' => new lang_string('suspended'),
'confirmed' => new lang_string('confirmed', 'admin'),
'username' => new lang_string('username'),
'moodlenetprofile' => new lang_string('moodlenetprofile', 'user'),
> 'timecreated' => new lang_string('timecreated', 'core_reportbuilder'),
];
}
/**
* Return appropriate column type for given user field
*
* @param string $userfield
* @return int
*/
protected function get_user_field_type(string $userfield): int {
switch ($userfield) {
> case 'description':
case 'confirmed':
> $fieldtype = column::TYPE_LONGTEXT;
case 'suspended':
> break;
$fieldtype = column::TYPE_BOOLEAN;
break;
case 'lastaccess':
> case 'timecreated':
$fieldtype = column::TYPE_TIMESTAMP;
break;
default:
$fieldtype = column::TYPE_TEXT;
break;
}
return $fieldtype;
}
/**
* Return list of all available filters
*
* @return filter[]
*/
protected function get_all_filters(): array {
> global $DB;
$filters = [];
>
$tablealias = $this->get_table_alias('user');
// Fullname filter.
$canviewfullnames = has_capability('moodle/site:viewfullnames', context_system::instance());
[$fullnamesql, $fullnameparams] = fields::get_sql_fullname($tablealias, $canviewfullnames);
$filters[] = (new filter(
text::class,
'fullname',
new lang_string('fullname'),
$this->get_entity_name(),
$fullnamesql,
$fullnameparams
))
->add_joins($this->get_joins());
// User fields filters.
$fields = $this->get_user_fields();
foreach ($fields as $field => $name) {
> $filterfieldsql = "{$tablealias}.{$field}";
$optionscallback = [static::class, 'get_options_for_' . $field];
> if ($this->get_user_field_type($field) === column::TYPE_LONGTEXT) {
if (is_callable($optionscallback)) {
> $filterfieldsql = $DB->sql_cast_to_char($filterfieldsql);
$classname = select::class;
> }
} else if ($this->get_user_field_type($field) === column::TYPE_BOOLEAN) {
>
$classname = boolean_select::class;
} else if ($this->get_user_field_type($field) === column::TYPE_TIMESTAMP) {
$classname = date::class;
} else {
$classname = text::class;
}
$filter = (new filter(
$classname,
$field,
$name,
$this->get_entity_name(),
< $tablealias . '.' . $field
> $filterfieldsql
))
->add_joins($this->get_joins());
// Populate filter options by callback, if available.
if (is_callable($optionscallback)) {
$filter->set_options_callback($optionscallback);
}
$filters[] = $filter;
}
// User select filter.
$filters[] = (new filter(
user_filter::class,
'userselect',
new lang_string('userselect', 'core_reportbuilder'),
$this->get_entity_name(),
"{$tablealias}.id"
))
->add_joins($this->get_joins());
>
> // Authentication method filter.
return $filters;
> $filters[] = (new filter(
}
> select::class,
> 'auth',
/**
> new lang_string('authentication', 'moodle'),
* List of options for the field country.
> $this->get_entity_name(),
*
> "{$tablealias}.auth"
* @return string[]
> ))
*/
> ->add_joins($this->get_joins())
public static function get_options_for_country(): array {
> ->set_options_callback(static function(): array {
return array_map('shorten_text', get_string_manager()->get_list_of_countries());
> $plugins = core_component::get_plugin_list('auth');
}
> $enabled = get_string('pluginenabled', 'core_plugin');
}
> $disabled = get_string('plugindisabled', 'core_plugin');
> $authoptions = [$enabled => [], $disabled => []];
>
> foreach ($plugins as $pluginname => $unused) {
> $plugin = get_auth_plugin($pluginname);
> if (is_enabled_auth($pluginname)) {
> $authoptions[$enabled][$pluginname] = $plugin->get_title();
> } else {
> $authoptions[$disabled][$pluginname] = $plugin->get_title();
> }
> }
> return $authoptions;
> });