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
Push — master ( 02340e...40ad3b )
by Felix
04:29
created

getStructureDifferencesForRemoval()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 0
cts 7
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 7
nc 2
nop 1
crap 6
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
    public function __construct()
74
    {
75
        /** @var ObjectManager $objectManager */
76
        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
77
78
        $this->schemaMigrationService = $objectManager->get(SqlSchemaMigrationService::class);
79
        $this->expectedSchemaService = $objectManager->get(SqlExpectedSchemaService::class);
80
        $this->categoryRegistry = $objectManager->get(CategoryRegistry::class);
81
82
        $this->setConsideredTypes($this->getUpdateTypes());
83
    }
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
    public function setConsideredTypes(array $consideredTypes)
93
    {
94
        $this->consideredTypes = $consideredTypes;
95
    }
96
97
    /**
98
     * Adds considered types.
99
     *
100
     * @param array $consideredTypes
101
     * @return void
102
     * @see updateStructureAction()
103
     */
104
    public function addConsideredTypes(array $consideredTypes)
105
    {
106
        $this->consideredTypes = array_unique(
107
            array_merge($this->consideredTypes, $consideredTypes)
108
        );
109
    }
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
    public function updateStructureAction(array $arguments)
131
    {
132
        $isExecuteEnabled = (isset($arguments['--execute']) || isset($arguments['-e']));
133
        $isRemovalEnabled = (isset($arguments['--remove']) || isset($arguments['-r']));
134
        $isModifyKeysEnabled = isset($arguments['--drop-keys']);
135
136
        $result = $this->executeUpdateStructureUntilNoMoreChanges($arguments, $isModifyKeysEnabled);
137
138
        if(isset($arguments['--dump-file'])) {
139
            $dumpFileName = $arguments['--dump-file'][0];
140
141
            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
            if (file_exists($dumpFileName) && !is_writable($dumpFileName)) {
148
                throw new \InvalidArgumentException(sprintf(
149
                    'file %s is not writable', $dumpFileName
150
                ));
151
            }
152
153
            file_put_contents($dumpFileName, $result);
154
            $result = sprintf("Output written to %s\n", $dumpFileName);
155
        }
156
157
        if ($isExecuteEnabled) {
158
            $result .= ($result ? PHP_EOL : '')
159
                            . $this->executeUpdateStructureUntilNoMoreChanges($arguments, $isRemovalEnabled);
160
        }
161
162
        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
    protected function executeUpdateStructureUntilNoMoreChanges(array $arguments, $allowKeyModifications = false)
178
    {
179
        $result = '';
180
        $iteration = 1;
181
        $loopResult = '';
182
183
        do {
184
            $previousLoopResult = $loopResult;
185
            $loopResult = $this->executeUpdateStructure($arguments, $allowKeyModifications);
186
187
            if ($loopResult == $previousLoopResult) {
188
                break;
189
            }
190
191
            $result .= sprintf("\n# Iteration %d\n%s", $iteration++, $loopResult);
192
193
            if ($iteration > 10) {
194
                $result .= "\nGiving up after 10 iterations.";
195
                break;
196
            }
197
        } while (!empty($loopResult));
198
199
        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
    protected function executeUpdateStructure(array $arguments, $allowKeyModifications = false)
211
    {
212
        $result = '';
213
214
        $isExecuteEnabled = (isset($arguments['--execute']) || isset($arguments['-e']));
215
        $isRemovalEnabled = (isset($arguments['--remove']) || isset($arguments['-r']));
216
        $isVerboseEnabled = (isset($arguments['--verbose']) || isset($arguments['-v']));
217
        $hasExcludes      = (isset($arguments['--excludes']) && is_array($arguments['--excludes']));
218
219
        $changes = $this->schemaMigrationService->getUpdateSuggestions(
220
            $this->getStructureDifferencesForUpdate($allowKeyModifications)
221
        );
222
223
        if ($isRemovalEnabled) {
224
                // Disable the delete prefix, thus tables and fields can be removed directly:
225
            $this->schemaMigrationService->setDeletedPrefixKey('');
226
227
                // Add types considered for removal:
228
            $this->addConsideredTypes($this->getRemoveTypes());
229
                // Merge update suggestions:
230
            $removals = $this->schemaMigrationService->getUpdateSuggestions(
231
                $this->getStructureDifferencesForRemoval($allowKeyModifications),
232
                'remove'
233
            );
234
            $changes = array_merge($changes, $removals);
235
        }
236
237
        if ($hasExcludes) {
238
            $excludes = explode(',', $arguments['--excludes'][0]);
239
            $this->removeConsideredTypes($excludes);
240
        }
241
242
        if ($isExecuteEnabled || $isVerboseEnabled) {
243
            $statements = [];
244
245
            // Concatenates all statements:
246
            foreach ($this->consideredTypes as $consideredType) {
247
                if (isset($changes[$consideredType]) && is_array($changes[$consideredType])) {
248
                    $statements += $changes[$consideredType];
249
                }
250
            }
251
252
            $statements = $this->sortStatements($statements);
253
254
            if ($isExecuteEnabled) {
255
                foreach ($statements as $statement) {
256
                    $GLOBALS['TYPO3_DB']->admin_query($statement);
257
                }
258
            }
259
260
            if ($isVerboseEnabled) {
261
                $result = implode(PHP_EOL, $statements);
262
            }
263
        }
264
265
        $this->checkChangesSyntax($result);
266
267
        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
    protected function checkChangesSyntax($changes)
277
    {
278
        if (strlen($changes) < 10) return;
279
280
        $checked = substr(ltrim($changes), 0, 10);
281
282
        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
    }
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
    protected function removeKeyModifications(array $differences)
299
    {
300
        $differences = $this->unsetSubKey($differences, 'extra', 'keys', 'whole_table');
301
        $differences = $this->unsetSubKey($differences, 'diff', 'keys');
302
303
        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
    protected function unsetSubKey(array $differences, $type, $subKey, $exception = '')
316
    {
317
        if (isset($differences[$type])) {
318
            foreach ($differences[$type] as $table => $information) {
319
                $isException = ($exception && isset($information[$exception]) && $information[$exception]);
320
                if (isset($information[$subKey]) && $isException === false) {
321
                    unset($differences[$type][$table][$subKey]);
322
                }
323
            }
324
        }
325
326
        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
    protected function getStructureDifferencesForUpdate($allowKeyModifications = false)
340
    {
341
        $differences = $this->schemaMigrationService->getDatabaseExtra(
342
            $this->getDefinedFieldDefinitions(),
343
            $this->schemaMigrationService->getFieldDefinitions_database()
344
        );
345
346
        if (!$allowKeyModifications) {
347
            $differences = $this->removeKeyModifications($differences);
348
        }
349
350
        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
    protected function getStructureDifferencesForRemoval($allowKeyModifications = false)
364
    {
365
        $differences = $this->schemaMigrationService->getDatabaseExtra(
366
            $this->schemaMigrationService->getFieldDefinitions_database(),
367
            $this->getDefinedFieldDefinitions()
368
        );
369
370
        if (!$allowKeyModifications) {
371
            $differences = $this->removeKeyModifications($differences);
372
        }
373
374
        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
    protected function getDefinedFieldDefinitions()
383
    {
384
        $cacheTables = $this->categoryRegistry->getDatabaseTableDefinitions();
385
        $content = $this->schemaMigrationService->getFieldDefinitions_fileContent(
386
            implode(chr(10), $this->getAllRawStructureDefinitions()) . $cacheTables
387
        );
388
        return $content;
389
    }
390
391
    /**
392
     * Gets all structure definitions of extensions the TYPO3 Core.
393
     *
394
     * @return array All structure definitions
395
     */
396
    protected function getAllRawStructureDefinitions()
397
    {
398
        $packageStates = include(PATH_typo3conf .'PackageStates.php');
399
400
        $tmp = $GLOBALS['TYPO3_LOADED_EXT'];
401
402
        $GLOBALS['TYPO3_LOADED_EXT'] = array_merge($packageStates['packages'], $GLOBALS['TYPO3_LOADED_EXT']);
403
404
        $expectedSchemaString = $this->expectedSchemaService->getTablesDefinitionString(true);
405
        $rawDefinitions = $this->schemaMigrationService->getStatementArray($expectedSchemaString, true);
406
407
        $GLOBALS['TYPO3_LOADED_EXT'] = $tmp;
408
409
        return $rawDefinitions;
410
    }
411
412
    /**
413
     * Gets the defined update types.
414
     *
415
     * @return array
416
     */
417
    protected function getUpdateTypes()
418
    {
419
        return \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', self::UpdateTypes_List, true);
420
    }
421
422
    /**
423
     * Gets the defined remove types.
424
     *
425
     * @return array
426
     */
427
    protected function getRemoveTypes()
428
    {
429
        return \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', self::RemoveTypes_list, true);
430
    }
431
432
    /**
433
     * sorts the statements in an array
434
     *
435
     * @param array $statements
436
     * @return array
437
     */
438
    protected function sortStatements($statements)
439
    {
440
        $newStatements = [];
441
442
        foreach($statements as $key=>$statement) {
443
            if($this->isDropKeyStatement($statement)) {
444
                $newStatements[$key] = $statement;
445
            }
446
        }
447
448
        foreach($statements as $key=>$statement) {
449
            // writing the statement again, does not change its position
450
            // this saves a condition check
451
            $newStatements[$key] = $statement;
452
        }
453
454
        return $newStatements;
455
    }
456
457
    /**
458
     * returns true if the given statement looks as if it drops a (primary) key
459
     *
460
     * @param $statement
461
     * @return bool
462
     */
463
    protected function isDropKeyStatement($statement)
464
    {
465
        return strpos($statement, ' DROP ') !== false && strpos($statement, ' KEY') !== false;
466
    }
467
}
468