svncommit
2006-01-08 d1d2c4fb1d0e9b7a46693e617835850b0edc0fd5
adding files and modifications for public ldap search


7 files added
8 files modified
1102 ■■■■■ changed files
config/main.inc.php.dist 14 ●●●●● patch | view | raw | blame | history
index.php 6 ●●●●● patch | view | raw | blame | history
program/include/main.inc 55 ●●●● patch | view | raw | blame | history
program/include/rcube_ldap.inc 259 ●●●●● patch | view | raw | blame | history
program/js/app.js 163 ●●●●● patch | view | raw | blame | history
program/localization/en/labels.inc 9 ●●●● patch | view | raw | blame | history
program/localization/en/messages.inc 9 ●●●● patch | view | raw | blame | history
program/steps/addressbook/func.inc 3 ●●●● patch | view | raw | blame | history
program/steps/addressbook/ldapsearchform.inc 264 ●●●●● patch | view | raw | blame | history
program/steps/addressbook/save.inc 162 ●●●● patch | view | raw | blame | history
skins/default/images/buttons/ldap_act.png patch | view | raw | blame | history
skins/default/images/buttons/ldap_pas.png patch | view | raw | blame | history
skins/default/includes/ldapscripts.html 73 ●●●●● patch | view | raw | blame | history
skins/default/ldapsearchform.css 54 ●●●●● patch | view | raw | blame | history
skins/default/templates/ldappublicsearch.html 31 ●●●●● patch | view | raw | blame | history
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 *****/
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);
                      
?>
?>
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));
  }
?>
?>
program/include/rcube_ldap.inc
New file
@@ -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
?>
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;
    }
  /*********************************************************/
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';
?>
?>
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?';
?>
$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';
?>
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;
  }
?>
?>
program/steps/addressbook/ldapsearchform.inc
New file
@@ -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');
?>
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 @@
    }
  }
?>
?>
skins/default/images/buttons/ldap_act.png
skins/default/images/buttons/ldap_pas.png
skins/default/includes/ldapscripts.html
New file
@@ -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>
skins/default/ldapsearchform.css
New file
@@ -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;
}
skins/default/templates/ldappublicsearch.html
New file
@@ -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>