Переглянути джерело

Added addressbook+LDAP functions.

pallo 25 роки тому
батько
коміт
232e4c1585

+ 2 - 2
TODO

@@ -3,8 +3,7 @@ Ideas to be implemented
 
 initials = taken by that person
 
-  -     LDAP support for address books
-  -     Importing of address books
+(pl/gf) Importing of address books
 (lme)   Better email message body parsing
   -     Use PHP4 Session management, get rid of cookies
   -     Make it possible to save preferences in MySQL DB or on Filesystem
@@ -23,3 +22,4 @@ Finished:
 (lme) (24.3.00) Saving sent messages  
 (lme) (24.3.00) Fix "Seen" bug with UW IMAP server 
 (lme) (24.3.00) Add "subscribe" to folders section
+(pl)  (25.3.00) LDAP support for address books

+ 204 - 0
functions/abook_ldap_server.php

@@ -0,0 +1,204 @@
+<?php
+
+  /**
+   **  abook_ldap_server.php
+   **
+   **  Address book backend for LDAP server
+   **
+   **  An array with the following elements must be passed to
+   **  the class constructor (elements marked ? are optional):
+   **
+   **     host      => LDAP server hostname/IP-address
+   **     base      => LDAP server root (base dn). Empty string allowed.
+   **   ? port      => LDAP server TCP port number (default: 389)
+   **   ? charset   => LDAP server charset (default: utf-8)
+   **   ? name      => Name for LDAP server (default "LDAP: hostname")
+   **                  Used to tag the result data
+   **   ? maxrows   => Maximum # of rows in search result
+   **
+   **  NOTE. This class should not be used directly. Use the
+   **        "AddressBook" class instead.
+   **/
+
+   class abook_ldap_server extends addressbook_backend {
+     var $btype = "remote";
+     var $bname = "ldap_server";
+
+     // Parameters changed by class
+     var $sname   = "LDAP";       // Service name
+     var $server  = "";           // LDAP server name
+     var $port    = 389;          // LDAP server port
+     var $basedn  = "";           // LDAP base DN 
+     var $charset = "utf-8";      // LDAP server charset
+     var $linkid  = false;        // PHP LDAP link ID
+     var $bound   = false;        // True if LDAP server is bound
+     var $maxrows = 250;          // Max rows in result
+     
+
+     // Constructor. Connects to database
+     function abook_ldap_server($param) {
+       if(is_array($param)) {
+	 $this->server = $param["host"];
+	 $this->basedn = $param["base"];
+	 if(!empty($param["port"]))
+	   $this->port = $param["port"];
+	 if(!empty($param["charset"]))
+	   $this->charset = strtolower($param["charset"]);
+	 if(isset($param["maxrows"]))
+	   $this->maxrows = $param["maxrows"];
+	 if(empty($param["name"]))
+	   $this->sname = "LDAP: ".$param["host"];
+	 else
+	   $this->sname = $param["name"];
+	 
+	 $this->open(true);
+       } else {
+	 $this->set_error(_("Invalid argument to constructor"));
+       }
+     }
+
+
+     // Open the LDAP server. New connection if $new is true
+     function open($new = false) {
+       $this->error = "";
+
+       // Connection is already open
+       if($this->linkid != false && !$new) 
+	 return true;
+
+       $this->linkid = @ldap_connect($this->server, $this->port);
+       if(!$this->linkid) 
+	 if(function_exists("ldap_error"))
+	   return $this->set_error(ldap_error($this->linkid)); 
+	 else
+	   return $this->set_error("ldap_connect failed");
+       
+       if(!@ldap_bind($this->linkid))
+	 if(function_exists("ldap_error"))
+	   return $this->set_error(ldap_error($this->linkid)); 
+	 else
+	   return $this->set_error("ldap_bind failed");
+
+       $this->bound = true;
+       
+       return true;
+     }
+
+
+     // Encode iso8859-1 string to the charset used by this LDAP server
+     function charset_encode($str) {
+       if($this->charset == "utf-8") {
+	 if(function_exists("utf8_encode"))
+	   return utf8_encode($str);
+	 else
+	   return $str;
+       } else {
+	 return $str;
+       }
+     }
+
+
+     // Decode from charset used by this LDAP server to iso8859-1
+     function charset_decode($str) {
+       if($this->charset == "utf-8") {
+	 if(function_exists("utf8_decode"))
+	   return utf8_decode($str);
+	 else
+	   return $str;
+       } else {
+	 return $str;
+       }
+     }
+
+     
+     // ========================== Public ========================
+
+     // Search the LDAP server
+     function search($expr) {
+
+       // To be replaced by advanded search expression parsing
+       if(is_array($expr)) return false;
+
+       // Encode the expression
+       $expr = $this->charset_encode($expr);
+       if(!ereg("\*", $expr)) 
+	 $expr = "*$expr*";
+       $expression = "cn=$expr";
+
+       // Make sure connection is there
+       if(!$this->open())
+	 return false;
+
+       // Do the search
+       $sret = @ldap_search($this->linkid, $this->basedn, $expression,
+			    array("dn", "o", "ou", "sn", "givenname", 
+				  "cn", "mail", "telephonenumber"));
+
+       // Should get error from server using the ldap_error() function,
+       // but it only exist in the PHP LDAP documentation.
+       if(!$sret)
+	 if(function_exists("ldap_error"))
+	   return $this->set_error(ldap_error($this->linkid)); 
+	 else
+	   return $this->set_error("ldap_search failed");       
+
+       if(@ldap_count_entries($this->linkid, $sret) <= 0)
+	 return array();
+
+       // Get results
+       $ret = array();
+       $returned_rows = 0;
+       $res = @ldap_get_entries($this->linkid, $sret);
+       for($i = 0 ; $i < $res["count"] ; $i++) {
+	 $row = $res[$i];
+
+	 // Extract data common for all e-mail addresses
+	 // of an object. Use only the first name
+	 $nickname = $this->charset_decode($row["dn"]);
+	 $fullname = $this->charset_decode($row["cn"][0]);
+
+	 if(empty($row["telephonenumber"][0])) $phone = "";
+	 else $phone = $this->charset_decode($row["telephonenumber"][0]);
+
+	 if(!empty($row["ou"][0]))
+	   $label = $this->charset_decode($row["ou"][0]);
+	 else if(!empty($row["o"][0]))
+	   $label = $this->charset_decode($row["o"][0]);
+	 else 
+	   $label = "";
+
+	 if(empty($row["givenname"][0])) $firstname = "";
+	 else $firstname = $this->charset_decode($row["givenname"][0]);
+
+	 if(empty($row["sn"][0])) $surname = "";
+	 else $surname = $this->charset_decode($row["sn"][0]);
+
+	 // Add one row to result for each e-mail address
+	 for($j = 0 ; $j < $row["mail"]["count"] ; $j++) {
+	   array_push($ret, array("nickname"  => $nickname,
+				  "name"      => $fullname,
+				  "firstname" => $firstname,
+				  "lastname"  => $surname,
+				  "email"     => $row["mail"][$j],
+				  "label"     => $label,
+				  "phone"     => $phone,
+				  "backend"   => $this->bnum,
+				  "source"    => &$this->sname));
+
+	   // Limit number of hits
+	   $returned_rows++;
+	   if(($returned_rows >= $this->maxrows) && 
+	      ($this->maxrows > 0) ) {
+	     ldap_free_result($sret);
+	     return $ret;
+	   }
+
+	 }
+       }
+
+       ldap_free_result($sret);
+       return $ret;
+     } // end search()
+
+   }
+?>

