DatabaseUpdate.php 20.5 KB
Newer Older
1
2
3
4
5
6
7
8
<?php
/**
 * Created by PhpStorm.
 * User: crose
 * Date: 5/9/17
 * Time: 8:56 AM
 */

Marc Egger's avatar
Marc Egger committed
9
10
namespace IMATHUZH\Qfq\Core\Database;

11
use IMATHUZH\Qfq\Core\Form\FormAsFile;
Marc Egger's avatar
Marc Egger committed
12
use IMATHUZH\Qfq\Core\Helper\Logger;
13
use IMATHUZH\Qfq\Core\Helper\Path;
14
use IMATHUZH\Qfq\Core\Store\Store;
15
use IMATHUZH\Qfq\Core\Typo3\T3Handler;
16
17
18
19
20
21


/*
 * Read the extension version number.
 * Read the QFQ database version number: stored in the comment field of table 'Form'. Format:  Version=x.y.z
 * If versions different:
Carsten  Rose's avatar
Carsten Rose committed
22
23
 *   * Read the update array 'DatabaseUpdateData.php'.
 *   * Play all changes from the update array after 'old' upto 'new'.
24
25
 *   * Save new QFQ database version in the comment field of table 'Form'
 *
Carsten  Rose's avatar
Carsten Rose committed
26
27
 * In a new QFQ installation, the comment field of table 'Form' is empty. On the first call of QFQ, the version string
 * will be set. Also the 'formEditor.sql' will be played initially.
28
29
30
 *
 */

31
32
33
34
/**
 * Class DatabaseUpdate
 * @package qfq
 */
35
36
37
class DatabaseUpdate {

    /**
Carsten  Rose's avatar
Carsten Rose committed
38
     * @var Database
39
40
     */
    protected $db = null;
Carsten  Rose's avatar
Carsten Rose committed
41
42
43
44

    /**
     * @var Store
     */
45
    protected $store = null;
46
47
48

    /**
     * @param Database $db
49
     * @param Store $store
50
     */
51
    public function __construct(Database $db, Store $store) {
52
        $this->db = $db;
53
        $this->store = $store;
54
55
56
57
    }

    /**
     * @return mixed
Marc Egger's avatar
Marc Egger committed
58
     * @throws \CodeException
59
60
61
62
     */
    private function getExtensionVersion() {
        $path = __DIR__ . '/../../../ext_emconf.php';
        $_EXTKEY = EXT_KEY;
Carsten  Rose's avatar
Carsten Rose committed
63
        $EM_CONF = null;
64
65
66
67
68
69
70
71
        if (@file_exists($path)) {
            include $path;

            if (isset($EM_CONF[$_EXTKEY]['version'])) {
                return $EM_CONF[$_EXTKEY]['version'];
            }
        }

Marc Egger's avatar
Marc Egger committed
72
        throw new \CodeException('Failed to read extension version', ERROR_QFQ_VERSION);
73
74
75
    }

    /**
76
77
78
79
     * Try to read the QFQ version number from 'comment' in table 'Form'.
     * In a very special situation , there might be table 'form' and 'Form' in the same Database. This should be handled
     *  in the way that the latest version number is the active one.
     *
Carsten  Rose's avatar
Carsten Rose committed
80
     * @return bool|string  false if there is no table 'Form' or if there is no comment set in table 'Form'.
Marc Egger's avatar
Marc Egger committed
81
82
83
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
84
85
86
     */
    private function getDatabaseVersion() {

87
88
89
90
91
92
        $arr = $this->db->sql("SHOW TABLE STATUS WHERE Name='Form'", ROW_REGULAR);

        $found = '';
        //
        foreach ($arr as $row) {
            if (isset($row['Comment'])) {
Marc Egger's avatar
Marc Egger committed
93
                parse_str($row['Comment'], $arr);
94
                if (($arr[QFQ_VERSION_KEY] ?? '') !== '' AND (version_compare($arr[QFQ_VERSION_KEY], $found) == 1)) {
Marc Egger's avatar
Marc Egger committed
95
                    $found = $arr;
96
97
98
                }
            } else {
                continue;
Carsten  Rose's avatar
Carsten Rose committed
99
            }
100
101
        }

Marc Egger's avatar
Marc Egger committed
102
        return ($found === '') ? false : $found;
103
104
105
106
    }

