<?php

  /**
   **  abook_local_file.php
   **
   **  Backend for addressbook as a pipe separated file
   **
   **  An array with the following elements must be passed to
   **  the class constructor (elements marked ? are optional):
   **
   **     filename  => path to addressbook file
   **   ? create    => if true: file is created if it does not exist.
   **   ? umask     => umask set before opening file.
   **
   **  NOTE. This class should not be used directly. Use the
   **        "AddressBook" class instead.
   **
   ** $Id: abook_local_file.php,v 1.2 2001/05/05 22:34:49 jengo Exp $
   **/

   class abook_local_file extends addressbook_backend {
     var $btype = 'local';
     var $bname = 'local_file';

     var $filename   = '';
     var $filehandle = 0;
     var $create     = false;
     var $umask;

     // ========================== Private =======================

     // Constructor
     function abook_local_file($param) {
       $this->sname = lang("Personal address book");
       $this->umask = Umask();

       if(is_array($param)) {
	 if(empty($param['filename']))
	   return $this->set_error('Invalid parameters');
	 if(!is_string($param['filename']))
	   return $this->set_error($param['filename'] . ': '.
				   lang("Not a file name"));

	 $this->filename = $param['filename'];

	 if($param['create'])
	   $this->create = true;
	 if(isset($param['umask'])) 
	   $this->umask = $param['umask'];

	 if(!empty($param['name']))
	   $this->sname = $param['name'];

	 $this->open(true);
       } else {
	 $this->set_error('Invalid argument to constructor');
       }
     }

     // Open the addressbook file and store the file pointer.
     // Use $file as the file to open, or the class' own 
     // filename property. If $param is empty and file is  
     // open, do nothing.
     function open($new = false) {
       $this->error = '';
       $file   = $this->filename;
       $create = $this->create;

       // Return true is file is open and $new is unset
       if($this->filehandle && !$new)
	 return true;

       // Check that new file exitsts
       if((!(file_exists($file) && is_readable($file))) && !$create)
	 return $this->set_error("$file: " . lang("No such file or directory"));

       // Close old file, if any
       if($this->filehandle) $this->close();
       
       // Open file. First try to open for reading and writing,
       // but fall back to read only.
       umask($this->umask);
       $fh = @fopen($file, 'a+');
       if($fh) {
	 $this->filehandle = &$fh;
	 $this->filename   = $file;
	 $this->writeable  = true;
       } else {
	 $fh = @fopen($file, 'r');
	 if($fh) {
	   $this->filehandle = &$fh;
	   $this->filename   = $file;
	   $this->writeable  = false;
	 } else {
	   return $this->set_error("$file: " . lang("Open failed"));
	 }
       }

       return true;
     }

     // Close the file and forget the filehandle
     function close() {
       @fclose($this->filehandle);
       $this->filehandle = 0;
       $this->filename   = '';
       $this->writable   = false;
     }

     // Lock the datafile - try 20 times in 5 seconds
     function lock() {
       for($i = 0 ; $i < 20 ; $i++) {
	 if(flock($this->filehandle, 2 + 4)) 
	   return true;
	 else
	   usleep(250000);
       }
       return false;
     }

     // Lock the datafile
     function unlock() {
       return flock($this->filehandle, 3);
     }

     // Overwrite the file with data from $rows
     // NOTE! Previous locks are broken by this function
     function overwrite($rows) {
       $newfh = @fopen($this->filename, 'w');
       if(!$newfh)
	 return $this->set_error("$file: " . lang("Open failed"));

       for($i = 0 ; $i < sizeof($rows) ; $i++) {
	 if(is_array($rows[$i]))
	   fwrite($newfh, join('|', $rows[$i]) . "\n");
       }       

       fclose($newfh);
       $this->unlock();
       $this->open(true);
       return true;
     }
     
     // ========================== Public ========================
     
     // Search the file
     function search($expr) {

       // To be replaced by advanded search expression parsing
       if(is_array($expr)) return;

       // Make regexp from glob'ed expression 
       $expr = ereg_replace('\?', '.', $expr);
       $expr = ereg_replace('\*', '.*', $expr);

       $res = array();
       if(!$this->open())
	 return false;

       @rewind($this->filehandle);
       
       while ($row = @fgetcsv($this->filehandle, 2048, '|')) {
	 $line = join(' ', $row);
	 if(eregi($expr, $line)) {
	   array_push($res, array('nickname'  => $row[0],
				  'name'      => $row[1] . ' ' . $row[2],
				  'firstname' => $row[1],
				  'lastname'  => $row[2],
				  'email'     => $row[3],
				  'label'     => $row[4],
				  'backend'   => $this->bnum,
				  'source'    => &$this->sname));
	 }
       }
       
       return $res;
     }
     
     // Lookup alias
     function lookup($alias) {
       if(empty($alias))
	 return array();

       $alias = strtolower($alias);
       
       $this->open();
       @rewind($this->filehandle);
       
       while ($row = @fgetcsv($this->filehandle, 2048, '|')) {
	 if(strtolower($row[0]) == $alias) {
	   return array('nickname'  => $row[0],
			'name'      => $row[1] . ' ' . $row[2],
			'firstname' => $row[1],
			'lastname'  => $row[2],
			'email'     => $row[3],
			'label'     => $row[4],
			'backend'   => $this->bnum,
			'source'    => &$this->sname);
	 }
       }
       
       return array();
     }

     // List all addresses
     function list_addr() {
       $res = array();
       $this->open();
       @rewind($this->filehandle);
       
       while ($row = @fgetcsv($this->filehandle, 2048, '|')) {
	 array_push($res, array('nickname'  => $row[0],
				'name'      => $row[1] . ' ' . $row[2],
				'firstname' => $row[1],
				'lastname'  => $row[2],
				'email'     => $row[3],
				'label'     => $row[4],
				'backend'   => $this->bnum,
				'source'    => &$this->sname));
       }
       return $res;
     }

     // Add address
     function add($userdata) {
       if(!$this->writeable) 
	 return $this->set_error(lang("Addressbook is read-only"));

       // See if user exist already
       $ret = $this->lookup($userdata['nickname']);
       if(!empty($ret))
	 return $this->set_error(sprintf(lang("User '%s' already exist"),
					 $ret['nickname']));

       // Here is the data to write
       $data = $userdata['nickname'] . '|' . $userdata['firstname'] . '|' .
               $userdata['lastname'] . '|' . $userdata['email'] . '|' .
               $userdata['label'];
       // Strip linefeeds
       $data = ereg_replace("[\r\n]", ' ', $data);
       // Add linefeed at end
       $data = $data . "\n";

       // Reopen file, just to be sure
       $this->open(true);
       if(!$this->writeable) 
	 return $this->set_error(lang("Addressbook is read-only"));

       // Lock the file
       if(!$this->lock())
	 return $this->set_error(lang("Could not lock datafile"));

       // Write
       $r = fwrite($this->filehandle, $data);

       // Unlock file
       $this->unlock();

       // Test write result and exit if OK
       if($r > 0) return true;

       // Fail
       $this->set_error(lang("Write to addressbook failed"));
       return false;
     }

     // Delete address
     function remove($alias) {
       if(!$this->writeable) 
	 return $this->set_error(lang("Addressbook is read-only"));

       // Lock the file to make sure we're the only process working
       // on it.
       if(!$this->lock())
	 return $this->set_error(lang("Could not lock datafile"));

       // Read file into memory, ignoring nicknames to delete
       $this->open();
       @rewind($this->filehandle);
       $i = 0;
       $rows = array();
       while($row = @fgetcsv($this->filehandle, 2048, '|')) {
	 if(!in_array($row[0], $alias))
	   $rows[$i++] = $row;
       }

       // Write data back
       if(!$this->overwrite(&$rows)) {
	 $this->unlock();
	 return false;
       }

       $this->unlock();
       return true;
     }

     // Modify address
     function modify($alias, $userdata) {
       if(!$this->writeable) 
	 return $this->set_error(lang("Addressbook is read-only"));

       // See if user exist
       $ret = $this->lookup($alias);
       if(empty($ret))
	 return $this->set_error(sprintf(lang("User '%s' does not exist"),
					 $alias));

       // Lock the file to make sure we're the only process working
       // on it.
       if(!$this->lock())
	 return $this->set_error(lang("Could not lock datafile"));

       // Read file into memory, modifying the data for the 
       // user identifyed by $alias
       $this->open();
       @rewind($this->filehandle);
       $i = 0;
       $rows = array();
       while($row = @fgetcsv($this->filehandle, 2048, '|')) {
	 if(strtolower($row[0]) != strtolower($alias)) {
	   $rows[$i++] = $row;
	 } else {
	   $rows[$i++] = array(0 => $userdata['nickname'],
			       1 => $userdata['firstname'],
			       2 => $userdata['lastname'],
			       3 => $userdata['email'], 
			       4 => $userdata['label']);
	 }
       }

       // Write data back
       if(!$this->overwrite(&$rows)) {
	 $this->unlock();
	 return false;
       }

       $this->unlock();
       return true;
     }
     
   } // End of class abook_local_file
?>