+ 220 - 0
functions/abook_local_file.php

@@ -0,0 +1,220 @@
+<?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.
+   **/
+
+   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 = _("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"] . ": ".
+				   _("Not a file name"));
+
+	 $this->filename = $param["filename"];
+
+	 if($param["create"])
+	   $this->create = true;
+	 if(isset($param["umask"])) 
+	   $this->umask = $param["umask"];
+
+	 $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: " . 
+				 _("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: "._("Open failed"));
+	 }
+       }
+
+       return true;
+     }
+
+     // Close the file and forget the filehandle
+     function close() {
+       @fclose($this->filehandle);
+       $this->filehandle = 0;
+       $this->filename   = "";
+       $this->writable   = false;
+     }
+     
+     // ========================== 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(_("Addressbook is read-only"));
+
+       // See if user exist already
+       $ret = $this->lookup($userdata["nickname"]);
+       if(!empty($ret))
+	 return $this->set_error(sprintf(_("User '%s' already exist"), 
+					 $ret["nickname"]));
+
+       // Here is the data to write
+       $data = sprintf("%s|%s|%s|%s|%s", $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(_("Addressbook is read-only"));
+
+       $r = fwrite($this->filehandle, $data);
+       if($r > 0)
+	 return true;
+
+       $this->set_error(_("Write to addressbook failed"));
+       return false;
+     }
+
+   }
+?>

