first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -0,0 +1,101 @@
<?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/>.
/**
* User profile set indicator.
*
* @package core_user
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_user\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* User profile set indicator.
*
* @package core_user
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_profile_set extends \core_analytics\local\indicator\linear {
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('indicator:completeduserprofile');
}
/**
* required_sample_data
*
* @return string[]
*/
public static function required_sample_data() {
return array('user');
}
/**
* calculate_sample
*
* @param int $sampleid
* @param string $sampleorigin
* @param int $starttime
* @param int $endtime
* @return float
*/
protected function calculate_sample($sampleid, $sampleorigin, $starttime = false, $endtime = false) {
global $CFG;
$user = $this->retrieve('user', $sampleid);
// Nothing set results in -1.
$calculatedvalue = self::MIN_VALUE;
if (\core_user::awaiting_action($user)) {
return self::MIN_VALUE;
}
if (!$user->confirmed) {
return self::MIN_VALUE;
}
if ($user->description != '') {
$calculatedvalue += 1;
}
if ($user->picture != '') {
$calculatedvalue += 1;
}
// 0.2 for any of the following fields being set (some of them may even be compulsory or have a default).
$fields = array('institution', 'department', 'address', 'city', 'country');
foreach ($fields as $fieldname) {
if ($user->{$fieldname} != '') {
$calculatedvalue += 0.2;
}
}
return $this->limit_value($calculatedvalue);
}
}
@@ -0,0 +1,71 @@
<?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/>.
/**
* User tracks forums indicator.
*
* @package core_user
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_user\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* User tracks forums indicator.
*
* @package core_user
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_track_forums extends \core_analytics\local\indicator\binary {
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('indicator:userforumstracking');
}
/**
* required_sample_data
*
* @return string[]
*/
public static function required_sample_data() {
return array('user');
}
/**
* calculate_sample
*
* @param int $sampleid
* @param string $samplesorigin
* @param int $starttime
* @param int $endtime
* @return float
*/
protected function calculate_sample($sampleid, $samplesorigin, $starttime = false, $endtime = false) {
$user = $this->retrieve('user', $sampleid);
return ($user->trackforums) ? self::get_max_value() : self::get_min_value();
}
}
@@ -0,0 +1,274 @@
<?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/>.
/**
* Upcoming activities due target.
*
* @package core
* @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_user\analytics\target;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/lib/enrollib.php');
/**
* Upcoming activities due target.
*
* @package core
* @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class upcoming_activities_due extends \core_analytics\local\target\binary {
/**
* Machine learning backends are not required to predict.
*
* @return bool
*/
public static function based_on_assumptions() {
return true;
}
/**
* Only update last analysis time when analysables are processed.
* @return bool
*/
public function always_update_analysis_time(): bool {
return false;
}
/**
* Only upcoming stuff.
*
* @param \core_analytics\local\time_splitting\base $timesplitting
* @return bool
*/
public function can_use_timesplitting(\core_analytics\local\time_splitting\base $timesplitting): bool {
return ($timesplitting instanceof \core_analytics\local\time_splitting\after_now);
}
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('target:upcomingactivitiesdue', 'user');
}
/**
* Overwritten to show a simpler language string.
*
* @param int $modelid
* @param \context $context
* @return string
*/
public function get_insight_subject(int $modelid, \context $context) {
return get_string('youhaveupcomingactivitiesdue');
}
/**
* classes_description
*
* @return string[]
*/
protected static function classes_description() {
return array(
get_string('no'),
get_string('yes'),
);
}
/**
* Returns the predicted classes that will be ignored.
*
* @return array
*/
public function ignored_predicted_classes() {
// No need to process users without upcoming activities due.
return array(0);
}
/**
* get_analyser_class
*
* @return string
*/
public function get_analyser_class() {
return '\core\analytics\analyser\users';
}
/**
* All users are ok.
*
* @param \core_analytics\analysable $analysable
* @param mixed $fortraining
* @return true|string
*/
public function is_valid_analysable(\core_analytics\analysable $analysable, $fortraining = true) {
// The calendar API used by \core_course\analytics\indicator\activities_due is already checking
// if the user has any courses.
return true;
}
/**
* Samples are users and all of them are ok.
*
* @param int $sampleid
* @param \core_analytics\analysable $analysable
* @param bool $fortraining
* @return bool
*/
public function is_valid_sample($sampleid, \core_analytics\analysable $analysable, $fortraining = true) {
return true;
}
/**
* Calculation based on activities due indicator.
*
* @param int $sampleid
* @param \core_analytics\analysable $analysable
* @param int $starttime
* @param int $endtime
* @return float
*/
protected function calculate_sample($sampleid, \core_analytics\analysable $analysable, $starttime = false, $endtime = false) {
$activitiesdueindicator = $this->retrieve('\core_course\analytics\indicator\activities_due', $sampleid);
if ($activitiesdueindicator == \core_course\analytics\indicator\activities_due::get_max_value()) {
return 1;
}
return 0;
}
/**
* No need to link to the insights report in this case.
*
* @return bool
*/
public function link_insights_report(): bool {
return false;
}
/**
* Returns the body message for an insight of a single prediction.
*
* This default method is executed when the analysable used by the model generates one insight
* for each analysable (one_sample_per_analysable === true)
*
* @param \context $context
* @param \stdClass $user
* @param \core_analytics\prediction $prediction
* @param \core_analytics\action[] $actions Passed by reference to remove duplicate links to actions.
* @return array Plain text msg, HTML message and the main URL for this
* insight (you can return null if you are happy with the
* default insight URL calculated in prediction_info())
*/
public function get_insight_body_for_prediction(\context $context, \stdClass $user, \core_analytics\prediction $prediction,
array &$actions) {
global $OUTPUT;
$fullmessageplaintext = get_string('youhaveupcomingactivitiesdueinfo', 'moodle', $user->firstname);
$sampledata = $prediction->get_sample_data();
$activitiesdue = $sampledata['core_course\analytics\indicator\activities_due:extradata'];
if (empty($activitiesdue)) {
// We can throw an exception here because this is a target based on assumptions and we require the
// activities_due indicator.
throw new \coding_exception('The activities_due indicator must be part of the model indicators.');
}
$activitiestext = [];
foreach ($activitiesdue as $key => $activitydue) {
// Human-readable version.
$activitiesdue[$key]->formattedtime = userdate($activitydue->time);
// We provide the URL to the activity through a script that records the user click.
$activityurl = new \moodle_url($activitydue->url);
$actionurl = \core_analytics\prediction_action::transform_to_forward_url($activityurl, 'viewupcoming',
$prediction->get_prediction_data()->id);
$activitiesdue[$key]->url = $actionurl->out(false);
if (count($activitiesdue) === 1) {
// We will use this activity as the main URL of this insight.
$insighturl = $actionurl;
}
$activitiestext[] = $activitydue->name . ': ' . $activitiesdue[$key]->url;
}
foreach ($actions as $key => $action) {
if ($action->get_action_name() === 'viewupcoming') {
// Use it as the main URL of the insight if there are multiple activities due.
if (empty($insighturl)) {
$insighturl = $action->get_url();
}
// Remove the 'viewupcoming' action from the list of actions for this prediction as the action has
// been included in the link to the activity.
unset($actions[$key]);
break;
}
}
$activitieshtml = $OUTPUT->render_from_template('core_user/upcoming_activities_due_insight_body', (object) [
'activitiesdue' => array_values($activitiesdue),
'userfirstname' => $user->firstname
]);
return [
FORMAT_PLAIN => $fullmessageplaintext . PHP_EOL . PHP_EOL . implode(PHP_EOL, $activitiestext) . PHP_EOL,
FORMAT_HTML => $activitieshtml,
'url' => $insighturl,
];
}
/**
* Adds a view upcoming events action.
*
* @param \core_analytics\prediction $prediction
* @param mixed $includedetailsaction
* @param bool $isinsightuser
* @return \core_analytics\prediction_action[]
*/
public function prediction_actions(\core_analytics\prediction $prediction, $includedetailsaction = false,
$isinsightuser = false) {
global $CFG, $USER;
$parentactions = parent::prediction_actions($prediction, $includedetailsaction, $isinsightuser);
if (!$isinsightuser && $USER->id != $prediction->get_prediction_data()->sampleid) {
return $parentactions;
}
// We force a lookahead of 30 days so we are sure that the upcoming activities due are shown.
$url = new \moodle_url('/calendar/view.php', ['view' => 'upcoming', 'lookahead' => '30']);
$pix = new \pix_icon('i/calendar', get_string('viewupcomingactivitiesdue', 'calendar'));
$action = new \core_analytics\prediction_action('viewupcoming', $prediction,
$url, $pix, get_string('viewupcomingactivitiesdue', 'calendar'));
return array_merge([$action], $parentactions);
}
}
+52
View File
@@ -0,0 +1,52 @@
<?php
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
namespace core_user;
/**
* Update public key against registered user device.
*
* @package core
* @copyright Alex Morris <alex.morris@catalyst.net.nz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.2
*/
class devicekey {
/**
* Update the users public key for the specified device and app.
*
* @param string $uuid The device UUID.
* @param string $appid The app id, usually something like com.moodle.moodlemobile.
* @param string $publickey The app generated public key.
* @return bool
* @since Moodle 4.2
*/
public static function update_device_public_key(string $uuid, string $appid, string $publickey): bool {
global $DB, $USER;
$params = [
'uuid' => $uuid,
'appid' => $appid,
'userid' => $USER->id,
];
if ($DB->record_exists('user_devices', $params)) {
$DB->set_field('user_devices', 'publickey', $publickey, $params);
return true;
}
return false;
}
}
+137
View File
@@ -0,0 +1,137 @@
<?php
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
namespace core_user\external;
use core_external\external_api;
use core_external\external_description;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
/**
* Provides the core_user_search_identity external function.
*
* @package core_user
* @category external
* @copyright 2021 David Mudrák <david@moodle.com>
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class search_identity extends external_api {
/**
* Describes the external function parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'query' => new external_value(PARAM_RAW, 'The search query', VALUE_REQUIRED),
]);
}
/**
* Finds users with the identity matching the given query.
*
* @param string $query The search request.
* @return array
*/
public static function execute(string $query): array {
global $DB, $CFG;
$params = external_api::validate_parameters(self::execute_parameters(), [
'query' => $query,
]);
$query = clean_param($params['query'], PARAM_TEXT);
// Validate context.
$context = \context_system::instance();
self::validate_context($context);
require_capability('moodle/user:viewalldetails', $context);
$hasviewfullnames = has_capability('moodle/site:viewfullnames', $context);
$fields = \core_user\fields::for_name()->with_identity($context, false);
$extrafields = $fields->get_required_fields([\core_user\fields::PURPOSE_IDENTITY]);
list($searchsql, $searchparams) = users_search_sql($query, '', USER_SEARCH_CONTAINS, $extrafields);
list($sortsql, $sortparams) = users_order_by_sql('', $query, $context);
$params = array_merge($searchparams, $sortparams);
$rs = $DB->get_recordset_select('user', $searchsql, $params, $sortsql,
'id' . $fields->get_sql()->selects, 0, $CFG->maxusersperpage + 1);
$count = 0;
$list = [];
foreach ($rs as $record) {
$user = (object)[
'id' => $record->id,
'fullname' => fullname($record, $hasviewfullnames),
'extrafields' => [],
];
foreach ($extrafields as $extrafield) {
// Sanitize the extra fields to prevent potential XSS exploit.
$user->extrafields[] = (object)[
'name' => $extrafield,
'value' => s($record->$extrafield)
];
}
$count++;
if ($count <= $CFG->maxusersperpage) {
$list[$record->id] = $user;
}
}
$rs->close();
return [
'list' => $list,
'maxusersperpage' => $CFG->maxusersperpage,
'overflow' => ($count > $CFG->maxusersperpage),
];
}
/**
* Describes the external function result value.
*
* @return external_description
*/
public static function execute_returns(): external_description {
return new external_single_structure([
'list' => new external_multiple_structure(
new external_single_structure([
'id' => new external_value(\core_user::get_property_type('id'), 'ID of the user'),
// The output of the {@see fullname()} can contain formatting HTML such as <ruby> tags.
// So we need PARAM_RAW here and the caller is supposed to render it appropriately.
'fullname' => new external_value(PARAM_RAW, 'The fullname of the user'),
'extrafields' => new external_multiple_structure(
new external_single_structure([
'name' => new external_value(PARAM_TEXT, 'Name of the extrafield.'),
'value' => new external_value(PARAM_TEXT, 'Value of the extrafield.'),
]), 'List of extra fields', VALUE_OPTIONAL)
])
),
'maxusersperpage' => new external_value(PARAM_INT, 'Configured maximum users per page.'),
'overflow' => new external_value(PARAM_BOOL, 'Were there more records than maxusersperpage found?'),
]);
}
}
+101
View File
@@ -0,0 +1,101 @@
<?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/>.
namespace core_user\external;
use context_system;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use core_user\devicekey;
/**
* Update public key against registered user device.
*
* @package core
* @copyright Alex Morris <alex.morris@catalyst.net.nz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.2
*/
class update_user_device_public_key extends external_api {
/**
* Returns description of method parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'uuid' => new external_value(PARAM_RAW, 'the device UUID'),
'appid' => new external_value(PARAM_NOTAGS, 'The app id, something like com.moodle.moodlemobile'),
'publickey' => new external_value(PARAM_RAW, 'the app generated public key'),
]);
}
/**
* Update public key against registered user device.
*
* @param string $uuid The device UUID.
* @param string $appid The app id, usually something like com.moodle.moodlemobile.
* @param string $publickey The app generated public key.
* @return array Status and list of possible warnings
*/
public static function execute($uuid, $appid, $publickey): array {
[
'uuid' => $uuid,
'appid' => $appid,
'publickey' => $publickey
] = self::validate_parameters(self::execute_parameters(), [
'uuid' => $uuid,
'appid' => $appid,
'publickey' => $publickey
]);
$context = context_system::instance();
self::validate_context($context);
$warnings = [];
$status = devicekey::update_device_public_key($uuid, $appid, $publickey);
if (!$status) {
$warnings[] = [
'item' => $uuid,
'warningcode' => 'devicedoesnotexist',
'message' => 'Could not find a device with the specified device UUID and app ID for this user'
];
}
return [
'status' => $status,
'warnings' => $warnings,
];
}
/**
* Returns description of method result value.
*
* @return external_single_structure
* @since Moodle 4.2
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'status' => new external_value(PARAM_BOOL, 'Whether the request was successful'),
'warnings' => new external_warnings()
]);
}
}
+145
View File
@@ -0,0 +1,145 @@
<?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/>.
/**
* Class for exporting a user summary from an stdClass.
*
* @package core_user
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_user\external;
defined('MOODLE_INTERNAL') || die();
use context_system;
use renderer_base;
use moodle_url;
/**
* Class for exporting a user summary from an stdClass.
*
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_summary_exporter extends \core\external\exporter {
protected function get_other_values(renderer_base $output) {
global $PAGE, $CFG;
// Add user picture.
$userpicture = new \user_picture($this->data);
$userpicture->size = 1; // Size f1.
$profileimageurl = $userpicture->get_url($PAGE)->out(false);
$userpicture->size = 0; // Size f2.
$profileimageurlsmall = $userpicture->get_url($PAGE)->out(false);
$profileurl = (new moodle_url('/user/profile.php', array('id' => $this->data->id)))->out(false);
// TODO Does not support custom user profile fields (MDL-70456).
$identityfields = array_flip(\core_user\fields::get_identity_fields(null, false));
$data = $this->data;
foreach ($identityfields as $field => $index) {
if (!empty($data->$field)) {
$identityfields[$field] = $data->$field;
} else {
unset($identityfields[$field]);
}
}
$identity = implode(', ', $identityfields);
return array(
'fullname' => fullname($this->data),
'profileimageurl' => $profileimageurl,
'profileimageurlsmall' => $profileimageurlsmall,
'profileurl' => $profileurl,
'identity' => $identity
);
}
/**
* Get the format parameters for department.
*
* @return array
*/
protected function get_format_parameters_for_department() {
return [
'context' => context_system::instance(), // The system context is cached, so we can get it right away.
];
}
/**
* Get the format parameters for institution.
*
* @return array
*/
protected function get_format_parameters_for_institution() {
return [
'context' => context_system::instance(), // The system context is cached, so we can get it right away.
];
}
public static function define_properties() {
return array(
'id' => array(
'type' => \core_user::get_property_type('id'),
),
'email' => array(
'type' => \core_user::get_property_type('email'),
'default' => ''
),
'idnumber' => array(
'type' => \core_user::get_property_type('idnumber'),
'default' => ''
),
'phone1' => array(
'type' => \core_user::get_property_type('phone1'),
'default' => ''
),
'phone2' => array(
'type' => \core_user::get_property_type('phone2'),
'default' => ''
),
'department' => array(
'type' => \core_user::get_property_type('department'),
'default' => ''
),
'institution' => array(
'type' => \core_user::get_property_type('institution'),
'default' => ''
)
);
}
public static function define_other_properties() {
return array(
'fullname' => array(
'type' => PARAM_RAW
),
'identity' => array(
'type' => PARAM_RAW
),
'profileurl' => array(
'type' => PARAM_URL
),
'profileimageurl' => array(
'type' => PARAM_URL
),
'profileimageurlsmall' => array(
'type' => PARAM_URL
),
);
}
}
+685
View File
@@ -0,0 +1,685 @@
<?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/>.
namespace core_user;
use core_text;
/**
* Class for retrieving information about user fields that are needed for displaying user identity.
*
* @package core_user
*/
class fields {
/** @var string Prefix used to identify custom profile fields */
const PROFILE_FIELD_PREFIX = 'profile_field_';
/** @var string Regular expression used to match a field name against the prefix */
const PROFILE_FIELD_REGEX = '~^' . self::PROFILE_FIELD_PREFIX . '(.*)$~';
/** @var int All fields required to display user's identity, based on server configuration */
const PURPOSE_IDENTITY = 0;
/** @var int All fields required to display a user picture */
const PURPOSE_USERPIC = 1;
/** @var int All fields required for somebody's name */
const PURPOSE_NAME = 2;
/** @var int Field required by custom include list */
const CUSTOM_INCLUDE = 3;
/** @var \context|null Context in use */
protected $context;
/** @var bool True to allow custom user fields */
protected $allowcustom;
/** @var bool[] Array of purposes (from PURPOSE_xx to true/false) */
protected $purposes;
/** @var string[] List of extra fields to include */
protected $include;
/** @var string[] List of fields to exclude */
protected $exclude;
/** @var int Unique identifier for different queries generated in same request */
protected static $uniqueidentifier = 1;
/** @var array|null Associative array from field => array of purposes it was used for => true */
protected $fields = null;
/**
* Protected constructor - use one of the for_xx methods to create an object.
*
* @param int $purpose Initial purpose for object or -1 for none
*/
protected function __construct(int $purpose = -1) {
$this->purposes = [
self::PURPOSE_IDENTITY => false,
self::PURPOSE_USERPIC => false,
self::PURPOSE_NAME => false,
];
if ($purpose != -1) {
$this->purposes[$purpose] = true;
}
$this->include = [];
$this->exclude = [];
$this->context = null;
$this->allowcustom = true;
}
/**
* Constructs an empty user fields object to get arbitrary user fields.
*
* You can add fields to retrieve with the including() function.
*
* @return fields User fields object ready for use
*/
public static function empty(): fields {
return new fields();
}
/**
* Constructs a user fields object to get identity information for display.
*
* The function does all the required capability checks to see if the current user is allowed
* to see them in the specified context. You can pass context null to get all the fields without
* checking permissions.
*
* If the code can only handle fields in the main user table, and not custom profile fields,
* then set $allowcustom to false.
*
* Note: After constructing the object you can use the ->with_xx, ->including, and ->excluding
* functions to control the required fields in more detail. For example:
*
* $fields = fields::for_identity($context)->with_userpic()->excluding('email');
*
* @param \context|null $context Context; if supplied, includes only fields the current user should see
* @param bool $allowcustom If true, custom profile fields may be included
* @return fields User fields object ready for use
*/
public static function for_identity(?\context $context, bool $allowcustom = true): fields {
$fields = new fields(self::PURPOSE_IDENTITY);
$fields->context = $context;
$fields->allowcustom = $allowcustom;
return $fields;
}
/**
* Constructs a user fields object to get information required for displaying a user picture.
*
* Note: After constructing the object you can use the ->with_xx, ->including, and ->excluding
* functions to control the required fields in more detail. For example:
*
* $fields = fields::for_userpic()->with_name()->excluding('email');
*
* @return fields User fields object ready for use
*/
public static function for_userpic(): fields {
return new fields(self::PURPOSE_USERPIC);
}
/**
* Constructs a user fields object to get information required for displaying a user full name.
*
* Note: After constructing the object you can use the ->with_xx, ->including, and ->excluding
* functions to control the required fields in more detail. For example:
*
* $fields = fields::for_name()->with_userpic()->excluding('email');
*
* @return fields User fields object ready for use
*/
public static function for_name(): fields {
return new fields(self::PURPOSE_NAME);
}
/**
* On an existing fields object, adds the fields required for displaying user pictures.
*
* @return $this Same object for chaining function calls
*/
public function with_userpic(): fields {
$this->purposes[self::PURPOSE_USERPIC] = true;
return $this;
}
/**
* On an existing fields object, adds the fields required for displaying user full names.
*
* @return $this Same object for chaining function calls
*/
public function with_name(): fields {
$this->purposes[self::PURPOSE_NAME] = true;
return $this;
}
/**
* On an existing fields object, adds the fields required for displaying user identity.
*
* The function does all the required capability checks to see if the current user is allowed
* to see them in the specified context. You can pass context null to get all the fields without
* checking permissions.
*
* If the code can only handle fields in the main user table, and not custom profile fields,
* then set $allowcustom to false.
*
* @param \context|null Context; if supplied, includes only fields the current user should see
* @param bool $allowcustom If true, custom profile fields may be included
* @return $this Same object for chaining function calls
*/
public function with_identity(?\context $context, bool $allowcustom = true): fields {
$this->context = $context;
$this->allowcustom = $allowcustom;
$this->purposes[self::PURPOSE_IDENTITY] = true;
return $this;
}
/**
* On an existing fields object, adds extra fields to be retrieved. You can specify either
* fields from the user table e.g. 'email', or profile fields e.g. 'profile_field_height'.
*
* @param string ...$include One or more fields to add
* @return $this Same object for chaining function calls
*/
public function including(string ...$include): fields {
$this->include = array_merge($this->include, $include);
return $this;
}
/**
* On an existing fields object, excludes fields from retrieval. You can specify either
* fields from the user table e.g. 'email', or profile fields e.g. 'profile_field_height'.
*
* This is useful when constructing queries where your query already explicitly references
* certain fields, so you don't want to retrieve them twice.
*
* @param string ...$exclude One or more fields to exclude
* @return $this Same object for chaining function calls
*/
public function excluding(...$exclude): fields {
$this->exclude = array_merge($this->exclude, $exclude);
return $this;
}
/**
* Gets an array of all fields that are required for the specified purposes, also taking
* into account the $includes and $excludes settings.
*
* The results may include basic field names (columns from the 'user' database table) and,
* unless turned off, custom profile field names in the format 'profile_field_myfield'.
*
* You should not rely on the order of fields, with one exception: if there is an id field
* it will be returned first. This is in case it is used with get_records calls.
*
* The $limitpurposes parameter is useful if you want to get a different set of fields than the
* purposes in the constructor. For example, if you want to get SQL for identity + user picture
* fields, but you then want to only get the identity fields as a list. (You can only specify
* purposes that were also passed to the constructor i.e. it can only be used to restrict the
* list, not add to it.)
*
* @param array $limitpurposes If specified, gets fields only for these purposes
* @return string[] Array of required fields
* @throws \coding_exception If any unknown purpose is listed
*/
public function get_required_fields(array $limitpurposes = []): array {
// The first time this is called, actually work out the list. There is no way to 'un-cache'
// it, but these objects are designed to be short-lived so it doesn't need one.
if ($this->fields === null) {
// Add all the fields as array keys so that there are no duplicates.
$this->fields = [];
if ($this->purposes[self::PURPOSE_IDENTITY]) {
foreach (self::get_identity_fields($this->context, $this->allowcustom) as $field) {
$this->fields[$field] = [self::PURPOSE_IDENTITY => true];
}
}
if ($this->purposes[self::PURPOSE_USERPIC]) {
foreach (self::get_picture_fields() as $field) {
if (!array_key_exists($field, $this->fields)) {
$this->fields[$field] = [];
}
$this->fields[$field][self::PURPOSE_USERPIC] = true;
}
}
if ($this->purposes[self::PURPOSE_NAME]) {
foreach (self::get_name_fields() as $field) {
if (!array_key_exists($field, $this->fields)) {
$this->fields[$field] = [];
}
$this->fields[$field][self::PURPOSE_NAME] = true;
}
}
foreach ($this->include as $field) {
if ($this->allowcustom || !preg_match(self::PROFILE_FIELD_REGEX, $field)) {
if (!array_key_exists($field, $this->fields)) {
$this->fields[$field] = [];
}
$this->fields[$field][self::CUSTOM_INCLUDE] = true;
}
}
foreach ($this->exclude as $field) {
unset($this->fields[$field]);
}
// If the id field is included, make sure it's first in the list.
if (array_key_exists('id', $this->fields)) {
$newfields = ['id' => $this->fields['id']];
foreach ($this->fields as $field => $purposes) {
if ($field !== 'id') {
$newfields[$field] = $purposes;
}
}
$this->fields = $newfields;
}
}
if ($limitpurposes) {
// Check the value was legitimate.
foreach ($limitpurposes as $purpose) {
if ($purpose != self::CUSTOM_INCLUDE && empty($this->purposes[$purpose])) {
throw new \coding_exception('$limitpurposes can only include purposes defined in object');
}
}
// Filter the fields to include only those matching the purposes.
$result = [];
foreach ($this->fields as $key => $purposes) {
foreach ($limitpurposes as $purpose) {
if (array_key_exists($purpose, $purposes)) {
$result[] = $key;
break;
}
}
}
return $result;
} else {
return array_keys($this->fields);
}
}
/**
* Gets fields required for user pictures.
*
* The results include only basic field names (columns from the 'user' database table).
*
* @return string[] All fields required for user pictures
*/
public static function get_picture_fields(): array {
return ['id', 'picture', 'firstname', 'lastname', 'firstnamephonetic', 'lastnamephonetic',
'middlename', 'alternatename', 'imagealt', 'email'];
}
/**
* Gets fields required for user names.
*
* The results include only basic field names (columns from the 'user' database table).
*
* Fields are usually returned in a specific order, which the fullname() function depends on.
* If you specify 'true' to the $strangeorder flag, then the firstname and lastname fields
* are moved to the front; this is useful in a few places in existing code. New code should
* avoid requiring a particular order.
*
* @param bool $differentorder In a few places, a different order of fields is required
* @return string[] All fields used to display user names
*/
public static function get_name_fields(bool $differentorder = false): array {
$fields = ['firstnamephonetic', 'lastnamephonetic', 'middlename', 'alternatename',
'firstname', 'lastname'];
if ($differentorder) {
return array_merge(array_slice($fields, -2), array_slice($fields, 0, -2));
} else {
return $fields;
}
}
/**
* Gets all fields required for user identity. These fields should be included in tables
* showing lists of users (in addition to the user's name which is included as standard).
*
* The results include basic field names (columns from the 'user' database table) and, unless
* turned off, custom profile field names in the format 'profile_field_myfield', note these
* fields will always be returned lower cased to match how they are returned by the DML library.
*
* This function does all the required capability checks to see if the current user is allowed
* to see them in the specified context. You can pass context null to get all the fields
* without checking permissions.
*
* @param \context|null $context Context; if not supplied, all fields will be included without checks
* @param bool $allowcustom If true, custom profile fields will be included
* @return string[] Array of required fields
* @throws \coding_exception
*/
public static function get_identity_fields(?\context $context, bool $allowcustom = true): array {
global $CFG;
// Only users with permission get the extra fields.
if ($context && !has_capability('moodle/site:viewuseridentity', $context)) {
return [];
}
// Split showuseridentity on comma (filter needed in case the showuseridentity is empty).
$extra = array_filter(explode(',', $CFG->showuseridentity));
// If there are any custom fields, remove them if necessary (either if allowcustom is false,
// or if the user doesn't have access to see them).
foreach ($extra as $key => $field) {
if (preg_match(self::PROFILE_FIELD_REGEX, $field, $matches)) {
$allowed = false;
if ($allowcustom) {
require_once($CFG->dirroot . '/user/profile/lib.php');
// Ensure the field exists (it may have been deleted since user identity was configured).
$field = profile_get_custom_field_data_by_shortname($matches[1], false);
if ($field !== null) {
$fieldinstance = profile_get_user_field($field->datatype, $field->id, 0, $field);
$allowed = $fieldinstance->is_visible($context);
}
}
if (!$allowed) {
unset($extra[$key]);
}
}
}
// For standard user fields, access is controlled by the hiddenuserfields option and
// some different capabilities. Check and remove these if the user can't access them.
$hiddenfields = array_filter(explode(',', $CFG->hiddenuserfields));
$hiddenidentifiers = array_intersect($extra, $hiddenfields);
if ($hiddenidentifiers) {
if (!$context) {
$canviewhiddenuserfields = true;
} else if ($context->get_course_context(false)) {
// We are somewhere inside a course.
$canviewhiddenuserfields = has_capability('moodle/course:viewhiddenuserfields', $context);
} else {
// We are not inside a course.
$canviewhiddenuserfields = has_capability('moodle/user:viewhiddendetails', $context);
}
if (!$canviewhiddenuserfields) {
// Remove hidden identifiers from the list.
$extra = array_diff($extra, $hiddenidentifiers);
}
}
// Re-index the entries and return.
$extra = array_values($extra);
return array_map([core_text::class, 'strtolower'], $extra);
}
/**
* Gets SQL that can be used in a query to get the necessary fields.
*
* The result of this function is an object with fields 'selects', 'joins', 'params', and
* 'mappings'.
*
* If not empty, the list of selects will begin with a comma and the list of joins will begin
* and end with a space. You can include the result in your existing query like this:
*
* SELECT (your existing fields)
* $selects
* FROM {user} u
* JOIN (your existing joins)
* $joins
*
* When there are no custom fields then the 'joins' result will always be an empty string, and
* 'params' will be an empty array.
*
* The $fieldmappings value is often not needed. It is an associative array from each field
* name to an SQL expression for the value of that field, e.g.:
* 'profile_field_frog' => 'uf1d_3.data'
* 'city' => 'u.city'
* This is helpful if you want to use the profile fields in a WHERE clause, becuase you can't
* refer to the aliases used in the SELECT list there.
*
* The leading comma is included because this makes it work in the pattern above even if there
* are no fields from the get_sql() data (which can happen if doing identity fields and none
* are selected). If you want the result without a leading comma, set $leadingcomma to false.
*
* If the 'id' field is included then it will always be first in the list. Otherwise, you
* should not rely on the field order.
*
* For identity fields, the function does all the required capability checks to see if the
* current user is allowed to see them in the specified context. You can pass context null
* to get all the fields without checking permissions.
*
* If your code for any reason cannot cope with custom fields then you can turn them off.
*
* You can have either named or ? params. If you use named params, they are of the form
* uf1s_2; the first number increments in each call using a static variable in this class and
* the second number refers to the field being queried. A similar pattern is used to make
* join aliases unique.
*
* If your query refers to the user table by an alias e.g. 'u' then specify this in the $alias
* parameter; otherwise it will use {user} (if there are any joins for custom profile fields)
* or simply refer to the field by name only (if there aren't).
*
* If you need to use a prefix on the field names (for example in case they might coincide with
* existing result columns from your query, or if you want a convenient way to split out all
* the user data into a separate object) then you can specify one here. For example, if you
* include name fields and the prefix is 'u_' then the results will include 'u_firstname'.
*
* If you don't want to prefix all the field names but only change the id field name, use
* the $renameid parameter. (When you use this parameter, it takes precedence over any prefix;
* the id field will not be prefixed, while all others will.)
*
* @param string $alias Optional (but recommended) alias for user table in query, e.g. 'u'
* @param bool $namedparams If true, uses named :parameters instead of indexed ? parameters
* @param string $prefix Optional prefix for all field names in result, e.g. 'u_'
* @param string $renameid Renames the 'id' field if specified, e.g. 'userid'
* @param bool $leadingcomma If true the 'selects' list will start with a comma
* @return \stdClass Object with necessary SQL components
*/
public function get_sql(string $alias = '', bool $namedparams = false, string $prefix = '',
string $renameid = '', bool $leadingcomma = true): \stdClass {
global $DB;
$fields = $this->get_required_fields();
$selects = '';
$joins = '';
$params = [];
$mappings = [];
$unique = self::$uniqueidentifier++;
$fieldcount = 0;
if ($alias) {
$usertable = $alias . '.';
} else {
// If there is no alias, we still need to use {user} to identify the table when there
// are joins with other tables. When there are no customfields then there are no joins
// so we can refer to the fields by name alone.
$gotcustomfields = false;
foreach ($fields as $field) {
if (preg_match(self::PROFILE_FIELD_REGEX, $field, $matches)) {
$gotcustomfields = true;
break;
}
}
if ($gotcustomfields) {
$usertable = '{user}.';
} else {
$usertable = '';
}
}
foreach ($fields as $field) {
if (preg_match(self::PROFILE_FIELD_REGEX, $field, $matches)) {
// Custom profile field.
$shortname = $matches[1];
$fieldcount++;
$fieldalias = 'uf' . $unique . 'f_' . $fieldcount;
$dataalias = 'uf' . $unique . 'd_' . $fieldcount;
if ($namedparams) {
$withoutcolon = 'uf' . $unique . 's' . $fieldcount;
$placeholder = ':' . $withoutcolon;
$params[$withoutcolon] = $shortname;
} else {
$placeholder = '?';
$params[] = $shortname;
}
$joins .= " JOIN {user_info_field} $fieldalias ON " .
$DB->sql_equal($fieldalias . '.shortname', $placeholder, false) . "
LEFT JOIN {user_info_data} $dataalias ON $dataalias.fieldid = $fieldalias.id
AND $dataalias.userid = {$usertable}id";
// For Oracle we need to convert the field into a usable format.
$fieldsql = $DB->sql_compare_text($dataalias . '.data', 255);
$selects .= ", $fieldsql AS $prefix$field";
$mappings[$field] = $fieldsql;
} else {
// Standard user table field.
$selects .= ", $usertable$field";
if ($field === 'id' && $renameid && $renameid !== 'id') {
$selects .= " AS $renameid";
} else if ($prefix) {
$selects .= " AS $prefix$field";
}
$mappings[$field] = "$usertable$field";
}
}
// Add a space to the end of the joins list; this means it can be appended directly into
// any existing query without worrying about whether the developer has remembered to add
// whitespace after it.
if ($joins) {
$joins .= ' ';
}
// Optionally remove the leading comma.
if (!$leadingcomma) {
$selects = ltrim($selects, ' ,');
}
return (object)['selects' => $selects, 'joins' => $joins, 'params' => $params,
'mappings' => $mappings];
}
/**
* Similar to {@see \moodle_database::sql_fullname} except it returns all user name fields as defined by site config, in a
* single select statement suitable for inclusion in a query/filter for a users fullname, e.g.
*
* [$select, $params] = fields::get_sql_fullname('u');
* $users = $DB->get_records_sql_menu("SELECT u.id, {$select} FROM {user} u", $params);
*
* @param string|null $tablealias User table alias, if set elsewhere in the query, null if not required
* @param bool $override If true then the alternativefullnameformat format rather than fullnamedisplay format will be used
* @return array SQL select snippet and parameters
*/
public static function get_sql_fullname(?string $tablealias = 'u', bool $override = false): array {
global $DB;
$unique = self::$uniqueidentifier++;
$namefields = self::get_name_fields();
// Create a dummy user object containing all name fields.
$dummyuser = (object) array_combine($namefields, $namefields);
$dummyfullname = fullname($dummyuser, $override);
// Extract any name fields from the fullname format in the order that they appear.
$matchednames = array_values(order_in_string($namefields, $dummyfullname));
$namelookup = $namepattern = $elements = $params = [];
foreach ($namefields as $index => $namefield) {
$namefieldwithalias = $tablealias ? "{$tablealias}.{$namefield}" : $namefield;
// Coalesce the name fields to ensure we don't return null.
$emptyparam = "uf{$unique}ep_{$index}";
$namelookup[$namefield] = "COALESCE({$namefieldwithalias}, :{$emptyparam})";
$params[$emptyparam] = '';
$namepattern[] = '\b' . preg_quote($namefield) . '\b';
}
// Grab any content between the name fields, inserting them after each name field.
$chunks = preg_split('/(' . implode('|', $namepattern) . ')/', $dummyfullname);
foreach ($chunks as $index => $chunk) {
if ($index > 0) {
$elements[] = $namelookup[$matchednames[$index - 1]];
}
if (core_text::strlen($chunk) > 0) {
// If content is just whitespace, add to elements directly (also Oracle doesn't support passing ' ' as param).
if (preg_match('/^\s+$/', $chunk)) {
$elements[] = "'$chunk'";
} else {
$elementparam = "uf{$unique}fp_{$index}";
$elements[] = ":{$elementparam}";
$params[$elementparam] = $chunk;
}
}
}
return [$DB->sql_concat(...$elements), $params];
}
/**
* Gets the display name of a given user field.
*
* Supports field names from the 'user' database table, and custom profile fields supplied in
* the format 'profile_field_xx'.
*
* @param string $field Field name in database
* @return string Field name for display to user
* @throws \coding_exception
*/
public static function get_display_name(string $field): string {
global $CFG;
// Custom fields have special handling.
if (preg_match(self::PROFILE_FIELD_REGEX, $field, $matches)) {
require_once($CFG->dirroot . '/user/profile/lib.php');
$fieldinfo = profile_get_custom_field_data_by_shortname($matches[1], false);
// Use format_string so it can be translated with multilang filter if necessary.
return $fieldinfo ? format_string($fieldinfo->name) : $field;
}
// Some fields have language strings which are not the same as field name.
switch ($field) {
case 'picture' : {
return get_string('pictureofuser');
}
}
// Otherwise just use the same lang string.
return get_string($field);
}
/**
* Resets the unique identifier used to ensure that multiple SQL fragments generated in the
* same request will have different identifiers for parameters and table aliases.
*
* This is intended only for use in unit testing.
*/
public static function reset_unique_identifier() {
self::$uniqueidentifier = 1;
}
/**
* Checks if a field name looks like a custom profile field i.e. it begins with profile_field_
* (does not check if that profile field actually exists).
*
* @param string $fieldname Field name
* @return string Empty string if not a profile field, or profile field name (without profile_field_)
*/
public static function match_custom_field(string $fieldname): string {
if (preg_match(self::PROFILE_FIELD_REGEX, $fieldname, $matches)) {
return $matches[1];
} else {
return '';
}
}
}
+157
View File
@@ -0,0 +1,157 @@
<?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/>.
/**
* Form to edit a users preferred language
*
* @copyright 2015 Shamim Rezaie http://foodle.org
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package core_user
*/
namespace core_user\form;
if (!defined('MOODLE_INTERNAL')) {
die('Direct access to this script is forbidden.'); // It must be included from a Moodle page.
}
require_once($CFG->dirroot.'/lib/formslib.php');
/**
* Class user_edit_calendar_form.
*
* @copyright 2015 Shamim Rezaie http://foodle.org
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class calendar_form extends \moodleform {
/**
* Define the form.
*/
public function definition() {
global $CFG, $USER;
$mform = $this->_form;
$userid = $USER->id;
if (is_array($this->_customdata)) {
if (array_key_exists('userid', $this->_customdata)) {
$userid = $this->_customdata['userid'];
}
}
// Add some extra hidden fields.
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
// We do not want to show this option unless there is more than one calendar type to display.
if (count(\core_calendar\type_factory::get_list_of_calendar_types()) > 1) {
$calendartypes = \core_calendar\type_factory::get_list_of_calendar_types();
$mform->addElement('select', 'calendartype', get_string('preferredcalendar', 'calendar'), $calendartypes);
$mform->setType('calendartype', PARAM_ALPHANUM);
$mform->setDefault('calendartype', $CFG->calendartype);
} else {
$mform->addElement('hidden', 'calendartype', $CFG->calendartype);
$mform->setType('calendartype', PARAM_ALPHANUM);
}
// Date / Time settings.
$options = array(
'0' => get_string('default', 'calendar'),
CALENDAR_TF_12 => get_string('timeformat_12', 'calendar'),
CALENDAR_TF_24 => get_string('timeformat_24', 'calendar')
);
$mform->addElement('select', 'timeformat', get_string('pref_timeformat', 'calendar'), $options);
$mform->addHelpButton('timeformat', 'pref_timeformat', 'calendar');
// First day of week.
$options = array(
0 => get_string('sunday', 'calendar'),
1 => get_string('monday', 'calendar'),
2 => get_string('tuesday', 'calendar'),
3 => get_string('wednesday', 'calendar'),
4 => get_string('thursday', 'calendar'),
5 => get_string('friday', 'calendar'),
6 => get_string('saturday', 'calendar')
);
$mform->addElement('select', 'startwday', get_string('pref_startwday', 'calendar'), $options);
$mform->addHelpButton('startwday', 'pref_startwday', 'calendar');
// Maximum events to display.
$options = array();
for ($i = 1; $i <= 20; $i++) {
$options[$i] = $i;
}
$mform->addElement('select', 'maxevents', get_string('pref_maxevents', 'calendar'), $options);
$mform->addHelpButton('maxevents', 'pref_maxevents', 'calendar');
// Calendar lookahead.
$options = array(365 => new \lang_string('numyear', '', 1),
270 => get_string('nummonths', '', 9),
180 => get_string('nummonths', '', 6),
150 => get_string('nummonths', '', 5),
120 => get_string('nummonths', '', 4),
90 => get_string('nummonths', '', 3),
60 => get_string('nummonths', '', 2),
30 => get_string('nummonth', '', 1),
21 => get_string('numweeks', '', 3),
14 => get_string('numweeks', '', 2),
7 => get_string('numweek', '', 1),
6 => get_string('numdays', '', 6),
5 => get_string('numdays', '', 5),
4 => get_string('numdays', '', 4),
3 => get_string('numdays', '', 3),
2 => get_string('numdays', '', 2),
1 => get_string('numday', '', 1));
$mform->addElement('select', 'lookahead', get_string('pref_lookahead', 'calendar'), $options);
$mform->addHelpButton('lookahead', 'pref_lookahead', 'calendar');
// Remember event filtering.
$options = array(
0 => get_string('no'),
1 => get_string('yes')
);
$mform->addElement('select', 'persistflt', get_string('pref_persistflt', 'calendar'), $options);
$mform->addHelpButton('persistflt', 'pref_persistflt', 'calendar');
$this->add_action_buttons(true, get_string('savechanges'));
}
/**
* Extend the form definition after the data has been parsed.
*/
public function definition_after_data() {
global $CFG;
$mform = $this->_form;
// If calendar type does not exist, use site default calendar type.
if ($calendarselected = $mform->getElementValue('calendartype')) {
if (is_array($calendarselected)) {
// There are multiple calendar types available.
$calendar = reset($calendarselected);
} else {
// There is only one calendar type available.
$calendar = $calendarselected;
}
// Check calendar type exists.
if (!array_key_exists($calendar, \core_calendar\type_factory::get_list_of_calendar_types())) {
$calendartypeel = $mform->getElement('calendartype');
$calendartypeel->setValue($CFG->calendartype);
}
}
}
}
@@ -0,0 +1,109 @@
<?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/>.
namespace core_user\form;
defined('MOODLE_INTERNAL') || die;
require_once($CFG->dirroot.'/lib/formslib.php');
/**
* Contact site support form.
*
* @package core_user
* @copyright 2022 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class contactsitesupport_form extends \moodleform {
/**
* Define the contact site support form.
*/
public function definition(): void {
global $CFG;
$mform = $this->_form;
$user = $this->_customdata;
$strrequired = get_string('required');
// Name.
$mform->addElement('text', 'name', get_string('name'));
$mform->addRule('name', $strrequired, 'required', null, 'client');
$mform->setType('name', PARAM_TEXT);
// Email.
$mform->addElement('text', 'email', get_string('email'));
$mform->addRule('email', get_string('missingemail'), 'required', null, 'client');
$mform->setType('email', PARAM_EMAIL);
// Subject.
$mform->addElement('text', 'subject', get_string('subject'));
$mform->addRule('subject', $strrequired, 'required', null, 'client');
$mform->setType('subject', PARAM_TEXT);
// Message.
$mform->addElement('textarea', 'message', get_string('message'));
$mform->addRule('message', $strrequired, 'required', null, 'client');
$mform->setType('message', PARAM_TEXT);
// If the user is logged in set name and email fields to the current user info.
if (isloggedin() && !isguestuser()) {
$mform->setDefault('name', fullname($user));
$mform->hardFreeze('name');
$mform->setDefault('email', $user->email);
$mform->hardFreeze('email');
}
if (!empty($CFG->recaptchapublickey) && !empty($CFG->recaptchaprivatekey)) {
$mform->addElement('recaptcha', 'recaptcha_element', get_string('security_question', 'auth'));
$mform->addHelpButton('recaptcha_element', 'recaptcha', 'auth');
$mform->closeHeaderBefore('recaptcha_element');
}
$this->add_action_buttons(true, get_string('submit'));
}
/**
* Validate user supplied data on the contact site support form.
*
* @param array $data array of ("fieldname"=>value) of submitted data
* @param array $files array of uploaded files "element_name"=>tmp_file_path
* @return array of "element_name"=>"error_description" if there are errors,
* or an empty array if everything is OK (true allowed for backwards compatibility too).
*/
public function validation($data, $files): array {
$errors = parent::validation($data, $files);
if (!validate_email($data['email'])) {
$errors['email'] = get_string('invalidemail');
}
if ($this->_form->elementExists('recaptcha_element')) {
$recaptchaelement = $this->_form->getElement('recaptcha_element');
if (!empty($this->_form->_submitValues['g-recaptcha-response'])) {
$response = $this->_form->_submitValues['g-recaptcha-response'];
if (!$recaptchaelement->verify($response)) {
$errors['recaptcha_element'] = get_string('incorrectpleasetryagain', 'auth');
}
} else {
$errors['recaptcha_element'] = get_string('missingrecaptchachallengefield');
}
}
return $errors;
}
}
@@ -0,0 +1,53 @@
<?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/>.
namespace core_user\form;
use \core_contentbank\content;
defined('MOODLE_INTERNAL') || die;
require_once($CFG->dirroot.'/lib/formslib.php');
/**
* Form to edit a user's preferences concerning the content bank.
*
* @package core_user
* @copyright 2020 François Moreau
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class contentbank_user_preferences_form extends \moodleform {
/**
* Define the form.
*/
public function definition() {
global $CFG, $USER;
$mform = $this->_form;
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
$options = [
content::VISIBILITY_PUBLIC => get_string('visibilitychoicepublic', 'core_contentbank'),
content::VISIBILITY_UNLISTED => get_string('visibilitychoiceunlisted', 'core_contentbank')
];
$mform->addElement('select', 'contentvisibility', get_string('visibilitypref', 'core_contentbank'), $options);
$mform->addHelpButton('contentvisibility', 'visibilitypref', 'core_contentbank');
$this->add_action_buttons(true, get_string('savechanges'));
}
}
@@ -0,0 +1,64 @@
<?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/>.
/**
* Form to allow user to set their default home page
*
* @package core_user
* @copyright 2019 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_user\form;
use lang_string;
defined('MOODLE_INTERNAL') || die;
require_once($CFG->dirroot . '/lib/formslib.php');
/**
* Form class
*
* @copyright 2019 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class defaulthomepage_form extends \moodleform {
/**
* Define the form.
*/
public function definition() {
global $CFG;
$mform = $this->_form;
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
$options = [HOMEPAGE_SITE => new lang_string('home')];
if (!empty($CFG->enabledashboard)) {
$options[HOMEPAGE_MY] = new lang_string('mymoodle', 'admin');
}
$options[HOMEPAGE_MYCOURSES] = new lang_string('mycourses', 'admin');
$mform->addElement('select', 'defaulthomepage', get_string('defaulthomepageuser'), $options);
$mform->addHelpButton('defaulthomepage', 'defaulthomepageuser');
$mform->setDefault('defaulthomepage', get_default_home_page());
$this->add_action_buttons(true, get_string('savechanges'));
}
}
+192
View File
@@ -0,0 +1,192 @@
<?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/>.
namespace core_user\form;
use html_writer;
use moodle_url;
/**
* Manage user private area files form
*
* @package core_user
* @copyright 2010 Petr Skoda (http://skodak.org)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class private_files extends \core_form\dynamic_form {
/**
* Add elements to this form.
*/
public function definition() {
global $OUTPUT;
$mform = $this->_form;
$options = $this->get_options();
// Show file area space usage.
$maxareabytes = $options['areamaxbytes'];
if ($maxareabytes != FILE_AREA_MAX_BYTES_UNLIMITED) {
$fileareainfo = file_get_file_area_info($this->get_context_for_dynamic_submission()->id, 'user', 'private');
// Display message only if we have files.
if ($fileareainfo['filecount']) {
$a = (object) [
'used' => display_size($fileareainfo['filesize_without_references']),
'total' => display_size($maxareabytes, 0)
];
$quotamsg = get_string('quotausage', 'moodle', $a);
$notification = new \core\output\notification($quotamsg, \core\output\notification::NOTIFY_INFO);
$mform->addElement('static', 'areabytes', '', $OUTPUT->render($notification));
}
}
$mform->addElement('filemanager', 'files_filemanager', get_string('files'), null, $options);
if ($link = $this->get_emaillink()) {
$emaillink = html_writer::link(new moodle_url('mailto:' . $link), $link);
$mform->addElement('static', 'emailaddress', '',
get_string('emailtoprivatefiles', 'moodle', $emaillink));
}
$mform->setType('returnurl', PARAM_LOCALURL);
// The 'nosubmit' param (default false) determines whether we should show the standard form action buttons (save/cancel).
// This value is set when the form is displayed within a modal, which adds the action buttons itself.
if (!$this->optional_param('nosubmit', false, PARAM_BOOL)) {
$this->add_action_buttons();
}
}
/**
* Validate incoming data.
*
* @param array $data
* @param array $files
* @return array
*/
public function validation($data, $files) {
$errors = array();
$draftitemid = $data['files_filemanager'];
$options = $this->get_options();
if (file_is_draft_area_limit_reached($draftitemid, $options['areamaxbytes'])) {
$errors['files_filemanager'] = get_string('userquotalimit', 'error');
}
return $errors;
}
/**
* Link to email private files
*
* @return string|null
* @throws \coding_exception
*/
protected function get_emaillink() {
global $USER;
// Attempt to generate an inbound message address to support e-mail to private files.
$generator = new \core\message\inbound\address_manager();
$generator->set_handler('\core\message\inbound\private_files_handler');
$generator->set_data(-1);
return $generator->generate($USER->id);
}
/**
* Check if current user has access to this form, otherwise throw exception
*
* Sometimes permission check may depend on the action and/or id of the entity.
* If necessary, form data is available in $this->_ajaxformdata or
* by calling $this->optional_param()
*/
protected function check_access_for_dynamic_submission(): void {
require_capability('moodle/user:manageownfiles', $this->get_context_for_dynamic_submission());
}
/**
* Returns form context
*
* If context depends on the form data, it is available in $this->_ajaxformdata or
* by calling $this->optional_param()
*
* @return \context
*/
protected function get_context_for_dynamic_submission(): \context {
global $USER;
return \context_user::instance($USER->id);
}
/**
* File upload options
*
* @return array
* @throws \coding_exception
*/
protected function get_options(): array {
global $CFG;
$maxbytes = $CFG->userquota;
$maxareabytes = $CFG->userquota;
if (has_capability('moodle/user:ignoreuserquota', $this->get_context_for_dynamic_submission())) {
$maxbytes = USER_CAN_IGNORE_FILE_SIZE_LIMITS;
$maxareabytes = FILE_AREA_MAX_BYTES_UNLIMITED;
}
return ['subdirs' => 1, 'maxbytes' => $maxbytes, 'maxfiles' => -1, 'accepted_types' => '*',
'areamaxbytes' => $maxareabytes];
}
/**
* Process the form submission, used if form was submitted via AJAX
*
* This method can return scalar values or arrays that can be json-encoded, they will be passed to the caller JS.
*
* Submission data can be accessed as: $this->get_data()
*
* @return mixed
*/
public function process_dynamic_submission() {
file_postupdate_standard_filemanager($this->get_data(), 'files',
$this->get_options(), $this->get_context_for_dynamic_submission(), 'user', 'private', 0);
return null;
}
/**
* Load in existing data as form defaults
*
* Can be overridden to retrieve existing values from db by entity id and also
* to preprocess editor and filemanager elements
*
* Example:
* $this->set_data(get_entity($this->_ajaxformdata['id']));
*/
public function set_data_for_dynamic_submission(): void {
$data = new \stdClass();
file_prepare_standard_filemanager($data, 'files', $this->get_options(),
$this->get_context_for_dynamic_submission(), 'user', 'private', 0);
$this->set_data($data);
}
/**
* Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX
*
* This is used in the form elements sensitive to the page url, such as Atto autosave in 'editor'
*
* If the form has arguments (such as 'id' of the element being edited), the URL should
* also have respective argument.
*
* @return \moodle_url
*/
protected function get_page_url_for_dynamic_submission(): \moodle_url {
return new moodle_url('/user/files.php');
}
}
+125
View File
@@ -0,0 +1,125 @@
<?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/>.
namespace core_user\form;
use context;
use core_form\dynamic_form;
use moodle_url;
/**
* Modal form to edit profile category
*
* @package core_user
* @copyright 2021 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class profile_category_form extends dynamic_form {
/**
* Form definition
*/
protected function definition() {
$mform = $this->_form;
$strrequired = get_string('required');
// Add some extra hidden fields.
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
$mform->addElement('hidden', 'action', 'editcategory');
$mform->setType('action', PARAM_ALPHANUMEXT);
$mform->addElement('text', 'name', get_string('profilecategoryname', 'admin'), 'maxlength="255" size="30"');
$mform->setType('name', PARAM_TEXT);
$mform->addRule('name', $strrequired, 'required', null, 'client');
}
/**
* Perform some moodle validation.
*
* @param array $data
* @param array $files
* @return array
*/
public function validation($data, $files) {
global $DB;
$errors = parent::validation($data, $files);
$duplicate = $DB->get_field('user_info_category', 'id', ['name' => $data['name']]);
// Check the name is unique.
if (!empty($data['id'])) { // We are editing an existing record.
$olddata = $DB->get_record('user_info_category', ['id' => $data['id']]);
// Name has changed, new name in use, new name in use by another record.
$dupfound = (($olddata->name !== $data['name']) && $duplicate && ($data['id'] != $duplicate));
} else { // New profile category.
$dupfound = $duplicate;
}
if ($dupfound ) {
$errors['name'] = get_string('profilecategorynamenotunique', 'admin');
}
return $errors;
}
/**
* Returns context where this form is used
*
* @return context
*/
protected function get_context_for_dynamic_submission(): context {
return \context_system::instance();
}
/**
* Checks if current user has access to this form, otherwise throws exception
*/
protected function check_access_for_dynamic_submission(): void {
require_capability('moodle/site:config', $this->get_context_for_dynamic_submission());
}
/**
* Process the form submission, used if form was submitted via AJAX
*/
public function process_dynamic_submission() {
global $CFG;
require_once($CFG->dirroot.'/user/profile/definelib.php');
profile_save_category($this->get_data());
}
/**
* Load in existing data as form defaults
*/
public function set_data_for_dynamic_submission(): void {
global $DB;
if ($id = $this->optional_param('id', 0, PARAM_INT)) {
$this->set_data($DB->get_record('user_info_category', ['id' => $id], '*', MUST_EXIST));
}
}
/**
* Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX
*
* @return moodle_url
*/
protected function get_page_url_for_dynamic_submission(): moodle_url {
$id = $this->optional_param('id', 0, PARAM_INT);
return new moodle_url('/user/profile/index.php',
['action' => 'editcategory', 'id' => $id]);
}
}
+183
View File
@@ -0,0 +1,183 @@
<?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/>.
namespace core_user\form;
use context;
use core_form\dynamic_form;
use moodle_url;
use profile_define_base;
/**
* Class field_form used for profile fields.
*
* @package core_user
* @copyright 2007 onwards Shane Elliot {@link http://pukunui.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class profile_field_form extends dynamic_form {
/** @var profile_define_base $field */
public $field;
/** @var \stdClass */
protected $fieldrecord;
/**
* Define the form
*/
public function definition() {
global $CFG;
require_once($CFG->dirroot.'/user/profile/definelib.php');
$mform = $this->_form;
// Everything else is dependant on the data type.
$datatype = $this->get_field_record()->datatype;
require_once($CFG->dirroot.'/user/profile/field/'.$datatype.'/define.class.php');
$newfield = 'profile_define_'.$datatype;
$this->field = new $newfield();
// Add some extra hidden fields.
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
$mform->addElement('hidden', 'action', 'editfield');
$mform->setType('action', PARAM_ALPHANUMEXT);
$mform->addElement('hidden', 'datatype', $datatype);
$mform->setType('datatype', PARAM_ALPHA);
$this->field->define_form($mform);
}
/**
* Alter definition based on existing or submitted data
*/
public function definition_after_data() {
$mform = $this->_form;
$this->field->define_after_data($mform);
}
/**
* Perform some moodle validation.
* @param array $data
* @param array $files
* @return array
*/
public function validation($data, $files) {
return $this->field->define_validate($data, $files);
}
/**
* Returns the defined editors for the field.
* @return array
*/
public function editors(): array {
$editors = $this->field->define_editors();
return is_array($editors) ? $editors : [];
}
/**
* Returns context where this form is used
*
* @return context
*/
protected function get_context_for_dynamic_submission(): context {
return \context_system::instance();
}
/**
* Checks if current user has access to this form, otherwise throws exception
*/
protected function check_access_for_dynamic_submission(): void {
require_capability('moodle/site:config', $this->get_context_for_dynamic_submission());
}
/**
* Process the form submission, used if form was submitted via AJAX
*/
public function process_dynamic_submission() {
global $CFG;
require_once($CFG->dirroot.'/user/profile/definelib.php');
profile_save_field($this->get_data(), $this->editors());
}
/**
* Load in existing data as form defaults
*/
public function set_data_for_dynamic_submission(): void {
$field = $this->get_field_record();
// Clean and prepare description for the editor.
$description = clean_text($field->description, $field->descriptionformat);
$field->description = ['text' => $description, 'format' => $field->descriptionformat, 'itemid' => 0];
// Convert the data format for.
if (is_array($this->editors())) {
foreach ($this->editors() as $editor) {
if (isset($field->$editor)) {
$editordesc = clean_text($field->$editor, $field->{$editor.'format'});
$field->$editor = ['text' => $editordesc, 'format' => $field->{$editor.'format'}, 'itemid' => 0];
}
}
}
$this->set_data($field);
}
/**
* Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX
*
* @return moodle_url
*/
protected function get_page_url_for_dynamic_submission(): moodle_url {
$id = $this->optional_param('id', 0, PARAM_INT);
$datatype = $this->optional_param('datatype', 'text', PARAM_PLUGIN);
return new moodle_url('/user/profile/index.php',
['action' => 'editfield', 'id' => $id, 'datatype' => $id ? null : $datatype]);
}
/**
* Record for the field from the database (or generic record for a new field)
*
* @return false|mixed|\stdClass
* @throws \coding_exception
* @throws \dml_exception
*/
public function get_field_record() {
global $DB;
if (!$this->fieldrecord) {
$id = $this->optional_param('id', 0, PARAM_INT);
if (!$id || !($this->fieldrecord = $DB->get_record('user_info_field', ['id' => $id]))) {
$datatype = $this->optional_param('datatype', 'text', PARAM_PLUGIN);
$this->fieldrecord = new \stdClass();
$this->fieldrecord->datatype = $datatype;
$this->fieldrecord->description = '';
$this->fieldrecord->descriptionformat = FORMAT_HTML;
$this->fieldrecord->defaultdata = '';
$this->fieldrecord->defaultdataformat = FORMAT_HTML;
$this->fieldrecord->categoryid = $this->optional_param('categoryid', 0, PARAM_INT);
}
if (!\core_component::get_component_directory('profilefield_'.$this->fieldrecord->datatype)) {
throw new \moodle_exception('fieldnotfound', 'customfield');
}
}
return $this->fieldrecord;
}
}
@@ -0,0 +1,34 @@
<?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/>.
namespace core_user\hook;
use core\hook\stoppable_trait;
/**
* Allow plugins to callback as soon possible after user has completed login.
*
* @package core_user
* @copyright 2024 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
#[\core\attribute\label('Allow plugins to callback as soon possible after user has completed login.')]
#[\core\attribute\tags('user', 'login')]
class after_login_completed implements
\Psr\EventDispatcher\StoppableEventInterface
{
use stoppable_trait;
}
+46
View File
@@ -0,0 +1,46 @@
<?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/>.
namespace core_user\hook;
use stdClass;
use Psr\EventDispatcher\StoppableEventInterface;
/**
* Hook before user deletion.
*
* @package core_user
* @copyright 2024 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
#[\core\attribute\label('Allows plugins or features to perform actions before a user is deleted.')]
#[\core\attribute\tags('user')]
class before_user_deleted implements
StoppableEventInterface
{
use \core\hook\stoppable_trait;
/**
* Constructor for the hook.
*
* @param stdClass $user The user instance
*/
public function __construct(
/** @var stdClass The user instance */
public readonly stdClass $user,
) {
}
}
+44
View File
@@ -0,0 +1,44 @@
<?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/>.
namespace core_user\hook;
use stdClass;
/**
* Hook before user information and data updates.
*
* @package core_user
* @copyright 2024 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
#[\core\attribute\label('Allows plugins or features to perform actions before a user is updated.')]
#[\core\attribute\tags('user')]
class before_user_updated {
/**
* Constructor for the hook.
*
* @param stdClass $user The user instance
* @param stdClass $currentuserdata The old user instance
*/
public function __construct(
/** @var stdClass The user instance */
public readonly stdClass $user,
/** @var stdClass The old user instance */
public readonly stdClass $currentuserdata,
) {
}
}
@@ -0,0 +1,120 @@
<?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/>.
namespace core_user\hook;
use action_link;
use core\hook\described_hook;
use core\hook\deprecated_callback_replacement;
/**
* Class extend_bulk_user_actions
*
* @package core_user
* @copyright 2024 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class extend_bulk_user_actions implements deprecated_callback_replacement, described_hook {
/**
* Describes the hook purpose.
*
* @return string
*/
public static function get_hook_description(): string {
return 'Extend bulk user actions menu';
}
/**
* List of tags that describe this hook.
*
* @return string[]
*/
public static function get_hook_tags(): array {
return ['user'];
}
/**
* Returns list of lib.php plugin callbacks that were deprecated by the hook.
*
* @return array
*/
public static function get_deprecated_plugin_callbacks(): array {
return ['bulk_user_actions'];
}
/** @var array Stores all added user actions */
private $actions = [];
/**
* To be called by callback to add an action to the bulk user actions menu
*
* Callbacks with higher priority will be called first and actions they added will be displayed first.
* Callbacks with lower priority can override actions added by callbacks with higher priority.
*
* To prevent accidental overrides plugins should prefix the action identifier with the plugin name.
*
* @param string $identifier Unique key for the action, recommended to prefix with plugin name
* @param action_link|null $action an object containing the action URL and text,
* other properties are ignored. Can be set to null to remove an action added by somebody else.
* @param ?string $category Label for the option group in the action select dropdown
*/
public function add_action(string $identifier, ?action_link $action, ?string $category = null): void {
$category = $category ?? get_string('actions', 'moodle');
// If an action with the same identifier already exists in another option group, remove it.
$oldcategory = $this->find_action_category($identifier);
if ($oldcategory !== null && ($oldcategory !== $category || $action === null)) {
unset($this->actions[$oldcategory][$identifier]);
if (empty($this->actions[$oldcategory])) {
unset($this->actions[$oldcategory]);
}
}
// Add the new action.
if ($action !== null) {
$this->actions += [$category => []];
$this->actions[$category][$identifier] = $action;
}
}
/**
* Returns all actions groupped by category
*
* @return action_link[][]
*/
public function get_actions(): array {
return $this->actions;
}
/**
* Allows to locate an action by the identifier
*
* This method returns the option group label. The action itself can be found as:
* $category = $this->find_action_category($identifier);
* $action = $this->get_actions()[$category][$identifier];
*
* @param string $identifier
* @return string|null
*/
public function find_action_category(string $identifier): ?string {
foreach ($this->actions as $category => $actions) {
if (array_key_exists($identifier, $actions)) {
return $category;
}
}
return null;
}
}
+199
View File
@@ -0,0 +1,199 @@
<?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/>.
/**
* Defines a category in my profile page navigation.
*
* @package core_user
* @copyright 2015 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_user\output\myprofile;
defined('MOODLE_INTERNAL') || die();
/**
* Defines a category in my profile page navigation.
*
* @since Moodle 2.9
* @package core_user
* @copyright 2015 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class category implements \renderable {
/**
* @var string Name of the category after which this category should appear.
*/
private $after;
/**
* @var string Name of the category.
*/
private $name;
/**
* @var string Title of the category.
*/
private $title;
/**
* @var node[] Array of nodes associated with this category.
*/
private $nodes = array();
/**
* @var string HTML class attribute for this category. Classes should be separated by a space, e.g. 'class1 class2'
*/
private $classes;
/**
* @var array list of properties publicly accessible via __get.
*/
private $properties = array('after', 'name', 'title', 'nodes', 'classes');
/**
* Constructor for category class.
*
* @param string $name Category name.
* @param string $title category title.
* @param null|string $after Name of category after which this category should appear.
* @param null|string $classes a list of css classes.
*/
public function __construct($name, $title, $after = null, $classes = null) {
$this->after = $after;
$this->name = $name;
$this->title = $title;
$this->classes = $classes;
}
/**
* Add a node to this category.
*
* @param node $node node object.
* @see \core_user\output\myprofile\tree::add_node()
*
* @throws \coding_exception
*/
public function add_node(node $node) {
$name = $node->name;
if (isset($this->nodes[$name])) {
throw new \coding_exception("Node with name $name already exists");
}
if ($node->parentcat !== $this->name) {
throw new \coding_exception("Node parent must match with the category it is added to");
}
$this->nodes[$node->name] = $node;
}
/**
* Sort nodes of the category in the order in which they should be displayed.
*
* @see \core_user\output\myprofile\tree::sort_categories()
* @throws \coding_exception
*/
public function sort_nodes() {
$tempnodes = array();
$this->validate_after_order();
// First content noes.
foreach ($this->nodes as $node) {
$after = $node->after;
$content = $node->content;
if (($after == null && !empty($content)) || $node->name === 'editprofile') {
// Can go anywhere in the cat. Also show content nodes first.
$tempnodes = array_merge($tempnodes, array($node->name => $node), $this->find_nodes_after($node));
}
}
// Now nodes with no content.
foreach ($this->nodes as $node) {
$after = $node->after;
$content = $node->content;
if ($after == null && empty($content)) {
// Can go anywhere in the cat. Also show content nodes first.
$tempnodes = array_merge($tempnodes, array($node->name => $node), $this->find_nodes_after($node));
}
}
if (count($tempnodes) !== count($this->nodes)) {
// Orphan nodes found.
throw new \coding_exception('Some of the nodes specified contains invalid \'after\' property');
}
$this->nodes = $tempnodes;
}
/**
* Verifies that node with content can come after node with content only . Also verifies the same thing for nodes without
* content.
* @throws \coding_exception
*/
protected function validate_after_order() {
$nodearray = $this->nodes;
foreach ($this->nodes as $node) {
$after = $node->after;
if (!empty($after)) {
if (empty($nodearray[$after])) {
throw new \coding_exception('node {$node->name} specified contains invalid \'after\' property');
} else {
// Valid node found.
$afternode = $nodearray[$after];
$beforecontent = $node->content;
$aftercontent = $afternode->content;
if ((empty($beforecontent) && !empty($aftercontent)) || (!empty($beforecontent) && empty($aftercontent))) {
// Only node with content are allowed after content nodes. Same goes for no content nodes.
throw new \coding_exception('node {$node->name} specified contains invalid \'after\' property');
}
}
}
}
}
/**
* Given a node object find all node objects that should appear after it.
*
* @param node $node node object
*
* @return array
*/
protected function find_nodes_after($node) {
$return = array();
$nodearray = $this->nodes;
foreach ($nodearray as $nodeelement) {
if ($nodeelement->after === $node->name) {
// Find all nodes that comes after this node as well.
$return = array_merge($return, array($nodeelement->name => $nodeelement), $this->find_nodes_after($nodeelement));
}
}
return $return;
}
/**
* Magic get method.
*
* @param string $prop property to get.
*
* @return mixed
* @throws \coding_exception
*/
public function __get($prop) {
if (in_array($prop, $this->properties)) {
return $this->$prop;
}
throw new \coding_exception('Property "' . $prop . '" doesn\'t exist');
}
}
+82
View File
@@ -0,0 +1,82 @@
<?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/>.
/**
* Defines Manager class for my profile navigation tree.
*
* @package core_user
* @copyright 2015 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_user\output\myprofile;
defined('MOODLE_INTERNAL') || die();
/**
* Defines MAnager class for myprofile navigation tree.
*
* @since Moodle 2.9
* @package core_user
* @copyright 2015 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class manager {
/**
* Parse all callbacks and builds the tree.
*
* @param \stdClass $user user for which the profile is displayed.
* @param bool $iscurrentuser true if the profile being viewed is of current user, else false.
* @param \stdClass $course Course object
*
* @return tree Fully build tree to be rendered on my profile page.
*/
public static function build_tree($user, $iscurrentuser, $course = null) {
global $CFG;
$tree = new tree();
// Add core nodes.
require_once($CFG->libdir . "/myprofilelib.php");
core_myprofile_navigation($tree, $user, $iscurrentuser, $course);
// Core components.
$components = \core_component::get_core_subsystems();
foreach ($components as $component => $directory) {
if (empty($directory)) {
continue;
}
$file = $directory . "/lib.php";
if (is_readable($file)) {
require_once($file);
$function = "core_" . $component . "_myprofile_navigation";
if (function_exists($function)) {
$function($tree, $user, $iscurrentuser, $course);
}
}
}
// Plugins.
$pluginswithfunction = get_plugins_with_function('myprofile_navigation', 'lib.php');
foreach ($pluginswithfunction as $plugins) {
foreach ($plugins as $function) {
$function($tree, $user, $iscurrentuser, $course);
}
}
$tree->sort_categories();
return $tree;
}
}
+120
View File
@@ -0,0 +1,120 @@
<?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/>.
/**
* Defines a node in my profile page navigation.
*
* @package core_user
* @copyright 2015 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_user\output\myprofile;
defined('MOODLE_INTERNAL') || die();
/**
* Defines a node in my profile page navigation.
*
* @since Moodle 2.9
* @package core_user
* @copyright 2015 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class node implements \renderable {
/**
* @var string Name of parent category.
*/
private $parentcat;
/**
* @var string Name of this node.
*/
private $name;
/**
* @var string Name of the node after which this node should appear.
*/
private $after;
/**
* @var string Title of this node.
*/
private $title;
/**
* @var string|\moodle_url Url that this node should link to.
*/
private $url;
/**
* @var string Content to display under this node.
*/
private $content;
/**
* @var string|\pix_icon Icon for this node.
*/
private $icon;
/**
* @var string HTML class attribute for this node. Classes should be separated by a space, e.g. 'class1 class2'
*/
private $classes;
/**
* @var array list of properties accessible via __get.
*/
private $properties = array('parentcat', 'after', 'name', 'title', 'url', 'content', 'icon', 'classes');
/**
* Constructor for the node.
*
* @param string $parentcat Name of parent category.
* @param string $name Name of this node.
* @param string $title Title of this node.
* @param null|string $after Name of the node after which this node should appear.
* @param null|string|\moodle_url $url Url that this node should link to.
* @param null|string $content Content to display under this node.
* @param null|string|\pix_icon $icon Icon for this node.
* @param null|string $classes a list of css classes.
*/
public function __construct($parentcat, $name, $title, $after = null, $url = null, $content = null, $icon = null,
$classes = null) {
$this->parentcat = $parentcat;
$this->after = $after;
$this->name = $name;
$this->title = $title;
$this->url = is_null($url) ? null : new \moodle_url($url);
$this->content = $content;
$this->icon = $icon;
$this->classes = $classes;
}
/**
* Magic get method.
*
* @param string $prop property to get.
*
* @return mixed
* @throws \coding_exception
*/
public function __get($prop) {
if (in_array($prop, $this->properties)) {
return $this->$prop;
}
throw new \coding_exception('Property "' . $prop . '" doesn\'t exist');
}
}
+129
View File
@@ -0,0 +1,129 @@
<?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/>.
/**
* myprofile renderer.
*
* @package core_user
* @copyright 2015 onwards Ankit Agarwal <ankit.agrr@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_user\output\myprofile;
defined('MOODLE_INTERNAL') || die;
/**
* Report log renderer's for printing reports.
*
* @since Moodle 2.9
* @package core_user
* @copyright 2015 onwards Ankit Agarwal <ankit.agrr@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends \plugin_renderer_base {
/**
* Render the whole tree.
*
* @param tree $tree
*
* @return string
*/
public function render_tree(tree $tree) {
$return = \html_writer::start_tag('div', array('class' => 'profile_tree'));
$categories = $tree->categories;
foreach ($categories as $category) {
$return .= $this->render($category);
}
$return .= \html_writer::end_tag('div');
return $return;
}
/**
* Render a category.
*
* @param category $category
*
* @return string
*/
public function render_category(category $category) {
$classes = $category->classes;
if (empty($classes)) {
$return = \html_writer::start_tag('section',
array('class' => 'node_category card d-inline-block w-100 mb-3'));
$return .= \html_writer::start_tag('div', array('class' => 'card-body'));
} else {
$return = \html_writer::start_tag('section',
array('class' => 'node_category card d-inline-block w-100 mb-3' . $classes));
$return .= \html_writer::start_tag('div', array('class' => 'card-body'));
}
$return .= \html_writer::tag('h3', $category->title, array('class' => 'lead'));
$nodes = $category->nodes;
if (empty($nodes)) {
// No nodes, nothing to render.
return '';
}
$return .= \html_writer::start_tag('ul');
foreach ($nodes as $node) {
$return .= $this->render($node);
}
$return .= \html_writer::end_tag('ul');
$return .= \html_writer::end_tag('div');
$return .= \html_writer::end_tag('section');
return $return;
}
/**
* Render a node.
*
* @param node $node
*
* @return string
*/
public function render_node(node $node) {
$return = '';
if (is_object($node->url)) {
$header = \html_writer::link($node->url, $node->title);
} else {
$header = $node->title;
}
$icon = $node->icon;
if (!empty($icon)) {
$header .= $this->render($icon);
}
$content = $node->content;
$classes = $node->classes;
if (!empty($content)) {
if ($header) {
// There is some content to display below this make this a header.
$return = \html_writer::tag('dt', $header);
$return .= \html_writer::tag('dd', $content);
$return = \html_writer::tag('dl', $return);
} else {
$return = \html_writer::span($content);
}
if ($classes) {
$return = \html_writer::tag('li', $return, array('class' => 'contentnode ' . $classes));
} else {
$return = \html_writer::tag('li', $return, array('class' => 'contentnode'));
}
} else {
$return = \html_writer::span($header);
$return = \html_writer::tag('li', $return, array('class' => $classes));
}
return $return;
}
}
+158
View File
@@ -0,0 +1,158 @@
<?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/>.
/**
* Defines profile page navigation tree.
*
* @package core_user
* @copyright 2015 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_user\output\myprofile;
defined('MOODLE_INTERNAL') || die();
/**
* Defines my profile page navigation tree.
*
* @since Moodle 2.9
* @package core_user
* @copyright 2015 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tree implements \renderable {
/**
* @var category[] Array of categories in the tree.
*/
private $categories = array();
/**
* @var node[] Array of nodes in the tree that were directly added to the tree.
*/
private $nodes = array();
/**
* @var array List of properties accessible via __get.
*/
private $properties = array('categories', 'nodes');
/**
* Add a node to the tree.
*
* @param node $node node object.
*
* @throws \coding_exception
*/
public function add_node(node $node) {
$name = $node->name;
if (isset($this->nodes[$name])) {
throw new \coding_exception("Node name $name already used");
}
$this->nodes[$node->name] = $node;
}
/**
* Add a category to the tree.
*
* @param category $cat category object.
*
* @throws \coding_exception
*/
public function add_category(category $cat) {
$name = $cat->name;
if (isset($this->categories[$name])) {
throw new \coding_exception("Category name $name already used");
}
$this->categories[$cat->name] = $cat;
}
/**
* Sort categories and nodes. Builds the tree structure that would be displayed to the user.
*
* @throws \coding_exception
*/
public function sort_categories() {
$this->attach_nodes_to_categories();
$tempcategories = array();
foreach ($this->categories as $category) {
$after = $category->after;
if ($after == null) {
// Can go anywhere in the tree.
$category->sort_nodes();
$tempcategories = array_merge($tempcategories, array($category->name => $category),
$this->find_categories_after($category));
}
}
if (count($tempcategories) !== count($this->categories)) {
// Orphan categories found.
throw new \coding_exception('Some of the categories specified contains invalid \'after\' property');
}
$this->categories = $tempcategories;
}
/**
* Attach various nodes to their respective categories.
*
* @throws \coding_exception
*/
protected function attach_nodes_to_categories() {
foreach ($this->nodes as $node) {
$parentcat = $node->parentcat;
if (!isset($this->categories[$parentcat])) {
throw new \coding_exception("Category $parentcat doesn't exist");
} else {
$this->categories[$parentcat]->add_node($node);
}
}
}
/**
* Find all category nodes that should be displayed after a given a category node.
*
* @param category $category category object
*
* @return category[] array of category objects
* @throws \coding_exception
*/
protected function find_categories_after($category) {
$return = array();
$categoryarray = $this->categories;
foreach ($categoryarray as $categoryelement) {
if ($categoryelement->after == $category->name) {
// Find all categories that comes after this category as well.
$categoryelement->sort_nodes();
$return = array_merge($return, array($categoryelement->name => $categoryelement),
$this->find_categories_after($categoryelement));
}
}
return $return;
}
/**
* Magic get method.
*
* @param string $prop property to get.
*
* @return mixed
* @throws \coding_exception
*/
public function __get($prop) {
if (in_array($prop, $this->properties)) {
return $this->$prop;
}
throw new \coding_exception('Property "' . $prop . '" doesn\'t exist');
}
}
+374
View File
@@ -0,0 +1,374 @@
<?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/>.
/**
* Class for rendering user filters on the course participants page.
*
* @package core_user
* @copyright 2020 Michael Hawkins <michaelh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_user\output;
use core_user\fields;
use renderer_base;
use stdClass;
/**
* Class for rendering user filters on the course participants page.
*
* @copyright 2020 Michael Hawkins <michaelh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class participants_filter extends \core\output\datafilter {
/**
* Get data for all filter types.
*
* @return array
*/
protected function get_filtertypes(): array {
$filtertypes = [];
$filtertypes[] = $this->get_keyword_filter();
if ($filtertype = $this->get_enrolmentstatus_filter()) {
$filtertypes[] = $filtertype;
}
if ($filtertype = $this->get_roles_filter()) {
$filtertypes[] = $filtertype;
}
if ($filtertype = $this->get_enrolments_filter()) {
$filtertypes[] = $filtertype;
}
if ($filtertype = $this->get_groups_filter()) {
$filtertypes[] = $filtertype;
}
if ($filtertype = $this->get_accesssince_filter()) {
$filtertypes[] = $filtertype;
}
if ($filtertype = $this->get_country_filter()) {
$filtertypes[] = $filtertype;
}
return $filtertypes;
}
/**
* Get data for the enrolment status filter.
*
* @return stdClass|null
*/
protected function get_enrolmentstatus_filter(): ?stdClass {
if (!has_capability('moodle/course:enrolreview', $this->context)) {
return null;
}
return $this->get_filter_object(
'status',
get_string('participationstatus', 'core_enrol'),
false,
true,
null,
[
(object) [
'value' => ENROL_USER_ACTIVE,
'title' => get_string('active'),
],
(object) [
'value' => ENROL_USER_SUSPENDED,
'title' => get_string('inactive'),
],
]
);
}
/**
* Get data for the roles filter.
*
* @return stdClass|null
*/
protected function get_roles_filter(): ?stdClass {
$roles = [];
$roles += [-1 => get_string('noroles', 'role')];
$roles += get_viewable_roles($this->context, null, ROLENAME_BOTH);
if (has_capability('moodle/role:assign', $this->context)) {
$roles += get_assignable_roles($this->context, ROLENAME_BOTH);
}
return $this->get_filter_object(
'roles',
get_string('roles', 'core_role'),
false,
true,
null,
array_map(function($id, $title) {
return (object) [
'value' => $id,
'title' => $title,
];
}, array_keys($roles), array_values($roles))
);
}
/**
* Get data for the roles filter.
*
* @return stdClass|null
*/
protected function get_enrolments_filter(): ?stdClass {
if (!has_capability('moodle/course:enrolreview', $this->context)) {
return null;
}
if ($this->course->id == SITEID) {
// No enrolment methods for the site.
return null;
}
$instances = enrol_get_instances($this->course->id, true);
$plugins = enrol_get_plugins(false);
return $this->get_filter_object(
'enrolments',
get_string('enrolmentinstances', 'core_enrol'),
false,
true,
null,
array_filter(array_map(function($instance) use ($plugins): ?stdClass {
if (!array_key_exists($instance->enrol, $plugins)) {
return null;
}
return (object) [
'value' => $instance->id,
'title' => $plugins[$instance->enrol]->get_instance_name($instance),
];
}, array_values($instances)))
);
}
/**
* Get data for the groups filter.
*
* @return stdClass|null
*/
protected function get_groups_filter(): ?stdClass {
global $USER;
// Filter options for groups, if available.
$seeallgroups = has_capability('moodle/site:accessallgroups', $this->context);
$seeallgroups = $seeallgroups || ($this->course->groupmode != SEPARATEGROUPS);
if ($seeallgroups) {
$groups = [];
$groups += [USERSWITHOUTGROUP => (object) [
'id' => USERSWITHOUTGROUP,
'name' => get_string('nogroup', 'group'),
]];
$groups += groups_get_all_groups($this->course->id);
} else {
// Otherwise, just list the groups the user belongs to.
$groups = groups_get_all_groups($this->course->id, $USER->id);
}
// Return no data if no groups found (which includes if the only value is 'No group').
if (empty($groups) || (count($groups) === 1 && array_key_exists(-1, $groups))) {
return null;
}
return $this->get_filter_object(
'groups',
get_string('groups', 'core_group'),
false,
true,
null,
array_map(function($group) {
return (object) [
'value' => $group->id,
'title' => format_string($group->name, true, ['context' => $this->context]),
];
}, array_values($groups))
);
}
/**
* Get data for the accesssince filter.
*
* @return stdClass|null
*/
protected function get_accesssince_filter(): ?stdClass {
global $CFG, $DB;
$hiddenfields = [];
if (!has_capability('moodle/course:viewhiddenuserfields', $this->context)) {
$hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
}
if (array_key_exists('lastaccess', $hiddenfields)) {
return null;
}
// Get minimum lastaccess for this course and display a dropbox to filter by lastaccess going back this far.
// We need to make it diferently for normal courses and site course.
if (!($this->course->id == SITEID)) {
// Regular course.
$params = [
'courseid' => $this->course->id,
'timeaccess' => 0,
];
$select = 'courseid = :courseid AND timeaccess != :timeaccess';
$minlastaccess = $DB->get_field_select('user_lastaccess', 'MIN(timeaccess)', $select, $params);
// Determine enrolled users, who do not have accompanying lastaccess to the course.
[$enrolledsql, $enrolledparams] = get_enrolled_sql($this->context);
$sql = "SELECT 'x'
FROM {user} u
JOIN ({$enrolledsql}) je ON je.id = u.id
LEFT JOIN {user_lastaccess} ula ON ula.userid = je.id AND ula.courseid = :courseid
WHERE COALESCE(ula.timeaccess, 0) = :timeaccess";
$lastaccess0exists = $DB->record_exists_sql($sql, array_merge($params, $enrolledparams));
} else {
// Front page.
$params = ['lastaccess' => 0];
$select = 'lastaccess != :lastaccess';
$minlastaccess = $DB->get_field_select('user', 'MIN(lastaccess)', $select, $params);
$lastaccess0exists = $DB->record_exists('user', $params);
}
$now = usergetmidnight(time());
$getoptions = function(int $count, string $singletype, string $type) use ($now, $minlastaccess): array {
$values = [];
for ($i = 1; $i <= $count; $i++) {
$timestamp = strtotime("-{$i} {$type}", $now);
if ($timestamp < $minlastaccess) {
break;
}
if ($i === 1) {
$title = get_string("num{$singletype}", 'moodle', $i);
} else {
$title = get_string("num{$type}", 'moodle', $i);
}
$values[] = [
'value' => $timestamp,
'title' => $title,
];
}
return $values;
};
$values = array_merge(
$getoptions(6, 'day', 'days'),
$getoptions(10, 'week', 'weeks'),
$getoptions(11, 'month', 'months'),
$getoptions(1, 'year', 'years')
);
if ($lastaccess0exists) {
$values[] = [
'value' => -1,
'title' => get_string('never', 'moodle'),
];
}
if (count($values) <= 1) {
// Nothing to show.
return null;
}
return $this->get_filter_object(
'accesssince',
get_string('usersnoaccesssince'),
false,
false,
null,
$values
);
}
/**
* Get data for the country filter
*
* @return stdClass|null
*/
protected function get_country_filter(): ?stdClass {
$extrauserfields = fields::get_identity_fields($this->context, false);
if (array_search('country', $extrauserfields) === false) {
return null;
}
$countries = get_string_manager()->get_list_of_countries(true);
return $this->get_filter_object(
'country',
get_string('country'),
false,
true,
'core/datafilter/filtertypes/country',
array_map(function(string $code, string $name): stdClass {
return (object) [
'value' => $code,
'title' => $name,
];
}, array_keys($countries), array_values($countries))
);
}
/**
* Get data for the keywords filter.
*
* @return stdClass|null
*/
protected function get_keyword_filter(): ?stdClass {
return $this->get_filter_object(
'keywords',
get_string('filterbykeyword', 'core_user'),
true,
true,
'core/datafilter/filtertypes/keyword',
[],
true
);
}
/**
* Export the renderer data in a mustache template friendly format.
*
* @param renderer_base $output Unused.
* @return stdClass Data in a format compatible with a mustache template.
*/
public function export_for_template(renderer_base $output): stdClass {
return (object) [
'tableregionid' => $this->tableregionid,
'courseid' => $this->context->instanceid,
'filtertypes' => $this->get_filtertypes(),
'rownumber' => 1,
];
return $data;
}
}
+171
View File
@@ -0,0 +1,171 @@
<?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/>.
/**
* Class containing the data necessary for rendering the status field in the course participants page.
*
* @package core_user
* @copyright 2017 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_user\output;
defined('MOODLE_INTERNAL') || die();
use renderable;
use renderer_base;
use stdClass;
use templatable;
use user_enrolment_action;
/**
* Class containing the data for the status field.
*
* @package core_user
* @copyright 2017 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class status_field implements renderable, templatable {
/** Active user enrolment status constant. */
const STATUS_ACTIVE = 0;
/** Suspended user enrolment status constant. */
const STATUS_SUSPENDED = 1;
/** Not current user enrolment status constant. */
const STATUS_NOT_CURRENT = 2;
/** @var string $enrolinstancename The enrolment instance name. */
protected $enrolinstancename;
/** @var string $coursename The course's full name. */
protected $coursename;
/** @var string $fullname The user's full name. */
protected $fullname;
/** @var string $status The user enrolment status. */
protected $status;
/** @var int $timestart The timestamp when the user's enrolment starts. */
protected $timestart;
/** @var int $timeend The timestamp when the user's enrolment ends. */
protected $timeend;
/** @var int $timeenrolled The timestamp when the user was enrolled. */
protected $timeenrolled;
/** @var user_enrolment_action[] $enrolactions Array of enrol action objects for the given enrolment method. */
protected $enrolactions;
/** @var bool $statusactive Indicates whether a user enrolment status should be rendered as active. */
protected $statusactive = false;
/** @var bool $statusactive Indicates whether a user enrolment status should be rendered as suspended. */
protected $statussuspended = false;
/** @var bool $statusactive Indicates whether a user enrolment status should be rendered as not current. */
protected $statusnotcurrent = false;
/**
* status_field constructor.
*
* @param string $enrolinstancename The enrolment instance name.
* @param string $coursename The course's full name.
* @param string $fullname The user's full name.
* @param string $status The user enrolment status.
* @param int|null $timestart The timestamp when the user's enrolment starts.
* @param int|null $timeend The timestamp when the user's enrolment ends.
* @param user_enrolment_action[] $enrolactions Array of enrol action objects for the given enrolment method.
* @param int|null $timeenrolled The timestamp when the user was enrolled.
*/
public function __construct($enrolinstancename, $coursename, $fullname, $status, $timestart = null, $timeend = null,
$enrolactions = [], $timeenrolled = null) {
$this->enrolinstancename = $enrolinstancename;
$this->coursename = $coursename;
$this->fullname = $fullname;
$this->status = $status;
$this->timestart = $timestart;
$this->timeend = $timeend;
$this->enrolactions = $enrolactions;
$this->timeenrolled = $timeenrolled;
}
/**
* Function to export the renderer data in a format that is suitable for a
* mustache template. This means:
* 1. No complex types - only stdClass, array, int, string, float, bool
* 2. Any additional info that is required for the template is pre-calculated (e.g. capability checks).
*
* @param renderer_base $output Used to do a final render of any components that need to be rendered for export.
* @return stdClass|array
*/
public function export_for_template(renderer_base $output) {
$data = new stdClass();
$data->enrolinstancename = $this->enrolinstancename;
$data->coursename = $this->coursename;
$data->fullname = $this->fullname;
$data->status = $this->status;
$data->active = $this->statusactive;
$data->suspended = $this->statussuspended;
$data->notcurrent = $this->statusnotcurrent;
if ($this->timestart) {
$data->timestart = userdate($this->timestart);
}
if ($this->timeend) {
$data->timeend = userdate($this->timeend);
}
if ($this->timeenrolled) {
$data->timeenrolled = userdate($this->timeenrolled);
}
$data->enrolactions = [];
foreach ($this->enrolactions as $enrolaction) {
$action = new stdClass();
$action->url = $enrolaction->get_url()->out(false);
$action->icon = $output->render($enrolaction->get_icon());
$action->attributes = [];
foreach ($enrolaction->get_attributes() as $name => $value) {
$attribute = (object) [
'name' => $name,
'value' => $value
];
$action->attributes[] = $attribute;
}
$data->enrolactions[] = $action;
}
return $data;
}
/**
* Status setter.
*
* @param int $status The user enrolment status representing one of this class' STATUS_* constants.
* @return status_field This class' instance. Useful for chaining.
*/
public function set_status($status = self::STATUS_ACTIVE) {
$this->statusactive = $status == static::STATUS_ACTIVE;
$this->statussuspended = $status == static::STATUS_SUSPENDED;
$this->statusnotcurrent = $status == static::STATUS_NOT_CURRENT;
return $this;
}
}
+236
View File
@@ -0,0 +1,236 @@
<?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/>.
namespace core_user\output;
use context_course;
use core_user;
use core_external\external_api;
use coding_exception;
/**
* Class to display list of user roles.
*
* @package core_user
* @copyright 2017 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_roles_editable extends \core\output\inplace_editable {
/** @var $context */
private $context = null;
/** @var \stdClass[] $courseroles */
private $courseroles;
/** @var \stdClass[] $profileroles */
private $profileroles;
/** @var \stdClass[] $viewableroles */
private $viewableroles;
/** @var \stdClass[] $assignableroles */
private $assignableroles;
/**
* Constructor.
*
* @param \stdClass $course The current course
* @param \context $context The course context
* @param \stdClass $user The current user
* @param \stdClass[] $courseroles The list of course roles.
* @param \stdClass[] $assignableroles The list of assignable roles in this course.
* @param \stdClass[] $profileroles The list of roles that should be visible in a users profile.
* @param \stdClass[] $userroles The list of user roles.
*/
public function __construct($course, $context, $user, $courseroles, $assignableroles, $profileroles, $userroles, $viewableroles = null) {
if ($viewableroles === null) {
debugging('Constructor for user_roles_editable now needs the result of get_viewable_roles passed as viewableroles');
}
// Check capabilities to get editable value.
$editable = has_capability('moodle/role:assign', $context);
// Invent an itemid.
$itemid = $course->id . ':' . $user->id;
$getrole = function($role) {
return $role->roleid;
};
$ids = array_values(array_unique(array_map($getrole, $userroles)));
$value = json_encode($ids);
// Remember these for the display value.
$this->courseroles = $courseroles;
$this->profileroles = $profileroles;
$this->viewableroles = array_keys($viewableroles);
$this->assignableroles = array_keys($assignableroles);
$this->context = $context;
parent::__construct('core_user', 'user_roles', $itemid, $editable, $value, $value);
// Removed the roles that were assigned to the user at a different context.
$options = $assignableroles;
foreach ($userroles as $role) {
if (isset($assignableroles[$role->roleid])) {
if ($role->contextid != $context->id) {
unset($options[$role->roleid]);
}
}
}
$fullname = htmlspecialchars(fullname($user), ENT_QUOTES, 'utf-8');
$this->edithint = get_string('xroleassignments', 'role', $fullname);
$this->editlabel = get_string('xroleassignments', 'role', $fullname);
$attributes = ['multiple' => true];
$this->set_type_autocomplete($options, $attributes);
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param \renderer_base $output
* @return array
*/
public function export_for_template(\renderer_base $output) {
$listofroles = [];
$roleids = json_decode($this->value);
$viewableroleids = array_intersect($roleids, array_merge($this->viewableroles, $this->assignableroles));
foreach ($viewableroleids as $id) {
// If this is a student, we only show a subset of the roles.
if ($this->editable || array_key_exists($id, $this->profileroles)) {
$listofroles[] = format_string($this->courseroles[$id]->localname, true, ['context' => $this->context]);
}
}
if (!empty($listofroles)) {
$this->displayvalue = implode(', ', $listofroles);
} else if (!empty($roleids) && empty($viewableroleids)) {
$this->displayvalue = get_string('novisibleroles', 'role');
} else {
$this->displayvalue = get_string('noroles', 'role');
}
return parent::export_for_template($output);
}
/**
* Updates the value in database and returns itself, called from inplace_editable callback
*
* @param int $itemid
* @param mixed $newvalue
* @return \self
*/
public static function update($itemid, $newvalue) {
global $DB;
// Check caps.
// Do the thing.
// Return one of me.
// Validate the inputs.
list($courseid, $userid) = explode(':', $itemid, 2);
$courseid = clean_param($courseid, PARAM_INT);
$userid = clean_param($userid, PARAM_INT);
$roleids = json_decode($newvalue);
foreach ($roleids as $index => $roleid) {
$roleids[$index] = clean_param($roleid, PARAM_INT);
}
// Check user is enrolled in the course.
$context = context_course::instance($courseid);
external_api::validate_context($context);
// Check permissions.
require_capability('moodle/role:assign', $context);
if (!is_enrolled($context, $userid)) {
throw new coding_exception('User does not belong to the course');
}
// Check that all the groups belong to the course.
$allroles = role_fix_names(get_all_roles($context), $context, ROLENAME_BOTH);
$assignableroles = get_assignable_roles($context, ROLENAME_BOTH, false);
$viewableroles = get_viewable_roles($context);
$userrolesbyid = get_user_roles($context, $userid, true, 'c.contextlevel DESC, r.sortorder ASC');
$profileroles = get_profile_roles($context);
// Set an array where the index is the roleid.
$userroles = array();
foreach ($userrolesbyid as $id => $role) {
$userroles[$role->roleid] = $role;
}
$rolestoprocess = [];
foreach ($roleids as $roleid) {
if (!isset($assignableroles[$roleid])) {
throw new coding_exception('Role cannot be assigned in this course.');
}
$rolestoprocess[$roleid] = $roleid;
}
// Process adds.
foreach ($rolestoprocess as $roleid) {
if (!isset($userroles[$roleid])) {
// Add them.
$id = role_assign($roleid, $userid, $context);
// Keep this variable in sync.
$role = new \stdClass();
$role->id = $id;
$role->roleid = $roleid;
$role->contextid = $context->id;
$userroles[$role->roleid] = $role;
}
}
// Process removals.
foreach ($assignableroles as $roleid => $rolename) {
if (isset($userroles[$roleid]) && !isset($rolestoprocess[$roleid])) {
// Do not remove the role if we are not in the same context.
if ($userroles[$roleid]->contextid != $context->id) {
continue;
}
$ras = $DB->get_records('role_assignments', ['contextid' => $context->id, 'userid' => $userid,
'roleid' => $roleid]);
$allremoved = true;
foreach ($ras as $ra) {
if ($ra->component) {
if (strpos($ra->component, 'enrol_') !== 0) {
continue;
}
if (!$plugin = enrol_get_plugin(substr($ra->component, 6))) {
continue;
}
if ($plugin->roles_protected()) {
$allremoved = false;
continue;
}
}
role_unassign($ra->roleid, $ra->userid, $ra->contextid, $ra->component, $ra->itemid);
}
if ($allremoved) {
unset($userroles[$roleid]);
}
}
}
$course = get_course($courseid);
$user = core_user::get_user($userid);
return new self($course, $context, $user, $allroles, $assignableroles, $profileroles, $userroles, $viewableroles);
}
}
+565
View File
@@ -0,0 +1,565 @@
<?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/>.
/**
* Privacy class for requesting user data.
*
* @package core_user
* @copyright 2018 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_user\privacy;
defined('MOODLE_INTERNAL') || die();
use \core_privacy\local\metadata\collection;
use \core_privacy\local\request\transform;
use \core_privacy\local\request\contextlist;
use \core_privacy\local\request\approved_contextlist;
use \core_privacy\local\request\writer;
use core_privacy\local\request\userlist;
use \core_privacy\local\request\approved_userlist;
/**
* Privacy class for requesting user data.
*
* @package core_comment
* @copyright 2018 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\core_userlist_provider,
\core_privacy\local\request\subsystem\provider,
\core_privacy\local\request\user_preference_provider {
/**
* Returns information about the user data stored in this component.
*
* @param collection $collection A list of information about this component
* @return collection The collection object filled out with information about this component.
*/
public static function get_metadata(collection $collection): collection {
$userfields = [
'id' => 'privacy:metadata:id',
'auth' => 'privacy:metadata:auth',
'confirmed' => 'privacy:metadata:confirmed',
'policyagreed' => 'privacy:metadata:policyagreed',
'deleted' => 'privacy:metadata:deleted',
'suspended' => 'privacy:metadata:suspended',
'mnethostid' => 'privacy:metadata:mnethostid',
'username' => 'privacy:metadata:username',
'password' => 'privacy:metadata:password',
'idnumber' => 'privacy:metadata:idnumber',
'firstname' => 'privacy:metadata:firstname',
'lastname' => 'privacy:metadata:lastname',
'email' => 'privacy:metadata:email',
'emailstop' => 'privacy:metadata:emailstop',
'phone1' => 'privacy:metadata:phone',
'phone2' => 'privacy:metadata:phone',
'institution' => 'privacy:metadata:institution',
'department' => 'privacy:metadata:department',
'address' => 'privacy:metadata:address',
'city' => 'privacy:metadata:city',
'country' => 'privacy:metadata:country',
'lang' => 'privacy:metadata:lang',
'calendartype' => 'privacy:metadata:calendartype',
'theme' => 'privacy:metadata:theme',
'timezone' => 'privacy:metadata:timezone',
'firstaccess' => 'privacy:metadata:firstaccess',
'lastaccess' => 'privacy:metadata:lastaccess',
'lastlogin' => 'privacy:metadata:lastlogin',
'currentlogin' => 'privacy:metadata:currentlogin',
'lastip' => 'privacy:metadata:lastip',
'secret' => 'privacy:metadata:secret',
'picture' => 'privacy:metadata:picture',
'description' => 'privacy:metadata:description',
'maildigest' => 'privacy:metadata:maildigest',
'maildisplay' => 'privacy:metadata:maildisplay',
'autosubscribe' => 'privacy:metadata:autosubscribe',
'trackforums' => 'privacy:metadata:trackforums',
'timecreated' => 'privacy:metadata:timecreated',
'timemodified' => 'privacy:metadata:timemodified',
'trustbitmask' => 'privacy:metadata:trustbitmask',
'imagealt' => 'privacy:metadata:imagealt',
'lastnamephonetic' => 'privacy:metadata:lastnamephonetic',
'firstnamephonetic' => 'privacy:metadata:firstnamephonetic',
'middlename' => 'privacy:metadata:middlename',
'alternatename' => 'privacy:metadata:alternatename',
'moodlenetprofile' => 'privacy:metadata:moodlenetprofile'
];
$passwordhistory = [
'userid' => 'privacy:metadata:userid',
'hash' => 'privacy:metadata:hash',
'timecreated' => 'privacy:metadata:timecreated'
];
$lastaccess = [
'userid' => 'privacy:metadata:userid',
'courseid' => 'privacy:metadata:courseid',
'timeaccess' => 'privacy:metadata:timeaccess'
];
$userpasswordresets = [
'userid' => 'privacy:metadata:userid',
'timerequested' => 'privacy:metadata:timerequested',
'timererequested' => 'privacy:metadata:timererequested',
'token' => 'privacy:metadata:token'
];
$userdevices = [
'userid' => 'privacy:metadata:userid',
'appid' => 'privacy:metadata:appid',
'name' => 'privacy:metadata:devicename',
'model' => 'privacy:metadata:model',
'platform' => 'privacy:metadata:platform',
'version' => 'privacy:metadata:version',
'pushid' => 'privacy:metadata:pushid',
'uuid' => 'privacy:metadata:uuid',
'timecreated' => 'privacy:metadata:timecreated',
'timemodified' => 'privacy:metadata:timemodified'
];
$usersessions = [
'state' => 'privacy:metadata:state',
'sid' => 'privacy:metadata:sid',
'userid' => 'privacy:metadata:userid',
'sessdata' => 'privacy:metadata:sessdata',
'timecreated' => 'privacy:metadata:timecreated',
'timemodified' => 'privacy:metadata:timemodified',
'firstip' => 'privacy:metadata:firstip',
'lastip' => 'privacy:metadata:lastip'
];
$courserequest = [
'fullname' => 'privacy:metadata:fullname',
'shortname' => 'privacy:metadata:shortname',
'summary' => 'privacy:metadata:summary',
'category' => 'privacy:metadata:category',
'reason' => 'privacy:metadata:reason',
'requester' => 'privacy:metadata:requester'
];
$mypages = [
'userid' => 'privacy:metadata:my_pages:userid',
'name' => 'privacy:metadata:my_pages:name',
'private' => 'privacy:metadata:my_pages:private',
];
$userpreferences = [
'userid' => 'privacy:metadata:user_preferences:userid',
'name' => 'privacy:metadata:user_preferences:name',
'value' => 'privacy:metadata:user_preferences:value'
];
$collection->add_database_table('user', $userfields, 'privacy:metadata:usertablesummary');
$collection->add_database_table('user_password_history', $passwordhistory, 'privacy:metadata:passwordtablesummary');
$collection->add_database_table('user_password_resets', $userpasswordresets, 'privacy:metadata:passwordresettablesummary');
$collection->add_database_table('user_lastaccess', $lastaccess, 'privacy:metadata:lastaccesstablesummary');
$collection->add_database_table('user_devices', $userdevices, 'privacy:metadata:devicetablesummary');
$collection->add_database_table('course_request', $courserequest, 'privacy:metadata:requestsummary');
$collection->add_database_table('sessions', $usersessions, 'privacy:metadata:sessiontablesummary');
$collection->add_database_table('my_pages', $mypages, 'privacy:metadata:my_pages');
$collection->add_database_table('user_preferences', $userpreferences, 'privacy:metadata:user_preferences');
$collection->add_subsystem_link('core_files', [], 'privacy:metadata:filelink');
$collection->add_user_preference(
'core_user_welcome',
'privacy:metadata:user_preference:core_user_welcome'
);
return $collection;
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param int $userid The user to search.
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
*/
public static function get_contexts_for_userid(int $userid): contextlist {
$params = ['userid' => $userid, 'contextuser' => CONTEXT_USER];
$sql = "SELECT id
FROM {context}
WHERE instanceid = :userid and contextlevel = :contextuser";
$contextlist = new contextlist();
$contextlist->add_from_sql($sql, $params);
return $contextlist;
}
/**
* Get the list of users within a specific context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist) {
$context = $userlist->get_context();
if (!$context instanceof \context_user) {
return;
}
$userlist->add_user($context->instanceid);
}
/**
* Export all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
$context = $contextlist->current();
$user = \core_user::get_user($contextlist->get_user()->id);
static::export_user($user, $context);
static::export_password_history($user->id, $context);
static::export_password_resets($user->id, $context);
static::export_lastaccess($user->id, $context);
static::export_course_requests($user->id, $context);
static::export_user_devices($user->id, $context);
static::export_user_session_data($user->id, $context);
}
/**
* Delete all data for all users in the specified context.
*
* @param context $context The specific context to delete data for.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
// Only delete data for a user context.
if ($context->contextlevel == CONTEXT_USER) {
static::delete_user_data($context->instanceid, $context);
}
}
/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist) {
$context = $userlist->get_context();
if ($context instanceof \context_user) {
static::delete_user_data($context->instanceid, $context);
}
}
/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
foreach ($contextlist as $context) {
// Let's be super certain that we have the right information for this user here.
if ($context->contextlevel == CONTEXT_USER && $contextlist->get_user()->id == $context->instanceid) {
static::delete_user_data($contextlist->get_user()->id, $contextlist->current());
}
}
}
/**
* Deletes non vital information about a user.
*
* @param int $userid The user ID to delete
* @param \context $context The user context
*/
protected static function delete_user_data(int $userid, \context $context) {
global $DB;
// Delete password history.
$DB->delete_records('user_password_history', ['userid' => $userid]);
// Delete last access.
$DB->delete_records('user_lastaccess', ['userid' => $userid]);
// Delete password resets.
$DB->delete_records('user_password_resets', ['userid' => $userid]);
// Delete user devices.
$DB->delete_records('user_devices', ['userid' => $userid]);
// Delete user course requests.
$DB->delete_records('course_request', ['requester' => $userid]);
// Delete sessions.
$DB->delete_records('sessions', ['userid' => $userid]);
// Do I delete user preferences? Seems like the right place to do it.
$DB->delete_records('user_preferences', ['userid' => $userid]);
// Delete all of the files for this user.
$fs = get_file_storage();
$fs->delete_area_files($context->id, 'user');
// For the user record itself we only want to remove unnecessary data. We still need the core data to keep as a record
// that we actually did follow the request to be forgotten.
$user = \core_user::get_user($userid);
// Update fields we wish to change to nothing.
$user->deleted = 1;
$user->idnumber = '';
$user->emailstop = 0;
$user->phone1 = '';
$user->phone2 = '';
$user->institution = '';
$user->department = '';
$user->address = '';
$user->city = '';
$user->country = '';
$user->lang = '';
$user->calendartype = '';
$user->theme = '';
$user->timezone = '';
$user->firstaccess = 0;
$user->lastaccess = 0;
$user->lastlogin = 0;
$user->currentlogin = 0;
$user->lastip = 0;
$user->secret = '';
$user->picture = '';
$user->description = '';
$user->descriptionformat = 0;
$user->mailformat = 0;
$user->maildigest = 0;
$user->maildisplay = 0;
$user->autosubscribe = 0;
$user->trackforums = 0;
$user->timecreated = 0;
$user->timemodified = 0;
$user->trustbitmask = 0;
$user->imagealt = '';
$user->lastnamephonetic = '';
$user->firstnamephonetic = '';
$user->middlename = '';
$user->alternatename = '';
$DB->update_record('user', $user);
}
/**
* Export core user data.
*
* @param \stdClass $user The user object.
* @param \context $context The user context.
*/
protected static function export_user(\stdClass $user, \context $context) {
$data = (object) [
'auth' => $user->auth,
'confirmed' => transform::yesno($user->confirmed),
'policyagreed' => transform::yesno($user->policyagreed),
'deleted' => transform::yesno($user->deleted),
'suspended' => transform::yesno($user->suspended),
'username' => $user->username,
'idnumber' => $user->idnumber,
'firstname' => format_string($user->firstname, true, ['context' => $context]),
'lastname' => format_string($user->lastname, true, ['context' => $context]),
'email' => $user->email,
'emailstop' => transform::yesno($user->emailstop),
'phone1' => format_string($user->phone1, true, ['context' => $context]),
'phone2' => format_string($user->phone2, true, ['context' => $context]),
'institution' => format_string($user->institution, true, ['context' => $context]),
'department' => format_string($user->department, true, ['context' => $context]),
'address' => format_string($user->address, true, ['context' => $context]),
'city' => format_string($user->city, true, ['context' => $context]),
'country' => format_string($user->country, true, ['context' => $context]),
'lang' => $user->lang,
'calendartype' => $user->calendartype,
'theme' => $user->theme,
'timezone' => $user->timezone,
'firstaccess' => $user->firstaccess ? transform::datetime($user->firstaccess) : null,
'lastaccess' => $user->lastaccess ? transform::datetime($user->lastaccess) : null,
'lastlogin' => $user->lastlogin ? transform::datetime($user->lastlogin) : null,
'currentlogin' => $user->currentlogin ? transform::datetime($user->currentlogin) : null,
'lastip' => $user->lastip,
'secret' => $user->secret,
'picture' => $user->picture,
'description' => format_text(
writer::with_context($context)->rewrite_pluginfile_urls(
[],
'user',
'profile',
'',
$user->description
), $user->descriptionformat, ['context' => $context]),
'maildigest' => transform::yesno($user->maildigest),
'maildisplay' => $user->maildisplay,
'autosubscribe' => transform::yesno($user->autosubscribe),
'trackforums' => transform::yesno($user->trackforums),
'timecreated' => transform::datetime($user->timecreated),
'timemodified' => transform::datetime($user->timemodified),
'imagealt' => format_string($user->imagealt, true, ['context' => $context]),
'lastnamephonetic' => format_string($user->lastnamephonetic, true, ['context' => $context]),
'firstnamephonetic' => format_string($user->firstnamephonetic, true, ['context' => $context]),
'middlename' => format_string($user->middlename, true, ['context' => $context]),
'alternatename' => format_string($user->alternatename, true, ['context' => $context])
];
writer::with_context($context)->export_area_files([], 'user', 'profile', 0)
->export_data([], $data);
// Export profile images.
writer::with_context($context)->export_area_files([get_string('privacy:profileimagespath', 'user')], 'user', 'icon', 0);
// Export private files.
writer::with_context($context)->export_area_files([get_string('privacy:privatefilespath', 'user')], 'user', 'private', 0);
// Export draft files.
writer::with_context($context)->export_area_files([get_string('privacy:draftfilespath', 'user')], 'user', 'draft', false);
}
/**
* Export information about the last time a user accessed a course.
*
* @param int $userid The user ID.
* @param \context $context The user context.
*/
protected static function export_lastaccess(int $userid, \context $context) {
global $DB;
$sql = "SELECT c.id, c.fullname, ul.timeaccess
FROM {user_lastaccess} ul
JOIN {course} c ON c.id = ul.courseid
WHERE ul.userid = :userid";
$params = ['userid' => $userid];
$records = $DB->get_records_sql($sql, $params);
if (!empty($records)) {
$lastaccess = (object) array_map(function($record) use ($context) {
return [
'course_name' => format_string($record->fullname, true, ['context' => $context]),
'timeaccess' => transform::datetime($record->timeaccess)
];
}, $records);
writer::with_context($context)->export_data([get_string('privacy:lastaccesspath', 'user')], $lastaccess);
}
}
/**
* Exports information about password resets.
*
* @param int $userid The user ID
* @param \context $context Context for this user.
*/
protected static function export_password_resets(int $userid, \context $context) {
global $DB;
$records = $DB->get_records('user_password_resets', ['userid' => $userid]);
if (!empty($records)) {
$passwordresets = (object) array_map(function($record) {
return [
'timerequested' => transform::datetime($record->timerequested),
'timererequested' => transform::datetime($record->timererequested)
];
}, $records);
writer::with_context($context)->export_data([get_string('privacy:passwordresetpath', 'user')], $passwordresets);
}
}
/**
* Exports information about the user's mobile devices.
*
* @param int $userid The user ID.
* @param \context $context Context for this user.
*/
protected static function export_user_devices(int $userid, \context $context) {
global $DB;
$records = $DB->get_records('user_devices', ['userid' => $userid]);
if (!empty($records)) {
$userdevices = (object) array_map(function($record) {
return [
'appid' => $record->appid,
'name' => $record->name,
'model' => $record->model,
'platform' => $record->platform,
'version' => $record->version,
'timecreated' => transform::datetime($record->timecreated),
'timemodified' => transform::datetime($record->timemodified)
];
}, $records);
writer::with_context($context)->export_data([get_string('privacy:devicespath', 'user')], $userdevices);
}
}
/**
* Exports information about course requests this user made.
*
* @param int $userid The user ID.
* @param \context $context The context object
*/
protected static function export_course_requests(int $userid, \context $context) {
global $DB;
$sql = "SELECT cr.shortname, cr.fullname, cr.summary, cc.name AS category, cr.reason
FROM {course_request} cr
JOIN {course_categories} cc ON cr.category = cc.id
WHERE cr.requester = :userid";
$params = ['userid' => $userid];
$records = $DB->get_records_sql($sql, $params);
if ($records) {
writer::with_context($context)->export_data([get_string('privacy:courserequestpath', 'user')], (object) $records);
}
}
/**
* Get details about the user's password history.
*
* @param int $userid The user ID that we are getting the password history for.
* @param \context $context the user context.
*/
protected static function export_password_history(int $userid, \context $context) {
global $DB;
// Just provide a count of how many entries we have.
$recordcount = $DB->count_records('user_password_history', ['userid' => $userid]);
if ($recordcount) {
$passwordhistory = (object) ['password_history_count' => $recordcount];
writer::with_context($context)->export_data([get_string('privacy:passwordhistorypath', 'user')], $passwordhistory);
}
}
/**
* Exports information about the user's session.
*
* @param int $userid The user ID.
* @param \context $context The context for this user.
*/
protected static function export_user_session_data(int $userid, \context $context) {
global $DB, $SESSION;
$records = $DB->get_records('sessions', ['userid' => $userid]);
if (!empty($records)) {
$sessiondata = (object) array_map(function($record) {
return [
'state' => $record->state,
'sessdata' => ($record->sessdata !== null) ? base64_decode($record->sessdata) : '',
'timecreated' => transform::datetime($record->timecreated),
'timemodified' => transform::datetime($record->timemodified),
'firstip' => $record->firstip,
'lastip' => $record->lastip
];
}, $records);
writer::with_context($context)->export_data([get_string('privacy:sessionpath', 'user')], $sessiondata);
}
}
/**
* Export all user preferences for the plugin.
*
* @param int $userid The userid of the user whose data is to be exported.
*/
public static function export_user_preferences(int $userid) {
$userwelcomepreference = get_user_preferences('core_user_welcome', null, $userid);
if ($userwelcomepreference !== null) {
writer::export_user_preference(
'core_user',
'core_user_welcome',
$userwelcomepreference,
get_string('privacy:metadata:user_preference:core_user_welcome', 'core_user')
);
}
}
}
@@ -0,0 +1,140 @@
<?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_user\reportbuilder\datasource;
use lang_string;
use core_cohort\reportbuilder\local\entities\cohort;
use core_reportbuilder\datasource;
use core_reportbuilder\local\entities\user;
use core_reportbuilder\local\filters\boolean_select;
use core_reportbuilder\local\helpers\database;
use core_tag\reportbuilder\local\entities\tag;
/**
* Users datasource
*
* @package core_user
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class users extends datasource {
/**
* Return user friendly name of the datasource
*
* @return string
*/
public static function get_name(): string {
return get_string('users');
}
/**
* Initialise report
*/
protected function initialise(): void {
global $CFG;
$userentity = new user();
$useralias = $userentity->get_table_alias('user');
$this->set_main_table('user', $useralias);
$this->add_entity($userentity);
$userparamguest = database::generate_param_name();
$this->add_base_condition_sql("{$useralias}.id != :{$userparamguest} AND {$useralias}.deleted = 0", [
$userparamguest => $CFG->siteguest,
]);
// Join the tag entity.
$tagentity = (new tag())
->set_table_alias('tag', $userentity->get_table_alias('tag'))
->set_entity_title(new lang_string('interests'));
$this->add_entity($tagentity
->add_joins($userentity->get_tag_joins()));
// Join the cohort entity.
$cohortentity = new cohort();
$cohortalias = $cohortentity->get_table_alias('cohort');
$cohortmemberalias = database::generate_alias();
$this->add_entity($cohortentity->add_joins([
"LEFT JOIN {cohort_members} {$cohortmemberalias} ON {$cohortmemberalias}.userid = {$useralias}.id",
"LEFT JOIN {cohort} {$cohortalias} ON {$cohortalias}.id = {$cohortmemberalias}.cohortid",
]));
// Add all columns/filters/conditions from entities to be available in custom reports.
$this->add_all_from_entity($userentity->get_entity_name());
$this->add_all_from_entity($tagentity->get_entity_name(), ['name', 'namewithlink'], ['name'], ['name']);
$this->add_all_from_entity($cohortentity->get_entity_name(), ['name', 'idnumber', 'description', 'customfield*'],
['cohortselect', 'name', 'idnumber', 'customfield*'], ['cohortselect', 'name', 'idnumber', 'customfield*']);
}
/**
* Return the columns that will be added to the report once is created
*
* @return string[]
*/
public function get_default_columns(): array {
return ['user:fullname', 'user:username', 'user:email'];
}
/**
* Return the filters that will be added to the report once is created
*
* @return string[]
*/
public function get_default_filters(): array {
return ['user:fullname', 'user:username', 'user:email'];
}
/**
* Return the conditions that will be added to the report once is created
*
* @return string[]
*/
public function get_default_conditions(): array {
return [
'user:fullname',
'user:username',
'user:email',
'user:suspended',
];
}
/**
* Return the conditions values that will be added to the report once is created
*
* @return array
*/
public function get_default_condition_values(): array {
return [
'user:suspended_operator' => boolean_select::NOT_CHECKED,
];
}
/**
* Return the default sorting that will be added to the report once it is created
*
* @return int[]
*/
public function get_default_column_sorting(): array {
return [
'user:fullname' => SORT_ASC,
];
}
}
+218
View File
@@ -0,0 +1,218 @@
<?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/>.
/**
* Index teachers in a course
*
* @package core_user
* @author Nathan Nguyen <nathannguyen@catalyst-au.net>
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_user\search;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/user/lib.php');
/**
* Search for user role assignment in a course
*
* @package core_user
* @author Nathan Nguyen <nathannguyen@catalyst-au.net>
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_teacher extends \core_search\base {
/**
* The context levels the search implementation is working on.
*
* @var array
*/
protected static $levels = [CONTEXT_COURSE];
/**
* Returns the moodle component name.
*
* It might be the plugin name (whole frankenstyle name) or the core subsystem name.
*
* @return string
*/
public function get_component_name() {
return 'course_teacher';
}
/**
* Returns recordset containing required data attributes for indexing.
*
* @param number $modifiedfrom
* @param \context|null $context Optional context to restrict scope of returned results
* @return \moodle_recordset|null Recordset (or null if no results)
*/
public function get_document_recordset($modifiedfrom = 0, \context $context = null) {
global $DB;
$teacherroleids = get_config('core', 'searchteacherroles');
// Only index teacher roles.
if (!empty($teacherroleids)) {
$teacherroleids = explode(',', $teacherroleids);
list($insql, $inparams) = $DB->get_in_or_equal($teacherroleids, SQL_PARAMS_NAMED);
} else {
// Do not index at all.
list($insql, $inparams) = [' = :roleid', ['roleid' => 0]];
}
$params = [
'coursecontext' => CONTEXT_COURSE,
'modifiedfrom' => $modifiedfrom
];
$params = array_merge($params, $inparams);
$recordset = $DB->get_recordset_sql("
SELECT u.*, ra.contextid, r.shortname as roleshortname, ra.id as itemid, ra.timemodified as timeassigned
FROM {role_assignments} ra
JOIN {context} ctx
ON ctx.id = ra.contextid
AND ctx.contextlevel = :coursecontext
JOIN {user} u
ON u.id = ra.userid
JOIN {role} r
ON r.id = ra.roleid
WHERE ra.timemodified >= :modifiedfrom AND r.id $insql
ORDER BY ra.timemodified ASC", $params);
return $recordset;
}
/**
* Returns document instances for each record in the recordset.
*
* @param \stdClass $record
* @param array $options
* @return \core_search\document
*/
public function get_document($record, $options = array()) {
$context = \context::instance_by_id($record->contextid);
// Content.
if ($context->contextlevel == CONTEXT_COURSE) {
$course = get_course($context->instanceid);
$contentdata = new \stdClass();
$contentdata->role = ucfirst($record->roleshortname);
$contentdata->course = $course->fullname;
$content = get_string('content:courserole', 'core_search', $contentdata);
} else {
return false;
}
$doc = \core_search\document_factory::instance($record->itemid, $this->componentname, $this->areaname);
// Assigning properties to our document.
$doc->set('title', content_to_text(fullname($record), false));
$doc->set('contextid', $context->id);
$doc->set('courseid', $context->instanceid);
$doc->set('itemid', $record->itemid);
$doc->set('modified', $record->timeassigned);
$doc->set('owneruserid', \core_search\manager::NO_OWNER_ID);
$doc->set('userid', $record->id);
$doc->set('content', $content);
// Check if this document should be considered new.
if (isset($options['lastindexedtime']) && $options['lastindexedtime'] < $record->timeassigned) {
$doc->set_is_new(true);
}
return $doc;
}
/**
* Checking whether I can access a document
*
* @param int $id user id
* @return int
*/
public function check_access($id) {
$user = $this->get_user($id);
if (!$user || $user->deleted) {
return \core_search\manager::ACCESS_DELETED;
}
if (user_can_view_profile($user)) {
return \core_search\manager::ACCESS_GRANTED;
}
return \core_search\manager::ACCESS_DENIED;
}
/**
* Returns a url to the document context.
*
* @param \core_search\document $doc
* @return \moodle_url
*/
public function get_context_url(\core_search\document $doc) {
$user = $this->get_user($doc->get('itemid'));
$courseid = $doc->get('courseid');
return new \moodle_url('/user/view.php', array('id' => $user->id, 'course' => $courseid));
}
/**
* Returns the user fullname to display as document title
*
* @param \core_search\document $doc
* @return string User fullname
*/
public function get_document_display_title(\core_search\document $doc) {
$user = $this->get_user($doc->get('itemid'));
return fullname($user);
}
/**
* Get user based on role assignment id
*
* @param int $itemid role assignment id
* @return mixed
*/
private function get_user($itemid) {
global $DB;
$sql = "SELECT u.*
FROM {user} u
JOIN {role_assignments} ra
ON ra.userid = u.id
WHERE ra.id = :raid";
return $DB->get_record_sql($sql, array('raid' => $itemid));
}
/**
* Returns a list of category names associated with the area.
*
* @return array
*/
public function get_category_names() {
return [\core_search\manager::SEARCH_AREA_CATEGORY_ALL, \core_search\manager::SEARCH_AREA_CATEGORY_USERS];
}
/**
* Link to the teacher in the course
*
* @param \core_search\document $doc the document
* @return \moodle_url
*/
public function get_doc_url(\core_search\document $doc) {
return $this->get_context_url($doc);
}
}
+228
View File
@@ -0,0 +1,228 @@
<?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/>.
/**
* Search area for Users for whom I have authority to view profile.
*
* @package core_user
* @copyright 2016 Devang Gaur {@link http://www.devanggaur.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_user\search;
require_once($CFG->dirroot . '/user/lib.php');
defined('MOODLE_INTERNAL') || die();
/**
* Search area for Users for whom I have access to view profile.
*
* @package core_user
* @copyright 2016 Devang Gaur {@link http://www.devanggaur.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user extends \core_search\base {
/**
* Returns recordset containing required data attributes for indexing.
*
* @param number $modifiedfrom
* @param \context|null $context Optional context to restrict scope of returned results
* @return \moodle_recordset|null Recordset (or null if no results)
*/
public function get_document_recordset($modifiedfrom = 0, \context $context = null) {
global $DB;
// Prepare query conditions.
$where = 'timemodified >= ? AND deleted = ? AND confirmed = ?';
$params = [$modifiedfrom, 0, 1];
// Handle context types.
if (!$context) {
$context = \context_system::instance();
}
switch ($context->contextlevel) {
case CONTEXT_MODULE:
case CONTEXT_BLOCK:
case CONTEXT_COURSE:
case CONTEXT_COURSECAT:
// These contexts cannot contain any users.
return null;
case CONTEXT_USER:
// Restrict to specific user.
$where .= ' AND id = ?';
$params[] = $context->instanceid;
break;
case CONTEXT_SYSTEM:
break;
default:
throw new \coding_exception('Unexpected contextlevel: ' . $context->contextlevel);
}
return $DB->get_recordset_select('user', $where, $params);
}
/**
* Returns document instances for each record in the recordset.
*
* @param \stdClass $record
* @param array $options
* @return \core_search\document
*/
public function get_document($record, $options = array()) {
$context = \context_system::instance();
// Prepare associative array with data from DB.
$doc = \core_search\document_factory::instance($record->id, $this->componentname, $this->areaname);
// Include all alternate names in title.
$array = [];
foreach (\core_user\fields::get_name_fields(true) as $field) {
$array[$field] = $record->$field;
}
$fullusername = join(' ', $array);
// Assigning properties to our document.
$doc->set('title', content_to_text($fullusername, false));
$doc->set('contextid', $context->id);
$doc->set('courseid', SITEID);
$doc->set('itemid', $record->id);
$doc->set('modified', $record->timemodified);
$doc->set('owneruserid', \core_search\manager::NO_OWNER_ID);
$doc->set('content', content_to_text($record->description, $record->descriptionformat));
// Check if this document should be considered new.
if (isset($options['lastindexedtime']) && $options['lastindexedtime'] < $record->timecreated) {
// If the document was created after the last index time, it must be new.
$doc->set_is_new(true);
}
return $doc;
}
/**
* Returns the user fullname to display as document title
*
* @param \core_search\document $doc
* @return string User fullname
*/
public function get_document_display_title(\core_search\document $doc) {
$user = \core_user::get_user($doc->get('itemid'));
return fullname($user);
}
/**
* Checking whether I can access a document
*
* @param int $id user id
* @return int
*/
public function check_access($id) {
global $DB, $USER;
$user = $DB->get_record('user', array('id' => $id));
if (!$user || $user->deleted) {
return \core_search\manager::ACCESS_DELETED;
}
if (user_can_view_profile($user)) {
return \core_search\manager::ACCESS_GRANTED;
}
return \core_search\manager::ACCESS_DENIED;
}
/**
* Returns a url to the profile page of user.
*
* @param \core_search\document $doc
* @return \moodle_url
*/
public function get_doc_url(\core_search\document $doc) {
return $this->get_context_url($doc);
}
/**
* Returns a url to the document context.
*
* @param \core_search\document $doc
* @return \moodle_url
*/
public function get_context_url(\core_search\document $doc) {
return new \moodle_url('/user/profile.php', array('id' => $doc->get('itemid')));
}
/**
* Returns true if this area uses file indexing.
*
* @return bool
*/
public function uses_file_indexing() {
return true;
}
/**
* Return the context info required to index files for
* this search area.
*
* Should be onerridden by each search area.
*
* @return array
*/
public function get_search_fileareas() {
$fileareas = array(
'profile' // Fileareas.
);
return $fileareas;
}
/**
* Returns the moodle component name.
*
* It might be the plugin name (whole frankenstyle name) or the core subsystem name.
*
* @return string
*/
public function get_component_name() {
return 'user';
}
/**
* Returns an icon instance for the document.
*
* @param \core_search\document $doc
*
* @return \core_search\document_icon
*/
public function get_doc_icon(\core_search\document $doc): \core_search\document_icon {
return new \core_search\document_icon('i/user');
}
/**
* Returns a list of category names associated with the area.
*
* @return array
*/
public function get_category_names() {
return [\core_search\manager::SEARCH_AREA_CATEGORY_USERS];
}
}
+499
View File
@@ -0,0 +1,499 @@
<?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/>.
/**
* Contains the class used for the displaying the participants table.
*
* @package core_user
* @copyright 2017 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types=1);
namespace core_user\table;
use DateTime;
use context;
use core_table\dynamic as dynamic_table;
use core_table\local\filter\filterset;
use core_user\output\status_field;
use core_user\table\participants_search;
use moodle_url;
defined('MOODLE_INTERNAL') || die;
global $CFG;
require_once($CFG->libdir . '/tablelib.php');
require_once($CFG->dirroot . '/user/lib.php');
/**
* Class for the displaying the participants table.
*
* @package core_user
* @copyright 2017 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class participants extends \table_sql implements dynamic_table {
/**
* @var int $courseid The course id
*/
protected $courseid;
/**
* @var string[] The list of countries.
*/
protected $countries;
/**
* @var \stdClass[] The list of groups with membership info for the course.
*/
protected $groups;
/**
* @var string[] Extra fields to display.
*/
protected $extrafields;
/**
* @var \stdClass $course The course details.
*/
protected $course;
/**
* @var context $context The course context.
*/
protected $context;
/**
* @var \stdClass[] List of roles indexed by roleid.
*/
protected $allroles;
/**
* @var \stdClass[] List of roles indexed by roleid.
*/
protected $allroleassignments;
/**
* @var \stdClass[] Assignable roles in this course.
*/
protected $assignableroles;
/**
* @var \stdClass[] Profile roles in this course.
*/
protected $profileroles;
/**
* @var filterset Filterset describing which participants to include.
*/
protected $filterset;
/** @var \stdClass[] $viewableroles */
private $viewableroles;
/** @var moodle_url $baseurl The base URL for the report. */
public $baseurl;
/**
* Render the participants table.
*
* @param int $pagesize Size of page for paginated displayed table.
* @param bool $useinitialsbar Whether to use the initials bar which will only be used if there is a fullname column defined.
* @param string $downloadhelpbutton
*/
public function out($pagesize, $useinitialsbar, $downloadhelpbutton = '') {
global $CFG, $OUTPUT, $PAGE;
// Define the headers and columns.
$headers = [];
$columns = [];
// At the very least, the user viewing this table will be able to use bulk actions to export it, so add 'select' column.
$mastercheckbox = new \core\output\checkbox_toggleall('participants-table', true, [
'id' => 'select-all-participants',
'name' => 'select-all-participants',
'label' => get_string('selectall'),
'labelclasses' => 'sr-only',
'classes' => 'm-1',
'checked' => false,
]);
$headers[] = $OUTPUT->render($mastercheckbox);
$columns[] = 'select';
$headers[] = get_string('fullname');
$columns[] = 'fullname';
$extrafields = \core_user\fields::get_identity_fields($this->context);
foreach ($extrafields as $field) {
$headers[] = \core_user\fields::get_display_name($field);
$columns[] = $field;
}
$headers[] = get_string('roles');
$columns[] = 'roles';
// Get the list of fields we have to hide.
$hiddenfields = array();
if (!has_capability('moodle/course:viewhiddenuserfields', $this->context)) {
$hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
}
// Add column for groups if the user can view them.
$canseegroups = !isset($hiddenfields['groups']);
if ($canseegroups) {
$headers[] = get_string('groups');
$columns[] = 'groups';
}
// Do not show the columns if it exists in the hiddenfields array.
if (!isset($hiddenfields['lastaccess'])) {
if ($this->courseid == SITEID) {
$headers[] = get_string('lastsiteaccess');
} else {
$headers[] = get_string('lastcourseaccess');
}
$columns[] = 'lastaccess';
}
$canreviewenrol = has_capability('moodle/course:enrolreview', $this->context);
if ($canreviewenrol && $this->courseid != SITEID) {
$columns[] = 'status';
$headers[] = get_string('participationstatus', 'enrol');
$this->no_sorting('status');
};
$this->define_columns($columns);
$this->define_headers($headers);
// The name column is a header.
$this->define_header_column('fullname');
// Make this table sorted by last name by default.
$this->sortable(true, 'lastname');
$this->no_sorting('select');
$this->no_sorting('roles');
if ($canseegroups) {
$this->no_sorting('groups');
}
$this->set_default_per_page(20);
$this->set_attribute('id', 'participants');
$this->countries = get_string_manager()->get_list_of_countries(true);
$this->extrafields = $extrafields;
if ($canseegroups) {
$this->groups = groups_get_all_groups($this->courseid, 0, 0, 'g.*', true);
}
// If user has capability to review enrol, show them both role names.
$allrolesnamedisplay = ($canreviewenrol ? ROLENAME_BOTH : ROLENAME_ALIAS);
$this->allroles = role_fix_names(get_all_roles($this->context), $this->context, $allrolesnamedisplay);
$this->assignableroles = get_assignable_roles($this->context, ROLENAME_BOTH, false);
$this->profileroles = get_profile_roles($this->context);
$this->viewableroles = get_viewable_roles($this->context);
parent::out($pagesize, $useinitialsbar, $downloadhelpbutton);
if (has_capability('moodle/course:enrolreview', $this->context)) {
$params = [
'contextid' => $this->context->id,
'uniqueid' => $this->uniqueid,
];
$PAGE->requires->js_call_amd('core_user/status_field', 'init', [$params]);
}
}
/**
* Generate the select column.
*
* @param \stdClass $data
* @return string
*/
public function col_select($data) {
global $OUTPUT;
$checkbox = new \core\output\checkbox_toggleall('participants-table', false, [
'classes' => 'usercheckbox m-1',
'id' => 'user' . $data->id,
'name' => 'user' . $data->id,
'checked' => false,
'label' => get_string('selectitem', 'moodle', fullname($data)),
'labelclasses' => 'accesshide',
]);
return $OUTPUT->render($checkbox);
}
/**
* Generate the fullname column.
*
* @param \stdClass $data
* @return string
*/
public function col_fullname($data) {
global $OUTPUT;
return $OUTPUT->render(\core_user::get_profile_picture($data, null,
['courseid' => $this->course->id, 'includefullname' => true]));
}
/**
* User roles column.
*
* @param \stdClass $data
* @return string
*/
public function col_roles($data) {
global $OUTPUT;
$roles = isset($this->allroleassignments[$data->id]) ? $this->allroleassignments[$data->id] : [];
$editable = new \core_user\output\user_roles_editable($this->course,
$this->context,
$data,
$this->allroles,
$this->assignableroles,
$this->profileroles,
$roles,
$this->viewableroles);
return $OUTPUT->render_from_template('core/inplace_editable', $editable->export_for_template($OUTPUT));
}
/**
* Generate the groups column.
*
* @param \stdClass $data
* @return string
*/
public function col_groups($data) {
global $OUTPUT;
$usergroups = [];
foreach ($this->groups as $coursegroup) {
if (isset($coursegroup->members[$data->id])) {
$usergroups[] = $coursegroup->id;
}
}
$editable = new \core_group\output\user_groups_editable($this->course, $this->context, $data, $this->groups, $usergroups);
return $OUTPUT->render_from_template('core/inplace_editable', $editable->export_for_template($OUTPUT));
}
/**
* Generate the country column.
*
* @param \stdClass $data
* @return string
*/
public function col_country($data) {
if (!empty($this->countries[$data->country])) {
return $this->countries[$data->country];
}
return '';
}
/**
* Generate the last access column.
*
* @param \stdClass $data
* @return string
*/
public function col_lastaccess($data) {
if ($data->lastaccess) {
return format_time(time() - $data->lastaccess);
}
return get_string('never');
}
/**
* Generate the status column.
*
* @param \stdClass $data The data object.
* @return string
*/
public function col_status($data) {
global $CFG, $OUTPUT, $PAGE;
$enrolstatusoutput = '';
$canreviewenrol = has_capability('moodle/course:enrolreview', $this->context);
if ($canreviewenrol) {
$canviewfullnames = has_capability('moodle/site:viewfullnames', $this->context);
$fullname = htmlspecialchars(fullname($data, $canviewfullnames), ENT_QUOTES, 'utf-8');
$coursename = format_string($this->course->fullname, true, array('context' => $this->context));
require_once($CFG->dirroot . '/enrol/locallib.php');
$manager = new \course_enrolment_manager($PAGE, $this->course);
$userenrolments = $manager->get_user_enrolments($data->id);
foreach ($userenrolments as $ue) {
$timestart = $ue->timestart;
$timeend = $ue->timeend;
$timeenrolled = $ue->timecreated;
$actions = $ue->enrolmentplugin->get_user_enrolment_actions($manager, $ue);
$instancename = $ue->enrolmentinstancename;
// Default status field label and value.
$status = get_string('participationactive', 'enrol');
$statusval = status_field::STATUS_ACTIVE;
switch ($ue->status) {
case ENROL_USER_ACTIVE:
$currentdate = new DateTime();
$now = $currentdate->getTimestamp();
$isexpired = $timestart > $now || ($timeend > 0 && $timeend < $now);
$enrolmentdisabled = $ue->enrolmentinstance->status == ENROL_INSTANCE_DISABLED;
// If user enrolment status has not yet started/already ended or the enrolment instance is disabled.
if ($isexpired || $enrolmentdisabled) {
$status = get_string('participationnotcurrent', 'enrol');
$statusval = status_field::STATUS_NOT_CURRENT;
}
break;
case ENROL_USER_SUSPENDED:
$status = get_string('participationsuspended', 'enrol');
$statusval = status_field::STATUS_SUSPENDED;
break;
}
$statusfield = new status_field($instancename, $coursename, $fullname, $status, $timestart, $timeend,
$actions, $timeenrolled);
$statusfielddata = $statusfield->set_status($statusval)->export_for_template($OUTPUT);
$enrolstatusoutput .= $OUTPUT->render_from_template('core_user/status_field', $statusfielddata);
}
}
return $enrolstatusoutput;
}
/**
* This function is used for the extra user fields.
*
* These are being dynamically added to the table so there are no functions 'col_<userfieldname>' as
* the list has the potential to increase in the future and we don't want to have to remember to add
* a new method to this class. We also don't want to pollute this class with unnecessary methods.
*
* @param string $colname The column name
* @param \stdClass $data
* @return string
*/
public function other_cols($colname, $data) {
// Do not process if it is not a part of the extra fields.
if (!in_array($colname, $this->extrafields)) {
return '';
}
return s($data->{$colname});
}
/**
* Query the database for results to display in the table.
*
* @param int $pagesize size of page for paginated displayed table.
* @param bool $useinitialsbar do you want to use the initials bar.
*/
public function query_db($pagesize, $useinitialsbar = true) {
list($twhere, $tparams) = $this->get_sql_where();
$psearch = new participants_search($this->course, $this->context, $this->filterset);
$total = $psearch->get_total_participants_count($twhere, $tparams);
$this->pagesize($pagesize, $total);
$sort = $this->get_sql_sort();
if ($sort) {
$sort = 'ORDER BY ' . $sort;
}
$rawdata = $psearch->get_participants($twhere, $tparams, $sort, $this->get_page_start(), $this->get_page_size());
$this->rawdata = [];
foreach ($rawdata as $user) {
$this->rawdata[$user->id] = $user;
}
$rawdata->close();
if ($this->rawdata) {
$this->allroleassignments = get_users_roles($this->context, array_keys($this->rawdata),
true, 'c.contextlevel DESC, r.sortorder ASC');
} else {
$this->allroleassignments = [];
}
// Set initial bars.
if ($useinitialsbar) {
$this->initialbars(true);
}
}
/**
* Override the table show_hide_link to not show for select column.
*
* @param string $column the column name, index into various names.
* @param int $index numerical index of the column.
* @return string HTML fragment.
*/
protected function show_hide_link($column, $index) {
if ($index > 0) {
return parent::show_hide_link($column, $index);
}
return '';
}
/**
* Set filters and build table structure.
*
* @param filterset $filterset The filterset object to get the filters from.
*/
public function set_filterset(filterset $filterset): void {
// Get the context.
$this->courseid = $filterset->get_filter('courseid')->current();
$this->course = get_course($this->courseid);
$this->context = \context_course::instance($this->courseid, MUST_EXIST);
// Process the filterset.
parent::set_filterset($filterset);
}
/**
* Guess the base url for the participants table.
*/
public function guess_base_url(): void {
$this->baseurl = new moodle_url('/user/index.php', ['id' => $this->courseid]);
}
/**
* Get the context of the current table.
*
* Note: This function should not be called until after the filterset has been provided.
*
* @return context
*/
public function get_context(): context {
return $this->context;
}
/**
* Check if the user has the capability to access this table.
*
* @return bool Return true if capability check passed.
*/
public function has_capability(): bool {
global $CFG;
require_once($CFG->dirroot . '/course/lib.php');
$context = $this->course->id == SITEID ? \context_system::instance() : $this->get_context();
return course_can_view_participants($context);
}
}
@@ -0,0 +1,80 @@
<?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/>.
/**
* Participants table filterset.
*
* @package core
* @category table
* @copyright 2020 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types=1);
namespace core_user\table;
use core_table\local\filter\filterset;
use core_table\local\filter\integer_filter;
use core_table\local\filter\string_filter;
/**
* Participants table filterset.
*
* @package core
* @copyright 2020 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class participants_filterset extends filterset {
/**
* Get the required filters.
*
* The only required filter is the courseid filter.
*
* @return array.
*/
public function get_required_filters(): array {
return [
'courseid' => integer_filter::class,
];
}
/**
* Get the optional filters.
*
* These are:
* - accesssince;
* - enrolments;
* - groups;
* - keywords;
* - country;
* - roles; and
* - status.
*
* @return array
*/
public function get_optional_filters(): array {
return [
'accesssince' => integer_filter::class,
'enrolments' => integer_filter::class,
'groups' => integer_filter::class,
'keywords' => string_filter::class,
'country' => string_filter::class,
'roles' => integer_filter::class,
'status' => integer_filter::class,
];
}
}
File diff suppressed because it is too large Load Diff