Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402] [Versions 402 and 403]
1 <?php 2 3 // This file is part of Moodle - http://moodle.org/ 4 // 5 // Moodle is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // Moodle is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 17 18 /** 19 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} 20 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 21 * @package core_group 22 */ 23 24 defined('MOODLE_INTERNAL') || die(); 25 26 /** 27 * Groups not used in course or activity 28 */ 29 define('NOGROUPS', 0); 30 31 /** 32 * Groups used, users do not see other groups 33 */ 34 define('SEPARATEGROUPS', 1); 35 36 /** 37 * Groups used, students see other groups 38 */ 39 define('VISIBLEGROUPS', 2); 40 41 /** 42 * This is for filtering users without any group. 43 */ 44 define('USERSWITHOUTGROUP', -1); 45 46 /** 47 * 'None' join type, used when filtering by groups (logical NOT) 48 */ 49 define('GROUPS_JOIN_NONE', 0); 50 51 /** 52 * 'Any' join type, used when filtering by groups (logical OR) 53 */ 54 define('GROUPS_JOIN_ANY', 1); 55 56 /** 57 * 'All' join type, used when filtering by groups (logical AND) 58 */ 59 define('GROUPS_JOIN_ALL', 2); 60 61 /** 62 * All users can see this group and its members. 63 */ 64 define('GROUPS_VISIBILITY_ALL', 0); 65 66 /** 67 * Members of this group can see this group and other members. 68 */ 69 define('GROUPS_VISIBILITY_MEMBERS', 1); 70 71 /** 72 * Members of this group can see the group and their own membership, but not each other's membership 73 */ 74 define('GROUPS_VISIBILITY_OWN', 2); 75 76 /** 77 * No-one can see this group or its members. Members of the group will not know they are in the group. 78 */ 79 define('GROUPS_VISIBILITY_NONE', 3); 80 81 /** 82 * Determines if a group with a given groupid exists. 83 * 84 * @category group 85 * @param int $groupid The groupid to check for 86 * @return bool True if the group exists, false otherwise or if an error 87 * occurred. 88 */ 89 function groups_group_exists($groupid) { 90 global $DB; 91 return $DB->record_exists('groups', array('id'=>$groupid)); 92 } 93 94 /** 95 * Gets the name of a group with a specified id 96 * 97 * Before output, you should call {@see format_string} on the result 98 * 99 * @category group 100 * @param int $groupid The id of the group 101 * @return string The name of the group 102 */ 103 function groups_get_group_name($groupid) { 104 global $DB; 105 return $DB->get_field('groups', 'name', array('id'=>$groupid)); 106 } 107 108 /** 109 * Gets the name of a grouping with a specified id 110 * 111 * Before output, you should call {@see format_string} on the result 112 * 113 * @category group 114 * @param int $groupingid The id of the grouping 115 * @return string The name of the grouping 116 */ 117 function groups_get_grouping_name($groupingid) { 118 global $DB; 119 return $DB->get_field('groupings', 'name', array('id'=>$groupingid)); 120 } 121 122 /** 123 * Returns the groupid of a group with the name specified for the course. 124 * Group names should be unique in course 125 * 126 * @category group 127 * @param int $courseid The id of the course 128 * @param string $name name of group (without magic quotes) 129 * @return int $groupid 130 */ 131 function groups_get_group_by_name($courseid, $name) { 132 $data = groups_get_course_data($courseid); 133 foreach ($data->groups as $group) { 134 if ($group->name == $name) { 135 return $group->id; 136 } 137 } 138 return false; 139 } 140 141 /** 142 * Returns the groupid of a group with the idnumber specified for the course. 143 * Group idnumbers should be unique within course 144 * 145 * @category group 146 * @param int $courseid The id of the course 147 * @param string $idnumber idnumber of group 148 * @return group object 149 */ 150 function groups_get_group_by_idnumber($courseid, $idnumber) { 151 if (empty($idnumber)) { 152 return false; 153 } 154 $data = groups_get_course_data($courseid); 155 foreach ($data->groups as $group) { 156 if ($group->idnumber == $idnumber) { 157 return $group; 158 } 159 } 160 return false; 161 } 162 163 /** 164 * Returns the groupingid of a grouping with the name specified for the course. 165 * Grouping names should be unique in course 166 * 167 * @category group 168 * @param int $courseid The id of the course 169 * @param string $name name of group (without magic quotes) 170 * @return int $groupid 171 */ 172 function groups_get_grouping_by_name($courseid, $name) { 173 $data = groups_get_course_data($courseid); 174 foreach ($data->groupings as $grouping) { 175 if ($grouping->name == $name) { 176 return $grouping->id; 177 } 178 } 179 return false; 180 } 181 182 /** 183 * Returns the groupingid of a grouping with the idnumber specified for the course. 184 * Grouping names should be unique within course 185 * 186 * @category group 187 * @param int $courseid The id of the course 188 * @param string $idnumber idnumber of the group 189 * @return grouping object 190 */ 191 function groups_get_grouping_by_idnumber($courseid, $idnumber) { 192 if (empty($idnumber)) { 193 return false; 194 } 195 $data = groups_get_course_data($courseid); 196 foreach ($data->groupings as $grouping) { 197 if ($grouping->idnumber == $idnumber) { 198 return $grouping; 199 } 200 } 201 return false; 202 } 203 204 /** 205 * Get the group object 206 * 207 * @category group 208 * @param int $groupid ID of the group. 209 * @param string $fields (default is all fields) 210 * @param int $strictness (IGNORE_MISSING - default) 211 * @return bool|stdClass group object or false if not found 212 * @throws dml_exception 213 */ 214 function groups_get_group($groupid, $fields='*', $strictness=IGNORE_MISSING) { 215 global $DB; 216 return $DB->get_record('groups', array('id'=>$groupid), $fields, $strictness); 217 } 218 219 /** 220 * Get the grouping object 221 * 222 * @category group 223 * @param int $groupingid ID of the group. 224 * @param string $fields 225 * @param int $strictness (IGNORE_MISSING - default) 226 * @return stdClass group object 227 */ 228 function groups_get_grouping($groupingid, $fields='*', $strictness=IGNORE_MISSING) { 229 global $DB; 230 return $DB->get_record('groupings', array('id'=>$groupingid), $fields, $strictness); 231 } 232 233 /** 234 * Gets array of all groups in a specified course (subject to the conditions imposed by the other arguments). 235 * 236 * If a user does not have moodle/course:viewhiddengroups, the list of groups and members will be restricted based on the 237 * visibility setting of each group. 238 * 239 * @category group 240 * @param int $courseid The id of the course. 241 * @param int|int[] $userid optional user id or array of ids, returns only groups continaing one or more of those users. 242 * @param int $groupingid optional returns only groups in the specified grouping. 243 * @param string $fields defaults to g.*. This allows you to vary which fields are returned. 244 * If $groupingid is specified, the groupings_groups table will be available with alias gg. 245 * If $userid is specified, the groups_members table will be available as gm. 246 * @param bool $withmembers if true return an extra field members (int[]) which is the list of userids that 247 * are members of each group. For this to work, g.id (or g.*) must be included in $fields. 248 * In this case, the final results will always be an array indexed by group id. 249 * @param bool $participationonly Only return groups where the participation field is true. 250 * @return array returns an array of the group objects (unless you have done something very weird 251 * with the $fields option). 252 */ 253 function groups_get_all_groups($courseid, $userid=0, $groupingid=0, $fields='g.*', $withmembers=false, $participationonly = false) { 254 global $DB, $USER; 255 256 // We need to check that we each field in the fields list belongs to the group table and that it has not being 257 // aliased. If its something else we need to avoid the cache and run the query as who knows whats going on. 258 $knownfields = true; 259 if ($fields !== 'g.*') { 260 // Quickly check if the first field is no longer g.id as using the 261 // cache will return an array indexed differently than when expect 262 if (strpos($fields, 'g.*') !== 0 && strpos($fields, 'g.id') !== 0) { 263 $knownfields = false; 264 } else { 265 $fieldbits = explode(',', $fields); 266 foreach ($fieldbits as $bit) { 267 $bit = trim($bit); 268 if (strpos($bit, 'g.') !== 0 or stripos($bit, ' AS ') !== false) { 269 $knownfields = false; 270 break; 271 } 272 } 273 } 274 } 275 276 if (empty($userid) && $knownfields && !$withmembers && \core_group\visibility::can_view_all_groups($courseid)) { 277 // We can use the cache. 278 $data = groups_get_course_data($courseid); 279 if (empty($groupingid)) { 280 // All groups.. Easy! 281 $groups = $data->groups; 282 } else { 283 $groups = array(); 284 foreach ($data->mappings as $mapping) { 285 if ($mapping->groupingid != $groupingid) { 286 continue; 287 } 288 if (isset($data->groups[$mapping->groupid])) { 289 $groups[$mapping->groupid] = $data->groups[$mapping->groupid]; 290 } 291 } 292 } 293 if ($participationonly) { 294 $groups = array_filter($groups, fn($group) => $group->participation); 295 } 296 // Yay! We could use the cache. One more query saved. 297 return $groups; 298 } 299 300 $params = []; 301 $userfrom = ''; 302 $userwhere = ''; 303 if (!empty($userid)) { 304 list($usql, $params) = $DB->get_in_or_equal($userid); 305 $userfrom = "JOIN {groups_members} gm ON gm.groupid = g.id"; 306 $userwhere = "AND gm.userid $usql"; 307 } 308 309 $groupingfrom = ''; 310 $groupingwhere = ''; 311 if (!empty($groupingid)) { 312 $groupingfrom = "JOIN {groupings_groups} gg ON gg.groupid = g.id"; 313 $groupingwhere = "AND gg.groupingid = ?"; 314 $params[] = $groupingid; 315 } 316 317 array_unshift($params, $courseid); 318 319 $visibilityfrom = ''; 320 $visibilitywhere = ''; 321 $viewhidden = has_capability('moodle/course:viewhiddengroups', context_course::instance($courseid)); 322 if (!$viewhidden) { 323 // Apply group visibility restrictions. Only return groups where visibility is ALL, or the current user is a member and the 324 // visibility is MEMBERS or OWN. 325 $userids = []; 326 if (empty($userid)) { 327 $userids = [$USER->id]; 328 $visibilityfrom = "LEFT JOIN {groups_members} gm ON gm.groupid = g.id AND gm.userid = ?"; 329 } 330 [$insql, $inparams] = $DB->get_in_or_equal([GROUPS_VISIBILITY_MEMBERS, GROUPS_VISIBILITY_OWN]); 331 $visibilitywhere = " AND (g.visibility = ? OR (g.visibility $insql AND gm.id IS NOT NULL))"; 332 $params = array_merge( 333 $userids, 334 $params, 335 [GROUPS_VISIBILITY_ALL], 336 $inparams 337 ); 338 } 339 340 $participationwhere = ''; 341 if ($participationonly) { 342 $participationwhere = "AND g.participation = ?"; 343 $params = array_merge($params, [1]); 344 } 345 346 $results = $DB->get_records_sql(" 347 SELECT $fields 348 FROM {groups} g 349 $userfrom 350 $groupingfrom 351 $visibilityfrom 352 WHERE g.courseid = ? 353 $userwhere 354 $groupingwhere 355 $visibilitywhere 356 $participationwhere 357 ORDER BY g.name ASC", $params); 358 359 if (!$withmembers) { 360 return $results; 361 } 362 363 // We also want group members. We do this in a separate query, becuse the above 364 // query will return a lot of data (e.g. g.description) for each group, and 365 // some groups may contain hundreds of members. We don't want the results 366 // to contain hundreds of copies of long descriptions. 367 $groups = []; 368 foreach ($results as $row) { 369 $groups[$row->id] = $row; 370 $groups[$row->id]->members = []; 371 } 372 373 $gmvisibilityfrom = ''; 374 $gmvisibilitywhere = ''; 375 $gmvisibilityparams = []; 376 if (!$viewhidden) { 377 // Only return membership records where visibility is ALL, visibility is MEMBERS and the current user is a member, 378 // or visibility is OWN and the record is for the current user. 379 $gmvisibilityfrom = " 380 JOIN {groups} g ON gm.groupid = g.id 381 "; 382 $gmvisibilitywhere = " 383 AND (g.visibility = ? 384 OR (g.visibility = ? 385 AND g.id IN (SELECT gm2.groupid FROM {groups_members} gm2 WHERE gm2.groupid = g.id AND gm2.userid = ?)) 386 OR (g.visibility = ? 387 AND gm.userid = ?))"; 388 $gmvisibilityparams = [ 389 GROUPS_VISIBILITY_ALL, 390 GROUPS_VISIBILITY_MEMBERS, 391 $USER->id, 392 GROUPS_VISIBILITY_OWN, 393 $USER->id 394 ]; 395 } 396 397 $groupmembers = []; 398 if (!empty($groups)) { 399 [$gmin, $gmparams] = $DB->get_in_or_equal(array_keys($groups)); 400 $params = array_merge($gmparams, $gmvisibilityparams); 401 $gmsql = " 402 SELECT gm.* 403 FROM {groups_members} gm 404 $gmvisibilityfrom 405 WHERE gm.groupid $gmin 406 $gmvisibilitywhere"; 407 $groupmembers = $DB->get_records_sql($gmsql, $params); 408 } 409 410 foreach ($groupmembers as $gm) { 411 $groups[$gm->groupid]->members[$gm->userid] = $gm->userid; 412 } 413 return $groups; 414 } 415 416 /** 417 * Gets array of all groups in current user. 418 * 419 * @since Moodle 2.5 420 * @category group 421 * @return array Returns an array of the group objects. 422 */ 423 function groups_get_my_groups() { 424 global $DB, $USER; 425 426 $params = ['userid' => $USER->id]; 427 428 $viewhidden = has_capability('moodle/course:viewhiddengroups', context_system::instance()); 429 $visibilitywhere = ''; 430 if (!$viewhidden) { 431 $params['novisibility'] = GROUPS_VISIBILITY_NONE; 432 $visibilitywhere = ' AND g.visibility != :novisibility'; 433 } 434 435 return $DB->get_records_sql("SELECT * 436 FROM {groups_members} gm 437 JOIN {groups} g 438 ON g.id = gm.groupid 439 WHERE gm.userid = :userid 440 $visibilitywhere 441 ORDER BY name ASC", $params); 442 } 443 444 /** 445 * Returns info about user's groups in course. 446 * 447 * @category group 448 * @param int $courseid 449 * @param int $userid $USER if not specified 450 * @return array Array[groupingid][groupid] including grouping id 0 which means all groups 451 */ 452 function groups_get_user_groups($courseid, $userid=0) { 453 global $USER, $DB; 454 455 if (empty($courseid)) { 456 return ['0' => []]; 457 } 458 459 if (empty($userid)) { 460 $userid = $USER->id; 461 } 462 463 $usergroups = false; 464 $viewhidden = has_capability('moodle/course:viewhiddengroups', context_course::instance($courseid)); 465 $viewall = \core_group\visibility::can_view_all_groups($courseid); 466 467 $cache = cache::make('core', 'user_group_groupings'); 468 469 if ($viewall) { 470 // Try to retrieve group ids from the cache. 471 $usergroups = $cache->get($userid); 472 } 473 474 if ($usergroups === false) { 475 476 $sql = "SELECT g.id, g.courseid, gg.groupingid 477 FROM {groups} g 478 JOIN {groups_members} gm ON gm.groupid = g.id 479 LEFT JOIN {groupings_groups} gg ON gg.groupid = g.id 480 WHERE gm.userid = :userid"; 481 482 $params = ['userid' => $userid]; 483 484 if (!$viewhidden) { 485 // Apply visibility restrictions. 486 // Everyone can see who is in groups with ALL visibility. 487 list($visibilitywhere, $visibilityparams) = \core_group\visibility::sql_group_visibility_where($userid); 488 $sql .= " AND " . $visibilitywhere; 489 $params = array_merge($params, $visibilityparams); 490 } 491 492 $sql .= ' ORDER BY g.id'; // To make results deterministic. 493 494 $rs = $DB->get_recordset_sql($sql, $params); 495 496 $usergroups = array(); 497 $allgroups = array(); 498 499 foreach ($rs as $group) { 500 if (!array_key_exists($group->courseid, $allgroups)) { 501 $allgroups[$group->courseid] = array(); 502 } 503 $allgroups[$group->courseid][$group->id] = $group->id; 504 if (!array_key_exists($group->courseid, $usergroups)) { 505 $usergroups[$group->courseid] = array(); 506 } 507 if (is_null($group->groupingid)) { 508 continue; 509 } 510 if (!array_key_exists($group->groupingid, $usergroups[$group->courseid])) { 511 $usergroups[$group->courseid][$group->groupingid] = array(); 512 } 513 $usergroups[$group->courseid][$group->groupingid][$group->id] = $group->id; 514 } 515 $rs->close(); 516 517 foreach (array_keys($allgroups) as $cid) { 518 $usergroups[$cid]['0'] = array_keys($allgroups[$cid]); // All user groups in the course. 519 } 520 521 if ($viewall) { 522 // Cache the data, if we got the full list of groups. 523 $cache->set($userid, $usergroups); 524 } 525 } 526 527 if (array_key_exists($courseid, $usergroups)) { 528 return $usergroups[$courseid]; 529 } else { 530 return array('0' => array()); 531 } 532 } 533 534 /** 535 * Gets an array of all groupings in a specified course. This value is cached 536 * for a single course (so you can call it repeatedly for the same course 537 * without a performance penalty). 538 * 539 * @category group 540 * @param int $courseid return all groupings from course with this courseid 541 * @return array Returns an array of the grouping objects (empty if none) 542 */ 543 function groups_get_all_groupings($courseid) { 544 $data = groups_get_course_data($courseid); 545 return $data->groupings; 546 } 547 548 /** 549 * Determines if the user is a member of the given group. 550 * 551 * If $userid is null, use the global object. 552 * 553 * @category group 554 * @param int $groupid The group to check for membership. 555 * @param int $userid The user to check against the group. 556 * @return bool True if the user is a member, false otherwise. 557 */ 558 function groups_is_member($groupid, $userid=null) { 559 global $USER, $DB; 560 561 if (!$userid) { 562 $userid = $USER->id; 563 } 564 565 $courseid = $DB->get_field('groups', 'courseid', ['id' => $groupid]); 566 if (!$courseid) { 567 return false; 568 } 569 570 if (\core_group\visibility::can_view_all_groups($courseid)) { 571 return $DB->record_exists('groups_members', ['groupid' => $groupid, 'userid' => $userid]); 572 } 573 574 $sql = "SELECT * 575 FROM {groups_members} gm 576 JOIN {groups} g ON gm.groupid = g.id 577 WHERE g.id = :groupid 578 AND gm.userid = :userid"; 579 $params = ['groupid' => $groupid, 'userid' => $userid]; 580 581 list($visibilitywhere, $visibilityparams) = \core_group\visibility::sql_group_visibility_where($userid); 582 583 $sql .= " AND " . $visibilitywhere; 584 $params = array_merge($params, $visibilityparams); 585 586 return $DB->record_exists_sql($sql, $params); 587 } 588 589 /** 590 * Determines if current or specified is member of any active group in activity 591 * 592 * @category group 593 * @staticvar array $cache 594 * @param stdClass|cm_info $cm course module object 595 * @param int $userid id of user, null means $USER->id 596 * @return bool true if user member of at least one group used in activity 597 */ 598 function groups_has_membership($cm, $userid=null) { 599 global $CFG, $USER, $DB; 600 601 static $cache = array(); 602 603 if (empty($userid)) { 604 $userid = $USER->id; 605 } 606 607 $cachekey = $userid.'|'.$cm->course.'|'.$cm->groupingid; 608 if (isset($cache[$cachekey])) { 609 return($cache[$cachekey]); 610 } 611 612 if ($cm->groupingid) { 613 // find out if member of any group in selected activity grouping 614 $sql = "SELECT 'x' 615 FROM {groups_members} gm, {groupings_groups} gg 616 WHERE gm.userid = ? AND gm.groupid = gg.groupid AND gg.groupingid = ?"; 617 $params = array($userid, $cm->groupingid); 618 619 } else { 620 // no grouping used - check all groups in course 621 $sql = "SELECT 'x' 622 FROM {groups_members} gm, {groups} g 623 WHERE gm.userid = ? AND gm.groupid = g.id AND g.courseid = ?"; 624 $params = array($userid, $cm->course); 625 } 626 627 $cache[$cachekey] = $DB->record_exists_sql($sql, $params); 628 629 return $cache[$cachekey]; 630 } 631 632 /** 633 * Returns the users in the specified group. 634 * 635 * @category group 636 * @param int $groupid The groupid to get the users for 637 * @param int $fields The fields to return 638 * @param int $sort optional sorting of returned users 639 * @return array Returns an array of the users for the specified group 640 */ 641 function groups_get_members($groupid, $fields='u.*', $sort='lastname ASC') { 642 global $DB; 643 644 if (empty($groupid)) { 645 return []; 646 } 647 648 $courseid = $DB->get_field('groups', 'courseid', ['id' => $groupid]); 649 if ($courseid === false) { 650 return []; 651 } 652 653 $select = "SELECT $fields"; 654 $from = "FROM {user} u 655 JOIN {groups_members} gm ON gm.userid = u.id"; 656 $where = "WHERE gm.groupid = :groupid"; 657 $order = "ORDER BY $sort"; 658 659 $params = ['groupid' => $groupid]; 660 661 if (!\core_group\visibility::can_view_all_groups($courseid)) { 662 $from .= " JOIN {groups} g ON g.id = gm.groupid"; 663 // Can view memberships of visibility is ALL, visibility is MEMBERS and current user is a member, 664 // or visibility is OWN and this is their membership. 665 list($visibilitywhere, $visibilityparams) = \core_group\visibility::sql_member_visibility_where(); 666 $params = array_merge($params, $visibilityparams); 667 $where .= ' AND ' . $visibilitywhere; 668 } 669 670 $sql = implode(PHP_EOL, [$select, $from, $where, $order]); 671 672 return $DB->get_records_sql($sql, $params); 673 } 674 675 676 /** 677 * Returns the users in the specified grouping. 678 * 679 * @category group 680 * @param int $groupingid The groupingid to get the users for 681 * @param string $fields The fields to return 682 * @param string $sort optional sorting of returned users 683 * @return array|bool Returns an array of the users for the specified 684 * group or false if no users or an error returned. 685 */ 686 function groups_get_grouping_members($groupingid, $fields='u.*', $sort='lastname ASC') { 687 global $DB; 688 689 return $DB->get_records_sql("SELECT $fields 690 FROM {user} u 691 INNER JOIN {groups_members} gm ON u.id = gm.userid 692 INNER JOIN {groupings_groups} gg ON gm.groupid = gg.groupid 693 WHERE gg.groupingid = ? 694 ORDER BY $sort", array($groupingid)); 695 } 696 697 /** 698 * Returns effective groupmode used in course 699 * 700 * @category group 701 * @param stdClass $course course object. 702 * @return int group mode 703 */ 704 function groups_get_course_groupmode($course) { 705 return $course->groupmode; 706 } 707 708 /** 709 * Returns effective groupmode used in activity, course setting 710 * overrides activity setting if groupmodeforce enabled. 711 * 712 * If $cm is an instance of cm_info it is easier to use $cm->effectivegroupmode 713 * 714 * @category group 715 * @param cm_info|stdClass $cm the course module object. Only the ->course and ->groupmode need to be set. 716 * @param stdClass $course object optional course object to improve perf 717 * @return int group mode 718 */ 719 function groups_get_activity_groupmode($cm, $course=null) { 720 if ($cm instanceof cm_info) { 721 return $cm->effectivegroupmode; 722 } 723 if (isset($course->id) and $course->id == $cm->course) { 724 //ok 725 } else { 726 // Get course object (reuse $COURSE if possible). 727 $course = get_course($cm->course, false); 728 } 729 730 return empty($course->groupmodeforce) ? $cm->groupmode : $course->groupmode; 731 } 732 733 /** 734 * Print group menu selector for course level. 735 * 736 * @category group 737 * @param stdClass $course course object 738 * @param mixed $urlroot return address. Accepts either a string or a moodle_url 739 * @param bool $return return as string instead of printing 740 * @return mixed void or string depending on $return param 741 */ 742 function groups_print_course_menu($course, $urlroot, $return=false) { 743 global $USER, $OUTPUT; 744 745 if (!$groupmode = $course->groupmode) { 746 if ($return) { 747 return ''; 748 } else { 749 return; 750 } 751 } 752 753 $context = context_course::instance($course->id); 754 $aag = has_capability('moodle/site:accessallgroups', $context); 755 756 $usergroups = array(); 757 if ($groupmode == VISIBLEGROUPS or $aag) { 758 $allowedgroups = groups_get_all_groups($course->id, 0, $course->defaultgroupingid); 759 // Get user's own groups and put to the top. 760 $usergroups = groups_get_all_groups($course->id, $USER->id, $course->defaultgroupingid); 761 } else { 762 $allowedgroups = groups_get_all_groups($course->id, $USER->id, $course->defaultgroupingid); 763 } 764 765 $activegroup = groups_get_course_group($course, true, $allowedgroups); 766 767 $groupsmenu = array(); 768 if (!$allowedgroups or $groupmode == VISIBLEGROUPS or $aag) { 769 $groupsmenu[0] = get_string('allparticipants'); 770 } 771 772 $groupsmenu += groups_sort_menu_options($allowedgroups, $usergroups); 773 774 if ($groupmode == VISIBLEGROUPS) { 775 $grouplabel = get_string('groupsvisible'); 776 } else { 777 $grouplabel = get_string('groupsseparate'); 778 } 779 780 if ($aag and $course->defaultgroupingid) { 781 if ($grouping = groups_get_grouping($course->defaultgroupingid)) { 782 $grouplabel = $grouplabel . ' (' . format_string($grouping->name) . ')'; 783 } 784 } 785 786 if (count($groupsmenu) == 1) { 787 $groupname = reset($groupsmenu); 788 $output = $grouplabel.': '.$groupname; 789 } else { 790 $select = new single_select(new moodle_url($urlroot), 'group', $groupsmenu, $activegroup, null, 'selectgroup'); 791 $select->label = $grouplabel; 792 $output = $OUTPUT->render($select); 793 } 794 795 $output = '<div class="groupselector">'.$output.'</div>'; 796 797 if ($return) { 798 return $output; 799 } else { 800 echo $output; 801 } 802 } 803 804 /** 805 * Turn an array of groups into an array of menu options. 806 * @param array $groups of group objects. 807 * @return array groupid => formatted group name. 808 */ 809 function groups_list_to_menu($groups) { 810 $groupsmenu = array(); 811 foreach ($groups as $group) { 812 $groupsmenu[$group->id] = format_string($group->name); 813 } 814 return $groupsmenu; 815 } 816 817 /** 818 * Takes user's allowed groups and own groups and formats for use in group selector menu 819 * If user has allowed groups + own groups will add to an optgroup 820 * Own groups are removed from allowed groups 821 * @param array $allowedgroups All groups user is allowed to see 822 * @param array $usergroups Groups user belongs to 823 * @return array 824 */ 825 function groups_sort_menu_options($allowedgroups, $usergroups) { 826 $useroptions = array(); 827 if ($usergroups) { 828 $useroptions = groups_list_to_menu($usergroups); 829 830 // Remove user groups from other groups list. 831 foreach ($usergroups as $group) { 832 unset($allowedgroups[$group->id]); 833 } 834 } 835 836 $allowedoptions = array(); 837 if ($allowedgroups) { 838 $allowedoptions = groups_list_to_menu($allowedgroups); 839 } 840 841 if ($useroptions && $allowedoptions) { 842 return array( 843 1 => array(get_string('mygroups', 'group') => $useroptions), 844 2 => array(get_string('othergroups', 'group') => $allowedoptions) 845 ); 846 } else if ($useroptions) { 847 return $useroptions; 848 } else { 849 return $allowedoptions; 850 } 851 } 852 853 /** 854 * Generates html to print menu selector for course level, listing all groups. 855 * Note: This api does not do any group mode check use groups_print_course_menu() instead if you want proper checks. 856 * 857 * @param stdclass $course course object. 858 * @param string|moodle_url $urlroot return address. Accepts either a string or a moodle_url. 859 * @param bool $update set this to true to update current active group based on the group param. 860 * @param int $activegroup Change group active to this group if $update set to true. 861 * 862 * @return string html or void 863 */ 864 function groups_allgroups_course_menu($course, $urlroot, $update = false, $activegroup = 0) { 865 global $SESSION, $OUTPUT, $USER; 866 867 $groupmode = groups_get_course_groupmode($course); 868 $context = context_course::instance($course->id); 869 $groupsmenu = array(); 870 871 if (has_capability('moodle/site:accessallgroups', $context)) { 872 $groupsmenu[0] = get_string('allparticipants'); 873 $allowedgroups = groups_get_all_groups($course->id, 0, $course->defaultgroupingid); 874 } else { 875 $allowedgroups = groups_get_all_groups($course->id, $USER->id, $course->defaultgroupingid); 876 } 877 878 $groupsmenu += groups_list_to_menu($allowedgroups); 879 880 if ($update) { 881 // Init activegroup array if necessary. 882 if (!isset($SESSION->activegroup)) { 883 $SESSION->activegroup = array(); 884 } 885 if (!isset($SESSION->activegroup[$course->id])) { 886 $SESSION->activegroup[$course->id] = array(SEPARATEGROUPS => array(), VISIBLEGROUPS => array(), 'aag' => array()); 887 } 888 if (empty($groupsmenu[$activegroup])) { 889 $activegroup = key($groupsmenu); // Force set to one of accessible groups. 890 } 891 $SESSION->activegroup[$course->id][$groupmode][$course->defaultgroupingid] = $activegroup; 892 } 893 894 $grouplabel = get_string('groups'); 895 if (count($groupsmenu) == 0) { 896 return ''; 897 } else if (count($groupsmenu) == 1) { 898 $groupname = reset($groupsmenu); 899 $output = $grouplabel.': '.$groupname; 900 } else { 901 $select = new single_select(new moodle_url($urlroot), 'group', $groupsmenu, $activegroup, null, 'selectgroup'); 902 $select->label = $grouplabel; 903 $output = $OUTPUT->render($select); 904 } 905 906 return $output; 907 908 } 909 910 /** 911 * Print group menu selector for activity. 912 * 913 * @category group 914 * @param stdClass|cm_info $cm course module object 915 * @param string|moodle_url $urlroot return address that users get to if they choose an option; 916 * should include any parameters needed, e.g. "$CFG->wwwroot/mod/forum/view.php?id=34" 917 * @param bool $return return as string instead of printing 918 * @param bool $hideallparticipants If true, this prevents the 'All participants' 919 * option from appearing in cases where it normally would. This is intended for 920 * use only by activities that cannot display all groups together. (Note that 921 * selecting this option does not prevent groups_get_activity_group from 922 * returning 0; it will still do that if the user has chosen 'all participants' 923 * in another activity, or not chosen anything.) 924 * @return mixed void or string depending on $return param 925 */ 926 function groups_print_activity_menu($cm, $urlroot, $return=false, $hideallparticipants=false) { 927 global $USER, $OUTPUT; 928 929 if ($urlroot instanceof moodle_url) { 930 // no changes necessary 931 932 } else { 933 if (strpos($urlroot, 'http') !== 0) { // Will also work for https 934 // Display error if urlroot is not absolute (this causes the non-JS version to break) 935 debugging('groups_print_activity_menu requires absolute URL for ' . 936 '$urlroot, not <tt>' . s($urlroot) . '</tt>. Example: ' . 937 'groups_print_activity_menu($cm, $CFG->wwwroot . \'/mod/mymodule/view.php?id=13\');', 938 DEBUG_DEVELOPER); 939 } 940 $urlroot = new moodle_url($urlroot); 941 } 942 943 if (!$groupmode = groups_get_activity_groupmode($cm)) { 944 if ($return) { 945 return ''; 946 } else { 947 return; 948 } 949 } 950 951 $context = context_module::instance($cm->id); 952 $aag = has_capability('moodle/site:accessallgroups', $context); 953 954 $usergroups = array(); 955 if ($groupmode == VISIBLEGROUPS or $aag) { 956 $allowedgroups = groups_get_all_groups($cm->course, 0, $cm->groupingid, 'g.*', false, true); // Any group in grouping. 957 // Get user's own groups and put to the top. 958 $usergroups = groups_get_all_groups($cm->course, $USER->id, $cm->groupingid, 'g.*', false, true); 959 } else { 960 // Only assigned groups. 961 $allowedgroups = groups_get_all_groups($cm->course, $USER->id, $cm->groupingid, 'g.*', false, true); 962 } 963 964 $activegroup = groups_get_activity_group($cm, true, $allowedgroups); 965 966 $groupsmenu = array(); 967 if ((!$allowedgroups or $groupmode == VISIBLEGROUPS or $aag) and !$hideallparticipants) { 968 $groupsmenu[0] = get_string('allparticipants'); 969 } 970 971 $groupsmenu += groups_sort_menu_options($allowedgroups, $usergroups); 972 973 if ($groupmode == VISIBLEGROUPS) { 974 $grouplabel = get_string('groupsvisible'); 975 } else { 976 $grouplabel = get_string('groupsseparate'); 977 } 978 979 if ($aag and $cm->groupingid) { 980 if ($grouping = groups_get_grouping($cm->groupingid)) { 981 $grouplabel = $grouplabel . ' (' . format_string($grouping->name) . ')'; 982 } 983 } 984 985 if (count($groupsmenu) == 1) { 986 $groupname = reset($groupsmenu); 987 $output = $grouplabel.': '.$groupname; 988 } else { 989 $select = new single_select($urlroot, 'group', $groupsmenu, $activegroup, null, 'selectgroup'); 990 $select->label = $grouplabel; 991 $output = $OUTPUT->render($select); 992 } 993 994 $output = '<div class="groupselector">'.$output.'</div>'; 995 996 if ($return) { 997 return $output; 998 } else { 999 echo $output; 1000 } 1001 } 1002 1003 /** 1004 * Returns group active in course, changes the group by default if 'group' page param present 1005 * 1006 * @category group 1007 * @param stdClass $course course bject 1008 * @param bool $update change active group if group param submitted 1009 * @param array $allowedgroups list of groups user may access (INTERNAL, to be used only from groups_print_course_menu()) 1010 * @return mixed false if groups not used, int if groups used, 0 means all groups (access must be verified in SEPARATE mode) 1011 */ 1012 function groups_get_course_group($course, $update=false, $allowedgroups=null) { 1013 global $USER, $SESSION; 1014 1015 if (!$groupmode = $course->groupmode) { 1016 // NOGROUPS used 1017 return false; 1018 } 1019 1020 $context = context_course::instance($course->id); 1021 if (has_capability('moodle/site:accessallgroups', $context)) { 1022 $groupmode = 'aag'; 1023 } 1024 1025 if (!is_array($allowedgroups)) { 1026 if ($groupmode == VISIBLEGROUPS or $groupmode === 'aag') { 1027 $allowedgroups = groups_get_all_groups($course->id, 0, $course->defaultgroupingid); 1028 } else { 1029 $allowedgroups = groups_get_all_groups($course->id, $USER->id, $course->defaultgroupingid); 1030 } 1031 } 1032 1033 _group_verify_activegroup($course->id, $groupmode, $course->defaultgroupingid, $allowedgroups); 1034 1035 // set new active group if requested 1036 $changegroup = optional_param('group', -1, PARAM_INT); 1037 if ($update and $changegroup != -1) { 1038 1039 if ($changegroup == 0) { 1040 // do not allow changing to all groups without accessallgroups capability 1041 if ($groupmode == VISIBLEGROUPS or $groupmode === 'aag') { 1042 $SESSION->activegroup[$course->id][$groupmode][$course->defaultgroupingid] = 0; 1043 } 1044 1045 } else { 1046 if ($allowedgroups and array_key_exists($changegroup, $allowedgroups)) { 1047 $SESSION->activegroup[$course->id][$groupmode][$course->defaultgroupingid] = $changegroup; 1048 } 1049 } 1050 } 1051 1052 return $SESSION->activegroup[$course->id][$groupmode][$course->defaultgroupingid]; 1053 } 1054 1055 /** 1056 * Returns group active in activity, changes the group by default if 'group' page param present 1057 * 1058 * @category group 1059 * @param stdClass|cm_info $cm course module object 1060 * @param bool $update change active group if group param submitted 1061 * @param array $allowedgroups list of groups user may access (INTERNAL, to be used only from groups_print_activity_menu()) 1062 * @return mixed false if groups not used, int if groups used, 0 means all groups (access must be verified in SEPARATE mode) 1063 */ 1064 function groups_get_activity_group($cm, $update=false, $allowedgroups=null) { 1065 global $USER, $SESSION; 1066 1067 if (!$groupmode = groups_get_activity_groupmode($cm)) { 1068 // NOGROUPS used 1069 return false; 1070 } 1071 1072 $context = context_module::instance($cm->id); 1073 if (has_capability('moodle/site:accessallgroups', $context)) { 1074 $groupmode = 'aag'; 1075 } 1076 1077 if (!is_array($allowedgroups)) { 1078 if ($groupmode == VISIBLEGROUPS or $groupmode === 'aag') { 1079 $allowedgroups = groups_get_all_groups($cm->course, 0, $cm->groupingid, 'g.*', false, true); 1080 } else { 1081 $allowedgroups = groups_get_all_groups($cm->course, $USER->id, $cm->groupingid, 'g.*', false, true); 1082 } 1083 } 1084 1085 _group_verify_activegroup($cm->course, $groupmode, $cm->groupingid, $allowedgroups); 1086 1087 // set new active group if requested 1088 $changegroup = optional_param('group', -1, PARAM_INT); 1089 if ($update and $changegroup != -1) { 1090 1091 if ($changegroup == 0) { 1092 // allgroups visible only in VISIBLEGROUPS or when accessallgroups 1093 if ($groupmode == VISIBLEGROUPS or $groupmode === 'aag') { 1094 $SESSION->activegroup[$cm->course][$groupmode][$cm->groupingid] = 0; 1095 } 1096 1097 } else { 1098 if ($allowedgroups and array_key_exists($changegroup, $allowedgroups)) { 1099 $SESSION->activegroup[$cm->course][$groupmode][$cm->groupingid] = $changegroup; 1100 } 1101 } 1102 } 1103 1104 return $SESSION->activegroup[$cm->course][$groupmode][$cm->groupingid]; 1105 } 1106 1107 /** 1108 * Gets a list of groups that the user is allowed to access within the 1109 * specified activity. 1110 * 1111 * @category group 1112 * @param stdClass|cm_info $cm Course-module 1113 * @param int $userid User ID (defaults to current user) 1114 * @return array An array of group objects, or false if none 1115 */ 1116 function groups_get_activity_allowed_groups($cm,$userid=0) { 1117 // Use current user by default 1118 global $USER; 1119 if(!$userid) { 1120 $userid=$USER->id; 1121 } 1122 1123 // Get groupmode for activity, taking into account course settings 1124 $groupmode=groups_get_activity_groupmode($cm); 1125 1126 // If visible groups mode, or user has the accessallgroups capability, 1127 // then they can access all groups for the activity... 1128 $context = context_module::instance($cm->id); 1129 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context, $userid)) { 1130 return groups_get_all_groups($cm->course, 0, $cm->groupingid, 'g.*', false, true); 1131 } else { 1132 // ...otherwise they can only access groups they belong to 1133 return groups_get_all_groups($cm->course, $userid, $cm->groupingid, 'g.*', false, true); 1134 } 1135 } 1136 1137 /** 1138 * Determine if a given group is visible to user or not in a given context. 1139 * 1140 * @since Moodle 2.6 1141 * @param int $groupid Group id to test. 0 for all groups. 1142 * @param stdClass $course Course object. 1143 * @param stdClass $cm Course module object. 1144 * @param int $userid user id to test against. Defaults to $USER. 1145 * @return boolean true if visible, false otherwise 1146 */ 1147 function groups_group_visible($groupid, $course, $cm = null, $userid = null) { 1148 global $USER; 1149 1150 if (empty($userid)) { 1151 $userid = $USER->id; 1152 } 1153 1154 $groupmode = empty($cm) ? groups_get_course_groupmode($course) : groups_get_activity_groupmode($cm, $course); 1155 if ($groupmode == NOGROUPS || $groupmode == VISIBLEGROUPS) { 1156 // Groups are not used, or everything is visible, no need to go any further. 1157 return true; 1158 } 1159 1160 $context = empty($cm) ? context_course::instance($course->id) : context_module::instance($cm->id); 1161 if (has_capability('moodle/site:accessallgroups', $context, $userid)) { 1162 // User can see everything. Groupid = 0 is handled here as well. 1163 return true; 1164 } else if ($groupid != 0) { 1165 // Group mode is separate, and user doesn't have access all groups capability. Check if user can see requested group. 1166 $groups = empty($cm) ? groups_get_all_groups($course->id, $userid) : groups_get_activity_allowed_groups($cm, $userid); 1167 if (array_key_exists($groupid, $groups)) { 1168 // User can see the group. 1169 return true; 1170 } 1171 } 1172 return false; 1173 } 1174 1175 /** 1176 * Get sql and parameters that will return user ids for a group or groups 1177 * 1178 * @param int|array $groupids Where this is an array of multiple groups, it will match on members of any of the groups 1179 * @param context $context Course context or a context within a course. Mandatory when $groupid = USERSWITHOUTGROUP 1180 * @param int $groupsjointype Join type logic used. Defaults to 'Any' (logical OR). 1181 * @return array($sql, $params) 1182 * @throws coding_exception if empty or invalid context submitted when $groupid = USERSWITHOUTGROUP 1183 */ 1184 function groups_get_members_ids_sql($groupids, context $context = null, $groupsjointype = GROUPS_JOIN_ANY) { 1185 if (!is_array($groupids)) { 1186 $groupids = [$groupids]; 1187 } 1188 1189 $groupjoin = groups_get_members_join($groupids, 'u.id', $context, $groupsjointype); 1190 1191 $sql = "SELECT DISTINCT u.id 1192 FROM {user} u 1193 $groupjoin->joins 1194 WHERE u.deleted = 0"; 1195 if (!empty($groupjoin->wheres)) { 1196 $sql .= ' AND '. $groupjoin->wheres; 1197 } 1198 1199 return array($sql, $groupjoin->params); 1200 } 1201 1202 /** 1203 * Returns array with SQL and parameters returning userids and concatenated group names for given course 1204 * 1205 * This function uses 'gn[0-9]+_' prefix for table names and parameters 1206 * 1207 * @param int $courseid 1208 * @param string $separator 1209 * @return array [$sql, $params] 1210 */ 1211 function groups_get_names_concat_sql(int $courseid, string $separator = ', '): array { 1212 global $DB; 1213 1214 // Use unique prefix just in case somebody makes some SQL magic with the result. 1215 static $i = 0; 1216 $i++; 1217 $prefix = "gn{$i}_"; 1218 1219 $groupalias = $prefix . 'g'; 1220 $groupmemberalias = $prefix . 'gm'; 1221 $groupcourseparam = $prefix . 'courseid'; 1222 1223 $sqlgroupconcat = $DB->sql_group_concat("{$groupalias}.name", $separator, "{$groupalias}.name"); 1224 1225 $sql = "SELECT {$groupmemberalias}.userid, {$sqlgroupconcat} AS groupnames 1226 FROM {groups} {$groupalias} 1227 JOIN {groups_members} {$groupmemberalias} ON {$groupmemberalias}.groupid = {$groupalias}.id 1228 WHERE {$groupalias}.courseid = :{$groupcourseparam} 1229 GROUP BY {$groupmemberalias}.userid"; 1230 1231 return [$sql, [$groupcourseparam => $courseid]]; 1232 }; 1233 1234 /** 1235 * Get sql join to return users in a group 1236 * 1237 * @param int|array $groupids The groupids, 0 or [] means all groups and USERSWITHOUTGROUP no group 1238 * @param string $useridcolumn The column of the user id from the calling SQL, e.g. u.id 1239 * @param context $context Course context or a context within a course. Mandatory when $groupids includes USERSWITHOUTGROUP 1240 * @param int $jointype Join type logic used. Defaults to 'Any' (logical OR). 1241 * @return \core\dml\sql_join Contains joins, wheres, params 1242 * @throws coding_exception if empty or invalid context submitted when $groupid = USERSWITHOUTGROUP 1243 */ 1244 function groups_get_members_join($groupids, $useridcolumn, context $context = null, int $jointype = GROUPS_JOIN_ANY) { 1245 global $DB; 1246 1247 // Use unique prefix just in case somebody makes some SQL magic with the result. 1248 static $i = 0; 1249 $i++; 1250 $prefix = 'gm' . $i . '_'; 1251 1252 if (!is_array($groupids)) { 1253 $groupids = $groupids ? [$groupids] : []; 1254 } 1255 1256 $joins = []; 1257 $where = ''; 1258 $param = []; 1259 1260 $coursecontext = (!empty($context)) ? $context->get_course_context() : null; 1261 if (in_array(USERSWITHOUTGROUP, $groupids) && empty($coursecontext)) { 1262 // Throw an exception if $context is empty or invalid because it's needed to get the users without any group. 1263 throw new coding_exception('Missing or wrong $context parameter in an attempt to get members without any group'); 1264 } 1265 // Can we view hidden groups within a course? 1266 [$ualias] = explode('.', $useridcolumn); 1267 $viewhidden = false; 1268 if (!empty($coursecontext)) { 1269 $viewhidden = \core_group\visibility::can_view_all_groups($coursecontext->instanceid); 1270 } 1271 1272 // Handle cases where we need to include/exclude users not in any groups. 1273 if (($nogroupskey = array_search(USERSWITHOUTGROUP, $groupids)) !== false) { 1274 $visibilityjoin = ''; 1275 $visibilitywhere = ''; 1276 1277 if (!$viewhidden) { 1278 $visibilityjoin = 'JOIN {user} u ON u.id = m.userid'; 1279 [$visibilitywhere, $visibilityparams] = \core_group\visibility::sql_member_visibility_where('g', 'm'); 1280 $param = array_merge($param, $visibilityparams); 1281 $visibilitywhere = 'WHERE ' . $visibilitywhere; 1282 } 1283 // Get members without any group, or only in groups we cannot see membership of. 1284 $joins[] = "LEFT JOIN ( 1285 SELECT g.courseid, m.groupid, m.userid 1286 FROM {groups_members} m 1287 JOIN {groups} g ON g.id = m.groupid 1288 {$visibilityjoin} 1289 {$visibilitywhere} 1290 ) {$prefix}gm ON ({$prefix}gm.userid = {$useridcolumn} AND {$prefix}gm.courseid = :{$prefix}gcourseid)"; 1291 1292 // Join type 'None' when filtering by 'no groups' means match users in at least one group. 1293 if ($jointype == GROUPS_JOIN_NONE) { 1294 $where = "{$prefix}gm.userid IS NOT NULL"; 1295 } else { 1296 // All other cases need to match users not in any group. 1297 $where = "{$prefix}gm.userid IS NULL"; 1298 } 1299 1300 $param["{$prefix}gcourseid"] = $coursecontext->instanceid; 1301 unset($groupids[$nogroupskey]); 1302 } 1303 1304 // Handle any specified groups that need to be included. 1305 if (!empty($groupids)) { 1306 switch ($jointype) { 1307 case GROUPS_JOIN_ALL: 1308 // Handle matching all of the provided groups (logical AND). 1309 $joinallwheres = []; 1310 $aliaskey = 0; 1311 foreach ($groupids as $groupid) { 1312 $gmalias = "{$prefix}gm{$aliaskey}"; 1313 $aliaskey++; 1314 $joins[] = "LEFT JOIN {groups_members} {$gmalias} 1315 ON ({$gmalias}.userid = {$useridcolumn} AND {$gmalias}.groupid = :{$gmalias}param)"; 1316 $joinallwheres[] = "{$gmalias}.userid IS NOT NULL"; 1317 $param["{$gmalias}param"] = $groupid; 1318 if (!$viewhidden) { 1319 $galias = "{$prefix}g{$aliaskey}"; 1320 $joins[] = "LEFT JOIN {groups} {$galias} ON {$gmalias}.groupid = {$galias}.id"; 1321 [$visibilitywhere, $visibilityparams] = \core_group\visibility::sql_member_visibility_where( 1322 $galias, 1323 $gmalias, 1324 $ualias, 1325 $prefix . $aliaskey . '_' 1326 ); 1327 $joinallwheres[] = $visibilitywhere; 1328 $param = array_merge($param, $visibilityparams); 1329 } 1330 } 1331 1332 // Members of all of the specified groups only. 1333 if (empty($where)) { 1334 $where = '(' . implode(' AND ', $joinallwheres) . ')'; 1335 } else { 1336 // Members of the specified groups and also no groups. 1337 // NOTE: This will always return no results, because you cannot be in specified groups and also be in no groups. 1338 $where = '(' . $where . ' AND ' . implode(' AND ', $joinallwheres) . ')'; 1339 } 1340 1341 break; 1342 1343 case GROUPS_JOIN_ANY: 1344 // Handle matching any of the provided groups (logical OR). 1345 list($groupssql, $groupsparams) = $DB->get_in_or_equal($groupids, SQL_PARAMS_NAMED, $prefix); 1346 1347 $joins[] = "LEFT JOIN {groups_members} {$prefix}gm2 1348 ON ({$prefix}gm2.userid = {$useridcolumn} AND {$prefix}gm2.groupid {$groupssql})"; 1349 $param = array_merge($param, $groupsparams); 1350 1351 // Members of any of the specified groups only. 1352 if (empty($where)) { 1353 $where = "{$prefix}gm2.userid IS NOT NULL"; 1354 } else { 1355 // Members of any of the specified groups or no groups. 1356 $where = "({$where} OR {$prefix}gm2.userid IS NOT NULL)"; 1357 } 1358 1359 if (!$viewhidden) { 1360 $joins[] = "LEFT JOIN {groups} {$prefix}g2 ON {$prefix}gm2.groupid = {$prefix}g2.id"; 1361 [$visibilitywhere, $visibilityparams] = \core_group\visibility::sql_member_visibility_where( 1362 $prefix . 'g2', 1363 $prefix . 'gm2', 1364 $ualias, 1365 $prefix . 'param_' 1366 ); 1367 $where .= ' AND ' . $visibilitywhere; 1368 $param = array_merge($param, $visibilityparams); 1369 } 1370 1371 break; 1372 1373 case GROUPS_JOIN_NONE: 1374 // Handle matching none of the provided groups (logical NOT). 1375 list($groupssql, $groupsparams) = $DB->get_in_or_equal($groupids, SQL_PARAMS_NAMED, $prefix); 1376 1377 $joins[] = "LEFT JOIN {groups_members} {$prefix}gm2 1378 ON ({$prefix}gm2.userid = {$useridcolumn} AND {$prefix}gm2.groupid {$groupssql})"; 1379 $param = array_merge($param, $groupsparams); 1380 1381 // Members of none of the specified groups only. 1382 if (empty($where)) { 1383 $where = "{$prefix}gm2.userid IS NULL"; 1384 } else { 1385 // Members of any unspecified groups (not a member of the specified groups, and not a member of no groups). 1386 $where = "({$where} AND {$prefix}gm2.userid IS NULL)"; 1387 } 1388 1389 if (!$viewhidden) { 1390 $joins[] = "LEFT JOIN {groups} {$prefix}g2 ON {$prefix}gm2.groupid = {$prefix}g2.id"; 1391 [$visibilitywhere, $visibilityparams] = \core_group\visibility::sql_member_visibility_where( 1392 $prefix . 'g2', 1393 $prefix . 'gm2', 1394 $ualias 1395 ); 1396 $where .= ' OR NOT ' . $visibilitywhere; 1397 $param = array_merge($param, $visibilityparams); 1398 } 1399 1400 break; 1401 } 1402 } 1403 1404 return new \core\dml\sql_join(implode("\n", $joins), $where, $param); 1405 } 1406 1407 /** 1408 * Internal method, sets up $SESSION->activegroup and verifies previous value 1409 * 1410 * @param int $courseid 1411 * @param int|string $groupmode SEPARATEGROUPS, VISIBLEGROUPS or 'aag' (access all groups) 1412 * @param int $groupingid 0 means all groups 1413 * @param array $allowedgroups list of groups user can see 1414 */ 1415 function _group_verify_activegroup($courseid, $groupmode, $groupingid, array $allowedgroups) { 1416 global $SESSION, $USER; 1417 1418 // init activegroup array if necessary 1419 if (!isset($SESSION->activegroup)) { 1420 $SESSION->activegroup = array(); 1421 } 1422 if (!array_key_exists($courseid, $SESSION->activegroup)) { 1423 $SESSION->activegroup[$courseid] = array(SEPARATEGROUPS=>array(), VISIBLEGROUPS=>array(), 'aag'=>array()); 1424 } 1425 1426 // make sure that the current group info is ok 1427 if (array_key_exists($groupingid, $SESSION->activegroup[$courseid][$groupmode]) and !array_key_exists($SESSION->activegroup[$courseid][$groupmode][$groupingid], $allowedgroups)) { 1428 // active group does not exist anymore or is 0 1429 if ($SESSION->activegroup[$courseid][$groupmode][$groupingid] > 0 or $groupmode == SEPARATEGROUPS) { 1430 // do not do this if all groups selected and groupmode is not separate 1431 unset($SESSION->activegroup[$courseid][$groupmode][$groupingid]); 1432 } 1433 } 1434 1435 // set up defaults if necessary 1436 if (!array_key_exists($groupingid, $SESSION->activegroup[$courseid][$groupmode])) { 1437 if ($groupmode == 'aag') { 1438 $SESSION->activegroup[$courseid][$groupmode][$groupingid] = 0; // all groups by default if user has accessallgroups 1439 1440 } else if ($allowedgroups) { 1441 if ($groupmode != SEPARATEGROUPS 1442 && $mygroups = groups_get_all_groups($courseid, $USER->id, $groupingid, 'g.*', false, true)) { 1443 $firstgroup = reset($mygroups); 1444 } else { 1445 $firstgroup = reset($allowedgroups); 1446 } 1447 $SESSION->activegroup[$courseid][$groupmode][$groupingid] = $firstgroup->id; 1448 1449 } else { 1450 // this happen when user not assigned into group in SEPARATEGROUPS mode or groups do not exist yet 1451 // mod authors must add extra checks for this when SEPARATEGROUPS mode used (such as when posting to forum) 1452 $SESSION->activegroup[$courseid][$groupmode][$groupingid] = 0; 1453 } 1454 } 1455 } 1456 1457 /** 1458 * Caches group data for a particular course to speed up subsequent requests. 1459 * 1460 * @param int $courseid The course id to cache data for. 1461 * @param cache $cache The cache if it has already been initialised. If not a new one will be created. 1462 * @return stdClass A data object containing groups, groupings, and mappings. 1463 */ 1464 function groups_cache_groupdata($courseid, cache $cache = null) { 1465 global $DB; 1466 1467 if ($cache === null) { 1468 // Initialise a cache if we wern't given one. 1469 $cache = cache::make('core', 'groupdata'); 1470 } 1471 1472 // Get the groups that belong to the course. 1473 $groups = $DB->get_records('groups', array('courseid' => $courseid), 'name ASC'); 1474 // Get the groupings that belong to the course. 1475 $groupings = $DB->get_records('groupings', array('courseid' => $courseid), 'name ASC'); 1476 1477 if (!is_array($groups)) { 1478 $groups = array(); 1479 } 1480 1481 if (!is_array($groupings)) { 1482 $groupings = array(); 1483 } 1484 1485 if (!empty($groupings)) { 1486 // Finally get the mappings between the two. 1487 list($insql, $params) = $DB->get_in_or_equal(array_keys($groupings)); 1488 $mappings = $DB->get_records_sql(" 1489 SELECT gg.id, gg.groupingid, gg.groupid 1490 FROM {groupings_groups} gg 1491 JOIN {groups} g ON g.id = gg.groupid 1492 WHERE gg.groupingid $insql 1493 ORDER BY g.name ASC", $params); 1494 } else { 1495 $mappings = array(); 1496 } 1497 1498 // Prepare the data array. 1499 $data = new stdClass; 1500 $data->groups = $groups; 1501 $data->groupings = $groupings; 1502 $data->mappings = $mappings; 1503 // Cache the data. 1504 $cache->set($courseid, $data); 1505 // Finally return it so it can be used if desired. 1506 return $data; 1507 } 1508 1509 /** 1510 * Gets group data for a course. 1511 * 1512 * This returns an object with the following properties: 1513 * - groups : An array of all the groups in the course. 1514 * - groupings : An array of all the groupings within the course. 1515 * - mappings : An array of group to grouping mappings. 1516 * 1517 * @param int $courseid The course id to get data for. 1518 * @param cache $cache The cache if it has already been initialised. If not a new one will be created. 1519 * @return stdClass 1520 */ 1521 function groups_get_course_data($courseid, cache $cache = null) { 1522 if ($cache === null) { 1523 // Initialise a cache if we wern't given one. 1524 $cache = cache::make('core', 'groupdata'); 1525 } 1526 // Try to retrieve it from the cache. 1527 $data = $cache->get($courseid); 1528 if ($data === false) { 1529 $data = groups_cache_groupdata($courseid, $cache); 1530 } 1531 return $data; 1532 } 1533 1534 /** 1535 * Determine if the current user can see at least one of the groups of the specified user. 1536 * 1537 * @param stdClass $course Course object. 1538 * @param int $userid user id to check against. 1539 * @param stdClass $cm Course module object. Optional, just for checking at activity level instead course one. 1540 * @return boolean true if visible, false otherwise 1541 * @since Moodle 2.9 1542 */ 1543 function groups_user_groups_visible($course, $userid, $cm = null) { 1544 global $USER; 1545 1546 $groupmode = empty($cm) ? groups_get_course_groupmode($course) : groups_get_activity_groupmode($cm, $course); 1547 if ($groupmode == NOGROUPS || $groupmode == VISIBLEGROUPS) { 1548 // Groups are not used, or everything is visible, no need to go any further. 1549 return true; 1550 } 1551 1552 $context = empty($cm) ? context_course::instance($course->id) : context_module::instance($cm->id); 1553 if (has_capability('moodle/site:accessallgroups', $context)) { 1554 // User can see everything. 1555 return true; 1556 } else { 1557 // Group mode is separate, and user doesn't have access all groups capability. 1558 if (empty($cm)) { 1559 $usergroups = groups_get_all_groups($course->id, $userid); 1560 $currentusergroups = groups_get_all_groups($course->id, $USER->id); 1561 } else { 1562 $usergroups = groups_get_activity_allowed_groups($cm, $userid); 1563 $currentusergroups = groups_get_activity_allowed_groups($cm, $USER->id); 1564 } 1565 1566 $samegroups = array_intersect_key($currentusergroups, $usergroups); 1567 if (!empty($samegroups)) { 1568 // We share groups! 1569 return true; 1570 } 1571 } 1572 return false; 1573 } 1574 1575 /** 1576 * Returns the users in the specified groups. 1577 * 1578 * This function does not return complete user objects by default. It returns the user_picture basic fields. 1579 * 1580 * @param array $groupsids The list of groups ids to check 1581 * @param array $extrafields extra fields to be included in result 1582 * @param int $sort optional sorting of returned users 1583 * @return array|bool Returns an array of the users for the specified group or false if no users or an error returned. 1584 * @since Moodle 3.3 1585 */ 1586 function groups_get_groups_members($groupsids, $extrafields=null, $sort='lastname ASC') { 1587 global $DB; 1588 1589 $wheres = []; 1590 $userfieldsapi = \core_user\fields::for_userpic()->including(...($extrafields ?? [])); 1591 $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; 1592 list($insql, $params) = $DB->get_in_or_equal($groupsids, SQL_PARAMS_NAMED); 1593 $wheres[] = "gm.groupid $insql"; 1594 1595 $courseids = $DB->get_fieldset_sql("SELECT DISTINCT courseid FROM {groups} WHERE id $insql", $params); 1596 1597 if (count($courseids) > 1) { 1598 // Groups from multiple courses. Have to check permission in system context. 1599 $context = context_system::instance(); 1600 } else { 1601 $courseid = reset($courseids); 1602 $context = context_course::instance($courseid); 1603 } 1604 1605 if (!has_capability('moodle/course:viewhiddengroups', $context)) { 1606 list($visibilitywhere, $visibilityparams) = \core_group\visibility::sql_member_visibility_where(); 1607 $params = array_merge($params, $visibilityparams); 1608 $wheres[] = $visibilitywhere; 1609 } 1610 1611 $where = implode(' AND ', $wheres); 1612 return $DB->get_records_sql("SELECT $userfields 1613 FROM {user} u 1614 JOIN {groups_members} gm ON u.id = gm.userid 1615 JOIN {groups} g ON g.id = gm.groupid 1616 WHERE {$where} 1617 GROUP BY $userfields 1618 ORDER BY $sort", $params); 1619 } 1620 1621 /** 1622 * Returns users who share group membership with the specified user in the given actiivty. 1623 * 1624 * @param stdClass|cm_info $cm course module 1625 * @param int $userid user id (empty for current user) 1626 * @return array a list of user 1627 * @since Moodle 3.3 1628 */ 1629 function groups_get_activity_shared_group_members($cm, $userid = null) { 1630 global $USER; 1631 1632 if (empty($userid)) { 1633 $userid = $USER->id; 1634 } 1635 1636 $groupsids = array_keys(groups_get_activity_allowed_groups($cm, $userid)); 1637 // No groups no users. 1638 if (empty($groupsids)) { 1639 return []; 1640 } 1641 return groups_get_groups_members($groupsids); 1642 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body