> $changes */ class Entry { /** * Entry DN * @var string|false */ protected $dn = false; /** * Attributes data * @var array> */ protected $data = array(); /** * Changed data * @var array{add: array>, replace: array>, delete: array>} */ protected $changes = array( 'add' => array(), 'replace' => array(), 'delete' => array(), ); /** * LDAP connection * @var Ldap|null */ protected $ldap = null; /** * Constructor * @param string|null $dn Object DN (optional, default=null) * @param array> $data Object attributes data (optional, default=null) * @param Ldap|null $ldap The LDAP connection (optional, default=null) * @return void */ public function __construct($dn=null, $data=null, $ldap=null) { if (is_string($dn)) $this -> dn = $dn; if (is_array($data)) foreach($data as $attr => $value) $this -> data[mb_strtolower($attr)] = $data[$attr]; $this -> ldap = $ldap; } /** * Get entry key * @param string $key * @return mixed */ public function __get($key) { switch ($key) { case 'dn': return $this -> dn; case 'changes': return $this -> changes; default: return $this -> get_value($key); } } /** * Set entry key value * @param string $key * @param mixed $value * @return void */ public function __set($key, $value) { switch ($key) { case 'dn': if (is_string($value) || $value === false) $this -> dn = $value; else $this -> error('Unexcepted DN value provided: must be a string or false'); default: $this -> set_value($key, $value); } } /** * Check if entry key is set * @param string $key * @return bool */ public function __isset($key) { if (in_array($key, array('dn', 'changes'))) return true; return $this->attribute_is_set($key); } /** * Check if entry attribute is set * @param string $attr * @return bool */ public function attribute_is_set($attr) { $attr = strtolower($attr); // Attribute defined in object data and not deleted if ( array_key_exists($attr, $this -> data) && ( !array_key_exists($attr, $this -> changes['delete']) || $this -> data[$attr] != $this -> changes['delete'][$attr] ) ) return true; // Note: Do not need to to replace changes, because we only replace value on pre-existing attribute // Attribute have been added if (array_key_exists($attr, $this -> changes['add'])) return true; return false; } /** * Check if the entry could have the specified attribute against the LDAP schema (if loaded) * @param string $attr * @return bool */ public function has_attribute($attr) { if (!$this -> ldap || !$this -> ldap -> schema) return true; $objectclasses = $this -> get_raw_values('objectclass'); if (!$objectclasses || !is_array($objectclasses)) return false; return $this -> ldap -> schema -> has_attribute($attr, $objectclasses); } /** * Get raw attribute values * @param string $attr The expected attribute name * @return array */ protected function _get_raw_values($attr) { $attr = mb_strtolower($attr); if (array_key_exists($attr, $this -> changes['replace'])) return $this -> changes['replace'][$attr]; $values = array_key_exists($attr, $this -> data)?$this -> data[$attr]:array(); if (array_key_exists($attr, $this -> changes['add'])) $values = array_merge($values, $this -> changes['add'][$attr]); if (array_key_exists($attr, $this -> changes['delete'])) { foreach ($this -> changes['delete'][$attr] as $value) if (($key = array_search($value, $values)) !== false) unset($values[$key]); } return $values; } /** * Get raw attribute values * @param string $attr The expected attribute name * @param mixed $default The default value if attribute is not set * (optional, default: null or array() if $all_values is True) * @param bool $all_values If True, return all the attribute values, otherwise return only the * first one (optional, default: true) * @return ( $all_values is True ? array : mixed ) */ public function get_raw_values($attr, $default=null, $all_values=true) { $values = $this -> _get_raw_values($attr); if (!$values) return is_null($default) && $all_values?array():$default; return $all_values?$values:$values[0]; } /** * Get an attribute value (using LDAP schema if used and a LDAP link is registered on entry) * @param string $attr The expected attribute name * @param mixed $default The default value if attribute is not set * (optional, default: null or array() if $all_values is True) * @param bool $first If True, return only the first attribute values, otherwise return * depending on the fact is single valued or not in LDAP schema (optional, * default: false) * @return mixed */ public function get_value($attr, $default=null, $first=false) { if (!$this -> ldap || !$this -> ldap -> schema) return $this -> get_raw_values($attr, $default, $first?false:true); $values = $this -> _get_raw_values($attr); $attribute = $this -> ldap -> schema -> attribute($attr); if (!$attribute) return $this -> error('Unkown attribute %s in schema', null, $attr); if (!$values) { if (is_null($default) && !$first && !$attribute->single) return array(); return $default; } return $attribute -> ldap2php($values); } /** * Set raw LDAP attribute values * @param string $attr * @param mixed $values * @return bool */ public function set_raw_values($attr, $values) { return $this -> replace($attr, $values, true); } /** * Set LDAP attribute values * @param string $attr * @param mixed $value * @return bool */ public function set_value($attr, $value) { return $this -> replace($attr, $value); } /** * Convert attribute PHP value to LDAP values * @param string $attr The attribute name * @param mixed $value * @param bool $one_value Convert one value of the attribute (optional, default: false) * @return ( $one_value is True ? string|null : array|false ) */ public function php2ldap($attr, $value, $one_value=false) { if (!$this -> ldap || !$this -> ldap -> schema) { // No LDAP schema available if ($one_value) return is_null($value)?null:strval($value); if (is_null($value)) return array(); $value = is_array($value)?$value:array($value); $ldap_values = array(); foreach($value as $v) $ldap_values[] = strval($v); return $ldap_values; } // Convert using LDAP schema $attribute = $this -> ldap -> schema -> attribute($attr); if (!$attribute) return $this -> error('Unkown attribute %s in schema', null, $attr); return $attribute -> php2ldap($value, $one_value); } /** * Add an attribute value * @param string $attr The attribute name * @param mixed $value The value to add. If it's an array, consider as multiple value to add * @return bool */ public function add($attr, $value) { if (!$this -> has_attribute($attr)) return $this -> error('%s: This entry could not have the attribute %s', $this, $attr); if (is_array($value)) { foreach($value as $v) if (!$this -> add($attr, $v)) return false; return true; } $attr = mb_strtolower($attr); $value = $this -> php2ldap($attr, $value, true); if (is_null($value)) return $this -> error('%s: Could not add null value the attribute %s', $this, $attr); // If we already do a replace on this attribute, add it in this attribute if (isset($this -> changes['replace'][$attr])) { if (in_array($value, $this -> changes['replace'][$attr])) return true; $this -> changes['replace'][$attr][] = $value; return true; } // If we already have a delete operation on this attribute, check this value was not deleted if (isset($this -> changes['delete'][$attr])) { if (($key = array_search($value, $this -> changes['delete'][$attr])) !== false) { unset($this -> changes['delete'][$attr][$key]); if (empty($this -> changes['delete'][$attr])) unset($this -> changes['delete'][$attr]); } } // Check if this value is not already in the attribute data if (isset($this -> data[$attr]) && in_array($value, $this -> data[$attr])) return true; // Check if this value was not already added if (isset($this -> changes['add'][$attr])) { if (in_array($value, $this -> changes['add'][$attr])) return true; $this -> changes['add'][$attr][] = $value; } else $this -> changes['add'][$attr] = array($value); return true; } /** * Delete an attribute value * @param string $attr The attribute name * @param mixed $value The value to delete. If it's an array, consider as multiple value to delete. * If not provided or null, delete all the attribute name. * @return bool */ public function delete($attr, $value=null) { if (!$this -> has_attribute($attr)) return $this -> error('%s: This entry could not have the attribute %s', $this, $attr); $attr = mb_strtolower($attr); // If null value provided, delete all attribute values if (is_null($value)) { // Remove all previous operations on this attribute if (isset($this -> changes['replace'][$attr])) unset($this -> changes['replace'][$attr]); if (isset($this -> changes['add'][$attr])) unset($this -> changes['add'][$attr]); if (isset($this -> changes['delete'][$attr])) unset($this -> changes['delete'][$attr]); // If attribute originally have data, delete all of it if (isset($this -> data[$attr])) $this -> changes['delete'][$attr] = $this -> data[$attr]; return true; } $values = $this -> php2ldap($attr, $value); if ($values === false) return false; foreach($values as $value) { // If we already do a replace operation on this attribute, delete the value in the replaced value if (isset($this -> changes['replace'][$attr])) { if (($key = array_search($value, $this -> changes['replace'][$attr])) !== false) { // Value detected in replaced value, delete if unset($this -> changes['replace'][$attr][$key]); // Check if we still have replace values if (empty($this -> changes['replace'][$attr])) { // No replaced value, consider the operation as a deletion unset($this -> changes['replace'][$attr]); if (isset($this -> data[$attr])) $this -> changes['delete'][$attr] = $this -> data[$attr]; } } continue; } // If we already have a delete operation on this attribute, check this value was not already deleted if (isset($this -> changes['delete'][$attr]) && in_array($value, $this-> changes['delete'][$attr])) continue; // Check this value is in the attribute data if (!isset($this -> data[$attr]) || !in_array($value, $this -> data[$attr])) continue; if (isset($this -> changes['delete'][$attr])) $this -> changes['delete'][$attr][] = $value; else $this -> changes['delete'][$attr] = array($value); } return true; } /** * Replace an attribute value * @param string $attr The attribute name * @param mixed $value The new attribute value to delete. If null, delete all the attribute name. * @param bool $consider_as_raw_values If true, consider provided value as raw (optional, default: false) * @return bool */ public function replace($attr, $value, $consider_as_raw_values=false) { if (!$this -> has_attribute($attr)) return $this -> error('%s: This entry could not have the attribute %s', $this, $attr); $attr = mb_strtolower($attr); // If raw values provied, just be sure it's an array of string if ($consider_as_raw_values) { $value = Util::ensure_array_of_string($value); } else { // Convert PHP value to LDAP values $value = $this -> php2ldap($attr, $value); } // If empty value, delete the attribute if (empty($value)) return $this -> delete($attr); // Remove all previous add/delete operations on this attribute if (isset($this -> changes['add'][$attr])) unset($this -> changes['add'][$attr]); if (isset($this -> changes['delete'][$attr])) unset($this -> changes['delete'][$attr]); if (isset($this -> changes['replace'][$attr])) unset($this -> changes['replace'][$attr]); if (!isset($this->data[$attr])) $this -> changes['add'][$attr] = $value; elseif ($value != $this->data[$attr]) $this -> changes['replace'][$attr] = $value; return true; } /** * Log and eventually raise an error * @param string $error The error message * @param array $extra_args If passed, will be used to compute the error message using sprintf * @return false * @throws LdapException */ protected function error($error, ...$extra_args) { if ($extra_args) $error = call_user_func_array('sprintf', array_merge(array($error), $extra_args)); // Note: sprintf always return string if ($this->ldap) // @phpstan-ignore-next-line return $this->ldap->error($error); throw new LdapException($error); // @phpstan-ignore-line } /** * Magic method to compute string representation of this schema entry object * @return string */ public function __toString() { if ($this -> dn) return sprintf('', $this->dn); return ''; } }