php-eesyldap/src/Entry.php

432 lines
14 KiB
PHP

<?php
namespace EesyLDAP;
/**
* @property string|false $dn
* @property-read array<string,array<string>> $changes
*/
class Entry {
/**
* Entry DN
* @var string|false
*/
protected $dn = false;
/**
* Attributes data
* @var array<string,array<string>>
*/
protected $data = array();
/**
* Changed data
* @var array{add: array<string,array<int,string>>, replace: array<string,array<int,string>>, delete: array<string,array<int,string>>}
*/
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<string,array<string>> $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<int,string>
*/
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<string> : 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<int,string>|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<mixed> $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('<LDAP Entry "%s">', $this->dn);
return '<New LDAP Entry>';
}
}