]> git.mjollnir.org Git - moodle.git/commitdiff
MDL17980 mnet improvements
authorpeterbulmer <peterbulmer>
Mon, 3 Aug 2009 03:38:23 +0000 (03:38 +0000)
committerpeterbulmer <peterbulmer>
Mon, 3 Aug 2009 03:38:23 +0000 (03:38 +0000)
* Allow multiple roles to be allocated by identity provider
* Allow existing enrolment plugins to manage mnet enrolments

Author: Peter Bulmer <peter.bulmer@catalyst.net.nz>

30 files changed:
admin/mnet/mnet_review.html
admin/mnet/mnet_rolemapping.php [new file with mode: 0644]
admin/mnet/mnet_rolemapping_form.php [new file with mode: 0644]
admin/mnet/mnet_rolespermitted.php [new file with mode: 0644]
admin/mnet/mnet_rolespermitted_form.php [new file with mode: 0644]
admin/mnet/peers.php
admin/roles/define.php
admin/settings/mnet.php
auth/mnet/auth.php
blocks/admin/block_admin.php
blocks/course_list/block_course_list.php
course/lib.php
course/mnet.html [new file with mode: 0644]
course/mnet.php [new file with mode: 0644]
course/view.php
enrol/mnet/allowed_courses.php
enrol/mnet/config.html
enrol/mnet/enrol.php
lang/en_utf8/help/mnetcontent.html [new file with mode: 0644]
lang/en_utf8/help/mnetcp.html [new file with mode: 0644]
lang/en_utf8/moodle.php
lang/en_utf8/role.php
lib/accesslib.php
lib/datalib.php
lib/db/access.php
lib/db/install.xml
lib/db/upgrade.php
mnet/lib.php
pix/i/content.gif [new file with mode: 0644]
version.php

index 44723b25d8d7c755ea0b8b7bb2a22e49b8e8cd61..a0c62f6c40a4aca29140d8257c80ddfc8b809439 100644 (file)
@@ -13,6 +13,8 @@ if (isset($mnet_peer->id) && $mnet_peer->id > 0) {
     $tabs[] = new tabobject('mnetthemes', 'mnet_themes.php?step=list&amp;hostid='.$mnet_peer->id, $strmnetthemes, $strmnetthemes, false);
     if ($mnet_peer->application->name == 'moodle') {
         $tabs[] = new tabobject('mnetlog', $logurl, $strmnetlog, $strmnetlog, false);
+        $tabs[] = new tabobject('rolespermitted', 'mnet_rolespermitted.php?hostid='.$mnet_peer->id, $strrolespermitted, $strrolespermitted, false);
+        $tabs[] = new tabobject('rolemapping', 'mnet_rolemapping.php?hostid='.$mnet_peer->id, $strrolemapping, $strrolemapping, false);
     }
 } else {
     $tabs[] = new tabobject('mnetdetails', '#', $strmnetedithost, $strmnetedithost, false);
diff --git a/admin/mnet/mnet_rolemapping.php b/admin/mnet/mnet_rolemapping.php
new file mode 100644 (file)
index 0000000..faf34d3
--- /dev/null
@@ -0,0 +1,100 @@
+<?php
+require_once(dirname(dirname(dirname(__FILE__))) . '/config.php');
+require_once('mnet_rolemapping_form.php');
+require_once($CFG->dirroot .'/mnet/lib.php');
+
+$mnethostid = required_param('hostid', PARAM_INT);
+$mnetpeer = mnet_get_peer_host($mnethostid);
+$PAGE->set_generaltype('form');
+$PAGE->set_url('admin/mnet/mnet_rolemapping.php', array('hostid' => $mnethostid));
+
+$pagetitle = get_string('mnetmaproles');
+$extranavlinks = array(
+        array('name' => 'Administration', 'link' => '', 'type'=> 'title'),
+        array('name' => 'Networking', 'link' => '', 'type' => 'title'),
+        array('name' => 'Peers', 'link' => $CFG->wwwroot . '/admin/mnet/peers.php', 'type' => 'title'),
+        array('name' => $mnetpeer->name, 'link' => $CFG->wwwroot . '/admin/mnet/peers.php?hostid='.$mnethostid, 'type' => 'title'),
+        array('name' => $pagetitle, 'link' => '', 'type' => 'title'),
+        );
+$navigation = build_navigation($extranavlinks);
+print_header_simple($pagetitle, $pagetitle, $navigation, '', '', false);
+
+$mform = new rolemapping_form();
+$rolessql =
+        'SELECT r.id, r.shortname, r.name, rm.remoterole, rm.id as mapid ' .
+        'FROM {role} r ' .
+        ' LEFT JOIN {mnet_role_mapping} rm ON rm.localrole = r.id AND rm.mnethost = ? ';
+$rolesparams = array($mnethostid);
+if ($mform->is_cancelled()){
+    redirect($CFG->wwwroot . '/admin/mnet/peers.php?hostid=' . $mnethostid, get_string('changescancelled'), 1);
+} else if ($fromform=$mform->get_data()) {
+    $roles = $DB->get_records_sql($rolessql, $rolesparams);
+    $usersinqueue = 0;
+
+    if (!empty($roles)) {
+        foreach ($roles as $role) {
+            if (isset($fromform->{'rolemapping-' . $role->id})) {
+                $newchoice = $fromform->{'rolemapping-' . $role->id};
+            } else {
+                // User has somehow supplied the form without saying what to do with this role - assume they mean no role mapping:
+                $newchoice = -1;
+            }
+            $toform['rolemapping-' . $role->id] = $newchoice;
+            // Don't actually store non-mappings in db:
+            if ($newchoice == -1) {
+                $newchoice = NULL;
+            }
+            if ($newchoice === $role->remoterole) {
+                // No change in this role mapping, nothing to do
+                continue;
+            }
+            // Role mapping has changed - get everyone with this local role in queue to get their remote role updated.
+            $usersinqueue += manage_role_mapping($mnethostid, $role->id);
+
+            $rolemappingobj = new stdclass;
+            $rolemappingobj->mnethost = $mnethostid;
+            $rolemappingobj->localrole = $role->id;
+            $rolemappingobj->remoterole = $newchoice;
+            if (isset($role->remoterole)) {
+                if ($newchoice === NULL) {
+                    $DB->delete_records('mnet_role_mapping', array('id' => $role->mapid));
+                } else {
+                    $rolemappingobj->id = $role->mapid;
+                    $DB->update_record('mnet_role_mapping', $rolemappingobj);
+                }
+            } else {
+                $DB->insert_record('mnet_role_mapping', $rolemappingobj);
+            }
+        }
+    }
+    print_string('settingssaved');
+    echo '<br />';
+    if ($usersinqueue) {
+        print_string('mnetusersinqueue', 'moodle', $usersinqueue);
+    }
+
+    $toform['hostid'] = $mnethostid;
+    $mform->set_data($toform);
+    $mform->display();
+    print_footer();
+
+} else {
+    $toform = array();
+    $roles = $DB->get_records_sql($rolessql, $rolesparams);
+    if (!empty($roles)) {
+        foreach ($roles as $role) {
+            if (isset($role->remoterole)) {
+                $toform['rolemapping-' . $role->id] = $role->remoterole;
+            } else {
+                $toform['rolemapping-' . $role->id] = -1;
+            }
+        }
+    }
+    $toform['hostid'] = $mnethostid;
+
+    $mform->set_data($toform);
+    $mform->display();
+    print_footer();
+
+}
+?>
diff --git a/admin/mnet/mnet_rolemapping_form.php b/admin/mnet/mnet_rolemapping_form.php
new file mode 100644 (file)
index 0000000..3f74f5e
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+require_once ('../../config.php');
+require_once ($CFG->libdir . '/formslib.php');
+
+class rolemapping_form extends moodleform {
+
+    function definition() {
+
+        global $CFG;
+        $mform =& $this->_form;
+        $mform->addElement('header', 'mapping', get_string('mnetrolemapping'));
+        $mform->addElement('static', 'instructions', '',get_string('mnetrolemappinginstructions'), ' ');
+        $mform->addElement('hidden', 'hostid', 'yes');
+        $this->add_action_buttons();
+
+    }
+
+    function definition_after_data() {
+        global $CFG, $DB;
+        require_once($CFG->dirroot . '/mnet/lib.php');
+        $mform =& $this->_form;
+        $mnethostid = $mform->getElementValue('hostid');
+        $mnethost = $DB->get_record('mnet_host', array('id' => $mnethostid));
+        $remoteroleoptions = array();
+
+        $rolessql = 'SELECT r.id, r.shortname, r.name, rm.remoterole, rm.id as mapid ' .
+                'FROM {role} r ' .
+                ' LEFT JOIN {mnet_role_mapping} rm ON rm.localrole = r.id and rm.mnethost = ? ' .
+                'ORDER BY r.id';
+        $rolesparams = array($mnethostid);
+        $roles = $DB->get_records_sql($rolessql, $rolesparams);
+
+        // Determine if we already have a role mapped to a remote role (mappings to default role don't count)
+        $actualremoterole = false;
+        foreach ($roles as $role) {
+            if (!empty($role->remoterole)) {
+                $actualremoterole = true;
+                break;
+            }
+        }
+
+        $remotedefaultrole = false;
+        if (!$actualremoterole) {
+            // Remote peer may be old & unable to tell us what roles it shares
+            // See if the mnet peer has upgraded to new mnet code since we last checked
+            // If it knows how to tell us it's default role, it also knows how to tell us what roles it shares w/ us
+            $remotedefaultrole = mnet_get_default_role($mnethostid);
+            if (!empty($remotedefaultrole)) {
+                $DB->set_field('mnet_role_mapping', 'remoterole', $remotedefaultrole->id,
+                        array('remoterole' => 0, 'mnethost' => $mnethostid));
+                $actualremoterole = true;
+            }
+        }
+        if ($actualremoterole) {
+            $remoteroles = mnet_get_allocatable_roles($mnethostid);
+            foreach ($remoteroles as $remoterole) {
+                $remoteroleoptions[$remoterole->id] = $mnethost->name . ' - ' . $remoterole->shortname;
+            }
+        } else {
+            //Still talking to an mnet peer that does't publish more than one role,
+            // and doesn't know how to tell us what that role is:
+            $remoteroleoptions[0] = $mnethost->name . ' - Default Role';
+        }
+
+        $remoteroleoptions[-1] = 'No Role';
+        foreach ($roles as $role) {
+            $mform->addElement('select', 'rolemapping-' . $role->id,
+                    $role->name . ' (' . $role->shortname . ') ',
+                    $remoteroleoptions);
+            if (!empty($role->remoterole) && !isset($remoteroleoptions[$role->remoterole])) {
+                //The remote role that this role is currently mapped to isn't shared any more
+                $DB->delete_records('mnet_role_mapping', array('id' => $role->mapid));
+                manage_role_mapping($mnethostid, $role->id);
+            }
+        }
+        $mform->removeElement('buttonar');
+        $this->add_action_buttons();
+    }
+}
+?>
diff --git a/admin/mnet/mnet_rolespermitted.php b/admin/mnet/mnet_rolespermitted.php
new file mode 100644 (file)
index 0000000..2eae14b
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+require_once(dirname(dirname(dirname(__FILE__))) . '/config.php');
+require_once('mnet_rolespermitted_form.php');
+require_once($CFG->dirroot .'/mnet/lib.php');
+
+$mnethostid = required_param('hostid', PARAM_INT);
+$mnetpeer = mnet_get_peer_host($mnethostid);
+$PAGE->set_generaltype('form');
+$PAGE->set_url('admin/mnet/mnet_rolespermitted.php', array('hostid' => $mnethostid));
+
+$pagetitle = get_string('mnetauthorisemnetroles');
+$extranavlinks = array(
+        array('name' => 'Administration', 'link' => '', 'type' => 'title'),
+        array('name' => 'Networking', 'link' => '', 'type' => 'title'),
+        array('name' => 'Peers', 'link' => $CFG->wwwroot . '/admin/mnet/peers.php', 'type' => 'title'),
+        array('name' => $mnetpeer->name, 'link' => $CFG->wwwroot . '/admin/mnet/peers.php?hostid='.$mnethostid, 'type' => 'title'),
+        array('name' => $pagetitle, 'link' => '', 'type' => 'title'),
+        );
+$navigation = build_navigation($extranavlinks);
+print_header_simple($pagetitle, $pagetitle, $navigation, '', '', false);
+$rolessql =
+        'SELECT ' .
+        ' r.id, r.shortname, r.name, rp.localrole as prepublished ' .
+        'FROM {role} r ' .
+        ' LEFT JOIN {mnet_role_published} rp ON rp.localrole = r.id and rp.mnethost = ? ';
+$rolesparams = array($mnethostid);
+
+$mform = new rolespermitted_form();
+if ($mform->is_cancelled()){
+   redirect($CFG->wwwroot . '/admin/mnet/peers.php?hostid=' . $mnethostid, get_string('changescancelled'), 1);
+} else if ($fromform=$mform->get_data()) {
+    $roles = $DB->get_records_sql($rolessql, $rolesparams);
+    $rolepublication = new stdclass();
+    $rolepublication->mnethost = $mnethostid;
+    foreach ($roles as $role) {
+        $rolepublication->localrole = $role->id;
+        if(isset($fromform->{$role->shortname})) {
+            // Role checkbox was ticked - add publication entry if not already present.
+            if (empty($role->prepublished)) {
+                $DB->insert_record('mnet_role_published', $rolepublication);
+                $toform[$role->shortname] = 1;
+            }
+        } else {
+            // Role checkbox was not ticked - delete its publication entry (if present)
+            if (!empty($role->prepublished)) {
+                $DB->delete_records('mnet_role_published',
+                        array('mnethost' => $rolepublication->mnethost,
+                        'localrole' => $rolepublication->localrole));
+                unassign_role_peer($rolepublication->localrole, $rolepublication->mnethost);
+            }
+        }
+    }
+    print_string('settingssaved');
+    $toform['hostid'] = $mnethostid;
+    $mform->set_data($toform);
+    $mform->display();
+    print_footer();
+
+} else {
+    $toform = array();
+
+    $roles = $DB->get_records_sql($rolessql, $rolesparams);
+    foreach ($roles as $role) {
+        if (!empty($role->prepublished)) {
+            $toform[$role->shortname] = 1;
+        }
+    }
+    $toform['hostid'] = $mnethostid;
+
+    $mform->set_data($toform);
+    $mform->display();
+    print_footer();
+
+}
+?>
diff --git a/admin/mnet/mnet_rolespermitted_form.php b/admin/mnet/mnet_rolespermitted_form.php
new file mode 100644 (file)
index 0000000..336c706
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+require_once ('../../config.php');
+require_once ($CFG->libdir . '/formslib.php');
+
+class rolespermitted_form extends moodleform {
+
+    function definition() {
+
+        global $DB;
+        $mform =& $this->_form;
+        $mform->addElement('header', 'permitted', get_string('mnetpermittedroles'));
+        $mform->addElement('static', 'instructions', '',get_string('mnetpermittedrolesinstructions'), ' ');
+        $mform->addElement('hidden', 'hostid', 'yes');
+        $roles = $DB->get_records('role', array(), 'id');
+        foreach ($roles as $role) {
+            $mform->addElement('checkbox', $role->shortname, $role->name,'('.$role->shortname.')');
+        }
+
+
+// buttons
+        $this->add_action_buttons();
+    }
+}
+
index 3bedb84ce0dba014bc013f27e49910aa3f621f2e..fd0b9524ccde4aa8bf779063f6a9717a710ef32d 100644 (file)
@@ -41,6 +41,8 @@ $strmnetservices   = get_string('mnetservices', 'mnet');
 $strmnetlog        = get_string('mnetlog', 'mnet');
 $strmnetedithost   = get_string('reviewhostdetails', 'mnet');
 $strmnetthemes     = get_string('mnetthemes', 'mnet');
+$strrolespermitted = get_string('mnetrolespermitted');
+$strrolemapping = get_string('mnetrolemapping');
 
 if (!isset($CFG->mnet_dispatcher_mode)) set_config('mnet_dispatcher_mode', 'off');
 
index 7981b5f6d7d50da0ae13a0725c81f837c9b25326..db26c1f836a5f56e847f3fd1b5cdc43e63b48c82 100755 (executable)
         $title = get_string('editingrolex', 'role', $rolenames[$roleid]->localname);
     }
     print_heading_with_help($title, 'roles');
