Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
72.17% covered (warning)
72.17%
83 / 115
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Migration400Alpha
72.17% covered (warning)
72.17%
83 / 115
75.00% covered (warning)
75.00%
3 / 4
21.52
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
 getDependencies
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
71.43% covered (warning)
71.43%
80 / 112
0.00% covered (danger)
0.00%
0 / 1
16.94
1<?php
2
3/**
4 * Migration for phpMyFAQ 4.0.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\Enums\ReleaseType;
23use phpMyFAQ\Setup\Migration\AbstractMigration;
24use phpMyFAQ\Setup\Migration\Operations\OperationRecorder;
25
26readonly class Migration400Alpha extends AbstractMigration
27{
28    public function getVersion(): string
29    {
30        return '4.0.0-alpha';
31    }
32
33    public function getDependencies(): array
34    {
35        return ['3.2.3'];
36    }
37
38    public function getDescription(): string
39    {
40        return 'New file layout, bookmarks, sticky order, online update config, remove social networks';
41    }
42
43    public function up(OperationRecorder $recorder): void
44    {
45        // Copy database configuration
46        if (defined('PMF_LEGACY_CONFIG_DIR') && defined('PMF_CONFIG_DIR')) {
47            $legacyConfigDir = (string) PMF_LEGACY_CONFIG_DIR;
48            $configDir = (string) PMF_CONFIG_DIR;
49            $recorder->copyFile($legacyConfigDir . '/database.php', $configDir . '/database.php');
50
51            // Copy Azure configuration, if available
52            $recorder->copyFile($legacyConfigDir . '/azure.php', $configDir . '/azure.php', true);
53
54            // Copy Elasticsearch configuration, if available
55            $recorder->copyFile($legacyConfigDir . '/elasticsearch.php', $configDir . '/elasticsearch.php', true);
56
57            // Copy LDAP configuration, if available
58            $recorder->copyFile($legacyConfigDir . '/ldap.php', $configDir . '/ldap.php', true);
59        }
60
61        if (defined('PMF_ROOT_DIR')) {
62            $rootDir = (string) PMF_ROOT_DIR;
63
64            // Copy data directory
65            $recorder->copyDirectory($rootDir . '/data', $rootDir . '/content/core');
66
67            // Copy logs directory
68            $recorder->copyDirectory($rootDir . '/logs', $rootDir . '/content/core');
69
70            // Copy attachments directory
71            $recorder->copyDirectory($rootDir . '/attachments', $rootDir . '/content/user');
72
73            // Copy images directory
74            $recorder->copyDirectory($rootDir . '/images', $rootDir . '/content/user');
75        }
76
77        // Online Update configuration
78        $recorder->addConfig('upgrade.onlineUpdateEnabled', true);
79        $recorder->addConfig('upgrade.releaseEnvironment', ReleaseType::DEVELOPMENT->value);
80        $recorder->addConfig('upgrade.dateLastChecked', '');
81        $recorder->addConfig('upgrade.lastDownloadedPackage', '');
82
83        // Rewrite rules are now mandatory, social network support removed
84        $recorder->deleteConfig('main.enableRewriteRules');
85        $recorder->deleteConfig('socialnetworks.enableTwitterSupport');
86        $recorder->deleteConfig('socialnetworks.twitterConsumerKey');
87        $recorder->deleteConfig('socialnetworks.twitterConsumerSecret');
88        $recorder->deleteConfig('socialnetworks.twitterAccessTokenKey');
89        $recorder->deleteConfig('socialnetworks.twitterAccessTokenSecret');
90        $recorder->deleteConfig('socialnetworks.disableAll');
91        $recorder->deleteConfig('mail.remoteSMTPEncryption');
92
93        // Bookmarks support
94        if ($this->isMySql()) {
95            $recorder->addSql(
96                sprintf(
97                    'CREATE TABLE %sfaqbookmarks (userid int(11) DEFAULT NULL, faqid int(11) DEFAULT NULL)',
98                    $this->tablePrefix,
99                ),
100                'Create bookmarks table (MySQL)',
101            );
102        }
103
104        if (!$this->isMySql()) {
105            $recorder->addSql(
106                sprintf(
107                    'CREATE TABLE %sfaqbookmarks (userid INTEGER DEFAULT NULL, faqid INTEGER DEFAULT NULL)',
108                    $this->tablePrefix,
109                ),
110                'Create bookmarks table',
111            );
112        }
113
114        // Custom order of sticky records
115        if ($this->isMySql()) {
116            $recorder->addSql(
117                sprintf('ALTER TABLE %sfaqdata ADD COLUMN sticky_order int(10) DEFAULT NULL', $this->tablePrefix),
118                'Add sticky_order column to faqdata (MySQL)',
119            );
120
121            $recorder->addSql(
122                sprintf(
123                    'ALTER TABLE %sfaqdata_revisions ADD COLUMN sticky_order int(10) DEFAULT NULL',
124                    $this->tablePrefix,
125                ),
126                'Add sticky_order column to faqdata_revisions (MySQL)',
127            );
128        }
129
130        if (!$this->isMySql()) {
131            $recorder->addSql(
132                sprintf('ALTER TABLE %sfaqdata ADD COLUMN sticky_order integer DEFAULT NULL', $this->tablePrefix),
133                'Add sticky_order column to faqdata',
134            );
135
136            $recorder->addSql(
137                sprintf(
138                    'ALTER TABLE %sfaqdata_revisions ADD COLUMN sticky_order integer DEFAULT NULL',
139                    $this->tablePrefix,
140                ),
141                'Add sticky_order column to faqdata_revisions',
142            );
143        }
144
145        $recorder->addConfig('records.orderStickyFaqsCustom', 'false');
146
147        // Remove template metadata tables
148        $recorder->addSql(sprintf('DROP TABLE %sfaqmeta', $this->tablePrefix), 'Drop faqmeta table');
149
150        // Blocked statistics browsers
151        $recorder->addConfig('main.botIgnoreList', 'nustcrape,webpost,GoogleBot,msnbot,crawler,scooter,
152            bravobrian,archiver,w3c,controler,wget,bot,spider,Yahoo! Slurp,htdig,gsa-crawler,AirControler,Uptime-Kuma');
153
154        // Enable/Disable cookie consent
155        $recorder->addConfig('main.enableCookieConsent', true);
156
157        // Add parent category ID to faqcategory_order
158        if ($this->isMySql()) {
159            $recorder->addSql(
160                sprintf(
161                    'ALTER TABLE %sfaqcategory_order ADD COLUMN parent_id int(11) DEFAULT NULL AFTER category_id',
162                    $this->tablePrefix,
163                ),
164                'Add parent_id column to faqcategory_order (MySQL)',
165            );
166        }
167
168        if (!$this->isMySql() && $this->isSqlServer()) {
169            $recorder->addSql(
170                sprintf(
171                    'ALTER TABLE %sfaqcategory_order ADD COLUMN parent_id INTEGER DEFAULT NULL',
172                    $this->tablePrefix,
173                ),
174                'Add parent_id column to faqcategory_order (SQL Server)',
175            );
176        }
177
178        if (!$this->isMySql() && !$this->isSqlServer()) {
179            // SQLite and PostgreSQL - table rebuild approach
180            $recorder->addSql(sprintf(
181                'CREATE TABLE %sfaqcategory_order_new (
182                    category_id INTEGER NOT NULL,
183                    parent_id INTEGER DEFAULT NULL,
184                    position INTEGER NOT NULL,
185                    PRIMARY KEY (category_id))',
186                $this->tablePrefix,
187            ), 'Create new faqcategory_order table with parent_id');
188
189            $recorder->addSql(
190                sprintf(
191                    'INSERT INTO %sfaqcategory_order_new (category_id, parent_id, position) SELECT category_id, NULL AS parent_id, position FROM %sfaqcategory_order',
192                    $this->tablePrefix,
193                    $this->tablePrefix,
194                ),
195                'Copy data to new faqcategory_order table',
196            );
197
198            $recorder->addSql(
199                sprintf('DROP TABLE %sfaqcategory_order', $this->tablePrefix),
200                'Drop old faqcategory_order table',
201            );
202
203            $recorder->addSql(
204                sprintf(
205                    'ALTER TABLE %sfaqcategory_order_new RENAME TO %sfaqcategory_order',
206                    $this->tablePrefix,
207                    $this->tablePrefix,
208                ),
209                'Rename new faqcategory_order table',
210            );
211        }
212    }
213}