From d1d2c4fb1d0e9b7a46693e617835850b0edc0fd5 Mon Sep 17 00:00:00 2001
From: svncommit <devs@roundcube.net>
Date: Sun, 08 Jan 2006 02:15:44 -0500
Subject: [PATCH] adding files and modifications for public ldap search

---
 program/include/main.inc                      |   55 ++
 program/steps/addressbook/save.inc            |  162 ++++++--
 skins/default/ldapsearchform.css              |   54 ++
 program/steps/addressbook/func.inc            |    3 
 config/main.inc.php.dist                      |   14 
 program/localization/en/labels.inc            |    9 
 program/localization/en/messages.inc          |    9 
 program/steps/addressbook/ldapsearchform.inc  |  264 +++++++++++++
 skins/default/images/buttons/ldap_pas.png     |    0 
 index.php                                     |    6 
 skins/default/images/buttons/ldap_act.png     |    0 
 skins/default/templates/ldappublicsearch.html |   31 +
 program/js/app.js                             |  163 +++++++
 skins/default/includes/ldapscripts.html       |   73 +++
 program/include/rcube_ldap.inc                |  259 +++++++++++++
 15 files changed, 1,021 insertions(+), 81 deletions(-)

diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist
index 17beaf1..e7ccc91 100644
--- a/config/main.inc.php.dist
+++ b/config/main.inc.php.dist
@@ -131,6 +131,20 @@
 // this string is used as a delimiter for message headers when sending
 $rcmail_config['mail_header_delimiter'] = "\r\n";
 
+// public ldap servers to search for contacts
+$rcmail_config['ldap_public'] = false;
+
+/** example config for Verisign directory
+$rcmail_config['ldap_public']['Verisign'] = array('hosts'         => array('directory.verisign.com'),
+                                                  'port'          => 389,
+                                                  'base_dn'       => '',
+                                                  'search_fields' => array('mail', 'cn'),
+                                                  'name_field'    => 'cn',
+                                                  'mail_field'    => 'mail',
+                                                  'scope'         => 'sub',
+                                                  'fuzzy_search'  => 0);
+**/
+
 
 /***** these settings can be overwritten by user's preferences *****/
 
diff --git a/index.php b/index.php
index f864aca..2490628 100644
--- a/index.php
+++ b/index.php
@@ -1,5 +1,4 @@
 <?php