    /**
     * @param $version
Carsten  Rose's avatar
Carsten Rose committed
107
     *
Marc Egger's avatar
Marc Egger committed
108
109
110
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
111
112
113
     */
    private function setDatabaseVersion($version) {

Marc Egger's avatar
Marc Egger committed
114
115
116
117
        if (is_array($version)) {
            $versionInfo = $version;
        } else {
            $versionInfo = $this->getDatabaseVersion();
118
            $versionInfo[QFQ_VERSION_KEY] = $version;
Marc Egger's avatar
Marc Egger committed
119
120
121
        }

        $this->db->sql("ALTER TABLE `Form` COMMENT = '" . http_build_query($versionInfo) . "'");
122
123
124
125

    }

    /**
126
127
128
     *
     * @param string $dbUpdate SYSTEM_DB_UPDATE_ON | SYSTEM_DB_UPDATE_OFF | SYSTEM_DB_UPDATE_AUTO
     *
Marc Egger's avatar
Marc Egger committed
129
130
131
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
132
     * @throws \UserReportException
133
     */
134
135
    public function checkNupdate($dbUpdate) {

136
        $new = $this->getExtensionVersion();
Marc Egger's avatar
Marc Egger committed
137
        $versionInfo = $this->getDatabaseVersion();
Carsten  Rose's avatar
Carsten Rose committed
138
        $old = $versionInfo[QFQ_VERSION_KEY] ?? false;
139

140
141
142
143
144
145
146
        $this->checkT3QfqConfig($old, $new);


        if ($dbUpdate === SYSTEM_DB_UPDATE_NEVER) {
            return;
        }

147
        if ($dbUpdate === SYSTEM_DB_UPDATE_ALWAYS || ($dbUpdate === SYSTEM_DB_UPDATE_AUTO && $new != $old)) {
Marc Egger's avatar
Marc Egger committed
148

149
            if (version_compare($old, '21.2.0') < 1 && !defined('PHPUNIT_QFQ')) {
150
151
152
                $this->enforceExistenceOfFormEditorReport();
            }

Carsten  Rose's avatar
Carsten Rose committed
153
154
155
            $newFunctionHash = $this->updateSqlFunctions($versionInfo[QFQ_VERSION_KEY_FUNCTION_HASH] ?? '');
            if (null !== $newFunctionHash) {
                $versionInfo[QFQ_VERSION_KEY_FUNCTION_HASH] = $newFunctionHash;
156
                $versionInfo[QFQ_VERSION_KEY_FUNCTION_VERSION] = $new;
Marc Egger's avatar
Marc Egger committed
157
158
            }

159
            if (FEATURE_FORM_FILE_SYNC) {
160
161
162
163
164
165
                if ($this->db->existTable('Form')) {
                    // If Form table exists, import all form files so everything is up to date.
                    FormAsFile::importAllForms($this->db, true); // Note: Creates path and exports all forms first if the form directory does not exist.

                    // create Form table and export all system forms
                    $this->db->playSqlFile(__DIR__ . '/../../Sql/formEditor.sql');
166
                    FormAsFile::exportAllForms($this->db);
167
168
169
                } else {
                    // If not, then create Form table and export all system forms
                    $this->db->playSqlFile(__DIR__ . '/../../Sql/formEditor.sql');
170
                    FormAsFile::exportAllForms($this->db);
171
172
173
174

                    // import form files which existed before the new installation
                    FormAsFile::importAllForms($this->db, true);
                }
175
            } else {
176
                $this->db->playSqlFile(__DIR__ . '/../../Sql/formEditor.sql');
177
            }
178

179
180
181
182
            // Perform dbUpdate only after formEditor.sql has been played:
            // in case a new system table has been added between old and new and there is an dbUpdate on the new system table now.
            $this->dbUpdateStatements($old, $new);

183
184
            FormAsFile::importSystemForms($this->db);

185
            Logger::logMessage(date('Y.m.d H:i:s ') . ": Updated from QFQ version '$old' to '$new'", Path::absoluteQfqLogFile());
186
187
188
189

            // Finally write the latest version number.
            $versionInfo[QFQ_VERSION_KEY] = $new;
            $this->setDatabaseVersion($versionInfo);
190
        }
191
192
193

        if ($old === false) {
            // A complete new installation get's some extra tables
Marc Egger's avatar
Marc Egger committed
194
            $this->db->playSqlFile(__DIR__ . '/../../Sql/customTable.sql');
195
        }
196
197
198

        if (version_compare($old, '19.9.0') === -1) {
            $this->updateSpecialColumns();
199
            if (FEATURE_FORM_FILE_SYNC) {
200
                FormAsFile::exportAllForms($this->db);
201
            }
202
203
        }

204
205
    }

206
207
208
209
210
211
212
213
214
    /**
     * Check Typo3 config if values needs to be updated.
     * This is typically necessary if default config values change, to guarantee existing installations behave in legacy mode.
     *
     * @param $old
     * @param $new
     */
    private function checkT3QfqConfig($old, $new) {

215
        if ($new == $old || $old===false ) {
216
217
218
219
220
221
222
223
224
225
226
227
            // Set baseUrl default
            if($GLOBALS["_SERVER"]["SCRIPT_URI"] !== ''){
                if($GLOBALS["TYPO3_CONF_VARS"]["EXTENSIONS"]["qfq"]["baseUrl"] === ''){
                    $fullUrl = parse_url($GLOBALS["_SERVER"]["SCRIPT_URI"]);
                    $fullUrl['sections'] = explode('/', $fullUrl['path']);
                    $baseUrl = $fullUrl['scheme'].'://'.$fullUrl['host'];
                    for($i = 1; $i < sizeof($fullUrl['sections']) -1; $i++){
                        $baseUrl .= '/'.$fullUrl['sections'][$i];
                    }
                    T3Handler::updateT3QfqConfig(SYSTEM_BASE_URL, $baseUrl); //Legacy behaviour.
                }
            }
228
            return;
229

230
231
232
233
234
235
        }

        if (version_compare($old, '20.2.0') == -1) {
            T3Handler::updateT3QfqConfig(SYSTEM_RENDER, SYSTEM_RENDER_BOTH); //Legacy behaviour.
        }
    }
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260

