3 /* Reminder: always indent with 4 spaces (no tabs). */
4 // +---------------------------------------------------------------------------+
6 // +---------------------------------------------------------------------------+
9 // | Geeklog user administration page. |
10 // +---------------------------------------------------------------------------+
11 // | Copyright (C) 2000-2009 by the following authors: |
13 // | Authors: Tony Bibbs - tony AT tonybibbs DOT com |
14 // | Mark Limburg - mlimburg AT users DOT sourceforge DOT net |
15 // | Jason Whittenburg - jwhitten AT securitygeeks DOT com |
16 // | Dirk Haun - dirk AT haun-online DOT de |
17 // +---------------------------------------------------------------------------+
19 // | This program is free software; you can redistribute it and/or |
20 // | modify it under the terms of the GNU General Public License |
21 // | as published by the Free Software Foundation; either version 2 |
22 // | of the License, or (at your option) any later version. |
24 // | This program is distributed in the hope that it will be useful, |
25 // | but WITHOUT ANY WARRANTY; without even the implied warranty of |
26 // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
27 // | GNU General Public License for more details. |
29 // | You should have received a copy of the GNU General Public License |
30 // | along with this program; if not, write to the Free Software Foundation, |
31 // | Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
33 // +---------------------------------------------------------------------------+
36 * User administration: Manage users (create, delete, import) and their
42 * Geeklog common function library
44 require_once '../lib-common.php';
47 * Security check to ensure user even belongs on this page
50 require_once 'auth.inc.php';
53 * User-related functions
55 require_once $_CONF['path_system'] . 'lib-user.php';
57 // Set this to true to get various debug messages from this script
58 $_USER_VERBOSE = false;
62 // Make sure user has access to this page
63 if (!SEC_hasRights('user.edit')) {
64 $display .= COM_siteHeader('menu', $MESSAGE[30])
65 . COM_showMessageText($MESSAGE[29], $MESSAGE[30])
67 COM_accessLog("User {$_USER['username']} tried to illegally access the user administration screen.");
73 * Shows the user edit form
75 * @param int $uid User to edit
76 * @param int $msg Error message to display
77 * @return string HTML for user edit form
80 function edituser($uid = '', $msg = '')
82 global $_CONF, $_TABLES, $_USER, $LANG28, $LANG_ACCESS, $LANG_ADMIN,
85 require_once $_CONF['path_system'] . 'lib-admin.php';
90 $retval .= COM_startBlock ($LANG28[22], '',
91 COM_getBlockTemplate ('_msg_block', 'header'))
93 . COM_endBlock (COM_getBlockTemplate ('_msg_block', 'footer'));
96 if (!empty ($msg) && !empty ($uid) && ($uid > 1)) {
97 // an error occured while editing a user - if it was a new account,
98 // don't bother trying to read the user's data from the database ...
99 $cnt = DB_count ($_TABLES['users'], 'uid', $uid);
105 if (!empty ($uid) && ($uid > 1)) {
106 $result = DB_query("SELECT * FROM {$_TABLES['users']} WHERE uid = '$uid'");
107 $A = DB_fetchArray($result);
108 if (empty ($A['uid'])) {
109 return COM_refresh ($_CONF['site_admin_url'] . '/user.php');
112 if (SEC_inGroup('Root',$uid) AND !SEC_inGroup('Root')) {
113 // the current admin user isn't Root but is trying to change
114 // a root account. Deny them and log it.
115 $retval .= COM_startBlock ($LANG28[1], '',
116 COM_getBlockTemplate ('_msg_block', 'header'));
117 $retval .= $LANG_ACCESS['editrootmsg'];
118 COM_accessLog("User {$_USER['username']} tried to edit a Root account with insufficient privileges.");
119 $retval .= COM_endBlock (COM_getBlockTemplate ('_msg_block', 'footer'));
122 $curtime = COM_getUserDateTimeFormat($A['regdate']);
123 $lastlogin = DB_getItem ($_TABLES['userinfo'], 'lastlogin', "uid = '$uid'");
124 $lasttime = COM_getUserDateTimeFormat ($lastlogin);
128 $curtime = COM_getUserDateTimeFormat();
131 $A['status'] = USER_ACCOUNT_ACTIVE;
134 $retval .= COM_startBlock ($LANG28[1], '',
135 COM_getBlockTemplate ('_admin_block', 'header'));
137 $user_templates = new Template($_CONF['path_layout'] . 'admin/user');
138 $user_templates->set_file (array ('form' => 'edituser.thtml',
139 'groupedit' => 'groupedit.thtml'));
140 $user_templates->set_var( 'xhtml', XHTML );
141 $user_templates->set_var('site_url', $_CONF['site_url']);
142 $user_templates->set_var('site_admin_url', $_CONF['site_admin_url']);
143 $user_templates->set_var('layout_url', $_CONF['layout_url']);
144 $user_templates->set_var('lang_save', $LANG_ADMIN['save']);
145 if (!empty($uid) && ($A['uid'] != $_USER['uid']) && SEC_hasRights('user.delete')) {
146 $delbutton = '<input type="submit" value="' . $LANG_ADMIN['delete']
147 . '" name="mode"%s' . XHTML . '>';
148 $jsconfirm = ' onclick="return confirm(\'' . $MESSAGE[76] . '\');"';
149 $user_templates->set_var ('delete_option',
150 sprintf ($delbutton, $jsconfirm));
151 $user_templates->set_var ('delete_option_no_confirmation',
152 sprintf ($delbutton, ''));
154 $user_templates->set_var('lang_cancel', $LANG_ADMIN['cancel']);
156 $user_templates->set_var('lang_userid', $LANG28[2]);
157 if (empty ($A['uid'])) {
158 $user_templates->set_var ('user_id', $LANG_ADMIN['na']);
160 $user_templates->set_var ('user_id', $A['uid']);
162 $user_templates->set_var('lang_regdate', $LANG28[14]);
163 $user_templates->set_var('regdate_timestamp', $curtime[1]);
164 $user_templates->set_var('user_regdate', $curtime[0]);
165 $user_templates->set_var('lang_lastlogin', $LANG28[35]);
166 if (empty ($lastlogin)) {
167 $user_templates->set_var('user_lastlogin', $LANG28[36]);
169 $user_templates->set_var('user_lastlogin', $lasttime[0]);
171 $user_templates->set_var('lang_username', $LANG28[3]);
172 if (isset ($A['username'])) {
173 $user_templates->set_var('username', $A['username']);
175 $user_templates->set_var('username', '');
179 if ($_CONF['show_servicename'] && ($_CONF['user_login_method']['3rdparty']
180 || $_CONF['user_login_method']['openid'])) {
181 if (! empty($A['remoteservice'])) {
182 $remoteservice = '@' . $A['remoteservice'];
185 $user_templates->set_var('remoteservice', $remoteservice);
187 if ($_CONF['allow_user_photo'] && ($A['uid'] > 0)) {
188 $photo = USER_getPhoto ($A['uid'], $A['photo'], $A['email'], -1);
189 $user_templates->set_var ('user_photo', $photo);
190 if (empty ($A['photo'])) {
191 $user_templates->set_var ('lang_delete_photo', '');
192 $user_templates->set_var ('delete_photo_option', '');
194 $user_templates->set_var ('lang_delete_photo', $LANG28[28]);
195 $user_templates->set_var ('delete_photo_option',
196 '<input type="checkbox" name="delete_photo"' . XHTML . '>');
199 $user_templates->set_var ('user_photo', '');
200 $user_templates->set_var ('lang_delete_photo', '');
201 $user_templates->set_var ('delete_photo_option', '');
204 $user_templates->set_var('lang_fullname', $LANG28[4]);
205 if (isset ($A['fullname'])) {
206 $user_templates->set_var ('user_fullname',
207 htmlspecialchars ($A['fullname']));
209 $user_templates->set_var ('user_fullname', '');
211 $user_templates->set_var('lang_password', $LANG28[5]);
212 $user_templates->set_var('lang_password_conf', $LANG28[39]);
213 $user_templates->set_var('lang_emailaddress', $LANG28[7]);
214 if (isset ($A['email'])) {
215 $user_templates->set_var('user_email', htmlspecialchars($A['email']));
217 $user_templates->set_var('user_email', '');
219 $user_templates->set_var('lang_homepage', $LANG28[8]);
220 if (isset ($A['homepage'])) {
221 $user_templates->set_var ('user_homepage',
222 htmlspecialchars ($A['homepage']));
224 $user_templates->set_var ('user_homepage', '');
226 $user_templates->set_var('do_not_use_spaces', '');
228 $statusarray = array(USER_ACCOUNT_AWAITING_ACTIVATION => $LANG28[43],
229 USER_ACCOUNT_ACTIVE => $LANG28[45]
235 if ($A['uid'] == $_USER['uid']) {
236 $allow_ban = false; // do not allow to ban yourself
237 } else if (SEC_inGroup('Root', $A['uid'])) { // editing a Root user?
238 $count_root_sql = "SELECT COUNT(ug_uid) AS root_count FROM {$_TABLES['group_assignments']} WHERE ug_main_grp_id = 1 GROUP BY ug_uid;";
239 $count_root_result = DB_query($count_root_sql);
240 $C = DB_fetchArray($count_root_result); // how many are left?
241 if ($C['root_count'] < 2) {
242 $allow_ban = false; // prevent banning the last root user
248 $statusarray[USER_ACCOUNT_DISABLED] = $LANG28[42];
251 if (($_CONF['usersubmission'] == 1) && !empty($uid)) {
252 $statusarray[USER_ACCOUNT_AWAITING_APPROVAL] = $LANG28[44];
255 $statusselect = '<select name="userstatus">';
256 foreach ($statusarray as $key => $value) {
257 $statusselect .= '<option value="' . $key . '"';
258 if ($key == $A['status']) {
259 $statusselect .= ' selected="selected"';
261 $statusselect .= '>' . $value . '</option>' . LB;
263 $statusselect .= '</select><input type="hidden" name="oldstatus" value="'
264 . $A['status'] . '"' . XHTML . '>';
265 $user_templates->set_var('user_status', $statusselect);
266 $user_templates->set_var('lang_user_status', $LANG28[46]);
268 if ($_CONF['custom_registration'] AND function_exists('CUSTOM_userEdit')) {
269 if (!empty($uid) && ($uid > 1)) {
270 $user_templates->set_var('customfields', CUSTOM_userEdit($uid));
272 $user_templates->set_var('customfields', CUSTOM_userEdit($A['uid']));
276 if (SEC_hasRights('group.assign')) {
277 $user_templates->set_var('lang_securitygroups',
278 $LANG_ACCESS['securitygroups']);
279 $user_templates->set_var('lang_groupinstructions',
280 $LANG_ACCESS['securitygroupsmsg']);
283 $usergroups = SEC_getUserGroups($uid);
284 if (is_array($usergroups) && !empty($uid)) {
285 $selected = implode(' ', $usergroups);
290 $selected = DB_getItem($_TABLES['groups'], 'grp_id',
291 "grp_name = 'All Users'")
293 $selected .= DB_getItem($_TABLES['groups'], 'grp_id',
294 "grp_name = 'Logged-in Users'");
296 $thisUsersGroups = SEC_getUserGroups();
297 $remoteGroup = DB_getItem($_TABLES['groups'], 'grp_id',
298 "grp_name = 'Remote Users'");
299 if (! empty($remoteGroup)) {
300 $thisUsersGroups[] = $remoteGroup;
302 $whereGroups = 'grp_id IN (' . implode (',', $thisUsersGroups) . ')';
305 array('text' => $LANG28[86], 'field' => 'checkbox', 'sort' => false),
306 array('text' => $LANG_ACCESS['groupname'], 'field' => 'grp_name', 'sort' => true),
307 array('text' => $LANG_ACCESS['description'], 'field' => 'grp_descr', 'sort' => true)
309 $defsort_arr = array('field' => 'grp_name', 'direction' => 'asc');
311 $form_url = $_CONF['site_admin_url']
312 . '/user.php?mode=edit&uid=' . $uid;
313 $text_arr = array('has_menu' => false,
314 'title' => '', 'instructions' => '',
315 'icon' => '', 'form_url' => $form_url,
319 $sql = "SELECT grp_id, grp_name, grp_descr FROM {$_TABLES['groups']} WHERE " . $whereGroups;
320 $query_arr = array('table' => 'groups',
322 'query_fields' => array('grp_name'),
323 'default_filter' => '',
328 $groupoptions = ADMIN_list('usergroups',
329 'ADMIN_getListField_usergroups',
330 $header_arr, $text_arr, $query_arr,
331 $defsort_arr, '', explode(' ', $selected));
333 $user_templates->set_var('group_options', $groupoptions);
334 $user_templates->parse('group_edit', 'groupedit', true);
336 // user doesn't have the rights to edit a user's groups so set to -1
337 // so we know not to handle the groups array when we save
338 $user_templates->set_var('group_edit',
339 '<input type="hidden" name="groups" value="-1"' . XHTML . '>');
341 $user_templates->set_var('gltoken_name', CSRF_TOKEN);
342 $user_templates->set_var('gltoken', SEC_createToken());
343 $user_templates->parse('output', 'form');
344 $retval .= $user_templates->finish($user_templates->get_var('output'));
345 $retval .= COM_endBlock (COM_getBlockTemplate ('_admin_block', 'footer'));
352 global $_CONF, $_TABLES, $LANG_ADMIN, $LANG04, $LANG28, $_IMAGE_TYPE;
354 require_once $_CONF['path_system'] . 'lib-admin.php';
358 if ($_CONF['lastlogin']) {
359 $login_text = $LANG28[41];
360 $login_field = 'lastlogin';
362 $login_text = $LANG28[40];
363 $login_field = 'regdate';
366 $header_arr = array( # display 'text' and use table field 'field'
367 array('text' => $LANG_ADMIN['edit'], 'field' => 'edit', 'sort' => false),
368 array('text' => $LANG28[37], 'field' => $_TABLES['users'] . '.uid', 'sort' => true),
369 array('text' => $LANG28[3], 'field' => 'username', 'sort' => true),
370 array('text' => $LANG28[4], 'field' => 'fullname', 'sort' => true),
371 array('text' => $login_text, 'field' => $login_field, 'sort' => true),
372 array('text' => $LANG28[7], 'field' => 'email', 'sort' => true)
375 if ($_CONF['user_login_method']['openid'] ||
376 $_CONF['user_login_method']['3rdparty']) {
377 $header_arr[] = array('text' => $LANG04[121], 'field' => 'remoteservice', 'sort' => true);
380 $defsort_arr = array('field' => $_TABLES['users'] . '.uid',
381 'direction' => 'ASC');
384 array('url' => $_CONF['site_admin_url'] . '/user.php?mode=edit',
385 'text' => $LANG_ADMIN['create_new']),
386 array('url' => $_CONF['site_admin_url'] . '/user.php?mode=importform',
387 'text' => $LANG28[23]),
388 array('url' => $_CONF['site_admin_url'] . '/user.php?mode=batchdelete',
389 'text' => $LANG28[54]),
390 array('url' => $_CONF['site_admin_url'],
391 'text' => $LANG_ADMIN['admin_home'])
394 $retval .= COM_startBlock($LANG28[11], '',
395 COM_getBlockTemplate('_admin_block', 'header'));
397 $retval .= ADMIN_createMenu(
400 $_CONF['layout_url'] . '/images/icons/user.' . $_IMAGE_TYPE
404 'has_extras' => true,
405 'form_url' => $_CONF['site_admin_url'] . '/user.php',
410 $select_userinfo = '';
411 if ($_CONF['lastlogin']) {
412 $join_userinfo .= "LEFT JOIN {$_TABLES['userinfo']} ON {$_TABLES['users']}.uid={$_TABLES['userinfo']}.uid ";
413 $select_userinfo .= ",lastlogin";
415 if ($_CONF['user_login_method']['openid'] ||
416 $_CONF['user_login_method']['3rdparty']) {
417 $select_userinfo .= ',remoteservice';
419 $sql = "SELECT {$_TABLES['users']}.uid,username,fullname,email,photo,status,regdate$select_userinfo "
420 . "FROM {$_TABLES['users']} $join_userinfo WHERE 1=1";
422 $query_arr = array('table' => 'users',
424 'query_fields' => array('username', 'email', 'fullname'),
425 'default_filter' => "AND {$_TABLES['users']}.uid > 1");
427 $retval .= ADMIN_list('user', 'ADMIN_getListField_users', $header_arr,
428 $text_arr, $query_arr, $defsort_arr);
429 $retval .= COM_endBlock(COM_getBlockTemplate('_admin_block', 'footer'));
435 * Saves user to the database
437 * @param int $uid user id
438 * @param string $usernmae (short) user name
439 * @param string $fullname user's full name
440 * @param string $email user's email address
441 * @param string $regdate date the user registered with the site
442 * @param string $homepage user's homepage URL
443 * @param array $groups groups the user belongs to
444 * @param string $delete_photo delete user's photo if == 'on'
445 * @return string HTML redirect or error message
448 function saveusers ($uid, $username, $fullname, $passwd, $passwd_conf, $email, $regdate, $homepage, $groups, $delete_photo = '', $userstatus=3, $oldstatus=3)
450 global $_CONF, $_TABLES, $_USER, $LANG28, $_USER_VERBOSE;
453 $userChanged = false;
455 if ($_USER_VERBOSE) COM_errorLog("**** entering saveusers****",1);
456 if ($_USER_VERBOSE) COM_errorLog("group size at beginning = " . count($groups),1);
458 if ($passwd != $passwd_conf) { // passwords don't match
459 return edituser($uid, 67);
462 $nameAndEmailOkay = true;
463 if (empty($username)) {
464 $nameAndEmailOkay = false;
465 } elseif (empty($email)) {
467 $nameAndEmailOkay = false; // new users need an email address
469 $service = DB_getItem($_TABLES['users'], 'remoteservice',
471 if (empty($service)) {
472 $nameAndEmailOkay = false; // not a remote user - needs email
477 if ($nameAndEmailOkay) {
479 if (!empty($email) && !COM_isEmail($email)) {
480 return edituser($uid, 52);
483 $uname = addslashes ($username);
485 $ucount = DB_getItem ($_TABLES['users'], 'COUNT(*)',
486 "username = '$uname'");
488 $uservice = DB_getItem ($_TABLES['users'], 'remoteservice', "uid = $uid");
489 if ($uservice != '') {
490 $uservice = addslashes($uservice);
491 $ucount = DB_getItem ($_TABLES['users'], 'COUNT(*)',
492 "username = '$uname' AND uid <> $uid AND remoteservice = '$uservice'");
494 $ucount = DB_getItem ($_TABLES['users'], 'COUNT(*)',
495 "username = '$uname' AND uid <> $uid AND (remoteservice = '' OR remoteservice IS NULL)");
499 // Admin just changed a user's username to one that already exists
500 return edituser ($uid, 51);
503 $emailaddr = addslashes($email);
504 $exclude_remote = " AND (remoteservice IS NULL OR remoteservice = '')";
506 $ucount = DB_getItem($_TABLES['users'], 'COUNT(*)',
507 "email = '$emailaddr'" . $exclude_remote);
509 $old_email = DB_getItem($_TABLES['users'], 'email', "uid = '$uid'");
510 if ($old_email == $email) {
511 // email address didn't change so don't care
514 $ucount = DB_getItem($_TABLES['users'], 'COUNT(*)',
515 "email = '$emailaddr' AND uid <> $uid"
520 // Admin just changed a user's email to one that already exists
521 return edituser($uid, 56);
524 if ($_CONF['custom_registration'] &&
525 function_exists('CUSTOM_userCheck')) {
526 $ret = CUSTOM_userCheck($username, $email);
528 // need a numeric return value - otherwise use default message
529 if (! is_numeric($ret['number'])) {
530 $ret['number'] = 400;
532 return edituser($uid, $ret['number']);
536 if (empty ($uid) || !empty ($passwd)) {
537 $passwd = SEC_encryptPassword($passwd);
539 $passwd = DB_getItem ($_TABLES['users'], 'passwd', "uid = $uid");
543 if (empty ($passwd)) {
544 // no password? create one ...
546 $passwd = md5 ($passwd);
547 $passwd = substr ($passwd, 1, 8);
548 $passwd = SEC_encryptPassword($passwd);
551 $uid = USER_createAccount ($username, $email, $passwd, $fullname,
554 DB_query("UPDATE {$_TABLES['users']} SET status = $userstatus WHERE uid = $uid");
557 $fullname = addslashes ($fullname);
558 $homepage = addslashes ($homepage);
559 $curphoto = DB_getItem($_TABLES['users'],'photo',"uid = $uid");
560 if (!empty ($curphoto) && ($delete_photo == 'on')) {
561 USER_deletePhoto ($curphoto);
565 if (($_CONF['allow_user_photo'] == 1) && !empty ($curphoto)) {
566 $curusername = DB_getItem ($_TABLES['users'], 'username',
568 if ($curusername != $username) {
569 // user has been renamed - rename the photo, too
570 $newphoto = preg_replace ('/' . $curusername . '/',
571 $username, $curphoto, 1);
572 $imgpath = $_CONF['path_images'] . 'userphotos/';
573 if (rename ($imgpath . $curphoto,
574 $imgpath . $newphoto) === false) {
575 $display = COM_siteHeader ('menu', $LANG28[22]);
576 $display .= COM_errorLog ('Could not rename userphoto "'
577 . $curphoto . '" to "' . $newphoto . '".');
578 $display .= COM_siteFooter ();
581 $curphoto = $newphoto;
585 $curphoto = addslashes ($curphoto);
586 DB_query("UPDATE {$_TABLES['users']} SET username = '$username', fullname = '$fullname', passwd = '$passwd', email = '$email', homepage = '$homepage', photo = '$curphoto', status='$userstatus' WHERE uid = $uid");
587 if ($_CONF['custom_registration'] AND (function_exists('CUSTOM_userSave'))) {
588 CUSTOM_userSave($uid);
590 if( ($_CONF['usersubmission'] == 1) && ($oldstatus == USER_ACCOUNT_AWAITING_APPROVAL)
591 && ($userstatus == USER_ACCOUNT_ACTIVE) ) {
592 USER_createAndSendPassword ($username, $email, $uid);
594 if ($userstatus == USER_ACCOUNT_DISABLED) {
595 SESS_endUserSession($uid);
600 // check that the user is allowed to change group assignments
601 if (is_array($groups) && SEC_hasRights('group.assign')) {
602 if (! SEC_inGroup('Root')) {
603 $rootgrp = DB_getItem($_TABLES['groups'], 'grp_id',
604 "grp_name = 'Root'");
605 if (in_array($rootgrp, $groups)) {
606 COM_accessLog("User {$_USER['username']} ({$_USER['uid']}) just tried to give Root permissions to user $username.");
607 echo COM_refresh($_CONF['site_admin_url'] . '/index.php');
612 // make sure the Remote Users group is in $groups
613 if (SEC_inGroup('Remote Users', $uid)) {
614 $remUsers = DB_getItem($_TABLES['groups'], 'grp_id',
615 "grp_name = 'Remote Users'");
616 if (! in_array($remUsers, $groups)) {
617 $groups[] = $remUsers;
621 if ($_USER_VERBOSE) {
622 COM_errorLog("deleting all group_assignments for user $uid/$username",1);
625 // remove user from all groups that the User Admin is a member of
626 $UserAdminGroups = SEC_getUserGroups();
627 $whereGroup = 'ug_main_grp_id IN ('
628 . implode (',', $UserAdminGroups) . ')';
629 DB_query("DELETE FROM {$_TABLES['group_assignments']} WHERE (ug_uid = $uid) AND " . $whereGroup);
631 // make sure to add user to All Users and Logged-in Users groups
632 $allUsers = DB_getItem($_TABLES['groups'], 'grp_id',
633 "grp_name = 'All Users'");
634 if (! in_array($allUsers, $groups)) {
635 $groups[] = $allUsers;
637 $logUsers = DB_getItem($_TABLES['groups'], 'grp_id',
638 "grp_name = 'Logged-in Users'");
639 if (! in_array($logUsers, $groups)) {
640 $groups[] = $logUsers;
643 foreach ($groups as $userGroup) {
644 if (in_array($userGroup, $UserAdminGroups)) {
645 if ($_USER_VERBOSE) {
646 COM_errorLog("adding group_assignment " . $userGroup
647 . " for $username", 1);
649 $sql = "INSERT INTO {$_TABLES['group_assignments']} (ug_main_grp_id, ug_uid) VALUES ($userGroup, $uid)";
656 PLG_userInfoChanged ($uid);
659 $errors = DB_error();
660 if (empty($errors)) {
661 echo PLG_afterSaveSwitch (
662 $_CONF['aftersave_user'],
663 "{$_CONF['site_url']}/users.php?mode=profile&uid=$uid",
668 $retval .= COM_siteHeader ('menu', $LANG28[22]);
669 $retval .= COM_errorLog ('Error in saveusers in '
670 . $_CONF['site_admin_url'] . '/user.php');
671 $retval .= COM_siteFooter ();
676 $retval = COM_siteHeader('menu', $LANG28[1]);
677 $retval .= COM_showMessageText($LANG28[10]);
678 if (DB_count($_TABLES['users'], 'uid', $uid) > 0) {
679 $retval .= edituser($uid);
681 $retval .= edituser();
683 $retval .= COM_siteFooter();
688 if ($_USER_VERBOSE) COM_errorLog("***************leaving saveusers*****************",1);
694 * This function allows the batch deletion of users that are inactive
695 * It shows the form that will filter user that will be deleted
697 * @return string HTML Form
699 function batchdelete()
701 global $_CONF, $_TABLES, $LANG_ADMIN, $LANG01, $LANG28, $_IMAGE_TYPE;
705 if (! $_CONF['lastlogin']) {
706 $retval .= '<p>'. $_LANG28[55] . '</p>';
710 require_once $_CONF['path_system'] . 'lib-admin.php';
713 if (isset($_REQUEST['usr_type'])) {
714 $usr_type = COM_applyFilter($_REQUEST['usr_type']);
716 $usr_type = 'phantom';
718 $usr_time_arr = array();
720 if (isset($_REQUEST['usr_time'])) {
721 $usr_time_arr = $_REQUEST['usr_time'];
723 // default values, in months
724 $usr_time_arr['phantom'] = 2;
725 $usr_time_arr['short'] = 6;
726 $usr_time_arr['old'] = 24;
727 $usr_time_arr['recent'] = 1;
729 $usr_time = $usr_time_arr[$usr_type];
731 // list of options for user display
734 // txt1 => text before input-field
735 // txt2 => text after input field
737 array('sel' => 'phantom', 'desc' => $LANG28[57], 'txt1' => $LANG28[60], 'txt2' => $LANG28[61]),
738 array('sel' => 'short', 'desc' => $LANG28[58], 'txt1' => $LANG28[62], 'txt2' => $LANG28[63]),
739 array('sel' => 'old', 'desc' => $LANG28[59], 'txt1' => $LANG28[64], 'txt2' => $LANG28[65]),
740 array('sel' => 'recent', 'desc' => $LANG28[74], 'txt1' => $LANG28[75], 'txt2' => $LANG28[76])
743 $user_templates = new Template($_CONF['path_layout'] . 'admin/user');
744 $user_templates->set_file (array ('form' => 'batchdelete.thtml',
745 'options' => 'batchdelete_options.thtml',
746 'reminder' => 'reminder.thtml'));
747 $user_templates->set_var ( 'xhtml', XHTML );
748 $user_templates->set_var ('site_url', $_CONF['site_url']);
749 $user_templates->set_var ('site_admin_url', $_CONF['site_admin_url']);
750 $user_templates->set_var ('layout_url', $_CONF['layout_url']);
751 $user_templates->set_var ('usr_type', $usr_type);
752 $user_templates->set_var ('usr_time', $usr_time);
753 $user_templates->set_var ('lang_instruction', $LANG28[56]);
754 $user_templates->set_var ('lang_updatelist', $LANG28[66]);
756 $num_opts = count($opt_arr);
757 for ($i = 0; $i < $num_opts; $i++) {
759 if ($usr_type == $opt_arr[$i]['sel']) {
760 $selector = ' checked="checked"';
762 $user_templates->set_var ('sel_id', $opt_arr[$i]['sel']);
763 $user_templates->set_var ('selector', $selector);
764 $user_templates->set_var ('lang_description', $opt_arr[$i]['desc']);
765 $user_templates->set_var ('lang_text_start', $opt_arr[$i]['txt1']);
766 $user_templates->set_var ('lang_text_end', $opt_arr[$i]['txt2']);
767 $user_templates->set_var ('id_value', $usr_time_arr[$opt_arr[$i]['sel']]);
768 $user_templates->parse('options_list', 'options', true);
770 $user_templates->parse('form', 'form');
771 $desc = $user_templates->finish($user_templates->get_var('form'));
773 $header_arr = array( # display 'text' and use table field 'field'
774 array('text' => $LANG28[37], 'field' => $_TABLES['users'] . '.uid', 'sort' => true),
775 array('text' => $LANG28[3], 'field' => 'username', 'sort' => true),
776 array('text' => $LANG28[4], 'field' => 'fullname', 'sort' => true)
781 $header_arr[] = array('text' => $LANG28[14], 'field' => 'regdate', 'sort' => true);
782 $header_arr[] = array('text' => $LANG28[41], 'field' => 'lastlogin_short', 'sort' => true);
783 $header_arr[] = array('text' => $LANG28[67], 'field' => 'phantom_date', 'sort' => true);
784 $list_sql = ", UNIX_TIMESTAMP()- UNIX_TIMESTAMP(regdate) as phantom_date";
785 $filter_sql = "lastlogin = 0 AND UNIX_TIMESTAMP()- UNIX_TIMESTAMP(regdate) > " . ($usr_time * 2592000) . " AND";
789 $header_arr[] = array('text' => $LANG28[14], 'field' => 'regdate', 'sort' => true);
790 $header_arr[] = array('text' => $LANG28[41], 'field' => 'lastlogin_short', 'sort' => true);
791 $header_arr[] = array('text' => $LANG28[68], 'field' => 'online_hours', 'sort' => true);
792 $header_arr[] = array('text' => $LANG28[69], 'field' => 'offline_months', 'sort' => true);
793 $list_sql = ", (lastlogin - UNIX_TIMESTAMP(regdate)) AS online_hours, (UNIX_TIMESTAMP() - lastlogin) AS offline_months";
794 $filter_sql = "lastlogin > 0 AND lastlogin - UNIX_TIMESTAMP(regdate) < 86400 "
795 . "AND UNIX_TIMESTAMP() - lastlogin > " . ($usr_time * 2592000) . " AND";
799 $header_arr[] = array('text' => $LANG28[41], 'field' => 'lastlogin_short', 'sort' => true);
800 $header_arr[] = array('text' => $LANG28[69], 'field' => 'offline_months', 'sort' => true);
801 $list_sql = ", (UNIX_TIMESTAMP() - lastlogin) AS offline_months";
802 $filter_sql = "lastlogin > 0 AND (UNIX_TIMESTAMP() - lastlogin) > " . ($usr_time * 2592000) . " AND";
806 $header_arr[] = array('text' => $LANG28[14], 'field' => 'regdate', 'sort' => true);
807 $header_arr[] = array('text' => $LANG28[41], 'field' => 'lastlogin_short', 'sort' => true);
809 $filter_sql = "(UNIX_TIMESTAMP() - UNIX_TIMESTAMP(regdate)) < " . ($usr_time * 2592000) . " AND";
814 $header_arr[] = array('text' => $LANG28[7], 'field' => 'email', 'sort' => true);
815 $header_arr[] = array('text' => $LANG28[87], 'field' => 'num_reminders', 'sort' => true);
817 array('url' => $_CONF['site_admin_url'] . '/user.php',
818 'text' => $LANG28[11]),
819 array('url' => $_CONF['site_admin_url'] . '/user.php?mode=importform',
820 'text' => $LANG28[23]),
821 array('url' => $_CONF['site_admin_url'],
822 'text' => $LANG_ADMIN['admin_home'])
825 $text_arr = array('has_menu' => true,
826 'has_extras' => true,
828 'instructions' => $desc,
829 'icon' => $_CONF['layout_url'] . '/images/icons/user.' . $_IMAGE_TYPE,
830 'form_url' => $_CONF['site_admin_url'] . "/user.php?mode=batchdelete&usr_type=$usr_type&usr_time=$usr_time",
834 $defsort_arr = array('field' => $sort,
835 'direction' => 'ASC');
837 $join_userinfo = "LEFT JOIN {$_TABLES['userinfo']} ON {$_TABLES['users']}.uid={$_TABLES['userinfo']}.uid ";
838 $select_userinfo = ", lastlogin as lastlogin_short $list_sql ";
840 $sql = "SELECT {$_TABLES['users']}.uid,username,fullname,email,photo,status,regdate,num_reminders$select_userinfo "
841 . "FROM {$_TABLES['users']} $join_userinfo WHERE 1=1";
846 'query_fields' => array('username', 'email', 'fullname'),
847 'default_filter' => "AND $filter_sql {$_TABLES['users']}.uid > 1"
849 $listoptions = array('chkdelete' => true, 'chkfield' => 'uid');
852 array('url' => $_CONF['site_admin_url'] . '/user.php',
853 'text' => $LANG28[11]),
854 array('url' => $_CONF['site_admin_url'] . '/user.php?mode=importform',
855 'text' => $LANG28[23]),
856 array('url' => $_CONF['site_admin_url'],
857 'text' => $LANG_ADMIN['admin_home'])
860 $retval .= COM_startBlock($LANG28[54], '',
861 COM_getBlockTemplate('_admin_block', 'header'));
863 $retval .= ADMIN_createMenu(
866 $_CONF['layout_url'] . '/images/icons/user.' . $_IMAGE_TYPE
869 $user_templates->set_var('lang_reminder', $LANG28[77]);
870 $user_templates->set_var('action_reminder', $LANG28[78]);
871 $user_templates->parse('test', 'reminder');
873 $form_arr['top'] = $user_templates->finish($user_templates->get_var('test'));
874 $token = SEC_createToken();
875 $form_arr['bottom'] = "<input type=\"hidden\" name=\"" . CSRF_TOKEN
876 . "\" value=\"{$token}\"" . XHTML . ">";
877 $retval .= ADMIN_list('user', 'ADMIN_getListField_users', $header_arr,
878 $text_arr, $query_arr, $defsort_arr, '', '',
879 $listoptions, $form_arr);
880 $retval .= COM_endBlock(COM_getBlockTemplate('_admin_block', 'footer'));
886 * This function deletes the users selected in the batchdeletelist function
888 * @return string HTML with success or error message
891 function batchdeleteexec()
893 global $_CONF, $LANG28;
896 $user_list = array();
897 if (isset($_POST['delitem'])) {
898 $user_list = $_POST['delitem'];
901 if (count($user_list) == 0) {
902 $msg = $LANG28[72] . '<br' . XHTML . '>';
906 if (isset($user_list) AND is_array($user_list)) {
907 foreach($user_list as $delitem) {
908 $delitem = COM_applyFilter($delitem);
909 if (!USER_deleteAccount ($delitem)) {
910 $msg .= "<strong>{$LANG28[2]} $delitem {$LANG28[70]}</strong><br" . XHTML . ">\n";
912 $c++; // count the deleted users
917 // Since this function is used for deletion only, it's necessary to say that
918 // zero were deleted instead of just leaving this message away.
919 COM_numberFormat($c); // just in case we have more than 999 ...
920 $msg .= "{$LANG28[71]}: $c<br" . XHTML . ">\n";
927 * This function used to send out reminders to users to access the site or account may be deleted
929 * @return string HTML with success or error message
932 function batchreminders()
934 global $_CONF, $_TABLES, $LANG04, $LANG28;
937 $user_list = array();
938 if (isset($_POST['delitem'])) {
939 $user_list = $_POST['delitem'];
942 if (count($user_list) == 0) {
943 $msg = $LANG28[79] . '<br' . XHTML . '>';
947 if (isset($_POST['delitem']) AND is_array($_POST['delitem'])) {
948 foreach($_POST['delitem'] as $delitem) {
949 $userid = COM_applyFilter($delitem);
950 $useremail = DB_getItem ($_TABLES['users'], 'email', "uid = '$userid'");
951 $username = DB_getItem ($_TABLES['users'], 'username', "uid = '$userid'");
952 $lastlogin = DB_getItem ($_TABLES['userinfo'], 'lastlogin', "uid = '$userid'");
953 $lasttime = COM_getUserDateTimeFormat ($lastlogin);
954 if (file_exists ($_CONF['path_data'] . 'reminder_email.txt')) {
955 $template = new Template ($_CONF['path_data']);
956 $template->set_file (array ('mail' => 'reminder_email.txt'));
957 $template->set_var ('site_url', $_CONF['site_url']);
958 $template->set_var ('site_name', $_CONF['site_name']);
959 $template->set_var ('site_slogan', $_CONF['site_slogan']);
960 $template->set_var ('lang_username', $LANG04[2]);
961 $template->set_var ('username', $username);
962 $template->set_var ('name', COM_getDisplayName ($uid));
963 $template->set_var ('lastlogin', $lasttime[0]);
965 $template->parse('output', 'mail');
966 $mailtext = $template->finish($template->get_var('output'));
968 if ($lastlogin == 0) {
969 $mailtext = $LANG28[83] . "\n\n";
971 $mailtext = sprintf($LANG28[82], $lasttime[0]) . "\n\n";
973 $mailtext .= sprintf($LANG28[84], $username) . "\n";
974 $mailtext .= sprintf($LANG28[85], $_CONF['site_url']
975 . '/users.php?mode=getpassword') . "\n\n";
978 $subject = sprintf($LANG28[81], $_CONF['site_name']);
979 if ($_CONF['site_mail'] !== $_CONF['noreply_mail']) {
980 $mailfrom = $_CONF['noreply_mail'];
981 $mailtext .= LB . LB . $LANG04[159];
983 $mailfrom = $_CONF['site_mail'];
986 if (COM_mail ($useremail, $subject, $mailtext, $mailfrom)) {
987 DB_query("UPDATE {$_TABLES['users']} SET num_reminders=num_reminders+1 WHERE uid=$userid");
990 COM_errorLog("Error attempting to send account reminder to use:$username ($userid)");
995 // Since this function is used for deletion only, its necessary to say that
996 // zero where deleted instead of just leaving this message away.
997 COM_numberFormat($c); // just in case we have more than 999)..
998 $msg .= "{$LANG28[80]}: $c<br" . XHTML . ">\n";
1005 * This function allows the administrator to import batches of users
1007 * TODO: This function should first display the users that are to be imported,
1008 * together with the invalid users and the reason of invalidity. Each valid line
1009 * should have a checkbox that allows selection of final to be imported users.
1010 * After clicking an extra button, the actual import should take place. This will
1011 * prevent problems in case the list formatting is incorrect.
1013 * @return string HTML with success or error message
1016 function importusers()
1018 global $_CONF, $_TABLES, $LANG04, $LANG28;
1020 // Setting this to true will cause import to print processing status to
1021 // webpage and to the error.log file
1022 $verbose_import = true;
1026 // Bulk import implies admin authorisation:
1027 $_CONF['usersubmission'] = 0;
1029 // First, upload the file
1030 require_once $_CONF['path_system'] . 'classes/upload.class.php';
1032 $upload = new upload ();
1033 $upload->setPath ($_CONF['path_data']);
1034 $upload->setAllowedMimeTypes (array ('text/plain' => '.txt'));
1035 $upload->setFileNames ('user_import_file.txt');
1036 if ($upload->uploadFiles()) {
1037 // Good, file got uploaded, now install everything
1038 $thefile = current($_FILES);
1039 $filename = $_CONF['path_data'] . 'user_import_file.txt';
1040 if (!file_exists($filename)) { // empty upload form
1041 $retval = COM_refresh($_CONF['site_admin_url']
1042 . '/user.php?mode=importform');
1046 // A problem occurred, print debug information
1047 $retval = COM_siteHeader ('menu', $LANG28[22]);
1048 $retval .= COM_startBlock ($LANG28[24], '',
1049 COM_getBlockTemplate ('_msg_block', 'header'));
1050 $retval .= $upload->printErrors(false);
1051 $retval .= COM_endBlock (COM_getBlockTemplate ('_msg_block', 'footer'));
1056 $users = file ($filename);
1058 $retval .= COM_siteHeader ('menu', $LANG28[24]);
1059 $retval .= COM_startBlock ($LANG28[31], '',
1060 COM_getBlockTemplate ('_admin_block', 'header'));
1062 // Following variables track import processing statistics
1065 foreach ($users as $line) {
1066 $line = rtrim ($line);
1067 if (empty ($line)) {
1071 list ($full_name, $u_name, $email) = explode ("\t", $line);
1073 $full_name = strip_tags ($full_name);
1074 $u_name = COM_applyFilter ($u_name);
1075 $email = COM_applyFilter ($email);
1077 if ($verbose_import) {
1078 $retval .="<br" . XHTML . "><b>Working on username=$u_name, fullname=$full_name, and email=$email</b><br" . XHTML . ">\n";
1079 COM_errorLog ("Working on username=$u_name, fullname=$full_name, and email=$email",1);
1082 // prepare for database
1083 $userName = trim ($u_name);
1084 $fullName = trim ($full_name);
1085 $emailAddr = trim ($email);
1087 if (COM_isEmail ($email)) {
1088 // email is valid form
1089 $ucount = DB_count ($_TABLES['users'], 'username',
1090 addslashes ($userName));
1091 $ecount = DB_count ($_TABLES['users'], 'email',
1092 addslashes ($emailAddr));
1094 if (($ucount == 0) && ($ecount == 0)) {
1095 // user doesn't already exist - pass in optional true for $batchimport parm
1096 $uid = USER_createAccount ($userName, $emailAddr, '',
1097 $fullName,'','','',true);
1099 $result = USER_createAndSendPassword ($userName, $emailAddr, $uid);
1101 if ($result && $verbose_import) {
1102 $retval .= "<br" . XHTML . "> Account for <b>$u_name</b> created successfully.<br" . XHTML . ">\n";
1103 COM_errorLog("Account for $u_name created successfully",1);
1104 } else if ($result) {
1107 // user creation failed
1108 $retval .= "<br" . XHTML . ">ERROR: There was a problem creating the account for <b>$u_name</b>.<br" . XHTML . ">\n";
1109 COM_errorLog("ERROR: here was a problem creating the account for $u_name.",1);
1112 if ($verbose_import) {
1113 $retval .= "<br" . XHTML . "><b>$u_name</b> or <b>$email</b> already exists, account not created.<br" . XHTML . ">\n"; // user already exists
1114 COM_errorLog("$u_name,$email: username or email already exists, account not created",1);
1117 } // end if $ucount == 0 && ecount == 0
1119 if ($verbose_import) {
1120 $retval .= "<br" . XHTML . "><b>$email</b> is not a valid email address, account not created<br" . XHTML . ">\n"; // malformed email
1121 COM_errorLog("$email is not a valid email address, account not created",1);
1124 } // end if COM_isEmail($email)
1129 $retval .= '<p>' . sprintf ($LANG28[32], $successes, $failures);
1131 $retval .= COM_endBlock (COM_getBlockTemplate ('_admin_block', 'footer'));
1132 $retval .= COM_siteFooter ();
1138 * Display "batch add" (import) form
1140 * @return string HTML for import form
1143 function display_batchAddform()
1145 global $_CONF, $LANG28, $LANG_ADMIN, $_IMAGE_TYPE;
1147 require_once $_CONF['path_system'] . 'lib-admin.php';
1151 $token = SEC_createToken();
1152 $retval .= COM_siteHeader('menu', $LANG28[24]);
1153 $retval .= COM_startBlock ($LANG28[24], '',
1154 COM_getBlockTemplate ('_admin_block', 'header'));
1157 array('url' => $_CONF['site_admin_url'] . '/user.php',
1158 'text' => $LANG28[11]),
1159 array('url' => $_CONF['site_admin_url'] . '/user.php?mode=batchdelete',
1160 'text' => $LANG28[54]),
1161 array('url' => $_CONF['site_admin_url'],
1162 'text' => $LANG_ADMIN['admin_home'])
1165 $desc = '<p>' . $LANG28[25] . '</p>';
1166 $icon = $_CONF['layout_url'] . '/images/icons/user.' . $_IMAGE_TYPE;
1167 $retval .= ADMIN_createMenu($menu_arr, $desc, $icon);
1169 $retval .= '<form action="' . $_CONF['site_admin_url']
1170 . '/user.php" method="post" enctype="multipart/form-data"><div>'
1172 . ': <input type="file" dir="ltr" name="importfile" size="40"'
1174 . '<input type="hidden" name="mode" value="import"' . XHTML . '>'
1175 . '<input type="submit" name="submit" value="' . $LANG28[30]
1176 . '"' . XHTML . '><input type="hidden" name="' . CSRF_TOKEN
1177 . "\" value=\"{$token}\"" . XHTML . '></div></form>' . LB;
1179 $retval .= COM_endBlock (COM_getBlockTemplate ('_admin_block', 'footer'));
1180 $retval .= COM_siteFooter();
1188 * @param int $uid id of user to delete
1189 * @return string HTML redirect
1192 function deleteUser ($uid)
1196 if (!USER_deleteAccount ($uid)) {
1197 return COM_refresh ($_CONF['site_admin_url'] . '/user.php');
1200 return COM_refresh ($_CONF['site_admin_url'] . '/user.php?msg=22');
1205 if (isset($_REQUEST['mode'])) {
1206 $mode = $_REQUEST['mode'];
1209 if (isset($_POST['delbutton_x'])) {
1210 $mode = 'batchdeleteexec';
1213 if (isset ($_REQUEST['order'])) {
1214 $order = COM_applyFilter ($_REQUEST['order'],true);
1217 if (isset ($_GET['direction'])) {
1218 $direction = COM_applyFilter ($_GET['direction']);
1221 if (isset ($_POST['passwd']) && isset ($_POST['passwd_conf']) &&
1222 ($_POST['passwd'] != $_POST['passwd_conf'])) {
1223 // entered passwords were different
1224 $uid = COM_applyFilter ($_POST['uid'], true);
1226 $display .= COM_refresh ($_CONF['site_admin_url']
1227 . '/user.php?mode=edit&msg=67&uid=' . $uid);
1229 $display .= COM_refresh ($_CONF['site_admin_url'] . '/user.php?msg=67');
1231 } elseif (($mode == $LANG_ADMIN['delete']) && !empty ($LANG_ADMIN['delete'])) { // delete
1232 $uid = COM_applyFilter($_POST['uid'], true);
1234 COM_errorLog('Attempted to delete user uid=' . $uid);
1235 $display = COM_refresh($_CONF['site_admin_url'] . '/user.php');
1236 } elseif (SEC_checkToken()) {
1237 $display .= deleteUser($uid);
1239 COM_accessLog("User {$_USER['username']} tried to illegally delete user $uid and failed CSRF checks.");
1240 echo COM_refresh($_CONF['site_admin_url'] . '/index.php');
1242 } elseif (($mode == $LANG_ADMIN['save']) && !empty($LANG_ADMIN['save']) && SEC_checkToken()) { // save
1244 if (isset ($_POST['delete_photo'])) {
1245 $delphoto = $_POST['delete_photo'];
1247 if (!isset ($_POST['oldstatus'])) {
1248 $_POST['oldstatus'] = USER_ACCOUNT_ACTIVE;
1250 if (!isset ($_POST['userstatus'])) {
1251 $_POST['userstatus'] = USER_ACCOUNT_ACTIVE;
1253 $display = saveusers (COM_applyFilter ($_POST['uid'], true),
1254 $_POST['username'], $_POST['fullname'],
1255 $_POST['passwd'], $_POST['passwd_conf'], $_POST['email'],
1256 $_POST['regdate'], $_POST['homepage'], $_POST['groups'],
1257 $delphoto, $_POST['userstatus'], $_POST['oldstatus']);
1258 if (!empty($display)) {
1259 $tmp = COM_siteHeader('menu', $LANG28[22]);
1261 $tmp .= COM_siteFooter();
1264 } elseif ($mode == 'edit') {
1265 $display .= COM_siteHeader('menu', $LANG28[1]);
1267 if (isset ($_GET['msg'])) {
1268 $msg = COM_applyFilter ($_GET['msg'], true);
1271 if (isset ($_GET['uid'])) {
1272 $uid = COM_applyFilter ($_GET['uid'], true);
1274 $display .= edituser ($uid, $msg);
1275 $display .= COM_siteFooter();
1276 } elseif (($mode == 'import') && SEC_checkToken()) {
1277 $display .= importusers();
1278 } elseif ($mode == 'importform') {
1279 $display .= display_batchAddform();
1280 } elseif ($mode == 'batchdelete') {
1281 $display .= COM_siteHeader ('menu', $LANG28[54]);
1282 $display .= batchdelete();
1283 $display .= COM_siteFooter();
1284 } elseif (($mode == $LANG28[78]) && !empty($LANG28[78]) && SEC_checkToken()) {
1285 $msg = batchreminders();
1286 $display .= COM_siteHeader ('menu', $LANG28[11])
1287 . COM_showMessage($msg)
1290 } elseif (($mode == 'batchdeleteexec') && SEC_checkToken()) {
1291 $msg = batchdeleteexec();
1292 $display .= COM_siteHeader ('menu', $LANG28[11])
1293 . COM_showMessage($msg)
1296 } else { // 'cancel' or no mode at all
1297 $display .= COM_siteHeader('menu', $LANG28[11]);
1298 $display .= COM_showMessageFromParameter();
1299 $display .= listusers();
1300 $display .= COM_siteFooter();
1303 COM_output($display);