<?php

/**
  * BLOG:CMS: PHP/MySQL Personal Content Management System (CMS)
  * http://blogcms.com/
  * ----------------------------------------------------------------
  *
  * Copyright (C) 2003-2005 Radek HULÁN
  * http://hulan.cz/contact/
  *
  * Based on: 
  * ----------------------------------------------------------------
  * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/) 
  * Copyright (C) 2002-2003 The Nucleus Group
  *
  * ----------------------------------------------------------------
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
  * as published by the Free Software Foundation; either version 2
  * of the License, or (at your option) any later version.
**/

/**
  * This function creates an sql dump of the database and sends it to
  * the user as a file (can be gzipped if they want)
  */
function do_backup($gzip = 0) {
	global $manager, $SQL_TYPE;
	
	if ($SQL_TYPE==2) {
	  do_backup_sqlite($gzip);
	  return;
	}

	// tables of which backup is needed
	$tables = array(
					sql_table('actionlog'),
					sql_table('ban'),
					sql_table('blog'),
					sql_table('comment'),
					sql_table('config'),
					sql_table('item'),
					sql_table('karma'),
					sql_table('member'),
					sql_table('skin'),
					sql_table('skin_desc'),
					sql_table('team'),
					sql_table('template'),
					sql_table('template_desc'),
					sql_table('plugin'),
					sql_table('plugin_event'),
					sql_table('plugin_option'),
					sql_table('plugin_option_desc'),
					sql_table('category')
			  );

	// add tables that plugins want to backup to the list
	// catch all output generated by plugins
	ob_start();
	$res = sql_query('SELECT pfile FROM '.sql_table('plugin'));
	while ($plugName = sql_fetch_object($res)) {
		$plug =& $manager->getPlugin($plugName->pfile);
		if ($plug) $tables = array_merge($tables, $plug->getTableList());
	}
	ob_end_clean();

	// remove duplicates
	$tables = array_unique($tables);

	// make sure browsers don't cache the backup
	header("Pragma: no-cache");

	// don't allow gzip compression when extension is not loaded
	if (($gzip != 0) && !extension_loaded("zlib")) $gzip = 0;

	if ($gzip) {
		// use an output buffer
		@ob_start();
		@ob_implicit_flush(0);
		// set filename
		$filename = 'blogcms_db_backup_'.strftime("%Y%m%d", time()).".sql.gz";
	} else {
		$filename = 'blogcms_db_backup_'.strftime("%Y%m%d", time()).".sql";
	}

	// send headers that tell the browser a file is coming
	header("Content-Type: text/x-delimtext; name=\"$filename\"");
	header("Content-disposition: attachment; filename=$filename");

	// dump header
	echo "#\n";
	echo "# This is a backup file generated by BLOG:CMS \n";
	echo "# http://blogcms.com/\n";
	echo "#\n";
	echo "# backup-date: " .  gmdate("d-m-Y H:i:s", time()) . " GMT\n";
	global $nucleus;
	echo "# BLOG:CMS version: " . $nucleus['version'] . "\n";
	echo "#\n";
	echo "# WARNING: Only try to restore on servers running the exact same version of BLOG:CMS\n";
	echo "#\n";

	// dump all tables
	reset($tables);
	array_walk($tables, '_backup_dump_table');

	if($gzip)
	{
		$Size = ob_get_length();
		$Crc = crc32(ob_get_contents());
		$contents = gzcompress(ob_get_contents());
		ob_end_clean();
		echo "\x1f\x8b\x08\x00\x00\x00\x00\x00".substr($contents, 0, strlen($contents) - 4).gzip_PrintFourChars($Crc).gzip_PrintFourChars($Size);
	}
	exit;
}

/**
  * This function will backup SQLite database file
  */
function do_backup_sqlite($gzip) {
	global $SQL_DATABASE;
	// make sure browsers don't cache the backup
	header("Pragma: no-cache");
	// don't allow gzip compression when extension is not loaded
	if (($gzip != 0) && !extension_loaded("zlib")) $gzip = 0;
	if ($gzip) {
		// use an output buffer
		@ob_start();
		@ob_implicit_flush(0);
		// set filename
		$filename = 'blogcms_sqlite_backup_'.strftime("%Y%m%d", time()).".gz";
	} else {
		$filename = 'blogcms_sqlite_backup_'.strftime("%Y%m%d", time());
	}

	$handle = fopen ($SQL_DATABASE, "r");
	if (!$handle) doError("Cannot open file $SQL_DATABASE for reading.");

	// send headers that tell the browser a file is coming
	header("Content-Type: application/octet-stream; name=\"$filename\"");
	header("Content-disposition: attachment; filename=$filename");

	while (!feof ($handle)) { 
	   $buffer = fgets($handle, 4096); 
	   echo $buffer; 
	} 
	fclose ($handle); 
	
	if($gzip)
	{
		$Size = ob_get_length();
		$Crc = crc32(ob_get_contents());
		$contents = gzcompress(ob_get_contents());
		ob_end_clean();
		echo "\x1f\x8b\x08\x00\x00\x00\x00\x00".substr($contents, 0, strlen($contents) - 4).gzip_PrintFourChars($Crc).gzip_PrintFourChars($Size);
	}
	exit;
}