    /**
     * Throws exception if no tt-content record exists which contains "file=_formEditor"
     *
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    private function enforceExistenceOfFormEditorReport() {
        $dbT3 = $this->store->getVar(SYSTEM_DB_NAME_T3, STORE_SYSTEM);
        $sql = "SELECT `uid` FROM " . $dbT3 . ".`tt_content` WHERE `CType`='qfq_qfq' AND `deleted`=0 AND `bodytext` LIKE '%file=_formEditor%'";
        $res = $this->db->sql($sql);
        if (empty($res)) {
            $message = '<h2>FormEditor Report not found</h2>'
                . 'Please create a Typo3 QFQ content element with the following content:'
                . '<pre>file=_formEditor</pre>'
                . 'More information: See Section "FormEditor" of the QFQ Documentation.';
            $errorMsg[ERROR_MESSAGE_TO_USER] = 'Error while updating qfq.';
            $errorMsg[ERROR_MESSAGE_TO_DEVELOPER] = $message;
            $errorMsg[ERROR_MESSAGE_TO_DEVELOPER_SANITIZE] = false;
            throw new \DbException(json_encode($errorMsg), E_ERROR);
        }
    }

261
262
263
264
265
266
267
268
269
    /**
     * Check if there are special columns without prepended underscore in the QFQ application. If yes, then throw an error.
     * A link is provided to automatically prepend all found special columns. And another link to skip the auto-replacement.
     *
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     */
    private function updateSpecialColumns() {
270

271
272
273
274
275
276
        // Prepare regex patterns to find "AS <special column name>"
        $special_columns = ['link', 'exec', 'Page', 'Pagec', 'Paged', 'Pagee', 'Pageh', 'Pagei', 'Pagen', 'Pages'
            , 'page', 'pagec', 'paged', 'pagee', 'pageh', 'pagei', 'pagen', 'pages', 'yank', 'Pdf', 'File', 'Zip'
            , 'pdf', 'file', 'zip', 'excel', 'savePdf', 'thumbnail', 'monitor', 'mimeType', 'fileSize', 'nl2br'
            , 'htmlentities', 'striptags', 'XLS', 'XLSs', 'XLSb', 'XLSn', 'bullet', 'check', 'img', 'mailto'
            , 'sendmail', 'vertical'];
277

278
279
280
        $make_pattern = function ($column) {
            return '/([aA][sS]\s+)(' . $column . ')/s';
        };
281

282
283
284
285
286
287
288
289
290
291
292
293
294
        $patterns = array_map($make_pattern, $special_columns);

        // Prepare search and replace
        $placeholder = '%%%UNDERLINE%%%';  // used temporarily to mark where '_' should go
        $actionSpecialColumn = $_GET[ACTION_SPECIAL_COLUMN_UPDATE] ?? ''; // get parameter to decide whether to execute the replacement
        $dbT3 = $this->store->getVar(SYSTEM_DB_NAME_T3, STORE_SYSTEM);
        $message = ''; // error message in case an old special column is found

        // TT_CONTENT tt_content.bodytext
        $message_fe = '';
        if (defined('PHPUNIT_QFQ')) {
            $res = array();
        } else {
295
            $res = $this->db->sql("SELECT `uid`, `header`, `bodytext` FROM `" . $dbT3 . "`.`tt_content` WHERE `CType`='qfq_qfq' AND `deleted`=0;");
296
297
298
299
300
301
        }
        foreach ($res as $i => $tt_content) {
            $replaced_placeholder = preg_replace($patterns, '${1}' . $placeholder . '${2}', $tt_content['bodytext']);
            if (strpos($replaced_placeholder, $placeholder) !== false) {
                if ($actionSpecialColumn === ACTION_SPECIAL_COLUMN_DO_REPLACE) {
                    $replace = str_replace($placeholder, '_', $replaced_placeholder);
302
                    $query = "UPDATE `" . $dbT3 . "`.`tt_content` SET `bodytext`='" . addslashes($replace) . "' WHERE `uid`='" . $tt_content['uid'] . "'";
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
                    $this->db->sql($query);
                }
                $message_fe .= '<hr><b>' . $tt_content['header'] . ' [uid:' . $tt_content['uid'] . ']</b><br><br>';
                $message_fe .= str_replace($placeholder,
                    '<span style="font-weight: bold; color: red;">>>>_</span>',
                    htmlentities($replaced_placeholder));
            }
        }
        if ($message_fe != '') {
            $message .= '<hr><h3>Typo3 Table: tt_content (column: bodytext)</h3>' . $message_fe;
        }

        // FORM ELEMENTS FormElement.value, FormElement.note
        $message_ttc = '';
        if (defined('PHPUNIT_QFQ')) {
            $res = array();
        } else {
320
            $res = $this->db->sql("SELECT `fe`.`id`, `fe`.`name`, `fe`.`value`, `fe`.`note` FROM `FormElement` AS fe WHERE `fe`.`type`='note' AND `fe`.`value` LIKE '#!report%' OR `fe`.`note` LIKE '%#!report%';");
321
322
323
324
325
326
327
328
        }
        foreach ($res as $i => $tt_content) {

            foreach (['value', 'note'] as $j => $columnName) {
                $replaced_placeholder = preg_replace($patterns, '${1}' . $placeholder . '${2}', $tt_content[$columnName]);
                if (strpos($replaced_placeholder, $placeholder) !== false) {
                    if ($actionSpecialColumn === ACTION_SPECIAL_COLUMN_DO_REPLACE) {
                        $replace = str_replace($placeholder, '_', $replaced_placeholder);
329
                        $query = "UPDATE `FormElement` SET `" . $columnName . "`='" . addslashes($replace) . "' WHERE `id`='" . $tt_content['id'] . "'";
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
                        $this->db->sql($query);
                    }
                    $message_ttc .= '<hr><b>' . $tt_content['name'] . ' [id:' . $tt_content['id'] . '] (FormElement.' . $columnName . ')</b><br><br>';
                    $message_ttc .= str_replace($placeholder,
                        '<span style="font-weight: bold; color: red;">>>>_</span>',
                        htmlentities($replaced_placeholder));
                }
            }
        }
        if ($message_ttc != '') {
            $message .= '<hr><h3>QFQ Table: FormElement (columns: value and note)</h3>' . $message_ttc;
        }

        // show error message or save log
        if ($message != '') {
            if ($actionSpecialColumn === ACTION_SPECIAL_COLUMN_DO_REPLACE) {
                // save log file
                $message = '<h1>Special column names replaced</h1>The following special column names were replaced.<hr>' . $message;
348
                Logger::logMessage($message, Path::absoluteLog() . '/' . date("YmdHi") . '_special_columns_auto_update.html');
349
350
351
352
353
354
355
356
357
358
359
            } elseif ($actionSpecialColumn === ACTION_SPECIAL_COLUMN_DO_SKIP_REPLACE) {
                // do nothing
            } else {
                // show error
                $message = $actionSpecialColumn
                    . '<h2>Special Column names without prepended underscore found.</h2>'
                    . ' Those are not supported any longer.'
                    . '<h2>SOLUTION</h2>'
                    . 'Click <a href="?' . http_build_query(array_merge($_GET, array(ACTION_SPECIAL_COLUMN_UPDATE => ACTION_SPECIAL_COLUMN_DO_REPLACE))) . '">Auto-Replace</a>'
                    . ' to automatically prepend the found column names with an underscore.'
                    . ' In the report below the missing underscores are marked by "<span style="font-weight: bold; color: red;">>>>_</span>".'
360
                    . ' This report will be saved in ' . Path::absoluteLog() . ' after the automatic replacement.'
361
362
363
364
365
366
367
368
369
370
371
372
                    . ' <br><br>To update qfq without changing the special columns (your app will probably be broken): '
                    . '<a href="?' . http_build_query(array_merge($_GET, array(ACTION_SPECIAL_COLUMN_UPDATE => ACTION_SPECIAL_COLUMN_DO_SKIP_REPLACE))) . '">Skip Auto-Replace</a>'
                    . '<h2>Report</h2>'
                    . $message;
                $errorMsg[ERROR_MESSAGE_TO_USER] = 'Error while updating qfq. ';
                $errorMsg[ERROR_MESSAGE_TO_DEVELOPER] = $message;
                $errorMsg[ERROR_MESSAGE_TO_DEVELOPER_SANITIZE] = false;
                throw new \DbException(json_encode($errorMsg), ERROR_PLAY_SQL_FILE);
            }
        }
    }

Marc Egger's avatar
Marc Egger committed
373
374
375
376
377
    /**
     * @param $oldFunctionsHash
     *
     * @return string
     *
Marc Egger's avatar
Marc Egger committed
378
379
     * @throws \DbException
     * @throws \UserFormException
Marc Egger's avatar
Marc Egger committed
380
381
     */
    private function updateSqlFunctions($oldFunctionsHash) {
Carsten  Rose's avatar
Carsten Rose committed
382

383
        if (ACTION_FUNCTION_UPDATE_NEVER === $oldFunctionsHash) {
Marc Egger's avatar
Marc Egger committed
384
385
386
            return null;
        }

Carsten  Rose's avatar
Carsten Rose committed
387
388
389
        $actionFunction = $_GET[ACTION_FUNCTION_UPDATE] ?? '';

        if ($actionFunction === ACTION_FUNCTION_UPDATE_NEXT_UPDATE) {
390
            return ACTION_FUNCTION_UPDATE_NOT_PERFORMED;
Carsten  Rose's avatar
Carsten Rose committed
391
        } elseif ($actionFunction === ACTION_FUNCTION_UPDATE_NEVER) {
392
393
394
            return ACTION_FUNCTION_UPDATE_NEVER;
        }

Marc Egger's avatar
Marc Egger committed
395
        $functionSql = file_get_contents(__DIR__ . '/../../Sql/function.sql');
396
        $functionHash = hash('md5', $functionSql);
Marc Egger's avatar
Marc Egger committed
397

398
        if ($functionHash === $oldFunctionsHash) {
Marc Egger's avatar
Marc Egger committed
399
400
401
            return null;
        }

402
        $query = str_replace('%%FUNCTIONSHASH%%', $functionHash, $functionSql);
Marc Egger's avatar
Marc Egger committed
403
404
        if (stripos($query, 'delimiter')) {
            $errorMsg[ERROR_MESSAGE_TO_USER] = 'Error while updating qfq.';
405
406
            $errorMsg[ERROR_MESSAGE_TO_DEVELOPER] = "Error in file " . QFQ_FUNCTION_SQL . ": The keyword DELIMITER is present " .
                "in " . QFQ_FUNCTION_SQL . ", this usually leads to errors when trying to execute it on the database.";
Marc Egger's avatar
Marc Egger committed
407
            throw new \DbException(json_encode($errorMsg), ERROR_PLAY_SQL_FILE);
Marc Egger's avatar
Marc Egger committed
408
        }
Carsten  Rose's avatar
Carsten Rose committed
409

Marc Egger's avatar
Marc Egger committed
410
        try {
Nicola Chiapolini's avatar
Nicola Chiapolini committed
411
            $this->db->playMultiQuery($query);
Marc Egger's avatar
Marc Egger committed
412
            $functionsHashTest = $this->db->sql('SELECT GETFUNCTIONSHASH() AS res;', ROW_EXPECT_1)['res'];
Marc Egger's avatar
Marc Egger committed
413
        } catch (\DbException $e) {
Marc Egger's avatar
Marc Egger committed
414
            $functionsHashTest = null;
Nicola Chiapolini's avatar
Nicola Chiapolini committed
415
416
        } catch (\CodeException $e) {
            $functionsHashTest = null;
Marc Egger's avatar
Marc Egger committed
417
        }
Carsten  Rose's avatar
Carsten Rose committed
418

419
        $qfqFunctionSqlRelToApp = Path::appToExt('Classes/Sql/' . QFQ_FUNCTION_SQL);
420

421
422
        if ($functionHash !== null AND $functionsHashTest === $functionHash) {
            return $functionHash;
Marc Egger's avatar
Marc Egger committed
423
424
        } else {
            $errorMsg[ERROR_MESSAGE_TO_USER] = 'Error while updating qfq.';
425
            $errorMsg[ERROR_MESSAGE_TO_DEVELOPER] =
426
                "Failed to play " . QFQ_FUNCTION_SQL . ", probably not enough <a href='https://mariadb.com/kb/en/library/stored-routine-privileges/'>permissions</a> for the qfq mysql user. " .
427
                "Possible solutions: <ul>" .
Nicola Chiapolini's avatar
Nicola Chiapolini committed
428
                '<li>Grant SUPER, CREATE ROUTINE, ALTER ROUTINE privileges to qfq mysql user temporarily.</li>' .
429
                '<li>Play the following file manually on the database: ' .
430
                '<a href="' . $qfqFunctionSqlRelToApp . '">' . $qfqFunctionSqlRelToApp . '</a><br>and grant the qfq mysql user execution privileges on the sql functions.</li>' .
431
432
                '<li><a href="?' . http_build_query(array_merge($_GET, array(ACTION_FUNCTION_UPDATE => ACTION_FUNCTION_UPDATE_NEXT_UPDATE))) . '">Click here</a> to skip the sql functions update until next qfq release update</li>' .
                '<li><a href="?' . http_build_query(array_merge($_GET, array(ACTION_FUNCTION_UPDATE => ACTION_FUNCTION_UPDATE_NEVER))) . '">Click here</a> to skip the sql functions update forever</li>' .
433
434
435
                '</ul>' .
                "To enable the sql functions update again you can delete the parameter 'functionsHash' in the table comments of the table 'Form'.";
            $errorMsg[ERROR_MESSAGE_TO_DEVELOPER_SANITIZE] = false;
Marc Egger's avatar
Marc Egger committed
436
            throw new \DbException(json_encode($errorMsg), ERROR_PLAY_SQL_FILE);
Marc Egger's avatar
Marc Egger committed
437
438
439
        }
    }

440
    /**
Carsten  Rose's avatar
Carsten Rose committed
441
     * @param $path
Carsten  Rose's avatar
Carsten Rose committed
442
     *
Carsten  Rose's avatar
Carsten Rose committed
443
     * @return array
Marc Egger's avatar
Marc Egger committed
444
     * @throws \CodeException
445
     */
Carsten  Rose's avatar
Carsten Rose committed
446
    private function readUpdateData($path) {
447
448

        if (!@file_exists($path)) {
Marc Egger's avatar
Marc Egger committed
449
            throw new \CodeException("File '$path'' not found", ERROR_IO_OPEN);
450
451
452
        }

        $UPDATE_ARRAY = null;
Carsten  Rose's avatar
Carsten Rose committed
453

454
        include $path;
Carsten  Rose's avatar
Carsten Rose committed
455

456
457
458
459
460
        return $UPDATE_ARRAY;

    }