+ 269 - 0
functions/addressbook.php

@@ -0,0 +1,269 @@
+<?php
+
+  /**
+   **  addressbook.php
+   **
+   **  Functions and classes for the addressbook system.
+   **
+   **/
+    
+   $addressbook_php = true;
+
+   // Include backends here.
+   include("../functions/abook_local_file.php");
+   include("../functions/abook_ldap_server.php");
+
+   // Create and initialize an addressbook object. 
+   // Returns the created object
+   function addressbook_init() {
+      global $data_dir, $username, $ldap_server;
+      
+      // Create a new addressbook object
+      $abook = new AddressBook;
+      
+      // Always add a local backend
+      $filename = sprintf("%s%s.abook", $data_dir, $username);
+      $r = $abook->add_backend("local_file", Array("filename" => $filename,
+						   "create"   => true));
+      if(!$r) {
+	 print _("Error opening ") ."$filename";
+	 exit;
+      }
+     
+
+      // Load configured LDAP servers
+      reset($ldap_server);
+      while(list($key,$param) = each($ldap_server))
+	 if(is_array($param))
+	    $abook->add_backend("ldap_server", $param);
+
+      // Return the initialized object
+      return $abook;
+   }
+
+
+
+  /**
+   ** This is the main address book class that connect all the
+   ** backends and provide services to the functions above.
+   **
+   **/
+   class AddressBook { 
+      var $backends    = array();
+      var $numbackends = 0;
+      var $error       = "";
+
+      // Constructor function.
+      function AddressBook() {
+      }
+
+      // Return an array of backends of a given type, 
+      // or all backends if no type is specified.
+      function get_backend_list($type = "") {
+	 $ret = array();
+	 for($i = 1 ; $i <= $this->numbackends ; $i++) {
+	    if(empty($type) || $type == $this->backends[$i]->btype) {
+	       array_push($ret, &$this->backends[$i]);
+	    }
+	 }
+	 return $ret;
+      }
+
+
+      // ========================== Public ========================
+
+      // Add a new backend. $backend is the name of a backend
+      // (without the abook_ prefix), and $param is an optional
+      // mixed variable that is passed to the backend constructor.
+      // See each of the backend classes for valid parameters.
+      function add_backend($backend, $param = "") {
+	 $backend_name = "abook_".$backend;
+	 eval("\$newback = new $backend_name(\$param);");
+	 if(!empty($newback->error)) {
+	    $this->error = $newback->error;
+	    return false;
+	 }
+
+	 $this->numbackends++;
+       
+	 $newback->bnum = $this->numbackends;
+	 $this->backends[$this->numbackends] = $newback;
+	 return $this->numbackends;
+      }
+
+
+      // Return a list of addresses matching expression in
+      // all backends of a given type.
+      function search($expression, $btype = "") {
+	 $ret = array();
+
+	 $sel = $this->get_backend_list($btype);
+	 for($i = 0 ; $i < sizeof($sel) ; $i++) {
+	    $backend = &$sel[$i];
+	    $backend->error = "";
+	    $res = $backend->search($expression);
+	    if(is_array($res)) {
+	       $ret = array_merge($ret, $res);
+	    } else {
+	       $this->error = $backend->error;
+	       return false;
+	    }
+	 }
+
+	 return $ret;
+      }
+
+
+      // Return a sorted search
+      function s_search($expression, $btype = "") {
+	 $ret = $this->search($expression, $btype);
+
+	 // Inline function - Not nice, but still.. 
+	 function cmp($a,$b) {   
+	    if($a["backend"] > $b["backend"]) 
+	       return 1;
+	    else if($a["backend"] < $b["backend"]) 
+	       return -1;
+	 
+	    return (strtolower($a["name"]) > strtolower($b["name"])) ? 1 : -1;
+	 }
+
+	 usort($ret, 'cmp');
+	 return $ret;
+      }
+
+
+      // Lookup an address by alias. Only possible in
+      // local backends.
+      function lookup($alias) {
+	 $ret = array();
+
+	 $sel = $this->get_backend_list("local");
+	 for($i = 0 ; $i < sizeof($sel) ; $i++) {
+	    $backend = &$sel[$i];
+	    $backend->error = "";
+	    $res = $backend->lookup($alias);
+	    if(is_array($res)) {
+	       return $res;
+	    } else {
+	       $this->error = $backend->error;
+	       return false;
+	    }
+	 }
+
+	 return $ret;
+      }
+
+
+      // Return all addresses
+      function list_addr() {
+	 $ret = array();
+
+	 $sel = $this->get_backend_list("local");
+	 for($i = 0 ; $i < sizeof($sel) ; $i++) {
+	    $backend = &$sel[$i];
+	    $backend->error = "";
+	    $res = $backend->list_addr();
+	    if(is_array($res)) {
+	       $ret = array_merge($ret, $res);
+	    } else {
+	       $this->error = $backend->error;
+	       return false;
+	    }
+	 }
+
+	 return $ret;
+      }
+
+
+      // Create a new address from $userdata, in backend $bnum.
+      // Return the backend number that the/ address was added
+      // to, or false if it failed.
+      function add($userdata, $bnum) {
+
+	 // Validate data
+	 if(!is_array($userdata)) {
+	    $this->error = _("Invalid input data");
+	    return false;
+	 }
+	 if(empty($userdata["fullname"]) &&
+	    empty($userdata["lastname"])) {
+	    $this->error = _("Name is missing");
+	    return false;
+	 }
+	 if(empty($userdata["email"])) {
+	    $this->error = _("E-mail address is missing");
+	    return false;
+	 }
+	 if(empty($userdata["nickname"])) {
+	    $userdata["nickname"] = $userdata["email"];
+	 }
+
+	 // Check that specified backend accept new entries
+	 if(!$this->backends[$bnum]->writeable) {
+	    $this->error = _("Addressbook is not writable");
+	    return false;
+	 }
+
+	 // Add address to backend
+	 $res = $this->backends[$bnum]->add($userdata);
+	 if($res) {
+	    return $bnum;
+	 } else {
+	    $this->error = $this->backends[$bnum]->error;
+	    return false;
+	 }
+
+	 return false;  // Not reached
+      }
+
+   }
+
+
+  /**
+   ** Generic backend that all other backends extend
+   **/
+   class addressbook_backend {
+
+      // Variables that all backends must provide.
+      var $btype      = "dummy";
+      var $bname      = "dummy";
+      var $sname      = "Dummy backend";
+
+      // Variables common for all backends, but that 
+      // should not be changed by the backends.
+      var $bnum       = -1;
+      var $error      = "";
+      var $writeable  = false;
+
+      function set_error($string) {
+	 $this->error = "[" . $this->sname . "] " . $string;
+	 return false;
+      }
+
+
+      // ========================== Public ========================
+
+      function search($expression) {
+	 $this->set_error("search not implemented");
+	 return false;
+      }
+
+      function lookup($alias) {
+	 $this->set_error("lookup not implemented");
+	 return false;
+      }
+
+      function list_addr() {
+	 $this->set_error("list_addr not implemented");
+	 return false;
+      }
+
+      function add($userdata) {
+	 $this->set_error("add not implemented");
+	 return false;
+      }
+
+   }
+
+?>

