[self::MOST_PREFERRED_STORAGE_ENGINE, "Aria", "Maria", "MyISAM"], "Aria" => ["Aria", "Maria", "MyISAM"], "InnoDB" => ["InnoDB", "XtraDB"], "XtraDB" => ["XtraDB", "InnoDB"], ]; var $fieldTypes = array('time', 'timestamp', 'datetime', 'year', 'tinyblob', 'blob', 'mediumblob', 'longblob', 'tinytext', 'mediumtext', 'longtext', 'text', 'date', 'json'); var $fieldTypeNum = array('bit', 'tinyint', 'smallint', 'mediumint', 'integer', 'int', 'bigint', 'real', 'double', 'float', 'decimal', 'numeric', 'varchar', 'char', 'binary', 'varbinary', 'enum', 'set'); const STATUS_TABLE_OK = 0x0; const STATUS_TABLE_MISSING = 0x1 << 1; const STATUS_TABLE_MISMATCH_STORAGE_ENGINE = 0x1 << 2; const STATUS_TABLE_MISMATCH_DEFAULT_CHARSET = 0x1 << 3; var $modes = array( 'missing_table' => 'create', 'mismatch' => 'alter', 'missing_field' => 'insert', 'missing_index' => 'index', 'mismatch_index' => '', // TODO ); var $errors = array(); const cachetag = 'Dbverify'; /** * Setup */ function __construct() { $this->backUrl = e_SELF; $this->init(); return $this; } /** * @return bool */ public function clearCache() { return e107::getCache()->clear(self::cachetag, true); } /** * @return array */ private function load() { $mes = e107::getMessage(); $pref = e107::getPref(); $ret = array(); $core_data = file_get_contents(e_CORE . 'sql/core_sql.php'); $ret['core'] = $this->getSqlFileTables($core_data); if(!empty($pref['e_sql_list'])) { foreach($pref['e_sql_list'] as $path => $file) { $filename = e_PLUGIN . $path . '/' . $file . '.php'; if(is_readable($filename)) { $id = str_replace('_sql', '', $file); $data = file_get_contents($filename); $this->currentTable = $id; $ret[$id] = $this->getSqlFileTables($data); unset($data); } else { $message = str_replace("[x]", $filename, DBVLAN_22); $mes->add($message, E_MESSAGE_WARNING); } } } return $ret; } /** * Permissive field validation */ private function diffStructurePermissive($expected, $actual) { $expected['default'] = isset($expected['default']) ? $expected['default'] : ''; $actual['default'] = isset($actual['default']) ? $actual['default'] : ''; if($expected['type'] === 'JSON' && $actual['type'] !== 'JSON') // Fix for JSON alias MySQL 5.7+ { $expected['type'] = 'LONGTEXT'; } // Permit actual text types that default to null even when // expected does not explicitly default to null if( 0 === strcasecmp($expected['type'], $actual['type']) && 1 === preg_match('/[A-Z]*TEXT/i', $expected['type']) && 0 === strcasecmp($actual['default'], "DEFAULT NULL") ) { $expected['default'] = $actual['default']; } // Loosely typed default value for numeric types if(1 === preg_match('/([A-Z]*INT|NUMERIC|DEC|FIXED|FLOAT|REAL|DOUBLE)/i', $expected['type'])) { $expected['default'] = preg_replace("/DEFAULT '(\d*\.?\d*)'/i", 'DEFAULT $1', $expected['default']); $actual['default'] = preg_replace("/DEFAULT '(\d*\.?\d*)'/i", 'DEFAULT $1', $actual['default']); } /** * Display width specification for integer data types was deprecated in MySQL 8.0.17 * * @see https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-19.html */ if(1 === preg_match('/([A-Z]*INT)/i', $expected['type'])) { $expected['value'] = ''; $actual['value'] = ''; } // Correct difference on CREATE TABLE statement between MariaDB and MySQL if(1 === preg_match('/(DATE|DATETIME|TIMESTAMP|TIME|YEAR)/i', $expected['default'])) { $expected['default'] = preg_replace("/CURRENT_TIMESTAMP\(\)/i", 'CURRENT_TIMESTAMP', $expected['default']); $actual['default'] = preg_replace("/CURRENT_TIMESTAMP\(\)/i", 'CURRENT_TIMESTAMP', $actual['default']); } return array_diff_assoc($expected, $actual); } /** * Main Routine for checking and rendering results. */ function verify() { if(!empty($_POST['runfix'])) { $this->runFix($_POST['fix']); } if(!empty($_POST['verify_table'])) { $this->runComparison($_POST['verify_table']); } else { $this->renderTableSelect(); } } /** * @param $fileArray * @return void */ function runComparison($fileArray) { $mes = e107::getMessage(); foreach($fileArray as $tab) { $this->compare($tab); foreach($this->sqlLanguageTables as $lng => $lantab) { $this->compare($tab, $lng); } } if($cnt = $this->errors()) { $message = str_replace("[x]", $cnt, DBVLAN_26); // Found [x] issues. $mes->add($message, E_MESSAGE_WARNING); $this->renderResults($fileArray); } else { if($this->internalError === false) { $mes->addSuccess(DBLAN_111); $mes->addSuccess("" . LAN_BACK . ""); } //$debug = "
".print_r($this->results,TRUE)."
"; //$mes->add($debug,E_MESSAGE_DEBUG); //$text .= "
".$frm->admin_button('back', DBVLAN_17, 'back')."
"; echo $mes->render(); // $ns->tablerender("Okay",$mes->render().$text); } } // $this->sqlTables = $this->sqlTableList(); // print_a($this->tables); // $this->renderTableSelect(); // print_a($field); // print_a($match[2]); // echo "
".$sql_data."
"; /** * Check core tables and installed plugin tables * * @param $exclude - array of plugins to exclude. */ function compareAll($exclude = null) { if(is_array($exclude)) { foreach($exclude as $val) { unset($this->sqlFileTables[$val]); } } $dtables = array_keys($this->sqlFileTables); foreach($dtables as $tb) { $this->compare($tb); } if(!empty($this->sqlLanguageTables)) // language tables. { foreach($this->sqlLanguageTables as $lng => $lantab) { foreach($dtables as $tb) { $this->compare($tb, $lng); } } } } /** * @param $selection * @param $language * @return false|void */ public function compare($selection, $language = '') { $this->currentTable = $selection; // var_dump($this->sqlFileTables[$selection]); if(!isset($this->sqlFileTables[$selection])) // doesn't have an SQL file. { // e107::getMessage()->addDebug("No SQL File for ".$selection); return false; } if(empty($this->sqlFileTables[$selection]['tables'])) { //$this->internalError = true; e107::getMessage()->addDebug("Couldn't read table data for " . $selection); return false; } foreach($this->sqlFileTables[$selection]['tables'] as $key => $tbl) { $rawSqlData = $this->getSqlData($tbl, $language); if($rawSqlData === false) { if($language) { continue; } $this->errors[$tbl]['_status'] = self::STATUS_TABLE_MISSING; $this->errors[$tbl]['_file'] = $selection; $this->results[$tbl] = []; // echo "missing table: $tbl"; continue; } // echo "

RAW

"; // print_a($rawSqlData); // $this->currentTable = $tbl;v $sqlDataArr = $this->getSqlFileTables($rawSqlData); // echo "

PARSED

"; // print_a($sqlDataArr); $fileData['field'] = $this->getFields($this->sqlFileTables[$selection]['data'][$key]); $sqlData['field'] = $this->getFields($sqlDataArr['data'][0]); $fileData['index'] = $this->getIndex($this->sqlFileTables[$selection]['data'][$key]); $sqlData['index'] = $this->getIndex($sqlDataArr['data'][0]); $maybeEngine = isset($sqlDataArr['engine'][0]) ? $sqlDataArr['engine'][0] : 'INTERNAL_ERROR:ENGINE'; $fileData['engine'] = $this->getIntendedStorageEngine($this->sqlFileTables[$selection]['engine'][$key]); $sqlData['engine'] = $this->getCanonicalStorageEngine($maybeEngine); $maybeCharset = isset($sqlDataArr['charset'][0]) ? $sqlDataArr['charset'][0] : 'INTERNAL_ERROR:CHARSET'; $fileData['charset'] = $this->getIntendedCharset($this->sqlFileTables[$selection]['charset'][$key]); $sqlData['charset'] = $sqlDataArr['charset'][0]; // check the actual charset. $this->getCanonicalCharset($maybeCharset); /* $debugA = print_r($fileFieldData,TRUE); // Extracted Field Arrays $debugA .= "

Index

"; $debugA .= print_r($fileIndexData,TRUE); $debugB = print_r($sqlFieldData,TRUE); // Extracted Field Arrays $debugB .= "

Index

"; $debugB .= print_r($sqlIndexData,TRUE); */ $debugA = $this->sqlFileTables[$selection]['data'][$key]; // Extracted Raw Field Text // $debugB = $rawSqlData; $debugB = $sqlDataArr['data'][0]; // Extracted Raw Field Text if(isset($debugA) && (e_PAGE === 'db.php')) { $engineA = !empty($this->sqlFileTables[$selection]['engine'][0]) ? $this->sqlFileTables[$selection]['engine'][0] : 'unknown'; $engineB = !empty($sqlDataArr['engine'][0]) ? $sqlDataArr['engine'][0] : 'unknown'; $charsetA = !empty($this->sqlFileTables[$selection]['charset'][0]) ? $this->sqlFileTables[$selection]['charset'][0] : 'not specified'; $charsetB = !empty($sqlDataArr['charset'][0]) ? $sqlDataArr['charset'][0] : 'unknown'; $debug = "
FILE: $tbl (key=$key) $engineA $charsetA SQL: $tbl $engineB $charsetB
" . $debugA . "
" . $debugB . "
"; $mes = e107::getMessage(); $mes->add($debug, E_MESSAGE_DEBUG); } if($language) { $tbl = "lan_" . $language . "_" . $tbl; } $this->prepareResults($tbl, $selection, $sqlData, $fileData); unset($data); } } /** * @param string $type fields|indices * @return array */ public function getResults($type = 'fields') { if($type === 'indices') { return $this->indices; } return $this->results; } public function hasSyntaxIssue($sqlFileData): bool { return false; // TODO check syntax for errrors. } /** * @param string $tbl table name without prefix. * @param string $selection 'core' OR plugin-folder name. * @param array $sqlData ie. array('field'=>getFields($data), 'index'=>getFields($data)); * @param array $fileData ie. array('field'=>getFields($data), 'index'=>getFields($data)); * @todo Check for additional fields in SQL that should be removed. * @todo Add support for MYSQL 5 table layout .eg. journal_id INT( 10 ) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY , */ public function prepareResults($tbl, $selection, $sqlData, $fileData) { if($this->hasSyntaxIssue($fileData)) { return false; } // Check field and index data foreach(['field', 'index'] as $type) { $results = 'results'; if($type === 'index') { $results = 'indices'; } if(!isset($this->errors[$tbl])) { $this->errors[$tbl] = []; } if(!isset($this->errors[$tbl]['_status'])) { $this->errors[$tbl]['_status'] = self::STATUS_TABLE_OK; } $this->errors[$tbl]['_file'] = $selection; foreach($fileData[$type] as $key => $value) { $this->{$results}[$tbl][$key]['_status'] = 'ok'; if(!isset($sqlData[$type][$key]) || !is_array($sqlData[$type][$key])) { $this->{$results}[$tbl][$key]['_status'] = "missing_$type"; // type status $this->{$results}[$tbl][$key]['_valid'] = $value; $this->{$results}[$tbl][$key]['_file'] = $selection; } elseif(count($diff = $this->diffStructurePermissive($value, $sqlData[$type][$key]))) { $this->{$results}[$tbl][$key]['_status'] = 'mismatch'; $this->{$results}[$tbl][$key]['_diff'] = $diff; $this->{$results}[$tbl][$key]['_valid'] = $value; $this->{$results}[$tbl][$key]['_invalid'] = $sqlData[$type][$key]; $this->{$results}[$tbl][$key]['_file'] = $selection; } } if($fileData['engine'] !== $sqlData['engine']) { $this->errors[$tbl]['_status'] |= self::STATUS_TABLE_MISMATCH_STORAGE_ENGINE; $this->errors[$tbl]['_valid_' . self::STATUS_TABLE_MISMATCH_STORAGE_ENGINE] = $fileData['engine']; $this->errors[$tbl]['_invalid_' . self::STATUS_TABLE_MISMATCH_STORAGE_ENGINE] = $sqlData['engine']; } if($fileData['charset'] !== $sqlData['charset']) { $this->errors[$tbl]['_status'] |= self::STATUS_TABLE_MISMATCH_DEFAULT_CHARSET; $this->errors[$tbl]['_valid_' . self::STATUS_TABLE_MISMATCH_DEFAULT_CHARSET] = $fileData['charset']; $this->errors[$tbl]['_invalid_' . self::STATUS_TABLE_MISMATCH_DEFAULT_CHARSET] = $sqlData['charset']; } } return null; } /** * Compile Results into a complete list of Fixes that could be run without the need of a form selection. */ function compileResults() { foreach($this->results as $tabs => $field) { $file = varset($this->results[$tabs]['_file'], $tabs); $errorStatus = is_int($this->errors[$tabs]['_status']) ? $this->errors[$tabs]['_status'] : self::STATUS_TABLE_OK; if($errorStatus & self::STATUS_TABLE_MISSING) // Missing Table { $this->fixList[$file][$tabs]['all'][] = 'create'; } elseif( $errorStatus & self::STATUS_TABLE_MISMATCH_STORAGE_ENGINE || $errorStatus & self::STATUS_TABLE_MISMATCH_DEFAULT_CHARSET ) { $this->fixList[$file][$tabs]['all'][] = 'convert'; } foreach($field as $k => $f) { if($f['_status'] == 'ok') { continue; } $status = $f['_status']; if(!empty($this->modes[$status])) { $this->fixList[$f['_file']][$tabs][$k][] = $this->modes[$status]; } } } // Index if(count($this->indices)) { foreach($this->indices as $tabs => $field) { if($this->errors[$tabs] != 'ok') { foreach($field as $k => $f) { if($f['_status'] == 'ok') { continue; } $this->fixList[$f['_file']][$tabs][$k][] = $this->modes[$f['_status']]; } } } } } /** * Returns the number of errors */ public function errors() { $badTableCount = 0; foreach($this->errors as $tableName => $tableMetadata) { if(!empty($tableMetadata['_status'])) { $badTableCount++; continue; } foreach($this->results[$tableName] as $fieldMetadata) { if(isset($fieldMetadata['_status']) && $fieldMetadata['_status'] != 'ok') { $badTableCount++; continue 2; } } foreach($this->indices[$tableName] as $indexMetadata) { if(isset($indexMetadata['_status']) && $indexMetadata['_status'] != 'ok') { $badTableCount++; continue 2; } } } return $badTableCount; } public function getErrors() { return $this->errors; } /** * @param $fileArray * @return void */ function renderResults($fileArray = array()) { $frm = e107::getForm(); $ns = e107::getRender(); $mes = e107::getMessage(); $text = "
" . DBVLAN_16 . " "; $info = array( self::STATUS_TABLE_MISSING => DBVLAN_13, self::STATUS_TABLE_MISMATCH_STORAGE_ENGINE => DBVLAN_17, self::STATUS_TABLE_MISMATCH_DEFAULT_CHARSET => DBVLAN_18, 'mismatch' => DBVLAN_8, 'missing_field' => DBVLAN_11, 'ok' => defset('ADMIN_TRUE_ICON', 'true'), 'missing_index' => DBVLAN_25, ); foreach($this->results as $tabs => $field) { $tableStatus = $this->errors[$tabs]['_status']; if($tableStatus != self::STATUS_TABLE_OK) // Missing Table { $errors = []; $parser = e107::getParser(); foreach([ self::STATUS_TABLE_MISSING, self::STATUS_TABLE_MISMATCH_STORAGE_ENGINE, self::STATUS_TABLE_MISMATCH_DEFAULT_CHARSET ] as $statusFlag) { if($tableStatus & $statusFlag) { $errors[] = $parser->lanVars( $info[$statusFlag], [ 'x' => $this->errors[$tabs]['_valid_' . $statusFlag], 'y' => $this->errors[$tabs]['_invalid_' . $statusFlag], ] ); } } $fixMode = $tableStatus & self::STATUS_TABLE_MISSING ? 'create' : 'convert'; $text .= " "; } foreach($field as $k => $f) { if($f['_status'] == 'ok') { continue; } $fstat = $info[$f['_status']]; $text .= " "; } } // Indices if(count($this->indices)) { foreach($this->indices as $tabs => $field) { if($this->errors[$tabs] != 'ok') { foreach($field as $k => $f) { if($f['_status'] == 'ok') { continue; } $fstat = $info[$f['_status']]; $text .= " "; } } } } $text .= "
" . DBVLAN_4 . " " . DBVLAN_5 . " " . DBVLAN_6 . " " . DBVLAN_7 . " " . DBVLAN_19 . "
" . $this->renderTableName($tabs) . " " . DBVLAN_28 . " " . DBVLAN_27 . " " . implode("
", $errors) . "
" . $this->fixForm($this->errors[$tabs]['_file'], $tabs, 'all', '', $fixMode) . "
" . $this->renderTableName($tabs) . " " . $k . "  " . $fstat . " " . $this->renderNotes($f) . "  " . $this->fixForm($f['_file'], $tabs, $k, $f['_valid'], $this->modes[$f['_status']]) . "
" . $this->renderTableName($tabs) . " " . $k . "  " . $fstat . " " . $this->renderNotes($f, 'index') . "  " . $this->fixForm($f['_file'], $tabs, $k, $f['_valid'], $this->modes[$f['_status']]) . "

"; $text .= "
" . $frm->admin_button('runfix', DBVLAN_21, 'execute', '', array('id' => false)) . " " . $frm->admin_button('check_all', 'jstarget:fix', 'action', LAN_CHECKALL, array('id' => false)) . " " . $frm->admin_button('uncheck_all', 'jstarget:fix', 'action', LAN_UNCHECKALL, array('id' => false)); foreach($fileArray as $tab) { $text .= $frm->hidden('verify_table[]', $tab); } $text .= "
"; $ns->tablerender(DBVLAN_23 . SEP . DBVLAN_16, $mes->render() . $text); } /** * @param $tabs * @return mixed|string */ function renderTableName($tabs) { if(strpos($tabs, "lan_") === 0) { list($tmp, $lang, $table) = explode("_", $tabs, 3); return $table . " (" . ucfirst($lang) . ")"; } return $tabs; } /** * @param $file * @param $table * @param $field * @param $newvalue * @param $mode * @param $after * @return string */ function fixForm($file, $table, $field, $newvalue, $mode, $after = '') { $frm = e107::getForm(); $text = $frm->checkbox("fix[$file][$table][$field][]", $mode, false, array('id' => false)); return $text; } /** * @param $data * @param $mode * @return string */ function renderNotes($data, $mode = 'field') { // return "
".print_r($data,TRUE)."
"; $v = $data['_valid']; $i = !empty($data['_invalid']) ? $data['_invalid'] : array(); $valid = $this->toMysql($v, $mode); $invalid = $this->toMysql($i, $mode); $text = ""; if($invalid) { $text .= "" . DBVLAN_9 . "
" . $invalid . "
"; } $text .= "" . DBVLAN_10 . "
" . $valid . "
"; return $text; } /** * @param $data * @param $mode * @return string|void */ public function toMysql($data, $mode = 'field') { if(!$data) { return; } if($mode === 'index') { // print_a($data); if($data['type']) { //return $data['type']." (".$data['field'].");"; // Check if index keyname exists and add backticks $keyname = (!empty($data['keyname']) ? " `" . $data['keyname'] . "`" : ""); return $data['type'] . $keyname . " (" . $data['field'] . ");"; } else { return "INDEX `" . $data['field'] . "` (" . $data['keyname'] . ");"; } } if(!in_array(strtolower($data['type']), $this->fieldTypes)) { $ret = $data['type'] . "(" . $data['value'] . ") " . $data['attributes'] . " " . $data['null'] . " " . $data['default']; return trim($ret); } else { $ret = $data['type']; $ret .= !empty($data['attributes']) ? " " . $data['attributes'] : ''; $ret .= !empty($data['null']) ? " " . $data['null'] : ''; $ret .= !empty($data['default']) ? " " . $data['default'] : ''; return $ret; } } // returns the previous Field /** * @param $array * @param $cur * @return int|mixed|string */ function getPrevious($array, $cur) { $fkeys = array_keys($array); $cur_key = array_search($cur, $fkeys); return @$fkeys[$cur_key - 1]; } /** * get the key ID for the current table which is being Fixed. */ function getId($tabl, $cur) { if(empty($tabl)) { return null; } $key = array_flip($tabl); if(strpos($cur, "lan_") === 0) // language table adjustment. { list($tmp, $lang, $cur) = explode("_", $cur, 3); } if(isset($key[$cur])) { return $key[$cur]; } } /** * @param string $mode index|alter|insert|drop|create|indexdrop * @param string $table eg. submitnews * @param string $field eg. submitnews_id * @param string $sqlFileData (after CREATE) eg. dblog_id int(10) unsigned NOT NULL auto_increment, ..... KEY.... * @param string $engine MyISAM|InnoDB * @param string $charset MySQL/MariaDB text character set * @return string SQL query */ function getFixQuery( $mode, $table, $field, $sqlFileData, $engine = self::MOST_PREFERRED_STORAGE_ENGINE, $charset = self::MOST_PREFERRED_CHARSET ) { if(strpos($mode, 'index') === 0) { $fdata = $this->getIndex($sqlFileData); $newval = $this->toMysql($fdata[$field], 'index'); } elseif($mode == 'alter' || $mode === 'insert' || $mode === 'index') { $fdata = $this->getFields($sqlFileData); $newval = $this->toMysql($fdata[$field]); } $query = ""; switch($mode) { case 'alter': $query = "ALTER TABLE `" . MPREFIX . $table . "` CHANGE `$field` `$field` $newval"; break; case 'insert': $after = ($aft = $this->getPrevious($fdata, $field)) ? " AFTER {$aft}" : ""; $query = "ALTER TABLE `" . MPREFIX . $table . "` ADD `$field` $newval{$after}"; break; case 'drop': $query = "ALTER TABLE `" . MPREFIX . $table . "` DROP `$field`"; break; case 'index': $newval = str_replace("PRIMARY", "PRIMARY KEY", $newval); $query = "ALTER TABLE `" . MPREFIX . $table . "` ADD " . $newval; break; case 'indexdrop': $query = "ALTER TABLE `" . MPREFIX . $table . "` DROP INDEX `$field`"; break; case 'create': $query = "CREATE TABLE `" . MPREFIX . $table . "` (" . $sqlFileData . ")" . " ENGINE=" . $engine . " DEFAULT CHARACTER SET=" . $charset . ";"; break; case 'convert': $showCreateTable = $this->getSqlData($table); $currentSchema = $this->getSqlFileTables($showCreateTable); if($engine != $currentSchema['engine'][0]) { $query .= "ALTER TABLE `" . MPREFIX . $table . "` ENGINE=" . $engine . ";"; } if($charset != $currentSchema['charset'][0]) { $query .= "ALTER TABLE `" . MPREFIX . $table . "` CONVERT TO CHARACTER SET " . $charset . ";"; } break; } return $query; } /** * Fix tables * FixArray eg. [core][table][field] = alter|create|index| etc. */ function runFix($fixArray = '') { $mes = e107::getMessage(); $log = e107::getLog(); if(!is_array($fixArray)) { $fixArray = $this->fixList; // Fix All } foreach($fixArray as $j => $file) { foreach($file as $table => $val) { $id = $this->getId($this->sqlFileTables[$j]['tables'], $table); $toFix = count($val); foreach($val as $field => $fixes) { foreach($fixes as $mode) { $query = $this->getFixQuery( $mode, $table, $field, $this->sqlFileTables[$j]['data'][$id], $this->getIntendedStorageEngine($this->sqlFileTables[$j]['engine'][$id]), $this->getIntendedCharset($this->sqlFileTables[$j]['charset'][$id]) ); // $mes->addDebug("Query: ".$query); // continue; if(e107::getDb()->gen($query) !== false) { $log->addDebug(defset('LAN_UPDATED', 'Updated') . ' [' . $query . ']'); $toFix--; } else { $log->addWarning(defset('LAN_UPDATED_FAILED', 'Update Failed') . ' [' . $query . ']'); $log->addWarning(e107::getDb()->getLastErrorText()); // PDO compatible. /*if(mysql_errno()) { $log->addWarning('SQL #'.mysql_errno().': '.mysql_error()); }*/ } } } if(empty($toFix)) { unset($this->errors[$table], $this->fixList[$j][$table]); // remove from error list since we are using a singleton } } // } $log->flushMessages("Database Table(s) Modified"); } /** * @param $sql_data * @return array|false */ function getSqlFileTables($sql_data) { if(!$sql_data) { e107::getMessage()->addError("No SQL Data found in file"); return false; } $ret = array(); $sql_data = preg_replace("#\/\*.*?\*\/#mis", '', $sql_data); // remove comments // $regex = "/CREATE TABLE (?:IF NOT EXISTS )?`?([\w]*)`?\s*?\(([^;]*)\)\s*((?:[\w\s]+=[^\s]+)+\s*)*;/i"; // $regex = "/CREATE TABLE (?:IF NOT EXISTS )?`?(\w*)`?\s*?\(([^;]*)\)\s*((?:[\w\s]+=\S+)+\s*)*;/i"; $regex = "/CREATE TABLE (?:IF NOT EXISTS )?`?(\w*)`?\s*?\(([^;]*)\)\s*((?:[\w\s]+=[^;]+)+\s*)*;/i"; preg_match_all($regex, $sql_data, $match); $tables = array(); foreach($match[1] as $c => $k) { if(strpos($k, 'e107_') === 0) // remove prefix if found in sql dump. { $k = substr($k, 5); } $tables[$c] = $k; } $ret['tables'] = $tables; $data = array(); if(!empty($match[2])) // clean/trim data. { foreach($match[2] as $dat) { $dat = str_replace("\t", '', $dat); // remove tab chars. $data[] = trim($dat); } } $ret['data'] = $data; $ret['engine'] = array(); $ret['charset'] = array(); foreach($match[3] as $rawTableOptions) { if(empty($rawTableOptions)) { continue; } $engine = null; $charset = null; // $tableOptionsRegex = "/([\w\s]+=[\w]+)+?\s*/"; $tableOptionsRegex = "/([\w\s]+=\s?\w+)+?\s*/"; preg_match_all($tableOptionsRegex, $rawTableOptions, $tableOptionsSplit); $tableOptionsSplit = current($tableOptionsSplit); foreach($tableOptionsSplit as $rawTableOption) { list($tableOptionName, $tableOptionValue) = explode("=", $rawTableOption, 2); $tableOptionName = strtoupper(trim($tableOptionName)); $tableOptionValue = trim($tableOptionValue); switch($tableOptionName) { case "ENGINE": case "TYPE": $engine = $tableOptionValue; break; case "DEFAULT CHARSET": case "DEFAULT CHARACTER SET": case "CHARSET": case "CHARACTER SET": $charset = $tableOptionValue; break; } } $ret['engine'][] = str_replace('MYISAM', 'MyISAM', $engine); $ret['charset'][] = $charset; } if(empty($ret['tables'])) { e107::getMessage()->addDebug("Unable to parse " . $this->currentTable . "_sql.php file data. Possibly missing a ';' at the end?"); e107::getMessage()->addDebug(print_a($regex, true)); } return $ret; } /** * @param $data * @param $print * @return array */ function getFields($data, $print = false) { // Clean $data and add ` ` arond field-names - prevents issues when field == field-type. $tmp = explode("\n", $data); $newline = array(); foreach($tmp as $line) { $line = trim($line); if(strpos($line, "PRIMARY") === 0 || strpos($line, "KEY") === 0 || strpos($line, "INDEX") === 0 || strpos($line, "FULLTEXT") === 0 || strpos($line, "FOREIGN") === 0) { $newline[] = ''; // Add a placeholder to preserve the structure continue; } $newline[] = preg_replace('/^([^`\s][0-9a-zA-Z\$_]*)/', "`$1`", $line); } $data = implode("\n", $newline); // -------------------- $mes = e107::getMessage(); // $regex = "/`?([\w]*)`?\s*?(".implode("|",$this->fieldTypes)."|".implode("|",$this->fieldTypeNum).")\s?(?:\([\s]?([0-9,]*)[\s]?\))?[\s]?(unsigned)?[\s]?.*?(?:(NOT NULL|NULL))?[\s]*(auto_increment|default .*)?[\s]?(?:PRIMARY KEY)?[\s]*?,?\s*?\n/im"; $regex = "/^\s*?`?([\w]*)`?\s*?(" . implode("|", $this->fieldTypes) . "|" . implode("|", $this->fieldTypeNum) . ")\s?(?:\([\s]?([0-9,]*)[\s]?\))?[\s]?(unsigned)?[\s]?.*?(?:(NOT NULL|NULL))?[\s]*(auto_increment|default|AUTO_INCREMENT|DEFAULT [\w'\s.\(:\)-]*)?[\s]?(comment [\w\s'.-]*)?[\s]?(?:PRIMARY KEY|FULLTEXT)?[\s]*?,?\s*?\n/im"; preg_match_all($regex, $data, $m); $ret = array(); if($print) { var_dump($regex, $m); } foreach($m[1] as $k => $val) { $ret[$val] = array( 'type' => trim(strtoupper($m[2][$k])), 'value' => $m[3][$k], 'attributes' => strtoupper($m[4][$k]), 'null' => strtoupper($m[5][$k]), 'default' => strtoupper($m[6][$k]) ); } return $ret; } /** * Helper method to clean column names * * @param string $col The raw column name to clean * @return string The cleaned column name */ private function cleanColumn($col) { $col = trim($col); $col = str_replace('(', ' (', $col); $tmp = explode(' ', $col); return str_replace('`', '', $tmp[0]); } /** * Parse index definitions from a string * * @param string $data The raw index definition string * @param bool $print Optional flag to print results (not used here) * @return array Parsed index information */ public function getIndex($data, $print = false) { // Regular expression to match index definitions $regex = "/(?PPRIMARY|UNIQUE|FULLTEXT|FOREIGN|KEY|INDEX)[\s]*?(?PINDEX|KEY)?[\s]*(?:`?(?P[\w]*)`?)?[\s]*?\((?P[^)]+)\)[\s]*,?/i"; preg_match_all($regex, $data, $m); $ret = []; // Process each matched index foreach($m['type'] as $k => $val) { $type = trim(strtoupper($m['type'][$k])); // Index type (e.g., PRIMARY, UNIQUE) $field = trim($m['field'][$k]); // Index name before parentheses $columnsRaw = trim($m['columns'][$k]); // Content inside parentheses // Split columns and clean each one using the helper method $columnsArray = array_map(array($this, 'cleanColumn'), explode(',', $columnsRaw)); $keyname = implode(',', $columnsArray); // Comma-separated column names // Determine the index name: use 'field' if provided, otherwise first column $i = $field ?: $columnsArray[0]; if($type === 'PRIMARY') { $i = $columnsArray[0]; // Primary key uses the column name } // Normalize KEY/INDEX to empty type for regular indexes if($type === 'KEY' || $type === 'INDEX') { $type = ''; } // Build the result array for this index $ret[$i] = array( 'type' => $type, 'keyname' => $keyname, 'field' => $i, ); } return $ret; } /** * @param $tbl * @param $language * @return false|string */ function getSqlData($tbl, $language = '') { $mes = e107::getMessage(); $prefix = MPREFIX; if($language) { if(!in_array($tbl, $this->sqlLanguageTables[$language])) { return false; } $prefix .= "lan_" . $language . "_"; // $mes->addDebug("

Retrieving Language Table Data: ".$prefix . $tbl."

"); } $sql = e107::getDb(); if(!$sql->isTable($tbl)) { $mes->addDebug('Missing table on db-verify: ' . $tbl); return false; } // mysql_query('SET SQL_QUOTE_SHOW_CREATE = 1'); $qry = 'SHOW CREATE TABLE `' . $prefix . $tbl . "`"; // $z = mysql_query($qry); $z = $sql->gen($qry); if($z) { // $row = mysql_fetch_row($z); $row = $sql->fetch('num'); //return $row[1]; return stripslashes($row[1]) . ';'; // backticks needed. // return str_replace("`", "", stripslashes($row[1])).';'; } else { $mes->addDebug('Failed: ' . $qry); $this->internalError = true; return false; } } /** * @return array */ function getSqlLanguages() { $sql = e107::getDb(); $list = $sql->tables('lan'); $array = array(); foreach($list as $tb) { list($tmp, $lang, $table) = explode("_", $tb, 3); $array[$lang][] = $table; } return $array; } /** * @return void */ function renderTableSelect() { $frm = e107::getForm(); $ns = e107::getRender(); $mes = e107::getMessage(); $text = "
" . DBVLAN_14 . " "; $c = 0; $plg = e107::getPlug(); foreach(array_keys($this->sqlFileTables) as $t => $x) { if($x !== 'core') { $plg->load($x); if(!$plg->getId()) // no data. { $plg->load($x . '_menu');// try menu folder. } $icon = $plg->getIcon(); $name = $plg->getName(); } else { $icon = defset('E_16_E107'); $name = LAN_CORE; } $text .= ($c === 0) ? "\n" : ''; $text .= ""; $text .= ($c === 2) ? "\n" : ''; $c++; if($c > 2) { $c = 0; } } while(($c % 3) !== 0) { $text .= "\n"; $text .= (($c + 1) % 3 == 0) ? "" : ""; $c++; } /* if($c !== 2) { $add = (3 - $c) + 1; $text .= ""; $text .= ""; }*/ $text .= "
" . $frm->checkbox('verify_table[' . $t . ']', $x, false, array('label' => $icon . ' ' . $name)) . "
 
".$c."
" . $frm->admin_button('db_verify', DBVLAN_15) . " " . $frm->admin_button('db_tools_back', LAN_BACK, 'back') . "
"; $ns->tablerender(DBVLAN_23 . SEP . DBVLAN_16, $mes->render() . $text); } /** * Get the available storage engines on this MySQL server * * This method is not memoized and should not be called repeatedly. * * @return string[] An unordered list of the storage engines supported by the current MySQL server */ private function getAvailableStorageEngines() { $db = e107::getDb(); $db->gen("SHOW ENGINES;"); $output = []; while($row = $db->fetch()) { $output[] = $row['Engine']; } return $output; } /** * Get the most compatible MySQL storage engine on this server for the provided storage engine * * @param string|null $maybeStorageEngine The requested storage engine * @return string|false The MySQL storage engine that should actually be used. false if no match found. */ public function getIntendedStorageEngine($maybeStorageEngine = null) { if($maybeStorageEngine === null) { return $this->getIntendedStorageEngine(self::MOST_PREFERRED_STORAGE_ENGINE); } if(strtoupper($maybeStorageEngine) === 'MYISAM') { $maybeStorageEngine = 'MyISAM'; } elseif(strtoupper($maybeStorageEngine) === 'INNODB') { $maybeStorageEngine = 'InnoDB'; } if(!array_key_exists($maybeStorageEngine, $this->storageEnginePreferenceMap)) { if(in_array($maybeStorageEngine, $this->availableStorageEngines)) { return $maybeStorageEngine; } return false; } $fit = array_intersect($this->storageEnginePreferenceMap[$maybeStorageEngine], $this->availableStorageEngines); return current($fit); } /** * Try to figure out what storage engine the provided one is referring to * * @param string $maybeStorageEngine The reported storage engine * @return string The probable storage engine the input is referring to * @throws UnexpectedValueException if the provided storage engine is not known as an available storage engine */ public function getCanonicalStorageEngine($maybeStorageEngine) { if(in_array($maybeStorageEngine, $this->availableStorageEngines)) { return $maybeStorageEngine; } throw new UnexpectedValueException( "Unknown storage engine: " . var_export($maybeStorageEngine, true) ); } /** * Get the most compatible MySQL character set based on the input * * @param string|null $maybeCharset The requested character set. null to retrieve the default * @return string The MySQL character set that should actually be used */ public function getIntendedCharset($maybeCharset = null) { if(empty($maybeCharset)) { return self::MOST_PREFERRED_CHARSET; } return $this->getCanonicalCharset($maybeCharset); } /** * Try to figure out what character set the provided one is referring to * * @param string $maybeCharset The reported character set * @return string The probable character set */ public function getCanonicalCharset($maybeCharset) { if($maybeCharset == "utf8") { return "utf8mb4"; } return $maybeCharset; } /** * Inititalize the class parameters. * * @return void */ public function init($clearCache = false): void { $sql = e107::getDb(); $sql->gen('SET SQL_QUOTE_SHOW_CREATE = 1'); if(!deftrue('e_DEBUG') && ($clearCache === false) && $tmp = e107::getCache()->retrieve(self::cachetag, 15, true, true)) { $cacheData = e107::unserialize($tmp); $this->sqlFileTables = isset($cacheData['sqlFileTables']) ? $cacheData['sqlFileTables'] : $this->load(); $this->availableStorageEngines = isset($cacheData['availableStorageEngines']) ? $cacheData['availableStorageEngines'] : $this->getAvailableStorageEngines(); } else { $this->sqlFileTables = $this->load(); $this->availableStorageEngines = $this->getAvailableStorageEngines(); $cacheData = e107::serialize([ 'sqlFileTables' => $this->sqlFileTables, 'availableStorageEngines' => $this->availableStorageEngines, ], 'json'); e107::getCache()->set(self::cachetag, $cacheData, true, true, true); } $this->sqlLanguageTables = $this->getSqlLanguages(); } } /* function check_tables($what) { global $tablines, $table_list, $frm, $emessage; $cur = 0; $table_list = ""; read_tables($what); $fix_active = FALSE; // Flag set as soon as there's a fix - enables 'Fix it' button $text = "
".DBVLAN_16." - $what ".DBVLAN_18." "; foreach(array_keys($table_list) as $k) { // $k is the DB table name (less prefix) $ttcount = 0; $ttext = " "; $prefix = MPREFIX; $current_tab = get_current($k, $prefix); // Get list of fields and keys from actual table unset($fields); unset($xfields); $xfield_errors = 0; if ($current_tab) { $lines = explode("\n", $current_tab); // Actual table - create one element of $lines per field or other line of info $fieldnum = 0; foreach($tablines[$k] as $x) { // $x is a line of the DB definition from the *_sql.php file $x = str_replace(' ',' ',$x); // Remove double spaces $fieldnum++; $ffound = 0; list($fname, $fparams) = explode(' ', $x, 2); // Pull out first word of definition if ($fname == 'UNIQUE' || $fname == 'FULLTEXT') { list($key, $key1, $keyname, $keyparms) = explode(' ', $x, 4); $fname = $key." ".$key1." ".$keyname; $fparams = $keyparms; } elseif ($fname == 'KEY') { list($key, $keyname, $keyparms) = explode(' ', $x, 3); $fname = $key." ".$keyname; $fparams = $keyparms; } elseif ($fname == 'PRIMARY') { // Nothing to do ATM } else { // Must be a field name $fname = str_replace('`','',$fname); // Just remove back ticks if present } $fields[$fname] = 1; $fparams = ltrim(rtrim($fparams)); $fparams = preg_replace("/\r?\n$|\r[^\n]$|,$/", '', $fparams); if(stristr($k, "lan_") !== FALSE && $cur != 1) { $cur = 1; } $head_txt = " "; $xfieldnum = -1; $body_txt = ''; foreach($lines as $l) { $xfieldnum++; list($xl, $tmp) = explode("\n", $l, 2); // $tmp should be null $xl = ltrim(rtrim(stripslashes($xl))); $xl = preg_replace('/\r?\n$|\r[^\n]$/', '', $xl); $xl = str_replace(' ',' ',$xl); // Remove double spaces list($xfname, $xfparams) = explode(" ", $xl, 2); // Field name and the rest if ($xfname == 'UNIQUE' || $xfname == 'FULLTEXT') { list($key, $key1, $keyname, $keyparms) = explode(" ", $xl, 4); $xfname = $key." ".$key1." ".$keyname; $xfparams = $keyparms; } elseif ($xfname == "KEY") { list($key, $keyname, $keyparms) = explode(" ", $xl, 3); $xfname = $key." ".$keyname; $xfparams = $keyparms; } if ($xfname != "CREATE" && $xfname != ")") { $xfields[$xfname] = 1; } $xfparams = preg_replace('/,$/', '', $xfparams); $fparams = preg_replace('/,$/', '', $fparams); if ($xfname == $fname) { // Field names match - or it could be the word 'KEY' and its name which matches $ffound = 1; //echo "Field: ".$xfname." Actuals: ".$xfparams." Expected: ".$fparams."
"; $xfsplit = explode(' ',$xfparams); $fsplit = explode(' ',$fparams); $skip = FALSE; $i = 0; $fld_err = FALSE; foreach ($xfsplit as $xf) { if ($skip) { $skip = FALSE; // echo " Unskip: ".$xf."
"; } elseif (strcasecmp(trim($xf),'collate') == 0) { // Strip out the collation definition $skip = TRUE; // cho "Skip = ".$xf; } else { // echo "Compare: ".$xf." - ".$fsplit[$i]."
"; // Since VARCHAR and CHAR are interchangeable, convert to CHAR (strictly, VARCHAR(3) and smalller becomes CHAR() ) if (stripos($xf,'VARCHAR') === 0) $xf = substr($xf,3); if (stripos($fsplit[$i],'VARCHAR') === 0) $fsplit[$i] = substr($fsplit[$i],3); if (strcasecmp(trim($xf),trim($fsplit[$i])) != 0) { $fld_err = TRUE; //echo "Mismatch: ".$xf." - ".$fsplit[$i]."
"; } $i++; } } if ($fld_err) { $body_txt .= " "; $fix_active = TRUE; $xfield_errors++; } /* FIXME - can't stay if there is no way of fixing the field numbers (e.g. AFTER query) elseif ($fieldnum != $xfieldnum) { // Field numbers different - missing field? $body_txt .= " "; } // DISABLED for now (show only errors), could be page setting // else // { // $body_txt .= " // // // // "; // } // } } // Finished checking one field if ($ffound == 0) { $prev_fname = $fname; //FIXME - wrong $prev_fname! $body_txt .= " "; $fix_active = TRUE; $xfield_errors++; } if($xfield_errors && $body_txt) { $ttext .= $head_txt.$body_txt." "; } } foreach(array_keys($xfields) as $tf) { if (!$fields[$tf] && $k != "user_extended") { $fix_active = TRUE; $xfield_errors++; $ttext .= " "; } } } else { // Table Missing. $ttext .= " "; $fix_active = TRUE; $xfield_errors++; } if(!$xfield_errors) { //no errors, so no table rows yet $ttext .= " "; } $ttext .= "
".DBVLAN_4.": {$k} ".DBVLAN_5." ".DBVLAN_6." ".DBVLAN_7." ".DBVLAN_19."
{$k} {$fname} "; if (strpos($fparams, 'KEY') !== FALSE) { $head_txt .= " {$fparams} aa"; } $head_txt .= "".DBVLAN_8." ".DBVLAN_9."
{$xfparams}
".DBVLAN_10."
{$fparams}
".fix_form($k, $fname, $fparams, "alter")."".DBVLAN_5." ".DBVLAN_8." ".DBVLAN_9.": #{$xfieldnum}
".DBVLAN_10.": #{$fieldnum}
 OK  ".DBVLAN_11." ".DBVLAN_10."
{$fparams}
".fix_form($k, $fname, $fparams, "insert", $prev_fname)."
$k $tf ".DBVLAN_12."   ".fix_form($k, $tf, $fparams, "drop")."
{$k}   ".DBVLAN_13."   ".fix_form($k, $tf, $tablines[$k], "create") . "
Table status OK

