user = $userid; $data->group = $groupid; $data->role = $role; handle_event('userchangegrouprole', array( 'id' => $groupid, 'eventfor' => 'group', 'parentid' => $userid, 'parenttype' => 'user', 'rules' => $data) ); } /** * Returns whether a user is allowed to edit views in a given group * * @param mixed $group The ID of the group * @param int $userid The ID of the user * @returns boolean */ function group_user_can_edit_views($group, $userid=null) { // root user can always do whatever it wants $sysuser = get_record('usr', 'username', 'root'); if ($sysuser->id == $userid) { return true; } if (empty($userid) && !is_logged_in()) { return false; } $groupid = is_numeric($group) ? group_param_groupid($group) : intval($group->id); $userid = group_param_userid($userid); if ($role = group_user_access($groupid, $userid)) { return group_role_can_edit_views($group, $role); } return false; } function group_role_can_edit_views($group, $role) { if (empty($role)) { return false; } if ($role == 'admin') { return true; } if (is_numeric($group)) { $editroles = get_field('group', 'editroles', 'id', $group); } else if (!isset($group->editroles)) { $editroles = get_field('group', 'editroles', 'id', $group->id); } else { $editroles = $group->editroles; } if ($role == 'member') { return ($editroles == 'all' && group_within_edit_window($group)); } return $editroles != 'admin'; } /** * Determine if the current date/time is within the editable window of the * group if one is set. By default, a group admin is considered to be within * the window. * @param object $group the group to check * @param bool $admin_always whether the admin should be OK regardless of time * @param bool $tutor_always whether the tutor should be OK regardless of time */ function group_within_edit_window($group, $admin_always=true, $tutor_always=true) { if (is_numeric($group)) { $group = get_group_by_id($group, true); } if ($admin_always && group_user_access($group->id) == 'admin') { return true; } if ($tutor_always && group_user_access($group->id) == 'tutor') { return true; } $start = !empty($group->editwindowstart) ? strtotime($group->editwindowstart) : null; $end = !empty($group->editwindowend) ? strtotime($group->editwindowend) : null; $now = time(); return (empty($start) && empty($end)) || (!empty($start) && $now > $start && empty($end)) || (empty($start) && $now < $end && !empty($end)) || ($start < $now && $now < $end); } function group_role_can_moderate_views($group, $role) { static $moderatingroles = array(); if (empty($role)) { return false; } if ($role == 'admin') { return true; } if (!isset($moderatingroles[$group])) { $grouptype = get_field('group', 'grouptype', 'id', $group); safe_require('grouptype', $grouptype); $moderatingroles[$group] = call_static_method('GroupType' . ucfirst($grouptype), 'get_view_moderating_roles'); } return in_array($role, $moderatingroles[$group]); } /** * Returns whether a user is allowed to see the report * * @param obj $group The group object * @param str $role The role of the user * @returns boolean */ function group_role_can_access_report($group, $role) { global $USER; if (!$group->groupparticipationreports) { return false; } if (group_user_access($group->id) && ($role == 'admin' || $USER->get('admin') || $USER->is_institutional_admin() || $USER->is_institutional_staff())) { return true; } return false; } /** * Returns whether a user is allowed to assess views that have been submitted * to the given group. * * @param int $groupid ID of group * @param int $userid ID of user * @return boolean */ function group_user_can_assess_submitted_views($groupid, $userid) { $groupid = group_param_groupid($groupid); $userid = group_param_userid($userid); return get_field_sql(' SELECT r.see_submitted_views FROM {group_member} m INNER JOIN {group} g ON (m.group = g.id AND g.deleted = 0) INNER JOIN {grouptype_roles} r ON (g.grouptype = r.grouptype AND r.role = m.role) WHERE m.member = ? AND m.group = ?', array($userid, $groupid)); } // Functions for creation/deletion of groups, and adding/removing users to groups /** * Creates a group. * * All group creation should be done through this function, as the * implementation of group creation may change over time. * * @param array $data Data required to create the group. The following * key/value pairs can be specified: * * - name: The group name [required, must be unique] * - description: The group description [optional, defaults to empty string] * - grouptype: The grouptype for the new group. Must be an installed grouptype. * - open (jointype): anyone can join the group * - controlled (jointype): admin adds members; members cannot leave the group * - request: allows membership requests * - ctime: The unix timestamp of the time the group will be recorded as having * been created. Defaults to the current time. * - members: Array of users who should be in the group, structured like this: * array( * userid => role, * userid => role, * ... * ) * @return int The ID of the created group * @throws InvalidArgumentException * UserException * SystemException * NotFoundException * AccessDeniedException */ function group_create($data) { if (!is_array($data)) { throw new InvalidArgumentException("group_create: data must be an array, see the doc comment for this " . "function for details on its format"); } if (!isset($data['name'])) { throw new InvalidArgumentException("group_create: must specify a name for the group"); } if (get_records_sql_array('SELECT id FROM {group} WHERE LOWER(TRIM(name)) = ?', array(strtolower(trim($data['name']))))) { throw new UserException(get_string('groupalreadyexists', 'group') . ': ' . $data['name']); } if (!isset($data['grouptype']) || !in_array($data['grouptype'], group_get_grouptypes())) { throw new InvalidArgumentException("group_create: grouptype specified must be an installed grouptype"); } safe_require('grouptype', $data['grouptype']); if (!empty($data['open'])) { if (!empty($data['controlled'])) { throw new InvalidArgumentException("group_create: a group cannot have both open and controlled membership"); } if (!empty($data['request'])) { throw new InvalidArgumentException("group_create: open-membership groups don't accept membership requests"); } $jointype = 'open'; } else if (!empty($data['controlled'])) { $jointype = 'controlled'; } else { $jointype = 'approve'; } if (isset($data['jointype'])) { log_warn("group_create: ignoring supplied jointype"); } if (!isset($data['ctime'])) { $data['ctime'] = time(); } $data['ctime'] = db_format_timestamp($data['ctime']); $data['public'] = (isset($data['public'])) ? intval($data['public']) : 0; $data['hidden'] = (isset($data['hidden'])) ? intval($data['hidden']) : 0; $data['hidemembers'] = (isset($data['hidemembers'])) ? intval($data['hidemembers']) : 0; $data['hidemembersfrommembers'] = (isset($data['hidemembersfrommembers'])) ? intval($data['hidemembersfrommembers']) : 0; $data['groupparticipationreports'] = (isset($data['groupparticipationreports'])) ? intval($data['groupparticipationreports']) : 0; $data['usersautoadded'] = (isset($data['usersautoadded'])) ? intval($data['usersautoadded']) : 0; $data['quota'] = get_config_plugin('artefact', 'file', 'defaultgroupquota'); if (!empty($data['invitefriends']) && !empty($data['suggestfriends'])) { throw new InvalidArgumentException("group_create: a group cannot enable both invitefriends and suggestfriends"); } $data['invitefriends'] = (isset($data['invitefriends'])) ? intval($data['invitefriends']) : 0; $data['suggestfriends'] = (isset($data['suggestfriends'])) ? intval($data['suggestfriends']) : 0; if (!empty($data['shortname'])) { // make sure it is unique and is correct length $shortname = group_generate_shortname($data['shortname']); // If we want to retain the supplied shortname we need to make sure it can be done if (!empty($data['retainshortname'])) { if ($shortname != $data['shortname']) { if ($shortname == strtolower($data['shortname'])) { throw new UserException('group_create: The supplied short name \'' . $data['shortname'] . '\' needs to be lowercase, eg \'' . $shortname . '\'.'); } else { throw new UserException('group_create: The supplied short name \'' . $data['shortname'] . '\' is already taken. This shortname \'' . $shortname . '\' is available.'); } } } $data['shortname'] = $shortname; } else { // Create it from group name $data['shortname'] = group_generate_shortname($data['name']); } if (!empty($data['institution']) && $data['institution'] != 'mahara') { global $USER; if (!$USER->can_edit_institution($data['institution'], true)) { $data['institution'] = 'mahara'; } } else { $data['institution'] = 'mahara'; } if (get_config('cleanurls') && (!isset($data['urlid']) || strlen($data['urlid']) == 0)) { $data['urlid'] = generate_urlid($data['name'], get_config('cleanurlgroupdefault'), 3, 30); $data['urlid'] = group_get_new_homepage_urlid($data['urlid']); } // Need to make sure group has at least one member if (empty($data['members'])) { global $USER; $data['members'] = array($USER->get('id') => 'admin'); } if (!isset($data['submittableto'])) { $data['submittableto'] = $data['grouptype'] != 'standard'; } if (!isset($data['editroles'])) { $data['editroles'] = $data['grouptype'] == 'standard' ? 'all' : 'notmember'; } else if (!in_array($data['editroles'], array_keys(group_get_editroles_options()))) { throw new InvalidArgumentException("group_create: invalid option for page editroles setting"); } if (!isset($data['editwindowstart'])) { $data['editwindowstart'] = null; } if (!isset($data['editwindowend'])) { $data['editwindowend'] = null; } if (!isset($data['sendnow'])) { $data['sendnow'] = null; } db_begin(); $id = insert_record( 'group', (object) array( 'name' => $data['name'], 'description' => isset($data['description']) ? $data['description'] : null, 'urlid' => isset($data['urlid']) ? $data['urlid'] : null, 'grouptype' => $data['grouptype'], 'category' => isset($data['category']) && !empty($data['category']) ? intval($data['category']) : null, 'jointype' => $jointype, 'ctime' => $data['ctime'], 'mtime' => $data['ctime'], 'public' => $data['public'], 'usersautoadded' => $data['usersautoadded'], 'quota' => $data['quota'], 'institution' => !empty($data['institution']) ? $data['institution'] : null, 'shortname' => $data['shortname'], 'request' => isset($data['request']) ? intval($data['request']) : 0, 'submittableto' => intval($data['submittableto']), 'allowarchives' => (!empty($data['submittableto']) && !empty($data['allowarchives'])) ? intval($data['allowarchives']) : 0, 'editroles' => $data['editroles'], 'hidden' => $data['hidden'], 'hidemembers' => $data['hidemembers'], 'hidemembersfrommembers' => $data['hidemembersfrommembers'], 'groupparticipationreports' => $data['groupparticipationreports'], 'invitefriends' => $data['invitefriends'], 'suggestfriends' => $data['suggestfriends'], 'editwindowstart' => $data['editwindowstart'], 'editwindowend' => $data['editwindowend'], 'sendnow' => isset($data['sendnow']) ? $data['sendnow'] : null, 'viewnotify' => !empty($data['viewnotify']) ? $data['viewnotify'] : null, 'feedbacknotify' => isset($data['feedbacknotify']) ? $data['feedbacknotify'] : null, ), 'id', true ); foreach ($data['members'] as $userid => $role) { insert_record( 'group_member', (object) array( 'group' => $id, 'member' => $userid, 'role' => $role, 'ctime' => $data['ctime'], ) ); } // Copy views for the new group $artefactcopies = array(); $templates = get_records_sql_array(" SELECT v.id, v.title, v.description FROM {view} v INNER JOIN {view_autocreate_grouptype} vag ON vag.view = v.id LEFT JOIN {collection_view} cv ON v.id = cv.view WHERE vag.grouptype = 'standard' AND cv.view IS NULL", array()); if ($templates) { require_once(get_config('libroot') . 'view.php'); foreach ($templates as $template) { list($view) = View::create_from_template(array( 'group' => $id, 'title' => $template->title, 'description' => $template->description, ), $template->id, null, false, false, $artefactcopies); $view->set_access(array(array( 'type' => 'group', 'id' => $id, 'startdate' => null, 'stopdate' => null, 'role' => null ))); } } // Copy collections for the new group $templates = get_records_sql_array(" SELECT DISTINCT c.id, c.name FROM {view} v INNER JOIN {view_autocreate_grouptype} vag ON vag.view = v.id INNER JOIN {collection_view} cv ON v.id = cv.view INNER JOIN {collection} c ON cv.collection = c.id WHERE vag.grouptype = ?", array($data['grouptype'])); if ($templates) { require_once('collection.php'); foreach ($templates as $template) { Collection::create_from_template(array('group' => $id), $template->id, null, false, true); } } $data['id'] = $id; // install the homepage require_once('view.php'); if ($t = get_record('view', 'type', 'grouphomepage', 'template', View::SITE_TEMPLATE, 'institution', 'mahara')) { $template = new View($t->id, (array)$t); list($homepage) = View::create_from_template(array( 'group' => $id, 'title' => $template->get('title'), 'description' => $template->get('description'), 'type' => 'grouphomepage', ), $t->id, 0, false, false, $artefactcopies); } else { throw new NotFoundException("group_create: group homepage is not found"); } // If 'Allow submissions' is true we need to update the 'Group portfolios' block to show // the 'Submissions to this group' section by default if ($data['submittableto']) { $groupview = get_record_sql("SELECT bi.id, bi.configdata FROM {block_instance} bi INNER JOIN {view} v ON v.id = bi.view WHERE bi.blocktype = 'groupviews' AND v.id = ?", array($homepage->get('id'))); if ($groupview) { $configdata = unserialize($groupview->configdata); if (!isset($configdata['showsubmitted'])) { $configdata['showsubmitted'] = 1; set_field('block_instance', 'configdata', serialize($configdata), 'id', $groupview->id); } } } $newaccess = (object) array( 'view' => $homepage->get('id'), 'accesstype' => $data['public'] ? 'public' : 'loggedin', 'ctime' => db_format_timestamp(time()), ); $vaid = insert_record('view_access', $newaccess, 'id', true); handle_event('updateviewaccess', array( 'id' => $vaid, 'eventfor' => $data['public'] ? 'public' : 'loggedin', 'parentid' => $homepage->get('id'), 'parenttype' => 'view', 'rules' => $newaccess) ); handle_event('creategroup', $data); db_commit(); return $id; } /** * Update details of an existing group. * * @param array $new New values for the group table. * @param bool $create Create the group if it doesn't exist yet */ function group_update($new, $create=false) { if (!empty($new->id)) { $old = get_record_select('group', 'id = ? AND deleted = 0', array($new->id)); } else if (!empty($new->shortname)) { $old = get_record_select( 'group', 'shortname = ? AND deleted = 0', array($new->shortname) ); if (!$old && $create) { return group_create((array)$new); } } if (!$old) { throw new NotFoundException("group_update: group not found"); } if ( (isset($new->submittableto) && empty($new->submittableto)) || (!isset($new->submittableto) && empty($old->submittableto)) ) { $new->allowarchives = 0; } $update_blog_access = ($new->editroles != $old->editroles); foreach (array('id', 'grouptype', 'public', 'request', 'submittableto', 'allowarchives', 'editroles', 'hidden', 'hidemembers', 'hidemembersfrommembers', 'groupparticipationreports') as $f) { if (!isset($new->$f)) { $new->$f = $old->$f; } } if (isset($new->jointype)) { log_warn("group_update: ignoring supplied jointype"); unset($new->jointype); } // If the caller isn't trying to enable open/controlled, use the old values if (!isset($new->open)) { $new->open = empty($new->controlled) && $old->jointype == 'open'; } if (!isset($new->controlled)) { $new->controlled = empty($new->open) && $old->jointype == 'controlled'; } if ($new->open) { if ($new->controlled) { throw new InvalidArgumentException("group_update: a group cannot have both open and controlled membership"); } $new->request = 0; $new->jointype = 'open'; } else if ($new->controlled) { $new->jointype = 'controlled'; } else { $new->jointype = 'approve'; } unset($new->open); unset($new->controlled); // Ensure only one of invitefriends,suggestfriends gets enabled. if (!empty($new->invitefriends)) { $new->suggestfriends = 0; } else if (!isset($new->invitefriends)) { $new->invitefriends = (int) ($old->invitefriends && empty($new->suggestfriends)); } if (!isset($new->suggestfriends)) { $new->suggestfriends = $old->suggestfriends; } $diff = array_diff_assoc((array)$new, (array)$old); if (empty($diff)) { return null; } db_begin(); if (isset($new->members)) { group_update_members($new->id, $new->members); unset($new->members); } $new->mtime = db_format_timestamp(time()); update_record('group', $new, 'id'); // Add users who have requested membership of a group that's becoming // open if ($old->jointype != 'open' && $new->jointype == 'open') { $userids = get_column_sql(' SELECT u.id FROM {usr} u JOIN {group_member_request} r ON u.id = r.member WHERE r.group = ? AND u.deleted = 0', array($new->id) ); if ($userids) { foreach ($userids as $uid) { group_add_user($new->id, $uid); } } } // Invitations to controlled groups are allowed, but if the admin is // changing a group to controlled membership, we'll assume they want // want to revoke all the existing invitations. if ($old->jointype != 'controlled' && $new->jointype == 'controlled') { delete_records('group_member_invite', 'group', $new->id); } // Remove requests if ($old->request && !$new->request) { delete_records('group_member_request', 'group', $new->id); } // When the group type changes, make sure everyone has a valid role. safe_require('grouptype', $new->grouptype); $allowedroles = call_static_method('GroupType' . ucfirst($new->grouptype), 'get_roles'); set_field_select( 'group_member', 'role', 'member', '"group" = ? AND NOT role IN (' . join(',', array_fill(0, count($allowedroles), '?')) . ')', array_merge(array($new->id), $allowedroles) ); // When the group type changes, make sure the access for tutors // to the group artefacts are updated if ($old->grouptype != $new->grouptype) { if ($new->grouptype == 'course') { if ($ids = get_records_select_array('artefact', '"group" = ' . $new->id . ' AND artefacttype IN (\'blog\', \'blogpost\')', null, '', 'id')) { $access = ($old->editroles == 'all' || $old->editroles == 'notmember'); db_begin(); foreach ($ids as $i => $artefact) { insert_record('artefact_access_role', (object) array( 'artefact' => $artefact->id, 'role' => 'tutor', 'can_view' => 1, 'can_edit' => (int) $access, 'can_republish' => (int) $access, )); } db_commit(); } } else { //grouptype = standard $query = 'DELETE FROM {artefact_access_role} WHERE role = \'tutor\' AND artefact IN ( SELECT a.id FROM {artefact} a WHERE a.group = ? AND a.artefacttype IN (\'blog\', \'blogpost\') )'; execute_sql($query, array($new->id)); } } // When a group changes from public -> private or vice versa, set the // appropriate access permissions on the group homepage view. $homepageid = get_field('view', 'id', 'type', 'grouphomepage', 'group', $new->id); if ($old->public != $new->public) { if ($old->public && !$new->public) { delete_records('view_access', 'view', $homepageid, 'accesstype', 'public'); $newaccess = (object) array( 'view' => $homepageid, 'accesstype' => 'loggedin', 'ctime' => db_format_timestamp(time()), ); $vaid = insert_record('view_access', $newaccess, 'id', true); handle_event('updateviewaccess', array( 'id' => $vaid, 'eventfor' => 'loggedin', 'parentid' => $homepageid, 'parenttype' => 'view', 'rules' => $newaccess) ); } else if (!$old->public && $new->public) { delete_records('view_access', 'view', $homepageid, 'accesstype', 'loggedin'); $newaccess = (object) array( 'view' => $homepageid, 'accesstype' => 'public', 'ctime' => db_format_timestamp(time()), ); $vaid = insert_record('view_access', $newaccess, 'id', true); handle_event('updateviewaccess', array( 'id' => $vaid, 'eventfor' => 'public', 'parentid' => $homepageid, 'parenttype' => 'view', 'rules' => $newaccess) ); } } // When the create/edit permissions change, update permissions on journal and posts if ($update_blog_access) { $edit_access = array(); if ($old->editroles == 'all') { $edit_access['member'] = 0; } else if ($old->editroles == 'admin') { $edit_access['tutor'] = 1; } if ($new->editroles == 'all') { $edit_access['member'] = 1; } else if ($new->editroles == 'admin') { $edit_access['tutor'] = 0; } foreach ($edit_access as $role => $value) { $query = 'UPDATE {artefact_access_role} SET can_edit = ?, can_republish = ? WHERE role = \'' . $role . '\' AND artefact IN ( SELECT a.id FROM {artefact} a WHERE a.group = ? AND a.artefacttype IN (\'blog\', \'blogpost\') )'; execute_sql($query, array($value, $value, $new->id)); } } // If 'Allow submissions' is changed we need to update the 'Group portfolios' block // to reflect the change $cansubmitto = 0; if (isset($new->submittableto) && !empty($new->submittableto)) { $cansubmitto = 1; } $groupview = get_record_sql("SELECT bi.id, bi.configdata FROM {block_instance} bi INNER JOIN {view} v ON v.id = bi.view WHERE bi.blocktype = 'groupviews' AND v.id = ?", array($homepageid)); if ($groupview) { $configdata = unserialize($groupview->configdata); $configdata['showsubmitted'] = $cansubmitto; set_field('block_instance', 'configdata', serialize($configdata), 'id', $groupview->id); } db_commit(); return $diff; } /** * Fetch group records from the db and convert them to a format suitable * for passing into group_update(). * * @param array $ids List of group ids * * @return array of stdclass objects */ function group_get_groups_for_editing($ids=null) { if (empty($ids)) { return array(); } $ids = array_map('intval', $ids); $groups = get_records_select_array( 'group', 'id IN (' . join(',', array_fill(0, count($ids), '?')) . ') AND deleted = 0', $ids ); if (!$groups) { return array(); } foreach ($groups as &$g) { $g->open = (int) ($g->jointype == 'open'); $g->controlled = (int) ($g->jointype == 'controlled'); unset($g->jointype); } return $groups; } /** * Deletes a group. * * All group deleting should be done through this function, even though it is * simple. What is required to perform group deletion may change over time. * * @param int $groupid The group to delete * @param string $shortname shortname of the group * @param string $institution institution of the group * * {{@internal Maybe later we can have a group_can_be_deleted function if * necessary}} */ function group_delete($groupid, $shortname=null, $institution=null, $notifymembers=true) { global $USER; if (empty($groupid) && !empty($institution) && !is_null($shortname) && strlen($shortname)) { // External call to delete a group, check permission of $USER. if (!$USER->can_edit_institution($institution)) { throw new AccessDeniedException("group_delete: cannot delete a group in this institution"); } $group = get_record('group', 'shortname', $shortname, 'institution', $institution); } else { $groupid = group_param_groupid($groupid); $group = get_group_by_id($groupid, true); } db_begin(); // Leave the group_member table alone, it's needed for the deleted // group notification that's about to happen on cron. delete_records('group_member_invite', 'group', $group->id); delete_records('group_member_request', 'group', $group->id); delete_records('view_access', 'group', $group->id); // Delete embedded images in the group description require_once('embeddedimage.php'); EmbeddedImage::delete_embedded_images('group', $group->id); // Delete views owned by the group require_once(get_config('libroot') . 'view.php'); foreach (get_column('view', 'id', 'group', $group->id) as $viewid) { $view = new View($viewid); $view->delete(); } // Release collections submitted to the group require_once(get_config('libroot') . 'collection.php'); foreach (get_column('collection', 'id', 'submittedgroup', $group->id) as $collectionid) { $collection = new Collection($collectionid); $collection->release(); } // Release views submitted to the group foreach (get_column('view', 'id', 'submittedgroup', $group->id) as $viewid) { $view = new View($viewid); $view->release(); } // Delete artefacts require_once(get_config('docroot') . 'artefact/lib.php'); ArtefactType::delete_by_artefacttype(get_column('artefact', 'id', 'group', $group->id)); // Delete forums require_once(get_config('docroot') . 'interaction/lib.php'); foreach (get_column('interaction_instance', 'id', 'group', $group->id) as $forumid) { $forum = interaction_instance_from_id($forumid); $forum->delete(); } // Delete lti submissions to the group if they exist if (is_plugin_active('lti', 'module')) { foreach (get_column('lti_assessment', 'id', 'group', $group->id) as $assessmentid) { delete_records('lti_assessment_submission', 'ltiassessment', $assessmentid); } delete_records('lti_assessment', 'group', $group->id); } if ($notifymembers) { require_once('activity.php'); activity_occurred('groupmessage', array( 'group' => $group->id, 'deletedgroup' => true, 'strings' => (object) array( 'subject' => (object) array( 'key' => 'deletegroupnotificationsubject', 'section' => 'group', 'args' => array(hsc($group->name)), ), 'message' => (object) array( 'key' => 'deletegroupnotificationmessage', 'section' => 'group', 'args' => array(hsc($group->name), get_config('sitename')), ), ), )); } // make sure the group name + deleted suffix will fit within 128 chars $delete_name = $group->name; if (strlen($delete_name) > 100) { $delete_name = substr($delete_name, 0, 100) . '(...)'; } update_record('group', array( 'deleted' => 1, 'name' => $delete_name . '.deleted.' . time(), 'shortname' => null, 'institution' => null, 'category' => null, 'urlid' => null, 'mtime' => db_format_timestamp(time()), ), array( 'id' => $group->id, ) ); db_commit(); // Need to reset grouproles - normally done via group_remove_user() but we don't call // it due to notification reasons (see above) $USER->reset_grouproles(); } /** * Adds a member to a group. * * Doesn't do any jointype checking, that should be handled by the caller. * * TODO: it should though. We should probably have group_user_can_be_added * * @param int $groupid * @param int $userid * @param string $role */ function group_add_user($groupid, $userid, $role=null, $method='internal') { $groupid = group_param_groupid($groupid); $userid = group_param_userid($userid); $gm = new stdClass(); $gm->member = $userid; $gm->group = $groupid; $gm->ctime = db_format_timestamp(time()); if (!$role) { $role = get_field_sql('SELECT gt.defaultrole FROM {grouptype} gt, {group} g WHERE g.id = ? AND g.grouptype = gt.name', array($groupid)); } $gm->role = $role; $gm->method = $method; db_begin(); insert_record('group_member', $gm); delete_records('group_member_request', 'group', $groupid, 'member', $userid); delete_records('group_member_invite', 'group', $groupid, 'member', $userid); $gm->id = $gm->group; $gm->eventfor = 'group'; handle_event('userjoinsgroup', $gm); db_commit(); global $USER; $USER->reset_grouproles(); } /** * Checks whether a user is allowed to leave a group. * * This checks things like if they're the owner and the group membership type * * @param mixed $group DB record or ID of group to check * @param int $userid (optional, will default to logged in user) */ function group_user_can_leave($group, $userid=null) { global $USER; static $result; $userid = optional_userid($userid); if (is_numeric($group)) { if (!$group = get_group_by_id($group)) { return false; } } // Return cached value if we have it if (isset($result[$group->id][$userid])) { return $result[$group->id][$userid]; } if ($group->jointype == 'controlled' && group_user_access($group->id, $USER->get('id')) != 'admin') { return ($result[$group->id][$userid] = false); } if (group_is_only_admin($group->id, $userid)) { return ($result[$group->id][$userid] = false); } return ($result[$group->id][$userid] = true); } /** * Checks whether a user is allowed to change the group's configuration settings. * (via edit/group.php) * * @param int $groupid Group to check * @param int $userid User to check (default: current user) * @param boolean $refresh Whether to re-check the user's role in the database (default: false) */ function group_user_can_configure($groupid, $userid = null, $refresh = false) { if ('admin' === group_user_access($groupid, $userid, $refresh)) { return true; } else { return false; } } /** * Removes a user from a group. * * @param int $groupid ID of group * @param int $userid ID of user to remove */ function group_remove_user($groupid, $userid=null, $force=false) { // group_user_can_leave checks the validity of groupid and userid if (!$force && !group_user_can_leave($groupid, $userid)) { throw new AccessDeniedException(get_string('usercantleavegroup', 'group')); } $data = new stdClass(); $data->user = $userid; $data->group = $groupid; $data->role = get_field('group_member', 'role', 'group', $groupid, 'member', $userid); handle_event('userleavesgroup', $data); delete_records('group_member', 'group', $groupid, 'member', $userid); global $USER; $USER->reset_grouproles(); require_once(get_config('docroot') . 'interaction/lib.php'); $interactions = get_column('interaction_instance', 'id', 'group', $groupid); foreach ($interactions as $interaction) { interaction_instance_from_id($interaction)->interaction_remove_user($userid); } } /** * Completely update the membership of a group * * @param int $groupid ID of group * @param array $members list of members and roles, structured like this: * array( * userid => role, * userid => role, * ... * ) * @param lines_done Number of lines in the file that have been completed so far * @param num_lines Number of lines in the file (this and the previous values * are used to update the progress meter. */ function group_update_members($groupid, $members, $lines_done = 0, $num_lines = 0) { global $USER; $groupid = group_param_groupid($groupid); if (!$group = get_group_by_id($groupid)) { throw new NotFoundException("group_update_members: group not found: $groupid"); } if (($group->institution && !$USER->can_edit_institution($group->institution)) || (!$group->institution && !$USER->get('admin'))) { throw new AccessDeniedException("group_update_members: access denied"); } if (!is_array($members)) { throw new SystemException("group_update_members: members must be an array"); } $badroles = array_unique(array_diff($members, array_keys(group_get_role_info($groupid)))); if (!empty($badroles)) { throw new UserException("group_update_members: invalid role(s) specified for group $group->name (id $groupid): " . join(', ', $badroles)); } if (!in_array('admin', $members)) { $groupname = get_field('group', 'name', 'id', $groupid); throw new UserException("group_update_members: no group admins listed for group $group->name (id $groupid)"); } // Check the new members list for invalid users if (!empty($members)) { $userids = array_map('intval', array_keys($members)); if ($group->institution && $group->institution != 'mahara') { $gooduserids = get_column_sql(' SELECT usr FROM {usr_institution} WHERE usr IN (' . join(',', $userids) . ') AND institution = ?', array($group->institution) ); if ($baduserids = array_diff($userids, $gooduserids)) { if (!(count($baduserids) == 1 && $baduserids[0] == $USER->id)) { throw new UserException("group_update_members: some members are not in the institution $group->institution: " . join(',', $baduserids)); } } } else { $gooduserids = get_column_sql(' SELECT id FROM {usr} WHERE id IN (' . join(',', $userids) . ') AND deleted = 0', array() ); if ($baduserids = array_diff($userids, $gooduserids)) { throw new UserException("group_update_members: some new members do not exist: " . join(',', $baduserids)); } } } // Update group members $oldmembers = get_records_assoc('group_member', 'group', $groupid, '', 'member,role'); $added = 0; $removed = 0; $updated = 0; $to_add = count(array_diff_key($members, $oldmembers)); $to_remove = count(array_diff_key($oldmembers, $members)); $to_update = count($members) - $to_add; db_begin(); foreach ($members as $userid => $role) { if (!isset($oldmembers[$userid])) { group_add_user($groupid, $userid, $role); $added ++; if (!(($lines_done + $added) % 25)) { set_progress_info('uploadgroupmemberscsv', $num_lines + 9 * ($lines_done + count($members) * $added / ($to_add + $to_remove)), $num_lines * 10, get_string('committingchanges', 'admin')); } } else if ($oldmembers[$userid]->role != $role) { set_field('group_member', 'role', $role, 'group', $groupid, 'member', $userid); $updated ++; } } foreach (array_keys($oldmembers) as $userid) { if (!isset($members[$userid])) { group_remove_user($groupid, $userid, true); $removed ++; if (!(($lines_done + $added + $removed) % 25)) { set_progress_info('uploadgroupmemberscsv', $num_lines + 9 * ($lines_done + count($members) * ($added + $removed) / ($to_add + $to_remove)), $num_lines * 10, get_string('committingchanges', 'admin')); } } } db_commit(); if ($added == 0 && $removed == 0 && $updated == 0) { return null; } return array('added' => $added, 'removed' => $removed, 'updated' => $updated); } /** * Invite a user to a group. * * @param object $group group * @param object $userid User to invite * @param object $userfrom User sending the invitation */ function group_invite_user($group, $userid, $userfrom, $role='member', $delay=null) { $user = optional_userobj($userid); $data = new stdClass(); $data->group = $group->id; $data->member= $user->id; $data->ctime = db_format_timestamp(time()); $data->role = $role; ensure_record_exists('group_member_invite', $data, $data); $lang = get_user_language($user->id); require_once('activity.php'); $activitydata = array( 'users' => array($user->id), 'subject' => get_string_from_language($lang, 'invitetogroupsubject', 'group'), 'message' => get_string_from_language($lang, 'invitetogroupmessage', 'group', display_name($userfrom, $user), $group->name), 'url' => group_homepage_url($group, false), 'urltext' => $group->name, ); activity_occurred('maharamessage', $activitydata, null, null, $delay); } // Pieforms for various operations on groups /** * Form for users to join a given group */ function group_get_join_form($name, $groupid) { return pieform(array( 'name' => $name, 'successcallback' => 'joingroup_submit', 'autofocus' => false, 'elements' => array( 'btngroup' => array( 'type' => 'fieldset', 'class' => 'group-request btn-top-right btn-group btn-group-top', 'elements' => array( 'join' => array( 'type' => 'button', 'usebuttontag' => true, 'class' => 'btn-secondary', 'value' => ' ' . get_string('joingroup', 'group') ) ) ), 'group' => array( 'type' => 'hidden', 'value' => $groupid, 'sesskey' => true, ) ) )); } /** * Form for accepting/declining a group invite */ function group_get_accept_form($name, $groupid) { return pieform(array( 'name' => $name, 'renderer' => 'div', 'successcallback' => 'group_invite_submit', 'elements' => array( 'btngroup' => array( 'type' => 'fieldset', 'class' => 'group-request btn-top-right btn-group btn-group-top', 'elements' => array( 'accept' => array( 'type' => 'button', 'usebuttontag' => true, 'class' => 'btn-secondary form-as-button float-left', 'value' => ' ' . get_string('acceptinvitegroup', 'group') ), 'decline' => array( 'type' => 'button', 'usebuttontag' => true, 'class' => 'btn-secondary form-as-button float-left', 'value' => ' ' . get_string('declineinvitegroup', 'group') ) ), ), 'group' => array( 'type' => 'hidden', 'value' => $groupid ) ) )); } /** * Form for adding a user to a group */ function group_get_adduser_form($userid, $groupid) { return pieform(array( 'name' => 'adduser' . $userid, 'successcallback' => 'group_adduser_submit', 'renderer' => 'div', 'class' => 'form-as-button float-left', 'elements' => array( 'group' => array( 'type' => 'hidden', 'value' => $groupid, ), 'member' => array( 'type' => 'hidden', 'value' => $userid, ), 'adduser' => array( 'type' => 'hidden', 'value' => true, ), 'submit' => array( 'type' => 'button', 'usebuttontag' => true, 'class' => 'btn-secondary', 'value' => ' ' .get_string('add'), ), ), )); } /** * Form for removing a user from a group */ function group_get_removeuser_form($userid, $groupid) { return pieform(array( 'name' => 'removeuser' . $userid, 'validatecallback' => 'group_removeuser_validate', 'successcallback' => 'group_removeuser_submit', 'renderer' => 'div', 'class' => 'float-left', 'elements' => array( 'group' => array( 'type' => 'hidden', 'value' => $groupid, ), 'member' => array( 'type' => 'hidden', 'value' => $userid, ), 'removeuser' => array( 'type' => 'hidden', 'value' => true, ), 'submit' => array( 'type' => 'button', 'usebuttontag' => true, 'class' => 'btn-secondary', 'value' => '' . get_string('removefromgroup', 'group'), ), ), )); } /** * Form for denying request (request group) */ function group_get_denyuser_form($userid, $groupid) { return pieform(array( 'name' => 'denyuser' . $userid, 'successcallback' => 'group_denyuser_submit', 'renderer' => 'div', 'class' => 'form-as-button float-left', 'elements' => array( 'group' => array( 'type' => 'hidden', 'value' => $groupid, ), 'member' => array( 'type' => 'hidden', 'value' => $userid, ), 'denyuser' => array( 'type' => 'hidden', 'value' => true, ), 'submit' => array( 'type' => 'button', 'usebuttontag' => true, 'class' => 'btn-secondary', 'value' => '' . get_string('declinerequest', 'group'), ), ), )); } // Functions for handling submission of group related forms function joingroup_submit(Pieform $form, $values) { global $SESSION, $USER; group_add_user($values['group'], $USER->get('id')); $SESSION->add_ok_msg(get_string('joinedgroup', 'group')); redirect(group_homepage_url(get_group_by_id($values['group'], true))); } function group_invite_submit(Pieform $form, $values) { global $SESSION, $USER; $inviterecord = get_record('group_member_invite', 'member', $USER->get('id'), 'group', $values['group']); if ($inviterecord) { delete_records('group_member_invite', 'group', $values['group'], 'member', $USER->get('id')); if (isset($values['accept'])) { group_add_user($values['group'], $USER->get('id'), $inviterecord->role); $SESSION->add_ok_msg(get_string('groupinviteaccepted', 'group')); redirect( group_homepage_url(get_group_by_id($values['group'], true))); } else { $SESSION->add_ok_msg(get_string('groupinvitedeclined', 'group')); redirect('/group/index.php'); } } } function group_adduser_submit(Pieform $form, $values) { global $SESSION; $group = (int)$values['group']; if (group_user_access($group) != 'admin') { $SESSION->add_error_msg(get_string('accessdenied', 'error')); redirect('/group/members.php?id=' . $group . '&membershiptype=request'); } group_add_user($group, $values['member']); $SESSION->add_ok_msg(get_string('useradded', 'group')); if (count_records('group_member_request', 'group', $group)) { redirect('/group/members.php?id=' . $group . '&membershiptype=request'); } redirect('/group/members.php?id=' . $group); } /** * Denying request (request group) */ function group_denyuser_submit(Pieform $form, $values) { global $SESSION; $group = (int)$values['group']; if (group_user_access($group) != 'admin') { $SESSION->add_error_msg(get_string('accessdenied', 'error')); redirect('/group/members.php?id=' . $group . '&membershiptype=request'); } delete_records('group_member_request', 'group', $values['group'], 'member', $values['member']); $SESSION->add_ok_msg(get_string('declinerequestsuccess', 'group')); if (count_records('group_member_request', 'group', $group)) { redirect('/group/members.php?id=' . $group . '&membershiptype=request'); } redirect('/group/members.php?id=' . $group); } function group_removeuser_validate(Pieform $form, $values) { global $user, $group, $SESSION; if (!group_user_can_leave($values['group'], $values['member'])) { $form->set_error('submit', get_string('usercantleavegroup', 'group')); } } function group_removeuser_submit(Pieform $form, $values) { global $SESSION; $group = (int)$values['group']; if (group_user_access($group) != 'admin') { $SESSION->add_error_msg(get_string('accessdenied', 'error')); redirect('/group/members.php?id=' . $group); } group_remove_user($group, $values['member']); $SESSION->add_ok_msg(get_string('userremoved', 'group')); redirect('/group/members.php?id=' . $group); } /** * Form for submitting views to a group */ function group_view_submission_form($groupid) { global $USER; list($collections, $views) = View::get_views_and_collections($USER->get('id')); $viewoptions = $collectionoptions = array(); foreach ($collections as $c) { if (empty($c['submittedgroup']) && empty($c['submittedhost'])) { $collectionoptions['c:' . $c['id']] = $c['name']; } } foreach ($views as $v) { if ($v['type'] != 'profile' && empty($v['submittedgroup']) && empty($v['submittedhost'])) { $viewoptions['v:' . $v['id']] = $v['name']; } } $options = $optgroups = null; if (!empty($collectionoptions) && !empty($viewoptions)) { $optgroups = array( 'collections' => array( 'label' => get_string('Collections', 'collection'), 'options' => $collectionoptions, ), 'views' => array( 'label' => get_string('Views', 'view'), 'options' => $viewoptions, ), ); } else if (!empty($collectionoptions)) { $options = $collectionoptions; } else if (!empty($viewoptions)) { $options = $viewoptions; } else { return; } return pieform(array( 'name' => 'group_view_submission_form_' . $groupid, 'method' => 'post', 'renderer' => 'div', 'class' => 'form-inline', 'autofocus' => false, 'successcallback' => 'group_view_submission_form_submit', 'elements' => array( 'inputgroup' => array( 'type' => 'fieldset', 'class' => 'input-group', 'elements' => array( 'options' => array( 'type' => 'select', 'title' => get_string('forassessment1', 'view'), 'collapseifoneoption' => false, 'optgroups' => $optgroups, 'options' => $options, 'class' => 'forassessment text-inline text-small', ), 'submit' => array( 'type' => 'button', 'usebuttontag' => true, 'class' => 'btn-primary input-group-append ', 'value' => get_string('submit') ), ), ), 'group' => array( 'type' => 'hidden', 'value' => $groupid ), ), )); } function group_view_submission_form_submit(Pieform $form, $values) { if (substr($values['options'], 0, 2) == 'v:') { $viewid = substr($values['options'], 2); redirect('/view/submit.php?id=' . $viewid . '&group=' . $values['group'] . '&returnto=group'); } if (substr($values['options'], 0, 2) == 'c:') { $collectionid = substr($values['options'], 2); redirect('/view/submit.php?collection=' . $collectionid . '&group=' . $values['group'] . '&returnto=group'); } redirect('/group/view.php?id=' . $values['group']); } // Miscellaneous group related functions /** * Returns a list of user IDs who are admins for a group * * @param int ID of group * @return array */ function group_get_admin_ids($groupid) { return (array)get_column_sql("SELECT \"member\" FROM {group_member} WHERE \"group\" = ? AND \"role\" = 'admin'", array($groupid)); } /** * Gets information about what the roles in a given group are able to do * * @param int $groupid ID of group to get role information for * @return array */ function group_get_role_info($groupid) { $roles = get_records_sql_assoc('SELECT "role", see_submitted_views, gr.grouptype FROM {grouptype_roles} gr INNER JOIN {group} g ON g.grouptype = gr.grouptype WHERE g.id = ?', array($groupid)); foreach ($roles as $role) { $role->display = get_string($role->role, 'grouptype.'.$role->grouptype); $role->name = $role->role; } return $roles; } function group_display_name($groupid) { return hsc(get_field('group', 'name', 'id', $groupid)); } function group_display_role($groupid, $role) { $roles = group_get_role_info($groupid); return $roles[$role]->display; } function group_get_default_artefact_permissions($groupid) { $permissions = array(); $records = get_records_sql_array(' SELECT g.editroles, r.role FROM {grouptype_roles} r, {group} g WHERE g.grouptype = r.grouptype AND g.id = ?', array($groupid) ); foreach ($records as $r) { $canedit = $r->role == 'admin' || $r->editroles == 'all' || ($r->role != 'member' && $r->editroles != 'admin'); $permissions[$r->role] = (object) array( 'view' => true, 'edit' => $canedit, 'republish' => $canedit, ); } return $permissions; } // Retrieve a list of group admins function group_get_admins($groupids) { $groupids = array_map('intval', $groupids); if (empty($groupids)) { return array(); } $groupadmins = get_records_sql_array(' SELECT m.group, m.member, u.id, u.username, u.firstname, u.lastname, u.preferredname, u.email, u.profileicon, u.urlid FROM {group_member} m JOIN {usr} u ON u.id = m.member WHERE m.group IN (' . implode(',', db_array_to_ph($groupids)) . ") AND m.role = 'admin'", $groupids ); if (!$groupadmins) { $groupadmins = array(); } return $groupadmins; } /** * Sets up groups for display in group/index.php * * @param array $groups Initial group data, including the current user's * membership type in each group. See index.php for * the query to build this information. */ function group_prepare_usergroups_for_display($groups) { if (!$groups) { return; } $groupids = array_map(function($a) { return $a->id; }, $groups); $groupadmins = group_get_admins($groupids); $i = 0; foreach ($groups as $group) { $group->admins = array(); foreach ($groupadmins as $admin) { if ($admin->group == $group->id) { $group->admins[] = $admin; } } if ($group->membershiptype == 'member') { $group->canleave = group_user_can_leave($group->id); } else if ($group->membershiptype == 'invite') { $group->invite = group_get_accept_form('invite' . $i++, $group->id); } else if ($group->jointype == 'open') { $group->groupjoin = group_get_join_form('joingroup' . $i++, $group->id); } $showmembercount = !$group->hidemembersfrommembers && !$group->hidemembers || $group->membershiptype == 'member' && !$group->hidemembersfrommembers || $group->membershiptype == 'admin'; if (!$showmembercount) { unset($group->membercount); } $group->editwindow = group_format_editwindow($group); $group->settingsdescription = group_display_settings($group); $group->homeurl = group_homepage_url($group); } } /* * Formats the edit window of a group into human readable format. */ function group_format_editwindow($group) { $dateformat = 'strftimedatetime'; $editwindowstart = isset($group->editwindowstart) ? strtotime($group->editwindowstart) : null; $editwindowend = isset($group->editwindowend) ? strtotime($group->editwindowend) : null; if (empty($editwindowstart) && empty($editwindowend)) { $formatted = ""; } else if (!empty($editwindowstart) && empty($editwindowend)) { $formatted = get_string('editwindowfrom', 'group', format_date($editwindowstart, $dateformat)); } else if (empty($editwindowstart) && !empty($editwindowend)) { $formatted = get_string('editwindowuntil', 'group', format_date($editwindowend, $dateformat)); } else { $formatted = get_string('editwindowbetween', 'group', format_date($editwindowstart, $dateformat), format_date($editwindowend, $dateformat)); } return $formatted; } /* * Used by admin/groups/groups.php and admin/groups/groups.json.php for listing groups. */ function build_grouplist_html($query, $limit, $offset, &$count=null, $institution) { global $USER; $groups = search_group($query, $limit, $offset, 'all', '', $institution); $count = $groups['count']; if ($ids = array_map(function($a) { return intval($a->id); }, $groups['data'])) { $sumsql = "(m.role = 'admin')"; if (is_postgres()) { $sumsql .= '::int'; } // Member & admin counts $ids = join(',', $ids); $counts = get_records_sql_assoc(" SELECT m.group, COUNT(m.member) AS members, SUM($sumsql) AS admins FROM {group_member} m WHERE m.group IN ($ids) GROUP BY m.group", array() ); } foreach ($groups['data'] as &$group) { $group->visibility = $group->public ? get_string('Public', 'group') : get_string('Members', 'group'); $group->admins = empty($counts[$group->id]->admins) ? 0 : $counts[$group->id]->admins; $group->members = empty($counts[$group->id]->members) ? 0 : $counts[$group->id]->members; if (get_config('allowgroupcategories')) { $group->categorytitle = ($group->category) ? get_field('group_category', 'title', 'id', $group->category) : ''; } $group->homepage_url = group_homepage_url($group); $group->displayname = $group->name; $group->submitpages = $group->submittableto; $group->roles = $group->grouptype; $group->institutionname = get_field('institution', 'displayname', 'name', $group->institution); switch ($group->jointype) { case 'open': $group->open = 1; $group->controlled = 0; break; case 'controlled': $group->open = 0; $group->controlled = 1; break; case 'approve': default: $group->open = 0; $group->controlled = 0; break; } $group->quota = display_size($group->quota); } $smarty = smarty_core(); $smarty->assign('groups', $groups['data']); $data = array(); $data['tablerows'] = $smarty->fetch('admin/groups/groupsresults.tpl'); $pagination = build_pagination(array( 'id' => 'admgroupslist_pagination', 'datatable' => 'admgroupslist', 'url' => get_config('wwwroot') . 'admin/groups/groups.php' . (($query != '') ? '?query=' . urlencode($query) : ''), 'jsonscript' => 'admin/groups/groups.json.php', 'count' => $count, 'limit' => $limit, 'offset' => $offset, 'setlimit' => true, 'jumplinks' => 6, 'numbersincludeprevnext' => 2, 'resultcounttextsingular' => get_string('group', 'group'), 'resultcounttextplural' => get_string('groups', 'group'), )); $data['pagination'] = $pagination['html']; $data['pagination_js'] = $pagination['javascript']; $csvfields = group_get_allowed_group_csv_keys(); $USER->set_download_file(generate_csv($groups['data'], $csvfields), 'groups.csv', 'text/csv'); $data['csv'] = true; return $data; } function group_get_membersearch_data($results, $group, $query, $membershiptype, $setlimit=false, $sortoption='') { global $USER; $params = array(); if ($query != '') { $params['query'] = $query; } if (!empty($membershiptype)) { $params['membershiptype'] = $membershiptype; } if (!empty($sortoption)) { $params['sortoption'] = $sortoption; } $searchurl = get_config('wwwroot') . 'group/members.php?id=' . $group . (!empty($params) ? ('&' . http_build_query($params)) : ''); $smarty = smarty_core(); $role = group_user_access($group); $userid = $USER->get('id'); foreach ($results['data'] as &$r) { if ($role == 'admin' && ($r['id'] != $userid || group_user_can_leave($group, $r['id']))) { $r['removeform'] = group_get_removeuser_form($r['id'], $group); } // NOTE: this is a quick approximation. We should really check whether, // for each role in the group, that the user can change to it (using // group_can_change_role). This only controls whether the 'change // role' link appears though, so it doesn't matter too much. If the // user clicks on this link, changerole.php does the full check and // sends them back here saying that the user has no roles they can // change to anyway. $r['canchangerole'] = !group_is_only_admin($group, $r['id']); } if (!empty($membershiptype)) { if ($membershiptype == 'request') { foreach ($results['data'] as &$r) { $r['addform'] = group_get_adduser_form($r['id'], $group); $r['denyform'] = group_get_denyuser_form($r['id'], $group); // TODO: this will suck when there's quite a few on the page, // would be better to grab all the reasons in one go $r['reason'] = get_field('group_member_request', 'reason', 'group', $group, 'member', $r['id']); } } $smarty->assign('membershiptype', $membershiptype); } $results['cdata'] = array_chunk($results['data'], 2); $results['roles'] = group_get_role_info($group); $smarty->assign('results', $results); $smarty->assign('searchurl', $searchurl); $smarty->assign('pagebaseurl', $searchurl); $smarty->assign('caneditroles', group_user_access($group) == 'admin'); $smarty->assign('group', $group); $html = $smarty->fetch('group/membersearchresults.tpl'); $pagination = build_pagination(array( 'id' => 'member_pagination', 'class' => 'center', 'url' => $searchurl, 'count' => $results['count'], 'setlimit' => $setlimit, 'limit' => $results['limit'], 'offset' => $results['offset'], 'jumplinks' => 8, 'numbersincludeprevnext' => 2, 'datatable' => 'membersearchresults', 'searchresultsheading' => 'searchresultsheading', 'jsonscript' => 'group/membersearchresults.json.php', 'firsttext' => '', 'previoustext' => '', 'nexttext' => '', 'lasttext' => '', 'numbersincludefirstlast' => false, 'resultcounttextsingular' => get_string('member', 'group'), 'resultcounttextplural' => get_string('members', 'group'), )); return array($html, $pagination, $results['count'], $results['offset'], $membershiptype); } /** * Returns a list of available grouptypes * * @return array */ function group_get_grouptypes() { static $grouptypes = null; if (is_null($grouptypes)) { $grouptypes = get_column('grouptype', 'name'); } return $grouptypes; } /** * Returns a list of grouptype options to be used in the edit * group drop-down. */ function group_get_grouptype_options($currentgrouptype=null) { $groupoptions = array(); $grouptypes = group_get_grouptypes(); $enabled = array_map(function($a) { return $a->name; }, plugins_installed('grouptype')); if (is_null($currentgrouptype) || in_array($currentgrouptype, $enabled)) { $grouptypes = array_intersect($enabled, $grouptypes); } foreach ($grouptypes as $grouptype) { safe_require('grouptype', $grouptype); if (call_static_method('GroupType' . $grouptype, 'can_be_created_by_user')) { $roles = array(); foreach (call_static_method('GroupType' . $grouptype, 'get_roles') as $role) { $roles[] = get_string($role, 'grouptype.' . $grouptype); } $groupoptions[$grouptype] = get_string('name', 'grouptype.' . $grouptype) . ': ' . join(', ', $roles); } } return $groupoptions; } function group_get_editroles_options($intkeys = false) { if ($intkeys) { return array(GROUP_ROLES_ALL => get_string('allgroupmembers', 'group'), GROUP_ROLES_NONMEMBER => get_string('allexceptmember', 'group'), GROUP_ROLES_ADMIN => get_string('groupadmins', 'group'), ); } $options = array( 'all' => get_string('allgroupmembers', 'group'), 'notmember' => get_string('allexceptmember', 'group'), 'admin' => get_string('groupadmins', 'group'), ); return $options; } /** * Select dropdown options for showing/hiding group membership * * @return $options */ function group_hide_members_options() { $options = array( GROUP_HIDE_NONE => get_string('no', 'mahara'), GROUP_HIDE_MEMBERS => get_string('hidegroupmembers', 'group'), GROUP_HIDE_TUTORS => get_string('hideonlygrouptutors', 'group'), ); return $options; } function group_can_list_members($group, $role) { return (!$group->hidemembersfrommembers && !$group->hidemembers) || (!$role && ($group->hidemembers == GROUP_HIDE_TUTORS || $group->hidemembersfrommembers == GROUP_HIDE_TUTORS)) || ($role == 'member' && (int) $group->hidemembersfrommembers !== GROUP_HIDE_MEMBERS) || ($role == 'tutor' && (int) $group->hidemembersfrommembers === GROUP_HIDE_TUTORS) || $role == 'admin'; } /** * Returns a datastructure describing the tabs that appear on a group page * * @param object $group Database record of group to get tabs for * @return array */ function group_get_menu_tabs() { static $menu; $group = group_current_group(); if (!$group) { return null; } $role = group_user_access($group->id); $menu = array( 'info' => array( 'path' => 'groups/info', 'url' => group_homepage_url($group, false), 'title' => get_string('About', 'group'), 'weight' => 20 ), ); if (group_can_list_members($group, $role)) { $menu['members'] = array( 'path' => 'groups/members', 'url' => 'group/members.php?id='.$group->id, 'title' => get_string('Members', 'group'), 'weight' => 30 ); } if ($interactionplugins = plugins_installed('interaction')) { foreach ($interactionplugins as $plugin) { safe_require('interaction', $plugin->name); $plugin_menu = call_static_method(generate_class_name('interaction', $plugin->name), 'group_menu_items', $group); $menu = array_merge($menu, $plugin_menu); } } $menu['subnav'] = array( 'class' => 'group' ); $menu['views'] = array( 'path' => 'groups/views', 'url' => 'view/groupviews.php?group='.$group->id, 'title' => get_string('Viewscollections', 'group'), 'weight' => 50, ); if (group_role_can_edit_views($group, $role)) { $menu['share'] = array( 'path' => 'groups/share', 'url' => 'group/shareviews.php?group='.$group->id, 'title' => get_string('share', 'view'), 'weight' => 70, ); } foreach (plugin_types_installed() as $plugin_type_installed) { foreach (plugins_installed($plugin_type_installed) as $plugin) { safe_require($plugin_type_installed, $plugin->name); if (method_exists(generate_class_name($plugin_type_installed, $plugin->name),'group_tabs')) { $plugin_menu = call_static_method( generate_class_name($plugin_type_installed, $plugin->name), 'group_tabs', $group->id, $role ); $menu = array_merge($menu, $plugin_menu); } } } if (group_role_can_access_report($group, $role)) { $menu['report'] = array( 'path' => 'groups/report', 'url' => 'group/report.php?group=' . $group->id, 'title' => get_string('report', 'group'), 'weight' => 70, ); } if (defined('MENUITEM_SUBPAGE') && isset($menu[MENUITEM_SUBPAGE])) { $menu[MENUITEM_SUBPAGE]['selected'] = true; } // Sort the menu items by weight uasort($menu, "sort_menu_by_weight"); return $menu; } /** * Used by this file to perform validation of group ID function arguments * * @param int $groupid * @return int * @throws InvalidArgumentException */ function group_param_groupid($groupid) { $groupid = (int)$groupid; if ($groupid == 0) { throw new InvalidArgumentException("group_user_access: group argument should be an integer"); } return $groupid; } /** * Used by this file to perform validation of user ID function arguments * * @param int $userid * @return int * @throws InvalidArgumentException */ function group_param_userid($userid) { if (is_null($userid)) { global $USER; $userid = (int)$USER->get('id'); } else { $userid = (int)$userid; } if ($userid == 0) { throw new InvalidArgumentException("group_user_access: user argument should be an integer"); } return $userid; } /** * Fetch the current group * * @param string $cache Set to false to override cache */ function group_current_group($cache=true) { static $group; if (isset($group) && $cache) { return $group; } if (defined('GROUP')) { $group = get_record_select('group', 'id = ? AND deleted = 0', array(GROUP), '*, ' . db_format_tsfield('ctime')); if (!$group) { throw new GroupNotFoundException(get_string('groupnotfound', 'group', GROUP)); } } else if (defined('GROUPURLID')) { $group = get_record_select('group', 'urlid = ? AND deleted = 0', array(GROUPURLID), '*, ' . db_format_tsfield('ctime')); if (!$group) { throw new GroupNotFoundException(get_string('groupnotfoundname', 'group', GROUPURLID)); } define('GROUP', $group->id); } else { $group = null; } return $group; } function group_get_associated_groups($userid, $filter='all', $limit=20, $offset=0, $category='', $query_string='') { // Strangely, casting is only needed for invite, request and admin and only in // postgres if (is_mysql()) { $invitesql = "'invite'"; $requestsql = "'request'"; $adminsql = "'admin'"; $empty = "''"; } else { $invitesql = "CAST('invite' AS TEXT)"; $requestsql = "CAST('request' AS TEXT)"; $adminsql = "CAST('admin' AS TEXT)"; $empty = "CAST('' AS TEXT)"; } // TODO: make it work on other databases? $ltijoin = is_plugin_active('lti', 'module') ? ' LEFT JOIN {lti_assessment} a ON g.id = a.group ' : ''; $ltiwhere = is_plugin_active('lti', 'module') ? ' AND a.id IS NULL ' : ''; // Different filters join on the different kinds of association if ($filter == 'admin') { $sql = " INNER JOIN ( SELECT g.id, $adminsql AS membershiptype, $empty AS reason, $adminsql AS role FROM {group} g INNER JOIN {group_member} gm ON (gm.group = g.id AND gm.member = ? AND gm.role = 'admin') ) t ON t.id = g.id"; $values = array($userid); } else if ($filter == 'member') { $sql = " INNER JOIN ( SELECT g.id, 'admin' AS membershiptype, $empty AS reason, $adminsql AS role FROM {group} g INNER JOIN {group_member} gm ON (gm.group = g.id AND gm.member = ? AND gm.role = 'admin') UNION SELECT g.id, 'member' AS type, $empty AS reason, gm.role AS role FROM {group} g INNER JOIN {group_member} gm ON (gm.group = g.id AND gm.member = ? AND gm.role != 'admin') ) t ON t.id = g.id"; $values = array($userid, $userid); } else if ($filter == 'invite') { $sql = " INNER JOIN ( SELECT g.id, $invitesql AS membershiptype, gmi.reason, gmi.role FROM {group} g INNER JOIN {group_member_invite} gmi ON (gmi.group = g.id AND gmi.member = ?) ) t ON t.id = g.id"; $values = array($userid); } else if ($filter == 'request') { $sql = " INNER JOIN ( SELECT g.id, $requestsql AS membershiptype, gmr.reason, $empty AS role FROM {group} g INNER JOIN {group_member_request} gmr ON (gmr.group = g.id AND gmr.member = ?) ) t ON t.id = g.id"; $values = array($userid); } else { // all or some other text $filter = 'all'; $sql = " INNER JOIN ( SELECT g.id, 'admin' AS membershiptype, '' AS reason, 'admin' AS role FROM {group} g INNER JOIN {group_member} gm ON (gm.group = g.id AND gm.member = ? AND gm.role = 'admin') UNION SELECT g.id, 'member' AS membershiptype, '' AS reason, gm.role AS role FROM {group} g INNER JOIN {group_member} gm ON (g.id = gm.group AND gm.member = ? AND gm.role != 'admin') UNION SELECT g.id, 'invite' AS membershiptype, gmi.reason, gmi.role FROM {group} g INNER JOIN {group_member_invite} gmi ON (gmi.group = g.id AND gmi.member = ?) UNION SELECT g.id, 'request' AS membershiptype, gmr.reason, '' AS role FROM {group} g INNER JOIN {group_member_request} gmr ON (gmr.group = g.id AND gmr.member = ?) ) t ON t.id = g.id"; $values = array($userid, $userid, $userid, $userid); } $values[] = 0; $catsql = ''; if (!empty($category)) { if ($category == -1) { //find unassigned groups $catsql = ' AND g.category IS NULL'; } else { $catsql = ' AND g.category = ?'; $values[] = $category; } } $sql .= $ltijoin; $query_where = ""; if ($query_string) { $query_where .= " AND ( g.name " . db_ilike() . " '%' || ? || '%' OR g.description " . db_ilike() . " '%' || ? || '%' OR g.shortname " . db_ilike() . " '%' || ? || '%' )"; $values = array_merge($values, array($query_string, $query_string, $query_string)); } $count = count_records_sql('SELECT COUNT(*) FROM {group} g ' . $sql . ' WHERE g.deleted = ? ' . $ltiwhere . $query_where . $catsql, $values); // almost the same as query used in find - common parts should probably be pulled out // gets the groups filtered by above $sql = ' SELECT g1.id, g1.name, g1.description, g1.public, g1.jointype, g1.request, g1.grouptype, g1.submittableto, g1.hidemembers, g1.hidemembersfrommembers, g1.groupparticipationreports, g1.urlid, g1.membershiptype, g1.reason, g1.role, g1.membercount, COUNT(gmr.member) AS requests, g1.editwindowstart, g1.editwindowend FROM ( SELECT g.id, g.name, g.description, g.public, g.jointype, g.request, g.grouptype, g.submittableto, g.hidemembers, g.hidemembersfrommembers, g.groupparticipationreports, g.urlid, t.membershiptype, t.reason, t.role, COUNT(gm.member) AS membercount, g.editwindowstart, g.editwindowend FROM {group} g LEFT JOIN {group_member} gm ON (gm.group = g.id)' . $sql . ' WHERE g.deleted = ? ' . $ltiwhere . $query_where . $catsql . ' GROUP BY g.id, g.name, g.description, g.public, g.jointype, g.request, g.grouptype, g.submittableto, g.hidemembers, g.hidemembersfrommembers, g.groupparticipationreports, g.urlid, t.membershiptype, t.reason, t.role, g.editwindowstart, g.editwindowend ) g1 LEFT JOIN {group_member_request} gmr ON (gmr.group = g1.id) GROUP BY g1.id, g1.name, g1.description, g1.public, g1.jointype, g1.request, g1.grouptype, g1.submittableto, g1.hidemembers, g1.hidemembersfrommembers, g1.groupparticipationreports, g1.urlid, g1.membershiptype, g1.reason, g1.role, g1.membercount, g1.editwindowstart, g1.editwindowend ORDER BY g1.name'; $groups = get_records_sql_array($sql, $values, $offset, $limit); if ($groups) { foreach ($groups as $group) { $group->homeurl = group_homepage_url($group); } } return array('groups' => $groups ? $groups : array(), 'count' => $count); } /** * returns a list of groups of a user by $userid or the current logged in user, given $roles from cache or database * where $roles is the list of the user's role in a group * if the user id is null, the logged in user will take into account * if $roles is empty, all groups of the user will be returned * * @param int $userid * @param array $roles * @param string $sort is 'earliest', 'latest', or 'alphabetical'(default), * sorts the list of groups based on the date the user joined the group or group name * if empty, the list will be sorted by group name, admin role first * @param int $limit The number of groups to display per page (page size) * @param int $offset The first group index in the current page to display * @param boolean $fromcache if yes, try to return the list from the cache first * or no, force to query database and update the cache * @return array $usergroups An array of groups the user belongs to. * Or if the $limit option is not empty * @return array, int $usergroups, $count You can fetch the results as list($usergroups, $count) */ function group_get_user_groups($userid=null, $roles=null, $sort=null, $limit=null, $offset=0, $fromcache=true) { global $USER; static $usergroups = array(); $loggedinid = $USER->get('id'); if (is_null($userid)) { $userid = $loggedinid; } if (!$fromcache || !isset($usergroups[$userid])) { $ltijoin = is_plugin_active('lti', 'module') ? ' LEFT JOIN {lti_assessment} a ON g.id = a.group ' : ''; $ltiwhere = is_plugin_active('lti', 'module') ? ' AND a.id IS NULL ' : ''; $groups = get_records_sql_array(" SELECT g.id, g.name, gm.role, g.jointype, g.request, g.grouptype, gtr.see_submitted_views, g.category, g.hidemembers, g.invitefriends, g.urlid, gm.ctime, gm1.role AS loggedinrole FROM {group} g JOIN {group_member} gm ON gm.group = g.id JOIN {grouptype_roles} gtr ON g.grouptype = gtr.grouptype AND gm.role = gtr.role LEFT OUTER JOIN {group_member} gm1 ON gm1.group = gm.group AND gm1.member = ?" . $ltijoin . " WHERE gm.member = ? AND g.deleted = 0" . $ltiwhere . " ORDER BY g.name, gm.role = 'admin' DESC, gm.role, g.id", array($loggedinid, $userid) ); if ($groups) { foreach ($groups as $key => $group) { $group->homeurl = group_homepage_url($group); } } $usergroups[$userid] = $groups ? $groups : array(); } if (!empty($sort)) { // Sort the list of groups based on the date the user joined the group if ($sort == 'earliest') { usort($usergroups[$userid], function ($g1, $g2) { if ($g1->ctime == $g2->ctime) { return ($g1->name < $g2->name) ? -1 : 1; } return ($g1->ctime < $g2->ctime) ? -1 : 1; } ); } else if ($sort == 'latest') { usort($usergroups[$userid], function ($g1, $g2) { if ($g1->ctime == $g2->ctime) { return ($g1->name < $g2->name) ? -1 : 1; } return ($g1->ctime > $g2->ctime) ? -1 : 1; } ); } else if ($sort == 'alphabetical') { // Do nothing, the list sorted by the SQL query } else { throw new SystemException('Unknown sort flag: "' . $sort . '"'); } } if (empty($roles) && $userid == $loggedinid) { $count = count($usergroups[$userid]); if (!empty($limit)) { $truncatedusergroups = array_slice($usergroups[$userid], $offset, $limit); return array($truncatedusergroups, $count); } return $usergroups[$userid]; } $filtered = array(); foreach ($usergroups[$userid] as $g) { $goodrole = empty($roles) || in_array($g->role, $roles); $visible = !$g->hidemembers || $g->loggedinrole || $USER->get('admin') || $USER->get('staff'); if ($goodrole && $visible) { $filtered[] = $g; } } $count = count($filtered); if (!empty($limit)) { $filtered = array_slice($filtered, $offset, $limit); return array($filtered, $count); } return $filtered; } function group_get_user_admintutor_groups() { $groups = array(); foreach (group_get_user_groups() as $g) { if ($g->role == 'admin' || $g->see_submitted_views) { $groups[] = $g; } } return $groups; } function group_get_member_ids($group, $roles=null, $includedeleted=false) { $rolesql = is_null($roles) ? '' : (' AND gm.role IN (' . join(',', array_map('db_quote', $roles)) . ')'); return get_column_sql(' SELECT gm.member FROM {group_member} gm INNER JOIN {group} g ON gm.group = g.id WHERE g.id = ? ' . ($includedeleted ? '' : ' AND g.deleted = 0') . $rolesql, array($group) ); } function group_can_create_groups() { global $USER; $creators = get_config('creategroups'); if ($creators == 'all') { return true; } if ($USER->get('admin') || $USER->is_institutional_admin()) { return true; } return $creators == 'staff' && ($USER->get('staff') || $USER->is_institutional_staff()); } /* Returns groups containing a given member which accept view submissions */ function group_get_user_course_groups($userid=null) { if (is_null($userid)) { global $USER; $userid = $USER->get('id'); } if ($groups = get_records_sql_array( "SELECT g.id, g.name FROM {group_member} u INNER JOIN {group} g ON (u.group = g.id AND g.deleted = 0) WHERE u.member = ? AND g.submittableto = 1 ORDER BY g.name ", array($userid))) { return $groups; } return array(); } function group_display_settings($group) { $settings = array(); if ($group->jointype != 'approve') { $settings[] = get_string('membershiptype.abbrev.'.$group->jointype, 'group'); } if ($group->request) { $settings[] = get_string('requestmembership', 'group'); } if ($group->submittableto) { $settings[] = get_string('allowssubmissions', 'group'); } if ($group->public) { $settings[] = get_string('publiclyvisible', 'group'); } return join(', ', $settings); } function group_get_groupinfo_data($group) { safe_require('artefact', 'file'); safe_require('interaction', 'forum'); $group->admins = group_get_admins(array($group->id)); $group->settingsdescription = group_display_settings($group); if (get_config('allowgroupcategories')) { $group->categorytitle = ($group->category) ? get_field('group_category', 'title', 'id', $group->category) : ''; } if (group_can_list_members($group, group_user_access($group->id))) { $group->membercount = count_records('group_member', 'group', $group->id); } $group->viewcount = count_records('view', 'group', $group->id); $group->filecounts = ArtefactTypeFileBase::count_user_files(null, $group->id, null); $group->forumcounts = PluginInteractionForum::count_group_forums($group->id); $group->topiccounts = PluginInteractionForum::count_group_topics($group->id); $group->postcounts = PluginInteractionForum::count_group_posts($group->id); return $group; } /** * Return the view object for this group's homepage view * * @param int $groupid the id of the group to fetch the view for * * @throws ViewNotFoundException */ function group_get_homepage_view($groupid) { $v = get_record('view', 'group', $groupid, 'type', 'grouphomepage'); return new View($v->id, (array)$v); } /** * Return the groupview block object of this group's homepage view * * @param int $groupid the id of the group to fetch the view for * @return object block instance * @throws SQLException if there are more than one groupview block instance */ function group_get_homepage_view_groupview_block($groupid) { $bi = get_record_sql(' SELECT bi.id FROM {view} v INNER JOIN {block_instance} bi ON v.id = bi.view WHERE bi.blocktype = ? AND v.group = ? AND v.type = ?', array('groupviews', $groupid, 'grouphomepage') ); return new BlockInstance($bi->id); } /** * install the group homepage view * This creates a template at system level * which is subsequently copied to group hompages * * @return int the id of the new template */ function install_system_grouphomepage_view() { $dbtime = db_format_timestamp(time()); // create a system template for group homepage views require_once(get_config('libroot') . 'view.php'); $view = View::create(array( 'type' => 'grouphomepage', 'owner' => null, 'institution' => 'mahara', 'template' => View::SITE_TEMPLATE, 'numrows' => 1, 'columnsperrow' => array((object)array('row' => 1, 'columns' => 1)), 'title' => get_string('Grouphomepage', 'view'), )); $view->set_access(array(array( 'type' => 'loggedin' ))); $blocktypes = array( array( 'blocktype' => 'groupinfo', 'title' => '', 'row' => 1, 'column' => 1, 'config' => null, ), array( 'blocktype' => 'recentforumposts', 'title' => '', 'row' => 1, 'column' => 1, 'config' => null, ), array( 'blocktype' => 'groupviews', 'title' => '', 'row' => 1, 'column' => 1, 'config' => array( 'showgroupviews' => 1, 'showsharedviews' => 1, 'showsharedcollections' => 1, ), ), array( 'blocktype' => 'groupmembers', 'title' => '', 'row' => 1, 'column' => 1, 'config' => null, ), ); $installed = get_column_sql('SELECT name FROM {blocktype_installed}'); $weights = array(1 => 0); foreach ($blocktypes as $blocktype) { if (in_array($blocktype['blocktype'], $installed)) { $weights[$blocktype['column']]++; $newblock = new BlockInstance(0, array( 'blocktype' => $blocktype['blocktype'], 'title' => $blocktype['title'], 'view' => $view->get('id'), 'row' => $blocktype['row'], 'column' => $blocktype['column'], 'order' => $weights[$blocktype['column']], 'configdata' => $blocktype['config'], )); $newblock->commit(); } } return $view->get('id'); } function get_forum_list($groupid, $userid = 0) { $forums = array(); if ( (is_numeric($groupid) && $groupid > 0) && (is_numeric($userid) && $userid >= 0) ) { $forums = get_records_sql_array( 'SELECT f.id, f.title, f.description, m.user AS moderator, COUNT(t.id) AS topiccount, s.forum AS subscribed FROM {interaction_instance} f LEFT JOIN ( SELECT m.forum, m.user FROM {interaction_forum_moderator} m INNER JOIN {usr} u ON (m.user = u.id AND u.deleted = 0) ) m ON m.forum = f.id LEFT JOIN {interaction_forum_topic} t ON (t.forum = f.id AND t.deleted != 1) INNER JOIN {interaction_forum_instance_config} c ON (c.forum = f.id AND c.field = \'weight\') LEFT JOIN {interaction_forum_subscription_forum} s ON (s.forum = f.id AND s."user" = ?) WHERE f.group = ? AND f.deleted != 1 GROUP BY 1, 2, 3, 4, 6, c.value ORDER BY CHAR_LENGTH(c.value), c.value, m.user', array($userid, $groupid) ); } return $forums; } function group_quota_allowed($groupid, $bytes) { if (!is_numeric($bytes) || $bytes < 0) { throw new InvalidArgumentException('parameter must be a positive integer to add to the quota'); } if (!$group = get_group_by_id($groupid)) { throw new GroupNotFoundException(get_string('groupnotfound', 'group', $groupid)); } if ($group->quotaused + $bytes > $group->quota) { return false; } return true; } function group_quota_add($groupid, $bytes) { if (!group_quota_allowed($groupid, $bytes)) { throw new QuotaExceededException('Adding ' . $bytes . ' bytes would exceed the group\'s quota'); } if (!$group = get_group_by_id($groupid)) { throw new GroupNotFoundException(get_string('groupnotfound', 'group', $groupid)); } $newquota = $group->quotaused + $bytes; $group = new stdClass(); $group->id = $groupid; $group->quotaused = $newquota; update_record('group', $group); } function group_quota_remove($groupid, $bytes) { if (!is_numeric($bytes) || $bytes < 0) { throw new InvalidArgumentException('parameter must be a positive integer to add to the quota'); } if (!$group = get_group_by_id($groupid)) { throw new GroupNotFoundException(get_string('groupnotfound', 'group', $groupid)); } $newquota = max(0, $group->quotaused - $bytes); $group = new stdClass(); $group->id = $groupid; $group->quotaused = $newquota; update_record('group', $group); } function group_get_new_homepage_urlid($desired) { $maxlen = 30; $desired = strtolower(substr($desired, 0, $maxlen)); $taken = get_column_sql('SELECT urlid FROM {group} WHERE urlid LIKE ?', array(substr($desired, 0, $maxlen - 6) . '%')); if (!$taken) { return $desired; } $i = 1; $newname = substr($desired, 0, $maxlen - 2) . '-1'; while (in_array($newname, $taken)) { $i++; $newname = substr($desired, 0, $maxlen - strlen($i) - 1) . '-' . $i; } return $newname; } /** * Returns the homepage url for a group * * @param stdclass $group object with at least an id * @param bool $full return a full url * @param bool $useid ignore clean url settings and always return a url with an id in it * * @return string */ function group_homepage_url($group, $full=true, $useid=false) { if (!$useid && !empty($group->urlid) && get_config('cleanurls')) { $url = get_config('cleanurlgroupdefault') . '/' . $group->urlid; } else if ($group->id) { $url = 'group/view.php?id=' . $group->id; } else { throw new SystemException("group_homepage_url called with no group id"); } if ($full) { $url = get_config('wwwroot') . $url; } return $url; } /** * Returns whether 'send now' is set for all memebers or not * If not set only admins/tutors/moderators can use 'send now' * * @param string $groupid the id of the group * @return boolean */ function group_sendnow($groupid) { if (!$sendnow = get_field('group', 'sendnow', 'id', $groupid)) { return false; } return !empty($sendnow); } /** * Generate a valid shortname for the group.shortname column, based on the specified display name * * @param string $groupname * @return string */ function group_generate_shortname($groupname) { // iconv can crash on strings that are too long, so truncate before converting $basename = mb_substr($groupname, 0, 255); $basename = iconv('UTF-8', 'ASCII//TRANSLIT', $groupname); $basename = strtolower($basename); $basename = preg_replace('/[^a-z0-9_.-]/', '', $basename); if (strlen($basename) < 2) { $basename = 'group' . $basename; } else { $basename = substr($basename, 0, 255); } // Make sure the name is unique. If it is not, add a suffix and see if // that makes it unique $finalname = $basename; $suffix = 'a'; while (record_exists('group', 'shortname', $finalname)) { // Add the suffix but make sure the name length doesn't go over 255 $finalname = substr($basename, 0, 255 - strlen($suffix)) . $suffix; // Will iterate a-z, aa-az, ba-bz, etc. // See: http://php.net/manual/en/language.operators.increment.php $suffix++; } return $finalname; } /** * Return an element for the shortname field of the group form. * * @param object $group_data Group data object. * * @return array */ function group_get_shortname_element($group_data) { global $USER; $required = $USER->can_edit_group_shortname($group_data); $title = get_string('groupshortname', 'group'); $disabled = !$USER->can_edit_group_shortname($group_data); $element = array( 'title' => $title, 'rules' => array( 'required' => $required, 'maxlength' => 255 ), 'disabled' => $disabled, 'description' => get_string('shortnameformat1', 'group'), ); if (isset($group_data->id)) { $element['type'] = 'text'; $element['defaultvalue'] = isset($group_data->shortname) ? $group_data->shortname : null; } else{ $element['type'] = 'hidden'; $element['value'] = isset($group_data->shortname) ? $group_data->shortname : null; } return $element; } /** * Return a list of allowed group keys for csv import/export. * * @return array A list of keys. */ function group_get_allowed_group_csv_keys() { global $USER; $keys = array( 'shortname', 'displayname', 'description', 'open', 'controlled', 'request', 'roles', 'public', 'submitpages', 'allowarchives', 'editroles', 'hidden', 'hidemembers', 'hidemembersfrommembers', 'invitefriends', 'suggestfriends', 'viewnotify', 'category', ); if ($USER->get('admin')) { $keys[] = 'usersautoadded'; $keys[] = 'quota'; } return $keys; } /** * Generates group membership file data. * * @param int $group_id Id of the group. * @param string $file_format A format of the file. * @param string $mimetype A mimetype of the file. * * @return array An empty array if error || array with following keys 'mimetype', 'name' and 'file'. */ function group_get_membership_file_data($group_id, $file_format = 'csv', $mimetype = 'text/csv') { global $USER; $data = array(); if (!$USER->get('admin')) { return $data; } $group = get_group_by_id($group_id, true); if (!$group) { return $data; } $membership_data = get_records_sql_array( "SELECT g.shortname, u.username, gm.role, u.firstname, u.lastname, u.email, u.preferredname FROM {group} g INNER JOIN {group_member} gm ON g.id = gm.group INNER JOIN {usr} u ON gm.member = u.id WHERE g.id = ? ORDER BY u.username", array($group_id) ); $csv_fields = array( 'shortname', 'username', 'role', 'firstname', 'lastname', 'email', 'preferredname', ); $file_content = generate_csv($membership_data, $csv_fields); if (empty($file_content)) { return $data; } $filename = get_random_key(); $dir = get_config('dataroot') . 'export/' . $USER->get('id') . '/'; if (!check_dir_exists($dir)) { return $data; } if (!file_put_contents($dir . $filename, $file_content)) { return $data; } $data['mimetype'] = $mimetype; $data['name'] = $group->shortname . '.' . $file_format; $data['file'] = $filename; return $data; } /** * Duplicate group - make a copy of the the group's pages, collections and group settings. * @param string/int $groupid The id of the group to copy * @param string $return The place to return to after the copying * * @TODO: Copy forums / files / journals not associated with views/blocks. * @TODO: Copy existing members to new group. */ function group_copy($groupid, $return) { global $USER, $SESSION; $userid = $USER->get('id'); $role = group_user_access($groupid, $userid); if (!($USER->get('admin') || $role == 'admin')) { throw new AccessDeniedException(); } // Copy the group $group = get_record_select('group', 'id = ? AND deleted = 0', array($groupid), '*, ' . db_format_tsfield('ctime')); unset($group->id); $group->ctime = $group->mtime = db_format_timestamp(time()); // need to update the name $group->name = new_group_name($group->name); $group->shortname = group_generate_shortname($group->shortname); if (empty($group->institution)) { $group->institution = 'mahara'; } if (isset($group->urlid)) { // need to sort out the cleanurl $group->urlid = generate_urlid($group->name, get_config('cleanurlgroupdefault'), 3, 30); $group->urlid = group_get_new_homepage_urlid($group->urlid); } db_begin(); $newvalues = (array)$group; $newvalues[$newvalues['jointype']] = 1; unset($newvalues['jointype']); $newvalues['members'] = array($USER->get('id') => 'admin'); $new_groupid = group_create($newvalues); $USER->reset_grouproles(); // Now update the description with any embedded image info $newvalues['description'] = EmbeddedImage::prepare_embedded_images($newvalues['description'], 'group', $new_groupid, $groupid); $newvalues['id'] = $new_groupid; unset($newvalues['members']); unset($newvalues['ctime']); unset($newvalues['mtime']); group_update((object)$newvalues); /* @TODO: Allow copying of the file artefacts $artefactmap = array(); // store the old ids and have them map to new ids $oldartefacts = get_records_assoc('artefact', 'group', $groupid, 'artefacttype, id'); foreach ($oldartefacts as $artefact) { $a = artefact_instance_from_id($artefact->id); $artefactmap[$artefact->id] = $a->copy_for_new_owner(null, $new_groupid, null); $a->commit(); } */ // Copy views for the new group $artefactcopies = array(); $templates = get_records_sql_array(" SELECT v.id, v.title, v.description, v.type FROM {view} v LEFT JOIN {collection_view} cv ON v.id = cv.view WHERE v.group = ? AND cv.view IS NULL", array($groupid)); if ($templates) { require_once(get_config('libroot') . 'view.php'); foreach ($templates as $template) { list($view) = View::create_from_template(array( 'group' => $new_groupid, 'title' => $template->title, 'description' => $template->description, ), $template->id, null, false, false, $artefactcopies); if ($template->type == 'grouphomepage') { $duplicate_homepage = $view; } $view->set_access(array(array( 'type' => 'group', 'id' => $new_groupid, 'startdate' => null, 'stopdate' => null, 'role' => null ))); } // Now update new homepage with the duplicated old one - it's blocks should be connected // to any new artefacts created. $new_homepage = get_record('view', 'group', $new_groupid, 'type', 'grouphomepage'); delete_records('block_instance', 'view', $new_homepage->id); $old_homepage_blocks = get_records_sql_array(" SELECT bi.* FROM {block_instance} bi JOIN {view} v ON v.id = bi.view WHERE v.id = ?", array($duplicate_homepage->get('id'))); foreach ($old_homepage_blocks as $block) { unset($block->id); $block->view = $new_homepage->id; insert_record('block_instance', $block); } // Add back correct layout update_record('view', array( 'layout' => $duplicate_homepage->get('layout'), 'numrows' => $duplicate_homepage->get('numrows'), ), array('id' => $new_homepage->id)); // Clear the existing view_rows_columns and add in correct ones delete_records('view_rows_columns', 'view', $new_homepage->id); $rowscolumns = $duplicate_homepage->get('columnsperrow'); foreach ($rowscolumns as $row) { insert_record('view_rows_columns', array( 'row' => $row->row, 'columns' => $row->columns, 'view' => $new_homepage->id) ); } // Now delete the duplicate homepage $duplicate_homepage->delete(); } // Copy collections for the new group $templates = get_records_sql_array(" SELECT DISTINCT c.id, c.name FROM {view} v INNER JOIN {collection_view} cv ON v.id = cv.view INNER JOIN {collection} c ON cv.collection = c.id WHERE v.group = ?", array($groupid)); if ($templates) { require_once('collection.php'); foreach ($templates as $template) { Collection::create_from_template(array('group' => $new_groupid), $template->id, null, false, true); } } db_commit(); $SESSION->add_ok_msg(get_string('groupsaved', 'group')); // now return to somewhere useful switch ($return) { case 'adminlist': $path = get_config('wwwroot') . 'admin/groups/groups.php'; break; default: $path = get_config('wwwroot') . 'group/view.php?id=' . $new_groupid; } redirect($path); } /** * Generates a name for a newly created group * @param string $name The name of the group * @return string The name with extension added, eg 'Mygroup v.2' */ function new_group_name($name) { $extText = get_string('version.', 'mahara'); $temptitle = preg_split('/ '. $extText . '[0-9]$/', $name); $title = $temptitle[0]; $taken = get_column_sql("SELECT name FROM {group} WHERE name LIKE ? || '%'", array($title)); $ext = ''; $i = 1; if ($taken) { while (in_array($title . $ext, $taken)) { $ext = ' ' . $extText . ++$i; } } return $title . $ext; } /** * * @param integer $groupid ID of the group to check * @param boolean $includedeleted Whether to include deleted groups in result * @param boolean $getrole Whether to return the role in group of logged in user * @param boolean $canedit Whether to return if the user has create/edit permission in group */ function get_group_by_id($groupid, $includedeleted = false, $getrole = false, $canedit = false) { $groupid = group_param_groupid($groupid); if ($includedeleted) { $group = get_record('group', 'id', $groupid); } else { $group = get_record('group', 'id', $groupid, 'deleted', 0); } if (empty($group)) { return false; } if ($getrole || $canedit) { $group->role = group_user_access($group->id); } if ($canedit) { $group->canedit = $group->role && group_role_can_edit_views($group, $group->role); } return $group; } /** * Sort group categories by natural sort order */ function group_sort_categories() { $groupcategories = get_records_array('group_category'); usort($groupcategories, function($a, $b) { return strnatcasecmp($a->title, $b->title); }); foreach ($groupcategories as $key => $gcategory) { if ($key != $gcategory->displayorder) { $gcategory->displayorder = $key; update_record('group_category', $gcategory); } } } function get_group_access_roles() { $roles = get_records_array('grouptype_roles'); $data = array(); foreach ($roles as $r) { $data[$r->grouptype][] = array('name' => $r->role, 'display' => get_string($r->role, 'grouptype.' . $r->grouptype)); } return $data; }