See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]
1 <?php 2 3 // This file is part of Moodle - http://moodle.org/ 4 // 5 // Moodle is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // Moodle is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 17 18 /** 19 * Provides various useful functionality to plugins that offer or use this MNet service 20 * 21 * Remote enrolment service is used by enrol_mnet plugin which publishes the server side 22 * methods. The client side is accessible from the admin tree. 23 * 24 * @package mnetservice 25 * @subpackage enrol 26 * @copyright 2010 David Mudrak <david@moodle.com> 27 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 28 */ 29 30 defined('MOODLE_INTERNAL') || die(); 31 32 require_once($CFG->dirroot . '/user/selector/lib.php'); 33 34 /** 35 * Singleton providing various functionality usable by plugin(s) implementing this MNet service 36 */ 37 class mnetservice_enrol { 38 39 /** @var mnetservice_enrol holds the singleton instance. */ 40 protected static $singleton; 41 42 /** @var caches the result of {@link self::get_remote_subscribers()} */ 43 protected $cachesubscribers = null; 44 45 /** @var caches the result of {@link self::get_remote_publishers()} */ 46 protected $cachepublishers = null; 47 48 /** 49 * This is singleton, use {@link mnetservice_enrol::get_instance()} 50 */ 51 protected function __construct() { 52 } 53 54 /** 55 * @return mnetservice_enrol singleton instance 56 */ 57 public static function get_instance() { 58 if (is_null(self::$singleton)) { 59 self::$singleton = new self(); 60 } 61 return self::$singleton; 62 } 63 64 /** 65 * Is this service enabled? 66 * 67 * Currently, this checks if whole MNet is available. In the future, additional 68 * checks can be done. Probably the field 'offer' should be checked but it does 69 * not seem to be used so far. 70 * 71 * @todo move this to some parent class once we have such 72 * @return bool 73 */ 74 public function is_available() { 75 global $CFG; 76 77 if (empty($CFG->mnet_dispatcher_mode) || $CFG->mnet_dispatcher_mode !== 'strict') { 78 return false; 79 } 80 return true; 81 } 82 83 /** 84 * Returns a list of remote servers that can enrol their users into our courses 85 * 86 * We must publish MNet service 'mnet_enrol' for the peers to allow them to enrol 87 * their users into our courses. 88 * 89 * @todo once the MNet core is refactored this may be part of a parent class 90 * @todo the name of the service should be changed to the name of this plugin 91 * @return array 92 */ 93 public function get_remote_subscribers() { 94 global $DB; 95 96 if (is_null($this->cachesubscribers)) { 97 $sql = "SELECT DISTINCT h.id, h.name AS hostname, h.wwwroot AS hosturl, 98 a.display_name AS appname 99 FROM {mnet_host} h 100 JOIN {mnet_host2service} hs ON h.id = hs.hostid 101 JOIN {mnet_service} s ON hs.serviceid = s.id 102 JOIN {mnet_application} a ON h.applicationid = a.id 103 WHERE s.name = 'mnet_enrol' 104 AND h.deleted = 0 105 AND hs.publish = 1"; 106 $this->cachesubscribers = $DB->get_records_sql($sql); 107 } 108 109 return $this->cachesubscribers; 110 } 111 112 /** 113 * Returns a list of remote servers that offer their courses for our users 114 * 115 * We must subscribe MNet service 'mnet_enrol' for the peers to allow our users to enrol 116 * into their courses. 117 * 118 * @todo once the MNet core is refactored this may be part of a parent class 119 * @todo the name of the service should be changed to the name of this plugin 120 * @return array 121 */ 122 public function get_remote_publishers() { 123 global $DB; 124 125 if (is_null($this->cachepublishers)) { 126 $sql = "SELECT DISTINCT h.id, h.name AS hostname, h.wwwroot AS hosturl, 127 a.display_name AS appname 128 FROM {mnet_host} h 129 JOIN {mnet_host2service} hs ON h.id = hs.hostid 130 JOIN {mnet_service} s ON hs.serviceid = s.id 131 JOIN {mnet_application} a ON h.applicationid = a.id 132 WHERE s.name = 'mnet_enrol' 133 AND h.deleted = 0 134 AND hs.subscribe = 1"; 135 $this->cachepublishers = $DB->get_records_sql($sql); 136 } 137 138 return $this->cachepublishers; 139 } 140 141 /** 142 * Fetches the information about the courses available on remote host for our students 143 * 144 * The information about remote courses available for us is cached in {mnetservice_enrol_courses}. 145 * This method either returns the cached information (typically when displaying the list to 146 * students) or fetch fresh data via new XML-RPC request (which updates the local cache, too). 147 * The lifetime of the cache is 1 day, so even if $usecache is set to true, the cache will be 148 * re-populated if we did not fetch from any server (not only the currently requested one) 149 * for some time. 150 * 151 * @param id $mnethostid MNet remote host id 152 * @param bool $usecache use cached data or invoke new XML-RPC? 153 * @uses mnet_xmlrpc_client Invokes XML-RPC request if the cache is not used 154 * @return array|string returned list or serialized array of mnet error messages 155 */ 156 public function get_remote_courses($mnethostid, $usecache=true) { 157 global $CFG, $DB; // $CFG needed! 158 159 $lastfetchcourses = get_config('mnetservice_enrol', 'lastfetchcourses'); 160 if (empty($lastfetchcourses) or (time()-$lastfetchcourses > DAYSECS)) { 161 $usecache = false; 162 } 163 164 if ($usecache) { 165 return $DB->get_records('mnetservice_enrol_courses', array('hostid' => $mnethostid), 'sortorder, shortname'); 166 } 167 168 // do not use cache - fetch fresh list from remote MNet host 169 require_once $CFG->dirroot.'/mnet/xmlrpc/client.php'; 170 $peer = new mnet_peer(); 171 if (!$peer->set_id($mnethostid)) { 172 return serialize(array('unknown mnet peer')); 173 } 174 175 $request = new mnet_xmlrpc_client(); 176 $request->set_method('enrol/mnet/enrol.php/available_courses'); 177 178 if ($request->send($peer)) { 179 $list = array(); 180 $response = $request->response; 181 182 // get the currently cached courses key'd on remote id - only need remoteid and id fields 183 $cachedcourses = $DB->get_records('mnetservice_enrol_courses', array('hostid' => $mnethostid), 'remoteid', 'remoteid, id'); 184 185 foreach ($response as &$remote) { 186 $course = new stdclass(); // record in our local cache 187 $course->hostid = $mnethostid; 188 $course->remoteid = (int)$remote['remoteid']; 189 $course->categoryid = (int)$remote['cat_id']; 190 $course->categoryname = substr($remote['cat_name'], 0, 255); 191 $course->sortorder = (int)$remote['sortorder']; 192 $course->fullname = substr($remote['fullname'], 0, 254); 193 $course->shortname = substr($remote['shortname'], 0, 100); 194 $course->idnumber = substr($remote['idnumber'], 0, 100); 195 $course->summary = $remote['summary']; 196 $course->summaryformat = empty($remote['summaryformat']) ? FORMAT_MOODLE : (int)$remote['summaryformat']; 197 $course->startdate = (int)$remote['startdate']; 198 $course->roleid = (int)$remote['defaultroleid']; 199 $course->rolename = substr($remote['defaultrolename'], 0, 255); 200 // We do not cache the following fields returned from peer in 2.0 any more 201 // not cached: cat_description 202 // not cached: cat_descriptionformat 203 // not cached: cost 204 // not cached: currency 205 206 if (empty($cachedcourses[$course->remoteid])) { 207 $course->id = $DB->insert_record('mnetservice_enrol_courses', $course); 208 } else { 209 $course->id = $cachedcourses[$course->remoteid]->id; 210 $DB->update_record('mnetservice_enrol_courses', $course); 211 } 212 213 $list[$course->remoteid] = $course; 214 } 215 216 // prune stale data from cache 217 if (!empty($cachedcourses)) { 218 foreach ($cachedcourses as $cachedcourse) { 219 if (!empty($list[$cachedcourse->remoteid])) { 220 unset($cachedcourses[$cachedcourse->remoteid]); 221 } 222 } 223 $staleremoteids = array_keys($cachedcourses); 224 if (!empty($staleremoteids)) { 225 list($sql, $params) = $DB->get_in_or_equal($staleremoteids, SQL_PARAMS_NAMED); 226 $select = "hostid=:hostid AND remoteid $sql"; 227 $params['hostid'] = $mnethostid; 228 $DB->delete_records_select('mnetservice_enrol_courses', $select, $params); 229 } 230 } 231 232 // and return the fresh data 233 set_config('lastfetchcourses', time(), 'mnetservice_enrol'); 234 return $list; 235 236 } else { 237 return serialize($request->error); 238 } 239 } 240 241 /** 242 * Updates local cache about enrolments of our users in remote courses 243 * 244 * The remote course must allow enrolments via our Remote enrolment service client. 245 * Because of legacy design of data structure returned by XML-RPC code, only one 246 * user enrolment per course is returned by 1.9 MNet servers. This may be an issue 247 * if the user is enrolled multiple times by various enrolment plugins. MNet 2.0 248 * servers do not use user name as array keys - they do not need to due to side 249 * effect of MDL-19219. 250 * 251 * @param id $mnethostid MNet remote host id 252 * @param int $remotecourseid ID of the course at the remote host 253 * @param bool $usecache use cached data or invoke new XML-RPC? 254 * @uses mnet_xmlrpc_client Invokes XML-RPC request 255 * @return bool|string true if success or serialized array of mnet error messages 256 */ 257 public function req_course_enrolments($mnethostid, $remotecourseid) { 258 global $CFG, $DB; // $CFG needed! 259 require_once $CFG->dirroot.'/mnet/xmlrpc/client.php'; 260 261 if (!$DB->record_exists('mnetservice_enrol_courses', array('hostid'=>$mnethostid, 'remoteid'=>$remotecourseid))) { 262 return serialize(array('course not available for remote enrolments')); 263 } 264 265 $peer = new mnet_peer(); 266 if (!$peer->set_id($mnethostid)) { 267 return serialize(array('unknown mnet peer')); 268 } 269 270 $request = new mnet_xmlrpc_client(); 271 $request->set_method('enrol/mnet/enrol.php/course_enrolments'); 272 $request->add_param($remotecourseid, 'int'); 273 274 if ($request->send($peer)) { 275 $list = array(); 276 $response = $request->response; 277 278 // prepare a table mapping usernames of our users to their ids 279 $usernames = array(); 280 foreach ($response as $unused => $remote) { 281 if (!isset($remote['username'])) { 282 // see MDL-19219 283 return serialize(array('remote host running old version of mnet server - does not return username attribute')); 284 } 285 if ($remote['username'] == 'guest') { // we can not use $CFG->siteguest here 286 // do not try nasty things you bastard! 287 continue; 288 } 289 $usernames[$remote['username']] = $remote['username']; 290 } 291 292 if (!empty($usernames)) { 293 list($usql, $params) = $DB->get_in_or_equal($usernames, SQL_PARAMS_NAMED); 294 list($sort, $sortparams) = users_order_by_sql(); 295 $params['mnetlocalhostid'] = $CFG->mnet_localhost_id; 296 $sql = "SELECT username,id 297 FROM {user} 298 WHERE mnethostid = :mnetlocalhostid 299 AND username $usql 300 AND deleted = 0 301 AND confirmed = 1 302 ORDER BY $sort"; 303 $usersbyusername = $DB->get_records_sql($sql, array_merge($params, $sortparams)); 304 } else { 305 $usersbyusername = array(); 306 } 307 308 // populate the returned list and update local cache of enrolment records 309 foreach ($response as $remote) { 310 if (empty($usersbyusername[$remote['username']])) { 311 // we do not know this user or she is deleted or not confirmed or is 'guest' 312 continue; 313 } 314 $enrolment = new stdclass(); 315 $enrolment->hostid = $mnethostid; 316 $enrolment->userid = $usersbyusername[$remote['username']]->id; 317 $enrolment->remotecourseid = $remotecourseid; 318 $enrolment->rolename = $remote['name']; // $remote['shortname'] not used 319 $enrolment->enroltime = $remote['timemodified']; 320 $enrolment->enroltype = $remote['enrol']; 321 322 $current = $DB->get_record('mnetservice_enrol_enrolments', array('hostid'=>$enrolment->hostid, 'userid'=>$enrolment->userid, 323 'remotecourseid'=>$enrolment->remotecourseid, 'enroltype'=>$enrolment->enroltype), 'id, enroltime'); 324 if (empty($current)) { 325 $enrolment->id = $DB->insert_record('mnetservice_enrol_enrolments', $enrolment); 326 } else { 327 $enrolment->id = $current->id; 328 if ($current->enroltime != $enrolment->enroltime) { 329 $DB->update_record('mnetservice_enrol_enrolments', $enrolment); 330 } 331 } 332 333 $list[$enrolment->id] = $enrolment; 334 } 335 336 // prune stale enrolment records 337 if (empty($list)) { 338 $DB->delete_records('mnetservice_enrol_enrolments', array('hostid'=>$mnethostid, 'remotecourseid'=>$remotecourseid)); 339 } else { 340 list($isql, $params) = $DB->get_in_or_equal(array_keys($list), SQL_PARAMS_NAMED, 'param', false); 341 $params['hostid'] = $mnethostid; 342 $params['remotecourseid'] = $remotecourseid; 343 $select = "hostid = :hostid AND remotecourseid = :remotecourseid AND id $isql"; 344 $DB->delete_records_select('mnetservice_enrol_enrolments', $select, $params); 345 } 346 347 // store the timestamp of the recent fetch, can be used for cache invalidate purposes 348 set_config('lastfetchenrolments', time(), 'mnetservice_enrol'); 349 // local cache successfully updated 350 return true; 351 352 } else { 353 return serialize($request->error); 354 } 355 } 356 357 /** 358 * Send request to enrol our user to the remote course 359 * 360 * Updates our remote enrolments cache if the enrolment was successful. 361 * 362 * @uses mnet_xmlrpc_client Invokes XML-RPC request 363 * @param object $user our user 364 * @param object $remotecourse record from mnetservice_enrol_courses table 365 * @return true|string true if success, error message from the remote host otherwise 366 */ 367 public function req_enrol_user(stdclass $user, stdclass $remotecourse) { 368 global $CFG, $DB; 369 require_once($CFG->dirroot.'/mnet/xmlrpc/client.php'); 370 371 $peer = new mnet_peer(); 372 $peer->set_id($remotecourse->hostid); 373 374 $request = new mnet_xmlrpc_client(); 375 $request->set_method('enrol/mnet/enrol.php/enrol_user'); 376 $request->add_param(mnet_strip_user((array)$user, mnet_fields_to_send($peer))); 377 $request->add_param($remotecourse->remoteid); 378 379 if ($request->send($peer) === true) { 380 if ($request->response === true) { 381 // cache the enrolment information in our table 382 $enrolment = new stdclass(); 383 $enrolment->hostid = $peer->id; 384 $enrolment->userid = $user->id; 385 $enrolment->remotecourseid = $remotecourse->remoteid; 386 $enrolment->enroltype = 'mnet'; 387 // $enrolment->rolename not known now, must be re-fetched 388 // $enrolment->enroltime not known now, must be re-fetched 389 $DB->insert_record('mnetservice_enrol_enrolments', $enrolment); 390 return true; 391 392 } else { 393 return serialize(array('invalid response: '.print_r($request->response, true))); 394 } 395 396 } else { 397 return serialize($request->error); 398 } 399 } 400 401 /** 402 * Send request to unenrol our user from the remote course 403 * 404 * Updates our remote enrolments cache if the unenrolment was successful. 405 * 406 * @uses mnet_xmlrpc_client Invokes XML-RPC request 407 * @param object $user our user 408 * @param object $remotecourse record from mnetservice_enrol_courses table 409 * @return true|string true if success, error message from the remote host otherwise 410 */ 411 public function req_unenrol_user(stdclass $user, stdclass $remotecourse) { 412 global $CFG, $DB; 413 require_once($CFG->dirroot.'/mnet/xmlrpc/client.php'); 414 415 $peer = new mnet_peer(); 416 $peer->set_id($remotecourse->hostid); 417 418 $request = new mnet_xmlrpc_client(); 419 $request->set_method('enrol/mnet/enrol.php/unenrol_user'); 420 $request->add_param($user->username); 421 $request->add_param($remotecourse->remoteid); 422 423 if ($request->send($peer) === true) { 424 if ($request->response === true) { 425 // clear the cached information 426 $DB->delete_records('mnetservice_enrol_enrolments', 427 array('hostid'=>$peer->id, 'userid'=>$user->id, 'remotecourseid'=>$remotecourse->remoteid, 'enroltype'=>'mnet')); 428 return true; 429 430 } else { 431 return serialize(array('invalid response: '.print_r($request->response, true))); 432 } 433 434 } else { 435 return serialize($request->error); 436 } 437 } 438 439 /** 440 * Prepares error messages returned by our XML-RPC requests to be send as debug info to {@see \moodle_exception()} 441 * 442 * MNet client-side methods in this class return request error as serialized array. 443 * 444 * @param string $error serialized array 445 * @return string 446 */ 447 public function format_error_message($errormsg) { 448 $errors = unserialize($errormsg); 449 $output = 'mnet_xmlrpc_client request returned errors:'."\n"; 450 foreach ($errors as $error) { 451 $output .= "$error\n"; 452 } 453 return $output; 454 } 455 } 456 457 /** 458 * Selector of our users enrolled into remote course via enrol_mnet plugin 459 */ 460 class mnetservice_enrol_existing_users_selector extends user_selector_base { 461 /** @var id of the MNet peer */ 462 protected $hostid; 463 /** @var id of the course at the remote server */ 464 protected $remotecourseid; 465 466 public function __construct($name, $options) { 467 $this->hostid = $options['hostid']; 468 $this->remotecourseid = $options['remotecourseid']; 469 parent::__construct($name, $options); 470 } 471 472 /** 473 * Find our users currently enrolled into the remote course 474 * 475 * @param string $search 476 * @return array 477 */ 478 public function find_users($search) { 479 global $DB; 480 481 list($wherecondition, $params) = $this->search_sql($search, 'u'); 482 $params['hostid'] = $this->hostid; 483 $params['remotecourseid'] = $this->remotecourseid; 484 485 $fields = "SELECT ".$this->required_fields_sql("u"); 486 $countfields = "SELECT COUNT(1)"; 487 488 $sql = " FROM {user} u 489 JOIN {mnetservice_enrol_enrolments} e ON e.userid = u.id 490 WHERE e.hostid = :hostid AND e.remotecourseid = :remotecourseid 491 AND e.enroltype = 'mnet' 492 AND $wherecondition"; 493 494 list($sort, $sortparams) = users_order_by_sql('u'); 495 $order = " ORDER BY $sort"; 496 497 if (!$this->is_validating()) { 498 $potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params); 499 if ($potentialmemberscount > 100) { 500 return $this->too_many_results($search, $potentialmemberscount); 501 } 502 } 503 504 $availableusers = $DB->get_records_sql($fields . $sql . $order, array_merge($params, $sortparams)); 505 506 if (empty($availableusers)) { 507 return array(); 508 } 509 510 if ($search) { 511 $groupname = get_string('enrolledusersmatching', 'enrol', $search); 512 } else { 513 $groupname = get_string('enrolledusers', 'enrol'); 514 } 515 516 return array($groupname => $availableusers); 517 } 518 519 protected function get_options() { 520 $options = parent::get_options(); 521 $options['hostid'] = $this->hostid; 522 $options['remotecourseid'] = $this->remotecourseid; 523 $options['file'] = 'mnet/service/enrol/locallib.php'; 524 return $options; 525 } 526 } 527 528 /** 529 * Selector of our users who could be enrolled into a remote course via their enrol_mnet 530 */ 531 class mnetservice_enrol_potential_users_selector extends user_selector_base { 532 /** @var id of the MNet peer */ 533 protected $hostid; 534 /** @var id of the course at the remote server */ 535 protected $remotecourseid; 536 537 public function __construct($name, $options) { 538 $this->hostid = $options['hostid']; 539 $this->remotecourseid = $options['remotecourseid']; 540 parent::__construct($name, $options); 541 } 542 543 /** 544 * Find our users who could be enrolled into the remote course 545 * 546 * Our users must have 'moodle/site:mnetlogintoremote' capability assigned. 547 * Remote users, guests, deleted and not confirmed users are not returned. 548 * 549 * @param string $search 550 * @return array 551 */ 552 public function find_users($search) { 553 global $CFG, $DB; 554 555 $systemcontext = context_system::instance(); 556 $userids = get_users_by_capability($systemcontext, 'moodle/site:mnetlogintoremote', 'u.id'); 557 558 if (empty($userids)) { 559 return array(); 560 } 561 562 list($usql, $uparams) = $DB->get_in_or_equal(array_keys($userids), SQL_PARAMS_NAMED, 'uid'); 563 564 list($wherecondition, $params) = $this->search_sql($search, 'u'); 565 566 $params = array_merge($params, $uparams); 567 $params['hostid'] = $this->hostid; 568 $params['remotecourseid'] = $this->remotecourseid; 569 $params['mnetlocalhostid'] = $CFG->mnet_localhost_id; 570 571 $fields = "SELECT ".$this->required_fields_sql("u"); 572 $countfields = "SELECT COUNT(1)"; 573 574 $sql = " FROM {user} u 575 WHERE $wherecondition 576 AND u.mnethostid = :mnetlocalhostid 577 AND u.id $usql 578 AND u.id NOT IN (SELECT e.userid 579 FROM {mnetservice_enrol_enrolments} e 580 WHERE (e.hostid = :hostid AND e.remotecourseid = :remotecourseid))"; 581 582 list($sort, $sortparams) = users_order_by_sql('u'); 583 $order = " ORDER BY $sort"; 584 585 if (!$this->is_validating()) { 586 $potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params); 587 if ($potentialmemberscount > 100) { 588 return $this->too_many_results($search, $potentialmemberscount); 589 } 590 } 591 592 $availableusers = $DB->get_records_sql($fields . $sql . $order, array_merge($params, $sortparams)); 593 594 if (empty($availableusers)) { 595 return array(); 596 } 597 598 if ($search) { 599 $groupname = get_string('enrolcandidatesmatching', 'enrol', $search); 600 } else { 601 $groupname = get_string('enrolcandidates', 'enrol'); 602 } 603 604 return array($groupname => $availableusers); 605 } 606 607 protected function get_options() { 608 $options = parent::get_options(); 609 $options['hostid'] = $this->hostid; 610 $options['remotecourseid'] = $this->remotecourseid; 611 $options['file'] = 'mnet/service/enrol/locallib.php'; 612 return $options; 613 } 614 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body