747 lines
22 KiB
PHP
747 lines
22 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/../db.php';
|
|
require_once __DIR__ . '/../auth.php';
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
function jsonResponse(array $data, int $status = 200): void
|
|
{
|
|
http_response_code($status);
|
|
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
|
|
header('Pragma: no-cache');
|
|
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
|
exit;
|
|
}
|
|
|
|
function getInputData(): array
|
|
{
|
|
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
|
|
|
|
if (str_contains($contentType, 'application/json')) {
|
|
$raw = file_get_contents('php://input');
|
|
$data = json_decode($raw, true);
|
|
|
|
return is_array($data) ? $data : [];
|
|
}
|
|
|
|
return $_POST;
|
|
}
|
|
|
|
function optionResponse(array $option): array
|
|
{
|
|
return [
|
|
'id' => (int)$option['id'],
|
|
'name' => $option['name'],
|
|
'icon' => $option['logo'] !== null
|
|
? 'data:image/svg+xml;base64,' . base64_encode($option['logo'])
|
|
: null
|
|
];
|
|
}
|
|
|
|
function customFieldResponse(array $field): array
|
|
{
|
|
if ($field['default_value'] === null) {
|
|
return [
|
|
'id' => (int)$field['id'],
|
|
'task_type' => (int)$field['task_type'],
|
|
'name' => $field['name'],
|
|
'type' => $field['type'],
|
|
'value' => null,
|
|
'raw_value' => ''
|
|
];
|
|
}
|
|
|
|
$decodedValue = json_decode((string)$field['default_value'], true);
|
|
$decodeOk = json_last_error() === JSON_ERROR_NONE;
|
|
$rawValue = (string)$field['default_value'];
|
|
|
|
if ($decodeOk) {
|
|
if ($field['type'] === 'json') {
|
|
$rawValue = json_encode($decodedValue, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
} elseif ($field['type'] === 'boolean') {
|
|
$rawValue = $decodedValue ? 'true' : 'false';
|
|
} else {
|
|
$rawValue = (string)$decodedValue;
|
|
}
|
|
}
|
|
|
|
return [
|
|
'id' => (int)$field['id'],
|
|
'task_type' => (int)$field['task_type'],
|
|
'name' => $field['name'],
|
|
'type' => $field['type'],
|
|
'value' => $decodeOk ? $decodedValue : null,
|
|
'raw_value' => $rawValue
|
|
];
|
|
}
|
|
|
|
function pictureDataUri(?string $picture): ?string
|
|
{
|
|
if ($picture === null || $picture === '') {
|
|
return null;
|
|
}
|
|
|
|
$info = @getimagesizefromstring($picture);
|
|
$mime = is_array($info) && isset($info['mime'])
|
|
? $info['mime']
|
|
: 'image/png';
|
|
|
|
return 'data:' . $mime . ';base64,' . base64_encode($picture);
|
|
}
|
|
|
|
function userResponse(array $user): array
|
|
{
|
|
return [
|
|
'id' => (int)$user['id'],
|
|
'name' => $user['name'],
|
|
'email' => $user['email'],
|
|
'picture' => pictureDataUri($user['picture'] ?? null)
|
|
];
|
|
}
|
|
|
|
function rightResponse(array $right): array
|
|
{
|
|
return [
|
|
'id' => (int)$right['id'],
|
|
'name' => $right['name']
|
|
];
|
|
}
|
|
|
|
function projectResponse(array $project): array
|
|
{
|
|
return [
|
|
'id' => $project['id'],
|
|
'name' => $project['name'],
|
|
'owner' => (int)$project['owner']
|
|
];
|
|
}
|
|
|
|
function optionTable(string $kind): string
|
|
{
|
|
return match ($kind) {
|
|
'type' => 'task_types',
|
|
'priority' => 'task_priorities',
|
|
default => jsonResponse([
|
|
'success' => false,
|
|
'error' => 'Option kind must be type or priority.'
|
|
], 400)
|
|
};
|
|
}
|
|
|
|
function optionLabel(string $kind): string
|
|
{
|
|
return $kind === 'type' ? 'Task type' : 'Task priority';
|
|
}
|
|
|
|
function normalizeName(mixed $name, string $label, int $maxLength = 128): string
|
|
{
|
|
$name = trim((string)$name);
|
|
|
|
if ($name === '') {
|
|
jsonResponse(['success' => false, 'error' => "{$label} name is required."], 400);
|
|
}
|
|
|
|
if (strlen($name) > $maxLength) {
|
|
jsonResponse(['success' => false, 'error' => "{$label} name is too long."], 400);
|
|
}
|
|
|
|
return $name;
|
|
}
|
|
|
|
function normalizeCustomFieldType(mixed $type): string
|
|
{
|
|
$type = strtolower(trim((string)$type));
|
|
$allowedTypes = ['string', 'int', 'float', 'date', 'boolean', 'text', 'json'];
|
|
|
|
if (!in_array($type, $allowedTypes, true)) {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'error' => 'Custom field type must be string, int, float, date, boolean, text, or json.'
|
|
], 400);
|
|
}
|
|
|
|
return $type;
|
|
}
|
|
|
|
function normalizeCustomFieldValue(mixed $value, string $type): ?string
|
|
{
|
|
$rawValue = trim((string)$value);
|
|
|
|
if ($rawValue === '') {
|
|
return null;
|
|
}
|
|
|
|
$normalized = match ($type) {
|
|
'int' => filter_var($rawValue, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE),
|
|
'float' => is_numeric($rawValue) ? (float)$rawValue : null,
|
|
'boolean' => match (strtolower($rawValue)) {
|
|
'1', 'true', 'yes', 'on' => true,
|
|
'0', 'false', 'no', 'off' => false,
|
|
default => null
|
|
},
|
|
'date' => preg_match('/^\d{4}-\d{2}-\d{2}$/', $rawValue) ? $rawValue : null,
|
|
'json' => json_decode($rawValue, true),
|
|
default => $rawValue
|
|
};
|
|
|
|
if ($type === 'json' && json_last_error() !== JSON_ERROR_NONE) {
|
|
jsonResponse(['success' => false, 'error' => 'Custom field JSON value is invalid.'], 400);
|
|
}
|
|
|
|
if ($normalized === null && $type !== 'json') {
|
|
jsonResponse(['success' => false, 'error' => "Custom field value does not match {$type}."], 400);
|
|
}
|
|
|
|
return json_encode($normalized, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
}
|
|
|
|
function ensureCustomTaskFieldsTable(): void
|
|
{
|
|
db()->exec(
|
|
'CREATE TABLE IF NOT EXISTS custom_task_fields (
|
|
id INT NOT NULL AUTO_INCREMENT,
|
|
task_type INT NOT NULL,
|
|
name VARCHAR(128) NOT NULL,
|
|
type VARCHAR(128) NOT NULL,
|
|
default_value TEXT NULL,
|
|
PRIMARY KEY (id),
|
|
INDEX idx_custom_task_fields_task_type (task_type)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci'
|
|
);
|
|
|
|
$columns = db()->query('SHOW COLUMNS FROM custom_task_fields')->fetchAll(PDO::FETCH_COLUMN);
|
|
|
|
if (!in_array('default_value', $columns, true)) {
|
|
db()->exec('ALTER TABLE custom_task_fields ADD COLUMN default_value TEXT NULL');
|
|
}
|
|
|
|
if (in_array('value', $columns, true)) {
|
|
db()->exec('UPDATE custom_task_fields SET default_value = `value` WHERE default_value IS NULL');
|
|
}
|
|
}
|
|
|
|
function ensureCustomFieldValuesTable(): void
|
|
{
|
|
db()->exec(
|
|
'CREATE TABLE IF NOT EXISTS custom_field_values (
|
|
id INT NOT NULL AUTO_INCREMENT,
|
|
field_id INT NOT NULL,
|
|
task_id VARCHAR(26) NOT NULL,
|
|
`value` TEXT NULL,
|
|
PRIMARY KEY (id),
|
|
INDEX idx_custom_field_values_task (task_id),
|
|
INDEX idx_custom_field_values_field (field_id)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci'
|
|
);
|
|
}
|
|
|
|
function uploadedLogo(): ?string
|
|
{
|
|
if (!isset($_FILES['logo']) || !is_array($_FILES['logo'])) {
|
|
return null;
|
|
}
|
|
|
|
$file = $_FILES['logo'];
|
|
|
|
if (($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) {
|
|
return null;
|
|
}
|
|
|
|
if (($file['error'] ?? UPLOAD_ERR_OK) !== UPLOAD_ERR_OK) {
|
|
jsonResponse(['success' => false, 'error' => 'Logo upload failed.'], 400);
|
|
}
|
|
|
|
if (($file['size'] ?? 0) > 1024 * 1024) {
|
|
jsonResponse(['success' => false, 'error' => 'Logo must be 1 MB or smaller.'], 400);
|
|
}
|
|
|
|
$content = file_get_contents($file['tmp_name']);
|
|
|
|
if ($content === false || trim($content) === '') {
|
|
jsonResponse(['success' => false, 'error' => 'Logo file is empty.'], 400);
|
|
}
|
|
|
|
if (!preg_match('/<svg\b/i', $content)) {
|
|
jsonResponse(['success' => false, 'error' => 'Logo must be an SVG file.'], 400);
|
|
}
|
|
|
|
return $content;
|
|
}
|
|
|
|
function requireOption(string $table, int $id, string $label): void
|
|
{
|
|
if ($id <= 0) {
|
|
jsonResponse(['success' => false, 'error' => "{$label} must be valid."], 400);
|
|
}
|
|
|
|
$stmt = db()->prepare("SELECT id FROM {$table} WHERE id = ? LIMIT 1");
|
|
$stmt->execute([$id]);
|
|
|
|
if (!$stmt->fetch()) {
|
|
jsonResponse(['success' => false, 'error' => "{$label} not found."], 404);
|
|
}
|
|
}
|
|
|
|
function preventDeleteIfUsed(string $kind, int $id): void
|
|
{
|
|
if ($kind === 'type') {
|
|
$taskStmt = db()->prepare('SELECT COUNT(*) FROM tasks WHERE type = ?');
|
|
$taskStmt->execute([$id]);
|
|
$assignmentStmt = db()->prepare('SELECT COUNT(*) FROM assigned_task_states WHERE task_type = ?');
|
|
$assignmentStmt->execute([$id]);
|
|
|
|
if ((int)$taskStmt->fetchColumn() > 0 || (int)$assignmentStmt->fetchColumn() > 0) {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'error' => 'Task type is still used by tasks or workflow assignments.'
|
|
], 409);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
$stmt = db()->prepare('SELECT COUNT(*) FROM tasks WHERE priority = ?');
|
|
$stmt->execute([$id]);
|
|
|
|
if ((int)$stmt->fetchColumn() > 0) {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'error' => 'Task priority is still used by tasks.'
|
|
], 409);
|
|
}
|
|
}
|
|
|
|
function requireCustomField(int $fieldId): array
|
|
{
|
|
if ($fieldId <= 0) {
|
|
jsonResponse(['success' => false, 'error' => 'Custom field must be valid.'], 400);
|
|
}
|
|
|
|
ensureCustomTaskFieldsTable();
|
|
|
|
$stmt = db()->prepare(
|
|
'SELECT id, task_type, name, type, default_value
|
|
FROM custom_task_fields
|
|
WHERE id = ?
|
|
LIMIT 1'
|
|
);
|
|
$stmt->execute([$fieldId]);
|
|
$field = $stmt->fetch();
|
|
|
|
if (!$field) {
|
|
jsonResponse(['success' => false, 'error' => 'Custom field not found.'], 404);
|
|
}
|
|
|
|
return $field;
|
|
}
|
|
|
|
function requireUserId(int $userId): void
|
|
{
|
|
if ($userId <= 0) {
|
|
jsonResponse(['success' => false, 'error' => 'User must be valid.'], 400);
|
|
}
|
|
|
|
$stmt = db()->prepare('SELECT id FROM users WHERE id = ? LIMIT 1');
|
|
$stmt->execute([$userId]);
|
|
|
|
if (!$stmt->fetch()) {
|
|
jsonResponse(['success' => false, 'error' => 'User not found.'], 404);
|
|
}
|
|
}
|
|
|
|
function requireRightId(int $rightId): void
|
|
{
|
|
if ($rightId <= 0) {
|
|
jsonResponse(['success' => false, 'error' => 'Right must be valid.'], 400);
|
|
}
|
|
|
|
$stmt = db()->prepare('SELECT id FROM rights WHERE id = ? LIMIT 1');
|
|
$stmt->execute([$rightId]);
|
|
|
|
if (!$stmt->fetch()) {
|
|
jsonResponse(['success' => false, 'error' => 'Right not found.'], 404);
|
|
}
|
|
}
|
|
|
|
function requireProjectId(string $projectId): void
|
|
{
|
|
if ($projectId === '') {
|
|
jsonResponse(['success' => false, 'error' => 'Project must be valid.'], 400);
|
|
}
|
|
|
|
$stmt = db()->prepare('SELECT id FROM projects WHERE id = ? LIMIT 1');
|
|
$stmt->execute([$projectId]);
|
|
|
|
if (!$stmt->fetch()) {
|
|
jsonResponse(['success' => false, 'error' => 'Project not found.'], 404);
|
|
}
|
|
}
|
|
|
|
function rightName(int $rightId): ?string
|
|
{
|
|
$stmt = db()->prepare('SELECT name FROM rights WHERE id = ? LIMIT 1');
|
|
$stmt->execute([$rightId]);
|
|
$name = $stmt->fetchColumn();
|
|
|
|
return $name !== false ? (string)$name : null;
|
|
}
|
|
|
|
function optionsData(): array
|
|
{
|
|
$types = array_map('optionResponse', db()->query(
|
|
'SELECT id, name, logo
|
|
FROM task_types
|
|
ORDER BY id ASC'
|
|
)->fetchAll());
|
|
|
|
$priorities = array_map('optionResponse', db()->query(
|
|
'SELECT id, name, logo
|
|
FROM task_priorities
|
|
ORDER BY id ASC'
|
|
)->fetchAll());
|
|
|
|
try {
|
|
ensureCustomTaskFieldsTable();
|
|
|
|
$customFields = array_map('customFieldResponse', db()->query(
|
|
'SELECT id, task_type, name, type, default_value
|
|
FROM custom_task_fields
|
|
ORDER BY task_type ASC, id ASC'
|
|
)->fetchAll());
|
|
} catch (Throwable $e) {
|
|
$customFields = [];
|
|
}
|
|
|
|
$users = array_map('userResponse', db()->query(
|
|
'SELECT id, name, email, picture
|
|
FROM users
|
|
ORDER BY name ASC, id ASC'
|
|
)->fetchAll());
|
|
|
|
$rights = array_map('rightResponse', db()->query(
|
|
'SELECT id, name
|
|
FROM rights
|
|
ORDER BY id ASC'
|
|
)->fetchAll());
|
|
|
|
$userRights = db()->query(
|
|
'SELECT user_id, right_id
|
|
FROM user_rights
|
|
ORDER BY user_id ASC, right_id ASC'
|
|
)->fetchAll();
|
|
|
|
$projects = array_map('projectResponse', db()->query(
|
|
'SELECT id, name, owner
|
|
FROM projects
|
|
ORDER BY name ASC, id ASC'
|
|
)->fetchAll());
|
|
|
|
$userAccess = db()->query(
|
|
'SELECT id, user_id, project_id
|
|
FROM user_access
|
|
ORDER BY user_id ASC, project_id ASC'
|
|
)->fetchAll();
|
|
|
|
return [
|
|
'types' => $types,
|
|
'priorities' => $priorities,
|
|
'custom_fields' => $customFields,
|
|
'users' => $users,
|
|
'rights' => $rights,
|
|
'user_rights' => array_map(static fn (array $row): array => [
|
|
'user_id' => (int)$row['user_id'],
|
|
'right_id' => (int)$row['right_id']
|
|
], $userRights),
|
|
'projects' => $projects,
|
|
'user_access' => array_map(static fn (array $row): array => [
|
|
'id' => (int)$row['id'],
|
|
'user_id' => (int)$row['user_id'],
|
|
'project_id' => $row['project_id']
|
|
], $userAccess)
|
|
];
|
|
}
|
|
|
|
$user = requireApiAuth();
|
|
requireUserRight((int)$user['id'], 'Admin');
|
|
|
|
$api = $_GET['api'] ?? '';
|
|
|
|
switch ($api) {
|
|
case 'OptionsData': {
|
|
jsonResponse([
|
|
'success' => true,
|
|
'options' => optionsData()
|
|
]);
|
|
}
|
|
|
|
case 'CreateOption': {
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
jsonResponse(['success' => false, 'error' => 'CreateOption requires POST.'], 405);
|
|
}
|
|
|
|
$data = getInputData();
|
|
$kind = (string)($data['kind'] ?? '');
|
|
$table = optionTable($kind);
|
|
$label = optionLabel($kind);
|
|
$name = normalizeName($data['name'] ?? '', $label, $kind === 'type' ? 20 : 128);
|
|
$logo = uploadedLogo();
|
|
|
|
$stmt = db()->prepare("INSERT INTO {$table} (name, logo) VALUES (?, ?)");
|
|
$stmt->execute([$name, $logo]);
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'option_id' => (int)db()->lastInsertId()
|
|
], 201);
|
|
}
|
|
|
|
case 'UpdateOption': {
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
jsonResponse(['success' => false, 'error' => 'UpdateOption requires POST.'], 405);
|
|
}
|
|
|
|
$data = getInputData();
|
|
$kind = (string)($data['kind'] ?? '');
|
|
$table = optionTable($kind);
|
|
$label = optionLabel($kind);
|
|
$id = (int)($data['id'] ?? 0);
|
|
$name = normalizeName($data['name'] ?? '', $label, $kind === 'type' ? 20 : 128);
|
|
$logo = uploadedLogo();
|
|
$removeLogo = !empty($data['remove_logo']);
|
|
|
|
requireOption($table, $id, $label);
|
|
|
|
if ($logo !== null) {
|
|
$stmt = db()->prepare("UPDATE {$table} SET name = ?, logo = ? WHERE id = ?");
|
|
$stmt->execute([$name, $logo, $id]);
|
|
} elseif ($removeLogo) {
|
|
$stmt = db()->prepare("UPDATE {$table} SET name = ?, logo = NULL WHERE id = ?");
|
|
$stmt->execute([$name, $id]);
|
|
} else {
|
|
$stmt = db()->prepare("UPDATE {$table} SET name = ? WHERE id = ?");
|
|
$stmt->execute([$name, $id]);
|
|
}
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'option_id' => $id
|
|
]);
|
|
}
|
|
|
|
case 'DeleteOption': {
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
jsonResponse(['success' => false, 'error' => 'DeleteOption requires POST.'], 405);
|
|
}
|
|
|
|
$kind = (string)($_GET['kind'] ?? '');
|
|
$table = optionTable($kind);
|
|
$label = optionLabel($kind);
|
|
$id = (int)($_GET['id'] ?? 0);
|
|
|
|
requireOption($table, $id, $label);
|
|
preventDeleteIfUsed($kind, $id);
|
|
|
|
if ($kind === 'type') {
|
|
ensureCustomTaskFieldsTable();
|
|
ensureCustomFieldValuesTable();
|
|
db()->prepare(
|
|
'DELETE custom_field_values
|
|
FROM custom_field_values
|
|
INNER JOIN custom_task_fields ON custom_task_fields.id = custom_field_values.field_id
|
|
WHERE custom_task_fields.task_type = ?'
|
|
)->execute([$id]);
|
|
db()->prepare('DELETE FROM custom_task_fields WHERE task_type = ?')->execute([$id]);
|
|
}
|
|
|
|
$stmt = db()->prepare("DELETE FROM {$table} WHERE id = ?");
|
|
$stmt->execute([$id]);
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'option_id' => $id
|
|
]);
|
|
}
|
|
|
|
case 'CreateCustomField': {
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
jsonResponse(['success' => false, 'error' => 'CreateCustomField requires POST.'], 405);
|
|
}
|
|
|
|
$data = getInputData();
|
|
$taskType = (int)($data['task_type'] ?? 0);
|
|
$name = normalizeName($data['name'] ?? '', 'Custom field');
|
|
$type = normalizeCustomFieldType($data['type'] ?? '');
|
|
$value = normalizeCustomFieldValue($data['value'] ?? '', $type);
|
|
|
|
requireOption('task_types', $taskType, 'Task type');
|
|
ensureCustomTaskFieldsTable();
|
|
|
|
$stmt = db()->prepare(
|
|
'INSERT INTO custom_task_fields (task_type, name, type, default_value)
|
|
VALUES (?, ?, ?, ?)'
|
|
);
|
|
$stmt->execute([$taskType, $name, $type, $value]);
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'field_id' => (int)db()->lastInsertId()
|
|
], 201);
|
|
}
|
|
|
|
case 'UpdateCustomField': {
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
jsonResponse(['success' => false, 'error' => 'UpdateCustomField requires POST.'], 405);
|
|
}
|
|
|
|
$data = getInputData();
|
|
$fieldId = (int)($data['id'] ?? 0);
|
|
$field = requireCustomField($fieldId);
|
|
$name = normalizeName($data['name'] ?? '', 'Custom field');
|
|
$type = normalizeCustomFieldType($data['type'] ?? '');
|
|
$value = normalizeCustomFieldValue($data['value'] ?? '', $type);
|
|
ensureCustomTaskFieldsTable();
|
|
|
|
$stmt = db()->prepare(
|
|
'UPDATE custom_task_fields
|
|
SET name = ?, type = ?, default_value = ?
|
|
WHERE id = ?'
|
|
);
|
|
$stmt->execute([$name, $type, $value, $field['id']]);
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'field_id' => (int)$field['id']
|
|
]);
|
|
}
|
|
|
|
case 'DeleteCustomField': {
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
jsonResponse(['success' => false, 'error' => 'DeleteCustomField requires POST.'], 405);
|
|
}
|
|
|
|
$fieldId = (int)($_GET['field_id'] ?? 0);
|
|
$field = requireCustomField($fieldId);
|
|
ensureCustomTaskFieldsTable();
|
|
|
|
$stmt = db()->prepare('DELETE FROM custom_task_fields WHERE id = ?');
|
|
$stmt->execute([$field['id']]);
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'field_id' => (int)$field['id']
|
|
]);
|
|
}
|
|
|
|
case 'SetUserRight': {
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
jsonResponse(['success' => false, 'error' => 'SetUserRight requires POST.'], 405);
|
|
}
|
|
|
|
$data = getInputData();
|
|
$userId = (int)($data['user_id'] ?? 0);
|
|
$rightId = (int)($data['right_id'] ?? 0);
|
|
$enabled = !empty($data['enabled']);
|
|
|
|
requireUserId($userId);
|
|
requireRightId($rightId);
|
|
|
|
if (!$enabled && $userId === (int)$user['id'] && rightName($rightId) === 'Admin') {
|
|
jsonResponse(['success' => false, 'error' => 'You cannot remove your own Admin right.'], 400);
|
|
}
|
|
|
|
if ($enabled) {
|
|
$stmt = db()->prepare(
|
|
'SELECT id
|
|
FROM user_rights
|
|
WHERE user_id = ?
|
|
AND right_id = ?
|
|
LIMIT 1'
|
|
);
|
|
$stmt->execute([$userId, $rightId]);
|
|
|
|
if (!$stmt->fetch()) {
|
|
db()->prepare(
|
|
'INSERT INTO user_rights (user_id, right_id)
|
|
VALUES (?, ?)'
|
|
)->execute([$userId, $rightId]);
|
|
}
|
|
} else {
|
|
db()->prepare(
|
|
'DELETE FROM user_rights
|
|
WHERE user_id = ?
|
|
AND right_id = ?'
|
|
)->execute([$userId, $rightId]);
|
|
}
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'user_id' => $userId,
|
|
'right_id' => $rightId,
|
|
'enabled' => $enabled
|
|
]);
|
|
}
|
|
|
|
case 'GrantProjectAccess': {
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
jsonResponse(['success' => false, 'error' => 'GrantProjectAccess requires POST.'], 405);
|
|
}
|
|
|
|
$data = getInputData();
|
|
$userId = (int)($data['user_id'] ?? 0);
|
|
$projectId = strtoupper(trim((string)($data['project_id'] ?? '')));
|
|
|
|
requireUserId($userId);
|
|
requireProjectId($projectId);
|
|
|
|
$stmt = db()->prepare(
|
|
'SELECT id
|
|
FROM user_access
|
|
WHERE user_id = ?
|
|
AND project_id = ?
|
|
LIMIT 1'
|
|
);
|
|
$stmt->execute([$userId, $projectId]);
|
|
|
|
if (!$stmt->fetch()) {
|
|
db()->prepare(
|
|
'INSERT INTO user_access (user_id, project_id)
|
|
VALUES (?, ?)'
|
|
)->execute([$userId, $projectId]);
|
|
}
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'user_id' => $userId,
|
|
'project_id' => $projectId
|
|
], 201);
|
|
}
|
|
|
|
case 'RevokeProjectAccess': {
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
jsonResponse(['success' => false, 'error' => 'RevokeProjectAccess requires POST.'], 405);
|
|
}
|
|
|
|
$accessId = (int)($_GET['access_id'] ?? 0);
|
|
|
|
if ($accessId <= 0) {
|
|
jsonResponse(['success' => false, 'error' => 'Project access must be valid.'], 400);
|
|
}
|
|
|
|
db()->prepare('DELETE FROM user_access WHERE id = ?')->execute([$accessId]);
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'access_id' => $accessId
|
|
]);
|
|
}
|
|
|
|
default:
|
|
jsonResponse([
|
|
'success' => false,
|
|
'error' => 'Unknown admin options API action.'
|
|
], 400);
|
|
}
|