backup.php

Go to the documentation of this file.
00001 <?php
00002 /*
00003  * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
00004  * Copyright (C) 2002-2007 The Nucleus Group
00005  *
00006  * This program is free software; you can redistribute it and/or
00007  * modify it under the terms of the GNU General Public License
00008  * as published by the Free Software Foundation; either version 2
00009  * of the License, or (at your option) any later version.
00010  * (see nucleus/documentation/index.html#license for more info)
00011  */
00033 function do_backup($gzip = 0) {
00034         global $manager;
00035 
00036         // tables of which backup is needed
00037         $tables = array(
00038                                         sql_table('actionlog'),
00039                                         sql_table('ban'),
00040                                         sql_table('blog'),
00041                                         sql_table('comment'),
00042                                         sql_table('config'),
00043                                         sql_table('item'),
00044                                         sql_table('karma'),
00045                                         sql_table('member'),
00046                                         sql_table('skin'),
00047                                         sql_table('skin_desc'),
00048                                         sql_table('team'),
00049                                         sql_table('template'),
00050                                         sql_table('template_desc'),
00051                                         sql_table('plugin'),
00052                                         sql_table('plugin_event'),
00053                                         sql_table('plugin_option'),
00054                                         sql_table('plugin_option_desc'),
00055                                         sql_table('category'),
00056                                         sql_table('activation'),
00057                                         sql_table('tickets'),
00058                           );
00059 
00060         // add tables that plugins want to backup to the list
00061         // catch all output generated by plugins
00062         ob_start();
00063         $res = sql_query('SELECT pfile FROM '.sql_table('plugin'));
00064         while ($plugName = mysql_fetch_object($res)) {
00065                 $plug =& $manager->getPlugin($plugName->pfile);
00066                 if ($plug) $tables = array_merge($tables, (array) $plug->getTableList());
00067         }
00068         ob_end_clean();
00069 
00070         // remove duplicates
00071         $tables = array_unique($tables);
00072 
00073         // make sure browsers don't cache the backup
00074         header("Pragma: no-cache");
00075 
00076         // don't allow gzip compression when extension is not loaded
00077         if (($gzip != 0) && !extension_loaded("zlib"))
00078                 $gzip = 0;
00079 
00080 
00081 
00082         if ($gzip) {
00083                 // use an output buffer
00084                 @ob_start();
00085                 @ob_implicit_flush(0);
00086 
00087                 // set filename
00088                 $filename = 'nucleus_db_backup_'.strftime("%Y%m%d", time()).".sql.gz";
00089         } else {
00090                 $filename = 'nucleus_db_backup_'.strftime("%Y%m%d", time()).".sql";
00091         }
00092 
00093 
00094         // send headers that tell the browser a file is coming
00095         header("Content-Type: text/x-delimtext; name=\"$filename\"");
00096         header("Content-disposition: attachment; filename=$filename");
00097 
00098         // dump header
00099         echo "#\n";
00100         echo "# This is a backup file generated by Nucleus \n";
00101         echo "# http://www.nucleuscms.org/\n";
00102         echo "#\n";
00103         echo "# backup-date: " .  gmdate("d-m-Y H:i:s", time()) . " GMT\n";
00104         global $nucleus;
00105         echo "# Nucleus CMS version: " . $nucleus['version'] . "\n";
00106         echo "#\n";
00107         echo "# WARNING: Only try to restore on servers running the exact same version of Nucleus\n";
00108         echo "#\n";
00109 
00110         // dump all tables
00111         reset($tables);
00112         array_walk($tables, '_backup_dump_table');
00113 
00114         if($gzip)
00115         {
00116                 $Size = ob_get_length();
00117                 $Crc = crc32(ob_get_contents());
00118                 $contents = gzcompress(ob_get_contents());
00119                 ob_end_clean();
00120                 echo "\x1f\x8b\x08\x00\x00\x00\x00\x00".substr($contents, 0, strlen($contents) - 4).gzip_PrintFourChars($Crc).gzip_PrintFourChars($Size);
00121         }
00122 
00123         exit;
00124 
00125 }
00126 
00127 
00132 function _backup_dump_table($tablename, $key) {
00133 
00134         echo "#\n";
00135         echo "# TABLE: " . $tablename . "\n";
00136         echo "#\n";
00137 
00138         // dump table structure
00139         _backup_dump_structure($tablename);
00140 
00141         // dump table contents
00142         _backup_dump_contents($tablename);
00143 }
00144 
00145 function _backup_dump_structure($tablename) {
00146 
00147         // add command to drop table on restore
00148         echo "DROP TABLE IF EXISTS $tablename;\n";
00149         echo "CREATE TABLE $tablename(\n";
00150 
00151         //
00152         // Ok lets grab the fields...
00153         //
00154         $result = mysql_query("SHOW FIELDS FROM $tablename");
00155         $row = mysql_fetch_array($result);
00156         while ($row) {
00157 
00158                 echo '  ' . $row['Field'] . ' ' . $row['Type'];
00159 
00160                 if(isset($row['Default']))
00161                         echo ' DEFAULT \'' . $row['Default'] . '\'';
00162 
00163                 if($row['Null'] != "YES")
00164                         echo ' NOT NULL';
00165 
00166                 if($row['Extra'] != "")
00167                         echo ' ' . $row['Extra'];
00168 
00169                 $row = mysql_fetch_array($result);
00170 
00171                 // add comma's except for last one
00172                 if ($row)
00173                         echo ",\n";
00174         }
00175 
00176         //
00177         // Get any Indexed fields from the database...
00178         //
00179         $result = mysql_query("SHOW KEYS FROM $tablename");
00180         while($row = mysql_fetch_array($result)) {
00181                 $kname = $row['Key_name'];
00182 
00183                 if(($kname != 'PRIMARY') && ($row['Non_unique'] == 0))
00184                         $kname = "UNIQUE|$kname";
00185                 if(($kname != 'PRIMARY') && ($row['Index_type'] == 'FULLTEXT'))
00186                         $kname = "FULLTEXT|$kname";
00187 
00188                 if(!is_array($index[$kname]))
00189                         $index[$kname] = array();
00190 
00191                 $index[$kname][] = $row['Column_name'] . ( ($row['Sub_part']) ? ' (' . $row['Sub_part'] . ')' : '');
00192         }
00193 
00194         while(list($x, $columns) = @each($index)) {
00195                 echo ", \n";
00196 
00197                 if($x == 'PRIMARY')
00198                         echo '  PRIMARY KEY (' . implode($columns, ', ') . ')';
00199                 elseif (substr($x,0,6) == 'UNIQUE')
00200                         echo '  UNIQUE KEY ' . substr($x,7) . ' (' . implode($columns, ', ') . ')';
00201                 elseif (substr($x,0,8) == 'FULLTEXT')
00202                         echo '  FULLTEXT KEY ' . substr($x,9) . ' (' . implode($columns, ', ') . ')';
00203                 elseif (($x == 'ibody') || ($x == 'cbody'))                     // karma 2004-05-30 quick and dirty fix. fulltext keys were not in SQL correctly.
00204                         echo '  FULLTEXT KEY ' . substr($x,9) . ' (' . implode($columns, ', ') . ')';
00205                 else
00206                         echo "  KEY $x (" . implode($columns, ', ') . ')';
00207         }
00208 
00209         echo "\n);\n\n";
00210 }
00211 
00218 function _backup_get_field_names($result, $num_fields) {
00219 
00220 /*      if (function_exists('mysqli_fetch_fields') ) {
00221                 
00222                 $fields = mysqli_fetch_fields($result);
00223                 for ($j = 0; $j < $num_fields; $j++)
00224                         $fields[$j] = $fields[$j]->name;
00225 
00226         } else {*/
00227 
00228                 $fields = array();
00229                 for ($j = 0; $j < $num_fields; $j++) {
00230                         $fields[] = mysql_field_name($result, $j);
00231                 }
00232 
00233 /*      }*/
00234         
00235         return '(' . implode(', ', $fields) . ')';      
00236 }
00237 
00238 function _backup_dump_contents($tablename) {
00239         //
00240         // Grab the data from the table.
00241         //
00242         $result = mysql_query("SELECT * FROM $tablename");
00243 
00244         if(mysql_num_rows($result) > 0)
00245                 echo "\n#\n# Table Data for $tablename\n#\n";
00246                 
00247         $num_fields = mysql_num_fields($result);
00248         
00249         //
00250         // Compose fieldname list
00251         //
00252         $tablename_list = _backup_get_field_names($result, $num_fields);
00253                 
00254         //
00255         // Loop through the resulting rows and build the sql statement.
00256         //
00257         while ($row = mysql_fetch_array($result))
00258         {
00259                 // Start building the SQL statement.
00260 
00261                 echo "INSERT INTO $tablename $tablename_list VALUES(";
00262 
00263                 // Loop through the rows and fill in data for each column
00264                 for ($j = 0; $j < $num_fields; $j++) {
00265                         if(!isset($row[$j])) {
00266                                 // no data for column
00267                                 echo ' NULL';
00268                         } elseif ($row[$j] != '') {
00269                                 // data
00270                                 echo " '" . addslashes($row[$j]) . "'";
00271                         } else {
00272                                 // empty column (!= no data!)
00273                                 echo "''";
00274                         }
00275 
00276                         // only add comma when not last column
00277                         if ($j != ($num_fields - 1))
00278                                 echo ",";
00279                 }
00280 
00281                 echo ");\n";
00282 
00283         }
00284 
00285 
00286         echo "\n";
00287 
00288 }
00289 
00290 // copied from phpBB
00291 function gzip_PrintFourChars($Val)
00292 {
00293         for ($i = 0; $i < 4; $i ++)
00294         {
00295                 $return .= chr($Val % 256);
00296                 $Val = floor($Val / 256);
00297         }
00298         return $return;
00299 }
00300 
00301 function do_restore() {
00302 
00303         $uploadInfo = postFileInfo('backup_file');
00304 
00305         // first of all: get uploaded file:
00306         if (empty($uploadInfo['name']))
00307                 return 'No file uploaded';
00308         if (!is_uploaded_file($uploadInfo['tmp_name']))
00309                 return 'No file uploaded';
00310 
00311         $backup_file_name = $uploadInfo['name'];
00312         $backup_file_tmpname = $uploadInfo['tmp_name'];
00313         $backup_file_type = $uploadInfo['type'];
00314 
00315         if (!file_exists($backup_file_tmpname))
00316                 return 'File Upload Error';
00317 
00318         if (!preg_match("/^(text\/[a-zA-Z]+)|(application\/(x\-)?gzip(\-compressed)?)|(application\/octet-stream)$/is", $backup_file_type) )
00319                 return 'The uploaded file is not of the correct type';
00320 
00321 
00322 
00323         if (preg_match("/\.gz/is",$backup_file_name))
00324                 $gzip = 1;
00325         else
00326                 $gzip = 0;
00327 
00328         if (!extension_loaded("zlib") && $gzip)
00329                 return "Cannot decompress gzipped backup (zlib package not installed)";
00330 
00331         // get sql query according to gzip setting (either decompress, or not)
00332         if($gzip)
00333         {
00334                 // decompress and read
00335                 $gz_ptr = gzopen($backup_file_tmpname, 'rb');
00336                 $sql_query = "";
00337                 while( !gzeof($gz_ptr) )
00338                         $sql_query .= gzgets($gz_ptr, 100000);
00339         } else {
00340                 // just read
00341                 $fsize = filesize($backup_file_tmpname);
00342                 if ($fsize <= 0)
00343                         $sql_query = '';
00344                 else
00345                         $sql_query = fread(fopen($backup_file_tmpname, 'r'), $fsize);
00346         }
00347 
00348         // time to execute the query
00349         _execute_queries($sql_query);
00350 }
00351 
00352 function _execute_queries($sql_query) {
00353         if (!$sql_query) return;
00354 
00355         // Strip out sql comments...
00356         $sql_query = remove_remarks($sql_query);
00357         $pieces = split_sql_file($sql_query);
00358 
00359         $sql_count = count($pieces);
00360         for($i = 0; $i < $sql_count; $i++)
00361         {
00362                 $sql = trim($pieces[$i]);
00363 
00364                 if(!empty($sql) and $sql[0] != "#")
00365                 {
00366                         // DEBUG
00367 //                      debug("Executing: " . htmlspecialchars($sql) . "\n");
00368 
00369                         $result = mysql_query($sql);
00370                         if (!$result) debug('SQL Error: ' . mysql_error());
00371 
00372                 }
00373         }
00374 
00375 }
00376 
00377 //
00378 // remove_remarks will strip the sql comment lines out of an uploaded sql file
00379 //
00380 function remove_remarks($sql)
00381 {
00382         $lines = explode("\n", $sql);
00383 
00384         // try to keep mem. use down
00385         $sql = "";
00386 
00387         $linecount = count($lines);
00388         $output = "";
00389 
00390         for ($i = 0; $i < $linecount; $i++)
00391         {
00392                 if (($i != ($linecount - 1)) || (strlen($lines[$i]) > 0))
00393                 {
00394                         if ($lines[$i][0] != "#")
00395                         {
00396                                 $output .= $lines[$i] . "\n";
00397                         }
00398                         else
00399                         {
00400                                 $output .= "\n";
00401                         }
00402                         // Trading a bit of speed for lower mem. use here.
00403                         $lines[$i] = "";
00404                 }
00405         }
00406 
00407         return $output;
00408 
00409 }
00410 
00411 
00412 //
00413 // split_sql_file will split an uploaded sql file into single sql statements.
00414 // Note: expects trim() to have already been run on $sql.
00415 //
00416 // taken from phpBB
00417 //
00418 function split_sql_file($sql)
00419 {
00420         // Split up our string into "possible" SQL statements.
00421         $tokens = explode( ";", $sql);
00422 
00423         // try to save mem.
00424         $sql = "";
00425         $output = array();
00426 
00427         // we don't actually care about the matches preg gives us.
00428         $matches = array();
00429 
00430         // this is faster than calling count($tokens) every time thru the loop.
00431         $token_count = count($tokens);
00432         for ($i = 0; $i < $token_count; $i++)
00433         {
00434                 // Don't wanna add an empty string as the last thing in the array.
00435                 if (($i != ($token_count - 1)) || (strlen($tokens[$i] > 0)))
00436                 {
00437 
00438                         // even number of quotes means a complete SQL statement
00439                         if (_evenNumberOfQuotes($tokens[$i]))
00440                         {
00441                                 $output[] = $tokens[$i];
00442                                 $tokens[$i] = "";       // save memory.
00443                         }
00444                         else
00445                         {
00446                                 // incomplete sql statement. keep adding tokens until we have a complete one.
00447                                 // $temp will hold what we have so far.
00448                                 $temp = $tokens[$i] .  ";";
00449                                 $tokens[$i] = "";       // save memory..
00450 
00451                                 // Do we have a complete statement yet?
00452                                 $complete_stmt = false;
00453 
00454                                 for ($j = $i + 1; (!$complete_stmt && ($j < $token_count)); $j++)
00455                                 {
00456                                         // odd number of quotes means a completed statement
00457                                         // (in combination with the odd number we had already)
00458                                         if (!_evenNumberOfQuotes($tokens[$j]))
00459                                         {
00460                                                 $output[] = $temp . $tokens[$j];
00461 
00462                                                 // save memory.
00463                                                 $tokens[$j] = "";
00464                                                 $temp = "";
00465 
00466                                                 // exit the loop.
00467                                                 $complete_stmt = true;
00468                                                 // make sure the outer loop continues at the right point.
00469                                                 $i = $j;
00470                                         }
00471                                         else
00472                                         {
00473                                                 // even number of unescaped quotes. We still don't have a complete statement.
00474                                                 // (1 odd and 1 even always make an odd)
00475                                                 $temp .= $tokens[$j] .  ";";
00476                                                 // save memory.
00477                                                 $tokens[$j] = "";
00478                                         }
00479 
00480                                 } // for..
00481                         } // else
00482                 }
00483         }
00484 
00485         return $output;
00486 }
00487 
00488 
00489 function _evenNumberOfQuotes($text) {
00490                 // This is the total number of single quotes in the token.
00491                 $total_quotes = preg_match_all("/'/", $text, $matches);
00492                 // Counts single quotes that are preceded by an odd number of backslashes,
00493                 // which means they're escaped quotes.
00494                 $escaped_quotes = preg_match_all("/(?<!\\\\)(\\\\\\\\)*\\\\'/", $text, $matches);
00495 
00496                 $unescaped_quotes = $total_quotes - $escaped_quotes;
00497 //              debug($total_quotes . "-" . $escaped_quotes . "-" . $unescaped_quotes);
00498                 return (($unescaped_quotes % 2) == 0);
00499 }
00500 
00501 ?>



Generated on Wed Jun 25 17:25:57 2008 by  doxygen 1.5.5