Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@

final class SetupController extends AbstractController
{
/**
* Run pre-upgrade checks for the installed phpMyFAQ version provided in the request body.
*
* Validates the request content as the installed version, ensures maintenance mode is enabled,
* verifies the installed version meets the minimum upgradable version, and checks hard requirements.
*
* @param Request $request The HTTP request whose body must contain the installed phpMyFAQ version string.
* @return JsonResponse A JSON response containing a `message` and an HTTP status code:
* - 200: Installation check successful.
* - 400: No version given or pre-upgrade requirement failure (exception message).
* - 409: Maintenance mode not enabled or installed version below minimum required (advice message).
*/
#[Route(path: 'setup/check', name: 'api.private.setup.check', methods: ['POST'])]
public function check(Request $request): JsonResponse
{
Expand Down Expand Up @@ -71,6 +83,17 @@ public function check(Request $request): JsonResponse
return $this->json(['message' => 'Installation check successful'], Response::HTTP_OK);
}

/**
* Create a configuration backup for the provided installed version.
*
* Validates the request body for an installed version, determines the configuration
* directory based on that version, and attempts to create a backup file.
* Returns a JSON response with HTTP 200 and `backupFile` on success,
* HTTP 400 if no version is provided, or HTTP 502 if the backup fails.
*
* @param Request $request HTTP request whose body contains the installed version string.
* @return JsonResponse JSON containing a message and, on success, a `backupFile` path.
*/
#[Route(path: 'setup/backup', name: 'api.private.setup.backup', methods: ['POST'])]
public function backup(Request $request): JsonResponse
{
Expand Down Expand Up @@ -99,6 +122,14 @@ public function backup(Request $request): JsonResponse
return $this->json(['message' => 'Backup successful', 'backupFile' => $pathToBackup], Response::HTTP_OK);
}

/**
* Performs the database upgrade for a given installed phpMyFAQ version and updates maintenance mode.
*
* Attempts to apply database updates for the installed version provided in the request body. On success, disables maintenance mode and returns a success message; on failure returns an error message describing the problem.
*
* @param Request $request HTTP request whose body must contain the installed phpMyFAQ version.
* @return JsonResponse JSON with `success` on success, or `error` with an explanatory message on failure.
*/
#[Route(path: 'setup/update-database', name: 'api.private.setup.update-database', methods: ['POST'])]
public function updateDatabase(Request $request): JsonResponse
{
Expand Down Expand Up @@ -126,4 +157,4 @@ public function updateDatabase(Request $request): JsonResponse
], Response::HTTP_BAD_GATEWAY);
}
}
}
}
150 changes: 113 additions & 37 deletions phpmyfaq/src/phpMyFAQ/Setup/Migration/AbstractMigration.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@
protected string $tablePrefix;
protected string $dbType;

