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 ( 7da262...614131 )
by Chetan
05:32
created

updateStructureAction()   D

Complexity

Conditions 9
Paths 32

Size

Total Lines 29
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

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