GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#4)
by Felix
12:38
created

DatabaseController::unsetSubKey()   B

Complexity

Conditions 7
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 7
cts 7
cp 1
rs 8.2222
c 0
b 0
f 0
cc 7
eloc 7
nc 2
nop 4
crap 7
1
<?php
2
namespace Aoe\T3deploy\Controller;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2018 AOE GmbH <[email protected]>
8
 *
9
 *  All rights reserved
10
 *
11
 *  This script is part of the TYPO3 project. The TYPO3 project is
12
 *  free software; you can redistribute it and/or modify
13
 *  it under the terms of the GNU General Public License as published by
14
 *  the Free Software Foundation; either version 3 of the License, or
15
 *  (at your option) any later version.
16
 *
17
 *  The GNU General Public License can be found at
18
 *  http://www.gnu.org/copyleft/gpl.html.
19
 *
20
 *  This script is distributed in the hope that it will be useful,
21
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 *  GNU General Public License for more details.
24
 *
25
 *  This copyright notice MUST APPEAR in all copies of the script!
26
 ***************************************************************/
27
28
use TYPO3\CMS\Core\Category\CategoryRegistry;
29
use TYPO3\CMS\Core\Utility\GeneralUtility;
30
use TYPO3\CMS\Extbase\Object\ObjectManager;
31
use TYPO3\CMS\Install\Service\SqlExpectedSchemaService;
32
use TYPO3\CMS\Install\Service\SqlSchemaMigrationService;
33
34
/**
35
 * Controller that handles database actions of the t3deploy process inside TYPO3.
36
 *
37
 * @package t3deploy
38
 */
