OSDN Git Service

Add key length when 'PRIMARY KEY' is used for full text column.
[nucleus-jp/nucleus-jp-ancient.git] / utf8 / nucleus / libs / backup.php
1 <?php
2 /*
3  * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
4  * Copyright (C) 2002-2007 The Nucleus Group
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  * (see nucleus/documentation/index.html#license for more info)
11  */
12 /**
13  * Scripts to create/restore a backup of the Nucleus database
14  *
15  * Based on code in phpBB (http://phpBB.sourceforge.net)
16  *
17  * @license http://nucleuscms.org/license.txt GNU General Public License
18  * @copyright Copyright (C) 2002-2007 The Nucleus Group
19  * @version $Id: backup.php,v 1.8 2007-02-19 22:27:59 kmorimatsu Exp $
20  * $NucleusJP: backup.php,v 1.7 2007/02/04 06:28:46 kimitake Exp $
21  */
22
23
24 /**
25   * This function creates an sql dump of the database and sends it to
26   * the user as a file (can be gzipped if they want)
27   *
28   * @requires
29   *             no output may have preceded (new headers are sent)
30   * @param gzip
31   *             1 = compress backup file, 0 = no compression (default)
32   */
33 function do_backup($gzip = 0) {
34         global $manager;
35
36         // tables of which backup is needed
37         $tables = array(
38                                         sql_table('actionlog'),
39                                         sql_table('ban'),
40                                         sql_table('blog'),
41                                         sql_table('comment'),
42                                         sql_table('config'),
43                                         sql_table('item'),
44                                         sql_table('karma'),
45                                         sql_table('member'),
46                                         sql_table('skin'),
47                                         sql_table('skin_desc'),
48                                         sql_table('team'),
49                                         sql_table('template'),
50                                         sql_table('template_desc'),
51                                         sql_table('plugin'),
52                                         sql_table('plugin_event'),
53                                         sql_table('plugin_option'),
54                                         sql_table('plugin_option_desc'),
55                                         sql_table('category'),
56                                         sql_table('activation'),
57                                         sql_table('tickets'),
58                           );
59
60         // add tables that plugins want to backup to the list
61         // catch all output generated by plugins
62         ob_start();
63         $res = sql_query('SELECT pfile FROM '.sql_table('plugin'));
64         while ($plugName = mysql_fetch_object($res)) {
65                 $plug =& $manager->getPlugin($plugName->pfile);
66                 if ($plug) $tables = array_merge($tables, (array) $plug->getTableList());
67         }
68         ob_end_clean();
69
70         // remove duplicates
71         $tables = array_unique($tables);
72
73         // make sure browsers don't cache the backup
74         header("Pragma: no-cache");
75
76         // don't allow gzip compression when extension is not loaded
77         if (($gzip != 0) && !extension_loaded("zlib"))
78                 $gzip = 0;
79
80
81
82         if ($gzip) {
83                 // use an output buffer
84                 @ob_start();
85                 @ob_implicit_flush(0);
86
87                 // set filename
88                 $filename = 'nucleus_db_backup_'.strftime("%Y%m%d", time()).".sql.gz";
89         } else {
90                 $filename = 'nucleus_db_backup_'.strftime("%Y%m%d", time()).".sql";
91         }
92
93
94         // send headers that tell the browser a file is coming
95         header("Content-Type: text/x-delimtext; name=\"$filename\"");
96         header("Content-disposition: attachment; filename=$filename");
97
98         // dump header
99         echo "#\n";
100         echo "# This is a backup file generated by Nucleus \n";
101         echo "# http://www.nucleuscms.org/\n";
102         echo "#\n";
103         echo "# backup-date: " .  gmdate("d-m-Y H:i:s", time()) . " GMT\n";
104         global $nucleus;
105         echo "# Nucleus CMS version: " . $nucleus['version'] . "\n";
106         echo "#\n";
107         echo "# WARNING: Only try to restore on servers running the exact same version of Nucleus\n";
108         echo "#\n";
109
110         // dump all tables
111         reset($tables);
112         array_walk($tables, '_backup_dump_table');
113
114         if($gzip)
115         {
116                 $Size = ob_get_length();
117                 $Crc = crc32(ob_get_contents());
118                 $contents = gzcompress(ob_get_contents());
119                 ob_end_clean();
120                 echo "\x1f\x8b\x08\x00\x00\x00\x00\x00".substr($contents, 0, strlen($contents) - 4).gzip_PrintFourChars($Crc).gzip_PrintFourChars($Size);
121         }
122
123         exit;
124
125 }
126
127
128 /**
129   * Creates a dump for a single table
130   * ($tablename and $key are filled in by array_walk)
131   */
132 function _backup_dump_table($tablename, $key) {
133
134         echo "#\n";
135         echo "# TABLE: " . $tablename . "\n";
136         echo "#\n";
137
138         // dump table structure
139         _backup_dump_structure($tablename);
140
141         // dump table contents
142         _backup_dump_contents($tablename);
143 }
144
145 function _backup_dump_structure($tablename) {
146
147         // add command to drop table on restore
148         echo "DROP TABLE IF EXISTS $tablename;\n";
149         echo "CREATE TABLE $tablename(\n";
150
151         //
152         // Ok lets grab the fields...
153         //
154         $result = mysql_query("SHOW FIELDS FROM $tablename");
155         $row = mysql_fetch_array($result);
156         while ($row) {
157
158                 echo '  ' . $row['Field'] . ' ' . $row['Type'];
159
160                 if(isset($row['Default']))
161                         echo ' DEFAULT \'' . $row['Default'] . '\'';
162
163                 if($row['Null'] != "YES")
164                         echo ' NOT NULL';
165
166                 if($row['Extra'] != "")
167                         echo ' ' . $row['Extra'];
168
169                 $row = mysql_fetch_array($result);
170
171                 // add comma's except for last one
172                 if ($row)
173                         echo ",\n";
174         }
175
176         //
177         // Get any Indexed fields from the database...
178         //
179         $result = mysql_query("SHOW KEYS FROM $tablename");
180         while($row = mysql_fetch_array($result)) {
181                 $kname = $row['Key_name'];
182
183                 if(($kname != 'PRIMARY') && ($row['Non_unique'] == 0))
184                         $kname = "UNIQUE|$kname";
185                 if(($kname != 'PRIMARY') && ($row['Index_type'] == 'FULLTEXT'))
186                         $kname = "FULLTEXT|$kname";
187
188                 if(!is_array($index[$kname]))
189                         $index[$kname] = array();
190
191                 $index[$kname][] = $row['Column_name'] . ( ($row['Sub_part']) ? ' (' . $row['Sub_part'] . ')' : '');
192         }
193
194         while(list($x, $columns) = @each($index)) {
195                 echo ", \n";
196
197                 if($x == 'PRIMARY')
198                         echo '  PRIMARY KEY (' . implode($columns, ', ') . ')';
199                 elseif (substr($x,0,6) == 'UNIQUE')
200                         echo '  UNIQUE KEY ' . substr($x,7) . ' (' . implode($columns, ', ') . ')';
201                 elseif (substr($x,0,8) == 'FULLTEXT')
202                         echo '  FULLTEXT KEY ' . substr($x,9) . ' (' . implode($columns, ', ') . ')';
203                 elseif (($x == 'ibody') || ($x == 'cbody'))                     // karma 2004-05-30 quick and dirty fix. fulltext keys were not in SQL correctly.
204                         echo '  FULLTEXT KEY ' . substr($x,9) . ' (' . implode($columns, ', ') . ')';
205                 else
206                         echo "  KEY $x (" . implode($columns, ', ') . ')';
207         }
208
209         echo "\n);\n\n";
210 }
211
212 /**
213  * Returns the field named for the given table in the 
214  * following format:
215  *
216  * (column1, column2, ..., columnn)
217  */
218 function _backup_get_field_names($result, $num_fields) {
219
220         if (function_exists('mysqli_fetch_fields') ) {
221                 
222                 $fields = mysqli_fetch_fields($result);
223                 for ($j = 0; $j < $num_fields; $j++)
224                         $fields[$j] = $fields[$j]->name;
225
226         } else {
227
228                 $fields = array();
229                 for ($j = 0; $j < $num_fields; $j++) {
230                         $fields[] = mysql_field_name($result, $j);
231                 }
232
233         }
234         
235         return '(' . implode(', ', $fields) . ')';      
236 }
237
238 function _backup_dump_contents($tablename) {
239         //
240         // Grab the data from the table.
241         //
242         $result = mysql_query("SELECT * FROM $tablename");
243
244         if(mysql_num_rows($result) > 0)
245                 echo "\n#\n# Table Data for $tablename\n#\n";
246                 
247         $num_fields = mysql_num_fields($result);
248         
249         //
250         // Compose fieldname list
251         //
252         $tablename_list = _backup_get_field_names($result, $num_fields);
253                 
254         //
255         // Loop through the resulting rows and build the sql statement.
256         //
257         while ($row = mysql_fetch_array($result))
258         {
259                 // Start building the SQL statement.
260
261                 echo "INSERT INTO $tablename $tablename_list VALUES(";
262
263                 // Loop through the rows and fill in data for each column
264                 for ($j = 0; $j < $num_fields; $j++) {
265                         if(!isset($row[$j])) {
266                                 // no data for column
267                                 echo ' NULL';
268                         } elseif ($row[$j] != '') {
269                                 // data
270                                 echo " '" . addslashes($row[$j]) . "'";
271                         } else {
272                                 // empty column (!= no data!)
273                                 echo "''";
274                         }
275
276                         // only add comma when not last column
277                         if ($j != ($num_fields - 1))
278                                 echo ",";
279                 }
280
281                 echo ");\n";
282
283         }
284
285
286         echo "\n";
287
288 }
289
290 // copied from phpBB
291 function gzip_PrintFourChars($Val)
292 {
293         for ($i = 0; $i < 4; $i ++)
294         {
295                 $return .= chr($Val % 256);
296                 $Val = floor($Val / 256);
297         }
298         return $return;
299 }
300
301 function do_restore() {
302
303         $uploadInfo = postFileInfo('backup_file');
304
305         // first of all: get uploaded file:
306         if (empty($uploadInfo['name']))
307                 return 'No file uploaded';
308         if (!is_uploaded_file($uploadInfo['tmp_name']))
309                 return 'No file uploaded';
310
311         $backup_file_name = $uploadInfo['name'];
312         $backup_file_tmpname = $uploadInfo['tmp_name'];
313         $backup_file_type = $uploadInfo['type'];
314
315         if (!file_exists($backup_file_tmpname))
316                 return 'File Upload Error';
317
318         if (!preg_match("/^(text\/[a-zA-Z]+)|(application\/(x\-)?gzip(\-compressed)?)|(application\/octet-stream)$/is", $backup_file_type) )
319                 return 'The uploaded file is not of the correct type';
320
321
322
323         if (preg_match("/\.gz/is",$backup_file_name))
324                 $gzip = 1;
325         else
326                 $gzip = 0;
327
328         if (!extension_loaded("zlib") && $gzip)
329                 return "Cannot decompress gzipped backup (zlib package not installed)";
330
331         // get sql query according to gzip setting (either decompress, or not)
332         if($gzip)
333         {
334                 // decompress and read
335                 $gz_ptr = gzopen($backup_file_tmpname, 'rb');
336                 $sql_query = "";
337                 while( !gzeof($gz_ptr) )
338                         $sql_query .= gzgets($gz_ptr, 100000);
339         } else {
340                 // just read
341                 $fsize = filesize($backup_file_tmpname);
342                 if ($fsize <= 0)
343                         $sql_query = '';
344                 else
345                         $sql_query = fread(fopen($backup_file_tmpname, 'r'), $fsize);
346         }
347
348         // time to execute the query
349         _execute_queries($sql_query);
350 }
351
352 function _execute_queries($sql_query) {
353         if (!$sql_query) return;
354
355         // Strip out sql comments...
356         $sql_query = remove_remarks($sql_query);
357         $pieces = split_sql_file($sql_query);
358
359         $sql_count = count($pieces);
360         for($i = 0; $i < $sql_count; $i++)
361         {
362                 $sql = trim($pieces[$i]);
363
364                 if(!empty($sql) and $sql[0] != "#")
365                 {
366                         // DEBUG
367 //                      debug("Executing: " . htmlspecialchars($sql) . "\n");
368
369                         $result = mysql_query($sql);
370                         if (!$result) debug('SQL Error: ' . mysql_error());
371
372                 }
373         }
374
375 }
376
377 //
378 // remove_remarks will strip the sql comment lines out of an uploaded sql file
379 //
380 function remove_remarks($sql)
381 {
382         $lines = explode("\n", $sql);
383
384         // try to keep mem. use down
385         $sql = "";
386
387         $linecount = count($lines);
388         $output = "";
389
390         for ($i = 0; $i < $linecount; $i++)
391         {
392                 if (($i != ($linecount - 1)) || (strlen($lines[$i]) > 0))
393                 {
394                         if ($lines[$i][0] != "#")
395                         {
396                                 $output .= $lines[$i] . "\n";
397                         }
398                         else
399                         {
400                                 $output .= "\n";
401                         }
402                         // Trading a bit of speed for lower mem. use here.
403                         $lines[$i] = "";
404                 }
405         }
406
407         return $output;
408
409 }
410
411
412 //
413 // split_sql_file will split an uploaded sql file into single sql statements.
414 // Note: expects trim() to have already been run on $sql.
415 //
416 // taken from phpBB
417 //
418 function split_sql_file($sql)
419 {
420         // Split up our string into "possible" SQL statements.
421         $tokens = explode( ";", $sql);
422
423         // try to save mem.
424         $sql = "";
425         $output = array();
426
427         // we don't actually care about the matches preg gives us.
428         $matches = array();
429
430         // this is faster than calling count($tokens) every time thru the loop.
431         $token_count = count($tokens);
432         for ($i = 0; $i < $token_count; $i++)
433         {
434                 // Don't wanna add an empty string as the last thing in the array.
435                 if (($i != ($token_count - 1)) || (strlen($tokens[$i] > 0)))
436                 {
437
438                         // even number of quotes means a complete SQL statement
439                         if (_evenNumberOfQuotes($tokens[$i]))
440                         {
441                                 $output[] = $tokens[$i];
442                                 $tokens[$i] = "";       // save memory.
443                         }
444                         else
445                         {
446                                 // incomplete sql statement. keep adding tokens until we have a complete one.
447                                 // $temp will hold what we have so far.
448                                 $temp = $tokens[$i] .  ";";
449                                 $tokens[$i] = "";       // save memory..
450
451                                 // Do we have a complete statement yet?
452                                 $complete_stmt = false;
453
454                                 for ($j = $i + 1; (!$complete_stmt && ($j < $token_count)); $j++)
455                                 {
456                                         // odd number of quotes means a completed statement
457                                         // (in combination with the odd number we had already)
458                                         if (!_evenNumberOfQuotes($tokens[$j]))
459                                         {
460                                                 $output[] = $temp . $tokens[$j];
461
462                                                 // save memory.
463                                                 $tokens[$j] = "";
464                                                 $temp = "";
465
466                                                 // exit the loop.
467                                                 $complete_stmt = true;
468                                                 // make sure the outer loop continues at the right point.
469                                                 $i = $j;
470                                         }
471                                         else
472                                         {
473                                                 // even number of unescaped quotes. We still don't have a complete statement.
474                                                 // (1 odd and 1 even always make an odd)
475                                                 $temp .= $tokens[$j] .  ";";
476                                                 // save memory.
477                                                 $tokens[$j] = "";
478                                         }
479
480                                 } // for..
481                         } // else
482                 }
483         }
484
485         return $output;
486 }
487
488
489 function _evenNumberOfQuotes($text) {
490                 // This is the total number of single quotes in the token.
491                 $total_quotes = preg_match_all("/'/", $text, $matches);
492                 // Counts single quotes that are preceded by an odd number of backslashes,
493                 // which means they're escaped quotes.
494                 $escaped_quotes = preg_match_all("/(?<!\\\\)(\\\\\\\\)*\\\\'/", $text, $matches);
495
496                 $unescaped_quotes = $total_quotes - $escaped_quotes;
497 //              debug($total_quotes . "-" . $escaped_quotes . "-" . $unescaped_quotes);
498                 return (($unescaped_quotes % 2) == 0);
499 }
500
501 ?>