* @author Benedikt Hallinger <beni@php.net>
* @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
-* @version SVN: $Id: LDAP2.php 286788 2009-08-04 06:05:49Z beni $
+* @version SVN: $Id: LDAP2.php 332308 2013-12-09 09:15:47Z beni $
* @link http://pear.php.net/package/Net_LDAP2/
*/
/**
* Net_LDAP2 Version
*/
-define('NET_LDAP2_VERSION', '2.0.7');
+define('NET_LDAP2_VERSION', '2.1.0');
/**
* Net_LDAP2 - manipulate LDAP servers the right way!
*/
public function startTLS()
{
- // Test to see if the server supports TLS first.
- // This is done via testing the extensions offered by the server.
- // The OID 1.3.6.1.4.1.1466.20037 tells us, if TLS is supported.
+ /* Test to see if the server supports TLS first.
+ This is done via testing the extensions offered by the server.
+ The OID 1.3.6.1.4.1.1466.20037 tells us, if TLS is supported.
+ Note, that not all servers allow to feth either the rootDSE or
+ attributes over an unencrypted channel, so we must ignore errors. */
$rootDSE = $this->rootDse();
if (self::isError($rootDSE)) {
- return $this->raiseError("Unable to fetch rootDSE entry ".
- "to see if TLS is supoported: ".$rootDSE->getMessage(), $rootDSE->getCode());
- }
-
- $supported_extensions = $rootDSE->getValue('supportedExtension');
- if (self::isError($supported_extensions)) {
- return $this->raiseError("Unable to fetch rootDSE attribute 'supportedExtension' ".
- "to see if TLS is supoported: ".$supported_extensions->getMessage(), $supported_extensions->getCode());
+ /* IGNORE this error, because server may refuse fetching the
+ RootDSE over an unencrypted connection. */
+ //return $this->raiseError("Unable to fetch rootDSE entry ".
+ //"to see if TLS is supoported: ".$rootDSE->getMessage(), $rootDSE->getCode());
+ } else {
+ /* Fetch suceeded, see, if the server supports TLS. Again, we
+ ignore errors, because the server may refuse to return
+ attributes over unencryted connections. */
+ $supported_extensions = $rootDSE->getValue('supportedExtension');
+ if (self::isError($supported_extensions)) {
+ /* IGNORE error, because server may refuse attribute
+ returning over an unencrypted connection. */
+ //return $this->raiseError("Unable to fetch rootDSE attribute 'supportedExtension' ".
+ //"to see if TLS is supoported: ".$supported_extensions->getMessage(), $supported_extensions->getCode());
+ } else {
+ // fetch succeedet, lets see if the server supports it.
+ // if not, then drop an error. If supported, then do nothing,
+ // because then we try to issue TLS afterwards.
+ if (!in_array('1.3.6.1.4.1.1466.20037', $supported_extensions)) {
+ return $this->raiseError("Server reports that it does not support TLS.");
+ }
+ }
}
- if (in_array('1.3.6.1.4.1.1466.20037', $supported_extensions)) {
- if (false === @ldap_start_tls($this->_link)) {
- return $this->raiseError("TLS not started: " .
- @ldap_error($this->_link),
- @ldap_errno($this->_link));
- }
- return true;
+ // Try to establish TLS.
+ if (false === @ldap_start_tls($this->_link)) {
+ // Starting TLS failed. This may be an error, or because
+ // the server does not support it but did not enable us to
+ // detect that above.
+ return $this->raiseError("TLS could not be started: " .
+ @ldap_error($this->_link),
+ @ldap_errno($this->_link));
} else {
- return $this->raiseError("Server reports that it does not support TLS");
+ return true; // TLS is started now.
}
}
// We have a failure. What type? We may be able to reconnect
// and try again.
$error_code = @ldap_errno($link);
- $error_name = $this->errorMessage($error_code);
+ $error_name = Net_LDAP2::errorMessage($error_code);
if (($error_name === 'LDAP_OPERATIONS_ERROR') &&
($this->_config['auto_reconnect'])) {
// We have a failure. What type?
// We may be able to reconnect and try again.
$error_code = @ldap_errno($link);
- $error_name = $this->errorMessage($error_code);
+ $error_name = Net_LDAP2::errorMessage($error_code);
- if (($this->errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
+ if ((Net_LDAP2::errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
($this->_config['auto_reconnect'])) {
// The server has become disconnected before trying the
// operation. We should try again, possibly with a
// We have a failure. What type? We may be able to reconnect
// and try again.
$error_code = $msg->getCode();
- $error_name = $this->errorMessage($error_code);
+ $error_name = Net_LDAP2::errorMessage($error_code);
- if (($this->errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
+ if ((Net_LDAP2::errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
($this->_config['auto_reconnect'])) {
// The server has become disconnected before trying the
// We have a failure. What type? We may be able to reconnect
// and try again.
$error_code = $msg->getCode();
- $error_name = $this->errorMessage($error_code);
+ $error_name = Net_LDAP2::errorMessage($error_code);
- if (($this->errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
+ if ((Net_LDAP2::errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
($this->_config['auto_reconnect'])) {
// The server has become disconnected before trying the
return $obj = new Net_LDAP2_Search ($search, $this, $attributes);
} elseif ($err == 87) {
// bad search filter
- return $this->raiseError($this->errorMessage($err) . "($filter)", $err);
+ return $this->raiseError(Net_LDAP2::errorMessage($err) . "($filter)", $err);
} elseif (($err == 1) && ($this->_config['auto_reconnect'])) {
// Errorcode 1 = LDAP_OPERATIONS_ERROR but we can try a reconnect.
$this->_link = false;
$this->performReconnect();
} else {
$msg = "\nParameters:\nBase: $base\nFilter: $filter\nScope: $scope";
- return $this->raiseError($this->errorMessage($err) . $msg, $err);
+ return $this->raiseError(Net_LDAP2::errorMessage($err) . $msg, $err);
}
} else {
return $obj = new Net_LDAP2_Search($search, $this, $attributes);
$msg = @ldap_err2str($err);
} else {
$err = NET_LDAP2_ERROR;
- $msg = $this->errorMessage($err);
+ $msg = Net_LDAP2::errorMessage($err);
}
return $this->raiseError($msg, $err);
}
$msg = @ldap_err2str($err);
} else {
$err = NET_LDAP2_ERROR;
- $msg = $this->errorMessage($err);
+ $msg = Net_LDAP2::errorMessage($err);
}
return $this->raiseError($msg, $err);
}
return PEAR::raiseError('Parameter $dn is not a string nor an entry object!');
}
- // make dn relative to parent
- $base = Net_LDAP2_Util::ldap_explode_dn($dn, array('casefold' => 'none', 'reverse' => false, 'onlyvalues' => false));
- if (self::isError($base)) {
- return $base;
- }
- $entry_rdn = array_shift($base);
- if (is_array($entry_rdn)) {
- // maybe the dn consist of a multivalued RDN, we must build the dn in this case
- // because the $entry_rdn is an array!
- $filter_dn = Net_LDAP2_Util::canonical_dn($entry_rdn);
- }
- $base = Net_LDAP2_Util::canonical_dn($base);
+ // search LDAP for that DN by performing a baselevel search for any
+ // object. We can only find the DN in question this way, or nothing.
+ $s_opts = array(
+ 'scope' => 'base',
+ 'sizelimit' => 1,
+ 'attributes' => '1.1' // select no attrs
+ );
+ $search = $this->search($dn, '(objectClass=*)', $s_opts);
- $result = @ldap_list($this->_link, $base, $entry_rdn, array(), 1, 1);
- if (@ldap_count_entries($this->_link, $result)) {
- return true;
- }
- if (ldap_errno($this->_link) == 32) {
- return false;
+ if (self::isError($search)) {
+ return $search;
}
- if (ldap_errno($this->_link) != 0) {
- return PEAR::raiseError(ldap_error($this->_link), ldap_errno($this->_link));
- }
- return false;
+
+ // retun wehter the DN exists; that is, we found an entry
+ return ($search->count() == 0)? false : true;
}
*
* @return string The errorstring for the error.
*/
- public function errorMessage($errorcode)
+ public static function errorMessage($errorcode)
{
$errorMessages = array(
0x00 => "LDAP_SUCCESS",
}
/**
- * Encodes given attributes to UTF8 if needed by schema
+ * Encodes given attributes from ISO-8859-1 to UTF-8 if needed by schema
*
* This function takes attributes in an array and then checks against the schema if they need
* UTF8 encoding. If that is so, they will be encoded. An encoded array will be returned and
}
/**
- * Decodes the given attribute values if needed by schema
+ * Decodes the given attribute values from UTF-8 to ISO-8859-1 if needed by schema
*
* $attributes is expected to be an array with keys describing
* the attribute names and the values as the value of this attribute:
}
/**
- * Encodes or decodes attribute values if needed
+ * Encodes or decodes UTF-8/ISO-8859-1 attribute values if needed by schema
*
* @param array $attributes Array of attributes
* @param array $function Function to apply to attribute values
continue;
}
- if (false !== strpos($attr['syntax'], '1.3.6.1.4.1.1466.115.121.1.15')) {
+ // Encoding is needed if this is a DIR_STR. We assume also
+ // needed encoding in case the schema contains no syntax
+ // information (he does not need to, see rfc2252, 4.2)
+ if (!array_key_exists('syntax', $attr) || false !== strpos($attr['syntax'], '1.3.6.1.4.1.1466.115.121.1.15')) {
$encode = true;
} else {
$encode = false;
* @author Benedikt Hallinger <beni@php.net>
* @copyright 2009 Tarjej Huse, Jan Wagner, Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
-* @version SVN: $Id: Entry.php 286787 2009-08-04 06:03:12Z beni $
+* @version SVN: $Id: Entry.php 332301 2013-12-09 08:17:14Z beni $
* @link http://pear.php.net/package/Net_LDAP2/
*/
* Includes
*/
require_once 'PEAR.php';
-require_once 'Util.php';
+require_once 'Net/LDAP2/Util.php';
/**
* Object representation of a directory entry
public function dn($dn = null)
{
if (false == is_null($dn)) {
- if (is_null($this->_dn)) {
+ if (is_null($this->_dn) ) {
$this->_dn = $dn;
} else {
$this->_newdn = $dn;
* The returned hash has the form
* <code>array('attributename' => 'single value',
* 'attributename' => array('value1', value2', value3'))</code>
+ * Only attributes present at the entry will be returned.
*
* @access public
* @return array Hash of all attributes with their values
*
* The first parameter is the name of the attribute
* The second parameter influences the way the value is returned:
- * 'single': only the first value is returned as string
- * 'all': all values including the value count are returned in an
- * array
+ * 'single': only the first value is returned as string
+ * 'all': all values are returned in an array
* 'default': in all other cases an attribute value with a single value is
* returned as string, if it has multiple values it is returned
- * as an array (without value count)
+ * as an array
*
- * @param string $attr Attribute name
- * @param string $option Option
+ * If the attribute is not set at this entry (no value or not defined in
+ * schema), "false" is returned when $option is 'single', an empty string if
+ * 'default', and an empty array when 'all'.
+ *
+ * You may use Net_LDAP2_Schema->checkAttribute() to see if the attribute
+ * is defined for the objectClasses of this entry.
+ *
+ * @param string $attr Attribute name
+ * @param string $option Option
*
* @access public
- * @return string|array|PEAR_Error string, array or PEAR_Error
+ * @return string|array
*/
public function getValue($attr, $option = null)
{
$attr = $this->getAttrName($attr);
- if (false == array_key_exists($attr, $this->_attributes)) {
- return PEAR::raiseError("Unknown attribute ($attr) requested");
- }
-
- $value = $this->_attributes[$attr];
+ // return depending on set $options
+ if (!array_key_exists($attr, $this->_attributes)) {
+ // attribute not set
+ switch ($option) {
+ case 'single':
+ $value = false;
+ break;
+ case 'all':
+ $value = array();
+ break;
+ default:
+ $value = '';
+ }
- if ($option == "single" || (count($value) == 1 && $option != 'all')) {
- $value = array_shift($value);
+ } else {
+ // attribute present
+ switch ($option) {
+ case 'single':
+ $value = $this->_attributes[$attr][0];
+ break;
+ case 'all':
+ $value = $this->_attributes[$attr];
+ break;
+ default:
+ $value = $this->_attributes[$attr];
+ if (count($value) == 1) {
+ $value = array_shift($value);
+ }
+ }
+
}
return $value;
if (false == is_array($attr)) {
return PEAR::raiseError("Parameter must be an array");
}
+ if ($this->isNew()) {
+ $this->setAttributes($attr);
+ }
foreach ($attr as $k => $v) {
$k = $this->getAttrName($k);
if (false == is_array($v)) {
$this->_attributes[$k] = $v;
}
// save changes for update()
- if (empty($this->_changes["add"][$k])) {
+ if (!isset($this->_changes["add"][$k])) {
$this->_changes["add"][$k] = array();
}
$this->_changes["add"][$k] = array_unique(array_merge($this->_changes["add"][$k], $v));
}
+
$return = true;
return $return;
}
$this->_changes['replace'] = array();
$this->_original = $this->_attributes;
+ // In case the "new" entry was moved after creation, we must
+ // adjust the internal DNs as the entry was already created
+ // with the most current DN.
+ if (false == is_null($this->_newdn)) {
+ $this->_dn = $this->_newdn;
+ $this->_newdn = null;
+ }
+
$return = true;
return $return;
}
$parent = Net_LDAP2_Util::canonical_dn($parent);
// rename/move
- if (false == @ldap_rename($link, $this->_dn, $child, $parent, true)) {
+ if (false == @ldap_rename($link, $this->_dn, $child, $parent, false)) {
+
return PEAR::raiseError("Entry not renamed: " .
@ldap_error($link), @ldap_errno($link));
}
}
/*
- * Carry out modifications to the entry
+ * Retrieve a entry that has all attributes we need so that the list of changes to build is created accurately
*/
+ $fullEntry = $ldap->getEntry( $this->dn() );
+ if ( Net_LDAP2::isError($fullEntry) ) {
+ return PEAR::raiseError("Could not retrieve a full set of attributes to reconcile changes with");
+ }
+ $modifications = array();
+
// ADD
foreach ($this->_changes["add"] as $attr => $value) {
- // if attribute exists, add new values
- if ($this->exists($attr)) {
- if (false === @ldap_mod_add($link, $this->dn(), array($attr => $value))) {
- return PEAR::raiseError("Could not add new values to attribute $attr: " .
- @ldap_error($link), @ldap_errno($link));
- }
- } else {
- // new attribute
- if (false === @ldap_modify($link, $this->dn(), array($attr => $value))) {
- return PEAR::raiseError("Could not add new attribute $attr: " .
- @ldap_error($link), @ldap_errno($link));
- }
- }
- // all went well here, I guess
- unset($this->_changes["add"][$attr]);
+ // if attribute exists, we need to combine old and new values
+ if ($fullEntry->exists($attr)) {
+ $currentValue = $fullEntry->getValue($attr, "all");
+ $value = array_merge( $currentValue, $value );
+ }
+
+ $modifications[$attr] = $value;
}
// DELETE
foreach ($this->_changes["delete"] as $attr => $value) {
// In LDAPv3 you need to specify the old values for deleting
if (is_null($value) && $ldap->getLDAPVersion() === 3) {
- $value = $this->_original[$attr];
+ $value = $fullEntry->getValue($attr);
}
- if (false === @ldap_mod_del($link, $this->dn(), array($attr => $value))) {
- return PEAR::raiseError("Could not delete attribute $attr: " .
- @ldap_error($link), @ldap_errno($link));
+ if (!is_array($value)) {
+ $value = array($value);
}
- unset($this->_changes["delete"][$attr]);
+
+ // Find out what is missing from $value and exclude it
+ $currentValue = isset($modifications[$attr]) ? $modifications[$attr] : $fullEntry->getValue($attr, "all");
+ $modifications[$attr] = array_values( array_diff( $currentValue, $value ) );
}
// REPLACE
foreach ($this->_changes["replace"] as $attr => $value) {
- if (false === @ldap_modify($link, $this->dn(), array($attr => $value))) {
- return PEAR::raiseError("Could not replace attribute $attr values: " .
- @ldap_error($link), @ldap_errno($link));
- }
- unset($this->_changes["replace"][$attr]);
+ $modifications[$attr] = $value;
}
- // all went well, so _original (server) becomes _attributes (local copy)
- $this->_original = $this->_attributes;
+ // COMMIT
+ if (false === @ldap_modify($link, $this->dn(), $modifications)) {
+ return PEAR::raiseError("Could not modify the entry: " . @ldap_error($link), @ldap_errno($link));
+ }
+
+ // all went well, so _original (server) becomes _attributes (local copy), reset _changes too...
+ $this->_changes['add'] = array();
+ $this->_changes['delete'] = array();
+ $this->_changes['replace'] = array();
+ $this->_original = $this->_attributes;
$return = true;
return $return;
// fetch attribute values
$attr = $this->getValue($attr_name, 'all');
- if (Net_LDAP2::isError($attr)) {
- return $attr;
- } else {
- unset($attr['count']);
- }
// perform preg_match() on all values
$match = false;
* @author Benedikt Hallinger <beni@php.net>
* @copyright 2009 Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
-* @version SVN: $Id: Filter.php 289978 2009-10-27 09:56:41Z beni $
+* @version SVN: $Id: Filter.php 332305 2013-12-09 08:51:41Z beni $
* @link http://pear.php.net/package/Net_LDAP2/
*/
* Includes
*/
require_once 'PEAR.php';
-require_once 'Util.php';
+require_once 'Net/LDAP2/Util.php';
+require_once 'Net/LDAP2/Entry.php';
/**
* Object representation of a part of a LDAP filter.
* - lessOrEqual: The attributes value is less or equal than $value
* - approx: One of the attributes values is similar to $value
*
+ * Negation ("not") can be done by prepending the above operators with the
+ * "not" or "!" keyword, see example below.
+ *
* If $escape is set to true (default) then $value will be escaped
* properly. If it is set to false then $value will be treaten as raw filter value string.
* You should escape yourself using {@link Net_LDAP2_Util::escape_filter_value()}!
* Examples:
* <code>
* // This will find entries that contain an attribute "sn" that ends with "foobar":
- * $filter = new Net_LDAP2_Filter('sn', 'ends', 'foobar');
+ * $filter = Net_LDAP2_Filter::create('sn', 'ends', 'foobar');
*
* // This will find entries that contain an attribute "sn" that has any value set:
- * $filter = new Net_LDAP2_Filter('sn', 'any');
+ * $filter = Net_LDAP2_Filter::create('sn', 'any');
+ *
+ * // This will build a negated equals filter:
+ * $filter = Net_LDAP2_Filter::create('sn', 'not equals', 'foobar');
* </code>
*
* @param string $attr_name Name of the attribute the filter should apply to
$array = Net_LDAP2_Util::escape_filter_value(array($value));
$value = $array[0];
}
- switch (strtolower($match)) {
+
+ $match = strtolower($match);
+
+ // detect negation
+ $neg_matches = array();
+ $negate_filter = false;
+ if (preg_match('/^(?:not|!)[\s_-](.+)/', $match, $neg_matches)) {
+ $negate_filter = true;
+ $match = $neg_matches[1];
+ }
+
+ // build basic filter
+ switch ($match) {
case 'equals':
+ case '=':
+ case '==':
$leaf_filter->_filter = '(' . $attr_name . '=' . $value . ')';
break;
case 'begins':
$leaf_filter->_filter = '(' . $attr_name . '=*' . $value . '*)';
break;
case 'greater':
+ case '>':
$leaf_filter->_filter = '(' . $attr_name . '>' . $value . ')';
break;
case 'less':
+ case '<':
$leaf_filter->_filter = '(' . $attr_name . '<' . $value . ')';
break;
case 'greaterorequal':
default:
return PEAR::raiseError('Net_LDAP2_Filter create error: matching rule "' . $match . '" not known!');
}
+
+ // negate if requested
+ if ($negate_filter) {
+ $leaf_filter = Net_LDAP2_Filter::combine('!', $leaf_filter);
+ }
+
return $leaf_filter;
}
*
* This static method combines two or more filter objects and returns one single
* filter object that contains all the others.
- * Call this method statically: $filter = Net_LDAP2_Filter('or', array($filter1, $filter2))
+ * Call this method statically: $filter = Net_LDAP2_Filter::combine('or', array($filter1, $filter2))
* If the array contains filter strings instead of filter objects, we will try to parse them.
*
- * @param string $log_op The locicall operator. May be "and", "or", "not" or the subsequent logical equivalents "&", "|", "!"
+ * @param string $log_op The locical operator. May be "and", "or", "not" or the subsequent logical equivalents "&", "|", "!"
* @param array|Net_LDAP2_Filter $filters array with Net_LDAP2_Filter objects
*
* @return Net_LDAP2_Filter|Net_LDAP2_Error
$filters = array($filter_o);
}
} elseif (is_array($filters)) {
- $err = PEAR::raiseError('Net_LDAP2_Filter combine error: operator is "not" but $filter is an array!');
- return $err;
+ if (count($filters) != 1) {
+ $err = PEAR::raiseError('Net_LDAP2_Filter combine error: operator is "not" but $filter is an array!');
+ return $err;
+ } elseif (!($filters[0] instanceof Net_LDAP2_Filter)) {
+ $err = PEAR::raiseError('Net_LDAP2_Filter combine error: operator is "not" but $filter is not a valid Net_LDAP2_Filter nor a filter string!');
+ return $err;
+ }
} else {
$err = PEAR::raiseError('Net_LDAP2_Filter combine error: operator is "not" but $filter is not a valid Net_LDAP2_Filter nor a filter string!');
return $err;
public static function parse($FILTER)
{
if (preg_match('/^\((.+?)\)$/', $FILTER, $matches)) {
+ // Check for right bracket syntax: count of unescaped opening
+ // brackets must match count of unescaped closing brackets.
+ // At this stage we may have:
+ // 1. one filter component with already removed outer brackets
+ // 2. one or more subfilter components
+ $c_openbracks = preg_match_all('/(?<!\\\\)\(/' , $matches[1], $notrelevant);
+ $c_closebracks = preg_match_all('/(?<!\\\\)\)/' , $matches[1], $notrelevant);
+ if ($c_openbracks != $c_closebracks) {
+ return PEAR::raiseError("Filter parsing error: invalid filter syntax - opening brackets do not match close brackets!");
+ }
+
if (in_array(substr($matches[1], 0, 1), array('!', '|', '&'))) {
// Subfilter processing: pass subfilters to parse() and combine
// the objects using the logical operator detected
if (stristr($matches[1], ')(')) {
return PEAR::raiseError("Filter parsing error: invalid filter syntax - multiple leaf components detected!");
} else {
- $filter_parts = preg_split('/(?<!\\\\)(=|=~|>|<|>=|<=)/', $matches[1], 2, PREG_SPLIT_DELIM_CAPTURE);
+ $filter_parts = Net_LDAP2_Util::split_attribute_string($matches[1], true, true);
if (count($filter_parts) != 3) {
return PEAR::raiseError("Filter parsing error: invalid filter syntax - unknown matching rule used");
} else {
return true; // Leaf!
}
}
+
+ /**
+ * Filter entries using this filter or see if a filter matches
+ *
+ * @todo Currently slow and naive implementation with preg_match, could be optimized (esp. begins, ends filters etc)
+ * @todo Currently only "="-based matches (equals, begins, ends, contains, any) implemented; Implement all the stuff!
+ * @todo Implement expert code with schema checks in case $entry is connected to a directory
+ * @param array|Net_LDAP2_Entry The entry (or array with entries) to check
+ * @param array If given, the array will be appended with entries who matched the filter. Return value is true if any entry matched.
+ * @return int|Net_LDAP2_Error Returns the number of matched entries or error
+ */
+ function matches(&$entries, &$results=array()) {
+ $numOfMatches = 0;
+
+ if (!is_array($entries)) {
+ $all_entries = array(&$entries);
+ } else {
+ $all_entries = &$entries;
+ }
+
+ foreach ($all_entries as $entry) {
+ // look at the current entry and see if filter matches
+
+ $entry_matched = false;
+ // if this is not a single component, do calculate all subfilters,
+ // then assert the partial results with the given combination modifier
+ if (!$this->isLeaf()) {
+
+ // get partial results from subfilters
+ $partial_results = array();
+ foreach ($this->_subfilters as $filter) {
+ $partial_results[] = $filter->matches($entry);
+ }
+
+ // evaluate partial results using this filters combination rule
+ switch ($this->_match) {
+ case '!':
+ // result is the neagtive result of the assertion
+ $entry_matched = !$partial_results[0];
+ break;
+
+ case '&':
+ // all partial results have to be boolean-true
+ $entry_matched = !in_array(false, $partial_results);
+ break;
+
+ case '|':
+ // at least one partial result has to be true
+ $entry_matched = in_array(true, $partial_results);
+ break;
+ }
+
+ } else {
+ // Leaf filter: assert given entry
+ // [TODO]: Could be optimized to avoid preg_match especially with "ends", "begins" etc
+
+ // Translate the LDAP-match to some preg_match expression and evaluate it
+ list($attribute, $match, $assertValue) = $this->getComponents();
+ switch ($match) {
+ case '=':
+ $regexp = '/^'.str_replace('*', '.*', $assertValue).'$/i'; // not case sensitive unless specified by schema
+ $entry_matched = $entry->pregMatch($regexp, $attribute);
+ break;
+
+ // -------------------------------------
+ // [TODO]: implement <, >, <=, >= and =~
+ // -------------------------------------
+
+ default:
+ $err = PEAR::raiseError("Net_LDAP2_Filter match error: unsupported match rule '$match'!");
+ return $err;
+ }
+
+ }
+
+ // process filter matching result
+ if ($entry_matched) {
+ $numOfMatches++;
+ $results[] = $entry;
+ }
+
+ }
+
+ return $numOfMatches;
+ }
+
+
+ /**
+ * Retrieve this leaf-filters attribute, match and value component.
+ *
+ * For leaf filters, this returns array(attr, match, value).
+ * Match is be the logical operator, not the text representation,
+ * eg "=" instead of "equals". Note that some operators are really
+ * a combination of operator+value with wildcard, like
+ * "begins": That will return "=" with the value "value*"!
+ *
+ * For non-leaf filters this will drop an error.
+ *
+ * @todo $this->_match is not always available and thus not usable here; it would be great if it would set in the factory methods and constructor.
+ * @return array|Net_LDAP2_Error
+ */
+ function getComponents() {
+ if ($this->isLeaf()) {
+ $raw_filter = preg_replace('/^\(|\)$/', '', $this->_filter);
+ $parts = Net_LDAP2_Util::split_attribute_string($raw_filter, true, true);
+ if (count($parts) != 3) {
+ return PEAR::raiseError("Net_LDAP2_Filter getComponents() error: invalid filter syntax - unknown matching rule used");
+ } else {
+ return $parts;
+ }
+ } else {
+ return PEAR::raiseError('Net_LDAP2_Filter getComponents() call is invalid for non-leaf filters!');
+ }
+ }
+
+
}
?>
* @author Benedikt Hallinger <beni@php.net>
* @copyright 2009 Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
-* @version SVN: $Id: LDIF.php 286718 2009-08-03 07:30:49Z beni $
+* @version SVN: $Id: LDIF.php 324918 2012-04-06 12:31:04Z clockwerx $
* @link http://pear.php.net/package/Net_LDAP2/
*/
+ count($entry_attrs_changes['replace'])
+ count($entry_attrs_changes['delete']);
+
$is_changed = ($num_of_changes > 0 || $entry->willBeDeleted() || $entry->willBeMoved());
// write version if not done yet
$attributes = array();
$dn = false;
foreach ($lines as $line) {
- if (preg_match('/^(\w+)(:|::|:<)\s(.+)$/', $line, $matches)) {
- $attr =& $matches[1];
- $delim =& $matches[2];
- $data =& $matches[3];
+ if (preg_match('/^(\w+(;binary)?)(:|::|:<)\s(.+)$/', $line, $matches)) {
+ $attr =& $matches[1] . $matches[2];
+ $delim =& $matches[3];
+ $data =& $matches[4];
if ($delim == ':') {
// normal data
if (preg_match('/^version:\s(.+)$/', $data, $match)) {
// version statement, set version
$this->version($match[1]);
- } elseif (preg_match('/^\w+::?\s.+$/', $data)) {
+ } elseif (preg_match('/^\w+(;binary)?::?\s.+$/', $data)) {
// normal attribute: add line
$commentmode = false;
$this->_lines_next[] = trim($data);
$datalines_read++;
} elseif (preg_match('/^\s(.+)$/', $data, $matches)) {
// wrapped data: unwrap if not in comment mode
+ // note that the \s above is some more liberal than
+ // the RFC requests as it also matches tabs etc.
if (!$commentmode) {
if ($datalines_read == 0) {
// first line of entry: wrapped data is illegal
$this->dropError('Net_LDAP2_LDIF error: illegal wrapping at input line '.$this->_input_line, $this->_input_line);
} else {
$last = array_pop($this->_lines_next);
- $last = $last.trim($matches[1]);
+ $last = $last.$matches[1];
$this->_lines_next[] = $last;
$datalines_read++;
}
* @author Benedikt Hallinger <beni@php.net>
* @copyright 2009 Jan Wagner, Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
-* @version SVN: $Id: Schema.php 286718 2009-08-03 07:30:49Z beni $
+* @version SVN: $Id: Schema.php 296515 2010-03-22 14:46:41Z beni $
* @link http://pear.php.net/package/Net_LDAP2/
* @todo see the comment at the end of the file
*/
array('attributes' => array_values($schema_o->types),
'scope' => 'base'));
if (Net_LDAP2::isError($result)) {
- return $result;
+ return PEAR::raiseError('Could not fetch Subschema entry: '.$result->getMessage());
}
$entry = $result->shiftEntry();
if (!$entry instanceof Net_LDAP2_Entry) {
- return PEAR::raiseError('Could not fetch Subschema entry');
+ if ($entry instanceof Net_LDAP2_Error) {
+ return PEAR::raiseError('Could not fetch Subschema entry: '.$entry->getMessage());
+ } else {
+ return PEAR::raiseError('Could not fetch Subschema entry (search returned '.$result->count().' entries. Check parameter \'basedn\')');
+ }
}
$schema_o->parse($entry);
/**
* Return a hash of entries for the given type
*
- * Returns a hash of entry for th givene type. Types may be:
+ * Returns a hash of entry for the givene type. Types may be:
* objectclasses, attributes, ditcontentrules, ditstructurerules, matchingrules,
* matchingruleuses, nameforms, syntaxes
*
return $return;
}
- // [TODO] add method that allows us to see to which objectclasses a certain attribute belongs to
- // it should return the result structured, e.g. sorted in "may" and "must". Optionally it should
- // be able to return it just "flat", e.g. array_merge()d.
- // We could use get_all() to achieve this easily, i think
+ /**
+ * See if an schema element exists
+ *
+ * @param string $type Type of name, see get()
+ * @param string $name Name or OID
+ *
+ * @return boolean
+ */
+ public function exists($type, $name)
+ {
+ $entry = $this->get($type, $name);
+ if ($entry instanceof Net_LDAP2_ERROR) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * See if an attribute is defined in the schema
+ *
+ * @param string $attribute Name or OID of the attribute
+ * @return boolean
+ */
+ public function attributeExists($attribute)
+ {
+ return $this->exists('attribute', $attribute);
+ }
+
+ /**
+ * See if an objectClass is defined in the schema
+ *
+ * @param string $ocl Name or OID of the objectClass
+ * @return boolean
+ */
+ public function objectClassExists($ocl)
+ {
+ return $this->exists('objectclass', $ocl);
+ }
+
+
+ /**
+ * See to which ObjectClasses an attribute is assigned
+ *
+ * The objectclasses are sorted into the keys 'may' and 'must'.
+ *
+ * @param string $attribute Name or OID of the attribute
+ *
+ * @return array|Net_LDAP2_Error Associative array with OCL names or Error
+ */
+ public function getAssignedOCLs($attribute)
+ {
+ $may = array();
+ $must = array();
+
+ // Test if the attribute type is defined in the schema,
+ // if so, retrieve real name for lookups
+ $attr_entry = $this->get('attribute', $attribute);
+ if ($attr_entry instanceof Net_LDAP2_ERROR) {
+ return PEAR::raiseError("Attribute $attribute not defined in schema: ".$attr_entry->getMessage());
+ } else {
+ $attribute = $attr_entry['name'];
+ }
+
+
+ // We need to get all defined OCLs for this.
+ $ocls = $this->getAll('objectclasses');
+ foreach ($ocls as $ocl => $ocl_data) {
+ // Fetch the may and must attrs and see if our searched attr is contained.
+ // If so, record it in the corresponding array.
+ $ocl_may_attrs = $this->may($ocl);
+ $ocl_must_attrs = $this->must($ocl);
+ if (is_array($ocl_may_attrs) && in_array($attribute, $ocl_may_attrs)) {
+ array_push($may, $ocl_data['name']);
+ }
+ if (is_array($ocl_must_attrs) && in_array($attribute, $ocl_must_attrs)) {
+ array_push($must, $ocl_data['name']);
+ }
+ }
+
+ return array('may' => $may, 'must' => $must);
+ }
+
+ /**
+ * See if an attribute is available in a set of objectClasses
+ *
+ * @param string $attribute Attribute name or OID
+ * @param array $ocls Names of OCLs to check for
+ *
+ * @return boolean TRUE, if the attribute is defined for at least one of the OCLs
+ */
+ public function checkAttribute($attribute, $ocls)
+ {
+ foreach ($ocls as $ocl) {
+ $ocl_entry = $this->get('objectclass', $ocl);
+ $ocl_may_attrs = $this->may($ocl);
+ $ocl_must_attrs = $this->must($ocl);
+ if (is_array($ocl_may_attrs) && in_array($attribute, $ocl_may_attrs)) {
+ return true;
+ }
+ if (is_array($ocl_must_attrs) && in_array($attribute, $ocl_must_attrs)) {
+ return true;
+ }
+ }
+ return false; // no ocl for the ocls found.
+ }
}
-?>
+?>
\ No newline at end of file
* @author Benedikt Hallinger <beni@php.net>
* @copyright 2009 Tarjej Huse, Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
-* @version SVN: $Id: Search.php 286718 2009-08-03 07:30:49Z beni $
+* @version SVN: $Id: Search.php 328961 2013-01-03 09:04:30Z beni $
* @link http://pear.php.net/package/Net_LDAP2/
*/
/**
* Cache variable for storing entries fetched internally
*
- * This currently is only used by {@link pop_entry()}
+ * This currently is not used by all functions and need consolidation.
*
* @access protected
* @var array
*/
protected $_entry_cache = false;
+ /**
+ * Cache variable for count()
+ *
+ * @see count()
+ * @access protected
+ * @var int
+ */
+ protected $_count_cache = null;
+
/**
* Constructor
*
}
/**
- * Returns an array of entry objects
+ * Returns an array of entry objects.
*
* @return array Array of entry objects.
*/
{
$entries = array();
- while ($entry = $this->shiftEntry()) {
- $entries[] = $entry;
+ if (false === $this->_entry_cache) {
+ // cache is empty: fetch from LDAP
+ while ($entry = $this->shiftEntry()) {
+ $entries[] = $entry;
+ }
+ $this->_entry_cache = $entries; // store result in cache
}
- return $entries;
+ return $this->_entry_cache;
}
/**
- * Get the next entry in the searchresult.
+ * Get the next entry in the searchresult from LDAP server.
*
* This will return a valid Net_LDAP2_Entry object or false, so
* you can use this method to easily iterate over the entries inside
*/
public function &shiftEntry()
{
- if ($this->count() == 0 ) {
- $false = false;
- return $false;
- }
-
if (is_null($this->_entry)) {
- $this->_entry = @ldap_first_entry($this->_link, $this->_search);
+ if(!$this->_entry = @ldap_first_entry($this->_link, $this->_search)) {
+ $false = false;
+ return $false;
+ }
$entry = Net_LDAP2_Entry::createConnected($this->_ldap, $this->_entry);
- if ($entry instanceof Net_LDAP2_Error) $entry = false;
+ if ($entry instanceof PEAR_Error) $entry = false;
} else {
if (!$this->_entry = @ldap_next_entry($this->_link, $this->_entry)) {
$false = false;
return $false;
}
$entry = Net_LDAP2_Entry::createConnected($this->_ldap, $this->_entry);
- if ($entry instanceof Net_LDAP2_Error) $entry = false;
+ if ($entry instanceof PEAR_Error) $entry = false;
}
return $entry;
}
if (!$this->_search) {
return 0;
}
- return @ldap_count_entries($this->_link, $this->_search);
+ // ldap_count_entries is slow (see pear bug #18752) with large results,
+ // so we cache the result internally.
+ if ($this->_count_cache === null) {
+ $this->_count_cache = @ldap_count_entries($this->_link, $this->_search);
+ }
+
+ return $this->_count_cache;
}
/**
* @author Benedikt Hallinger <beni@php.net>
* @copyright 2009 Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
-* @version SVN: $Id: Util.php 286718 2009-08-03 07:30:49Z beni $
+* @version SVN: $Id: Util.php 332278 2013-12-05 11:01:15Z beni $
* @link http://pear.php.net/package/Net_LDAP2/
*/
}
/**
- * Splits a attribute=value syntax into an array
+ * Splits an attribute=value syntax into an array
*
- * The split will occur at the first unescaped '=' character.
+ * If escaped delimeters are used, they are returned escaped as well.
+ * The split will occur at the first unescaped delimeter character.
+ * In case an invalid delimeter is given, no split will be performed and an
+ * one element array gets returned.
+ * Optional also filter-assertion delimeters can be considered (>, <, >=, <=, ~=).
*
- * @param string $attr Attribute and Value Syntax
+ * @param string $attr Attribute and Value Syntax ("foo=bar")
+ * @param boolean $extended If set to true, also filter-assertion delimeter will be matched
+ * @param boolean $withDelim If set to true, the return array contains the delimeter at index 1, putting the value to index 2
*
- * @return array Indexed array: 0=attribute name, 1=attribute value
+ * @return array Indexed array: 0=attribute name, 1=attribute value OR ($withDelim=true): 0=attr, 1=delimeter, 2=value
*/
- public static function split_attribute_string($attr)
+ public static function split_attribute_string($attr, $extended=false, $withDelim=false)
{
- return preg_split('/(?<!\\\\)=/', $attr, 2);
+ if ($withDelim) $withDelim = PREG_SPLIT_DELIM_CAPTURE;
+
+ if (!$extended) {
+ return preg_split('/(?<!\\\\)(=)/', $attr, 2, $withDelim);
+ } else {
+ return preg_split('/(?<!\\\\)(>=|<=|>|<|~=|=)/', $attr, 2, $withDelim);
+ }
}
/**