plugins/admin/admin_modules/yf_admin.class.php
<?php
/**
* Admin users manager.
*
* @author YFix Team <yfix.dev@gmail.com>
* @version 1.0
*/
class yf_admin
{
const table = 'admin';
public function _init()
{
$admin_id = main()->ADMIN_ID;
$this->display_func = function ($a = [], $a2 = []) use ($admin_id) {
isset($a2['id']) && $a = $a2;
return ! ($a['id'] == $admin_id || $a['id'] == 1);
};
}
public function _show_quick_filter()
{
$filter = _class('admin_methods')->_get_filter();
foreach ((array) $filter as $k => $v) {
$a[] = '<small class="text-info">' . $k . ':' . $v . '</small> ';
}
$a[] = a('/@object/filter_save/clear/', 'Clear filter', 'fa fa-close', '', '', '');
return $a ? '<div class="pull-right">' . implode(PHP_EOL, $a) . '</div>' : '';
}
public function show()
{
$quick_filter = $this->_show_quick_filter();
return
'<div class="col-md-12">' .
($quick_filter ? '<div class="col-md-6 pull-right" title="' . t('Quick filter') . '">' . $quick_filter . '</div>' : '') .
'</div>' .
table(from(self::table), [
'filter' => true,
'filter_params' => [
'login' => 'like',
'email' => 'like',
],
'hide_empty' => true,
'custom_fields' => [
'auths' => (string) select('admin_id', 'COUNT(*)')->from('sys_log_admin_auth')->group_by('admin_id')->where_raw('admin_id IN(%ids)'),
'visits' => (string) select('admin_id', 'COUNT(*)')->from('sys_log_admin_exec')->group_by('admin_id')->where_raw('admin_id IN(%ids)'),
'last_visit' => (string) select('admin_id', 'MAX(`date`)')->from('sys_log_admin_exec')->group_by('admin_id')->where_raw('admin_id IN(%ids)'),
'revisions' => (string) select('user_id', 'COUNT(*)')->from('sys_revisions')->group_by('user_id')->where_raw('user_id IN(%ids)'),
'wall' => (string) select('user_id', 'COUNT(*)')->from('admin_walls')->group_by('user_id')->where_raw('user_id IN(%ids)'),
],
])
->on_before_render(function ($p, $data, $table) {
$table->data_admin_groups = main()->get_data('admin_groups');
})
->func('group', function ($gid, $e, $a, $p, $t) {
return '<b class="text-info">' . $a['login'] . '</b> <small><br><i>' . $a['email'] . '</i></small><br>' .
$a['first_name'] . ' ' . $a['last_name'] . ' ' .
'<div class="pull-right">' .
a('/admin_groups/edit/' . $gid, $t->data_admin_groups[$gid], 'fa fa-users') .
a('/@object/filter_save/clear/?filter=group:' . $gid, '', 'fa fa-filter', '') .
'</div>';
}, ['desc' => 'Info'])
->func('id', function ($v, $e, $a) {
return $a['visits'] ? a('/log_admin_exec/show_for_admin/' . $a['id'], (int) $a['visits'], 'fa fa-filter') : '';
}, ['desc' => 'Visits'])
->func('id', function ($v, $e, $a) {
return $a['auths'] ? a('/log_admin_auth/show_for_admin/' . $a['id'], (int) $a['auths'], 'fa fa-filter') : '';
}, ['desc' => 'Auths'])
->func('id', function ($v, $e, $a) {
return $a['revisions'] ? a('/log_admin_revisions/show_for_admin/' . $a['id'], (int) $a['revisions'], 'fa fa-filter') : '';
}, ['desc' => 'Revisions'])
->func('id', function ($v, $e, $a) {
return $a['wall'] ? a('/log_admin_wall/show_for_admin/' . $a['id'], (int) $a['wall'], 'fa fa-filter') : '';
}, ['desc' => 'Wall'])
->text('go_after_login')
->func('add_date', function ($v, $e, $a) {
$a['last_visit'] && $out[] = '<span title="' . t('Last visit date') . '"><i class="fa fa-eye"></i> ' . _format_date($a['last_visit'], 'full') . '</span>';
$a['last_login'] && $out[] = '<span title="' . t('Last login date') . '"><i class="fa fa-sign-in"></i> ' . _format_date($a['last_login'], 'full') . '</span>';
$a['add_date'] && $out[] = '<span title="' . t('Account add date') . '"><i class="fa fa-plus"></i> ' . _format_date($a['add_date'], 'full') . '</span>';
return $out ? '<small>' . implode('<br />' . PHP_EOL, $out) . '</small>' : null;
}, ['desc' => 'Dates'])
->btn_edit(['no_ajax' => 1, 'btn_no_text' => 1])
->btn_delete(['display_func' => $this->display_func, 'btn_no_text' => 1])
->btn_active(['display_func' => $this->display_func, 'btn_no_text' => 1, 'short' => 1])
->btn('login as', url('/@object/login_as/%d'), ['display_func' => $this->display_func, 'no_ajax' => 1, 'icon' => 'fa fa-sign-in', 'btn_no_text' => 1, 'class_add' => 'btn-info'])
->footer_add(['no_ajax' => 1, 'class_add' => 'btn-primary'])
->footer_link('Auth log', url('/log_admin_auth'))
->footer_link('Auth fails log', url('/log_admin_auth_fails'));
}
public function edit()
{
$id = (int) ($_GET['id']);
if ( ! $id) {
return _e('Wrong id');
}
$a = from(self::table)->whereid($id)->get();
$a['back_link'] = url('/@object');
$a['redirect_link'] = url('/@object');
$a['password'] = '';
$func = $this->display_func;
$display = $func($a);
$a = (array) $_POST + $a;
$up = ['login', 'email', 'first_name', 'last_name', 'go_after_login', 'password'];
if ($display) {
$up[] = 'group';
$up[] = 'active';
}
return
'<div class="col-md-6">'
. form($a, ['autocomplete' => 'off', 'show_alerts' => 1])
->validate([
'__before__' => 'trim',
'login' => 'required|alpha_dash_dots|is_unique_without[admin.login.' . $id . ']',
'email' => 'required|valid_email|is_unique_without[admin.email.' . $id . ']',
'first_name' => 'required',
'last_name' => 'required',
'password' => 'password_update',
'group' => $display ? 'required|exists[admin_groups.id]' : '',
])
->db_update_if_ok(self::table, $up)
->on_after_update(function () use ($a, $id) {
common()->admin_wall_add([t('admin account edited: %login', ['%login' => $_POST['login']]), $id]);
})
->login()
->email()
->password()
->text('first_name')
->text('last_name')
->select_box('group', main()->get_data('admin_groups'), ['selected' => $a['group'], 'display_func' => $this->display_func])
->text('go_after_login', 'Url after login')
->active_box('', ['display_func' => $this->display_func])
->save_and_back()
. '</div>'
. '<div class="col-md-6">' . $this->ajax($id) . '</div>';
}
public function add()
{
$a = $_POST;
$a['redirect_link'] = url('/@object');
return form($a, ['autocomplete' => 'off', 'show_alerts' => 1])
->validate([
'__before__' => 'trim',
'login' => 'required|alpha_dash_dots|is_unique[admin.login]',
'email' => 'required|valid_email|is_unique[admin.email]',
'first_name' => 'required',
'last_name' => 'required',
'password' => 'required|md5',
'group' => 'required|exists[admin_groups.id]',
])
->db_insert_if_ok(self::table, ['login', 'email', 'first_name', 'last_name', 'go_after_login', 'password', 'group', 'active'], ['add_date' => time()])
->on_after_update(function () {
common()->admin_wall_add(['admin account added: ' . $_POST['login'] . '', main()->ADMIN_ID]);
})
->login()
->email()
->password(['value' => ''])
->text('first_name')
->text('last_name')
->select_box('group', main()->get_data('admin_groups'), ['selected' => $a['group']])
->text('go_after_login', 'Url after login')
->active_box()
->save_and_back();
}
/**
* @param mixed $force_id
*/
public function ajax($force_id = false)
{
$id = (int) ($force_id ?: $_GET['id']);
$admin = from(self::table)->whereid($id)->get();
if ( ! $id || ! $admin) {
return _404();
}
$a = [
'auths' => (int) select('COUNT(*)')->from('sys_log_admin_auth')->group_by('admin_id')->where('admin_id', $id)->one(),
'visits' => (int) select('COUNT(*)')->from('sys_log_admin_exec')->group_by('admin_id')->where('admin_id', $id)->one(),
'last_visit' => (int) select('MAX(`date`)')->from('sys_log_admin_exec')->group_by('admin_id')->where('admin_id', $id)->one(),
'revisions' => (int) select('COUNT(*)')->from('sys_revisions')->group_by('user_id')->where('user_id', $id)->one(),
'wall' => (int) select('COUNT(*)')->from('admin_walls')->group_by('user_id')->where('user_id', $id)->one(),
];
$info = [];
$a['auths'] ? $info[] = a('/log_admin_auth/show_for_admin/' . $id, t('Auths'), 'fa fa-sign-in', (int) $a['auths']) : '';
$a['visits'] ? $info[] = a('/log_admin_exec/show_for_admin/' . $id, t('Visits'), 'fa fa-eye', (int) $a['visits']) : '';
$a['revisions'] ? $info[] = a('/log_admin_revisions/show_for_admin/' . $id, t('Content revisions'), 'fa fa-file-text', (int) $a['revisions']) : '';
$a['wall'] ? $info[] = a('/log_admin_wall/show_for_admin/' . $id, t('Wall records'), 'fa fa-asterisk', (int) $a['wall']) : '';
$format = 'long';
$dates = [];
$admin['add_date'] && $dates[] = html()->icon('fa fa-plus', t('Account add date'), _format_date($admin['add_date'], $format));
$admin['last_login'] && $dates[] = html()->icon('fa fa-sign-in', t('Last sign in'), _format_date($admin['last_login'], $format));
$a['last_visit'] && $dates[] = html()->icon('fa fa-eye', t('Last visit'), _format_date($a['last_visit'], $format));
$pair_active = str_replace('class="', 'disabled class="', main()->get_data('pair_active'));
$pair_yes_no = str_replace('class="', 'disabled class="', main()->get_data('pair_yes_no'));
$func = $this->display_func;
$allowed = $func($admin);
$actions = [];
$actions[] = $pair_active[$admin['active']];
$actions[] = a('/@object/edit/' . $id, t('Edit'), 'fa fa-edit', '', 'no-popover');
$allowed && $actions[] = a('/@object/login_as/' . $id, t('Login as'), 'fa fa-sign-in', '', 'no-popover');
$visits_chart = '';
if ( ! is_ajax()) {
$daily_visits = $this->_get_admin_daily_visits($id);
if ($daily_visits) {
$visits_chart = _class('charts')->jquery_sparklines($daily_visits[$id]);
}
}
$auths_chart = '';
if ( ! is_ajax()) {
$daily_auths = $this->_get_admin_daily_auths($id);
if ($daily_auths) {
$auths_chart = _class('charts')->jquery_sparklines($daily_auths[$id]);
}
}
$online_admin_ids = (array) redis()->smembers('online_by_admin');
$online_last_time = (int) (redis()->hget('online_by_admin_last', $id) / 100);
$online_minutes_ago = floor((time() - $online_last_time) / 60);
$online_sockets = array_map('stripslashes', (array) redis()->smembers('socket_by_admin:' . $id));
$is_online = in_array($id, $online_admin_ids) && $online_minutes_ago <= 30 && $online_sockets;
if ($is_online) {
$online_info = '<small class="text-success"><b>'
. a('#', t('Last visit time') . ': ' . date('Y-m-d H:i:s', $online_last_time), 'fa fa-clock-o', $online_minutes_ago . ' ' . t('minutes ago'), 'btn-success') . ' '
. a('#', t('Websockets'), 'fa fa-link', count((array) $online_sockets), '')
. '</b></small>';
}
$gid = $admin['group'];
$admin_groups = main()->get_data('admin_groups');
$group_html = a('/admin_groups/edit/' . $gid, $admin_groups[$gid], 'fa fa-users') .
a('/@object/filter_save/clear/?filter=group:' . $gid, '', 'fa fa-filter', '');
$data = [
'is_online' => $is_online ? $online_info : '',
'id' => $admin['id'] . ' ' . implode(PHP_EOL, $actions),
'login' => _prepare_html($admin['login']),
'name' => _prepare_html($admin['first_name'] . ' ' . $admin['last_name']),
'email' => _prepare_html($admin['email']),
'group' => $group_html,
'info' => implode(PHP_EOL, $info),
'dates' => $dates ? '<small>' . implode(PHP_EOL . '<br />', $dates) . '</small>' : '',
'visits' => $visits_chart,
'auths' => $auths_chart,
];
foreach ((array) $data as $k => $v) {
if ( ! strlen($v)) {
unset($data[$k]);
}
}
$body = html()->simple_table($data, [
'condensed' => 1,
'val' => [
'func' => function ($in) {
return $in ?: false;
},
],
]);
if ($force_id) {
return $body;
}
echo is_ajax() ? $body : common()->show_empty_page($body);
}
/**
* @param mixed $admin_ids
*/
public function _get_admin_daily_visits($admin_ids = [])
{
$days = 60;
$min_time = time() - $days * 86400;
$sql = select('admin_id', 'FROM_UNIXTIME(`date`, "%Y-%m-%d") AS d', 'COUNT(*) AS c')
->from('sys_log_admin_exec')
->whereid($admin_ids, 'admin_id')
->where('`date`', '>', $min_time)
->group_by('admin_id', 'FROM_UNIXTIME(`date`, "%Y-%m-%d")');
return $this->_get_admin_daily_info($sql, $days, $admin_ids);
}
/**
* @param mixed $admin_ids
*/
public function _get_admin_daily_auths($admin_ids = [])
{
$days = 60;
$min_time = time() - $days * 86400;
$sql = select('admin_id', 'FROM_UNIXTIME(`date`, "%Y-%m-%d") AS d', 'COUNT(*) AS c')
->from('sys_log_admin_auth')
->whereid($admin_ids, 'admin_id')->where('`date`', '>', $min_time)
->group_by('admin_id', 'FROM_UNIXTIME(`date`, "%Y-%m-%d")');
return $this->_get_admin_daily_info($sql, $days, $admin_ids);
}
/**
* @param mixed $sql
* @param mixed $days
* @param mixed $admin_ids
*/
public function _get_admin_daily_info($sql, $days = 60, $admin_ids = [])
{
if ( ! $admin_ids) {
return false;
}
if ( ! is_array($admin_ids)) {
$admin_ids = [$admin_ids];
}
$time = time();
$days = $days ?: 60;
$data = [];
foreach ((array) $sql->all() as $a) {
$data[$a['admin_id']][$a['d']] = $a['c'];
}
if ( ! $data) {
return false;
}
$dates = [];
foreach (range($days, 0) as $days_ago) {
$date = date('Y-m-d', $time - $days_ago * 86400);
$dates[$date] = $days_ago;
}
$out = [];
foreach ((array) $data as $admin_id => $admin_dates) {
$out[$admin_id] = [];
$_data = null;
foreach ($dates as $date => $days_ago) {
$_data = $admin_dates[$date];
// Trim empty values from left side
if ( ! $out[$admin_id] && ! $_data) {
continue;
}
$out[$admin_id][$date] = $_data;
}
// Trim values from the right side too
foreach (array_reverse($out[$admin_id], $preserve_keys = true) as $k => $v) {
if ($v) {
break;
}
unset($out[$admin_id][$k]);
}
}
return $out;
}
public function delete()
{
$id = (int) $_GET['id'];
$id && $a = from(self::table)->whereid($id)->get();
$func = $this->display_func;
$allowed = $func($a);
if ($a['id'] && $allowed) {
db()->delete(self::table, $id);
common()->admin_wall_add(['admin account deleted', $id]);
}
if (is_ajax()) {
no_graphics(true);
echo $id;
} else {
return js_redirect(url('/@object'));
}
}
public function active()
{
$id = (int) $_GET['id'];
$id && $a = from(self::table)->whereid($id)->get();
$func = $this->display_func;
$allowed = $func($a);
if ($a['id'] && $allowed) {
db()->update_safe(self::table, ['active' => (int) ! $a['active']], $id);
common()->admin_wall_add(['admin account ' . ($a['active'] ? 'inactivated' : 'activated'), $id]);
}
if (is_ajax()) {
no_graphics(true);
echo (int) ( ! $a['active']);
} else {
return js_redirect(url('/@object'));
}
}
public function login_as()
{
$id = (int) $_GET['id'];
if ( ! $id) {
return _e('Wrong id');
}
if (main()->ADMIN_GROUP != 1) {
return _e('Allowed only for super-admins');
}
return _class('auth_admin', 'classes/auth/')->login_as($id);
}
public function filter_save()
{
return _class('admin_methods')->filter_save();
}
public function _show_filter()
{
if ( ! in_array($_GET['action'], ['show'])) {
return false;
}
$order_fields = [];
foreach (explode('|', 'login|email|group|first_name|last_name|add_date|last_login|num_logins|active') as $f) {
$order_fields[$f] = $f;
}
return form($r, [
'filter' => true,
])
->login('login', ['class' => 'input-medium', 'no_label' => 1])
->email('email', ['class' => 'input-medium', 'no_label' => 1])
->select_box('group', main()->get_data('admin_groups'), ['no_label' => 1, 'show_text' => 1])
->row_start()
->select_box('order_by', $order_fields, ['show_text' => '= ' . t('Order by') . ' =', 'desc' => t('Order by')])
->select_box('order_direction', ['asc' => '⇑', 'desc' => '⇓'])
->row_end()
->save_and_clear();
}
}