diff --git a/dev/config.php b/dev/config.php index 2d5a834..438981e 100644 --- a/dev/config.php +++ b/dev/config.php @@ -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'); diff --git a/dev/version.php b/dev/version.php new file mode 100644 index 0000000..3c5c494 --- /dev/null +++ b/dev/version.php @@ -0,0 +1,6 @@ +<?php +declare(strict_types = 1); + +define('MIGRATION_VERSION', TRACKER_MIGRATION_VERSION); +define('MIGRATION_TASK', 0); +?> diff --git a/src/Configuration/SystemConfig.php b/src/Configuration/SystemConfig.php index c81507e..71c5b7e 100644 --- a/src/Configuration/SystemConfig.php +++ b/src/Configuration/SystemConfig.php @@ -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; + } } ?> diff --git a/src/Configuration/VersionFile.php b/src/Configuration/VersionFile.php new file mode 100644 index 0000000..4438997 --- /dev/null +++ b/src/Configuration/VersionFile.php @@ -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); + } +} + +?> diff --git a/src/Pages/Models/Root/SettingsGeneralModel.php b/src/Pages/Models/Root/SettingsGeneralModel.php index 54c773b..fc27085 100644 --- a/src/Pages/Models/Root/SettingsGeneralModel.php +++ b/src/Pages/Models/Root/SettingsGeneralModel.php @@ -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; } diff --git a/src/Update/AbstractMigrationProcess.php b/src/Update/AbstractMigrationProcess.php new file mode 100644 index 0000000..346fa9e --- /dev/null +++ b/src/Update/AbstractMigrationProcess.php @@ -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; +} + +?> diff --git a/src/Update/AbstractMigrationTask.php b/src/Update/AbstractMigrationTask.php new file mode 100644 index 0000000..f5d9313 --- /dev/null +++ b/src/Update/AbstractMigrationTask.php @@ -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{ + } +} + +?> diff --git a/src/Update/MigrationManager.php b/src/Update/MigrationManager.php new file mode 100644 index 0000000..3903bef --- /dev/null +++ b/src/Update/MigrationManager.php @@ -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.').'); + } + } +} + +?> diff --git a/src/Update/Migrations/Migration6.php b/src/Update/Migrations/Migration6.php new file mode 100644 index 0000000..18230bc --- /dev/null +++ b/src/Update/Migrations/Migration6.php @@ -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') + ]; + } +} + +?> diff --git a/src/Update/Tasks/DropAllForeignKeysTask.php b/src/Update/Tasks/DropAllForeignKeysTask.php new file mode 100644 index 0000000..2276f3a --- /dev/null +++ b/src/Update/Tasks/DropAllForeignKeysTask.php @@ -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'].'`'); + } + } +} + +?> diff --git a/src/Update/Tasks/SqlTask.php b/src/Update/Tasks/SqlTask.php new file mode 100644 index 0000000..fd13c61 --- /dev/null +++ b/src/Update/Tasks/SqlTask.php @@ -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); + } +} + +?> diff --git a/src/Update/Tasks/SqlTransactionTask.php b/src/Update/Tasks/SqlTransactionTask.php new file mode 100644 index 0000000..890dee8 --- /dev/null +++ b/src/Update/Tasks/SqlTransactionTask.php @@ -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(); + } +} + +?> diff --git a/src/bootstrap.php b/src/bootstrap.php index 79ee405..762de61 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -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'; } diff --git a/src/install.php b/src/install.php index 4bc96a6..d5ce0c5 100644 --- a/src/install.php +++ b/src/install.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\'.'; } diff --git a/src/update.php b/src/update.php index eadd9a5..d89f616 100644 --- a/src/update.php +++ b/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.'); } ?>