]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/LdapCommon/extlib/Net/LDAP2/Entry.php
Updated LDAP2 extlib to latest version.
[quix0rs-gnu-social.git] / plugins / LdapCommon / 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 332301 2013-12-09 08:17:14Z beni $
16 * @link      http://pear.php.net/package/Net_LDAP2/
17 */
18
19 /**
20 * Includes
21 */
22 require_once 'PEAR.php';
23 require_once 'Net/LDAP2/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     * Only attributes present at the entry will be returned.
423     *
424     * @access public
425     * @return array Hash of all attributes with their values
426     */
427     public function getValues()
428     {
429         $attrs = array();
430         foreach ($this->_attributes as $attr => $value) {
431             $attrs[$attr] = $this->getValue($attr);
432         }
433         return $attrs;
434     }
435
436     /**
437     * Get the value of a specific attribute
438     *
439     * The first parameter is the name of the attribute
440     * The second parameter influences the way the value is returned:
441     * 'single':  only the first value is returned as string
442     * 'all':     all values are returned in an 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
446     *
447     * If the attribute is not set at this entry (no value or not defined in
448     * schema), "false" is returned when $option is 'single', an empty string if
449     * 'default', and an empty array when 'all'.
450     *
451     * You may use Net_LDAP2_Schema->checkAttribute() to see if the attribute
452     * is defined for the objectClasses of this entry.
453     *
454     * @param string  $attr         Attribute name
455     * @param string  $option       Option
456     *
457     * @access public
458     * @return string|array
459     */
460     public function getValue($attr, $option = null)
461     {
462         $attr = $this->getAttrName($attr);
463
464         // return depending on set $options
465         if (!array_key_exists($attr, $this->_attributes)) {
466             // attribute not set
467             switch ($option) {
468                 case 'single':
469                     $value = false;
470                 break;
471                 case 'all':
472                     $value = array();
473                 break;
474                 default:
475                     $value = '';
476             }
477
478         } else {
479             // attribute present
480             switch ($option) {
481                 case 'single':
482                     $value = $this->_attributes[$attr][0];
483                 break;
484                 case 'all':
485                     $value = $this->_attributes[$attr];
486                 break;
487                 default:
488                     $value = $this->_attributes[$attr];
489                     if (count($value) == 1) {
490                         $value = array_shift($value);
491                     }
492             }
493             
494         }
495
496         return $value;
497     }
498
499     /**
500     * Alias function of getValue for perl-ldap interface
501     *
502     * @see getValue()
503     * @return string|array|PEAR_Error
504     */
505     public function get_value()
506     {
507         $args = func_get_args();
508         return call_user_func_array(array( &$this, 'getValue' ), $args);
509     }
510
511     /**
512     * Returns an array of attributes names
513     *
514     * @access public
515     * @return array Array of attribute names
516     */
517     public function attributes()
518     {
519         return array_keys($this->_attributes);
520     }
521
522     /**
523     * Returns whether an attribute exists or not
524     *
525     * @param string $attr Attribute name
526     *
527     * @access public
528     * @return boolean
529     */
530     public function exists($attr)
531     {
532         $attr = $this->getAttrName($attr);
533         return array_key_exists($attr, $this->_attributes);
534     }
535
536     /**
537     * Adds a new attribute or a new value to an existing attribute
538     *
539     * The paramter has to be an array of the form:
540     * array('attributename' => 'single value',
541     *       'attributename' => array('value1', 'value2))
542     * When the attribute already exists the values will be added, else the
543     * attribute will be created. These changes are local to the entry and do
544     * not affect the entry on the server until update() is called.
545     *
546     * Note, that you can add values of attributes that you haven't selected, but if
547     * you do so, {@link getValue()} and {@link getValues()} will only return the
548     * values you added, _NOT_ all values present on the server. To avoid this, just refetch
549     * the entry after calling {@link update()} or select the attribute.
550     *
551     * @param array $attr Attributes to add
552     *
553     * @access public
554     * @return true|Net_LDAP2_Error
555     */
556     public function add($attr = array())
557     {
558         if (false == is_array($attr)) {
559             return PEAR::raiseError("Parameter must be an array");
560         }
561         if ($this->isNew()) {
562             $this->setAttributes($attr);
563         }
564         foreach ($attr as $k => $v) {
565             $k = $this->getAttrName($k);
566             if (false == is_array($v)) {
567                 // Do not add empty values
568                 if ($v == null) {
569                     continue;
570                 } else {
571                     $v = array($v);
572                 }
573             }
574             // add new values to existing attribute or add new attribute
575             if ($this->exists($k)) {
576                 $this->_attributes[$k] = array_unique(array_merge($this->_attributes[$k], $v));
577             } else {
578                 $this->_map[strtolower($k)] = $k;
579                 $this->_attributes[$k]      = $v;
580             }
581             // save changes for update()
582             if (!isset($this->_changes["add"][$k])) {
583                 $this->_changes["add"][$k] = array();
584             }
585             $this->_changes["add"][$k] = array_unique(array_merge($this->_changes["add"][$k], $v));
586         }
587
588         $return = true;
589         return $return;
590     }
591
592     /**
593     * Deletes an whole attribute or a value or the whole entry
594     *
595     * The parameter can be one of the following:
596     *
597     * "attributename" - The attribute as a whole will be deleted
598     * array("attributename1", "attributename2) - All given attributes will be
599     *                                            deleted
600     * array("attributename" => "value") - The value will be deleted
601     * array("attributename" => array("value1", "value2") - The given values
602     *                                                      will be deleted
603     * If $attr is null or omitted , then the whole Entry will be deleted!
604     *
605     * These changes are local to the entry and do
606     * not affect the entry on the server until {@link update()} is called.
607     *
608     * Please note that you must select the attribute (at $ldap->search() for example)
609     * to be able to delete values of it, Otherwise {@link update()} will silently fail
610     * and remove nothing.
611     *
612     * @param string|array $attr Attributes to delete (NULL or missing to delete whole entry)
613     *
614     * @access public
615     * @return true
616     */
617     public function delete($attr = null)
618     {
619         if (is_null($attr)) {
620             $this->_delete = true;
621             return true;
622         }
623         if (is_string($attr)) {
624             $attr = array($attr);
625         }
626         // Make the assumption that attribute names cannot be numeric,
627         // therefore this has to be a simple list of attribute names to delete
628         if (is_numeric(key($attr))) {
629             foreach ($attr as $name) {
630                 if (is_array($name)) {
631                     // someone mixed modes (list mode but specific values given!)
632                     $del_attr_name = array_search($name, $attr);
633                     $this->delete(array($del_attr_name => $name));
634                 } else {
635                     // mark for update() if this attr was not marked before
636                     $name = $this->getAttrName($name);
637                     if ($this->exists($name)) {
638                         $this->_changes["delete"][$name] = null;
639                         unset($this->_attributes[$name]);
640                     }
641                 }
642             }
643         } else {
644             // Here we have a hash with "attributename" => "value to delete"
645             foreach ($attr as $name => $values) {
646                 if (is_int($name)) {
647                     // someone mixed modes and gave us just an attribute name
648                     $this->delete($values);
649                 } else {
650                     // mark for update() if this attr was not marked before;
651                     // this time it must consider the selected values also
652                     $name = $this->getAttrName($name);
653                     if ($this->exists($name)) {
654                         if (false == is_array($values)) {
655                             $values = array($values);
656                         }
657                         // save values to be deleted
658                         if (empty($this->_changes["delete"][$name])) {
659                             $this->_changes["delete"][$name] = array();
660                         }
661                         $this->_changes["delete"][$name] =
662                             array_unique(array_merge($this->_changes["delete"][$name], $values));
663                         foreach ($values as $value) {
664                             // find the key for the value that should be deleted
665                             $key = array_search($value, $this->_attributes[$name]);
666                             if (false !== $key) {
667                                 // delete the value
668                                 unset($this->_attributes[$name][$key]);
669                             }
670                         }
671                     }
672                 }
673             }
674         }
675         $return = true;
676         return $return;
677     }
678
679     /**
680     * Replaces attributes or its values
681     *
682     * The parameter has to an array of the following form:
683     * array("attributename" => "single value",
684     *       "attribute2name" => array("value1", "value2"),
685     *       "deleteme1" => null,
686     *       "deleteme2" => "")
687     * If the attribute does not yet exist it will be added instead (see also $force).
688     * If the attribue value is null, the attribute will de deleted.
689     *
690     * These changes are local to the entry and do
691     * not affect the entry on the server until {@link update()} is called.
692     *
693     * In some cases you are not allowed to read the attributes value (for
694     * example the ActiveDirectory attribute unicodePwd) but are allowed to
695     * replace the value. In this case replace() would assume that the attribute
696     * is not in the directory yet and tries to add it which will result in an
697     * LDAP_TYPE_OR_VALUE_EXISTS error.
698     * To force replace mode instead of add, you can set $force to true.
699     *
700     * @param array $attr  Attributes to replace
701     * @param bool  $force Force replacing mode in case we can't read the attr value but are allowed to replace it
702     *
703     * @access public
704     * @return true|Net_LDAP2_Error
705     */
706     public function replace($attr = array(), $force = false)
707     {
708         if (false == is_array($attr)) {
709             return PEAR::raiseError("Parameter must be an array");
710         }
711         foreach ($attr as $k => $v) {
712             $k = $this->getAttrName($k);
713             if (false == is_array($v)) {
714                 // delete attributes with empty values; treat ints as string
715                 if (is_int($v)) {
716                     $v = "$v";
717                 }
718                 if ($v == null) {
719                     $this->delete($k);
720                     continue;
721                 } else {
722                     $v = array($v);
723                 }
724             }
725             // existing attributes will get replaced
726             if ($this->exists($k) || $force) {
727                 $this->_changes["replace"][$k] = $v;
728                 $this->_attributes[$k]         = $v;
729             } else {
730                 // new ones just get added
731                 $this->add(array($k => $v));
732             }
733         }
734         $return = true;
735         return $return;
736     }
737
738     /**
739     * Update the entry on the directory server
740     *
741     * This will evaluate all changes made so far and send them
742     * to the directory server.
743     * Please note, that if you make changes to objectclasses wich
744     * have mandatory attributes set, update() will currently fail.
745     * Remove the entry from the server and readd it as new in such cases.
746     * This also will deal with problems with setting structural object classes.
747     *
748     * @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
749     *
750     * @access public
751     * @return true|Net_LDAP2_Error
752     * @todo Entry rename with a DN containing special characters needs testing!
753     */
754     public function update($ldap = null)
755     {
756         if ($ldap) {
757             $msg = $this->setLDAP($ldap);
758             if (Net_LDAP2::isError($msg)) {
759                 return PEAR::raiseError('You passed an invalid $ldap variable to update()');
760             }
761         }
762
763         // ensure we have a valid LDAP object
764         $ldap =& $this->getLDAP();
765         if (!$ldap instanceof Net_LDAP2) {
766             return PEAR::raiseError("The entries LDAP object is not valid");
767         }
768
769         // Get and check link
770         $link = $ldap->getLink();
771         if (!is_resource($link)) {
772             return PEAR::raiseError("Could not update entry: internal LDAP link is invalid");
773         }
774
775         /*
776         * Delete the entry
777         */
778         if (true === $this->_delete) {
779             return $ldap->delete($this);
780         }
781
782         /*
783         * New entry
784         */
785         if (true === $this->_new) {
786             $msg = $ldap->add($this);
787             if (Net_LDAP2::isError($msg)) {
788                 return $msg;
789             }
790             $this->_new                = false;
791             $this->_changes['add']     = array();
792             $this->_changes['delete']  = array();
793             $this->_changes['replace'] = array();
794             $this->_original           = $this->_attributes;
795
796             // In case the "new" entry was moved after creation, we must
797             // adjust the internal DNs as the entry was already created
798             // with the most current DN.
799             if (false == is_null($this->_newdn)) {
800                 $this->_dn    = $this->_newdn;
801                 $this->_newdn = null;
802             }
803
804             $return = true;
805             return $return;
806         }
807
808         /*
809         * Rename/move entry
810         */
811         if (false == is_null($this->_newdn)) {
812             if ($ldap->getLDAPVersion() !== 3) {
813                 return PEAR::raiseError("Renaming/Moving an entry is only supported in LDAPv3");
814             }
815             // make dn relative to parent (needed for ldap rename)
816             $parent = Net_LDAP2_Util::ldap_explode_dn($this->_newdn, array('casefolding' => 'none', 'reverse' => false, 'onlyvalues' => false));
817             if (Net_LDAP2::isError($parent)) {
818                 return $parent;
819             }
820             $child = array_shift($parent);
821             // maybe the dn consist of a multivalued RDN, we must build the dn in this case
822             // because the $child-RDN is an array!
823             if (is_array($child)) {
824                 $child = Net_LDAP2_Util::canonical_dn($child);
825             }
826             $parent = Net_LDAP2_Util::canonical_dn($parent);
827
828             // rename/move
829             if (false == @ldap_rename($link, $this->_dn, $child, $parent, false)) {
830
831                 return PEAR::raiseError("Entry not renamed: " .
832                                         @ldap_error($link), @ldap_errno($link));
833             }
834             // reflect changes to local copy
835             $this->_dn    = $this->_newdn;
836             $this->_newdn = null;
837         }
838
839         /*
840         * Retrieve a entry that has all attributes we need so that the list of changes to build is created accurately
841         */
842         $fullEntry = $ldap->getEntry( $this->dn() );
843         if ( Net_LDAP2::isError($fullEntry) ) {
844             return PEAR::raiseError("Could not retrieve a full set of attributes to reconcile changes with");
845         }
846         $modifications = array();
847
848         // ADD
849         foreach ($this->_changes["add"] as $attr => $value) {
850             // if attribute exists, we need to combine old and new values
851             if ($fullEntry->exists($attr)) {
852                 $currentValue = $fullEntry->getValue($attr, "all");
853                 $value = array_merge( $currentValue, $value );
854             } 
855             
856             $modifications[$attr] = $value;
857         }
858
859         // DELETE
860         foreach ($this->_changes["delete"] as $attr => $value) {
861             // In LDAPv3 you need to specify the old values for deleting
862             if (is_null($value) && $ldap->getLDAPVersion() === 3) {
863                 $value = $fullEntry->getValue($attr);
864             }
865             if (!is_array($value)) {
866                 $value = array($value);
867             }
868             
869             // Find out what is missing from $value and exclude it
870             $currentValue = isset($modifications[$attr]) ? $modifications[$attr] : $fullEntry->getValue($attr, "all");
871             $modifications[$attr] = array_values( array_diff( $currentValue, $value ) );
872         }
873
874         // REPLACE
875         foreach ($this->_changes["replace"] as $attr => $value) {
876             $modifications[$attr] = $value;
877         }
878
879         // COMMIT
880         if (false === @ldap_modify($link, $this->dn(), $modifications)) {
881             return PEAR::raiseError("Could not modify the entry: " . @ldap_error($link), @ldap_errno($link));
882         }
883
884         // all went well, so _original (server) becomes _attributes (local copy), reset _changes too...
885         $this->_changes['add']     = array();
886         $this->_changes['delete']  = array();
887         $this->_changes['replace'] = array();
888         $this->_original           = $this->_attributes;
889
890         $return = true;
891         return $return;
892     }
893
894     /**
895     * Returns the right attribute name
896     *
897     * @param string $attr Name of attribute
898     *
899     * @access protected
900     * @return string The right name of the attribute
901     */
902     protected function getAttrName($attr)
903     {
904         $name = strtolower($attr);
905         if (array_key_exists($name, $this->_map)) {
906             $attr = $this->_map[$name];
907         }
908         return $attr;
909     }
910
911     /**
912     * Returns a reference to the LDAP-Object of this entry
913     *
914     * @access public
915     * @return Net_LDAP2|Net_LDAP2_Error   Reference to the Net_LDAP2 Object (the connection) or Net_LDAP2_Error
916     */
917     public function &getLDAP()
918     {
919         if (!$this->_ldap instanceof Net_LDAP2) {
920             $err = new PEAR_Error('LDAP is not a valid Net_LDAP2 object');
921             return $err;
922         } else {
923             return $this->_ldap;
924         }
925     }
926
927     /**
928     * Sets a reference to the LDAP-Object of this entry
929     *
930     * After setting a Net_LDAP2 object, calling update() will use that object for
931     * updating directory contents. Use this to dynamicly switch directorys.
932     *
933     * @param Net_LDAP2 &$ldap Net_LDAP2 object that this entry should be connected to
934     *
935     * @access public
936     * @return true|Net_LDAP2_Error
937     */
938     public function setLDAP(&$ldap)
939     {
940         if (!$ldap instanceof Net_LDAP2) {
941             return PEAR::raiseError("LDAP is not a valid Net_LDAP2 object");
942         } else {
943             $this->_ldap =& $ldap;
944             return true;
945         }
946     }
947
948     /**
949     * Marks the entry as new/existing.
950     *
951     * If an Entry is marked as new, it will be added to the directory
952     * when calling {@link update()}.
953     * If the entry is marked as old ($mark = false), then the entry is
954     * assumed to be present in the directory server wich results in
955     * modification when calling {@link update()}.
956     *
957     * @param boolean $mark Value to set, defaults to "true"
958     *
959     * @return void
960     */
961     public function markAsNew($mark = true)
962     {
963         $this->_new = ($mark)? true : false;
964     }
965
966     /**
967     * Applies a regular expression onto a single- or multivalued attribute (like preg_match())
968     *
969     * This method behaves like PHPs preg_match() but with some exceptions.
970     * If you want to retrieve match information, then you MUST pass the
971     * $matches parameter via reference! otherwise you will get no matches.
972     * Since it is possible to have multi valued attributes the $matches
973     * array will have a additionally numerical dimension (one for each value):
974     * <code>
975     * $matches = array(
976     *         0 => array (usual preg_match() returnarray),
977     *         1 => array (usual preg_match() returnarray)
978     *     )
979     * </code>
980     * Please note, that $matches will be initialized to an empty array inside.
981     *
982     * Usage example:
983     * <code>
984     * $result = $entry->preg_match('/089(\d+)/', 'telephoneNumber', &$matches);
985     * if ( $result === true ){
986     *     echo "First match: ".$matches[0][1];   // Match of value 1, content of first bracket
987     * } else {
988     *     if ( Net_LDAP2::isError($result) ) {
989     *         echo "Error: ".$result->getMessage();
990     *     } else {
991     *         echo "No match found.";
992     *     }
993     * }
994     * </code>
995     *
996     * Please note that it is important to test for an Net_LDAP2_Error, because objects are
997     * evaluating to true by default, thus if an error occured, and you only check using "==" then
998     * you get misleading results. Use the "identical" (===) operator to test for matches to
999     * avoid this as shown above.
1000     *
1001     * @param string $regex     The regular expression
1002     * @param string $attr_name The attribute to search in
1003     * @param array  $matches   (optional, PASS BY REFERENCE!) Array to store matches in
1004     *
1005     * @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
1006     */
1007     public function pregMatch($regex, $attr_name, $matches = array())
1008     {
1009         $matches = array();
1010
1011         // fetch attribute values
1012         $attr = $this->getValue($attr_name, 'all');
1013
1014         // perform preg_match() on all values
1015         $match = false;
1016         foreach ($attr as $thisvalue) {
1017             $matches_int = array();
1018             if (preg_match($regex, $thisvalue, $matches_int)) {
1019                 $match = true;
1020                 array_push($matches, $matches_int); // store matches in reference
1021             }
1022         }
1023         return $match;
1024     }
1025
1026     /**
1027     * Alias of {@link pregMatch()} for compatibility to Net_LDAP 1
1028     *
1029     * @see pregMatch()
1030     * @return boolean|Net_LDAP2_Error
1031     */
1032     public function preg_match()
1033     {
1034         $args = func_get_args();
1035         return call_user_func_array(array( &$this, 'pregMatch' ), $args);
1036     }
1037
1038     /**
1039     * Tells if the entry is consiedered as new (not present in the server)
1040     *
1041     * Please note, that this doesn't tell you if the entry is present on the server.
1042     * Use {@link Net_LDAP2::dnExists()} to see if an entry is already there.
1043     *
1044     * @return boolean
1045     */
1046     public function isNew()
1047     {
1048         return $this->_new;
1049     }
1050
1051
1052     /**
1053     * Is this entry going to be deleted once update() is called?
1054     *
1055     * @return boolean
1056     */
1057     public function willBeDeleted()
1058     {
1059         return $this->_delete;
1060     }
1061
1062     /**
1063     * Is this entry going to be moved once update() is called?
1064     *
1065     * @return boolean
1066     */
1067     public function willBeMoved()
1068     {
1069         return ($this->dn() !== $this->currentDN());
1070     }
1071
1072     /**
1073     * Returns always the original DN
1074     *
1075     * If an entry will be moved but {@link update()} was not called,
1076     * {@link dn()} will return the new DN. This method however, returns
1077     * always the current active DN.
1078     *
1079     * @return string
1080     */
1081     public function currentDN()
1082     {
1083         return $this->_dn;
1084     }
1085
1086     /**
1087     * Returns the attribute changes to be carried out once update() is called
1088     *
1089     * @return array
1090     */
1091     public function getChanges()
1092     {
1093         return $this->_changes;
1094     }
1095 }
1096 ?>