diff --git a/e107_admin/sql/core_sql.php b/e107_admin/sql/core_sql.php index 6a4f0c493..e6454eef6 100644 --- a/e107_admin/sql/core_sql.php +++ b/e107_admin/sql/core_sql.php @@ -11,8 +11,8 @@ | GNU General Public License (http://gnu.org). | | $Source: /cvs_backup/e107_0.8/e107_admin/sql/core_sql.php,v $ -| $Revision: 1.3 $ -| $Date: 2007-09-22 21:46:09 $ +| $Revision: 1.4 $ +| $Date: 2007-12-08 15:11:43 $ | $Author: e107steved $ +----------------------------------------------------------------------------+ */ @@ -34,15 +34,40 @@ exit; # Database : # -------------------------------------------------------- +# +# Table structure for table `audit_log` - user audit trail +# +CREATE TABLE audit_log ( + dblog_id int(10) unsigned NOT NULL auto_increment, + dblog_datestamp int(10) unsigned NOT NULL default '0', + dblog_microtime int(10) unsigned NOT NULL default '0', + dblog_eventcode varchar(10) NOT NULL default '', + dblog_user_id int(10) unsigned NOT NULL default '0', + dblog_user_name varchar(100) NOT NULL default '', + dblog_ip varchar(45) NOT NULL default '', + dblog_title varchar(255) NOT NULL default '', + dblog_remarks text NOT NULL, + PRIMARY KEY (dblog_id), + KEY dblog_datestamp (dblog_datestamp) +) TYPE=MyISAM AUTO_INCREMENT=1; +# -------------------------------------------------------- + + # # Table structure for table `banlist` # CREATE TABLE banlist ( banlist_ip varchar(100) NOT NULL default '', + banlist_bantype tinyint(3) unsigned NOT NULL default '0', + banlist_datestamp int(10) unsigned NOT NULL default '0', + banlist_banexpires int(10) unsigned NOT NULL default '0', banlist_admin smallint(5) unsigned NOT NULL default '0', banlist_reason tinytext NOT NULL, - PRIMARY KEY (banlist_ip) + banlist_notes tinytext NOT NULL, + PRIMARY KEY (banlist_ip), + KEY banlist_datestamp (banlist_datestamp), + KEY banlist_banexpires (banlist_banexpires) ) TYPE=MyISAM; # -------------------------------------------------------- @@ -105,13 +130,16 @@ CREATE TABLE core ( # CREATE TABLE dblog ( dblog_id int(10) unsigned NOT NULL auto_increment, - dblog_type varchar(60) NOT NULL default '', dblog_datestamp int(10) unsigned NOT NULL default '0', + dblog_microtime int(10) unsigned NOT NULL default '0', + dblog_type tinyint(3) NOT NULL default '0', + dblog_eventcode varchar(10) NOT NULL default '', dblog_user_id int(10) unsigned NOT NULL default '0', - dblog_ip varchar(80) NOT NULL default '', + dblog_ip varchar(45) NOT NULL default '', dblog_title varchar(255) NOT NULL default '', dblog_remarks text NOT NULL, - PRIMARY KEY (dblog_id) + PRIMARY KEY (dblog_id), + KEY dblog_datestamp (dblog_datestamp) ) TYPE=MyISAM; # -------------------------------------------------------- @@ -364,6 +392,28 @@ CREATE TABLE rbinary ( ) TYPE=MyISAM; # -------------------------------------------------------- +# +# Table structure for table `rl_history` - rolling log +# + +CREATE TABLE rl_history ( + dblog_id int(10) unsigned NOT NULL auto_increment, + dblog_datestamp int(10) unsigned NOT NULL default '0', + dblog_microtime int(10) unsigned NOT NULL default '0', + dblog_type tinyint(3) NOT NULL default '0', + dblog_eventcode varchar(10) NOT NULL default '', + dblog_user_id int(10) unsigned NOT NULL default '0', + dblog_user_name varchar(100) NOT NULL default '', + dblog_ip varchar(45) NOT NULL default '', + dblog_caller varchar(255) NOT NULL default '', + dblog_title varchar(255) NOT NULL default '', + dblog_remarks text NOT NULL, + PRIMARY KEY (dblog_id), + KEY dblog_datestamp (dblog_datestamp) +) TYPE=MyISAM AUTO_INCREMENT=1; + +# -------------------------------------------------------- + # # Table structure for table `session` # @@ -484,6 +534,10 @@ CREATE TABLE userclass_classes ( userclass_name varchar(100) NOT NULL default '', userclass_description varchar(250) NOT NULL default '', userclass_editclass tinyint(3) unsigned NOT NULL default '0', + userclass_parent tinyint(3) unsigned NOT NULL default '0', + userclass_accum varchar(250) NOT NULL default '', + userclass_visibility tinyint(3) unsigned NOT NULL default '0', + userclass_icon varchar(250) NOT NULL default '', PRIMARY KEY (userclass_id) ) TYPE=MyISAM; # -------------------------------------------------------- diff --git a/e107_admin/update_routines.php b/e107_admin/update_routines.php index ad118ea03..00b2be7d5 100644 --- a/e107_admin/update_routines.php +++ b/e107_admin/update_routines.php @@ -11,13 +11,14 @@ | GNU General Public License (http://gnu.org). | | $Source: /cvs_backup/e107_0.8/e107_admin/update_routines.php,v $ -| $Revision: 1.10 $ -| $Date: 2007-09-22 21:46:09 $ +| $Revision: 1.11 $ +| $Date: 2007-12-08 15:11:43 $ | $Author: e107steved $ +----------------------------------------------------------------------------+ */ require_once("../class2.php"); +require_once(e_HANDLER.'db_table_admin_class.php'); // Modified update routine - combines checking and update code into one block per function @@ -163,7 +164,7 @@ function update_core_prefs($type='') { if ($k && !array_key_exists($k,$pref)) { - if ($just_check) return update_needed(); + if ($just_check) return update_needed('Missing pref: '.$k); $pref[$k] = $v; $do_save = TRUE; } @@ -181,11 +182,21 @@ function update_706_to_800($type='') global $sql,$ns, $pref; // List of unwanted $pref values which can go - $obs_prefs = array('frontpage_type'); + $obs_prefs = array('frontpage_type','rss_feeds'); // List of DB tables not required (includes a few from 0.6xx) $obs_tables = array('flood', 'headlines', 'stat_info', 'stat_counter', 'stat_last'); + + // List of DB tables newly required (defined in core_sql.php) + $new_tables = array('audit_log', 'rl_history'); + + + // List of changed DB tables (defined in core_sql.php) + // (primarily those which have changed significantly; for the odd field write some explicit code - it'll run faster) + $changed_tables = array('dblog','rl_history', 'userclass_classes', 'banlist'); + + // List of DB tables (key) and field (value) which need changing to accommodate IPV6 addresses $ip_upgrade = array('comments' => 'comment_ip', 'download_requests' => 'download_request_ip', @@ -195,7 +206,8 @@ function update_706_to_800($type='') 'user' => 'user_ip', 'chatbox' => 'cb_ip' ); - + + $db_parser = new db_table_admin; // Class to read table defs and process them $do_save = FALSE; $just_check = $type == 'do' ? FALSE : TRUE; // TRUE if we're just seeing if an update is needed @@ -307,6 +319,61 @@ function update_706_to_800($type='') } + // Tables defined in core_sql.php + //--------------------------------- + + // New tables required (list at top. Definitions in core_sql.php) + foreach ($new_tables as $nt) + { + if (!mysql_table_exists($nt)) + { + if ($just_check) return update_needed("Add table: ".$nt); + // Get the definition + $defs = $db_parser->get_table_def($nt,e_ADMIN."sql/core_sql.php"); + if (count($defs)) + { // **** Add in table here + $sql->db_Select_gen('CREATE TABLE `'.MPREFIX.$defs[0][1].'` ('.$defs[0][2].') TYPE='.$defs[0][3]); + catch_error(); + } + else + { // error parsing defs file + } + unset($defs); + } + } + + + // Tables whose definition needs changing significantly + foreach ($changed_tables as $ct) + { + $req_defs = $db_parser->get_table_def($ct,e_ADMIN."sql/core_sql.php"); + $req_fields = $db_parser->parse_field_defs($req_defs[0][2]); // Required definitions +// echo $db_parser->make_field_list($req_fields); + + if ((($actual_defs = $db_parser->get_current_table($ct)) === FALSE) || !is_array($actual_defs)) // Adds current default prefix + { + echo "Couldn't get table structure: {$ct}
"; + } + else + { +// echo $db_parser->make_table_list($actual_defs); + $actual_fields = $db_parser->parse_field_defs($actual_defs[0][2]); +// echo $db_parser->make_field_list($actual_fields); + + $diffs = $db_parser->compare_field_lists($req_fields,$actual_fields); + if (count($diffs[0])) + { // Changes needed + if ($just_check) return update_needed("Field changes rqd; table: ".$ct); + // Do the changes here + $qry = 'ALTER TABLE '.MPREFIX.$ct.' '.implode(', ',$diffs[1]); +// echo "Query: ".$qry."
"; + mysql_query($qry); + catch_error(); + } + } + } + + // Obsolete tables (list at top) foreach ($obs_tables as $ot) { diff --git a/e107_handlers/db_table_admin_class.php b/e107_handlers/db_table_admin_class.php new file mode 100644 index 000000000..218646f96 --- /dev/null +++ b/e107_handlers/db_table_admin_class.php @@ -0,0 +1,382 @@ +"; + $sql->db_Select_gen('SET SQL_QUOTE_SHOW_CREATE = 1'); + $qry = 'SHOW CREATE TABLE `'.$prefix.$table_name."`"; + if (!($z = $sql->db_Select_gen($qry))) return FALSE; + $row = $sql->db_Fetch(); + $tmp = str_replace("`", "", stripslashes($row[1])).';'; // Add semicolon to work with our parser + $count = preg_match_all("#CREATE\s+?TABLE\s+?`{0,1}({$prefix}{$table_name})`{0,1}\s+?\((.*?)\)\s+?(?:TYPE|ENGINE)\s*\=\s*(.*?);#is",$tmp,$matches,PREG_SET_ORDER); + if ($count === FALSE) return "Error occurred"; + if (!$count) return "No matches"; + return $matches; + } + + +// Routine to do first-level parse of table structure +//--------------------------------------------------- +// Given the name of a file, returns an array, with each element being a table creation definition. +// Tracks the last file read - only reads it once +// If the file name is an empty string, uses a previously read/set buffer +// If a table name is given, returns only that table's info; otherwise returns a list of all tables +// The table name must include a prefix where appropriate (although not required with standard E107 table definition files) +// Each element is itself an array: +// [0] - The complete string which creates a table (unless a prefix needs adding to the table name), including terminating ';' +// [1] - The table name. Any backticks are stripped +// [2] - Field definitions, with the surrounding (...) stripped +// [3] - The 'TYPE' field ('TYPE=' is stripped) and any AUTO-INCREMENT definition or other text. +// function get_table_def($table_name='',$file_name = e_ADMIN."sql/core_sql.php") + function get_table_def($table_name='',$file_name ="") + { + if ($file_name != '') + { // Read in and buffer a new file (if we've not already got one) + if ($this->last_file != $file_name) + { + if (!is_readable($file_name)) return "No file"; + $temp = file_get_contents($file_name); + // Strip any php header + $this->file_buffer = preg_replace("#\<\?php.*?\?\>#mis",'',$temp); + $this->last_file = $file_name; + } + } + if (!$table_name) $table_name = '\w+?'; + // Regex should be identical to that in get_current_table (apart from the source text variable name) + $count = preg_match_all("#CREATE\s+?TABLE\s+?`{0,1}({$table_name})`{0,1}\s+?\((.*?)\)\s+?(?:TYPE|ENGINE)\s*\=\s*(.*?);#is",$this->file_buffer,$matches,PREG_SET_ORDER); + if ($count === FALSE) return "Error occurred"; + if (!$count) return "No matches"; + return $matches; + } + + +// Parses the block of lines which make up the field and index definitions +// Returns an array where each entry is the definitions of a field or index + function parse_field_defs($text) + { + $ans = array(); + $field_lines = explode(',',$text); + foreach ($field_lines as $fv) + { + unset($defs); + $fv = trim(str_replace(' ',' ',$fv)); +// echo "Line: ".$fv."
"; + if ($fv) + { + $fd = explode(' ',$fv); + switch (strtoupper($fd[0])) + { + case 'PRIMARY' : + if (strtoupper($fd[1]) == 'KEY') + $defs['type'] = 'pkey'; + $defs['name'] = $fd[2]; + break; + case 'UNIQUE' : + if (count($fd) < 3) + { + echo "Truncated definition after UNIQUE {$i}: ".$fd[1]."
"; + } + elseif (strtoupper($fd[1]) == 'KEY') + { + $defs['type'] = 'ukey'; + $defs['name'] = $fd[2]; + if (isset($fd[3])) $defs['keyfield'] = $fd[3]; else $defs['keyfield'] = '['.$fd[2].']'; + } + else + { + echo "Unrecognised word after UNIQUE in definition {$i}: ".$fd[1]."
"; + } + break; + case 'KEY' : + $defs['type'] = 'key'; + $defs['name'] = $fd[1]; + if (isset($fd[2])) $defs['keyfield'] = $fd[2]; else $defs['keyfield'] = '['.$fd[1].']'; + break; + default : // Must be a DB field name + $defs['type'] = 'field'; + $defs['name'] = $fd[0]; + $defs['fieldtype'] = $fd[1]; + $i = 2; // First unused field + if ((strpos($fd[1],'int') === 0) || (strpos($fd[1],'tinyint') === 0) || (strpos($fd[1],'smallint') === 0) || (strpos($fd[1],'bigint') === 0)) + { + if (isset($fd[2]) && (strtoupper($fd[2]) == 'UNSIGNED')) + { + $defs['vartype'] = $fd[2]; + $i++; + } + } + while ($i < count($fd)) + { + switch(strtoupper($fd[$i])) + { + case 'NOT' : + if (isset($fd[$i+1]) && strtoupper($fd[$i+1]) == 'NULL') + { + $i++; + $defs['nulltype'] = 'NOT NULL'; + } + else + { // Syntax error + echo "Unrecognised word in definition {$i} after 'NOT': ".$fd[$i+1]."
"; + } + break; + case 'DEFAULT' : + if (isset($fd[$i+1])) + { + $i++; + $defs['default'] = $fd[$i]; + } + break; + case 'COLLATE' : + $i++; // Just skip over - we ignore collation + break; + case 'AUTO_INCREMENT' : + $defs['autoinc'] = TRUE; + break; + default : + echo "Unknown definition {$i}: ".$fd[$i]."
"; + } + $i++; + } + } + } + if (count($defs) > 1) $ans[] = $defs; else echo "Partial definition
"; + } + if (!count($ans)) return FALSE; + return $ans; + } + + + + // Utility routine - given our array-based definition, create a string MySQL field definition + function make_def($list) + { + switch ($list['type']) + { + case 'key' : + return 'KEY '.$list['name'].' ('.$list['name'].')'; + case 'ukey' : + return 'UNIQUE KEY '.$list['name'].' ('.$list['name'].')'; + case 'pkey' : + return 'PRIMARY KEY ('.$list['name'].')'; + case 'field' : // Require a field - got a key. so add a field at the end + $def = $list['name']; + if (isset($list['fieldtype'])) $def .= ' '.$list['fieldtype']; + if (isset($list['vartype'])) $def .= ' '.$list['vartype']; + if (isset($list['nulltype'])) $def .= ' '.$list['nulltype']; + if (isset($list['default'])) $def .= ' default '.$list['default']; + if (varsettrue($list['autoinc'])) $def .= ' auto_increment'; + return $def; + } + return "Cannot generate definition for: ".$list['type'].' '.$list['name']; + } + + + // Compare two field/index lists as generated by parse_field_defs + // If $stop_on_error is TRUE, returns TRUE if the same, false if different + // Return a text list of differences, plus an array of MySQL queries to fix + // List1 is the reference, List 2 is the actual + // This version looks ahead on a failed match, and moves a field up in the table if already defined - should retain as much as possible + function compare_field_lists($list1, $list2, $stop_on_error=FALSE) + { + $i = 0; // Counts records in list1 (required format) + $j = 0; // Counts records in $created_list (our 'table so far' list) + $error_list = array(); // Free text list of differences + $change_list = array(); // MySQL statements to implement changes + $created_list = array(); // List of field defs that we build up (just names) + while ($i < count($list1)) + { + if (count($list2) == 0) + { // Missing field at end + if ($stop_on_error) return FALSE; + $error_list[] = 'Missing field at end: '.$list1[$i]['name']; + $change_list[] = 'ADD '.$this->make_def($list1[$i]); + $created_list[$j] = $list1[$i]['name']; + $j++; + } + elseif ($list1[$i]['type'] == $list2[0]['type']) + { // Worth doing a compare - fields are same type + if (strcasecmp($list1[$i]['name'],$list2[0]['name']) != 0) + { // Names differ, so need to add or subtract a field. +// echo $i.': names differ - '.$list1[$i]['name'].', '.$list2[0]['name'].'
'; + if ($stop_on_error) return FALSE; + $found = FALSE; + for ($k = 0; $k < count($list2); $k++) + { +// echo "Compare ".$list1[$i]['name'].' with '.$list2[$k]['name']; + if (strcasecmp($list1[$i]['name'],$list2[$k]['name']) == 0) + { // Field found; we need to move it up +// echo " - match
"; + $found = TRUE; + break; + } +// echo " - no match
"; + } + if ($found) + { + $error_list[] = 'Field out of position: '.$list2[$k]['name']; + $change_list[] = 'MODIFY '.$this->make_def($list1[$i]).(count($created_list) ? ' AFTER '.$created_list[count($created_list)-1] : ' FIRST'); + array_splice($list2,$k,1); // Finished with this element - delete it, and renumber the keys + $created_list[$j] = $list1[$i]['name']; + $j++; + // The above also amends any parameters as necessary + } + else + { // Need to insert a field + $error_list[] = 'Missing field: '.$list1[$i]['name'].' (found: '.$list2[0]['type'].' '.$list2[0]['name'].')'; + $change_list[] = 'ADD '.$this->make_def($list1[$i]).(count($created_list) ? ' AFTER '.$created_list[count($created_list)-1] : ' FIRST'); + $created_list[$j] = $list1[$i]['name']; + $j++; + } + } + else + { // Field/index is present as required; may be changes though + // Any difference and we need to update the table +// echo $i.': name match - '.$list1[$i]['name'].'
'; + foreach ($list1[$i] as $fi => $v) + { + $t = $list2[0][$fi]; + if (stripos($v,'varchar') !== FALSE) $v = substr($v,3); // Treat char, varchar the same + if (stripos($t,'varchar') !== FALSE) $t = substr($t,3); // Treat char, varchar the same + if (strcasecmp($t , $v) !== 0) + { + if ($stop_on_error) return FALSE; + $error_list[] = 'Incorrect definition: '.$fi.' = '.$v; + $change_list[] = 'MODIFY '.$this->make_def($list1[$i]); + break; + } + } + array_shift($list2); + $created_list[$j] = $list1[$i]['name']; + $j++; + } + } + else + { // Field type has changed. We know fields come before indexes. So something's missing +// echo $i.': types differ - '.$list1[$i]['type'].' '.$list1[$i]['name'].', '.$list2[$k]['type'].' '.$list2[$k]['name'].'
'; + if ($stop_on_error) return FALSE; + switch ($list1[$i]['type']) + { + case 'key' : + case 'ukey' : + case 'pkey' : // Require a key - got a field + while ((count($list2)>0) && ($list2[0]['type'] == 'field')) + { + $error_list[] = 'Extra field: '.$list2[0]['name']; + $change_list[] = 'DROP '.$list2[0]['name']; + array_shift($list2); + } + break; + + case 'field' : // Require a field - got a key. so add a field at the end + $error_list[] = 'Missing field: '.$list1[$i]['name'].' (found: '.$list2[0]['type'].' '.$list2[0]['name'].')'; + $change_list[] = 'ADD '.$this->make_def($list1[$i]); + break; + + default : + $error_list[] = 'Unknown field type: '.$list1[$i]['type']; + $change_list[] = ''; // Null entry to keep them in step + } + } // End - missing or extra field + + $i++; // On to next field + } + if ($stop_on_error) return TRUE; // If doing a simple comparison and we get to here, all matches + return array($error_list, $change_list); + } + + + // Return a table of info from the output of get_table_def + function make_table_list($result) + { + if (!is_array($result)) return "Not an array
"; + $text = ""; + for ($i = 0; $i < count($result); $i++) + { + $text .= ""; + $text .= ""; + $text .= ""; + $text .= "\n"; + } + $text .= "
{$result[$i][0]}{$result[$i][1]}{$result[$i][2]}{$result[$i][3]}


"; + return $text; + } + + + // Return a table of info from the output of parse_field_defs() + function make_field_list($fields) + { + $text = ""; + foreach ($fields as $f) + { + switch($f['type']) + { + case 'pkey' : + $text .= ""; + break; + case 'ukey' : + $text .= ""; + break; + case 'key' : + $text .= ""; + break; + case 'field' : + $text .= ""; + if (isset($f['nulltype'])) $text .= ""; else $text .= ""; + if (isset($f['default'])) $text .= ""; elseif + (isset($f['autoinc'])) $text .= ""; else $text .= ""; + $text .= ""; + break; + default : + $text .= ""; + } + } + $text .= "
PRIMARY KEY{$f['name']} 
UNIQUE KEY{$f['name']}{$f['keyfield']}
KEY{$f['name']}{$f['keyfield']}
FIELD{$f['name']}{$f['fieldtype']}"; + if (isset($f['vartype'])) $text .= " ".$f['vartype']; + $text .= "{$f['nulltype']} default {$f['default']}AUTO_INCREMENT 
!!Unknown!!{$f['type']} 


--Ends--
"; + return $text; + } +} + + + +?> \ No newline at end of file