"; //FIXME - add 'show_if_ok' switch if($xfield_errors || (!$xfield_errors && varsettrue($_GET['show_if_ok']))) { $text .= $ttext; $ttcount++; } } if(!$fix_active) { //Everything should be OK $emessage->add('DB successfully verified - no problems were found.', E_MESSAGE_SUCCESS); if(!$ttcount) { //very tired and sick of this page, so quick and dirty $text .= " "; } } if($fix_active) { $text .= "
".$frm->admin_button('do_fix', DBVLAN_21, 'execute', '', array('id'=>false))." ".$frm->admin_button('check_all', 'jstarget:fix_active', 'action', LAN_CHECKALL, array('id'=>false))." ".$frm->admin_button('uncheck_all', 'jstarget:fix_active', 'action', LAN_UNCHECKALL, array('id'=>false))."
"; } foreach(array_keys($_POST) as $j) { $match = array(); if (preg_match('/table_(.*)/', $j, $match)) { $lx = $match[1]; $text .= "
\n"; } } $text .= "
".$frm->admin_button('back', DBVLAN_17, 'back')."
"; return $text; } global $table_list; // -------------------- Table Fixing ------------------------------ if(isset($_POST['do_fix'])) { //$emessage->add(DBVLAN_20); foreach( $_POST['fix_active'] as $key=>$val) { if (MAGIC_QUOTES_GPC == TRUE) { $table = stripslashes($_POST['fix_table'][$key][0]); $newval = stripslashes($_POST['fix_newval'][$key][0]); $mode = stripslashes($_POST['fix_mode'][$key][0]); $after = stripslashes($_POST['fix_after'][$key][0]); } else { $table = $_POST['fix_table'][$key][0]; $newval = $_POST['fix_newval'][$key][0]; $mode = $_POST['fix_mode'][$key][0]; $after = $_POST['fix_after'][$key][0]; } $field= $key; switch($mode) { case 'alter': $query = "ALTER TABLE `".MPREFIX.$table."` CHANGE `$field` `$field` $newval"; break; case 'insert': if($after) $after = " AFTER {$after}"; $query = "ALTER TABLE `".MPREFIX.$table."` ADD `$field` $newval{$after}"; break; case 'drop': $query = "ALTER TABLE `".MPREFIX.$table."` DROP `$field` "; break; case 'index': $query = "ALTER TABLE `".MPREFIX.$table."` ADD INDEX `$field` ($newval)"; break; case 'indexalt': $query = "ALTER TABLE `".MPREFIX.$table."` ADD $field ($newval)"; break; case 'indexdrop': $query = "ALTER TABLE `".MPREFIX.$table."` DROP INDEX `$field`"; break; case 'create': $query = "CREATE TABLE `".MPREFIX.$table."` ({$newval}"; if (!preg_match('#.*?\s+?(?:TYPE|ENGINE)\s*\=\s*(.*?);#is', $newval)) { $query .= ') TYPE=MyISAM;'; } break; } return $query; //FIXME - db handler!!! if(mysql_query($query)) $emessage->add(LAN_UPDATED.' [ '.$query.' ]', E_MESSAGE_SUCCESS); else { $emessage->add(LAN_UPDATED_FAILED.' [ '.$query.' ]', E_MESSAGE_WARNING); if(mysql_errno()) { $emessage->add('     SQL #'.mysql_errno().': '.mysql_error(), E_MESSAGE_WARNING); } } } } $text = "
".DBVLAN_14." "; foreach(array_keys($tables) as $x) { $text .= " "; } $text .= "
".$frm->checkbox_toggle('check-all-verify', 'table_').LAN_CHECKALL.' | '.LAN_UNCHECKALL."
".$frm->checkbox('table_'.$x, $x).$frm->label($x, 'table_'.$x, $x)."
".$frm->admin_button('db_verify', DBVLAN_15)." ".$frm->admin_button('db_tools_back', DBVLAN_17, 'back')."
"; $e107->ns->tablerender(DBVLAN_23.' - '.DBVLAN_16, $emessage->render().$text); require_once(e_ADMIN."footer.php"); exit; // -------------------------------------------------------------- function fix_form($table,$field, $newvalue,$mode,$after ='') { global $frm; if($mode == 'create') { $newvalue = implode("\n",$newvalue); $field = $table; // Value for $field may be rubbish! } else { if(stristr($field, 'KEY ') !== FALSE) { $field = chop(str_replace('KEY ','',$field)); $mode = ($mode == 'drop') ? 'indexdrop' : 'index'; $search = array('(', ')'); $newvalue = str_replace($search,'',$newvalue); $after = ''; } if($mode == 'index' && (stristr($field, 'FULLTEXT ') !== FALSE || stristr($field, 'UNIQUE ') !== FALSE)) { $mode = 'indexalt'; } elseif($mode == 'indexdrop' && (stristr($field, 'FULLTEXT ') !== FALSE || stristr($field, 'UNIQUE ') !== FALSE)) { $field = trim(str_replace(array('FULLTEXT ', 'UNIQUE '), '', $field)); } $field = trim($field, '`'); } $text .= "\n\n"; $text .= $frm->checkbox("fix_active[$field][]", 1, false, array('id'=>false)); $text .= "\n"; $text .= "\n"; $text .= "\n"; $text .= ($after) ? "\n" : ""; $text .= "\n\n"; return $text; } function table_list() { // grab default language lists. global $mySQLdefaultdb; $exclude[] = "banlist"; $exclude[] = "banner"; $exclude[] = "cache"; $exclude[] = "core"; $exclude[] = "online"; $exclude[] = "parser"; $exclude[] = "plugin"; $exclude[] = "user"; $exclude[] = "upload"; $exclude[] = "userclass_classes"; $exclude[] = "rbinary"; $exclude[] = "session"; $exclude[] = "tmp"; $exclude[] = "flood"; $exclude[] = "stat_info"; $exclude[] = "stat_last"; $exclude[] = "submit_news"; $exclude[] = "rate"; $exclude[] = "stat_counter";$exclude[] = "user_extended"; $exclude[] = "user_extended_struct"; $exclude[] = "pm_messages"; $exclude[] = "pm_blocks"; $replace = array(); $lanlist = explode(",",e_LANLIST); foreach($lanlist as $lang) { if($lang != $pref['sitelanguage']) { $replace[] = "lan_".strtolower($lang)."_"; } } $tables = mysql_list_tables($mySQLdefaultdb); while (list($temp) = mysql_fetch_array($tables)) { $prefix = MPREFIX."lan_"; $match = array(); if(strpos($temp,$prefix)!==FALSE) { $e107tab = str_replace(MPREFIX, "", $temp); $core = str_replace($replace,"",$e107tab); if (str_replace($exclude, "", $e107tab)) { $tabs[$core] = $e107tab; } } } return $tabs; } */