-
 /*
  +-----------------------------------------------------------------------+
  | RoundCube Webmail IMAP Client                                         |
@@ -281,6 +280,9 @@
 
   if ($_action=='list' && $_GET['_remote'])
     include('program/steps/addressbook/list.inc');
+
+  if ($_action=='ldappublicsearch')
+    include('program/steps/addressbook/ldapsearchform.inc');
   }
 
 
@@ -325,4 +327,4 @@
                   'file' => __FILE__,
                   'message' => "Invalid request"), TRUE, TRUE);
                       
-?>
\ No newline at end of file
+?>
diff --git a/program/include/main.inc b/program/include/main.inc
index 24110d3..c64ac7d 100644
--- a/program/include/main.inc
+++ b/program/include/main.inc
@@ -924,6 +924,8 @@
         'recordscountdisplay' => 'rcmail_rowcount_display',
         'contactdetails' => 'rcmail_contact_details',
         'contacteditform' => 'rcmail_contact_editform',
+        'ldappublicsearch' => 'rcmail_ldap_public_search_form',
+        'ldappublicaddresslist' => 'rcmail_ldap_public_list',
 
         // USER SETTINGS
         'userprefs' => 'rcmail_user_prefs_form',
@@ -1110,7 +1112,7 @@
 
 
 
-function rcube_table_output($attrib, $sql_result, $a_show_cols, $id_col)
+function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
   {
   global $DB;
   
@@ -1128,21 +1130,44 @@
   $table .= "</tr></thead>\n<tbody>\n";
   
   $c = 0;
-  while ($sql_result && ($sql_arr = $DB->fetch_assoc($sql_result)))
+
+  if (!is_array($table_data)) 
     {
-    $zebra_class = $c%2 ? 'even' : 'odd';
-
-    $table .= sprintf('<tr id="rcmrow%d" class="contact '.$zebra_class.'">'."\n", $sql_arr[$id_col]);
-
-    // format each col
-    foreach ($a_show_cols as $col)
+    while ($table_data && ($sql_arr = $DB->fetch_assoc($table_data)))
       {
-      $cont = rep_specialchars_output($sql_arr[$col]);
-	  $table .= '<td class="'.$col.'">' . $cont . "</td>\n";
-      }
+      $zebra_class = $c%2 ? 'even' : 'odd';
 
-    $table .= "</tr>\n";
-    $c++;
+      $table .= sprintf('<tr id="rcmrow%d" class="contact '.$zebra_class.'">'."\n", $sql_arr[$id_col]);
+
+      // format each col
+      foreach ($a_show_cols as $col)
+        {
+        $cont = rep_specialchars_output($sql_arr[$col]);
+	    $table .= '<td class="'.$col.'">' . $cont . "</td>\n";
+        }
+
+      $table .= "</tr>\n";
+      $c++;
+      }
+    }
+  else 
+    {
+    foreach ($table_data as $row_data)
+      {
+      $zebra_class = $c%2 ? 'even' : 'odd';
+
+      $table .= sprintf('<tr id="rcmrow%d" class="contact '.$zebra_class.'">'."\n", $row_data[$id_col]);
+
+      // format each col
+      foreach ($a_show_cols as $col)
+        {
+        $cont = rep_specialchars_output($row_data[$col]);
+	    $table .= '<td class="'.$col.'">' . $cont . "</td>\n";
+        }
+
+      $table .= "</tr>\n";
+      $c++;
+      }
     }
 
   // complete message table
@@ -1420,7 +1445,6 @@
   }
 
 
-
 function rcube_timer()
   {
   list($usec, $sec) = explode(" ", microtime());
@@ -1442,5 +1466,4 @@
   console(sprintf("%s: %0.4f sec", $label, $diff));
   }
 
-
-?>
\ No newline at end of file
+?>
diff --git a/program/include/rcube_ldap.inc b/program/include/rcube_ldap.inc
new file mode 100644
index 0000000..7cb9dee
--- /dev/null
+++ b/program/include/rcube_ldap.inc
@@ -0,0 +1,259 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcube_ldap.inc                                        |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Manage an LDAP connection                                           |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Jeremy Jongsma <jeremy@jongsma.org>                           |
+ +-----------------------------------------------------------------------+
+
+ $Id$
+
+*/
+
+require_once("bugs.inc");
+
+class rcube_ldap
+  {
+  var $conn;
+  var $host;
+  var $port;
+  var $protocol;
+  var $base_dn;
+  var $bind_dn;
+  var $bind_pass;
+
+  // PHP 5 constructor
+  function __construct()
+    {
+    }
+
+  // PHP 4 constructor
+  function rcube_ldap()
+    {
+    $this->__construct();
+    }
+
+  function connect($hosts, $port=389, $protocol=3)
+    {
+    if (!function_exists('ldap_connect'))
+      raise_error(array("type" => "ldap",
+                        "message" => "No ldap support in this installation of php."),
+                         TRUE);
+
+    if (is_resource($this->conn))
+      return TRUE;
+    
+    if (!is_array($hosts))
+      $hosts = array($hosts);
+
+    foreach ($hosts as $host)
+      {
+      if ($lc = @ldap_connect($host, $port))
+        {
+        @ldap_set_option($lc, LDAP_OPT_PROTOCOL_VERSION, $protocol);
+        $this->host = $host;
+        $this->port = $port;
+        $this->protocol = $protocol;
+        $this->conn = $lc;
+        return TRUE;
+        }
+      }
+    
+    if (!is_resource($this->conn))
+      raise_error(array("type" => "ldap",
+                        "message" => "Could not connect to any LDAP server, tried $host:$port last"),
+                         TRUE);
+    }
+
+  function close()
+    {
+    if ($this->conn)
+      {
+      if (@ldap_unbind($this->conn))
+        return TRUE;
+      else
+        raise_error(array("code" => ldap_errno($this->conn),
+                          "type" => "ldap",
+                          "message" => "Could not close connection to LDAP server: ".ldap_error($this->conn)),
+                    TRUE);
+      }
+    return FALSE;
+    }
+
+  // Merge with connect()?
+  function bind($dn=null, $pass=null)
+    {
+    if ($this->conn)
+      {
+      if ($dn)
+        if (@ldap_bind($this->conn, $dn, $pass))
+          return TRUE;
+        else
+          raise_error(array("code" => ldap_errno($this->conn),
+                            "type" => "ldap",
+                            "message" => "Bind failed for dn=$dn: ".ldap_error($this->conn)),
+                      TRUE);
+      else
+        if (@ldap_bind($this->conn))
+          return TRUE;
+        else
+          raise_error(array("code" => ldap_errno($this->conn),
+                            "type" => "ldap",
+                            "message" => "Anonymous bind failed: ".ldap_error($this->conn)),
+                      TRUE);
+      }
+    else
+      raise_error(array("type" => "ldap",
+                        "message" => "Attempted bind on nonexistent connection"), TRUE);
+    return FALSE;
+    }
+
+  function count($base, $filter=null, $attributes=null, $scope="sub")
+    {
+    if ($this->conn)
+      {
+      if ($scope === 'sub')
+        $sr = @ldap_search($this->conn, $base, $filter, $attributes, 0, $limit);
+      else if ($scope === 'one')
+        $sr = @ldap_list($this->conn, $base, $filter, $attributes, 0, $limit);
+      else if ($scope === 'base')
+        $sr = @ldap_read($this->conn, $base, $filter, $attributes, 0, $limit);
+      if ($sr)
+        return @ldap_count_entries($this->conn, $sr);
+      }
+    else
+      raise_error(array("type" => "ldap",
+                        "message" => "Attempted count search on nonexistent connection"), TRUE);
+    return FALSE;
+    }
+
+  function search($base, $filter=null, $attributes=null, $scope='sub', $sort=null, $limit=0)
+    {
+    if ($this->conn)
+      {
+      if ($scope === 'sub')
+        $sr = @ldap_search($this->conn, $base, $filter, $attributes, 0, $limit);
+      else if ($scope === 'one')
+        $sr = @ldap_list($this->conn, $base, $filter, $attributes, 0, $limit);
+      else if ($scope === 'base')
+        $sr = @ldap_read($this->conn, $base, $filter, $attributes, 0, $limit);
+      if ($sr)
+        {
+        if ($sort && $scope !== "base")
+          {
+          if (is_array($sort))
+            {
+            // Start from the end so first sort field has highest priority
+            $sortfields = array_reverse($sort);
+            foreach ($sortfields as $sortfield)
+              @ldap_sort($this->conn, $sr, $sortfield);
+            }
+          else
+            @ldap_sort($this->conn, $sr, $sort);
+          }
+        return @ldap_get_entries($this->conn, $sr);
+        }
+      }
+    else
+      raise_error(array("type" => "ldap",
+                        "message" => "Attempted search on nonexistent connection"), TRUE);
+    return FALSE;
+    }
+
+  function add($dn, $object)
+    {
+    if ($this->conn)
+      {
+      if (@ldap_add($this->conn, $dn, $object))
+        return TRUE;
+      else
+        raise_error(array("code" => ldap_errno($this->conn),
+                          "type" => "ldap",
+                          "message" => "Add object failed: ".ldap_error($this->conn)),
+                    TRUE);
+      }
+    else
+      raise_error(array("type" => "ldap",
+                        "message" => "Add object faile: no connection"),
+                  TRUE);
+    return FALSE;
+    }
+
+  function modify($dn, $object)
+    {
+    if ($this->conn)
+      {
+      if (@ldap_modify($this->conn, $dn, $object))
+        return TRUE;
+      else
+        raise_error(array("code" => ldap_errno($this->conn),
+                          "type" => "ldap",
+                          "message" => "Modify object failed: ".ldap_error($this->conn)),
+                    TRUE);
+      }
+    else
+      raise_error(array("type" => "ldap",
+                        "message" => "Modify object failed: no connection"),
+                  TRUE);
+    return FALSE;
+    }
+
+  function rename($dn, $newrdn, $parentdn)
+    {
+    if ($this->protocol < 3)
+      {
+      raise_error(array("type" => "ldap",
+                        "message" => "rename() support requires LDAPv3 or above "),
+                  TRUE);
+      return FALSE;
+      }
+
+    if ($this->conn)
+      {
+      if (@ldap_rename($this->conn, $dn, $newrdn, $parentdn, TRUE))
+        return TRUE;
+      else
+        raise_error(array("code" => ldap_errno($this->conn),
+                          "type" => "ldap",
+                          "message" => "Rename object failed: ".ldap_error($this->conn)),
+                    TRUE);
+      }
+    else
+      raise_error(array("type" => "ldap",
+                        "message" => "Rename object failed: no connection"),
+                  TRUE);
+    return FALSE;
+    }
+
+  function delete($dn)
+    {
+    if ($this->conn)
+      {
+      if (@ldap_delete($this->conn, $dn))
+        return TRUE;
+      else
+        raise_error(array("code" => ldap_errno($this->conn),
+                          "type" => "ldap",
+                          "message" => "Delete object failed: ".ldap_error($this->conn)),
+                    TRUE);
+      }
+    else
+      raise_error(array("type" => "ldap",
+                        "message" => "Delete object failed: no connection"),
+                  TRUE);
+    return FALSE;
+    }
+
+  }
+
+// vi: et ts=2 sw=2
+?>
diff --git a/program/js/app.js b/program/js/app.js
index dc0275c..6d76d04 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -160,10 +160,15 @@
 
 
       case 'addressbook':
