1<?php
2
3declare(strict_types=1);
4
5/*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18namespace TYPO3\CMS\Core\Database\Schema;
19
20use Psr\EventDispatcher\EventDispatcherInterface;
21use TYPO3\CMS\Core\Database\Event\AlterTableDefinitionStatementsEvent;
22use TYPO3\CMS\Core\Package\PackageManager;
23
24/**
25 * Helper methods to handle raw SQL input and transform it into individual statements
26 * for further processing.
27 *
28 * @internal
29 */
30class SqlReader
31{
32    /**
33     * @var EventDispatcherInterface
34     */
35    protected $eventDispatcher;
36
37    /**
38     * @var PackageManager
39     */
40    protected $packageManager;
41
42    /**
43     * @param EventDispatcherInterface $eventDispatcher
44     * @param PackageManager $packageManager
45     * @throws \InvalidArgumentException
46     */
47    public function __construct(EventDispatcherInterface $eventDispatcher, PackageManager $packageManager)
48    {
49        $this->eventDispatcher = $eventDispatcher;
50        $this->packageManager = $packageManager;
51    }
52
53    /**
54     * Cycle through all loaded extensions and get full table definitions as concatenated string
55     *
56     * @param bool $withStatic TRUE if sql from ext_tables_static+adt.sql should be loaded, too.
57     * @return string Concatenated SQL of loaded extensions ext_tables.sql
58     */
59    public function getTablesDefinitionString(bool $withStatic = false): string
60    {
61        $sqlString = [];
62
63        // Find all ext_tables.sql of loaded extensions
64        foreach ($this->packageManager->getActivePackages() as $package) {
65            $packagePath = $package->getPackagePath();
66            if (@file_exists($packagePath . 'ext_tables.sql')) {
67                $sqlString[] = (string)file_get_contents($packagePath . 'ext_tables.sql');
68            }
69            if ($withStatic && @file_exists($packagePath . 'ext_tables_static+adt.sql')) {
70                $sqlString[] = (string)file_get_contents($packagePath . 'ext_tables_static+adt.sql');
71            }
72        }
73
74        /** @var AlterTableDefinitionStatementsEvent $event */
75        $event = $this->eventDispatcher->dispatch(new AlterTableDefinitionStatementsEvent($sqlString));
76        $sqlString = $event->getSqlData();
77
78        return implode(LF . LF, $sqlString);
79    }
80
81    /**
82     * Returns an array where every entry is a single SQL-statement.
83     * Input must be formatted like an ordinary MySQL dump file. Every statements needs to be terminated by a ';'
84     * and there may only be one statement (or partial statement) per line.
85     *
86     * @param string $dumpContent The SQL dump content.
87     * @param string $queryRegex Regex to select which statements to return.
88     * @return array Array of SQL statements
89     */
90    public function getStatementArray(string $dumpContent, string $queryRegex = null): array
91    {
92        $statementArray = [];
93        $statementArrayPointer = 0;
94        $isInMultilineComment = false;
95        foreach (explode(LF, $dumpContent) as $lineContent) {
96            $lineContent = trim($lineContent);
97
98            // Skip empty lines and comments
99            if ($lineContent === '' || $lineContent[0] === '#' || strpos($lineContent, '--') === 0 ||
100                strpos($lineContent, '/*') === 0 || substr($lineContent, -2) === '*/' || $isInMultilineComment
101            ) {
102                // skip c style multiline comments
103                if (strpos($lineContent, '/*') === 0 && substr($lineContent, -2) !== '*/') {
104                    $isInMultilineComment = true;
105                }
106                if (substr($lineContent, -2) === '*/') {
107                    $isInMultilineComment = false;
108                }
109                continue;
110            }
111
112            $statementArray[$statementArrayPointer] = ($statementArray[$statementArrayPointer] ?? '') . $lineContent;
113
114            if (substr($lineContent, -1) === ';') {
115                $statement = trim($statementArray[$statementArrayPointer]);
116                if (!$statement || ($queryRegex && !preg_match('/' . $queryRegex . '/i', $statement))) {
117                    unset($statementArray[$statementArrayPointer]);
118                }
119                $statementArrayPointer++;
120            } else {
121                $statementArray[$statementArrayPointer] .= ' ';
122            }
123        }
124
125        return $statementArray;
126    }
127
128    /**
129     * Extract only INSERT statements from SQL dump
130     *
131     * @param string $dumpContent
132     * @return array
133     */
134    public function getInsertStatementArray(string $dumpContent): array
135    {
136        return $this->getStatementArray($dumpContent, '^INSERT');
137    }
138
139    /**
140     * Extract only CREATE TABLE statements from SQL dump
141     *
142     * @param string $dumpContent
143     * @return array
144     */
145    public function getCreateTableStatementArray(string $dumpContent): array
146    {
147        return $this->getStatementArray($dumpContent, '^CREATE TABLE');
148    }
149}
150