getCurrentAction(); // Initialize selected fields if (empty($taskInfo['scheduler_optimizeDatabaseTables_selectedTables'])) { $taskInfo['scheduler_optimizeDatabaseTables_selectedTables'] = []; if ($currentSchedulerModuleAction->equals(Action::ADD)) { // In case of new task, select no tables by default $taskInfo['scheduler_optimizeDatabaseTables_selectedTables'] = []; } elseif ($currentSchedulerModuleAction->equals(Action::EDIT)) { // In case of editing the task, set to currently selected value $taskInfo['scheduler_optimizeDatabaseTables_selectedTables'] = $task->selectedTables; } } $fieldName = 'tx_scheduler[scheduler_optimizeDatabaseTables_selectedTables][]'; $fieldId = 'scheduler_optimizeDatabaseTables_selectedTables'; $fieldOptions = $this->getDatabaseTableOptions($taskInfo['scheduler_optimizeDatabaseTables_selectedTables']); $fieldHtml = ''; $additionalFields[$fieldId] = [ 'code' => $fieldHtml, 'label' => $this->languageFile . ':label.optimizeDatabaseTables.selectTables', 'cshKey' => '_MOD_system_txschedulerM1', 'cshLabel' => $fieldId, ]; return $additionalFields; } /** * Checks that all selected backends exist in available backend list * * @param array $submittedData Reference to the array containing the data submitted by the user * @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module) * @return bool TRUE if validation was ok (or selected class is not relevant), FALSE otherwise */ public function validateAdditionalFields(array &$submittedData, SchedulerModuleController $schedulerModule) { $validData = true; $availableTables = $this->getOptimizableTables(); if (is_array($submittedData['scheduler_optimizeDatabaseTables_selectedTables'])) { $invalidTables = array_diff( $submittedData['scheduler_optimizeDatabaseTables_selectedTables'], $availableTables ); if (!empty($invalidTables)) { $this->addMessage( $GLOBALS['LANG']->sL($this->languageFile . ':msg.selectionOfNonExistingDatabaseTables'), FlashMessage::ERROR ); $validData = false; } } else { $this->addMessage( $GLOBALS['LANG']->sL($this->languageFile . ':msg.noDatabaseTablesSelected'), FlashMessage::ERROR ); $validData = false; } return $validData; } /** * Save selected backends in task object * * @param array $submittedData Contains data submitted by the user * @param AbstractTask $task Reference to the current task object */ public function saveAdditionalFields(array $submittedData, AbstractTask $task) { $task->selectedTables = $submittedData['scheduler_optimizeDatabaseTables_selectedTables']; } /** * Build select options of available backends and set currently selected backends * * @param array $selectedTables Selected backends * @return string HTML of selectbox options */ protected function getDatabaseTableOptions(array $selectedTables) { $options = []; $availableTables = $this->getOptimizableTables(); foreach ($availableTables as $tableName) { $selected = in_array($tableName, $selectedTables, true) ? ' selected="selected"' : ''; $options[] = ''; } return implode('', $options); } /** * Get all tables that are capable of optimization * * @return array Names of table that can be optimized. */ protected function getOptimizableTables() { $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class); $defaultConnection = $connectionPool->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME); // Retrieve all optimizable tables for the default connection $optimizableTables = $this->getOptimizableTablesForConnection($defaultConnection); // Retrieve additional optimizable tables that have been remapped to a different connection $tableMap = $GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'] ?? []; if ($tableMap) { // Remove all remapped tables from the list of optimizable tables // These tables will be rechecked and possibly re-added to the list // of optimizable tables. This ensures that no orphaned table from // the default connection gets mistakenly labeled as optimizable. $optimizableTables = array_diff($optimizableTables, array_keys($tableMap)); // Walk each connection and check all tables that have been // remapped to it for optimization support. $connectionNames = array_keys(array_flip($tableMap)); foreach ($connectionNames as $connectionName) { $connection = $connectionPool->getConnectionByName($connectionName); $tablesOnConnection = array_keys(array_filter( $tableMap, function ($value) use ($connectionName) { return $value === $connectionName; } )); $tables = $this->getOptimizableTablesForConnection($connection, $tablesOnConnection); $optimizableTables = array_merge($optimizableTables, $tables); } } sort($optimizableTables); return $optimizableTables; } /** * Retrieve all optimizable tables for a connection, optionally restricted to the subset * of table names in the $tableNames array. * * @param Connection $connection * @param array $tableNames * @return array */ protected function getOptimizableTablesForConnection(Connection $connection, array $tableNames = []): array { // Return empty list if the database platform is not MySQL if (strpos($connection->getServerVersion(), 'MySQL') !== 0) { return []; } // Retrieve all tables from the MySQL informaation schema that have an engine type // that supports the OPTIMIZE TABLE command. $queryBuilder = $connection->createQueryBuilder(); $queryBuilder->select('TABLE_NAME AS Table', 'ENGINE AS Engine') ->from('information_schema.TABLES') ->where( $queryBuilder->expr()->eq( 'TABLE_TYPE', $queryBuilder->createNamedParameter('BASE TABLE', \PDO::PARAM_STR) ), $queryBuilder->expr()->in( 'ENGINE', $queryBuilder->createNamedParameter(['InnoDB', 'MyISAM', 'ARCHIVE'], Connection::PARAM_STR_ARRAY) ), $queryBuilder->expr()->eq( 'TABLE_SCHEMA', $queryBuilder->createNamedParameter($connection->getDatabase(), \PDO::PARAM_STR) ) ); if (!empty($tableNames)) { $queryBuilder->andWhere( $queryBuilder->expr()->in( 'TABLE_NAME', $queryBuilder->createNamedParameter($tableNames, \PDO::PARAM_STR) ) ); } $tables = $queryBuilder->execute()->fetchAll(); return array_column($tables, 'Table'); } }