-        var contacts_list = this.gui_objects.contactslist;
+        var contacts_list      = this.gui_objects.contactslist;
+        var ldap_contacts_list = this.gui_objects.ldapcontactslist;
+
         if (contacts_list)
           this.init_contactslist(contacts_list);
       
+        if (ldap_contacts_list)
+          this.init_ldapsearchlist(ldap_contacts_list);
+
         this.set_page_buttons();
           
         if (this.env.cid)
@@ -172,7 +177,7 @@
         if ((this.env.action=='add' || this.env.action=='edit') && this.gui_objects.editform)
           this.enable_command('save', true);
       
-        this.enable_command('list', 'add', true);
+        this.enable_command('list', 'add', 'ldappublicsearch', true);
         break;
 
 
@@ -372,6 +377,26 @@
     };
 
 
+  // get all contact rows from HTML table and init each row
+  this.init_ldapsearchlist = function(ldap_contacts_list)
+    {
+    if (ldap_contacts_list && ldap_contacts_list.tBodies[0])
+      {
+      this.ldap_contact_rows = new Array();
+
+      var row;
+      for(var r=0; r<ldap_contacts_list.tBodies[0].childNodes.length; r++)
+        {
+        row = ldap_contacts_list.tBodies[0].childNodes[r];
+        this.init_table_row(row, 'ldap_contact_rows');
+        }
+      }
+
+    // alias to common rows array
+    this.list_rows = this.ldap_contact_rows;
+    };
+
+
   // make references in internal array and set event handlers
   this.init_table_row = function(row, array_name)
     {
@@ -548,7 +573,15 @@
 
       case 'add':
         if (this.task=='addressbook')
-          this.load_contact(0, 'add');
+          if (!window.frames[this.env.contentframe].rcmail)
+            this.load_contact(0, 'add');
+          else
+            {
+            if (window.frames[this.env.contentframe].rcmail.selection.length)
+              this.add_ldap_contacts();
+            else
+              this.load_contact(0, 'add');
+            }
         else if (this.task=='settings')
           {
           this.clear_selection();
@@ -682,7 +715,8 @@
           this.show_message(this.env.prev_uid);
           //location.href = this.env.comm_path+'&_action=show&_uid='+this.env.prev_uid+'&_mbox='+this.env.mailbox;
         break;
-
+      
+      
       case 'compose':
         var url = this.env.comm_path+'&_action=compose';
         
@@ -699,21 +733,36 @@
           // get selected contacts
           else
             {
-            for (var n=0; n<this.selection.length; n++)
-              a_cids[a_cids.length] = this.selection[n];
+            if (!window.frames[this.env.contentframe].rcmail.selection.length)
+              {
+              for (var n=0; n<this.selection.length; n++)
+                a_cids[a_cids.length] = this.selection[n];
+              }
+            else
+              {
+              var frameRcmail = window.frames[this.env.contentframe].rcmail;
+              // get the email address(es)
+              for (var n=0; n<frameRcmail.selection.length; n++)
+                a_cids[a_cids.length] = frameRcmail.ldap_contact_rows[frameRcmail.selection[n]].obj.cells[1].innerHTML;
+              }
             }
-
           if (a_cids.length)
             url += '&_to='+a_cids.join(',');
           else
             break;
+            
           }
         else if (props)
            url += '&_to='+props;
+        
+        // don't know if this is necessary...
+        url = url.replace(/&_framed=1/, "");
 
         this.set_busy(true);
-        location.href = url;
-        break;      
+
+        // need parent in case we are coming from the contact frame
+        parent.window.location.href = url;
+        break;    
 
       case 'send':
         if (!this.gui_objects.messageform)
@@ -777,6 +826,15 @@
       case 'add-contact':
         this.add_contact(props);
         break;
+      
+
+      // ldap search
+      case 'ldappublicsearch':
+        if (this.gui_objects.ldappublicsearchform) 
+          this.gui_objects.ldappublicsearchform.submit();
+        else 
+          this.ldappublicsearch(command);
+        break; 
 
 
       // user settings commands
@@ -954,7 +1012,7 @@
   // onmouseup-handler of message list row
   this.click_row = function(e, id)
     {
-    var ctrl = this.check_ctrlkey(e);
+    var shift = this.check_shiftkey(e);
     
     // don't do anything (another action processed before)
     if (this.dont_select)
@@ -964,26 +1022,58 @@
       }
     
     if (!this.drag_active && this.in_selection_before==id)
-      this.select(id, (ctrl && this.task!='settings'));
+      {
+      this.select(id, (shift && this.task!='settings'));
+      }
     
     this.drag_start = false;
     this.in_selection_before = false;
         
     // row was double clicked
-    if (this.task=='mail' && this.list_rows && this.list_rows[id].clicked && !ctrl)
+    if (this.task=='mail' && this.list_rows && this.list_rows[id].clicked && !shift)
       {
       this.show_message(id);
       return false;
       }
     else if (this.task=='addressbook')
       {
-      if (this.selection.length==1 && this.env.contentframe)
+      if (this.contact_rows && this.selection.length==1)
+        {
         this.load_contact(this.selection[0], 'show', true);
-      else if (this.task=='addressbook' && this.list_rows && this.list_rows[id].clicked)
+        // change the text for the add contact button
+        var links = parent.document.getElementById('abooktoolbar').getElementsByTagName('A');
+        for (i = 0; i < links.length; i++)
+          {
+          var onclickstring = new String(links[i].onclick);
+          if (onclickstring.search('\"add\"') != -1)
+            links[i].title = this.env.newcontact;
+          }
+        }
+      else if (this.contact_rows && this.contact_rows[id].clicked)
         {
         this.load_contact(id, 'show');
         return false;
         }
+      else if (this.ldap_contact_rows && !this.ldap_contact_rows[id].clicked)
+        {
+        // clear selection
+        parent.rcmail.clear_selection();
+
+        // disable delete
+        parent.rcmail.set_button('delete', 'pas');
+
+        // change the text for the add contact button
+        var links = parent.document.getElementById('abooktoolbar').getElementsByTagName('A');
+        for (i = 0; i < links.length; i++)
+          {
+          var onclickstring = new String(links[i].onclick);
+          if (onclickstring.search('\"add\"') != -1)
+            links[i].title = this.env.addcontact;
+          }
+        }
+      // handle double click event
+      else if (this.ldap_contact_rows && this.selection.length==1 && this.ldap_contact_rows[id].clicked)
+        this.command('compose', this.ldap_contact_rows[id].obj.cells[1].innerHTML);
       else if (this.env.contentframe)
         {
         var elm = document.getElementById(this.env.contentframe);
@@ -1001,7 +1091,6 @@
       
     return false;
     };
-
 
 
 
@@ -1919,6 +2008,50 @@
 
     };
   
+  
+  // load ldap search form
+  this.ldappublicsearch = function(action)
+    {
+    var add_url = '';
+    var target = window;
+    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
+      {
+      add_url = '&_framed=1';
+      target = window.frames[this.env.contentframe];
+      document.getElementById(this.env.contentframe).style.visibility = 'inherit';
+      }
+    else
+      return false; 
+
+
+    if (action == 'ldappublicsearch')
+      target.location.href = this.env.comm_path+'&_action='+action+add_url;
+    };
+ 
+  // add ldap contacts to address book
+  this.add_ldap_contacts = function()
+    {
+    if (window.frames[this.env.contentframe].rcmail)
+      {
+      var frame = window.frames[this.env.contentframe];
+
+      // build the url
+      var url    = '&_framed=1';
+      var emails = '&_emails=';
+      var names  = '&_names=';
+      var end    = '';
+      for (var n=0; n<frame.rcmail.selection.length; n++)
+        {
+        end = n < frame.rcmail.selection.length - 1 ? ',' : '';
+        emails += frame.rcmail.ldap_contact_rows[frame.rcmail.selection[n]].obj.cells[1].innerHTML + end;
+        names  += frame.rcmail.ldap_contact_rows[frame.rcmail.selection[n]].obj.cells[0].innerHTML + end;
+        }
+       
+      frame.location.href = this.env.comm_path + '&_action=save&_framed=1' + emails + names;
+      }
+    return false;
+    }
+  
 
 
   /*********************************************************/
diff --git a/program/localization/en/labels.inc b/program/localization/en/labels.inc
index 086c308..1355c07 100644
--- a/program/localization/en/labels.inc
+++ b/program/localization/en/labels.inc
@@ -143,12 +143,19 @@
 $labels['delete'] = 'Delete';
 
 $labels['newcontact']     = 'Create new contact card';
+$labels['addcontact']     = 'Add selected contact to your addressbook';
 $labels['deletecontact']  = 'Delete selected contacts';
 $labels['composeto']      = 'Compose mail to';
 $labels['contactsfromto'] = 'Contacts $from to $to of $count';
 $labels['print']          = 'Print';
 $labels['export']         = 'Export';
 
+$labels['ldappublicsearchname']    = 'Contact name';
+$labels['ldappublicsearchtype'] = 'Exact match?';
+$labels['ldappublicserverselect'] = 'Select servers';
+$labels['ldappublicsearchfield'] = 'Search on';
+$labels['ldappublicsearchform'] = 'Look for a contact';
+$labels['ldappublicsearch'] = 'Search';
 
 // settings
 $labels['settingsfor']  = 'Settings for';
@@ -183,4 +190,4 @@
 $labels['sortasc']  = 'Sort ascending';
 $labels['sortdesc'] = 'Sort descending';
 
-?>
\ No newline at end of file
+?>
diff --git a/program/localization/en/messages.inc b/program/localization/en/messages.inc
index c1520ec..5a662d2 100644
--- a/program/localization/en/messages.inc
+++ b/program/localization/en/messages.inc
@@ -78,5 +78,12 @@
 
 $messages['notsentwarning'] = 'Message has not been sent. Do you want to discard your message?';
 
+$messages['notsentwarning'] = 'Message has not been sent. Do you want to discard your message?';
 
-?>
\ No newline at end of file
+$messages['noldapserver'] = 'Please select an ldap server to search';
+
+$messages['nocontactsreturned'] = 'No contacts were found';
+
+$messages['nosearchname'] = 'Please enter a contact name or email address';
+
+?>
diff --git a/program/steps/addressbook/func.inc b/program/steps/addressbook/func.inc
index 8065219..ee3b880 100644
--- a/program/steps/addressbook/func.inc
+++ b/program/steps/addressbook/func.inc
@@ -81,6 +81,7 @@
   $javascript = sprintf("%s.gui_object('contactslist', '%s');\n", $JS_OBJECT_NAME, $attrib['id']);
   $javascript .= sprintf("%s.set_env('current_page', %d);\n", $JS_OBJECT_NAME, $CONTACTS_LIST['page']);
   $javascript .= sprintf("%s.set_env('pagecount', %d);\n", $JS_OBJECT_NAME, ceil($rowcount/$CONFIG['pagesize']));
+  $javascript .= "rcmail.set_env('newcontact', '" . rcube_label('newcontact') . "');";
   //$javascript .= sprintf("%s.set_env('contacts', %s);", $JS_OBJECT_NAME, array2js($a_js_message_arr));
   
   $OUTPUT->add_script($javascript);  
@@ -191,4 +192,4 @@
   return $out;
   }
 