/**
  * Creates a dump for a single table
  * ($tablename and $key are filled in by array_walk)
  */
function _backup_dump_table($tablename, $key) {
	echo "#\n";
	echo "# TABLE: " . $tablename . "\n";
	echo "#\n";
	// dump table structure
	_backup_dump_structure($tablename);
	// dump table contents
	_backup_dump_contents($tablename);
}

function _backup_dump_structure($tablename) {
	// add command to drop table on restore
	echo "DROP TABLE IF EXISTS $tablename;\n";
	// show create table
	$result = sql_query("SHOW CREATE TABLE $tablename");
	if ($row = sql_fetch_array($result)) echo $row["Create Table"].";\n\n";
}

function _backup_dump_contents($tablename) {
	//
	// table fields
	//
	$result = sql_query("SHOW FIELDS FROM $tablename");
	$tablename_list = '(';
	$num_fields = sql_num_rows($result);
	while ($row = sql_fetch_array($result)) $tablename_list .= $row['Field'].', ';
	// remove last comma
	$tablename_list = ereg_replace(', $', '', $tablename_list);
	$tablename_list .= ')';
	sql_free_result($result);
	
	//
	// Grab the data from the table.
	//
	$result = sql_query("SELECT * FROM $tablename");
	if(sql_num_rows($result) > 0) echo "\n#\n# Table Data for $tablename\n#\n";
	while ($row = sql_fetch_row($result))
	{
		// Start building the SQL statement.
		echo "INSERT INTO $tablename $tablename_list VALUES(";
		// Loop through the rows and fill in data for each column
		for ($j = 0; $j < $num_fields; $j++) {
			if(!isset($row[$j])) {
				// no data for column
				echo ' NULL';
			} elseif ($row[$j] != '') {
				// data
				echo " '" . sql_escape($row[$j]) . "'";
			} else {
				// empty column (!= no data!)
				echo "''";
			}
			// only add comma when not last column
			if ($j != ($num_fields - 1)) echo ",";
		}
		echo ");\n";
	}
	echo "\n";
}

// copied from phpBB
function gzip_PrintFourChars($Val)
{
	for ($i = 0; $i < 4; $i ++)
	{
		$return .= chr($Val % 256);
		$Val = floor($Val / 256);
	}
	return $return;
}

// restore SQLite DB
function do_restore_sqlite() {
	global $SQL_DATABASE;
	// first of all: get uploaded file:
	if (empty($_FILES['backup_file']['name'])) return 'No file uploaded';
	if (!is_uploaded_file($_FILES['backup_file']['tmp_name'])) return 'No file uploaded';
	$backup_file_name = $_FILES['backup_file']['name'];
	$backup_file_tmpname = $_FILES['backup_file']['tmp_name'];
	$backup_file_type = $_FILES['backup_file']['type'];
	if (!file_exists($backup_file_tmpname)) return 'File Upload Error';
	if (!preg_match("/^(text\/[a-zA-Z]+)|(application\/(x\-)?gzip(\-compressed)?)|(application\/octet-stream)$/is", $backup_file_type) ) return 'The uploaded file is not of the correct type';
	if (preg_match("/\.gz/is",$backup_file_name)) $gzip = 1; else $gzip = 0;
	if (!extension_loaded("zlib") && $gzip) return "Cannot decompress gzipped backup (zlib package not installed)";

	if($gzip) {
		// close current file
		sql_close();
		$file=fopen($SQL_DATABASE,'w');
		if (!$file) {
			sql_connect(true);
			return "Cannot open $SQL_DATABASE for writing";
		}
		$ptr = gzopen($backup_file_tmpname, 'rb');
		while (!gzeof($ptr)) {
			$data = gzgets($ptr, 100000);
			if (!fwrite($file,$data)) {
				sql_connect(true);
				return "Cannot write to $SQL_DATABASE";
			}
		}
		gzclose($ptr);
		fclose($file);
		sql_connect(true);
	} else {
		// close current file
		sql_close();
		$file=fopen($SQL_DATABASE,'w');
		if (!$file) {
			sql_connect(true);
			return "Cannot open $SQL_DATABASE for writing";
		}
		$ptr = fopen($backup_file_tmpname, 'r');
		while (!feof($ptr)) {
			$data = fgets($ptr, 100000);
			if (!fwrite($file,$data)) {
				sql_connect(true);
				return "Cannot write to $SQL_DATABASE";
			}
		}
		fclose($ptr);
		fclose($file);
		sql_connect(true);
	}
	return;
}