+ 43 - 0
src/addrbook_popup.php

@@ -0,0 +1,43 @@
+<?php
+   /**
+    **  addrbook_popup.php
+    **
+    **  Frameset for the JavaScript version of the address book.
+    **
+    **/
+
+   if(!isset($logged_in)) {
+      echo _("You must ");
+      echo _("login");
+      echo _(" first.");
+      exit;
+   }
+   if(!isset($username) || !isset($key)) {
+      echo _("You need a valid user and password to access this page!");
+      exit;
+   }
+
+   if (!isset($config_php))
+      include("../config/config.php");
+   if (!isset($page_header_php))
+      include("../functions/page_header.php");
+   if (!isset($addressbook_php))
+      include("../functions/addressbook.php");
+?>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
+
+<HTML>
+<HEAD>
+<TITLE><?php 
+   printf("%s: %s", $org_title, _("Address Book")); 
+?></TITLE>
+</HEAD>
+
+<FRAMESET ROWS="60,*" BORDER=0>
+ <FRAME NAME="abookmain" MARGINWIDTH=0 SCROLLING=NO
+        SRC="addrbook_search.php?show=form" BORDER=0>
+ <FRAME NAME="abookres" MARGINWIDTH=0 SRC="addrbook_search.php?show=blank"
+        BORDER=0>
+</FRAMESET>
+
+</HTML>

