Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Class registration 19 * 20 * @package core 21 * @copyright 2017 Marina Glancy 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace core\hub; 26 defined('MOODLE_INTERNAL') || die(); 27 28 use moodle_exception; 29 use moodle_url; 30 use context_system; 31 use stdClass; 32 use html_writer; 33 34 /** 35 * Methods to use when registering the site at the moodle sites directory. 36 * 37 * @package core 38 * @copyright 2017 Marina Glancy 39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 40 */ 41 class registration { 42 43 /** @var array Fields used in a site registration form. 44 * IMPORTANT: any new fields with non-empty defaults have to be added to CONFIRM_NEW_FIELDS */ 45 const FORM_FIELDS = ['policyagreed', 'language', 'countrycode', 'privacy', 46 'contactemail', 'contactable', 'emailalert', 'emailalertemail', 'commnews', 'commnewsemail', 47 'contactname', 'name', 'description', 'imageurl', 'contactphone', 'regioncode', 'geolocation', 'street']; 48 49 /** @var array List of new FORM_FIELDS or siteinfo fields added indexed by the version when they were added. 50 * If site was already registered, admin will be promted to confirm new registration data manually. Until registration is manually confirmed, 51 * the scheduled task updating registration will be paused. 52 * Keys of this array are not important as long as they increment, use current date to avoid confusions. 53 */ 54 const CONFIRM_NEW_FIELDS = [ 55 2017092200 => [ 56 'commnews', // Receive communication news. This was added in 3.4 and is "On" by default. Admin must confirm or opt-out. 57 'mobileservicesenabled', 'mobilenotificationsenabled', 'registereduserdevices', 'registeredactiveuserdevices' // Mobile stats added in 3.4. 58 ], 59 // Analytics stats added in Moodle 3.7. 60 2019022200 => ['analyticsenabledmodels', 'analyticspredictions', 'analyticsactions', 'analyticsactionsnotuseful'], 61 // Active users stats added in Moodle 3.9. 62 2020022600 => ['activeusers', 'activeparticipantnumberaverage'], 63 // Database type, course date info, site theme, primary auth type added in Moodle 4.2. 64 2023021700 => ['dbtype', 'coursesnodates', 'sitetheme', 'primaryauthtype'], 65 ]; 66 67 /** @var string Site privacy: not displayed */ 68 const HUB_SITENOTPUBLISHED = 'notdisplayed'; 69 70 /** @var string Site privacy: public */ 71 const HUB_SITENAMEPUBLISHED = 'named'; 72 73 /** @var string Site privacy: public and global */ 74 const HUB_SITELINKPUBLISHED = 'linked'; 75 76 /** @var stdClass cached site registration information */ 77 protected static $registration = null; 78 79 /** 80 * Get site registration 81 * 82 * @param bool $confirmed 83 * @return stdClass|null 84 */ 85 protected static function get_registration($confirmed = true) { 86 global $DB; 87 88 if (self::$registration === null) { 89 self::$registration = $DB->get_record('registration_hubs', ['huburl' => HUB_MOODLEORGHUBURL]) ?: null; 90 } 91 92 if (self::$registration && (bool)self::$registration->confirmed == (bool)$confirmed) { 93 return self::$registration; 94 } 95 96 return null; 97 } 98 99 /** 100 * Same as get_registration except it throws exception if site not registered 101 * 102 * @return stdClass 103 * @throws \moodle_exception 104 */ 105 public static function require_registration() { 106 if ($registration = self::get_registration()) { 107 return $registration; 108 } 109 if (has_capability('moodle/site:config', context_system::instance())) { 110 throw new moodle_exception('registrationwarning', 'admin', new moodle_url('/admin/registration/index.php')); 111 } else { 112 throw new moodle_exception('registrationwarningcontactadmin', 'admin'); 113 } 114 } 115 116 /** 117 * Checks if site is registered 118 * 119 * @return bool 120 */ 121 public static function is_registered() { 122 return self::get_registration() ? true : false; 123 } 124 125 /** 126 * Returns registration token 127 * 128 * @param int $strictness if set to MUST_EXIST and site is not registered will throw an exception 129 * @return null 130 * @throws moodle_exception 131 */ 132 public static function get_token($strictness = IGNORE_MISSING) { 133 if ($strictness == MUST_EXIST) { 134 $registration = self::require_registration(); 135 } else if (!$registration = self::get_registration()) { 136 return null; 137 } 138 return $registration->token; 139 } 140 141 /** 142 * When was the registration last updated 143 * 144 * @return int|null timestamp or null if site is not registered 145 */ 146 public static function get_last_updated() { 147 if ($registration = self::get_registration()) { 148 return $registration->timemodified; 149 } 150 return null; 151 } 152 153 /** 154 * Calculates and prepares site information to send to the sites directory as a part of registration. 155 * 156 * @param array $defaults default values for inputs in the registration form (if site was never registered before) 157 * @return array site info 158 */ 159 public static function get_site_info($defaults = []) { 160 global $CFG, $DB; 161 require_once($CFG->libdir . '/badgeslib.php'); 162 require_once($CFG->dirroot . "/course/lib.php"); 163 164 $siteinfo = array(); 165 foreach (self::FORM_FIELDS as $field) { 166 $siteinfo[$field] = get_config('hub', 'site_'.$field); 167 if ($siteinfo[$field] === false) { 168 $siteinfo[$field] = array_key_exists($field, $defaults) ? $defaults[$field] : null; 169 } 170 } 171 172 // Statistical data. 173 $siteinfo['courses'] = $DB->count_records('course') - 1; 174 $siteinfo['users'] = $DB->count_records('user', array('deleted' => 0)); 175 $siteinfo['activeusers'] = $DB->count_records_select('user', 'deleted = ? AND lastlogin > ?', [0, time() - DAYSECS * 30]); 176 $siteinfo['enrolments'] = $DB->count_records('role_assignments'); 177 $siteinfo['posts'] = $DB->count_records('forum_posts'); 178 $siteinfo['questions'] = $DB->count_records('question'); 179 $siteinfo['resources'] = $DB->count_records('resource'); 180 $siteinfo['badges'] = $DB->count_records_select('badge', 'status <> ' . BADGE_STATUS_ARCHIVED); 181 $siteinfo['issuedbadges'] = $DB->count_records('badge_issued'); 182 $siteinfo['participantnumberaverage'] = average_number_of_participants(); 183 $siteinfo['activeparticipantnumberaverage'] = average_number_of_participants(true, time() - DAYSECS * 30); 184 $siteinfo['modulenumberaverage'] = average_number_of_courses_modules(); 185 $siteinfo['dbtype'] = $CFG->dbtype; 186 $siteinfo['coursesnodates'] = $DB->count_records_select('course', 'enddate = ?', [0]) - 1; 187 $siteinfo['sitetheme'] = get_config('core', 'theme'); 188 189 // Primary auth type. 190 $primaryauthsql = 'SELECT auth, count(auth) as tc FROM {user} GROUP BY auth ORDER BY tc DESC'; 191 $siteinfo['primaryauthtype'] = $DB->get_field_sql($primaryauthsql, null, IGNORE_MULTIPLE); 192 193 // Version and url. 194 $siteinfo['moodlerelease'] = $CFG->release; 195 $siteinfo['url'] = $CFG->wwwroot; 196 197 // Mobile related information. 198 $siteinfo['mobileservicesenabled'] = 0; 199 $siteinfo['mobilenotificationsenabled'] = 0; 200 $siteinfo['registereduserdevices'] = 0; 201 $siteinfo['registeredactiveuserdevices'] = 0; 202 if (!empty($CFG->enablewebservices) && !empty($CFG->enablemobilewebservice)) { 203 $siteinfo['mobileservicesenabled'] = 1; 204 $siteinfo['registereduserdevices'] = $DB->count_records('user_devices'); 205 $airnotifierextpath = $CFG->dirroot . '/message/output/airnotifier/externallib.php'; 206 if (file_exists($airnotifierextpath)) { // Maybe some one uninstalled the plugin. 207 require_once($airnotifierextpath); 208 $siteinfo['mobilenotificationsenabled'] = \message_airnotifier_external::is_system_configured(); 209 $siteinfo['registeredactiveuserdevices'] = $DB->count_records('message_airnotifier_devices', array('enable' => 1)); 210 } 211 } 212 213 // Analytics related data follow. 214 $siteinfo['analyticsenabledmodels'] = \core_analytics\stats::enabled_models(); 215 $siteinfo['analyticspredictions'] = \core_analytics\stats::predictions(); 216 $siteinfo['analyticsactions'] = \core_analytics\stats::actions(); 217 $siteinfo['analyticsactionsnotuseful'] = \core_analytics\stats::actions_not_useful(); 218 219 // IMPORTANT: any new fields in siteinfo have to be added to the constant CONFIRM_NEW_FIELDS. 220 221 return $siteinfo; 222 } 223 224 /** 225 * Human-readable summary of data that will be sent to the sites directory. 226 * 227 * @param array $siteinfo result of get_site_info() 228 * @return string 229 */ 230 public static function get_stats_summary($siteinfo) { 231 $fieldsneedconfirm = self::get_new_registration_fields(); 232 $summary = html_writer::tag('p', get_string('sendfollowinginfo_help', 'hub')) . 233 html_writer::start_tag('ul'); 234 235 $mobileservicesenabled = $siteinfo['mobileservicesenabled'] ? get_string('yes') : get_string('no'); 236 $mobilenotificationsenabled = $siteinfo['mobilenotificationsenabled'] ? get_string('yes') : get_string('no'); 237 $moodlerelease = $siteinfo['moodlerelease']; 238 if (preg_match('/^(\d+\.\d.*?)[\. ]/', $moodlerelease, $matches)) { 239 $moodlerelease = $matches[1]; 240 } 241 $senddata = [ 242 'moodlerelease' => get_string('sitereleasenum', 'hub', $moodlerelease), 243 'courses' => get_string('coursesnumber', 'hub', $siteinfo['courses']), 244 'users' => get_string('usersnumber', 'hub', $siteinfo['users']), 245 'activeusers' => get_string('activeusersnumber', 'hub', $siteinfo['activeusers']), 246 'enrolments' => get_string('roleassignmentsnumber', 'hub', $siteinfo['enrolments']), 247 'posts' => get_string('postsnumber', 'hub', $siteinfo['posts']), 248 'questions' => get_string('questionsnumber', 'hub', $siteinfo['questions']), 249 'resources' => get_string('resourcesnumber', 'hub', $siteinfo['resources']), 250 'badges' => get_string('badgesnumber', 'hub', $siteinfo['badges']), 251 'issuedbadges' => get_string('issuedbadgesnumber', 'hub', $siteinfo['issuedbadges']), 252 'participantnumberaverage' => get_string('participantnumberaverage', 'hub', 253 format_float($siteinfo['participantnumberaverage'], 2)), 254 'activeparticipantnumberaverage' => get_string('activeparticipantnumberaverage', 'hub', 255 format_float($siteinfo['activeparticipantnumberaverage'], 2)), 256 'modulenumberaverage' => get_string('modulenumberaverage', 'hub', 257 format_float($siteinfo['modulenumberaverage'], 2)), 258 'mobileservicesenabled' => get_string('mobileservicesenabled', 'hub', $mobileservicesenabled), 259 'mobilenotificationsenabled' => get_string('mobilenotificationsenabled', 'hub', $mobilenotificationsenabled), 260 'registereduserdevices' => get_string('registereduserdevices', 'hub', $siteinfo['registereduserdevices']), 261 'registeredactiveuserdevices' => get_string('registeredactiveuserdevices', 'hub', $siteinfo['registeredactiveuserdevices']), 262 'analyticsenabledmodels' => get_string('analyticsenabledmodels', 'hub', $siteinfo['analyticsenabledmodels']), 263 'analyticspredictions' => get_string('analyticspredictions', 'hub', $siteinfo['analyticspredictions']), 264 'analyticsactions' => get_string('analyticsactions', 'hub', $siteinfo['analyticsactions']), 265 'analyticsactionsnotuseful' => get_string('analyticsactionsnotuseful', 'hub', $siteinfo['analyticsactionsnotuseful']), 266 'dbtype' => get_string('dbtype', 'hub', $siteinfo['dbtype']), 267 'coursesnodates' => get_string('coursesnodates', 'hub', $siteinfo['coursesnodates']), 268 'sitetheme' => get_string('sitetheme', 'hub', $siteinfo['sitetheme']), 269 'primaryauthtype' => get_string('primaryauthtype', 'hub', $siteinfo['primaryauthtype']), 270 ]; 271 272 foreach ($senddata as $key => $str) { 273 $class = in_array($key, $fieldsneedconfirm) ? ' needsconfirmation mark' : ''; 274 $summary .= html_writer::tag('li', $str, ['class' => 'site' . $key . $class]); 275 } 276 $summary .= html_writer::end_tag('ul'); 277 return $summary; 278 } 279 280 /** 281 * Save registration info locally so it can be retrieved when registration needs to be updated 282 * 283 * @param stdClass $formdata data from {@link site_registration_form} 284 */ 285 public static function save_site_info($formdata) { 286 foreach (self::FORM_FIELDS as $field) { 287 set_config('site_' . $field, $formdata->$field, 'hub'); 288 } 289 // Even if the connection with the sites directory fails, admin has manually submitted the form which means they don't need 290 // to be redirected to the site registration page any more. 291 set_config('site_regupdateversion', max(array_keys(self::CONFIRM_NEW_FIELDS)), 'hub'); 292 } 293 294 /** 295 * Updates site registration when "Update reigstration" button is clicked by admin 296 */ 297 public static function update_manual() { 298 global $DB; 299 300 if (!$registration = self::get_registration()) { 301 return false; 302 } 303 304 $siteinfo = self::get_site_info(); 305 try { 306 api::update_registration($siteinfo); 307 } catch (moodle_exception $e) { 308 if (!self::is_registered()) { 309 // Token was rejected during registration update and site and locally stored token was reset, 310 // proceed to site registration. This method will redirect away. 311 self::register(''); 312 } 313 \core\notification::add(get_string('errorregistrationupdate', 'hub', $e->getMessage()), 314 \core\output\notification::NOTIFY_ERROR); 315 return false; 316 } 317 $DB->update_record('registration_hubs', ['id' => $registration->id, 'timemodified' => time()]); 318 \core\notification::add(get_string('siteregistrationupdated', 'hub'), 319 \core\output\notification::NOTIFY_SUCCESS); 320 self::$registration = null; 321 return true; 322 } 323 324 /** 325 * Updates site registration via cron 326 * 327 * @throws moodle_exception 328 */ 329 public static function update_cron() { 330 global $DB; 331 332 if (!$registration = self::get_registration()) { 333 mtrace(get_string('registrationwarning', 'admin')); 334 return; 335 } 336 337 if (self::get_new_registration_fields()) { 338 mtrace(get_string('pleaserefreshregistrationnewdata', 'admin')); 339 return; 340 } 341 342 $siteinfo = self::get_site_info(); 343 api::update_registration($siteinfo); 344 $DB->update_record('registration_hubs', ['id' => $registration->id, 'timemodified' => time()]); 345 mtrace(get_string('siteregistrationupdated', 'hub')); 346 self::$registration = null; 347 } 348 349 /** 350 * Confirms registration by the sites directory. 351 * 352 * @param string $token 353 * @param string $newtoken 354 * @param string $hubname 355 * @throws moodle_exception 356 */ 357 public static function confirm_registration($token, $newtoken, $hubname) { 358 global $DB; 359 360 $registration = self::get_registration(false); 361 if (!$registration || $registration->token !== $token) { 362 throw new moodle_exception('wrongtoken', 'hub', new moodle_url('/admin/registration/index.php')); 363 } 364 $record = ['id' => $registration->id]; 365 $record['token'] = $newtoken; 366 $record['confirmed'] = 1; 367 $record['hubname'] = $hubname; 368 $record['timemodified'] = time(); 369 $DB->update_record('registration_hubs', $record); 370 self::$registration = null; 371 372 $siteinfo = self::get_site_info(); 373 if (strlen(http_build_query($siteinfo)) > 1800) { 374 // Update registration again because the initial request was too long and could have been truncated. 375 api::update_registration($siteinfo); 376 self::$registration = null; 377 } 378 379 // Finally, allow other plugins to perform actions once a site is registered for first time. 380 $pluginsfunction = get_plugins_with_function('post_site_registration_confirmed'); 381 foreach ($pluginsfunction as $plugins) { 382 foreach ($plugins as $pluginfunction) { 383 $pluginfunction($registration->id); 384 } 385 } 386 } 387 388 /** 389 * Retrieve the options for site privacy form element to use in registration form 390 * @return array 391 */ 392 public static function site_privacy_options() { 393 return [ 394 self::HUB_SITENOTPUBLISHED => get_string('siteprivacynotpublished', 'hub'), 395 self::HUB_SITENAMEPUBLISHED => get_string('siteprivacypublished', 'hub'), 396 self::HUB_SITELINKPUBLISHED => get_string('siteprivacylinked', 'hub') 397 ]; 398 } 399 400 /** 401 * Registers a site 402 * 403 * This method will make sure that unconfirmed registration record is created and then redirect to 404 * registration script on the sites directory. 405 * The sites directory will check that the site is accessible, register it and redirect back 406 * to /admin/registration/confirmregistration.php 407 * 408 * @param string $returnurl 409 * @throws \coding_exception 410 */ 411 public static function register($returnurl) { 412 global $DB, $SESSION; 413 414 if (self::is_registered()) { 415 // Caller of this method must make sure that site is not registered. 416 throw new \coding_exception('Site already registered'); 417 } 418 419 $hub = self::get_registration(false); 420 if (empty($hub)) { 421 // Create a new record in 'registration_hubs'. 422 $hub = new stdClass(); 423 $hub->token = get_site_identifier(); 424 $hub->secret = $hub->token; 425 $hub->huburl = HUB_MOODLEORGHUBURL; 426 $hub->hubname = 'moodle'; 427 $hub->confirmed = 0; 428 $hub->timemodified = time(); 429 $hub->id = $DB->insert_record('registration_hubs', $hub); 430 self::$registration = null; 431 } 432 433 $params = self::get_site_info(); 434 435 // The most conservative limit for the redirect URL length is 2000 characters. Only pass parameters before 436 // we reach this limit. The next registration update will update all fields. 437 // We will also update registration after we receive confirmation from moodle.net. 438 $url = new moodle_url(HUB_MOODLEORGHUBURL . '/local/hub/siteregistration.php', 439 ['token' => $hub->token, 'url' => $params['url']]); 440 foreach ($params as $key => $value) { 441 if (strlen($url->out(false, [$key => $value])) > 2000) { 442 break; 443 } 444 $url->param($key, $value); 445 } 446 447 $SESSION->registrationredirect = $returnurl; 448 redirect($url); 449 } 450 451 /** 452 * Unregister site 453 * 454 * @param bool $unpublishalladvertisedcourses 455 * @param bool $unpublishalluploadedcourses 456 * @return bool 457 */ 458 public static function unregister($unpublishalladvertisedcourses, $unpublishalluploadedcourses) { 459 global $DB; 460 461 if (!$hub = self::get_registration()) { 462 return true; 463 } 464 465 // Course unpublish went ok, unregister the site now. 466 try { 467 api::unregister_site(); 468 } catch (moodle_exception $e) { 469 \core\notification::add(get_string('unregistrationerror', 'hub', $e->getMessage()), 470 \core\output\notification::NOTIFY_ERROR); 471 return false; 472 } 473 474 $DB->delete_records('registration_hubs', array('id' => $hub->id)); 475 self::$registration = null; 476 return true; 477 } 478 479 /** 480 * Resets the registration token without changing site identifier so site can be re-registered 481 * 482 * @return bool 483 */ 484 public static function reset_token() { 485 global $DB; 486 if (!$hub = self::get_registration()) { 487 return true; 488 } 489 $DB->delete_records('registration_hubs', array('id' => $hub->id)); 490 self::$registration = null; 491 } 492 493 /** 494 * Generate a new token for the site that is not registered 495 * 496 * @param string $token 497 * @throws moodle_exception 498 */ 499 public static function reset_site_identifier($token) { 500 global $DB, $CFG; 501 502 $registration = self::get_registration(false); 503 if (!$registration || $registration->token != $token) { 504 throw new moodle_exception('wrongtoken', 'hub', 505 new moodle_url('/admin/registration/index.php')); 506 } 507 508 $DB->delete_records('registration_hubs', array('id' => $registration->id)); 509 self::$registration = null; 510 511 $CFG->siteidentifier = null; 512 get_site_identifier(); 513 } 514 515 /** 516 * Returns information about the sites directory. 517 * 518 * Example of the return array: 519 * { 520 * "courses": 384, 521 * "description": "Official moodle sites directory", 522 * "downloadablecourses": 0, 523 * "enrollablecourses": 0, 524 * "hublogo": 1, 525 * "language": "en", 526 * "name": "moodle", 527 * "sites": 274175, 528 * "url": "https://stats.moodle.org", 529 * "imgurl": "https://stats.moodle.org/local/hub/webservice/download.php?filetype=hubscreenshot" 530 * } 531 * 532 * @return array|null 533 */ 534 public static function get_moodlenet_info() { 535 try { 536 return api::get_hub_info(); 537 } catch (moodle_exception $e) { 538 // Ignore error, we only need it for displaying information about the sites directory. 539 // If this request fails, it's not a big deal. 540 return null; 541 } 542 } 543 544 /** 545 * Does admin need to be redirected to the registration page after install? 546 * 547 * @param bool|null $markasviewed if set to true will mark the registration form as viewed and admin will not be redirected 548 * to the registration form again (regardless of whether the site was registered or not). 549 * @return bool 550 */ 551 public static function show_after_install($markasviewed = null) { 552 global $CFG; 553 if (self::is_registered()) { 554 $showregistration = false; 555 $markasviewed = true; 556 } else { 557 $showregistration = !empty($CFG->registrationpending); 558 if ($showregistration && !site_is_public()) { 559 // If it's not a public site, don't redirect to registration, it won't work anyway. 560 $showregistration = false; 561 $markasviewed = true; 562 } 563 } 564 if ($markasviewed !== null) { 565 set_config('registrationpending', !$markasviewed); 566 } 567 return $showregistration; 568 } 569 570 /** 571 * Returns the list of the fields in the registration form that were added since registration or last manual update 572 * 573 * If this list is not empty the scheduled task will be paused and admin will be reminded to update registration manually. 574 * 575 * @return array 576 */ 577 public static function get_new_registration_fields() { 578 $fieldsneedconfirm = []; 579 if (!self::is_registered()) { 580 // Nothing to update if site is not registered. 581 return $fieldsneedconfirm; 582 } 583 584 $lastupdated = (int)get_config('hub', 'site_regupdateversion'); 585 foreach (self::CONFIRM_NEW_FIELDS as $version => $fields) { 586 if ($version > $lastupdated) { 587 $fieldsneedconfirm = array_merge($fieldsneedconfirm, $fields); 588 } 589 } 590 return $fieldsneedconfirm; 591 } 592 593 /** 594 * Redirect to the site registration form if it's a new install, upgrade or registration needs updating. 595 * 596 * @param string|moodle_url $url 597 */ 598 public static function registration_reminder($url) { 599 if (defined('BEHAT_SITE_RUNNING') && BEHAT_SITE_RUNNING) { 600 // No redirection during behat runs. 601 return; 602 } 603 if (!has_capability('moodle/site:config', context_system::instance())) { 604 return; 605 } 606 if (self::show_after_install() || self::get_new_registration_fields()) { 607 $returnurl = new moodle_url($url); 608 redirect(new moodle_url('/admin/registration/index.php', ['returnurl' => $returnurl->out_as_local_url(false)])); 609 } 610 } 611 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body