39
class DatabaseController
40
{
41
    /*
42
     * List of all possible update types:
43
     *  + add, change, drop, create_table, change_table, drop_table, clear_table
44
     * List of all sensible update types:
45
     *  + add, change, create_table, change_table
46
     */
47
    const UpdateTypes_List = 'add,change,create_table,change_table';
0 ignored issues
show
Coding Style introduced by
Constant UpdateTypes_List should be defined in uppercase
Loading history...
48
    const RemoveTypes_list = 'drop,drop_table,clear_table';
0 ignored issues
show
Coding Style introduced by
Constant RemoveTypes_list should be defined in uppercase
Loading history...
49
50
    /**
51
     * @var \TYPO3\CMS\Install\Service\SqlSchemaMigrationService
52
     */
53
    protected $schemaMigrationService;
54
55
    /**
56
     * @var \TYPO3\CMS\Install\Service\SqlExpectedSchemaService
57
     */
58
    protected $expectedSchemaService;
59
60
    /**
61
     * @var \TYPO3\CMS\Core\Category\CategoryRegistry
62
     */
63
    protected $categoryRegistry;
64
65
    /**
66
     * @var array
67
     */
68
    protected $consideredTypes;
69
70
    /**
71
     * Creates this object.
72
     */
73 5
    public function __construct()
74
    {
75
        /** @var ObjectManager $objectManager */
76 5
        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
77
78 5
        $this->schemaMigrationService = $objectManager->get(SqlSchemaMigrationService::class);
79 5
        $this->expectedSchemaService = $objectManager->get(SqlExpectedSchemaService::class);
80 5
        $this->categoryRegistry = $objectManager->get(CategoryRegistry::class);
81
82 5
        $this->setConsideredTypes($this->getUpdateTypes());
83 5
    }
84
85
    /**
86
     * Sets the types considered to be executed (updates and/or removal).
87
     *
88
     * @param array $consideredTypes
89
     * @return void
90
     * @see updateStructureAction()
91
     */
92 5
    public function setConsideredTypes(array $consideredTypes)
93
    {
94 5
        $this->consideredTypes = $consideredTypes;
95 5
    }
96
97
    /**
98
     * Adds considered types.
99
     *
100
     * @param array $consideredTypes
101
     * @return void
102
     * @see updateStructureAction()
103
     */
104 2
    public function addConsideredTypes(array $consideredTypes)
105
    {
106 2
        $this->consideredTypes = array_unique(
107 2
            array_merge($this->consideredTypes, $consideredTypes)
108
        );
109 2
    }
110
111
    /**
112
     * Removes defined types from considered types.
113
     *
114
     * @param array $removals
115
     * @return void
116
     * @see updateStructureAction()
117
     */
118
    public function removeConsideredTypes(array $removals)
119
    {
120
        $this->consideredTypes = array_diff($this->consideredTypes, $removals);
121
    }
122
123
    /**
124
     * Updates the database structure.
125
     *
126
     * @param array $arguments Optional arguments passed to this action
127
     * @return string
128
     * @throws \Exception
129
     */
130 5
    public function updateStructureAction(array $arguments)
131
    {
132 5
        $isExecuteEnabled = (isset($arguments['--execute']) || isset($arguments['-e']));
133 5
        $isRemovalEnabled = (isset($arguments['--remove']) || isset($arguments['-r']));
134 5
        $isModifyKeysEnabled = isset($arguments['--drop-keys']);
135
136 5
        $result = $this->executeUpdateStructureUntilNoMoreChanges($arguments, $isModifyKeysEnabled);
137
138 5
        if(isset($arguments['--dump-file'])) {
139 1
            $dumpFileName = $arguments['--dump-file'][0];
140
141 1
            if (!file_exists(dirname($dumpFileName))) {
142
                throw new \InvalidArgumentException(sprintf(
143
                    'directory %s does not exist or is not readable', dirname($dumpFileName)
144
                ));
145
            }
146
147 1
            if (file_exists($dumpFileName) && !is_writable($dumpFileName)) {
148
                throw new \InvalidArgumentException(sprintf(
149
                    'file %s is not writable', $dumpFileName
150
                ));
151
            }
152
153 1
            file_put_contents($dumpFileName, $result);
154 1
            $result = sprintf("Output written to %s\n", $dumpFileName);
155
        }
156
157 5
        if ($isExecuteEnabled) {
158 2
            $result .= ($result ? PHP_EOL : '')
159 2
                            . $this->executeUpdateStructureUntilNoMoreChanges($arguments, $isRemovalEnabled);
160
        }
161
162 5
        return $result;
163
    }
164
165
    /**
166
     * call executeUpdateStructure until there are no more changes.
167
     *
168
     * The install tool sometimes relies on the user hitting the "update" button multiple times. This method
169
     * encapsulates that behaviour.
170
     *
171
     * @see executeUpdateStructure()
172
     * @param array $arguments
173
     * @param bool $allowKeyModifications
174
     * @return string
175
     * @throws \Exception
176
     */
177 5
    protected function executeUpdateStructureUntilNoMoreChanges(array $arguments, $allowKeyModifications = false)
178
    {
179 5
        $result = '';
180 5
        $iteration = 1;
181 5
        $loopResult = '';
182
183
        do {
184 5
            $previousLoopResult = $loopResult;
185 5
            $loopResult = $this->executeUpdateStructure($arguments, $allowKeyModifications);
186
187 5
            if ($loopResult == $previousLoopResult) {
188 5
                break;
189
            }
190
191 3
            $result .= sprintf("\n# Iteration %d\n%s", $iteration++, $loopResult);
192
193 3
            if ($iteration > 10) {
194
                $result .= "\nGiving up after 10 iterations.";
195
                break;
196
            }
197 3
        } while (!empty($loopResult));
198
199 5
        return $result;
200
    }
201
202
    /**
203
     * Executes the database structure updates.
204
     *
205
     * @param array $arguments Optional arguments passed to this action
206
     * @param boolean $allowKeyModifications Whether to allow key modifications
207
     * @return string
208
     * @throws \Exception
209
     */
210 5
    protected function executeUpdateStructure(array $arguments, $allowKeyModifications = false)
211
    {
212 5
        $result = '';
213
214 5
        $isExecuteEnabled = (isset($arguments['--execute']) || isset($arguments['-e']));
215 5
        $isRemovalEnabled = (isset($arguments['--remove']) || isset($arguments['-r']));
216 5
        $isVerboseEnabled = (isset($arguments['--verbose']) || isset($arguments['-v']));
217 5
        $hasExcludes      = (isset($arguments['--excludes']) && is_array($arguments['--excludes']));
218
219 5
        $changes = $this->schemaMigrationService->getUpdateSuggestions(
220 5
            $this->getStructureDifferencesForUpdate($allowKeyModifications)
221
        );
222
223 5
        if ($isRemovalEnabled) {
224
                // Disable the delete prefix, thus tables and fields can be removed directly:
225 2
            $this->schemaMigrationService->setDeletedPrefixKey('');
226
227
                // Add types considered for removal:
228 2
            $this->addConsideredTypes($this->getRemoveTypes());
229
                // Merge update suggestions:
230 2
            $removals = $this->schemaMigrationService->getUpdateSuggestions(
231 2
                $this->getStructureDifferencesForRemoval($allowKeyModifications),
232 2
                'remove'
233
            );
234 2
            $changes = array_merge($changes, $removals);
235
        }
236
237 5
        if ($hasExcludes) {
238
            $excludes = explode(',', $arguments['--excludes'][0]);
239
            $this->removeConsideredTypes($excludes);
240
        }
241
242 5
        if ($isExecuteEnabled || $isVerboseEnabled) {
243 5
            $statements = [];
244
245
            // Concatenates all statements:
246 5
            foreach ($this->consideredTypes as $consideredType) {
247 5
                if (isset($changes[$consideredType]) && is_array($changes[$consideredType])) {
248 5
                    $statements += $changes[$consideredType];
249
                }
250
            }
251
252 5
            $statements = $this->sortStatements($statements);
253
254 5
            if ($isExecuteEnabled) {
255 2
                foreach ($statements as $statement) {
256 2
                    $GLOBALS['TYPO3_DB']->admin_query($statement);
257
                }
258
            }
259
260 5
            if ($isVerboseEnabled) {
261 3
                $result = implode(PHP_EOL, $statements);
262
            }
263
        }
264
265 5
        $this->checkChangesSyntax($result);
266
267 5
        return $result;
268
    }
269
270
    /**
271
     * performs some basic checks on the database changes to identify most common errors
272
     *
273
     * @param string $changes the changes to check
274
     * @throws \Exception if the file seems to contain bad data
275
     */
276 5
    protected function checkChangesSyntax($changes)
277
    {
278 5
        if (strlen($changes) < 10) return;
279
280 3
        $checked = substr(ltrim($changes), 0, 10);
281
282 3
        if ($checked != trim(strtoupper($checked))) {
283
            throw new \Exception(
284
                'Changes for schema_up seem to contain weird data, it starts with this:' . PHP_EOL
285
                    . substr($changes, 0, 200).PHP_EOL.'==================================' . PHP_EOL
286
                    . 'If the file is ok, please add your conditions to file '
287
                    . 'res/extensions/t3deploy/classes/class.tx_t3deploy_databaseController.php in t3deploy.'
288
            );
289
        }
290 3
    }
291
292
    /**
293
     * Removes key modifications that will cause errors.
294
     *
295
     * @param array $differences The differences to be cleaned up
296
     * @return array The cleaned differences
297
     */
298 5
    protected function removeKeyModifications(array $differences)
299
    {
300 5
        $differences = $this->unsetSubKey($differences, 'extra', 'keys', 'whole_table');
301 5
        $differences = $this->unsetSubKey($differences, 'diff', 'keys');
302
303 5
        return $differences;
304
    }
305
306
    /**
307
     * Unsets a subkey in a given differences array.
308
     *
309
     * @param array $differences
310
     * @param string $type e.g. extra or diff
311
     * @param string $subKey e.g. keys or fields
312
     * @param string $exception e.g. whole_table that stops the removal
313
     * @return array
314
     */
315 5
    protected function unsetSubKey(array $differences, $type, $subKey, $exception = '')
316
    {
317 5
        if (isset($differences[$type])) {
318 5
            foreach ($differences[$type] as $table => $information) {
319 5
                $isException = ($exception && isset($information[$exception]) && $information[$exception]);
320 5
                if (isset($information[$subKey]) && $isException === false) {
321 5
                    unset($differences[$type][$table][$subKey]);
322
                }
323
            }
324
        }
325
326 5
        return $differences;
327
    }
328
329
    /**
330
     * Gets the differences in the database structure by comparing
331
     * the current structure with the SQL definitions of all extensions
332
     * and the TYPO3 core in t3lib/stddb/tables.sql.
333
     *
334
     * This method searches for fields/tables to be added/updated.
335
     *
336
     * @param boolean $allowKeyModifications Whether to allow key modifications
337
     * @return array The database statements to update the structure
338
     */
339 5
    protected function getStructureDifferencesForUpdate($allowKeyModifications = false)
340
    {
341 5
        $differences = $this->schemaMigrationService->getDatabaseExtra(
342 5
            $this->getDefinedFieldDefinitions(),
343 5
            $this->schemaMigrationService->getFieldDefinitions_database()
344
        );
345
346 5
        if (!$allowKeyModifications) {
347 5
            $differences = $this->removeKeyModifications($differences);
348
        }
349
350 5
        return $differences;
351
    }
352
353
    /**
354
     * Gets the differences in the database structure by comparing
355
     * the current structure with the SQL definitions of all extensions
356
     * and the TYPO3 core in t3lib/stddb/tables.sql.
357
     *
358
     * This method searches for fields/tables to be removed.
359
     *
360
     * @param boolean $allowKeyModifications Whether to allow key modifications
361
     * @return array The database statements to update the structure
362
     */
363 2
    protected function getStructureDifferencesForRemoval($allowKeyModifications = false)
364
    {
365 2
        $differences = $this->schemaMigrationService->getDatabaseExtra(
366 2
            $this->schemaMigrationService->getFieldDefinitions_database(),
367 2
            $this->getDefinedFieldDefinitions()
368
        );
369
370 2
        if (!$allowKeyModifications) {
371 2
            $differences = $this->removeKeyModifications($differences);
372
        }
373
374 2
        return $differences;
375
    }
376
377
    /**
378
     * Gets the defined field definitions from the ext_tables.sql files.
379
     *
380
     * @return array The accordant definitions
381
     */
382 5
    protected function getDefinedFieldDefinitions()
383
    {
384 5
        $cacheTables = $this->categoryRegistry->getDatabaseTableDefinitions();
385 5
        $content = $this->schemaMigrationService->getFieldDefinitions_fileContent(
386 5
            implode(chr(10), $this->getAllRawStructureDefinitions()) . $cacheTables
387
        );
388 5
        return $content;
389
    }
390
391
    /**
392
     * Gets all structure definitions of extensions the TYPO3 Core.
393
     *
394
     * @return array All structure definitions
395
     */
396 5
    protected function getAllRawStructureDefinitions()
397
    {
398
399 5
        $expectedSchemaString = $this->expectedSchemaService->getTablesDefinitionString(true);
400 5
        $rawDefinitions = $this->schemaMigrationService->getStatementArray($expectedSchemaString, true);
401
402 5
        return $rawDefinitions;
403
    }
404
405
    /**
406
     * Gets the defined update types.
407
     *
408
     * @return array
409
     */
410 5
    protected function getUpdateTypes()
411
    {
412 5
        return \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', self::UpdateTypes_List, true);
413
    }
414
415
    /**
416
     * Gets the defined remove types.
417
     *
418
     * @return array
419
     */
420 2
    protected function getRemoveTypes()
421
    {
422 2
        return \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', self::RemoveTypes_list, true);
423
    }
424
425
    /**
426
     * sorts the statements in an array
427
     *
428
     * @param array $statements
429
     * @return array
430
     */
431 5
    protected function sortStatements($statements)
432
    {
433 5
        $newStatements = [];
434
435 5
        foreach($statements as $key=>$statement) {
436 5
            if($this->isDropKeyStatement($statement)) {
437 5
                $newStatements[$key] = $statement;
438
            }
439
        }
440
441 5
        foreach($statements as $key=>$statement) {
442
            // writing the statement again, does not change its position
443
            // this saves a condition check
444 5
            $newStatements[$key] = $statement;
445
        }
446
447 5
        return $newStatements;
448
    }
449
450
    /**
451
     * returns true if the given statement looks as if it drops a (primary) key
452
     *
453
     * @param $statement
454
     * @return bool
455
     */
456 5
    protected function isDropKeyStatement($statement)
457
    {
458 5
        return strpos($statement, ' DROP ') !== false && strpos($statement, ' KEY') !== false;
459
    }
460
}
461