mirror of
https://github.com/chylex/Lightning-Tracker.git
synced 2025-05-30 14:34:09 +02:00
Rewrite update migrations to save progress & remove old migrations
This commit is contained in:
parent
2da94652ab
commit
ddd8f9bd9c
dev
src
@ -3,8 +3,6 @@ declare(strict_types = 1);
|
||||
|
||||
define('DEBUG', true);
|
||||
|
||||
define('INSTALLED_MIGRATION_VERSION', TRACKER_MIGRATION_VERSION);
|
||||
|
||||
define('SYS_ENABLE_REGISTRATION', true);
|
||||
define('BASE_URL', 'http://localhost');
|
||||
|
||||
|
6
dev/version.php
Normal file
6
dev/version.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
define('MIGRATION_VERSION', TRACKER_MIGRATION_VERSION);
|
||||
define('MIGRATION_TASK', 0);
|
||||
?>
|
@ -57,7 +57,7 @@ final class SystemConfig{
|
||||
$validator->validate();
|
||||
}
|
||||
|
||||
public function generate(int $migration_version = TRACKER_MIGRATION_VERSION): string{
|
||||
public function generate(): string{
|
||||
$sys_enable_registration = $this->sys_enable_registration ? 'true' : 'false';
|
||||
$base_url = addcslashes($this->base_url, '\'\\');
|
||||
$db_name = addcslashes($this->db_name, '\'\\');
|
||||
@ -70,8 +70,6 @@ final class SystemConfig{
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
define('INSTALLED_MIGRATION_VERSION', $migration_version);
|
||||
|
||||
define('SYS_ENABLE_REGISTRATION', $sys_enable_registration);
|
||||
define('BASE_URL', '$base_url');
|
||||
|
||||
@ -85,6 +83,10 @@ PHP;
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
public function write(string $file): bool{
|
||||
return file_put_contents($file, $this->generate(), LOCK_EX) !== false;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
34
src/Configuration/VersionFile.php
Normal file
34
src/Configuration/VersionFile.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Configuration;
|
||||
|
||||
final class VersionFile{
|
||||
private int $migration_version;
|
||||
private int $migration_task;
|
||||
|
||||
public function __construct(int $migration_version, int $migration_task){
|
||||
$this->migration_version = $migration_version;
|
||||
$this->migration_task = $migration_task;
|
||||
}
|
||||
|
||||
public function generate(): string{
|
||||
/** @noinspection ALL */
|
||||
$contents = <<<PHP
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
define('MIGRATION_VERSION', $this->migration_version);
|
||||
define('MIGRATION_TASK', $this->migration_task);
|
||||
?>
|
||||
PHP;
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
public function writeSafe(string $target_file, string $tmp_file): bool{
|
||||
return file_put_contents($tmp_file, $this->generate(), LOCK_EX) && rename($tmp_file, $target_file);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -120,7 +120,7 @@ HTML;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!file_put_contents(CONFIG_FILE, $config->generate(), LOCK_EX)){
|
||||
if (!$config->write(CONFIG_FILE)){
|
||||
$this->form->addMessage(FormComponent::MESSAGE_ERROR, Text::blocked('Error updating \'config.php\'.'));
|
||||
return false;
|
||||
}
|
||||
|
19
src/Update/AbstractMigrationProcess.php
Normal file
19
src/Update/AbstractMigrationProcess.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Update;
|
||||
|
||||
use Update\Tasks\SqlTask;
|
||||
|
||||
abstract class AbstractMigrationProcess{
|
||||
protected static final function sql(string $sql): SqlTask{
|
||||
return new SqlTask($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AbstractMigrationTask[]
|
||||
*/
|
||||
public abstract function getTasks(): array;
|
||||
}
|
||||
|
||||
?>
|
18
src/Update/AbstractMigrationTask.php
Normal file
18
src/Update/AbstractMigrationTask.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Update;
|
||||
|
||||
use PDO;
|
||||
|
||||
abstract class AbstractMigrationTask{
|
||||
public function prepare(PDO $db): void{
|
||||
}
|
||||
|
||||
public abstract function execute(PDO $db): void;
|
||||
|
||||
public function finalize(PDO $db): void{
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
53
src/Update/MigrationManager.php
Normal file
53
src/Update/MigrationManager.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Update;
|
||||
|
||||
use Configuration\VersionFile;
|
||||
use Exception;
|
||||
|
||||
final class MigrationManager{
|
||||
private int $current_version;
|
||||
private int $current_task;
|
||||
|
||||
public function __construct(int $current_version, int $current_task){
|
||||
$this->current_version = $current_version;
|
||||
$this->current_task = $current_task;
|
||||
}
|
||||
|
||||
public function getCurrentVersion(): int{
|
||||
return $this->current_version;
|
||||
}
|
||||
|
||||
public function getCurrentTask(): int{
|
||||
return $this->current_task;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function finishVersion(): void{
|
||||
++$this->current_version;
|
||||
$this->current_task = 0;
|
||||
$this->writeFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function finishTask(): void{
|
||||
++$this->current_task;
|
||||
$this->writeFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function writeFile(): void{
|
||||
if (!(new VersionFile($this->current_version, $this->current_task))->writeSafe(VERSION_FILE, VERSION_TMP_FILE)){
|
||||
throw new Exception('Error updating version file (migration '.$this->current_version.', task '.$this->current_task.').');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
95
src/Update/Migrations/Migration6.php
Normal file
95
src/Update/Migrations/Migration6.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Update\Migrations;
|
||||
|
||||
use Data\UserId;
|
||||
use PDO;
|
||||
use Update\AbstractMigrationProcess;
|
||||
use Update\AbstractMigrationTask;
|
||||
use Update\Tasks\DropAllForeignKeysTask;
|
||||
|
||||
final class Migration6 extends AbstractMigrationProcess{
|
||||
public function getTasks(): array{
|
||||
/** @noinspection SqlResolve, SqlWithoutWhere */
|
||||
return [
|
||||
new DropAllForeignKeysTask(),
|
||||
|
||||
self::sql('ALTER TABLE users ADD public_id CHAR(9) NOT NULL FIRST'),
|
||||
|
||||
self::sql('ALTER TABLE issues CHANGE author_id author_id_old INT NULL'),
|
||||
self::sql('ALTER TABLE issues CHANGE assignee_id assignee_id_old INT NULL'),
|
||||
self::sql('ALTER TABLE project_members CHANGE user_id user_id_old INT NOT NULL'),
|
||||
self::sql('ALTER TABLE projects CHANGE owner_id owner_id_old INT NOT NULL'),
|
||||
self::sql('ALTER TABLE project_user_settings CHANGE user_id user_id_old INT NOT NULL'),
|
||||
self::sql('ALTER TABLE user_logins CHANGE id id_old INT NOT NULL'),
|
||||
|
||||
self::sql('ALTER TABLE project_members DROP PRIMARY KEY'),
|
||||
self::sql('ALTER TABLE project_user_settings DROP PRIMARY KEY'),
|
||||
self::sql('ALTER TABLE user_logins DROP PRIMARY KEY'),
|
||||
|
||||
self::sql('ALTER TABLE issues ADD author_id CHAR(9) NULL AFTER author_id_old'),
|
||||
self::sql('ALTER TABLE issues ADD assignee_id CHAR(9) NULL AFTER assignee_id_old'),
|
||||
self::sql('ALTER TABLE project_members ADD user_id CHAR(9) NOT NULL AFTER user_id_old'),
|
||||
self::sql('ALTER TABLE projects ADD owner_id CHAR(9) NOT NULL AFTER owner_id_old'),
|
||||
self::sql('ALTER TABLE project_user_settings ADD user_id CHAR(9) NOT NULL AFTER user_id_old'),
|
||||
self::sql('ALTER TABLE user_logins ADD id CHAR(9) NOT NULL AFTER id_old'),
|
||||
|
||||
new class extends AbstractMigrationTask{
|
||||
public function execute(PDO $db): void{
|
||||
$stmt = $db->query('SELECT id FROM users');
|
||||
|
||||
while(($res = $stmt->fetchColumn()) !== false){
|
||||
/** @noinspection SqlResolve */
|
||||
$s2 = $db->prepare('UPDATE users SET public_id = ? WHERE id = ?');
|
||||
$s2->bindValue(1, UserId::generateNew());
|
||||
$s2->bindValue(2, (int)$res, PDO::PARAM_INT);
|
||||
$s2->execute();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
self::sql('UPDATE issues SET author_id = (SELECT u.public_id FROM users u WHERE u.id = author_id_old)'),
|
||||
self::sql('UPDATE issues SET assignee_id = (SELECT u.public_id FROM users u WHERE u.id = assignee_id_old)'),
|
||||
self::sql('UPDATE project_members SET user_id = (SELECT u.public_id FROM users u WHERE u.id = user_id_old)'),
|
||||
self::sql('UPDATE projects SET owner_id = (SELECT u.public_id FROM users u WHERE u.id = owner_id_old)'),
|
||||
self::sql('UPDATE project_user_settings SET user_id = (SELECT u.public_id FROM users u WHERE u.id = user_id_old)'),
|
||||
self::sql('UPDATE user_logins SET id = (SELECT u.public_id FROM users u WHERE u.id = id_old)'),
|
||||
|
||||
self::sql('ALTER TABLE users DROP id'),
|
||||
self::sql('ALTER TABLE users CHANGE public_id id CHAR(9) NOT NULL'),
|
||||
self::sql('ALTER TABLE users ADD PRIMARY KEY (id)'),
|
||||
|
||||
self::sql('ALTER TABLE project_members ADD PRIMARY KEY (project_id, user_id)'),
|
||||
self::sql('ALTER TABLE project_user_settings ADD PRIMARY KEY (project_id, user_id)'),
|
||||
self::sql('ALTER TABLE user_logins ADD PRIMARY KEY (id, token)'),
|
||||
|
||||
self::sql('ALTER TABLE issues ADD CONSTRAINT fk__issue__project FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON UPDATE CASCADE ON DELETE CASCADE'),
|
||||
self::sql('ALTER TABLE issues ADD CONSTRAINT fk__issue__author FOREIGN KEY (`author_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE SET NULL'),
|
||||
self::sql('ALTER TABLE issues ADD CONSTRAINT fk__issue__assignee FOREIGN KEY (`assignee_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE SET NULL'),
|
||||
self::sql('ALTER TABLE issues ADD CONSTRAINT fk__issue__milestone FOREIGN KEY (`milestone_id`, `project_id`) REFERENCES `milestones` (`milestone_id`, `project_id`) ON UPDATE CASCADE ON DELETE RESTRICT'),
|
||||
self::sql('ALTER TABLE issues ADD CONSTRAINT fk__issue__scale FOREIGN KEY (`scale`) REFERENCES `issue_weights` (`scale`) ON UPDATE RESTRICT ON DELETE RESTRICT'),
|
||||
self::sql('ALTER TABLE milestones ADD CONSTRAINT fk__milestone__project FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON UPDATE CASCADE ON DELETE CASCADE'),
|
||||
self::sql('ALTER TABLE project_members ADD CONSTRAINT fk__project_member__project FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON UPDATE CASCADE ON DELETE CASCADE'),
|
||||
self::sql('ALTER TABLE project_members ADD CONSTRAINT fk__project_member__user FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE CASCADE'),
|
||||
self::sql('ALTER TABLE project_members ADD CONSTRAINT fk__project_member__role FOREIGN KEY (`role_id`, `project_id`) REFERENCES `project_roles` (`role_id`, `project_id`) ON UPDATE CASCADE ON DELETE RESTRICT'),
|
||||
self::sql('ALTER TABLE project_role_permissions ADD CONSTRAINT fk__project_role_permission__role FOREIGN KEY (`role_id`, `project_id`) REFERENCES `project_roles` (`role_id`, `project_id`) ON UPDATE CASCADE ON DELETE CASCADE'),
|
||||
self::sql('ALTER TABLE project_roles ADD CONSTRAINT fk__project_role__project FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON UPDATE CASCADE ON DELETE CASCADE '),
|
||||
self::sql('ALTER TABLE projects ADD CONSTRAINT fk__project__owner FOREIGN KEY (`owner_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE RESTRICT'),
|
||||
self::sql('ALTER TABLE project_user_settings ADD CONSTRAINT fk__project_user_setting__user FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE CASCADE'),
|
||||
self::sql('ALTER TABLE project_user_settings ADD CONSTRAINT fk__project_user_setting__active_milestone FOREIGN KEY (`active_milestone`, `project_id`) REFERENCES `milestones` (`milestone_id`, `project_id`) ON UPDATE CASCADE ON DELETE CASCADE'),
|
||||
self::sql('ALTER TABLE system_role_permissions ADD CONSTRAINT fk__system_role_permission__role FOREIGN KEY (`role_id`) REFERENCES `system_roles` (`id`) ON UPDATE CASCADE ON DELETE CASCADE'),
|
||||
self::sql('ALTER TABLE user_logins ADD CONSTRAINT fk__user_login__user FOREIGN KEY (`id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE CASCADE'),
|
||||
self::sql('ALTER TABLE users ADD CONSTRAINT fk__user__role FOREIGN KEY (`role_id`) REFERENCES `system_roles` (`id`) ON UPDATE CASCADE ON DELETE SET NULL'),
|
||||
|
||||
self::sql('ALTER TABLE issues DROP author_id_old'),
|
||||
self::sql('ALTER TABLE issues DROP assignee_id_old'),
|
||||
self::sql('ALTER TABLE project_members DROP user_id_old'),
|
||||
self::sql('ALTER TABLE projects DROP owner_id_old'),
|
||||
self::sql('ALTER TABLE project_user_settings DROP user_id_old'),
|
||||
self::sql('ALTER TABLE user_logins DROP id_old')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
29
src/Update/Tasks/DropAllForeignKeysTask.php
Normal file
29
src/Update/Tasks/DropAllForeignKeysTask.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Update\Tasks;
|
||||
|
||||
use PDO;
|
||||
use Update\AbstractMigrationTask;
|
||||
|
||||
final class DropAllForeignKeysTask extends AbstractMigrationTask{
|
||||
public function execute(PDO $db): void{
|
||||
$stmt = $db->prepare(<<<SQL
|
||||
SELECT DISTINCT TABLE_NAME AS tbl, CONSTRAINT_NAME AS constr
|
||||
FROM information_schema.KEY_COLUMN_USAGE
|
||||
WHERE TABLE_SCHEMA = :db_name AND REFERENCED_TABLE_SCHEMA = TABLE_SCHEMA
|
||||
SQL
|
||||
);
|
||||
|
||||
$stmt->bindValue('db_name', DB_NAME);
|
||||
$stmt->execute();
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
foreach($rows as $row){
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE `'.$row['tbl'].'` DROP FOREIGN KEY `'.$row['constr'].'`');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
21
src/Update/Tasks/SqlTask.php
Normal file
21
src/Update/Tasks/SqlTask.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Update\Tasks;
|
||||
|
||||
use PDO;
|
||||
use Update\AbstractMigrationTask;
|
||||
|
||||
final class SqlTask extends AbstractMigrationTask{
|
||||
private string $sql;
|
||||
|
||||
public function __construct(string $sql){
|
||||
$this->sql = $sql;
|
||||
}
|
||||
|
||||
public function execute(PDO $db): void{
|
||||
$db->exec($this->sql);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
34
src/Update/Tasks/SqlTransactionTask.php
Normal file
34
src/Update/Tasks/SqlTransactionTask.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Update\Tasks;
|
||||
|
||||
use PDO;
|
||||
use Update\AbstractMigrationTask;
|
||||
|
||||
final class SqlTransactionTask extends AbstractMigrationTask{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private array $statements;
|
||||
|
||||
public function __construct(array $statements){
|
||||
$this->statements = $statements;
|
||||
}
|
||||
|
||||
public function prepare(PDO $db): void{
|
||||
$db->beginTransaction();
|
||||
}
|
||||
|
||||
public function execute(PDO $db): void{
|
||||
foreach($this->statements as $sql){
|
||||
$db->exec($sql);
|
||||
}
|
||||
}
|
||||
|
||||
public function finalize(PDO $db): void{
|
||||
$db->commit();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -1,6 +1,8 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
use Configuration\SystemConfig;
|
||||
use Configuration\VersionFile;
|
||||
use Logging\Log;
|
||||
use Routing\Request;
|
||||
use Routing\Router;
|
||||
@ -15,6 +17,9 @@ define('TRACKER_RESOURCE_VERSION', ''); // autogenerated
|
||||
define('CONFIG_FILE', __DIR__.'/config.php');
|
||||
define('CONFIG_BACKUP_FILE', __DIR__.'/config.old.php');
|
||||
|
||||
define('VERSION_FILE', __DIR__.'/version.php');
|
||||
define('VERSION_TMP_FILE', __DIR__.'/version.tmp.php');
|
||||
|
||||
setlocale(LC_ALL, 'C');
|
||||
date_default_timezone_set('UTC');
|
||||
header_remove('x-powered-by');
|
||||
@ -31,13 +36,22 @@ spl_autoload_register(static function($class){
|
||||
|
||||
require_once 'utils.php';
|
||||
|
||||
if (!file_exists('config.php')){
|
||||
if (!file_exists(CONFIG_FILE)){
|
||||
require_once 'install.php';
|
||||
return;
|
||||
}
|
||||
|
||||
/** @noinspection PhpIncludeInspection */
|
||||
require_once 'config.php';
|
||||
require_once CONFIG_FILE;
|
||||
|
||||
if (!file_exists(VERSION_FILE)){
|
||||
$version_file = new VersionFile(constant('INSTALLED_MIGRATION_VERSION'), 0);
|
||||
$version_file->writeSafe(VERSION_FILE, VERSION_TMP_FILE);
|
||||
SystemConfig::fromCurrentInstallation()->write(CONFIG_FILE);
|
||||
}
|
||||
|
||||
/** @noinspection PhpIncludeInspection */
|
||||
require_once VERSION_FILE;
|
||||
|
||||
if (!defined('DEBUG')){
|
||||
define('DEBUG', false);
|
||||
@ -61,7 +75,7 @@ define('BASE_URL_ENC', $base_url_protocol.(new UrlString($base_url_domain_path))
|
||||
|
||||
// Migration
|
||||
|
||||
if (TRACKER_MIGRATION_VERSION > INSTALLED_MIGRATION_VERSION){
|
||||
if (TRACKER_MIGRATION_VERSION > MIGRATION_VERSION){
|
||||
require_once 'update.php';
|
||||
}
|
||||
|
||||
|
@ -253,7 +253,7 @@ if (!empty($_POST) && $submit_action !== $action_value_conflict_cancel){
|
||||
|
||||
// Configuration File
|
||||
|
||||
if (empty($errors) && !file_put_contents(__DIR__.'/config.php', $config->generate(), LOCK_EX)){
|
||||
if (empty($errors) && !$config->write(CONFIG_FILE)){
|
||||
$errors[] = 'Error creating \'config.php\'.';
|
||||
}
|
||||
|
||||
|
374
src/update.php
374
src/update.php
@ -1,369 +1,57 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
use Configuration\SystemConfig;
|
||||
use Data\UserId;
|
||||
use Database\DB;
|
||||
use Logging\Log;
|
||||
use Update\AbstractMigrationProcess;
|
||||
use Update\MigrationManager;
|
||||
use Update\Migrations\Migration6;
|
||||
|
||||
function begin_transaction(PDO $db): void{
|
||||
if (!$db->inTransaction()){
|
||||
$db->beginTransaction();
|
||||
function get_migration(int $id): ?AbstractMigrationProcess{
|
||||
switch($id){
|
||||
case 6:
|
||||
return new Migration6();
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function upgrade_config(PDO $db, int $version): void{
|
||||
if (!file_put_contents(CONFIG_FILE, SystemConfig::fromCurrentInstallation()->generate($version), LOCK_EX)){
|
||||
die('Lightning Tracker tried updating to a new version and failed updating the configuration file.');
|
||||
}
|
||||
|
||||
if (isset($db) && $db->inTransaction()){
|
||||
$db->commit();
|
||||
}
|
||||
}
|
||||
$manager = new MigrationManager(MIGRATION_VERSION, MIGRATION_TASK);
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
function read_sql_file(string $path): string{
|
||||
$file = __DIR__.'/~database/'.$path;
|
||||
$contents = file_get_contents($file);
|
||||
|
||||
if ($contents === false){
|
||||
throw new Exception('Error reading file \''.$path.'\'.');
|
||||
}
|
||||
|
||||
return $contents;
|
||||
try{
|
||||
$db = DB::get();
|
||||
}catch(Exception $e){
|
||||
die('Lightning Tracker tried updating to a new version but could not connect to the database.');
|
||||
}
|
||||
|
||||
try{
|
||||
if (!copy(CONFIG_FILE, CONFIG_BACKUP_FILE)){
|
||||
die('Lightning Tracker tried updating to a new version and failed creating a backup configuration file.');
|
||||
}
|
||||
|
||||
$migration_version = INSTALLED_MIGRATION_VERSION;
|
||||
|
||||
if ($migration_version === 1){
|
||||
$db = DB::get();
|
||||
while(($version = $manager->getCurrentVersion()) < TRACKER_MIGRATION_VERSION){
|
||||
$migration = get_migration($version);
|
||||
|
||||
$db->exec('ALTER TABLE system_roles ADD special BOOL DEFAULT FALSE NOT NULL');
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE tracker_roles ADD special BOOL DEFAULT FALSE NOT NULL');
|
||||
|
||||
$stmt = $db->prepare(<<<SQL
|
||||
SELECT DISTINCT TABLE_NAME AS tbl, CONSTRAINT_NAME AS constr
|
||||
FROM information_schema.KEY_COLUMN_USAGE
|
||||
WHERE TABLE_SCHEMA = :db_name AND REFERENCED_TABLE_SCHEMA = TABLE_SCHEMA
|
||||
AND (
|
||||
(TABLE_NAME = 'issues' AND COLUMN_NAME = 'milestone_id') OR
|
||||
(TABLE_NAME = 'tracker_user_settings' AND COLUMN_NAME = 'active_milestone') OR
|
||||
(TABLE_NAME = 'tracker_user_settings' AND COLUMN_NAME = 'tracker_id')
|
||||
)
|
||||
SQL
|
||||
);
|
||||
|
||||
$stmt->bindValue('db_name', DB_NAME);
|
||||
$stmt->execute();
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
foreach($rows as $row){
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE `'.$row['tbl'].'` DROP FOREIGN KEY `'.$row['constr'].'`');
|
||||
if ($migration === null){
|
||||
die('Cannot automatically update the installed version.');
|
||||
}
|
||||
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE milestones CHANGE id milestone_id INT NOT NULL AFTER tracker_id');
|
||||
$db->exec('ALTER TABLE milestones DROP PRIMARY KEY');
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE milestones ADD PRIMARY KEY (tracker_id, milestone_id)');
|
||||
$tasks = $migration->getTasks();
|
||||
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec(<<<SQL
|
||||
ALTER TABLE issues
|
||||
ADD FOREIGN KEY (`milestone_id`, `tracker_id`)
|
||||
REFERENCES `milestones` (`milestone_id`, `tracker_id`)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE RESTRICT
|
||||
SQL
|
||||
);
|
||||
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec(<<<SQL
|
||||
ALTER TABLE tracker_user_settings
|
||||
ADD FOREIGN KEY (`active_milestone`, `tracker_id`)
|
||||
REFERENCES `milestones` (`milestone_id`, `tracker_id`)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE
|
||||
SQL
|
||||
);
|
||||
|
||||
begin_transaction($db);
|
||||
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec(<<<SQL
|
||||
INSERT INTO tracker_roles (tracker_id, title, special)
|
||||
SELECT tracker_id, 'Owner' AS title, TRUE AS special
|
||||
FROM tracker_roles
|
||||
GROUP BY tracker_id
|
||||
SQL
|
||||
);
|
||||
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec(<<<SQL
|
||||
INSERT INTO tracker_members (tracker_id, user_id, role_id)
|
||||
SELECT t.id AS tracker_id, t.owner_id AS user_id, tr.id AS role_id
|
||||
FROM trackers t
|
||||
JOIN tracker_roles tr ON t.id = tr.tracker_id AND tr.title = 'Owner' AND tr.special = TRUE
|
||||
SQL
|
||||
);
|
||||
|
||||
/** @noinspection SqlWithoutWhere */
|
||||
$db->exec('UPDATE milestones SET milestone_id = ordering');
|
||||
|
||||
upgrade_config($db, $migration_version = 2);
|
||||
}
|
||||
|
||||
if ($migration_version === 2){
|
||||
$db = DB::get();
|
||||
|
||||
$db->exec('ALTER TABLE milestones MODIFY ordering MEDIUMINT NOT NULL');
|
||||
|
||||
$stmt = $db->prepare(<<<SQL
|
||||
SELECT DISTINCT TABLE_NAME AS tbl, CONSTRAINT_NAME AS constr
|
||||
FROM information_schema.KEY_COLUMN_USAGE
|
||||
WHERE TABLE_SCHEMA = :db_name
|
||||
AND REFERENCED_TABLE_SCHEMA = TABLE_SCHEMA
|
||||
AND (TABLE_NAME = 'tracker_members' AND REFERENCED_TABLE_NAME = 'tracker_roles')
|
||||
SQL
|
||||
);
|
||||
|
||||
$stmt->bindValue('db_name', DB_NAME);
|
||||
$stmt->execute();
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
foreach($rows as $row){
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE `'.$row['tbl'].'` DROP FOREIGN KEY `'.$row['constr'].'`');
|
||||
while(($id = $manager->getCurrentTask()) < count($tasks)){
|
||||
$task = $tasks[$id];
|
||||
$task->prepare($db);
|
||||
$task->execute($db);
|
||||
$task->finalize($db);
|
||||
$manager->finishTask();
|
||||
}
|
||||
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('DROP TABLE tracker_role_perms');
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('DROP TABLE tracker_roles');
|
||||
|
||||
$db->exec(read_sql_file('TrackerRoleTable.sql'));
|
||||
$db->exec(read_sql_file('TrackerRolePermTable.sql'));
|
||||
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('UPDATE tracker_members SET role_id = NULL');
|
||||
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec(<<<SQL
|
||||
ALTER TABLE tracker_members
|
||||
ADD FOREIGN KEY (`role_id`, `tracker_id`)
|
||||
REFERENCES `tracker_roles` (`role_id`, `tracker_id`)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE RESTRICT
|
||||
SQL
|
||||
);
|
||||
|
||||
begin_transaction($db);
|
||||
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec(<<<SQL
|
||||
INSERT INTO tracker_roles (tracker_id, role_id, title, ordering, special)
|
||||
SELECT t.id, 1 AS role_id, 'Owner' AS title, 0 AS ordering, TRUE AS special
|
||||
FROM trackers t
|
||||
GROUP BY t.id
|
||||
SQL
|
||||
);
|
||||
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec(<<<SQL
|
||||
INSERT INTO tracker_members (tracker_id, user_id, role_id)
|
||||
SELECT t.id AS tracker_id, t.owner_id AS user_id, tr.role_id AS role_id
|
||||
FROM trackers t
|
||||
JOIN tracker_roles tr ON t.id = tr.tracker_id AND tr.title = 'Owner' AND tr.special = TRUE
|
||||
ON DUPLICATE KEY UPDATE role_id = tr.role_id
|
||||
SQL
|
||||
);
|
||||
|
||||
upgrade_config($db, $migration_version = 3);
|
||||
}
|
||||
|
||||
if ($migration_version === 3){
|
||||
$db = DB::get();
|
||||
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE tracker_role_perms MODIFY permission ENUM (\'settings\', \'members.list\', \'members.manage\', \'milestones.manage\', \'issues.create\', \'issues.fields.all\', \'issues.edit.all\', \'issues.delete.all\') NOT NULL');
|
||||
|
||||
begin_transaction($db);
|
||||
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec(<<<SQL
|
||||
INSERT IGNORE INTO tracker_role_perms (tracker_id, role_id, permission)
|
||||
SELECT tr.tracker_id AS tracker_id, tr.role_id AS role_id, 'issues.fields.all' AS permission
|
||||
FROM tracker_roles tr
|
||||
WHERE tr.title = 'Developer'
|
||||
SQL
|
||||
);
|
||||
|
||||
upgrade_config($db, $migration_version = 4);
|
||||
}
|
||||
|
||||
if ($migration_version === 4){
|
||||
$db = DB::get();
|
||||
|
||||
$db->exec('RENAME TABLE trackers TO projects');
|
||||
$db->exec('RENAME TABLE tracker_roles TO project_roles');
|
||||
$db->exec('RENAME TABLE tracker_role_perms TO project_role_perms');
|
||||
$db->exec('RENAME TABLE tracker_members TO project_members');
|
||||
$db->exec('RENAME TABLE tracker_user_settings TO project_user_settings');
|
||||
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE project_roles CHANGE tracker_id project_id INT NOT NULL');
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE project_role_perms CHANGE tracker_id project_id INT NOT NULL');
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE project_members CHANGE tracker_id project_id INT NOT NULL');
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE project_user_settings CHANGE tracker_id project_id INT NOT NULL');
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE issues CHANGE tracker_id project_id INT NOT NULL');
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE milestones CHANGE tracker_id project_id INT NOT NULL');
|
||||
|
||||
upgrade_config($db, $migration_version = 5);
|
||||
}
|
||||
|
||||
if ($migration_version === 5){
|
||||
$db = DB::get();
|
||||
|
||||
$db->exec('RENAME TABLE project_role_perms TO project_role_permissions');
|
||||
$db->exec('RENAME TABLE system_role_perms TO system_role_permissions');
|
||||
$db->exec('ALTER TABLE system_role_permissions MODIFY permission ENUM (\'settings\', \'projects.list\', \'projects.list.all\', \'projects.create\', \'projects.manage\', \'users.list\', \'users.view.emails\', \'users.create\', \'users.manage\') NOT NULL');
|
||||
|
||||
upgrade_config($db, $migration_version = 6);
|
||||
}
|
||||
|
||||
if ($migration_version === 6){
|
||||
$db = DB::get();
|
||||
|
||||
$stmt = $db->prepare(<<<SQL
|
||||
SELECT DISTINCT TABLE_NAME AS tbl, CONSTRAINT_NAME AS constr
|
||||
FROM information_schema.KEY_COLUMN_USAGE
|
||||
WHERE TABLE_SCHEMA = :db_name AND REFERENCED_TABLE_SCHEMA = TABLE_SCHEMA
|
||||
SQL
|
||||
);
|
||||
|
||||
$stmt->bindValue('db_name', DB_NAME);
|
||||
$stmt->execute();
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
foreach($rows as $row){
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE `'.$row['tbl'].'` DROP FOREIGN KEY `'.$row['constr'].'`');
|
||||
}
|
||||
|
||||
$db->exec('ALTER TABLE users ADD public_id CHAR(9) NOT NULL FIRST');
|
||||
|
||||
$db->exec('ALTER TABLE issues CHANGE author_id author_id_old INT NULL');
|
||||
$db->exec('ALTER TABLE issues CHANGE assignee_id assignee_id_old INT NULL');
|
||||
$db->exec('ALTER TABLE project_members CHANGE user_id user_id_old INT NOT NULL');
|
||||
$db->exec('ALTER TABLE projects CHANGE owner_id owner_id_old INT NOT NULL');
|
||||
$db->exec('ALTER TABLE project_user_settings CHANGE user_id user_id_old INT NOT NULL');
|
||||
$db->exec('ALTER TABLE user_logins CHANGE id id_old INT NOT NULL');
|
||||
|
||||
$db->exec('ALTER TABLE project_members DROP PRIMARY KEY');
|
||||
$db->exec('ALTER TABLE project_user_settings DROP PRIMARY KEY');
|
||||
$db->exec('ALTER TABLE user_logins DROP PRIMARY KEY');
|
||||
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE issues ADD author_id CHAR(9) NULL AFTER author_id_old');
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE issues ADD assignee_id CHAR(9) NULL AFTER assignee_id_old');
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE project_members ADD user_id CHAR(9) NOT NULL AFTER user_id_old');
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE projects ADD owner_id CHAR(9) NOT NULL AFTER owner_id_old');
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE project_user_settings ADD user_id CHAR(9) NOT NULL AFTER user_id_old');
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE user_logins ADD id CHAR(9) NOT NULL AFTER id_old');
|
||||
|
||||
$stmt = $db->query('SELECT id FROM users');
|
||||
|
||||
while(($res = $stmt->fetchColumn()) !== false){
|
||||
/** @noinspection SqlResolve */
|
||||
$s2 = $db->prepare('UPDATE users SET public_id = ? WHERE id = ?');
|
||||
$s2->bindValue(1, UserId::generateNew());
|
||||
$s2->bindValue(2, (int)$res, PDO::PARAM_INT);
|
||||
$s2->execute();
|
||||
}
|
||||
|
||||
/** @noinspection SqlResolve, SqlWithoutWhere */
|
||||
$db->exec('UPDATE issues SET author_id = (SELECT u.public_id FROM users u WHERE u.id = author_id_old)');
|
||||
/** @noinspection SqlResolve, SqlWithoutWhere */
|
||||
$db->exec('UPDATE issues SET assignee_id = (SELECT u.public_id FROM users u WHERE u.id = assignee_id_old)');
|
||||
/** @noinspection SqlResolve, SqlWithoutWhere */
|
||||
$db->exec('UPDATE project_members SET user_id = (SELECT u.public_id FROM users u WHERE u.id = user_id_old)');
|
||||
/** @noinspection SqlResolve, SqlWithoutWhere */
|
||||
$db->exec('UPDATE projects SET owner_id = (SELECT u.public_id FROM users u WHERE u.id = owner_id_old)');
|
||||
/** @noinspection SqlResolve, SqlWithoutWhere */
|
||||
$db->exec('UPDATE project_user_settings SET user_id = (SELECT u.public_id FROM users u WHERE u.id = user_id_old)');
|
||||
/** @noinspection SqlResolve, SqlWithoutWhere */
|
||||
$db->exec('UPDATE user_logins SET id = (SELECT u.public_id FROM users u WHERE u.id = id_old)');
|
||||
|
||||
$db->exec('ALTER TABLE users DROP id');
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE users CHANGE public_id id CHAR(9) NOT NULL');
|
||||
$db->exec('ALTER TABLE users ADD PRIMARY KEY (id)');
|
||||
|
||||
$db->exec('ALTER TABLE project_members ADD PRIMARY KEY (project_id, user_id)');
|
||||
$db->exec('ALTER TABLE project_user_settings ADD PRIMARY KEY (project_id, user_id)');
|
||||
$db->exec('ALTER TABLE user_logins ADD PRIMARY KEY (id, token)');
|
||||
|
||||
$db->exec('ALTER TABLE issues ADD CONSTRAINT fk__issue__project FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON UPDATE CASCADE ON DELETE CASCADE');
|
||||
$db->exec('ALTER TABLE issues ADD CONSTRAINT fk__issue__author FOREIGN KEY (`author_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE SET NULL');
|
||||
$db->exec('ALTER TABLE issues ADD CONSTRAINT fk__issue__assignee FOREIGN KEY (`assignee_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE SET NULL');
|
||||
$db->exec('ALTER TABLE issues ADD CONSTRAINT fk__issue__milestone FOREIGN KEY (`milestone_id`, `project_id`) REFERENCES `milestones` (`milestone_id`, `project_id`) ON UPDATE CASCADE ON DELETE RESTRICT');
|
||||
$db->exec('ALTER TABLE issues ADD CONSTRAINT fk__issue__scale FOREIGN KEY (`scale`) REFERENCES `issue_weights` (`scale`) ON UPDATE RESTRICT ON DELETE RESTRICT');
|
||||
$db->exec('ALTER TABLE milestones ADD CONSTRAINT fk__milestone__project FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON UPDATE CASCADE ON DELETE CASCADE');
|
||||
$db->exec('ALTER TABLE project_members ADD CONSTRAINT fk__project_member__project FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON UPDATE CASCADE ON DELETE CASCADE');
|
||||
$db->exec('ALTER TABLE project_members ADD CONSTRAINT fk__project_member__user FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE CASCADE');
|
||||
$db->exec('ALTER TABLE project_members ADD CONSTRAINT fk__project_member__role FOREIGN KEY (`role_id`, `project_id`) REFERENCES `project_roles` (`role_id`, `project_id`) ON UPDATE CASCADE ON DELETE RESTRICT');
|
||||
$db->exec('ALTER TABLE project_role_permissions ADD CONSTRAINT fk__project_role_permission__role FOREIGN KEY (`role_id`, `project_id`) REFERENCES `project_roles` (`role_id`, `project_id`) ON UPDATE CASCADE ON DELETE CASCADE');
|
||||
$db->exec('ALTER TABLE project_roles ADD CONSTRAINT fk__project_role__project FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON UPDATE CASCADE ON DELETE CASCADE ');
|
||||
$db->exec('ALTER TABLE projects ADD CONSTRAINT fk__project__owner FOREIGN KEY (`owner_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE RESTRICT');
|
||||
$db->exec('ALTER TABLE project_user_settings ADD CONSTRAINT fk__project_user_setting__user FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE CASCADE');
|
||||
$db->exec('ALTER TABLE project_user_settings ADD CONSTRAINT fk__project_user_setting__active_milestone FOREIGN KEY (`active_milestone`, `project_id`) REFERENCES `milestones` (`milestone_id`, `project_id`) ON UPDATE CASCADE ON DELETE CASCADE');
|
||||
$db->exec('ALTER TABLE system_role_permissions ADD CONSTRAINT fk__system_role_permission__role FOREIGN KEY (`role_id`) REFERENCES `system_roles` (`id`) ON UPDATE CASCADE ON DELETE CASCADE');
|
||||
$db->exec('ALTER TABLE user_logins ADD CONSTRAINT fk__user_login__user FOREIGN KEY (`id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE CASCADE');
|
||||
$db->exec('ALTER TABLE users ADD CONSTRAINT fk__user__role FOREIGN KEY (`role_id`) REFERENCES `system_roles` (`id`) ON UPDATE CASCADE ON DELETE SET NULL');
|
||||
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE issues DROP author_id_old');
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE issues DROP assignee_id_old');
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE project_members DROP user_id_old');
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE projects DROP owner_id_old');
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE project_user_settings DROP user_id_old');
|
||||
/** @noinspection SqlResolve */
|
||||
$db->exec('ALTER TABLE user_logins DROP id_old');
|
||||
|
||||
upgrade_config($db, $migration_version = 7);
|
||||
$manager->finishVersion();
|
||||
}
|
||||
}catch(Exception $e){
|
||||
if (isset($db) && $db->inTransaction()){
|
||||
Log::critical($e);
|
||||
|
||||
if ($db->inTransaction()){
|
||||
$db->rollBack();
|
||||
}
|
||||
|
||||
Log::critical($e);
|
||||
die('Lightning Tracker tried updating to a new version and encountered an unexpected error. Please check the server logs.');
|
||||
die('Lightning Tracker tried updating to a new version and encountered an unexpected error (migration '.$manager->getCurrentVersion().', task '.$manager->getCurrentTask().'). Please check the server logs.');
|
||||
}
|
||||
?>
|
||||
|
Loading…
Reference in New Issue
Block a user