Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
PdoSqlite
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
2 / 2
4
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createTables
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3/**
4 * The phpMyFAQ instances a database class with CREATE TABLE statements for SQLite3.
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 2025 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     2025-10-26
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Instance\Database;
21
22use phpMyFAQ\Configuration;
23use phpMyFAQ\Instance\Database;
24
25/**
26 * Class Sqlite3
27 *
28 * @package phpMyFAQ\Instance\Database
29 */
30class PdoSqlite extends Database implements DriverInterface
31{
32    /** @var string[] */
33    private array $createTableStatements = [
34        'faqadminlog' => 'CREATE TABLE %sfaqadminlog (
35            id INTEGER NOT NULL,
36            time INTEGER NOT NULL,
37            usr INTEGER NOT NULL,
38            text VARCHAR(8000) NOT NULL,
39            hash VARCHAR(64) NULL,
40            previous_hash VARCHAR(64) NULL,
41            ip VARCHAR(64) NOT NULL,
42            PRIMARY KEY (id))',
43        'faqattachment' => 'CREATE TABLE %sfaqattachment (
44            id INTEGER NOT NULL,
45            record_id INTEGER NOT NULL,
46            record_lang VARCHAR(5) NOT NULL,
47            real_hash CHAR(32) NOT NULL,
48            virtual_hash CHAR(32) NOT NULL,
49            password_hash CHAR(40) NULL,
50            filename VARCHAR(255) NOT NULL,
51            filesize INTEGER NOT NULL,
52            encrypted INTEGER NOT NULL DEFAULT 0,
53            mime_type VARCHAR(255) NULL,
54            PRIMARY KEY (id))',
55        'faqattachment_file' => 'CREATE TABLE %sfaqattachment_file (
56            virtual_hash CHAR(32) NOT NULL,
57            contents TEXT NOT NULL,
58            PRIMARY KEY (virtual_hash))',
59        'faqbackup' => 'CREATE TABLE %sfaqbackup (
60            id INT(11) NOT NULL,
61            filename VARCHAR(255) NOT NULL,
62            authkey VARCHAR(255) NOT NULL,
63            authcode VARCHAR(255) NOT NULL,
64            created timestamp NOT NULL,
65            PRIMARY KEY (id))',
66        'faqbookmarks' => 'CREATE TABLE %sfaqbookmarks (
67            userid INT(11) DEFAULT NULL,
68            faqid INT(11) DEFAULT NULL)',
69        'faqcaptcha' => 'CREATE TABLE %sfaqcaptcha (
70            id VARCHAR(6) NOT NULL,
71            useragent VARCHAR(255) NOT NULL,
72            language VARCHAR(5) NOT NULL,
73            ip VARCHAR(64) NOT NULL,
74            captcha_time INTEGER NOT NULL,
75            PRIMARY KEY (id))',
76        'faqcategories' => 'CREATE TABLE %sfaqcategories (
77            id INTEGER NOT NULL,
78            lang VARCHAR(5) NOT NULL,
79            parent_id INTEGER NOT NULL,
80            name VARCHAR(255) NOT NULL,
81            description VARCHAR(255) DEFAULT NULL,
82            user_id INTEGER NOT NULL,
83            group_id INTEGER NOT NULL DEFAULT -1,
84            active INTEGER NULL DEFAULT 1,
85            image VARCHAR(255) DEFAULT NULL,
86            show_home INTEGER DEFAULT NULL,
87            PRIMARY KEY (id, lang))',
88        'faqcategoryrelations' => 'CREATE TABLE %sfaqcategoryrelations (
89            category_id INTEGER NOT NULL,
90            category_lang VARCHAR(5) NOT NULL,
91            record_id INTEGER NOT NULL,
92            record_lang VARCHAR(5) NOT NULL,
93            PRIMARY KEY (category_id, category_lang, record_id, record_lang))',
94        'faqcategoryrelations_idx' => 'CREATE INDEX idx_records ON %sfaqcategoryrelations (record_id, record_lang)',
95        'faqcategory_group' => 'CREATE TABLE %sfaqcategory_group (
96            category_id INTEGER NOT NULL,
97            group_id INTEGER NOT NULL,
98            PRIMARY KEY (category_id, group_id))',
99        'faqcategory_news' => 'CREATE TABLE %sfaqcategory_news (
100            category_id INTEGER NOT NULL,
101            news_id INTEGER NOT NULL,
102            PRIMARY KEY (category_id, news_id))',
103        'faqcategory_order' => 'CREATE TABLE %sfaqcategory_order (
104            category_id INTEGER NOT NULL,
105            parent_id INTEGER DEFAULT NULL,
106            position INTEGER NOT NULL,
107            PRIMARY KEY (category_id))',
108        'faqcategory_user' => 'CREATE TABLE %sfaqcategory_user (
109            category_id INTEGER NOT NULL,
110            user_id INTEGER NOT NULL,
111            PRIMARY KEY (category_id, user_id))',
112        'faqchanges' => 'CREATE TABLE %sfaqchanges (
113            id INTEGER NOT NULL,
114            beitrag SMALLINT NOT NULL,
115            lang VARCHAR(5) NOT NULL,
116            revision_id INTEGER NOT NULL DEFAULT 0,
117            usr INTEGER NOT NULL ,
118            datum INTEGER NOT NULL,
119            what text DEFAULT NULL,
120            PRIMARY KEY (id, lang))',
121        'faqcomments' => 'CREATE TABLE %sfaqcomments (
122            id_comment INTEGER NOT NULL,
123            id INTEGER NOT NULL,
124            type VARCHAR(10) NOT NULL,
125            usr VARCHAR(255) NOT NULL,
126            email VARCHAR(255) NOT NULL,
127            comment text NOT NULL,
128            datum VARCHAR(64) NOT NULL,
129            helped text DEFAULT NULL,
130            PRIMARY KEY (id_comment))',
131        'faqconfig' => 'CREATE TABLE %sfaqconfig (
132            config_name VARCHAR(255) NOT NULL default \'\',
133            config_value TEXT DEFAULT NULL,
134            PRIMARY KEY (config_name))',
135        'faqdata' => 'CREATE TABLE %sfaqdata (
136            id INTEGER NOT NULL,
137            lang VARCHAR(5) NOT NULL,
138            solution_id INTEGER NOT NULL,
139            revision_id INTEGER NOT NULL DEFAULT 0,
140            active char(3) NOT NULL,
141            sticky INTEGER NOT NULL,
142            keywords text DEFAULT NULL,
143            thema text NOT NULL,
144            content text DEFAULT NULL,
145            author VARCHAR(255) NOT NULL,
146            email VARCHAR(255) NOT NULL,
147            comment char(1) default \'y\',
148            updated VARCHAR(15) NOT NULL,
149            date_start VARCHAR(14) NOT NULL DEFAULT \'00000000000000\',
150            date_end VARCHAR(14) NOT NULL DEFAULT \'99991231235959\',
151            created DATETIME DEFAULT CURRENT_TIMESTAMP,
152            notes text DEFAULT NULL,
153            sticky_order INTEGER DEFAULT NULL,
154            PRIMARY KEY (id, lang))',
155        'faqdata_revisions' => 'CREATE TABLE %sfaqdata_revisions (
156            id INTEGER NOT NULL,
157            lang VARCHAR(5) NOT NULL,
158            solution_id INTEGER NOT NULL,
159            revision_id INTEGER NOT NULL DEFAULT 0,
160            active char(3) NOT NULL,
161            sticky INTEGER NOT NULL,
162            keywords text DEFAULT NULL,
163            thema text NOT NULL,
164            content text DEFAULT NULL,
165            author VARCHAR(255) NOT NULL,
166            email VARCHAR(255) NOT NULL,
167            comment char(1) default \'y\',
168            updated VARCHAR(15) NOT NULL,
169            date_start VARCHAR(14) NOT NULL DEFAULT \'00000000000000\',
170            date_end VARCHAR(14) NOT NULL DEFAULT \'99991231235959\',
171            created DATETIME DEFAULT CURRENT_TIMESTAMP,
172            notes text DEFAULT NULL,
173            sticky_order INTEGER DEFAULT NULL,
174            PRIMARY KEY (id, lang, solution_id, revision_id))',
175        'faqdata_group' => 'CREATE TABLE %sfaqdata_group (
176            record_id INTEGER NOT NULL,
177            group_id INTEGER NOT NULL,
178            PRIMARY KEY (record_id, group_id))',
179        'faqdata_tags' => 'CREATE TABLE %sfaqdata_tags (
180            record_id INTEGER NOT NULL,
181            tagging_id INTEGER NOT NULL,
182            PRIMARY KEY (record_id, tagging_id))',
183        'faqdata_user' => 'CREATE TABLE %sfaqdata_user (
184            record_id INTEGER NOT NULL,
185            user_id INTEGER NOT NULL,
186            PRIMARY KEY (record_id, user_id))',
187        'faqforms' => 'CREATE TABLE %sfaqforms (
188            form_id INTEGER NOT NULL,
189            input_id INTEGER NOT NULL,
190            input_type VARCHAR(1000) NOT NULL,
191            input_label VARCHAR(500) NOT NULL,
192            input_active INTEGER NOT NULL,
193            input_required INTEGER NOT NULL,
194            input_lang VARCHAR(11) NOT NULL)',
195        'faqglossary' => 'CREATE TABLE %sfaqglossary (
196            id INTEGER NOT NULL ,
197            lang VARCHAR(5) NOT NULL ,
198            item VARCHAR(255) NOT NULL ,
199            definition text NOT NULL,
200            PRIMARY KEY (id, lang))',
201        'faqgroup' => 'CREATE TABLE %sfaqgroup (
202            group_id INTEGER NOT NULL,
203            name VARCHAR(25) NULL,
204            description text NULL,
205            auto_join INTEGER NULL,
206            PRIMARY KEY(group_id))',
207        'faqgroup_idx' => 'CREATE UNIQUE INDEX idx_name ON %sfaqgroup (name)',
208        'faqgroup_right' => 'CREATE TABLE %sfaqgroup_right (
209            group_id INTEGER NOT NULL,
210            right_id INTEGER NOT NULL,
211            PRIMARY KEY(group_id, right_id))',
212        'faqinstances' => 'CREATE TABLE %sfaqinstances (
213            id INT NOT NULL,
214            url VARCHAR(255) NOT NULL,
215            instance VARCHAR(255) NOT NULL,
216            comment TEXT NULL,
217            created DATETIME NOT NULL,
218            modified DATETIME NOT NULL,
219            PRIMARY KEY (id))',
220        'faqinstances_config' => 'CREATE TABLE %sfaqinstances_config (
221            instance_id INT NOT NULL,
222            config_name VARCHAR(255) NOT NULL default \'\',
223            config_value VARCHAR(255) DEFAULT NULL,
224            PRIMARY KEY (instance_id, config_name))',
225        'faqnews' => 'CREATE TABLE %sfaqnews (
226            id INTEGER NOT NULL,
227            lang VARCHAR(5) NOT NULL,
228            header VARCHAR(255) NOT NULL,
229            artikel text NOT NULL,
230            datum VARCHAR(14) NOT NULL,
231            author_name  VARCHAR(255) NULL,
232            author_email VARCHAR(255) NULL,
233            active char(1) default \'y\',
234            comment char(1) default \'n\',
235            link VARCHAR(255) DEFAULT NULL,
236            linktitel VARCHAR(255) DEFAULT NULL,
237            target VARCHAR(255) NOT NULL,
238            PRIMARY KEY (id))',
239        'faqcustompages' => 'CREATE TABLE %sfaqcustompages (
240            id INTEGER NOT NULL,
241            lang VARCHAR(5) NOT NULL,
242            page_title VARCHAR(255) NOT NULL,
243            slug VARCHAR(255) NOT NULL,
244            content TEXT NOT NULL,
245            author_name VARCHAR(255) NOT NULL,
246            author_email VARCHAR(255) NOT NULL,
247            active CHAR(1) NOT NULL DEFAULT \'n\',
248            created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
249            updated TIMESTAMP NULL,
250            seo_title VARCHAR(60) NULL,
251            seo_description VARCHAR(160) NULL,
252            seo_robots VARCHAR(50) NOT NULL DEFAULT \'index,follow\',
253            PRIMARY KEY (id, lang))',
254        'faqquestions' => 'CREATE TABLE %sfaqquestions (
255            id INTEGER NOT NULL,
256            lang VARCHAR(5) NOT NULL,
257            username VARCHAR(100) NOT NULL,
258            email VARCHAR(100) NOT NULL,
259            category_id INTEGER NOT NULL,
260            question text NOT NULL,
261            created VARCHAR(20) NOT NULL,
262            is_visible char(1) default \'Y\',
263            answer_id INTEGER NOT NULL DEFAULT 0,
264            PRIMARY KEY (id))',
265        'faqright' => 'CREATE TABLE %sfaqright (
266            right_id INTEGER NOT NULL,
267            name VARCHAR(50) NULL,
268            description text NULL,
269            for_users INTEGER NULL DEFAULT 1,
270            for_groups INTEGER NULL DEFAULT 1,
271            for_sections INTEGER NULL DEFAULT 1,
272            PRIMARY KEY (right_id))',
273        'faqsearches' => 'CREATE TABLE %sfaqsearches (
274            id INTEGER NOT NULL ,
275            lang VARCHAR(5) NOT NULL ,
276            searchterm VARCHAR(255) NOT NULL ,
277            searchdate DATETIME,
278            PRIMARY KEY (id, lang))',
279        'faqseo' => 'CREATE TABLE %sfaqseo (
280            id INT NOT NULL,
281            type VARCHAR(32) NOT NULL,
282            reference_id INT NOT NULL,
283            reference_language VARCHAR(5) NOT NULL,
284            title TEXT NULL,
285            description TEXT NULL,
286            slug TEXT NULL,
287            created DATE NOT NULL DEFAULT (date(\'now\')),
288            PRIMARY KEY (id))',
289        'faqsessions' => 'CREATE TABLE %sfaqsessions (
290            sid INTEGER NOT NULL,
291            user_id INTEGER NOT NULL,
292            ip VARCHAR(64) NOT NULL,
293            time INTEGER NOT NULL,
294            PRIMARY KEY (sid))',
295        'faqsessions_idx' => 'CREATE INDEX idx_time ON %sfaqsessions (time)',
296        'faqsearches_searchterm_idx' => 'CREATE INDEX idx_faqsearches_searchterm ON %sfaqsearches (searchterm)',
297        'faqsearches_date_term_idx' => 'CREATE INDEX idx_faqsearches_date_term ON %sfaqsearches (searchdate, searchterm)',
298        'faqsearches_date_term_lang_idx' => 'CREATE INDEX idx_faqsearches_date_term_lang ON %sfaqsearches (searchdate, searchterm, lang)',
299        'faqstopwords' => 'CREATE TABLE %sfaqstopwords (
300            id INTEGER NOT NULL,
301            lang VARCHAR(5) NOT NULL,
302            stopword VARCHAR(64) NOT NULL,
303            PRIMARY KEY (id, lang))',
304        'faqtags' => 'CREATE TABLE %sfaqtags (
305            tagging_id INTEGER NOT NULL,
306            tagging_name VARCHAR(255) NOT NULL ,
307            PRIMARY KEY (tagging_id, tagging_name))',
308        'faquser' => 'CREATE TABLE %sfaquser (
309            user_id INTEGER NOT NULL,
310            login VARCHAR(128) NOT NULL,
311            session_id VARCHAR(150) NULL,
312            session_timestamp INTEGER NULL,
313            ip VARCHAR(64) NULL,
314            account_status VARCHAR(50) NULL,
315            last_login VARCHAR(14) NULL,
316            auth_source VARCHAR(100) NULL,
317            member_since VARCHAR(14) NULL,
318            remember_me VARCHAR(150) NULL,
319            success INT(1) NULL DEFAULT 1,
320            is_superadmin INT(1) NULL DEFAULT 0,
321            login_attempts INT(1) NULL DEFAULT 0,
322            refresh_token TEXT NULL DEFAULT NULL,
323            access_token TEXT NULL DEFAULT NULL,
324            code_verifier VARCHAR(255) NULL DEFAULT NULL,
325            jwt TEXT NULL DEFAULT NULL,
326            webauthnkeys TEXT NULL DEFAULT NULL,
327            PRIMARY KEY (user_id))',
328        'faquserdata' => 'CREATE TABLE %sfaquserdata (
329            user_id INTEGER NOT NULL,
330            last_modified VARCHAR(14) NULL,
331            display_name VARCHAR(128) NULL,
332            email VARCHAR(128) NULL,
333            keycloak_sub VARCHAR(255) NULL,
334            is_visible INT(1) NULL DEFAULT 0,
335            twofactor_enabled INT(1) NULL DEFAULT 0,
336            secret VARCHAR(128) NULL DEFAULT NULL)',
337        'faquserlogin' => 'CREATE TABLE %sfaquserlogin (
338            login VARCHAR(128) NOT NULL,
339            pass VARCHAR(80) NULL,
340            domain VARCHAR(255) NULL,
341            PRIMARY KEY (login))',
342        'faquser_group' => 'CREATE TABLE %sfaquser_group (
343            user_id INTEGER NOT NULL,
344            group_id INTEGER NOT NULL,
345            PRIMARY KEY (user_id, group_id))',
346        'faquser_right' => 'CREATE TABLE %sfaquser_right (
347            user_id INTEGER NOT NULL,
348            right_id INTEGER NOT NULL,
349            PRIMARY KEY (user_id, right_id))',
350        'faqvisits' => 'CREATE TABLE %sfaqvisits (
351            id INTEGER NOT NULL,
352            lang VARCHAR(5) NOT NULL,
353            visits INTEGER NOT NULL,
354            last_visit INTEGER NOT NULL,
355            PRIMARY KEY (id, lang))',
356        'faqvoting' => 'CREATE TABLE %sfaqvoting (
357            id INTEGER NOT NULL,
358            artikel SMALLINT NOT NULL,
359            vote SMALLINT NOT NULL,
360            usr SMALLINT NOT NULL,
361            datum VARCHAR(20) DEFAULT \'\',
362            ip VARCHAR(15) DEFAULT \'\',
363            PRIMARY KEY (id))',
364        'faqchat_messages' => 'CREATE TABLE %sfaqchat_messages (
365            id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
366            sender_id INTEGER NOT NULL,
367            recipient_id INTEGER NOT NULL,
368            message TEXT NOT NULL,
369            is_read INTEGER NOT NULL DEFAULT 0,
370            created_at DATETIME DEFAULT CURRENT_TIMESTAMP)',
371        'faqchat_messages_idx_sender' => 'CREATE INDEX idx_chat_sender ON %sfaqchat_messages (sender_id)',
372        'faqchat_messages_idx_recipient' => 'CREATE INDEX idx_chat_recipient ON %sfaqchat_messages (recipient_id)',
373        'faqchat_messages_idx_conversation' => 'CREATE INDEX idx_chat_conversation ON %sfaqchat_messages (sender_id, recipient_id)',
374        'faqchat_messages_idx_created' => 'CREATE INDEX idx_chat_created ON %sfaqchat_messages (created_at)',
375    ];
376
377    /**
378     * Constructor.
379     */
380    public function __construct(Configuration $configuration)
381    {
382        $this->configuration = $configuration;
383    }
384
385    /**
386     * Executes all CREATE TABLE and CREATE INDEX statements.
387     *
388     *
389     */
390    public function createTables(string $prefix = '', ?string $schema = null): bool
391    {
392        // SQLite does not support schema-based isolation; use separate database files instead.
393
394        foreach ($this->createTableStatements as $createTableStatement) {
395            $result = $this->configuration->getDb()->query(sprintf($createTableStatement, $prefix));
396
397            if (!$result) {
398                return false;
399            }
400        }
401
402        return true;
403    }
404}