c954349b70b524a3d3f2b64a6d1a01be9130459c
[Packages/TYPO3.CMS.git] / typo3 / sysext / scheduler / Classes / Task / OptimizeDatabaseTableAdditionalFieldProvider.php
1 <?php
2 namespace TYPO3\CMS\Scheduler\Task;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Database\Connection;
18 use TYPO3\CMS\Core\Database\ConnectionPool;
19 use TYPO3\CMS\Core\Messaging\FlashMessage;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21 use TYPO3\CMS\Scheduler\AdditionalFieldProviderInterface;
22 use TYPO3\CMS\Scheduler\Controller\SchedulerModuleController;
23
24 /**
25 * Additional BE fields for optimize database table task.
26 */
27 class OptimizeDatabaseTableAdditionalFieldProvider implements AdditionalFieldProviderInterface
28 {
29 /**
30 * @var string
31 */
32 protected $languageFile = 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf';
33
34 /**
35 * Add a multi select box with all available database tables.
36 *
37 * @param array $taskInfo Reference to the array containing the info used in the add/edit form
38 * @param AbstractTask|NULL $task When editing, reference to the current task. NULL when adding.
39 * @param SchedulerModuleController $parentObject Reference to the calling object (Scheduler's BE module)
40 * @return array Array containing all the information pertaining to the additional fields
41 */
42 public function getAdditionalFields(array &$taskInfo, $task, SchedulerModuleController $parentObject)
43 {
44 // Initialize selected fields
45 if (empty($taskInfo['scheduler_optimizeDatabaseTables_selectedTables'])) {
46 $taskInfo['scheduler_optimizeDatabaseTables_selectedTables'] = [];
47 if ($parentObject->CMD === 'add') {
48 // In case of new task, select no tables by default
49 $taskInfo['scheduler_optimizeDatabaseTables_selectedTables'] = [];
50 } elseif ($parentObject->CMD === 'edit') {
51 // In case of editing the task, set to currently selected value
52 $taskInfo['scheduler_optimizeDatabaseTables_selectedTables'] = $task->selectedTables;
53 }
54 }
55 $fieldName = 'tx_scheduler[scheduler_optimizeDatabaseTables_selectedTables][]';
56 $fieldId = 'scheduler_optimizeDatabaseTables_selectedTables';
57 $fieldOptions = $this->getDatabaseTableOptions($taskInfo['scheduler_optimizeDatabaseTables_selectedTables']);
58 $fieldHtml = '<select class="form-control" name="' . $fieldName
59 . '" id="' . $fieldId
60 . '" class="from-control" size="10" multiple="multiple">'
61 . $fieldOptions
62 . '</select>';
63 $additionalFields[$fieldId] = [
64 'code' => $fieldHtml,
65 'label' => $this->languageFile . ':label.optimizeDatabaseTables.selectTables',
66 'cshKey' => '_MOD_system_txschedulerM1',
67 'cshLabel' => $fieldId,
68 ];
69
70 return $additionalFields;
71 }
72
73 /**
74 * Checks that all selected backends exist in available backend list
75 *
76 * @param array $submittedData Reference to the array containing the data submitted by the user
77 * @param SchedulerModuleController $parentObject Reference to the calling object (Scheduler's BE module)
78 * @return bool TRUE if validation was ok (or selected class is not relevant), FALSE otherwise
79 */
80 public function validateAdditionalFields(array &$submittedData, SchedulerModuleController $parentObject)
81 {
82 $validData = true;
83 $availableTables = $this->getOptimizableTables();
84 if (is_array($submittedData['scheduler_optimizeDatabaseTables_selectedTables'])) {
85 $invalidTables = array_diff(
86 $submittedData['scheduler_optimizeDatabaseTables_selectedTables'],
87 $availableTables
88 );
89 if (!empty($invalidTables)) {
90 $parentObject->addMessage(
91 $GLOBALS['LANG']->sL($this->languageFile . ':msg.selectionOfNonExistingDatabaseTables'),
92 FlashMessage::ERROR
93 );
94 $validData = false;
95 }
96 } else {
97 $parentObject->addMessage(
98 $GLOBALS['LANG']->sL($this->languageFile . ':msg.noDatabaseTablesSelected'),
99 FlashMessage::ERROR
100 );
101 $validData = false;
102 }
103
104 return $validData;
105 }
106
107 /**
108 * Save selected backends in task object
109 *
110 * @param array $submittedData Contains data submitted by the user
111 * @param AbstractTask $task Reference to the current task object
112 */
113 public function saveAdditionalFields(array $submittedData, AbstractTask $task)
114 {
115 $task->selectedTables = $submittedData['scheduler_optimizeDatabaseTables_selectedTables'];
116 }
117
118 /**
119 * Build select options of available backends and set currently selected backends
120 *
121 * @param array $selectedTables Selected backends
122 * @return string HTML of selectbox options
123 */
124 protected function getDatabaseTableOptions(array $selectedTables)
125 {
126 $options = [];
127 $availableTables = $this->getOptimizableTables();
128 foreach ($availableTables as $tableName) {
129 $selected = in_array($tableName, $selectedTables, true) ? ' selected="selected"' : '';
130 $options[] = '<option value="' . $tableName . '"' . $selected . '>' . $tableName . '</option>';
131 }
132
133 return implode('', $options);
134 }
135
136 /**
137 * Get all tables that are capable of optimization
138 *
139 * @return array Names of table that can be optimized.
140 */
141 protected function getOptimizableTables()
142 {
143 $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
144 $defaultConnection = $connectionPool->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
145
146 // Retrieve all optimizable tables for the default connection
147 $optimizableTables = $this->getOptimizableTablesForConnection($defaultConnection);
148
149 // Retrieve additional optimizable tables that have been remapped to a different connection
150 if (isset($GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'])
151 && is_array($GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'])
152 ) {
153 $tableMap = $GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'];
154 // Remove all remapped tables from the list of optimizable tables
155 // These tables will be rechecked and possibly re-added to the list
156 // of optimizable tables. This ensures that no orphaned table from
157 // the default connection gets mistakenly labeled as optimizable.
158 $optimizableTables = array_diff($optimizableTables, array_keys($tableMap));
159
160 // Walk each connection and check all tables that have been
161 // remapped to it for optimization support.
162 $connectionNames = array_keys(array_flip($tableMap));
163 foreach ($connectionNames as $connectionName) {
164 $connection = $connectionPool->getConnectionByName($connectionName);
165 $tablesOnConnection = array_keys(array_filter(
166 $tableMap,
167 function ($value) use ($connectionName) {
168 return $value === $connectionName;
169 }
170 ));
171 $tables = $this->getOptimizableTablesForConnection($connection, $tablesOnConnection);
172 $optimizableTables = array_merge($optimizableTables, $tables);
173 }
174 }
175
176 sort($optimizableTables);
177
178 return $optimizableTables;
179 }
180
181 /**
182 * Retrieve all optimizable tables for a connection, optionally restricted to the subset
183 * of table names in the $tableNames array.
184 *
185 * @param \TYPO3\CMS\Core\Database\Connection $connection
186 * @param array $tableNames
187 * @return array
188 */
189 protected function getOptimizableTablesForConnection(Connection $connection, array $tableNames = []): array
190 {
191 // Return empty list if the database platform is not MySQL
192 if (strpos($connection->getServerVersion(), 'MySQL') !== 0) {
193 return [];
194 }
195
196 // Retrieve all tables from the MySQL informaation schema that have an engine type
197 // that supports the OPTIMIZE TABLE command.
198 $queryBuilder = $connection->createQueryBuilder();
199 $queryBuilder->select('TABLE_NAME AS Table', 'ENGINE AS Engine')
200 ->from('information_schema.TABLES')
201 ->where(
202 $queryBuilder->expr()->eq(
203 'TABLE_TYPE',
204 $queryBuilder->createNamedParameter('BASE TABLE', \PDO::PARAM_STR)
205 ),
206 $queryBuilder->expr()->in(
207 'ENGINE',
208 $queryBuilder->createNamedParameter(['InnoDB', 'MyISAM', 'ARCHIVE'], Connection::PARAM_STR_ARRAY)
209 ),
210 $queryBuilder->expr()->eq(
211 'TABLE_SCHEMA',
212 $queryBuilder->createNamedParameter($connection->getDatabase(), \PDO::PARAM_STR)
213 )
214 );
215
216 if (!empty($tableNames)) {
217 $queryBuilder->andWhere(
218 $queryBuilder->expr()->in(
219 'TABLE_NAME',
220 $queryBuilder->createNamedParameter($tableNames, \PDO::PARAM_STR)
221 )
222 );
223 }
224
225 $tables = $queryBuilder->execute()->fetchAll();
226
227 return array_column($tables, 'Table');
228 }
229 }