made installer and seperated stuff into diferent files

This commit is contained in:
2026-06-14 10:47:39 +02:00
parent 9045841645
commit 904607a045
22 changed files with 4766 additions and 3952 deletions

View File

@@ -0,0 +1,251 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../db.php';
function installerEmbedded(): bool {
return defined('PROJECTKILN_INSTALL_EMBEDDED') && PROJECTKILN_INSTALL_EMBEDDED === true;
}
function installerTableExists(PDO $pdo, string $table): bool {
$stmt = $pdo->prepare(
'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?'
);
$stmt->execute([$table]);
return (int)$stmt->fetchColumn() > 0;
}
function installerIsInstalled(PDO $pdo): bool {
if (!installerTableExists($pdo, 'settings')) {
return false;
}
$stmt = $pdo->prepare(
"SELECT COUNT(*) FROM settings WHERE setting_name = 'installed' AND LOWER(setting_value) = 'true'"
);
$stmt->execute();
return (int)$stmt->fetchColumn() > 0;
}
function redirectHome(): never {
header('Location: ' . (installerEmbedded() ? '?page=home' : '../?page=home'));
exit;
}
if (installerEmbedded()) {
$pageStyles[] = 'app/css/login.css';
}
$pdo = db();
if (installerIsInstalled($pdo)) {
redirectHome();
}
$installError = null;
$formError = null;
try {
require __DIR__ . '/install_db.php';
require_once __DIR__ . '/../auth.php';
} catch (Throwable $exception) {
$installError = $exception->getMessage();
}
if ($installError === null && $_SERVER['REQUEST_METHOD'] === 'POST') {
$username = trim((string)($_POST['username'] ?? ''));
$email = strtolower(trim((string)($_POST['email'] ?? '')));
$password = (string)($_POST['password'] ?? '');
$passwordConfirm = (string)($_POST['password_confirm'] ?? '');
if ($username === '' || $email === '' || $password === '') {
$formError = 'Please fill in all required fields.';
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$formError = 'Please enter a valid email address.';
} elseif (strlen($password) < 8) {
$formError = 'Password must be at least 8 characters long.';
} elseif ($password !== $passwordConfirm) {
$formError = 'Passwords do not match.';
} else {
$userId = register($username, $email, $password);
if ($userId === null) {
$formError = 'Admin user could not be created. Check if the email is already used.';
} else {
$adminRightStmt = $pdo->prepare("SELECT id FROM rights WHERE name = 'Admin' LIMIT 1");
$adminRightStmt->execute();
$adminRightId = (int)$adminRightStmt->fetchColumn();
if ($adminRightId <= 0) {
$formError = 'Admin right is missing after database setup.';
} else {
$existingRight = $pdo->prepare(
'SELECT COUNT(*) FROM user_rights WHERE user_id = ? AND right_id = ?'
);
$existingRight->execute([$userId, $adminRightId]);
if ((int)$existingRight->fetchColumn() === 0) {
$assignAdmin = $pdo->prepare(
'INSERT INTO user_rights (user_id, right_id) VALUES (?, ?)'
);
$assignAdmin->execute([$userId, $adminRightId]);
}
$markInstalled = $pdo->prepare(
"INSERT INTO settings (id, setting_name, setting_value)
VALUES (2, 'installed', 'true')
ON DUPLICATE KEY UPDATE setting_name = VALUES(setting_name), setting_value = VALUES(setting_value)"
);
$markInstalled->execute();
login($email, $password, false);
redirectHome();
}
}
}
}
function oldInput(string $name): string {
return htmlspecialchars((string)($_POST[$name] ?? ''), ENT_QUOTES, 'UTF-8');
}
?>
<?php if (!installerEmbedded()): ?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ProjectKiln Install</title>
<link rel="stylesheet" href="../app/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="../app/fontawesome/css/all.min.css">
<link rel="stylesheet" href="../app/css/main.css">
<link rel="stylesheet" href="../app/css/dark_mode.css">
<link rel="stylesheet" href="../app/css/login.css">
</head>
<body>
<?php endif; ?>
<main class="container auth-container d-flex align-items-center justify-content-center">
<div class="card auth-card shadow-lg border">
<div class="row g-0 h-100">
<div class="col-lg-6 auth-brand p-5 d-flex flex-column justify-content-center">
<div class="auth-logo mb-4">
<i class="fa-solid fa-fire-flame-simple"></i>
</div>
<h1 class="display-5 fw-bold mb-3">
ProjectKiln Setup
</h1>
<p class="text-secondary mb-0">
Create the first administrator account to finish the installation.
</p>
</div>
<div class="col-lg-6 p-5 d-flex flex-column justify-content-center">
<?php if ($installError !== null): ?>
<div class="alert alert-danger mb-4">
<i class="fa-solid fa-circle-exclamation me-2"></i>
Database setup failed: <?= htmlspecialchars($installError, ENT_QUOTES, 'UTF-8') ?>
</div>
<?php endif; ?>
<?php if ($formError !== null): ?>
<div class="alert alert-danger mb-4">
<i class="fa-solid fa-circle-exclamation me-2"></i>
<?= htmlspecialchars($formError, ENT_QUOTES, 'UTF-8') ?>
</div>
<?php endif; ?>
<form method="post">
<div class="mb-3">
<label class="form-label">Username</label>
<div class="input-group">
<span class="input-group-text">
<i class="fa-solid fa-user"></i>
</span>
<input
type="text"
name="username"
class="form-control"
value="<?= oldInput('username') ?>"
autocomplete="username"
required
<?= $installError !== null ? 'disabled' : '' ?>
>
</div>
</div>
<div class="mb-3">
<label class="form-label">Email Address</label>
<div class="input-group">
<span class="input-group-text">
<i class="fa-solid fa-envelope"></i>
</span>
<input
type="email"
name="email"
class="form-control"
value="<?= oldInput('email') ?>"
autocomplete="email"
required
<?= $installError !== null ? 'disabled' : '' ?>
>
</div>
</div>
<div class="mb-3">
<label class="form-label">Password</label>
<div class="input-group">
<span class="input-group-text">
<i class="fa-solid fa-lock"></i>
</span>
<input
type="password"
name="password"
class="form-control"
autocomplete="new-password"
minlength="8"
required
<?= $installError !== null ? 'disabled' : '' ?>
>
</div>
</div>
<div class="mb-4">
<label class="form-label">Confirm Password</label>
<div class="input-group">
<span class="input-group-text">
<i class="fa-solid fa-key"></i>
</span>
<input
type="password"
name="password_confirm"
class="form-control"
autocomplete="new-password"
minlength="8"
required
<?= $installError !== null ? 'disabled' : '' ?>
>
</div>
</div>
<button
type="submit"
class="btn btn-primary w-100 auth-submit"
<?= $installError !== null ? 'disabled' : '' ?>
>
<i class="fa-solid fa-user-shield me-2"></i>
Create Admin
</button>
</form>
</div>
</div>
</div>
</main>
<?php if (!installerEmbedded()): ?>
</body>
</html>
<?php endif; ?>

