Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
80.56% covered (success)
80.56%
58 / 72
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
Migration320Alpha
80.56% covered (success)
80.56%
58 / 72
66.67% covered (warning)
66.67%
2 / 3
6.26
0.00% covered (danger)
0.00%
0 / 1
 getVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDescription
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 up
80.00% covered (success)
80.00%
56 / 70
0.00% covered (danger)
0.00%
0 / 1
4.13
1<?php
2
3/**
4 * Migration for phpMyFAQ 3.2.0-alpha.
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public License,
7 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
8 * obtain one at https://mozilla.org/MPL/2.0/.
9 *
10 * @package   phpMyFAQ
11 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
12 * @copyright 2023-2026 phpMyFAQ Team
13 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14 * @link      https://www.phpmyfaq.de
15 * @since     2026-01-25
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Setup\Migration\Versions;
21
22use phpMyFAQ\Setup\Migration\AbstractMigration;
23use phpMyFAQ\Setup\Migration\Operations\OperationRecorder;
24
25readonly class Migration320Alpha extends AbstractMigration
26{
27    public function getVersion(): string
28    {
29        return '3.2.0-alpha';
30    }
31
32    public function getDescription(): string
33    {
34        return 'Microsoft Entra ID support, 2FA support, backup table, Google ReCAPTCHA v2, remove section tables';
35    }
36
37    public function up(OperationRecorder $recorder): void
38    {
39        // Microsoft Entra ID support and 2FA-support
40        $recorder->addConfig('security.enableSignInWithMicrosoft', false);
41
42        // Add columns for OAuth tokens
43        if ($this->isSqlite()) {
44            $recorder->addSql(
45                sprintf('ALTER TABLE %sfaquser ADD COLUMN refresh_token TEXT NULL DEFAULT NULL', $this->tablePrefix),
46                'Add OAuth refresh_token to faquser (SQLite)',
47            );
48            $recorder->addSql(
49                sprintf('ALTER TABLE %sfaquser ADD COLUMN access_token TEXT NULL DEFAULT NULL', $this->tablePrefix),
50                'Add OAuth access_token to faquser (SQLite)',
51            );
52            $recorder->addSql(
53                sprintf(
54                    'ALTER TABLE %sfaquser ADD COLUMN code_verifier VARCHAR(255) NULL DEFAULT NULL',
55                    $this->tablePrefix,
56                ),
57                'Add OAuth code_verifier to faquser (SQLite)',
58            );
59            $recorder->addSql(
60                sprintf('ALTER TABLE %sfaquser ADD COLUMN jwt TEXT NULL DEFAULT NULL', $this->tablePrefix),
61                'Add OAuth jwt to faquser (SQLite)',
62            );
63
64            $recorder->addSql(
65                sprintf(
66                    'ALTER TABLE %sfaquserdata ADD COLUMN twofactor_enabled INT(1) NULL DEFAULT 0',
67                    $this->tablePrefix,
68                ),
69                'Add 2FA twofactor_enabled to faquserdata (SQLite)',
70            );
71            $recorder->addSql(
72                sprintf(
73                    'ALTER TABLE %sfaquserdata ADD COLUMN secret VARCHAR(128) NULL DEFAULT NULL',
74                    $this->tablePrefix,
75                ),
76                'Add 2FA secret to faquserdata (SQLite)',
77            );
78        }
79
80        if (!$this->isSqlite()) {
81            $recorder->addSql(sprintf(
82                'ALTER TABLE %sfaquser
83                    ADD refresh_token TEXT NULL DEFAULT NULL,
84                    ADD access_token TEXT NULL DEFAULT NULL,
85                    ADD code_verifier VARCHAR(255) NULL DEFAULT NULL,
86                    ADD jwt TEXT NULL DEFAULT NULL',
87                $this->tablePrefix,
88            ), 'Add OAuth token columns to faquser');
89
90            $recorder->addSql(sprintf(
91                'ALTER TABLE %sfaquserdata
92                    ADD twofactor_enabled INT NULL DEFAULT 0,
93                    ADD secret VARCHAR(128) NULL DEFAULT NULL',
94                $this->tablePrefix,
95            ), 'Add 2FA columns to faquserdata');
96        }
97
98        // New backup table
99        $timestampType = $this->timestampType(false);
100        $recorder->addSql(
101            sprintf('CREATE TABLE %sfaqbackup (
102                id INT NOT NULL,
103                filename VARCHAR(255) NOT NULL,
104                authkey VARCHAR(255) NOT NULL,
105                authcode VARCHAR(255) NOT NULL,
106                created %s NOT NULL,
107                PRIMARY KEY (id))', $this->tablePrefix, $timestampType),
108            'Create backup table',
109        );
110
111        // Migrate MySQL from MyISAM to InnoDB
112        if ($this->isMySql()) {
113            $recorder->addSql(
114                sprintf('ALTER TABLE %sfaqdata ENGINE=INNODB', $this->tablePrefix),
115                'Migrate faqdata to InnoDB',
116            );
117        }
118
119        // New options
120        $recorder->addConfig('main.enableAskQuestions', true);
121        $recorder->addConfig('main.enableNotifications', true);
122
123        // Update options
124        $recorder->renameConfig('security.loginWithEmailAddress', 'security.loginWithEmailAddress');
125
126        // Handle permLevel migration - note: actual update requires reading current value
127        // This is handled by the configuration update operation
128
129        // Google ReCAPTCHAv3 support
130        $recorder->addConfig('security.enableGoogleReCaptchaV2', false);
131        $recorder->addConfig('security.googleReCaptchaV2SiteKey', '');
132        $recorder->addConfig('security.googleReCaptchaV2SecretKey', '');
133
134        // Remove section tables
135        $recorder->addSql(sprintf('DROP TABLE %sfaqsections', $this->tablePrefix), 'Drop faqsections table');
136        $recorder->addSql(
137            sprintf('DROP TABLE %sfaqsection_category', $this->tablePrefix),
138            'Drop faqsection_category table',
139        );
140        $recorder->addSql(sprintf('DROP TABLE %sfaqsection_group', $this->tablePrefix), 'Drop faqsection_group table');
141        $recorder->addSql(sprintf('DROP TABLE %sfaqsection_news', $this->tablePrefix), 'Drop faqsection_news table');
142    }
143}