Commit 6266cae9 authored by Richard Mansfield's avatar Richard Mansfield
Browse files

Add admin report on access lists of user pages (bug #919009)



Adds a new "User reports" page to the admin section, accessible by
admins, which produces a list of all the pages owned by a given set of
users, and a list of who is on the access list for each page.

The users are selected using the checkboxes on the admin user search
page, and the page is reachable from admin user search using a new
"View reports" button on that page.

The CSV download that previously appeared on the Bulk actions page is
more appropriate on a reports page, so CSV download is also moved from
Bulk actions to User reports.  Email and remoteuser fields are only
displayed to site and institution administrators.

Because some sites will not want to allow staff to see the page access
lists of all users in their institutions, access to this page by staff
is controlled by a new "Staff report access" site setting.

Change-Id: Id02b58416e3dfb28fd39c1170426ddefe6669efe
Signed-off-by: default avatarRichard Mansfield <richard.mansfield@catalyst.net.nz>
parent 89e175f8
......@@ -183,6 +183,13 @@ $siteoptionform = array(
'disabled' => in_array('loggedinprofileviewaccess', $OVERRIDDEN),
'help' => true,
),
'staffreports' => array(
'type' => 'checkbox',
'title' => get_string('staffuserreports', 'admin'),
'description' => get_string('staffuserreportsdescription', 'admin'),
'defaultvalue' => get_config('staffreports'),
'disabled' => in_array('staffreports', $OVERRIDDEN),
),
),
),
'searchsettings' => array(
......@@ -608,6 +615,7 @@ function siteoptions_submit(Pieform $form, $values) {
'proxyaddress', 'proxyauthmodel', 'proxyauthcredentials', 'smtphosts', 'smtpport', 'smtpuser', 'smtppass', 'smtpsecure',
'noreplyaddress', 'defaultnotificationmethod', 'homepageinfo', 'showonlineuserssideblock', 'onlineuserssideblockmaxusers',
'registerterms', 'allowmobileuploads', 'creategroups', 'createpublicgroups', 'allowgroupcategories', 'wysiwyg',
'staffreports',
);
// if public views are disabled, sitemap generation must also be disabled.
......
......@@ -67,18 +67,6 @@ if ($uneditableusers = count($userids) - count($users)) {
$userids = array_keys($users);
// Export CSV
$csv = "username,email,firstname,lastname,studentid,preferredname,remoteuser\n";
foreach ($users as $user) {
$u = array();
foreach (array('username', 'email', 'firstname', 'lastname', 'studentid', 'preferredname', 'remoteuser') as $f) {
$u[] = str_replace('"', '""', $user->$f);
}
$csv .= '"' . join('","', $u) . '"' . "\n";
}
$USER->set_download_file($csv, 'users.csv', 'text/csv');
// Hidden drop-down to submit the list of users back to this page.
// Used in all three forms
$userelement = array(
......
<?php
/**
* Mahara: Electronic portfolio, weblog, resume builder and social networking
* Copyright (C) 2012 Catalyst IT Ltd and others; see:
* http://wiki.mahara.org/Contributors
*
* 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 admin
* @author Richard Mansfield
*
*/
define('INTERNAL', 1);
define('INSTITUTIONALSTAFF', 1);
define('MENUITEM', 'configusers');
require(dirname(dirname(dirname(__FILE__))) . '/init.php');
define('TITLE', get_string('userreports', 'admin'));
$tabs = array(
'users' => array(
'id' => 'users',
'name' => get_string('users'),
),
'accesslist' => array(
'id' => 'accesslist',
'name' => get_string('accesslist', 'view'),
),
);
$selected = 'users';
foreach (array_keys($tabs) as $t) {
if (param_variable('report:' . $t, false)) {
$selected = $t;
}
}
$tabs[$selected]['selected'] = true;
$userids = array_map('intval', param_variable('users'));
$ph = $userids;
$institutionsql = '';
if (!$USER->get('admin') && !$USER->get('staff')) {
// Filter the users by the user's admin/staff institutions
$institutions = $USER->get('admininstitutions');
if (get_config('staffreports')) {
$institutions = array_merge($institutions, $USER->get('staffinstitutions'));
}
$institutions = array_values($institutions);
$ph = array_merge($ph, $institutions);
$institutionsql = '
AND id IN (
SELECT usr FROM {usr_institution} WHERE institution IN (' . join(',', array_fill(0, count($institutions), '?')) . ')
)';
}
$users = get_records_sql_assoc('
SELECT
u.id, u.username, u.email, u.firstname, u.lastname, u.studentid, u.preferredname,
aru.remoteusername AS remoteuser
FROM {usr} u
LEFT JOIN {auth_remote_user} aru ON u.id = aru.localusr AND u.authinstance = aru.authinstance
WHERE id IN (' . join(',', array_fill(0, count($userids), '?')) . ')
AND deleted = 0' . $institutionsql . '
ORDER BY ' . ($selected == 'users' ? 'username' : 'lastname, firstname, id'),
$ph
);
if (!get_config('staffreports')) {
// Display the number of users filtered out due to institution permissions. This is not an exception, because the
// logged in user might be an admin in one institution, and staff in another.
if ($uneditableusers = count($userids) - count($users)) {
$SESSION->add_info_msg(get_string('uneditableusers', 'admin', $uneditableusers));
}
}
if ($users && !$USER->get('admin')) {
// Remove email & remoteuser when viewed by staff
$admininstitutions = $USER->get('admininstitutions');
if (empty($admininstitutions)) {
$myusers = array();
}
else {
$ph = array_merge($userids, array_values($admininstitutions));
$myusers = get_records_sql_assoc('
SELECT id,id FROM {usr}
WHERE id IN (' . join(',', array_fill(0, count($userids), '?')) . ')
AND deleted = 0
AND id IN (
SELECT usr FROM {usr_institution}
WHERE institution IN (' . join(',', array_fill(0, count($admininstitutions), '?')) . ')
)',
$ph
);
}
foreach ($users as $u) {
if (!isset($myusers[$u->id])) {
$u->email = null;
$u->remoteuser = null;
$u->hideemail = true;
}
}
}
$userids = array_keys($users);
if ($selected == 'users') {
$smarty = smarty_core();
$smarty->assign_by_ref('users', $users);
$smarty->assign_by_ref('USER', $USER);
$userlisthtml = $smarty->fetch('admin/users/userlist.tpl');
if ($USER->get('admin') || $USER->is_institutional_admin()) {
$csvfields = array('username', 'email', 'firstname', 'lastname', 'studentid', 'preferredname', 'remoteuser');
}
else {
$csvfields = array('username', 'firstname', 'lastname', 'studentid', 'preferredname');
}
$csv = join(',', $csvfields) . "\n";
foreach ($users as $user) {
$u = array();
foreach ($csvfields as $f) {
$u[] = str_replace('"', '""', $user->$f);
}
$csv .= '"' . join('","', $u) . '"' . "\n";
}
$USER->set_download_file($csv, 'users.csv', 'text/csv');
}
else if ($selected == 'accesslist') {
require_once(get_config('libroot') . 'view.php');
$accesslists = View::get_accesslists(array_keys($users));
foreach ($accesslists['collections'] as $k => $c) {
if (!isset($users[$c['owner']]->collections)) {
$users[$c['owner']]->collections = array();
}
$users[$c['owner']]->collections[$k] = $c;
}
foreach ($accesslists['views'] as $k => $v) {
if (!isset($users[$v['owner']]->views)) {
$users[$v['owner']]->views = array();
}
$users[$v['owner']]->views[$k] = $v;
}
$smarty = smarty_core();
$smarty->assign_by_ref('users', $users);
$smarty->assign_by_ref('USER', $USER);
$userlisthtml = $smarty->fetch('admin/users/accesslists.tpl');
}
$smarty = smarty();
$smarty->assign('PAGEHEADING', TITLE);
$smarty->assign('tabs', $tabs);
$smarty->assign('users', $users);
$smarty->assign('userlisthtml', $userlisthtml);
$smarty->assign('csv', isset($csv));
$smarty->display('admin/users/report.tpl');
......@@ -209,15 +209,15 @@ function UserSearch() {
userSearch = new UserSearch();
addLoadEvent(function() {
forEach(getElementsByTagAndClassName('input', 'button', 'bulkactions'), function(input) {
function connectSelectedUsersForm(formid) {
forEach(getElementsByTagAndClassName('input', 'button', formid), function(input) {
connect(input, 'onclick', function() {
// Some of the selected users aren't on the page, so just add them all to the
// form now.
var count = 0;
if (userSearch.selectusers) {
for (j in userSearch.selectusers) {
appendChildNodes('bulkactions', INPUT({
appendChildNodes(formid, INPUT({
'type': 'checkbox',
'name': 'users[' + j + ']',
'value': j,
......@@ -229,16 +229,20 @@ addLoadEvent(function() {
}
if (count) {
addElementClass('nousersselected', 'hidden');
appendChildNodes('bulkactions', INPUT({
appendChildNodes(formid, INPUT({
'type': 'hidden',
'name': 'action',
'value': input.name
}));
$('bulkactions').submit();
$(formid).submit();
return false;
}
removeElementClass('nousersselected', 'hidden');
return false;
});
});
}
addLoadEvent(function() {
forEach(['bulkactions', 'report'], connectSelectedUsersForm);
});
......@@ -303,6 +303,8 @@ $string['smallviewheaders'] = 'Small page headers';
$string['smallviewheadersdescription'] = 'If enabled, a small header and site navigation block will be displayed when viewing or editing portfolio pages created by users.';
$string['spamhaus'] = 'Enable Spamhaus URL blacklist';
$string['spamhausdescription'] = 'If enabled, URLs will be checked against the Spamhaus DNSBL';
$string['staffuserreports'] = 'Staff report access';
$string['staffuserreportsdescription'] = 'If enabled, site and institutional staff will have access to the reports page for users in their institutions. This page is normally restricted to administrators, and lists extra user information including page access lists.';
$string['surbl'] = 'Enable SURBL URL blacklist';
$string['surbldescription'] = 'If enabled, URLs will be checked against the SURBL DNSBL';
$string['disableexternalresources'] = 'Disable external resources in user HTML';
......@@ -865,11 +867,12 @@ $string['invitedby'] = 'Invited by';
$string['requestto'] = 'Request to';
$string['useradded'] = 'User added';
$string['invitationsent'] = 'Invitation sent';
$string['editselectedusers'] = 'Edit selected users';
$string['withselectedusers'] = 'With selected users';
$string['getreports'] = 'Get reports';
// Bulk actions
// Bulk actions & user reports
$string['bulkactions'] = 'Bulk actions';
$string['editselectedusersdescription'] = 'Suspend, delete, change authentication method, or download a CSV file of the users you have selected on the search page.';
$string['editselectedusersdescription1'] = 'Suspend, delete, or change the authentication method of the users you have selected on the search page.';
$string['uneditableusers'] = array(
0 => 'One of the users you selected is not editable by you, and has been removed from the list.',
1 => 'You selected %s users that are not editable by you. They have been removed from the list.',
......@@ -886,6 +889,8 @@ $string['bulkchangeauthmethodresetpassword'] = 'You have chosen an authenticatio
$string['bulkdeleteuserssuccess'] = 'Deleted %d user(s)';
$string['selectedusers'] = 'Selected users';
$string['remoteuser'] = 'Remote username';
$string['userreports'] = 'User reports';
$string['userreportsdescription'] = 'View or download information about the users you selected on the search page.';
// general stuff
$string['notificationssaved'] = 'Notification settings saved';
......
......@@ -955,6 +955,7 @@ $string['nusers'] = array(
'1 user',
'%s users',
);
$string['hidden'] = 'hidden';
// import related strings (maybe separated later)
$string['importedfrom'] = 'Imported from %s';
......
......@@ -77,6 +77,10 @@ $string['viewsubmittedtogroup'] = 'This page has been submitted to <a href="%s">
$string['viewsubmittedtogroupon'] = 'This page was submitted to <a href="%s">%s</a> on %s';
$string['nobodycanseethisview2'] = 'Only you can see this page';
$string['noviews'] = 'No pages.';
$string['nviews'] = array(
'1 page',
'%s pages',
);
$string['youhavenoviews'] = 'You have no pages.';
$string['youhaventcreatedanyviewsyet'] = "You haven't created any pages yet.";
$string['youhaveoneview'] = 'You have 1 page.';
......
......@@ -354,6 +354,9 @@ function build_admin_user_search_results($search, $offset, $limit) {
if (!$USER->get('admin') && !$USER->is_institutional_admin()) {
unset($cols['email']);
if (!get_config('staffreports')) {
unset($cols['select']);
}
}
else if (!$USER->get('admin')) {
foreach ($results['data'] as &$r) {
......
{if $item.access}<div>{$item.access}</div>{/if}
{foreach from=$item.accessgroups item=accessgroup name=ags}{strip}
{if $accessgroup.accesstype == 'loggedin'}
{str tag="loggedin" section="view"}
{elseif $accessgroup.accesstype == 'public'}
{str tag="public" section="view"}
{elseif $accessgroup.accesstype == 'friends'}
{str tag="friends" section="view"}
{elseif $accessgroup.accesstype == 'group'}
<a href="{$WWWROOT}group/view.php?id={$accessgroup.id}">{$accessgroup.name}</a>{if $accessgroup.role} ({$accessgroup.roledisplay}){/if}
{elseif $accessgroup.accesstype == 'institution'}
<a href="{$WWWROOT}institution/index.php?institution={$accessgroup.id}">{$accessgroup.id|institution_display_name}</a>
{elseif $accessgroup.accesstype == 'user'}
<a href="{$WWWROOT}user/view.php?id={$accessgroup.id}">{$accessgroup.id|display_name:null:true:true}</a>
{/if}
{if $accessgroup.startdate}
{if $accessgroup.stopdate}
<span class="date"> {$accessgroup.startdate|strtotime|format_date:'strfdaymonthyearshort'}&rarr;{$accessgroup.stopdate|strtotime|format_date:'strfdaymonthyearshort'}</span>
{else}
<span class="date"> {str tag=after} {$accessgroup.startdate|strtotime|format_date:'strfdaymonthyearshort'}</span>
{/if}
{elseif $accessgroup.stopdate}
<span class="date"> {str tag=before} {$accessgroup.stopdate|strtotime|format_date:'strfdaymonthyearshort'}</span>
{/if}{if !$dwoo.foreach.ags.last}, {/if}
{/strip}{/foreach}
{if $item.template}<div>{str tag=thisviewmaybecopied section=view}</div>{/if}
{if $item.secreturls}<div>{str tag=secreturls section=view} ({$item.secreturls})</div>{/if}
<table class="fullwidth">
<thead>
<tr>
<th>{str tag=Owner section=view}</th>
<th>{str tag=View section=view}/{str tag=Collection section=collection}</th>
<th>{str tag=accesslist section=view}</th>
</tr>
</thead>
<tbody>
{foreach from=$users item=user}
{if !$user->views && !$user->collections}
<tr class="{cycle values='r0,r1'}">
<td><a href="{$WWWROOT}user/view.php?id={$user->id}">{$user|display_name:null:true:true}</a></td>
<td colspan=3>{str tag=noviews section=view}</td>
</tr>
{else}
{foreach from=$user->views item=item}
<tr class="{cycle values='r0,r1'}">
<td><a href="{$WWWROOT}user/view.php?id={$user->id}">{$user|display_name:null:true:true}</a></td>
<td><a href="{$item.url}">{$item.name|str_shorten_text:50:true}</a></td>
<td>{include file="admin/users/accesslistitem.tpl" item=$item}</td>
</tr>
{/foreach}
{foreach from=$user->collections item=item}
<tr class="{cycle values='r0,r1'}">
<td><a href="{$WWWROOT}user/view.php?id={$user->id}">{$user|display_name:null:true:true}</a></td>
<td><a href="{$item.views[$item.viewid].url}">{$item.name|str_shorten_text:40:true}</a> ({str tag=nviews section=view arg1=count($item.views)})</td>
<td>{include file="admin/users/accesslistitem.tpl" item=$item}</td>
</tr>
{/foreach}
{/if}
{/foreach}
</tbody>
</table>
{include file="header.tpl"}
<p>{str tag=editselectedusersdescription section=admin}</p>
<p>{str tag=editselectedusersdescription1 section=admin}</p>
<div>
<div class="bulkactionform">
<span class="bulkaction-title">{str tag=exportusersascsv section=admin}:</span>
<a href="{$WWWROOT}download.php" target="_blank">{str tag=Download section=admin}</a>
</div>
{$suspendform|safe}
</div>
<div>
{$changeauthform|safe}
{$deleteform|safe}
</div>
......
{include file="header.tpl"}
<p>{str tag=userreportsdescription section=admin}</p>
<form id="report" method="post">
<select id="users" class="hidden" multiple="multiple" name="users[]">
{foreach from=$users key=id item=item}
<option selected="selected" value="{$id}">{$id}</option>
{/foreach}
</select>
<div class="tabswrap"><ul class="in-page-tabs">
{foreach from=$tabs item=tab}
<li {if $tab.selected} class="current-tab"{/if}>
<input type="submit" class="linkbtn{if $tab.selected} current-tab{/if}" name="report:{$tab.id}" value="{$tab.name}">
</li>
{/foreach}
</ul></div>
</form>
<div class="subpage">
{if $csv}
<div class="fr">
<span class="bulkaction-title">{str tag=exportusersascsv section=admin}:</span>
<a href="{$WWWROOT}download.php" target="_blank">{str tag=Download section=admin}</a>
</div>
{/if}
<h2>{str tag=selectedusers section=admin} ({count($users)})</h2>
{$userlisthtml|safe}
</div>
{include file="footer.tpl"}
......@@ -22,13 +22,20 @@
</span>
{/foreach}
</div>
{if $USER->get('admin') || $USER->is_institutional_admin() || get_config('staffreports')}
<div class="fr">
<strong>{str tag=withselectedusers section=admin}:</strong>&nbsp;
{if $USER->get('admin') || $USER->is_institutional_admin()}
<form class="fr nojs-hidden-block" id="bulkactions" action="{$WWWROOT}admin/users/bulk.php" method="post">
{str tag=editselectedusers section=admin}:
<input type="button" class="button" name="go" value="{str tag=go}">
<div id="nousersselected" class="hidden error">{str tag=nousersselected section=admin}</div>
<form class="nojs-hidden-inline" id="bulkactions" action="{$WWWROOT}admin/users/bulk.php" method="post">
<input type="button" class="button" name="edit" value="{str tag=edit}">
</form>
{/if}
<form class="nojs-hidden-inline" id="report" action="{$WWWROOT}admin/users/report.php" method="post">
<input type="button" class="button" name="reports" value="{str tag=getreports section=admin}">
</form>
<div id="nousersselected" class="hidden error">{str tag=nousersselected section=admin}</div>
</div>
{/if}
<form action="{$WWWROOT}admin/users/search.php" method="post">
<div class="searchform">
<label>{str tag='Search' section='admin'}:</label>
......
......@@ -2,24 +2,24 @@
<thead>
<tr>
<th>{str tag=username}</th>
<th>{str tag=email}</th>
{if $USER->get('admin') || $USER->is_institutional_admin()}<th>{str tag=email}</th>{/if}
<th>{str tag=firstname}</th>
<th>{str tag=lastname}</th>
<th>{str tag=studentid}</th>
<th>{str tag=preferredname}</th>
<th>{str tag=remoteuser section=admin}</th>
{if $USER->get('admin') || $USER->is_institutional_admin()}<th>{str tag=remoteuser section=admin}</th>{/if}
</tr>
</thead>
<tbody>
{foreach from=$users item=user}
<tr class="{cycle values='r0,r1'}">
<td>{$user->username}</td>
<td>{$user->email}</td>
{if $USER->get('admin') || $USER->is_institutional_admin()}<td>{if $user->hideemail}<span class="dull">({str tag=hidden})</span>{else}{$user->email}{/if}</td>{/if}
<td>{$user->firstname}</td>
<td>{$user->lastname}</td>
<td>{$user->studentid}</td>
<td>{$user->preferredname}</td>
<td>{$user->remoteuser}</td>
{if $USER->get('admin') || $USER->is_institutional_admin()}<td>{if $user->hideemail}<span class="dull">({str tag=hidden})</span>{else}{$user->remoteuser}{/if}</td>{/if}
</tr>
{/foreach}
</tbody>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment