3 * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
\r
4 * Copyright (C) 2002-2010 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
13 * Scripts to create/restore a backup of the Nucleus database
\r
15 * Based on code in phpBB (http://phpBB.sourceforge.net)
\r
17 * @license http://nucleuscms.org/license.txt GNU General Public License
\r
18 * @copyright Copyright (C) 2002-2010 The Nucleus Group
\r
20 * $NucleusJP: backup.php,v 1.8.2.1 2007/08/08 05:23:31 kimitake Exp $
\r
37 * This function creates an sql dump of the database and sends it to
\r
38 * the user as a file (can be gzipped if they want)
\r
41 * no output may have preceded (new headers are sent)
\r
43 * 1 = compress backup file, 0 = no compression (default)
\r
45 function do_backup($gzip = 0) {
\r
48 // tables of which backup is needed
\r
50 sql_table('actionlog'),
\r
53 sql_table('comment'),
\r
54 sql_table('config'),
\r
57 sql_table('member'),
\r
59 sql_table('skin_desc'),
\r
61 sql_table('template'),
\r
62 sql_table('template_desc'),
\r
63 sql_table('plugin'),
\r
64 sql_table('plugin_event'),
\r
65 sql_table('plugin_option'),
\r
66 sql_table('plugin_option_desc'),
\r
67 sql_table('category'),
\r
68 sql_table('activation'),
\r
69 sql_table('tickets'),
\r
72 // add tables that plugins want to backup to the list
\r
73 // catch all output generated by plugins
\r
75 $res = sql_query('SELECT pfile FROM '.sql_table('plugin'));
\r
76 while ($plugName = sql_fetch_object($res)) {
\r
77 $plug =& $manager->getPlugin($plugName->pfile);
\r
78 if ($plug) $tables = array_merge($tables, (array) $plug->getTableList());
\r
82 // remove duplicates
\r
83 $tables = array_unique($tables);
\r
85 // make sure browsers don't cache the backup
\r
86 header("Pragma: no-cache");
\r
88 // don't allow gzip compression when extension is not loaded
\r
89 if (($gzip != 0) && !extension_loaded("zlib")) {
\r
94 // use an output buffer
\r
96 @ob_implicit_flush(0);
\r
99 $filename = 'nucleus_db_backup_'.strftime("%Y-%m-%d-%H-%M-%S", time()).".sql.gz";
\r
101 $filename = 'nucleus_db_backup_'.strftime("%Y-%m-%d-%H-%M-%S", time()).".sql";
\r
105 // send headers that tell the browser a file is coming
\r
106 header("Content-Type: text/x-delimtext; name=\"$filename\"");
\r
107 header("Content-disposition: attachment; filename=$filename");
\r
111 echo "# " . _BACKUP_BACKUPFILE_TITLE . " \n";
\r
112 echo "# " . _ADMINPAGEFOOT_OFFICIALURL . "\n";
\r
114 echo "# " . _BACKUP_BACKUPFILE_BACKUPDATE . gmdate("d-m-Y H:i:s", time()) . " GMT\n";
\r
116 echo "# " . _BACKUP_BACKUPFILE_NUCLEUSVERSION . $nucleus['version'] . "\n";
\r
118 echo "# " . _BACKUP_WARNING_NUCLEUSVERSION . "\n";
\r
123 array_walk($tables, array(&$this, '_backup_dump_table'));
\r
126 $Size = ob_get_length();
\r
127 $Crc = crc32(ob_get_contents());
\r
128 $contents = gzcompress(ob_get_contents());
\r
130 echo "\x1f\x8b\x08\x00\x00\x00\x00\x00".substr($contents, 0, strlen($contents) - 4).$this->gzip_PrintFourChars($Crc).$this->gzip_PrintFourChars($Size);
\r
139 * Creates a dump for a single table
\r
140 * ($tablename and $key are filled in by array_walk)
\r
142 function _backup_dump_table($tablename, $key) {
\r
145 echo "# " . _BACKUP_BACKUPFILE_TABLE_NAME . $tablename . "\n";
\r
148 // dump table structure
\r
149 $this->_backup_dump_structure($tablename);
\r
151 // dump table contents
\r
152 $this->_backup_dump_contents($tablename);
\r
156 * Creates a dump of the table structure for one table
\r
158 function _backup_dump_structure($tablename) {
\r
160 // add command to drop table on restore
\r
161 echo "DROP TABLE IF EXISTS $tablename;\n";
\r
162 $result = sql_query("SHOW CREATE TABLE $tablename");
\r
163 $create = sql_fetch_assoc($result);
\r
164 echo $create['Create Table'];
\r
169 * Creates a dump of the table structure for one table
\r
171 /* replaced by code above in 3.5
\r
172 function _backup_dump_structure($tablename) {
\r
174 // add command to drop table on restore
\r
175 echo "DROP TABLE IF EXISTS $tablename;\n";
\r
176 echo "CREATE TABLE $tablename(\n";
\r
179 // Ok lets grab the fields...
\r
181 $result = mysql_query("SHOW FIELDS FROM $tablename");
\r
182 $row = mysql_fetch_array($result);
\r
185 echo ' `' . $row['Field'] . '` ' . $row['Type'];
\r
187 if(isset($row['Default']))
\r
188 echo ' DEFAULT \'' . $row['Default'] . '\'';
\r
190 if($row['Null'] != "YES")
\r
193 if($row['Extra'] != "")
\r
194 echo ' ' . $row['Extra'];
\r
196 $row = mysql_fetch_array($result);
\r
198 // add comma's except for last one
\r
204 // Get any Indexed fields from the database...
\r
206 $result = mysql_query("SHOW KEYS FROM $tablename");
\r
207 while($row = mysql_fetch_array($result)) {
\r
208 $kname = $row['Key_name'];
\r
210 if(($kname != 'PRIMARY') && ($row['Non_unique'] == 0))
\r
211 $kname = "UNIQUE|$kname";
\r
212 if(($kname != 'PRIMARY') && ($row['Index_type'] == 'FULLTEXT'))
\r
213 $kname = "FULLTEXT|$kname";
\r
215 if(!is_array($index[$kname]))
\r
216 $index[$kname] = array();
\r
218 $index[$kname][] = $row['Column_name'] . ( ($row['Sub_part']) ? ' (' . $row['Sub_part'] . ')' : '');
\r
221 while(list($x, $columns) = @each($index)) {
\r
224 if($x == 'PRIMARY')
\r
225 echo ' PRIMARY KEY (`' . implode($columns, '`, `') . '`)';
\r
226 elseif (substr($x,0,6) == 'UNIQUE')
\r
227 echo ' UNIQUE KEY ' . substr($x,7) . ' (`' . implode($columns, '`, `') . '`)';
\r
228 elseif (substr($x,0,8) == 'FULLTEXT')
\r
229 echo ' FULLTEXT KEY ' . substr($x,9) . ' (`' . implode($columns, '`, `') . '`)';
\r
230 elseif (($x == 'ibody') || ($x == 'cbody')) // karma 2004-05-30 quick and dirty fix. fulltext keys were not in SQL correctly.
\r
231 echo ' FULLTEXT KEY ' . substr($x,9) . ' (`' . implode($columns, '`, `') . '`)';
\r
233 echo " KEY $x (`" . implode($columns, '`, `') . '`)';
\r
241 * Returns the field named for the given table in the
\r
242 * following format:
\r
244 * (column1, column2, ..., columnn)
\r
246 function _backup_get_field_names($result, $num_fields) {
\r
248 /* if (function_exists('mysqli_fetch_fields') ) {
\r
250 $fields = mysqli_fetch_fields($result);
\r
251 for ($j = 0; $j < $num_fields; $j++)
\r
252 $fields[$j] = $fields[$j]->name;
\r
257 for ($j = 0; $j < $num_fields; $j++) {
\r
258 $fields[] = sql_field_name($result, $j);
\r
263 return '(`' . implode('`, `', $fields) . '`)';
\r
267 * Creates a dump of the table content for one table
\r
269 function _backup_dump_contents($tablename) {
\r
271 // Grab the data from the table.
\r
273 $result = sql_query("SELECT * FROM $tablename");
\r
275 if(sql_num_rows($result) > 0)
\r
276 echo "\n#\n# " . sprintf(_BACKUP_BACKUPFILE_TABLEDATAFOR, $tablename) . "\n#\n";
\r
278 $num_fields = sql_num_fields($result);
\r
281 // Compose fieldname list
\r
283 $tablename_list = $this->_backup_get_field_names($result, $num_fields);
\r
286 // Loop through the resulting rows and build the sql statement.
\r
288 while ($row = sql_fetch_array($result))
\r
290 // Start building the SQL statement.
\r
292 echo "INSERT INTO `".$tablename."` $tablename_list VALUES(";
\r
294 // Loop through the rows and fill in data for each column
\r
295 for ($j = 0; $j < $num_fields; $j++) {
\r
296 if(!isset($row[$j])) {
\r
297 // no data for column
\r
299 } elseif ($row[$j] != '') {
\r
301 echo " '" . addslashes($row[$j]) . "'";
\r
303 // empty column (!= no data!)
\r
307 // only add comma when not last column
\r
308 if ($j != ($num_fields - 1))
\r
322 * copied from phpBB
\r
324 function gzip_PrintFourChars($Val)
\r
326 for ($i = 0; $i < 4; $i ++)
\r
328 $return .= chr($Val % 256);
\r
329 $Val = floor($Val / 256);
\r
335 * Restores a database backup
\r
337 function do_restore() {
\r
339 $uploadInfo = postFileInfo('backup_file');
\r
341 // first of all: get uploaded file:
\r
342 if (empty($uploadInfo['name']))
\r
343 return _BACKUP_RESTOR_NOFILEUPLOADED;
\r
344 if (!is_uploaded_file($uploadInfo['tmp_name']))
\r
345 return _BACKUP_RESTOR_NOFILEUPLOADED;
\r
347 $backup_file_name = $uploadInfo['name'];
\r
348 $backup_file_tmpname = $uploadInfo['tmp_name'];
\r
349 $backup_file_type = $uploadInfo['type'];
\r
351 if (!file_exists($backup_file_tmpname))
\r
352 return _BACKUP_RESTOR_UPLOAD_ERROR;
\r
354 if (!preg_match("/^(text\/[a-zA-Z]+)|(application\/(x\-)?gzip(\-compressed)?)|(application\/octet-stream)$/is", $backup_file_type) )
\r
355 return _BACKUP_RESTOR_UPLOAD_NOCORRECTTYPE;
\r
358 if (preg_match("/\.gz/is",$backup_file_name))
\r
363 if (!extension_loaded("zlib") && $gzip)
\r
364 return _BACKUP_RESTOR_UPLOAD_NOZLIB;
\r
366 // get sql query according to gzip setting (either decompress, or not)
\r
369 // decompress and read
\r
370 $gz_ptr = gzopen($backup_file_tmpname, 'rb');
\r
372 while( !gzeof($gz_ptr) )
\r
373 $sql_query .= gzgets($gz_ptr, 100000);
\r
376 $fsize = filesize($backup_file_tmpname);
\r
380 $sql_query = fread(fopen($backup_file_tmpname, 'r'), $fsize);
\r
383 // time to execute the query
\r
384 $this->_execute_queries($sql_query);
\r
388 * Executes a SQL query
\r
390 function _execute_queries($sql_query) {
\r
391 if (!$sql_query) return;
\r
393 // Strip out sql comments...
\r
394 $sql_query = $this->remove_remarks($sql_query);
\r
395 $pieces = $this->split_sql_file($sql_query);
\r
397 $sql_count = count($pieces);
\r
398 for($i = 0; $i < $sql_count; $i++)
\r
400 $sql = trim($pieces[$i]);
\r
402 if(!empty($sql) and $sql[0] != "#")
\r
405 // debug("Executing: " . htmlspecialchars($sql) . "\n");
\r
407 $result = sql_query($sql);
\r
408 if (!$result) debug(_BACKUP_RESTOR_SQL_ERROR . sql_error());
\r
416 * remove_remarks will strip the sql comment lines
\r
417 * out of an uploaded sql file
\r
419 function remove_remarks($sql)
\r
421 $lines = explode("\n", $sql);
\r
423 // try to keep mem. use down
\r
426 $linecount = count($lines);
\r
429 for ($i = 0; $i < $linecount; $i++)
\r
431 if (($i != ($linecount - 1)) || (strlen($lines[$i]) > 0))
\r
433 if ($lines[$i][0] != "#")
\r
435 $output .= $lines[$i] . "\n";
\r
441 // Trading a bit of speed for lower mem. use here.
\r
451 * split_sql_file will split an uploaded sql file
\r
452 * into single sql statements.
\r
454 * Note: expects trim() to have already been run on $sql.
\r
457 function split_sql_file($sql)
\r
459 // Split up our string into "possible" SQL statements.
\r
460 $tokens = explode( ";", $sql);
\r
462 // try to save mem.
\r
466 // we don't actually care about the matches preg gives us.
\r
467 $matches = array();
\r
469 // this is faster than calling count($tokens) every time thru the loop.
\r
470 $token_count = count($tokens);
\r
471 for ($i = 0; $i < $token_count; $i++)
\r
473 // Don't wanna add an empty string as the last thing in the array.
\r
474 if (($i != ($token_count - 1)) || (strlen($tokens[$i] > 0)))
\r
477 // even number of quotes means a complete SQL statement
\r
478 if ($this->_evenNumberOfQuotes($tokens[$i]))
\r
480 $output[] = $tokens[$i];
\r
481 $tokens[$i] = ""; // save memory.
\r
485 // incomplete sql statement. keep adding tokens until we have a complete one.
\r
486 // $temp will hold what we have so far.
\r
487 $temp = $tokens[$i] . ";";
\r
488 $tokens[$i] = ""; // save memory..
\r
490 // Do we have a complete statement yet?
\r
491 $complete_stmt = false;
\r
493 for ($j = $i + 1; (!$complete_stmt && ($j < $token_count)); $j++)
\r
495 // odd number of quotes means a completed statement
\r
496 // (in combination with the odd number we had already)
\r
497 if (!$this->_evenNumberOfQuotes($tokens[$j]))
\r
499 $output[] = $temp . $tokens[$j];
\r
506 $complete_stmt = true;
\r
507 // make sure the outer loop continues at the right point.
\r
512 // even number of unescaped quotes. We still don't have a complete statement.
\r
513 // (1 odd and 1 even always make an odd)
\r
514 $temp .= $tokens[$j] . ";";
\r
528 * sub function of split_sql_file
\r
532 function _evenNumberOfQuotes($text) {
\r
533 // This is the total number of single quotes in the token.
\r
534 $total_quotes = preg_match_all("/'/", $text, $matches);
\r
535 // Counts single quotes that are preceded by an odd number of backslashes,
\r
536 // which means they're escaped quotes.
\r
537 $escaped_quotes = preg_match_all("/(?<!\\\\)(\\\\\\\\)*\\\\'/", $text, $matches);
\r
539 $unescaped_quotes = $total_quotes - $escaped_quotes;
\r
540 // debug($total_quotes . "-" . $escaped_quotes . "-" . $unescaped_quotes);
\r
541 return (($unescaped_quotes % 2) == 0);
\r