753 lines
23 KiB
PHP
753 lines
23 KiB
PHP
<?php
|
|
/**
|
|
* 数据访问层(全部使用预处理语句)
|
|
*/
|
|
require_once __DIR__ . DIRECTORY_SEPARATOR . 'db.php';
|
|
|
|
/* ============================================================
|
|
* 通用工具
|
|
* ============================================================ */
|
|
function now_iso()
|
|
{
|
|
return date('c');
|
|
}
|
|
|
|
function gen_id($prefix)
|
|
{
|
|
return $prefix . bin2hex(random_bytes(8));
|
|
}
|
|
|
|
function db_all($sql, $params = array())
|
|
{
|
|
$st = db()->prepare($sql);
|
|
$st->execute($params);
|
|
return $st->fetchAll();
|
|
}
|
|
|
|
function db_one($sql, $params = array())
|
|
{
|
|
$st = db()->prepare($sql);
|
|
$st->execute($params);
|
|
$row = $st->fetch();
|
|
return $row === false ? null : $row;
|
|
}
|
|
|
|
function db_val($sql, $params = array())
|
|
{
|
|
$st = db()->prepare($sql);
|
|
$st->execute($params);
|
|
return $st->fetchColumn();
|
|
}
|
|
|
|
function db_run($sql, $params = array())
|
|
{
|
|
$st = db()->prepare($sql);
|
|
$st->execute($params);
|
|
return $st;
|
|
}
|
|
|
|
function like_param($q)
|
|
{
|
|
return '%' . $q . '%';
|
|
}
|
|
|
|
/* ============================================================
|
|
* 用户
|
|
* ============================================================ */
|
|
function user_find_by_name($username)
|
|
{
|
|
return db_one('SELECT * FROM users WHERE username = ?', array($username));
|
|
}
|
|
|
|
function user_find_by_id($id)
|
|
{
|
|
return db_one('SELECT * FROM users WHERE id = ?', array($id));
|
|
}
|
|
|
|
function user_name_map()
|
|
{
|
|
$map = array();
|
|
foreach (db_all('SELECT id, username FROM users') as $r) {
|
|
$map[$r['id']] = $r['username'];
|
|
}
|
|
return $map;
|
|
}
|
|
|
|
function user_search($q, $limit, $offset)
|
|
{
|
|
$limit = (int) $limit;
|
|
$offset = (int) $offset;
|
|
if ($q === '') {
|
|
return db_all('SELECT * FROM users ORDER BY created_at DESC, username ASC LIMIT ' . $limit . ' OFFSET ' . $offset);
|
|
}
|
|
$p = like_param($q);
|
|
return db_all(
|
|
'SELECT * FROM users WHERE username LIKE ? OR role LIKE ? ORDER BY created_at DESC, username ASC LIMIT ' . $limit . ' OFFSET ' . $offset,
|
|
array($p, $p)
|
|
);
|
|
}
|
|
|
|
function user_count($q)
|
|
{
|
|
if ($q === '') {
|
|
return (int) db_val('SELECT COUNT(*) FROM users');
|
|
}
|
|
$p = like_param($q);
|
|
return (int) db_val('SELECT COUNT(*) FROM users WHERE username LIKE ? OR role LIKE ?', array($p, $p));
|
|
}
|
|
|
|
function user_insert($data)
|
|
{
|
|
db_run(
|
|
'INSERT INTO users (id, username, password_hash, role, disabled, must_change_password, protected, created_at, last_login_at, login_count)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
array(
|
|
$data['id'],
|
|
$data['username'],
|
|
$data['password_hash'],
|
|
isset($data['role']) ? $data['role'] : 'user',
|
|
!empty($data['disabled']) ? 1 : 0,
|
|
!empty($data['must_change_password']) ? 1 : 0,
|
|
!empty($data['protected']) ? 1 : 0,
|
|
isset($data['created_at']) ? $data['created_at'] : now_iso(),
|
|
isset($data['last_login_at']) ? $data['last_login_at'] : null,
|
|
isset($data['login_count']) ? (int) $data['login_count'] : 0,
|
|
)
|
|
);
|
|
return $data['id'];
|
|
}
|
|
|
|
function user_update($id, $fields)
|
|
{
|
|
$allowed = array('username', 'password_hash', 'role', 'disabled', 'must_change_password');
|
|
$sets = array();
|
|
$params = array();
|
|
foreach ($allowed as $f) {
|
|
if (array_key_exists($f, $fields)) {
|
|
$sets[] = $f . ' = ?';
|
|
$params[] = $fields[$f];
|
|
}
|
|
}
|
|
if (!$sets) {
|
|
return false;
|
|
}
|
|
$params[] = $id;
|
|
db_run('UPDATE users SET ' . implode(', ', $sets) . ' WHERE id = ?', $params);
|
|
return true;
|
|
}
|
|
|
|
function user_delete($id)
|
|
{
|
|
db_run('DELETE FROM users WHERE id = ?', array($id));
|
|
db_run('DELETE FROM sessions WHERE user_id = ?', array($id));
|
|
}
|
|
|
|
function user_touch_login($id)
|
|
{
|
|
db_run('UPDATE users SET last_login_at = ?, login_count = login_count + 1 WHERE id = ?', array(now_iso(), $id));
|
|
}
|
|
|
|
/* ============================================================
|
|
* 用户分组
|
|
* ============================================================ */
|
|
function group_list()
|
|
{
|
|
return db_all('SELECT * FROM user_groups ORDER BY created_at ASC, name ASC');
|
|
}
|
|
|
|
function group_find($id)
|
|
{
|
|
return db_one('SELECT * FROM user_groups WHERE id = ?', array($id));
|
|
}
|
|
|
|
function group_find_by_name($name)
|
|
{
|
|
return db_one('SELECT * FROM user_groups WHERE name = ?', array($name));
|
|
}
|
|
|
|
function group_insert($data)
|
|
{
|
|
db_run(
|
|
'INSERT INTO user_groups (id, name, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?)',
|
|
array(
|
|
$data['id'],
|
|
$data['name'],
|
|
isset($data['description']) ? $data['description'] : '',
|
|
isset($data['created_at']) ? $data['created_at'] : now_iso(),
|
|
isset($data['updated_at']) ? $data['updated_at'] : now_iso(),
|
|
)
|
|
);
|
|
return $data['id'];
|
|
}
|
|
|
|
function group_update($id, $fields)
|
|
{
|
|
$allowed = array('name', 'description');
|
|
$sets = array();
|
|
$params = array();
|
|
foreach ($allowed as $f) {
|
|
if (array_key_exists($f, $fields)) {
|
|
$sets[] = $f . ' = ?';
|
|
$params[] = $fields[$f];
|
|
}
|
|
}
|
|
if (!$sets) {
|
|
return false;
|
|
}
|
|
$sets[] = 'updated_at = ?';
|
|
$params[] = now_iso();
|
|
$params[] = $id;
|
|
db_run('UPDATE user_groups SET ' . implode(', ', $sets) . ' WHERE id = ?', $params);
|
|
return true;
|
|
}
|
|
|
|
function group_delete($id)
|
|
{
|
|
db_run('DELETE FROM user_groups WHERE id = ?', array($id));
|
|
db_run('DELETE FROM user_group_members WHERE group_id = ?', array($id));
|
|
db_run("DELETE FROM topo_shares WHERE target_type = 'group' AND target_id = ?", array($id));
|
|
}
|
|
|
|
function group_members($gid)
|
|
{
|
|
return db_all(
|
|
'SELECT u.id, u.username FROM user_group_members m JOIN users u ON u.id = m.user_id
|
|
WHERE m.group_id = ? ORDER BY u.username ASC',
|
|
array($gid)
|
|
);
|
|
}
|
|
|
|
function group_member_ids($gid)
|
|
{
|
|
$out = array();
|
|
foreach (db_all('SELECT user_id FROM user_group_members WHERE group_id = ?', array($gid)) as $r) {
|
|
$out[] = $r['user_id'];
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
function group_set_members($gid, $userIds)
|
|
{
|
|
db_run('DELETE FROM user_group_members WHERE group_id = ?', array($gid));
|
|
$seen = array();
|
|
foreach ((array) $userIds as $uid) {
|
|
$uid = (string) $uid;
|
|
if ($uid === '' || isset($seen[$uid])) {
|
|
continue;
|
|
}
|
|
$seen[$uid] = true;
|
|
db_run(
|
|
'INSERT INTO user_group_members (group_id, user_id, created_at) VALUES (?, ?, ?)',
|
|
array($gid, $uid, now_iso())
|
|
);
|
|
}
|
|
}
|
|
|
|
function group_ids_of_user($uid)
|
|
{
|
|
$out = array();
|
|
foreach (db_all('SELECT group_id FROM user_group_members WHERE user_id = ?', array($uid)) as $r) {
|
|
$out[] = $r['group_id'];
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
function group_member_counts()
|
|
{
|
|
$map = array();
|
|
foreach (db_all('SELECT group_id, COUNT(*) AS c FROM user_group_members GROUP BY group_id') as $r) {
|
|
$map[$r['group_id']] = (int) $r['c'];
|
|
}
|
|
return $map;
|
|
}
|
|
|
|
/* ============================================================
|
|
* 会话
|
|
* ============================================================ */
|
|
function session_create($token, $userId, $expires, $ip, $ua)
|
|
{
|
|
db_run(
|
|
'INSERT INTO sessions (token, user_id, created_at, expires_at, ip, user_agent) VALUES (?, ?, ?, ?, ?, ?)',
|
|
array($token, $userId, now_iso(), (int) $expires, $ip, $ua)
|
|
);
|
|
}
|
|
|
|
function session_find($token)
|
|
{
|
|
return db_one('SELECT * FROM sessions WHERE token = ?', array($token));
|
|
}
|
|
|
|
function session_delete($token)
|
|
{
|
|
db_run('DELETE FROM sessions WHERE token = ?', array($token));
|
|
}
|
|
|
|
function session_delete_by_user($userId)
|
|
{
|
|
db_run('DELETE FROM sessions WHERE user_id = ?', array($userId));
|
|
}
|
|
|
|
function session_purge_expired($now)
|
|
{
|
|
db_run('DELETE FROM sessions WHERE expires_at < ?', array((int) $now));
|
|
}
|
|
|
|
/* ============================================================
|
|
* 拓扑
|
|
* ============================================================ */
|
|
function topo_find($id)
|
|
{
|
|
return db_one('SELECT * FROM topologies WHERE id = ?', array($id));
|
|
}
|
|
|
|
function topo_list_for_user($user)
|
|
{
|
|
/* 主应用 / 用户后台返回:本人创建的 + 公开的 + 通过成员/分组共享且已开启共享模式的。
|
|
全部拓扑(含他人私有)只能在管理后台查看,见 topo_admin_search()。 */
|
|
return db_all(
|
|
"SELECT DISTINCT t.* FROM topologies t
|
|
LEFT JOIN topo_shares s ON s.topo_id = t.id
|
|
WHERE t.owner_id = ?
|
|
OR t.visibility = 'public'
|
|
OR (t.share_mode = 1 AND (
|
|
(s.target_type = 'user' AND s.target_id = ?)
|
|
OR (s.target_type = 'group' AND s.target_id IN (
|
|
SELECT group_id FROM user_group_members WHERE user_id = ?
|
|
))
|
|
))
|
|
ORDER BY t.updated_at DESC",
|
|
array($user['id'], $user['id'], $user['id'])
|
|
);
|
|
}
|
|
|
|
function topo_admin_search($q, $ownerId, $limit, $offset)
|
|
{
|
|
$limit = (int) $limit;
|
|
$offset = (int) $offset;
|
|
$where = array();
|
|
$params = array();
|
|
if ($q !== '') {
|
|
$where[] = 'name LIKE ?';
|
|
$params[] = like_param($q);
|
|
}
|
|
if ($ownerId !== '') {
|
|
$where[] = 'owner_id = ?';
|
|
$params[] = $ownerId;
|
|
}
|
|
$sql = 'SELECT * FROM topologies';
|
|
if ($where) {
|
|
$sql .= ' WHERE ' . implode(' AND ', $where);
|
|
}
|
|
$sql .= ' ORDER BY updated_at DESC LIMIT ' . $limit . ' OFFSET ' . $offset;
|
|
return db_all($sql, $params);
|
|
}
|
|
|
|
function topo_admin_count($q, $ownerId)
|
|
{
|
|
$where = array();
|
|
$params = array();
|
|
if ($q !== '') {
|
|
$where[] = 'name LIKE ?';
|
|
$params[] = like_param($q);
|
|
}
|
|
if ($ownerId !== '') {
|
|
$where[] = 'owner_id = ?';
|
|
$params[] = $ownerId;
|
|
}
|
|
$sql = 'SELECT COUNT(*) FROM topologies';
|
|
if ($where) {
|
|
$sql .= ' WHERE ' . implode(' AND ', $where);
|
|
}
|
|
return (int) db_val($sql, $params);
|
|
}
|
|
|
|
function topo_recent($limit)
|
|
{
|
|
$limit = (int) $limit;
|
|
return db_all('SELECT * FROM topologies ORDER BY updated_at DESC LIMIT ' . $limit);
|
|
}
|
|
|
|
/* 导出用:根据 id 列表返回完整行;ids 为空时返回全部 */
|
|
function topo_export_rows($ids)
|
|
{
|
|
if (empty($ids)) {
|
|
return db_all('SELECT * FROM topologies ORDER BY updated_at DESC');
|
|
}
|
|
$ph = implode(',', array_fill(0, count($ids), '?'));
|
|
return db_all('SELECT * FROM topologies WHERE id IN (' . $ph . ') ORDER BY updated_at DESC', $ids);
|
|
}
|
|
|
|
function topo_insert($t)
|
|
{
|
|
db_run(
|
|
'INSERT INTO topologies (id, name, owner_id, visibility, data, node_count, edge_count, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
array(
|
|
$t['id'],
|
|
$t['name'],
|
|
$t['owner_id'],
|
|
isset($t['visibility']) ? $t['visibility'] : 'private',
|
|
isset($t['data']) ? $t['data'] : '{"nodes":[],"edges":[]}',
|
|
isset($t['node_count']) ? (int) $t['node_count'] : 0,
|
|
isset($t['edge_count']) ? (int) $t['edge_count'] : 0,
|
|
isset($t['created_at']) ? $t['created_at'] : now_iso(),
|
|
isset($t['updated_at']) ? $t['updated_at'] : now_iso(),
|
|
)
|
|
);
|
|
return $t['id'];
|
|
}
|
|
|
|
function topo_update($id, $data, $nodeCount, $edgeCount, $updatedAt, $name = null)
|
|
{
|
|
if ($name !== null && $name !== '') {
|
|
db_run(
|
|
'UPDATE topologies SET data = ?, node_count = ?, edge_count = ?, updated_at = ?, rev = rev + 1, name = ? WHERE id = ?',
|
|
array($data, (int) $nodeCount, (int) $edgeCount, $updatedAt, $name, $id)
|
|
);
|
|
} else {
|
|
db_run(
|
|
'UPDATE topologies SET data = ?, node_count = ?, edge_count = ?, updated_at = ?, rev = rev + 1 WHERE id = ?',
|
|
array($data, (int) $nodeCount, (int) $edgeCount, $updatedAt, $id)
|
|
);
|
|
}
|
|
}
|
|
|
|
function topo_delete($id)
|
|
{
|
|
db_run('DELETE FROM topologies WHERE id = ?', array($id));
|
|
}
|
|
|
|
function topo_set_visibility($id, $vis, $updatedAt)
|
|
{
|
|
db_run('UPDATE topologies SET visibility = ?, updated_at = ?, rev = rev + 1 WHERE id = ?', array($vis, $updatedAt, $id));
|
|
}
|
|
|
|
function topo_set_name($id, $name, $updatedAt)
|
|
{
|
|
db_run('UPDATE topologies SET name = ?, updated_at = ?, rev = rev + 1 WHERE id = ?', array($name, $updatedAt, $id));
|
|
}
|
|
|
|
function topo_set_share_mode($id, $on, $updatedAt)
|
|
{
|
|
db_run('UPDATE topologies SET share_mode = ?, updated_at = ?, rev = rev + 1 WHERE id = ?', array($on ? 1 : 0, $updatedAt, $id));
|
|
}
|
|
|
|
/* ============================================================
|
|
* 拓扑共享
|
|
* ============================================================ */
|
|
function share_list_by_topo($topoId)
|
|
{
|
|
return db_all('SELECT * FROM topo_shares WHERE topo_id = ? ORDER BY created_at ASC', array($topoId));
|
|
}
|
|
|
|
/**
|
|
* 整体替换某拓扑的共享列表(事务)
|
|
*/
|
|
function share_replace($topoId, $rows, $by)
|
|
{
|
|
$pdo = db();
|
|
$pdo->beginTransaction();
|
|
try {
|
|
db_run('DELETE FROM topo_shares WHERE topo_id = ?', array($topoId));
|
|
foreach ((array) $rows as $r) {
|
|
db_run(
|
|
'INSERT INTO topo_shares (id, topo_id, target_type, target_id, permission, created_by, created_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)',
|
|
array(
|
|
gen_id('s'),
|
|
$topoId,
|
|
$r['target_type'],
|
|
$r['target_id'],
|
|
$r['permission'],
|
|
$by,
|
|
now_iso(),
|
|
)
|
|
);
|
|
}
|
|
$pdo->commit();
|
|
} catch (Exception $e) {
|
|
if ($pdo->inTransaction()) {
|
|
$pdo->rollBack();
|
|
}
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 计算用户对拓扑的有效权限:owner | edit | view | none
|
|
*/
|
|
function topo_permission_for($row, $user)
|
|
{
|
|
if (!$row || !$user) {
|
|
return 'none';
|
|
}
|
|
if (isset($user['role']) && $user['role'] === 'admin') {
|
|
return 'owner';
|
|
}
|
|
if ($row['owner_id'] === $user['id']) {
|
|
return 'owner';
|
|
}
|
|
$best = 'none';
|
|
if (!empty($row['share_mode'])) {
|
|
$gids = group_ids_of_user($user['id']);
|
|
foreach (share_list_by_topo($row['id']) as $s) {
|
|
$hit = false;
|
|
if ($s['target_type'] === 'user' && $s['target_id'] === $user['id']) {
|
|
$hit = true;
|
|
} elseif ($s['target_type'] === 'group' && in_array($s['target_id'], $gids, true)) {
|
|
$hit = true;
|
|
}
|
|
if (!$hit) {
|
|
continue;
|
|
}
|
|
if ($s['permission'] === 'edit') {
|
|
return 'edit';
|
|
}
|
|
if ($s['permission'] === 'view') {
|
|
$best = 'view';
|
|
}
|
|
}
|
|
}
|
|
if ($best === 'view') {
|
|
return 'view';
|
|
}
|
|
if ($row['visibility'] === 'public') {
|
|
return 'view';
|
|
}
|
|
return 'none';
|
|
}
|
|
|
|
/* ============================================================
|
|
* 在线状态(协同)
|
|
* ============================================================ */
|
|
function presence_touch($topoId, $user)
|
|
{
|
|
$now = time();
|
|
$exists = db_one('SELECT topo_id FROM topo_presence WHERE topo_id = ? AND user_id = ?', array($topoId, $user['id']));
|
|
if ($exists) {
|
|
db_run(
|
|
'UPDATE topo_presence SET username = ?, last_seen = ? WHERE topo_id = ? AND user_id = ?',
|
|
array($user['username'], $now, $topoId, $user['id'])
|
|
);
|
|
} else {
|
|
db_run(
|
|
'INSERT INTO topo_presence (topo_id, user_id, username, last_seen) VALUES (?, ?, ?, ?)',
|
|
array($topoId, $user['id'], $user['username'], $now)
|
|
);
|
|
}
|
|
}
|
|
|
|
function presence_list($topoId, $activeWithin)
|
|
{
|
|
$cut = time() - (int) $activeWithin;
|
|
return db_all(
|
|
'SELECT user_id, username, last_seen FROM topo_presence WHERE topo_id = ? AND last_seen >= ? ORDER BY last_seen DESC',
|
|
array($topoId, $cut)
|
|
);
|
|
}
|
|
|
|
function presence_remove($topoId, $userId)
|
|
{
|
|
db_run('DELETE FROM topo_presence WHERE topo_id = ? AND user_id = ?', array($topoId, $userId));
|
|
}
|
|
|
|
function presence_purge($beforeTs)
|
|
{
|
|
db_run('DELETE FROM topo_presence WHERE last_seen < ?', array((int) $beforeTs));
|
|
}
|
|
|
|
function topo_rev($id)
|
|
{
|
|
return (int) db_val('SELECT rev FROM topologies WHERE id = ?', array($id));
|
|
}
|
|
|
|
/* ============================================================
|
|
* 操作日志
|
|
* ============================================================ */
|
|
function log_add($userId, $username, $action, $target, $detail, $ip)
|
|
{
|
|
db_run(
|
|
'INSERT INTO activity_logs (user_id, username, action, target, detail, ip, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
|
array($userId, $username, $action, $target, $detail, $ip, now_iso())
|
|
);
|
|
}
|
|
|
|
function log_search($q, $limit, $offset)
|
|
{
|
|
$limit = (int) $limit;
|
|
$offset = (int) $offset;
|
|
if ($q === '') {
|
|
return db_all('SELECT * FROM activity_logs ORDER BY id DESC LIMIT ' . $limit . ' OFFSET ' . $offset);
|
|
}
|
|
$p = like_param($q);
|
|
return db_all(
|
|
'SELECT * FROM activity_logs WHERE username LIKE ? OR action LIKE ? OR target LIKE ? OR detail LIKE ?
|
|
ORDER BY id DESC LIMIT ' . $limit . ' OFFSET ' . $offset,
|
|
array($p, $p, $p, $p)
|
|
);
|
|
}
|
|
|
|
function log_count($q)
|
|
{
|
|
if ($q === '') {
|
|
return (int) db_val('SELECT COUNT(*) FROM activity_logs');
|
|
}
|
|
$p = like_param($q);
|
|
return (int) db_val(
|
|
'SELECT COUNT(*) FROM activity_logs WHERE username LIKE ? OR action LIKE ? OR target LIKE ? OR detail LIKE ?',
|
|
array($p, $p, $p, $p)
|
|
);
|
|
}
|
|
|
|
function log_recent($limit)
|
|
{
|
|
$limit = (int) $limit;
|
|
return db_all('SELECT * FROM activity_logs ORDER BY id DESC LIMIT ' . $limit);
|
|
}
|
|
|
|
/* ============================================================
|
|
* 统计
|
|
* ============================================================ */
|
|
function stats_overview()
|
|
{
|
|
$today = date('Y-m-d') . '%';
|
|
return array(
|
|
'userCount' => (int) db_val('SELECT COUNT(*) FROM users'),
|
|
'adminCount' => (int) db_val("SELECT COUNT(*) FROM users WHERE role = 'admin' AND disabled = 0"),
|
|
'disabledCount' => (int) db_val('SELECT COUNT(*) FROM users WHERE disabled = 1'),
|
|
'topoCount' => (int) db_val('SELECT COUNT(*) FROM topologies'),
|
|
'publicCount' => (int) db_val("SELECT COUNT(*) FROM topologies WHERE visibility = 'public'"),
|
|
'todayTopoCount' => (int) db_val('SELECT COUNT(*) FROM topologies WHERE created_at LIKE ?', array($today)),
|
|
'todayLoginCount' => (int) db_val("SELECT COUNT(*) FROM activity_logs WHERE action = 'login' AND created_at LIKE ?", array($today)),
|
|
'todayLogCount' => (int) db_val('SELECT COUNT(*) FROM activity_logs WHERE created_at LIKE ?', array($today)),
|
|
);
|
|
}
|
|
|
|
/* ============================================================
|
|
* 站点设置(key-value)
|
|
* ============================================================ */
|
|
function setting_get($key, $default = null)
|
|
{
|
|
$row = db_one('SELECT sval FROM settings WHERE skey = ?', array($key));
|
|
return $row === null ? $default : $row['sval'];
|
|
}
|
|
|
|
function setting_set($key, $val)
|
|
{
|
|
$now = now_iso();
|
|
if (db_one('SELECT skey FROM settings WHERE skey = ?', array($key)) !== null) {
|
|
db_run('UPDATE settings SET sval = ?, updated_at = ? WHERE skey = ?', array($val, $now, $key));
|
|
} else {
|
|
db_run('INSERT INTO settings (skey, sval, updated_at) VALUES (?, ?, ?)', array($key, $val, $now));
|
|
}
|
|
}
|
|
|
|
function settings_all()
|
|
{
|
|
$out = array();
|
|
foreach (db_all('SELECT skey, sval FROM settings') as $r) {
|
|
$out[$r['skey']] = $r['sval'];
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
/* ============================================================
|
|
* 旧 JSON 数据迁移
|
|
* ============================================================ */
|
|
function migrate_legacy_json()
|
|
{
|
|
$dir = data_dir();
|
|
$usersFile = $dir . DIRECTORY_SEPARATOR . 'users.json';
|
|
$topoIndexFile = $dir . DIRECTORY_SEPARATOR . 'topologies.json';
|
|
$topoDir = $dir . DIRECTORY_SEPARATOR . 'topologies';
|
|
|
|
if (is_file($usersFile) && (int) db_val('SELECT COUNT(*) FROM users') === 0) {
|
|
$data = json_decode(@file_get_contents($usersFile), true);
|
|
if (isset($data['users']) && is_array($data['users'])) {
|
|
foreach ($data['users'] as $u) {
|
|
if (empty($u['id']) || empty($u['username'])) {
|
|
continue;
|
|
}
|
|
user_insert(array(
|
|
'id' => $u['id'],
|
|
'username' => $u['username'],
|
|
'password_hash' => isset($u['passwordHash']) ? $u['passwordHash'] : '',
|
|
'role' => isset($u['role']) ? $u['role'] : 'user',
|
|
'disabled' => !empty($u['disabled']) ? 1 : 0,
|
|
'must_change_password' => !empty($u['mustChangePassword']) ? 1 : 0,
|
|
'created_at' => isset($u['createdAt']) ? $u['createdAt'] : now_iso(),
|
|
));
|
|
}
|
|
}
|
|
@rename($usersFile, $usersFile . '.bak');
|
|
}
|
|
|
|
if (is_file($topoIndexFile)) {
|
|
$idx = json_decode(@file_get_contents($topoIndexFile), true);
|
|
if (isset($idx['topologies']) && is_array($idx['topologies'])) {
|
|
foreach ($idx['topologies'] as $t) {
|
|
if (empty($t['id']) || topo_find($t['id'])) {
|
|
continue;
|
|
}
|
|
$content = array('nodes' => array(), 'edges' => array());
|
|
$cf = $topoDir . DIRECTORY_SEPARATOR . $t['id'] . '.json';
|
|
if (is_file($cf)) {
|
|
$c = json_decode(@file_get_contents($cf), true);
|
|
if (is_array($c)) {
|
|
$content = $c;
|
|
}
|
|
}
|
|
topo_insert(array(
|
|
'id' => $t['id'],
|
|
'name' => isset($t['name']) ? $t['name'] : '未命名',
|
|
'owner_id' => isset($t['ownerId']) ? $t['ownerId'] : '',
|
|
'visibility' => isset($t['visibility']) ? $t['visibility'] : 'private',
|
|
'data' => json_encode($content, JSON_UNESCAPED_UNICODE),
|
|
'node_count' => isset($content['nodes']) ? count($content['nodes']) : 0,
|
|
'edge_count' => isset($content['edges']) ? count($content['edges']) : 0,
|
|
'created_at' => isset($t['createdAt']) ? $t['createdAt'] : now_iso(),
|
|
'updated_at' => isset($t['updatedAt']) ? $t['updatedAt'] : now_iso(),
|
|
));
|
|
}
|
|
}
|
|
@rename($topoIndexFile, $topoIndexFile . '.bak');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 首次初始化:迁移旧数据 + 创建默认管理员
|
|
*/
|
|
function ensure_seed()
|
|
{
|
|
migrate_legacy_json();
|
|
|
|
if ((int) db_val('SELECT COUNT(*) FROM users') > 0) {
|
|
protect_configured_admin();
|
|
return;
|
|
}
|
|
$cfg = app_config();
|
|
$admin = isset($cfg['default_admin']) ? $cfg['default_admin'] : array('username' => 'admin', 'password' => 'admin123');
|
|
user_insert(array(
|
|
'id' => gen_id('u'),
|
|
'username' => $admin['username'],
|
|
'password_hash' => password_hash($admin['password'], PASSWORD_DEFAULT),
|
|
'role' => 'admin',
|
|
'disabled' => 0,
|
|
'must_change_password' => 1,
|
|
'created_at' => now_iso(),
|
|
'protected' => 1,
|
|
));
|
|
}
|
|
|
|
/**
|
|
* 默认管理员(配置文件中的 default_admin)始终置为受保护账号,不可被其他用户改动
|
|
*/
|
|
function protect_configured_admin()
|
|
{
|
|
$cfg = app_config();
|
|
$name = isset($cfg['default_admin']['username']) ? $cfg['default_admin']['username'] : 'admin';
|
|
if ($name !== '' && $name !== null) {
|
|
db_run('UPDATE users SET protected = 1 WHERE username = ?', array($name));
|
|
}
|
|
}
|