9274b2f2a70d5d038454e846ad8d107d68cd7171
[moodle.git] / group / lib.php
1 <?php
2 /**
3  * Extra library for groups and groupings.
4  *
5  * @copyright &copy; 2006 The Open University
6  * @author J.White AT open.ac.uk, Petr Skoda (skodak)
7  * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
8  * @package groups
9  */
11 /*
12  * INTERNAL FUNCTIONS - to be used by moodle core only
13  * require_once $CFG->dirroot.'/group/lib.php' must be used
14  */
16 /**
17  * Adds a specified user to a group
18  * @param mixed $groupid  The group id or group object
19  * @param mixed $userid   The user id or user object
20  * @return boolean True if user added successfully or the user is already a
21  * member of the group, false otherwise.
22  */
23 function groups_add_member($grouporid, $userorid) {
24     global $DB;
26     if (is_object($userorid)) {
27         $userid = $userorid->id;
28         $user   = $userorid;
29     } else {
30         $userid = $userorid;
31         $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
32     }
34     if (is_object($grouporid)) {
35         $groupid = $grouporid->id;
36         $group   = $grouporid;
37     } else {
38         $groupid = $grouporid;
39         $group = $DB->get_record('groups', array('id'=>$groupid), '*', MUST_EXIST);
40     }
42     //check if the user a participant of the group course
43     if (!is_enrolled(get_context_instance(CONTEXT_COURSE, $group->courseid), $userid)) {
44         return false;
45     }
47     if (groups_is_member($groupid, $userid)) {
48         return true;
49     }
51     $member = new object();
52     $member->groupid   = $groupid;
53     $member->userid    = $userid;
54     $member->timeadded = time();
56     $DB->insert_record('groups_members', $member);
58     //update group info
59     $DB->set_field('groups', 'timemodified', $member->timeadded, array('id'=>$groupid));
61     //trigger groups events
62     $eventdata = new object();
63     $eventdata->groupid = $groupid;
64     $eventdata->userid  = $userid;
65     events_trigger('groups_member_added', $eventdata);
67     return true;
68 }
70 /**
71  * Deletes the link between the specified user and group.
72  * @param mixed $groupid  The group id or group object
73  * @param mixed $userid   The user id or user object
74  * @return boolean True if deletion was successful, false otherwise
75  */
76 function groups_remove_member($grouporid, $userorid) {
77     global $DB;
79     if (is_object($userorid)) {
80         $userid = $userorid->id;
81         $user   = $userorid;
82     } else {
83         $userid = $userorid;
84         $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
85     }
87     if (is_object($grouporid)) {
88         $groupid = $grouporid->id;
89         $group   = $grouporid;
90     } else {
91         $groupid = $grouporid;
92         $group = $DB->get_record('groups', array('id'=>$groupid), '*', MUST_EXIST);
93     }
95     if (!groups_is_member($groupid, $userid)) {
96         return true;
97     }
99     $DB->delete_records('groups_members', array('groupid'=>$groupid, 'userid'=>$userid));
101     //update group info
102     $DB->set_field('groups', 'timemodified', time(), array('id'=>$groupid));
104     //trigger groups events
105     $eventdata = new object();
106     $eventdata->groupid = $groupid;
107     $eventdata->userid  = $userid;
108     events_trigger('groups_member_removed', $eventdata);
110     return true;
113 /**
114  * Add a new group
115  * @param object $data group properties (with magic quotes)
116  * @param object $um upload manager with group picture
117  * @return id of group or false if error
118  */
119 function groups_create_group($data, $editform=false, $editoroptions=null) {
120     global $CFG, $DB;
121     require_once("$CFG->libdir/gdlib.php");
123     //check that courseid exists
124     $course = $DB->get_record('course', array('id' => $data->courseid), '*', MUST_EXIST);
126     $data->timecreated  = time();
127     $data->timemodified = $data->timecreated;
128     $data->name         = trim($data->name);
130     if ($editform) {
131         $data->description = $data->description_editor['text'];
132         $data->descriptionformat = $data->description_editor['format'];
133     }
135     $id = $DB->insert_record('groups', $data);
137     $data->id = $id;
138     if ($editform) {
139         //update image
140         if (save_profile_image($id, $editform, 'groups')) {
141             $DB->set_field('groups', 'picture', 1, array('id'=>$id));
142         }
143         $data->picture = 1;
145         if (method_exists($editform, 'get_editor_options')) {
146             // Update description from editor with fixed files
147             $editoroptions = $editform->get_editor_options();
148             $description = new stdClass;
149             $description->id = $data->id;
150             $description->description_editor = $data->description_editor;
151             $description = file_postupdate_standard_editor($description, 'description', $editoroptions, $editoroptions['context'], 'course_group_description', $description->id);
152             $DB->update_record('groups', $description);
153         }
154     }
156     //trigger groups events
157     events_trigger('groups_group_created', $data);
159     return $id;
162 /**
163  * Add a new grouping
164  * @param object $data grouping properties (with magic quotes)
165  * @return id of grouping or false if error
166  */
167 function groups_create_grouping($data, $editoroptions=null) {
168     global $DB;
170     $data->timecreated  = time();
171     $data->timemodified = $data->timecreated;
172     $data->name         = trim($data->name);
174     if ($editoroptions !== null) {
175         $data->description = $data->description_editor['text'];
176         $data->descriptionformat = $data->description_editor['format'];
177     }
179     $id = $DB->insert_record('groupings', $data);
181     //trigger groups events
182     $data->id = $id;
184     if ($editoroptions !== null) {
185         $description = new stdClass;
186         $description->id = $data->id;
187         $description->description_editor = $data->description_editor;
188         $description = file_postupdate_standard_editor($description, 'description', $editoroptions, $editoroptions['context'], 'course_grouping_description', $description->id);
189         $DB->update_record('groupings', $description);
190     }
192     events_trigger('groups_grouping_created', $data);
194     return $id;
197 /**
198  * Update group
199  * @param object $data group properties (with magic quotes)
200  * @param object $um upload manager with group picture
201  * @return boolean true or exception
202  */
203 function groups_update_group($data, $editform=false) {
204     global $CFG, $DB;
205     require_once("$CFG->libdir/gdlib.php");
207     $data->timemodified = time();
208     $data->name         = trim($data->name);
210     if ($editform && method_exists($editform, 'get_editor_options')) {
211         $editoroptions = $editform->get_editor_options();
212         $data = file_postupdate_standard_editor($data, 'description', $editoroptions, $editoroptions['context'], 'course_group_description', $data->id);
213     }
215     $DB->update_record('groups', $data);
217     if ($editform) {
218         //update image
219         if (save_profile_image($data->id, $editform, 'groups')) {
220         $DB->set_field('groups', 'picture', 1, array('id'=>$data->id));
221             $data->picture = 1;
222         }
223     }
225     //trigger groups events
226     events_trigger('groups_group_updated', $data);
228     return true;
231 /**
232  * Update grouping
233  * @param object $data grouping properties (with magic quotes)
234  * @return boolean true or exception
235  */
236 function groups_update_grouping($data, $editoroptions=null) {
237     global $DB;
238     $data->timemodified = time();
239     $data->name         = trim($data->name);
240     if ($editoroptions !== null) {
241         $data = file_postupdate_standard_editor($data, 'description', $editoroptions, $editoroptions['context'], 'course_grouping_description', $data->id);
242     }
243     $DB->update_record('groupings', $data);
244     //trigger groups events
245     events_trigger('groups_grouping_updated', $data);
247     return true;
250 /**
251  * Delete a group best effort, first removing members and links with courses and groupings.
252  * Removes group avatar too.
253  * @param mixed $grouporid The id of group to delete or full group object
254  * @return boolean True if deletion was successful, false otherwise
255  */
256 function groups_delete_group($grouporid) {
257     global $CFG, $DB;
258     require_once("$CFG->libdir/gdlib.php");
260     if (is_object($grouporid)) {
261         $groupid = $grouporid->id;
262         $group   = $grouporid;
263     } else {
264         $groupid = $grouporid;
265         if (!$group = $DB->get_record('groups', array('id'=>$groupid))) {
266             //silently ignore attempts to delete missing already deleted groups ;-)
267             return true;
268         }
269     }
271     // delete group calendar events
272     $DB->delete_records('event', array('groupid'=>$groupid));
273     //first delete usage in groupings_groups
274     $DB->delete_records('groupings_groups', array('groupid'=>$groupid));
275     //delete members
276     $DB->delete_records('groups_members', array('groupid'=>$groupid));
277     //then imge
278     delete_profile_image($groupid, 'groups');
279     //group itself last
280     $DB->delete_records('groups', array('id'=>$groupid));
282     // Delete all files associated with this group
283     $context = get_context_instance(CONTEXT_COURSE, $group->courseid);
284     $fs = get_file_storage();
285     $files = $fs->get_area_files($context->id, 'course_group_description', $groupid);
286     foreach ($files as $file) {
287         $file->delete();
288     }
290     //trigger groups events
291     events_trigger('groups_group_deleted', $group);
293     return true;
296 /**
297  * Delete grouping
298  * @param int $groupingid
299  * @return bool success
300  */
301 function groups_delete_grouping($groupingorid) {
302     global $DB;
304     if (is_object($groupingorid)) {
305         $groupingid = $groupingorid->id;
306         $grouping   = $groupingorid;
307     } else {
308         $groupingid = $groupingorid;
309         if (!$grouping = $DB->get_record('groupings', array('id'=>$groupingorid))) {
310             //silently ignore attempts to delete missing already deleted groupings ;-)
311             return true;
312         }
313     }
315     //first delete usage in groupings_groups
316     $DB->delete_records('groupings_groups', array('groupingid'=>$groupingid));
317     // remove the default groupingid from course
318     $DB->set_field('course', 'defaultgroupingid', 0, array('defaultgroupingid'=>$groupingid));
319     // remove the groupingid from all course modules
320     $DB->set_field('course_modules', 'groupingid', 0, array('groupingid'=>$groupingid));
321     //group itself last
322     $DB->delete_records('groupings', array('id'=>$groupingid));
324     $context = get_context_instance(CONTEXT_COURSE, $grouping->courseid);
325     $fs = get_file_storage();
326     $files = $fs->get_area_files($context->id, 'course_grouping_description', $groupingid);
327     foreach ($files as $file) {
328         $file->delete();
329     }
331     //trigger groups events
332     events_trigger('groups_grouping_deleted', $grouping);
334     return true;
337 /**
338  * Remove all users (or one user) from all groups in course
339  * @param int $courseid
340  * @param int $userid 0 means all users
341  * @param bool $showfeedback
342  * @return bool success
343  */
344 function groups_delete_group_members($courseid, $userid=0, $showfeedback=false) {
345     global $DB, $OUTPUT;
347     if (is_bool($userid)) {
348         debugging('Incorrect userid function parameter');
349         return false;
350     }
352     $params = array('courseid'=>$courseid);
354     if ($userid) {
355         $usersql = "AND userid = :userid";
356         $params['userid'] = $userid;
357     } else {
358         $usersql = "";
359     }
361     $groupssql = "SELECT id FROM {groups} g WHERE g.courseid = :courseid";
362     $DB->delete_records_select('groups_members', "groupid IN ($groupssql) $usersql", $params);
364     //trigger groups events
365     $eventdata = new object();
366     $eventdata->courseid = $courseid;
367     $eventdata->userid   = $userid;
368     events_trigger('groups_members_removed', $eventdata);
370     if ($showfeedback) {
371         echo $OUTPUT->notification(get_string('deleted').' groups_members');
372     }
374     return true;
377 /**
378  * Remove all groups from all groupings in course
379  * @param int $courseid
380  * @param bool $showfeedback
381  * @return bool success
382  */
383 function groups_delete_groupings_groups($courseid, $showfeedback=false) {
384     global $DB, $OUTPUT;
386     $groupssql = "SELECT id FROM {groups} g WHERE g.courseid = ?";
387     $DB->delete_records_select('groupings_groups', "groupid IN ($groupssql)", array($courseid));
389     // Delete all files associated with groupings for this course
390     $context = get_context_instance(CONTEXT_COURSE, $courseid);
391     $fs = get_file_storage();
392     $fs->delete_area_files($context->id, 'course_group_description');
394     //trigger groups events
395     events_trigger('groups_groupings_groups_removed', $courseid);
397     if ($showfeedback) {
398         echo $OUTPUT->notification(get_string('deleted').' groupings_groups');
399     }
401     return true;
404 /**
405  * Delete all groups from course
406  * @param int $courseid
407  * @param bool $showfeedback
408  * @return bool success
409  */
410 function groups_delete_groups($courseid, $showfeedback=false) {
411     global $CFG, $DB, $OUTPUT;
412     require_once($CFG->libdir.'/gdlib.php');
414     // delete any uses of groups
415     // Any associated files are deleted as part of groups_delete_groupings_groups
416     groups_delete_groupings_groups($courseid, $showfeedback);
417     groups_delete_group_members($courseid, 0, $showfeedback);
419     // delete group pictures
420     if ($groups = $DB->get_records('groups', array('courseid'=>$courseid))) {
421         foreach($groups as $group) {
422             delete_profile_image($group->id, 'groups');
423         }
424     }
426     // delete group calendar events
427     $groupssql = "SELECT id FROM {groups} g WHERE g.courseid = ?";
428     $DB->delete_records_select('event', "groupid IN ($groupssql)", array($courseid));
430     $DB->delete_records('groups', array('courseid'=>$courseid));
432     //trigger groups events
433     events_trigger('groups_groups_deleted', $courseid);
435     if ($showfeedback) {
436         echo $OUTPUT->notification(get_string('deleted').' groups');
437     }
439     return true;
442 /**
443  * Delete all groupings from course
444  * @param int $courseid
445  * @param bool $showfeedback
446  * @return bool success
447  */
448 function groups_delete_groupings($courseid, $showfeedback=false) {
449     global $DB, $OUTPUT;
451     // delete any uses of groupings
452     $sql = "DELETE FROM {groupings_groups}
453              WHERE groupingid in (SELECT id FROM {groupings} g WHERE g.courseid = ?)";
454     $DB->execute($sql, array($courseid));
456     // remove the default groupingid from course
457     $DB->set_field('course', 'defaultgroupingid', 0, array('id'=>$courseid));
458     // remove the groupingid from all course modules
459     $DB->set_field('course_modules', 'groupingid', 0, array('course'=>$courseid));
461     $DB->delete_records('groupings', array('courseid'=>$courseid));
463     // Delete all files associated with groupings for this course
464     $context = get_context_instance(CONTEXT_COURSE, $courseid);
465     $fs = get_file_storage();
466     $fs->delete_area_files($context->id, 'course_grouping_description');
468     //trigger groups events
469     events_trigger('groups_groupings_deleted', $courseid);
471     if ($showfeedback) {
472         echo $OUTPUT->notification(get_string('deleted').' groupings');
473     }
475     return true;
478 /* =================================== */
479 /* various functions used by groups UI */
480 /* =================================== */
482 /**
483  * Obtains a list of the possible roles that group members might come from,
484  * on a course. Generally this includes all the roles who would have
485  * course:view on that course, except the doanything roles.
486  * @param object $context Context of course
487  * @return Array of role ID integers, or false if error/none.
488  */
489 function groups_get_possible_roles($context) {
490     $capability = 'moodle/course:participate';
492     // find all possible "student" roles
493     if ($possibleroles = get_roles_with_capability($capability, CAP_ALLOW, $context)) {
494         $validroleids = array();
495         foreach ($possibleroles as $possiblerole) {
496             if ($caps = role_context_capabilities($possiblerole->id, $context, $capability)) { // resolved list
497                 if (isset($caps[$capability]) && $caps[$capability] > 0) { // resolved capability > 0
498                     $validroleids[] = $possiblerole->id;
499                 }
500             }
501         }
502         if (empty($validroleids)) {
503             return false;
504         }
505         return $validroleids;
506     } else {
507         return false;  // No need to continue, since no roles have this capability set
508     }
512 /**
513  * Gets potential group members for grouping
514  * @param int $courseid The id of the course
515  * @param int $roleid The role to select users from
516  * @param string $orderby The colum to sort users by
517  * @return array An array of the users
518  */
519 function groups_get_potential_members($courseid, $roleid = null, $orderby = 'lastname ASC, firstname ASC') {
520     global $DB;
522     $context = get_context_instance(CONTEXT_COURSE, $courseid);
524     // we are looking for all users with this role assigned in this context or higher
525     $listofcontexts = get_related_contexts_string($context);
527     list($esql, $params) = get_enrolled_sql($context);
528     
529     if ($roleid) {
530         $params['roleid'] = $roleid;
531         $where = "WHERE u.id IN (SELECT userid
532                                    FROM {role_assignments}
533                                   WHERE roleid = :roleid AND contextid $listofcontexts)";
534     } else {
535         $where = "";
536     }
538     $sql = "SELECT u.id, u.username, u.firstname, u.lastname, u.idnumber
539               FROM {user} u
540               JOIN ($esql) e ON e.id = u.id
541             $where
542           ORDER BY $orderby";
544     return $DB->get_records_sql($sql, $params);
548 /**
549  * Parse a group name for characters to replace
550  * @param string $format The format a group name will follow
551  * @param int $groupnumber The number of the group to be used in the parsed format string
552  * @return string the parsed format string
553  */
554 function groups_parse_name($format, $groupnumber) {
555     if (strstr($format, '@') !== false) { // Convert $groupnumber to a character series
556         $letter = 'A';
557         for($i=0; $i<$groupnumber; $i++) {
558             $letter++;
559         }
560         $str = str_replace('@', $letter, $format);
561     } else {
562         $str = str_replace('#', $groupnumber+1, $format);
563     }
564     return($str);
567 /**
568  * Assigns group into grouping
569  * @param int groupingid
570  * @param int groupid
571  * @return bool true or exception
572  */
573 function groups_assign_grouping($groupingid, $groupid) {
574     global $DB;
576     if ($DB->record_exists('groupings_groups', array('groupingid'=>$groupingid, 'groupid'=>$groupid))) {
577         return true;
578     }
579     $assign = new object();
580     $assign->groupingid = $groupingid;
581     $assign->groupid    = $groupid;
582     $assign->timeadded  = time();
583     $DB->insert_record('groupings_groups', $assign);
585     return true;
588 /**
589  * Unassigns group grom grouping
590  * @param int groupingid
591  * @param int groupid
592  * @return bool success
593  */
594 function groups_unassign_grouping($groupingid, $groupid) {
595     global $DB;
596     $DB->delete_records('groupings_groups', array('groupingid'=>$groupingid, 'groupid'=>$groupid));
598     return true;
601 /**
602  * Lists users in a group based on their role on the course.
603  * Returns false if there's an error or there are no users in the group.
604  * Otherwise returns an array of role ID => role data, where role data includes:
605  * (role) $id, $shortname, $name
606  * $users: array of objects for each user which include the specified fields
607  * Users who do not have a role are stored in the returned array with key '-'
608  * and pseudo-role details (including a name, 'No role'). Users with multiple
609  * roles, same deal with key '*' and name 'Multiple roles'. You can find out
610  * which roles each has by looking in the $roles array of the user object.
611  * @param int $groupid
612  * @param int $courseid Course ID (should match the group's course)
613  * @param string $fields List of fields from user table prefixed with u, default 'u.*'
614  * @param string $sort SQL ORDER BY clause, default 'u.lastname ASC'
615  * @param string $extrawheretest extra SQL conditions ANDed with the existing where clause.
616  * @param array $whereparams any parameters required by $extrawheretest (named parameters).
617  * @return array Complex array as described above
618  */
619 function groups_get_members_by_role($groupid, $courseid, $fields='u.*',
620         $sort='u.lastname ASC', $extrawheretest='', $whereparams=array()) {
621     global $CFG, $DB;
623     // Retrieve information about all users and their roles on the course or
624     // parent ('related') contexts
625     $context = get_context_instance(CONTEXT_COURSE, $courseid);
627     if ($extrawheretest) {
628         $extrawheretest = ' AND ' . $extrawheretest;
629     }
631     $sql = "SELECT r.id AS roleid, r.shortname AS roleshortname, r.name AS rolename,
632                    u.id AS userid, $fields
633               FROM {groups_members} gm
634               JOIN {user} u ON u.id = gm.userid
635               JOIN {role_assignments} ra ON ra.userid = u.id
636               JOIN {role} r ON r.id = ra.roleid
637              WHERE gm.groupid=:mgroupid
638                    AND ra.contextid ".get_related_contexts_string($context).
639                    $extrawheretest."
640           ORDER BY r.sortorder, $sort";
641     $whereparams['mgroupid'] = $groupid;
642     $rs = $DB->get_recordset_sql($sql, $whereparams);
644     return groups_calculate_role_people($rs, $context);
647 /**
648  * Internal function used by groups_get_members_by_role to handle the
649  * results of a database query that includes a list of users and possible
650  * roles on a course.
651  *
652  * @param object $rs The record set (may be false)
653  * @param int $contextid ID of course context
654  * @return array As described in groups_get_members_by_role
655  */
656 function groups_calculate_role_people($rs, $context) {
657     global $CFG, $DB;
659     if (!$rs) {
660         return array();
661     }
663     $roles = $DB->get_records_menu('role', null, 'name', 'id, name');
664     $aliasnames = role_fix_names($roles, $context);
666     // Array of all involved roles
667     $roles = array();
668     // Array of all retrieved users
669     $users = array();
670     // Fill arrays
671     foreach ($rs as $rec) {
672         // Create information about user if this is a new one
673         if (!array_key_exists($rec->userid, $users)) {
674             // User data includes all the optional fields, but not any of the
675             // stuff we added to get the role details
676             $userdata=clone($rec);
677             unset($userdata->roleid);
678             unset($userdata->roleshortname);
679             unset($userdata->rolename);
680             unset($userdata->userid);
681             $userdata->id = $rec->userid;
683             // Make an array to hold the list of roles for this user
684             $userdata->roles = array();
685             $users[$rec->userid] = $userdata;
686         }
687         // If user has a role...
688         if (!is_null($rec->roleid)) {
689             // Create information about role if this is a new one
690             if (!array_key_exists($rec->roleid,$roles)) {
691                 $roledata = new object();
692                 $roledata->id        = $rec->roleid;
693                 $roledata->shortname = $rec->roleshortname;
694                 if (array_key_exists($rec->roleid, $aliasnames)) {
695                     $roledata->name = $aliasnames[$rec->roleid];
696                 } else {
697                     $roledata->name = $rec->rolename;
698                 }
699                 $roledata->users = array();
700                 $roles[$roledata->id] = $roledata;
701             }
702             // Record that user has role
703             $users[$rec->userid]->roles[] = $roles[$rec->roleid];
704         }
705     }
706     $rs->close();
708     // Return false if there weren't any users
709     if (count($users)==0) {
710         return false;
711     }
713     // Add pseudo-role for multiple roles
714     $roledata = new object();
715     $roledata->name = get_string('multipleroles','role');
716     $roledata->users = array();
717     $roles['*'] = $roledata;
719     // Now we rearrange the data to store users by role
720     foreach ($users as $userid=>$userdata) {
721         $rolecount = count($userdata->roles);
722         if ($rolecount==0) {
723             debugging("Unexpected: user $userid is missing roles");
724         } else if($rolecount>1) {
725             $roleid = '*';
726         } else {
727             $roleid = $userdata->roles[0]->id;
728         }
729         $roles[$roleid]->users[$userid] = $userdata;
730     }
732     // Delete roles not used
733     foreach ($roles as $key=>$roledata) {
734         if (count($roledata->users)===0) {
735             unset($roles[$key]);
736         }
737     }
739     // Return list of roles containing their users
740     return $roles;