Newer
Older
backup-commander / install / apply-patches.php
<?php

/**
 * SQL files must have a semi-colon at the end of each SQL statement
 * 
 * PHP files can also be used as a patch but are optional
 * 
 * The SQL file for a given patch number is applied before the PHP file
 * 
 * If the PHP file bombs out, then there will be no way to recover. This can be seen in the PARAMS table where
 * db_apply_patch_php is the number of the patch which failed.
 * 
*/
	
$g_database_path = '../html/data/data.db';
$g_patch_dir = 'db';

apply_patches();
	
function apply_patches(){

	global $g_database_path;
	
	//which patch to start with
	$curr_version = get_curr_patch_ver( );
	$st_patch_val = $curr_version;

        //clear any previous php errors
        $db = new SQLite3( $g_database_path );
        $db->exec("delete from PARAMS where P_Name='db_apply_patch_php'");


	print( "Current version is $curr_version\n" );
	$tot_applied=0;
	do{
		$st_patch_val++;
		$was_applied = false;
		
		//process the SQL patch first
		$patch_name = get_patch_name( $st_patch_val );	
		$patch_data = read_patch( $patch_name );
		if($patch_data!=null){
			apply_patch_sql( $st_patch_val, $patch_name, $patch_data );
			$was_applied = true;
		}
		
		//process the PHP patch
		$patch_name = get_patch_name( $st_patch_val, "php");
		$patch_data = read_patch( $patch_name );
		if($patch_data!=null){
			apply_patch_php( $st_patch_val, $patch_name, $patch_data );
			$was_applied = true;
		}
		if($was_applied) $tot_applied++;
				
	}while($patch_data!=null);
	
	print( "Applied $tot_applied patches\n" );

}

function apply_patch_php( $st_patch_val, $patch_name, $patch_data ){
	
	print("Applying patch: $patch_name \n");
	
	global $g_database_path;
		
	//first mark the database so that we know a patch has started	
	$db = new SQLite3( $g_database_path );
	$db->exec("insert into PARAMS(P_Name, P_Value) values('db_apply_patch_php', $st_patch_val )");
	try{
		
		eval($patch_data);
		$db->exec("delete from PARAMS where P_Name='db_apply_patch_php'");
		
	}catch( Exception $e ){
		print( "Exception: " . $e->getMessage() );
		throw $e;
	}
}


function apply_patch_sql( $st_patch_val, $patch_name, $patch_data ){

	print("Applying patch: $patch_name \n");

	$arr_sql_stat = split_sql_data( $patch_data );
	if( count($arr_sql_stat) == 0){
		print("Patch empty\n");
		return;
	}
	
	global $g_database_path;
	
	//first mark the database so that we know a patch has started	
	$db = new SQLite3( $g_database_path );
	$db->exec("insert into PARAMS(P_Name, P_Value) values('db_apply_patch', $st_patch_val )");		
	
	try{
		for( $i=0; $i<count($arr_sql_stat); $i++ ){
			$db->exec( $arr_sql_stat[$i] );
		}

		// update the version
		$db->exec("delete from PARAMS where P_Name='db_apply_patch'");
		$db->exec("update PARAMS set P_Value=$st_patch_val where P_Name='db_curr_patch'");		
		
	}catch( Exception $e ){
		print( "Exception: " . $e->getMessage() );
		throw $e;
	}
	

}

/**
 * Simple return the patch contents as a string.
 * 
 * Returns null if no such patch exists.
 * 
 */
function read_patch( $patch_name ){
	global $g_patch_dir;
	$fn = $g_patch_dir."/$patch_name";	
	if(!file_exists($fn)) return null;
	return file_get_contents($fn);
}

/**
 * Get the file name of the patch given it's integer number.
 * 
 */
function get_patch_name( $patch_int, $patch_type="sql"){
	return "p-$patch_int.$patch_type";
}

/**
 * Read the database patch version from the params table.
 * Will create the table if it does not exists thus assuming that this the first run, setting db_curr_patch=0
 */
function get_curr_patch_ver( ){

	global $g_database_path;
	
	$db = new SQLite3( $g_database_path );
	$results = $db->query("SELECT name FROM sqlite_master WHERE type='table' AND name='PARAMS'");
	$row = $results->fetchArray();
	if( !$row ) {
		//create the table and the the version number
		print "Creating table PARAMS\n";
		$db->exec("CREATE TABLE \"PARAMS\" ( \"P_Name\"	TEXT NOT NULL UNIQUE, \"P_Value\"	TEXT, PRIMARY KEY(\"P_Name\"))");
		$db->exec("insert into PARAMS(P_Name, P_Value) values('db_curr_patch', 0 )");		
		return 0;
	}

	//read the version number
	$results = $db->query("SELECT P_Value FROM PARAMS WHERE P_Name='db_curr_patch'");
	$row = $results->fetchArray();
	if( !$row ) {
		print "Param db_curr_patch not found\n";
		exit(1);
	}
	
	return $row['P_Value'];
}

/**
 * Return an array of strings, one for each SQL statement. Comments are ignored.
 */
function split_sql_data( $patch_data ){
	
	$arr_sql_stat = array();
	
	$lines = preg_split("/\r\n|\n|\r/", $patch_data);
	$stat_nxt = '';
	$i=0;
	while( $i<count($lines) ){
		if( str_starts_with( trim($lines[$i]), '--') ) { $i++; continue; }
		$stat_nxt .= trim($lines[$i]);
		if( str_ends_with( trim($lines[$i]), ';') ) {
			array_push( $arr_sql_stat, $stat_nxt );
			$stat_nxt = '';
		}
		$i++;
	}
	
	return $arr_sql_stat;
}


?>