Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?php/*** Library functions for mnet** @author Donal McMullan donal@catalyst.net.nz* @version 0.0.1* @license http://www.gnu.org/copyleft/gpl.html GNU Public License* @package mnet*/require_once $CFG->dirroot.'/mnet/xmlrpc/xmlparser.php';require_once $CFG->dirroot.'/mnet/peer.php';require_once $CFG->dirroot.'/mnet/environment.php';/// CONSTANTS ///////////////////////////////////////////////////////////define('RPC_OK', 0);define('RPC_NOSUCHFILE', 1);define('RPC_NOSUCHCLASS', 2);define('RPC_NOSUCHFUNCTION', 3);define('RPC_FORBIDDENFUNCTION', 4);define('RPC_NOSUCHMETHOD', 5);define('RPC_FORBIDDENMETHOD', 6);/*** Strip extraneous detail from a URL or URI and return the hostname** @param string $uri The URI of a file on the remote computer, optionally* including its http:// prefix like* http://www.example.com/index.html* @return string Just the hostname*/function mnet_get_hostname_from_uri($uri = null) {$count = preg_match("@^(?:http[s]?://)?([A-Z0-9\-\.]+).*@i", $uri, $matches);if ($count > 0) return $matches[1];return false;}/*** Get the remote machine's SSL Cert** @param string $uri The URI of a file on the remote computer, including* its http:// or https:// prefix* @return string A PEM formatted SSL Certificate.*/function mnet_get_public_key($uri, $application=null) {global $CFG, $DB;$mnet = get_mnet_environment();// The key may be cached in the mnet_set_public_key function...// check this first$key = mnet_set_public_key($uri);if ($key != false) {return $key;}if (empty($application)) {$application = $DB->get_record('mnet_application', array('name'=>'moodle'));}$params = [new \PhpXmlRpc\Value($CFG->wwwroot),new \PhpXmlRpc\Value($mnet->public_key),new \PhpXmlRpc\Value($application->name),];$request = new \PhpXmlRpc\Request('system/keyswap', $params);// Let's create a client to handle the request and the response easily.$client = new \PhpXmlRpc\Client($uri . $application->xmlrpc_server_url);$client->setOption('use_curl', \PhpXmlRpc\Client::USE_CURL_ALWAYS);$client->setOption('user_agent', 'Moodle');$client->return_type = 'xmlrpcvals'; // This (keyswap) is not encrypted, so we can expect proper xmlrpc in this case.$client->setOption('request_charset_encoding', 'utf-8');$client->setOption('accepted_charset_encodings', ['utf-8']);// TODO: Link this to DEBUG DEVELOPER or with MNET debugging...// $client->setdebug(1); // See a good number of complete requests and responses.$client->setOption('verifyhost', 0);$client->setOption('verifypeer', false);// TODO: It's curious that this service (keyswap) that needs// a custom client, different from mnet_xmlrpc_client, because// this is not encrypted / signed, does support proxies and the// general one does not. Worth analysing if the support below// should be added to it.// Some curl options need to be set apart, accumulate them here.$extracurloptions = [];// Check for proxy.if (!empty($CFG->proxyhost) && !is_proxybypass($uri)) {// SOCKS supported in PHP5 only.if (!empty($CFG->proxytype) && ($CFG->proxytype == 'SOCKS5')) {if (defined('CURLPROXY_SOCKS5')) {$extracurloptions[CURLOPT_PROXYTYPE] = CURLPROXY_SOCKS5;} else {throw new \moodle_exception( 'socksnotsupported', 'mnet');}}$extracurloptions[CURLOPT_HTTPPROXYTUNNEL] = false;// Configure proxy host, port, user, pass and auth.$client->setProxy($CFG->proxyhost,empty($CFG->proxyport) ? 0 : $CFG->proxyport,empty($CFG->proxyuser) ? '' : $CFG->proxyuser,empty($CFG->proxypassword) ? '' : $CFG->proxypassword,defined('CURLOPT_PROXYAUTH') ? CURLAUTH_BASIC | CURLAUTH_NTLM : 1);}// Finally, add the extra curl options we may have accumulated.$client->setCurlOptions($extracurloptions);$response = $client->send($request, 60);// Check curl / xmlrpc errors.if ($response->faultCode()) {debugging("Request for $uri failed with error {$response->faultCode()}: {$response->faultString()}");return false;}// Check HTTP error code.$status = $response->httpResponse()['status_code'];if (!empty($status) && ($status != 200)) {debugging("Request for $uri failed with HTTP code " . $status);return false;}// Get the peer actual public key from the response.$res = $response->value()->scalarval();if (!is_array($res)) { // ! error$public_certificate = $res;$credentials=array();if (strlen(trim($public_certificate))) {$credentials = openssl_x509_parse($public_certificate);$host = $credentials['subject']['CN'];if (array_key_exists( 'subjectAltName', $credentials['subject'])) {$host = $credentials['subject']['subjectAltName'];}if (strpos($uri, $host) !== false) {mnet_set_public_key($uri, $public_certificate);return $public_certificate;}else {debugging("Request for $uri returned public key for different URI - $host");}}else {debugging("Request for $uri returned empty response");}}else {debugging( "Request for $uri returned unexpected result");}return false;}/*** Store a URI's public key in a static variable, or retrieve the key for a URI** @param string $uri The URI of a file on the remote computer, including its* https:// prefix* @param mixed $key A public key to store in the array OR null. If the key* is null, the function will return the previously stored* key for the supplied URI, should it exist.* @return mixed A public key OR true/false.*/function mnet_set_public_key($uri, $key = null) {static $keyarray = array();if (isset($keyarray[$uri]) && empty($key)) {return $keyarray[$uri];} elseif (!empty($key)) {$keyarray[$uri] = $key;return true;}return false;}/*** Sign a message and return it in an XML-Signature document** This function can sign any content, but it was written to provide a system of* signing XML-RPC request and response messages. The message will be base64* encoded, so it does not need to be text.** We compute the SHA1 digest of the message.* We compute a signature on that digest with our private key.* We link to the public key that can be used to verify our signature.* We base64 the message data.* We identify our wwwroot - this must match our certificate's CN** The XML-RPC document will be parceled inside an XML-SIG document, which holds* the base64_encoded XML as an object, the SHA1 digest of that document, and a* signature of that document using the local private key. This signature will* uniquely identify the RPC document as having come from this server.** See the {@Link http://www.w3.org/TR/xmldsig-core/ XML-DSig spec} at the W3c* site** @param string $message The data you want to sign* @param resource $privatekey The private key to sign the response with* @return string An XML-DSig document*/function mnet_sign_message($message, $privatekey = null) {global $CFG;$digest = sha1($message);$mnet = get_mnet_environment();// If the user hasn't supplied a private key (for example, one of our older,// expired private keys, we get the current default private key and use that.if ($privatekey == null) {$privatekey = $mnet->get_private_key();}// The '$sig' value below is returned by reference.// We initialize it first to stop my IDE from complaining.$sig = '';$bool = openssl_sign($message, $sig, $privatekey);// Avoid passing null values to base64_encode.if ($bool === false) {throw new \moodle_exception('opensslsignerror');}$message = '<?xml version="1.0" encoding="iso-8859-1"?><signedMessage><Signature Id="MoodleSignature" xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><Reference URI="#XMLRPC-MSG"><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>'.$digest.'</DigestValue></Reference></SignedInfo><SignatureValue>'.base64_encode($sig).'</SignatureValue><KeyInfo><RetrievalMethod URI="'.$CFG->wwwroot.'/mnet/publickey.php"/></KeyInfo></Signature><object ID="XMLRPC-MSG">'.base64_encode($message).'</object><wwwroot>'.$mnet->wwwroot.'</wwwroot><timestamp>'.time().'</timestamp></signedMessage>';return $message;}/*** Encrypt a message and return it in an XML-Encrypted document** This function can encrypt any content, but it was written to provide a system* of encrypting XML-RPC request and response messages. The message will be* base64 encoded, so it does not need to be text - binary data should work.** We compute the SHA1 digest of the message.* We compute a signature on that digest with our private key.* We link to the public key that can be used to verify our signature.* We base64 the message data.* We identify our wwwroot - this must match our certificate's CN** The XML-RPC document will be parceled inside an XML-SIG document, which holds* the base64_encoded XML as an object, the SHA1 digest of that document, and a* signature of that document using the local private key. This signature will* uniquely identify the RPC document as having come from this server.** See the {@Link http://www.w3.org/TR/xmlenc-core/ XML-ENC spec} at the W3c* site** @param string $message The data you want to sign* @param string $remote_certificate Peer's certificate in PEM format* @return string An XML-ENC document*/function mnet_encrypt_message($message, $remote_certificate) {$mnet = get_mnet_environment();// Generate a key resource from the remote_certificate text string$publickey = openssl_get_publickey($remote_certificate);if ($publickey === false) {// Remote certificate is faulty.return false;}// Initialize vars$encryptedstring = '';$symmetric_keys = array();// passed by ref -> &$encryptedstring &$symmetric_keys$bool = openssl_seal($message, $encryptedstring, $symmetric_keys, array($publickey), 'RC4');// Avoid passing null values to base64_encode.if ($bool === false) {throw new \moodle_exception('opensslsealerror');}$message = $encryptedstring;$symmetrickey = array_pop($symmetric_keys);$message = '<?xml version="1.0" encoding="iso-8859-1"?><encryptedMessage><EncryptedData Id="ED" xmlns="http://www.w3.org/2001/04/xmlenc#"><EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#arcfour"/><ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:RetrievalMethod URI="#EK" Type="http://www.w3.org/2001/04/xmlenc#EncryptedKey"/><ds:KeyName>XMLENC</ds:KeyName></ds:KeyInfo><CipherData><CipherValue>'.base64_encode($message).'</CipherValue></CipherData></EncryptedData><EncryptedKey Id="EK" xmlns="http://www.w3.org/2001/04/xmlenc#"><EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/><ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:KeyName>SSLKEY</ds:KeyName></ds:KeyInfo><CipherData><CipherValue>'.base64_encode($symmetrickey).'</CipherValue></CipherData><ReferenceList><DataReference URI="#ED"/></ReferenceList><CarriedKeyName>XMLENC</CarriedKeyName></EncryptedKey><wwwroot>'.$mnet->wwwroot.'</wwwroot></encryptedMessage>';return $message;}/*** Get your SSL keys from the database, or create them (if they don't exist yet)** Get your SSL keys from the database, or (if they don't exist yet) call* mnet_generate_keypair to create them** @param string $string The text you want to sign* @return string The signature over that text*/function mnet_get_keypair() {global $CFG, $DB;static $keypair = null;if (!is_null($keypair)) return $keypair;if ($result = get_config('mnet', 'openssl')) {list($keypair['certificate'], $keypair['keypair_PEM']) = explode('@@@@@@@@', $result);return $keypair;} else {$keypair = mnet_generate_keypair();return $keypair;}}/*** Generate public/private keys and store in the config table** Use the distinguished name provided to create a CSR, and then sign that CSR* with the same credentials. Store the keypair you create in the config table.* If a distinguished name is not provided, create one using the fullname of* 'the course with ID 1' as your organization name, and your hostname (as* detailed in $CFG->wwwroot).** @param array $dn The distinguished name of the server* @return string The signature over that text*/function mnet_generate_keypair($dn = null, $days=28) {global $CFG, $USER, $DB;// check if lifetime has been overridenif (!empty($CFG->mnetkeylifetime)) {$days = $CFG->mnetkeylifetime;}$host = strtolower($CFG->wwwroot);$host = preg_replace("~^http(s)?://~",'',$host);$break = strpos($host.'/' , '/');$host = substr($host, 0, $break);$site = get_site();$organization = $site->fullname;$keypair = array();$country = 'NZ';$province = 'Wellington';$locality = 'Wellington';$email = !empty($CFG->noreplyaddress) ? $CFG->noreplyaddress : 'noreply@'.$_SERVER['HTTP_HOST'];if(!empty($USER->country)) {$country = $USER->country;}if(!empty($USER->city)) {$province = $USER->city;$locality = $USER->city;}if(!empty($USER->email)) {$email = $USER->email;}if (is_null($dn)) {$dn = array("countryName" => $country,"stateOrProvinceName" => $province,"localityName" => $locality,"organizationName" => $organization,"organizationalUnitName" => 'Moodle',"commonName" => substr($CFG->wwwroot, 0, 64),"subjectAltName" => $CFG->wwwroot,"emailAddress" => $email);}$dnlimits = array('countryName' => 2,'stateOrProvinceName' => 128,'localityName' => 128,'organizationName' => 64,'organizationalUnitName' => 64,'commonName' => 64,'emailAddress' => 128);foreach ($dnlimits as $key => $length) {$dn[$key] = core_text::substr($dn[$key], 0, $length);}// ensure we remove trailing slashes$dn["commonName"] = preg_replace(':/$:', '', $dn["commonName"]);if (!empty($CFG->opensslcnf)) { //allow specification of openssl.cnf especially for Windows installs$new_key = openssl_pkey_new(array("config" => $CFG->opensslcnf));} else {$new_key = openssl_pkey_new();}if ($new_key === false) {// can not generate keys - missing openssl.cnf??return null;}if (!empty($CFG->opensslcnf)) { //allow specification of openssl.cnf especially for Windows installs$csr_rsc = openssl_csr_new($dn, $new_key, array("config" => $CFG->opensslcnf));$selfSignedCert = openssl_csr_sign($csr_rsc, null, $new_key, $days, array("config" => $CFG->opensslcnf));} else {$csr_rsc = openssl_csr_new($dn, $new_key, array('private_key_bits',2048));$selfSignedCert = openssl_csr_sign($csr_rsc, null, $new_key, $days);}unset($csr_rsc); // Free up the resource// We export our self-signed certificate to a string.openssl_x509_export($selfSignedCert, $keypair['certificate']);// TODO: Remove this block once PHP 8.0 becomes required.if (PHP_MAJOR_VERSION < 8) {openssl_x509_free($selfSignedCert);}// Export your public/private key pair as a PEM encoded string. You// can protect it with an optional passphrase if you wish.if (!empty($CFG->opensslcnf)) { //allow specification of openssl.cnf especially for Windows installs$export = openssl_pkey_export($new_key, $keypair['keypair_PEM'], null, array("config" => $CFG->opensslcnf));} else {$export = openssl_pkey_export($new_key, $keypair['keypair_PEM'] /* , $passphrase */);}// TODO: Remove this block once PHP 8.0 becomes required.if (PHP_MAJOR_VERSION < 8) {openssl_pkey_free($new_key);}unset($new_key); // Free up the resourcereturn $keypair;}function mnet_update_sso_access_control($username, $mnet_host_id, $accessctrl) {global $DB;$mnethost = $DB->get_record('mnet_host', array('id'=>$mnet_host_id));if ($aclrecord = $DB->get_record('mnet_sso_access_control', array('username'=>$username, 'mnet_host_id'=>$mnet_host_id))) {// Update.$aclrecord->accessctrl = $accessctrl;$DB->update_record('mnet_sso_access_control', $aclrecord);// Trigger access control updated event.$params = array('objectid' => $aclrecord->id,'context' => context_system::instance(),'other' => array('username' => $username,'hostname' => $mnethost->name,'accessctrl' => $accessctrl));$event = \core\event\mnet_access_control_updated::create($params);$event->add_record_snapshot('mnet_host', $mnethost);$event->trigger();} else {// Insert.$aclrecord = new stdClass();$aclrecord->username = $username;$aclrecord->accessctrl = $accessctrl;$aclrecord->mnet_host_id = $mnet_host_id;$aclrecord->id = $DB->insert_record('mnet_sso_access_control', $aclrecord);// Trigger access control created event.$params = array('objectid' => $aclrecord->id,'context' => context_system::instance(),'other' => array('username' => $username,'hostname' => $mnethost->name,'accessctrl' => $accessctrl));$event = \core\event\mnet_access_control_created::create($params);$event->add_record_snapshot('mnet_host', $mnethost);$event->trigger();}return true;}function mnet_get_peer_host ($mnethostid) {global $DB;static $hosts;if (!isset($hosts[$mnethostid])) {$host = $DB->get_record('mnet_host', array('id' => $mnethostid));$hosts[$mnethostid] = $host;}return $hosts[$mnethostid];}/*** Inline function to modify a url string so that mnet users are requested to* log in at their mnet identity provider (if they are not already logged in)* before ultimately being directed to the original url.** @param string $jumpurl the url which user should initially be directed to.* This is a URL associated with a moodle networking peer when it* is fulfiling a role as an identity provider (IDP). Different urls for* different peers, the jumpurl is formed partly from the IDP's webroot, and* partly from a predefined local path within that webwroot.* The result of the user hitting this jump url is that they will be asked* to login (at their identity provider (if they aren't already)), mnet* will prepare the necessary authentication information, then redirect* them back to somewhere at the content provider(CP) moodle (this moodle)* @param array $url array with 2 elements* 0 - context the url was taken from, possibly just the url, possibly href="url"* 1 - the destination url* @return string the url the remote user should be supplied with.*/function mnet_sso_apply_indirection ($jumpurl, $url) {global $USER, $CFG;$localpart='';$urlparts = parse_url($url[1]);if($urlparts) {if (isset($urlparts['path'])) {$path = $urlparts['path'];// if our wwwroot has a path component, need to strip that path from beginning of the// 'localpart' to make it relative to moodle's wwwroot$wwwrootpath = parse_url($CFG->wwwroot, PHP_URL_PATH);if (!empty($wwwrootpath) && strpos($path, $wwwrootpath) === 0) {$path = substr($path, strlen($wwwrootpath));}$localpart .= $path;}if (isset($urlparts['query'])) {$localpart .= '?'.$urlparts['query'];}if (isset($urlparts['fragment'])) {$localpart .= '#'.$urlparts['fragment'];}}$indirecturl = $jumpurl . urlencode($localpart);//If we matched on more than just a url (ie an html link), return the url to an href formatif ($url[0] != $url[1]) {$indirecturl = 'href="'.$indirecturl.'"';}return $indirecturl;}function mnet_get_app_jumppath ($applicationid) {global $DB;static $appjumppaths;if (!isset($appjumppaths[$applicationid])) {$ssojumpurl = $DB->get_field('mnet_application', 'sso_jump_url', array('id' => $applicationid));$appjumppaths[$applicationid] = $ssojumpurl;}return $appjumppaths[$applicationid];}/*** Output debug information about mnet. this will go to the <b>error_log</b>.** @param mixed $debugdata this can be a string, or array or object.* @param int $debuglevel optional , defaults to 1. bump up for very noisy debug info*/function mnet_debug($debugdata, $debuglevel=1) {global $CFG;$setlevel = get_config('', 'mnet_rpcdebug');if (empty($setlevel) || $setlevel < $debuglevel) {return;}if (is_object($debugdata)) {$debugdata = (array)$debugdata;}if (is_array($debugdata)) {mnet_debug('DUMPING ARRAY');foreach ($debugdata as $key => $value) {mnet_debug("$key: $value");}mnet_debug('END DUMPING ARRAY');return;}$prefix = 'MNET DEBUG ';if (defined('MNET_SERVER')) {$prefix .= " (server $CFG->wwwroot";if ($peer = get_mnet_remote_client() && !empty($peer->wwwroot)) {$prefix .= ", remote peer " . $peer->wwwroot;}$prefix .= ')';} else {$prefix .= " (client $CFG->wwwroot) ";}error_log("$prefix $debugdata");}/*** Return an array of information about all moodle's profile fields* which ones are optional, which ones are forced.* This is used as the basis of providing lists of profile fields to the administrator* to pick which fields to import/export over MNET** @return array(forced => array, optional => array)*/function mnet_profile_field_options() {global $DB;static $info;if (!empty($info)) {return $info;}$excludes = array('id', // makes no sense'mnethostid', // makes no sense'timecreated', // will be set to relative to the host anyway'timemodified', // will be set to relative to the host anyway'auth', // going to be set to 'mnet''deleted', // we should never get deleted users sent over, but don't send this anyway'confirmed', // unconfirmed users can't log in to their home site, all remote users considered confirmed'password', // no password for mnet users'theme', // handled separately'lastip', // will be set to relative to the host anyway);// these are the ones that user_not_fully_set_up will complain about// and also special case ones$forced = array('username','email','firstname','lastname','auth','wwwroot','session.gc_lifetime','_mnet_userpicture_timemodified','_mnet_userpicture_mimetype',);// these are the ones we used to send/receive (pre 2.0)$legacy = array('username','email','auth','deleted','firstname','lastname','city','country','lang','timezone','description','mailformat','maildigest','maildisplay','htmleditor','wwwroot','picture',);// get a random user record from the database to pull the fields off$randomuser = $DB->get_record('user', array(), '*', IGNORE_MULTIPLE);foreach ($randomuser as $key => $discard) {if (in_array($key, $excludes) || in_array($key, $forced)) {continue;}$fields[$key] = $key;}$info = array('forced' => $forced,'optional' => $fields,'legacy' => $legacy,);return $info;}/*** Returns information about MNet peers** @param bool $withdeleted should the deleted peers be returned too* @return array*/function mnet_get_hosts($withdeleted = false) {global $CFG, $DB;$sql = "SELECT h.id, h.deleted, h.wwwroot, h.ip_address, h.name, h.public_key, h.public_key_expires,h.transport, h.portno, h.last_connect_time, h.last_log_id, h.applicationid,a.name as app_name, a.display_name as app_display_name, a.xmlrpc_server_urlFROM {mnet_host} hJOIN {mnet_application} a ON h.applicationid = a.idWHERE h.id <> ?";if (!$withdeleted) {$sql .= " AND h.deleted = 0";}$sql .= " ORDER BY h.deleted, h.name, h.id";return $DB->get_records_sql($sql, array($CFG->mnet_localhost_id));}/*** return an array information about services enabled for the given peer.* in two modes, fulldata or very basic data.** @param mnet_peer $mnet_peer the peer to get information abut* @param boolean $fulldata whether to just return which services are published/subscribed, or more information (defaults to full)** @return array If $fulldata is false, an array is returned like:* publish => array(* serviceid => boolean,* serviceid => boolean,* ),* subscribe => array(* serviceid => boolean,* serviceid => boolean,* )* If $fulldata is true, an array is returned like:* servicename => array(* apiversion => array(* name => string* offer => boolean* apiversion => int* plugintype => string* pluginname => string* hostsubscribes => boolean* hostpublishes => boolean* ),* )*/function mnet_get_service_info(mnet_peer $mnet_peer, $fulldata=true) {global $CFG, $DB;$requestkey = (!empty($fulldata) ? 'fulldata' : 'mydata');static $cache = array();if (array_key_exists($mnet_peer->id, $cache)) {return $cache[$mnet_peer->id][$requestkey];}$id_list = $mnet_peer->id;if (!empty($CFG->mnet_all_hosts_id)) {$id_list .= ', '.$CFG->mnet_all_hosts_id;}$concat = $DB->sql_concat('COALESCE(h2s.id,0) ', ' \'-\' ', ' svc.id', '\'-\'', 'r.plugintype', '\'-\'', 'r.pluginname');$query = "SELECT DISTINCT$concat as id,svc.id as serviceid,svc.name,svc.offer,svc.apiversion,r.plugintype,r.pluginname,h2s.hostid,h2s.publish,h2s.subscribeFROM{mnet_service2rpc} s2r,{mnet_rpc} r,{mnet_service} svcLEFT JOIN{mnet_host2service} h2sONh2s.hostid in ($id_list) ANDh2s.serviceid = svc.idWHEREsvc.offer = '1' ANDs2r.serviceid = svc.id ANDs2r.rpcid = r.idORDER BYsvc.name ASC";$resultset = $DB->get_records_sql($query);if (is_array($resultset)) {$resultset = array_values($resultset);} else {$resultset = array();}require_once $CFG->dirroot.'/mnet/xmlrpc/client.php';$remoteservices = array();if ($mnet_peer->id != $CFG->mnet_all_hosts_id) {// Create a new request object$mnet_request = new mnet_xmlrpc_client();// Tell it the path to the method that we want to execute$mnet_request->set_method('system/listServices');$mnet_request->send($mnet_peer);if (is_array($mnet_request->response)) {foreach($mnet_request->response as $service) {$remoteservices[$service['name']][$service['apiversion']] = $service;}}}$myservices = array();$mydata = array();foreach($resultset as $result) {$result->hostpublishes = false;$result->hostsubscribes = false;if (isset($remoteservices[$result->name][$result->apiversion])) {if ($remoteservices[$result->name][$result->apiversion]['publish'] == 1) {$result->hostpublishes = true;}if ($remoteservices[$result->name][$result->apiversion]['subscribe'] == 1) {$result->hostsubscribes = true;}}if (empty($myservices[$result->name][$result->apiversion])) {$myservices[$result->name][$result->apiversion] = array('serviceid' => $result->serviceid,'name' => $result->name,'offer' => $result->offer,'apiversion' => $result->apiversion,'plugintype' => $result->plugintype,'pluginname' => $result->pluginname,'hostsubscribes' => $result->hostsubscribes,'hostpublishes' => $result->hostpublishes);}// allhosts_publish allows us to tell the admin that even though he// is disabling a service, it's still available to the host because// he's also publishing it to 'all hosts'if ($result->hostid == $CFG->mnet_all_hosts_id && $CFG->mnet_all_hosts_id != $mnet_peer->id) {$myservices[$result->name][$result->apiversion]['allhosts_publish'] = $result->publish;$myservices[$result->name][$result->apiversion]['allhosts_subscribe'] = $result->subscribe;} elseif (!empty($result->hostid)) {$myservices[$result->name][$result->apiversion]['I_publish'] = $result->publish;$myservices[$result->name][$result->apiversion]['I_subscribe'] = $result->subscribe;}$mydata['publish'][$result->serviceid] = $result->publish;$mydata['subscribe'][$result->serviceid] = $result->subscribe;}$cache[$mnet_peer->id]['fulldata'] = $myservices;$cache[$mnet_peer->id]['mydata'] = $mydata;return $cache[$mnet_peer->id][$requestkey];}/*** return an array of the profile fields to send* with user information to the given mnet host.** @param mnet_peer $peer the peer to send the information to** @return array (like 'username', 'firstname', etc)*/function mnet_fields_to_send(mnet_peer $peer) {return _mnet_field_helper($peer, 'export');}/*** return an array of the profile fields to import* from the given host, when creating/updating user accounts** @param mnet_peer $peer the peer we're getting the information from** @return array (like 'username', 'firstname', etc)*/function mnet_fields_to_import(mnet_peer $peer) {return _mnet_field_helper($peer, 'import');}/*** helper for {@see mnet_fields_to_import} and {@mnet_fields_to_send}** @access private** @param mnet_peer $peer the peer object* @param string $key 'import' or 'export'** @return array (like 'username', 'firstname', etc)*/function _mnet_field_helper(mnet_peer $peer, $key) {$tmp = mnet_profile_field_options();$defaults = explode(',', get_config('moodle', 'mnetprofile' . $key . 'fields'));if ('1' === get_config('mnet', 'host' . $peer->id . $key . 'default')) {return array_merge($tmp['forced'], $defaults);}$hostsettings = get_config('mnet', 'host' . $peer->id . $key . 'fields');if (false === $hostsettings) {return array_merge($tmp['forced'], $defaults);}return array_merge($tmp['forced'], explode(',', $hostsettings));}/*** given a user object (or array) and a list of allowed fields,* strip out all the fields that should not be included.* This can be used both for outgoing data and incoming data.** @param mixed $user array or object representing a database record* @param array $fields an array of allowed fields (usually from mnet_fields_to_{send,import}** @return mixed array or object, depending what type of $user object was passed (datatype is respected)*/function mnet_strip_user($user, $fields) {if (is_object($user)) {$user = (array)$user;$wasobject = true; // so we can cast back before we return}foreach ($user as $key => $value) {if (!in_array($key, $fields)) {unset($user[$key]);}}if (!empty($wasobject)) {$user = (object)$user;}return $user;}