+ 168 - 0
src/addrbook_search.php

@@ -0,0 +1,168 @@
+<?php
+   /**
+    **  addrbook_search.php
+    **
+    **  Handle addressbook searching in the popup window.
+    **
+    **/
+
+   if(!isset($logged_in)) {
+      echo _("You must ");
+      echo _("login");
+      echo _(" first.");
+      exit;
+   }
+   if(!isset($username) || !isset($key)) {
+      echo _("You need a valid user and password to access this page!");
+      exit;
+   }
+
+   if (!isset($config_php))
+      include("../config/config.php");
+   if (!isset($array_php))
+      include("../functions/array.php");
+   if (!isset($strings_php))
+      include("../functions/strings.php");
+   if (!isset($imap_php))
+      include("../functions/imap.php");
+   if (!isset($page_header_php))
+      include("../functions/page_header.php");
+   if (!isset($addressbook_php))
+      include("../functions/addressbook.php");
+
+   // Authenticate user and load prefs
+   $imapConnection = sqimap_login($username, $key, 
+				  $imapServerAddress, $imapPort, 10);
+   include("../src/load_prefs.php");
+   sqimap_logout ($imapConnection);
+
+?>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+
+<HTML>
+<HEAD>
+<TITLE><?php 
+   printf("%s: %s", $org_title, _("Address Book")); 
+?></TITLE>
+</HEAD>
+
+<?php
+   // Choose correct colors for top and bottom frame
+   if($show == "form") {
+      echo "<BODY BGCOLOR=\"$color[3]\" TEXT=\"$color[6]\" ";
+      echo "LINK=\"$color[6]\" VLINK=\"$color[6]\" ALINK=\"$color[6]\" ";
+      echo "OnLoad=\"document.sform.query.focus();\">";  
+   } else {
+      echo "<BODY TEXT=\"$color[8]\" BGCOLOR=\"$color[4]\" ";
+      echo "LINK=\"$color[7]\" VLINK=\"$color[7]\" ALINK=\"$color[7]\">\n";
+   }
+
+   // Just make a blank page and exit
+   if(($show == "blank") || (empty($query) && empty($show)))  {
+      printf("<P ALIGN=center><BR>%s</P>\n</BODY></HTML>\n",
+	     _("Search results will display here"));
+      exit;
+   }
+
+   // Create search form 
+   if($show == "form") {
+      printf("<FORM NAME=sform TARGET=abookres ACTION=\"%s\" METHOD=GET>\n",
+	     $PHP_SELF);
+      printf("<TABLE BORDER=0 WIDTH=\"100%%\" HEIGHT=\"100%%\">");
+      printf("<TR><TD NOWRAP VALIGN=middle>\n");
+      printf("  <STRONG>%s:</STRONG>\n</TD><TD VALIGN=middle>\n",
+	     _("Search for"));
+      printf("  <INPUT TYPE=text NAME=query VALUE=\"%s\" SIZE=30>\n",
+	     htmlspecialchars($query));
+      printf("</TD><TD VALIGN=middle>\n");
+      printf("  <INPUT TYPE=submit VALUE=\"%s\">",
+	     _("Search"));
+      printf("</TD><TD WIDTH=\"50%%\" VALIGN=middle ALIGN=right>\n");
+      printf("<INPUT TYPE=button VALUE=\"%s\" onclick=\"parent.close();\">\n",
+             _("Close window"));
+      printf("</TD></TR></TABLE></FORM>\n");
+   }
+
+   // Include JavaScript code if this is search results
+   if(!empty($query)) {
+?>
+<SCRIPT LANGUAGE="Javascript"><!--
+
+function to_address($addr) {
+  var prefix    = "";
+  var pwintype = typeof parent.opener.document.compose;
+
+  if(pwintype != "undefined" ) {
+    if ( parent.opener.document.compose.send_to.value ) {
+      prefix = ", ";
+      parent.opener.document.compose.send_to.value = 
+        parent.opener.document.compose.send_to.value + ", " + $addr;      
+    } else {
+      parent.opener.document.compose.send_to.value = $addr;
+    }
+  }
+}
+
+function cc_address($addr) {
+  var prefix    = "";
+  var pwintype = typeof parent.opener.document.compose;
+
+  if(pwintype != "undefined" ) {
+    if ( parent.opener.document.compose.send_to_cc.value ) {
+      prefix = ", ";
+      parent.opener.document.compose.send_to_cc.value = 
+        parent.opener.document.compose.send_to_cc.value + ", " + $addr;      
+    } else {
+      parent.opener.document.compose.send_to_cc.value = $addr;
+    }
+  }
+}
+
+function bcc_address($addr) {
+  var prefix    = "";
+  var pwintype = typeof parent.opener.document.compose;
+
+  if(pwintype != "undefined" ) {
+    if ( parent.opener.document.compose.bcc.value ) {
+      prefix = ", ";
+      parent.opener.document.compose.bcc.value = 
+        parent.opener.document.compose.bcc.value + ", " + $addr;      
+    } else {
+      parent.opener.document.compose.bcc.value = $addr;
+    }
+  }
+}
+
+// --></SCRIPT>
+
+<?php 
+   } // End of included JavaScript code
+
+   // Do the search
+   if(!empty($query)) {
+      $abook = addressbook_init();
+      $res = $abook->s_search($query);
+
+      if(!is_array($res)) {
+	 printf("<P ALIGN=center><BR>%s.</P>\n</BODY></HTML>\n",
+		_("No persons matching your search was found"));
+	 exit;
+      }
+
+      // List search results
+      $line = 0;
+      print "<table border=0 width=100%>";
+      print "<tr bgcolor=\"$color[9]\"><TH align=left>&nbsp;<TH align=left>Name<TH align=left>E-mail<TH align=left>Info<TH align=left>Source</tr>\n";
+
+      while(list($key, $row) = each($res)) {
+	 printf("<tr%s nowrap><td nowrap><a href=\"javascript:to_address('%s');\">To</A> | <a href=\"javascript:cc_address('%s');\">Cc</A><td nowrap>%s&nbsp;<td nowrap>%s&nbsp;<td nowrap>%s&nbsp;<td nowrap>%s</tr>\n", 
+		($line % 2) ? " bgcolor=\"$color[0]\"" : "", $row["email"],
+		$row["email"], $row["name"], $row["email"], $row["label"], 
+		$row["source"]);
+	 $line++;
+      }
+      print "</TABLE>";
+   }
+?>
+
+</BODY></HTML>

