]> git.mjollnir.org Git - moodle.git/commitdiff
user selection: MDL-16994 Improve the user selector used on the assign roles and...
authortjhunt <tjhunt>
Wed, 29 Oct 2008 08:18:24 +0000 (08:18 +0000)
committertjhunt <tjhunt>
Wed, 29 Oct 2008 08:18:24 +0000 (08:18 +0000)
group/clientlib.js
group/lib.php
group/members.php
lang/en_utf8/error.php
lang/en_utf8/group.php
theme/standard/styles_layout.css
user/selector/lib.php
user/selector/script.js
user/selector/test.php

index d1f77f9a0e20d2b9d85face3261b8eb6cc17a734..f074ec1afb1bd0f0147a1d6d12fa515a788272e8 100644 (file)
@@ -201,3 +201,17 @@ var removeLoaderImgs = function (elClass, parentId) {
         parentEl.removeChild(loader);
     }
 };
+
+function init_add_remove_members_page() {
+    var addselect = user_selector.get('addselect');
+    document.getElementById('add').disabled = addselect.is_selection_empty();
+    addselect.subscribe('selectionchanged', function(isempty) {
+        document.getElementById('add').disabled = isempty;
+    });
+
+    var removeselect = user_selector.get('removeselect');
+    document.getElementById('remove').disabled = removeselect.is_selection_empty();
+    removeselect.subscribe('selectionchanged', function(isempty) {
+        document.getElementById('remove').disabled = isempty;
+    });
+}
\ No newline at end of file
index 23e359820ef610641fc5eaaa1a76013bf73ceb48..a647dbac346e18d26719dd7e0fc8c9352a879685 100644 (file)
@@ -392,57 +392,6 @@ function groups_delete_groupings($courseid, $showfeedback=false) {
 /* various functions used by groups UI */
 /* =================================== */
 
-/**
- * Gets the users for a course who are not in a specified group, and returns
- * them in an array organised by role. For the array format, see 
- * groups_get_members_by_role.
- * @param int $groupid The id of the group
- * @param string searchtext similar to searchtext in role assign, search
- * @return array An array of role id or '*' => information about that role 
- *   including a list of users
- */
-function groups_get_users_not_in_group_by_role($courseid, $groupid, $searchtext='', $sort = 'u.lastname ASC') {
-    global $CFG, $DB;
-
-    $context = get_context_instance(CONTEXT_COURSE, $courseid);
-
-/// Get list of allowed roles     
-    if (!$validroleids = groups_get_possible_roles($context)) {
-        return array();
-    }
-    list($roleids, $params) = $DB->get_in_or_equal($validroleids, SQL_PARAMS_NAMED, $start='r0');
-    
-    if ($searchtext !== '') {   // Search for a subset of remaining users
-        $LIKE      = $DB->sql_ilike();
-        $FULLNAME  = $DB->sql_fullname();
-        $wheresearch = " AND u.id IN (SELECT id FROM {user} WHERE $FULLNAME $LIKE :search1 OR email $LIKE :search2)";
-        $params['search1'] = "%$searchtext%";
-        $params['search2'] = "%$searchtext%";
-    } else {
-        $wheresearch = '';
-    }
-
-/// Construct the main SQL
-    $sql = " SELECT r.id AS roleid,r.shortname AS roleshortname,r.name AS rolename,
-                    u.id AS userid, u.firstname, u.lastname
-               FROM {user} u
-               JOIN {role_assignments} ra ON ra.userid = u.id
-               JOIN {role} r ON r.id = ra.roleid
-              WHERE ra.contextid ".get_related_contexts_string($context)."
-                    AND u.deleted = 0
-                    AND ra.roleid $roleids
-                    AND u.id NOT IN (SELECT userid
-                                      FROM {groups_members}
-                                     WHERE groupid = :groupid)
-                    $wheresearch
-           ORDER BY $sort";
-    $params['groupid'] = $groupid;
-
-    $rs = $DB->get_recordset_sql($sql, $params);
-    return groups_calculate_role_people($rs, $context);
-}
-
-
 /**
  * Obtains a list of the possible roles that group members might come from,
  * on a course. Generally this includes all the roles who would have 
@@ -620,15 +569,22 @@ function groups_unassign_grouping($groupingid, $groupid) {
  * @param int $courseid Course ID (should match the group's course)
  * @param string $fields List of fields from user table prefixed with u, default 'u.*'
  * @param string $sort SQL ORDER BY clause, default 'u.lastname ASC'
+ * @param string $extrawheretest extra SQL conditions ANDed with the existing where clause.
+ * @param array $whereparams any parameters required by $extrawheretest.
  * @return array Complex array as described above
  */
-function groups_get_members_by_role($groupid, $courseid, $fields='u.*', $sort='u.lastname ASC') {
+function groups_get_members_by_role($groupid, $courseid, $fields='u.*',
+        $sort='u.lastname ASC', $extrawheretest='', $whereparams=array()) {
     global $CFG, $DB;
 
     // Retrieve information about all users and their roles on the course or
     // parent ('related') contexts 
     $context = get_context_instance(CONTEXT_COURSE, $courseid);
 
+    if ($extrawheretest) {
+        $extrawheretest = ' AND ' . $extrawheretest;
+    }
+
     $sql = "SELECT r.id AS roleid, r.shortname AS roleshortname, r.name AS rolename,
                    u.id AS userid, $fields
               FROM {groups_members} gm
@@ -636,10 +592,11 @@ function groups_get_members_by_role($groupid, $courseid, $fields='u.*', $sort='u
               JOIN {role_assignments} ra ON ra.userid = u.id 
               JOIN {role} r ON r.id = ra.roleid
              WHERE gm.groupid=?
-                   AND ra.contextid ".get_related_contexts_string($context)."
+                   AND ra.contextid ".get_related_contexts_string($context).
+                   $extrawheretest."
           ORDER BY r.sortorder, $sort";
-    $params = array($groupid);
-    $rs = $DB->get_recordset_sql($sql, $params);
+    array_unshift($whereparams, $groupid);
+    $rs = $DB->get_recordset_sql($sql, $whereparams);
 
     return groups_calculate_role_people($rs, $context);
 }
index c704129f8b3777fc68e4a26bff4cca0bb32fd8ab..16924f53980a3bad858c62c271065f8bc1c00729 100644 (file)
@@ -2,24 +2,18 @@
 /**
  * Add/remove members from group.
  *
- * @copyright &copy; 2006 The Open University
+ * @copyright &copy; 2006 The Open University and others
  * @author N.D.Freear AT open.ac.uk
- * @author J.White AT open.ac.uk
+ * @author J.White AT open.ac.uk and others
  * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
  * @package groups
  */
-require_once('../config.php');
-require_once('lib.php');
-
-define("MAX_USERS_PER_PAGE", 5000);
+require_once(dirname(__FILE__) . '/../config.php');
+require_once(dirname(__FILE__) . '/lib.php');
+require_once($CFG->dirroot . '/user/selector/lib.php');
+require_js('group/clientlib.js');
 
 $groupid    = required_param('group', PARAM_INT);
-$searchtext = optional_param('searchtext', '', PARAM_RAW); // search string
-$showall    = optional_param('showall', 0, PARAM_BOOL);
-
-if ($showall) {
-    $searchtext = '';
-}
 
 if (!$group = $DB->get_record('groups', array('id'=>$groupid))) {
     print_error('invalidgroupid');
@@ -34,103 +28,42 @@ require_login($course);
 $context = get_context_instance(CONTEXT_COURSE, $courseid);
 require_capability('moodle/course:managegroups', $context);
 
-$strsearch = get_string('search');
-$strshowall = get_string('showall');
 $returnurl = $CFG->wwwroot.'/group/index.php?id='.$courseid.'&group='.$group->id;
 
+if (optional_param('cancel', false, PARAM_BOOL)) {
+    redirect($returnurl);
+}
 
-if ($frm = data_submitted() and confirm_sesskey()) {
-
-    if (isset($frm->cancel)) {
-        redirect($returnurl);
-
-    } else if (isset($frm->add) and !empty($frm->addselect)) {
-
-        foreach ($frm->addselect as $userid) {
-            if (! $userid = clean_param($userid, PARAM_INT)) {
-                continue;
-            }
-            if (!groups_add_member($groupid, $userid)) {
+$groupmembersselector = new group_members_selector('removeselect',
+        array('groupid' => $groupid, 'courseid' => $course->id));
+$groupmembersselector->set_extra_fields(array());
+$potentialmembersselector = new group_non_members_selector('addselect',
+        array('groupid' => $groupid, 'courseid' => $course->id));
+$potentialmembersselector->set_extra_fields(array());
+        
+if (optional_param('add', false, PARAM_BOOL) && confirm_sesskey()) {
+    $userstoadd = $potentialmembersselector->get_selected_users();
+    if (!empty($userstoadd)) {
+        foreach ($userstoadd as $user) {
+            if (!groups_add_member($groupid, $user->id)) {
                 print_error('erroraddremoveuser', 'group', $returnurl);
             }
+            $groupmembersselector->invalidate_selected_users();
+            $potentialmembersselector->invalidate_selected_users();
         }
-
-    } else if (isset($frm->remove) and !empty($frm->removeselect)) {
-
-        foreach ($frm->removeselect as $userid) {
-            if (! $userid = clean_param($userid, PARAM_INT)) {
-                continue;
-            }
-            if (!groups_remove_member($groupid, $userid)) {
-                print_error('erroraddremoveuser', 'group', $returnurl);
-            }
-        }
-    }
-}
-
-$groupmembersoptions = '';
-$groupmemberscount = 0;
-
-// Get members, organised by role, and display
-if ($groupmemberroles = groups_get_members_by_role($groupid,$courseid,'u.id,u.firstname,u.lastname')) {
-    foreach($groupmemberroles as $roleid=>$roledata) {
-        $groupmembersoptions .= '<optgroup label="'.s($roledata->name).'">';
-        foreach($roledata->users as $member) {
-            $groupmembersoptions .= '<option value="'.$member->id.'">'.fullname($member, true).'</option>';
-            $groupmemberscount ++;
-        }
-        $groupmembersoptions .= '</optgroup>';
     }
-} else {
-    $groupmembersoptions .= '<option>&nbsp;</option>';
 }
 
-$potentialmembers = array();
-$potentialmembersoptions = '';
-$potentialmemberscount = 0;
-
-// Get potential members, organised by role, and count them
-$potentialmembersbyrole = groups_get_users_not_in_group_by_role($courseid, $groupid, $searchtext);
-$potentialmemberscount=0;
-$potentialmembersids=array();
-if (!empty($potentialmembersbyrole)) {
-    foreach($potentialmembersbyrole as $roledata) {
-        $potentialmemberscount += count($roledata->users);
-        $potentialmembersids = array_merge($potentialmembersids, array_keys($roledata->users));
-    }
-}
-
-$usergroups = array();
-
-if ($potentialmemberscount <=  MAX_USERS_PER_PAGE) {
-    if ($potentialmemberscount > 0) {
-        // Get other groups user already belongs to
-        list($potentialmembersids, $params) = $DB->get_in_or_equal($potentialmembersids, SQL_PARAMS_NAMED, 'pm0');
-        $sql = "SELECT u.id AS userid, g.*
-                  FROM {user} u
-                  JOIN {groups_members} gm ON u.id = gm.userid
-                  JOIN {groups} g ON gm.groupid = g.id
-                 WHERE u.id $potentialmembersids AND g.courseid = :courseid ";
-        $params['courseid'] = $course->id;
-        if ($rs = $DB->get_recordset_sql($sql, $params)) {
-            foreach ($rs as $usergroup) {
-                $usergroups[$usergroup->userid][$usergroup->id] = $usergroup;
-            }
-            $rs->close();
-        }
-
-        foreach ($potentialmembersbyrole as $roleid=>$roledata) {
-            $potentialmembersoptions .= '<optgroup label="'.s($roledata->name).'">';
-            foreach($roledata->users as $member) {
-                $name = s(fullname($member, true));
-                $potentialmembersoptions .= '<option value="'.$member->id.
-                    '" title="'.$name.'">'.$name.
-                    ' ('.@count($usergroups[$member->id]).')</option>';
+if (optional_param('remove', false, PARAM_BOOL) && confirm_sesskey()) {
+    $userstoremove = $groupmembersselector->get_selected_users();
+    if (!empty($userstoremove)) {
+        foreach ($userstoremove as $user) {
+            if (!groups_remove_member($groupid, $user->id)) {
+                print_error('erroraddremoveuser', 'group', $returnurl);
             }
-            $potentialmembersoptions .= '</optgroup>';
+            $groupmembersselector->invalidate_selected_users();
+            $potentialmembersselector->invalidate_selected_users();
         }
-    } else {
-        $potentialmembersoptions .= '<option>&nbsp;</option>';
     }
 }
 
@@ -149,62 +82,8 @@ $navlinks[] = array('name' => $stradduserstogroup, 'link' => null, 'type' => 'mi
 $navigation = build_navigation($navlinks);
 
 print_header("$course->shortname: $strgroups", $course->fullname, $navigation, '', '', true, '', user_login_string($course, $USER));
-
-// Print Javascript for showing the selected users group membership
-?>
-<script type="text/javascript">
-//<![CDATA[
-var userSummaries = Array(
-<?php
-$membercnt = count($potentialmembers);
-$i=1;
-foreach ($potentialmembers as $userid => $potentalmember) {
-
-    if (isset($usergroups[$userid])) {
-        $usergrouplist = '<ul>';
-
-        foreach ($usergroups[$userid] as $groupitem) {
-            $usergrouplist .= '<li>'.addslashes_js(format_string($groupitem->name)).'</li>';
-        }
-        $usergrouplist .= '</ul>';
-    }
-    else {
-       $usergrouplist = '';
-    }
-    echo "'$usergrouplist'";
-    if ($i < $membercnt) {
-       echo ', ';
-    }
-    $i++;
-}
+check_theme_arrows();
 ?>
-);
-
-function updateUserSummary() {
-
-    var selectEl = document.getElementById('addselect');
-    var summaryDiv = document.getElementById('group-usersummary');
-    var length = selectEl.length;
-    var selectCnt = 0;
-    var selectIdx = -1;
-
-    for(i=0;i<length;i++) {
-        if (selectEl.options[i].selected) {
-               selectCnt++;
-            selectIdx = i;
-        }
-    }
-
-    if (selectCnt == 1 && userSummaries[selectIdx]) {
-        summaryDiv.innerHTML = userSummaries[selectIdx];
-    } else {
-        summaryDiv.innerHTML = '';
-    }
-
-    return(true);
-}
-//]]>
-</script>
 
 <div id="addmembersform">
     <h3 class="main"><?php print_string('adduserstogroup', 'group'); echo ": $groupname"; ?></h3>
@@ -214,71 +93,28 @@ function updateUserSummary() {
     <input type="hidden" name="sesskey" value="<?php p(sesskey()); ?>" />
     <input type="hidden" name="group" value="<?php echo $groupid; ?>" />
 
-    <table cellpadding="6" class="generaltable generalbox groupmanagementtable boxaligncenter" summary="">
+    <table class="generaltable generalbox groupmanagementtable boxaligncenter" summary="">
     <tr>
-      <td valign="top">
+      <td id='memberscell'>
           <p>
-            <label for="removeselect"><?php print_string('existingmembers', 'group', $groupmemberscount); ?></label>
+            <label for="removeselect"><?php print_string('groupmembers', 'group'); ?></label>
           </p>
-          <select name="removeselect[]" size="20" id="removeselect" multiple="multiple"
-                  onfocus="document.getElementById('assignform').add.disabled=true;
-                           document.getElementById('assignform').remove.disabled=false;
-                           document.getElementById('assignform').addselect.selectedIndex=-1;"
-                  onclick="this.focus();updateUserSummary();">
-          <?php echo $groupmembersoptions ?>
-          </select></td>
-      <td valign="top">
-<?php // Hidden assignment? ?>
-
-        <?php check_theme_arrows(); ?>
+          <?php $groupmembersselector->display(); ?>
+          </td>
+      <td id='buttonscell'>
         <p class="arrow_button">
             <input name="add" id="add" type="submit" value="<?php echo $THEME->larrow.'&nbsp;'.get_string('add'); ?>" title="<?php print_string('add'); ?>" /><br />
             <input name="remove" id="remove" type="submit" value="<?php echo get_string('remove').'&nbsp;'.$THEME->rarrow; ?>" title="<?php print_string('remove'); ?>" />
         </p>
       </td>
-      <td valign="top">
+      <td id='nonmemberscell'>
           <p>
-            <label for="addselect"><?php print_string('potentialmembers', 'group', $potentialmemberscount); ?></label>
+            <label for="addselect"><?php print_string('potentialmembs', 'group'); ?></label>
           </p>
-          <select name="addselect[]" size="20" id="addselect" multiple="multiple"
-                  onfocus="updateUserSummary();document.getElementById('assignform').add.disabled=false;
-                           document.getElementById('assignform').remove.disabled=true;
-                           document.getElementById('assignform').removeselect.selectedIndex=-1;"
-                  onclick="this.focus();updateUserSummary();">
-          <?php
-            if ($potentialmemberscount > MAX_USERS_PER_PAGE) {
-                echo '<optgroup label="'.get_string('toomanytoshow').'"><option></option></optgroup>'."\n"
-                        .'<optgroup label="'.get_string('trysearching').'"><option></option></optgroup>'."\n";
-            } else {
-                echo $potentialmembersoptions;
-            }
-          ?>
-         </select>
-         <br />
-         <label for="searchtext" class="accesshide"><?php p($strsearch) ?></label>
-         <input type="text" name="searchtext" id="searchtext" size="21" value="<?php p($searchtext) ?>"
-                  onfocus ="getElementById('assignform').add.disabled=true;
-                            getElementById('assignform').remove.disabled=true;
-                            getElementById('assignform').removeselect.selectedIndex=-1;
-                            getElementById('assignform').addselect.selectedIndex=-1;"
-                  onkeydown = "var keyCode = event.which ? event.which : event.keyCode;
-                               if (keyCode == 13) {
-                                    getElementById('assignform').previoussearch.value=1;
-                                    getElementById('assignform').submit();
-                               } " />
-         <input name="search" id="search" type="submit" value="<?php p($strsearch) ?>" />
-         <?php
-              if (!empty($searchtext)) {
-                  echo '<br /><input name="showall" id="showall" type="submit" value="'.s($strshowall).'" />'."\n";
-              }
-         ?>
-       </td>
-       <td valign="top">
-        <p><?php echo($strusergroupmembership) ?></p>
-        <div id="group-usersummary"></div>
-       </td>
+          <?php $potentialmembersselector->display(); ?>
+      </td>
     </tr>
-    <tr><td>
+    <tr><td colspan="3" id='backcell'>
         <input type="submit" name="cancel" value="<?php print_string('backtogroups', 'group'); ?>" />
     </td></tr>
     </table>
@@ -287,5 +123,6 @@ function updateUserSummary() {
 </div>
 
 <?php
+    print_js_call('init_add_remove_members_page');
     print_footer($course);
 ?>
index c724a69b4ce30472b4afe5b6319061c897041952..dbaf14e9335b472baadad4639a33360ab4aa6cf9 100644 (file)
@@ -269,7 +269,7 @@ $string['invalidnumkey'] = '\$conditions array may not contain numeric keys, ple
 $string['invalidoutcome'] = 'Incorrect outcome id';
 $string['invalidpagesize'] = 'Invalid page size';
 $string['invalidpaymentmethod'] = 'Invalid payment method: $a';
-$stirng['invalidqueryparam'] = 'ERROR: Incorrect number of query parameters!!';
+$string['invalidqueryparam'] = 'ERROR: Incorrect number of query parameters. Expected $a->expected, got $a->actual.';
 $string['invalidrequest'] = 'Invalid request';
 $string['invalidrole'] = 'Invalid role';
 $string['invalidroleid'] = 'Invalid role ID';
index c1b6658ec158609fe3484514c809087189ae3be5..9ee7678bfe1f18a009fcb36dee1ce4443c086d5b 100644 (file)
@@ -72,6 +72,7 @@ $string['newgrouping'] = 'New grouping';
 $string['backtogroups'] = 'Back to groups';
 $string['backtogroupings'] = 'Back to groupings';
 $string['existingmembers'] = 'Existing members: $a'; 
+$string['potentialmembs'] = 'Potential members';
 $string['potentialmembers'] = 'Potential members: $a';
 $string['groupinfo'] = 'Info about selected group';
 $string['groupinfomembers'] = 'Info about selected members';
index f27ae4d5a0fdd5e808f5b37a7f80f57be9c1893f..16d7304ea2846f56db63b7ef68a39d81a5f87149 100644 (file)
@@ -433,16 +433,45 @@ form.popupform label {
   overflow:hidden;
 }
 
+.groupmanagementtable {
+  width: 90%;
+}
+
 .groupmanagementtable td {
-  vertical-align: top;
+  vertical-align: middle;
 }
 
 .groupmanagementtable p {
   text-align: center;
 }
 
-.groupmanagementtable select {
-  width: 18em;
+.groupmanagementtable #memberscell,
+.groupmanagementtable #nonmemberscell {
+  width: 42%;
+}
+.groupmanagementtable #memberscell label,
+.groupmanagementtable #nonmemberscell label {
+  font-weight: bold;
+}
+.groupmanagementtable #buttonscell {
+  width: 16%;
+}
+.groupmanagementtable #buttonscell input {
+    width: 80%;
+    padding: 1em 0;
+    margin: 2em 0;
+}
+.groupmanagementtable #backcell {
+    padding-top: 2em;
+    text-align: center;
+}
+#removeselect_wrapper,
+#addselect_wrapper {
+    width: 100%;
+}
+.groupmanagementtable #removeselect_wrapper label,
+.groupmanagementtable #addselect_wrapper label {
+  font-weight: normal;
 }
 
 #group-groupings .buttons {
@@ -517,6 +546,13 @@ div.hide {
   display:none;
 }
 
+.userselector select {
+  width: 100%;
+}
+.userselector div label {
+  margin-right: 0.3em;
+}
+
 /***
  *** Forms
  ***/
index 9160e636f2dbe20a1d84b6e1edb0f8c43a7fbbb0..04b1b3d87c960d6fe8fa3e3569ed4f672fe6f531 100644 (file)
@@ -37,6 +37,10 @@ define('USER_SELECTOR_DEFAULT_ROWS', 20);
 
 /**
  * Base class for user selectors.
+ *
+ * In your theme, you must give each user-selector a defined width. If the
+ * user selector has name="myid", then the div myid_wrapper must have a width
+ * specified.
  */
 abstract class user_selector_base {
     /** The control name (and id) in the HTML. */
@@ -115,14 +119,22 @@ abstract class user_selector_base {
         return $this->selected;
     }
 
+    /**
+     * If you update the database in such a way that it is likely to change the
+     * list of users that this component is allowed to select from, then you
+     * must call this method. For example, on the role assign page, after you have
+     * assigned some roles to some users, you should call this.
+     */
+    public function invalidate_selected_users() {
+        $this->selected = null;
+    }
+
     /**
      * Output this user_selector as HTML.
      * @param boolean $return if true, return the HTML as a string instead of outputting it.
      * @return mixed if $return is true, returns the HTML as a string, otherwise returns nothing.
      */
     public function display($return = false) {
-        global $USER, $CFG;
-
         // Get the list of requested users.
         $search = optional_param($this->name . '_searchtext', '', PARAM_RAW);
         $groupedusers = $this->find_users($search);
@@ -199,6 +211,16 @@ abstract class user_selector_base {
         return $this->name;
     }
 
+    /**
+     * Set the user fields that are displayed in the selector in addition to the
+     * user's name.
+     *
+     * @param array $fields a list of field names that exist in the user table.
+     */
+    public function set_extra_fields($fields) {
+        $this->extrafields = $fields;
+    }
+
     // API for sublasses =======================================================
 
     /**
@@ -351,7 +373,7 @@ abstract class user_selector_base {
         // If $groupedusers is empty, make a 'no matching users' group. If there
         // is only one selected user, set a flag to select them.
         $select = false;
-        if (empty($groupedusers) && empty($this->selected)) {
+        if (empty($groupedusers)) {
             $groupedusers = array(get_string('nomatchingusers') => array());
         } else if (count($groupedusers) == 1 && count(reset($groupedusers)) == 1) {
             $select = true;
@@ -387,7 +409,7 @@ abstract class user_selector_base {
      */
     protected function output_optgroup($groupname, $users, $select) {
         if (!empty($users)) {
-            $output = '<optgroup label="' . s($groupname) . ' (' . count($users) . ')">' . "\n";
+            $output = '  <optgroup label="' . s($groupname) . ' (' . count($users) . ')">' . "\n";
             foreach ($users as $user) {
                 if ($select || isset($this->selected[$user->id])) {
                     $selectattr = ' selected="selected"';
@@ -395,14 +417,14 @@ abstract class user_selector_base {
                     $selectattr = '';
                 }
                 unset($this->selected[$user->id]);
-                $output .= '<option' . $selectattr . ' value="' . $user->id . '">' .
+                $output .= '    <option' . $selectattr . ' value="' . $user->id . '">' .
                         $this->output_user($user) . "</option>\n";
             }
         } else {
-            $output = '<optgroup label="' . s($groupname) . '">' . "\n";
-            $output .= '<option disabled="disabled">&nbsp;</option>' . "\n";
+            $output = '  <optgroup label="' . s($groupname) . '">' . "\n";
+            $output .= '    <option disabled="disabled">&nbsp;</option>' . "\n";
         }
-        $output .= "</optgroup>\n";
+        $output .= "  </optgroup>\n";
         return $output;
     }
 
@@ -449,7 +471,8 @@ abstract class user_selector_base {
 
         // Initialise the selector.
         $output .= print_js_call('new user_selector', array($this->name, $hash,
-                sesskey(), $this->extrafields, get_string('previouslyselectedusers')), true);
+                sesskey(), $this->extrafields, get_string('previouslyselectedusers'),
+                get_string('nomatchingusers')), true);
         return $output;
     }
 }
@@ -474,9 +497,119 @@ class role_assign_current_user_selector extends user_selector_base {
     }
 }
 
-class group_members_user_selector extends user_selector_base {
+abstract class groups_user_selector_base extends user_selector_base {
+    protected $groupid;
+    protected $courseid;
+
+    /**
+     * @param string $name control name
+     * @param array $options should have two elements with keys groupid and courseid.
+     */
+    public function __construct($name, $options) {
+        global $CFG;
+        parent::__construct($name, $options);
+        $this->groupid = $options['groupid'];
+        $this->courseid = $options['courseid'];
+        require_once($CFG->dirroot . '/group/lib.php');
+    }
+
+    protected function get_options() {
+        $options = parent::get_options();
+        $options['groupid'] = $this->groupid;
+        $options['courseid'] = $this->courseid;
+        return $options;
+    }
+
+    /**
+     * Enter description here...
+     *
+     * @param array $roles array in the format returned by groups_calculate_role_people.
+     * @return array array in the format find_users is supposed to return.
+     */
+    protected function convert_array_format($roles) {
+        if (empty($roles)) {
+            $roles = array();
+        }
+        $groupedusers = array();
+        foreach ($roles as $role) {
+            $groupedusers[$role->name] = $role->users;
+            foreach ($groupedusers[$role->name] as &$user) {
+                unset($user->roles);
+                $user->fullname = fullname($user);
+            }
+        }
+        return $groupedusers;
+    }
+}
+
+/**
+ * User selector subclass for the list of users who are in a certain group.
+ * Used on the add group memebers page.
+ */
+class group_members_selector extends groups_user_selector_base {
     public function find_users($search) {
-        return array(); // TODO
+        list($wherecondition, $params) = $this->search_sql($search, 'u');
+        $roles = groups_get_members_by_role($this->groupid, $this->courseid,
+                $this->required_fields_sql('u'), 'u.lastname, u.firstname',
+                $wherecondition, $params);
+        return $this->convert_array_format($roles);
+    }
+}
+
+/**
+ * User selector subclass for the list of users who are not in a certain group.
+ * Used on the add group memebers page.
+ */
+class group_non_members_selector extends groups_user_selector_base {
+    const MAX_USERS_PER_PAGE = 100;
+
+    protected function output_user($user) {
+        return parent::output_user($user) . ' (' . $user->numgroups . ')';
+    }
+
+    public function find_users($search) {
+        global $DB;
+
+        // Get list of allowed roles.
+        $context = get_context_instance(CONTEXT_COURSE, $this->courseid);
+        if (!$validroleids = groups_get_possible_roles($context)) {
+            return array();
+        }
+        list($roleids, $roleparams) = $DB->get_in_or_equal($validroleids);
+
+        // Get the search condition.
+        list($searchcondition, $searchparams) = $this->search_sql($search, 'u');
+
+        // Build the SQL
+        $fields = "SELECT r.id AS roleid, r.shortname AS roleshortname, r.name AS rolename, u.id AS userid, " . 
+                $this->required_fields_sql('u') .
+                ', (SELECT count(igm.groupid) FROM {groups_members} igm JOIN {groups} ig ON
+                    igm.groupid = ig.id WHERE igm.userid = u.id AND ig.courseid = ?) AS numgroups ';
+        $sql = "   FROM {user} u
+                   JOIN {role_assignments} ra ON ra.userid = u.id
+                   JOIN {role} r ON r.id = ra.roleid
+                  WHERE ra.contextid " . get_related_contexts_string($context) . "
+                        AND u.deleted = 0
+                        AND ra.roleid $roleids
+                        AND u.id NOT IN (SELECT userid
+                                          FROM {groups_members}
+                                         WHERE groupid = ?)
+                        AND $searchcondition";
+        $orderby = " ORDER BY u.lastname, u.firstname";
+
+        $params = array_merge($roleparams, array($this->groupid), $searchparams);
+        $potentialmemberscount = $DB->count_records_sql('SELECT count(DISTINCT u.id) ' . $sql, $params);
+
+        if ($potentialmemberscount > group_non_members_selector::MAX_USERS_PER_PAGE) {
+            return array(get_string('toomanytoshow') => array(),
+                    get_string('trysearching') => array());
+        }
+
+        array_unshift($params, $this->courseid);
+        $rs = $DB->get_recordset_sql($fields . $sql . $orderby, $params);
+        $roles =  groups_calculate_role_people($rs, $context);
+
+        return $this->convert_array_format($roles);
     }
 }
 ?>
\ No newline at end of file
index be8822f7566482fc0535ad13542d9ef79c9ea33d..b551c6d874f4a7c299ebb69e2ebc0446a100bdf9 100644 (file)
  * @param Array extrafields extra fields we are displaying for each user in addition to fullname.
  * @param String label used for the optgroup of users who are selected but who do not match the current search.
  */
-function user_selector(name, hash, sesskey, extrafields, strprevselected) {
+function user_selector(name, hash, sesskey, extrafields, strprevselected, strnomatchingusers) {
     this.name = name;
     this.extrafields = extrafields;
     this.strprevselected = strprevselected;
+    this.strnomatchingusers = strnomatchingusers;
+    this.searchurl = moodle_cfg.wwwroot + '/user/selector/search.php?selectorid=' +
+            hash + '&sesskey=' + sesskey + '&search='
 
     // Set up the data source.
-    this.datasource = new YAHOO.util.XHRDataSource(moodle_cfg.wwwroot +
-            '/user/selector/search.php?selectorid=' + hash + '&sesskey=' + sesskey + '&search='); 
+    this.datasource = new YAHOO.util.XHRDataSource(this.searchurl); 
     this.datasource.connXhrMode = 'cancelStaleRequests';
     this.datasource.responseType = YAHOO.util.XHRDataSource.TYPE_JSON;
     this.datasource.responseSchema = {resultsList: 'results'};
@@ -92,6 +94,14 @@ user_selector.prototype.extrafields = [];
  */
 user_selector.prototype.strprevselected = '';
 
+/**
+ * Name of the no matching users group.
+ *
+ * @property strnomatchingusers
+ * @type String
+ */
+user_selector.prototype.strnomatchingusers = '';
+
 // Fields that configure the control's behaviour ===============================
 
 /**
@@ -107,6 +117,13 @@ user_selector.prototype.querydelay = 0.2;
 
 // Internal fields =============================================================
 
+/**
+ * The URL for the datasource.
+ * @property searchurl
+ * @type String
+ */
+user_selector.prototype.searchurl = null;
+
 /**
  * The datasource used to fetch lists of users from Moodle.
  * @property datasource
@@ -199,6 +216,7 @@ user_selector.prototype.get_search_text = function() {
  */
 user_selector.prototype.send_query = function() {
     var value = this.get_search_text();
+    this.searchfield.className = '';
     if (this.lastsearch == value) {
         return;
     }
@@ -226,6 +244,15 @@ user_selector.prototype.handle_response = function(request, data) {
  */
 user_selector.prototype.handle_failure = function() {
     this.listbox.style.background = '';
+    this.searchfield.className = 'error';
+
+    // If we are in developer debug mode, output a link to help debug the failure.
+    if (moodle_cfg.developerdebug) {
+        var link = document.createElement('a');
+        link.href = this.searchurl + this.get_search_text();
+        link.appendChild(document.createTextNode('Ajax call failed. Click here to try the search call directly.'))
+        this.searchfield.parentNode.appendChild(link);
+    }
 }
 
 /**
@@ -236,10 +263,10 @@ user_selector.prototype.is_selection_empty = function() {
     for (i = 0; i < options.length; i++) {
         var option = options[i];
         if (option.selected) {
-            return true;
+            return false;
         }
     }
-    return false;
+    return true;
 }
 
 /**
@@ -283,8 +310,14 @@ user_selector.prototype.output_options = function(data) {
 
     // Output each optgroup.
     this.onlyoption = null;
+    var nogroups = true;
     for (groupname in results) {
         this.output_group(groupname, results[groupname], false);
+        nogroups = false;
+    }
+
+    if (nogroups) {
+        this.output_group(this.strnomatchingusers, {}, false)
     }
 
     // If there was only one option matching the search results, select it.
@@ -343,7 +376,7 @@ user_selector.prototype.output_group = function(groupname, users, select) {
         var option = document.createElement('option');
         option.disabled = 'disabled';
         option.appendChild(document.createTextNode('\u00A0'));
-        optgroup.appendchild(option);
+        optgroup.appendChild(option);
     }
     this.listbox.appendChild(optgroup);
 }
index c1e334136ba25be78138d870e13a4cccf78d152f..dfbc5fd7c7fd8ce90d81112b80e4242c6abe7cd3 100644 (file)
@@ -34,7 +34,6 @@ class test_user_selector extends user_selector_base {
         $options['file'] = 'user/selector/test.php';
         return $options;
     }
-    
 }
 
 if ($justdefineclass) {
@@ -46,7 +45,7 @@ require_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM));
 print_header();
 
 $userselector = new test_user_selector('myuserselector');
-$userselector->set_multiselect(false);
+//$userselector->set_multiselect(false);
 
 $users = $userselector->get_selected_users();
 if (!empty($users)) {
@@ -58,14 +57,14 @@ if (!empty($users)) {
     echo '</ul>';
 }
 
-echo '<form action="test.php"><div><label for="myuserselector">Select users</label>';
+echo '<form action="test.php"><div style="width: 30em;"><label for="myuserselector">Select users</label>';
 $userselector->display();
 echo '<p><input type="submit" id="submitbutton" value="Submit" /></p>';
 echo '</div></form>';
 
 echo '<script type="text/javascript">
 function selection_change(isempty) {
-    document.getElementById("submitbutton").disabled = !isempty;
+    document.getElementById("submitbutton").disabled = isempty;
 }
 user_selector.get("myuserselector").subscribe("selectionchanged", selection_change);
 </script>';