Source of file Node.php
Size: 28,141 Bytes - Last Modified: 2014-03-12T23:21:18+01:00
/home/theseer/Downloads/ZendFramework-2.3.0/library/Zend/Ldap/Node.php
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094 | <?php /** * Zend Framework (http://framework.zend.com/) * * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ namespace Zend\Ldap; use Iterator; use RecursiveIterator; use Zend\EventManager\EventManager; /** * Zend\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 * * @var bool */ private $iteratorRewind = false; /** @var EventManager */ protected $events; /** * Constructor. * * Constructor is protected to enforce the use of factory methods. * * @param Dn $dn * @param array $data * @param bool $fromDataSource * @param Ldap $ldap * @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 array('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. * * @param Ldap $ldap * @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 = array()) { if (null === $this->events) { if (class_exists('\Zend\EventManager\EventManager')) { $this->events = new EventManager(__CLASS__); } else { return; } } $this->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 = array(); } $this->children = null; $this->markAsNew(($fromDataSource === true) ? false : true); $this->markAsToBeDeleted(false); } /** * Factory method to create a new detached Zend\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 = array()) { 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, array(), false, null); $new->ensureRdnAttributeValues(); $new->setAttribute('objectClass', $objectClass); return $new; } /** * Factory method to create an attached Zend\Ldap\Node for a given DN. * * @param string|array|Dn $dn * @param Ldap $ldap * @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, array('*', '+'), true); if ($data === null) { return null; } $entry = new static($dn, $data, true, $ldap); return $entry; } /** * Factory method to create a detached Zend\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) ? true : false; $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 = ($new === false) ? false : true; } /** * 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 = ($delete === true) ? true : false; } /** * 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) { return ($this->dn != $this->newDn); } return false; } /** * Sends all pending changes to the LDAP server * * @param Ldap $ldap * @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 Zend\Ldap\Dn. * * This is an offline method. * * @return Dn */ protected function _getDn() { return ($this->newDn === null) ? parent::_getDn() : $this->newDn; } /** * Gets the current DN of the current node as a Zend\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() { $dn = clone parent::_getDn(); return $dn; } /** * 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 = array()) { $attributes = array_merge(array('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 Zend\Ldap\Ldap::add()} and {@link Zend\Ldap\Ldap::update()}. * * This is an offline method. * * @return array */ public function getChangedData() { $changed = array(); 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 = array( 'add' => array(), 'delete' => array(), 'replace' => array()); 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 */ protected function _setAttribute($name, $value, $append) { $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 */ protected function _setDateTimeAttribute($name, $value, $utc, $append) { $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.'); } elseif (array_key_exists($name, $rdn)) { throw new Exception\LdapException(null, 'Cannot change attribute because it\'s part of the RDN'); } elseif (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 $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. * * @param Ldap $ldap * @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. * * @param Ldap $ldap * @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 Node\Collection * @throws Exception\LdapException */ public function searchSubtree($filter, $scope = Ldap::SEARCH_SCOPE_SUB, $sort = null) { return $this->getLdap()->search( $filter, $this->_getDn(), $scope, array('*', '+'), $sort, 'Zend\Ldap\Node\Collection' ); } /** * 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 Node\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 */ 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 */ public function getChildren() { if (!is_array($this->children)) { $this->children = array(); 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. * * @param Ldap $ldap * @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 */ public function current() { return $this; } /** * Return the attribute name. * Implements Iterator * * @return string */ public function key() { return $this->getRdnString(); } /** * Move forward to next attribute. * Implements Iterator */ public function next() { $this->iteratorRewind = false; } /** * Rewind the Iterator to the first attribute. * Implements Iterator */ public function rewind() { $this->iteratorRewind = true; } /** * Check if there is a current attribute * after calls to rewind() or next(). * Implements Iterator * * @return bool */ public function valid() { return $this->iteratorRewind; } } |