+    $rolepublishedsql =
+             'SELECT distinct(localrole), localrole ' .
+             'FROM {mnet_role_published} ' .
+             'WHERE localrole = ? ';
+    $params = array($roleid);
+    $rolepublished = $DB->get_records_sql($rolepublishedsql, $params);
+
+    if (!empty($rolepublished)) {
+        print_box(get_string('mnetroleispublished','moodle'), 'generalbox', 'notice');
+    }
 
 /// Work out some button labels.
     if ($action == 'add' || $action == 'duplicate') {
index 8db1cc0249393cac5c47e1531e3c5552e28e2b26..cc30f8cc0fc853b318fb899bb4a05b61eb68fdd7 100644 (file)
@@ -15,9 +15,6 @@ $ADMIN->add('mnet', new admin_externalpage('mnetpeers', get_string('mnetpeers',
 $ADMIN->add('mnet', new admin_externalpage('ssoaccesscontrol', get_string('ssoaccesscontrol', 'mnet'),
                                            "$CFG->wwwroot/$CFG->admin/mnet/access_control.php",
                                            'moodle/site:config'));
-$ADMIN->add('mnet', new admin_externalpage('mnetenrol', get_string('mnetenrol', 'mnet'),
-                                           "$CFG->wwwroot/$CFG->admin/mnet/enr_hosts.php",
-                                           'moodle/site:config'));
 $ADMIN->add('mnet', new admin_externalpage('trustedhosts', get_string('trustedhosts', 'mnet'),
                                            "$CFG->wwwroot/$CFG->admin/mnet/trustedhosts.php",
                                            'moodle/site:config'));
index cf83619313ef345bdf066457090b9d432a301d8b..f734bd1cba2935a3531e639faac7f257896c290a 100644 (file)
@@ -47,7 +47,7 @@ class auth_plugin_mnet extends auth_plugin_base {
         $sso_sp = array();
         $sso_sp['name']         = 'sso_sp'; // Name & Description go in lang file
         $sso_sp['apiversion']   = 1;
-        $sso_sp['methods']      = array('keepalive_client','kill_child');
+        $sso_sp['methods']      = array('keepalive_client','kill_child','createupdate_user_bool');
 
         return array($sso_idp, $sso_sp);
     }
@@ -264,6 +264,11 @@ class auth_plugin_mnet extends auth_plugin_base {
         // Thunderbirds are go! Do RPC call and store response
         if ($mnetrequest->send($remotepeer) === true) {
             $remoteuser = (object) $mnetrequest->response;
+            if (!empty($mnetrequest->response['session.gc_maxlifeime'])) {
+                $session_gc_maxlifetime = $mnetrequest->response['session.gc_maxlifeime'];
+            } else {
+                $session_gc_maxlifetime = 1440;
+            }
         } else {
             foreach ($mnetrequest->error as $errormessage) {
                 list($code, $message) = array_map('trim',explode(':', $errormessage, 2));
@@ -1187,6 +1192,83 @@ class auth_plugin_mnet extends auth_plugin_base {
         return false;
     }
 
+    /**
+     * Create or update remote mnet user
+     * Create mnet user record in db populated from userinfo or update existing mnet user record.
+     * @param userinfo array - current information about the user to be created or updated (unescaped)
+     * @return bool false on failure, true on success
+     */
+    function createupdate_user_bool($userinfo) {
+        $userinfo = (object)$userinfo;
+        $updateresult = $this->createupdate_user($userinfo);
+        if (!empty($updateresult)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Create or update remote mnet user
+     * Create mnet user record in db populated from userinfo or update existing mnet user record.
+     * @param userinfo object - current information about the user to be created or updated (unescaped)
+     * @return mixed false on failure, user object on success.
+     */
+    function createupdate_user($userinfo) {
+        global $CFG, $MNET_REMOTE_CLIENT, $DB;
+        if (!empty($MNET_REMOTE_CLIENT->id)) {
+            $userinfo->mnethostid = $MNET_REMOTE_CLIENT->id;
+        } else {
+            if (empty($userinfo->mnethostid)) {
+                // No information available about where user is from - unable to proceed.
+                return false;
+            }
+        }
+
+        // get the local record for the remote user
+        $userspec = array('username' => $userinfo->username, 'mnethostid' => $userinfo->mnethostid);
+        $localuser = $DB->get_record('user', $userspec);
+
+        if (empty($localuser->id)) {
+            // No pre-existing local record of this user
+            if (empty($MNET_REMOTE_CLIENT->id)) {
+                // Local call - could be user trying to log-in cold
+                // (prior to their idp requesting any enrolment or account creation)
+                if (empty($this->config->auto_add_remote_users)) {
+                    // Users aren't allowed to log in cold.
+                    print_error('nolocaluser', 'mnet');
+                }
+            }
+            $userinfo->id = $DB->insert_record('user', $userinfo);
+            if (!$userinfo->id) {
+                print_error('databaseerror', 'mnet');
+            }
+            if (! $localuser = $DB->get_record('user', $userspec)) {
+                print_error('nolocaluser', 'mnet');
+            }
+            $localuser->newrecord = true;
+        } else {
+            // update the local user record with remote user data
+            foreach ((array) $localuser as $key => $val) {
+                if ($key == 'id') {
+                    continue;
+                }
+                if (isset($userinfo->{$key})) {
+                    $localuser->{$key} = $userinfo->{$key};
+                }
+            }
+            $localuser->newrecord = false;
+        }
+        if (!empty($userinfo->imagehash)) {
+            $localuser->picture = $this->createupdate_user_image($localuser, $userinfo->imagehash);
+        }
+
+        $bool = $DB->update_record('user', $localuser);
+        if (!$bool) {
+            error("updating user failed in mnet/auth/confirm_mnet_session ");
+        }
+        return $localuser;
+    }
+
     /**
      * To delete a host, we must delete all current sessions that users from
      * that host are currently engaged in.
@@ -1347,7 +1429,52 @@ class auth_plugin_mnet extends auth_plugin_base {
         return $logline;
     }
 
+    /**
+     * Retreive a new copy of user profile image over mnet
+     *
+     * @param object $user userobject for user we want to update the image for
+     * @param string $newimagehash sha1 hash of the latest available image on the idp
+     * @return bool true if new primary image has been retreived & saved, false otherwise
+     */
+    function createupdate_user_image($user, $newimagehash) {
+        global $CFG;
+        //Establish what the user's directory would be named (no create)
+        $dirname = make_user_directory($user->id, true);
+        $filename = "$dirname/f1.jpg";
+
+        $localhash = '';
+        if (file_exists($filename)) {
+            $localhash = sha1(file_get_contents($filename));
+        } elseif (!file_exists($dirname)) {
+            mkdir($dirname);
+        }
 
+        $picture = 0;
+        if ($localhash != $newimagehash) {
+            require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
+            $remotepeer = new mnet_peer();
+            $remotepeer->set_id($user->mnethostid);
+
+            // fetch image from remote host
+            $fetchrequest = new mnet_xmlrpc_client();
+            $fetchrequest->set_method('auth/mnet/auth.php/fetch_user_image');
+            $fetchrequest->add_param($user->username);
+            if ($fetchrequest->send($remotepeer) === true) {
+                if (strlen($fetchrequest->response['f1']) > 0) {
+                    $imagecontents = base64_decode($fetchrequest->response['f1']);
+                    file_put_contents($filename, $imagecontents);
+                    $picture = 1;
+                } else {
+                    $picture = 0;
+                }
+                if (strlen($fetchrequest->response['f2']) > 0) {
+                    $imagecontents = base64_decode($fetchrequest->response['f2']);
+                    file_put_contents($dirname.'/f2.jpg', $imagecontents);
+                }
+            }
+        }
+        return $picture;
+    }
 }
 
 ?>
index bfe5f8c5d6d54d05cea64e0cd6bf0e4171c340ba..afbc81df4296b70409c497bc9523422e1daf1e35 100644 (file)
@@ -54,6 +54,15 @@ class block_admin extends block_list {
             }
         }
 
+    /// Configure Mnet content provider:
+        if ($course->id != SITEID) {
+            if (has_capability('moodle/course:linkmnetcourse', $this->page->context)) {
+                $this->content->items[] = '<a href="' . $CFG->wwwroot . '/course/mnet.php?id=' . $course->id . '">' .
+                        get_string('mnetcontentsource') . '</a>';
+                $this->content->icons[] = '<img src="' . $OUTPUT->old_icon_url('i/content') . '" class="icon" alt="" />';
+            }
+        }
+
     /// View course grades (or just your own grades, same link)
     /// find all accessible reports
         if ($course->id !== SITEID) {
index 1099800cffc4aff8b0c4c70eb9b248ea7b8c4324..702eefb2094fa1a644ad854eac7fade694da93c4 100644 (file)
@@ -26,8 +26,13 @@ class block_course_list extends block_list {
 
         $icon  = "<img src=\"" . $OUTPUT->old_icon_url('i/course') . "\"".
                  " class=\"icon\" alt=\"".get_string("coursecategory")."\" />";
+        $networkicon  = '<img src="' . $OUTPUT->old_icon_url('i/mnethost') . "\"".
+                 " class=\"icon\" alt=\"".get_string('course')."\" />";
        
         $adminseesall = true;
+
+        $mneturl = array($CFG->wwwroot . '/auth/mnet/jump.php?hostid=', '&wantsurl=' . urlencode('/course/view.php?id='));
+        $localurl = $CFG->wwwroot . '/course/view.php?id=';
         if (isset($CFG->block_course_list_adminview)) {
            if ( $CFG->block_course_list_adminview == 'own'){
                $adminseesall = false;
@@ -43,10 +48,22 @@ class block_course_list extends block_list {
                     if ($course->id == SITEID) {
                         continue;
                     }
-                    $linkcss = $course->visible ? "" : " class=\"dimmed\" ";
-                    $this->content->items[]="<a $linkcss title=\"" . format_string($course->shortname) . "\" ".
-                               "href=\"$CFG->wwwroot/course/view.php?id=$course->id\">" . format_string($course->fullname) . "</a>";
-                    $this->content->icons[]=$icon;
+                    $linkclasses = $course->visible ? "" : "dimmed";
+                    if (empty($course->mnetpeer)
+                            || empty($course->remotecourseid)
+                            || has_capability('moodle/course:seemnetshell', $course->context)) {
+                        $linkclasses .= ' local';
+                        $contentlink = $localurl . $course->id;
+                        $this->content->icons[]=$icon;
+                    } else {
+                        $linkclasses .= ' remote';
+                        $contentlink = $mneturl[0] . $course->mnetpeer . $mneturl[1] . $course->remotecourseid;
+                        $this->content->icons[]=$networkicon;
+                    }
+                    $linkcss = ' classes="' . $linkclasses . '" ';
+                    $contentitem = '<a ' . $linkcss . 'title="' . format_string($course->shortname) . '" href="';
+                    $contentitem .= $contentlink . '">' . format_string($course->fullname) . '</a>';
+                    $this->content->items[] = $contentitem;
                 }
                 $this->title = get_string('mycourses');
             /// If we can update any course of the view all isn't hidden, show the view all courses link
@@ -54,7 +71,6 @@ class block_course_list extends block_list {
                     $this->content->footer = "<a href=\"$CFG->wwwroot/course/index.php\">".get_string("fulllistofcourses")."</a> ...";
                 }
             }
-            $this->get_remote_courses();
             if ($this->content->items) { // make sure we don't return an empty list
                 return $this->content;
             }
@@ -91,7 +107,6 @@ class block_course_list extends block_list {
                     if (has_capability('moodle/course:update', get_context_instance(CONTEXT_SYSTEM)) || empty($CFG->block_course_list_hideallcourseslink)) {
                         $this->content->footer .= "<a href=\"$CFG->wwwroot/course/index.php\">".get_string('fulllistofcourses').'</a> ...';
                     }
-                    $this->get_remote_courses();
                 } else {
                     
                     $this->content->icons[] = '';
@@ -99,7 +114,6 @@ class block_course_list extends block_list {
                     if (has_capability('moodle/course:create', get_context_instance(CONTEXT_COURSECAT, $category->id))) {
                         $this->content->footer = '<a href="'.$CFG->wwwroot.'/course/edit.php?category='.$category->id.'">'.get_string("addnewcourse").'</a> ...';
                     }
-                    $this->get_remote_courses();
                 }
                 $this->title = get_string('courses');
             }
@@ -107,49 +121,6 @@ class block_course_list extends block_list {
 
         return $this->content;
     }
-
-    function get_remote_courses() {
-        global $CFG, $USER, $OUTPUT;
-
-        if (!is_enabled_auth('mnet')) {
-            // no need to query anything remote related
-            return;
-        }
-
-        $icon  = '<img src="'.$OUTPUT->old_icon_url('i/mnethost') . '" class="icon" alt="'.get_string('course').'" />';
-
-        // only for logged in users!
-        if (!isloggedin() || isguest()) {
-            return false;
-        }
-
-        if ($courses = get_my_remotecourses()) {
-            $this->content->items[] = get_string('remotecourses','mnet');
-            $this->content->icons[] = '';
-            foreach ($courses as $course) {
-                $this->content->items[]="<a title=\"" . format_string($course->shortname) . "\" ".
-                    "href=\"{$CFG->wwwroot}/auth/mnet/jump.php?hostid={$course->hostid}&amp;wantsurl=/course/view.php?id={$course->remoteid}\">" 
-                    . format_string($course->fullname) . "</a>";
-                $this->content->icons[]=$icon;
-            }
-            // if we listed courses, we are done
-            return true;
-        }
-
-        if ($hosts = get_my_remotehosts()) {
-            $this->content->items[] = get_string('remotemoodles','mnet'); 
-            $this->content->icons[] = '';
-            foreach($USER->mnet_foreign_host_array as $somehost) {
-                $this->content->items[] = $somehost['count'].get_string('courseson','mnet').'<a title="'.$somehost['name'].'" href="'.$somehost['url'].'">'.$somehost['name'].'</a>';
-                $this->content->icons[] = $icon;
-            }
-            // if we listed hosts, done
-            return true;
-        }
-
-        return false;
-    }
-
 }
 
 ?>
index 7257c2754928e0b4f8e86366badb334121b9c855..548bff258d7dd62aea30197e092d83a2fc06fc28 100644 (file)
@@ -2110,10 +2110,21 @@ function print_course($course, $highlightterms = '') {
 
     $linkcss = $course->visible ? '' : ' class="dimmed" ';
 
+    if (empty($course->mnetpeer) || empty($course->remotecourseid)
+        || has_capability('moodle/course:seemnetshell', $context)) {
+        $linkurl = $CFG->wwwroot.'/course/view.php?id='.$course->id;
+    } else {
+        // Course has content provided by remote mnet host, and user not permitted to view the local
+        // shell course - link should direct them to jump to the content provider.
+        $linkurl = $CFG->wwwroot . '/auth/mnet/jump.php' .
+                '?hostid=' . $course->mnetpeer .
+                '&wantsurl=' . urlencode('/course/view.php?id=' . $course->remotecourseid);
+    }
+
     echo '<div class="coursebox clearfix">';
     echo '<div class="info">';
     echo '<div class="name"><a title="'.get_string('entercourse').'"'.
-         $linkcss.' href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.
+         $linkcss.' href="' . $linkurl . '">'.
          highlight($highlightterms, format_string($course->fullname)).'</a></div>';
 
     /// first find all roles that are supposed to be displayed
@@ -2218,42 +2229,19 @@ function print_my_moodle() {
     }
 
     $courses  = get_my_courses($USER->id, 'visible DESC,sortorder ASC', array('summary'));
-    $rhosts   = array();
-    $rcourses = array();
-    if (!empty($CFG->mnet_dispatcher_mode) && $CFG->mnet_dispatcher_mode==='strict') {
-        $rcourses = get_my_remotecourses($USER->id);
-        $rhosts   = get_my_remotehosts();
-    }
-
-    if (!empty($courses) || !empty($rcourses) || !empty($rhosts)) {
-
-        if (!empty($courses)) {
-            echo '<ul class="unlist">';
-            foreach ($courses as $course) {
-                if ($course->id == SITEID) {
-                    continue;
-                }
-                echo '<li>';
-                print_course($course);
-                echo "</li>\n";
+    if (!empty($courses)) {
+        echo '<ul class="unlist">';
+        foreach ($courses as $course) {
+            if ($course->id == SITEID) {
+                continue;
             }
-            echo "</ul>\n";
+            echo '<li>';
+            print_course($course);
+            echo "</li>\n";
         }
+        echo "</ul>\n";
 
-        // MNET
-        if (!empty($rcourses)) {
-            // at the IDP, we know of all the remote courses
-            foreach ($rcourses as $course) {
-                print_remote_course($course, "100%");
-            }
-        } elseif (!empty($rhosts)) {
-            // non-IDP, we know of all the remote servers, but not courses
-            foreach ($rhosts as $host) {
-                print_remote_host($host, "100%");
-            }
-        }
         unset($course);
-        unset($host);
 
         if ($DB->count_records("course") > (count($courses) + 1) ) {  // Some courses not being displayed
             echo "<table width=\"100%\"><tr><td align=\"center\">";
@@ -3520,4 +3508,244 @@ function get_course_by_idnumber ($idnumber) {
     return $DB->get_record('course', array('idnumber' => $idnumber));
 }
 
+/**
+ * Get array of enrolable courses from $mnetpeerid that are candidates to link to $courseid
+ * remote courses already linked to other local courses are not candidates
+ * @param mnetpeerid integer the id number of the peer we are interested in
+ * @param courseid the local course under consideration
+ * @return array courses that are are available for linking to local courses
+ */
+function mnet_get_available_courses($mnetpeerid, $courseid = 0) {
+    global $DB;
+    //Get records of those courses related to the target mnet peer which are already linked to a
+    //local course
+    $sql = 'SELECT remotecourseid ' .
+        ' FROM {course} c' .
+        ' WHERE c.mnetpeer = ? ' .
+        ' AND c.id <> ? ';
+    $params = array($mnetpeerid, $courseid);
+    $linkedcourses = $DB->get_records_sql($sql, $params);
+
+    //Get records of the courses the mnet peer offers us and trim out pre-linked courses
+    $availablecourses = mnet_get_courses($mnetpeerid);
+    foreach ($availablecourses as $remotecourseid => $course) {
+        if (isset($linkedcourses[$remotecourseid])) {
+            unset($availablecourses[$remotecourseid]);
+        }
+    }
+    return $availablecourses;
+}
+
+
+/**
+ * Get array of enrolable courses from an mnet peer
+ * @param mnetpeerid integer the id number of the peer we are interested in
+ * @return array enrolable courses from the mnet peer
+ */
+function mnet_get_courses($mnetpeerid) {
+    // Set up the content provider connection
+    $new_cp = new mnet_peer();
+    $new_cp->set_id($mnetpeerid);
+
+    // set up the RPC request
+    $mnetrequest = new mnet_xmlrpc_client();
+    $mnetrequest->set_method('enrol/mnet/enrol.php/available_courses');
+
+    // Initialise $message
+    $message = '';
+
+    if ($mnetrequest->send($new_cp) !== true) {
+        print_error("mnetpeeruncontactable");
+    }
+    $peercourses = array();
+    if (is_array($mnetrequest->response)) {
+        foreach($mnetrequest->response as $peercourse) {
+            $peercourses[$peercourse['remoteid']] = $peercourse;
+        }
+    }
+    return $peercourses;
+}
+
+
+/**
+ * Update enrolments in a course provided over mnet.
+ * - Make necessary enrolments/unenrolments so that the right students can access
+ * the specified course on the specified peer
+ *
+ * @param course course object
+ * @param integer selectedpeer - the id of the mnet peer that will be providing content
+ * @param integer selectedcourse - the id of the specific course on the remote peer
+ * @return array how many users have been enqueued, and if unenrolment took place.
+ */
+function mnet_update_enrolments(&$course, $selectedpeer, $selectedcourse) {
+    global $DB, $CFG;
+    require_once($CFG->libdir.'/accesslib.php');
+    $result = array('enqueued users'=>0,'oldcp unenroled'=> false);
+
+    if ($course->mnetpeer) {
+        // Set up connection to the old CP
+        $old_cp = new mnet_peer();
+        $old_cp->set_id($course->mnetpeer);
+    }
+    $oldmnetpeer = $course->mnetpeer;
+    $oldcourseid = $course->remotecourseid;
+
+    //Get a list of users who we want to get/retain their role on the remote course
+    $courseusers = mnet_get_courseusers($course->id, $selectedpeer);
+
+    if (($selectedpeer == $course->mnetpeer) && ($selectedcourse == $course->remotecourseid)) {
+        // the new course is the same as the old course, this is essentially a resync
+        // the remote peer should be told whose enrolments to not delete.
+        $keepusers = $courseusers;
+    } else {
+        // If there is currently a remote course configured, and we're changing to a different one
+        // we don't want to keep any existing enrolments in the current remote course
+        $keepusers = array();
+
+        // Update the local record in the course table with new info
+        if ($selectedpeer == 0 || $selectedcourse == 0) {
+            $course->mnetpeer = null;
+            $course->remotecourseid = null;
+        } else {
+            $course->mnetpeer = $selectedpeer;
+            $course->remotecourseid = $selectedcourse;
+        }
+        $courseupdated = $DB->update_record('course', $course);
+    }
+
+    if ($oldmnetpeer) {
+        $result['oldcp unenroled'] = mnet_unenrol_course_users_bulk($old_cp, $oldcourseid, $keepusers);
+    }
+    if (!empty($selectedpeer) && !empty($selectedcourse)) {
+        if (($course->mnetpeer != $oldmnetpeer) || ($course->remotecourseid != $oldcourseid)) {
+            // Safety check - send a message to the new CP asking them to unenrol all our users
+            // (except $courseusers)
+            $new_cp = new mnet_peer();
+            $new_cp->set_id($course->mnetpeer);
+            mnet_unenrol_course_users_bulk($new_cp, $selectedcourse, $courseusers);
+        }
+        $rolemanagementobj = new stdClass();
+        $rolemanagementobj->localcourse = $course->id;
+        $rolemanagementobj->mnetpeer = null;
+        $rolemanagementobj->remotecourseid = null;
+        foreach ($courseusers as $userid => $username) {
+            $rolemanagementobj->userid = $userid;
+            mnet_enqueue_role_update($rolemanagementobj);
+            $result['enqueued users']++;
+        }
+    }
+    return $result;
+}
+
+/**
+ * Get a list of any users that have a relevant role assigned which relates to the local course of interest
+ *
+ * @param integer courseid the id of the local course
+ * @param integer selectedpeer - the id of the mnet peer that will be providing content
+ * @return array keyed on userid, value of username, empty if there are no users with a relevant role
+ */
+function mnet_get_courseusers($courseid, $selectedpeer) {
+    global $DB;
+    $coursecontext = get_context_instance(CONTEXT_COURSE, $courseid);
+    $contexts = explode('/', substr($coursecontext->path, 1));
+    list($contextfragment, $courseusersparams) = $DB->get_in_or_equal($contexts);
+    $courseuserssql =
+            'SELECT DISTINCT ra.userid, u.username ' .
+            'FROM {role_assignments} ra ' .
+            ' INNER JOIN {user} u on u.id = ra.userid ' .
+            ' INNER JOIN {mnet_role_mapping} rm ON rm.localrole = ra.roleid ' .
+            'WHERE ra.contextid ' . $contextfragment .
+            ' AND rm.mnethost = ?';
+    $courseusersparams[] = $selectedpeer;
+
+    $courseuserobjects = $DB->get_records_sql($courseuserssql, $courseusersparams);
+
+    //Clean up the results and return an array
+    if (!empty($courseuserobjects)) {
+        foreach ($courseuserobjects as $userid=>$courseuserobject) {
+            $courseusers[$userid] = $courseuserobject->username;
+        }
+    } else {
+        $courseusers = array();
+    }
+    return $courseusers;
+}
+
+/**
+ * Try calling a new service on remote peer to unenrol our users from a specified course, leaving a specified list
+ * - if this doesn't seem to work, try to acheive the same thing with 1-at-a-time calls.
+ * @param object $mnetpeer object containing details about the mnet peer to be contacted
+ * @param integer $courseid - the id of the course we need to tweak enrolments for
+ * @param array $keepusers - keyed by userid, array containing usernames of users to not unenrol
+ * @return mixed true if immediately successful, int if eventually successful, false on fail
+ */
+function mnet_unenrol_course_users_bulk($mnetpeer, $courseid, $keepusers = array()) {
+    // Send a message to the mnet peer:
+    // Unenrol all my users from course x except $users
+    $mnetrequest = new mnet_xmlrpc_client();
+    $mnetrequest->set_method('enrol/mnet/enrol.php/abridge_course_enrolments');
+    $mnetrequest->add_param($keepusers);
+    $mnetrequest->add_param($courseid);
+
+    if ($mnetrequest->send($mnetpeer) == true) {
+        return true;
+    }
+    // Remote mnet host didn't respond to:
+    // "unenrol all my users from this course except $keepusers".
+    // Try falling back to older mnet methods -
+    return mnet_unenrol_course_users($mnetpeer, $courseid, $keepusers);
+}
+
+/**
+ * Ask mnetpeer for a list of our users enroled in $oldcourseid,
+ * - and request everyone not in $keepusers be unenroled
+ * @param object $mnetpeer object containing details about the mnet peer to be contacted
+ * @param integer $courseid - the id of the course we need to tweak enrolments for
+ * @param array $keepusers - keyed by userid, array containing usernames of users to not unenrol
+ * @return mixed false on problem, int if no problem - the # of users unenrolled
+ */
+function mnet_unenrol_course_users($mnetpeer, $courseid, $keepusers = array()) {
+    $unenrolledusers = 0;
+    //Request a list of enrolments
+    $listenrolmentsrq = new mnet_xmlrpc_client();
+    $listenrolmentsrq->set_method('enrol/mnet/enrol.php/course_enrolments');
+    $listenrolmentsrq->add_param($courseid);
+    if($listenrolmentsrq->send($mnetpeer)) {
+        if (is_array($listenrolmentsrq->response)) {
+            foreach($listenrolmentsrq->response as $existingusername => $existinguser) {
+                if (in_array($existingusername, $keepusers)) {
+                    continue;
+                }
+                if (!mnet_unenrol_course_username($mnetpeer, $courseid, $existingusername)) {
+                    //TODO add full details to role management queue for later processing
+                    break;
+                }
+                $unenrolledusers++;
+            }
+        }
+    } else {
+        print_error("mnetpeeruncontactable");
+        // Failed to get list from remote host describing which of our users it has enrolled
+        //TODO as a best guess, for all users enrolled in this course
+        // -  add full detail to role management queue for later processing
+        return false;
+    }
+    return $unenrolledusers;
+}
+
+
+/**
+ * Ask $mnetpeer to unenrol our user named $username from course $courseid
+ * @param object $mnetpeer object containing details about the mnet peer to be contacted
+ * @param integer $courseid - the id of the course we need to tweak enrolments for
+ * @return boolean true on success
+ */
+function mnet_unenrol_course_username($mnetpeer, $courseid, $username) {
+    $mnetrequest = new mnet_xmlrpc_client();
+    $mnetrequest->set_method('enrol/mnet/enrol.php/unenrol_user');
+    $mnetrequest->add_param($username);
+    $mnetrequest->add_param($courseid);
+    $rqresult = $mnetrequest->send($mnetpeer);
+    return $rqresult;
+}
 ?>
diff --git a/course/mnet.html b/course/mnet.html
new file mode 100644 (file)
index 0000000..7287399
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+// show changes applied (if any)
+if (isset($updateresult['oldcp unenroled']) && $updateresult['oldcp unenroled'] !== false) {
+    if($updateresult['oldcp unenroled'] === true) {
+        print_string('mnetoldcpunenroledbulk');
+    } else {
+        print_string('mnetoldcpunenroled', '', $updateresult['oldcp unenroled']);
+    }
+    echo "<br />\n";
+}
+if (isset($updateresult['enqueued users']) && $updateresult['enqueued users'] != false) {
+    print_string('mnetusersenqueued', '', $updateresult['enqueued users']);
+    echo "<br />\n";
+}
+
+helpbutton('mnetcp', get_string('mnetcontentprovision'), 'moodle');
+
+// show current mnet content provider
+if (empty($course->mnetpeer)) {
+    print_string('courselocalcp');
+} else {
+    $currentpeer = $contentpeers[$course->mnetpeer];
+    print_string('coursemnetcp', '', $currentpeer->name . ' - ' . $currentpeer->wwwroot);
+    if(!empty($peercourses[$course->remotecourseid])) {
+        echo ' - ' . $peercourses[$course->remotecourseid]['fullname'];
+    }
+}
+?>
+<form id="mnetcpform" action="mnet.php?id=<?php echo $courseid ?>" method="post">
+<select name="mnetpeer" onchange="this.form.submit();">
+<option value="0"<?php if ($selectedpeer == 0) { echo ' selected = "true"'; }?>>No mnet peer</option>
+<?php
+    foreach ($contentpeers as $contentpeer) {
+        echo '<option value="' . $contentpeer->id .'"';
+        if ($contentpeer->id == $selectedpeer) {
+            echo ' selected="true"';
+        }
+        echo '>' . $contentpeer->name . ' - ' . $contentpeer->wwwroot . '</option>';
+    }
+?>
+</select>
+<select name="remotecourseid">'
+<?php
+    if (!empty($peercourses)) {
+        foreach ($peercourses as $peercourse) {
+            echo '<option value="' . $peercourse['remoteid'] . '"';
+            if ($selectedcourse == $peercourse['remoteid']) {
+                echo ' selected="true"';
+            }
+            echo '>' . $peercourse['fullname'] . '</option>';
+        }
+    }
+?>
+</select>
+<input type="hidden" name="saveform" value="1" />
+<input type="submit" name="reloadupdate" value="reload/update" />
+</form>
diff --git a/course/mnet.php b/course/mnet.php
new file mode 100644 (file)
index 0000000..722412a
--- /dev/null
@@ -0,0 +1,121 @@
+<?php // $Id$
+    require_once('../config.php');
+    require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
+    require_once $CFG->dirroot . '/course/lib.php';
+
+    require_login();
+
+    $courseid = required_param('id', PARAM_INT); // course id
+    $mnetpeer = optional_param('mnetpeer', 0, PARAM_INT); // mnet peer id
+    $remotecourseid = optional_param('remotecourseid', 0, PARAM_INT); //Course id on remote peer
+
+    // True if user has submitted a form (by js or otherwise)
+    $saveform = optional_param('saveform', 0, PARAM_INT);
+
+    // "reload/update" if user clicked to submit the form
+    $reloadupdate = optional_param('reloadupdate', '', PARAM_TEXT);
+    $clicked = !empty($reloadupdate);
+
+    if($courseid == SITEID){
+        // don't allow editing of 'site course' using this from
+        error('You cannot edit the site course using this form');
+    }
+
+    if (!$course = $DB->get_record('course', array('id' => $courseid))) {
+        error('Course ID was incorrect');
+    }
+
+    $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
+
+    $streditcoursemnetsettings = get_string("editcoursemnetsettings");
+    $strmnetsettings = get_string("mnetsettings");
+    $navlinks[] = array('name' => $course->shortname,
+                        'link' => 'view.php?id=' . $courseid,
+                        'type' => 'misc');
+    $navlinks[] = array('name' => $strmnetsettings,
+                        'link' => 'mnet.php?id=' . $courseid,
+                        'type' => 'misc');
+    $title = $streditcoursemnetsettings;
+    $fullname = $course->fullname;
+
+    $PAGE->set_generaltype('form');
+    $PAGE->set_url('course/mnet.php', array('id' => $courseid));
+    $navigation = build_navigation($navlinks);
+    print_header($title, $fullname, $navigation);
+
+    print_heading(get_string('editcoursemnetsettings'));
+    if (!has_capability('moodle/course:update', $coursecontext)) {
+        print_error("notpermittedtoview");
+    }
+
+
+    //Select those peers to whome we subscribe to for enrolment services
+    $contentpeerssql = 'SELECT h.id, h.name, h.wwwroot '.
+                    'FROM {mnet_service} s'.
+                    ' INNER JOIN {mnet_host2service} h2s on h2s.serviceid=s.id'.
+                    ' INNER JOIN {mnet_host} h on h.id = h2s.hostid '.
+                    'WHERE s.name = \'mnet_enrol\''.
+                    ' AND h2s.subscribe <> 0 '.
+                    ' AND h.deleted = 0 '.
+                    'ORDER BY h.name ASC, h.wwwroot ASC, h.id ASC';
+    $contentpeers = $DB->get_records_sql($contentpeerssql);
+    if (!$contentpeers) {
+        print_error('mnetpeersnoenrolment');
+    }
+
+    // If the user has made a selection check if that selection is valid
+    // Valid selections are a) 0, or b) rows of $contentpeers
+    // If it's not valid override it with no-change, or no-mnetpeer
+    if ($saveform && (!empty($contentpeers[$mnetpeer]) || $mnetpeer == 0)) {
+        $selectedpeer = $mnetpeer;
+        if (!$clicked && $selectedpeer != $course->mnetpeer) {
+            $remotecourseid = 0;
+        }
+    } elseif (!empty($course->mnetpeer) && !empty($contentpeers[$course->mnetpeer])) {
+        $selectedpeer = $course->mnetpeer;
+    } else {
+        $selectedpeer = 0;
+    }
+
+    if (!has_capability('moodle/course:linkmnetcourse', $coursecontext)) {
+        print_error('mnetnotpermittedtolink');
+    }
+    $peercourses = array();
+    if ($selectedpeer) {
+        $peercourses = mnet_get_available_courses($selectedpeer, $courseid);
+    }
+
+    // Ensure any course the user has selected is a valid option
+    // Valid options are a) 0 (no mnet content provider after all), or
+    // b) a course that is provided by the selected mnetpeer that isn't a content provider for one of our other courses;
+    if ($saveform && ($remotecourseid == 0 || (!empty($peercourses[$remotecourseid]) && empty($peercourses[$remotecourseid]->unavailable)))) {
+        $selectedcourse = $remotecourseid;
+    } else {
+        $selectedcourse = 0;
+    }
+
+    $updateenrolments = false;
+
+    // Assuming the user has made peer/course selections and we haven't had to override either
+    // We can update course information & process enrolments/unenrolments.
+    if ($clicked && ($mnetpeer == $selectedpeer) && ($remotecourseid == $selectedcourse)) {
+        $updateenrolments = true;
+    }
+
+    // If user has selected to not use remote peer/course, or has supplied invalid details
+    // we also need to update enrolments
+    if ($saveform && (empty($selectedpeer) || empty($selectedcourse))) {
+        $updateenrolments = true;
+    }
+
+    if (!$updateenrolments) {
+        // User hasn't made selections - preset form with current values:
+        $selectedpeer = $course->mnetpeer;
+        $selectedcourse = $course->remotecourseid;
+    } else {
+        //                                     &course
+        $updateresult = mnet_update_enrolments($course, $selectedpeer, $selectedcourse);
+    }
+    include('mnet.html');
+    print_footer();
+?>
index 735f16c5c9415b2f21db84040d4637f856a098de..15be9653bca3367bcba35a23ede0975a51c914b2 100644 (file)
         // This course is not a real course.
         redirect($CFG->wwwroot .'/');
     }
-
+    // If this course is just an mnet shell (content provided by a course on a remote mnet host)
+    // direct anyone that isn't permitted to see the shell course directly to the content provider
+    if (!empty($course->mnetpeer) && !empty($course->remotecourseid)) {
+        $jumpurl = $CFG->wwwroot . '/auth/mnet/jump.php' .
+                '?hostid=' . $course->mnetpeer .
+                '&wantsurl=' . urlencode('/course/view.php?id=' . $course->remotecourseid);
+        if (!has_capability('moodle/course:seemnetshell', $context)) {
+            redirect($jumpurl);
+        }
+    }
 
     // AJAX-capable course format?
     $useajax = false; 
     print_header($title, $course->fullname, $navigation, '', '', true,
                  $buttons, user_login_string($course, $USER), false, $bodytags);
 
+    // If we are displaying a shell course, let the user know that this isn't what all users see
+    if (!empty($course->mnetpeer) && !empty($course->remotecourseid)
+        && has_capability('moodle/course:seemnetshell', $context)) {
+        print_box(get_string('thiscourseisshell', 'moodle', $jumpurl), 'noticebox');
+    }
+
     if ($completion->is_enabled() && ajaxenabled()) {
         // This value tracks whether there has been a dynamic change to the page.
         // It is used so that if a user does this - (a) set some tickmarks, (b)
index aa264bc65d7c05cb10933823e1836ba8fd63a947..1b60c564149f9274126cfdabcfcbc3c96292c738 100644 (file)
@@ -10,6 +10,7 @@ require_once dirname(dirname(dirname(__FILE__))) . '/config.php';
 require_once $CFG->libdir . '/adminlib.php';
 include_once $CFG->dirroot . '/mnet/lib.php';
 
+require_login();
 
 admin_externalpage_setup('ssoaccesscontrol');
 admin_externalpage_print_header();
@@ -19,7 +20,6 @@ $removecategory = optional_param('removecategory', 0, PARAM_BOOL);
 $addcourse      = optional_param('addcourse', 0, PARAM_BOOL);
 $removecourse   = optional_param('removecourse', 0, PARAM_BOOL);
 
-require_login();
 $sitecontext = get_context_instance(CONTEXT_SYSTEM);
 $sesskey = sesskey();
 $formerror = array();
index 3ed12aefd818cc71fae717e8225ef857ad6559b9..863af9a5dadcbf662bdaf31688b6660f21bd6f46 100644 (file)
@@ -18,7 +18,11 @@ if (is_array($mnethosts)) {
 <tr valign="top">
     <td align="right">allow_allcourses:</td>
     <td>
-    <?php print choose_from_menu_yesno('enrol_mnet_allow_allcourses', $frm->enrol_mnet_allow_allcourses); ?>
+    <?php 
+        global $OUTPUT;
+        $selectmenu = moodle_select_menu::make_yes_no('enrol_mnet_allow_allcourses', $frm->enrol_mnet_allow_allcourses);
+        echo $OUTPUT->select_menu($selectmenu);
+    ?>
     </td>
     <td>
     <?php  print_string("allow_allcourses", "enrol_mnet") ?>
index 95154196a74e3328444278cea2469c6e267a73b1..d4bfc5fbe98a9a931e40977ee63683d0e2e556fa 100644 (file)
@@ -55,9 +55,9 @@ class enrolment_plugin_mnet {
     /**
      * Override the base cron() function
      */
-    //function cron() {
-    //
-    //} // end of cron()
+    function cron() {
+        $this->process_role_updates();
+    } // end of cron()
 
 
 
@@ -70,8 +70,9 @@ class enrolment_plugin_mnet {
         $enrol = array();
         $enrol['name']        = 'mnet_enrol'; // Name & Description go in lang file
         $enrol['apiversion']  = 1;
-        $enrol['methods']     = array('available_courses','user_enrolments', 'enrol_user', 'unenrol_user', 'course_enrolments' );
-
+        $enrol['methods'] = array('available_courses','user_enrolments', 'enrol_user',
+                'unenrol_user', 'course_enrolments', 'abridge_course_enrolments',
+                'assign_role_user', 'get_default_role', 'get_allocatable_roles');
         return array($enrol);
     }
 
@@ -111,7 +112,9 @@ class enrolment_plugin_mnet {
                 {role} r ON r.id = co.defaultrole
             WHERE
                 co.visible = 1 AND
-                co.enrollable = 1
+                co.enrollable = 1 AND
+                coalesce(co.mnetpeer, 0) = 0 AND
+                coalesce(co.remotecourseid, 0) = 0
             ORDER BY
                 sortorder ASC
                 ";
@@ -178,7 +181,9 @@ class enrolment_plugin_mnet {
                 {role} r ON r.id = co.defaultrole
             WHERE
                 co.visible = 1 AND
-                co.enrollable = 1 $where
+                co.enrollable = 1 $where AND
+                coalesce(co.mnetpeer, 0) = 0 AND
+                coalesce(co.remotecourseid, 0) = 0
             ORDER BY
                 sortorder ASC
                 ";
@@ -212,7 +217,9 @@ class enrolment_plugin_mnet {
                 WHERE
                     co.visible = 1 AND
                     co.enrollable = 1 AND
-                    co.id IN ({$CFG->enrol_mnet_allowed_courses})
+                    co.id IN ({$CFG->enrol_mnet_allowed_courses}) AND
+                    coalesce(co.mnetpeer, 0) = 0 AND
+                    coalesce(co.remotecourseid, 0) = 0
                 ORDER BY
                     sortorder ASC
                     ";
@@ -341,7 +348,7 @@ class enrolment_plugin_mnet {
     * @return bool              Whether the user can login from the remote host
     */
     function unenrol_user($username, $courseid) {
-        global $MNET_REMOTE_CLIENT;
+        global $MNET_REMOTE_CLIENT, $DB;
 
         $userrecord = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$MNET_REMOTE_CLIENT->id));
 
@@ -389,13 +396,11 @@ class enrolment_plugin_mnet {
                 h.id, 
                 h.name
             FROM 
-                {mnet_host} h,
-                {mnet_host2service} h2s,
                 {mnet_service} s
+                INNER JOIN {mnet_host2service} h2s on h2s.serviceid = s.id
+                INNER JOIN {mnet_host} h on h.id = h2s.hostid
             WHERE
-                h.id          = h2s.hostid   AND
-                h2s.serviceid = s.id         AND
-                s.name        = 'mnet_enrol' AND
+                s.name = 'mnet_enrol' AND
                 h2s.subscribe = 1";
 
         $res = $DB->get_records_sql($sql);
@@ -590,6 +595,494 @@ class enrolment_plugin_mnet {
         return false;
     }
 
+    /**
+    * Unassign all role assignments (relating to calling mnet peer) in a course context - except specified list
+    *
+    * @param array keepusers users who should not be unenrolled from specified course
+    * @param int $courseid the course we should be unenrolling users from.
+    */
+    function abridge_course_enrolments($keepusersarray, $courseid) {
+        global $MNET_REMOTE_CLIENT, $CFG, $DB;
+
+        // Check that detail about the remote mnet peer is available:
+        if (empty($MNET_REMOTE_CLIENT->id)) {
+            return false;
+        }
+
+        // Get a list of (mnet) role assignments in the course where the user is from the selected mnet peer
+        // (and not in keepusers)
+        $params = array();
+        $delenrolmentssql = 'SELECT ' .
+                            ' ra.id, ra.userid, ra.contextid ' .
+                            'FROM ' .
+                            "{context} c " .
+                            " INNER JOIN {role_assignments} ra on ra.contextid = c.id " .
+                            " INNER JOIN {user} u on u.id = ra.userid " .
+                            'WHERE c.contextlevel = 50 ' .
+                            ' AND c.instanceid = ? ' .
+                            ' AND ra.enrol = \'mnet\' ' .
+                            ' AND u.mnethostid = ' . $MNET_REMOTE_CLIENT->id;
+        if (!empty($keepusersarray)) {
+            list($sqlfragment, $params) = $DB->get_in_or_equal($keepusersarray, SQL_PARAMS_QM, 'param0000', FALSE);
+            $delenrolmentssql .= " AND u.username $sqlfragment ";
+        }
+        array_unshift($params, $courseid);
+        $delenrolments = $DB->get_records_sql($delenrolmentssql, $params);
+
+        if ($delenrolments === false OR !is_array($delenrolments)) {
+            return false;
+        }
+
+        foreach ($delenrolments as $enrolment) {
+            if (!role_unassign(0, $enrolment->userid, 0, $enrolment->contextid)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /*
+    * Step through the mnet_role_management_queue sending role updates to mnet peers
+    * @return bool true if we had some success
+    */
+    function process_role_updates() {
+        global $CFG, $MNET, $DB;
+        echo "Processing mnet role management queue\n";
+        $course2contexts = $this->get_queue_relevant_contexts();
+        if (empty($course2contexts)) {
+            //no courses of interest, nothing for us to do
+            return true;
+        }
+
+        //Get current mnet cp course (if any) for courses of interest
+        $cpdetails = $this->map_courses_to_cp(array_keys($course2contexts));
+        //Get current local roles that are relevant in relation to each mnet peer
+        $peersrolemappings = $this->get_mnet_role_mappings($cpdetails);
+        foreach ($peersrolemappings as $peerid => $peerrolemappings) {
+            if (is_array($peerrolemappings)) {
+                $rolesmapped[$peerid] = array_keys($peerrolemappings);
+            }
+        }
+
+        //Allow the role update process to run for an arbitary amount of time.
+        $starttime = time();
+        $timelimit = $starttime + 100;
+        $queueitemssql = 'SELECT * FROM {mnet_role_management_queue} ORDER BY id ASC ';
+        $queueitems = $DB->get_records_sql($queueitemssql);
+        echo count($queueitems) . " items found ";
+        $updatecount = 0;
+        $rowcount = 0;
+        while (time() < $timelimit) {
+            if (empty($queueitems)) {
+                break;
+            }
+            $queueitem = array_shift($queueitems);
+            if (empty($queueitem->mnetpeer) || empty($queueitem->remotecourseid)) {
+                // If peer & remote course are not defined, this is a basic create/update request
+                // determine mnetpeer and remotecourseid based on record in course table
+                $queueitem->mnetpeer = $cpdetails[$queueitem->localcourse]->mnetpeer;
+                $queueitem->remotecourseid = $cpdetails[$queueitem->localcourse]->remotecourseid;
+            }
+            if (!empty($rolesmapped[$queueitem->mnetpeer])) {
+                $remoteroleid = $this->map_user_role($queueitem->userid, $rolesmapped[$queueitem->mnetpeer],
+                        $course2contexts[$queueitem->localcourse], $peersrolemappings[$queueitem->mnetpeer]);
+
+                // Enrol the user on the remote peer
+                $updateresult = $this->update_role_remote_course($queueitem->userid, $queueitem->mnetpeer, $queueitem->remotecourseid, $remoteroleid);
+            } else {
+                // No local roles map to anything on this peer - nothing to do:
+                $updateresult = true;
+            }
+            if ($updateresult) {
+                $deleteresult = $DB->delete_records('mnet_role_management_queue', array('id' => $queueitem->id));
+                if (!empty($deleteresult)) {
+                    ++$updatecount;
+                    if (++$rowcount > 40) {
+                        echo "\n";
+                        $rowcount = 1;
+                    }
+                    echo ".";
+                } else {
+                    echo "\nWarning - unable to remove role update from queue this is an unsustainable problem";
+                }
+            } else {
+                echo "Problem encountered - if this problem persists contact your mnet administrator\n";
+                return false;
+            }
+        }
+        echo "\n";
+        return true;
+    }
+
+    /**
+    * Update a local user's role in a remote course
+    * @param userid integer id of the user who we want the role assigned to
+    * @param mnethostid integer id of the mnet host the course is hosted on
+    * @param remotecourseid the id number of the course on the remote moodle
+    * @return bool true on success, false on failure
+    */
+    function update_role_remote_course($userid, $mnethostid, $remotecourseid, $remoteroleid) {
+        global $DB, $CFG, $MNET;
+
+        require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
+        // Create content-provider object
+        $mnet_cp = new mnet_peer();
+        $mnet_cp->set_id($mnethostid);
+
+        // Set up a minimilist user record for passing to remote function
+        $user = $DB->get_record('user',array('id' => $userid));
+        if ($user->mnethostid != $CFG->mnet_localhost_id) {
+            // If user is not local, force unenrolment
+            $remoteroleid = FALSE;
+        }
+
+        $minimalistuser = new stdclass;
+        $minimalistuser->id = $user->id;
+        $minimalistuser->username = $user->username;
+        $minimalistuser->firstname = $user->firstname;
+        $minimalistuser->lastname = $user->lastname;
+        $minimalistuser->email = $user->email;
+
+        if ($remoteroleid === FALSE) {
+            return $this->unenrol_user_remote($minimalistuser, $remotecourseid, $mnet_cp);
+        } elseif (empty($remoteroleid)) {
+            //Talking to an older system - can only enrol course's default role.
+            return $this->enrol_user_remote($minimalistuser, $remotecourseid, $mnet_cp);
+        } else {
+            return $this->full_enrol_user_remote($user, $remotecourseid, $mnet_cp, $remoteroleid);
+        }
+    }
+
+    /*
+    * Retreive information about contexts relevant to courses listed in the mnet_role_management_queue
+    * @return array keyed by localcourseid, containging array of contexts (course and its parents)
+    */
+    function get_queue_relevant_contexts() {
+        global $DB;
+        $coursecontextssql =
+                'SELECT ' .
+                ' DISTINCT localcourse, ' .
+                ' c.path ' .
+                'FROM {mnet_role_management_queue} que ' .
+                ' INNER JOIN {context} c on c.instanceid=que.localcourse and c.contextlevel=50';
+        $courses = $DB->get_records_sql($coursecontextssql);
+        if (empty($courses)) {
+            return false;
+        }
+        foreach ($courses as $course) {
+            $course2contexts[$course->localcourse] = explode('/',substr($course->path,1));
+        }
+        return $course2contexts;
+    }
+
+    /*
+    * Retreive information about the mnet content provider for specified courses
+    * @param array courses id number of local courses
+    * @return array of objects, keyed by localcourseid
+    */
+    function map_courses_to_cp($courses) {
+        global $DB;
+        //Get current mapping of localcourses to remote cp:
+        list($coursefragment, $params) = $DB->get_in_or_equal($courses);
+        $cpdetailsql =
+                'SELECT ' .
+                ' id, mnetpeer, remotecourseid ' .
+                'FROM {course} WHERE id ' . $coursefragment;
+        $cpdetails = $DB->get_records_sql($cpdetailsql, $params);
+        return $cpdetails;
+    }
+
+    /**
+    * form an array linking local and remote roles for each mnet peer
+    *
+    * @param array details of localcourses, mnet peer, and mnetcourseid
+    * @return array
+    */
+    function get_mnet_role_mappings($cpdetails) {
+        global $DB;
+        foreach ($cpdetails as $cpdetail) {
+            if (!isset($rolemappings[$cpdetail->mnetpeer])) {
+                //Get current all rolemappings for specified mnet peer
+                $rolemapsql = 'SELECT localrole, remoterole '.
+                        'FROM {mnet_role_mapping} '.
+                        'WHERE mnethost = ?';
+                $params = array($cpdetail->mnetpeer);
+                $rolemaps = $DB->get_records_sql($rolemapsql, $params);
+                if (!empty($rolemaps)) {
+                    foreach ($rolemaps as $rolemap) {
+                        $rolemappings[$cpdetail->mnetpeer][$rolemap->localrole] = $rolemap->remoterole;
+                    }
+                } else {
+                    $rolemappings[$cpdetail->mnetpeer] = null;
+                }
+            }
+        }
+        return $rolemappings;
+    }
+
+    /**
+    * Assign role to remote mnet user on specified local course
+    *
+    * @param array $user basic identity information about user getting role assigned
+    * @param int $courseid the id of the course the user is being assigned to
+    * @param int $roleid the role to be assigned
+    * @return bool Result of the role assignment
+    */
+    function assign_role_user($user, $courseid, $roleid) {
+        global $MNET, $MNET_REMOTE_CLIENT, $DB;
+        $context = $DB->get_record('context', array('contextlevel' => 50, 'instanceid' => $courseid));
+        $user = $DB->get_record('user', array('username' => $user['username'], 'mnethostid' => $MNET_REMOTE_CLIENT->id));
+        if (empty($context) || empty($user)) {
+            return false;
+        }
+
+        // Check that we allow this mnet client to assign this role
+        $published = $DB->get_record('mnet_role_published', array('mnethost' => $MNET_REMOTE_CLIENT->id, 'localrole' => $roleid));
+        if (empty($published)) {
+            return false;
+        }
+        // Check that this course is shared over mnet
+        if (!$this->course_published($courseid)) {
+            return false;
+        }
+
+        // Mnet peers can only assign a user to one role at a time
+        $existingrolessql =
+                'SELECT * ' .
+                'FROM {role_assignments} ' .
+                'WHERE userid = ? AND contextid = ? AND enrol = \'mnet\'';
+        $params = array($user->id, $context->id);
+        $existingroles = $DB->get_records_sql($existingrolessql, $params);
+        if (!empty($existingroles)) {
+            foreach ($existingroles as $existingrole) {
+                if ($existingrole->roleid != $roleid) {
+                    role_unassign($existingrole->roleid, $user->id, 0, $context->id, 'mnet');
+                }
+            }
+        }
+        return role_assign($roleid, $user->id, 0, $context->id, 0, 0, 0, 'mnet');
+    }
+
+    /**
+    * Determine if specified courseid is published over mnet
+    *
+    * @param integer $courseid
+    * @return bool true if the course is available over mnet, false otherwise
+    */
+    function course_published ($courseid) {
+        global $CFG, $DB;
+        //If the course in question is itself a shell course, it can not be published
+        $course = $DB->get_record('course', array('id' => $courseid));
+        if (!empty($course->mnetpeer) && !empty($course->remotecourseid)) {
+            return false;
+        }
+        //First check to see if all courses are allowed to be shared:
+        if ($CFG->enrol_mnet_allow_allcourses) {
+            return true;
+        }
+
+        //Check to see if the specified course is named in the list of shared courses
+        if (!empty($CFG->enrol_mnet_allowed_courses)) {
+            $sharedcourses = explode(',', $CFG->enrol_mnet_allowed_courses);
+            if (in_array($courseid,$sharedcourses)) {
+                return true;
+            }
+        }
+
+        //Check to see if any of the course's parent categories are shared
+        if (!empty($CFG->enrol_mnet_allowed_categories)) {
+            $categorysql =
+                    'SELECT ca.id, ca.path ' .
+                    'FROM {course} co ' .
+                    ' INNER JOIN {course_categories} ca ON ca.id=co.category '.
+                    'WHERE co.id = ?';
+            $params = array($courseid);
+            $category = $DB->get_record_sql($categorysql,$params);
+            if (empty($category)) {
+                return false;
+            }
+
+            $sharedcategories = explode(',',$CFG->enrol_mnet_allowed_categories);
+            $categories = explode('/',substr($category->path,1));
+            foreach ($categories as $category) {
+                if (in_array($category, $sharedcategories)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+    * Map roles that a user has assigned to them relevant to course context (and parent contexts)
+    * to a role we can allocate on the course's content provider
+    * @param userid integer the local user's identifier
+    * @param relevantroles array list of local roleids that we may want to translate to remote roleid
+    * @param relevantcontexts array list of contexts where relevant roles might be assigned
+    * @param peerrolemappings array keyed on local roleid, value is remote role id.
+   */
+    function map_user_role ($userid, $relevantroles, $relevantcontexts, $peerrolemappings) {
+        global $DB;
+        if (!empty($relevantroles)) {
+            list($contextfragment, $contextids) = $DB->get_in_or_equal($relevantcontexts);
+            list($rolefragment, $roleids) = $DB->get_in_or_equal($relevantroles);
+            $roletoassignsql =
+                    'SELECT ra.roleid, ra.roleid as roleid2 ' .
+                    'FROM {role_assignments} ra ' .
+                    ' INNER JOIN {context} con ON con.id=ra.contextid ' .
+                    'WHERE con.id ' . $contextfragment .
+                    '  AND ra.roleid ' . $rolefragment .
+                    '  AND ra.userid = ? ' .
+                    'ORDER BY con.contextlevel desc, ra.timemodified desc ' .
+                    'LIMIT 1';
+            $params = array_merge($contextids, $roleids);
+            $params[] = $userid;
+            $localrolestoassign = $DB->get_records_sql($roletoassignsql, $params);
+            if (!empty($localrolestoassign)) {
+                $localroletoassign = array_shift($localrolestoassign);
+            } else {
+                $localroletoassign = NULL;
+            }
+        } else {
+            $localroletoassign = NULL;
+        }
+
+        // Determine the role to be assigned on the remote peer based on the user's role in
+        // the local course, and the role mappings for the relevant peer
+        if (empty($localroletoassign)) {
+            // No local role means that this is an unenrolment
+            return FALSE;
+        } elseif (!isset($peerrolemappings[$localroletoassign->roleid])) {
+            // User has local role, but this is not mapped to anything on the remote peer
+            // - this is also an unenrolment
+            return FALSE;
+        } else {
+            return $peerrolemappings[$localroletoassign->roleid];
+        }
+    }
+
+    /**
+    * Unenrol user from course on mnet peer
+    * @param minimalistuser object user object with only basic properties
+    * @param remotecourseid integer courseid on the remote peer
+    * @param mnetcp object basic peer info
+    * @return bool true on successful unenrolment
+   */
+    function unenrol_user_remote($minimalistuser, $remotecourseid, $mnet_cp) {
+        global $CFG, $MNET;
+        require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
+        //Unenrol user from remote course
+        $mnetrequest = new mnet_xmlrpc_client();
+        $mnetrequest->set_method('enrol/mnet/enrol.php/unenrol_user');
+        $mnetrequest->add_param($minimalistuser->username);
+        $mnetrequest->add_param($remotecourseid);
+        if ($mnetrequest->send($mnet_cp) === true) {
+            if($mnetrequest->response) {
+                return true;
+            }
+            // Legacy mnet considers request to unenrol 'nonexistant' user an error
+            // Check for this situ by creating usr by enroling in dummy course,
+            // Then repeating the unenrol request.
+            $mnetrequest = new mnet_xmlrpc_client();
+            $mnetrequest->set_method('enrol/mnet/enrol.php/enrol_user');
+            $mnetrequest->add_param($minimalistuser);
+            $mnetrequest->add_param(0); //dummy remote course id
+            // Ignore result, we expect it to fail due to dummy remote course id
+            $mnetrequest->send($mnet_cp);
+
+            $mnetrequest = new mnet_xmlrpc_client();
+            $mnetrequest->set_method('enrol/mnet/enrol.php/unenrol_user');
+            $mnetrequest->add_param($minimalistuser->username);
+            $mnetrequest->add_param($remotecourseid);
+            if ($mnetrequest->send($mnet_cp) === true) {
+                return $mnetrequest->response;
+            }
+        }
+    }
+
+    /**
+    * Enrol user into course on mnet peer
+    * @param minimalistuser object user object with only basic properties
+    * @param remotecourseid integer courseid on the remote peer
+    * @param mnetcp object basic peer info
+    * @return bool true on successful enrolment
+   */
+    function enrol_user_remote($minimalistuser, $remotecourseid, $mnet_cp) {
+        global $CFG, $MNET;
+        require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
+        //Unenrol user from remote course
+        $mnetrequest = new mnet_xmlrpc_client();
+        $mnetrequest->set_method('enrol/mnet/enrol.php/enrol_user');
+        $mnetrequest->add_param($minimalistuser);
+        $mnetrequest->add_param($remotecourseid);
+        if ($mnetrequest->send($mnet_cp) === true) {
+            return $mnetrequest->response;
+        }
+    }
+
+    /**
+    * Create user on remote mnet peer and assign remote role againnst specified course
+    * @param user object properties about the user to create/assign role
+    * @param remotecourseid integer courseid on the remote peer
+    * @param mnetcp object basic peer info
+    * @return bool true on successful enrolment
+   */
+    function full_enrol_user_remote($user, $remotecourseid, $mnet_cp, $remoteroleid) {
+        global $CFG, $MNET;
+        require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
+        //Make sure the user exists and is up to date on the remote peer:
+        $userupdaterequest = new mnet_xmlrpc_client();
+        $userupdaterequest->set_method('auth/mnet/auth.php/createupdate_user_bool');
+        $userupdaterequest->add_param($user);
+        $userupdaterequest->send($mnet_cp);
+        $remoteuserexists = $userupdaterequest->response;
+        if (empty($remoteuserexists)) {
+            // Couldn't verify that remote user exists:
+            return false;
+        }
+        //Then call full enrol function
+        $mnetrequest = new mnet_xmlrpc_client();
+        $mnetrequest->set_method('enrol/mnet/enrol.php/assign_role_user');
+        $mnetrequest->add_param((array)$user,'array');
+        $mnetrequest->add_param($remotecourseid);
+        $mnetrequest->add_param($remoteroleid);
+        if ($mnetrequest->send($mnet_cp) === true) {
+            return $mnetrequest->response;
+        }
+        return false;
+    }
+
+    /**
+     * Get the default course role
+     * @return mixed array record from our role table, or false on failure;
+     */
+    function get_default_role() {
+        global $CFG, $DB;
+        $roles = $DB->get_records('role', array('id' => $CFG->defaultcourseroleid));
+        if (empty($roles)) {
+            return false;
+        }
+        return (array) array_shift($roles);
+    }
+
+    /**
+     * Get array of roles that an mnethost is allowed to allocate
+     * @return mixed array records from our role table, or false on failure;
+     */
+    function get_allocatable_roles() {
+        global $MNET_REMOTE_CLIENT, $DB;
+        $rolessql =
+                'SELECT r.id, r.shortname, r.name ' .
+                'FROM {mnet_role_published} mrp ' .
+                ' INNER JOIN {role} r ON r.id = mrp.localrole ' .
+                'WHERE mrp.mnethost = ? ';
+        $rolesparams = array( $MNET_REMOTE_CLIENT->id );
+        $roles = $DB->get_records_sql($rolessql, $rolesparams);
+        if (empty($roles)) {
+            return false;
+        }
+        return $roles;
+    }
 } // end of class
 
 ?>
diff --git a/lang/en_utf8/help/mnetcontent.html b/lang/en_utf8/help/mnetcontent.html
new file mode 100644 (file)
index 0000000..7866cb6
--- /dev/null
@@ -0,0 +1,4 @@
+<h1>Mnet Content</h1>
+
+<p>A course's content can be provided by a remote moodle where there is an established mnet trust relationship, and the remote moodle is publishing courses.</p>
+<p> When configured, aspects of the local course are reflected on the remote moodle (enrolments etc)</p>
diff --git a/lang/en_utf8/help/mnetcp.html b/lang/en_utf8/help/mnetcp.html
new file mode 100644 (file)
index 0000000..b0c0845
--- /dev/null
@@ -0,0 +1,8 @@
+<h1> Mnet Content Provision </h1>
+<p> To configure this course to use content provided by another moodle, select the desired content provider in the first dropdown box, then from the list of courses they are offering (in the second dropdown box) select the course you want to use to provide content to your users.</p>
+
+<p> Once you have selected a course and clicked 'reload/update', any previous mnet content provider will be sent a message to instruct them to unenrol any users on their moodle (from this moodle) who are no-longer relevant. All users enrolled in the local course are added to a queue to have their roles added/updated on the remote course. These updates take place using normal scheduled processing.</p>
+
+<p> When users attempt to view the local course, they are automatically directed to jump to the relvant course on the mnet provider. </p>
+
+<p> In the event that enrolments get out-of-sync, you can leave the mnet peer, and course selection drop-downs as set, and click 'reload-update' to bring the enrolments between the identity provider (this moodle), and content provider (remote moodle) back into sync.</p>
index 8d375302730ec1714cd5dd41284d6947eebd6810..7fdb0062a4124fb0220fbe3b97ecafd7fa5d7e4b 100644 (file)
@@ -208,6 +208,7 @@ $string['categoryupdated'] = 'The category \'$a\' was updated';
 $string['changedpassword'] = 'Changed password';
 $string['changepassword'] = 'Change password';
 $string['changessaved'] = 'Changes saved';
+$string['changescancelled'] = 'Changes cancelled';
 $string['check'] = 'Check';
 $string['checkall'] = 'Check all';
 $string['checkingbackup'] = 'Checking backup';
@@ -253,6 +254,7 @@ $string['comparelanguage'] = 'Compare and edit current language';
 $string['complete'] = 'Complete';
 $string['completereport'] = 'Complete report';
 $string['configuration'] = 'Configuration';
+$string['configuremnetsource'] = 'Click $a to configure mnet content provider';
 $string['confirm'] = 'Confirm';
 $string['confirmcheckfull'] = 'Are you absolutely sure you want to confirm $a ?';
 $string['confirmed'] = 'Your registration has been confirmed';
@@ -311,7 +313,9 @@ $string['coursehelpenrolmentkey'] = 'If set, users will need this key to be enro
 $string['coursehidden'] = 'This course is currently unavailable to students';
 $string['courseimportnotaught'] = 'You don\'t seem to be an editing teacher in any other courses, there are no courses for you to import from.';
 $string['courseinfo'] = 'Course info';
+$string['courselocalcp'] = 'This course\'s content is currently locally provided';
 $string['coursemessage'] = 'Message course users';
+$string['coursemnetcp'] = 'This course\'s mnet content provider is currently $a';
 $string['coursenotaccessible'] = 'This course does not allow public access';
 $string['courseoverview'] = 'Course overview';
 $string['courseoverviewgraph'] = 'Course overview graph';
@@ -471,6 +475,7 @@ $string['edit'] = 'Edit $a';
 $string['editcategorysettings'] = 'Edit category settings';
 $string['editcategorythis'] = 'Edit this category';
 $string['editcoursesettings'] = 'Edit course settings';
+$string['editcoursemnetsettings'] = 'Edit course mnet content provider settings';
 $string['editfiles'] = 'Edit files';
 $string['editgroupprofile'] = 'Edit group profile';
 $string['editinga'] = 'Editing $a';
@@ -1018,6 +1023,25 @@ $string['missingsummary'] = 'Missing summary';
 $string['missingteacher'] = 'Must choose something';
 $string['missingurl'] = 'Missing URL';
 $string['missingusername'] = 'Missing username';
+$string['mnetauthorisemnetroles'] = 'Authorise Mnet Roles';
+$string['mnetcontentnotset'] = 'This course is not configured as a shell course for mnet content';
+$string['mnetcontentset'] = 'This course is a shell for content provided by an mnet host';
+$string['mnetcontentsource'] = 'Mnet content source';
+$string['mnetmaproles'] = 'Map Roles';
+$string['mnetoldcpunenroled'] = '$a users unenrolled from old content-provider course';
+$string['mnetoldcpunenroledbulk'] = 'Users unenrolled from old content-provider course';
+$string['mnetpeeruncontactable'] = 'Could not contact specified mnet peer';
+$string['mnetpermittedroles'] = 'Permitted Roles';
+$string['mnetpermittedrolesinstructions'] = 'Specify roles this mnet peer is allowed to allocate to its users.  If you remove roles from this list, existing role assignments will be removed.';
+$string['mnetroleispublished'] = 'Notice: This role can be assigned by remote mnet admistrators - changes you make here to permissions affect both users who are assigned this role locally, and remote users assigned this role by remote administrators.';
+$string['mnetrolemapping'] = 'Role Mapping';
+$string['mnetrolemappinginstructions'] = 'Specify how local roles should be translated to roles on the remote mnet peer.  For each local role, choose from the list of roles the remote peer makes available.  When you remap roles, users who have been assigned the old role will be queued to have that enrolment updated.';
+$string['mnetrolespermitted'] = 'Roles Permitted';
+$string['mnetsettings'] = 'Mnet settings';
+$string['mnetusersenqueued'] = '$a local course users are now in the queue to have their role added/updated in the new content-provider course';
+$string['mnetusersinqueue'] = '$a enrolments are now in queue to have their role updated in the content-provider course';
+$string['mnetcontent'] = 'mnetcontent';
+$string['mnetcontentprovision'] = 'Mnet Content Provision';
 $string['modified'] = 'Modified';
 $string['moduledeleteconfirm'] = 'You are about to completely delete the module \'$a\'.  This will completely delete everything in the database associated with this activity module.  Are you SURE you want to continue?';
 $string['moduledeletefiles'] = 'All data associated with the module \'$a->module\' has been deleted from the database.  To complete the deletion (and prevent the module re-installing itself), you should now delete this directory from your server: $a->directory';
@@ -1420,6 +1444,7 @@ $string['separateandconnected'] = 'Separate and Connected ways of knowing';
 $string['serverlocaltime'] = 'Server\'s local time';
 $string['setcategorytheme'] = 'Set Category Theme';
 $string['settings'] = 'Settings';
+$string['settingssaved'] = 'Settings have been saved';
 $string['shortname'] = 'Short name';
 $string['shortnamecourse'] = 'Course short name';
 $string['shortnameuser'] = 'User short name';
@@ -1574,6 +1599,10 @@ $string['themesaved'] = 'New theme saved';
 $string['thereareno'] = 'There are no $a in this course';
 $string['thiscategorycontains'] = 'This category contains';
 $string['thischarset'] = 'UTF-8';
+$string['thiscourseisshell'] = 'This view is not available to standard course users <br />
+Those users without the ability to see the mnet shell are directed to the content provider
+ before seeing this page <br />
+If your role is mapped to a role on the content provider site you can click to see the <a href=\"$a\">course content</a>';
 $string['thisdirection'] = 'ltr';
 $string['thislanguage'] = 'English';
 $string['time'] = 'Time';
index ed5995df4b580a368b04d72fe6cb8eefcde3812b..e09e54f7fef673cdec13093faf04ef7b322437b2 100644 (file)
@@ -59,6 +59,7 @@ $string['course:changeshortname'] = 'Change course short name';
 $string['course:changeidnumber'] = 'Change course ID number';
 $string['course:create'] = 'Create courses';
 $string['course:delete'] = 'Delete courses';
+$string['course:linkmnetcourse'] = 'Link course to mnet content provider';
 $string['course:manageactivities'] = 'Manage activities';
 $string['course:managefiles'] = 'Manage files';
 $string['course:managegroups'] = 'Manage groups';
@@ -67,6 +68,7 @@ $string['course:managescales'] = 'Manage scales';
 $string['course:request'] = 'Request new courses';
 $string['course:reset'] = 'Reset course';
 $string['course:sectionvisibility'] = 'Control section visibility';
+$string['course:seemnetshell'] = 'See contents of shell mnet course';
 $string['course:setcurrentsection'] = 'Set current section';
 $string['course:update'] = 'Update course settings';
 $string['course:useremail'] = 'Enable/disable email address';
index ed4d4671c1cb6be09adede5aab027a814fb90301..e869cc770d7790a05ae3a93d2e9d5378387543d4 100755 (executable)
@@ -3004,6 +3004,11 @@ function role_assign($roleid, $userid, $groupid, $contextid, $timestart=0, $time
         }
     }
 
+    if ($context->contextlevel <= CONTEXT_COURSE) {
+        update_mnet_cp_roles($userid, $context);
+    }
+
+
     events_trigger('role_assigned', $ra);
 
     return $ra->id;
@@ -3107,6 +3112,10 @@ function role_unassign($roleid=0, $userid=0, $groupid=0, $contextid=0, $enrol=NU
                     }
                 }
 
+                if ($context->contextlevel <= CONTEXT_COURSE) {
+                    update_mnet_cp_roles($userid, $context);
+                }
+
                 if ($fireevent) {
                     events_trigger('role_unassigned', $ra);
                 }
@@ -6286,4 +6295,90 @@ function role_cap_duplicate($sourcerole, $targetrole) {
     }
 }
 
+/**
+ * Enqueue users to get role updated on mnet content providers (CPs)
+ *
+ * @param int $userid id of local user whose role has changed
+ * @param object $context where the user has had a role change
+ *
+ * @return bool true on success, false on failure.
+ */
+function update_mnet_cp_roles($userid, $context) {
+    global $DB;
+    // Prepare an object to insert once for each relevant course at or below the specified context
+    $rolemanagementobj = new stdClass();
+    $rolemanagementobj->userid = $userid;
+    // Actual cp details to be determined when the update gets to the front of the queue
+    $rolemanagementobj->mnetpeer = null;
+    $rolemanagementobj->remotecourseid = null;
+
+    //SQL to select courses at or below the specifed context which have a content provider.
+    $queryparams = array();
+    $coursessql =
+            "SELECT " .
+            " c.id, c.id as junk " .
+            "FROM {context} cx " .
+            " INNER JOIN {course} c ON c.id = cx.instanceid " .
+            "WHERE " .
+            " cx.contextlevel = 50 " .
+            " AND c.mnetpeer IS NOT NULL " .
+            " AND c.remotecourseid IS NOT NULL ";
+    if ($context->contextlevel < CONTEXT_COURSE) {
+        $coursessql .= " AND cx.path like ?";
+        $queryparams[] = $context->path . '/%';
+    } else {
+        $coursessql .= " AND cx.id = ? ";
+        $queryparams[] = $context->id;
+    }
+    $coursestoupdate = $DB->get_records_sql($coursessql, $queryparams);
+    if (empty ($coursestoupdate)) {
+        return true; // All required courses have been processed
+    }
+
+    foreach ($coursestoupdate as $courseid => $junk) {
+        $rolemanagementobj->localcourse = $courseid;
+        if (!mnet_enqueue_role_update($rolemanagementobj)) {
+            return false;
+        }
+    }
+}
+
+/**
+ * Assert than queue object is present in role management queue
+ *
+ * @param object $rolemanagementobj (rmo) what we want to make sure is in the db
+ *
+ * @return bool true on success, false if object not in db, and unable to insert
+ */
+function mnet_enqueue_role_update($rolemanagementobj) {
+    global $DB;
+    // Form where clause to determine if rmo is already in queue
+    $queryparams = array((int)$rolemanagementobj->userid, (int)$rolemanagementobj->localcourse);
+    $whereclause = 'WHERE userid = ? AND localcourse = ? ';
+
+    if (!empty($rolemanagementobj->mnetpeer)) {
+        $whereclause .= ' AND mnetpeer = ? ';
+        $queryparams[] = $rolemanagementobj->mnetpeer;
+    } else {
+        $whereclause .= ' AND mnetpeer is null';
+    }
+
+    if (!empty($rolemanagementobj->remotecourseid)) {
+        $whereclause .= ' AND remotecourseid = ?';
+        $queryparams[] = $rolemanagementobj->remotecourseid;
+    } else {
+        $whereclause .= ' AND remotecourseid is null';
+    }
+    $queueobjectsql = "SELECT * FROM {mnet_role_management_queue} " . $whereclause;
+
+    $preexisting = $DB->get_records_sql($queueobjectsql, $queryparams);
+    if (empty($preexisting)) {
+        // If we couldn't find the object in the queue,
+        // put it in the queue
+        $insertresult = $DB->insert_record('mnet_role_management_queue', $rolemanagementobj, false);
+        return $insertresult;
+    }
+    return true; // role update already in queue
+}
+
 ?>
index adfd7d777b5e92dd286f5725fb50209c26387e5b..ff277cc0ae1b6ca4e1e29ff3c7f175ab93487fe7 100644 (file)
@@ -624,6 +624,7 @@ function get_courses_wmanagers($categoryid=0, $sort="c.sortorder ASC", $fields=a
                         'shortname', 'fullname', 'idnumber',
                         'guest', 'startdate', 'visible',
                         'newsitems',  'cost', 'enrol',
+                        'mnetpeer', 'remotecourseid',
                         'groupmode', 'groupmodeforce');
 
     if (!is_null($fields) && is_string($fields)) {
@@ -838,6 +839,7 @@ function get_my_courses($userid, $sort='visible DESC,sortorder ASC', $fields=NUL
                         'shortname', 'fullname', 'idnumber',
                         'guest', 'startdate', 'visible',
                         'newsitems',  'cost', 'enrol',
+                        'mnetpeer', 'remotecourseid',
                         'groupmode', 'groupmodeforce');
 
     if (!is_null($fields) && is_string($fields)) {
index 8ff7b5c3c5805361d7b05dae20c8c96931ee88cd..ef1d97fcdcf2d7329ec88d6e367e2cdc45ac4d58 100644 (file)
@@ -510,6 +510,27 @@ $moodle_capabilities = array(
         )
     ),
 
+    'moodle/course:linkmnetcourse' => array(
+
+        'riskbitmask' => RISK_SPAM | RISK_PERSONAL | RISK_XSS,
+
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_COURSE,
+        'legacy' => array(
+            'admin' => CAP_ALLOW
+        )
+    ),
+
+    'moodle/course:seemnetshell' => array(
+
+        'captype' => 'read',
+        'contextlevel' => CONTEXT_COURSE,
+        'legacy' => array(
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW
+        )
+    ),
+
     'moodle/course:delete' => array(
 
         'riskbitmask' => RISK_DATALOSS,
index 6ba9fbbe7848842a04a1b3ccf4fca5658f7346a9..46ef057cd7c2f78a011bc695afd7608e80add3df 100644 (file)
         <FIELD NAME="enrolenddate" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="enrolstartdate" NEXT="enrol"/>
         <FIELD NAME="enrol" TYPE="char" LENGTH="20" NOTNULL="true" SEQUENCE="false" PREVIOUS="enrolenddate" NEXT="defaultrole"/>
         <FIELD NAME="defaultrole" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="The default role given to participants who self-enrol" PREVIOUS="enrol" NEXT="enablecompletion"/>
-        <FIELD NAME="enablecompletion" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="1 = allow use of 'completion' progress-tracking on this course. 0 = disable completion tracking on this course." PREVIOUS="defaultrole"/>
+        <FIELD NAME="enablecompletion" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="1 = allow use of 'completion' progress-tracking on this course. 0 = disable completion tracking on this course." PREVIOUS="defaultrole" NEXT="mnetpeer"/>
+        <FIELD NAME="mnetpeer" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" PREVIOUS="enablecompletion" NEXT="remotecourseid"/>
+        <FIELD NAME="remotecourseid" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" PREVIOUS="mnetpeer"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
       <INDEXES>
         <INDEX NAME="category" UNIQUE="false" FIELDS="category" NEXT="idnumber"/>
         <INDEX NAME="idnumber" UNIQUE="false" FIELDS="idnumber" PREVIOUS="category" NEXT="shortname"/>
-        <INDEX NAME="shortname" UNIQUE="false" FIELDS="shortname" PREVIOUS="idnumber"/>
+        <INDEX NAME="shortname" UNIQUE="false" FIELDS="shortname" PREVIOUS="idnumber" NEXT="coursecontent"/>
+        <INDEX NAME="coursecontent" UNIQUE="true" FIELDS="remotecourseid, mnetpeer" PREVIOUS="shortname"/>
       </INDEXES>
     </TABLE>
     <TABLE NAME="course_categories" COMMENT="Course categories" PREVIOUS="course" NEXT="course_display">
         <INDEX NAME="blockinstanceid-contextid-pagetype-subpage" UNIQUE="true" FIELDS="blockinstanceid, contextid, pagetype, subpage"/>
       </INDEXES>
     </TABLE>
-    <TABLE NAME="comments" COMMENT="moodle comments module" PREVIOUS="block_positions">
+    <TABLE NAME="comments" COMMENT="moodle comments module" PREVIOUS="block_positions" NEXT="mnet_role_management_queue">
       <FIELDS>
         <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="contextid"/>
         <FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" PREVIOUS="id" NEXT="commentarea"/>
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
       </KEYS>
     </TABLE>
+    <TABLE NAME="mnet_role_management_queue" COMMENT="track users who need their role updated on remote courses" PREVIOUS="comments" NEXT="mnet_role_mapping">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="false" SEQUENCE="true" NEXT="userid"/>
+        <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="false" SEQUENCE="false" PREVIOUS="id" NEXT="localcourse"/>
+        <FIELD NAME="localcourse" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="false" SEQUENCE="false" PREVIOUS="userid" NEXT="mnetpeer"/>
+        <FIELD NAME="mnetpeer" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="false" SEQUENCE="false" PREVIOUS="localcourse" NEXT="remotecourseid"/>
+        <FIELD NAME="remotecourseid" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="false" SEQUENCE="false" PREVIOUS="mnetpeer"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="userid-localcourse-mnetpeer-remotecourseid" UNIQUE="true" FIELDS="userid, localcourse, mnetpeer, remotecourseid"/>
+      </INDEXES>
+    </TABLE>
+    <TABLE NAME="mnet_role_mapping" COMMENT="how our local roles map to roles on remote sites" PREVIOUS="mnet_role_management_queue" NEXT="mnet_role_published">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="false" SEQUENCE="true" NEXT="mnethost"/>
+        <FIELD NAME="mnethost" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="false" SEQUENCE="false" PREVIOUS="id" NEXT="localrole"/>
+        <FIELD NAME="localrole" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="false" SEQUENCE="false" PREVIOUS="mnethost" NEXT="remoterole"/>
+        <FIELD NAME="remoterole" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="false" SEQUENCE="false" PREVIOUS="localrole"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="mnethost-localrole" UNIQUE="true" FIELDS="mnethost, localrole"/>
+      </INDEXES>
+    </TABLE>
+    <TABLE NAME="mnet_role_published" COMMENT="Which roles we allow remote admins to allocate" PREVIOUS="mnet_role_mapping">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="false" SEQUENCE="true" NEXT="mnethost"/>
+        <FIELD NAME="mnethost" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="false" SEQUENCE="false" PREVIOUS="id" NEXT="localrole"/>
+        <FIELD NAME="localrole" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="false" SEQUENCE="false" PREVIOUS="mnethost"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="mnethost-localrole" UNIQUE="true" FIELDS="mnethost, localrole"/>
+      </INDEXES>
+    </TABLE>
   </TABLES>
-</XMLDB>
\ No newline at end of file
+</XMLDB>
index dea7578a07ae82f4da0c996baf751d0f71ca96a5..0534cc6585702f11055ae215655007ff1522e32a 100644 (file)
@@ -2362,11 +2362,160 @@ WHERE gradeitemid IS NOT NULL AND grademax IS NOT NULL");
         if (!$dbman->table_exists($table)) {
             $dbman->create_table($table);
         }
-
     /// Main savepoint reached
         upgrade_main_savepoint($result, 2009072400);
     }
 
+
+    if ($result && $oldversion < 2009073101) {
+        // Add details to record that course is a shell of remote mnet peer, which course on that peer supplies the content
+        $table = new xmldb_table('course');
+        $field = new xmldb_field('mnetpeer', XMLDB_TYPE_INTEGER, '10', false, null, null, null, null, null, null);
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+        $field = new xmldb_field('remotecourseid', XMLDB_TYPE_INTEGER, '10', false, null, null, null, null, null, null);
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+        // Each mnetpeer/remote course can only be mapped to by one local course:
+        $index = new xmldb_index('coursecontent', XMLDB_INDEX_UNIQUE, array('remotecourseid','mnetpeer'));
+        if (!$dbman->index_exists($table, $index)) {
+            $dbman->add_index($table, $index);
+        }
+
+        // Place to track which mnet peer we need to talk to about recent changes:
+        $table = new xmldb_table('mnet_role_management_queue');
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', false, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+        $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', false, XMLDB_NOTNULL, null, null, null, null);
+        $table->add_field('localcourse', XMLDB_TYPE_INTEGER, '10', false, XMLDB_NOTNULL, null, null, null, null);
+        $table->add_field('mnetpeer', XMLDB_TYPE_INTEGER, '10', false, null, null, null, null, null);
+        $table->add_field('remotecourseid', XMLDB_TYPE_INTEGER, '10', false, null, null, null, null, null);
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $table->add_index('userid-localcourse-mnetpeer-remotecourseid', XMLDB_INDEX_UNIQUE, array('userid', 'localcourse', 'mnetpeer', 'remotecourseid'));
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+        // Create table to record how our local roles map to roles on remote sites
+        $table = new XMLDBTable('mnet_role_mapping');
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', false, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+        $table->add_field('mnethost', XMLDB_TYPE_INTEGER, '10', false, XMLDB_NOTNULL, null, null, null, null);
+        $table->add_field('localrole', XMLDB_TYPE_INTEGER, '10', false, XMLDB_NOTNULL, null, null, null, null);
+        $table->add_field('remoterole', XMLDB_TYPE_INTEGER, '10', false, XMLDB_NOTNULL, null, null, null, null);
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $table->add_index('mnethost-localrole', XMLDB_INDEX_UNIQUE, array('mnethost', 'localrole'));
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+        // Table to record which roles we allow remote admins to allocate
+        $table = new XMLDBTable('mnet_role_published');
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', false, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+        $table->add_field('mnethost', XMLDB_TYPE_INTEGER, '10', false, XMLDB_NOTNULL, null, null, null, null);
+        $table->add_field('localrole', XMLDB_TYPE_INTEGER, '10', false, XMLDB_NOTNULL, null, null, null, null);
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $table->add_index('mnethost-localrole', XMLDB_INDEX_UNIQUE, array('mnethost', 'localrole'));
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+        // Need to convert old mnet's records of "enrol_courses" (remote courses) and "enrol_assignments"
+        // (enrolments of local users in remote courses) to
+        // courses and role assignments.
+        // And set default role sharing and role mapping to match pre-upgrade setup
+        $existinghostssql =
+                'SELECT h2s.hostid, h2s.hostid as hostid2 ' .
+                'FROM {mnet_service} s ' .
+                ' INNER JOIN {mnet_host2service} h2s on h2s.serviceid=s.id ' .
+                'WHERE s.name=\'sso_sp\' and h2s.publish = 1 ';
+        $existingssohosts = $DB->get_records_sql($existinghostssql);
+        if ($existingssohosts) {
+            $publication = new stdclass;
+            $publication->localrole = $CFG->defaultcourseroleid;
+            $mapping = new stdclass;
+            $mapping->localrole = $CFG->defaultcourseroleid;
+            $mapping->remoterole = 0; //For now assume that the other mnet peer isn't updated too
+
+            require_once($CFG->dirroot . '/course/lib.php');
+
+            // Find a category to put the courses in:
+            $categories = $DB->get_records_sql('SELECT * ' .
+                    'FROM {course_categories} ' .
+                    'WHERE name ilike \'misc%\' ' .
+                    'ORDER BY id asc');
+            if (!empty($categories)) {
+                $category = array_shift($categories);
+            } else {
+                $categorysql = 'SELECT * ' .
+                        'FROM ' . $CFG->prefix . 'course_categories ' .
+                        'ORDER BY id asc';
+                $category = array_shift($DB->get_records_sql($categorysql));
+            }
+
+            foreach ($existingssohosts as $existingssohost) {
+                $publication->mnethost = $existingssohost->hostid;
+                $mapping->mnethost = $existingssohost->hostid;
+                $DB->insert_record('mnet_role_published', $publication);
+                $DB->insert_record('mnet_role_mapping', $mapping);
+
+                $enrolledcoursessql = 'SELECT distinct courseid ' .
+                        'FROM {mnet_enrol_assignments} ' .
+                        'WHERE ' .
+                        " (enroltype like 'mnet' or enroltype like '')" .
+                        ' AND hostid = ? ';
+                $params = array($existingssohost->hostid);
+                $activecoursessql =
+                        'SELECT '.
+                        ' c.id, c.remoteid, c.fullname, c.shortname,' .
+                        ' c.idnumber, c.summary, c.remoteid ' .
+                        'FROM {mnet_enrol_course} c ' .
+                        ' INNER JOIN ( ' . $enrolledcoursessql . ' ) as ec on ec.courseid = c.id ' .
+                        ' INNER JOIN {mnet_host} h' .
+                        '  on h.id=c.hostid and h.deleted = 0 ' .
+                        'ORDER BY c.hostid, c.id';
+                $activecourses = $DB->get_records_sql($activecoursessql, $params);
+                if (empty($activecourses)) {
+                    continue;
+                }
+                foreach ($activecourses as $coursedata) {
+                    $coursedata->category = $category->id;
+                    $coursedata->startdate = time();
+                    $coursedata->idnumber = '';
+                    $oldmnetcourseid = $coursedata->id;
+
+                    $newcourse = create_course($coursedata);
+                    $newcourse->mnetpeer = $existingssohost->hostid;
+                    $newcourse->remotecourseid = $coursedata->remoteid;
+                    update_record('course', $newcourse);
+
+                    $contextsql = 'SELECT * ' .
+                            'FROM {context} ' .
+                            'WHERE instanceid = ? ' .
+                            ' AND contextlevel = 50';
+                    $contextparams = array($newcourse->id);
+                    $context = array_shift($DB->get_records_sql($contextsql, $contextparams));
+
+                    // Get a list of user enrolments in the old mnet 'course' and apply them to the new course
+                    $oldenrolmentssql =
+                            'SELECT * ' .
+                            'FROM {mnet_enrol_assignments} ' .
+                            'WHERE hostid = ? ' .
+                            ' AND courseid =  ? ';
+                    $oldenrolmentparams = array($existingssohost->hostid, $oldmnetcourseid);
+                    $courseenrolments = $DB->get_records_sql($oldenrolmentssql, $oldenrolmentparams);
+                    foreach($courseenrolments as $enrolment) {
+                        role_assign($CFG->defaultcourseroleid, $enrolment->userid, 0, $context->id);
+                    }
+
+                    $DB->delete_records('mnet_enrol_assignments',
+                            array('hostid' => $existingssohost->hostid, 'courseid' => $oldmnetcourseid));
+                }
+
+            }
+            $DB->delete_records('mnet_enrol_course', array('hostid' => $existingssohost->hostid));
+        }
+        upgrade_main_savepoint($result, 2009073101);
+    }
     return $result;
 }
 
index 276ace1c21c3e411c77ee39c3795fe8bdffa2631..1d4f3c75a091f68f9611be358a109645c3db8081 100644 (file)
@@ -641,5 +641,145 @@ function mnet_get_app_jumppath ($applicationid) {
     }
     return $appjumppaths[$applicationid];
 }
+/**
+ * Remove any mnet assignments of a specific role to users from a specific mnet host
+ * @param int $roleid the id of the role which should not be assigned to any $mnethostid users
+ * @param int $mnethostid the id of the mnet peer which is to have users unassigned
+ * @return bool true on success
+ */
+function unassign_role_peer($roleid, $mnethostid) {
+    global $DB;
+    $mnetroleassignmentssql =
+            'SELECT ra.id as id, u.id as uid, ra.contextid as cid ' .
+            'FROM {role_assignments} ra ' .
+            ' INNER JOIN {user} u on ra.userid = u.id ' .
+            'WHERE u.mnethostid = ? ' .
+            ' AND ra.roleid = ? ' .
+            ' AND ra.enrol = \'enrol\'';
+    $params = array($mnethostid, $roleid);
+    $mnetroleassignments = $DB->get_records_sql($mnetroleassignmentssql, $params);
+    if (empty($mnetroleassignments)) {
+        // No role assignments for this mnet host and role,
+        // - nothing to unenrol
+        // - return success
+        return true;
+    }
+
+    $result = true;
+    foreach ($mnetroleassignments as $roleassignment) {
+        $subresult = role_unassign($roleid, $roleassignment->uid, 0, $roleassignment->cid);
+        $result &= $subresult;
+    }
+    return $result;
+}
+
+/**
+ * Get the default course role on an mnet peer
+ * @param int $mnethostid the id of the mnet peer which is to have users unassigned
+ * @return mixed record from remote peer's role table or false
+ */
+function mnet_get_default_role($mnethostid) {
+    global $CFG, $MNET;
+    require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
+
+    // get the Service Provider info
+    $mnet_sp = new mnet_peer();
+    $mnet_sp->set_id($mnethostid);
+
+    // set up the RPC request
+    $mnetrequest = new mnet_xmlrpc_client();
+    $mnetrequest->set_method('enrol/mnet/enrol.php/get_default_role');
+
+    // Thunderbirds are go! Do RPC call and store response
+    if ($mnetrequest->send($mnet_sp) === true) {
+        return (object)$mnetrequest->response;
+    }
+    return false;
+}
+
+/**
+ * Get the roles an mnet peer is publishing, and update any invalid role mappings
+ * @param int $mnethostid the id of the mnet peer which is to have users unassigned
+ * @return array
+ */
+function mnet_get_allocatable_roles($mnethostid) {
+    global $CFG, $MNET, $DB;
+    require_once $CFG->dirroot . '/mnet/xmlrpc/client.php';
+
+    $mnet_sp = new mnet_peer();
+    $mnet_sp->set_id($mnethostid);
+
+    $mnetrequest = new mnet_xmlrpc_client();
+    $mnetrequest->set_method('enrol/mnet/enrol.php/get_allocatable_roles');
+
+    if ($mnetrequest->send($mnet_sp) !== true || !is_array($mnetrequest->response)) {
+        return array();
+    }
+    $remoteroles = $mnetrequest->response;
+    foreach($remoteroles as $remoterole) {
+        $allocatableroles[$remoterole['id']] = (object)$remoterole;
+    }
+
+    $rolemappingssql = 'SELECT * FROM {mnet_role_mapping} rm WHERE rm.mnethost = ?';
+    $params = array($mnethostid);
+    $rolemappings = $DB->get_records_sql($rolemappingssql, $params);
+    if (!empty($rolemappings)) {
+        foreach ($rolemappings as $rolemapping) {
+            //If we have allocated a role which is no-longer available, we need to fix those enrolments
+            if (!isset($allocatableroles[$rolemapping->remoterole])) {
+                manage_role_mapping($mnethostid, $rolemapping->localrole);
+                $DB->delete_records('mnet_role_mapping', array('mnethost' => $mnethostid, 'localrole' => $rolemapping->localrole));
+            }
+        }
+    }
+    return $allocatableroles;
+}
 
+/**
+ * Deal with the fact that a role mapping needs to change either because:
+ *  - the remote role is unavailable
+ *  - the local admin has changed what this role maps to
+ * @param int $mnethostid the id of the mnet peer which is to have users unassigned
+ * @param int $localroleid the id of role which no-longer has a remote equivilent.
+ * @return int number of users who are now in role management queue to get new role mapping propagated
+ */
+function manage_role_mapping($mnethostid, $localroleid) {
+    global $DB;
+    $coursessql =
+            'SELECT c.id, cx.path '.
+            'FROM {course} c '.
+            ' INNER JOIN {context} cx on cx.instanceid = c.id and cx.contextlevel=50 ' .
+            'WHERE c.mnetpeer = ? ';
+    $params = array($mnethostid);
+    $courses = $DB->get_records_sql($coursessql, $params);
+    if (empty($courses)) {
+        // No courses have content provided by the specified mnet host - nothing to do.
+        return 0;
+    }
+    $queueitem = new stdclass;
+    $usersinqueue = 0;
+    foreach ($courses as $course) {
+        $queueitem->localcourse = $course->id;
+        $contextids = explode('/',substr($course->path, 1));
+        list($contextfragment, $params) = $DB->get_in_or_equal($contextids);
+        $userssql =
+                'SELECT u.id, u.id ' .
+                'FROM {role_assignments} ra ' .
+                ' INNER JOIN {user} u ON u.id = ra.userid ' .
+                'WHERE ra.contextid ' . $contextfragment .
+                ' AND ra.roleid = ? ';
+        $params[] = $localroleid;
+        $users = $DB->get_records_sql($userssql, $params);
+        if (empty($users)) {
+            // No users with the relevant role in this course - skip to the next course:
+            continue;
+        }
+        foreach ($users as $user) {
+            $queueitem->userid = $user->id;
+            mnet_enqueue_role_update($queueitem);
+            $usersinqueue++;
+        }
+    }
+    return $usersinqueue;
+}
 ?>
diff --git a/pix/i/content.gif b/pix/i/content.gif
new file mode 100644 (file)
index 0000000..def7f16
Binary files /dev/null and b/pix/i/content.gif differ
index 6a5176099e34375123be274107038bb3ed1b98a9..ba4b1592fcba4dd4902529b4b0be2eb5d52d4001 100644 (file)
@@ -6,7 +6,7 @@
 // This is compared against the values stored in the database to determine
 // whether upgrades should be performed (see lib/db/*.php)
 
-    $version = 2009073000;  // YYYYMMDD   = date of the last version bump
+    $version = 2009073101;  // YYYYMMDD   = date of the last version bump
                             //         XX = daily increments
 
     $release = '2.0 dev (Build: 20090803)';  // Human-friendly version name