3 * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
\r
4 * Copyright (C) 2002-2005 The Nucleus Group
\r
6 * This program is free software; you can redistribute it and/or
\r
7 * modify it under the terms of the GNU General Public License
\r
8 * as published by the Free Software Foundation; either version 2
\r
9 * of the License, or (at your option) any later version.
\r
10 * (see nucleus/documentation/index.html#license for more info)
\r
12 * Scripts to create/restore a backup of the Nucleus database
\r
14 * Based on code in phpBB (http://phpBB.sourceforge.net)
\r
16 * $Id: backup.php,v 1.3 2005-03-16 08:10:35 kimitake Exp $
\r
17 * $NucleusJP: backup.php,v 1.3 2005/03/12 06:19:05 kimitake Exp $
\r
22 * This function creates an sql dump of the database and sends it to
\r
23 * the user as a file (can be gzipped if they want)
\r
26 * no output may have preceded (new headers are sent)
\r
28 * 1 = compress backup file, 0 = no compression (default)
\r
30 function do_backup($gzip = 0) {
\r
33 // tables of which backup is needed
\r
35 sql_table('actionlog'),
\r
38 sql_table('comment'),
\r
39 sql_table('config'),
\r
42 sql_table('member'),
\r
44 sql_table('skin_desc'),
\r
46 sql_table('template'),
\r
47 sql_table('template_desc'),
\r
48 sql_table('plugin'),
\r
49 sql_table('plugin_event'),
\r
50 sql_table('plugin_option'),
\r
51 sql_table('plugin_option_desc'),
\r
52 sql_table('category'),
\r
53 sql_table('activation'),
\r
54 sql_table('tickets'),
\r
57 // add tables that plugins want to backup to the list
\r
58 // catch all output generated by plugins
\r
60 $res = sql_query('SELECT pfile FROM '.sql_table('plugin'));
\r
61 while ($plugName = mysql_fetch_object($res)) {
\r
62 $plug =& $manager->getPlugin($plugName->pfile);
\r
63 if ($plug) $tables = array_merge($tables, $plug->getTableList());
\r
67 // remove duplicates
\r
68 $tables = array_unique($tables);
\r
70 // make sure browsers don't cache the backup
\r
71 header("Pragma: no-cache");
\r
73 // don't allow gzip compression when extension is not loaded
\r
74 if (($gzip != 0) && !extension_loaded("zlib"))
\r
80 // use an output buffer
\r
82 @ob_implicit_flush(0);
\r
85 $filename = 'nucleus_db_backup_'.strftime("%Y%m%d", time()).".sql.gz";
\r
87 $filename = 'nucleus_db_backup_'.strftime("%Y%m%d", time()).".sql";
\r
91 // send headers that tell the browser a file is coming
\r
92 header("Content-Type: text/x-delimtext; name=\"$filename\"");
\r
93 header("Content-disposition: attachment; filename=$filename");
\r
97 echo "# This is a backup file generated by Nucleus \n";
\r
98 echo "# http://www.nucleuscms.org/\n";
\r
100 echo "# backup-date: " . gmdate("d-m-Y H:i:s", time()) . " GMT\n";
\r
102 echo "# Nucleus CMS version: " . $nucleus['version'] . "\n";
\r
104 echo "# WARNING: Only try to restore on servers running the exact same version of Nucleus\n";
\r
109 array_walk($tables, '_backup_dump_table');
\r
113 $Size = ob_get_length();
\r
114 $Crc = crc32(ob_get_contents());
\r
115 $contents = gzcompress(ob_get_contents());
\r
117 echo "\x1f\x8b\x08\x00\x00\x00\x00\x00".substr($contents, 0, strlen($contents) - 4).gzip_PrintFourChars($Crc).gzip_PrintFourChars($Size);
\r
126 * Creates a dump for a single table
\r
127 * ($tablename and $key are filled in by array_walk)
\r
129 function _backup_dump_table($tablename, $key) {
\r
132 echo "# TABLE: " . $tablename . "\n";
\r
135 // dump table structure
\r
136 _backup_dump_structure($tablename);
\r
138 // dump table contents
\r
139 _backup_dump_contents($tablename);
\r
142 function _backup_dump_structure($tablename) {
\r
144 // add command to drop table on restore
\r
145 echo "DROP TABLE IF EXISTS $tablename;\n";
\r
146 echo "CREATE TABLE $tablename(\n";
\r
149 // Ok lets grab the fields...
\r
151 $result = mysql_query("SHOW FIELDS FROM $tablename");
\r
152 $row = mysql_fetch_array($result);
\r
155 echo ' ' . $row['Field'] . ' ' . $row['Type'];
\r
157 if(!empty($row['Default']))
\r
158 echo ' DEFAULT \'' . $row['Default'] . '\'';
\r
160 if($row['Null'] != "YES")
\r
163 if($row['Extra'] != "")
\r
164 echo ' ' . $row['Extra'];
\r
166 $row = mysql_fetch_array($result);
\r
168 // add comma's except for last one
\r
174 // Get any Indexed fields from the database...
\r
176 $result = mysql_query("SHOW KEYS FROM $tablename");
\r
177 while($row = mysql_fetch_array($result)) {
\r
178 $kname = $row['Key_name'];
\r
180 if(($kname != 'PRIMARY') && ($row['Non_unique'] == 0))
\r
181 $kname = "UNIQUE|$kname";
\r
182 if(($kname != 'PRIMARY') && ($row['Index_type'] == 'FULLTEXT'))
\r
183 $kname = "FULLTEXT|$kname";
\r
185 if(!is_array($index[$kname]))
\r
186 $index[$kname] = array();
\r
188 $index[$kname][] = $row['Column_name'];
\r
191 while(list($x, $columns) = @each($index)) {
\r
194 if($x == 'PRIMARY')
\r
195 echo ' PRIMARY KEY (' . implode($columns, ', ') . ')';
\r
196 elseif (substr($x,0,6) == 'UNIQUE')
\r
197 echo ' UNIQUE KEY ' . substr($x,7) . ' (' . implode($columns, ', ') . ')';
\r
198 elseif (substr($x,0,8) == 'FULLTEXT')
\r
199 echo ' FULLTEXT KEY ' . substr($x,9) . ' (' . implode($columns, ', ') . ')';
\r
200 elseif (($x == 'ibody') || ($x == 'cbody')) // karma 2004-05-30 quick and dirty fix. fulltext keys were not in SQL correctly.
\r
201 echo ' FULLTEXT KEY ' . substr($x,9) . ' (' . implode($columns, ', ') . ')';
\r
203 echo " KEY $x (" . implode($columns, ', ') . ')';
\r
209 function _backup_dump_contents($tablename) {
\r
211 // Grab the data from the table.
\r
213 $result = mysql_query("SELECT * FROM $tablename");
\r
215 if(mysql_numrows($result) > 0)
\r
216 echo "\n#\n# Table Data for $tablename\n#\n";
\r
219 // Loop through the resulting rows and build the sql statement.
\r
221 while ($row = mysql_fetch_array($result))
\r
223 $tablename_list = '(';
\r
224 $num_fields = mysql_num_fields($result);
\r
227 // Grab the list of field names.
\r
229 for ($j = 0; $j < $num_fields; $j++)
\r
230 $tablename_list .= mysql_field_name($result, $j) . ', ';
\r
233 // Get rid of the last comma
\r
235 $tablename_list = ereg_replace(', $', '', $tablename_list);
\r
236 $tablename_list .= ')';
\r
238 // Start building the SQL statement.
\r
240 echo "INSERT INTO $tablename $tablename_list VALUES(";
\r
242 // Loop through the rows and fill in data for each column
\r
243 for ($j = 0; $j < $num_fields; $j++) {
\r
244 if(!isset($row[$j])) {
\r
245 // no data for column
\r
247 } elseif ($row[$j] != '') {
\r
249 echo " '" . addslashes($row[$j]) . "'";
\r
251 // empty column (!= no data!)
\r
255 // only add comma when not last column
\r
256 if ($j != ($num_fields - 1))
\r
269 // copied from phpBB
\r
270 function gzip_PrintFourChars($Val)
\r
272 for ($i = 0; $i < 4; $i ++)
\r
274 $return .= chr($Val % 256);
\r
275 $Val = floor($Val / 256);
\r
280 function do_restore() {
\r
282 $uploadInfo = postFileInfo('backup_file');
\r
284 // first of all: get uploaded file:
\r
285 if (empty($uploadInfo['name']))
\r
286 return 'No file uploaded';
\r
287 if (!is_uploaded_file($uploadInfo['tmp_name']))
\r
288 return 'No file uploaded';
\r
290 $backup_file_name = $uploadInfo['name'];
\r
291 $backup_file_tmpname = $uploadInfo['tmp_name'];
\r
292 $backup_file_type = $uploadInfo['type'];
\r
294 if (!file_exists($backup_file_tmpname))
\r
295 return 'File Upload Error';
\r
297 if (!preg_match("/^(text\/[a-zA-Z]+)|(application\/(x\-)?gzip(\-compressed)?)|(application\/octet-stream)$/is", $backup_file_type) )
\r
298 return 'The uploaded file is not of the correct type';
\r
302 if (preg_match("/\.gz/is",$backup_file_name))
\r
307 if (!extension_loaded("zlib") && $gzip)
\r
308 return "Cannot decompress gzipped backup (zlib package not installed)";
\r
310 // get sql query according to gzip setting (either decompress, or not)
\r
313 // decompress and read
\r
314 $gz_ptr = gzopen($backup_file_tmpname, 'rb');
\r
316 while( !gzeof($gz_ptr) )
\r
317 $sql_query .= gzgets($gz_ptr, 100000);
\r
320 $fsize = filesize($backup_file_tmpname);
\r
324 $sql_query = fread(fopen($backup_file_tmpname, 'r'), $fsize);
\r
327 // time to execute the query
\r
328 _execute_queries($sql_query);
\r
331 function _execute_queries($sql_query) {
\r
332 if (!$sql_query) return;
\r
334 // Strip out sql comments...
\r
335 $sql_query = remove_remarks($sql_query);
\r
336 $pieces = split_sql_file($sql_query);
\r
338 $sql_count = count($pieces);
\r
339 for($i = 0; $i < $sql_count; $i++)
\r
341 $sql = trim($pieces[$i]);
\r
343 if(!empty($sql) and $sql[0] != "#")
\r
346 // debug("Executing: " . htmlspecialchars($sql) . "\n");
\r
348 $result = mysql_query($sql);
\r
349 if (!$result) debug("SQL Error: " + mysql_error());
\r
357 // remove_remarks will strip the sql comment lines out of an uploaded sql file
\r
359 function remove_remarks($sql)
\r
361 $lines = explode("\n", $sql);
\r
363 // try to keep mem. use down
\r
366 $linecount = count($lines);
\r
369 for ($i = 0; $i < $linecount; $i++)
\r
371 if (($i != ($linecount - 1)) || (strlen($lines[$i]) > 0))
\r
373 if ($lines[$i][0] != "#")
\r
375 $output .= $lines[$i] . "\n";
\r
381 // Trading a bit of speed for lower mem. use here.
\r
392 // split_sql_file will split an uploaded sql file into single sql statements.
\r
393 // Note: expects trim() to have already been run on $sql.
\r
395 // taken from phpBB
\r
397 function split_sql_file($sql)
\r
399 // Split up our string into "possible" SQL statements.
\r
400 $tokens = explode( ";", $sql);
\r
402 // try to save mem.
\r
406 // we don't actually care about the matches preg gives us.
\r
407 $matches = array();
\r
409 // this is faster than calling count($tokens) every time thru the loop.
\r
410 $token_count = count($tokens);
\r
411 for ($i = 0; $i < $token_count; $i++)
\r
413 // Don't wanna add an empty string as the last thing in the array.
\r
414 if (($i != ($token_count - 1)) || (strlen($tokens[$i] > 0)))
\r
417 // even number of quotes means a complete SQL statement
\r
418 if (_evenNumberOfQuotes($tokens[$i]))
\r
420 $output[] = $tokens[$i];
\r
421 $tokens[$i] = ""; // save memory.
\r
425 // incomplete sql statement. keep adding tokens until we have a complete one.
\r
426 // $temp will hold what we have so far.
\r
427 $temp = $tokens[$i] . ";";
\r
428 $tokens[$i] = ""; // save memory..
\r
430 // Do we have a complete statement yet?
\r
431 $complete_stmt = false;
\r
433 for ($j = $i + 1; (!$complete_stmt && ($j < $token_count)); $j++)
\r
435 // odd number of quotes means a completed statement
\r
436 // (in combination with the odd number we had already)
\r
437 if (!_evenNumberOfQuotes($tokens[$j]))
\r
439 $output[] = $temp . $tokens[$j];
\r
446 $complete_stmt = true;
\r
447 // make sure the outer loop continues at the right point.
\r
452 // even number of unescaped quotes. We still don't have a complete statement.
\r
453 // (1 odd and 1 even always make an odd)
\r
454 $temp .= $tokens[$j] . ";";
\r
468 function _evenNumberOfQuotes($text) {
\r
469 // This is the total number of single quotes in the token.
\r
470 $total_quotes = preg_match_all("/'/", $text, $matches);
\r
471 // Counts single quotes that are preceded by an odd number of backslashes,
\r
472 // which means they're escaped quotes.
\r
473 $escaped_quotes = preg_match_all("/(?<!\\\\)(\\\\\\\\)*\\\\'/", $text, $matches);
\r
475 $unescaped_quotes = $total_quotes - $escaped_quotes;
\r
476 // debug($total_quotes . "-" . $escaped_quotes . "-" . $unescaped_quotes);
\r
477 return (($unescaped_quotes % 2) == 0);
\r