%PDF- %PDF-
| Direktori : /home/vacivi36/intranet.vacivitta.com.br/protected/vendor/laminas/laminas-ldap/src/ |
| Current File : /home/vacivi36/intranet.vacivitta.com.br/protected/vendor/laminas/laminas-ldap/src/Node.php |
<?php
namespace Laminas\Ldap;
use ArrayAccess;
use Iterator;
use Laminas\EventManager\EventManager;
use Laminas\Ldap\Node\Collection;
use RecursiveIterator;
use ReturnTypeWillChange;
use function array_key_exists;
use function array_merge;
use function class_exists;
use function count;
use function in_array;
use function is_array;
use function is_string;
use function strtolower;
/**
* Laminas\Ldap\Node provides an object oriented view into a LDAP node.
*/
class Node extends Node\AbstractNode implements Iterator, RecursiveIterator
{
/**
* Holds the node's new Dn if node is renamed.
*
* @var Dn
*/
protected $newDn;
/**
* Holds the node's original attributes (as loaded).
*
* @var array
*/
protected $originalData;
/**
* This node will be added
*
* @var bool
*/
protected $new;
/**
* This node will be deleted
*
* @var bool
*/
protected $delete;
/**
* Holds the connection to the LDAP server if in connected mode.
*
* @var Ldap
*/
protected $ldap;
/**
* Holds an array of the current node's children.
*
* @var Node[]
*/
protected $children;
/**
* Controls iteration status
*/
private bool $iteratorRewind = false;
/** @var EventManager */
protected $events;
/**
* Constructor is protected to enforce the use of factory methods.
*
* @param array $data
* @param bool $fromDataSource
* @throws Exception\LdapException
*/
protected function __construct(Dn $dn, array $data, $fromDataSource, ?Ldap $ldap = null)
{
parent::__construct($dn, $data, $fromDataSource);
if ($ldap !== null) {
$this->attachLdap($ldap);
} else {
$this->detachLdap();
}
}
/**
* Serialization callback
*
* Only Dn and attributes will be serialized.
*
* @return array
*/
public function __sleep()
{
return [
'dn',
'currentData',
'newDn',
'originalData',
'new',
'delete',
'children',
];
}
/**
* Deserialization callback
*
* Enforces a detached node.
*/
public function __wakeup()
{
$this->detachLdap();
}
/**
* Gets the current LDAP connection.
*
* @return Ldap
* @throws Exception\LdapException
*/
public function getLdap()
{
if ($this->ldap === null) {
throw new Exception\LdapException(
null,
'No LDAP connection specified.',
Exception\LdapException::LDAP_OTHER
);
}
return $this->ldap;
}
/**
* Attach node to an LDAP connection
*
* This is an offline method.
*
* @return Node Provides a fluid interface
* @throws Exception\LdapException
*/
public function attachLdap(Ldap $ldap)
{
if (! Dn::isChildOf($this->_getDn(), $ldap->getBaseDn())) {
throw new Exception\LdapException(
null,
'LDAP connection is not responsible for given node.',
Exception\LdapException::LDAP_OTHER
);
}
if ($ldap !== $this->ldap) {
$this->ldap = $ldap;
if (is_array($this->children)) {
foreach ($this->children as $child) {
$child->attachLdap($ldap);
}
}
}
return $this;
}
/**
* Detach node from LDAP connection
*
* This is an offline method.
*
* @return Node Provides a fluid interface
*/
public function detachLdap()
{
$this->ldap = null;
if (is_array($this->children)) {
foreach ($this->children as $child) {
$child->detachLdap();
}
}
return $this;
}
/**
* Checks if the current node is attached to a LDAP server.
*
* This is an offline method.
*
* @return bool
*/
public function isAttached()
{
return $this->ldap !== null;
}
/**
* Trigger an event
*
* @param string $event Event name
* @param array|ArrayAccess $argv Array of arguments; typically, should be associative
*/
protected function triggerEvent($event, $argv = [])
{
$events = $this->getEventManager();
if (! $events) {
return;
}
$events->trigger($event, $this, $argv);
}
/**
* @param array $data
* @param bool $fromDataSource
* @throws Exception\LdapException
*/
protected function loadData(array $data, $fromDataSource)
{
parent::loadData($data, $fromDataSource);
if ($fromDataSource === true) {
$this->originalData = $data;
} else {
$this->originalData = [];
}
$this->children = null;
$this->markAsNew($fromDataSource !== true);
$this->markAsToBeDeleted(false);
}
/**
* Factory method to create a new detached Laminas\Ldap\Node for a given DN.
*
* @param string|array|Dn $dn
* @param array $objectClass
* @return Node
* @throws Exception\LdapException
*/
public static function create($dn, array $objectClass = [])
{
if (is_string($dn) || is_array($dn)) {
$dn = Dn::factory($dn);
} elseif ($dn instanceof Dn) {
$dn = clone $dn;
} else {
throw new Exception\LdapException(null, '$dn is of a wrong data type.');
}
$new = new static($dn, [], false, null);
$new->ensureRdnAttributeValues();
$new->setAttribute('objectClass', $objectClass);
return $new;
}
/**
* Factory method to create an attached Laminas\Ldap\Node for a given DN.
*
* @param string|array|Dn $dn
* @return Node|null
* @throws Exception\LdapException
*/
public static function fromLdap($dn, Ldap $ldap)
{
if (is_string($dn) || is_array($dn)) {
$dn = Dn::factory($dn);
} elseif ($dn instanceof Dn) {
$dn = clone $dn;
} else {
throw new Exception\LdapException(null, '$dn is of a wrong data type.');
}
$data = $ldap->getEntry($dn, ['*', '+'], true);
if ($data === null) {
return;
}
return new static($dn, $data, true, $ldap);
}
/**
* Factory method to create a detached Laminas\Ldap\Node from array data.
*
* @param array $data
* @param bool $fromDataSource
* @return Node
* @throws Exception\LdapException
*/
public static function fromArray(array $data, $fromDataSource = false)
{
if (! array_key_exists('dn', $data)) {
throw new Exception\LdapException(null, '\'dn\' key is missing in array.');
}
if (is_string($data['dn']) || is_array($data['dn'])) {
$dn = Dn::factory($data['dn']);
} elseif ($data['dn'] instanceof Dn) {
$dn = clone $data['dn'];
} else {
throw new Exception\LdapException(null, '\'dn\' key is of a wrong data type.');
}
$fromDataSource = $fromDataSource === true;
$new = new static($dn, $data, $fromDataSource, null);
$new->ensureRdnAttributeValues();
return $new;
}
/**
* Ensures that teh RDN attributes are correctly set.
*
* @param bool $overwrite True to overwrite the RDN attributes
* @return void
*/
protected function ensureRdnAttributeValues($overwrite = false)
{
foreach ($this->getRdnArray() as $key => $value) {
if (! array_key_exists($key, $this->currentData) || $overwrite) {
Attribute::setAttribute($this->currentData, $key, $value, false);
} elseif (! in_array($value, $this->currentData[$key])) {
Attribute::setAttribute($this->currentData, $key, $value, true);
}
}
}
/**
* Marks this node as new.
*
* Node will be added (instead of updated) on calling update() if $new is true.
*
* @param bool $new
*/
protected function markAsNew($new)
{
$this->new = (bool) $new;
}
/**
* Tells if the node is considered as new (not present on the server)
*
* Please note, that this doesn't tell you if the node is present on the server.
* Use {@link exists()} to see if a node is already there.
*
* @return bool
*/
public function isNew()
{
return $this->new;
}
/**
* Marks this node as to be deleted.
*
* Node will be deleted on calling update() if $delete is true.
*
* @param bool $delete
*/
protected function markAsToBeDeleted($delete)
{
$this->delete = (bool) $delete;
}
/**
* Is this node going to be deleted once update() is called?
*
* @return bool
*/
public function willBeDeleted()
{
return $this->delete;
}
/**
* Marks this node as to be deleted
*
* Node will be deleted on calling update() if $delete is true.
*
* @return Node Provides a fluid interface
*/
public function delete()
{
$this->markAsToBeDeleted(true);
return $this;
}
/**
* Is this node going to be moved once update() is called?
*
* @return bool
*/
public function willBeMoved()
{
if ($this->isNew() || $this->willBeDeleted()) {
return false;
} elseif ($this->newDn !== null) {
// non-strict comparison necessary here, as we compare two objects by value
return $this->dn != $this->newDn; // phpcs:ignore
}
return false;
}
/**
* Sends all pending changes to the LDAP server
*
* @return Node Provides a fluid interface
* @throws Exception\LdapException
* @trigger pre-delete
* @trigger post-delete
* @trigger pre-add
* @trigger post-add
* @trigger pre-rename
* @trigger post-rename
* @trigger pre-update
* @trigger post-update
*/
public function update(?Ldap $ldap = null)
{
if ($ldap !== null) {
$this->attachLdap($ldap);
}
$ldap = $this->getLdap();
if (! $ldap instanceof Ldap) {
throw new Exception\LdapException(null, 'No LDAP connection available');
}
if ($this->willBeDeleted()) {
if ($ldap->exists($this->dn)) {
$this->triggerEvent('pre-delete');
$ldap->delete($this->dn);
$this->triggerEvent('post-delete');
}
return $this;
}
if ($this->isNew()) {
$this->triggerEvent('pre-add');
$data = $this->getData();
$ldap->add($this->_getDn(), $data);
$this->loadData($data, true);
$this->triggerEvent('post-add');
return $this;
}
$changedData = $this->getChangedData();
if ($this->willBeMoved()) {
$this->triggerEvent('pre-rename');
$recursive = $this->hasChildren();
$ldap->rename($this->dn, $this->newDn, $recursive, false);
foreach ($this->newDn->getRdn() as $key => $value) {
if (array_key_exists($key, $changedData)) {
unset($changedData[$key]);
}
}
$this->dn = $this->newDn;
$this->newDn = null;
$this->triggerEvent('post-rename');
}
if (count($changedData) > 0) {
$this->triggerEvent('pre-update');
$ldap->update($this->_getDn(), $changedData);
$this->triggerEvent('post-update');
}
$this->originalData = $this->currentData;
return $this;
}
/**
* Gets the DN of the current node as a Laminas\Ldap\Dn.
*
* This is an offline method.
*
* @return Dn
*/
// @codingStandardsIgnoreStart
protected function _getDn()
{
// @codingStandardsIgnoreEnd
return $this->newDn ?? parent::_getDn();
}
/**
* Gets the current DN of the current node as a Laminas\Ldap\Dn.
* The method returns a clone of the node's DN to prohibit modification.
*
* This is an offline method.
*
* @return Dn
*/
public function getCurrentDn()
{
return clone parent::_getDn();
}
/**
* Sets the new DN for this node
*
* This is an offline method.
*
* @param Dn|string|array $newDn
* @throws Exception\LdapException
* @return Node Provides a fluid interface
*/
public function setDn($newDn)
{
if ($newDn instanceof Dn) {
$this->newDn = clone $newDn;
} else {
$this->newDn = Dn::factory($newDn);
}
$this->ensureRdnAttributeValues(true);
return $this;
}
/**
* {@see setDn()}
*
* This is an offline method.
*
* @param Dn|string|array $newDn
* @throws Exception\LdapException
* @return Node Provides a fluid interface
*/
public function move($newDn)
{
return $this->setDn($newDn);
}
/**
* {@see setDn()}
*
* This is an offline method.
*
* @param Dn|string|array $newDn
* @throws Exception\LdapException
* @return Node Provides a fluid interface
*/
public function rename($newDn)
{
return $this->setDn($newDn);
}
/**
* Sets the objectClass.
*
* This is an offline method.
*
* @param array|string $value
* @return Node Provides a fluid interface
* @throws Exception\LdapException
*/
public function setObjectClass($value)
{
$this->setAttribute('objectClass', $value);
return $this;
}
/**
* Appends to the objectClass.
*
* This is an offline method.
*
* @param array|string $value
* @return Node Provides a fluid interface
* @throws Exception\LdapException
*/
public function appendObjectClass($value)
{
$this->appendToAttribute('objectClass', $value);
return $this;
}
/**
* Returns a LDIF representation of the current node
*
* @param array $options Additional options used during encoding
* @return string
*/
public function toLdif(array $options = [])
{
$attributes = array_merge(['dn' => $this->getDnString()], $this->getData(false));
return Ldif\Encoder::encode($attributes, $options);
}
/**
* Gets changed node data.
*
* The array contains all changed attributes.
* This format can be used in {@link Laminas\Ldap\Ldap::add()} and {@link Laminas\Ldap\Ldap::update()}.
*
* This is an offline method.
*
* @return array
*/
public function getChangedData()
{
$changed = [];
foreach ($this->currentData as $key => $value) {
if (! array_key_exists($key, $this->originalData) && ! empty($value)) {
$changed[$key] = $value;
} elseif ($this->originalData[$key] !== $this->currentData[$key]) {
$changed[$key] = $value;
}
}
return $changed;
}
/**
* Returns all changes made.
*
* This is an offline method.
*
* @return array
*/
public function getChanges()
{
$changes = [
'add' => [],
'delete' => [],
'replace' => [],
];
foreach ($this->currentData as $key => $value) {
if (! array_key_exists($key, $this->originalData) && ! empty($value)) {
$changes['add'][$key] = $value;
} elseif (count($this->originalData[$key]) === 0 && ! empty($value)) {
$changes['add'][$key] = $value;
} elseif ($this->originalData[$key] !== $this->currentData[$key]) {
if (empty($value)) {
$changes['delete'][$key] = $value;
} else {
$changes['replace'][$key] = $value;
}
}
}
return $changes;
}
/**
* Sets a LDAP attribute.
*
* This is an offline method.
*
* @param string $name
* @param mixed $value
* @return Node Provides a fluid interface
* @throws Exception\LdapException
*/
public function setAttribute($name, $value)
{
$this->_setAttribute($name, $value, false);
return $this;
}
/**
* Appends to a LDAP attribute.
*
* This is an offline method.
*
* @param string $name
* @param mixed $value
* @return Node Provides a fluid interface
* @throws Exception\LdapException
*/
public function appendToAttribute($name, $value)
{
$this->_setAttribute($name, $value, true);
return $this;
}
/**
* Checks if the attribute can be set and sets it accordingly.
*
* @param string $name
* @param mixed $value
* @param bool $append
* @throws Exception\LdapException
*/
// @codingStandardsIgnoreStart
protected function _setAttribute($name, $value, $append)
{
// @codingStandardsIgnoreEnd
$this->assertChangeableAttribute($name);
Attribute::setAttribute($this->currentData, $name, $value, $append);
}
/**
* Sets a LDAP date/time attribute.
*
* This is an offline method.
*
* @param string $name
* @param int|array $value
* @param bool $utc
* @return Node Provides a fluid interface
* @throws Exception\LdapException
*/
public function setDateTimeAttribute($name, $value, $utc = false)
{
$this->_setDateTimeAttribute($name, $value, $utc, false);
return $this;
}
/**
* Appends to a LDAP date/time attribute.
*
* This is an offline method.
*
* @param string $name
* @param int|array $value
* @param bool $utc
* @return Node Provides a fluid interface
* @throws Exception\LdapException
*/
public function appendToDateTimeAttribute($name, $value, $utc = false)
{
$this->_setDateTimeAttribute($name, $value, $utc, true);
return $this;
}
/**
* Checks if the attribute can be set and sets it accordingly.
*
* @param string $name
* @param int|array $value
* @param bool $utc
* @param bool $append
* @throws Exception\LdapException
*/
// @codingStandardsIgnoreStart
protected function _setDateTimeAttribute($name, $value, $utc, $append)
{
// @codingStandardsIgnoreEnd
$this->assertChangeableAttribute($name);
Attribute::setDateTimeAttribute($this->currentData, $name, $value, $utc, $append);
}
/**
* Sets a LDAP password.
*
* @param string $password
* @param string $hashType
* @param string $attribName
* @return Node Provides a fluid interface
* @throws Exception\LdapException
*/
public function setPasswordAttribute(
$password,
$hashType = Attribute::PASSWORD_HASH_MD5,
$attribName = 'userPassword'
) {
$this->assertChangeableAttribute($attribName);
Attribute::setPassword($this->currentData, $password, $hashType, $attribName);
return $this;
}
/**
* Deletes a LDAP attribute.
*
* This method deletes the attribute.
*
* This is an offline method.
*
* @param string $name
* @return Node Provides a fluid interface
* @throws Exception\LdapException
*/
public function deleteAttribute($name)
{
if ($this->existsAttribute($name, true)) {
$this->_setAttribute($name, null, false);
}
return $this;
}
/**
* Removes duplicate values from a LDAP attribute
*
* @param string $attribName
* @return void
*/
public function removeDuplicatesFromAttribute($attribName)
{
Attribute::removeDuplicatesFromAttribute($this->currentData, $attribName);
}
/**
* Remove given values from a LDAP attribute
*
* @param string $attribName
* @param mixed|array $value
* @return void
*/
public function removeFromAttribute($attribName, $value)
{
Attribute::removeFromAttribute($this->currentData, $attribName, $value);
}
/**
* @param string $name
* @return bool
* @throws Exception\LdapException
*/
protected function assertChangeableAttribute($name)
{
$name = strtolower($name);
$rdn = $this->getRdnArray(Dn::ATTR_CASEFOLD_LOWER);
if ($name === 'dn') {
throw new Exception\LdapException(null, 'DN cannot be changed.');
}
if (array_key_exists($name, $rdn)) {
throw new Exception\LdapException(null, 'Cannot change attribute because it\'s part of the RDN');
}
if (in_array($name, static::$systemAttributes)) {
throw new Exception\LdapException(null, 'Cannot change attribute because it\'s read-only');
}
return true;
}
/**
* Sets a LDAP attribute.
*
* This is an offline method.
*
* @param string $name
* @param mixed $value
*/
public function __set($name, $value)
{
$this->setAttribute($name, $value);
}
/**
* Deletes a LDAP attribute.
*
* This method deletes the attribute.
*
* This is an offline method.
*
* @param string $name
* @throws Exception\LdapException
*/
public function __unset($name)
{
$this->deleteAttribute($name);
}
/**
* Sets a LDAP attribute.
* Implements ArrayAccess.
*
* This is an offline method.
*
* @param string $name
* @param mixed $value
* @throws Exception\LdapException
*/
public function offsetSet($name, $value)
{
$this->setAttribute($name, $value);
}
/**
* Deletes a LDAP attribute.
* Implements ArrayAccess.
*
* This method deletes the attribute.
*
* This is an offline method.
*
* @param string $name
* @throws Exception\LdapException
*/
public function offsetUnset($name)
{
$this->deleteAttribute($name);
}
/**
* Check if node exists on LDAP.
*
* This is an online method.
*
* @return bool
* @throws Exception\LdapException
*/
public function exists(?Ldap $ldap = null)
{
if ($ldap !== null) {
$this->attachLdap($ldap);
}
$ldap = $this->getLdap();
return $ldap->exists($this->_getDn());
}
/**
* Reload node attributes from LDAP.
*
* This is an online method.
*
* @return Node Provides a fluid interface
* @throws Exception\LdapException
*/
public function reload(?Ldap $ldap = null)
{
if ($ldap !== null) {
$this->attachLdap($ldap);
}
$ldap = $this->getLdap();
parent::reload($ldap);
return $this;
}
/**
* Search current subtree with given options.
*
* This is an online method.
*
* @param string|Filter\AbstractFilter $filter
* @param int $scope
* @param string $sort
* @return Collection
* @throws Exception\LdapException
*/
public function searchSubtree($filter, $scope = Ldap::SEARCH_SCOPE_SUB, $sort = null)
{
return $this->getLdap()->search(
$filter,
$this->_getDn(),
$scope,
['*', '+'],
$sort,
Collection::class
);
}
/**
* Count items in current subtree found by given filter.
*
* This is an online method.
*
* @param string|Filter\AbstractFilter $filter
* @param int $scope
* @return int
* @throws Exception\LdapException
*/
public function countSubtree($filter, $scope = Ldap::SEARCH_SCOPE_SUB)
{
return $this->getLdap()->count($filter, $this->_getDn(), $scope);
}
/**
* Count children of current node.
*
* This is an online method.
*
* @return int
* @throws Exception\LdapException
*/
public function countChildren()
{
return $this->countSubtree('(objectClass=*)', Ldap::SEARCH_SCOPE_ONE);
}
/**
* Gets children of current node.
*
* This is an online method.
*
* @param string|Filter\AbstractFilter $filter
* @param string $sort
* @return Collection
* @throws Exception\LdapException
*/
public function searchChildren($filter, $sort = null)
{
return $this->searchSubtree($filter, Ldap::SEARCH_SCOPE_ONE, $sort);
}
/**
* Checks if current node has children.
* Returns whether the current element has children.
*
* Can be used offline but returns false if children have not been retrieved yet.
*
* @return bool
* @throws Exception\LdapException
*/
#[ReturnTypeWillChange]
public function hasChildren()
{
if (! is_array($this->children)) {
if ($this->isAttached()) {
return $this->countChildren() > 0;
}
return false;
}
return count($this->children) > 0;
}
/**
* Returns the children for the current node.
*
* Can be used offline but returns an empty array if children have not been retrieved yet.
*
* @return Node\ChildrenIterator
* @throws Exception\LdapException
*/
#[ReturnTypeWillChange]
public function getChildren()
{
if (! is_array($this->children)) {
$this->children = [];
if ($this->isAttached()) {
$children = $this->searchChildren('(objectClass=*)', null);
foreach ($children as $child) {
$this->children[$child->getRdnString(Dn::ATTR_CASEFOLD_LOWER)] = $child;
}
}
}
return new Node\ChildrenIterator($this->children);
}
/**
* Returns the parent of the current node.
*
* @return Node
* @throws Exception\LdapException
*/
public function getParent(?Ldap $ldap = null)
{
if ($ldap !== null) {
$this->attachLdap($ldap);
}
$ldap = $this->getLdap();
$parentDn = $this->_getDn()->getParentDn(1);
return static::fromLdap($parentDn, $ldap);
}
/**
* Return the current attribute.
* Implements Iterator
*
* @return array
*/
#[ReturnTypeWillChange]
public function current()
{
return $this;
}
/**
* Return the attribute name.
* Implements Iterator
*
* @return string
*/
#[ReturnTypeWillChange]
public function key()
{
return $this->getRdnString();
}
/**
* Move forward to next attribute.
* Implements Iterator
*/
#[ReturnTypeWillChange]
public function next()
{
$this->iteratorRewind = false;
}
/**
* Rewind the Iterator to the first attribute.
* Implements Iterator
*/
#[ReturnTypeWillChange]
public function rewind()
{
$this->iteratorRewind = true;
}
/**
* Check if there is a current attribute
* after calls to rewind() or next().
* Implements Iterator
*
* @return bool
*/
#[ReturnTypeWillChange]
public function valid()
{
return $this->iteratorRewind;
}
/**
* Attempt to marshal an EventManager instance.
*
* If an instance is already available, return it.
*
* If the laminas-eventmanager component is not present, return nothing.
*
* Otherwise, marshal the instance in a version-agnostic way, and return
* it.
*
* @return null|EventManager
*/
private function getEventManager()
{
if ($this->events) {
return $this->events;
}
if (! class_exists(EventManager::class)) {
return;
}
$this->events = new EventManager();
$this->events->setIdentifiers([self::class]);
return $this->events;
}
}