/**
* Initialize the migration with a Configuration instance and capture environment-specific database settings.
*
* The constructor stores the provided Configuration for later access and sets the internal table prefix and
* database type based on the current Database environment.
*
* @param Configuration $configuration The configuration service used to read setup settings.
*/
public function __construct(
protected Configuration $configuration,
) {
Expand All @@ -36,17 +44,19 @@ public function __construct(
}

/**
* Default implementation returns an empty array (no dependencies).
* Get migration dependencies; override to declare required migration class names.
*
* @return string[]
* @return string[] List of migration class names this migration depends on. Empty array if there are no dependencies.
*/
public function getDependencies(): array
{
return [];
}

/**
* Default implementation - migrations are not reversible unless overridden.
* Indicates whether the migration is reversible.
*
* @return bool `true` if the migration is reversible, `false` otherwise.
*/
public function isReversible(): bool
{
Expand All @@ -62,17 +72,21 @@ public function down(OperationRecorder $recorder): void
}

/**
* Returns the table name with a prefix.
* Resolve a table name using the configured table prefix.
*
* @param string $name The base table name (without prefix).
* @return string The fully qualified table name including the configured prefix.
*/
protected function table(string $name): string
{
return $this->tablePrefix . $name;
}

/**
* Checks if the current database type matches any of the given types.
* Determines whether the current database type matches any of the provided type names.
*
* @param string|string[] $types Database type(s) to check
* @param string|string[] $types Database type name or list of names to compare against (e.g. 'mysqli', 'pdo_mysql').
* @return bool `true` if the current database type is one of the provided types, `false` otherwise.
*/
protected function isDbType(string|array $types): bool
{
Expand All @@ -81,41 +95,54 @@ protected function isDbType(string|array $types): bool
}

/**
* Returns true if running MySQL/MariaDB.
*/
* Determine whether the active database type is MySQL or MariaDB.
*
* @return bool `true` if the current database type is MySQL/MariaDB, `false` otherwise.
*/
protected function isMySql(): bool
{
return $this->isDbType(['mysqli', 'pdo_mysql']);
}

/**
* Returns true if running PostgreSQL.
* Determine whether the active database is PostgreSQL.
*
* @return bool `true` if the current database type is PostgreSQL (`pgsql` or `pdo_pgsql`), `false` otherwise.
*/
protected function isPostgreSql(): bool
{
return $this->isDbType(['pgsql', 'pdo_pgsql']);
}

/**
* Returns true if running SQLite.
* Determine whether the current database type is SQLite.
*
* @return bool `true` if the active DB type is `sqlite3` or `pdo_sqlite`, `false` otherwise.
*/
protected function isSqlite(): bool
{
return $this->isDbType(['sqlite3', 'pdo_sqlite']);
}

/**
* Returns true if running SQL Server.
*/
* Determine whether the current database type is Microsoft SQL Server.
*
* @return bool `true` if the active database type is SQL Server (`sqlsrv` or `pdo_sqlsrv`), `false` otherwise.
*/
protected function isSqlServer(): bool
{
return $this->isDbType(['sqlsrv', 'pdo_sqlsrv']);
}

/**
* Helper to add a column to a table.
* Returns the appropriate SQL for the current database type.
*/
* Builds an ALTER TABLE statement to add a column adapted to the active database dialect.
*
* @param string $table Unprefixed table name; the configured table prefix will be applied.
* @param string $column Column name to add.
* @param string $type SQL type definition for the new column (e.g. `VARCHAR(255)`).
* @param string|null $default Optional SQL expression for the DEFAULT clause (include quotes/literals as required).
* @return string The ALTER TABLE SQL statement that adds the specified column for the current DB type.
*/
protected function addColumn(string $table, string $column, string $type, ?string $default = null): string
{
$tableName = $this->table($table);
Expand All @@ -131,7 +158,11 @@ protected function addColumn(string $table, string $column, string $type, ?strin
}

/**
* Helper to drop a column from a table.
* Builds an SQL statement to drop a column from a prefixed table.
*
* @param string $table Unprefixed table name.
* @param string $column Column name to drop.
* @return string The SQL statement that drops the specified column from the prefixed table.
*/
protected function dropColumn(string $table, string $column): string
{
Expand All @@ -140,10 +171,12 @@ protected function dropColumn(string $table, string $column): string
}

/**
* Helper to drop multiple columns from a table (MySQL syntax).
*
* @param string[] $columns
*/
* Build an ALTER TABLE statement that drops multiple columns using MySQL-style syntax.
*
* @param string $table Unprefixed table name (will be prefixed by the migration's table prefix).
* @param string[] $columns Names of columns to drop.
* @return string The generated ALTER TABLE ... DROP COLUMN ... SQL statement.
*/
protected function dropColumns(string $table, array $columns): string
{
$tableName = $this->table($table);
Expand All @@ -152,7 +185,12 @@ protected function dropColumns(string $table, array $columns): string
}

/**
* Helper to create an index.
* Builds a CREATE INDEX SQL statement appropriate for the active database.
*
* @param string $table The logical table name (will be prefixed using the migration's table prefix).
* @param string $indexName The name of the index to create.
* @param string|array $columns Column name or list of column names to include in the index; an array is joined with ", ".
* @return string The SQL statement that creates the index if it does not already exist for the current DB.
*/
protected function createIndex(string $table, string $indexName, string|array $columns): string
{
Expand All @@ -173,23 +211,37 @@ protected function createIndex(string $table, string $indexName, string|array $c
}

/**
* Helper to drop a table.
* Build a DROP TABLE statement for the given table name using the configured prefix.
*
* @param string $table Unprefixed table name.
* @return string SQL DROP TABLE statement targeting the prefixed table.
*/
protected function dropTable(string $table): string
{
return sprintf('DROP TABLE %s', $this->table($table));
}

/**
* Helper to drop a table if it exists.
*/
* Builds a DROP TABLE IF EXISTS SQL statement for the given table name.
*
* @param string $table The table name without prefix.
* @return string The SQL statement to drop the prefixed table if it exists.
*/
protected function dropTableIfExists(string $table): string
{
return sprintf('DROP TABLE IF EXISTS %s', $this->table($table));
}

/**
* Helper for UPDATE queries with a language code fix pattern.
* Builds an UPDATE SQL statement that replaces a language code value in a specified column.
*
* The table name is passed through the migration's table-prefix resolver before being used.
*
* @param string $table The (unprefixed) table name to update.
* @param string $column The column containing the language code.
* @param string $oldCode The language code value to replace.
* @param string $newCode The language code value to set.
* @return string The constructed UPDATE SQL statement.
*/
protected function updateLanguageCode(string $table, string $column, string $oldCode, string $newCode): string
{
Expand All @@ -204,7 +256,9 @@ protected function updateLanguageCode(string $table, string $column, string $old
}

/**
* Returns the INTEGER type appropriate for the database.
* Get the SQL integer column type for the current database.
*
* @return string The SQL type name: 'INT' for MySQL/MariaDB, 'INTEGER' for other databases.
*/
protected function integerType(): string
{
Expand All @@ -215,7 +269,9 @@ protected function integerType(): string
}

/**
* Returns the TEXT type appropriate for the database.
* Determine the SQL column type to use for text fields for the current database.
*
* @return string The SQL type name for text columns: 'NVARCHAR(MAX)' for SQL Server variants, otherwise 'TEXT'.
*/
protected function textType(): string
{
Expand All @@ -226,7 +282,10 @@ protected function textType(): string
}

/**
* Returns the VARCHAR type appropriate for the database.
* Selects the VARCHAR column type declaration appropriate for the active database.
*
* @param int $length The character length for the VARCHAR/NVARCHAR column.
* @return string The SQL type declaration, e.g. `NVARCHAR(255)` for SQL Server or `VARCHAR(255)` for other databases.
*/
protected function varcharType(int $length): string
{
Expand All @@ -237,8 +296,13 @@ protected function varcharType(int $length): string
}

/**
* Returns the TIMESTAMP/DATETIME type with default appropriate for the database.
*/
* Selects the appropriate TIMESTAMP/DATETIME column type for the active database.
*
* When $withDefault is true, the returned string includes a database-specific default/current-timestamp clause.
*
* @param bool $withDefault If true, append the database-specific DEFAULT/current-timestamp clause.
* @return string The SQL column type (optionally including NOT NULL and DEFAULT clause) suitable for the current DB driver.
*/
protected function timestampType(bool $withDefault = true): string
{
$type = match ($this->dbType) {
Expand All @@ -259,8 +323,10 @@ protected function timestampType(bool $withDefault = true): string
}

/**
* Returns the BOOLEAN/TINYINT type appropriate for the database.
*/
* Determine the SQL column type used to represent boolean values for the current database.
*
* @return string `TINYINT(1)` for MySQL, `TINYINT` for SQL Server, `INTEGER` for other databases.
*/
protected function booleanType(): string
{
return match ($this->dbType) {
Expand All @@ -271,7 +337,10 @@ protected function booleanType(): string
}

/**
* Returns the auto-increment column definition appropriate for the database.
* Produce an auto-increment column definition suited to the active database type.
*
* @param string $columnName Name of the column; defaults to "id".
* @return string Column definition fragment including type and auto-increment syntax.
*/
protected function autoIncrementColumn(string $columnName = 'id'): string
{
Expand All @@ -285,16 +354,23 @@ protected function autoIncrementColumn(string $columnName = 'id'): string
}

/**
* Gets a configuration value safely.
* Retrieve the configuration value for the specified key.
*
* @param string $key The configuration key to retrieve.
* @return mixed The value associated with the configuration key.
*/
protected function getConfig(string $key): mixed
{
return $this->configuration->get(item: $key);
}

/**
* Calculates a checksum for migration integrity.
*/
* Compute a checksum representing this migration's identity.
*
* The checksum is derived from the migration's version, description, and class name.
*
* @return string The SHA-256 hash of a JSON object containing the migration's version, description, and class name.
*/
public function getChecksum(): string
{
$data = [
Expand All @@ -304,4 +380,4 @@ public function getChecksum(): string
];
return hash('sha256', json_encode($data));
}
}
}
Loading
Loading