class.t3lib_install.php

Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003 *  Copyright notice
00004 *
00005 *  (c) 1999-2008 Kasper Skaarhoj (kasperYYYY@typo3.com)
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  * Class to setup values in localconf.php and verify the TYPO3 DB tables/fields
00029  *
00030  * $Id: class.t3lib_install.php 4459 2008-11-12 18:40:28Z ohader $
00031  *
00032  * @author  Kasper Skaarhoj <kasperYYYY@typo3.com>
00033  */
00034 /**
00035  * [CLASS/FUNCTION INDEX of SCRIPT]
00036  *
00037  *
00038  *
00039  *   83: class t3lib_install
00040  *  108:     function t3lib_install()
00041  *
00042  *              SECTION: Writing to localconf.php
00043  *  132:     function setValueInLocalconfFile(&$line_array, $variable, $value)
00044  *  183:     function writeToLocalconf_control($inlines='',$absFullPath='')
00045  *  253:     function checkForBadString($string)
00046  *  266:     function slashValueForSingleDashes($value)
00047  *
00048  *              SECTION: SQL
00049  *  291:     function getFieldDefinitions_fileContent($fileContent)
00050  *  359:     function getFieldDefinitions_sqlContent_parseTypes(&$total)
00051  *  406:     function getFieldDefinitions_database()
00052  *  450:     function getDatabaseExtra($FDsrc, $FDcomp, $onlyTableList='')
00053  *  496:     function getUpdateSuggestions($diffArr,$keyList='extra,diff')
00054  *  589:     function assembleFieldDefinition($row)
00055  *  611:     function getStatementArray($sqlcode,$removeNonSQL=0,$query_regex='')
00056  *  649:     function getCreateTables($statements, $insertCountFlag=0)
00057  *  683:     function getTableInsertStatements($statements, $table)
00058  *  704:     function performUpdateQueries($arr,$keyArr)
00059  *  720:     function getListOfTables()
00060  *  736:     function generateUpdateDatabaseForm_checkboxes($arr,$label,$checked=1,$iconDis=0,$currentValue=array(),$cVfullMsg=0)
00061  *
00062  * TOTAL FUNCTIONS: 17
00063  * (This index is automatically created/updated by the extension "extdeveval")
00064  *
00065  */
00066 
00067 
00068 
00069 
00070 
00071 
00072 
00073 
00074 require_once(PATH_t3lib.'class.t3lib_sqlparser.php');
00075 
00076 /**
00077  * Class to setup values in localconf.php and verify the TYPO3 DB tables/fields
00078  *
00079  * @author  Kasper Skaarhoj <kasperYYYY@typo3.com>
00080  * @package TYPO3
00081  * @subpackage t3lib
00082  */
00083 class t3lib_install {
00084 
00085 
00086         // External, Static
00087     var $updateIdentity = '';                   // Set to string which identifies the script using this class.
00088     var $deletedPrefixKey = 'zzz_deleted_';     // Prefix used for tables/fields when deleted/renamed.
00089     var $dbUpdateCheckboxPrefix = 'TYPO3_INSTALL[database_update]'; // Prefix for checkbox fields when updating database.
00090     var $localconf_addLinesOnly = 0;            // If this is set, modifications to localconf.php is done by adding new lines to the array only. If unset, existing values are recognized and changed.
00091     var $localconf_editPointToken = 'INSTALL SCRIPT EDIT POINT TOKEN - all lines after this points may be changed by the install script!';      // If set and addLinesOnly is disabled, lines will be change only if they are after this token (on a single line!) in the file
00092     var $allowUpdateLocalConf = 0;      // If true, this class will allow the user to update the localconf.php file. Is set true in the init.php file.
00093     var $backPath = '../';              // Backpath (used for icons etc.)
00094 
00095     var $multiplySize = 1;              // Multiplier of SQL field size (for char, varchar and text fields)
00096     var $character_sets = array();          // Caching output of $GLOBALS['TYPO3_DB']->admin_get_charsets()
00097 
00098         // Internal, dynamic:
00099     var $setLocalconf = 0;              // Used to indicate that a value is change in the line-array of localconf and that it should be written.
00100     var $messages = array();            // Used to set (error)messages from the executing functions like mail-sending, writing Localconf and such
00101     var $touchedLine = 0;               // updated with line in localconf.php file that was changed.
00102 
00103 
00104     /**
00105      * Constructor function
00106      *
00107      * @return  void
00108      */
00109     function t3lib_install()    {
00110         if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['multiplyDBfieldSize']>= 1 && $GLOBALS['TYPO3_CONF_VARS']['SYS']['multiplyDBfieldSize']<=5)  {
00111             $this->multiplySize = (double)$GLOBALS['TYPO3_CONF_VARS']['SYS']['multiplyDBfieldSize'];
00112         }
00113     }
00114 
00115 
00116 
00117     /**************************************
00118      *
00119      * Writing to localconf.php
00120      *
00121 
00122      **************************************/
00123 
00124     /**
00125      * This functions takes an array with lines from localconf.php, finds a variable and inserts the new value.
00126      *
00127      * @param   array       $line_array the localconf.php file exploded into an array by linebreaks. (see writeToLocalconf_control())
00128      * @param   string      $variable   The variable name to find and substitute. This string must match the first part of a trimmed line in the line-array. Matching is done backwards so the last appearing line will be substituted.
00129      * @param   string      $value      Is the value to be insert for the variable
00130      * @return  void
00131      * @see writeToLocalconf_control()
00132      */
00133     function setValueInLocalconfFile(&$line_array, $variable, $value)   {
00134         if (!$this->checkForBadString($value))  return 0;
00135 
00136             // Initialize:
00137         $found = 0;
00138         $this->touchedLine = '';
00139         $commentKey = '## ';
00140         $inArray = in_array($commentKey.$this->localconf_editPointToken,$line_array);
00141         $tokenSet = ($this->localconf_editPointToken && !$inArray);     // Flag is set if the token should be set but is not yet...
00142         $stopAtToken = ($this->localconf_editPointToken && $inArray);
00143         $comment = ' Modified or inserted by '.$this->updateIdentity.'.';
00144 
00145             // Search for variable name:
00146         if (!$this->localconf_addLinesOnly && !$tokenSet)   {
00147             $line_array = array_reverse($line_array);
00148             foreach($line_array as $k => $v)    {
00149                 $v2 = trim($v);
00150                 if ($stopAtToken && !strcmp($v2,$commentKey.$this->localconf_editPointToken))   break;      // If stopAtToken and token found, break out of the loop..
00151                 if (!strcmp(substr($v2,0,strlen($variable.' ')),$variable.' ')) {
00152                     $mainparts = explode($variable,$v,2);
00153                     if (count($mainparts)==2)   {   // should ALWAYS be....
00154                         $subparts = explode('//',$mainparts[1],2);
00155                         $line_array[$k] = $mainparts[0].$variable." = '".$this->slashValueForSingleDashes($value)."';   ".('//'.$comment.str_replace($comment,'',$subparts[1]));
00156                         $this->touchedLine = count($line_array)-$k-1;
00157                         $found = 1;
00158                         break;
00159                     }
00160                 }
00161             }
00162             $line_array = array_reverse($line_array);
00163         }
00164         if (!$found)    {
00165             if ($tokenSet)      {
00166                 $line_array[] = $commentKey.$this->localconf_editPointToken;
00167                 $line_array[] = '';
00168             }
00169             $line_array[] = $variable." = '".$this->slashValueForSingleDashes($value)."';   // ".$comment;
00170             $this->touchedLine = -1;
00171         }
00172         $this->messages[] = $variable." = '".htmlspecialchars($value)."'";
00173         $this->setLocalconf = 1;
00174     }
00175 
00176     /**
00177      * Writes or returns lines from localconf.php
00178      *
00179      * @param   array       Array of lines to write back to localconf.php. Possibly
00180      * @param   string      Absolute path of alternative file to use (Notice: this path is not validated in terms of being inside 'TYPO3 space')
00181      * @return  mixed       If $inlines is not an array it will return an array with the lines from localconf.php. Otherwise it will return a status string, either "continue" (updated) or "nochange" (not updated)
00182      * @see setValueInLocalconfFile()
00183      */
00184     function writeToLocalconf_control($inlines='',$absFullPath='')  {
00185         $tmpExt = '.TMP.php';
00186         $writeToLocalconf_dat['file'] = $absFullPath ? $absFullPath : PATH_typo3conf.'localconf.php';
00187         $writeToLocalconf_dat['tmpfile'] = $writeToLocalconf_dat['file'].$tmpExt;
00188 
00189             // Checking write state of localconf.php:
00190         if (!$this->allowUpdateLocalConf)   {
00191             die('->allowUpdateLocalConf flag in the install object is not set and therefore "localconf.php" cannot be altered.');
00192         }
00193         if (!@is_writable($writeToLocalconf_dat['file']))   {
00194             die($writeToLocalconf_dat['file'].' is not writable!');
00195         }
00196 
00197                 // Splitting localconf.php file into lines:
00198         $lines = explode(chr(10),str_replace(chr(13),'',trim(t3lib_div::getUrl($writeToLocalconf_dat['file']))));
00199         $writeToLocalconf_dat['endLine'] = array_pop($lines);   // Getting "? >" ending.
00200 
00201             // Checking if "updated" line was set by this tool - if so remove old line.
00202         $updatedLine = array_pop($lines);
00203         $writeToLocalconf_dat['updatedText'] = '// Updated by '.$this->updateIdentity.' ';
00204 
00205         if (!strstr($updatedLine, $writeToLocalconf_dat['updatedText']))    {
00206             array_push($lines,$updatedLine);
00207         }
00208 
00209         if (is_array($inlines)) {   // Setting a line and write:
00210                 // Setting configuration
00211             $updatedLine = $writeToLocalconf_dat['updatedText'].date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'].' H:i:s');
00212             array_push($inlines,$updatedLine);
00213             array_push($inlines,$writeToLocalconf_dat['endLine']);
00214 
00215             if ($this->setLocalconf)    {
00216                 $success = FALSE;
00217                 if (!t3lib_div::writeFile($writeToLocalconf_dat['tmpfile'],implode(chr(10),$inlines)))  {
00218                     $msg = 'typo3conf/localconf.php'.$tmpExt.' could not be written - maybe a write access problem?';
00219                 }
00220                 elseif (strcmp(t3lib_div::getUrl($writeToLocalconf_dat['tmpfile']), implode(chr(10),$inlines))) {
00221                     @unlink($writeToLocalconf_dat['tmpfile']);
00222                     $msg = 'typo3conf/localconf.php'.$tmpExt.' was NOT written properly (written content didn\'t match file content) - maybe a disk space problem?';
00223                 }
00224                 elseif (!@copy($writeToLocalconf_dat['tmpfile'],$writeToLocalconf_dat['file'])) {
00225                     $msg = 'typo3conf/localconf.php could not be replaced by typo3conf/localconf.php'.$tmpExt.' - maybe a write access problem?';
00226                 }
00227                 else {
00228                     @unlink($writeToLocalconf_dat['tmpfile']);
00229                     $success = TRUE;
00230                     $msg = 'Configuration written to typo3conf/localconf.php';
00231                 }
00232                 $this->messages[] = $msg;
00233 
00234                 if ($success)   {
00235                     return 'continue';
00236                 } else {
00237                     t3lib_div::sysLog($msg, 'Core', 3);
00238                     return 'nochange';
00239                 }
00240             } else {
00241                 return 'nochange';
00242             }
00243         } else {    // Return lines found in localconf.php
00244             return $lines;
00245         }
00246     }
00247 
00248     /**
00249      * Checking for linebreaks in the string
00250      *
00251      * @param   string      String to test
00252      * @return  boolean     Returns TRUE if string is OK
00253      * @see setValueInLocalconfFile()
00254      */
00255     function checkForBadString($string) {
00256         return preg_match('/['.chr(10).chr(13).']/',$string) ? FALSE : TRUE;
00257     }
00258 
00259     /**
00260      * Replaces ' with \' and \ with \\
00261      *
00262      * @param   string      Input value
00263      * @return  string      Output value
00264      * @see setValueInLocalconfFile()
00265      */
00266     function slashValueForSingleDashes($value)  {
00267         $value = str_replace("'.chr(10).'", '###INSTALL_TOOL_LINEBREAK###', $value);
00268         $value = str_replace("'","\'",str_replace('\\','\\\\',$value));
00269         $value = str_replace('###INSTALL_TOOL_LINEBREAK###', "'.chr(10).'", $value);
00270 
00271         return $value;
00272     }
00273 
00274 
00275 
00276 
00277 
00278 
00279 
00280 
00281 
00282 
00283     /*************************************
00284      *
00285      * SQL
00286      *
00287      *************************************/
00288 
00289     /**
00290      * Reads the field definitions for the input SQL-file string
00291      *
00292      * @param   string      Should be a string read from an SQL-file made with 'mysqldump [database_name] -d'
00293      * @return  array       Array with information about table.
00294      */
00295     function getFieldDefinitions_fileContent($fileContent)  {
00296         $lines = t3lib_div::trimExplode(chr(10), $fileContent, 1);
00297         $table = '';
00298         $total = array();
00299 
00300         foreach ($lines as $value) {
00301             if (substr($value,0,1)=='#') {
00302                 continue;   // Ignore comments
00303             }
00304 
00305             if (!strlen($table)) {
00306                 $parts = explode(' ',$value);
00307                 if ($parts[0]=='CREATE' && $parts[1]=='TABLE')  {
00308                     $table = str_replace( '`', '', $parts[2]);
00309                     if (TYPO3_OS=='WIN') {  // tablenames are always lowercase on windows!
00310                         $table = strtolower($table);
00311                     }
00312                 }
00313             } else {
00314                 if (substr($value,0,1)==')' && substr($value,-1)==';')  {
00315                     $ttype = array();
00316                     if (preg_match('/(ENGINE|TYPE)[ ]*=[ ]*([a-zA-Z]*)/',$value,$ttype)) {
00317                         $total[$table]['extra']['ENGINE'] = $ttype[2];
00318                     } // Otherwise, just do nothing: If table engine is not defined, just accept the system default.
00319 
00320                         // Set the collation, if specified
00321                     if (preg_match('/(COLLATE)[ ]*=[ ]*([a-zA-z0-9_-]+)/', $value, $tcollation)) {
00322                         $total[$table]['extra']['COLLATE'] = $tcollation[2];
00323                     } else {
00324                             // Otherwise, get the CHARACTER SET and try to find the default collation for it as returned by "SHOW CHARACTER SET" query (for details, see http://dev.mysql.com/doc/refman/5.1/en/charset-table.html)
00325                         if (preg_match('/(CHARSET|CHARACTER SET)[ ]*=[ ]*([a-zA-z0-9_-]+)/', $value, $tcharset)) {  // Note: Keywords "DEFAULT CHARSET" and "CHARSET" are the same, so "DEFAULT" can just be ignored
00326                             $charset = $tcharset[2];
00327                         } else {
00328                             $charset = $GLOBALS['TYPO3_DB']->default_charset;   // Fallback to default charset
00329                         }
00330                         $total[$table]['extra']['COLLATE'] = $this->getCollationForCharset($charset);
00331                     }
00332 
00333                     $table = '';    // Remove table marker and start looking for the next "CREATE TABLE" statement
00334                 } else {
00335                     $lineV = preg_replace('/,$/','',$value);    // Strip trailing commas
00336                     $lineV = str_replace('`', '', $lineV);
00337                     $lineV = str_replace('  ', ' ', $lineV);    // Remove double blanks
00338 
00339                     $parts = explode(' ', $lineV, 2);
00340                     if (!preg_match('/(PRIMARY|UNIQUE|FULLTEXT|INDEX|KEY)/',$parts[0])) {   // Field definition
00341 
00342                             // Make sure there is no default value when auto_increment is set
00343                         if (stristr($parts[1],'auto_increment')) {
00344                             $parts[1] = preg_replace('/ default \'0\'/i','',$parts[1]);
00345                         }
00346                             // "default" is always lower-case
00347                         if (stristr($parts[1], ' DEFAULT '))    {
00348                             $parts[1] = str_ireplace(' DEFAULT ', ' default ', $parts[1]);
00349                         }
00350 
00351                             // Change order of "default" and "null" statements
00352                         $parts[1] = preg_replace('/(.*) (default .*) (NOT NULL)/', '$1 $3 $2', $parts[1]);
00353                         $parts[1] = preg_replace('/(.*) (default .*) (NULL)/', '$1 $3 $2', $parts[1]);
00354 
00355                         $key = $parts[0];
00356                         $total[$table]['fields'][$key] = $parts[1];
00357 
00358                     } else {    // Key definition
00359                         $search = array('/UNIQUE (INDEX|KEY)/', '/FULLTEXT (INDEX|KEY)/', '/INDEX/');
00360                         $replace = array('UNIQUE', 'FULLTEXT', 'KEY');
00361                         $lineV = preg_replace($search, $replace, $lineV);
00362 
00363                         if (preg_match('/PRIMARY|UNIQUE|FULLTEXT/', $parts[0])) {
00364                             $parts[1] = preg_replace('/^(KEY|INDEX) /', '', $parts[1]);
00365                         }
00366 
00367                         $newParts = explode(' ',$parts[1],2);
00368                         $key = $parts[0]=='PRIMARY' ? $parts[0] : $newParts[0];
00369 
00370                         $total[$table]['keys'][$key] = $lineV;
00371 
00372                             // This is a protection against doing something stupid: Only allow clearing of cache_* and index_* tables.
00373                         if (preg_match('/^(cache|index)_/',$table)) {
00374                                 // Suggest to truncate (clear) this table
00375                             $total[$table]['extra']['CLEAR'] = 1;
00376                         }
00377                     }
00378                 }
00379             }
00380         }
00381 
00382         $this->getFieldDefinitions_sqlContent_parseTypes($total);
00383         return $total;
00384     }
00385 
00386     /**
00387      * Multiplies varchars/tinytext fields in size according to $this->multiplySize
00388      * Useful if you want to use UTF-8 in the database and needs to extend the field sizes in the database so UTF-8 chars are not discarded. For most charsets available as single byte sets, multiplication with 2 should be enough. For chinese, use 3.
00389      *
00390      * @param   array       Total array (from getFieldDefinitions_fileContent())
00391      * @return  void
00392      * @access private
00393      * @see getFieldDefinitions_fileContent()
00394      */
00395     function getFieldDefinitions_sqlContent_parseTypes(&$total) {
00396 
00397         $mSize = (double)$this->multiplySize;
00398         if ($mSize > 1) {
00399 
00400                 // Init SQL parser:
00401             $sqlParser = t3lib_div::makeInstance('t3lib_sqlparser');
00402             foreach($total as $table => $cfg)   {
00403                 if (is_array($cfg['fields'])) {
00404                     foreach($cfg['fields'] as $fN => $fType)    {
00405                         $orig_fType = $fType;
00406                         $fInfo = $sqlParser->parseFieldDef($fType);
00407 
00408                         switch($fInfo['fieldType']) {
00409                             case 'char':
00410                             case 'varchar':
00411                                 $newSize = round($fInfo['value']*$mSize);
00412 
00413                                 if ($newSize <= 255)    {
00414                                     $fInfo['value'] = $newSize;
00415                                 } else {
00416                                     $fInfo = array(
00417                                         'fieldType' => 'text',
00418                                         'featureIndex' => array(
00419                                             'NOTNULL' => array(
00420                                                 'keyword' => 'NOT NULL'
00421                                             )
00422                                         )
00423                                     );
00424                                         // Change key definition if necessary (must use "prefix" on TEXT columns)
00425                                     if (is_array($cfg['keys'])) {
00426                                         foreach ($cfg['keys'] as $kN => $kType) {
00427                                             $match = array();
00428                                             preg_match('/^([^(]*)\(([^)]+)\)(.*)/', $kType, $match);
00429                                             $keys = array();
00430                                             foreach (t3lib_div::trimExplode(',',$match[2]) as $kfN) {
00431                                                 if ($fN == $kfN)    {
00432                                                     $kfN .= '('.$newSize.')';
00433                                                 }
00434                                                 $keys[] = $kfN;
00435                                             }
00436                                             $total[$table]['keys'][$kN] = $match[1].'('.implode(',',$keys).')'.$match[3];
00437                                         }
00438                                     }
00439                                 }
00440                             break;
00441                             case 'tinytext':
00442                                 $fInfo['fieldType'] = 'text';
00443                             break;
00444                         }
00445 
00446                         $total[$table]['fields'][$fN] = $sqlParser->compileFieldCfg($fInfo);
00447                         if ($sqlParser->parse_error)    die($sqlParser->parse_error);
00448                     }
00449                 }
00450             }
00451         }
00452     }
00453 
00454     /**
00455      * Look up the default collation for specified character set based on "SHOW CHARACTER SET" output
00456      *
00457      * @param   string      Character set
00458      * @return  string      Corresponding default collation
00459      */
00460     function getCollationForCharset($charset)   {
00461             // Load character sets, if not cached already
00462         if (!count($this->character_sets)) {
00463             $this->character_sets = $GLOBALS['TYPO3_DB']->admin_get_charsets();
00464         }
00465 
00466         if (isset($this->character_sets[$charset]['Default collation'])) {
00467             $collation = $this->character_sets[$charset]['Default collation'];
00468         }
00469 
00470         return $collation;
00471     }
00472 
00473     /**
00474      * Reads the field definitions for the current database
00475      *
00476      * @return  array       Array with information about table.
00477      */
00478     function getFieldDefinitions_database() {
00479         $total = array();
00480         $tempKeys = array();
00481         $tempKeysPrefix = array();
00482 
00483         $GLOBALS['TYPO3_DB']->sql_select_db(TYPO3_db);
00484         echo $GLOBALS['TYPO3_DB']->sql_error();
00485 
00486         $tables = $GLOBALS['TYPO3_DB']->admin_get_tables(TYPO3_db);
00487         foreach ($tables as $tableName => $tableStatus) {
00488 
00489                 // Fields:
00490             $fieldInformation = $GLOBALS['TYPO3_DB']->admin_get_fields($tableName);
00491             foreach ($fieldInformation as $fN => $fieldRow) {
00492                 $total[$tableName]['fields'][$fN] = $this->assembleFieldDefinition($fieldRow);
00493             }
00494 
00495                 // Keys:
00496             $keyInformation = $GLOBALS['TYPO3_DB']->admin_get_keys($tableName);
00497 
00498             foreach ($keyInformation as $keyRow) {
00499                 $keyName = $keyRow['Key_name'];
00500                 $colName = $keyRow['Column_name'];
00501                 if ($keyRow['Sub_part']) {
00502                     $colName.= '('.$keyRow['Sub_part'].')';
00503                 }
00504                 $tempKeys[$tableName][$keyName][$keyRow['Seq_in_index']] = $colName;
00505                 if ($keyName=='PRIMARY') {
00506                     $prefix = 'PRIMARY KEY';
00507                 } else {
00508                     if ($keyRow['Index_type']=='FULLTEXT') {
00509                         $prefix = 'FULLTEXT';
00510                     } elseif ($keyRow['Non_unique']) {
00511                         $prefix = 'KEY';
00512                     } else {
00513                         $prefix = 'UNIQUE';
00514                     }
00515                     $prefix.= ' '.$keyName;
00516                 }
00517                 $tempKeysPrefix[$tableName][$keyName] = $prefix;
00518             }
00519 
00520                 // Table status (storage engine, collaction, etc.)
00521             if (is_array($tableStatus)) {
00522                 $tableExtraFields = array(
00523                     'Engine' => 'ENGINE',
00524                     'Collation' => 'COLLATE',
00525                 );
00526 
00527                 foreach ($tableExtraFields as $mysqlKey=>$internalKey) {
00528                     if (isset($tableStatus[$mysqlKey])) {
00529                         $total[$tableName]['extra'][$internalKey] = $tableStatus[$mysqlKey];
00530                     }
00531                 }
00532             }
00533         }
00534 
00535             // Compile key information:
00536         if (count($tempKeys)) {
00537             foreach ($tempKeys as $table => $keyInf) {
00538                 foreach ($keyInf as $kName => $index) {
00539                     ksort($index);
00540                     $total[$table]['keys'][$kName] = $tempKeysPrefix[$table][$kName].' ('.implode(',',$index).')';
00541                 }
00542             }
00543         }
00544 
00545         return $total;
00546     }
00547 
00548     /**
00549      * Compares two arrays with field information and returns information about fields that are MISSING and fields that have CHANGED.
00550      * FDsrc and FDcomp can be switched if you want the list of stuff to remove rather than update.
00551      *
00552      * @param   array       Field definitions, source (from getFieldDefinitions_fileContent())
00553      * @param   array       Field definitions, comparison. (from getFieldDefinitions_database())
00554      * @param   string      Table names (in list) which is the ONLY one observed.
00555      * @param   boolean     If set, this function ignores NOT NULL statements of the SQL file field definition when comparing current field definition from database with field definition from SQL file. This way, NOT NULL statements will be executed when the field is initially created, but the SQL parser will never complain about missing NOT NULL statements afterwards.
00556      * @return  array       Returns an array with 1) all elements from $FDsrc that is not in $FDcomp (in key 'extra') and 2) all elements from $FDsrc that is different from the ones in $FDcomp
00557      */
00558     function getDatabaseExtra($FDsrc, $FDcomp, $onlyTableList='',$ignoreNotNullWhenComparing=true)  {
00559         $extraArr = array();
00560         $diffArr = array();
00561 
00562         if (is_array($FDsrc)) {
00563             foreach ($FDsrc as $table => $info) {
00564                 if (!strlen($onlyTableList) || t3lib_div::inList($onlyTableList, $table)) {
00565                     if (!isset($FDcomp[$table])) {
00566                         $extraArr[$table] = $info;      // If the table was not in the FDcomp-array, the result array is loaded with that table.
00567                         $extraArr[$table]['whole_table']=1;
00568                     } else {
00569                         $keyTypes = explode(',','extra,fields,keys');
00570                         foreach ($keyTypes as $theKey) {
00571                             if (is_array($info[$theKey])) {
00572                                 foreach ($info[$theKey] as $fieldN => $fieldC) {
00573                                     $fieldN = str_replace('`','',$fieldN);
00574                                     if ($fieldN=='COLLATE') {
00575                                         continue;   // TODO: collation support is currently disabled (needs more testing)
00576                                     }
00577 
00578                                     if (!isset($FDcomp[$table][$theKey][$fieldN])) {
00579                                         $extraArr[$table][$theKey][$fieldN] = $fieldC;
00580                                     } else {
00581                                         $fieldC = trim($fieldC);
00582                                         if ($ignoreNotNullWhenComparing) {
00583                                             $fieldC = str_replace(' NOT NULL', '', $fieldC);
00584                                             $FDcomp[$table][$theKey][$fieldN] = str_replace(' NOT NULL', '', $FDcomp[$table][$theKey][$fieldN]);
00585                                         }
00586                                         if ($fieldC !== $FDcomp[$table][$theKey][$fieldN]) {
00587                                             $diffArr[$table][$theKey][$fieldN] = $fieldC;
00588                                             $diffArr_cur[$table][$theKey][$fieldN] = $FDcomp[$table][$theKey][$fieldN];
00589                                         }
00590                                     }
00591                                 }
00592                             }
00593                         }
00594                     }
00595                 }
00596             }
00597         }
00598 
00599         $output = array(
00600             'extra' => $extraArr,
00601             'diff' => $diffArr,
00602             'diff_currentValues' => $diffArr_cur
00603         );
00604 
00605         return $output;
00606     }
00607 
00608     /**
00609      * Returns an array with SQL-statements that is needed to update according to the diff-array
00610      *
00611      * @param   array       Array with differences of current and needed DB settings. (from getDatabaseExtra())
00612      * @param   string      List of fields in diff array to take notice of.
00613      * @return  array       Array of SQL statements (organized in keys depending on type)
00614      */
00615     function getUpdateSuggestions($diffArr,$keyList='extra,diff')   {
00616         $statements = array();
00617         $deletedPrefixKey = $this->deletedPrefixKey;
00618         $remove = 0;
00619         if ($keyList == 'remove')   {
00620             $remove = 1;
00621             $keyList = 'extra';
00622         }
00623         $keyList = explode(',',$keyList);
00624         foreach ($keyList as $theKey) {
00625             if (is_array($diffArr[$theKey])) {
00626                 foreach ($diffArr[$theKey] as $table => $info) {
00627                     $whole_table = array();
00628                     if (is_array($info['fields'])) {
00629                         foreach ($info['fields'] as $fN => $fV) {
00630                             if ($info['whole_table']) {
00631                                 $whole_table[]=$fN.' '.$fV;
00632                             } else {
00633                                     // Special case to work around MySQL problems when adding auto_increment fields:
00634                                 if (stristr($fV, 'auto_increment')) {
00635                                         // The field can only be set "auto_increment" if there exists a PRIMARY key of that field already.
00636                                         // The check does not look up which field is primary but just assumes it must be the field with the auto_increment value...
00637                                     if (isset($diffArr['extra'][$table]['keys']['PRIMARY'])) {
00638                                             // Remove "auto_increment" from the statement - it will be suggested in a 2nd step after the primary key was created
00639                                         $fV = str_replace(' auto_increment', '', $fV);
00640                                     } else {
00641                                             // In the next step, attempt to clear the table once again (2 = force)
00642                                         $info['extra']['CLEAR'] = 2;
00643                                     }
00644                                 }
00645                                 if ($theKey=='extra') {
00646                                     if ($remove) {
00647                                         if (substr($fN,0,strlen($deletedPrefixKey))!=$deletedPrefixKey) {
00648                                             $statement = 'ALTER TABLE '.$table.' CHANGE '.$fN.' '.$deletedPrefixKey.$fN.' '.$fV.';';
00649                                             $statements['change'][md5($statement)] = $statement;
00650                                         } else {
00651                                             $statement = 'ALTER TABLE '.$table.' DROP '.$fN.';';
00652                                             $statements['drop'][md5($statement)] = $statement;
00653                                         }
00654                                     } else {
00655                                         $statement = 'ALTER TABLE '.$table.' ADD '.$fN.' '.$fV.';';
00656                                         $statements['add'][md5($statement)] = $statement;
00657                                     }
00658                                 } elseif ($theKey=='diff') {
00659                                     $statement = 'ALTER TABLE '.$table.' CHANGE '.$fN.' '.$fN.' '.$fV.';';
00660                                     $statements['change'][md5($statement)] = $statement;
00661                                     $statements['change_currentValue'][md5($statement)] = $diffArr['diff_currentValues'][$table]['fields'][$fN];
00662                                 }
00663                             }
00664                         }
00665                     }
00666                     if (is_array($info['keys'])) {
00667                         foreach ($info['keys'] as $fN => $fV) {
00668                             if ($info['whole_table']) {
00669                                 $whole_table[] = $fV;
00670                             } else {
00671                                 if ($theKey=='extra') {
00672                                     if ($remove) {
00673                                         $statement = 'ALTER TABLE '.$table.($fN=='PRIMARY' ? ' DROP PRIMARY KEY' : ' DROP KEY '.$fN).';';
00674                                         $statements['drop'][md5($statement)] = $statement;
00675                                     } else {
00676                                         $statement = 'ALTER TABLE '.$table.' ADD '.$fV.';';
00677                                         $statements['add'][md5($statement)] = $statement;
00678                                     }
00679                                 } elseif ($theKey=='diff') {
00680                                     $statement = 'ALTER TABLE '.$table.($fN=='PRIMARY' ? ' DROP PRIMARY KEY' : ' DROP KEY '.$fN).';';
00681                                     $statements['change'][md5($statement)] = $statement;
00682                                     $statement = 'ALTER TABLE '.$table.' ADD '.$fV.';';
00683                                     $statements['change'][md5($statement)] = $statement;
00684                                 }
00685                             }
00686                         }
00687                     }
00688                     if (is_array($info['extra'])) {
00689                         $extras = array();
00690                         $extras_currentValue = array();
00691                         $clear_table = false;
00692 
00693                         foreach ($info['extra'] as $fN => $fV) {
00694 
00695                                 // Only consider statements which are missing in the database but don't remove existing properties
00696                             if (!$remove) {
00697                                 if (!$info['whole_table']) {    // If the whole table is created at once, we take care of this later by imploding all elements of $info['extra']
00698                                     if ($fN=='CLEAR') {
00699                                             // Truncate table must happen later, not now
00700                                             // Valid values for CLEAR: 1=only clear if keys are missing, 2=clear anyway (force)
00701                                         if (count($info['keys']) || $fV==2) {
00702                                             $clear_table = true;
00703                                         }
00704                                         continue;
00705                                     } else {
00706                                         $extras[] = $fN.'='.$fV;
00707                                         $extras_currentValue[] = $fN.'='.$diffArr['diff_currentValues'][$table]['extra'][$fN];
00708                                     }
00709                                 }
00710                             }
00711                         }
00712                         if ($clear_table) {
00713                             $statement = 'TRUNCATE TABLE '.$table.';';
00714                             $statements['clear_table'][md5($statement)] = $statement;
00715                         }
00716                         if (count($extras)) {
00717                             $statement = 'ALTER TABLE '.$table.' '.implode(' ',$extras).';';
00718                             $statements['change'][md5($statement)] = $statement;
00719                             $statements['change_currentValue'][md5($statement)] = implode(' ',$extras_currentValue);
00720                         }
00721                     }
00722                     if ($info['whole_table']) {
00723                         if ($remove) {
00724                             if (substr($table,0,strlen($deletedPrefixKey))!=$deletedPrefixKey) {
00725                                 $statement = 'ALTER TABLE '.$table.' RENAME '.$deletedPrefixKey.$table.';';
00726                                 $statements['change_table'][md5($statement)] = $statement;
00727                             } else {
00728                                 $statement = 'DROP TABLE '.$table.';';
00729                                 $statements['drop_table'][md5($statement)] = $statement;
00730                             }
00731                                 // count:
00732                             $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('count(*)', $table, '');
00733                             list($count) = $GLOBALS['TYPO3_DB']->sql_fetch_row($res);
00734                             $statements['tables_count'][md5($statement)] = $count?'Records in table: '.$count:'';
00735                         } else {
00736                             $statement = 'CREATE TABLE '.$table." (\n".implode(",\n",$whole_table)."\n)";
00737                             if ($info['extra']) {
00738                                 foreach ($info['extra'] as $k=>$v) {
00739                                     if ($k=='COLLATE' || $k=='CLEAR') {
00740                                         continue;   // Skip these special statements. TODO: collation support is currently disabled (needs more testing)
00741                                     }
00742                                     $statement.= ' '.$k.'='.$v; // Add extra attributes like ENGINE, CHARSET, etc.
00743                                 }
00744                             }
00745                             $statement.= ';';
00746                             $statements['create_table'][md5($statement)] = $statement;
00747                         }
00748                     }
00749                 }
00750             }
00751         }
00752 
00753         return $statements;
00754     }
00755 
00756     /**
00757      * Converts a result row with field information into the SQL field definition string
00758      *
00759      * @param   array       MySQL result row
00760      * @return  string      Field definition
00761      */
00762     function assembleFieldDefinition($row)  {
00763         $field = array($row['Type']);
00764 
00765         if ($row['Null']=='NO') {
00766             $field[] = 'NOT NULL';
00767         }
00768         if (!strstr($row['Type'],'blob') && !strstr($row['Type'],'text')) {
00769                 // Add a default value if the field is not auto-incremented (these fields never have a default definition)
00770             if (!stristr($row['Extra'],'auto_increment')) {
00771                 $field[] = 'default \''.addslashes($row['Default']).'\'';
00772             }
00773         }
00774         if ($row['Extra']) {
00775             $field[] = $row['Extra'];
00776         }
00777 
00778         return implode(' ',$field);
00779     }
00780 
00781     /**
00782      * Returns an array where every entry is a single SQL-statement. Input must be formatted like an ordinary MySQL-dump files.
00783      *
00784      * @param   string      The SQL-file content. Provided that 1) every query in the input is ended with ';' and that a line in the file contains only one query or a part of a query.
00785      * @param   boolean     If set, non-SQL content (like comments and blank lines) is not included in the final output
00786      * @param   string      Regex to filter SQL lines to include
00787      * @return  array       Array of SQL statements
00788      */
00789     function getStatementArray($sqlcode,$removeNonSQL=0,$query_regex='')    {
00790         $sqlcodeArr = explode(chr(10), $sqlcode);
00791 
00792             // Based on the assumption that the sql-dump has
00793         $statementArray = array();
00794         $statementArrayPointer = 0;
00795 
00796         foreach ($sqlcodeArr as $line => $lineContent) {
00797             $is_set = 0;
00798 
00799                 // auto_increment fields cannot have a default value!
00800             if (stristr($lineContent,'auto_increment')) {
00801                 $lineContent = preg_replace('/ default \'0\'/i', '', $lineContent);
00802             }
00803 
00804             if (!$removeNonSQL || (strcmp(trim($lineContent),'') && substr(trim($lineContent),0,1)!='#' && substr(trim($lineContent),0,2)!='--')) {     // '--' is seen as mysqldump comments from server version 3.23.49
00805                 $statementArray[$statementArrayPointer].= $lineContent;
00806                 $is_set = 1;
00807             }
00808             if (substr(trim($lineContent),-1)==';') {
00809                 if (isset($statementArray[$statementArrayPointer])) {
00810                     if (!trim($statementArray[$statementArrayPointer]) || ($query_regex && !eregi($query_regex,trim($statementArray[$statementArrayPointer])))) {
00811                         unset($statementArray[$statementArrayPointer]);
00812                     }
00813                 }
00814                 $statementArrayPointer++;
00815 
00816             } elseif ($is_set) {
00817                 $statementArray[$statementArrayPointer].= chr(10);
00818             }
00819         }
00820 
00821         return $statementArray;
00822     }
00823 
00824     /**
00825      * Returns tables to create and how many records in each
00826      *
00827      * @param   array       Array of SQL statements to analyse.
00828      * @param   boolean     If set, will count number of INSERT INTO statements following that table definition
00829      * @return  array       Array with table definitions in index 0 and count in index 1
00830      */
00831     function getCreateTables($statements, $insertCountFlag=0) {
00832         $crTables = array();
00833         $insertCount = array();
00834         foreach ($statements as $line => $lineContent) {
00835             $reg = array();
00836             if (eregi('^create[[:space:]]*table[[:space:]]*[`]?([[:alnum:]_]*)[`]?',substr($lineContent,0,100),$reg)) {
00837                 $table = trim($reg[1]);
00838                 if ($table) {
00839                         // table names are always lowercase on Windows!
00840                     if (TYPO3_OS == 'WIN') {
00841                         $table = strtolower($table);
00842                     }
00843                     $sqlLines = explode(chr(10), $lineContent);
00844                     foreach ($sqlLines as $k=>$v) {
00845                         if (stristr($v,'auto_increment')) {
00846                             $sqlLines[$k] = preg_replace('/ default \'0\'/i', '', $v);
00847                         }
00848                     }
00849                     $lineContent = implode(chr(10), $sqlLines);
00850                     $crTables[$table] = $lineContent;
00851                 }
00852             } elseif ($insertCountFlag && eregi('^insert[[:space:]]*into[[:space:]]*[`]?([[:alnum:]_]*)[`]?',substr($lineContent,0,100),$reg)) {
00853                 $nTable = trim($reg[1]);
00854                 $insertCount[$nTable]++;
00855             }
00856         }
00857 
00858         return array($crTables,$insertCount);
00859     }
00860 
00861     /**
00862      * Extracts all insert statements from $statement array where content is inserted into $table
00863      *
00864      * @param   array       Array of SQL statements
00865      * @param   string      Table name
00866      * @return  array       Array of INSERT INTO statements where table match $table
00867      */
00868     function getTableInsertStatements($statements, $table)  {
00869         $outStatements=array();
00870         foreach($statements as $line => $lineContent) {
00871             $reg = array();
00872             if (preg_match('/^insert[[:space:]]*into[[:space:]]*[`]?([[:alnum:]_]*)[`]?/i',substr($lineContent,0,100),$reg)) {
00873                 $nTable = trim($reg[1]);
00874                 if ($nTable && !strcmp($table,$nTable)) {
00875                     $outStatements[] = $lineContent;
00876                 }
00877             }
00878         }
00879         return $outStatements;
00880     }
00881 
00882     /**
00883      * Performs the queries passed from the input array.
00884      *
00885      * @param   array       Array of SQL queries to execute.
00886      * @param   array       Array with keys that must match keys in $arr. Only where a key in this array is set and true will the query be executed (meant to be passed from a form checkbox)
00887      * @return  mixed       Array with error message from database if any occured. Otherwise true if everything was executed successfully.
00888      */
00889     function performUpdateQueries($arr,$keyArr) {
00890         $result = array();
00891         if (is_array($arr)) {
00892             foreach($arr as $key => $string)    {
00893                 if (isset($keyArr[$key]) && $keyArr[$key])  {
00894                     $res = $GLOBALS['TYPO3_DB']->admin_query($string);
00895                     if ($res === false) {
00896                         $result[$key] = $GLOBALS['TYPO3_DB']->sql_error();
00897                     } elseif (is_resource($res)) {
00898                         $GLOBALS['TYPO3_DB']->sql_free_result($res);
00899                     }
00900                 }
00901             }
00902         }
00903         if (count($result) > 0) {
00904             return $result;
00905         } else {
00906             return true;
00907         }
00908     }
00909 
00910     /**
00911      * Returns list of tables in the database
00912      *
00913      * @return  array       List of tables.
00914      * @see t3lib_db::admin_get_tables()
00915      */
00916     function getListOfTables()  {
00917         $whichTables = $GLOBALS['TYPO3_DB']->admin_get_tables(TYPO3_db);
00918         foreach ($whichTables as $key=>&$value) {
00919             $value = $key;
00920         }
00921         return $whichTables;
00922     }
00923 
00924     /**
00925      * Creates a table which checkboxes for updating database.
00926      *
00927      * @param   array       Array of statements (key / value pairs where key is used for the checkboxes)
00928      * @param   string      Label for the table.
00929      * @param   boolean     If set, then checkboxes are set by default.
00930      * @param   boolean     If set, then icons are shown.
00931      * @param   array       Array of "current values" for each key/value pair in $arr. Shown if given.
00932      * @param   boolean     If set, will show the prefix "Current value" if $currentValue is given.
00933      * @return  string      HTML table with checkboxes for update. Must be wrapped in a form.
00934      */
00935     function generateUpdateDatabaseForm_checkboxes($arr,$label,$checked=1,$iconDis=0,$currentValue=array(),$cVfullMsg=0)    {
00936         $out = array();
00937         if (is_array($arr)) {
00938             foreach($arr as $key => $string)    {
00939                 $ico = '';
00940                 $warnings = array();
00941 
00942                 if ($iconDis)   {
00943                     if (preg_match('/^TRUNCATE/i',$string)) {
00944                         $ico.= '<img src="'.$this->backPath.'gfx/icon_warning.gif" width="18" height="16" align="top" alt="" /><strong> </strong>';
00945                         $warnings['clear_table_info'] = 'Clearing the table is sometimes neccessary when adding new keys. In case of cache_* tables this should not hurt at all. However, use it with care.';
00946                     } elseif (stristr($string,' user_'))    {
00947                         $ico.= '<img src="'.$this->backPath.'gfx/icon_warning.gif" width="18" height="16" align="top" alt="" /><strong>(USER) </strong>';
00948                     } elseif (stristr($string,' app_')) {
00949                         $ico.= '<img src="'.$this->backPath.'gfx/icon_warning.gif" width="18" height="16" align="top" alt="" /><strong>(APP) </strong>';
00950                     } elseif (stristr($string,' ttx_') || stristr($string,' tx_'))  {
00951                         $ico.= '<img src="'.$this->backPath.'gfx/icon_warning.gif" width="18" height="16" align="top" alt="" /><strong>(EXT) </strong>';
00952                     }
00953                 }
00954                 $out[]='
00955                     <tr>
00956                         <td valign="top"><input type="checkbox" id="db-'.$key.'" name="'.$this->dbUpdateCheckboxPrefix.'['.$key.']" value="1"'.($checked?' checked="checked"':'').' /></td>
00957                         <td nowrap="nowrap"><label for="db-'.$key.'">'.nl2br($ico.htmlspecialchars($string)).'</label></td>
00958                     </tr>';
00959                 if (isset($currentValue[$key])) {
00960                     $out[]='
00961                     <tr>
00962                         <td valign="top"></td>
00963                         <td nowrap="nowrap" style="color : #666666;">'.nl2br((!$cVfullMsg?"Current value: ":"").'<em>'.$currentValue[$key].'</em>').'</td>
00964                     </tr>';
00965                 }
00966             }
00967             if (count($warnings)) {
00968                 $out[] = '
00969                     <tr>
00970                         <td valign="top"></td>
00971                         <td style="color : #666666;"><em>' . implode('<br />',$warnings) . '</em></td>
00972                     </tr>';
00973             }
00974 
00975             // Compile rows:
00976             $content = '
00977                 <!-- Update database fields / tables -->
00978                 <h3>'.$label.'</h3>
00979                 <table border="0" cellpadding="2" cellspacing="2" class="update-db-fields">'.implode('',$out).'
00980                 </table>';
00981         }
00982 
00983         return $content;
00984     }
00985 
00986     /**
00987      * Reads the field definitions for the input SQL-file string
00988      *
00989      * @param   string      Should be a string read from an SQL-file made with 'mysqldump [database_name] -d'
00990      * @return  array       Array with information about table.
00991      * @deprecated  since TYPO3 4.2 Use ->getFieldDefinitions_fileContent() instead!
00992      */
00993     function getFieldDefinitions_sqlContent($fileContent)   {
00994         return $this->getFieldDefinitions_fileContent($fileContent);
00995     }
00996 }
00997 
00998 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_install.php'])   {
00999     include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_install.php']);
01000 }
01001 
01002 ?>

Generated on Sat Jan 3 04:23:26 2009 for TYPO3 API by  doxygen 1.4.7