559 lines
20 KiB
PHP
559 lines
20 KiB
PHP
<?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 core_component;
|
|
use html_writer;
|
|
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',
|
|
'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
|
|
* used by the {@see user_profile_fields} helper
|
|
*
|
|
* @param string $identityfield Field from the user table, or a custom profile field
|
|
* @return column
|
|
*/
|
|
public function get_identity_column(string $identityfield): column {
|
|
if (preg_match(fields::PROFILE_FIELD_REGEX, $identityfield, $matches)) {
|
|
$identityfield = 'profilefield_' . $matches[1];
|
|
}
|
|
|
|
return $this->get_column($identityfield);
|
|
}
|
|
|
|
/**
|
|
* Returns filter that corresponds to the given identity field, profile field identifiers will be converted to those
|
|
* used by the {@see user_profile_fields} helper
|
|
*
|
|
* @param string $identityfield Field from the user table, or a custom profile field
|
|
* @return filter
|
|
*/
|
|
public function get_identity_filter(string $identityfield): filter {
|
|
if (preg_match(fields::PROFILE_FIELD_REGEX, $identityfield, $matches)) {
|
|
$identityfield = 'profilefield_' . $matches[1];
|
|
}
|
|
|
|
return $this->get_filter($identityfield);
|
|
}
|
|
|
|
/**
|
|
* Return joins necessary for retrieving tags
|
|
*
|
|
* @return string[]
|
|
*/
|
|
public function get_tag_joins(): array {
|
|
return $this->get_tag_joins_for_entity('core', 'user', $this->get_table_alias('user') . '.id');
|
|
}
|
|
|
|
/**
|
|
* Returns list of all available columns
|
|
*
|
|
* These are all the columns available to use in any report that uses this entity.
|
|
*
|
|
* @return column[]
|
|
*/
|
|
protected function get_all_columns(): array {
|
|
global $DB;
|
|
|
|
$usertablealias = $this->get_table_alias('user');
|
|
$contexttablealias = $this->get_table_alias('context');
|
|
|
|
$fullnameselect = self::get_name_fields_select($usertablealias);
|
|
$fullnamesort = explode(', ', $fullnameselect);
|
|
|
|
$userpictureselect = fields::for_userpic()->get_sql($usertablealias, false, '', '', false)->selects;
|
|
$viewfullnames = has_capability('moodle/site:viewfullnames', context_system::instance());
|
|
|
|
// Fullname column.
|
|
$columns[] = (new column(
|
|
'fullname',
|
|
new lang_string('fullname'),
|
|
$this->get_entity_name()
|
|
))
|
|
->add_joins($this->get_joins())
|
|
->add_fields($fullnameselect)
|
|
->set_type(column::TYPE_TEXT)
|
|
->set_is_sortable($this->is_sortable('fullname'), $fullnamesort)
|
|
->add_callback(static function(?string $value, stdClass $row) use ($viewfullnames): string {
|
|
if ($value === null) {
|
|
return '';
|
|
}
|
|
|
|
// Ensure we populate all required name properties.
|
|
$namefields = fields::get_name_fields();
|
|
foreach ($namefields as $namefield) {
|
|
$row->{$namefield} = $row->{$namefield} ?? '';
|
|
}
|
|
|
|
return fullname($row, $viewfullnames);
|
|
});
|
|
|
|
// Formatted fullname columns (with link, picture or both).
|
|
$fullnamefields = [
|
|
'fullnamewithlink' => new lang_string('userfullnamewithlink', 'core_reportbuilder'),
|
|
'fullnamewithpicture' => new lang_string('userfullnamewithpicture', 'core_reportbuilder'),
|
|
'fullnamewithpicturelink' => new lang_string('userfullnamewithpicturelink', 'core_reportbuilder'),
|
|
];
|
|
foreach ($fullnamefields as $fullnamefield => $fullnamelang) {
|
|
$column = (new column(
|
|
$fullnamefield,
|
|
$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
|
|
->add_join("LEFT JOIN {context} {$contexttablealias}
|
|
ON {$contexttablealias}.contextlevel = " . CONTEXT_USER . "
|
|
AND {$contexttablealias}.instanceid = {$usertablealias}.id")
|
|
->add_fields("{$usertablealias}.descriptionformat, {$usertablealias}.id")
|
|
->add_fields(context_helper::get_preload_record_columns_sql($contexttablealias));
|
|
}
|
|
|
|
$columns[] = $column;
|
|
}
|
|
|
|
return $columns;
|
|
}
|
|
|
|
/**
|
|
* 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') {
|
|
if (empty($row->id)) {
|
|
return '';
|
|
}
|
|
|
|
require_once("{$CFG->libdir}/filelib.php");
|
|
|
|
context_helper::preload_from_record($row);
|
|
$context = context_user::instance($row->id);
|
|
|
|
$description = file_rewrite_pluginfile_urls($value, 'pluginfile.php', $context->id, 'user', 'profile', null);
|
|
return format_text($description, $row->descriptionformat, ['context' => $context->id]);
|
|
}
|
|
|
|
return s($value);
|
|
}
|
|
|
|
/**
|
|
* Returns a SQL statement to select all user fields necessary for fullname() function
|
|
*
|
|
* Note the implementation here is similar to {@see fields::get_sql_fullname} but without concatenation
|
|
*
|
|
* @param string $usertablealias
|
|
* @return string
|
|
*/
|
|
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);
|
|
$viewfullnames = has_capability('moodle/site:viewfullnames', context_system::instance());
|
|
$dummyfullname = fullname($dummyuser, $viewfullnames);
|
|
|
|
// 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':
|
|
$fieldtype = column::TYPE_LONGTEXT;
|
|
break;
|
|
case 'confirmed':
|
|
case 'suspended':
|
|
$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}";
|
|
if ($this->get_user_field_type($field) === column::TYPE_LONGTEXT) {
|
|
$filterfieldsql = $DB->sql_cast_to_char($filterfieldsql);
|
|
}
|
|
|
|
$optionscallback = [static::class, 'get_options_for_' . $field];
|
|
if (is_callable($optionscallback)) {
|
|
$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(),
|
|
$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.
|
|
$filters[] = (new filter(
|
|
select::class,
|
|
'auth',
|
|
new lang_string('authentication', 'moodle'),
|
|
$this->get_entity_name(),
|
|
"{$tablealias}.auth"
|
|
))
|
|
->add_joins($this->get_joins())
|
|
->set_options_callback(static function(): array {
|
|
$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;
|
|
});
|
|
|
|
return $filters;
|
|
}
|
|
|
|
/**
|
|
* List of options for the field country.
|
|
*
|
|
* @return string[]
|
|
*/
|
|
public static function get_options_for_country(): array {
|
|
return array_map('shorten_text', get_string_manager()->get_list_of_countries());
|
|
}
|
|
}
|