1
0
mirror of https://github.com/e107inc/e107.git synced 2025-08-03 13:17:24 +02:00

Issue #5382 - Fix history table creation.

This commit is contained in:
camer0n
2025-04-08 17:29:10 -07:00
parent 62d53cd5d8
commit e5edbf665c
2 changed files with 606 additions and 509 deletions

View File

@@ -15,7 +15,10 @@
*
*/
if (!defined('e107_INIT')) { exit; }
if(!defined('e107_INIT'))
{
exit;
}
e107::includeLan(e_LANGUAGEDIR . e_LANGUAGE . '/admin/lan_db_verify.php');
@@ -25,6 +28,7 @@ e107::includeLan(e_LANGUAGEDIR.e_LANGUAGE.'/admin/lan_db_verify.php');
*/
class db_verify
{
var $backUrl = "";
public $sqlFileTables = array();
const MOST_PREFERRED_STORAGE_ENGINE = "InnoDB";
@@ -40,6 +44,7 @@ class db_verify
/**
* Aliases for preferred storage engines when provided the key
*
* @var string[][]
*/
private $storageEnginePreferenceMap = [
@@ -71,6 +76,7 @@ class db_verify
var $errors = array();
const cachetag = 'Dbverify';
/**
* Setup
*/
@@ -78,7 +84,6 @@ class db_verify
{
$this->backUrl = e_SELF;
$this->init();
@@ -103,6 +108,7 @@ class db_verify
*/
private function load()
{
$mes = e107::getMessage();
$pref = e107::getPref();
@@ -143,6 +149,7 @@ class db_verify
*/
private function diffStructurePermissive($expected, $actual)
{
$expected['default'] = isset($expected['default']) ? $expected['default'] : '';
$actual['default'] = isset($actual['default']) ? $actual['default'] : '';
@@ -153,9 +160,11 @@ class db_verify
// Permit actual text types that default to null even when
// expected does not explicitly default to null
if(0 === strcasecmp($expected['type'], $actual['type']) &&
if(
0 === strcasecmp($expected['type'], $actual['type']) &&
1 === preg_match('/[A-Z]*TEXT/i', $expected['type']) &&
0 === strcasecmp($actual['default'], "DEFAULT NULL"))
0 === strcasecmp($actual['default'], "DEFAULT NULL")
)
{
$expected['default'] = $actual['default'];
}
@@ -169,6 +178,7 @@ class db_verify
/**
* 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']))
@@ -192,6 +202,7 @@ class db_verify
*/
function verify()
{
if(!empty($_POST['runfix']))
{
$this->runFix($_POST['fix']);
@@ -215,6 +226,7 @@ class db_verify
*/
function runComparison($fileArray)
{
$mes = e107::getMessage();
foreach($fileArray as $tab)
@@ -261,6 +273,7 @@ class db_verify
/**
* Check core tables and installed plugin tables
*
* @param $exclude - array of plugins to exclude.
*/
function compareAll($exclude = null)
@@ -318,6 +331,7 @@ class db_verify
{
//$this->internalError = true;
e107::getMessage()->addDebug("Couldn't read table data for " . $selection);
return false;
}
@@ -326,11 +340,12 @@ class db_verify
$rawSqlData = $this->getSqlData($tbl, $language);
if($rawSqlData === false)
{
if($language) continue;
if($language)
{
continue;
}
$this->errors[$tbl]['_status'] = self::STATUS_TABLE_MISSING;
@@ -416,6 +431,7 @@ class db_verify
*/
public function getResults($type = 'fields')
{
if($type === 'indices')
{
return $this->indices;
@@ -427,6 +443,7 @@ class db_verify
public function hasSyntaxIssue($sqlFileData): bool
{
return false; // TODO check syntax for errrors.
}
@@ -440,6 +457,7 @@ class db_verify
*/
public function prepareResults($tbl, $selection, $sqlData, $fileData)
{
if($this->hasSyntaxIssue($fileData))
{
return false;
@@ -455,8 +473,14 @@ class db_verify
$results = 'indices';
}
if (!isset($this->errors[$tbl])) $this->errors[$tbl] = [];
if (!isset($this->errors[$tbl]['_status'])) $this->errors[$tbl]['_status'] = self::STATUS_TABLE_OK;
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)
@@ -501,15 +525,12 @@ class db_verify
}
/**
* 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);
@@ -529,7 +550,10 @@ class db_verify
}
foreach($field as $k => $f)
{
if($f['_status']=='ok') continue;
if($f['_status'] == 'ok')
{
continue;
}
$status = $f['_status'];
if(!empty($this->modes[$status]))
{
@@ -547,7 +571,10 @@ class db_verify
{
foreach($field as $k => $f)
{
if($f['_status']=='ok') continue;
if($f['_status'] == 'ok')
{
continue;
}
$this->fixList[$f['_file']][$tabs][$k][] = $this->modes[$f['_status']];
}
}
@@ -562,6 +589,7 @@ class db_verify
*/
public function errors()
{
$badTableCount = 0;
foreach($this->errors as $tableName => $tableMetadata)
{
@@ -593,6 +621,7 @@ class db_verify
public function getErrors()
{
return $this->errors;
}
@@ -657,6 +686,7 @@ class db_verify
] as $statusFlag)
{
if($tableStatus & $statusFlag)
{
$errors[] = $parser->lanVars(
$info[$statusFlag],
[
@@ -665,6 +695,7 @@ class db_verify
]
);
}
}
$fixMode = $tableStatus & self::STATUS_TABLE_MISSING ? 'create' : 'convert';
@@ -680,7 +711,10 @@ class db_verify
}
foreach($field as $k => $f)
{
if($f['_status']=='ok') continue;
if($f['_status'] == 'ok')
{
continue;
}
$fstat = $info[$f['_status']];
@@ -709,7 +743,10 @@ class db_verify
{
foreach($field as $k => $f)
{
if($f['_status']=='ok') continue;
if($f['_status'] == 'ok')
{
continue;
}
$fstat = $info[$f['_status']];
@@ -767,8 +804,10 @@ class db_verify
if(strpos($tabs, "lan_") === 0)
{
list($tmp, $lang, $table) = explode("_", $tabs, 3);
return $table . " (" . ucfirst($lang) . ")";
}
return $tabs;
}
@@ -784,6 +823,7 @@ class db_verify
*/
function fixForm($file, $table, $field, $newvalue, $mode, $after = '')
{
$frm = e107::getForm();
$text = $frm->checkbox("fix[$file][$table][$field][]", $mode, false, array('id' => false));
@@ -798,6 +838,7 @@ class db_verify
*/
function renderNotes($data, $mode = 'field')
{
// return "<pre>".print_r($data,TRUE)."</pre>";
$v = $data['_valid'];
@@ -828,7 +869,10 @@ class db_verify
public function toMysql($data, $mode = 'field')
{
if(!$data) return;
if(!$data)
{
return;
}
if($mode === 'index')
{
@@ -838,6 +882,7 @@ class db_verify
//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
@@ -850,6 +895,7 @@ class db_verify
if(!in_array(strtolower($data['type']), $this->fieldTypes))
{
$ret = $data['type'] . "(" . $data['value'] . ") " . $data['attributes'] . " " . $data['null'] . " " . $data['default'];
return trim($ret);
}
else
@@ -873,8 +919,10 @@ class db_verify
*/
function getPrevious($array, $cur)
{
$fkeys = array_keys($array);
$cur_key = array_search($cur, $fkeys);
return @$fkeys[$cur_key - 1];
}
@@ -883,6 +931,7 @@ class db_verify
*/
function getId($tabl, $cur)
{
if(empty($tabl))
{
return null;
@@ -983,13 +1032,13 @@ class db_verify
}
/**
* Fix tables
* FixArray eg. [core][table][field] = alter|create|index| etc.
*/
function runFix($fixArray = '')
{
$mes = e107::getMessage();
$log = e107::getLog();
@@ -1064,9 +1113,11 @@ class db_verify
*/
function getSqlFileTables($sql_data)
{
if(!$sql_data)
{
e107::getMessage()->addError("No SQL Data found in file");
return false;
}
@@ -1113,7 +1164,10 @@ class db_verify
foreach($match[3] as $rawTableOptions)
{
if (empty($rawTableOptions)) continue;
if(empty($rawTableOptions))
{
continue;
}
$engine = null;
$charset = null;
@@ -1193,7 +1247,10 @@ class db_verify
$ret = array();
if($print) var_dump($regex, $m);
if($print)
{
var_dump($regex, $m);
}
foreach($m[1] as $k => $val)
{
@@ -1211,108 +1268,70 @@ class db_verify
/**
* @param $data
* @param $print
* @return array
* Helper method to clean column names
*
* @param string $col The raw column name to clean
* @return string The cleaned column name
*/
function getIndex($data, $print = false)
private function cleanColumn($col)
{
// $regex = "/(PRIMARY|UNIQUE|FULLTEXT|FOREIGN)?[\s]*?(INDEX|KEY)[\s]*(?:`?([\w]*)`?)?[\s]*?\(`?([\w]+)`?(?:\s*\(\d+\))?(?:\s*(ASC|DESC))?\)[\s]*,?/i";
// $regex = "/(?P<type>PRIMARY|PRIMARY|UNIQUE|FULLTEXT|FOREIGN|KEY)[\s]*?(?P<key_type>INDEX|KEY)?[\s]*(?:`?(?P<field>[\w]*)`?)?[\s]*?\(`?(?P<keyname>[\w]+)`?(?:\s*\(\d+\))?(?:\s*(?P<order>ASC|DESC))?\)[\s]*,?/i";
$regex = "/(?P<type>PRIMARY|UNIQUE|FULLTEXT|FOREIGN|KEY|INDEX)[\s]*?(?P<key_type>INDEX|KEY)?[\s]*(?:`?(?P<field>[\w]*)`?)?[\s]*?\(`?(?P<keyname>[\w]+)`?(?:\s*\((?P<length>\d+)\))?(?:\s*(?P<order>ASC|DESC))?\)[\s]*,?/i";
$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 = "/(?P<type>PRIMARY|UNIQUE|FULLTEXT|FOREIGN|KEY|INDEX)[\s]*?(?P<key_type>INDEX|KEY)?[\s]*(?:`?(?P<field>[\w]*)`?)?[\s]*?\((?P<columns>[^)]+)\)[\s]*,?/i";
preg_match_all($regex, $data, $m);
$ret = [];
// Process each matched index
foreach($m['type'] as $k => $val)
{
$i = $m['field'][$k];
$type = trim(strtoupper($m['type'][$k]));
$keyname = trim($m['keyname'][$k]);
$field = trim($m['field'][$k]);
$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
if(empty($field) || $field === 'KEY')
// 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')
{
$field = $keyname;
$i = $keyname;
$i = $columnsArray[0]; // Primary key uses the column name
}
// Normalize KEY/INDEX to empty type for regular indexes
if($type === 'KEY' || $type === 'INDEX')
{
$type = '';
}
if(empty($keyname) || $keyname === 'KEY')
{
$keyname = $field;
}
// Build the result array for this index
$ret[$i] = array(
'type' => $type,
'keyname' => $keyname,
'field' => $field,
'field' => $i,
);
}
return $ret;
/*
if (count($m) > 0)
{
unset($m[2]);
$m = array_combine(range(0, count($m)-1), array_values($m));
}
$ret = array();
if($print)
{
e107::getDebug()->log($m);
}
// Standard Detection Method.
$fieldReplace = array("`"," ");
foreach($m[3] as $k=>$val)
{
if(!$val) continue;
$val = str_replace("`","",$val);
$key = !empty($m[2][$k]) ? $m[2][$k] : $val;
$ret[$key] = array(
'type' => strtoupper($m[1][$k]),
'keyname' => (!empty($m[2][$k])) ? str_replace("`","",$m[2][$k]) : str_replace("`","",$m[3][$k]),
'field' => str_replace($fieldReplace,"",$m[3][$k])
);
}
//Alternate Index detection method.
// eg. `table_id` INT( 10 ) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
$regex = "/`?([\w]*)`? .*((?:AUTO_INCREMENT))\s?(PRIMARY|UNIQUE|FULLTEXT|FOREIGN)\s?KEY\s?,/i";
preg_match_all($regex,$data,$m);
foreach($m[1] as $k=>$val)
{
if(!$val) continue;
$ret[$val] = array(
'type' => strtoupper($m[3][$k]),
'keyname' => $m[1][$k],
'field' => str_replace($fieldReplace,"",$m[1][$k])
);
}
if($print)
{
e107::getDebug()->log($ret);
}
return $ret;
*/
}
@@ -1331,7 +1350,7 @@ class db_verify
{
if(!in_array($tbl, $this->sqlLanguageTables[$language]))
{
return FALSE;
return false;
}
$prefix .= "lan_" . $language . "_";
@@ -1339,13 +1358,12 @@ class db_verify
}
$sql = e107::getDb();
if(!$sql->isTable($tbl))
{
$mes->addDebug('Missing table on db-verify: ' . $tbl);
return false;
}
@@ -1360,6 +1378,7 @@ class db_verify
{
// $row = mysql_fetch_row($z);
$row = $sql->fetch('num');
//return $row[1];
return stripslashes($row[1]) . ';'; // backticks needed.
@@ -1369,7 +1388,8 @@ class db_verify
{
$mes->addDebug('Failed: ' . $qry);
$this->internalError = true;
return FALSE;
return false;
}
}
@@ -1379,6 +1399,7 @@ class db_verify
*/
function getSqlLanguages()
{
$sql = e107::getDb();
$list = $sql->tables('lan');
@@ -1400,6 +1421,7 @@ class db_verify
*/
function renderTableSelect()
{
$frm = e107::getForm();
$ns = e107::getRender();
$mes = e107::getMessage();
@@ -1497,6 +1519,7 @@ class db_verify
*/
private function getAvailableStorageEngines()
{
$db = e107::getDb();
$db->gen("SHOW ENGINES;");
$output = [];
@@ -1504,6 +1527,7 @@ class db_verify
{
$output[] = $row['Engine'];
}
return $output;
}
@@ -1533,11 +1557,15 @@ class db_verify
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);
}
@@ -1550,8 +1578,11 @@ class db_verify
*/
public function getCanonicalStorageEngine($maybeStorageEngine)
{
if(in_array($maybeStorageEngine, $this->availableStorageEngines))
{
return $maybeStorageEngine;
}
throw new UnexpectedValueException(
"Unknown storage engine: " . var_export($maybeStorageEngine, true)
@@ -1566,7 +1597,11 @@ class db_verify
*/
public function getIntendedCharset($maybeCharset = null)
{
if (empty($maybeCharset)) return self::MOST_PREFERRED_CHARSET;
if(empty($maybeCharset))
{
return self::MOST_PREFERRED_CHARSET;
}
return $this->getCanonicalCharset($maybeCharset);
}
@@ -1579,17 +1614,23 @@ class db_verify
*/
public function getCanonicalCharset($maybeCharset)
{
if ($maybeCharset == "utf8") return "utf8mb4";
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');

View File

@@ -17,6 +17,7 @@ class db_verifyTest extends \Codeception\Test\Unit
protected function _before()
{
require_once(e_HANDLER . "db_verify_class.php");
try
{
@@ -32,6 +33,7 @@ class db_verifyTest extends \Codeception\Test\Unit
public function testGetFields()
{
$data = "table_id int(10) unsigned NOT NULL auto_increment,
table_name varchar(100) NOT NULL default '',
table_email varchar(100) NOT NULL default '',
@@ -216,6 +218,7 @@ class db_verifyTest extends \Codeception\Test\Unit
}
/*
public function testClearCache()
{
@@ -296,6 +299,7 @@ class db_verifyTest extends \Codeception\Test\Unit
*/
public function testGetIndexOptionalLengthAndSortOrder()
{
$data = <<<EOF
`field1` int(10) unsigned NOT NULL AUTO_INCREMENT,
`field2` varchar(100) NOT NULL DEFAULT '',
@@ -338,6 +342,51 @@ EOF;
self::assertEquals($expected, $result);
}
function testGetIndexCompositeKeys()
{
$data = <<<DATA
history_id int(10) unsigned NOT NULL auto_increment,
history_table varchar(64) NOT NULL default '',
history_pid varchar(64) NOT NULL default '',
history_record_id int(10) unsigned NOT NULL default '0',
history_action enum('delete','restore','update') NOT NULL,
history_data LONGTEXT DEFAULT NULL,
history_user_id int(10) unsigned NOT NULL default '0',
history_datestamp int(10) unsigned NOT NULL default '0',
history_restored int(10) unsigned NOT NULL default '0',
PRIMARY KEY (history_id),
KEY history_table_record (history_table, history_record_id),
KEY history_datestamp (history_datestamp)
DATA;
$expected = array(
'history_id' =>
array(
'type' => 'PRIMARY',
'keyname' => 'history_id',
'field' => 'history_id',
),
'history_table_record' =>
array(
'type' => '',
'keyname' => 'history_table,history_record_id',
'field' => 'history_table_record',
),
'history_datestamp' =>
array(
'type' => '',
'keyname' => 'history_datestamp',
'field' => 'history_datestamp',
),
);
$actual = $this->dbv->getIndex($data);
self::assertEquals($expected, $actual);
}
/**
* FIXME: This test has no assertions!
*/
@@ -445,6 +494,7 @@ EOF;
public function testToMysql()
{
$tests = array(
0 =>
array(
@@ -510,6 +560,7 @@ EOF;
*/
public function testGetSqlFileTables()
{
$tests = array(
@@ -850,7 +901,6 @@ EOF;
{
$sql = "`schedule_id` int(10) unsigned NOT NULL auto_increment,
`schedule_user_id` int(11) NOT NULL,
`schedule_invoice_id` int(11) NOT NULL,
@@ -1018,6 +1068,7 @@ EOF;
public function testSyntaxCheck()
{
$file = "`affiliate_id` int(10) unsigned NOT NULL auto_increment,
`affiliate_code` varchar(10) NOT NULL DEFAULT '',
`affiliate_name` varchar(30) NOT NULL DEFAULT '',
@@ -1042,7 +1093,6 @@ EOF;
$result = $this->dbv->hasSyntaxIssue($file);
var_export($result);
}
@@ -1071,6 +1121,7 @@ EOF;
public function testGetCanonicalStorageEngine()
{
$input = "InnoDB";
$output = $this->dbv->getCanonicalStorageEngine($input);
@@ -1080,6 +1131,7 @@ EOF;
public function testGetCanonicalStorageEngineUnknownStorageEngine()
{
$this->expectException(UnexpectedValueException::class);
$this->dbv->getCanonicalStorageEngine("FakeEngine");
@@ -1087,6 +1139,7 @@ EOF;
public function testGetCanonicalCharsetUtf8Alias()
{
$input = "utf8";
$expected = "utf8mb4";
@@ -1097,6 +1150,7 @@ EOF;
public function testGetCanonicalCharsetOther()
{
$inputs = ["latin1", "utf8mb3", "utf8mb4"];
foreach($inputs as $input)
@@ -1109,6 +1163,7 @@ EOF;
public function testGetIntendedStorageEngine()
{
$output = $this->dbv->getIntendedStorageEngine("MyISAM");
self::assertEquals("InnoDB", $output);
@@ -1130,6 +1185,7 @@ EOF;
public function testGetIntendedCharset()
{
$output = $this->dbv->getIntendedCharset("");
self::assertEquals("utf8mb4", $output);
@@ -1154,6 +1210,7 @@ EOF;
public function testRunFix()
{
self::markTestSkipped('Inconsistent behavior');
$sql = e107::getDb();
@@ -1190,7 +1247,6 @@ EOF;
}
}
}