+ 19 - 1
src/compose.php

@@ -146,7 +146,16 @@
       $reply_subj = decodeHeader($reply_subj);
       $forward_subj = decodeHeader($forward_subj);
 
-      echo "\n<FORM action=\"compose.php\" METHOD=POST\n";
+      echo "\n<SCRIPT LANGUAGE=JavaScript><!--\n";
+      echo "function open_abook() { \n";
+      echo "  var nwin = window.open(\"addrbook_popup.php\",\"abookpopup\",";
+      echo "\"width=670,height=300,resizable=yes,scrollbars=yes\");\n";
+      echo "  if((!nwin.opener) && (document.windows != null))\n";
+      echo "    nwin.opener = document.windows;\n";
+      echo "}\n";
+      echo "// --></SCRIPT>\n\n";
+
+      echo "\n<FORM name=compose action=\"compose.php\" METHOD=POST\n";
       echo "ENCTYPE=\"multipart/form-data\">\n";
       echo "<TABLE COLS=2 WIDTH=50 ALIGN=center CELLSPACING=0 BORDER=0>\n";
       echo "   <TR>\n";
@@ -179,6 +188,15 @@
          echo "         <INPUT TYPE=TEXT NAME=send_to_bcc SIZE=60><BR>";
       echo "      </TD>\n";
       echo "   </TR>\n";
+
+      echo "<SCRIPT LANGUAGE=JavaScript><!--\n document.write(\"";
+      echo "<TR><TD BGCOLOR=\\\"$color[4]\\\">&nbsp;</TD>";
+      echo "</TD><TD BGCOLOR=\\\"$color[4]\\\" ALIGN=LEFT>";
+      printf("<A HREF=\\\"javascript:open_abook();\\\">%s</A>",
+	     _("Lookup recipients in addressbook.<BR>"));
+      echo "</TD></TR>\");\n";
+      echo "// --></SCRIPT>\n";
+
       echo "   <TR>\n";
       echo "      <TD WIDTH=50 BGCOLOR=\"$color[4]\" ALIGN=RIGHT>\n";
       echo _("Subject:");