Commit f68308cc authored by Nigel McNie's avatar Nigel McNie Committed by Nigel McNie
Browse files

Beginnings of group view page reworked.

The member list is able to be ajax-ly paginated through, and shows more detail than the previous one.

The page layout is showing shadows of what it will eventually become, although a lot of functionality isn't there yet - like joining/leaving the group.
parent 54a5e53e
<?php
/**
* Mahara: Electronic portfolio, weblog, resume builder and social networking
* Copyright (C) 2006-2008 Catalyst IT Ltd (http://www.catalyst.net.nz)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package mahara
* @subpackage core
* @author Catalyst IT Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
* @copyright (C) 2006-2008 Catalyst IT Ltd http://catalyst.net.nz
*
*/
define('INTERNAL', 1);
define('JSON', 1);
require(dirname(dirname(__FILE__)) . '/init.php');
require('group.php');
require('searchlib.php');
$id = param_integer('id');
$query = trim(param_variable('query', ''));
$offset = param_integer('offset', 0);
$limit = param_integer('limit', 5);
list($html, $pagination, $count, $offset) = group_get_membersearch_data($id, $query, $offset, $limit);
json_reply(false, array(
'message' => null,
'data' => array(
'tablerows' => $html,
'pagination' => $pagination['html'],
'pagination_js' => $pagination['javascript'],
'count' => $count,
'results' => $count . ' ' . ($count == 1 ? get_string('result') : get_string('results')),
'offset' => $offset,
)
));
?>
...@@ -29,253 +29,34 @@ define('MENUITEM', 'groups'); ...@@ -29,253 +29,34 @@ define('MENUITEM', 'groups');
require(dirname(dirname(__FILE__)) . '/init.php'); require(dirname(dirname(__FILE__)) . '/init.php');
define('TITLE', get_string('groups')); define('TITLE', get_string('groups'));
require_once('group.php'); require_once('group.php');
require_once('searchlib.php');
require_once(get_config('docroot') . 'interaction/lib.php'); require_once(get_config('docroot') . 'interaction/lib.php');
$id = param_integer('id'); $id = param_integer('id');
$joincontrol = param_alpha('joincontrol', null);
$pending = param_integer('pending', 0);
if (!$group = get_record('group', 'id', $id, 'deleted', 0)) { if (!$group = get_record('group', 'id', $id, 'deleted', 0)) {
throw new GroupNotFoundException("Couldn't find group with id $id"); throw new GroupNotFoundException("Couldn't find group with id $id");
} }
$group->ownername = display_name(get_record('usr', 'id', $group->owner));
$membership = user_can_access_group($id); $group->admins = get_column_sql("SELECT member
// $membership is a bit string summing all membership types FROM {group_member}
$ismember = (bool) ($membership & GROUP_MEMBERSHIP_MEMBER); WHERE \"group\" = ?
AND role = 'admin'", array($id));
if (!empty($joincontrol)) { $role = group_user_access($id);
// leave, join, acceptinvite, request
switch ($joincontrol) {
case 'join':
if (!$ismember && $group->jointype == 'open') {
group_add_member($id, $USER->get('id'));
$SESSION->add_ok_msg(get_string('joinedgroup', 'group'));
}
else {
$SESSION->add_error_msg(get_string('couldnotjoingroup', 'group'));
}
break;
case 'acceptinvite':
case 'declineinvite':
if (!$request = get_record('group_member_invite', 'member', $USER->get('id'), 'group', $id)) {
$SESSION->add_error_msg(get_string('groupnotinvited', 'group'));
break;
}
if ($joincontrol == 'acceptinvite') {
group_add_member($id, $USER->get('id'));
$message = get_string('groupinviteaccepted', 'group');
}
else {
$message = get_string('groupinvitedeclined', 'group');
}
delete_records('group_member_invite', 'member', $USER->get('id'), 'group', $id);
$SESSION->add_ok_msg($message);
break;
}
// redirect, stuff will have changed
redirect('/group/view.php?id=' . $id);
exit;
}
$invited = get_record('group_member_invite', 'group', $id, 'member', $USER->get('id')); // Search related stuff for member pager
$requested = get_record('group_member_request', 'group', $id, 'member', $USER->get('id')); $query = trim(param_variable('query', ''));
$offset = param_integer('offset', 0);
$limit = param_integer('limit', 5);
list($html, $pagination, $count, $offset) = group_get_membersearch_data($id, $query, $offset, $limit);
$userview = get_config('wwwroot') . 'user/view.php?id='; $smarty = smarty(array('paginator', 'groupmembersearch'), array(), array(), array('sideblocks' => array(interaction_sideblock($id, $role))));
$viewview = get_config('wwwroot') . 'view/view.php?id=';
$commview = get_config('wwwroot') . 'group/view.php';
$profilepic = get_config('wwwroot') . 'thumb.php?type=profileicon&maxsize=25&id=';
// strings that are used in the js
$releaseviewstr = get_string('releaseview', 'group');
$tutorstr = get_string('tutor', 'group');
$memberstr = get_string('member', 'group');
$removestr = get_string('remove');
$declinestr = get_string('declinerequest', 'group');
$updatefailedstr = get_string('updatefailed', 'group');
$requeststr = get_string('sendrequest');
$reasonstr = get_string('reason', 'group');
// all the permissions stuff
//$tutor = (int)($membership && ($membership != GROUP_MEMBERSHIP_MEMBER));
$controlled = (int)($group->jointype == 'controlled');
$request = (int)($group->jointype == 'request');
$tutor = (int)(bool)($membership & GROUP_MEMBERSHIP_TUTOR);
$admin = (int)(bool)($membership & GROUP_MEMBERSHIP_ADMIN);
$staff = (int)(bool)($membership & GROUP_MEMBERSHIP_STAFF);
$owner = (int)(bool)($membership & GROUP_MEMBERSHIP_OWNER);
$canupdate = (int)(bool)($tutor || $staff || $admin || $owner);
$canpromote = (int)(bool)(($staff || $admin) && $controlled);
$canremove = (int)(bool)(($tutor && $controlled) || $staff || $admin || $owner);
$canleave = ($ismember && group_user_can_leave($id, $USER->get('id')));
$canrequestjoin = (!$ismember && empty($invited) && empty($requested) && $group->jointype == 'request');
$canjoin = (!$ismember && $group->jointype == 'open' && !$owner);
$javascript = '';
if ($membership) {
$javascript .= <<<EOF
viewlist = new TableRenderer(
'group_viewlist',
'view.json.php',
[
function (r) {
return TD(null, A({'href': '{$viewview}' + r.id}, r.title));
},
function (r) {
return TD(null, A({'href': '{$userview}' + r.owner}, r.ownername));
},
function (r,d) {
if (r.submittedto && {$tutor} == 1) {
return TD(null, A({'href': '', 'onclick': 'return releaseView(' + r.id + ');'}, '{$releaseviewstr}'));
}
return TD(null);
}
]
);
viewlist.type = 'views';
viewlist.submitted = 0;
viewlist.id = $id;
viewlist.statevars.push('type');
viewlist.statevars.push('id');
viewlist.statevars.push('submitted');
viewlist.updateOnLoad();
memberlist = new TableRenderer(
'memberlist',
'view.json.php',
[
function (r) {
return TD(null, A({'href': '{$userview}' + r.id}, IMG({'alt': '', 'src': '{$profilepic}' + r.id}), r.displayname));
},
EOF;
if ($canupdate) {
$javascript .= <<<EOF
'reason',
function (r) {
var options = new Array();
var member = OPTION({'value': 'member'}, '{$memberstr}');
if (r.request != 1) {
member.selected = true;
}
options.push(member);
if (r.request) {
var nonmember = OPTION({'value': 'declinerequest'}, '{$declinestr}');
nonmember.selected = true;
options.push(nonmember);
}
EOF;
if ($canpromote) {
$javascript .= <<<EOF
var tutor = OPTION({'value': 'tutor'}, '{$tutorstr}');
if (r.tutor == 1) {
member.selected = false;
tutor.selected = true;
}
options.push(tutor);
EOF;
}
if ($canremove) {
$javascript .= <<<EOF
if (!r.request) {
var remove = OPTION({'value': 'remove'}, '{$removestr}');
options.push(remove);
}
EOF;
}
$javascript .= <<<EOF
if (r.id != $group->owner) {
return TD(null, SELECT({'name': 'member-' + r.id, 'class': 'member'}, options));
}
}
EOF;
}
$javascript .= <<<EOF
]
);
memberlist.id = $id;
memberlist.type='members';
memberlist.pending = 0;
memberlist.statevars.push('type');
memberlist.statevars.push('pending');
memberlist.statevars.push('id');
memberlist.updateOnLoad();
addLoadEvent(function () { hideElement($('pendingreasonheader')); });
function switchPending(force) {
if (force) {
pending = force;
var theOption = filter(
function (o) { if ( o.value == pending ) return true; return false; },
$('pendingselect').options
);
theOption[0].selected = true;
}
else {
var pending = $('pendingselect').options[$('pendingselect').selectedIndex].value;
}
if (pending == 0) {
hideElement($('pendingreasonheader'));
}
else {
showElement($('pendingreasonheader'));
}
memberlist.pending = pending;
memberlist.doupdate();
}
function releaseView(id) {
var pd = {'type': 'release', 'id': '{$group->id}', 'view': id};
sendjsonrequest('view.json.php', pd, 'GET', function (data) {
viewlist.doupdate();
});
return false;
}
function updateMembership() {
var pd = {'type': 'membercontrol', 'id': '{$group->id}'};
var e = getElementsByTagAndClassName(null, 'member');
for (s in e) {
pd[e[s].name] = e[s].options[e[s].selectedIndex].value;
}
sendjsonrequest('view.json.php', pd, 'GET', function (data) {
if (memberlist.pending == 1) {
memberlist.offset = 0;
}
memberlist.doupdate();
});
}
EOF;
}// end of membership only javascript (tablerenderers etc)
if (!empty($pending) && $canupdate && $request) {
$javascript .= <<<EOF
addLoadEvent(function () { switchPending(1) });
EOF;
}
$smarty = smarty(array('tablerenderer'), array(), array(), array('sideblocks' => array(interaction_sideblock($id, $membership))));
$smarty->assign('INLINEJAVASCRIPT', $javascript);
$smarty->assign('member', $membership);
$smarty->assign('tutor', $tutor);
$smarty->assign('staff', $staff);
$smarty->assign('admin', $admin);
$smarty->assign('controlled', $controlled);
$smarty->assign('request', $request);
$smarty->assign('canjoin', $canjoin);
$smarty->assign('canrequestjoin', $canrequestjoin);
$smarty->assign('canleave', $canleave);
$smarty->assign('canpromote', $canpromote);
$smarty->assign('canupdate', $canupdate);
$smarty->assign('canacceptinvite', $invited);
$smarty->assign('group', $group); $smarty->assign('group', $group);
$smarty->assign('hasmembers', group_has_members($group->id)); $smarty->assign('query', $query);
$smarty->assign('results', $html);
$smarty->assign('pagination', $pagination['html']);
$smarty->assign('pagination_js', $pagination['javascript']);
$smarty->display('group/view.tpl'); $smarty->display('group/view.tpl');
?> ?>
/**
* 'Speeds up' the group member search if the user has javascript enabled in their
* browser
*
* Copyright: 2006-2008 Catalyst IT Ltd
* This file is licensed under the same terms as Mahara itself
*/
function UserSearch() {
var self = this;
this.init = function () {
self.rewriteQueryButton();
self.rewritePaging();
self.params = {};
}
this.searchByChildLink = function (element) {
var children = getElementsByTagAndClassName('a', null, element);
if (children.length == 1) {
var href = getNodeAttribute(children[0], 'href');
self.params = parseQueryString(href.substring(href.indexOf('?')+1, href.length));
self.doSearch();
}
}
this.changePage = function(e) {
e.stop();
self.searchByChildLink(this);
}
this.rewritePaging = function() {
forEach(getElementsByTagAndClassName('span', 'pagination', 'membersearchresults'), function(i) {
connect(i, 'onclick', self.changePage);
});
}
this.rewriteQueryButton = function() {
connect($('query-button'), 'onclick', self.newQuery);
}
this.newQuery = function(e) {
self.params = {};
self.params.query = $('query').value;
self.doSearch();
e.stop();
}
this.doSearch = function() {
self.params.action = 'search';
sendjsonrequest('membersearchresults.php', self.params, 'POST', function(data) {
$('results').innerHTML = data.data;
if ($('searchresults')) {
self.rewritePaging();
}
});
}
addLoadEvent(self.init);
}
userSearch = new UserSearch();
...@@ -469,6 +469,41 @@ function group_invite_submit(Pieform $form, $values) { ...@@ -469,6 +469,41 @@ function group_invite_submit(Pieform $form, $values) {
} }
} }
function group_get_membersearch_data($group, $query, $offset, $limit) {
$results = get_group_user_search_results($group, $query, $offset, $limit);
$params = array();
if (!empty($query)) {
$params[] = 'query=' . $query;
}
$params[] = 'limit=' . $limit;
$searchurl = get_config('wwwroot') . 'group/view.php?' . join('&amp;', $params);
$smarty = smarty_core();
$smarty->assign_by_ref('results', $results);
$smarty->assign('searchurl', $searchurl);
$smarty->assign('pagebaseurl', $searchurl);
$html = $smarty->fetch('group/membersearchresults.tpl');
$pagination = build_pagination(array(
'id' => 'member_pagination',
'class' => 'center',
'url' => get_config('wwwroot') . 'group/view.php?id=' . $group,
'count' => $results['count'],
'limit' => $limit,
'offset' => $offset,
'datatable' => 'membersearchresults',
'jsonscript' => 'group/membersearchresults.php',
'firsttext' => '',
'previoustext' => '',
'nexttext' => '',
'lasttext' => '',
'numbersincludefirstlast' => false,
));
return array($html, $pagination, $results['count'], $offset);
}
/** /**
* Where is the syntax error? * Where is the syntax error?
*/ */
......
...@@ -331,6 +331,64 @@ function admin_user_search($queries, $constraints, $offset, $limit, $sortfield, ...@@ -331,6 +331,64 @@ function admin_user_search($queries, $constraints, $offset, $limit, $sortfield,
} }
/**
* Returns search results for users in a particular group
*
* The search term is applied against first and last names of the users in the group
*
* @param int $group The group to build results for
* @param string $query A search string to filter by
* @param int $offset What result to start showing paginated results from
* @param int $limit How many results to show
*/
function get_group_user_search_results($group, $query, $offset, $limit) {
$queries = array();
$constraints = array();
if (!empty($query)) {
list($words, $fullnames) = parse_name_query($query);
foreach ($words as $word) {
$queries[] = array('field' => 'firstname',
'type' => 'contains',
'string' => $word);
$queries[] = array('field' => 'lastname',
'type' => 'contains',
'string' => $word);
}
foreach ($fullnames as $n) {
$constraints[] = array('field' => 'firstname',
'type' => 'contains',
'string' => $n[0]);
$constraints[] = array('field' => 'lastname',
'type' => 'contains',
'string' => $n[1]);
}
}
$results = group_user_search($group, $queries, $constraints, $offset, $limit);
if ($results['count']) {
$userids = array_map(create_function('$a', 'return $a["id"];'), $results['data']);
$introductions = get_records_sql_assoc("SELECT owner, title
FROM {artefact}
WHERE artefacttype = 'introduction'
AND owner IN (" . implode(',', db_array_to_ph($userids)) . ')',
$userids);
foreach ($results['data'] as &$result) {
$result['name'] = display_name($result);
$result['introduction'] = isset($introductions[$result['id']]) ? $introductions[$result['id']]->title : '';
$result['jointime'] = strftime(get_string('strfdaymonthyearshort'), $result['jointime']);
}
}
return $results;
}
function group_user_search($group, $queries, $constraints, $offset, $limit) {
$plugin = get_config('searchplugin');
safe_require('search', $plugin);
return call_static_method(generate_class_name('search', $plugin), 'group_search_user',
$group, $queries, $constraints, $offset, $limit);
}
/** /**
* Given a query string and limits, return an array of matching groups using the * Given a query string and limits, return an array of matching groups using the
* search plugin defined in config.php * search plugin defined in config.php
......
...@@ -2072,7 +2072,7 @@ function str_shorten($str, $maxlen=100, $truncate=false, $newlines=true) { ...@@ -2072,7 +2072,7 @@ function str_shorten($str, $maxlen=100, $truncate=false, $newlines=true) {
/** /**
* Builds pagination links for HTML display. * Builds pagination links for HTML display.
* *
* The pagination is quite configurable, but at the same time gives a consitent * The pagination is quite configurable, but at the same time gives a consistent
* look and feel to all pagination. * look and feel to all pagination.
* *
* This function takes one array that contains the options to configure the * This function takes one array that contains the options to configure the
......
...@@ -402,6 +402,58 @@ class PluginSearchInternal extends PluginSearch { ...@@ -402,6 +402,58 @@ class PluginSearchInternal extends PluginSearch {
} }
public static function group_search_user($group, $queries, $constraints, $offset, $limit) {
$where = 'WHERE gm.group = ?';
$values = array($group);
// Get the correct keyword for case insensitive LIKE
$ilike = db_ilike();
// Only handle OR/AND expressions at the top level. Eventually we may need subexpressions.
if (!empty($queries)) {
$where .= ' AND ( ';
$str = array();
foreach ($queries as $f) {
$str[] = 'u.' . $f['field']
. PluginSearchInternal::match_expression($f['type'], $f['string'], $values, $ilike);
}
$where .= join(' OR ', $str) . ') ';
}
$count = get_field_sql('SELECT COUNT(*) FROM {usr} u INNER JOIN {group_member} gm ON (gm.member = u.id) ' . $where, $values);
if ($count > 0) {
$data = get_records_sql_assoc('
SELECT
u.id, u.firstname, u.lastname, u.username, u.email, u.staff, ' . db_format_tsfield('gm.ctime', 'jointime') . '
FROM
{usr} u
INNER JOIN {group_member} gm ON (gm.member = u.id) ' . $where . '
ORDER BY gm.ctime, u.firstname, u.lastname, u.id',
$values,
$offset,
$limit);
if ($data) {
foreach ($data as &$item) {
$item = (array)$item;
}
$data = array_values($data);
}
}
else {
$data = false;
}
return array(
'count' => $count,
'limit' => $limit,
'offset' => $offset,
'data' => $data,
);
}
public static function institutional_admin_search_user($query, $institution, $limit) { public static function institutional_admin_search_user($query, $institution, $limit) {
$sql = ' $sql = '
......
{if !empty($results.data)}
{foreach from=$results.data item=r}
<tr class="{cycle values="r0,r1"}">
<td style="width: 300px; border: 1px solid red;">
<!--<div style="float: right;"><select><option>Member</option></select></div>-->
<h4 style="margin:0;"><a href="{$WWWROOT}user/view.php?id={$r.id|escape}">{$r.name|escape}</a></h4>
<div style="float: left;"><img src="{$WWWROOT}thumb.php?type=profileicon&amp;maxsize=40&amp;id={$r.id|escape}" alt=""></div>
<p style="margin: .25em 0 0 65px;">{$r.introduction|str_shorten:80:true}</p>
<p style="margin: .25em 0 0 65px;"><strong>Joined:</strong> {$r.jointime}</p>
</td>
</tr>
{/foreach}
{else}
<div>{str tag="noresultsfound"}</div>
{/if}
...@@ -3,66 +3,63 @@ ...@@ -3,66 +3,63 @@
{include file="columnleftstart.tpl"} {include file="columnleftstart.tpl"}