    /**
461
462
     * Play all update statement with version number are '>' than $old and '<=' to $new.
     *
463
464
     * @param $old
     * @param $new
Marc Egger's avatar
Marc Egger committed
465
466
467
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
468
469
     */
    private function dbUpdateStatements($old, $new) {
470
471
472

        $dummy = array();

Marc Egger's avatar
Marc Egger committed
473
        if ($new == '' || $old === false || $old === null) {
474
475
            return;
        }
Carsten  Rose's avatar
Carsten Rose committed
476
        $updateArray = $this->readUpdateData(__DIR__ . '/DatabaseUpdateData.php');
477
478
479
480

        $apply = false;
        foreach ($updateArray as $key => $sqlStatements) {

481
482
483
484
            // Search starting point to apply updates. Do not apply updates for $key>$new
            $rc1 = version_compare($key, $old);
            $rc2 = version_compare($key, $new);
            if ($rc1 == 1 && $rc2 != 1) {
485
486
487
                $apply = true;
            }

488
489
490
            if ($apply) {
                // Play Statements
                foreach ($sqlStatements as $sql) {
491
492
493
                    $this->db->sql($sql, ROW_REGULAR, array(),
                        "Apply updates to QFQ database. Installed version: $old. New QFQ version: $new",
                        $dummy, $dummy, [1060] /* duplicate column name */);
494
                }
495
496
                // Remember already applied updates - in case something breaks and the update has to be repeated.
                $this->setDatabaseVersion($new);
497
498
499
500
            }
        }
    }
}