Lines
52.00%
78 / 150
Methods
60.00%
18 / 30
Classes
0.00%
0 / 1
| Name | Lines | Methods | CRAP | ||||
|---|---|---|---|---|---|---|---|
| getVersion | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| getDependencies | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| getDescription | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| up | 0.00% | 0 / 3 | 0.00% | 0 / 1 | 2 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] __construct | 100.00% | 2 / 2 | 100.00% | 1 / 1 | 1 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] isReversible | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] down | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] table | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] isDbType | 100.00% | 2 / 2 | 100.00% | 1 / 1 | 1 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] isMySql | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] isPostgreSql | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] isSqlite | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] isSqlServer | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] addColumn | 62.50% | 5 / 8 | 0.00% | 0 / 1 | 4.84 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] dropColumn | 100.00% | 2 / 2 | 100.00% | 1 / 1 | 1 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] dropColumns | 100.00% | 6 / 6 | 100.00% | 1 / 1 | 1 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] createIndex | 33.33% | 5 / 15 | 0.00% | 0 / 1 | 8.74 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] createUniqueIndex | 28.57% | 6 / 21 | 0.00% | 0 / 1 | 6.28 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] indexExists | 40.62% | 13 / 32 | 0.00% | 0 / 1 | 10.23 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] dropTable | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] dropTableIfExists | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] updateLanguageCode | 100.00% | 11 / 11 | 100.00% | 1 / 1 | 1 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] integerType | 66.66% | 2 / 3 | 0.00% | 0 / 1 | 3.33 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] textType | 66.66% | 2 / 3 | 0.00% | 0 / 1 | 3.33 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] varcharType | 0.00% | 0 / 3 | 0.00% | 0 / 1 | 12 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] timestampType | 40.00% | 4 / 10 | 0.00% | 0 / 1 | 21.82 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] booleanType | 0.00% | 0 / 4 | 0.00% | 0 / 1 | 20 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] autoIncrementColumn | 0.00% | 0 / 6 | 0.00% | 0 / 1 | 42 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] getConfig | 0.00% | 0 / 1 | 0.00% | 0 / 1 | 2 | ||
| [phpMyFAQ\Setup\Migration\AbstractMigration] getChecksum | 100.00% | 6 / 6 | 100.00% | 1 / 1 | 1 | ||
| 26 | readonly class Migration410Alpha2 extends AbstractMigration | |
| 27 | { | |
| 28 | public function getVersion(): string | |
| 29 | { | |
| 30 | return '4.1.0-alpha.2'; | |
| 31 | } | |
| 32 | ||
| 33 | public function getDependencies(): array | |
| 34 | { | |
| 35 | return ['4.1.0-alpha']; | |
| 36 | } | |
| 37 | ||
| 38 | public function getDescription(): string | |
| 39 | { | |
| 40 | return 'Admin session timeout counter, OpenSearch config, FAQ translate permission'; | |
| 41 | } | |
| 42 | ||
| 43 | public function up(OperationRecorder $recorder): void | |
| 44 | { | |
| 45 | $recorder->addConfig('security.enableAdminSessionTimeoutCounter', true); | |
| 46 | $recorder->addConfig('search.enableOpenSearch', false); | |
| 47 | ||
| 48 | // Add new permission to translate FAQs | |
| 49 | $recorder->grantPermission(PermissionType::FAQ_TRANSLATE->value, 'Right to translate FAQs'); | |
| 50 | } | |
| 51 | } |
Inherited from phpMyFAQ\Setup\Migration\AbstractMigration
| 32 | public function __construct( | |
| 33 | protected Configuration $configuration, | |
| 34 | ) { | |
| 35 | $this->tablePrefix = Database::getTablePrefix(); | |
| 36 | $this->dbType = Database::getType(); | |
| 37 | } |
| 52 | public function isReversible(): bool | |
| 53 | { | |
| 54 | return false; | |
| 55 | } |
| 60 | public function down(OperationRecorder $recorder): void | |
| 61 | { | |
| 62 | // Override in subclass if migration is reversible | |
| 63 | } |
| 68 | protected function table(string $name): string | |
| 69 | { | |
| 70 | return $this->tablePrefix . $name; | |
| 71 | } |
| 78 | protected function isDbType(string|array $types): bool | |
| 79 | { | |
| 80 | $types = (array) $types; | |
| 81 | return in_array($this->dbType, $types, strict: true); | |
| 82 | } |
| 87 | protected function isMySql(): bool | |
| 88 | { | |
| 89 | return $this->isDbType(['mysqli', 'pdo_mysql']); | |
| 90 | } |
| 95 | protected function isPostgreSql(): bool | |
| 96 | { | |
| 97 | return $this->isDbType(['pgsql', 'pdo_pgsql']); | |
| 98 | } |
| 103 | protected function isSqlite(): bool | |
| 104 | { | |
| 105 | return $this->isDbType(['sqlite3', 'pdo_sqlite']); | |
| 106 | } |
| 111 | protected function isSqlServer(): bool | |
| 112 | { | |
| 113 | return $this->isDbType(['sqlsrv', 'pdo_sqlsrv']); | |
| 114 | } |
| 120 | protected function addColumn(string $table, string $column, string $type, ?string $default = null): string | |
| 121 | { | |
| 122 | $tableName = $this->table($table); | |
| 123 | $defaultClause = ''; | |
| 124 | if ($default !== null) { | |
| 125 | $defaultClause = " DEFAULT {$default}"; | |
| 126 | } | |
| 127 | ||
| 128 | if ($this->isSqlite()) { | |
| 129 | return sprintf('ALTER TABLE %s ADD COLUMN %s %s%s', $tableName, $column, $type, $defaultClause); | |
| 130 | } | |
| 131 | ||
| 132 | // MySQL, PostgreSQL, SQL Server | |
| 133 | $columnKeyword = $this->isSqlServer() ? '' : 'COLUMN '; | |
| 134 | return sprintf('ALTER TABLE %s ADD %s%s %s%s', $tableName, $columnKeyword, $column, $type, $defaultClause); | |
| 135 | } |
| 140 | protected function dropColumn(string $table, string $column): string | |
| 141 | { | |
| 142 | $tableName = $this->table($table); | |
| 143 | return sprintf('ALTER TABLE %s DROP COLUMN %s', $tableName, $column); | |
| 144 | } |
| 153 | protected function dropColumns(string $table, array $columns): array | |
| 154 | { | |
| 155 | $tableName = $this->table($table); | |
| 156 | return array_map(static fn(string $col): string => sprintf( | |
| 157 | 'ALTER TABLE %s DROP COLUMN %s', | |
| 158 | $tableName, | |
| 159 | $col, | |
| 160 | ), $columns); | |
| 161 | } |
| 168 | protected function createIndex(string $table, string $indexName, string|array $columns): string | |
| 169 | { | |
| 170 | $tableName = $this->table($table); | |
| 171 | $columnList = is_array($columns) ? implode(', ', $columns) : $columns; | |
| 172 | ||
| 173 | if ($this->isSqlServer()) { | |
| 174 | return sprintf( | |
| 175 | "IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = '%s' AND object_id = OBJECT_ID('%s')) " | |
| 176 | . 'CREATE INDEX %s ON %s (%s)', | |
| 177 | $indexName, | |
| 178 | $tableName, | |
| 179 | $indexName, | |
| 180 | $tableName, | |
| 181 | $columnList, | |
| 182 | ); | |
| 183 | } | |
| 184 | ||
| 185 | // MySQL/MariaDB don't support IF NOT EXISTS in older versions | |
| 186 | // Use conditional execution or manual check instead | |
| 187 | if ($this->isMySql()) { | |
| 188 | return sprintf('CREATE INDEX %s ON %s (%s)', $indexName, $tableName, $columnList); | |
| 189 | } | |
| 190 | ||
| 191 | // PostgreSQL and SQLite support IF NOT EXISTS | |
| 192 | return sprintf('CREATE INDEX IF NOT EXISTS %s ON %s (%s)', $indexName, $tableName, $columnList); | |
| 193 | } |
| 204 | protected function createUniqueIndex(string $table, string $indexName, string|array $columns): string | |
| 205 | { | |
| 206 | $tableName = $this->table($table); | |
| 207 | $columns = (array) $columns; | |
| 208 | $columnList = implode(', ', $columns); | |
| 209 | ||
| 210 | if ($this->isSqlServer()) { | |
| 211 | $whereClause = implode(' AND ', array_map(static fn(string $col): string => sprintf( | |
| 212 | '%s IS NOT NULL', | |
| 213 | $col, | |
| 214 | ), $columns)); | |
| 215 | return sprintf( | |
| 216 | "IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = '%s' AND object_id = OBJECT_ID('%s')) " | |
| 217 | . 'CREATE UNIQUE INDEX %s ON %s (%s) WHERE %s', | |
| 218 | $indexName, | |
| 219 | $tableName, | |
| 220 | $indexName, | |
| 221 | $tableName, | |
| 222 | $columnList, | |
| 223 | $whereClause, | |
| 224 | ); | |
| 225 | } | |
| 226 | ||
| 227 | if ($this->isMySql()) { | |
| 228 | return sprintf('CREATE UNIQUE INDEX %s ON %s (%s)', $indexName, $tableName, $columnList); | |
| 229 | } | |
| 230 | ||
| 231 | return sprintf('CREATE UNIQUE INDEX IF NOT EXISTS %s ON %s (%s)', $indexName, $tableName, $columnList); | |
| 232 | } |
| 238 | protected function indexExists(string $table, string $indexName): string | |
| 239 | { | |
| 240 | $db = $this->configuration->getDb(); | |
| 241 | $tableName = $this->table($table); | |
| 242 | $escapedTableName = $db->escape($tableName); | |
| 243 | $escapedIndexName = $db->escape($indexName); | |
| 244 | ||
| 245 | if ($this->isMySql()) { | |
| 246 | return sprintf( | |
| 247 | 'SELECT COUNT(*) as idx_count FROM information_schema.STATISTICS ' | |
| 248 | . "WHERE table_schema = DATABASE() AND table_name = '%s' AND index_name = '%s'", | |
| 249 | $escapedTableName, | |
| 250 | $escapedIndexName, | |
| 251 | ); | |
| 252 | } | |
| 253 | ||
| 254 | if ($this->isPostgreSql()) { | |
| 255 | return sprintf( | |
| 256 | 'SELECT COUNT(*) as idx_count FROM pg_indexes ' | |
| 257 | . "WHERE schemaname = 'public' AND tablename = '%s' AND indexname = '%s'", | |
| 258 | $escapedTableName, | |
| 259 | $escapedIndexName, | |
| 260 | ); | |
| 261 | } | |
| 262 | ||
| 263 | if ($this->isSqlite()) { | |
| 264 | return sprintf( | |
| 265 | 'SELECT COUNT(*) as idx_count FROM sqlite_master ' | |
| 266 | . "WHERE type = 'index' AND name = '%s' AND tbl_name = '%s'", | |
| 267 | $escapedIndexName, | |
| 268 | $escapedTableName, | |
| 269 | ); | |
| 270 | } | |
| 271 | ||
| 272 | if ($this->isSqlServer()) { | |
| 273 | return sprintf( | |
| 274 | 'SELECT COUNT(*) as idx_count FROM sys.indexes ' . "WHERE name = '%s' AND object_id = OBJECT_ID('%s')", | |
| 275 | $escapedIndexName, | |
| 276 | $escapedTableName, | |
| 277 | ); | |
| 278 | } | |
| 279 | ||
| 280 | throw new \RuntimeException("Unsupported database type for index existence check: {$this->dbType}"); | |
| 281 | } |
| 286 | protected function dropTable(string $table): string | |
| 287 | { | |
| 288 | return sprintf('DROP TABLE %s', $this->table($table)); | |
| 289 | } |
| 294 | protected function dropTableIfExists(string $table): string | |
| 295 | { | |
| 296 | return sprintf('DROP TABLE IF EXISTS %s', $this->table($table)); | |
| 297 | } |
| 302 | protected function updateLanguageCode(string $table, string $column, string $oldCode, string $newCode): string | |
| 303 | { | |
| 304 | $db = $this->configuration->getDb(); | |
| 305 | $escapedOldCode = $db->escape($oldCode); | |
| 306 | $escapedNewCode = $db->escape($newCode); | |
| 307 | ||
| 308 | return sprintf( | |
| 309 | "UPDATE %s SET %s='%s' WHERE %s='%s'", | |
| 310 | $this->table($table), | |
| 311 | $column, | |
| 312 | $escapedNewCode, | |
| 313 | $column, | |
| 314 | $escapedOldCode, | |
| 315 | ); | |
| 316 | } |
| 321 | protected function integerType(): string | |
| 322 | { | |
| 323 | return match ($this->dbType) { | |
| 324 | 'mysqli', 'pdo_mysql' => 'INT', | |
| 325 | default => 'INTEGER', | |
| 326 | }; | |
| 327 | } |
| 332 | protected function textType(): string | |
| 333 | { | |
| 334 | return match ($this->dbType) { | |
| 335 | 'sqlsrv', 'pdo_sqlsrv' => 'NVARCHAR(MAX)', | |
| 336 | default => 'TEXT', | |
| 337 | }; | |
| 338 | } |
| 343 | protected function varcharType(int $length): string | |
| 344 | { | |
| 345 | return match ($this->dbType) { | |
| 346 | 'sqlsrv', 'pdo_sqlsrv' => "NVARCHAR({$length})", | |
| 347 | default => "VARCHAR({$length})", | |
| 348 | }; | |
| 349 | } |
| 354 | protected function timestampType(bool $withDefault = true): string | |
| 355 | { | |
| 356 | $type = match ($this->dbType) { | |
| 357 | 'mysqli', 'pdo_mysql' => 'TIMESTAMP', | |
| 358 | 'sqlsrv', 'pdo_sqlsrv' => 'DATETIME', | |
| 359 | default => 'TIMESTAMP', | |
| 360 | }; | |
| 361 | ||
| 362 | if (!$withDefault) { | |
| 363 | return $type; | |
| 364 | } | |
| 365 | ||
| 366 | return match ($this->dbType) { | |
| 367 | 'sqlsrv', 'pdo_sqlsrv' => $type . ' NOT NULL DEFAULT GETDATE()', | |
| 368 | 'sqlite3', 'pdo_sqlite' => 'DATETIME DEFAULT CURRENT_TIMESTAMP', | |
| 369 | default => $type . ' NOT NULL DEFAULT CURRENT_TIMESTAMP', | |
| 370 | }; | |
| 371 | } |
| 376 | protected function booleanType(): string | |
| 377 | { | |
| 378 | return match ($this->dbType) { | |
| 379 | 'mysqli', 'pdo_mysql' => 'TINYINT(1)', | |
| 380 | 'sqlsrv', 'pdo_sqlsrv' => 'TINYINT', | |
| 381 | default => 'INTEGER', | |
| 382 | }; | |
| 383 | } |
| 388 | protected function autoIncrementColumn(string $columnName = 'id'): string | |
| 389 | { | |
| 390 | return match ($this->dbType) { | |
| 391 | 'mysqli', 'pdo_mysql' => "{$columnName} INT NOT NULL AUTO_INCREMENT", | |
| 392 | 'pgsql', 'pdo_pgsql' => "{$columnName} SERIAL NOT NULL", | |
| 393 | 'sqlite3', 'pdo_sqlite' => "{$columnName} INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT", | |
| 394 | 'sqlsrv', 'pdo_sqlsrv' => "{$columnName} INT IDENTITY(1,1) NOT NULL", | |
| 395 | default => "{$columnName} INTEGER NOT NULL", | |
| 396 | }; | |
| 397 | } |
| 402 | protected function getConfig(string $key): mixed | |
| 403 | { | |
| 404 | return $this->configuration->get(item: $key); | |
| 405 | } |
| 410 | public function getChecksum(): string | |
| 411 | { | |
| 412 | $data = [ | |
| 413 | 'version' => $this->getVersion(), | |
| 414 | 'description' => $this->getDescription(), | |
| 415 | 'class' => static::class, | |
| 416 | ]; | |
| 417 | return hash('sha256', (string) json_encode($data)); | |
| 418 | } |