lib.php 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099
  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Extra library for groups and groupings.
  18. *
  19. * @copyright 2006 The Open University, J.White AT open.ac.uk, Petr Skoda (skodak)
  20. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  21. * @package core_group
  22. */
  23. /*
  24. * INTERNAL FUNCTIONS - to be used by moodle core only
  25. * require_once $CFG->dirroot.'/group/lib.php' must be used
  26. */
  27. /**
  28. * Adds a specified user to a group
  29. *
  30. * @param mixed $grouporid The group id or group object
  31. * @param mixed $userorid The user id or user object
  32. * @param string $component Optional component name e.g. 'enrol_imsenterprise'
  33. * @param int $itemid Optional itemid associated with component
  34. * @return bool True if user added successfully or the user is already a
  35. * member of the group, false otherwise.
  36. */
  37. function groups_add_member($grouporid, $userorid, $component=null, $itemid=0) {
  38. global $DB;
  39. if (is_object($userorid)) {
  40. $userid = $userorid->id;
  41. $user = $userorid;
  42. if (!isset($user->deleted)) {
  43. $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
  44. }
  45. } else {
  46. $userid = $userorid;
  47. $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
  48. }
  49. if ($user->deleted) {
  50. return false;
  51. }
  52. if (is_object($grouporid)) {
  53. $groupid = $grouporid->id;
  54. $group = $grouporid;
  55. } else {
  56. $groupid = $grouporid;
  57. $group = $DB->get_record('groups', array('id'=>$groupid), '*', MUST_EXIST);
  58. }
  59. // Check if the user a participant of the group course.
  60. $context = context_course::instance($group->courseid);
  61. if (!is_enrolled($context, $userid)) {
  62. return false;
  63. }
  64. if (groups_is_member($groupid, $userid)) {
  65. return true;
  66. }
  67. $member = new stdClass();
  68. $member->groupid = $groupid;
  69. $member->userid = $userid;
  70. $member->timeadded = time();
  71. $member->component = '';
  72. $member->itemid = 0;
  73. // Check the component exists if specified
  74. if (!empty($component)) {
  75. $dir = core_component::get_component_directory($component);
  76. if ($dir && is_dir($dir)) {
  77. // Component exists and can be used
  78. $member->component = $component;
  79. $member->itemid = $itemid;
  80. } else {
  81. throw new coding_exception('Invalid call to groups_add_member(). An invalid component was specified');
  82. }
  83. }
  84. if ($itemid !== 0 && empty($member->component)) {
  85. // An itemid can only be specified if a valid component was found
  86. throw new coding_exception('Invalid call to groups_add_member(). A component must be specified if an itemid is given');
  87. }
  88. $DB->insert_record('groups_members', $member);
  89. // Update group info, and group object.
  90. $DB->set_field('groups', 'timemodified', $member->timeadded, array('id'=>$groupid));
  91. $group->timemodified = $member->timeadded;
  92. // Trigger group event.
  93. $params = array(
  94. 'context' => $context,
  95. 'objectid' => $groupid,
  96. 'relateduserid' => $userid,
  97. 'other' => array(
  98. 'component' => $member->component,
  99. 'itemid' => $member->itemid
  100. )
  101. );
  102. $event = \core\event\group_member_added::create($params);
  103. $event->add_record_snapshot('groups', $group);
  104. $event->trigger();
  105. return true;
  106. }
  107. /**
  108. * Checks whether the current user is permitted (using the normal UI) to
  109. * remove a specific group member, assuming that they have access to remove
  110. * group members in general.
  111. *
  112. * For automatically-created group member entries, this checks with the
  113. * relevant plugin to see whether it is permitted. The default, if the plugin
  114. * doesn't provide a function, is true.
  115. *
  116. * For other entries (and any which have already been deleted/don't exist) it
  117. * just returns true.
  118. *
  119. * @param mixed $grouporid The group id or group object
  120. * @param mixed $userorid The user id or user object
  121. * @return bool True if permitted, false otherwise
  122. */
  123. function groups_remove_member_allowed($grouporid, $userorid) {
  124. global $DB;
  125. if (is_object($userorid)) {
  126. $userid = $userorid->id;
  127. } else {
  128. $userid = $userorid;
  129. }
  130. if (is_object($grouporid)) {
  131. $groupid = $grouporid->id;
  132. } else {
  133. $groupid = $grouporid;
  134. }
  135. // Get entry
  136. if (!($entry = $DB->get_record('groups_members',
  137. array('groupid' => $groupid, 'userid' => $userid), '*', IGNORE_MISSING))) {
  138. // If the entry does not exist, they are allowed to remove it (this
  139. // is consistent with groups_remove_member below).
  140. return true;
  141. }
  142. // If the entry does not have a component value, they can remove it
  143. if (empty($entry->component)) {
  144. return true;
  145. }
  146. // It has a component value, so we need to call a plugin function (if it
  147. // exists); the default is to allow removal
  148. return component_callback($entry->component, 'allow_group_member_remove',
  149. array($entry->itemid, $entry->groupid, $entry->userid), true);
  150. }
  151. /**
  152. * Deletes the link between the specified user and group.
  153. *
  154. * @param mixed $grouporid The group id or group object
  155. * @param mixed $userorid The user id or user object
  156. * @return bool True if deletion was successful, false otherwise
  157. */
  158. function groups_remove_member($grouporid, $userorid) {
  159. global $DB;
  160. if (is_object($userorid)) {
  161. $userid = $userorid->id;
  162. } else {
  163. $userid = $userorid;
  164. }
  165. if (is_object($grouporid)) {
  166. $groupid = $grouporid->id;
  167. $group = $grouporid;
  168. } else {
  169. $groupid = $grouporid;
  170. $group = $DB->get_record('groups', array('id'=>$groupid), '*', MUST_EXIST);
  171. }
  172. if (!groups_is_member($groupid, $userid)) {
  173. return true;
  174. }
  175. $DB->delete_records('groups_members', array('groupid'=>$groupid, 'userid'=>$userid));
  176. // Update group info.
  177. $time = time();
  178. $DB->set_field('groups', 'timemodified', $time, array('id' => $groupid));
  179. $group->timemodified = $time;
  180. // Trigger group event.
  181. $params = array(
  182. 'context' => context_course::instance($group->courseid),
  183. 'objectid' => $groupid,
  184. 'relateduserid' => $userid
  185. );
  186. $event = \core\event\group_member_removed::create($params);
  187. $event->add_record_snapshot('groups', $group);
  188. $event->trigger();
  189. return true;
  190. }
  191. /**
  192. * Add a new group
  193. *
  194. * @param stdClass $data group properties
  195. * @param stdClass $editform
  196. * @param array $editoroptions
  197. * @return id of group or false if error
  198. */
  199. function groups_create_group($data, $editform = false, $editoroptions = false) {
  200. global $CFG, $DB;
  201. //check that courseid exists
  202. $course = $DB->get_record('course', array('id' => $data->courseid), '*', MUST_EXIST);
  203. $context = context_course::instance($course->id);
  204. $data->timecreated = time();
  205. $data->timemodified = $data->timecreated;
  206. $data->name = trim($data->name);
  207. if (isset($data->idnumber)) {
  208. $data->idnumber = trim($data->idnumber);
  209. if (groups_get_group_by_idnumber($course->id, $data->idnumber)) {
  210. throw new moodle_exception('idnumbertaken');
  211. }
  212. }
  213. if ($editform and $editoroptions) {
  214. $data->description = $data->description_editor['text'];
  215. $data->descriptionformat = $data->description_editor['format'];
  216. }
  217. $data->id = $DB->insert_record('groups', $data);
  218. if ($editform and $editoroptions) {
  219. // Update description from editor with fixed files
  220. $data = file_postupdate_standard_editor($data, 'description', $editoroptions, $context, 'group', 'description', $data->id);
  221. $upd = new stdClass();
  222. $upd->id = $data->id;
  223. $upd->description = $data->description;
  224. $upd->descriptionformat = $data->descriptionformat;
  225. $DB->update_record('groups', $upd);
  226. }
  227. $group = $DB->get_record('groups', array('id'=>$data->id));
  228. if ($editform) {
  229. groups_update_group_icon($group, $data, $editform);
  230. }
  231. // Invalidate the grouping cache for the course
  232. cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($course->id));
  233. // Trigger group event.
  234. $params = array(
  235. 'context' => $context,
  236. 'objectid' => $group->id
  237. );
  238. $event = \core\event\group_created::create($params);
  239. $event->add_record_snapshot('groups', $group);
  240. $event->trigger();
  241. return $group->id;
  242. }
  243. /**
  244. * Add a new grouping
  245. *
  246. * @param stdClass $data grouping properties
  247. * @param array $editoroptions
  248. * @return id of grouping or false if error
  249. */
  250. function groups_create_grouping($data, $editoroptions=null) {
  251. global $DB;
  252. $data->timecreated = time();
  253. $data->timemodified = $data->timecreated;
  254. $data->name = trim($data->name);
  255. if (isset($data->idnumber)) {
  256. $data->idnumber = trim($data->idnumber);
  257. if (groups_get_grouping_by_idnumber($data->courseid, $data->idnumber)) {
  258. throw new moodle_exception('idnumbertaken');
  259. }
  260. }
  261. if ($editoroptions !== null) {
  262. $data->description = $data->description_editor['text'];
  263. $data->descriptionformat = $data->description_editor['format'];
  264. }
  265. $id = $DB->insert_record('groupings', $data);
  266. $data->id = $id;
  267. if ($editoroptions !== null) {
  268. $description = new stdClass;
  269. $description->id = $data->id;
  270. $description->description_editor = $data->description_editor;
  271. $description = file_postupdate_standard_editor($description, 'description', $editoroptions, $editoroptions['context'], 'grouping', 'description', $description->id);
  272. $DB->update_record('groupings', $description);
  273. }
  274. // Invalidate the grouping cache for the course
  275. cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($data->courseid));
  276. // Trigger group event.
  277. $params = array(
  278. 'context' => context_course::instance($data->courseid),
  279. 'objectid' => $id
  280. );
  281. $event = \core\event\grouping_created::create($params);
  282. $event->trigger();
  283. return $id;
  284. }
  285. /**
  286. * Update the group icon from form data
  287. *
  288. * @param stdClass $group group information
  289. * @param stdClass $data
  290. * @param stdClass $editform
  291. */
  292. function groups_update_group_icon($group, $data, $editform) {
  293. global $CFG, $DB;
  294. require_once("$CFG->libdir/gdlib.php");
  295. $fs = get_file_storage();
  296. $context = context_course::instance($group->courseid, MUST_EXIST);
  297. $newpicture = $group->picture;
  298. if (!empty($data->deletepicture)) {
  299. $fs->delete_area_files($context->id, 'group', 'icon', $group->id);
  300. $newpicture = 0;
  301. } else if ($iconfile = $editform->save_temp_file('imagefile')) {
  302. if ($rev = process_new_icon($context, 'group', 'icon', $group->id, $iconfile)) {
  303. $newpicture = $rev;
  304. } else {
  305. $fs->delete_area_files($context->id, 'group', 'icon', $group->id);
  306. $newpicture = 0;
  307. }
  308. @unlink($iconfile);
  309. }
  310. if ($newpicture != $group->picture) {
  311. $DB->set_field('groups', 'picture', $newpicture, array('id' => $group->id));
  312. $group->picture = $newpicture;
  313. // Invalidate the group data as we've updated the group record.
  314. cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($group->courseid));
  315. }
  316. }
  317. /**
  318. * Update group
  319. *
  320. * @param stdClass $data group properties (with magic quotes)
  321. * @param stdClass $editform
  322. * @param array $editoroptions
  323. * @return bool true or exception
  324. */
  325. function groups_update_group($data, $editform = false, $editoroptions = false) {
  326. global $CFG, $DB;
  327. $context = context_course::instance($data->courseid);
  328. $data->timemodified = time();
  329. if (isset($data->name)) {
  330. $data->name = trim($data->name);
  331. }
  332. if (isset($data->idnumber)) {
  333. $data->idnumber = trim($data->idnumber);
  334. if (($existing = groups_get_group_by_idnumber($data->courseid, $data->idnumber)) && $existing->id != $data->id) {
  335. throw new moodle_exception('idnumbertaken');
  336. }
  337. }
  338. if ($editform and $editoroptions) {
  339. $data = file_postupdate_standard_editor($data, 'description', $editoroptions, $context, 'group', 'description', $data->id);
  340. }
  341. $DB->update_record('groups', $data);
  342. // Invalidate the group data.
  343. cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($data->courseid));
  344. $group = $DB->get_record('groups', array('id'=>$data->id));
  345. if ($editform) {
  346. groups_update_group_icon($group, $data, $editform);
  347. }
  348. // Trigger group event.
  349. $params = array(
  350. 'context' => $context,
  351. 'objectid' => $group->id
  352. );
  353. $event = \core\event\group_updated::create($params);
  354. $event->add_record_snapshot('groups', $group);
  355. $event->trigger();
  356. return true;
  357. }
  358. /**
  359. * Update grouping
  360. *
  361. * @param stdClass $data grouping properties (with magic quotes)
  362. * @param array $editoroptions
  363. * @return bool true or exception
  364. */
  365. function groups_update_grouping($data, $editoroptions=null) {
  366. global $DB;
  367. $data->timemodified = time();
  368. if (isset($data->name)) {
  369. $data->name = trim($data->name);
  370. }
  371. if (isset($data->idnumber)) {
  372. $data->idnumber = trim($data->idnumber);
  373. if (($existing = groups_get_grouping_by_idnumber($data->courseid, $data->idnumber)) && $existing->id != $data->id) {
  374. throw new moodle_exception('idnumbertaken');
  375. }
  376. }
  377. if ($editoroptions !== null) {
  378. $data = file_postupdate_standard_editor($data, 'description', $editoroptions, $editoroptions['context'], 'grouping', 'description', $data->id);
  379. }
  380. $DB->update_record('groupings', $data);
  381. // Invalidate the group data.
  382. cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($data->courseid));
  383. // Trigger group event.
  384. $params = array(
  385. 'context' => context_course::instance($data->courseid),
  386. 'objectid' => $data->id
  387. );
  388. $event = \core\event\grouping_updated::create($params);
  389. $event->trigger();
  390. return true;
  391. }
  392. /**
  393. * Delete a group best effort, first removing members and links with courses and groupings.
  394. * Removes group avatar too.
  395. *
  396. * @param mixed $grouporid The id of group to delete or full group object
  397. * @return bool True if deletion was successful, false otherwise
  398. */
  399. function groups_delete_group($grouporid) {
  400. global $CFG, $DB;
  401. require_once("$CFG->libdir/gdlib.php");
  402. if (is_object($grouporid)) {
  403. $groupid = $grouporid->id;
  404. $group = $grouporid;
  405. } else {
  406. $groupid = $grouporid;
  407. if (!$group = $DB->get_record('groups', array('id'=>$groupid))) {
  408. //silently ignore attempts to delete missing already deleted groups ;-)
  409. return true;
  410. }
  411. }
  412. // delete group calendar events
  413. $DB->delete_records('event', array('groupid'=>$groupid));
  414. //first delete usage in groupings_groups
  415. $DB->delete_records('groupings_groups', array('groupid'=>$groupid));
  416. //delete members
  417. $DB->delete_records('groups_members', array('groupid'=>$groupid));
  418. //group itself last
  419. $DB->delete_records('groups', array('id'=>$groupid));
  420. // Delete all files associated with this group
  421. $context = context_course::instance($group->courseid);
  422. $fs = get_file_storage();
  423. $fs->delete_area_files($context->id, 'group', 'description', $groupid);
  424. $fs->delete_area_files($context->id, 'group', 'icon', $groupid);
  425. // Invalidate the grouping cache for the course
  426. cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($group->courseid));
  427. // Trigger group event.
  428. $params = array(
  429. 'context' => $context,
  430. 'objectid' => $groupid
  431. );
  432. $event = \core\event\group_deleted::create($params);
  433. $event->add_record_snapshot('groups', $group);
  434. $event->trigger();
  435. return true;
  436. }
  437. /**
  438. * Delete grouping
  439. *
  440. * @param int $groupingorid
  441. * @return bool success
  442. */
  443. function groups_delete_grouping($groupingorid) {
  444. global $DB;
  445. if (is_object($groupingorid)) {
  446. $groupingid = $groupingorid->id;
  447. $grouping = $groupingorid;
  448. } else {
  449. $groupingid = $groupingorid;
  450. if (!$grouping = $DB->get_record('groupings', array('id'=>$groupingorid))) {
  451. //silently ignore attempts to delete missing already deleted groupings ;-)
  452. return true;
  453. }
  454. }
  455. //first delete usage in groupings_groups
  456. $DB->delete_records('groupings_groups', array('groupingid'=>$groupingid));
  457. // remove the default groupingid from course
  458. $DB->set_field('course', 'defaultgroupingid', 0, array('defaultgroupingid'=>$groupingid));
  459. // remove the groupingid from all course modules
  460. $DB->set_field('course_modules', 'groupingid', 0, array('groupingid'=>$groupingid));
  461. //group itself last
  462. $DB->delete_records('groupings', array('id'=>$groupingid));
  463. $context = context_course::instance($grouping->courseid);
  464. $fs = get_file_storage();
  465. $files = $fs->get_area_files($context->id, 'grouping', 'description', $groupingid);
  466. foreach ($files as $file) {
  467. $file->delete();
  468. }
  469. // Invalidate the grouping cache for the course
  470. cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($grouping->courseid));
  471. // Trigger group event.
  472. $params = array(
  473. 'context' => $context,
  474. 'objectid' => $groupingid
  475. );
  476. $event = \core\event\grouping_deleted::create($params);
  477. $event->add_record_snapshot('groupings', $grouping);
  478. $event->trigger();
  479. return true;
  480. }
  481. /**
  482. * Remove all users (or one user) from all groups in course
  483. *
  484. * @param int $courseid
  485. * @param int $userid 0 means all users
  486. * @param bool $unused - formerly $showfeedback, is no longer used.
  487. * @return bool success
  488. */
  489. function groups_delete_group_members($courseid, $userid=0, $unused=false) {
  490. global $DB, $OUTPUT;
  491. // Get the users in the course which are in a group.
  492. $sql = "SELECT gm.id as gmid, gm.userid, g.*
  493. FROM {groups_members} gm
  494. INNER JOIN {groups} g
  495. ON gm.groupid = g.id
  496. WHERE g.courseid = :courseid";
  497. $params = array();
  498. $params['courseid'] = $courseid;
  499. // Check if we want to delete a specific user.
  500. if ($userid) {
  501. $sql .= " AND gm.userid = :userid";
  502. $params['userid'] = $userid;
  503. }
  504. $rs = $DB->get_recordset_sql($sql, $params);
  505. foreach ($rs as $usergroup) {
  506. groups_remove_member($usergroup, $usergroup->userid);
  507. }
  508. $rs->close();
  509. // TODO MDL-41312 Remove events_trigger_legacy('groups_members_removed').
  510. // This event is kept here for backwards compatibility, because it cannot be
  511. // translated to a new event as it is wrong.
  512. $eventdata = new stdClass();
  513. $eventdata->courseid = $courseid;
  514. $eventdata->userid = $userid;
  515. events_trigger_legacy('groups_members_removed', $eventdata);
  516. return true;
  517. }
  518. /**
  519. * Remove all groups from all groupings in course
  520. *
  521. * @param int $courseid
  522. * @param bool $showfeedback
  523. * @return bool success
  524. */
  525. function groups_delete_groupings_groups($courseid, $showfeedback=false) {
  526. global $DB, $OUTPUT;
  527. $groupssql = "SELECT id FROM {groups} g WHERE g.courseid = ?";
  528. $results = $DB->get_recordset_select('groupings_groups', "groupid IN ($groupssql)",
  529. array($courseid), '', 'groupid, groupingid');
  530. foreach ($results as $result) {
  531. groups_unassign_grouping($result->groupingid, $result->groupid, false);
  532. }
  533. // Invalidate the grouping cache for the course
  534. cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($courseid));
  535. // TODO MDL-41312 Remove events_trigger_legacy('groups_groupings_groups_removed').
  536. // This event is kept here for backwards compatibility, because it cannot be
  537. // translated to a new event as it is wrong.
  538. events_trigger_legacy('groups_groupings_groups_removed', $courseid);
  539. // no need to show any feedback here - we delete usually first groupings and then groups
  540. return true;
  541. }
  542. /**
  543. * Delete all groups from course
  544. *
  545. * @param int $courseid
  546. * @param bool $showfeedback
  547. * @return bool success
  548. */
  549. function groups_delete_groups($courseid, $showfeedback=false) {
  550. global $CFG, $DB, $OUTPUT;
  551. $groups = $DB->get_recordset('groups', array('courseid' => $courseid));
  552. foreach ($groups as $group) {
  553. groups_delete_group($group);
  554. }
  555. // Invalidate the grouping cache for the course
  556. cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($courseid));
  557. // TODO MDL-41312 Remove events_trigger_legacy('groups_groups_deleted').
  558. // This event is kept here for backwards compatibility, because it cannot be
  559. // translated to a new event as it is wrong.
  560. events_trigger_legacy('groups_groups_deleted', $courseid);
  561. if ($showfeedback) {
  562. echo $OUTPUT->notification(get_string('deleted').' - '.get_string('groups', 'group'), 'notifysuccess');
  563. }
  564. return true;
  565. }
  566. /**
  567. * Delete all groupings from course
  568. *
  569. * @param int $courseid
  570. * @param bool $showfeedback
  571. * @return bool success
  572. */
  573. function groups_delete_groupings($courseid, $showfeedback=false) {
  574. global $DB, $OUTPUT;
  575. $groupings = $DB->get_recordset_select('groupings', 'courseid = ?', array($courseid));
  576. foreach ($groupings as $grouping) {
  577. groups_delete_grouping($grouping);
  578. }
  579. // Invalidate the grouping cache for the course.
  580. cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($courseid));
  581. // TODO MDL-41312 Remove events_trigger_legacy('groups_groupings_deleted').
  582. // This event is kept here for backwards compatibility, because it cannot be
  583. // translated to a new event as it is wrong.
  584. events_trigger_legacy('groups_groupings_deleted', $courseid);
  585. if ($showfeedback) {
  586. echo $OUTPUT->notification(get_string('deleted').' - '.get_string('groupings', 'group'), 'notifysuccess');
  587. }
  588. return true;
  589. }
  590. /* =================================== */
  591. /* various functions used by groups UI */
  592. /* =================================== */
  593. /**
  594. * Obtains a list of the possible roles that group members might come from,
  595. * on a course. Generally this includes only profile roles.
  596. *
  597. * @param context $context Context of course
  598. * @return Array of role ID integers, or false if error/none.
  599. */
  600. function groups_get_possible_roles($context) {
  601. $roles = get_profile_roles($context);
  602. return array_keys($roles);
  603. }
  604. /**
  605. * Gets potential group members for grouping
  606. *
  607. * @param int $courseid The id of the course
  608. * @param int $roleid The role to select users from
  609. * @param mixed $source restrict to cohort, grouping or group id
  610. * @param string $orderby The column to sort users by
  611. * @param int $notingroup restrict to users not in existing groups
  612. * @param bool $onlyactiveenrolments restrict to users who have an active enrolment in the course
  613. * @return array An array of the users
  614. */
  615. function groups_get_potential_members($courseid, $roleid = null, $source = null,
  616. $orderby = 'lastname ASC, firstname ASC',
  617. $notingroup = null, $onlyactiveenrolments = false) {
  618. global $DB;
  619. $context = context_course::instance($courseid);
  620. list($esql, $params) = get_enrolled_sql($context, '', 0, $onlyactiveenrolments);
  621. $notingroupsql = "";
  622. if ($notingroup) {
  623. // We want to eliminate users that are already associated with a course group.
  624. $notingroupsql = "u.id NOT IN (SELECT userid
  625. FROM {groups_members}
  626. WHERE groupid IN (SELECT id
  627. FROM {groups}
  628. WHERE courseid = :courseid))";
  629. $params['courseid'] = $courseid;
  630. }
  631. if ($roleid) {
  632. // We are looking for all users with this role assigned in this context or higher.
  633. list($relatedctxsql, $relatedctxparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true),
  634. SQL_PARAMS_NAMED,
  635. 'relatedctx');
  636. $params = array_merge($params, $relatedctxparams, array('roleid' => $roleid));
  637. $where = "WHERE u.id IN (SELECT userid
  638. FROM {role_assignments}
  639. WHERE roleid = :roleid AND contextid $relatedctxsql)";
  640. $where .= $notingroup ? "AND $notingroupsql" : "";
  641. } else if ($notingroup) {
  642. $where = "WHERE $notingroupsql";
  643. } else {
  644. $where = "";
  645. }
  646. $sourcejoin = "";
  647. if (is_int($source)) {
  648. $sourcejoin .= "JOIN {cohort_members} cm ON (cm.userid = u.id AND cm.cohortid = :cohortid) ";
  649. $params['cohortid'] = $source;
  650. } else {
  651. // Auto-create groups from an existing cohort membership.
  652. if (isset($source['cohortid'])) {
  653. $sourcejoin .= "JOIN {cohort_members} cm ON (cm.userid = u.id AND cm.cohortid = :cohortid) ";
  654. $params['cohortid'] = $source['cohortid'];
  655. }
  656. // Auto-create groups from an existing group membership.
  657. if (isset($source['groupid'])) {
  658. $sourcejoin .= "JOIN {groups_members} gp ON (gp.userid = u.id AND gp.groupid = :groupid) ";
  659. $params['groupid'] = $source['groupid'];
  660. }
  661. // Auto-create groups from an existing grouping membership.
  662. if (isset($source['groupingid'])) {
  663. $sourcejoin .= "JOIN {groupings_groups} gg ON gg.groupingid = :groupingid ";
  664. $sourcejoin .= "JOIN {groups_members} gm ON (gm.userid = u.id AND gm.groupid = gg.groupid) ";
  665. $params['groupingid'] = $source['groupingid'];
  666. }
  667. }
  668. $allusernamefields = get_all_user_name_fields(true, 'u');
  669. $sql = "SELECT DISTINCT u.id, u.username, $allusernamefields, u.idnumber
  670. FROM {user} u
  671. JOIN ($esql) e ON e.id = u.id
  672. $sourcejoin
  673. $where
  674. ORDER BY $orderby";
  675. return $DB->get_records_sql($sql, $params);
  676. }
  677. /**
  678. * Parse a group name for characters to replace
  679. *
  680. * @param string $format The format a group name will follow
  681. * @param int $groupnumber The number of the group to be used in the parsed format string
  682. * @return string the parsed format string
  683. */
  684. function groups_parse_name($format, $groupnumber) {
  685. if (strstr($format, '@') !== false) { // Convert $groupnumber to a character series
  686. $letter = 'A';
  687. for($i=0; $i<$groupnumber; $i++) {
  688. $letter++;
  689. }
  690. $str = str_replace('@', $letter, $format);
  691. } else {
  692. $str = str_replace('#', $groupnumber+1, $format);
  693. }
  694. return($str);
  695. }
  696. /**
  697. * Assigns group into grouping
  698. *
  699. * @param int groupingid
  700. * @param int groupid
  701. * @param int $timeadded The time the group was added to the grouping.
  702. * @param bool $invalidatecache If set to true the course group cache will be invalidated as well.
  703. * @return bool true or exception
  704. */
  705. function groups_assign_grouping($groupingid, $groupid, $timeadded = null, $invalidatecache = true) {
  706. global $DB;
  707. if ($DB->record_exists('groupings_groups', array('groupingid'=>$groupingid, 'groupid'=>$groupid))) {
  708. return true;
  709. }
  710. $assign = new stdClass();
  711. $assign->groupingid = $groupingid;
  712. $assign->groupid = $groupid;
  713. if ($timeadded != null) {
  714. $assign->timeadded = (integer)$timeadded;
  715. } else {
  716. $assign->timeadded = time();
  717. }
  718. $DB->insert_record('groupings_groups', $assign);
  719. $courseid = $DB->get_field('groupings', 'courseid', array('id' => $groupingid));
  720. if ($invalidatecache) {
  721. // Invalidate the grouping cache for the course
  722. cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($courseid));
  723. }
  724. // Trigger event.
  725. $params = array(
  726. 'context' => context_course::instance($courseid),
  727. 'objectid' => $groupingid,
  728. 'other' => array('groupid' => $groupid)
  729. );
  730. $event = \core\event\grouping_group_assigned::create($params);
  731. $event->trigger();
  732. return true;
  733. }
  734. /**
  735. * Unassigns group from grouping
  736. *
  737. * @param int groupingid
  738. * @param int groupid
  739. * @param bool $invalidatecache If set to true the course group cache will be invalidated as well.
  740. * @return bool success
  741. */
  742. function groups_unassign_grouping($groupingid, $groupid, $invalidatecache = true) {
  743. global $DB;
  744. $DB->delete_records('groupings_groups', array('groupingid'=>$groupingid, 'groupid'=>$groupid));
  745. $courseid = $DB->get_field('groupings', 'courseid', array('id' => $groupingid));
  746. if ($invalidatecache) {
  747. // Invalidate the grouping cache for the course
  748. cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($courseid));
  749. }
  750. // Trigger event.
  751. $params = array(
  752. 'context' => context_course::instance($courseid),
  753. 'objectid' => $groupingid,
  754. 'other' => array('groupid' => $groupid)
  755. );
  756. $event = \core\event\grouping_group_unassigned::create($params);
  757. $event->trigger();
  758. return true;
  759. }
  760. /**
  761. * Lists users in a group based on their role on the course.
  762. * Returns false if there's an error or there are no users in the group.
  763. * Otherwise returns an array of role ID => role data, where role data includes:
  764. * (role) $id, $shortname, $name
  765. * $users: array of objects for each user which include the specified fields
  766. * Users who do not have a role are stored in the returned array with key '-'
  767. * and pseudo-role details (including a name, 'No role'). Users with multiple
  768. * roles, same deal with key '*' and name 'Multiple roles'. You can find out
  769. * which roles each has by looking in the $roles array of the user object.
  770. *
  771. * @param int $groupid
  772. * @param int $courseid Course ID (should match the group's course)
  773. * @param string $fields List of fields from user table prefixed with u, default 'u.*'
  774. * @param string $sort SQL ORDER BY clause, default (when null passed) is what comes from users_order_by_sql.
  775. * @param string $extrawheretest extra SQL conditions ANDed with the existing where clause.
  776. * @param array $whereorsortparams any parameters required by $extrawheretest (named parameters).
  777. * @return array Complex array as described above
  778. */
  779. function groups_get_members_by_role($groupid, $courseid, $fields='u.*',
  780. $sort=null, $extrawheretest='', $whereorsortparams=array()) {
  781. global $DB;
  782. // Retrieve information about all users and their roles on the course or
  783. // parent ('related') contexts
  784. $context = context_course::instance($courseid);
  785. // We are looking for all users with this role assigned in this context or higher.
  786. list($relatedctxsql, $relatedctxparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'relatedctx');
  787. if ($extrawheretest) {
  788. $extrawheretest = ' AND ' . $extrawheretest;
  789. }
  790. if (is_null($sort)) {
  791. list($sort, $sortparams) = users_order_by_sql('u');
  792. $whereorsortparams = array_merge($whereorsortparams, $sortparams);
  793. }
  794. $sql = "SELECT r.id AS roleid, u.id AS userid, $fields
  795. FROM {groups_members} gm
  796. JOIN {user} u ON u.id = gm.userid
  797. LEFT JOIN {role_assignments} ra ON (ra.userid = u.id AND ra.contextid $relatedctxsql)
  798. LEFT JOIN {role} r ON r.id = ra.roleid
  799. WHERE gm.groupid=:mgroupid
  800. ".$extrawheretest."
  801. ORDER BY r.sortorder, $sort";
  802. $whereorsortparams = array_merge($whereorsortparams, $relatedctxparams, array('mgroupid' => $groupid));
  803. $rs = $DB->get_recordset_sql($sql, $whereorsortparams);
  804. return groups_calculate_role_people($rs, $context);
  805. }
  806. /**
  807. * Internal function used by groups_get_members_by_role to handle the
  808. * results of a database query that includes a list of users and possible
  809. * roles on a course.
  810. *
  811. * @param moodle_recordset $rs The record set (may be false)
  812. * @param int $context ID of course context
  813. * @return array As described in groups_get_members_by_role
  814. */
  815. function groups_calculate_role_people($rs, $context) {
  816. global $CFG, $DB;
  817. if (!$rs) {
  818. return array();
  819. }
  820. $allroles = role_fix_names(get_all_roles($context), $context);
  821. // Array of all involved roles
  822. $roles = array();
  823. // Array of all retrieved users
  824. $users = array();
  825. // Fill arrays
  826. foreach ($rs as $rec) {
  827. // Create information about user if this is a new one
  828. if (!array_key_exists($rec->userid, $users)) {
  829. // User data includes all the optional fields, but not any of the
  830. // stuff we added to get the role details
  831. $userdata = clone($rec);
  832. unset($userdata->roleid);
  833. unset($userdata->roleshortname);
  834. unset($userdata->rolename);
  835. unset($userdata->userid);
  836. $userdata->id = $rec->userid;
  837. // Make an array to hold the list of roles for this user
  838. $userdata->roles = array();
  839. $users[$rec->userid] = $userdata;
  840. }
  841. // If user has a role...
  842. if (!is_null($rec->roleid)) {
  843. // Create information about role if this is a new one
  844. if (!array_key_exists($rec->roleid, $roles)) {
  845. $role = $allroles[$rec->roleid];
  846. $roledata = new stdClass();
  847. $roledata->id = $role->id;
  848. $roledata->shortname = $role->shortname;
  849. $roledata->name = $role->localname;
  850. $roledata->users = array();
  851. $roles[$roledata->id] = $roledata;
  852. }
  853. // Record that user has role
  854. $users[$rec->userid]->roles[$rec->roleid] = $roles[$rec->roleid];
  855. }
  856. }
  857. $rs->close();
  858. // Return false if there weren't any users
  859. if (count($users) == 0) {
  860. return false;
  861. }
  862. // Add pseudo-role for multiple roles
  863. $roledata = new stdClass();
  864. $roledata->name = get_string('multipleroles','role');
  865. $roledata->users = array();
  866. $roles['*'] = $roledata;
  867. $roledata = new stdClass();
  868. $roledata->name = get_string('noroles','role');
  869. $roledata->users = array();
  870. $roles[0] = $roledata;
  871. // Now we rearrange the data to store users by role
  872. foreach ($users as $userid=>$userdata) {
  873. $rolecount = count($userdata->roles);
  874. if ($rolecount == 0) {
  875. // does not have any roles
  876. $roleid = 0;
  877. } else if($rolecount > 1) {
  878. $roleid = '*';
  879. } else {
  880. $userrole = reset($userdata->roles);
  881. $roleid = $userrole->id;
  882. }
  883. $roles[$roleid]->users[$userid] = $userdata;
  884. }
  885. // Delete roles not used
  886. foreach ($roles as $key=>$roledata) {
  887. if (count($roledata->users)===0) {
  888. unset($roles[$key]);
  889. }
  890. }
  891. // Return list of roles containing their users
  892. return $roles;
  893. }
  894. /**
  895. * Synchronises enrolments with the group membership
  896. *
  897. * Designed for enrolment methods provide automatic synchronisation between enrolled users
  898. * and group membership, such as enrol_cohort and enrol_meta .
  899. *
  900. * @param string $enrolname name of enrolment method without prefix
  901. * @param int $courseid course id where sync needs to be performed (0 for all courses)
  902. * @param string $gidfield name of the field in 'enrol' table that stores group id
  903. * @return array Returns the list of removed and added users. Each record contains fields:
  904. * userid, enrolid, courseid, groupid, groupname
  905. */
  906. function groups_sync_with_enrolment($enrolname, $courseid = 0, $gidfield = 'customint2') {
  907. global $DB;
  908. $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
  909. $params = array(
  910. 'enrolname' => $enrolname,
  911. 'component' => 'enrol_'.$enrolname,
  912. 'courseid' => $courseid
  913. );
  914. $affectedusers = array(
  915. 'removed' => array(),
  916. 'added' => array()
  917. );
  918. // Remove invalid.
  919. $sql = "SELECT ue.userid, ue.enrolid, e.courseid, g.id AS groupid, g.name AS groupname
  920. FROM {groups_members} gm
  921. JOIN {groups} g ON (g.id = gm.groupid)
  922. JOIN {enrol} e ON (e.enrol = :enrolname AND e.courseid = g.courseid $onecourse)
  923. JOIN {user_enrolments} ue ON (ue.userid = gm.userid AND ue.enrolid = e.id)
  924. WHERE gm.component=:component AND gm.itemid = e.id AND g.id <> e.{$gidfield}";
  925. $rs = $DB->get_recordset_sql($sql, $params);
  926. foreach ($rs as $gm) {
  927. groups_remove_member($gm->groupid, $gm->userid);
  928. $affectedusers['removed'][] = $gm;
  929. }
  930. $rs->close();
  931. // Add missing.
  932. $sql = "SELECT ue.userid, ue.enrolid, e.courseid, g.id AS groupid, g.name AS groupname
  933. FROM {user_enrolments} ue
  934. JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = :enrolname $onecourse)
  935. JOIN {groups} g ON (g.courseid = e.courseid AND g.id = e.{$gidfield})
  936. JOIN {user} u ON (u.id = ue.userid AND u.deleted = 0)
  937. LEFT JOIN {groups_members} gm ON (gm.groupid = g.id AND gm.userid = ue.userid)
  938. WHERE gm.id IS NULL";
  939. $rs = $DB->get_recordset_sql($sql, $params);
  940. foreach ($rs as $ue) {
  941. groups_add_member($ue->groupid, $ue->userid, 'enrol_'.$enrolname, $ue->enrolid);
  942. $affectedusers['added'][] = $ue;
  943. }
  944. $rs->close();
  945. return $affectedusers;
  946. }