// restore DB
function do_restore() {
	global $_FILES, $SQL_TYPE;

	if ($SQL_TYPE==2) {
	  do_restore_sqlite();
	  return;
	}

	// first of all: get uploaded file:
	if (empty($_FILES['backup_file']['name']))
		return 'No file uploaded';
	if (!is_uploaded_file($_FILES['backup_file']['tmp_name']))
		return 'No file uploaded';
	$backup_file_name = $_FILES['backup_file']['name'];
	$backup_file_tmpname = $_FILES['backup_file']['tmp_name'];
	$backup_file_type = $_FILES['backup_file']['type'];
	if (!file_exists($backup_file_tmpname)) return 'File Upload Error';
	if (!preg_match("/^(text\/[a-zA-Z]+)|(application\/(x\-)?gzip(\-compressed)?)|(application\/octet-stream)$/is", $backup_file_type) )
		return 'The uploaded file is not of the correct type';
	if (preg_match("/\.gz/is",$backup_file_name))
		$gzip = 1;
	else
		$gzip = 0;
	if (!extension_loaded("zlib") && $gzip)
		return "Cannot decompress gzipped backup (zlib package not installed)";
	// get sql query according to gzip setting (either decompress, or not)
	if($gzip)
	{
		// decompress and read
		$gz_ptr = gzopen($backup_file_tmpname, 'rb');
		$sql_query = "";
		while( !gzeof($gz_ptr) )
			$sql_query .= gzgets($gz_ptr, 100000);
	} else {
		// just read
		$sql_query = fread(fopen($backup_file_tmpname, 'r'), filesize($backup_file_tmpname));
	}
	// time to execute the query
	_execute_queries($sql_query);
}

function _execute_queries($sql_query) {
	if (!$sql_query) return;
	// Strip out sql comments...
	$sql_query = remove_remarks($sql_query);
	$pieces = split_sql_file($sql_query);
	$sql_count = count($pieces);
	for($i = 0; $i < $sql_count; $i++)
	{
		$sql = trim($pieces[$i]);
		if(!empty($sql) and $sql[0] != "#")
		{
			$result = sql_query($sql);
			if (!$result) debug("SQL Error: " + sql_error());
		}
	}
}

//
// remove_remarks will strip the sql comment lines out of an uploaded sql file
//
function remove_remarks($sql)
{
	$lines = explode("\n", $sql);
	// try to keep mem. use down
	$sql = "";
	$linecount = count($lines);
	$output = "";
	for ($i = 0; $i < $linecount; $i++)
	{
		if (($i != ($linecount - 1)) || (strlen($lines[$i]) > 0))
		{
			if ($lines[$i][0] != "#")
				$output .= $lines[$i] . "\n";
			else
				$output .= "\n";
			// Trading a bit of speed for lower mem. use here.
			$lines[$i] = "";
		}
	}
	return $output;
}


//
// split_sql_file will split an uploaded sql file into single sql statements.
// Note: expects trim() to have already been run on $sql.
//
// taken from phpBB
//
function split_sql_file($sql)
{
	// Split up our string into "possible" SQL statements.
	$tokens = explode( ";", $sql);
	// try to save mem.
	$sql = "";
	$output = array();
	// we don't actually care about the matches preg gives us.
	$matches = array();
	// this is faster than calling count($tokens) every time thru the loop.
	$token_count = count($tokens);
	for ($i = 0; $i < $token_count; $i++)
	{
		// Don't wanna add an empty string as the last thing in the array.
		if (($i != ($token_count - 1)) || (strlen($tokens[$i] > 0)))
		{
			// even number of quotes means a complete SQL statement
			if (_evenNumberOfQuotes($tokens[$i]))
			{
				$output[] = $tokens[$i];
				$tokens[$i] = ""; 	// save memory.
			}
			else
			{
				// incomplete sql statement. keep adding tokens until we have a complete one.
				// $temp will hold what we have so far.
				$temp = $tokens[$i] .  ";";
				$tokens[$i] = "";	// save memory..
				// Do we have a complete statement yet?
				$complete_stmt = false;
				for ($j = $i + 1; (!$complete_stmt && ($j < $token_count)); $j++)
				{
					// odd number of quotes means a completed statement
					// (in combination with the odd number we had already)
					if (!_evenNumberOfQuotes($tokens[$j]))
					{
						$output[] = $temp . $tokens[$j];
						// save memory.
						$tokens[$j] = "";
						$temp = "";
						// exit the loop.
						$complete_stmt = true;
						// make sure the outer loop continues at the right point.
						$i = $j;
					}
					else
					{
						// even number of unescaped quotes. We still don't have a complete statement.
						// (1 odd and 1 even always make an odd)
						$temp .= $tokens[$j] .  ";";
						// save memory.
						$tokens[$j] = "";
					}
				} // for..
			} // else
		}
	}
	return $output;
}

function _evenNumberOfQuotes($text) {
		// This is the total number of single quotes in the token.
		$total_quotes = preg_match_all("/'/", $text, $matches);
		// Counts single quotes that are preceded by an odd number of backslashes,
		// which means they're escaped quotes.
		$escaped_quotes = preg_match_all("/(?<!\\\\)(\\\\\\\\)*\\\\'/", $text, $matches);
		$unescaped_quotes = $total_quotes - $escaped_quotes;
		return (($unescaped_quotes % 2) == 0);
}

?>