<?php
/**
 * Installer functions
 *
 * Create database, tables and config file
 *
 * @package Setup
 * @author  Andreas Goetz <cpuidle@gmx.de>
 * @version $Id: install.core.php,v 1.11 2010/02/18 15:15:37 andig2 Exp $
 */


/**
 * Build formatted message string for html output
 *
 * @param string    $msg    Message to print
 * @param string    $color  Color code for <div> tag
 * @param boolean   $print  If true, output directly, else append to message string
 */
function showmessage($msg, $color, $print = false)
{
    global $message;
    
    if ($msg) $msg = "<span style='color: $color'>$msg</span><br/><br/>\n";
    if ($print)
    {
        // print directly
        echo $msg;
    }
    else 
    {
        // return
        $message .= $msg;
    }
}

/**
 * Prepare formatted error message for output
 *
 * @param string    $msg    Message to print
 * @param boolean   $print  If true, output directly, else append to message string
 */
function error($msg, $print = false)
{
    showmessage($msg, 'red', $print);
}

/**
 * Prepare formatted warning message for output
 *
 * @param string    $msg    Message to print
 * @param boolean   $print  If true, output directly, else append to message string
 */
function warn($msg, $print = false)
{
    showmessage($msg, 'orange', $print);
}

/**
 * Prepare formatted info message for output
 *
 * @param string    $msg    Message to print
 * @param boolean   $print  If true, output directly, else append to message string
 */
function info($msg, $print = false)
{
    showmessage($msg, 'green', $print);
}


/**
 * Recursively delete files from directory
 * used for Smarty cache cleanup during installation
 *
 * @param string    $dir        Directory name
 * @param boolean   $recursive  Recurse into subfolders
 */
function delete_files($dir, $recursive = false)
{
	if ($dh = @opendir($dir))
	{
		while (($file = readdir($dh)) !== false)
		{
			// next if . or ..
			if (preg_match("/^\.\.?$/", $file)) continue; 

			// recursion?
			if (is_dir("$dir/$file")) 
			{
				if ($recursive) delete_files("$dir/$file", $recursive);
			}
			else 
			{
				// delete file
				unlink("$dir/$file");
			}
		}
		closedir($dh);

		if ($recursive) rmdir($dir);
    }
}

/**
 * Parse config file and replace settings according
 * to associate array parameter
 *
 * @param   array   new parameter values
 */
function parse_config($vars)
{
    $raw = null;

    if ($file = @file_get_contents(CONFIG_FILE)) {
        $raw = explode("\n", $file);

    	for ($i = 0; $i < count($raw); $i++)
    	{
    		foreach ($vars as $name => $val)
    		{
    			if (preg_match("/^(.*?'$name'.*?=\s*)(.*?)(\s*;.*?)$/", $raw[$i], $matches))
                {
    				# quoted?
    				if (preg_match("/^[\"'].*[\"']$/", $matches[2])) $val = "'$val'";
    				$matches[2] = $val;
    				$raw[$i]    = join('', array_slice($matches,1));
    			}
    		}
    	}
    }

    // fallback if config file is empty or invalid
    if (count($raw) < 4)
    {
        $raw = array('<?php');
        foreach ($vars as $name => $val)
        {
            $line   = "\$config['$name'] = ";
            $line  .= (is_numeric($val)) ? "$val;" : "'$val';";
            $raw[]  = $line;
        }
        $raw[] = '?>';
    }

	return join("\n", $raw);
}

/**
 * Parse database upgrade SQL file and 
 * build associate array of upgrade sql steps per version
 *
 * @return  array   associative array of upgrade steps
 */
function parse_upgrades($upgrade_file)
{
	$cfg = file_get_contents($upgrade_file);
	$raw = preg_split('/# changes in DB version /i', $cfg);

    // loop through list of db upgrades split by comments
	foreach ($raw as $str)
	{
        // upgrade version comment found?
		if (preg_match('/(\d+)\s*#\s*(.+)/s', $str, $m)) 
        {
			$key = $m[1];
			$str = $m[2];
		}
		else 
        {
			if (empty($upgrades['3'])) 
            {
				// this is the first supported version
				$key = 3;
			}
			else 
            {
                // unexpected first db upgrade version found
                trigger_error('Could not parse <a href="'.$upgrade_file.'">'.$upgrade_file.'</a>, please fix!', E_USER_ERROR);
            }
		}
		$upgrades["$key"] = $str;
	}
	return $upgrades;
}