View File

@@ -0,0 +1,495 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../db.php';
$pdo = db();
function quoteIdentifier(string $identifier): string {
if (!preg_match('/^[A-Za-z0-9_]+$/', $identifier)) {
throw new InvalidArgumentException('Invalid identifier: ' . $identifier);
}
return '`' . $identifier . '`';
}
function tableExists(PDO $pdo, string $table): bool {
$stmt = $pdo->prepare(
'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?'
);
$stmt->execute([$table]);
return (int) $stmt->fetchColumn() > 0;
}
function indexExists(PDO $pdo, string $table, string $index): bool {
$stmt = $pdo->prepare(
'SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND INDEX_NAME = ?'
);
$stmt->execute([$table, $index]);
return (int) $stmt->fetchColumn() > 0;
}
function foreignKeyExists(PDO $pdo, string $constraint): bool {
$stmt = $pdo->prepare(
"SELECT COUNT(*) FROM information_schema.TABLE_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA = DATABASE()
AND CONSTRAINT_NAME = ?
AND CONSTRAINT_TYPE = 'FOREIGN KEY'"
);
$stmt->execute([$constraint]);
return (int) $stmt->fetchColumn() > 0;
}
function addIndexIfMissing(PDO $pdo, string $table, string $index, string $definition): void {
if (indexExists($pdo, $table, $index)) {
return;
}
$pdo->exec('ALTER TABLE ' . quoteIdentifier($table) . ' ADD ' . $definition);
}
function addForeignKeyIfMissing(PDO $pdo, string $constraint, string $definition): void {
if (foreignKeyExists($pdo, $constraint)) {
return;
}
$pdo->exec($definition);
}
function assetBlob(string $relativePath): ?string {
$path = __DIR__ . '/../../raw-resources/' . ltrim($relativePath, '/');
if (!is_file($path)) {
return null;
}
return file_get_contents($path) ?: null;
}
function upsert(PDO $pdo, string $sql, array $params): void {
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
}
try {
$pdo->exec('SET FOREIGN_KEY_CHECKS = 0');
$pdo->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS `task_states` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(128) COLLATE utf8mb4_0900_bin NOT NULL,
`color` varchar(7) COLLATE utf8mb4_0900_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin
SQL);
$pdo->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS `task_priorities` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(20) COLLATE utf8mb4_0900_bin NOT NULL,
`logo` blob,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin
SQL);
$pdo->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS `task_types` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin NOT NULL,
`logo` blob,
`default_state` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin
SQL);
$pdo->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS `users` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(128) COLLATE utf8mb4_0900_bin NOT NULL,
`email` varchar(128) COLLATE utf8mb4_0900_bin NOT NULL,
`passwd` varchar(255) COLLATE utf8mb4_0900_bin NOT NULL,
`picture` mediumblob,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin
SQL);
$pdo->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS `projects` (
`id` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin NOT NULL,
`name` varchar(128) COLLATE utf8mb4_0900_bin NOT NULL,
`owner` int NOT NULL,
`created_date` date NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin
SQL);
$pdo->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS `versions` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(128) COLLATE utf8mb4_0900_bin NOT NULL,
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin,
`created_date` date NOT NULL,
`due_date` date DEFAULT NULL,
`released_date` date DEFAULT NULL,
`project` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin
SQL);
$pdo->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS `tasks` (
`id` varchar(26) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin NOT NULL,
`title` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin NOT NULL,
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin,
`project` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin NOT NULL,
`created_date` date NOT NULL,
`last_changed` date NOT NULL,
`reporter` int NOT NULL,
`assignee` int DEFAULT NULL,
`fix_version` int DEFAULT NULL,
`type` int NOT NULL,
`priority` int NOT NULL,
`status` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin
SQL);
$pdo->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS `assigned_task_states` (
`id` int NOT NULL AUTO_INCREMENT,
`state` int NOT NULL,
`task_type` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin
SQL);
$pdo->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS `auth_tokens` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`token_hash` char(64) COLLATE utf8mb4_0900_bin NOT NULL,
`expires_at` datetime NOT NULL,
`revoked_at` datetime DEFAULT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`last_used_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin
SQL);
$pdo->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS `comments` (
`id` int NOT NULL AUTO_INCREMENT,
`response_to` int DEFAULT NULL,
`task_id` varchar(26) COLLATE utf8mb4_0900_bin NOT NULL,
`commenter` int NOT NULL,
`comment` text COLLATE utf8mb4_0900_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin
SQL);
$pdo->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS `custom_task_fields` (
`id` int NOT NULL AUTO_INCREMENT,
`task_type` int NOT NULL,
`name` varchar(128) COLLATE utf8mb4_0900_bin NOT NULL,
`type` varchar(128) COLLATE utf8mb4_0900_bin NOT NULL,
`default_value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin
SQL);
$pdo->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS `custom_field_values` (
`id` int NOT NULL AUTO_INCREMENT,
`field_id` int NOT NULL,
`task_id` varchar(26) COLLATE utf8mb4_0900_bin NOT NULL,
`value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin
SQL);
$pdo->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS `remember_tokens` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`selector` char(32) COLLATE utf8mb4_0900_bin NOT NULL,
`token_hash` char(64) COLLATE utf8mb4_0900_bin NOT NULL,
`expires_at` datetime NOT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`last_used_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin
SQL);
$pdo->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS `rights` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(128) COLLATE utf8mb4_0900_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin
SQL);
$pdo->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS `settings` (
`id` int NOT NULL AUTO_INCREMENT,
`setting_name` varchar(128) COLLATE utf8mb4_0900_bin NOT NULL,
`setting_value` varchar(256) COLLATE utf8mb4_0900_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin
SQL);
$pdo->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS `task_state_transitions` (
`id` int NOT NULL AUTO_INCREMENT,
`from_id` int NOT NULL,
`to_id` int NOT NULL,
`action_name` varchar(128) COLLATE utf8mb4_0900_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin
SQL);
$pdo->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS `user_access` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`project_id` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin
SQL);
$pdo->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS `user_rights` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`right_id` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin
SQL);
$pdo->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS `user_settings` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`setting_name` varchar(128) COLLATE utf8mb4_0900_bin NOT NULL,
`setting_value` varchar(256) COLLATE utf8mb4_0900_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin
SQL);
$indexes = [
['assigned_task_states', 'state', 'KEY `state` (`state`)'],
['assigned_task_states', 'task_type', 'KEY `task_type` (`task_type`)'],
['auth_tokens', 'unique_token_hash', 'UNIQUE KEY `unique_token_hash` (`token_hash`)'],
['auth_tokens', 'user_id', 'KEY `user_id` (`user_id`)'],
['comments', 'response_to', 'KEY `response_to` (`response_to`)'],
['comments', 'task_id', 'KEY `task_id` (`task_id`)'],
['comments', 'commenter', 'KEY `commenter` (`commenter`)'],
['custom_field_values', 'field_id', 'KEY `field_id` (`field_id`)'],
['custom_field_values', 'task_id', 'KEY `task_id` (`task_id`)'],
['custom_task_fields', 'task_type', 'KEY `task_type` (`task_type`)'],
['projects', 'owner', 'KEY `owner` (`owner`)'],
['remember_tokens', 'unique_selector', 'UNIQUE KEY `unique_selector` (`selector`)'],
['remember_tokens', 'user_id', 'KEY `user_id` (`user_id`)'],
['tasks', 'project', 'KEY `project` (`project`)'],
['tasks', 'reporter', 'KEY `reporter` (`reporter`,`assignee`,`fix_version`,`type`,`priority`)'],
['tasks', 'type', 'KEY `type` (`type`)'],
['tasks', 'priority', 'KEY `priority` (`priority`)'],
['tasks', 'assignee', 'KEY `assignee` (`assignee`)'],
['tasks', 'fix_version', 'KEY `fix_version` (`fix_version`)'],
['tasks', 'status', 'KEY `status` (`status`)'],
['task_state_transitions', 'from_id', 'KEY `from_id` (`from_id`)'],
['task_state_transitions', 'to_id', 'KEY `to_id` (`to_id`)'],
['task_types', 'default_state', 'KEY `default_state` (`default_state`)'],
['users', 'unique_email', 'UNIQUE KEY `unique_email` (`email`)'],
['user_access', 'user_id', 'KEY `user_id` (`user_id`)'],
['user_access', 'project_id', 'KEY `project_id` (`project_id`)'],
['user_rights', 'user_id', 'KEY `user_id` (`user_id`)'],
['user_rights', 'right_id', 'KEY `right_id` (`right_id`)'],
['user_settings', 'user_id', 'KEY `user_id` (`user_id`)'],
['versions', 'project', 'KEY `project` (`project`)'],
];
foreach ($indexes as [$table, $index, $definition]) {
addIndexIfMissing($pdo, $table, $index, $definition);
}
upsert(
$pdo,
"INSERT INTO `settings` (`id`, `setting_name`, `setting_value`)
VALUES (1, 'version', '0.0.1')
ON DUPLICATE KEY UPDATE `setting_name` = VALUES(`setting_name`), `setting_value` = VALUES(`setting_value`)",
[]
);
$states = [
[1, 'Open', '#858e99'],
[2, 'In Progress', '#3384e1'],
[3, 'Resolved', '#1a7f21'],
[4, 'Reopened', '#8693a2'],
[5, 'Closed', '#3c810e'],
];
foreach ($states as $state) {
upsert(
$pdo,
"INSERT INTO `task_states` (`id`, `name`, `color`)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `color` = VALUES(`color`)",
$state
);
}
$priorities = [
[1, 'Critical', 'images_task_priority/1_critical.svg'],
[2, 'Heigh', 'images_task_priority/2_heigh.svg'],
[3, 'Medium', 'images_task_priority/3_medium.svg'],
[4, 'Low', 'images_task_priority/4_low.svg'],
[5, 'Trivial', 'images_task_priority/5_trivial.svg'],
];
foreach ($priorities as [$id, $name, $asset]) {
upsert(
$pdo,
"INSERT INTO `task_priorities` (`id`, `name`, `logo`)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `logo` = VALUES(`logo`)",
[$id, $name, assetBlob($asset)]
);
}
$types = [
[1, 'Unknown', 'images_task_types/unknown.svg', 1],
[2, 'Bug', 'images_task_types/bug.svg', 1],
[3, 'New Feature', 'images_task_types/new_feature.svg', 1],
[4, 'Improvement', 'images_task_types/improvement.svg', 1],
[5, 'Task', 'images_task_types/task.svg', 1],
];
foreach ($types as [$id, $name, $asset, $defaultState]) {
upsert(
$pdo,
"INSERT INTO `task_types` (`id`, `name`, `logo`, `default_state`)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `logo` = VALUES(`logo`), `default_state` = VALUES(`default_state`)",
[$id, $name, assetBlob($asset), $defaultState]
);
}
$transitions = [
[1, 1, 2, 'Start Work'],
[2, 2, 3, 'Resolve Issue'],
[3, 3, 4, 'Reopen Issue'],
[4, 4, 2, 'Start Work'],
[5, 1, 5, 'Close'],
[6, 2, 5, 'Close'],
[7, 2, 1, 'Stop Work'],
[8, 5, 4, 'Reopen Issue'],
];
foreach ($transitions as $transition) {
upsert(
$pdo,
"INSERT INTO `task_state_transitions` (`id`, `from_id`, `to_id`, `action_name`)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE `from_id` = VALUES(`from_id`), `to_id` = VALUES(`to_id`), `action_name` = VALUES(`action_name`)",
$transition
);
}
$assignedStates = [
[1, 1, 5], [2, 2, 5], [3, 3, 5], [4, 5, 5], [5, 4, 5],
[6, 1, 1],
[7, 1, 2], [8, 2, 2], [9, 3, 2], [10, 4, 2], [11, 5, 2],
[12, 1, 3], [13, 2, 3], [14, 3, 3], [15, 4, 3], [16, 5, 3],
[17, 1, 4], [18, 2, 4], [19, 3, 4], [20, 4, 4], [21, 5, 4],
];
foreach ($assignedStates as $assignedState) {
upsert(
$pdo,
"INSERT INTO `assigned_task_states` (`id`, `state`, `task_type`)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE `state` = VALUES(`state`), `task_type` = VALUES(`task_type`)",
$assignedState
);
}
$rights = [
[1, 'Admin'],
[2, 'Create Tasks'],
[3, 'Edit Tasks'],
[4, 'Create Versions'],
[5, 'Edit Versions'],
[6, 'Create Projects'],
[7, 'Edit Projects'],
];
foreach ($rights as $right) {
upsert(
$pdo,
"INSERT INTO `rights` (`id`, `name`)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE `name` = VALUES(`name`)",
$right
);
}
$pdo->exec('SET FOREIGN_KEY_CHECKS = 1');
$foreignKeys = [
['assigned_task_states_ibfk_1', 'ALTER TABLE `assigned_task_states` ADD CONSTRAINT `assigned_task_states_ibfk_1` FOREIGN KEY (`task_type`) REFERENCES `task_types` (`id`) ON DELETE CASCADE ON UPDATE CASCADE'],
['assigned_task_states_ibfk_2', 'ALTER TABLE `assigned_task_states` ADD CONSTRAINT `assigned_task_states_ibfk_2` FOREIGN KEY (`state`) REFERENCES `task_states` (`id`) ON DELETE CASCADE ON UPDATE CASCADE'],
['auth_tokens_ibfk_1', 'ALTER TABLE `auth_tokens` ADD CONSTRAINT `auth_tokens_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE'],
['comments_ibfk_1', 'ALTER TABLE `comments` ADD CONSTRAINT `comments_ibfk_1` FOREIGN KEY (`commenter`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE'],
['comments_ibfk_2', 'ALTER TABLE `comments` ADD CONSTRAINT `comments_ibfk_2` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`id`) ON DELETE CASCADE ON UPDATE CASCADE'],
['comments_ibfk_3', 'ALTER TABLE `comments` ADD CONSTRAINT `comments_ibfk_3` FOREIGN KEY (`response_to`) REFERENCES `comments` (`id`) ON DELETE CASCADE ON UPDATE CASCADE'],
['custom_field_values_ibfk_1', 'ALTER TABLE `custom_field_values` ADD CONSTRAINT `custom_field_values_ibfk_1` FOREIGN KEY (`field_id`) REFERENCES `custom_task_fields` (`id`) ON DELETE CASCADE ON UPDATE CASCADE'],
['custom_field_values_ibfk_2', 'ALTER TABLE `custom_field_values` ADD CONSTRAINT `custom_field_values_ibfk_2` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`id`) ON DELETE CASCADE ON UPDATE CASCADE'],
['custom_task_fields_ibfk_1', 'ALTER TABLE `custom_task_fields` ADD CONSTRAINT `custom_task_fields_ibfk_1` FOREIGN KEY (`task_type`) REFERENCES `task_types` (`id`) ON DELETE CASCADE ON UPDATE CASCADE'],
['projects_ibfk_1', 'ALTER TABLE `projects` ADD CONSTRAINT `projects_ibfk_1` FOREIGN KEY (`owner`) REFERENCES `users` (`id`) ON UPDATE CASCADE'],
['remember_tokens_ibfk_1', 'ALTER TABLE `remember_tokens` ADD CONSTRAINT `remember_tokens_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE'],
['tasks_ibfk_1', 'ALTER TABLE `tasks` ADD CONSTRAINT `tasks_ibfk_1` FOREIGN KEY (`type`) REFERENCES `task_types` (`id`) ON UPDATE CASCADE'],
['tasks_ibfk_2', 'ALTER TABLE `tasks` ADD CONSTRAINT `tasks_ibfk_2` FOREIGN KEY (`priority`) REFERENCES `task_priorities` (`id`) ON UPDATE CASCADE'],
['tasks_ibfk_3', 'ALTER TABLE `tasks` ADD CONSTRAINT `tasks_ibfk_3` FOREIGN KEY (`reporter`) REFERENCES `users` (`id`) ON UPDATE CASCADE'],
['tasks_ibfk_4', 'ALTER TABLE `tasks` ADD CONSTRAINT `tasks_ibfk_4` FOREIGN KEY (`assignee`) REFERENCES `users` (`id`) ON UPDATE CASCADE'],
['tasks_ibfk_5', 'ALTER TABLE `tasks` ADD CONSTRAINT `tasks_ibfk_5` FOREIGN KEY (`project`) REFERENCES `projects` (`id`) ON UPDATE CASCADE'],
['tasks_ibfk_6', 'ALTER TABLE `tasks` ADD CONSTRAINT `tasks_ibfk_6` FOREIGN KEY (`fix_version`) REFERENCES `versions` (`id`) ON UPDATE CASCADE'],
['tasks_ibfk_7', 'ALTER TABLE `tasks` ADD CONSTRAINT `tasks_ibfk_7` FOREIGN KEY (`status`) REFERENCES `task_states` (`id`) ON DELETE SET NULL ON UPDATE CASCADE'],
['task_state_transitions_ibfk_1', 'ALTER TABLE `task_state_transitions` ADD CONSTRAINT `task_state_transitions_ibfk_1` FOREIGN KEY (`to_id`) REFERENCES `task_states` (`id`) ON DELETE CASCADE ON UPDATE CASCADE'],
['task_state_transitions_ibfk_2', 'ALTER TABLE `task_state_transitions` ADD CONSTRAINT `task_state_transitions_ibfk_2` FOREIGN KEY (`from_id`) REFERENCES `task_states` (`id`) ON DELETE CASCADE ON UPDATE CASCADE'],
['task_types_ibfk_1', 'ALTER TABLE `task_types` ADD CONSTRAINT `task_types_ibfk_1` FOREIGN KEY (`default_state`) REFERENCES `task_states` (`id`) ON DELETE SET NULL ON UPDATE CASCADE'],
['user_access_ibfk_1', 'ALTER TABLE `user_access` ADD CONSTRAINT `user_access_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE'],
['user_access_ibfk_2', 'ALTER TABLE `user_access` ADD CONSTRAINT `user_access_ibfk_2` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE ON UPDATE CASCADE'],
['user_rights_ibfk_1', 'ALTER TABLE `user_rights` ADD CONSTRAINT `user_rights_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE'],
['user_rights_ibfk_2', 'ALTER TABLE `user_rights` ADD CONSTRAINT `user_rights_ibfk_2` FOREIGN KEY (`right_id`) REFERENCES `rights` (`id`) ON DELETE CASCADE ON UPDATE CASCADE'],
['user_settings_ibfk_1', 'ALTER TABLE `user_settings` ADD CONSTRAINT `user_settings_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE'],
['versions_ibfk_1', 'ALTER TABLE `versions` ADD CONSTRAINT `versions_ibfk_1` FOREIGN KEY (`project`) REFERENCES `projects` (`id`) ON UPDATE CASCADE'],
];
foreach ($foreignKeys as [$constraint, $definition]) {
addForeignKeyIfMissing($pdo, $constraint, $definition);
}
if (realpath($_SERVER['SCRIPT_FILENAME'] ?? '') === __FILE__) {
header('Location: install.php');
exit;
}
} catch (Throwable $exception) {
try {
$pdo->exec('SET FOREIGN_KEY_CHECKS = 1');
} catch (Throwable) {
}
if (realpath($_SERVER['SCRIPT_FILENAME'] ?? '') !== __FILE__) {
throw $exception;
}
http_response_code(500);
echo '<!doctype html><meta charset="utf-8"><title>ProjectKiln install failed</title>';
echo '<h1>Database install failed</h1>';
echo '<pre>' . htmlspecialchars($exception->getMessage(), ENT_QUOTES, 'UTF-8') . '</pre>';
}