<?

class CSV {
	 // Public vars
	 var $dataFile = "data.dat";
	 var $dir = "data";
	 var $assoc = false ;
	 var $cryptKey = false;
	 var $db ;
	 var $newrows ;
	 var $error = false;
	 var $lastUpd = "";
	 var $written = 0;

	 function CSV ($df = '', $dd = '', $as = false, $ck = false) 
	 // Class constructor
	 {
		  $this->newrows = array();
		  $this->db = array();

				$this->dataFile = $df;

				$this->dir = $dd;

		  if ($as !== false) {
				$this->assoc = $as;
		  } 
		  if ($ck !== false) {
				$this->cryptKey = $ck;
		  } 
	 } 
	 function readDB() 
	 // Returns true for success or false for error
	 {
		  $cf = "$this->dir/$this->dataFile";
		  $row = 0;
		  $this->lastUpd = @filemtime($cf) or $this->error = "Cannot get timestamp for $cf\n";
		if(file_exists($cf)) {
		  $fp = @fopen($cf, 'rb') ;
		  if (is_resource($fp)) {
				while ($data = fgetcsv ($fp, 1024, ",")) {
					 if ($this->cryptKey) {
						  for ($n = 0;$n < count($data);$n++) {
								$this->decrypt($data[$n], $this->cryptKey);
						  } 
					 } 
					 if (($this->assoc === true) && ($row == 0)) {
						  $num = count ($data);
						  $key = $data;
						  $row = 1;
					 } else {
						  $num = count ($data);
						  $tmp = array() ;
						  for ($n = 0 ;$n < $num ; $n++) {
								if ($this->assoc === true) {
									 $k = strtolower($key[$n]) ;
								} else {
									 $k = $n;
								} 
								$d = $data[$n] ;
								$tmp[$k] = $d ;
						  } 
						  $this->db[] = $tmp ;
					 } 
				} 
				$this->db = array_change_key_case($this->db, CASE_LOWER);
				return @fclose($fp);
		  } else { // is resource
				$this->error .= "DB: Cannot open $cf\n" ;
				return false;
		  }
		 } else {
				$this->error .= "DB: $cf  doesn't exist\n" ;
				return false;
		 }
	 } 

	 function appendDB() 
	 // appends data from newrows  array to file
	 // Returns true for success or false for error
	 {
		  if ($this->rowstowrite() == 0) {
				// $this->error = "DB::appendDB : Nothing to append !";
				return false;
		  } 
		  $tmp = '';
		  $this->written = 0;
		  for ($n = 0;$n < count($this->newrows);$n++) {
				$tmp .= $this->package($this->newrows[$n]);
				$this->written++ ;
		  } 
		  $cf = "$this->dir/$this->dataFile";
		  $fp = @fopen($cf, 'ab') ;
		  if (is_resource($fp)) {
				if (!@fwrite($fp, $tmp)) {
					 $this->error = "DB:: appendDB:Cannot Write to $cf\n";
					 @fclose($fp);
					 return false;
				} 
				$this->newrows = array();
				return @fclose($fp);
		  } else {
				$this->error = "DB::appendDB:Cannot Open:$cf";
				return false;
		  } 
	 } 
	 function delete($rownumber, $n = 1) 
	 // Returns true for success or false for error
	 {
		  $key_index = array_keys(array_keys($this->db), $rownumber);
		  array_splice($this->db, $key_index[0], 1);
		  if (!$this->db) {
				$this->error = "DB::delete: Delete $n row from  $rownumber. Failed " . $this->rowCount();
				return false;
		  } else {
				return true;
		  } 
	 } 
	 function update($rownumber, $aValues) 
	 // Returns true for success or false for error
	 {
		  $aValues = array_change_key_case($aValues, CASE_LOWER);
		  while (list($key, $val) = each($aValues)) {
				if (@array_key_exists($key, $this->db[$rownumber])) {
					 // echo "found key $key new value=> $val<br />";
					 $this->db[$rownumber][$key] = $val;
				} else {
					 $this->error = "DB::update: Array key [$key] Not Found in Row [$rownumber]";
					 return false;
				} 
		  } // while
		  return true;
	 }



	 function append() 
	 // Returns number of lines appended to $this->db
	 {
		  $n = $this->rowstowrite();
		  
		  //maybe array_push() is better for this job
		  
		  foreach($this->newrows as $new) {
				$this->db[] = $new;
		  }
		  $this->newrows = array();
		  return $n;
	 }

	 function find($aValues)
	 // returns an array of rownumbers that match the
	 // key/value pairs supplied as argument
	 // false on failure to find any matches
	 // case and space insensitive
	 {
		  $afound = false;
		  $n = 0;
		  $target = count($aValues); 
		  // echo "Target = $target <pre>";
		  // print_r($aValues);
		  // echo "</pre>";
		  foreach($this->db as $record) {
				// echo $n."<br />";
				$found = 0;
				reset($aValues);
				while (list($key, $val) = each($aValues)) {
					 if ((array_key_exists($key, $record)) && (strcasecmp(trim(onespace($record[$key])), trim(onespace($val))) == 0)) {
						  // echo "$key => [$val]	 record[$n] ?= [".$record[$key]."]<br />";
						  $found += 1;
					 } else {
						  $found = 0;
						  continue 1;
					 } 
				} // while
				if ($found == $target) {
					 // echo 'Matched<br />';
					 $afound[] = $n;
				} 
				$n++;
		  } 
		  return $afound;
	 } 

