From 1495ac7913095ae8284c3501b7d4e6dd31a484ec Mon Sep 17 00:00:00 2001 From: alecpl <alec@alec.pl> Date: Sat, 14 Apr 2012 07:15:28 -0400 Subject: [PATCH] - More public methods, code cleanup --- program/include/rcube_ldap.php | 668 +++++++++++++++++++++++++++++++++++++++---------------- 1 files changed, 475 insertions(+), 193 deletions(-) diff --git a/program/include/rcube_ldap.php b/program/include/rcube_ldap.php index 58e2ca9..545fef9 100644 --- a/program/include/rcube_ldap.php +++ b/program/include/rcube_ldap.php @@ -4,9 +4,12 @@ | program/include/rcube_ldap.php | | | | This file is part of the Roundcube Webmail client | - | Copyright (C) 2006-2011, The Roundcube Dev Team | + | Copyright (C) 2006-2012, The Roundcube Dev Team | | Copyright (C) 2011, Kolab Systems AG | - | Licensed under the GNU GPL | + | | + | Licensed under the GNU General Public License version 3 or | + | any later version with exceptions for skins & plugins. | + | See the README file for a full license statement. | | | | PURPOSE: | | Interface to an LDAP address directory | @@ -35,19 +38,16 @@ public $readonly = true; public $ready = false; public $group_id = 0; - public $list_page = 1; - public $page_size = 10; public $coltypes = array(); /** private properties */ protected $conn; protected $prop = array(); protected $fieldmap = array(); - + protected $sub_filter; protected $filter = ''; protected $result = null; protected $ldap_result = null; - protected $sort_col = ''; protected $mail_domain = ''; protected $debug = false; @@ -63,12 +63,11 @@ /** * Object constructor * - * @param array LDAP connection properties - * @param boolean Enables debug mode - * @param string Current user mail domain name - * @param integer User-ID + * @param array $p LDAP connection properties + * @param boolean $debug Enables debug mode + * @param string $mail_domain Current user mail domain name */ - function __construct($p, $debug=false, $mail_domain=NULL) + function __construct($p, $debug = false, $mail_domain = null) { $this->prop = $p; @@ -95,7 +94,7 @@ foreach ($p['fieldmap'] as $rf => $lf) $this->fieldmap[$rf] = $this->_attr_name(strtolower($lf)); } - else { + else if (!empty($p)) { // read deprecated *_field properties to remain backwards compatible foreach ($p as $prop => $value) if (preg_match('/^(.+)_field$/', $prop, $matches)) @@ -127,15 +126,37 @@ } } } - else if ($this->coltypes['address']) + else if ($this->coltypes['address']) { $this->coltypes['address'] = array('type' => 'textarea', 'childs' => null, 'limit' => 1, 'size' => 40); + } // make sure 'required_fields' is an array - if (!is_array($this->prop['required_fields'])) + if (!is_array($this->prop['required_fields'])) { $this->prop['required_fields'] = (array) $this->prop['required_fields']; + } - foreach ($this->prop['required_fields'] as $key => $val) + // make sure LDAP_rdn field is required + if (!empty($this->prop['LDAP_rdn']) && !in_array($this->prop['LDAP_rdn'], $this->prop['required_fields'])) { + $this->prop['required_fields'][] = $this->prop['LDAP_rdn']; + } + + foreach ($this->prop['required_fields'] as $key => $val) { $this->prop['required_fields'][$key] = $this->_attr_name(strtolower($val)); + } + + // Build sub_fields filter + if (!empty($this->prop['sub_fields']) && is_array($this->prop['sub_fields'])) { + $this->sub_filter = ''; + foreach ($this->prop['sub_fields'] as $attr => $class) { + if (!empty($class)) { + $class = is_array($class) ? array_pop($class) : $class; + $this->sub_filter .= '(objectClass=' . $class . ')'; + } + } + if (count($this->prop['sub_fields']) > 1) { + $this->sub_filter = '(|' . $this->sub_filter . ')'; + } + } $this->sort_col = is_array($p['sort']) ? $p['sort'][0] : $p['sort']; $this->debug = $debug; @@ -154,10 +175,10 @@ */ private function _connect() { - global $RCMAIL; + $RCMAIL = rcmail::get_instance(); if (!function_exists('ldap_connect')) - raise_error(array('code' => 100, 'type' => 'ldap', + rcube::raise_error(array('code' => 100, 'type' => 'ldap', 'file' => __FILE__, 'line' => __LINE__, 'message' => "No ldap support in this installation of PHP"), true, true); @@ -173,10 +194,10 @@ foreach ($this->prop['hosts'] as $host) { - $host = idn_to_ascii(rcube_parse_host($host)); + $host = idn_to_ascii(rcmail::parse_host($host)); $hostname = $host.($this->prop['port'] ? ':'.$this->prop['port'] : ''); - $this->_debug("C: Connect [$hostname]"); + $this->_debug("C: Connect [$hostname] [{$this->prop['name']}]"); if ($lc = @ldap_connect($host, $this->prop['port'])) { @@ -203,7 +224,7 @@ } if (!is_resource($this->conn)) { - raise_error(array('code' => 100, 'type' => 'ldap', + rcube::raise_error(array('code' => 100, 'type' => 'ldap', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Could not connect to any LDAP server, last tried $hostname"), true); @@ -226,7 +247,7 @@ } // Get the pieces needed for variable replacement. - if ($fu = $RCMAIL->user->get_username()) + if ($fu = $RCMAIL->get_user_name()) list($u, $d) = explode('@', $fu); else $d = $this->mail_domain; @@ -236,6 +257,10 @@ $replaces = array('%dn' => '', '%dc' => $dc, '%d' => $d, '%fu' => $fu, '%u' => $u); if ($this->prop['search_base_dn'] && $this->prop['search_filter']) { + if (!empty($this->prop['search_bind_dn']) && !empty($this->prop['search_bind_pw'])) { + $this->bind($this->prop['search_bind_dn'], $this->prop['search_bind_pw']); + } + // Search for the dn to use to authenticate $this->prop['search_base_dn'] = strtr($this->prop['search_base_dn'], $replaces); $this->prop['search_filter'] = strtr($this->prop['search_filter'], $replaces); @@ -261,7 +286,7 @@ if (!empty($this->prop['search_dn_default'])) $replaces['%dn'] = $this->prop['search_dn_default']; else { - raise_error(array( + rcube::raise_error(array( 'code' => 100, 'type' => 'ldap', 'file' => __FILE__, 'line' => __LINE__, 'message' => "DN not found using LDAP search."), true); @@ -315,7 +340,7 @@ } if (!function_exists('ldap_sasl_bind')) { - raise_error(array('code' => 100, 'type' => 'ldap', + rcube::raise_error(array('code' => 100, 'type' => 'ldap', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Unable to bind: ldap_sasl_bind() not exists"), true, true); @@ -341,7 +366,7 @@ $this->_debug("S: ".ldap_error($this->conn)); - raise_error(array( + rcube::raise_error(array( 'code' => ldap_errno($this->conn), 'type' => 'ldap', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Bind failed for authcid=$authc ".ldap_error($this->conn)), @@ -374,7 +399,7 @@ $this->_debug("S: ".ldap_error($this->conn)); - raise_error(array( + rcube::raise_error(array( 'code' => ldap_errno($this->conn), 'type' => 'ldap', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Bind failed for dn=$dn: ".ldap_error($this->conn)), @@ -410,24 +435,15 @@ /** - * Set internal list page + * Set internal sort settings * - * @param number $page Page number to list + * @param string $sort_col Sort column + * @param string $sort_order Sort order */ - function set_page($page) + function set_sort_order($sort_col, $sort_order = null) { - $this->list_page = (int)$page; - } - - - /** - * Set internal page size - * - * @param number $size Number of messages to display on one page - */ - function set_pagesize($size) - { - $this->page_size = (int)$size; + if ($this->fieldmap[$sort_col]) + $this->sort_col = $this->fieldmap[$sort_col]; } @@ -559,7 +575,7 @@ $entries = @ldap_get_entries($this->conn, $result); } - for ($i=0; $i < $entries["count"]; $i++) + for ($i=0; $i < $entries['count']; $i++) { $entry = $entries[$i]; @@ -569,6 +585,7 @@ foreach ((array)$entry['objectclass'] as $objectclass) { switch (strtolower($objectclass)) { + case "group": case "groupofnames": case "kolabgroupofnames": $group_members = array_merge($group_members, $this->_list_group_members($dn, $entry, 'member', $count)); @@ -582,7 +599,7 @@ break; } } - + if ($this->prop['sizelimit'] && count($group_members) > $this->prop['sizelimit']) break; } @@ -615,6 +632,9 @@ for ($i=0; $i < $entry[$attr]['count']; $i++) { + if (empty($entry[$attr][$i])) + continue; + $result = @ldap_read($this->conn, $entry[$attr][$i], '(objectclass=*)', $attrib, 0, (int)$this->prop['sizelimit'], (int)$this->prop['timelimit']); @@ -659,14 +679,11 @@ $attrib = $count ? array('dn') : array_values($this->fieldmap); if ($result = @$func($this->conn, $m[1], $filter, - $attrib, 0, (int)$this->prop['sizelimit'], (int)$this->prop['timelimit'])) - { + $attrib, 0, (int)$this->prop['sizelimit'], (int)$this->prop['timelimit']) + ) { $this->_debug("S: ".ldap_count_entries($this->conn, $result)." record(s) for ".$m[1]); - if ($err = ldap_errno($this->conn)) - $this->_debug("S: Error: " .ldap_err2str($err)); } - else - { + else { $this->_debug("S: ".ldap_error($this->conn)); return $group_members; } @@ -869,10 +886,8 @@ // The filter is not set, set it. $this->filter = $this->prop['filter']; } - $this->_exec_search(true); - if ($this->ldap_result) { - $count = ldap_count_entries($this->conn, $this->ldap_result); - } + + $count = (int) $this->_exec_search(true); } return new rcube_result_set($count, ($this->list_page-1) * $this->page_size); @@ -900,28 +915,42 @@ */ function get_record($dn, $assoc=false) { - $res = null; + $res = $this->result = null; + if ($this->conn && $dn) { $dn = self::dn_decode($dn); $this->_debug("C: Read [dn: $dn] [(objectclass=*)]"); - if ($this->ldap_result = @ldap_read($this->conn, $dn, '(objectclass=*)', array_values($this->fieldmap))) - $entry = ldap_first_entry($this->conn, $this->ldap_result); - else + if ($ldap_result = @ldap_read($this->conn, $dn, '(objectclass=*)', array_values($this->fieldmap))) { + $this->_debug("S: OK"); + + $entry = ldap_first_entry($this->conn, $ldap_result); + + if ($entry && ($rec = ldap_get_attributes($this->conn, $entry))) { + $rec = array_change_key_case($rec, CASE_LOWER); + } + } + else { $this->_debug("S: ".ldap_error($this->conn)); + } - if ($entry && ($rec = ldap_get_attributes($this->conn, $entry))) - { - $this->_debug("S: OK"/* . print_r($rec, true)*/); + // Use ldap_list to get subentries like country (c) attribute (#1488123) + if (!empty($rec) && $this->sub_filter) { + if ($entries = $this->ldap_list($dn, $this->sub_filter, array_keys($this->prop['sub_fields']))) { + foreach ($entries as $entry) { + $lrec = array_change_key_case($entry, CASE_LOWER); + $rec = array_merge($lrec, $rec); + } + } + } - $rec = array_change_key_case($rec, CASE_LOWER); - + if (!empty($rec)) { // Add in the dn for the entry. $rec['dn'] = $dn; $res = $this->_ldap2result($rec); - $this->result = new rcube_result_set(1); + $this->result = new rcube_result_set(); $this->result->add($res); } } @@ -940,6 +969,11 @@ */ public function validate(&$save_data, $autofix = false) { + // validate e-mail addresses + if (!parent::validate($save_data, $autofix)) { + return false; + } + // check for name input if (empty($save_data['name'])) { $this->set_error(self::ERROR_VALIDATE, 'nonamewarning'); @@ -950,7 +984,7 @@ $missing = null; $ldap_data = $this->_map_data($save_data); foreach ($this->prop['required_fields'] as $fld) { - if (!isset($ldap_data[$fld])) { + if (!isset($ldap_data[$fld]) || $ldap_data[$fld] === '') { $missing[$fld] = 1; } } @@ -958,26 +992,43 @@ if ($missing) { // try to complete record automatically if ($autofix) { + $sn_field = $this->fieldmap['surname']; + $fn_field = $this->fieldmap['firstname']; + $mail_field = $this->fieldmap['email']; + + // try to extract surname and firstname from displayname $reverse_map = array_flip($this->fieldmap); - $name_parts = preg_split('/[\s,.]+/', $save_data['name']); - if ($missing['sn']) { - $sn_field = $reverse_map['sn']; - $save_data[$sn_field] = array_pop ($name_parts); - } - if ($missing[($fn_field = $this->fieldmap['firstname'])]) { - $save_data['firstname'] = array_shift($name_parts); + $name_parts = preg_split('/[\s,.]+/', $save_data['name']); + + if ($sn_field && $missing[$sn_field]) { + $save_data['surname'] = array_pop($name_parts); + unset($missing[$sn_field]); } - return $this->validate($save_data, false); + if ($fn_field && $missing[$fn_field]) { + $save_data['firstname'] = array_shift($name_parts); + unset($missing[$fn_field]); + } + + // try to fix missing e-mail, very often on import + // from vCard we have email:other only defined + if ($mail_field && $missing[$mail_field]) { + $emails = $this->get_col_values('email', $save_data, true); + if (!empty($emails) && ($email = array_shift($emails))) { + $save_data['email'] = $email; + unset($missing[$mail_field]); + } + } } // TODO: generate message saying which fields are missing - $this->set_error(self::ERROR_VALIDATE, 'formincomplete'); - return false; + if (!empty($missing)) { + $this->set_error(self::ERROR_VALIDATE, 'formincomplete'); + return false; + } } - // validate e-mail addresses - return parent::validate($save_data, $autofix); + return true; } @@ -1012,21 +1063,36 @@ // Build the new entries DN. $dn = $this->prop['LDAP_rdn'].'='.$this->_quote_string($newentry[$this->prop['LDAP_rdn']], true).','.$this->base_dn; - $this->_debug("C: Add [dn: $dn]: ".print_r($newentry, true)); + // Remove attributes that need to be added separately (child objects) + $xfields = array(); + if (!empty($this->prop['sub_fields']) && is_array($this->prop['sub_fields'])) { + foreach ($this->prop['sub_fields'] as $xf => $xclass) { + if (!empty($newentry[$xf])) { + $xfields[$xf] = $newentry[$xf]; + unset($newentry[$xf]); + } + } + } - $res = ldap_add($this->conn, $dn, $newentry); - if ($res === FALSE) { - $this->_debug("S: ".ldap_error($this->conn)); + if (!$this->ldap_add($dn, $newentry)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; - } // end if + } - $this->_debug("S: OK"); + foreach ($xfields as $xidx => $xf) { + $xdn = $xidx.'='.$this->_quote_string($xf).','.$dn; + $xf = array( + $xidx => $xf, + 'objectClass' => (array) $this->prop['sub_fields'][$xidx], + ); + + $this->ldap_add($xdn, $xf); + } $dn = self::dn_encode($dn); // add new contact to the selected group - if ($this->groups) + if ($this->group_id) $this->add_to_group($this->group_id, $dn); return $dn; @@ -1044,26 +1110,60 @@ function update($id, $save_cols) { $record = $this->get_record($id, true); - $result = $this->get_result(); - $record = $result->first(); - $newdata = array(); + $newdata = array(); $replacedata = array(); - $deletedata = array(); + $deletedata = array(); + $subdata = array(); + $subdeldata = array(); + $subnewdata = array(); $ldap_data = $this->_map_data($save_cols); - $old_data = $record['_raw_attrib']; + $old_data = $record['_raw_attrib']; + + // special handling of photo col + if ($photo_fld = $this->fieldmap['photo']) { + // undefined means keep old photo + if (!array_key_exists('photo', $save_cols)) { + $ldap_data[$photo_fld] = $record['photo']; + } + } foreach ($this->fieldmap as $col => $fld) { - $val = $ldap_data[$fld]; if ($fld) { + $val = $ldap_data[$fld]; + $old = $old_data[$fld]; // remove empty array values if (is_array($val)) $val = array_filter($val); + // $this->_map_data() result and _raw_attrib use different format + // make sure comparing array with one element with a string works as expected + if (is_array($old) && count($old) == 1 && !is_array($val)) { + $old = array_pop($old); + } + if (is_array($val) && count($val) == 1 && !is_array($old)) { + $val = array_pop($val); + } + // Subentries must be handled separately + if (!empty($this->prop['sub_fields']) && isset($this->prop['sub_fields'][$fld])) { + if ($old != $val) { + if ($old !== null) { + $subdeldata[$fld] = $old; + } + if ($val) { + $subnewdata[$fld] = $val; + } + } + else if ($old !== null) { + $subdata[$fld] = $old; + } + continue; + } + // The field does exist compare it to the ldap record. - if ($old_data[$fld] != $val) { + if ($old != $val) { // Changed, but find out how. - if (!isset($old_data[$fld])) { + if ($old === null) { // Field was not set prior, need to add it. $newdata[$fld] = $val; } @@ -1073,7 +1173,7 @@ // It is not, safe to clear. $deletedata[$fld] = $old_data[$fld]; } - } // end elseif + } else { // The data was modified, save it out. $replacedata[$fld] = $val; @@ -1081,19 +1181,27 @@ } // end if } // end if } // end foreach - +/* + console($old_data); + console($ldap_data); + console('----'); + console($newdata); + console($replacedata); + console($deletedata); + console('----'); + console($subdata); + console($subnewdata); + console($subdeldata); +*/ $dn = self::dn_decode($id); // Update the entry as required. if (!empty($deletedata)) { // Delete the fields. - $this->_debug("C: Delete [dn: $dn]: ".print_r($deletedata, true)); - if (!ldap_mod_del($this->conn, $dn, $deletedata)) { - $this->_debug("S: ".ldap_error($this->conn)); + if (!$this->ldap_mod_del($dn, $deletedata)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; } - $this->_debug("S: OK"); } // end if if (!empty($replacedata)) { @@ -1110,41 +1218,49 @@ } // Replace the fields. if (!empty($replacedata)) { - $this->_debug("C: Replace [dn: $dn]: ".print_r($replacedata, true)); - if (!ldap_mod_replace($this->conn, $dn, $replacedata)) { - $this->_debug("S: ".ldap_error($this->conn)); + if (!$this->ldap_mod_replace($dn, $replacedata)) { + $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; } - $this->_debug("S: OK"); - } // end if + } } // end if + + // RDN change, we need to remove all sub-entries + if (!empty($newrdn)) { + $subdeldata = array_merge($subdeldata, $subdata); + $subnewdata = array_merge($subnewdata, $subdata); + } + + // remove sub-entries + if (!empty($subdeldata)) { + foreach ($subdeldata as $fld => $val) { + $subdn = $fld.'='.$this->_quote_string($val).','.$dn; + if (!$this->ldap_delete($subdn)) { + return false; + } + } + } if (!empty($newdata)) { // Add the fields. - $this->_debug("C: Add [dn: $dn]: ".print_r($newdata, true)); - if (!ldap_mod_add($this->conn, $dn, $newdata)) { - $this->_debug("S: ".ldap_error($this->conn)); + if (!$this->ldap_mod_add($dn, $newdata)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; } - $this->_debug("S: OK"); } // end if // Handle RDN change if (!empty($newrdn)) { - $this->_debug("C: Rename [dn: $dn] [dn: $newrdn]"); - if (!ldap_rename($this->conn, $dn, $newrdn, NULL, TRUE)) { - $this->_debug("S: ".ldap_error($this->conn)); + if (!$this->ldap_rename($dn, $newrdn, null, true)) { + $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; } - $this->_debug("S: OK"); $dn = self::dn_encode($dn); $newdn = self::dn_encode($newdn); // change the group membership of the contact - if ($this->groups) - { + if ($this->groups) { $group_ids = $this->get_record_groups($dn); foreach ($group_ids as $group_id) { @@ -1153,10 +1269,22 @@ } } - return $newdn; + $dn = self::dn_decode($newdn); } - return true; + // add sub-entries + if (!empty($subnewdata)) { + foreach ($subnewdata as $fld => $val) { + $subdn = $fld.'='.$this->_quote_string($val).','.$dn; + $xf = array( + $fld => $val, + 'objectClass' => (array) $this->prop['sub_fields'][$fld], + ); + $this->ldap_add($subdn, $xf); + } + } + + return $newdn ? $newdn : true; } @@ -1177,15 +1305,24 @@ foreach ($ids as $id) { $dn = self::dn_decode($id); - $this->_debug("C: Delete [dn: $dn]"); + + // Need to delete all sub-entries first + if ($this->sub_filter) { + if ($entries = $this->ldap_list($dn, $this->sub_filter)) { + foreach ($entries as $entry) { + if (!$this->ldap_delete($entry['dn'])) { + $this->set_error(self::ERROR_SAVING, 'errorsaving'); + return false; + } + } + } + } + // Delete the record. - $res = ldap_delete($this->conn, $dn); - if ($res === FALSE) { - $this->_debug("S: ".ldap_error($this->conn)); + if (!$this->ldap_delete($dn)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; - } // end if - $this->_debug("S: OK"); + } // remove contact from all groups where he was member if ($this->groups) { @@ -1198,6 +1335,23 @@ } // end foreach return count($ids); + } + + + /** + * Remove all contact records + */ + function delete_all() + { + //searching for contact entries + $dn_list = $this->ldap_list($this->base_dn, $this->prop['filter'] ? $this->prop['filter'] : '(objectclass=*)'); + + if (!empty($dn_list)) { + foreach ($dn_list as $idx => $entry) { + $dn_list[$idx] = self::dn_encode($entry['dn']); + } + $this->delete($dn_list); + } } @@ -1231,23 +1385,28 @@ // only fetch dn for count (should keep the payload low) $attrs = $count ? array('dn') : array_values($this->fieldmap); if ($this->ldap_result = @$function($this->conn, $this->base_dn, $filter, - $attrs, 0, (int)$this->prop['sizelimit'], (int)$this->prop['timelimit'])) - { + $attrs, 0, (int)$this->prop['sizelimit'], (int)$this->prop['timelimit']) + ) { // when running on a patched PHP we can use the extended functions to retrieve the total count from the LDAP search result - if ($this->vlv_active && function_exists('ldap_parse_virtuallist_control') && - ldap_parse_result($this->conn, $this->ldap_result, $errcode, $matcheddn, $errmsg, $referrals, $serverctrls)) { - ldap_parse_virtuallist_control($this->conn, $serverctrls, $last_offset, $this->vlv_count, $vresult); - $this->_debug("S: VLV result: last_offset=$last_offset; content_count=$this->vlv_count"); + if ($this->vlv_active && function_exists('ldap_parse_virtuallist_control')) { + if (ldap_parse_result($this->conn, $this->ldap_result, + $errcode, $matcheddn, $errmsg, $referrals, $serverctrls) + ) { + ldap_parse_virtuallist_control($this->conn, $serverctrls, + $last_offset, $this->vlv_count, $vresult); + $this->_debug("S: VLV result: last_offset=$last_offset; content_count=$this->vlv_count"); + } + else { + $this->_debug("S: ".($errmsg ? $errmsg : ldap_error($this->conn))); + } } - $this->_debug("S: ".ldap_count_entries($this->conn, $this->ldap_result)." record(s)"); - if ($err = ldap_errno($this->conn)) - $this->_debug("S: Error: " .ldap_err2str($err)); + $entries_count = ldap_count_entries($this->conn, $this->ldap_result); + $this->_debug("S: $entries_count record(s)"); - return $count ? ldap_count_entries($this->conn, $this->ldap_result) : true; + return $count ? $entries_count : true; } - else - { + else { $this->_debug("S: ".ldap_error($this->conn)); } } @@ -1272,7 +1431,7 @@ $ns_function = 'ldap_read'; break; } - + return $function; } @@ -1314,12 +1473,13 @@ if (!($value = $rec[$lf][$i])) continue; + list($col, $subtype) = explode(':', $rf); $out['_raw_attrib'][$lf][$i] = $value; if ($rf == 'email' && $this->mail_domain && !strpos($value, '@')) $out[$rf][] = sprintf('%s@%s', $value, $this->mail_domain); - else if (in_array($rf, array('street','zipcode','locality','country','region'))) - $out['address'][$i][$rf] = $value; + else if (in_array($col, array('street','zipcode','locality','country','region'))) + $out['address'.($subtype?':':'').$subtype][$i][$col] = $value; else if ($rec[$lf]['count'] > 1) $out[$rf][] = $value; else @@ -1374,7 +1534,7 @@ $ldap_data[$fld] = $val; } } - + return $ldap_data; } @@ -1401,8 +1561,9 @@ */ private function _debug($str) { - if ($this->debug) - write_log('ldap', $str); + if ($this->debug) { + rcmail::write_log('ldap', $str); + } } @@ -1537,22 +1698,7 @@ $groups[$group_id]['ID'] = $group_id; $groups[$group_id]['dn'] = $ldap_data[$i]['dn']; $groups[$group_id]['name'] = $group_name; - $groups[$group_id]['member_attr'] = $this->prop['member_attr']; - - // check objectClass attributes of group and act accordingly - for ($j=0; $j < $ldap_data[$i]['objectclass']['count']; $j++) { - switch (strtolower($ldap_data[$i]['objectclass'][$j])) { - case 'groupofnames': - case 'kolabgroupofnames': - $groups[$group_id]['member_attr'] = 'member'; - break; - - case 'groupofuniquenames': - case 'kolabgroupofuniquenames': - $groups[$group_id]['member_attr'] = 'uniqueMember'; - break; - } - } + $groups[$group_id]['member_attr'] = $this->get_group_member_attr($ldap_data[$i]['objectclass']); // list email attributes of a group for ($j=0; $ldap_data[$i][$email_attr] && $j < $ldap_data[$i][$email_attr]['count']; $j++) { @@ -1613,8 +1759,8 @@ $base_dn = $this->groups_base_dn; $new_dn = "cn=$group_name,$base_dn"; $new_gid = self::dn_encode($group_name); - $member_attr = $this->prop['groups']['member_attr']; - $name_attr = $this->prop['groups']['name_attr']; + $member_attr = $this->get_group_member_attr(); + $name_attr = $this->prop['groups']['name_attr'] ? $this->prop['groups']['name_attr'] : 'cn'; $new_entry = array( 'objectClass' => $this->prop['groups']['object_classes'], @@ -1622,17 +1768,11 @@ $member_attr => '', ); - $this->_debug("C: Add [dn: $new_dn]: ".print_r($new_entry, true)); - - $res = ldap_add($this->conn, $new_dn, $new_entry); - if ($res === false) - { - $this->_debug("S: ".ldap_error($this->conn)); + if (!$this->ldap_add($new_dn, $new_entry)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; } - $this->_debug("S: OK"); $this->cache->remove('groups'); return array('id' => $new_gid, 'name' => $group_name); @@ -1653,17 +1793,11 @@ $group_name = $group_cache[$group_id]['name']; $del_dn = "cn=$group_name,$base_dn"; - $this->_debug("C: Delete [dn: $del_dn]"); - - $res = ldap_delete($this->conn, $del_dn); - if ($res === false) - { - $this->_debug("S: ".ldap_error($this->conn)); + if (!$this->ldap_delete($del_dn)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; } - $this->_debug("S: OK"); $this->cache->remove('groups'); return true; @@ -1688,17 +1822,11 @@ $new_rdn = "cn=$new_name"; $new_gid = self::dn_encode($new_name); - $this->_debug("C: Rename [dn: $old_dn] [dn: $new_rdn]"); - - $res = ldap_rename($this->conn, $old_dn, $new_rdn, NULL, TRUE); - if ($res === false) - { - $this->_debug("S: ".ldap_error($this->conn)); + if (!$this->ldap_rename($old_dn, $new_rdn, null, true)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; } - $this->_debug("S: OK"); $this->cache->remove('groups'); return $new_name; @@ -1716,26 +1844,23 @@ if (($group_cache = $this->cache->get('groups')) === null) $group_cache = $this->_fetch_groups(); + if (!is_array($contact_ids)) + $contact_ids = explode(',', $contact_ids); + $base_dn = $this->groups_base_dn; $group_name = $group_cache[$group_id]['name']; $member_attr = $group_cache[$group_id]['member_attr']; $group_dn = "cn=$group_name,$base_dn"; $new_attrs = array(); - foreach (explode(",", $contact_ids) as $id) + foreach ($contact_ids as $id) $new_attrs[$member_attr][] = self::dn_decode($id); - $this->_debug("C: Add [dn: $group_dn]: ".print_r($new_attrs, true)); - - $res = ldap_mod_add($this->conn, $group_dn, $new_attrs); - if ($res === false) - { - $this->_debug("S: ".ldap_error($this->conn)); + if (!$this->ldap_mod_add($group_dn, $new_attrs)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return 0; } - $this->_debug("S: OK"); $this->cache->remove('groups'); return count($new_attrs['member']); @@ -1762,17 +1887,11 @@ foreach (explode(",", $contact_ids) as $id) $del_attrs[$member_attr][] = self::dn_decode($id); - $this->_debug("C: Delete [dn: $group_dn]: ".print_r($del_attrs, true)); - - $res = ldap_mod_del($this->conn, $group_dn, $del_attrs); - if ($res === false) - { - $this->_debug("S: ".ldap_error($this->conn)); + if (!$this->ldap_mod_del($group_dn, $del_attrs)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return 0; } - $this->_debug("S: OK"); $this->cache->remove('groups'); return count($del_attrs['member']); @@ -1793,8 +1912,8 @@ $base_dn = $this->groups_base_dn; $contact_dn = self::dn_decode($contact_id); - $name_attr = $this->prop['groups']['name_attr']; - $member_attr = $this->prop['member_attr']; + $name_attr = $this->prop['groups']['name_attr'] ? $this->prop['groups']['name_attr'] : 'cn'; + $member_attr = $this->get_group_member_attr(); $add_filter = ''; if ($member_attr != 'member' && $member_attr != 'uniqueMember') $add_filter = "($member_attr=$contact_dn)"; @@ -1819,6 +1938,42 @@ $groups[$group_id] = $group_id; } return $groups; + } + + /** + * Detects group member attribute name + */ + private function get_group_member_attr($object_classes = array()) + { + if (empty($object_classes)) { + $object_classes = $this->prop['groups']['object_classes']; + } + if (!empty($object_classes)) { + foreach ((array)$object_classes as $oc) { + switch (strtolower($oc)) { + case 'group': + case 'groupofnames': + case 'kolabgroupofnames': + $member_attr = 'member'; + break; + + case 'groupofuniquenames': + case 'kolabgroupofuniquenames': + $member_attr = 'uniqueMember'; + break; + } + } + } + + if (!empty($member_attr)) { + return $member_attr; + } + + if (!empty($this->prop['groups']['member_attr'])) { + return $this->prop['groups']['member_attr']; + } + + return 'member'; } @@ -1977,4 +2132,131 @@ $str = str_pad(strtr($str, '-_', '+/'), strlen($str) % 4, '=', STR_PAD_RIGHT); return base64_decode($str); } + + /** + * Wrapper for ldap_add() + */ + protected function ldap_add($dn, $entry) + { + $this->_debug("C: Add [dn: $dn]: ".print_r($entry, true)); + + $res = ldap_add($this->conn, $dn, $entry); + if ($res === false) { + $this->_debug("S: ".ldap_error($this->conn)); + return false; + } + + $this->_debug("S: OK"); + return true; + } + + /** + * Wrapper for ldap_delete() + */ + protected function ldap_delete($dn) + { + $this->_debug("C: Delete [dn: $dn]"); + + $res = ldap_delete($this->conn, $dn); + if ($res === false) { + $this->_debug("S: ".ldap_error($this->conn)); + return false; + } + + $this->_debug("S: OK"); + return true; + } + + /** + * Wrapper for ldap_mod_replace() + */ + protected function ldap_mod_replace($dn, $entry) + { + $this->_debug("C: Replace [dn: $dn]: ".print_r($entry, true)); + + if (!ldap_mod_replace($this->conn, $dn, $entry)) { + $this->_debug("S: ".ldap_error($this->conn)); + return false; + } + + $this->_debug("S: OK"); + return true; + } + + /** + * Wrapper for ldap_mod_add() + */ + protected function ldap_mod_add($dn, $entry) + { + $this->_debug("C: Add [dn: $dn]: ".print_r($entry, true)); + + if (!ldap_mod_add($this->conn, $dn, $entry)) { + $this->_debug("S: ".ldap_error($this->conn)); + return false; + } + + $this->_debug("S: OK"); + return true; + } + + /** + * Wrapper for ldap_mod_del() + */ + protected function ldap_mod_del($dn, $entry) + { + $this->_debug("C: Delete [dn: $dn]: ".print_r($entry, true)); + + if (!ldap_mod_del($this->conn, $dn, $entry)) { + $this->_debug("S: ".ldap_error($this->conn)); + return false; + } + + $this->_debug("S: OK"); + return true; + } + + /** + * Wrapper for ldap_rename() + */ + protected function ldap_rename($dn, $newrdn, $newparent = null, $deleteoldrdn = true) + { + $this->_debug("C: Rename [dn: $dn] [dn: $newrdn]"); + + if (!ldap_rename($this->conn, $dn, $newrdn, $newparent, $deleteoldrdn)) { + $this->_debug("S: ".ldap_error($this->conn)); + return false; + } + + $this->_debug("S: OK"); + return true; + } + + /** + * Wrapper for ldap_list() + */ + protected function ldap_list($dn, $filter, $attrs = array('')) + { + $list = array(); + $this->_debug("C: List [dn: $dn] [{$filter}]"); + + if ($result = ldap_list($this->conn, $dn, $filter, $attrs)) { + $list = ldap_get_entries($this->conn, $result); + + if ($list === false) { + $this->_debug("S: ".ldap_error($this->conn)); + return array(); + } + + $count = $list['count']; + unset($list['count']); + + $this->_debug("S: $count record(s)"); + } + else { + $this->_debug("S: ".ldap_error($this->conn)); + } + + return $list; + } + } -- Gitblit v1.9.1