]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - extlib/Net/LDAP2/Entry.php
Merge branch '0.9.x-mobile' into 0.9.x
[quix0rs-gnu-social.git] / extlib / Net / LDAP2 / Entry.php
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4: */
3 /**
4 * File containing the Net_LDAP2_Entry interface class.
5 *
6 * PHP version 5
7 *
8 * @category  Net
9 * @package   Net_LDAP2
10 * @author    Jan Wagner <wagner@netsols.de>
11 * @author    Tarjej Huse <tarjei@bergfald.no>
12 * @author    Benedikt Hallinger <beni@php.net>
13 * @copyright 2009 Tarjej Huse, Jan Wagner, Benedikt Hallinger
14 * @license   http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
15 * @version   SVN: $Id: Entry.php 286787 2009-08-04 06:03:12Z beni $
16 * @link      http://pear.php.net/package/Net_LDAP2/
17 */
18
19 /**
20 * Includes
21 */
22 require_once 'PEAR.php';
23 require_once 'Util.php';
24
25 /**
26 * Object representation of a directory entry
27 *
28 * This class represents a directory entry. You can add, delete, replace
29 * attributes and their values, rename the entry, delete the entry.
30 *
31 * @category Net
32 * @package  Net_LDAP2
33 * @author   Jan Wagner <wagner@netsols.de>
34 * @author   Tarjej Huse <tarjei@bergfald.no>
35 * @author   Benedikt Hallinger <beni@php.net>
36 * @license  http://www.gnu.org/copyleft/lesser.html LGPL
37 * @link     http://pear.php.net/package/Net_LDAP2/
38 */
39 class Net_LDAP2_Entry extends PEAR
40 {
41     /**
42     * Entry ressource identifier
43     *
44     * @access protected
45     * @var ressource
46     */
47     protected $_entry = null;
48
49     /**
50     * LDAP ressource identifier
51     *
52     * @access protected
53     * @var ressource
54     */
55     protected $_link = null;
56
57     /**
58     * Net_LDAP2 object
59     *
60     * This object will be used for updating and schema checking
61     *
62     * @access protected
63     * @var object Net_LDAP2
64     */
65     protected $_ldap = null;
66
67     /**
68     * Distinguished name of the entry
69     *
70     * @access protected
71     * @var string
72     */
73     protected $_dn = null;
74
75     /**
76     * Attributes
77     *
78     * @access protected
79     * @var array
80     */
81     protected $_attributes = array();
82
83     /**
84     * Original attributes before any modification
85     *
86     * @access protected
87     * @var array
88     */
89     protected $_original = array();
90
91
92     /**
93     * Map of attribute names
94     *
95     * @access protected
96     * @var array
97     */
98     protected $_map = array();
99
100
101     /**
102     * Is this a new entry?
103     *
104     * @access protected
105     * @var boolean
106     */
107     protected $_new = true;
108
109     /**
110     * New distinguished name
111     *
112     * @access protected
113     * @var string
114     */
115     protected $_newdn = null;
116
117     /**
118     * Shall the entry be deleted?
119     *
120     * @access protected
121     * @var boolean
122     */
123     protected $_delete = false;
124
125     /**
126     * Map with changes to the entry
127     *
128     * @access protected
129     * @var array
130     */
131     protected $_changes = array("add"     => array(),
132                                 "delete"  => array(),
133                                 "replace" => array()
134                                );
135     /**
136     * Internal Constructor
137     *
138     * Constructor of the entry. Sets up the distinguished name and the entries
139     * attributes.
140     * You should not call this method manually! Use {@link Net_LDAP2_Entry::createFresh()}
141     * or {@link Net_LDAP2_Entry::createConnected()} instead!
142     *
143     * @param Net_LDAP2|ressource|array &$ldap Net_LDAP2 object, ldap-link ressource or array of attributes
144     * @param string|ressource          $entry Either a DN or a LDAP-Entry ressource
145     *
146     * @access protected
147     * @return none
148     */
149     protected function __construct(&$ldap, $entry = null)
150     {
151         $this->PEAR('Net_LDAP2_Error');
152
153         // set up entry resource or DN
154         if (is_resource($entry)) {
155             $this->_entry = &$entry;
156         } else {
157             $this->_dn = $entry;
158         }
159
160         // set up LDAP link
161         if ($ldap instanceof Net_LDAP2) {
162             $this->_ldap = &$ldap;
163             $this->_link = $ldap->getLink();
164         } elseif (is_resource($ldap)) {
165             $this->_link = $ldap;
166         } elseif (is_array($ldap)) {
167             // Special case: here $ldap is an array of attributes,
168             // this means, we have no link. This is a "virtual" entry.
169             // We just set up the attributes so one can work with the object
170             // as expected, but an update() fails unless setLDAP() is called.
171             $this->setAttributes($ldap);
172         }
173
174         // if this is an entry existing in the directory,
175         // then set up as old and fetch attrs
176         if (is_resource($this->_entry) && is_resource($this->_link)) {
177             $this->_new = false;
178             $this->_dn  = @ldap_get_dn($this->_link, $this->_entry);
179             $this->setAttributes();  // fetch attributes from server
180         }
181     }
182
183     /**
184     * Creates a fresh entry that may be added to the directory later on
185     *
186     * Use this method, if you want to initialize a fresh entry.
187     *
188     * The method should be called statically: $entry = Net_LDAP2_Entry::createFresh();
189     * You should put a 'objectClass' attribute into the $attrs so the directory server
190     * knows which object you want to create. However, you may omit this in case you
191     * don't want to add this entry to a directory server.
192     *
193     * The attributes parameter is as following:
194     * <code>
195     * $attrs = array( 'attribute1' => array('value1', 'value2'),
196     *                 'attribute2' => 'single value'
197     *          );
198     * </code>
199     *
200     * @param string $dn    DN of the Entry
201     * @param array  $attrs Attributes of the entry
202     *
203     * @static
204     * @return Net_LDAP2_Entry|Net_LDAP2_Error
205     */
206     public static function createFresh($dn, $attrs = array())
207     {
208         if (!is_array($attrs)) {
209             return PEAR::raiseError("Unable to create fresh entry: Parameter \$attrs needs to be an array!");
210         }
211
212         $entry = new Net_LDAP2_Entry($attrs, $dn);
213         return $entry;
214     }
215
216     /**
217     * Creates a Net_LDAP2_Entry object out of an ldap entry resource
218     *
219     * Use this method, if you want to initialize an entry object that is
220     * already present in some directory and that you have read manually.
221     *
222     * Please note, that if you want to create an entry object that represents
223     * some already existing entry, you should use {@link createExisting()}.
224     *
225     * The method should be called statically: $entry = Net_LDAP2_Entry::createConnected();
226     *
227     * @param Net_LDAP2 $ldap  Net_LDA2 object
228     * @param resource  $entry PHP LDAP entry resource
229     *
230     * @static
231     * @return Net_LDAP2_Entry|Net_LDAP2_Error
232     */
233     public static function createConnected($ldap, $entry)
234     {
235         if (!$ldap instanceof Net_LDAP2) {
236             return PEAR::raiseError("Unable to create connected entry: Parameter \$ldap needs to be a Net_LDAP2 object!");
237         }
238         if (!is_resource($entry)) {
239             return PEAR::raiseError("Unable to create connected entry: Parameter \$entry needs to be a ldap entry resource!");
240         }
241
242         $entry = new Net_LDAP2_Entry($ldap, $entry);
243         return $entry;
244     }
245
246     /**
247     * Creates an Net_LDAP2_Entry object that is considered already existing
248     *
249     * Use this method, if you want to modify an already existing entry
250     * without fetching it first.
251     * In most cases however, it is better to fetch the entry via Net_LDAP2->getEntry()!
252     *
253     * Please note that you should take care if you construct entries manually with this
254     * because you may get weird synchronisation problems.
255     * The attributes and values as well as the entry itself are considered existent
256     * which may produce errors if you try to modify an entry which doesn't really exist
257     * or if you try to overwrite some attribute with an value already present.
258     *
259     * This method is equal to calling createFresh() and after that markAsNew(FALSE).
260     *
261     * The method should be called statically: $entry = Net_LDAP2_Entry::createExisting();
262     *
263     * The attributes parameter is as following:
264     * <code>
265     * $attrs = array( 'attribute1' => array('value1', 'value2'),
266     *                 'attribute2' => 'single value'
267     *          );
268     * </code>
269     *
270     * @param string $dn    DN of the Entry
271     * @param array  $attrs Attributes of the entry
272     *
273     * @static
274     * @return Net_LDAP2_Entry|Net_LDAP2_Error
275     */
276     public static function createExisting($dn, $attrs = array())
277     {
278         if (!is_array($attrs)) {
279             return PEAR::raiseError("Unable to create entry object: Parameter \$attrs needs to be an array!");
280         }
281
282         $entry = Net_LDAP2_Entry::createFresh($dn, $attrs);
283         if ($entry instanceof Net_LDAP2_Error) {
284             return $entry;
285         } else {
286             $entry->markAsNew(false);
287             return $entry;
288         }
289     }
290
291     /**
292     * Get or set the distinguished name of the entry
293     *
294     * If called without an argument the current (or the new DN if set) DN gets returned.
295     * If you provide an DN, this entry is moved to the new location specified if a DN existed.
296     * If the DN was not set, the DN gets initialized. Call {@link update()} to actually create
297     * the new Entry in the directory.
298     * To fetch the current active DN after setting a new DN but before an update(), you can use
299     * {@link currentDN()} to retrieve the DN that is currently active.
300     *
301     * Please note that special characters (eg german umlauts) should be encoded using utf8_encode().
302     * You may use {@link Net_LDAP2_Util::canonical_dn()} for properly encoding of the DN.
303     *
304     * @param string $dn New distinguished name
305     *
306     * @access public
307     * @return string|true Distinguished name (or true if a new DN was provided)
308     */
309     public function dn($dn = null)
310     {
311         if (false == is_null($dn)) {
312             if (is_null($this->_dn)) {
313                 $this->_dn = $dn;
314             } else {
315                 $this->_newdn = $dn;
316             }
317             return true;
318         }
319         return (isset($this->_newdn) ? $this->_newdn : $this->currentDN());
320     }
321
322     /**
323     * Renames or moves the entry
324     *
325     * This is just a convinience alias to {@link dn()}
326     * to make your code more meaningful.
327     *
328     * @param string $newdn The new DN
329     *
330     * @return true
331     */
332     public function move($newdn)
333     {
334         return $this->dn($newdn);
335     }
336
337     /**
338     * Sets the internal attributes array
339     *
340     * This fetches the values for the attributes from the server.
341     * The attribute Syntax will be checked so binary attributes will be returned
342     * as binary values.
343     *
344     * Attributes may be passed directly via the $attributes parameter to setup this
345     * entry manually. This overrides attribute fetching from the server.
346     *
347     * @param array $attributes Attributes to set for this entry
348     *
349     * @access protected
350     * @return void
351     */
352     protected function setAttributes($attributes = null)
353     {
354         /*
355         * fetch attributes from the server
356         */
357         if (is_null($attributes) && is_resource($this->_entry) && is_resource($this->_link)) {
358             // fetch schema
359             if ($this->_ldap instanceof Net_LDAP2) {
360                 $schema =& $this->_ldap->schema();
361             }
362             // fetch attributes
363             $attributes = array();
364             do {
365                 if (empty($attr)) {
366                     $ber  = null;
367                     $attr = @ldap_first_attribute($this->_link, $this->_entry, $ber);
368                 } else {
369                     $attr = @ldap_next_attribute($this->_link, $this->_entry, $ber);
370                 }
371                 if ($attr) {
372                     $func = 'ldap_get_values'; // standard function to fetch value
373
374                     // Try to get binary values as binary data
375                     if ($schema instanceof Net_LDAP2_Schema) {
376                         if ($schema->isBinary($attr)) {
377                              $func = 'ldap_get_values_len';
378                         }
379                     }
380                     // fetch attribute value (needs error checking?)
381                     $attributes[$attr] = $func($this->_link, $this->_entry, $attr);
382                 }
383             } while ($attr);
384         }
385
386         /*
387         * set attribute data directly, if passed
388         */
389         if (is_array($attributes) && count($attributes) > 0) {
390             if (isset($attributes["count"]) && is_numeric($attributes["count"])) {
391                 unset($attributes["count"]);
392             }
393             foreach ($attributes as $k => $v) {
394                 // attribute names should not be numeric
395                 if (is_numeric($k)) {
396                     continue;
397                 }
398                 // map generic attribute name to real one
399                 $this->_map[strtolower($k)] = $k;
400                 // attribute values should be in an array
401                 if (false == is_array($v)) {
402                     $v = array($v);
403                 }
404                 // remove the value count (comes from ldap server)
405                 if (isset($v["count"])) {
406                     unset($v["count"]);
407                 }
408                 $this->_attributes[$k] = $v;
409             }
410         }
411
412         // save a copy for later use
413         $this->_original = $this->_attributes;
414     }
415
416     /**
417     * Get the values of all attributes in a hash
418     *
419     * The returned hash has the form
420     * <code>array('attributename' => 'single value',
421     *       'attributename' => array('value1', value2', value3'))</code>
422     *
423     * @access public
424     * @return array Hash of all attributes with their values
425     */
426     public function getValues()
427     {
428         $attrs = array();
429         foreach ($this->_attributes as $attr => $value) {
430             $attrs[$attr] = $this->getValue($attr);
431         }
432         return $attrs;
433     }
434
435     /**
436     * Get the value of a specific attribute
437     *
438     * The first parameter is the name of the attribute
439     * The second parameter influences the way the value is returned:
440     * 'single': only the first value is returned as string
441     * 'all': all values including the value count are returned in an
442     *               array
443     * 'default': in all other cases an attribute value with a single value is
444     *            returned as string, if it has multiple values it is returned
445     *            as an array (without value count)
446     *
447     * @param string $attr   Attribute name
448     * @param string $option Option
449     *
450     * @access public
451     * @return string|array|PEAR_Error string, array or PEAR_Error
452     */
453     public function getValue($attr, $option = null)
454     {
455         $attr = $this->getAttrName($attr);
456
457         if (false == array_key_exists($attr, $this->_attributes)) {
458             return PEAR::raiseError("Unknown attribute ($attr) requested");
459         }
460
461         $value = $this->_attributes[$attr];
462
463         if ($option == "single" || (count($value) == 1 && $option != 'all')) {
464             $value = array_shift($value);
465         }
466
467         return $value;
468     }
469
470     /**
471     * Alias function of getValue for perl-ldap interface
472     *
473     * @see getValue()
474     * @return string|array|PEAR_Error
475     */
476     public function get_value()
477     {
478         $args = func_get_args();
479         return call_user_func_array(array( &$this, 'getValue' ), $args);
480     }
481
482     /**
483     * Returns an array of attributes names
484     *
485     * @access public
486     * @return array Array of attribute names
487     */
488     public function attributes()
489     {
490         return array_keys($this->_attributes);
491     }
492
493     /**
494     * Returns whether an attribute exists or not
495     *
496     * @param string $attr Attribute name
497     *
498     * @access public
499     * @return boolean
500     */
501     public function exists($attr)
502     {
503         $attr = $this->getAttrName($attr);
504         return array_key_exists($attr, $this->_attributes);
505     }
506
507     /**
508     * Adds a new attribute or a new value to an existing attribute
509     *
510     * The paramter has to be an array of the form:
511     * array('attributename' => 'single value',
512     *       'attributename' => array('value1', 'value2))
513     * When the attribute already exists the values will be added, else the
514     * attribute will be created. These changes are local to the entry and do
515     * not affect the entry on the server until update() is called.
516     *
517     * Note, that you can add values of attributes that you haven't selected, but if
518     * you do so, {@link getValue()} and {@link getValues()} will only return the
519     * values you added, _NOT_ all values present on the server. To avoid this, just refetch
520     * the entry after calling {@link update()} or select the attribute.
521     *
522     * @param array $attr Attributes to add
523     *
524     * @access public
525     * @return true|Net_LDAP2_Error
526     */
527     public function add($attr = array())
528     {
529         if (false == is_array($attr)) {
530             return PEAR::raiseError("Parameter must be an array");
531         }
532         foreach ($attr as $k => $v) {
533             $k = $this->getAttrName($k);
534             if (false == is_array($v)) {
535                 // Do not add empty values
536                 if ($v == null) {
537                     continue;
538                 } else {
539                     $v = array($v);
540                 }
541             }
542             // add new values to existing attribute or add new attribute
543             if ($this->exists($k)) {
544                 $this->_attributes[$k] = array_unique(array_merge($this->_attributes[$k], $v));
545             } else {
546                 $this->_map[strtolower($k)] = $k;
547                 $this->_attributes[$k]      = $v;
548             }
549             // save changes for update()
550             if (empty($this->_changes["add"][$k])) {
551                 $this->_changes["add"][$k] = array();
552             }
553             $this->_changes["add"][$k] = array_unique(array_merge($this->_changes["add"][$k], $v));
554         }
555         $return = true;
556         return $return;
557     }
558
559     /**
560     * Deletes an whole attribute or a value or the whole entry
561     *
562     * The parameter can be one of the following:
563     *
564     * "attributename" - The attribute as a whole will be deleted
565     * array("attributename1", "attributename2) - All given attributes will be
566     *                                            deleted
567     * array("attributename" => "value") - The value will be deleted
568     * array("attributename" => array("value1", "value2") - The given values
569     *                                                      will be deleted
570     * If $attr is null or omitted , then the whole Entry will be deleted!
571     *
572     * These changes are local to the entry and do
573     * not affect the entry on the server until {@link update()} is called.
574     *
575     * Please note that you must select the attribute (at $ldap->search() for example)
576     * to be able to delete values of it, Otherwise {@link update()} will silently fail
577     * and remove nothing.
578     *
579     * @param string|array $attr Attributes to delete (NULL or missing to delete whole entry)
580     *
581     * @access public
582     * @return true
583     */
584     public function delete($attr = null)
585     {
586         if (is_null($attr)) {
587             $this->_delete = true;
588             return true;
589         }
590         if (is_string($attr)) {
591             $attr = array($attr);
592         }
593         // Make the assumption that attribute names cannot be numeric,
594         // therefore this has to be a simple list of attribute names to delete
595         if (is_numeric(key($attr))) {
596             foreach ($attr as $name) {
597                 if (is_array($name)) {
598                     // someone mixed modes (list mode but specific values given!)
599                     $del_attr_name = array_search($name, $attr);
600                     $this->delete(array($del_attr_name => $name));
601                 } else {
602                     // mark for update() if this attr was not marked before
603                     $name = $this->getAttrName($name);
604                     if ($this->exists($name)) {
605                         $this->_changes["delete"][$name] = null;
606                         unset($this->_attributes[$name]);
607                     }
608                 }
609             }
610         } else {
611             // Here we have a hash with "attributename" => "value to delete"
612             foreach ($attr as $name => $values) {
613                 if (is_int($name)) {
614                     // someone mixed modes and gave us just an attribute name
615                     $this->delete($values);
616                 } else {
617                     // mark for update() if this attr was not marked before;
618                     // this time it must consider the selected values also
619                     $name = $this->getAttrName($name);
620                     if ($this->exists($name)) {
621                         if (false == is_array($values)) {
622                             $values = array($values);
623                         }
624                         // save values to be deleted
625                         if (empty($this->_changes["delete"][$name])) {
626                             $this->_changes["delete"][$name] = array();
627                         }
628                         $this->_changes["delete"][$name] =
629                             array_unique(array_merge($this->_changes["delete"][$name], $values));
630                         foreach ($values as $value) {
631                             // find the key for the value that should be deleted
632                             $key = array_search($value, $this->_attributes[$name]);
633                             if (false !== $key) {
634                                 // delete the value
635                                 unset($this->_attributes[$name][$key]);
636                             }
637                         }
638                     }
639                 }
640             }
641         }
642         $return = true;
643         return $return;
644     }
645
646     /**
647     * Replaces attributes or its values
648     *
649     * The parameter has to an array of the following form:
650     * array("attributename" => "single value",
651     *       "attribute2name" => array("value1", "value2"),
652     *       "deleteme1" => null,
653     *       "deleteme2" => "")
654     * If the attribute does not yet exist it will be added instead (see also $force).
655     * If the attribue value is null, the attribute will de deleted.
656     *
657     * These changes are local to the entry and do
658     * not affect the entry on the server until {@link update()} is called.
659     *
660     * In some cases you are not allowed to read the attributes value (for
661     * example the ActiveDirectory attribute unicodePwd) but are allowed to
662     * replace the value. In this case replace() would assume that the attribute
663     * is not in the directory yet and tries to add it which will result in an
664     * LDAP_TYPE_OR_VALUE_EXISTS error.
665     * To force replace mode instead of add, you can set $force to true.
666     *
667     * @param array $attr  Attributes to replace
668     * @param bool  $force Force replacing mode in case we can't read the attr value but are allowed to replace it
669     *
670     * @access public
671     * @return true|Net_LDAP2_Error
672     */
673     public function replace($attr = array(), $force = false)
674     {
675         if (false == is_array($attr)) {
676             return PEAR::raiseError("Parameter must be an array");
677         }
678         foreach ($attr as $k => $v) {
679             $k = $this->getAttrName($k);
680             if (false == is_array($v)) {
681                 // delete attributes with empty values; treat ints as string
682                 if (is_int($v)) {
683                     $v = "$v";
684                 }
685                 if ($v == null) {
686                     $this->delete($k);
687                     continue;
688                 } else {
689                     $v = array($v);
690                 }
691             }
692             // existing attributes will get replaced
693             if ($this->exists($k) || $force) {
694                 $this->_changes["replace"][$k] = $v;
695                 $this->_attributes[$k]         = $v;
696             } else {
697                 // new ones just get added
698                 $this->add(array($k => $v));
699             }
700         }
701         $return = true;
702         return $return;
703     }
704
705     /**
706     * Update the entry on the directory server
707     *
708     * This will evaluate all changes made so far and send them
709     * to the directory server.
710     * Please note, that if you make changes to objectclasses wich
711     * have mandatory attributes set, update() will currently fail.
712     * Remove the entry from the server and readd it as new in such cases.
713     * This also will deal with problems with setting structural object classes.
714     *
715     * @param Net_LDAP2 $ldap If passed, a call to setLDAP() is issued prior update, thus switching the LDAP-server. This is for perl-ldap interface compliance
716     *
717     * @access public
718     * @return true|Net_LDAP2_Error
719     * @todo Entry rename with a DN containing special characters needs testing!
720     */
721     public function update($ldap = null)
722     {
723         if ($ldap) {
724             $msg = $this->setLDAP($ldap);
725             if (Net_LDAP2::isError($msg)) {
726                 return PEAR::raiseError('You passed an invalid $ldap variable to update()');
727             }
728         }
729
730         // ensure we have a valid LDAP object
731         $ldap =& $this->getLDAP();
732         if (!$ldap instanceof Net_LDAP2) {
733             return PEAR::raiseError("The entries LDAP object is not valid");
734         }
735
736         // Get and check link
737         $link = $ldap->getLink();
738         if (!is_resource($link)) {
739             return PEAR::raiseError("Could not update entry: internal LDAP link is invalid");
740         }
741
742         /*
743         * Delete the entry
744         */
745         if (true === $this->_delete) {
746             return $ldap->delete($this);
747         }
748
749         /*
750         * New entry
751         */
752         if (true === $this->_new) {
753             $msg = $ldap->add($this);
754             if (Net_LDAP2::isError($msg)) {
755                 return $msg;
756             }
757             $this->_new                = false;
758             $this->_changes['add']     = array();
759             $this->_changes['delete']  = array();
760             $this->_changes['replace'] = array();
761             $this->_original           = $this->_attributes;
762
763             $return = true;
764             return $return;
765         }
766
767         /*
768         * Rename/move entry
769         */
770         if (false == is_null($this->_newdn)) {
771             if ($ldap->getLDAPVersion() !== 3) {
772                 return PEAR::raiseError("Renaming/Moving an entry is only supported in LDAPv3");
773             }
774             // make dn relative to parent (needed for ldap rename)
775             $parent = Net_LDAP2_Util::ldap_explode_dn($this->_newdn, array('casefolding' => 'none', 'reverse' => false, 'onlyvalues' => false));
776             if (Net_LDAP2::isError($parent)) {
777                 return $parent;
778             }
779             $child = array_shift($parent);
780             // maybe the dn consist of a multivalued RDN, we must build the dn in this case
781             // because the $child-RDN is an array!
782             if (is_array($child)) {
783                 $child = Net_LDAP2_Util::canonical_dn($child);
784             }
785             $parent = Net_LDAP2_Util::canonical_dn($parent);
786
787             // rename/move
788             if (false == @ldap_rename($link, $this->_dn, $child, $parent, true)) {
789                 return PEAR::raiseError("Entry not renamed: " .
790                                         @ldap_error($link), @ldap_errno($link));
791             }
792             // reflect changes to local copy
793             $this->_dn    = $this->_newdn;
794             $this->_newdn = null;
795         }
796
797         /*
798         * Carry out modifications to the entry
799         */
800         // ADD
801         foreach ($this->_changes["add"] as $attr => $value) {
802             // if attribute exists, add new values
803             if ($this->exists($attr)) {
804                 if (false === @ldap_mod_add($link, $this->dn(), array($attr => $value))) {
805                     return PEAR::raiseError("Could not add new values to attribute $attr: " .
806                                             @ldap_error($link), @ldap_errno($link));
807                 }
808             } else {
809                 // new attribute
810                 if (false === @ldap_modify($link, $this->dn(), array($attr => $value))) {
811                     return PEAR::raiseError("Could not add new attribute $attr: " .
812                                             @ldap_error($link), @ldap_errno($link));
813                 }
814             }
815             // all went well here, I guess
816             unset($this->_changes["add"][$attr]);
817         }
818
819         // DELETE
820         foreach ($this->_changes["delete"] as $attr => $value) {
821             // In LDAPv3 you need to specify the old values for deleting
822             if (is_null($value) && $ldap->getLDAPVersion() === 3) {
823                 $value = $this->_original[$attr];
824             }
825             if (false === @ldap_mod_del($link, $this->dn(), array($attr => $value))) {
826                 return PEAR::raiseError("Could not delete attribute $attr: " .
827                                         @ldap_error($link), @ldap_errno($link));
828             }
829             unset($this->_changes["delete"][$attr]);
830         }
831
832         // REPLACE
833         foreach ($this->_changes["replace"] as $attr => $value) {
834             if (false === @ldap_modify($link, $this->dn(), array($attr => $value))) {
835                 return PEAR::raiseError("Could not replace attribute $attr values: " .
836                                         @ldap_error($link), @ldap_errno($link));
837             }
838             unset($this->_changes["replace"][$attr]);
839         }
840
841         // all went well, so _original (server) becomes _attributes (local copy)
842         $this->_original = $this->_attributes;
843
844         $return = true;
845         return $return;
846     }
847
848     /**
849     * Returns the right attribute name
850     *
851     * @param string $attr Name of attribute
852     *
853     * @access protected
854     * @return string The right name of the attribute
855     */
856     protected function getAttrName($attr)
857     {
858         $name = strtolower($attr);
859         if (array_key_exists($name, $this->_map)) {
860             $attr = $this->_map[$name];
861         }
862         return $attr;
863     }
864
865     /**
866     * Returns a reference to the LDAP-Object of this entry
867     *
868     * @access public
869     * @return Net_LDAP2|Net_LDAP2_Error   Reference to the Net_LDAP2 Object (the connection) or Net_LDAP2_Error
870     */
871     public function &getLDAP()
872     {
873         if (!$this->_ldap instanceof Net_LDAP2) {
874             $err = new PEAR_Error('LDAP is not a valid Net_LDAP2 object');
875             return $err;
876         } else {
877             return $this->_ldap;
878         }
879     }
880
881     /**
882     * Sets a reference to the LDAP-Object of this entry
883     *
884     * After setting a Net_LDAP2 object, calling update() will use that object for
885     * updating directory contents. Use this to dynamicly switch directorys.
886     *
887     * @param Net_LDAP2 &$ldap Net_LDAP2 object that this entry should be connected to
888     *
889     * @access public
890     * @return true|Net_LDAP2_Error
891     */
892     public function setLDAP(&$ldap)
893     {
894         if (!$ldap instanceof Net_LDAP2) {
895             return PEAR::raiseError("LDAP is not a valid Net_LDAP2 object");
896         } else {
897             $this->_ldap =& $ldap;
898             return true;
899         }
900     }
901
902     /**
903     * Marks the entry as new/existing.
904     *
905     * If an Entry is marked as new, it will be added to the directory
906     * when calling {@link update()}.
907     * If the entry is marked as old ($mark = false), then the entry is
908     * assumed to be present in the directory server wich results in
909     * modification when calling {@link update()}.
910     *
911     * @param boolean $mark Value to set, defaults to "true"
912     *
913     * @return void
914     */
915     public function markAsNew($mark = true)
916     {
917         $this->_new = ($mark)? true : false;
918     }
919
920     /**
921     * Applies a regular expression onto a single- or multivalued attribute (like preg_match())
922     *
923     * This method behaves like PHPs preg_match() but with some exceptions.
924     * If you want to retrieve match information, then you MUST pass the
925     * $matches parameter via reference! otherwise you will get no matches.
926     * Since it is possible to have multi valued attributes the $matches
927     * array will have a additionally numerical dimension (one for each value):
928     * <code>
929     * $matches = array(
930     *         0 => array (usual preg_match() returnarray),
931     *         1 => array (usual preg_match() returnarray)
932     *     )
933     * </code>
934     * Please note, that $matches will be initialized to an empty array inside.
935     *
936     * Usage example:
937     * <code>
938     * $result = $entry->preg_match('/089(\d+)/', 'telephoneNumber', &$matches);
939     * if ( $result === true ){
940     *     echo "First match: ".$matches[0][1];   // Match of value 1, content of first bracket
941     * } else {
942     *     if ( Net_LDAP2::isError($result) ) {
943     *         echo "Error: ".$result->getMessage();
944     *     } else {
945     *         echo "No match found.";
946     *     }
947     * }
948     * </code>
949     *
950     * Please note that it is important to test for an Net_LDAP2_Error, because objects are
951     * evaluating to true by default, thus if an error occured, and you only check using "==" then
952     * you get misleading results. Use the "identical" (===) operator to test for matches to
953     * avoid this as shown above.
954     *
955     * @param string $regex     The regular expression
956     * @param string $attr_name The attribute to search in
957     * @param array  $matches   (optional, PASS BY REFERENCE!) Array to store matches in
958     *
959     * @return boolean|Net_LDAP2_Error  TRUE, if we had a match in one of the values, otherwise false. Net_LDAP2_Error in case something went wrong
960     */
961     public function pregMatch($regex, $attr_name, $matches = array())
962     {
963         $matches = array();
964
965         // fetch attribute values
966         $attr = $this->getValue($attr_name, 'all');
967         if (Net_LDAP2::isError($attr)) {
968             return $attr;
969         } else {
970             unset($attr['count']);
971         }
972
973         // perform preg_match() on all values
974         $match = false;
975         foreach ($attr as $thisvalue) {
976             $matches_int = array();
977             if (preg_match($regex, $thisvalue, $matches_int)) {
978                 $match = true;
979                 array_push($matches, $matches_int); // store matches in reference
980             }
981         }
982         return $match;
983     }
984
985     /**
986     * Alias of {@link pregMatch()} for compatibility to Net_LDAP 1
987     *
988     * @see pregMatch()
989     * @return boolean|Net_LDAP2_Error
990     */
991     public function preg_match()
992     {
993         $args = func_get_args();
994         return call_user_func_array(array( &$this, 'pregMatch' ), $args);
995     }
996
997     /**
998     * Tells if the entry is consiedered as new (not present in the server)
999     *
1000     * Please note, that this doesn't tell you if the entry is present on the server.
1001     * Use {@link Net_LDAP2::dnExists()} to see if an entry is already there.
1002     *
1003     * @return boolean
1004     */
1005     public function isNew()
1006     {
1007         return $this->_new;
1008     }
1009
1010
1011     /**
1012     * Is this entry going to be deleted once update() is called?
1013     *
1014     * @return boolean
1015     */
1016     public function willBeDeleted()
1017     {
1018         return $this->_delete;
1019     }
1020
1021     /**
1022     * Is this entry going to be moved once update() is called?
1023     *
1024     * @return boolean
1025     */
1026     public function willBeMoved()
1027     {
1028         return ($this->dn() !== $this->currentDN());
1029     }
1030
1031     /**
1032     * Returns always the original DN
1033     *
1034     * If an entry will be moved but {@link update()} was not called,
1035     * {@link dn()} will return the new DN. This method however, returns
1036     * always the current active DN.
1037     *
1038     * @return string
1039     */
1040     public function currentDN()
1041     {
1042         return $this->_dn;
1043     }
1044
1045     /**
1046     * Returns the attribute changes to be carried out once update() is called
1047     *
1048     * @return array
1049     */
1050     public function getChanges()
1051     {
1052         return $this->_changes;
1053     }
1054 }
1055 ?>