Files
webman-buildadmin/app/admin/controller/auth/Admin.php
2026-05-29 12:01:00 +08:00

868 lines
31 KiB
PHP
Raw 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'];
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'];
/** 使用 parent_admin_id 树过滤,不用角色组 dataLimit */
protected string|int|bool $dataLimit = false;
protected string $dataLimitField = 'id';
protected function initController(Request $request): ?Response
{
$this->model = new AdminModel();
return null;
}
public function index(Request $request): Response
{
$response = $this->initializeBackend($request);
if ($response !== null) {
return $response;
}
if ($request->get('select') ?? $request->post('select')) {
$selectRes = $this->select($request);
if ($selectRes !== null) {
return $selectRes;
}
}
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 = AdminCommissionDistributionService::getVisibleAdminIdsForOperator(
intval($this->auth->id),
$this->auth->isSuperAdmin()
);
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));
$isTopLevelGroup = ($request->get('is_top_level') ?? $request->post('is_top_level')) === '1'
|| ($request->get('is_top_level') ?? $request->post('is_top_level')) === 1
|| ($request->get('is_top_level') ?? $request->post('is_top_level')) === true;
$channelId = intval($request->get('channel_id', 0));
if ($isTopLevelGroup) {
if ($channelId <= 0) {
return $this->success('', [
'used_rate' => '0.00',
'remaining_rate' => '100.00',
'parent_has_no_share' => false,
'is_top_level' => true,
]);
}
$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_top_level' => true,
]);
}
if ($parentAdminId <= 0) {
return $this->success('', [
'used_rate' => '0.00',
'remaining_rate' => '100.00',
'parent_has_no_share' => false,
'is_top_level' => 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', 'channel_id'])->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,
'channel_id' => $group['channel_id'] ?? null,
]);
}
/**
* 远程下拉(重写:支持 top_group=1 仅返回顶级组管理员)
*/
protected function _select(): Response
{
if (empty($this->model)) {
return $this->success('', [
'list' => [],
'total' => 0,
]);
}
$pk = $this->model->getPk();
$fields = [$pk];
$quickSearchArr = is_array($this->quickSearchField) ? $this->quickSearchField : explode(',', (string) $this->quickSearchField);
foreach ($quickSearchArr as $f) {
$f = trim((string) $f);
if ($f === '') {
continue;
}
$f = str_contains($f, '.') ? substr($f, strrpos($f, '.') + 1) : $f;
if ($f !== '' && !in_array($f, $fields, true)) {
$fields[] = $f;
}
}
list($where, $alias, $limit, $order) = $this->queryBuilder();
$modelTable = strtolower($this->model->getTable());
$mainAlias = ($alias[$modelTable] ?? $modelTable) . '.';
$selectFields = [];
foreach ($fields as $f) {
$f = trim((string) $f);
if ($f === '') {
continue;
}
$selectFields[] = $mainAlias . $f . ' as ' . $f;
}
$qualifiedOrder = [];
if (is_array($order)) {
foreach ($order as $k => $v) {
$k = (string) $k;
$qualifiedOrder[str_contains($k, '.') ? $k : ($mainAlias . $k)] = $v;
}
}
$query = $this->model
->field($selectFields)
->alias($alias)
->where($where);
$visibleIds = AdminCommissionDistributionService::getVisibleAdminIdsForOperator(
intval($this->auth->id),
$this->auth->isSuperAdmin()
);
if ($visibleIds !== []) {
$query->where($mainAlias . 'id', 'in', $visibleIds);
}
$topGroup = $this->request ? ($this->request->get('top_group') ?? $this->request->post('top_group')) : null;
if ($topGroup === '1' || $topGroup === 1 || $topGroup === true) {
$query = $query
->join('admin_group_access aga', $mainAlias . 'id = aga.uid')
->join('admin_group ag', 'aga.group_id = ag.id')
->where('ag.pid', 0)
->distinct(true);
}
$res = $query
->order($qualifiedOrder ?: $order)
->paginate($limit);
return $this->success('', [
'list' => $res->items(),
'total' => $res->total(),
]);
}
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);
$creatorChannelId = $this->getCreatorChannelId();
$groupChannelId = $this->resolveChannelIdFromPrimaryGroup($data['group_arr'] ?? []);
if (!$this->auth->isSuperAdmin()) {
if ($creatorChannelId === null || $creatorChannelId === '') {
return $this->error(__('You have no permission'));
}
if ($groupChannelId === null || $groupChannelId === '') {
return $this->error(__('Selected role group is not bound to a channel'));
}
if ((string) $groupChannelId !== (string) $creatorChannelId) {
return $this->error(__('Selected role group channel does not match current account'));
}
$data['channel_id'] = $creatorChannelId;
$data['parent_admin_id'] = $this->auth->id;
} else {
$postedChannel = $data['channel_id'] ?? null;
if ($postedChannel === null || $postedChannel === '') {
$data['channel_id'] = ($groupChannelId === null || $groupChannelId === '') ? null : $groupChannelId;
}
}
$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 && $editGroupArr !== null) {
$creatorChannelId = $this->getCreatorChannelId();
$groupChannelId = $this->resolveChannelIdFromPrimaryGroup($editGroupArr);
if (!$this->auth->isSuperAdmin()) {
if ($creatorChannelId === null || $creatorChannelId === '') {
return $this->error(__('You have no permission'));
}
if ($groupChannelId === null || $groupChannelId === '') {
return $this->error(__('Selected role group is not bound to a channel'));
}
if ((string) $groupChannelId !== (string) $creatorChannelId) {
return $this->error(__('Selected role group channel does not match current account'));
}
$data['channel_id'] = $creatorChannelId;
} else {
$data['channel_id'] = ($groupChannelId === null || $groupChannelId === '') ? null : $groupChannelId;
}
}
if (!$isSelfEdit) {
if (!$this->auth->isSuperAdmin()) {
unset($data['parent_admin_id']);
}
$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);
if ($rowData['primary_group_is_top_level']) {
$channelId = intval($rowData['channel_id'] ?? 0);
if ($channelId > 0) {
$rowData['root_share_remainder'] = AdminCommissionDistributionService::getChannelRootShareRemainder(
$channelId,
intval($id)
);
}
} elseif ($parentId > 0) {
$remainder = AdminCommissionDistributionService::getShareRemainder($parentId, intval($id));
$rowData['share_remainder'] = $remainder;
}
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
{
return parent::select($request);
}
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 getCreatorChannelId(): mixed
{
$currentAdmin = Db::name('admin')
->field(['id', 'channel_id'])
->where('id', $this->auth->id)
->find();
if ($currentAdmin && !empty($currentAdmin['channel_id'])) {
return $currentAdmin['channel_id'];
}
return null;
}
/**
* @param array<int|string> $groupIds
*/
private function resolveChannelIdFromPrimaryGroup(array $groupIds): mixed
{
if ($groupIds === []) {
return null;
}
$gid = $groupIds[0];
if ($gid === null || $gid === '') {
return null;
}
return Db::name('admin_group')->where('id', $gid)->value('channel_id');
}
private function canManageAdminId(int $adminId): bool
{
if ($this->auth->isSuperAdmin()) {
return true;
}
if ($adminId === intval($this->auth->id)) {
return true;
}
$visible = AdminCommissionDistributionService::getVisibleAdminIdsForOperator(
intval($this->auth->id),
false
);
return in_array($adminId, $visible, 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;
$channelId = $data['channel_id'] ?? null;
if ($channelId === null || $channelId === '') {
$channelId = $this->resolveChannelIdFromPrimaryGroup($groupIds);
if ($channelId !== null && $channelId !== '') {
$data['channel_id'] = $channelId;
}
}
$channelIdInt = intval($channelId ?? 0);
$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;
}
$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) {
$existingParent = Db::name('admin')->where('id', $editAdminId)->value('parent_admin_id');
$parentId = intval($existingParent ?? 0);
}
if ($parentId <= 0) {
$data['parent_admin_id'] = null;
$data['commission_share_rate'] = null;
return null;
}
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');
}
$channelId = $data['channel_id'] ?? null;
if ($channelId !== null && $channelId !== '' && (string) ($parent['channel_id'] ?? '') !== (string) $channelId) {
return (string) __('Parent administrator must belong to the same channel');
}
if (($channelId === null || $channelId === '') && !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);
$data['parent_admin_id'] = $parentId;
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;
}
}