Files
webman-buildadmin/app/admin/controller/auth/Admin.php

941 lines
32 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace app\admin\controller\auth;
use ba\Random;
use ba\Tree;
use Throwable;
use support\think\Db;
use support\validation\Validator;
use support\validation\ValidationException;
use app\common\controller\Backend;
use app\common\service\AdminCommissionDistributionService;
use app\admin\model\Admin as AdminModel;
use support\Response;
use Webman\Http\Request;
class Admin extends Backend
{
/**
* 分红比例余量查询(表单提示用)
*/
protected array $noNeedPermission = ['commissionShareRemainder', 'groupMeta', 'parentMeta'];
protected ?object $model = null;
protected array|string $preExcludeFields = ['create_time', 'update_time', 'password', 'salt', 'login_failure', 'last_login_time', 'last_login_ip'];
protected array|string $quickSearchField = ['username', 'nickname', 'mobile', 'invite_code'];
/** 使用 parent_admin_id 树过滤,不用角色组 dataLimit */
protected string|int|bool $dataLimit = false;
protected string $dataLimitField = 'id';
protected Tree $tree;
protected array $initValue = [];
protected string $keyword = '';
protected bool $assembleTree = false;
protected function initController(Request $request): ?Response
{
$this->model = new AdminModel();
$this->tree = Tree::instance();
$isTree = $request->get('isTree') ?? $request->post('isTree') ?? false;
$initValue = $request->get('initValue') ?? $request->post('initValue') ?? [];
if (is_array($initValue)) {
$this->initValue = array_filter($initValue);
} elseif ($initValue !== null && $initValue !== '') {
$this->initValue = array_filter(explode(',', (string) $initValue));
} else {
$this->initValue = [];
}
$this->keyword = (string) ($request->get('quickSearch') ?? $request->post('quickSearch') ?? '');
$this->assembleTree = self::isRequestTruthy($isTree) && $this->initValue === [];
return null;
}
/**
* 解析请求中的布尔参数(兼容 true / "true" / 1 / "1"
*/
private static function isRequestTruthy(mixed $value): bool
{
if ($value === false || $value === null || $value === '' || $value === 0 || $value === '0' || $value === 'false') {
return false;
}
return true;
}
/**
* 远程下拉树形选项isTree=1
*/
private function shouldFormatSelectTree(Request $request): bool
{
$isTree = $request->get('isTree') ?? $request->post('isTree') ?? false;
return self::isRequestTruthy($isTree);
}
/**
* 是否为远程下拉请求
*/
private function isRemoteSelectRequest(Request $request): bool
{
if ($request->get('select') ?? $request->post('select')) {
return true;
}
return $this->shouldFormatSelectTree($request);
}
public function index(Request $request): Response
{
$response = $this->initializeBackend($request);
if ($response !== null) {
return $response;
}
if ($this->isRemoteSelectRequest($request)) {
return $this->select($request);
}
list($where, $alias, $limit, $order) = $this->queryBuilder();
$adminAlias = $alias[strtolower($this->model->getTable())] ?? 'admin';
$query = $this->model
->withoutField('login_failure,password,salt')
->alias($alias)
->where($where);
$visibleIds = $this->getVisibleAdminIdsForAdminModule();
if ($visibleIds !== []) {
$query->where($adminAlias . '.id', 'in', $visibleIds);
}
$topGroup = $request->get('top_group') ?? $request->post('top_group');
if ($topGroup === '1' || $topGroup === 1 || $topGroup === true) {
$query = $query
->join('admin_group_access aga', $adminAlias . '.id = aga.uid')
->join('admin_group ag', 'aga.group_id = ag.id')
->where('ag.pid', 0)
->distinct(true);
}
$rows = $query->order($order)->select()->toArray();
$rows = $this->enrichAdminRows($rows);
foreach ($rows as $k => $row) {
$parentId = intval($row['parent_admin_id'] ?? 0);
$rows[$k]['pid'] = $parentId > 0 ? $parentId : 0;
}
$tree = Tree::instance()->assembleChild($rows, 'pid', 'id');
return $this->success('', [
'list' => $tree,
'total' => count($rows),
'remark' => get_route_remark(),
]);
}
/**
* 查询同上级下剩余可分配分红比例(表单提示)
*/
public function commissionShareRemainder(Request $request): Response
{
$response = $this->initializeBackend($request);
if ($response !== null) {
return $response;
}
$parentAdminId = intval($request->get('parent_admin_id', 0));
$excludeId = intval($request->get('exclude_id', 0));
$channelId = intval($request->get('channel_id', 0));
if ($parentAdminId <= 0 && $channelId > 0) {
$stats = AdminCommissionDistributionService::getChannelRootShareRemainder(
$channelId,
$excludeId > 0 ? $excludeId : null
);
return $this->success('', [
'used_rate' => $stats['used_rate'],
'remaining_rate' => $stats['remaining_rate'],
'parent_has_no_share' => bccomp($stats['remaining_rate'], '0', 2) <= 0,
'is_channel_root' => true,
]);
}
if ($parentAdminId <= 0) {
return $this->success('', [
'used_rate' => '0.00',
'remaining_rate' => '100.00',
'parent_has_no_share' => false,
'is_channel_root' => false,
]);
}
if (!$this->canManageAdminId($parentAdminId)) {
return $this->error(__('You have no permission'));
}
$stats = AdminCommissionDistributionService::getShareRemainder(
$parentAdminId,
$excludeId > 0 ? $excludeId : null
);
return $this->success('', [
'used_rate' => $stats['used_rate'],
'remaining_rate' => $stats['remaining_rate'],
'parent_has_no_share' => bccomp($stats['remaining_rate'], '0', 2) <= 0,
'is_top_level' => false,
]);
}
/**
* 查询角色组是否为顶级pid=0供管理员表单联动
*/
public function groupMeta(Request $request): Response
{
$response = $this->initializeBackend($request);
if ($response !== null) {
return $response;
}
$groupId = intval($request->get('group_id', 0));
if ($groupId <= 0) {
return $this->error(__('Invalid parameters'));
}
$group = Db::name('admin_group')->where('id', $groupId)->field(['id', 'pid'])->find();
if (!is_array($group)) {
return $this->error(__('Record not found'));
}
if (!$this->auth->isSuperAdmin()) {
$authGroups = $this->getManageableGroupIds();
if (!in_array($groupId, $authGroups, true)) {
return $this->error(__('You have no permission'));
}
}
$pid = intval($group['pid'] ?? 0);
return $this->success('', [
'is_top_level' => $pid === 0,
'pid' => $pid,
]);
}
/**
* 上级代理渠道信息(子代理表单只读展示用)
*/
public function parentMeta(Request $request): Response
{
$response = $this->initializeBackend($request);
if ($response !== null) {
return $response;
}
$parentId = intval($request->get('parent_admin_id', 0));
if ($parentId <= 0) {
return $this->success('', [
'channel_id' => null,
'channel_name' => '',
]);
}
if (!$this->canManageAdminId($parentId) && !$this->auth->isSuperAdmin()) {
return $this->error(__('You have no permission'));
}
$parent = Db::name('admin')->where('id', $parentId)->field(['id', 'channel_id'])->find();
if (!is_array($parent)) {
return $this->error(__('Invalid parent administrator'));
}
$cid = $parent['channel_id'] ?? null;
$channelName = '';
if ($cid !== null && $cid !== '') {
$channelName = (string) (Db::name('channel')->where('id', $cid)->value('name') ?? '');
}
return $this->success('', [
'channel_id' => $cid,
'channel_name' => $channelName,
]);
}
/**
* 远程下拉(兼容 Backend trait统一走 select 树形)
*/
protected function _select(): Response
{
if ($this->request instanceof Request) {
return $this->select($this->request);
}
return $this->success('', [
'options' => [],
]);
}
public function add(Request $request): Response
{
$response = $this->initializeBackend($request);
if ($response !== null) {
return $response;
}
if ($request->method() === 'POST') {
$data = $request->post();
if (!$data) {
return $this->error(__('Parameter %s can not be empty', ['']));
}
$data = $this->normalizeSingleGroup($data);
if (!$this->hasSingleGroup($data['group_arr'] ?? null)) {
return $this->error(__('Please select exactly one role group'));
}
if ($this->modelValidate) {
try {
$rules = [
'username' => 'required|string|regex:/^[a-zA-Z][a-zA-Z0-9_]{2,15}$/|unique:admin,username',
'nickname' => 'required|string',
'password' => 'required|string|regex:/^(?!.*[&<>"\'\n\r]).{6,32}$/',
'email' => 'email|unique:admin,email',
'mobile' => 'regex:/^1[3-9]\d{9}$/|unique:admin,mobile',
'group_arr' => 'required|array',
];
$messages = [
'username.regex' => __('Please input correct username'),
'password.regex' => __('Please input correct password'),
];
Validator::make($data, $rules, $messages)->validate();
} catch (ValidationException $e) {
return $this->error($e->getMessage());
}
}
$passwd = $data['password'] ?? '';
$data = $this->excludeFields($data);
$parentErr = $this->normalizeParentAndShareFields($data, null, $data['group_arr'] ?? []);
if ($parentErr !== null) {
return $this->error($parentErr);
}
$data['invite_code'] = $this->generateUniqueInviteCode();
$result = false;
if (!empty($data['group_arr'])) {
$authRes = $this->checkGroupAuth($data['group_arr']);
if ($authRes !== null) {
return $authRes;
}
}
$this->model->startTrans();
try {
$result = $this->model->save($data);
if (!empty($data['group_arr'])) {
$groupAccess = [];
foreach ($data['group_arr'] as $datum) {
$groupAccess[] = [
'uid' => $this->model->id,
'group_id' => $datum,
];
}
Db::name('admin_group_access')->insertAll($groupAccess);
}
$this->model->commit();
if (!empty($passwd)) {
$this->model->resetPassword($this->model->id, $passwd);
}
} catch (Throwable $e) {
$this->model->rollback();
return $this->error($e->getMessage());
}
if ($result !== false) {
return $this->success(__('Added successfully'));
}
return $this->error(__('No rows were added'));
}
return $this->error(__('Parameter error'));
}
public function edit(Request $request): Response
{
$response = $this->initializeBackend($request);
if ($response !== null) {
return $response;
}
$pk = $this->model->getPk();
$id = $request->get($pk) ?? $request->post($pk);
$row = $this->model->find($id);
if (!$row) {
return $this->error(__('Record not found'));
}
if (!$this->canManageAdminId(intval($row['id']))) {
return $this->error(__('You have no permission'));
}
if ($request->method() === 'POST') {
$data = $request->post();
if (!$data) {
return $this->error(__('Parameter %s can not be empty', ['']));
}
$isSelfEdit = (int) $this->auth->id === (int) $id;
if ($isSelfEdit) {
unset($data['group_arr'], $data['group_name_arr'], $data['parent_admin_id'], $data['commission_share_rate']);
}
$editGroupArr = null;
if (array_key_exists('group_arr', $data)) {
$data = $this->normalizeSingleGroup($data);
if (!$this->hasSingleGroup($data['group_arr'] ?? null)) {
return $this->error(__('Please select exactly one role group'));
}
$editGroupArr = $data['group_arr'];
} elseif (!$isSelfEdit) {
return $this->error(__('Please select exactly one role group'));
}
if ($this->modelValidate) {
try {
$rules = [
'username' => 'required|string|regex:/^[a-zA-Z][a-zA-Z0-9_]{2,15}$/|unique:admin,username,' . $id,
'nickname' => 'required|string',
'password' => 'nullable|string|regex:/^(?!.*[&<>"\'\n\r]).{6,32}$/',
'email' => 'email|unique:admin,email,' . $id,
'mobile' => 'regex:/^1[3-9]\d{9}$/|unique:admin,mobile,' . $id,
];
if (array_key_exists('group_arr', $data)) {
$rules['group_arr'] = 'required|array';
}
$messages = [
'username.regex' => __('Please input correct username'),
'password.regex' => __('Please input correct password'),
];
Validator::make($data, $rules, $messages)->validate();
} catch (ValidationException $e) {
return $this->error($e->getMessage());
}
}
if ($this->auth->id == $data['id'] && ($data['status'] ?? '') == 'disable') {
return $this->error(__('Please use another administrator account to disable the current account!'));
}
if (!empty($data['password'])) {
$this->model->resetPassword($row->id, $data['password']);
}
$groupAccess = [];
if (!$isSelfEdit && !empty($editGroupArr)) {
$checkGroups = [];
$rowGroupArr = $row->group_arr ?? [];
foreach ($editGroupArr as $datum) {
if (!in_array($datum, $rowGroupArr)) {
$checkGroups[] = $datum;
}
$groupAccess[] = [
'uid' => $id,
'group_id' => $datum,
];
}
$authRes = $this->checkGroupAuth($checkGroups);
if ($authRes !== null) {
return $authRes;
}
}
$data = $this->excludeFields($data);
unset($data['invite_code'], $data['group_arr'], $data['group_name_arr']);
if (!$isSelfEdit) {
$parentErr = $this->normalizeParentAndShareFields($data, intval($id), $editGroupArr ?? []);
if ($parentErr !== null) {
return $this->error($parentErr);
}
}
$result = false;
$this->model->startTrans();
try {
$result = $row->save($data);
if (!$isSelfEdit) {
Db::name('admin_group_access')
->where('uid', $id)
->delete();
if ($groupAccess) {
Db::name('admin_group_access')->insertAll($groupAccess);
}
}
$this->model->commit();
} catch (Throwable $e) {
$this->model->rollback();
return $this->error($e->getMessage());
}
if ($result !== false) {
return $this->success(__('Update successful'));
}
return $this->error(__('No rows updated'));
}
unset($row['salt'], $row['login_failure']);
$row['password'] = '';
$rowData = $row->toArray();
$enriched = $this->enrichAdminRows([$rowData]);
$rowData = $enriched[0] ?? $rowData;
$groupArr = is_array($rowData['group_arr'] ?? null) ? $rowData['group_arr'] : [];
$rowData['primary_group_is_top_level'] = $this->isPrimaryGroupTopLevel($groupArr);
$parentId = intval($rowData['parent_admin_id'] ?? 0);
$channelId = intval($rowData['channel_id'] ?? 0);
if ($parentId > 0) {
$remainder = AdminCommissionDistributionService::getShareRemainder($parentId, intval($id));
$rowData['share_remainder'] = $remainder;
} elseif ($channelId > 0) {
$rowData['root_share_remainder'] = AdminCommissionDistributionService::getChannelRootShareRemainder(
$channelId,
intval($id)
);
}
return $this->success('', [
'row' => $rowData,
]);
}
public function del(Request $request): Response
{
$response = $this->initializeBackend($request);
if ($response !== null) {
return $response;
}
$ids = $request->get('ids') ?? $request->post('ids') ?? [];
$ids = is_array($ids) ? $ids : [];
if ($ids === []) {
return $this->error(__('Parameter error'));
}
$data = $this->model->where($this->model->getPk(), 'in', $ids)->select();
$count = 0;
$this->model->startTrans();
try {
foreach ($data as $v) {
if ($v->id == $this->auth->id) {
continue;
}
if (!$this->canManageAdminId(intval($v->id))) {
continue;
}
$childCount = Db::name('admin')->where('parent_admin_id', $v->id)->count();
if ($childCount > 0) {
$this->model->rollback();
return $this->error(__('Cannot delete administrator with sub-agents'));
}
$count += $v->delete();
Db::name('admin_group_access')
->where('uid', $v['id'])
->delete();
}
$this->model->commit();
} catch (Throwable $e) {
$this->model->rollback();
return $this->error($e->getMessage());
}
if ($count) {
return $this->success(__('Deleted successfully'));
}
return $this->error(__('No rows were deleted'));
}
public function select(Request $request): Response
{
$response = $this->initializeBackend($request);
if ($response !== null) {
return $response;
}
$rows = $this->getAdminsForSelect($request);
if ($this->shouldFormatSelectTree($request)) {
$treeData = $this->tree->assembleChild($rows, 'pid', 'id');
$rows = $this->tree->assembleTree($this->tree->getTreeArray($treeData, 'username'));
}
return $this->success('', [
'options' => $rows,
]);
}
/**
* @return array<int, array<string, mixed>>
*/
private function getAdminsForSelect(Request $request): array
{
list($where, $alias, $limit, $order) = $this->queryBuilder();
$adminAlias = $alias[strtolower($this->model->getTable())] ?? 'admin';
$query = $this->model
->withoutField('login_failure,password,salt')
->alias($alias)
->where($where);
$visibleIds = $this->getVisibleAdminIdsForAdminModule();
if ($visibleIds !== []) {
$query->where($adminAlias . '.id', 'in', $visibleIds);
}
$rows = $query->order($order)->select()->toArray();
foreach ($rows as $k => $row) {
$parentId = intval($row['parent_admin_id'] ?? 0);
$rows[$k]['pid'] = $parentId > 0 ? $parentId : 0;
}
return $rows;
}
/**
* 非超管可见管理员:本人 + 代理树下级 + 角色组管理范围内(本人所在组及下级组)的全部成员。
*
* @return int[] 空数组表示不限制(超管)
*/
private function getVisibleAdminIdsForAdminModule(): array
{
if ($this->auth->isSuperAdmin()) {
return [];
}
$operatorId = intval($this->auth->id);
$ids = AdminCommissionDistributionService::getVisibleAdminIdsForOperator($operatorId, false);
$groupIds = $this->getManageableGroupIds();
if ($groupIds !== []) {
$groupAdminIds = Db::name('admin_group_access')
->where('group_id', 'in', $groupIds)
->column('uid');
foreach ($groupAdminIds as $uid) {
$uidInt = intval($uid);
if ($uidInt > 0) {
$ids[] = $uidInt;
}
}
}
if ($operatorId > 0) {
$ids[] = $operatorId;
}
$ids = array_values(array_unique(array_filter($ids, static fn(int $id): bool => $id > 0)));
return $ids === [] ? [0] : $ids;
}
private function checkGroupAuth(array $groups): ?Response
{
if ($this->auth->isSuperAdmin()) {
return null;
}
$authGroups = $this->getManageableGroupIds();
foreach ($groups as $group) {
if (!in_array($group, $authGroups)) {
return $this->error(__('You have no permission to add an administrator to this group!'));
}
}
return null;
}
private function getManageableGroupIds(): array
{
if ($this->auth->isSuperAdmin()) {
return Db::name('admin_group')->where('status', 1)->column('id');
}
$own = Db::name('admin_group_access')->where('uid', $this->auth->id)->column('group_id');
$children = $this->auth->getAdminChildGroups();
return array_values(array_unique(array_merge($own, $children)));
}
private function operatorMayAssignChannel(): bool
{
if ($this->auth->isSuperAdmin()) {
return true;
}
foreach (['channel/index', 'channel/Index', 'Channel/index', 'Channel/Index'] as $routePath) {
if ($this->auth->check($routePath)) {
return true;
}
}
return false;
}
private function canManageAdminId(int $adminId): bool
{
if ($this->auth->isSuperAdmin()) {
return true;
}
if ($adminId === intval($this->auth->id)) {
return true;
}
return in_array($adminId, $this->getVisibleAdminIdsForAdminModule(), true);
}
/**
* @param array<int|string> $groupIds
*/
private function isPrimaryGroupTopLevel(array $groupIds): bool
{
if ($groupIds === []) {
return false;
}
$gid = intval($groupIds[0]);
if ($gid <= 0) {
return false;
}
$pid = Db::name('admin_group')->where('id', $gid)->value('pid');
return intval($pid ?? 0) === 0;
}
/**
* @param array<string, mixed> $data
* @param array<int|string> $groupIds
*/
private function normalizeParentAndShareFields(array &$data, ?int $editAdminId, array $groupIds = []): ?string
{
if ($this->isPrimaryGroupTopLevel($groupIds)) {
$data['parent_admin_id'] = null;
}
$parentId = isset($data['parent_admin_id']) && $data['parent_admin_id'] !== '' && $data['parent_admin_id'] !== null
? intval($data['parent_admin_id'])
: 0;
if ($parentId <= 0 && $editAdminId !== null && $editAdminId > 0 && !array_key_exists('parent_admin_id', $data)) {
$existingParent = Db::name('admin')->where('id', $editAdminId)->value('parent_admin_id');
$parentId = intval($existingParent ?? 0);
}
if ($parentId > 0) {
if ($editAdminId !== null && $parentId === $editAdminId) {
return (string) __('Cannot set yourself as parent administrator');
}
$parent = Db::name('admin')->where('id', $parentId)->find();
if (!is_array($parent)) {
return (string) __('Invalid parent administrator');
}
if (!$this->canManageAdminId($parentId) && !$this->auth->isSuperAdmin()) {
return (string) __('You have no permission');
}
$data['parent_admin_id'] = $parentId;
if (!empty($parent['channel_id'])) {
$data['channel_id'] = $parent['channel_id'];
$shareErr = AdminCommissionDistributionService::validateCommissionShareRate(
$parentId,
$data['commission_share_rate'] ?? null,
$editAdminId
);
if ($shareErr !== null) {
return $shareErr;
}
$data['commission_share_rate'] = bcadd(strval($data['commission_share_rate'] ?? '0'), '0', 2);
} else {
$data['channel_id'] = null;
$data['commission_share_rate'] = null;
}
return null;
}
$data['parent_admin_id'] = null;
if ($editAdminId !== null && $editAdminId > 0 && !array_key_exists('channel_id', $data)) {
return null;
}
$channelIdInt = intval($data['channel_id'] ?? 0);
if ($channelIdInt <= 0) {
$data['channel_id'] = null;
$data['commission_share_rate'] = null;
if ($editAdminId === null && !$this->auth->isSuperAdmin() && !$this->operatorMayAssignChannel()) {
return (string) __('Please select a parent agent');
}
return null;
}
if (!$this->auth->isSuperAdmin() && !$this->operatorMayAssignChannel()) {
return (string) __('You have no permission');
}
$exists = Db::name('channel')->where('id', $channelIdInt)->value('id');
if (!$exists) {
return (string) __('Record not found');
}
$shareErr = AdminCommissionDistributionService::validateChannelRootCommissionShareRate(
$channelIdInt,
$data['commission_share_rate'] ?? null,
$editAdminId
);
if ($shareErr !== null) {
return $shareErr;
}
$data['commission_share_rate'] = bcadd(strval($data['commission_share_rate'] ?? '0'), '0', 2);
return null;
}
/**
* @param array<int, array<string, mixed>> $rows
* @return array<int, array<string, mixed>>
*/
private function enrichAdminRows(array $rows): array
{
if ($rows === []) {
return [];
}
$adminIds = [];
$channelIds = [];
$parentIds = [];
foreach ($rows as $row) {
$aid = intval($row['id'] ?? 0);
if ($aid > 0) {
$adminIds[] = $aid;
}
$cid = $row['channel_id'] ?? null;
if ($cid !== null && $cid !== '') {
$channelIds[] = intval($cid);
}
$pid = intval($row['parent_admin_id'] ?? 0);
if ($pid > 0) {
$parentIds[] = $pid;
}
}
$channelNames = $channelIds !== []
? Db::name('channel')->where('id', 'in', array_unique($channelIds))->column('name', 'id')
: [];
$parentNames = $parentIds !== []
? Db::name('admin')->where('id', 'in', array_unique($parentIds))->column('username', 'id')
: [];
$groupMap = [];
if ($adminIds !== []) {
$accessRows = Db::name('admin_group_access')->where('uid', 'in', $adminIds)->select()->toArray();
$groupIds = array_unique(array_map(static fn(array $r): int => intval($r['group_id'] ?? 0), $accessRows));
$groupNames = $groupIds !== []
? Db::name('admin_group')->where('id', 'in', $groupIds)->column('name', 'id')
: [];
foreach ($accessRows as $access) {
$uid = intval($access['uid'] ?? 0);
$gid = intval($access['group_id'] ?? 0);
if ($uid <= 0 || $gid <= 0) {
continue;
}
if (!isset($groupMap[$uid])) {
$groupMap[$uid] = [];
}
$name = $groupNames[$gid] ?? '';
if ($name !== '') {
$groupMap[$uid][] = $name;
}
}
}
foreach ($rows as $k => $row) {
$cid = $row['channel_id'] ?? null;
$rows[$k]['channel_name'] = ($cid !== null && $cid !== '') ? strval($channelNames[intval($cid)] ?? '') : '';
$pid = intval($row['parent_admin_id'] ?? 0);
$rows[$k]['parent_admin_username'] = $pid > 0 ? strval($parentNames[$pid] ?? '') : '';
$aid = intval($row['id'] ?? 0);
$rows[$k]['group_name_arr'] = $groupMap[$aid] ?? [];
}
return $rows;
}
private function generateUniqueInviteCode(): string
{
$tries = 0;
do {
$tries++;
$code = strtoupper(substr(Random::uuid(), 0, 8));
$exists = Db::name('admin')->where('invite_code', $code)->find();
} while ($exists && $tries < 20);
return $code;
}
private function normalizeSingleGroup(array $data): array
{
if (!array_key_exists('group_arr', $data)) {
return $data;
}
$group = $data['group_arr'];
if (is_array($group)) {
$data['group_arr'] = array_values(array_filter($group, static function ($item) {
return $item !== null && $item !== '';
}));
return $data;
}
if ($group === null || $group === '') {
$data['group_arr'] = [];
return $data;
}
$data['group_arr'] = [$group];
return $data;
}
private function hasSingleGroup(mixed $groups): bool
{
return is_array($groups) && count($groups) === 1;
}
/**
* 兼容旧 worker 内存中仍调用本方法的 add/edit新逻辑在 normalizeParentAndShareFields。
*
* @param array<string, mixed> $data
* @param array<int|string> $groupIds
*/
private function applyTopLevelChannelFromOperator(array &$data, array $groupIds): ?string
{
if (!$this->isPrimaryGroupTopLevel($groupIds)) {
return null;
}
$parentId = isset($data['parent_admin_id']) && $data['parent_admin_id'] !== '' && $data['parent_admin_id'] !== null
? intval($data['parent_admin_id'])
: 0;
if ($parentId > 0) {
return null;
}
if (!$this->auth->isSuperAdmin()) {
$mayAssignChannel = false;
foreach (['channel/index', 'channel/Index', 'Channel/index', 'Channel/Index'] as $routePath) {
if ($this->auth->check($routePath)) {
$mayAssignChannel = true;
break;
}
}
if (!$mayAssignChannel) {
unset($data['channel_id']);
}
}
$cid = $data['channel_id'] ?? null;
if ($cid === null || $cid === '') {
$data['channel_id'] = null;
return null;
}
$exists = Db::name('channel')->where('id', $cid)->value('id');
if (!$exists) {
return (string) __('Record not found');
}
return null;
}
}