░▒▓████████████████████████████████▓▒░ ░▒▓█ ▓▒░ ░▒▓█ ~ S W A M P ~ ▓▒░ ░▒▓█ ▓▒░ ░▒▓████████████████████████████████▓▒░
pplmvsvm
LOCATION:
/var/www/moodle/public/lib/classes/hub
☗ ROOT
↻ REFRESH
✎ CARVE FLESH
EDITING: registration.php
<?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Class registration * * @package core * @copyright 2017 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\hub; defined('MOODLE_INTERNAL') || die(); use moodle_exception; use moodle_url; use context_system; use stdClass; use html_writer; use core_plugin_manager; /** * Methods to use when registering the site at the moodle sites directory. * * @package core * @copyright 2017 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class registration { /** @var array Fields used in a site registration form. * IMPORTANT: any new fields with non-empty defaults have to be added to CONFIRM_NEW_FIELDS */ const FORM_FIELDS = ['policyagreed', 'language', 'countrycode', 'privacy', 'contactemail', 'emailalert', 'emailalertemail', 'commnews', 'commnewsemail', 'contactname', 'name', 'description', 'imageurl', 'contactphone', 'regioncode', 'geolocation', 'street', 'organisationtype', 'commnewsfirstname', 'commnewslastname']; /** @var array List of new FORM_FIELDS or siteinfo fields added indexed by the version when they were added. * If site was already registered, admin will be promted to confirm new registration data manually. Until registration is manually confirmed, * the scheduled task updating registration will be paused. * Keys of this array are not important as long as they increment, use current date to avoid confusions. */ const CONFIRM_NEW_FIELDS = [ 2017092200 => [ 'commnews', // Receive communication news. This was added in 3.4 and is "On" by default. Admin must confirm or opt-out. 'mobileservicesenabled', 'mobilenotificationsenabled', 'registereduserdevices', 'registeredactiveuserdevices' // Mobile stats added in 3.4. ], // Analytics stats added in Moodle 3.7. 2019022200 => ['analyticsenabledmodels', 'analyticspredictions', 'analyticsactions', 'analyticsactionsnotuseful'], // Active users stats added in Moodle 3.9. 2020022600 => ['activeusers', 'activeparticipantnumberaverage'], // Database type, course date info, site theme, primary auth type added in Moodle 4.2. 2023021700 => ['dbtype', 'coursesnodates', 'sitetheme', 'primaryauthtype'], // Plugin usage added in Moodle 4.5. 2023072300 => ['pluginusage'], // AI usage added in Moodle 4.5. 2023081200 => ['aiusage'], ]; /** @var string Site privacy: not displayed */ const HUB_SITENOTPUBLISHED = 'notdisplayed'; /** @var string Site privacy: public */ const HUB_SITENAMEPUBLISHED = 'named'; /** @var string Site privacy: public and global */ const HUB_SITELINKPUBLISHED = 'linked'; /** @var stdClass cached site registration information */ protected static $registration = null; /** * Get site registration * * @param bool $confirmed * @return stdClass|null */ protected static function get_registration($confirmed = true) { global $DB; if (self::$registration === null) { self::$registration = $DB->get_record('registration_hubs', ['huburl' => HUB_MOODLEORGHUBURL]) ?: null; } if (self::$registration && (bool)self::$registration->confirmed == (bool)$confirmed) { return self::$registration; } return null; } /** * Same as get_registration except it throws exception if site not registered * * @return stdClass * @throws \moodle_exception */ public static function require_registration() { if ($registration = self::get_registration()) { return $registration; } if (has_capability('moodle/site:config', context_system::instance())) { throw new moodle_exception('registrationwarning', 'admin', new moodle_url('/admin/registration/index.php')); } else { throw new moodle_exception('registrationwarningcontactadmin', 'admin'); } } /** * Checks if site is registered * * @return bool */ public static function is_registered() { return self::get_registration() ? true : false; } /** * Returns registration token * * @param int $strictness if set to MUST_EXIST and site is not registered will throw an exception * @return null * @throws moodle_exception */ public static function get_token($strictness = IGNORE_MISSING) { if ($strictness == MUST_EXIST) { $registration = self::require_registration(); } else if (!$registration = self::get_registration()) { return null; } return $registration->token; } /** * Returns registration secret. * * @return string */ public static function get_secret(): string { if ($registration = self::get_registration()) { return $registration->secret; } return ''; } /** * When was the registration last updated * * @return int|null timestamp or null if site is not registered */ public static function get_last_updated() { if ($registration = self::get_registration()) { return $registration->timemodified; } return null; } /** * Calculates and prepares site information to send to the sites directory as a part of registration. * * @param array $defaults default values for inputs in the registration form (if site was never registered before) * @return array site info */ public static function get_site_info($defaults = []) { global $CFG, $DB; require_once($CFG->libdir . '/badgeslib.php'); require_once($CFG->dirroot . "/course/lib.php"); $siteinfo = array(); foreach (self::FORM_FIELDS as $field) { $siteinfo[$field] = get_config('hub', 'site_'.$field); if ($siteinfo[$field] === false) { $siteinfo[$field] = array_key_exists($field, $defaults) ? $defaults[$field] : null; } } // Statistical data. $siteinfo['courses'] = $DB->count_records('course') - 1; $siteinfo['users'] = $DB->count_records('user', array('deleted' => 0)); $siteinfo['activeusers'] = $DB->count_records_select('user', 'deleted = ? AND lastlogin > ?', [0, time() - DAYSECS * 30]); $siteinfo['enrolments'] = $DB->count_records('role_assignments'); $siteinfo['posts'] = $DB->count_records('forum_posts'); $siteinfo['questions'] = $DB->count_records('question'); $siteinfo['resources'] = $DB->count_records('resource'); $siteinfo['badges'] = $DB->count_records_select('badge', 'status <> ' . BADGE_STATUS_ARCHIVED); $siteinfo['issuedbadges'] = $DB->count_records('badge_issued'); $siteinfo['participantnumberaverage'] = average_number_of_participants(); $siteinfo['activeparticipantnumberaverage'] = average_number_of_participants(true, time() - DAYSECS * 30); $siteinfo['modulenumberaverage'] = average_number_of_courses_modules(); $siteinfo['dbtype'] = $CFG->dbtype; $siteinfo['coursesnodates'] = $DB->count_records_select('course', 'enddate = ?', [0]) - 1; $siteinfo['sitetheme'] = get_config('core', 'theme'); $siteinfo['pluginusage'] = json_encode(self::get_plugin_usage_data()); // AI usage data. $aiusagedata = self::get_ai_usage_data(); $siteinfo['aiusage'] = !empty($aiusagedata) ? json_encode($aiusagedata) : ''; // Primary auth type. $primaryauthsql = 'SELECT auth, count(auth) as tc FROM {user} GROUP BY auth ORDER BY tc DESC'; $siteinfo['primaryauthtype'] = $DB->get_field_sql($primaryauthsql, null, IGNORE_MULTIPLE); // Version and url. $siteinfo['moodlerelease'] = $CFG->release; $siteinfo['url'] = $CFG->wwwroot; // Mobile related information. $siteinfo['mobileservicesenabled'] = 0; $siteinfo['mobilenotificationsenabled'] = 0; $siteinfo['registereduserdevices'] = 0; $siteinfo['registeredactiveuserdevices'] = 0; if (!empty($CFG->enablewebservices) && !empty($CFG->enablemobilewebservice)) { $siteinfo['mobileservicesenabled'] = 1; $siteinfo['registereduserdevices'] = $DB->count_records('user_devices'); $airnotifierextpath = $CFG->dirroot . '/message/output/airnotifier/externallib.php'; if (file_exists($airnotifierextpath)) { // Maybe some one uninstalled the plugin. require_once($airnotifierextpath); $siteinfo['mobilenotificationsenabled'] = \message_airnotifier_external::is_system_configured(); $siteinfo['registeredactiveuserdevices'] = $DB->count_records('message_airnotifier_devices', array('enable' => 1)); } } // Analytics related data follow. $siteinfo['analyticsenabledmodels'] = \core_analytics\stats::enabled_models(); $siteinfo['analyticspredictions'] = \core_analytics\stats::predictions(); $siteinfo['analyticsactions'] = \core_analytics\stats::actions(); $siteinfo['analyticsactionsnotuseful'] = \core_analytics\stats::actions_not_useful(); // IMPORTANT: any new fields in siteinfo have to be added to the constant CONFIRM_NEW_FIELDS. return $siteinfo; } /** * Human-readable summary of data that will be sent to the sites directory. * * @param array $siteinfo result of get_site_info() * @return string */ public static function get_stats_summary($siteinfo) { global $OUTPUT; $fieldsneedconfirm = self::get_new_registration_fields(); $summary = html_writer::tag('p', get_string('sendfollowinginfo_help', 'hub')) . html_writer::start_tag('ul'); $mobileservicesenabled = $siteinfo['mobileservicesenabled'] ? get_string('yes') : get_string('no'); $mobilenotificationsenabled = $siteinfo['mobilenotificationsenabled'] ? get_string('yes') : get_string('no'); $moodlerelease = $siteinfo['moodlerelease']; if (preg_match('/^(\d+\.\d.*?)[\. ]/', $moodlerelease, $matches)) { $moodlerelease = $matches[1]; } $pluginusagelinks = [ 'overview' => new moodle_url('/admin/plugins.php'), 'activities' => new moodle_url('/admin/modules.php'), 'blocks' => new moodle_url('/admin/blocks.php'), ]; $senddata = [ 'moodlerelease' => get_string('sitereleasenum', 'hub', $moodlerelease), 'courses' => get_string('coursesnumber', 'hub', $siteinfo['courses']), 'users' => get_string('usersnumber', 'hub', $siteinfo['users']), 'activeusers' => get_string('activeusersnumber', 'hub', $siteinfo['activeusers']), 'enrolments' => get_string('roleassignmentsnumber', 'hub', $siteinfo['enrolments']), 'posts' => get_string('postsnumber', 'hub', $siteinfo['posts']), 'questions' => get_string('questionsnumber', 'hub', $siteinfo['questions']), 'resources' => get_string('resourcesnumber', 'hub', $siteinfo['resources']), 'badges' => get_string('badgesnumber', 'hub', $siteinfo['badges']), 'issuedbadges' => get_string('issuedbadgesnumber', 'hub', $siteinfo['issuedbadges']), 'participantnumberaverage' => get_string('participantnumberaverage', 'hub', format_float($siteinfo['participantnumberaverage'], 2)), 'activeparticipantnumberaverage' => get_string('activeparticipantnumberaverage', 'hub', format_float($siteinfo['activeparticipantnumberaverage'], 2)), 'modulenumberaverage' => get_string('modulenumberaverage', 'hub', format_float($siteinfo['modulenumberaverage'], 2)), 'mobileservicesenabled' => get_string('mobileservicesenabled', 'hub', $mobileservicesenabled), 'mobilenotificationsenabled' => get_string('mobilenotificationsenabled', 'hub', $mobilenotificationsenabled), 'registereduserdevices' => get_string('registereduserdevices', 'hub', $siteinfo['registereduserdevices']), 'registeredactiveuserdevices' => get_string('registeredactiveuserdevices', 'hub', $siteinfo['registeredactiveuserdevices']), 'analyticsenabledmodels' => get_string('analyticsenabledmodels', 'hub', $siteinfo['analyticsenabledmodels']), 'analyticspredictions' => get_string('analyticspredictions', 'hub', $siteinfo['analyticspredictions']), 'analyticsactions' => get_string('analyticsactions', 'hub', $siteinfo['analyticsactions']), 'analyticsactionsnotuseful' => get_string('analyticsactionsnotuseful', 'hub', $siteinfo['analyticsactionsnotuseful']), 'dbtype' => get_string('dbtype', 'hub', $siteinfo['dbtype']), 'coursesnodates' => get_string('coursesnodates', 'hub', $siteinfo['coursesnodates']), 'sitetheme' => get_string('sitetheme', 'hub', $siteinfo['sitetheme']), 'primaryauthtype' => get_string('primaryauthtype', 'hub', $siteinfo['primaryauthtype']), 'pluginusage' => get_string('pluginusagedata', 'hub', $pluginusagelinks), 'aiusage' => $OUTPUT->render_from_template('core/ai_usage_data', ['aiusagedata' => self::show_ai_usage()]), ]; foreach ($senddata as $key => $str) { $class = in_array($key, $fieldsneedconfirm) ? ' needsconfirmation mark' : ''; $summary .= html_writer::tag('li', $str, ['class' => 'site' . $key . $class]); } $summary .= html_writer::end_tag('ul'); return $summary; } /** * Save registration info locally so it can be retrieved when registration needs to be updated * * @param stdClass $formdata data from {@link site_registration_form} */ public static function save_site_info($formdata) { foreach (self::FORM_FIELDS as $field) { set_config('site_' . $field, $formdata->$field, 'hub'); } // Even if the connection with the sites directory fails, admin has manually submitted the form which means they don't need // to be redirected to the site registration page any more. set_config('site_regupdateversion', max(array_keys(self::CONFIRM_NEW_FIELDS)), 'hub'); } /** * Updates site registration when "Update reigstration" button is clicked by admin */ public static function update_manual() { global $DB; if (!$registration = self::get_registration()) { return false; } $siteinfo = self::get_site_info(); try { api::update_registration($siteinfo); } catch (moodle_exception $e) { if (!self::is_registered()) { // Token was rejected during registration update and site and locally stored token was reset, // proceed to site registration. This method will redirect away. self::register(''); } \core\notification::add(get_string('errorregistrationupdate', 'hub', $e->getMessage()), \core\output\notification::NOTIFY_ERROR); return false; } $DB->update_record('registration_hubs', ['id' => $registration->id, 'timemodified' => time()]); \core\notification::add(get_string('siteregistrationupdated', 'hub'), \core\output\notification::NOTIFY_SUCCESS); self::$registration = null; return true; } /** * Updates site registration via cron * * @throws moodle_exception */ public static function update_cron() { global $DB; if (!$registration = self::get_registration()) { mtrace(get_string('registrationwarning', 'admin')); return; } if (self::get_new_registration_fields()) { mtrace(get_string('pleaserefreshregistrationnewdata', 'admin')); return; } $siteinfo = self::get_site_info(); api::update_registration($siteinfo); $DB->update_record('registration_hubs', ['id' => $registration->id, 'timemodified' => time()]); mtrace(get_string('siteregistrationupdated', 'hub')); self::$registration = null; } /** * Confirms registration by the sites directory. * * @param string $token * @param string $newtoken * @param string $hubname * @throws moodle_exception */ public static function confirm_registration($token, $newtoken, $hubname) { global $DB; $registration = self::get_registration(false); if (!$registration || $registration->token !== $token) { throw new moodle_exception('wrongtoken', 'hub', new moodle_url('/admin/registration/index.php')); } // Update hub information of the site. $record = ['id' => $registration->id]; $record['token'] = $newtoken; $record['confirmed'] = 1; $record['hubname'] = $hubname; $record['timemodified'] = time(); $DB->update_record('registration_hubs', $record); self::$registration = null; $siteinfo = self::get_site_info(); if (strlen(http_build_query($siteinfo)) > 1800) { // Update registration again because the initial request was too long and could have been truncated. api::update_registration($siteinfo); self::$registration = null; } // Finally, allow other plugins to perform actions once a site is registered for first time. $pluginsfunction = get_plugins_with_function('post_site_registration_confirmed'); foreach ($pluginsfunction as $plugins) { foreach ($plugins as $pluginfunction) { $pluginfunction($registration->id); } } } /** * Retrieve the options for site privacy form element to use in registration form * @return array */ public static function site_privacy_options() { return [ self::HUB_SITENOTPUBLISHED => get_string('siteprivacynotpublished', 'hub'), self::HUB_SITENAMEPUBLISHED => get_string('siteprivacypublished', 'hub'), self::HUB_SITELINKPUBLISHED => get_string('siteprivacylinked', 'hub') ]; } /** * Get the options for organisation type form element to use in registration form. * * Indexes reference Moodle internal ids and should not be changed. * * @return array */ public static function get_site_organisation_type_options(): array { return [ 1 => get_string('siteorganisationtype:wholeuniversity', 'hub'), 2 => get_string('siteorganisationtype:universitydepartment', 'hub'), 3 => get_string('siteorganisationtype:college', 'hub'), 4 => get_string('siteorganisationtype:collegedepartment', 'hub'), 5 => get_string('siteorganisationtype:highschool', 'hub'), 6 => get_string('siteorganisationtype:highschooldepartment', 'hub'), 7 => get_string('siteorganisationtype:primaryschool', 'hub'), 8 => get_string('siteorganisationtype:independentteacher', 'hub'), 9 => get_string('siteorganisationtype:companyinternal', 'hub'), 10 => get_string('siteorganisationtype:companydepartment', 'hub'), 11 => get_string('siteorganisationtype:commercialcourseprovider', 'hub'), 12 => get_string('siteorganisationtype:other', 'hub'), 13 => get_string('siteorganisationtype:highschooldistrict', 'hub'), 14 => get_string('siteorganisationtype:government', 'hub'), 16 => get_string('siteorganisationtype:charityornotforprofit', 'hub'), 17 => get_string('siteorganisationtype:charterschool', 'hub'), 18 => get_string('siteorganisationtype:schooldistrict', 'hub'), 19 => get_string('siteorganisationtype:hospital', 'hub'), ]; } /** * Registers a site * * This method will make sure that unconfirmed registration record is created and then redirect to * registration script on the sites directory. * The sites directory will check that the site is accessible, register it and redirect back * to /admin/registration/confirmregistration.php * * @param string $returnurl * @throws \coding_exception */ public static function register($returnurl) { global $DB, $SESSION; // We should also check if the url is registered in the hub. if (self::is_registered() && api::is_site_registered_in_hub()) { // Caller of this method must make sure that site is not registered. throw new \coding_exception('Site already registered'); } // Delete 'confirmed' registrations. $DB->delete_records('registration_hubs', ['confirmed' => 1]); // Get 'unconfirmed' registration. $hub = self::get_registration(false); if (empty($hub)) { // Create a new record in 'registration_hubs'. $hub = new stdClass(); // Let's add date('Ymdhis') to make the token unique. $hub->token = get_site_identifier() . date('Ymdhis'); // Secret is identical to token until registration confirmed (confirmregistration.php). $hub->secret = $hub->token; $hub->huburl = HUB_MOODLEORGHUBURL; $hub->hubname = 'moodle'; $hub->confirmed = 0; $hub->timemodified = time(); $hub->id = $DB->insert_record('registration_hubs', $hub); self::$registration = null; } $params = self::get_site_info(); // The most conservative limit for the redirect URL length is 2000 characters. Only pass parameters before // we reach this limit. The next registration update will update all fields. // We will also update registration after we receive confirmation from moodle.net. $url = new moodle_url(HUB_MOODLEORGHUBURL . '/local/hub/siteregistration.php', ['token' => $hub->token, 'url' => $params['url']]); foreach ($params as $key => $value) { if (strlen($url->out(false, [$key => $value])) > 2000) { break; } $url->param($key, $value); } $SESSION->registrationredirect = $returnurl; redirect($url); } /** * Unregister site * * @param bool $unpublishalladvertisedcourses * @param bool $unpublishalluploadedcourses * @return bool */ public static function unregister($unpublishalladvertisedcourses, $unpublishalluploadedcourses) { global $DB; if (!$hub = self::get_registration()) { return true; } // Course unpublish went ok, unregister the site now. try { api::unregister_site(); } catch (moodle_exception $e) { \core\notification::add(get_string('unregistrationerror', 'hub', $e->getMessage()), \core\output\notification::NOTIFY_ERROR); return false; } // Unset saved site info. foreach (self::FORM_FIELDS as $field) { set_config('site_' . $field, null, 'hub'); } $DB->delete_records('registration_hubs', array('id' => $hub->id)); self::$registration = null; return true; } /** * Resets the registration token without changing site identifier so site can be re-registered * * @return bool */ public static function reset_token() { global $DB; if (!$hub = self::get_registration()) { return true; } $DB->delete_records('registration_hubs', array('id' => $hub->id)); self::$registration = null; } /** * Generate a new token for the site that is not registered * * @param string $token * @throws moodle_exception */ public static function reset_site_identifier($token) { global $DB, $CFG; $registration = self::get_registration(false); if (!$registration || $registration->token != $token) { throw new moodle_exception('wrongtoken', 'hub', new moodle_url('/admin/registration/index.php')); } $DB->delete_records('registration_hubs', array('id' => $registration->id)); self::$registration = null; $CFG->siteidentifier = null; get_site_identifier(); } /** * Returns information about the sites directory. * * Example of the return array: * { * "courses": 384, * "description": "Official moodle sites directory", * "downloadablecourses": 0, * "enrollablecourses": 0, * "hublogo": 1, * "language": "en", * "name": "moodle", * "sites": 274175, * "url": "https://stats.moodle.org", * "imgurl": "https://stats.moodle.org/local/hub/webservice/download.php?filetype=hubscreenshot" * } * * @return array|null */ public static function get_moodlenet_info() { try { return api::get_hub_info(); } catch (moodle_exception $e) { // Ignore error, we only need it for displaying information about the sites directory. // If this request fails, it's not a big deal. return null; } } /** * Does admin need to be redirected to the registration page after install? * * @param bool|null $markasviewed if set to true will mark the registration form as viewed and admin will not be redirected * to the registration form again (regardless of whether the site was registered or not). * @return bool */ public static function show_after_install($markasviewed = null) { global $CFG; if (self::is_registered()) { $showregistration = false; $markasviewed = true; } else { $showregistration = !empty($CFG->registrationpending); if ($showregistration && !site_is_public()) { // If it's not a public site, don't redirect to registration, it won't work anyway. $showregistration = false; $markasviewed = true; } } if ($markasviewed !== null) { set_config('registrationpending', !$markasviewed); } return $showregistration; } /** * Returns the list of the fields in the registration form that were added since registration or last manual update * * If this list is not empty the scheduled task will be paused and admin will be reminded to update registration manually. * * @return array */ public static function get_new_registration_fields() { $fieldsneedconfirm = []; if (!self::is_registered()) { // Nothing to update if site is not registered. return $fieldsneedconfirm; } $lastupdated = (int)get_config('hub', 'site_regupdateversion'); foreach (self::CONFIRM_NEW_FIELDS as $version => $fields) { if ($version > $lastupdated) { $fieldsneedconfirm = array_merge($fieldsneedconfirm, $fields); } } return $fieldsneedconfirm; } /** * Redirect to the site registration form if it's a new install, upgrade or registration needs updating. * * @param string|moodle_url $url */ public static function registration_reminder($url) { if (defined('BEHAT_SITE_RUNNING') && BEHAT_SITE_RUNNING) { // No redirection during behat runs. return; } if (!has_capability('moodle/site:config', context_system::instance())) { return; } if ( site_is_public() && (self::show_after_install() || self::get_new_registration_fields()) ) { $returnurl = new moodle_url($url); redirect(new moodle_url('/admin/registration/index.php', ['returnurl' => $returnurl->out_as_local_url(false)])); } } /** * Return a list of plugins. * * Only blocks and activities will include instance counts. * * @return array */ public static function get_plugin_usage_data(): array { global $DB; $pluginman = core_plugin_manager::instance(); $plugininfo = $pluginman->get_plugins(); $data = []; foreach ($plugininfo as $plugins) { foreach ($plugins as $plugin) { // Plugins are considered enabled if $plugin->is_enabled() returns true or null. // Plugins that return null cannot be disabled. $enabled = ($plugin->is_enabled() || is_null($plugin->is_enabled())); $data[$plugin->type][$plugin->name]['enabled'] = $enabled ? 1 : 0; if ($plugin->type === 'mod') { $mid = $DB->get_field('modules', 'id', ['name' => $plugin->name]); $count = $DB->count_records('course_modules', ['module' => $mid]); $data[$plugin->type][$plugin->name]['count'] = $count; } else if ($plugin->type === 'block') { $count = $DB->count_records('block_instances', ['blockname' => $plugin->name]); $data[$plugin->type][$plugin->name]['count'] = $count; } } } return $data; } /** * Get the time range to use in collected and reporting AI usage data. * * @param bool $format Use true to format timestamp. * @return array */ private static function get_ai_usage_time_range(bool $format = false): array { global $DB; // We will try and use the last time this site was last registered for our 'from' time. // Otherwise, default to using one week's worth of data to roughly match the site rego scheduled task. $timenow = \core\di::get(\core\clock::class)->time(); $defaultfrom = $timenow - WEEKSECS; $timeto = $timenow; $params = [ 'huburl' => HUB_MOODLEORGHUBURL, 'confirmed' => 1, ]; $lastregistered = $DB->get_field('registration_hubs', 'timemodified', $params); $timefrom = $lastregistered ? (int)$lastregistered : $defaultfrom; if ($format) { $timefrom = userdate($timefrom); $timeto = userdate($timeto); } return [ 'timefrom' => $timefrom, 'timeto' => $timeto, ]; } /** * Displays AI usage data for all providers. * * @return array Array containing usage data, grouped by provider */ public static function show_ai_usage(): array { // Initialize aiusage collection. $aiusage = []; // Process each provider's data. foreach (self::get_ai_usage_data() as $provider => $actions) { if ($provider === 'time_range') { $aiusage['timerange'] = [ 'label' => get_string($provider, 'hub'), 'values' => self::format_ai_usage_actions($actions), ]; } else { // Initialize provider data structure. $aiusage['providers'][] = [ 'providername' => get_string('pluginname', $provider), 'aiactions' => self::format_ai_usage_actions($actions), ]; } } return $aiusage; } /** * Formats individual actions for a provider. * * @param array $actions Raw actions data * @return array Formatted action data */ private static function format_ai_usage_actions(array $actions): array { $formattedactions = []; foreach ($actions as $action => $values) { if (in_array($action, ['timefrom', 'timeto'])) { $formattedactions[] = get_string($action, 'hub', userdate($values)); } else { $formattedactions[] = [ 'actionname' => get_string("action_$action", 'core_ai'), 'aiactionvalues' => self::format_ai_usage_action_values($values), ]; } } return $formattedactions; } /** * Formats action values into formatted strings. * * @param array $values Action values to format * @return array Formatted action values */ private static function format_ai_usage_action_values(array $values): array { $formattedvalues = []; foreach ($values as $key => $value) { if (get_string_manager()->string_exists($key, 'hub')) { if ($key === 'models') { $formattedvalues[] = [ 'label' => get_string($key, 'hub'), 'models' => self::format_ai_usage_model_values($value), ]; } else { $formattedvalues[]['values'] = get_string($key, 'hub', $value); } } } return $formattedvalues; } /** * Formats model values with their counts. * * @param array $values Formatted model values */ private static function format_ai_usage_model_values(array $values): array { $modelvalues = []; foreach ($values as $model => $count) { $modelvalues[] = "$model ($count[count])"; } return $modelvalues; } /** * Get AI usage data. * * @return array */ public static function get_ai_usage_data(): array { global $DB; $params = self::get_ai_usage_time_range(); $sql = "SELECT aar.* FROM {ai_action_register} aar WHERE aar.timecompleted >= :timefrom AND aar.timecompleted <= :timeto"; $actions = $DB->get_records_sql($sql, $params); // Build data for site info reporting. $data = []; foreach ($actions as $action) { $provider = $action->provider; $actionname = $action->actionname; // Initialise data structure. if (!isset($data[$provider][$actionname])) { $data[$provider][$actionname] = [ 'success_count' => 0, 'fail_count' => 0, 'times' => [], 'errors' => [], 'modelstemp' => [], ]; } if ($action->success === '1') { $data[$provider][$actionname]['success_count'] += 1; // Collect AI processing times for averaging. $data[$provider][$actionname]['times'][] = (int)$action->timecompleted - (int)$action->timecreated; } else { $data[$provider][$actionname]['fail_count'] += 1; // Collect errors for determing the predominant one. $data[$provider][$actionname]['errors'][] = $action->errorcode; } // Collect models used and identify unknown ones. $model = $action->model ?? 'unknown'; $data[$provider][$actionname]['modelstemp'][] = $model; } // Parse the errors, average the times, count the models and then add them to the data. foreach ($data as $p => $provider) { foreach ($provider as $a => $actionname) { if (isset($data[$p][$a]['errors'])) { // Create an array with the error codes counted. $errors = array_count_values($data[$p][$a]['errors']); if (!empty($errors)) { // Sort values descending and convert to an array of error codes (most predominant will be at start). arsort($errors); $errors = array_keys($errors); $data[$p][$a]['predominant_error'] = $errors[0]; } unset($data[$p][$a]['errors']); } if (isset($data[$p][$a]['times'])) { $count = count($data[$p][$a]['times']); if ($count > 0) { // Average the time to perform the action (seconds). $totaltime = array_sum($data[$p][$a]['times']); $data[$p][$a]['average_time'] = round($totaltime / $count); } } unset($data[$p][$a]['times']); if (isset($data[$p][$a]['modelstemp'])) { // Create an array with the models counted. $countedmodels = array_count_values($data[$p][$a]['modelstemp']); foreach ($countedmodels as $model => $count) { $data[$p][$a]['models'][$model]['count'] = $count; } } unset($data[$p][$a]['modelstemp']); } } // Include the time range used to help interpret the data. if (!empty($data)) { $data['time_range'] = $params; } return $data; } }
CANCEL
Name
Type
Size
Modified
Actions
↩ ..
DIR
—
—
📄 api.php
PHP
17.8 KB
2026-02-11 01:44
EDIT
📄 registration.php
PHP
37.3 KB
2026-02-11 01:44
EDIT
📄 site_registration_form.php
PHP
13.6 KB
2026-02-11 01:44
EDIT