-?>
\ No newline at end of file
+?>
diff --git a/program/steps/addressbook/ldapsearchform.inc b/program/steps/addressbook/ldapsearchform.inc
new file mode 100644
index 0000000..f7d7cc9
--- /dev/null
+++ b/program/steps/addressbook/ldapsearchform.inc
@@ -0,0 +1,264 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/ldapsearch.inc                              |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Show an ldap search form in the addressbook                         |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Justin Randell <justin.randell@gmail.com>                     |
+ +-----------------------------------------------------------------------+
+
+ $Id$
+
+*/
+require_once 'include/rcube_ldap.inc';
+
+/**
+ * draw the ldap public search form
+ */
+function rcmail_ldap_public_search_form($attrib)
+  {
+  global $CONFIG, $JS_OBJECT_NAME, $OUTPUT; 
+  if (!$CONFIG['ldap_public'])
+    {
+    // no ldap servers to search
+    show_message('noldapserver', 'warning');
+    rcmail_overwrite_action('add');
+    return false;
+    }
+  else
+    {
+    // store some information in the session
+    $_SESSION['ldap_public']['server_count'] = $server_count = count($CONFIG['ldap_public']);
+    $_SESSION['ldap_public']['server_names'] = $server_names = array_keys($CONFIG['ldap_public']);
+    }
+  
+  list($form_start, $form_end) = get_form_tags($attrib);
+  $out = "$form_start<table id=\"ldap_public_search_table\">\n\n";
+  
+  // search name field
+  $search_name = new textfield(array('name' => '_ldap_public_search_name',
+                                     'id'   => 'rcmfd_ldap_public_search_name'));
+  $out .= "<tr><td class=\"title\"><label for=\"rcmfd_ldap_public_search_name\">" . 
+          rep_specialchars_output(rcube_label('ldappublicsearchname')) . 
+          "</label></td><td>" . $search_name->show() . "</td></tr>\n";
+
+
+  // there's more than one server to search for, show a dropdown menu
+  if ($server_count > 1)
+    {
+    $select_server = new select(array('name' => '_ldap_public_servers', 
+                                      'id'   => 'rcfmd_ldap_public_servers'));
+     
+    $select_server->add($server_names, $server_names);
+
+    $out .= '<tr><td class="title"><label for="rcfmd_ldap_public_servers">' .
+            rep_specialchars_output(rcube_label('ldappublicserverselect')) .
+            "</label></td><td>" . $select_server->show() . "</td></tr>\n";
+    }
+  
+  // foreach configured ldap server, set up the search fields
+  for ($i = 0; $i < $server_count; $i++)
+    {
+    $server = $CONFIG['ldap_public'][$server_names[$i]];
+    
+    // only display one search fields select - js takes care of the rest
+    if (!$i)
+      {
+      $field_name = '_ldap_public_search_field';
+      $field_id   = 'rcfmd_ldap_public_search_field';
+
+      $search_fields = new select(array('name' => $field_name, 
+                                        'id'   => $field_id));
+
+      $search_fields->add($server['search_fields'], $server['search_fields']);
+      $out .= '<tr><td class="title"><label for="' . $field_id . '">' .
+              rep_specialchars_output(rcube_label('ldappublicsearchfield')) . 
+              "</label></td><td>" . $search_fields->show() . "</td></tr>\n";
+
+      $search_type = new checkbox(array('name' => '_ldap_public_search_type',
+                                        'id' => 'rcmfd_ldap_public_search_type', 'value' => 0));
+
+      $out .= '<tr id="ldap_fuzzy_search"><td class="title"><label for="rcmfd_ldap_public_search_type">' .
+              rep_specialchars_output(rcube_label('ldappublicsearchtype')) .
+              "</label></td><td>" . $search_type->show() . "</td></tr>\n";
+      }
+
+    // store the search fields in a js array for each server
+    $js = '';
+    foreach ($server['search_fields'] as $k => $search_field)
+      $js .= "'$search_field', ";
+
+    // store whether this server accepts fuzzy search as last item in array
+    $js .= $server['fuzzy_search'] ? "'fuzzy'" : "'exact'";
+    $OUTPUT->add_script("rcmail.set_env('{$server_names[$i]}_search_fields', new Array($js));");
+    }
+
+  // add contact button label text
+  $OUTPUT->add_script("rcmail.set_env('addcontact', '" . rcube_label('addcontact') . "');");
+
+  $out .= "\n</table>$form_end";
+  return $out;  
+  }
+
+/**
+ * get search values and return ldap contacts
+ */
+function rcmail_ldap_public_list()
+  {
+  // just return if we are not being called from a search form
+  if (!isset($_POST['_action']))
+    return null;
+
+  global $CONFIG, $OUTPUT, $JS_OBJECT_NAME;
+  
+  // show no search name warning and exit
+  if (empty($_POST['_ldap_public_search_name']) || trim($_POST['_ldap_public_search_name']) == '')
+    {
+    show_message('nosearchname', 'warning');
+    return false;
+    }
+  
+  // set up ldap server(s) array or bail
+  if ($_SESSION['ldap_public']['server_count'] > 1)
+    // show no ldap server warning and exit
+    if (empty($_POST['_ldap_public_servers']))
+      {
+      show_message('noldappublicserver', 'warning');
+      return false;
+      }
+    else
+      $server_name = $_POST['_ldap_public_servers'];
+  else if ($_SESSION['ldap_public']['server_count'] == 1)
+    $server_name = $_SESSION['ldap_public']['server_names'][0];
+  else
+    return false;
+
+  // get search parameters
+  $search_value = $_POST['_ldap_public_search_name'];
+  $search_field = $_POST['_ldap_public_search_field'];
+
+  // only use the post var for search type if the ldap server allows 'like'
+  $exact = true;
+  if ($CONFIG['ldap_public'][$server_name]['fuzzy_search'])
+    $exact = isset($_POST['_ldap_public_search_type']) ? true : false; 
+  
+  // perform an ldap search
+  $contacts = rcmail_ldap_contact_search($search_value, 
+                                         $search_field, 
+                                         $CONFIG['ldap_public'][$server_name], 
+                                         $exact);
+  
+  // if no results, show a warning and return
+  if (!$contacts)
+    {
+    show_message('nocontactsreturned', 'warning');
+    return false;
+    }
+
+  // add id to message list table if not specified
+  if (!strlen($attrib['id']))
+    $attrib['id'] = 'ldapAddressList';
+  
+  // define table class
+  $attrib['class'] = 'records-table';
+  $attrib['cellspacing'] = 0;
+
+  // define list of cols to be displayed
+  $a_show_cols = array('name', 'email');
+
+  // create XHTML table  
+  $out = rcube_table_output($attrib, $contacts, $a_show_cols, 'row_id');
+
+  // set client env
+  $javascript = "$JS_OBJECT_NAME.gui_object('ldapcontactslist', '{$attrib['id']}');\n";
+  $OUTPUT->add_script($javascript);  
+  
+  return $out;  
+  }
+
+/**
+ * perform search for contacts from given public ldap server
+ */
+function rcmail_ldap_contact_search($search_value, $search_field, $server, $exact=true)
+  {
+  global $CONFIG;
+  
+  $attributes = array($server['name_field'], $server['mail_field']); 
+
+  $LDAP = new rcube_ldap();
+  if ($LDAP->connect($server['hosts'], $server['port'], $server['protocol']))
+    {
+    $filter = "$search_field=" . ($exact ? $search_value : "*$search_value*"); 
+    $result = $LDAP->search($server['base_dn'],
+                            $filter, 
+                            $attributes, 
+                            $server['scope'], 
+                            $sort=null);
+         
+    // add any results to contact array
+    if ($result['count'])
+      {
+      for ($n = 0; $n < $result['count']; $n++)
+        {
+        $contacts[$n]['name']   = $result[$n][$server['name_field']][0];
+        $contacts[$n]['email']  = $result[$n][$server['mail_field']][0];
+        $contacts[$n]['row_id'] = $n + 1;
+        }
+      }
+    }
+  else
+    return false;
+
+  // cleanup
+  $LDAP->close();
+
+  if (!$result['count'])
+    return false;
+ 
+  // weed out duplicate emails
+  for ($n = 0; $n < $result['count']; $n++)
+    for ($i = 0; $i < $result['count']; $i++)
+      if ($contacts[$i]['email'] == $contacts[$n]['email'] && $i != $n)
+        unset($contacts[$n]);
+
+  return $contacts;
+  }
+
+function get_form_tags($attrib)
+  {
+  global $OUTPUT, $JS_OBJECT_NAME, $EDIT_FORM, $SESS_HIDDEN_FIELD;  
+
+  $form_start = '';
+  if (!strlen($EDIT_FORM))
+    {
+    $hiddenfields = new hiddenfield(array('name' => '_task', 'value' => $GLOBALS['_task']));
+    $hiddenfields->add(array('name' => '_action', 'value' => 'ldappublicsearch'));
+    
+    if ($_GET['_framed'] || $_POST['_framed'])
+      $hiddenfields->add(array('name' => '_framed', 'value' => 1));
+    
+    $form_start .= !strlen($attrib['form']) ? '<form name="form" action="./" method="post">' : '';
+    $form_start .= "\n$SESS_HIDDEN_FIELD\n";
+    $form_start .= $hiddenfields->show();
+    }
+    
+  $form_end = (strlen($EDIT_FORM) && !strlen($attrib['form'])) ? '</form>' : '';
+  $form_name = strlen($attrib['form']) ? $attrib['form'] : 'form';
+  
+  $OUTPUT->add_script("$JS_OBJECT_NAME.gui_object('ldappublicsearchform', '$form_name');");
+  
+  $EDIT_FORM = $form_name;
+
+  return array($form_start, $form_end);  
+  }
+
+parse_template('ldappublicsearch');
+?>
diff --git a/program/steps/addressbook/save.inc b/program/steps/addressbook/save.inc
index f5ba139..24e375e 100644
--- a/program/steps/addressbook/save.inc
+++ b/program/steps/addressbook/save.inc
@@ -19,18 +19,17 @@
 
 */
 
