TYPO3 API  SVNRelease
class.tx_dbal_em.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003  *  Copyright notice
00004  *
00005  *  (c) 2010 Xavier Perseguers <typo3@perseguers.ch>
00006  *  All rights reserved
00007  *
00008  *  This script is part of the TYPO3 project. The TYPO3 project is
00009  *  free software; you can redistribute it and/or modify
00010  *  it under the terms of the GNU General Public License as published by
00011  *  the Free Software Foundation; either version 2 of the License, or
00012  *  (at your option) any later version.
00013  *
00014  *  The GNU General Public License can be found at
00015  *  http://www.gnu.org/copyleft/gpl.html.
00016  *  A copy is found in the textfile GPL.txt and important notices to the license
00017  *  from the author is found in LICENSE.txt distributed with these scripts.
00018  *
00019  *
00020  *  This script is distributed in the hope that it will be useful,
00021  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00022  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00023  *  GNU General Public License for more details.
00024  *
00025  *  This copyright notice MUST APPEAR in all copies of the script!
00026  ***************************************************************/
00027 
00028 /**
00029  * Hooks for TYPO3 Extension Manager.
00030  *
00031  * $Id: class.tx_dbal_em.php 40828 2010-12-05 14:55:53Z xperseguers $
00032  *
00033  * @author Xavier Perseguers <typo3@perseguers.ch>
00034  *
00035  * @package TYPO3
00036  * @subpackage dbal
00037  */
00038 class tx_dbal_em implements tx_em_Index_CheckDatabaseUpdatesHook {
00039 
00040     /**
00041      * Maximal length for an identifier in Oracle.
00042      *
00043      * @var integer
00044      */
00045     protected $maxIdentifierLength = 30;
00046 
00047     /**
00048      * Table names should be short enough in order to let ADOdb generates
00049      * the corresponding sequence for the auto-increment field 'uid'.
00050      * That is, a sequence of the form {table}_uid
00051      *
00052      * @var integer
00053      */
00054     protected $tableNameCharacterReservation = 4;
00055 
00056     /**
00057      * Mapping of table and field names.
00058      *
00059      * @var array
00060      */
00061     protected $mapping;
00062 
00063     /**
00064      * Initializes internal variables.
00065      *
00066      * @return void
00067      */
00068     protected function init() {
00069         $mapping = @$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['dbal']['mapping'];
00070         if (!$mapping) {
00071             $mapping = array();
00072         }
00073         $this->mapping = $mapping;
00074     }
00075 
00076     /**
00077      * Hook that allows pre-processing of database structure modifications.
00078      * This returns a user form that will temporarily replace the standard
00079      * database update form to let user configure mapping.
00080      *
00081      * @param string $extKey: Extension key
00082      * @param array $extInfo: Extension information array
00083      * @param array $diff: Database differences
00084      * @param t3lib_install $instObj: Instance of the installer
00085      * @param SC_mod_tools_em_index $parent: The calling parent object
00086      * @return string Either empty string or a pre-processing user form
00087      */
00088     public function preProcessDatabaseUpdates($extKey, array $extInfo, array $diff, t3lib_install $instObj, SC_mod_tools_em_index $parent) {
00089         $content = '';
00090 
00091         // Remapping is only mandatory for Oracle:
00092         if ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['dbal']['handlerCfg']['_DEFAULT']['type'] !== 'adodb'
00093                 && $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['dbal']['handlerCfg']['_DEFAULT']['config']['driver'] !== 'oci8') {
00094 
00095             // Not using Oracle
00096             return '';
00097         }
00098 
00099         $this->init();
00100 
00101         if (t3lib_div::GPvar('dbal')) {
00102             $this->updateMapping(t3lib_div::GPvar('dbal'), $instObj);
00103         }
00104 
00105         // Search all table and field names which should be remapped
00106         $tableCandidates = array();
00107         $fieldsCandidates = array();
00108         foreach ($diff['extra'] as $table => $config) {
00109             if ($this->needsMapping($table)) {
00110                 $tableCandidates[] = $table;
00111             }
00112             foreach ($config['fields'] as $field => $type) {
00113                 if ($this->needsMapping($table, $field)) {
00114                     if (!isset($fieldsCandidates[$table])) {
00115                         $fieldsCandidates[$table] = array();
00116                     }
00117                     $fieldsCandidates[$table][$field] = array(
00118                         'fullName' => $field,
00119                     );
00120                 }
00121 
00122             }
00123 
00124             /*
00125             if (!isset($config['keys'])) {
00126                 continue;   // Process next table
00127             }
00128 
00129             foreach ($config['keys'] as $field => $def) {
00130                 if ($field !== 'PRIMARY' && $this->needsMapping($table, $field, TRUE)) {
00131                     if (!t3lib_div::inArray($tableCandidates, $table)) {
00132                         $tableCandidates[] = $table;
00133                     }
00134                     if (!isset($fieldsCandidates[$table])) {
00135                         $fieldsCandidates[$table] = array();
00136                     }
00137                     $fieldsCandidates[$table][$field] = array(
00138                         'fullName' => $table . '_' . $field,
00139                     );
00140                 }
00141             }
00142             */
00143         }
00144 
00145         if ($tableCandidates || $fieldsCandidates) {
00146             $mappingSuggestions = $this->getMappingSuggestions($extKey, $extInfo, $tableCandidates, $fieldsCandidates);
00147             $content .= $this->generateMappingForm($tableCandidates, $fieldsCandidates, $mappingSuggestions);
00148         }
00149 
00150         return $content;
00151     }
00152 
00153     /**
00154      * Returns TRUE if either the table or the field name should be remapped.
00155      *
00156      * @param string $table
00157      * @param string $field
00158      * @param boolean $isKeyField
00159      * @return boolean TRUE if mapping is needed, otherwise FALSE
00160      */
00161     protected function needsMapping($table, $field = '', $isKeyField = FALSE) {
00162         $needsRemapping = FALSE;
00163 
00164         // Take existing DBAL mapping into account
00165         $origTable = $table;
00166         if (isset($this->mapping[$origTable])) {
00167             if (isset($this->mapping[$origTable]['mapTableName'])) {
00168                 $table = $this->mapping[$origTable]['mapTableName'];
00169             }
00170             if ($field !== '' && isset($this->mapping[$origTable]['mapFieldNames'])) {
00171                 if (isset($this->mapping[$origTable]['mapFieldNames'][$field])) {
00172                     $field = $this->mapping[$origTable]['mapFieldNames'][$field];
00173                 }
00174             }
00175         }
00176 
00177         if ($field === '') {
00178             if (substr($table, -3) === '_mm') {
00179                 $needsRemapping = strlen($table) > $this->maxIdentifierLength;
00180             } else {
00181                 $needsRemapping = strlen($table) > $this->maxIdentifierLength - $this->tableNameCharacterReservation;
00182             }
00183         } elseif (!$isKeyField) {
00184             $needsRemapping = strlen($field) > $this->maxIdentifierLength;
00185         } else {
00186             $needsRemapping = strlen($table . '_' . $field) > $this->maxIdentifierLength;
00187         }
00188 
00189         return $needsRemapping;
00190     }
00191 
00192     /**
00193      * Returns suggestions for the mapping of table/field names.
00194      *
00195      * @param string $extKey
00196      * @param array $extInfo
00197      * @param array $tables
00198      * @param array $fields
00199      * @return array
00200      * @api
00201      */
00202     public function getMappingSuggestions($extKey, array $extInfo, array $tables, array $fields) {
00203         $suggestions = array();
00204         switch ($extKey) {
00205             case 'direct_mail':
00206                 $suggestions['sys_dmail_ttaddress_category_mm'] = array(
00207                     'mapTableName' => 'sys_dmail_ttaddress_cat_mm',
00208                 );
00209                 $suggestions['sys_dmail_ttcontent_category_mm'] = array(
00210                     'mapTableName' => 'sys_dmail_ttcontent_cat_mm',
00211                 );
00212                 break;
00213             case 'extbase':
00214                 $suggestions['tx_extbase_cache_reflection_tags'] = array(
00215                     'mapTableName' => 'tx_extbase_cache_reflect_tags',
00216                 );
00217                 break;
00218             case 'templavoila':
00219                 $suggestions['tx_templavoila_datastructure'] = array(
00220                     'mapTableName' => 'tx_templavoila_ds',
00221                 );
00222                 $suggestions['tx_templavoila_tmplobj'] = array(
00223                     'mapTableName' => 'tx_templavoila_tmpl',
00224                 );
00225                 break;
00226             default:
00227                 $dependencies = array_keys($extInfo['EM_CONF']['constraints']['depends']);
00228                 if (t3lib_div::inArray($dependencies, 'extbase')) {
00229                     $this->storeExtbaseMappingSuggestions($suggestions, $extKey, $extInfo, $tables, $fields);
00230                 }
00231         }
00232 
00233         // Existing mapping take precedence over suggestions
00234         $suggestions = t3lib_div::array_merge_recursive_overrule($suggestions, $this->mapping);
00235 
00236         return $suggestions;
00237     }
00238 
00239     /**
00240      * Stores suggestions for the mapping of table/field names for an Extbase-based extension.
00241      *
00242      * @param array &$suggestions
00243      * @param string $extKey
00244      * @param array $extInfo
00245      * @param array $tables
00246      * @param array $fields
00247      * @return void
00248      */
00249     protected function storeExtbaseMappingSuggestions(array &$suggestions, $extKey, array $extInfo, array $tables, array $fields) {
00250         foreach ($tables as $table) {
00251             // Remove the "domain_model" part of the table name
00252             $suggestions[$table] = array(
00253                 'mapTableName' => str_replace('domain_model_', '', $table),
00254             );
00255         }
00256     }
00257 
00258     /**
00259      * Generates a mapping form.
00260      *
00261      * @param array $tables
00262      * @param array $fields
00263      * @param array $suggestions
00264      * @return string
00265      */
00266     protected function generateMappingForm(array $tables, array $fields, array $suggestions) {
00267         $out = array();
00268         $tableId = uniqid('table');
00269         $label = 'DBAL Mapping';
00270         $description = sprintf('Some table names are longer than %s characters and/or some field names are longer than %s characters.'
00271                 . ' This is incompatible with your database:'
00272                 . ' <ul style="list-style: square; margin: 3px 1em; padding: 3px 1em;">'
00273                 . '     <li>Table names should be short enough to let ADOdb generates a sequence of the form {table}_uid for the'
00274                 . '         auto-increment "uid" field within %s characters;</li>'
00275                 . '     <li>Field names may not contain more than %s characters.</li>'
00276                 . ' </ul>',
00277                 $this->maxIdentifierLength - $this->tableNameCharacterReservation,
00278             $this->maxIdentifierLength,
00279             $this->maxIdentifierLength,
00280             $this->maxIdentifierLength
00281         );
00282 
00283         $tables = array_unique(array_merge($tables, array_keys($fields)));
00284         foreach ($tables as $table) {
00285             $newTableName = $table;
00286             if (isset($suggestions[$table]) && isset($suggestions[$table]['mapTableName'])) {
00287                 $newTableName = $suggestions[$table]['mapTableName'];
00288             }
00289             $out[] = '
00290                 <tr>
00291                     <td style="padding-top: 1em;"><label for="table-' . $table . '">' . $table . '</label></td>
00292                     <td style="padding-top: 1em;">=&gt;</td>
00293                     <td style="padding-top: 1em;"><input type="text" size="35" id="table-' . $table . '" name="dbal[tables][' . $table . ']" value="' . $newTableName . '" /> '
00294                     . strlen($newTableName) . ' characters'
00295                     . '</td>
00296                 </tr>';
00297 
00298             if (isset($fields[$table])) {
00299                 foreach ($fields[$table] as $field => $info) {
00300                     $newFieldName = $field;
00301                     if (isset($suggestions[$table]) && isset($suggestions[$table]['mapFieldNames'])) {
00302                         if (isset($suggestions[$table]['mapFieldNames'][$field])) {
00303                             $newFieldName = $suggestions[$table]['mapFieldNames'][$field];
00304                         }
00305                     }
00306                     $newFieldFullName = preg_replace('/^' . $table . '/', $newTableName, $info['fullName']);
00307                     $newFieldFullName = preg_replace('/' . $field . '$/', $newFieldName, $newFieldFullName);
00308                     $out[] = '
00309                         <tr>
00310                             <td>&nbsp;&nbsp;&nbsp;&nbsp;<label for="field-' . $table . '_' . $field . '">' . $field . '</label></td>
00311                             <td>=&gt;</td>
00312                             <td><input type="text" size="35" id="field-' . $table . '_' . $field . '" name="dbal[fields][' . $table . '][' . $field . ']" value="' . $newFieldName . '" /> '
00313                             . ($info['fullname'] !== $field ? strlen($newFieldFullName) . ' characters: ' . $newFieldFullName : '')
00314                             . '</td>
00315                         </tr>';
00316                 }
00317             }
00318         }
00319 
00320         // Compile rows:
00321         $content = '
00322             <!-- Remapping database fields / tables -->
00323             <h3>' . $label . '</h3>
00324             <p>' . $description . '</p>
00325             <table border="0" cellpadding="2" cellspacing="2" id="' . $tableId . '" class="remap-db-table-fields">' . implode('', $out) . '
00326             </table>';
00327 
00328         return $content;
00329     }
00330 
00331     /**
00332      * Updates the mapping in localconf.php according to form input values.
00333      *
00334      * @param array $data
00335      * @param t3lib_install $instObj
00336      * @return void
00337      * @api
00338      */
00339     public function updateMapping(array $data, t3lib_install $instObj) {
00340         $newMapping = $this->mapping;
00341 
00342         foreach ($data['tables'] as $table => $newName) {
00343             $newName = trim($newName);
00344             if ($newName && $newName !== $table) {
00345                 if (!isset($newMapping[$table])) {
00346                     $newMapping[$table] = array();
00347                 }
00348                 $newMapping[$table]['mapTableName'] = $newName;
00349             }
00350             if (isset($data['fields'][$table])) {
00351                 foreach ($data['fields'][$table] as $field => $newName) {
00352                     $newName = trim($newName);
00353                     if ($newName && $newName !== $field) {
00354                         if (!isset($newMapping[$table])) {
00355                             $newMapping[$table] = array();
00356                         }
00357                         if (!isset($newMapping[$table]['mapFieldNames'])) {
00358                             $newMapping[$table]['mapFieldNames'] = array();
00359                         }
00360                         $newMapping[$table]['mapFieldNames'][$field] = $newName;
00361                     }
00362                 }
00363             }
00364         }
00365 
00366         // Sort table and field names
00367         foreach ($newMapping as $table => &$config) {
00368             if (isset($config['mapFieldNames'])) {
00369                 ksort($config['mapFieldNames']);
00370             }
00371         }
00372         ksort($newMapping);
00373 
00374         // Write new mapping to localconf.php
00375         $key = '$TYPO3_CONF_VARS[\'EXTCONF\'][\'dbal\'][\'mapping\']';
00376         $instObj->allowUpdateLocalConf = 1;
00377         $instObj->updateIdentity = 'TYPO3 Extension Manager';
00378         $lines = $instObj->writeToLocalconf_control();
00379         $instObj->setArrayValueInLocalconfFile($lines, $key, $newMapping);
00380 
00381         if ($instObj->writeToLocalconf($lines)) {
00382             $this->mapping = $newMapping;
00383         }
00384     }
00385 
00386 }
00387 
00388 
00389 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/dbal/class.tx_dbal_em.php'])) {
00390     include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/dbal/class.tx_dbal_em.php']);
00391 }
00392 ?>