	 function writeDB($append = false, $force = false)
	 { 
		  // writes/appends DB  array to  file
		  // true on success / false on error
		  $cf = "$this->dir/$this->dataFile";
		  if (! $force) {
				if ($this->lastUpd != filemtime($cf)) {
					 $this->error = "DB::writeDB: $cf Updated by others since last read";
					 return false;
				} 
		  } 
		  $mode = ($append ? 'ab+' : 'wb+');
		  if ($this->assoc) {
				$loop = 0;
		  } else {
				$loop = 1;
		  } 
		  $fp = @fopen($cf, $mode);
		  $tmp = '';
		  if (is_resource($fp)) {
				foreach($this->db as $row) {
					 if (($loop == 0) && (!$append)) {
						  $loop = 1;
						  $tmp .= $this->package(array_keys($row));
						  $tmp .= $this->package($row);
						  $this->written++ ;
					 } else {
						  $test = implode(' ', $row);
						  if (trim($test) > '') { // row is not null
								$tmp .= $this->package($row);
								$this->written++ ;
						  } 
					 } 
				} 
				if ($this->written == 0) { // stop it squaking
					 $tmp .= " ";
				} 
				if (!@fwrite($fp, $tmp)) {
					 $this->error = "DB::writeDB:Cannot Write to $cf (" . $this->written . " Rows )";
					 @fclose($fp);
					 return false;
				} 
		  } else {
				$this->error = "DB::writeDB:Cannot open $cf as file resource";
				return false;
		  } 
		  return @fclose($fp);
	 } 
	 function writeable()
	 {
		  $cf = "$this->dir/$this->dataFile";
		  if (!is_writeable($cf)) {
				return false;
		  } else {
				return true;
		  } 
	 } 
	 function rowCount()
	 { 
		  // returns number of rowss in database
		  return count($this->db);
	 } 
	 function rowstowrite()
	 { 
		  // returns number of rowss in newrows (append) database
		  return count ($this->newrows);
	 } 
	 function package($aData)
	 { 
		  // packages an array of data into a formatted string
		  if ($this->cryptKey) {
				while (list($key, $val) = each($aData)) {
					 $this->encrypt($aData[$key], $this->cryptKey);
				} // while
		  } 
		  return '"' . implode ('","', $aData) . '"' . "\r\n";
	 } 
	 function xpackage($aData)
	 { 
		  // packages an array of data into a formatted string
		  return '"' . implode ('","', $aData) . '"' . "\r\n";
	 } 
	 // Encryption & Decryption Routines
	 function encrypt(&$txt, $key) 
	 // encrypt / obfuscate data
	 {
		  srand((double)microtime() * 1000000);
		  $encrypt_key = md5(rand(0, 32000));
		  $ctr = 0;
		  $tmp = "";
		  $tx = $txt;
		  for ($i = 0;$i < strlen($tx);$i++) {
				if ($ctr == strlen($encrypt_key))
					 $ctr = 0;
				$tmp .= substr($encrypt_key, $ctr, 1) . (substr($tx, $i, 1) ^ substr($encrypt_key, $ctr, 1));
				$ctr++;
		  } 
		  $txt = base64_encode($this->krypt($tmp, $key));
	 } 

	 function decrypt(&$txt, $key) 
	 // reverse the obfuscation
	 {
		  $tx = $this->krypt(base64_decode($txt), $key);
		  $tmp = "";
		  for ($i = 0;$i < strlen($tx);$i++) {
				$md5 = substr($tx, $i, 1);
				$i++;
				$tmp .= (substr($tx, $i, 1) ^ $md5);
		  } 
		  $txt = $tmp;
	 } 

	 function krypt($txt, $crypt_key) 
	 // Inner function for encryption/decryption
	 {
		  $md5 = md5($crypt_key);
		  $ctr = 0;
		  $tmp = "";
		  for ($i = 0;$i < strlen($txt);$i++) {
				if ($ctr == strlen($md5)) $ctr = 0;
				$tmp .= substr($txt, $i, 1) ^ substr($md5, $ctr, 1);
				$ctr++;
		  } 
		  return $tmp;
	 } 



/* CUSTOM FUNCTIONS */

	 function addColumn($columnName, $defaultValue="") {
	 	//insert column name
		$this->db[0][] = $columnName;
		//add default value to all records
		for ($i = 1 ; $i < sizeof($this->db) ; $i++) {
			$this->db[$i][] = $defaultValue;
		}
	 }

	 function deleteColumn($colNr) {
	 	for ($i = 0 ; $i < sizeof($this->db) ; $i++) {
			unset($this->db[$i][$colNr]);
		}
	 }

	 function search($col, $str) {
	 	$tmp = array();
	 	for ($i = 0 ; $i < sizeof($this->db) ; $i++) {
			if (preg_match ("/".$str."/i", $this->db[$i][$col])) {
				$tmp[] = $i;
			}
		}
		return $tmp;
	 }
	 
	 function searchFirstLetter($col, $letter) {
	 	$tmp = array();
	 	for ($i = 0 ; $i < sizeof($this->db) ; $i++) {
			if (strtolower(substr($this->db[$i][$col], 0, 1)) == strtolower($letter)) {
				$tmp[] = $i;
			}
		}
		return $tmp;
	 }


}
?>