<?php
/**
* SeekQuarry/Yioop --
* Open Source Pure PHP Search Engine, Crawler, and Indexer
*
* Copyright (C) 2009 - 2026 Chris Pollett chris@pollett.org
*
* LICENSE:
*
* 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 <https://www.gnu.org/licenses/>.
*
* END LICENSE
*
* @author Chris Pollett chris@pollett.org
* @license https://www.gnu.org/licenses/ GPL3
* @link https://www.seekquarry.com/
* @copyright 2009 - 2026
* @filesource
*/
namespace seekquarry\yioop\views\elements;
use seekquarry\yioop as B;
use seekquarry\yioop\configs as C;
/**
* Element responsible for drawing the activity screen for User manipulation
* in the AdminView.
*
* @author Chris Pollett
*/
class ManageusersElement extends Element
{
/**
* Draws a screen in which an admin can add users, delete users,
* and manipulate user roles.
*
* @param array $data info about current users and current roles, CSRF token
*/
public function render($data)
{
?>
<div class="current-activity">
<?= $this->renderUserTable($data); ?>
<script>
function submitViewUserRole()
{
elt('viewUserRoleForm').submit();
}
</script>
</div>
<?php
}
/**
* Draws the table that displays the users and their properties for
* the Yioop system
* @param array $data info about current users and current roles, CSRF token
*/
public function renderUserTable($data)
{
$data['TABLE_TITLE'] = tl('manageusers_element_users');
if ($data['FORM_TYPE'] == "edituser") {
$data['NO_TITLE'] = true;
}
$data['ACTIVITY'] = 'manageUsers';
$data['VIEW'] = $this->view; ?>
<div id='admin-info' ><?php
$default_accounts = [C\ROOT_ID, C\PUBLIC_USER_ID];
$num_columns = $_SERVER["MOBILE"] ? 4 : 8;
if (in_array($data['FORM_TYPE'], ['edituser', 'search'])) {
$data['DISABLE_ADD_TOGGLE'] = true;
}
$base_url = htmlentities(B\controllerUrl('admin', true)) .
C\p('CSRF_TOKEN') . "=" . $data[C\p('CSRF_TOKEN')] .
"&a=manageUsers";
if (isset($data['START_ROW'])) {
$base_url .= "&start_row=".$data['START_ROW'].
"&end_row=".$data['END_ROW'].
"&num_show=".$data['NUM_SHOW'];
}
$context = "";
$arg_context= "";
if ($data['FORM_TYPE'] == 'search' ||
!empty($data['context']) && $data['context'] == 'search') {
$context = 'context=search';
$arg_context= "&arg=search";
}
if ($data['FORM_TYPE'] == 'edituser') {
$this->view->helper("close")->render($base_url . $arg_context);
$this->renderUserForm($data);
e("</div>");
return;
}
$icon_helper = $this->view->helper('iconlink');
?>
<table class="admin-table">
<tr><td class="no-border" colspan="<?=
$num_columns ?>"><?php $this->view->helper(
"pagingtable")->render($data);
if ($data['FORM_TYPE'] != "edituser") { ?>
<div id='admin-form-row' class='admin-form-row'><?php
if ($data['FORM_TYPE'] == "search") {
$this->renderSearchForm($data);
} else {
$this->renderUserForm($data);
}?>
</div><?php
} ?></td>
</tr>
<tr>
<th><?= tl('manageusers_element_username') ?></th><?php
if (!$_SERVER["MOBILE"]) { ?>
<th><?= tl('manageusers_element_firstname')?></th>
<th><?= tl('manageusers_element_lastname') ?></th>
<th><?= tl('manageusers_element_email') ?></th>
<th><?= tl('manageusers_element_groups') ?></th><?php
} ?>
<th><?= tl('manageusers_element_status') ?></th>
<th colspan='2'><?= tl('manageusers_element_actions') ?></th>
</tr><?php
$delete_url = $base_url . "&arg=deleteuser&$context";
$edit_url = $base_url . "&arg=edituser&$context";
$mobile_columns = ['USER_NAME', 'STATUS'];
$stretch = ($_SERVER["MOBILE"]) ? 1 : 2;
$out_columns = ['USER_NAME', 'FIRST_NAME', 'LAST_NAME',
'EMAIL', 'NUM_GROUPS', 'STATUS'];
foreach ($data['USERS'] as $user) { ?>
<tr><?php
foreach ($out_columns as $colname) {
$user_column = $user[$colname];
if ($colname == "USER_ID" || ($_SERVER["MOBILE"] &&
!in_array($colname, $mobile_columns))) {
continue;
}
if (strcmp($colname, "STATUS") == 0) {
?><td><?=$data['STATUS_CODES'][$user['STATUS']] ?>
</td><?php
} else {?>
<td><?=$user_column ?></td><?php
}
}
if ($user['USER_ID'] == C\PUBLIC_USER_ID) {?>
<td class='back-light-gray'><?=
$icon_helper->renderLink("javascript:return false;",
"edit", "", false, "large-padding disabled");
?></td><?php
} else { ?>
<td><?= $icon_helper->renderLink($edit_url .
'&user_name=' . $user['USER_NAME'], "edit",
"", false, "large-padding"); ?></td><?php
}
$disabled = in_array($user['USER_ID'], $default_accounts);
$back_gray = ($disabled ) ? " class='back-light-gray' " : "";
?>
<td<?=$back_gray?>><?php
if ($disabled) {
$icon_helper->renderLink("javascript:return false;",
"delete", "", false, "large-padding disabled");
} else {
$icon_helper->renderLink($delete_url .
'&user_name='. $user['USER_NAME'], "delete",
"", false, "large-padding");
}?></td>
</tr><?php
} ?>
</table>
</div><?php
}
/**
* Draws the add user and edit user forms
*
* @param array $data consists of values of user fields set
* so far as well as values of the drops downs on the form
*/
public function renderUserForm($data)
{
$admin_url = htmlentities(B\controllerUrl('admin', true));
$base_url = $admin_url .
C\p('CSRF_TOKEN')."=".$data[C\p('CSRF_TOKEN')].
"&a=manageUsers&";
$visibles = "visible_roles=".$data['visible_roles'].
"&visible_groups=".$data['visible_groups'];
$limits = isset($data['ROLE_LIMIT']) ?
"role_limit=".$data['ROLE_LIMIT']: "role_limit=0";
$limits .= isset($data['GROUP_LIMIT']) ?
"&group_limit=".$data['GROUP_LIMIT']: "&group_limit=0";
$filters = isset($data['ROLE_FILTER']) ?
"role_filter=".$data['ROLE_FILTER'] : "";
$f_amp = isset($data['ROLE_FILTER']) ? "&" : "";
$filters .= isset($data['GROUP_FILTER']) ?
"$f_amp;group_filter=".$data['GROUP_FILTER'] : "";
$base_url .= $visibles;
$edituser = ($data['FORM_TYPE'] == "edituser") ? true: false;
?>
<form id="admin-form" method="post" autocomplete="off" ><?php
if ($edituser) {
e("<h2>".tl('manageusers_element_user_info'). "</h2>");
} else {
e("<h2>".tl('manageusers_element_add_user'). "</h2>");
}
?>
<input type="hidden" name="c" value="admin" >
<input type="hidden" name="<?= C\p('CSRF_TOKEN') ?>" value="<?=
$data[C\p('CSRF_TOKEN')] ?>" >
<input type="hidden" name="a" value="manageUsers" >
<input type="hidden" name="arg" value="<?=
$data['FORM_TYPE'] ?>" >
<input type="hidden" id="visible-roles" name="visible_roles"
value="<?= $data['visible_roles'] ?>" >
<input type="hidden" id="visible-groups" name="visible_groups"
value="<?= $data['visible_groups'] ?>" >
<table class="name-table">
<tr><th class="table-label"><label for="user-name"><?=
tl('manageusers_element_username')?>:</label></th>
<td><input type="text" id="user-name"
name="user_name" maxlength="<?= C\NAME_LEN ?>"
value="<?= $data['CURRENT_USER']['user_name'] ?>"
class="narrow-field" <?php
if ($edituser) {
e(' disabled="disabled" ');
}
?> ></td></tr>
<?php
if (!empty($data['CAN_ACT_AS_USER'])) {
$act_as_url = $base_url . "&arg=actasuser&user_name=" .
urlencode($data['CURRENT_USER']['user_name']);
?>
<tr><th></th><td><a href="<?= $act_as_url ?>"><?=
tl('manageusers_element_act_as_user') ?></a></td></tr>
<?php
}
?>
<tr><th class="table-label"><label for="first-name"><?=
tl('manageusers_element_firstname') ?>:</label></th>
<td><input type="text" id="first-name"
name="first_name" maxlength="<?= C\NAME_LEN ?>"
value="<?= $data['CURRENT_USER']['first_name'] ?>"
class="narrow-field" ></td></tr>
<tr><th class="table-label"><label for="last-name"><?=
tl('manageusers_element_lastname') ?>:</label></th>
<td><input type="text" id="last-name"
name="last_name" maxlength="<?=C\NAME_LEN ?>"
value="<?= $data['CURRENT_USER']['last_name'] ?>"
class="narrow-field" ></td></tr>
<tr><th class="table-label"><label for="e-mail"><?=
tl('manageusers_element_email') ?>:</label></th>
<td><input type="email" id="e-mail"
name="email" maxlength="<?= C\LONG_NAME_LEN ?>"
value="<?= $data['CURRENT_USER']['email'] ?>"
class="narrow-field" ></td></tr>
<tr><th class="table-label"><label
for="update-userstatus-currentuser"><?=
tl('manageusers_element_status') ?>:</label></th>
<td><?php
$additional_attributes = [];
if ($data['CURRENT_USER']['user_name'] == 'root') {
$additional_attributes["disabled"] = "disabled";
}
$this->view->helper("options")->render(
"update-userstatus-currentuser",
"status", $data['STATUS_CODES'],
$data['CURRENT_USER']['status'], false,
$additional_attributes);
?></td></tr>
<?php
if ($edituser) {
$context = "";
if (!empty($data['context']) && $data['context'] == 'search') {
$context = 'context=search';
}
?>
<tr><th class="table-label" style="vertical-align:top"><?=
tl('manageusers_element_roles') ?>:</th>
<td><div class='light-gray-box'>
<div class="center">
[<button class="button-anchor"
form="admin-form" type='submit' name='visible_roles'
value='<?= ($data['visible_roles'] == 'true') ?
"false" : "true" ?>'><?=
tl('manageusers_element_num_roles',
$data['NUM_USER_ROLES'])?></button>]<?php
if ($data['visible_roles'] == 'true') {
$context = "";
if (!empty($data['context'])) {
$context = 'context=search';
}
$sort_string = urlencode(json_encode(
$data['ROLE_SORTS']));
$action_url = "$base_url&user_name=".
$data['CURRENT_USER']['user_name'].
"&role_filter=" . $data['ROLE_FILTER'] .
"&role_sorts=" . $sort_string;
$with_selected_actions = [
-1 => tl('manageusers_element_with_selected'),
$action_url . "&arg=deleteuserrole&" =>
tl('manageusers_element_delete'),
];
$this->view->helper("options")->render(
"with-selected-user-roles",
"with_selected_roles",
$with_selected_actions, -1, '
var ids_string = arrayIntoFormVariable(
role_ids, "role_ids");
window.location =
this[this.selectedIndex].value +
ids_string;
', ["aria-label" =>
$with_selected_actions[-1]]);
}?>
</div>
<?php
if ($data['visible_roles'] == 'true') {
$action_url = $base_url. "&arg=edituser&$context".
"&user_name=" . $data['CURRENT_USER']['user_name'].
"&role_filter=".$data['ROLE_FILTER'].
"&group_filter=".$data['GROUP_FILTER'];
$sort_urls = ['ROLE_NAME' => ''];
foreach ($sort_urls as $field => $url) {
$role_sorts = $data['ROLE_SORTS'];
$sort_dir = (empty($role_sorts[$field]) ||
$role_sorts[$field]=='ASC') ? 'DESC' : 'ASC';
unset($role_sorts[$field]);
$role_sorts = array_merge([$field => $sort_dir],
$role_sorts);
$role_sorts = urlencode(json_encode($role_sorts));
$sort_urls[$field] =
"$action_url&role_sorts=$role_sorts";
}
?>
<div id="user-roles">
<table><tr><td></td>
<th><a href='<?=$sort_urls['ROLE_NAME'] ?>'><?=
tl('manageusers_element_name') ?></a></th>
<th class='center'><?=
tl('manageusers_element_action') ?></th></tr><?php
foreach ($data['USER_ROLES'] as $role_array) {
e("<tr><td><input type='checkbox' class='role-id' " .
"id='role-" . $role_array['ROLE_ID'] . "' " .
"name='" . $role_array['ROLE_ID']. "' " .
" value='" . $role_array['ROLE_ID'] . "' >");
e("</td><td><b><label for='role-".
$role_array['ROLE_ID'] ."'>".
$role_array['ROLE_NAME'].
"</label></b></td>");
if ($data['CURRENT_USER']['user_name'] == 'root' &&
$role_array['ROLE_NAME'] == 'Admin') {
e("<td><span class='gray'>".
tl('manageusers_element_delete').
"</span></td>");
} else {
e("<td><a href='{$admin_url}a=manageUsers".
"&arg=deleteuserrole&$context".
"&role_ids=" . $role_array['ROLE_ID']);
e("&user_name=".
$data['CURRENT_USER']['user_name'].
"&".C\p('CSRF_TOKEN')."=".
$data[C\p('CSRF_TOKEN')].
"&$visibles&$limits'>".
tl('manageusers_element_delete').
"</a></td>");
}
e("</tr>");
}
?>
</table>
<?php
if ($data['ROLE_FILTER'] != "" ||
(isset($data['NUM_USER_ROLES']) &&
$data['NUM_USER_ROLES'] > C\NUM_RESULTS_PER_PAGE)) {
$limit = isset($data['ROLE_LIMIT']) ?
$data['ROLE_LIMIT']: 0;
?>
<div class="center">
<?php
$action_url = $base_url. "&user_name=".
$data['CURRENT_USER']['user_name'].
"&role_filter=".$data['ROLE_FILTER'].
"&group_filter=".$data['GROUP_FILTER'];
if ($limit >= C\NUM_RESULTS_PER_PAGE ) {
?><a href='<?=
"$action_url&arg=edituser&$context" .
"&role_limit=".
($limit - C\NUM_RESULTS_PER_PAGE) ?>'
><<</a><?php
}
?>
<input class="very-narrow-field center"
name="role_filter" type="text" maxlength="<?=
C\NAME_LEN ?>" value='<?=$data['ROLE_FILTER'] ?>' >
<?php
if ($data['NUM_USER_ROLES'] > $limit +
C\NUM_RESULTS_PER_PAGE) {
?><a href='<?=
"$action_url&arg=edituser&$context" .
"&role_limit=".
($limit + C\NUM_RESULTS_PER_PAGE) ?>'
>>></a>
<?php
}
?><br>
<button type="submit" name="change_filter"
value="role"><?=tl('manageusers_element_filter')
?></button>
<br>
</div>
<?php
}
?>
<div class="center" >
<input type="text" name="selectrole" id='select-role'
class="very-narrow-field" >
<button type="submit"
class="button-box">
<label for='select-role'><?=
tl('manageusers_element_add_role') ?></label>
</button>
</div>
</div>
<?php
}
?>
</div>
</td></tr>
<tr><th class="table-label" style="vertical-align:top"><?=
tl('manageusers_element_groups') ?>:</th>
<td><div class='light-gray-box'>
<div class="center">
[<button class="button-anchor"
form="admin-form" type='submit'
name='visible_groups'
value='<?= ($data['visible_groups'] == 'true') ?
"false" : "true" ?>'><?=
tl('manageusers_element_num_groups',
$data['NUM_USER_GROUPS'])?></button>]<?php
if ($data['visible_groups'] == 'true') {
$context = "";
if (!empty($data['context'])) {
$context = 'context=search';
}
$sort_string = urlencode(json_encode(
$data['GROUP_SORTS']));
$action_url = "$base_url&user_name=".
$data['CURRENT_USER']['user_name'].
"&group_filter=" . $data['GROUP_FILTER'] .
"&group_sorts=" . $sort_string;
$with_selected_actions = [
-1 => tl('manageusers_element_with_selected'),
$action_url . "&arg=deleteusergroup&" =>
tl('manageusers_element_delete'),
];
$this->view->helper("options")->render(
"with-selected-user-groups", "with_selected_group",
$with_selected_actions, -1, '
var ids_string = arrayIntoFormVariable(
group_ids, "group_ids");
window.location =
this[this.selectedIndex].value +
ids_string;
', ["aria-label" => $with_selected_actions[-1]]);
}?>
</div>
<?php
if ($data['visible_groups'] == 'true') {
$action_url = $base_url. "&arg=edituser&$context".
"&user_name=" . $data['CURRENT_USER']['user_name'].
"&role_filter=".$data['ROLE_FILTER'].
"&group_filter=".$data['GROUP_FILTER'];
$sort_urls = ['GROUP_NAME' => '', 'STATUS' => ''];
foreach ($sort_urls as $field => $url) {
$group_sorts = $data['GROUP_SORTS'];
$sort_dir = (empty($group_sorts[$field]) ||
$group_sorts[$field] == 'ASC') ? 'DESC' : 'ASC';
unset($group_sorts[$field]);
$group_sorts = array_merge([$field => $sort_dir],
$group_sorts);
$group_sorts = urlencode(json_encode($group_sorts));
$sort_urls[$field] =
"$action_url&group_sorts=$group_sorts";
}
?>
<div id="user-groups">
<table><tr><td></td>
<th><a href='<?=$sort_urls['GROUP_NAME'] ?>'><?=
tl('manageusers_element_name') ?></a></th>
<th><a href='<?=$sort_urls['STATUS'] ?>'><?=
tl('manageusers_element_status') ?></a></th>
<th class='center'><?=
tl('manageusers_element_action') ?></th></tr><?php
foreach ($data['USER_GROUPS'] as $group_array) {
e("<tr><td><input type='checkbox' class='group-id' " .
"id='group-" . $group_array['GROUP_ID'] . "' " .
"name='" . $group_array['GROUP_ID'] . "' " .
" value='" . $group_array['GROUP_ID'] . "' >");
e("<td><b><label for='group-" .
$group_array['GROUP_ID'] . "'>".
$group_array['GROUP_NAME'].
"</label></b></td>");
e("<td class='gray'>".
$data["MEMBERSHIP_CODES"][$group_array['STATUS']].
"</td>");
e("<td><a href='{$admin_url}a=manageUsers".
"&arg=deleteusergroup&$context".
"&group_ids=".
$group_array['GROUP_ID']);
e("&user_name=".$data['CURRENT_USER']['user_name'].
"&".C\p('CSRF_TOKEN')."=".
$data[C\p('CSRF_TOKEN')].
"&$visibles&$limits'>".
tl('manageusers_element_delete')."</a></td>");
}
?>
</table>
<?php
if ($data['GROUP_FILTER'] != "" ||
(isset($data['NUM_USER_GROUPS']) &&
$data['NUM_USER_GROUPS'] > C\NUM_RESULTS_PER_PAGE)) {
$limit = isset($data['GROUP_LIMIT']) ?
$data['GROUP_LIMIT']: 0;
?>
<div class="center">
<?php
$action_url = $base_url. "&user_name=".
$data['CURRENT_USER']['user_name'].
"&role_filter=".$data['ROLE_FILTER'].
"&group_filter=".$data['GROUP_FILTER'];
if ($limit >= C\NUM_RESULTS_PER_PAGE ) {
?><a href='<?=
"$action_url&arg=edituser&$context" .
"&group_limit=".
($limit - C\NUM_RESULTS_PER_PAGE) ?>'
><<</a><?php
} ?>
<input class="very-narrow-field center"
name="group_filter" type="text" maxlength="<?=
C\SHORT_TITLE_LEN ?>" value='<?=
$data['GROUP_FILTER'] ?>' >
<?php
if ($data['NUM_USER_GROUPS'] > $limit +
C\NUM_RESULTS_PER_PAGE) {
?><a href='<?=
"$action_url&arg=edituser&$context" .
"&group_limit=".
($limit + C\NUM_RESULTS_PER_PAGE)
?>'>>></a>
<?php
}
?><br>
<button type="submit" name="change_filter"
value="group"><?= tl('manageusers_element_filter')
?></button><br>
</div>
<?php
}
?>
<div class="center" >
<input type="text" name="selectgroup" id='select-group'
class="very-narrow-field" >
<button type="submit"
class="button-box">
<label for='select-group'><?=
tl('manageusers_element_add_group')
?></label></button>
</div>
</div>
<?php
}
?>
</div>
</td></tr>
<?php
}
if (!empty($data['CAN_SET_PASSWORD'])) {
?>
<tr><th class="table-label"><label for="pass-word"><?=
tl('manageusers_element_password') ?>:</label></th>
<td><input type="password" id="pass-word"
onclick='this.select();'
name="password" maxlength="<?= C\LONG_NAME_LEN?>"
value="<?= $data['CURRENT_USER']['password'] ?>"
class="narrow-field" ><?php
$this->view->helper("passwordrequirements")->render(
"pass-word"); ?></td></tr>
<tr><th class="table-label"><label for="retype-password"><?=
tl('manageusers_element_retype_password') ?>:</label></th>
<td><input type="password" id="retype-password"
onclick='this.select();'
name="retypepassword" maxlength="<?= C\LONG_NAME_LEN ?>"
value="<?= $data['CURRENT_USER']['password'] ?>"
class="narrow-field" ></td></tr>
<?php
}
?>
<tr><td></td><td class="center"><button
class="button-box" <?php
if ($data['FORM_TYPE'] == 'edituser') {
e("id='focus-button'");
}?>
type="submit"><?= tl('manageusers_element_save')
?></button></td>
</tr>
</table>
</form>
<script>
/**
* Roles that have been checked for a user for a possible action
* @var Array
*/
var role_ids = [];
/**
* Groups that have been checked for a user for a possible action
* @var Array
*/
var group_ids = [];
/*
* Converts an array arr into a single string
* name_of_some_for_variable=serialized_as_string_array
* suitable to be concatenated to a url
*
* @param Array arr array to make into an HTTP form variable string
* @param String form_var name of form variable for output
* @return String in format
* name_of_some_for_variable=serialized_as_string_array
*/
function arrayIntoFormVariable(arr, form_var)
{
arr_string = arr.join("*");
return form_var + '=' + arr_string;
}
/*
* Adds the value of a checkboxed item to the end of an array
* if the item is checked, otherwise delete the value of the
* item from the array if the item is unchecked.
*
* @param Array ids array used to maintain a set of item values
* @param DOMElement elt (presumably a checkbox) used to adjust
* array based on it being checked or not
*/
function updateCheckedIds(ids, elt)
{
var form = this.form;
if (elt.checked) {
ids[ids.length] = elt.value;
} else {
var index = ids.indexOf(elt.value);
if (index > -1) {
ids.splice(index, 1);
}
}
}
</script>
<?php
}
/**
* Draws the search for users forms
*
* @param array $data consists of values of user fields set
* so far as well as values of the drops downs on the form
*/
public function renderSearchForm($data)
{
$controller = "admin";
$activity = "manageUsers";
$view = $this->view;
$title = tl('manageusers_element_search_user');
$fields = [
tl('manageusers_element_username') => "user",
tl('manageusers_element_firstname') => "first",
tl('manageusers_element_lastname') => "last",
tl('manageusers_element_email') => "email",
tl('manageusers_element_status') =>
["status", $data['EQUAL_COMPARISON_TYPES']],
];
$postfix = "name";
$dropdowns = [
"status" => $data['STATUS_CODES']
];
$view->helper("searchform")->render($data, $controller, $activity,
$view, $title, $fields, $dropdowns,
$postfix);
}
}