<?php
/**
 * Zend Framework (http://framework.zend.com/)
 *
 * @link      http://github.com/zendframework/zf2 for the canonical source repository
 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
 * @license   http://framework.zend.com/license/new-bsd New BSD License
 */

namespace Zend\Ldap;

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$fromDataSourceLdap $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(), falsenull);
        
$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($dnLdap $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$datatrue$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$fromDataSourcenull);
        
$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$valuefalse);
            } elseif (!
in_array($value$this->currentData[$key])) {
                
Attribute::setAttribute($this->currentData$key$valuetrue);
            }
        }
    }

    
/**
     * 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 exits()} 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($datatrue);
            
$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$recursivefalse);
            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]) === && !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$valuefalse);
        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$valuetrue);

        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  integer|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$utcfalse);
        return 
$this;
    }

    
/**
     * Appends to a LDAP date/time attribute.
     *
     * This is an offline method.
     *
     * @param  string        $name
     * @param  integer|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$utctrue);

        return 
$this;
    }

    
/**
     * Checks if the attribute can be set and sets it accordingly.
     *
     * @param  string        $name
     * @param  integer|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($nametrue)) {
            
$this->_setAttribute($namenullfalse);
        }

        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  integer                      $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  integer                      $scope
     * @return integer
     * @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 integer
     * @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($filterLdap::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;
    }
}