3 * Library functions for mnet
5 * @author Donal McMullan donal@catalyst.net.nz
7 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
10 require_once $CFG->dirroot
.'/mnet/xmlrpc/xmlparser.php';
11 require_once $CFG->dirroot
.'/mnet/peer.php';
12 require_once $CFG->dirroot
.'/mnet/environment.php';
14 /// CONSTANTS ///////////////////////////////////////////////////////////
17 define('RPC_NOSUCHFILE', 1);
18 define('RPC_NOSUCHCLASS', 2);
19 define('RPC_NOSUCHFUNCTION', 3);
20 define('RPC_FORBIDDENFUNCTION', 4);
21 define('RPC_NOSUCHMETHOD', 5);
22 define('RPC_FORBIDDENMETHOD', 6);
25 * Strip extraneous detail from a URL or URI and return the hostname
27 * @param string $uri The URI of a file on the remote computer, optionally
28 * including its http:// prefix like
29 * http://www.example.com/index.html
30 * @return string Just the hostname
32 function mnet_get_hostname_from_uri($uri = null) {
33 $count = preg_match("@^(?:http[s]?://)?([A-Z0-9\-\.]+).*@i", $uri, $matches);
34 if ($count > 0) return $matches[1];
39 * Get the remote machine's SSL Cert
41 * @param string $uri The URI of a file on the remote computer, including
42 * its http:// or https:// prefix
43 * @return string A PEM formatted SSL Certificate.
45 function mnet_get_public_key($uri, $application=null) {
47 $mnet = get_mnet_environment();
48 // The key may be cached in the mnet_set_public_key function...
50 $key = mnet_set_public_key($uri);
55 if (empty($application)) {
56 $application = $DB->get_record('mnet_application', array('name'=>'moodle'));
60 new \PhpXmlRpc\
Value($CFG->wwwroot
),
61 new \PhpXmlRpc\
Value($mnet->public_key
),
62 new \PhpXmlRpc\
Value($application->name
),
64 $request = new \PhpXmlRpc\
Request('system/keyswap', $params);
66 // Let's create a client to handle the request and the response easily.
67 $client = new \PhpXmlRpc\
Client($uri . $application->xmlrpc_server_url
);
68 $client->setOption('use_curl', \PhpXmlRpc\Client
::USE_CURL_ALWAYS
);
69 $client->setOption('user_agent', 'Moodle');
70 $client->return_type
= 'xmlrpcvals'; // This (keyswap) is not encrypted, so we can expect proper xmlrpc in this case.
71 $client->setOption('request_charset_encoding', 'utf-8');
72 $client->setOption('accepted_charset_encodings', ['utf-8']);
74 // TODO: Link this to DEBUG DEVELOPER or with MNET debugging...
75 // $client->setdebug(1); // See a good number of complete requests and responses.
77 $client->setOption('verifyhost', 0);
78 $client->setOption('verifypeer', false);
80 // TODO: It's curious that this service (keyswap) that needs
81 // a custom client, different from mnet_xmlrpc_client, because
82 // this is not encrypted / signed, does support proxies and the
83 // general one does not. Worth analysing if the support below
84 // should be added to it.
86 // Some curl options need to be set apart, accumulate them here.
87 $extracurloptions = [];
90 if (!empty($CFG->proxyhost
) && !is_proxybypass($uri)) {
91 // SOCKS supported in PHP5 only.
92 if (!empty($CFG->proxytype
) && ($CFG->proxytype
== 'SOCKS5')) {
93 if (defined('CURLPROXY_SOCKS5')) {
94 $extracurloptions[CURLOPT_PROXYTYPE
] = CURLPROXY_SOCKS5
;
96 throw new \
moodle_exception( 'socksnotsupported', 'mnet');
100 $extracurloptions[CURLOPT_HTTPPROXYTUNNEL
] = false;
102 // Configure proxy host, port, user, pass and auth.
105 empty($CFG->proxyport
) ?
0 : $CFG->proxyport
,
106 empty($CFG->proxyuser
) ?
'' : $CFG->proxyuser
,
107 empty($CFG->proxypassword
) ?
'' : $CFG->proxypassword
,
108 defined('CURLOPT_PROXYAUTH') ? CURLAUTH_BASIC | CURLAUTH_NTLM
: 1);
111 // Finally, add the extra curl options we may have accumulated.
112 $client->setCurlOptions($extracurloptions);
114 $response = $client->send($request, 60);
116 // Check curl / xmlrpc errors.
117 if ($response->faultCode()) {
118 debugging("Request for $uri failed with error {$response->faultCode()}: {$response->faultString()}");
122 // Check HTTP error code.
123 $status = $response->httpResponse()['status_code'];
124 if (!empty($status) && ($status != 200)) {
125 debugging("Request for $uri failed with HTTP code " . $status);
129 // Get the peer actual public key from the response.
130 $res = $response->value()->scalarval();
132 if (!is_array($res)) { // ! error
133 $public_certificate = $res;
134 $credentials=array();
135 if (strlen(trim($public_certificate))) {
136 $credentials = openssl_x509_parse($public_certificate);
137 $host = $credentials['subject']['CN'];
138 if (array_key_exists( 'subjectAltName', $credentials['subject'])) {
139 $host = $credentials['subject']['subjectAltName'];
141 if (strpos($uri, $host) !== false) {
142 mnet_set_public_key($uri, $public_certificate);
143 return $public_certificate;
146 debugging("Request for $uri returned public key for different URI - $host");
150 debugging("Request for $uri returned empty response");
154 debugging( "Request for $uri returned unexpected result");
160 * Store a URI's public key in a static variable, or retrieve the key for a URI
162 * @param string $uri The URI of a file on the remote computer, including its
164 * @param mixed $key A public key to store in the array OR null. If the key
165 * is null, the function will return the previously stored
166 * key for the supplied URI, should it exist.
167 * @return mixed A public key OR true/false.
169 function mnet_set_public_key($uri, $key = null) {
170 static $keyarray = array();
171 if (isset($keyarray[$uri]) && empty($key)) {
172 return $keyarray[$uri];
173 } elseif (!empty($key)) {
174 $keyarray[$uri] = $key;
181 * Sign a message and return it in an XML-Signature document
183 * This function can sign any content, but it was written to provide a system of
184 * signing XML-RPC request and response messages. The message will be base64
185 * encoded, so it does not need to be text.
187 * We compute the SHA1 digest of the message.
188 * We compute a signature on that digest with our private key.
189 * We link to the public key that can be used to verify our signature.
190 * We base64 the message data.
191 * We identify our wwwroot - this must match our certificate's CN
193 * The XML-RPC document will be parceled inside an XML-SIG document, which holds
194 * the base64_encoded XML as an object, the SHA1 digest of that document, and a
195 * signature of that document using the local private key. This signature will
196 * uniquely identify the RPC document as having come from this server.
198 * See the {@Link http://www.w3.org/TR/xmldsig-core/ XML-DSig spec} at the W3c
201 * @param string $message The data you want to sign
202 * @param resource $privatekey The private key to sign the response with
203 * @return string An XML-DSig document
205 function mnet_sign_message($message, $privatekey = null) {
207 $digest = sha1($message);
209 $mnet = get_mnet_environment();
210 // If the user hasn't supplied a private key (for example, one of our older,
211 // expired private keys, we get the current default private key and use that.
212 if ($privatekey == null) {
213 $privatekey = $mnet->get_private_key();
216 // The '$sig' value below is returned by reference.
217 // We initialize it first to stop my IDE from complaining.
219 $bool = openssl_sign($message, $sig, $privatekey);
221 // Avoid passing null values to base64_encode.
222 if ($bool === false) {
223 throw new \
moodle_exception('opensslsignerror');
226 $message = '<?xml version="1.0" encoding="iso-8859-1"?>
228 <Signature Id="MoodleSignature" xmlns="http://www.w3.org/2000/09/xmldsig#">
230 <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
231 <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
232 <Reference URI="#XMLRPC-MSG">
233 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
234 <DigestValue>'.$digest.'</DigestValue>
237 <SignatureValue>'.base64_encode($sig).'</SignatureValue>
239 <RetrievalMethod URI="'.$CFG->wwwroot
.'/mnet/publickey.php"/>
242 <object ID="XMLRPC-MSG">'.base64_encode($message).'</object>
243 <wwwroot>'.$mnet->wwwroot
.'</wwwroot>
244 <timestamp>'.time().'</timestamp>
250 * Encrypt a message and return it in an XML-Encrypted document
252 * This function can encrypt any content, but it was written to provide a system
253 * of encrypting XML-RPC request and response messages. The message will be
254 * base64 encoded, so it does not need to be text - binary data should work.
256 * We compute the SHA1 digest of the message.
257 * We compute a signature on that digest with our private key.
258 * We link to the public key that can be used to verify our signature.
259 * We base64 the message data.
260 * We identify our wwwroot - this must match our certificate's CN
262 * The XML-RPC document will be parceled inside an XML-SIG document, which holds
263 * the base64_encoded XML as an object, the SHA1 digest of that document, and a
264 * signature of that document using the local private key. This signature will
265 * uniquely identify the RPC document as having come from this server.
267 * See the {@Link http://www.w3.org/TR/xmlenc-core/ XML-ENC spec} at the W3c
270 * @param string $message The data you want to sign
271 * @param string $remote_certificate Peer's certificate in PEM format
272 * @return string An XML-ENC document
274 function mnet_encrypt_message($message, $remote_certificate) {
275 $mnet = get_mnet_environment();
277 // Generate a key resource from the remote_certificate text string
278 $publickey = openssl_get_publickey($remote_certificate);
280 if ($publickey === false) {
281 // Remote certificate is faulty.
286 $encryptedstring = '';
287 $symmetric_keys = array();
289 // passed by ref -> &$encryptedstring &$symmetric_keys
290 $bool = openssl_seal($message, $encryptedstring, $symmetric_keys, array($publickey), 'RC4');
292 // Avoid passing null values to base64_encode.
293 if ($bool === false) {
294 throw new \
moodle_exception('opensslsealerror');
297 $message = $encryptedstring;
298 $symmetrickey = array_pop($symmetric_keys);
300 $message = '<?xml version="1.0" encoding="iso-8859-1"?>
302 <EncryptedData Id="ED" xmlns="http://www.w3.org/2001/04/xmlenc#">
303 <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#arcfour"/>
304 <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
305 <ds:RetrievalMethod URI="#EK" Type="http://www.w3.org/2001/04/xmlenc#EncryptedKey"/>
306 <ds:KeyName>XMLENC</ds:KeyName>
309 <CipherValue>'.base64_encode($message).'</CipherValue>
312 <EncryptedKey Id="EK" xmlns="http://www.w3.org/2001/04/xmlenc#">
313 <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
314 <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
315 <ds:KeyName>SSLKEY</ds:KeyName>
318 <CipherValue>'.base64_encode($symmetrickey).'</CipherValue>
321 <DataReference URI="#ED"/>
323 <CarriedKeyName>XMLENC</CarriedKeyName>
325 <wwwroot>'.$mnet->wwwroot
.'</wwwroot>
326 </encryptedMessage>';
331 * Get your SSL keys from the database, or create them (if they don't exist yet)
333 * Get your SSL keys from the database, or (if they don't exist yet) call
334 * mnet_generate_keypair to create them
336 * @param string $string The text you want to sign
337 * @return string The signature over that text
339 function mnet_get_keypair() {
341 static $keypair = null;
342 if (!is_null($keypair)) return $keypair;
343 if ($result = get_config('mnet', 'openssl')) {
344 list($keypair['certificate'], $keypair['keypair_PEM']) = explode('@@@@@@@@', $result);
347 $keypair = mnet_generate_keypair();
353 * Generate public/private keys and store in the config table
355 * Use the distinguished name provided to create a CSR, and then sign that CSR
356 * with the same credentials. Store the keypair you create in the config table.
357 * If a distinguished name is not provided, create one using the fullname of
358 * 'the course with ID 1' as your organization name, and your hostname (as
359 * detailed in $CFG->wwwroot).
361 * @param array $dn The distinguished name of the server
362 * @return string The signature over that text
364 function mnet_generate_keypair($dn = null, $days=28) {
365 global $CFG, $USER, $DB;
367 // check if lifetime has been overriden
368 if (!empty($CFG->mnetkeylifetime
)) {
369 $days = $CFG->mnetkeylifetime
;
372 $host = strtolower($CFG->wwwroot
);
373 $host = preg_replace("~^http(s)?://~",'',$host);
374 $break = strpos($host.'/' , '/');
375 $host = substr($host, 0, $break);
378 $organization = $site->fullname
;
383 $province = 'Wellington';
384 $locality = 'Wellington';
385 $email = !empty($CFG->noreplyaddress
) ?
$CFG->noreplyaddress
: 'noreply@'.$_SERVER['HTTP_HOST'];
387 if(!empty($USER->country
)) {
388 $country = $USER->country
;
390 if(!empty($USER->city
)) {
391 $province = $USER->city
;
392 $locality = $USER->city
;
394 if(!empty($USER->email
)) {
395 $email = $USER->email
;
400 "countryName" => $country,
401 "stateOrProvinceName" => $province,
402 "localityName" => $locality,
403 "organizationName" => $organization,
404 "organizationalUnitName" => 'Moodle',
405 "commonName" => substr($CFG->wwwroot
, 0, 64),
406 "subjectAltName" => $CFG->wwwroot
,
407 "emailAddress" => $email
413 'stateOrProvinceName' => 128,
414 'localityName' => 128,
415 'organizationName' => 64,
416 'organizationalUnitName' => 64,
418 'emailAddress' => 128
421 foreach ($dnlimits as $key => $length) {
422 $dn[$key] = core_text
::substr($dn[$key], 0, $length);
425 // ensure we remove trailing slashes
426 $dn["commonName"] = preg_replace(':/$:', '', $dn["commonName"]);
427 if (!empty($CFG->opensslcnf
)) { //allow specification of openssl.cnf especially for Windows installs
428 $new_key = openssl_pkey_new(array("config" => $CFG->opensslcnf
));
430 $new_key = openssl_pkey_new();
432 if ($new_key === false) {
433 // can not generate keys - missing openssl.cnf??
436 if (!empty($CFG->opensslcnf
)) { //allow specification of openssl.cnf especially for Windows installs
437 $csr_rsc = openssl_csr_new($dn, $new_key, array("config" => $CFG->opensslcnf
));
438 $selfSignedCert = openssl_csr_sign($csr_rsc, null, $new_key, $days, array("config" => $CFG->opensslcnf
));
440 $csr_rsc = openssl_csr_new($dn, $new_key, array('private_key_bits',2048));
441 $selfSignedCert = openssl_csr_sign($csr_rsc, null, $new_key, $days);
443 unset($csr_rsc); // Free up the resource
445 // We export our self-signed certificate to a string.
446 openssl_x509_export($selfSignedCert, $keypair['certificate']);
447 // TODO: Remove this block once PHP 8.0 becomes required.
448 if (PHP_MAJOR_VERSION
< 8) {
449 openssl_x509_free($selfSignedCert);
452 // Export your public/private key pair as a PEM encoded string. You
453 // can protect it with an optional passphrase if you wish.
454 if (!empty($CFG->opensslcnf
)) { //allow specification of openssl.cnf especially for Windows installs
455 $export = openssl_pkey_export($new_key, $keypair['keypair_PEM'], null, array("config" => $CFG->opensslcnf
));
457 $export = openssl_pkey_export($new_key, $keypair['keypair_PEM'] /* , $passphrase */);
459 // TODO: Remove this block once PHP 8.0 becomes required.
460 if (PHP_MAJOR_VERSION
< 8) {
461 openssl_pkey_free($new_key);
463 unset($new_key); // Free up the resource
469 function mnet_update_sso_access_control($username, $mnet_host_id, $accessctrl) {
472 $mnethost = $DB->get_record('mnet_host', array('id'=>$mnet_host_id));
473 if ($aclrecord = $DB->get_record('mnet_sso_access_control', array('username'=>$username, 'mnet_host_id'=>$mnet_host_id))) {
475 $aclrecord->accessctrl
= $accessctrl;
476 $DB->update_record('mnet_sso_access_control', $aclrecord);
478 // Trigger access control updated event.
480 'objectid' => $aclrecord->id
,
481 'context' => context_system
::instance(),
483 'username' => $username,
484 'hostname' => $mnethost->name
,
485 'accessctrl' => $accessctrl
488 $event = \core\event\mnet_access_control_updated
::create($params);
489 $event->add_record_snapshot('mnet_host', $mnethost);
493 $aclrecord = new stdClass();
494 $aclrecord->username
= $username;
495 $aclrecord->accessctrl
= $accessctrl;
496 $aclrecord->mnet_host_id
= $mnet_host_id;
497 $aclrecord->id
= $DB->insert_record('mnet_sso_access_control', $aclrecord);
499 // Trigger access control created event.
501 'objectid' => $aclrecord->id
,
502 'context' => context_system
::instance(),
504 'username' => $username,
505 'hostname' => $mnethost->name
,
506 'accessctrl' => $accessctrl
509 $event = \core\event\mnet_access_control_created
::create($params);
510 $event->add_record_snapshot('mnet_host', $mnethost);
516 function mnet_get_peer_host ($mnethostid) {
519 if (!isset($hosts[$mnethostid])) {
520 $host = $DB->get_record('mnet_host', array('id' => $mnethostid));
521 $hosts[$mnethostid] = $host;
523 return $hosts[$mnethostid];
527 * Inline function to modify a url string so that mnet users are requested to
528 * log in at their mnet identity provider (if they are not already logged in)
529 * before ultimately being directed to the original url.
531 * @param string $jumpurl the url which user should initially be directed to.
532 * This is a URL associated with a moodle networking peer when it
533 * is fulfiling a role as an identity provider (IDP). Different urls for
534 * different peers, the jumpurl is formed partly from the IDP's webroot, and
535 * partly from a predefined local path within that webwroot.
536 * The result of the user hitting this jump url is that they will be asked
537 * to login (at their identity provider (if they aren't already)), mnet
538 * will prepare the necessary authentication information, then redirect
539 * them back to somewhere at the content provider(CP) moodle (this moodle)
540 * @param array $url array with 2 elements
541 * 0 - context the url was taken from, possibly just the url, possibly href="url"
542 * 1 - the destination url
543 * @return string the url the remote user should be supplied with.
545 function mnet_sso_apply_indirection ($jumpurl, $url) {
549 $urlparts = parse_url($url[1]);
551 if (isset($urlparts['path'])) {
552 $path = $urlparts['path'];
553 // if our wwwroot has a path component, need to strip that path from beginning of the
554 // 'localpart' to make it relative to moodle's wwwroot
555 $wwwrootpath = parse_url($CFG->wwwroot
, PHP_URL_PATH
);
556 if (!empty($wwwrootpath) && strpos($path, $wwwrootpath) === 0) {
557 $path = substr($path, strlen($wwwrootpath));
561 if (isset($urlparts['query'])) {
562 $localpart .= '?'.$urlparts['query'];
564 if (isset($urlparts['fragment'])) {
565 $localpart .= '#'.$urlparts['fragment'];
568 $indirecturl = $jumpurl . urlencode($localpart);
569 //If we matched on more than just a url (ie an html link), return the url to an href format
570 if ($url[0] != $url[1]) {
571 $indirecturl = 'href="'.$indirecturl.'"';
576 function mnet_get_app_jumppath ($applicationid) {
578 static $appjumppaths;
579 if (!isset($appjumppaths[$applicationid])) {
580 $ssojumpurl = $DB->get_field('mnet_application', 'sso_jump_url', array('id' => $applicationid));
581 $appjumppaths[$applicationid] = $ssojumpurl;
583 return $appjumppaths[$applicationid];
588 * Output debug information about mnet. this will go to the <b>error_log</b>.
590 * @param mixed $debugdata this can be a string, or array or object.
591 * @param int $debuglevel optional , defaults to 1. bump up for very noisy debug info
593 function mnet_debug($debugdata, $debuglevel=1) {
595 $setlevel = get_config('', 'mnet_rpcdebug');
596 if (empty($setlevel) ||
$setlevel < $debuglevel) {
599 if (is_object($debugdata)) {
600 $debugdata = (array)$debugdata;
602 if (is_array($debugdata)) {
603 mnet_debug('DUMPING ARRAY');
604 foreach ($debugdata as $key => $value) {
605 mnet_debug("$key: $value");
607 mnet_debug('END DUMPING ARRAY');
610 $prefix = 'MNET DEBUG ';
611 if (defined('MNET_SERVER')) {
612 $prefix .= " (server $CFG->wwwroot";
613 if ($peer = get_mnet_remote_client() && !empty($peer->wwwroot
)) {
614 $prefix .= ", remote peer " . $peer->wwwroot
;
618 $prefix .= " (client $CFG->wwwroot) ";
620 error_log("$prefix $debugdata");
624 * Return an array of information about all moodle's profile fields
625 * which ones are optional, which ones are forced.
626 * This is used as the basis of providing lists of profile fields to the administrator
627 * to pick which fields to import/export over MNET
629 * @return array(forced => array, optional => array)
631 function mnet_profile_field_options() {
639 'id', // makes no sense
640 'mnethostid', // makes no sense
641 'timecreated', // will be set to relative to the host anyway
642 'timemodified', // will be set to relative to the host anyway
643 'auth', // going to be set to 'mnet'
644 'deleted', // we should never get deleted users sent over, but don't send this anyway
645 'confirmed', // unconfirmed users can't log in to their home site, all remote users considered confirmed
646 'password', // no password for mnet users
647 'theme', // handled separately
648 'lastip', // will be set to relative to the host anyway
651 // these are the ones that user_not_fully_set_up will complain about
652 // and also special case ones
660 'session.gc_lifetime',
661 '_mnet_userpicture_timemodified',
662 '_mnet_userpicture_mimetype',
665 // these are the ones we used to send/receive (pre 2.0)
686 // get a random user record from the database to pull the fields off
687 $randomuser = $DB->get_record('user', array(), '*', IGNORE_MULTIPLE
);
688 foreach ($randomuser as $key => $discard) {
689 if (in_array($key, $excludes) ||
in_array($key, $forced)) {
692 $fields[$key] = $key;
696 'optional' => $fields,
704 * Returns information about MNet peers
706 * @param bool $withdeleted should the deleted peers be returned too
709 function mnet_get_hosts($withdeleted = false) {
712 $sql = "SELECT h.id, h.deleted, h.wwwroot, h.ip_address, h.name, h.public_key, h.public_key_expires,
713 h.transport, h.portno, h.last_connect_time, h.last_log_id, h.applicationid,
714 a.name as app_name, a.display_name as app_display_name, a.xmlrpc_server_url
716 JOIN {mnet_application} a ON h.applicationid = a.id
720 $sql .= " AND h.deleted = 0";
723 $sql .= " ORDER BY h.deleted, h.name, h.id";
725 return $DB->get_records_sql($sql, array($CFG->mnet_localhost_id
));
730 * return an array information about services enabled for the given peer.
731 * in two modes, fulldata or very basic data.
733 * @param mnet_peer $mnet_peer the peer to get information abut
734 * @param boolean $fulldata whether to just return which services are published/subscribed, or more information (defaults to full)
736 * @return array If $fulldata is false, an array is returned like:
738 * serviceid => boolean,
739 * serviceid => boolean,
741 * subscribe => array(
742 * serviceid => boolean,
743 * serviceid => boolean,
745 * If $fulldata is true, an array is returned like:
746 * servicename => array(
747 * apiversion => array(
751 * plugintype => string
752 * pluginname => string
753 * hostsubscribes => boolean
754 * hostpublishes => boolean
758 function mnet_get_service_info(mnet_peer
$mnet_peer, $fulldata=true) {
761 $requestkey = (!empty($fulldata) ?
'fulldata' : 'mydata');
763 static $cache = array();
764 if (array_key_exists($mnet_peer->id
, $cache)) {
765 return $cache[$mnet_peer->id
][$requestkey];
768 $id_list = $mnet_peer->id
;
769 if (!empty($CFG->mnet_all_hosts_id
)) {
770 $id_list .= ', '.$CFG->mnet_all_hosts_id
;
773 $concat = $DB->sql_concat('COALESCE(h2s.id,0) ', ' \'-\' ', ' svc.id', '\'-\'', 'r.plugintype', '\'-\'', 'r.pluginname');
788 {mnet_service2rpc} s2r,
792 {mnet_host2service} h2s
794 h2s.hostid in ($id_list) AND
795 h2s.serviceid = svc.id
798 s2r.serviceid = svc.id AND
803 $resultset = $DB->get_records_sql($query);
805 if (is_array($resultset)) {
806 $resultset = array_values($resultset);
808 $resultset = array();
811 require_once $CFG->dirroot
.'/mnet/xmlrpc/client.php';
813 $remoteservices = array();
814 if ($mnet_peer->id
!= $CFG->mnet_all_hosts_id
) {
815 // Create a new request object
816 $mnet_request = new mnet_xmlrpc_client();
818 // Tell it the path to the method that we want to execute
819 $mnet_request->set_method('system/listServices');
820 $mnet_request->send($mnet_peer);
821 if (is_array($mnet_request->response
)) {
822 foreach($mnet_request->response
as $service) {
823 $remoteservices[$service['name']][$service['apiversion']] = $service;
828 $myservices = array();
830 foreach($resultset as $result) {
831 $result->hostpublishes
= false;
832 $result->hostsubscribes
= false;
833 if (isset($remoteservices[$result->name
][$result->apiversion
])) {
834 if ($remoteservices[$result->name
][$result->apiversion
]['publish'] == 1) {
835 $result->hostpublishes
= true;
837 if ($remoteservices[$result->name
][$result->apiversion
]['subscribe'] == 1) {
838 $result->hostsubscribes
= true;
842 if (empty($myservices[$result->name
][$result->apiversion
])) {
843 $myservices[$result->name
][$result->apiversion
] = array('serviceid' => $result->serviceid
,
844 'name' => $result->name
,
845 'offer' => $result->offer
,
846 'apiversion' => $result->apiversion
,
847 'plugintype' => $result->plugintype
,
848 'pluginname' => $result->pluginname
,
849 'hostsubscribes' => $result->hostsubscribes
,
850 'hostpublishes' => $result->hostpublishes
854 // allhosts_publish allows us to tell the admin that even though he
855 // is disabling a service, it's still available to the host because
856 // he's also publishing it to 'all hosts'
857 if ($result->hostid
== $CFG->mnet_all_hosts_id
&& $CFG->mnet_all_hosts_id
!= $mnet_peer->id
) {
858 $myservices[$result->name
][$result->apiversion
]['allhosts_publish'] = $result->publish
;
859 $myservices[$result->name
][$result->apiversion
]['allhosts_subscribe'] = $result->subscribe
;
860 } elseif (!empty($result->hostid
)) {
861 $myservices[$result->name
][$result->apiversion
]['I_publish'] = $result->publish
;
862 $myservices[$result->name
][$result->apiversion
]['I_subscribe'] = $result->subscribe
;
864 $mydata['publish'][$result->serviceid
] = $result->publish
;
865 $mydata['subscribe'][$result->serviceid
] = $result->subscribe
;
869 $cache[$mnet_peer->id
]['fulldata'] = $myservices;
870 $cache[$mnet_peer->id
]['mydata'] = $mydata;
872 return $cache[$mnet_peer->id
][$requestkey];
876 * return an array of the profile fields to send
877 * with user information to the given mnet host.
879 * @param mnet_peer $peer the peer to send the information to
881 * @return array (like 'username', 'firstname', etc)
883 function mnet_fields_to_send(mnet_peer
$peer) {
884 return _mnet_field_helper($peer, 'export');
888 * return an array of the profile fields to import
889 * from the given host, when creating/updating user accounts
891 * @param mnet_peer $peer the peer we're getting the information from
893 * @return array (like 'username', 'firstname', etc)
895 function mnet_fields_to_import(mnet_peer
$peer) {
896 return _mnet_field_helper($peer, 'import');
900 * helper for {@see mnet_fields_to_import} and {@mnet_fields_to_send}
904 * @param mnet_peer $peer the peer object
905 * @param string $key 'import' or 'export'
907 * @return array (like 'username', 'firstname', etc)
909 function _mnet_field_helper(mnet_peer
$peer, $key) {
910 $tmp = mnet_profile_field_options();
911 $defaults = explode(',', get_config('moodle', 'mnetprofile' . $key . 'fields'));
912 if ('1' === get_config('mnet', 'host' . $peer->id
. $key . 'default')) {
913 return array_merge($tmp['forced'], $defaults);
915 $hostsettings = get_config('mnet', 'host' . $peer->id
. $key . 'fields');
916 if (false === $hostsettings) {
917 return array_merge($tmp['forced'], $defaults);
919 return array_merge($tmp['forced'], explode(',', $hostsettings));
924 * given a user object (or array) and a list of allowed fields,
925 * strip out all the fields that should not be included.
926 * This can be used both for outgoing data and incoming data.
928 * @param mixed $user array or object representing a database record
929 * @param array $fields an array of allowed fields (usually from mnet_fields_to_{send,import}
931 * @return mixed array or object, depending what type of $user object was passed (datatype is respected)
933 function mnet_strip_user($user, $fields) {
934 if (is_object($user)) {
935 $user = (array)$user;
936 $wasobject = true; // so we can cast back before we return
939 foreach ($user as $key => $value) {
940 if (!in_array($key, $fields)) {
944 if (!empty($wasobject)) {
945 $user = (object)$user;