-
-$a_save_cols = array('name', 'firstname', 'surname', 'email');
-
-
 // check input
-if (empty($_POST['_name']) || empty($_POST['_email']))
+if ((empty($_POST['_name']) || empty($_POST['_email'])) && empty($_GET['_framed']))
   {
   show_message('formincomplete', 'warning');
   rcmail_overwrite_action($_POST['_cid'] ? 'show' : 'add');
   return;
   }
 
+// setup some vars we need
+$a_save_cols = array('name', 'firstname', 'surname', 'email');
+$contacts_table = get_table_name('contacts');
 
 // update an existing contact
 if ($_POST['_cid'])
@@ -48,7 +47,7 @@
 
   if (sizeof($a_write_sql))
     {
-    $DB->query("UPDATE ".get_table_name('contacts')."
+    $DB->query("UPDATE $contacts_table
                 SET    changed=now(), ".join(', ', $a_write_sql)."
                 WHERE  contact_id=?
                 AND    user_id=?
@@ -70,7 +69,7 @@
       $a_show_cols = array('name', 'email');
       $a_js_cols = array();
   
-      $sql_result = $DB->query("SELECT * FROM ".get_table_name('contacts')."
+      $sql_result = $DB->query("SELECT * FROM $contacts_table
                                 WHERE  contact_id=?
                                 AND    user_id=?
                                 AND    del<>1",
@@ -104,64 +103,137 @@
 else
   {
   $a_insert_cols = $a_insert_values = array();
-  
+
   // check for existing contacts
-  $sql_result = $DB->query("SELECT 1 FROM ".get_table_name('contacts')."
-                            WHERE  user_id=?
-                            AND    email=?
-                            AND    del<>1",
-                           $_SESSION['user_id'],
-                           $_POST['_email']);
+  $sql = "SELECT 1 FROM $contacts_table
+          WHERE  user_id = {$_SESSION['user_id']}
+          AND del <> '1' ";
+
+  // get email and name, build sql for existing user check
+  if (isset($_GET['_emails']) && isset($_GET['_names']))
+    {
+    $sql   .= "AND email IN (";
+    $emails = explode(',', $_GET['_emails']);
+    $names  = explode(',', $_GET['_names']);
+    $count  = count($emails);
+    $n = 0;
+    foreach ($emails as $email)
+      {
+      $end  = (++$n == $count) ? '' : ',';
+      $sql .= $DB->quote(strip_tags($email)) . $end;
+      }
+    $sql .= ")";
+    $ldap_form = true; 
+    }
+  else if (isset($_POST['_email'])) 
+    $sql  .= "AND email = " . $DB->quote(strip_tags($_POST['_email']));
+
+  $sql_result = $DB->query($sql);
 
   // show warning message
   if ($DB->num_rows($sql_result))
     {
     show_message('contactexists', 'warning');
-    $_action = 'add';
+
+    if ($ldap_form)
+      rcmail_overwrite_action('ldappublicsearch');
+    else
+      rcmail_overwrite_action('add');
+
     return;
     }
 
-  foreach ($a_save_cols as $col)
+  if ($ldap_form)
     {
-    $fname = '_'.$col;
-    if (!isset($_POST[$fname]))
-      continue;
-    
-    $a_insert_cols[] = $col;
-    $a_insert_values[] = $DB->quote(strip_tags($_POST[$fname]));
+    $n = 0; 
+    foreach ($emails as $email) 
+      {
+      $DB->query("INSERT INTO $contacts_table 
+                 (user_id, name, email)
+                 VALUES ({$_SESSION['user_id']}," . $DB->quote(strip_tags($names[$n++])) . "," . 
+                                      $DB->quote(strip_tags($email)) . ")");
+      $insert_id[] = $DB->insert_id();
+      }
     }
-    
-  if (sizeof($a_insert_cols))
+  else
     {
-    $DB->query("INSERT INTO ".get_table_name('contacts')."
+    foreach ($a_save_cols as $col)
+      {
+      $fname = '_'.$col;
+      if (!isset($_POST[$fname]))
+        continue;
+    
+      $a_insert_cols[] = $col;
+      $a_insert_values[] = $DB->quote(strip_tags($_POST[$fname]));
+      }
+    
+    if (sizeof($a_insert_cols))
+      {
+      $DB->query("INSERT INTO $contacts_table
                 (user_id, changed, del, ".join(', ', $a_insert_cols).")
                 VALUES (?, now(), 0, ".join(', ', $a_insert_values).")",
                 $_SESSION['user_id']);
                        
-    $insert_id = $DB->insert_id(get_sequence_name('contacts'));
+      $insert_id = $DB->insert_id(get_sequence_name('contacts'));
+      }
     }
     
   if ($insert_id)
     {
-    $_action = 'show';
-    $_GET['_cid'] = $insert_id;
+    if (!$ldap_form)
+      {
+      $_action = 'show';
+      $_GET['_cid'] = $insert_id;
 
-    if ($_POST['_framed'])
+      if ($_POST['_framed'])
+        {
+        // add contact row or jump to the page where it should appear
+        $commands = sprintf("if(parent.%s)parent.", $JS_OBJECT_NAME);
+        $sql_result = $DB->query("SELECT * FROM $contacts_table
+                                  WHERE  contact_id=?
+                                  AND    user_id=?",
+                                  $insert_id,
+                                  $_SESSION['user_id']);
+        $commands .= rcmail_js_contacts_list($sql_result, $JS_OBJECT_NAME);
+
+        $commands .= sprintf("if(parent.%s)parent.%s.select('%d');\n",
+                             $JS_OBJECT_NAME, 
+                             $JS_OBJECT_NAME,
+                             $insert_id);
+      
+        // update record count display
+        $commands .= sprintf("if(parent.%s)parent.%s.set_rowcount('%s');\n",
+                             $JS_OBJECT_NAME, 
+                             $JS_OBJECT_NAME,
+                             rcmail_get_rowcount_text());
+
+        $OUTPUT->add_script($commands);
+        }
+
+      // show confirmation
+      show_message('successfullysaved', 'confirmation');      
+      }
+    else 
       {
       // add contact row or jump to the page where it should appear
-      $commands = sprintf("if(parent.%s)parent.", $JS_OBJECT_NAME);
-      $sql_result = $DB->query("SELECT * FROM ".get_table_name('contacts')."
-                                WHERE  contact_id=?
-                                AND    user_id=?",
-                                $insert_id,
-                                $_SESSION['user_id']);
-      $commands .= rcmail_js_contacts_list($sql_result, $JS_OBJECT_NAME);
+      $commands = '';
+      foreach ($insert_id as $id) 
+        {
+        $sql_result = $DB->query("SELECT * FROM $contacts_table
+                                  WHERE  contact_id = $id
+                                  AND    user_id    = {$_SESSION['user_id']}");
+        
+        $commands .= sprintf("if(parent.%s)parent.", $JS_OBJECT_NAME);
+        $commands .= rcmail_js_contacts_list($sql_result, $JS_OBJECT_NAME);
+        $last_id = $id;
+        }
 
+      // display the last insert id
       $commands .= sprintf("if(parent.%s)parent.%s.select('%d');\n",
-                           $JS_OBJECT_NAME, 
-                           $JS_OBJECT_NAME,
-                           $insert_id);
-      
+                            $JS_OBJECT_NAME, 
+                            $JS_OBJECT_NAME,
+                            $last_id);
+
       // update record count display
       $commands .= sprintf("if(parent.%s)parent.%s.set_rowcount('%s');\n",
                            $JS_OBJECT_NAME, 
@@ -169,10 +241,11 @@
                            rcmail_get_rowcount_text());
 
       $OUTPUT->add_script($commands);
-      
-      // show confirmation
-      show_message('successfullysaved', 'confirmation');      
+      rcmail_overwrite_action('ldappublicsearch');
       }
+
+    // show confirmation
+    show_message('successfullysaved', 'confirmation');      
     }
   else
     {
@@ -182,5 +255,4 @@
     }
   }
 
-
-?>
\ No newline at end of file
+?>
diff --git a/skins/default/images/buttons/ldap_act.png b/skins/default/images/buttons/ldap_act.png
new file mode 100644
index 0000000..b09f267
--- /dev/null
+++ b/skins/default/images/buttons/ldap_act.png
Binary files differ
diff --git a/skins/default/images/buttons/ldap_pas.png b/skins/default/images/buttons/ldap_pas.png
new file mode 100644
index 0000000..b09f267
--- /dev/null
+++ b/skins/default/images/buttons/ldap_pas.png
Binary files differ
diff --git a/skins/default/includes/ldapscripts.html b/skins/default/includes/ldapscripts.html
new file mode 100644
index 0000000..0dfda6c
--- /dev/null
+++ b/skins/default/includes/ldapscripts.html
@@ -0,0 +1,73 @@
+<script type="text/javascript">
+var ldap_server_select = document.getElementById('rcfmd_ldap_public_servers');
+if (ldap_server_select) {
+  // attach event to ldap server drop down
+  ldap_server_select.onchange = function() {
+    updateLdapSearchFields(this);
+    return false;
+  }
+}
+
+// update the fields on page load
+updateLdapSearchFields(ldap_server_select);
+
+/**
+ * function to change the attributes of the ldap server search fields select box
+ * this function is triggered by an onchange event in the server select box 
+ */
+function updateLdapSearchFields(element) {
+
+  // get the search fields select element
+  var search_fields = document.getElementById('rcfmd_ldap_public_search_field');
+
+  // get rid of the current options for the select
+  for (i = search_fields.length - 1; i>=0; i--)
+    search_fields.remove(i);
+
+  // get the array containing this servers search fields
+  var server_fields = rcmail.env[element.value + '_search_fields'];
+
+  // add a new option for each of the possible search fields for the selected server
+  for (i = 0; i < server_fields.length; i++) {
+
+    // the last array value is for fuzzy search, so skip that one
+    if (i < (server_fields.length - 1)) {
+      var new_option = document.createElement('option');
+      new_option.text  = server_fields[i];
+      new_option.value = server_fields[i];
+
+      // standards compliant browsers
+      try {
+        search_fields.add(new_option, null);
+      }
+      // for the standards challenged one...
+      catch(e) {
+        search_fields.add(new_option);      
+      }
+    } else {
+      // ok, last member of array, so check the value of fuzzy_search
+      var fuzzy_search = server_fields[i];
+      var search_check_box = document.getElementById('rcmfd_ldap_public_search_type');
+
+      if (fuzzy_search == 'fuzzy') {
+        // we should enable the check box
+        if (search_check_box.disabled)
+          search_check_box.disabled = false;
+
+        // make sure the checkbox is unchecked
+        if (search_check_box.checked)
+          search_check_box.checked = false;
+
+      } else {
+        // we should disable the check box
+        if (!search_check_box.disabled)
+          search_check_box.disabled = true;
+
+        // check the checkbox (just a visual clue for the user)
+        if (!search_check_box.checked)
+          search_check_box.checked = true;
+      }
+    }
+  }
+}
+</script>
diff --git a/skins/default/ldapsearchform.css b/skins/default/ldapsearchform.css
new file mode 100644
index 0000000..9661442
--- /dev/null
+++ b/skins/default/ldapsearchform.css
@@ -0,0 +1,54 @@
+/***** RoundCube|Mail address book task styles *****/
+
+
+body.iframe,
+{
+  background-color: #F9F9F9;
+}
+
+#ldapsearch-title
+{
+  height: 12px !important;
+/*  height: 20px; */
+  padding: 4px 20px 3px 20px;
+  border-bottom: 1px solid #999999;
+  color: #333333;
+  font-size: 11px;
+  font-weight: bold;
+  background-color: #EBEBEB;
+  background-image: url(images/listheader_aqua.gif); 
+}
+
+#ldapsearch-details
+{
+  padding: 15px 20px 10px 20px;
+}
+
+#ldapsearch-details table td.title
+{
+  color: #666666;
+  font-weight: bold;
+  text-align: right;
+  padding-right: 10px;
+}
+
+#ldapAddressList 
+{
+  width: 100%;
+  table-layout: fixed;
+  /* css hack for IE */
+  width: expression(document.getElementById('addresslist').clientWidth);
+}
+
+#ldapAddressList table 
+{
+  border-top: 1px solid #999999;
+}
+
+#ldap-search-results div
+{
+  width: 100%;
+  color: red;
+  background-color: green;
+}
+
diff --git a/skins/default/templates/ldappublicsearch.html b/skins/default/templates/ldappublicsearch.html
new file mode 100644
index 0000000..70570c0
--- /dev/null
+++ b/skins/default/templates/ldappublicsearch.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<link rel="stylesheet" type="text/css" href="/common.css" />
+<link rel="stylesheet" type="text/css" href="/ldapsearchform.css" />
+</head>
+<body class="iframe">
+
+<div id="ldapsearch-title"><roundcube:label name="ldappublicsearchform" /></div>
+
+<div id="ldapsearch-details">
+<roundcube:object name="ldappublicsearch" size="40" />
+<p>
+<roundcube:button command="ldappublicsearch" type="input" class="button" label="ldappublicsearch" />
+<input type="button" value="<roundcube:label name="cancel" />" class="button" onclick="history.back()" />&nbsp;
+<br /></p>
+</div>
+
+
+<div id="ldapsearch-results">
+<roundcube:object name="ldappublicaddresslist"
+  id="ldappublicaddresslist"
+  cellspacing="0"
+  summary="Ldap email address list" />
+</div>
+
+<roundcube:include file="/includes/ldapscripts.html" />
+
+</body>
+</html>

--
Gitblit v1.9.1