/**
 * Callback function for adding prefix to table name in FROM clauses- extended to include sub queries
 *
 * $match[2] is table
 */
function sql_add_prefix($match)
{
    global $db_prefix;

    $match[2] = preg_replace('/(\'?\w+\'?)(\s*\w*)(,?)/', "`$db_prefix$1`$2$3", $match[2]);
    return join(array_slice($match, 1, 3));
}

/**
 * Prefix table names with table name prefix
 * This will only work for simple queries with known structure (createtables.sql, updatedb.sql)
 *
 * @param   string      SQL command
 * @return  string      SQL command with new table names
 */
function prefix_query($query)
{
    global $db_prefix;

    $query = preg_replace('/((CREATE|ALTER)\s+TABLE\s+(IF\s+NOT\s+EXISTS\s+)?)`?(\w+)`?/', "$1`$db_prefix$4`", $query);
    $query = preg_replace('/((INSERT(\s+IGNORE)?|REPLACE)\s+INTO\s+)`?(\w+)`?/', "$1`$db_prefix$4`", $query);

    $query = preg_replace('/(DROP\s+TABLE\s+(IF\s+EXISTS\s+)?)`?(\w+)`?(;?)/', "$1`$db_prefix$3`$4", $query);
    // changed to allow only replace if sql update to row
    $query = preg_replace('/(UPDATE\s+)`?(\w+)`?(\s+SET\s+)`?/', "$1`$db_prefix$2`$3", $query);
   
    // FROM matches at beginning of string or subquery opened by left bracket
    $query = preg_replace_callback( "/(\s+FROM\s+)(.*?)((\s+WHERE|ORDER|LEFT|RIGHT|OUTER|JOIN)|\)|$)/msi", 'sql_add_prefix', $query);

    return $query;
}

/**
 * Run multiple comma-separated queries
 *
 * @todo    Split SQL queries more cleverly (currently not needed)
 *
 * @param   string      SQL commands
 * @param   ressource   database handle
 * @return  mixed       result array or false
 */
function runSQL($sql, $dbh, $verify = false)
{
    $result = true;
    foreach (explode(';', $sql) as $query)
    {
        $query = trim($query);
        if (empty($query)) continue;

        $query  = prefix_query($query);
        $result = mysqli_query($dbh, $query);

        // error running SQL?
        if ($result === false)
        {
            $sql = $query;
            break;
        }
    }

    // error running SQL?
    if ($result === false)
    {
        if ($verify)
        {
            error("Error in SQL statement:<br/>".
                  "<code>$sql</code><br/>".mysqli_error($dbh));
        }
    }

    // result set returned?
    elseif ($result !== true)
    {
        $res = array();

        for ($i=0; $i < mysqli_num_rows($result); $i++)
        {
            $res[] = mysqli_fetch_assoc($result);
        }
        mysqli_free_result($result);

        return $res;
    }
    
    return $result;
}

/**
 * Run all upgrade scripts passed as array step by step
 *
 * @param array $upgrade_steps  Associative array of upgrade sql steps
 */
function db_upgrade($upgrade_steps)
{
    global $dbh, $version;
    global $step;

    foreach ($upgrade_steps as $ver => $sql)
    {
        #info("Upgrading to database version: $ver");
        $sql = preg_replace('/#.*\n/m','',$sql);

        if (runSQL($sql, $dbh) === false)
        {
            error('Error upgrading database, try full install instead of upgrade:<br/>'.mysqli_error($dbh).
                  '<br/><br/><pre>'.$sql.'</pre>');
            return false;
        }

        // perform additional upgrade steps
        $upgrade_file = "./install/upgrade_v$ver.php";
        if (file_exists($upgrade_file))
        {
            $result = include_once($upgrade_file);
            if (!$result) return($false);
        }

        // add DB version information- this will make the separate update statement in upgrade.sql obsolete
        runSQL("REPLACE INTO config (opt,value) VALUES ('dbversion', ".$ver.");", $dbh);
#       runSQL("update config set value=25 where  opt='dbversion'");

        $version = $ver;
    }

    // perform generic upgrade validation
    $upgrade_file = "./install/upgrade.php";
    if (file_exists($upgrade_file))
    {
        $result = include_once($upgrade_file);
        if (!$result) return($false);
    }

    return $version;
}

?>