This file is indexed.

/usr/share/php/Horde/Ldap/Entry.php is in php-horde-ldap 2.3.2-3.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
<?php
/**
 * Horde_Ldap_Entry represents an LDAP entry.
 *
 * Copyright 2003-2007 Tarjej Huse, Jan Wagner, Benedikt Hallinger
 * Copyright 2009-2016 Horde LLC (http://www.horde.org/)
 *
 * @package   Ldap
 * @author    Jan Wagner <wagner@netsols.de>
 * @author    Tarjej Huse <tarjei@bergfald.no>
 * @author    Benedikt Hallinger <beni@php.net>
 * @author    Ben Klang <ben@alkaloid.net>
 * @author    Jan Schneider <jan@horde.org>
 * @license   http://www.gnu.org/licenses/lgpl-3.0.html LGPL-3.0
 */
class Horde_Ldap_Entry
{
    /**
     * Entry resource identifier.
     *
     * @var resource
     */
    protected $_entry;

    /**
     * LDAP resource identifier.
     *
     * @var resource
     */
    protected $_link;

    /**
     * Horde_Ldap object.
     *
     * This object will be used for updating and schema checking.
     *
     * @var Horde_Ldap
     */
    protected $_ldap;

    /**
     * Distinguished name of the entry.
     *
     * @var string
     */
    protected $_dn;

    /**
     * Attributes.
     *
     * @var array
     */
    protected $_attributes = array();

    /**
     * Original attributes before any modification.
     *
     * @var array
     */
    protected $_original = array();

    /**
     * Map of attribute names.
     *
     * @var array
     */
    protected $_map = array();

    /**
     * Is this a new entry?
     *
     * @var boolean
     */
    protected $_new = true;

    /**
     * New distinguished name.
     *
     * @var string
     */
    protected $_newdn;

    /**
     * Shall the entry be deleted?
     *
     * @var boolean
     */
    protected $_delete = false;

    /**
     * Map with changes to the entry.
     *
     * @var array
     */
    protected $_changes = array('add'     => array(),
                                'delete'  => array(),
                                'replace' => array());

    /**
     * Constructor.
     *
     * Sets up the distinguished name and the entries attributes.
     *
     * Use {@link Horde_Ldap_Entry::createFresh()} or {@link
     * Horde_Ldap_Entry::createConnected()} to create Horde_Ldap_Entry objects.
     *
     * @param Horde_Ldap|resource|array $ldap Horde_Ldap object, LDAP
     *                                        connection resource or
     *                                        array of attributes.
     * @param string|resource          $entry Either a DN or a LDAP entry
     *                                        resource.
     */
    protected function __construct($ldap, $entry = null)
    {
        /* Set up entry resource or DN. */
        if (is_resource($entry)) {
            $this->_entry = $entry;
        } else {
            $this->_dn = $entry;
        }

        /* Set up LDAP link. */
        if ($ldap instanceof Horde_Ldap) {
            $this->_ldap = $ldap;
            $this->_link = $ldap->getLink();
        } elseif (is_resource($ldap)) {
            $this->_link = $ldap;
        } elseif (is_array($ldap)) {
            /* Special case: here $ldap is an array of attributes, this means,
             * we have no link. This is a "virtual" entry.  We just set up the
             * attributes so one can work with the object as expected, but an
             * update() fails unless setLDAP() is called. */
            $this->_loadAttributes($ldap);
        }

        /* If this is an entry existing in the directory, then set up as old
         * and fetch attributes. */
        if (is_resource($this->_entry) && is_resource($this->_link)) {
            $this->_new = false;
            $this->_dn  = @ldap_get_dn($this->_link, $this->_entry);
            /* Fetch attributes from server. */
            $this->_loadAttributes();
        }
    }

    /**
     * Creates a fresh entry that may be added to the directory later.
     *
     * You should put a 'objectClass' attribute into the $attrs so the
     * directory server knows which object you want to create. However, you may
     * omit this in case you don't want to add this entry to a directory
     * server.
     *
     * The attributes parameter is as following:
     * <code>
     * $attrs = array('attribute1' => array('value1', 'value2'),
     *                'attribute2' => 'single value');
     * </code>
     *
     * @param string $dn    DN of the entry.
     * @param array  $attrs Attributes of the entry.
     *
     * @return Horde_Ldap_Entry
     * @throws Horde_Ldap_Exception
     */
    public static function createFresh($dn, array $attrs = array())
    {
        return new Horde_Ldap_Entry($attrs, $dn);
    }

    /**
     * Creates an entry object out of an LDAP entry resource.
     *
     * Use this method, if you want to initialize an entry object that is
     * already present in some directory and that you have read manually.
     *
     * @param Horde_Ldap $ldap Horde_Ldap object.
     * @param resource  $entry PHP LDAP entry resource.
     *
     * @return Horde_Ldap_Entry
     * @throws Horde_Ldap_Exception
     */
    public static function createConnected(Horde_Ldap $ldap, $entry)
    {
        if (!is_resource($entry)) {
            throw new Horde_Ldap_Exception('Unable to create connected entry: Parameter $entry needs to be a ldap entry resource!');
        }

        return new Horde_Ldap_Entry($ldap, $entry);
    }

    /**
     * Creates an entry object that is considered to exist already.
     *
     * Use this method, if you want to modify an already existing entry without
     * fetching it first.  In most cases however, it is better to fetch the
     * entry via Horde_Ldap::getEntry().
     *
     * You should take care if you construct entries manually with this because
     * you may get weird synchronisation problems.  The attributes and values
     * as well as the entry itself are considered existent which may produce
     * errors if you try to modify an entry which doesn't really exist or if
     * you try to overwrite some attribute with an value already present.
     *
     * The attributes parameter is as following:
     * <code>
     * $attrs = array('attribute1' => array('value1', 'value2'),
     *                'attribute2' => 'single value');
     * </code>
     *
     * @param string $dn    DN of the entry.
     * @param array  $attrs Attributes of the entry.
     *
     * @return Horde_Ldap_Entry
     * @throws Horde_Ldap_Exception
     */
    public static function createExisting($dn, array $attrs = array())
    {
        $entry = self::createFresh($dn, $attrs);
        $entry->markAsNew(false);
        return $entry;
    }

    /**
     * Returns or sets the distinguished name of the entry.
     *
     * If called without an argument the current (or the new DN if set) DN gets
     * returned.
     *
     * If you provide an DN, this entry is moved to the new location specified
     * if a DN existed.
     *
     * If the DN was not set, the DN gets initialized. Call {@link update()} to
     * actually create the new entry in the directory.
     *
     * To fetch the current active DN after setting a new DN but before an
     * update(), you can use {@link currentDN()} to retrieve the DN that is
     * currently active.
     *
     * @todo expect utf-8 data.
     * Please note that special characters (eg german umlauts) should be encoded using utf8_encode().
     * You may use {@link Horde_Ldap_Util::canonicalDN()} for properly encoding of the DN.
     *
     * @param string $dn New distinguished name.
     *
     * @return string Distinguished name.
     */
    public function dn($dn = null)
    {
        if (!is_null($dn)) {
            if (is_null($this->_dn)) {
                $this->_dn = $dn;
            } else {
                $this->_newdn = $dn;
            }
            return $dn;
        }
        return isset($this->_newdn) ? $this->_newdn : $this->currentDN();
    }

    /**
     * Sets the internal attributes array.
     *
     * This method fetches the values for the attributes from the server.  The
     * attribute syntax will be checked so binary attributes will be returned
     * as binary values.
     *
     * Attributes may be passed directly via the $attributes parameter to setup
     * this entry manually. This overrides attribute fetching from the server.
     *
     * @param array $attributes Attributes to set for this entry.
     */
    protected function _loadAttributes(array $attributes = null)
    {
        /* Fetch attributes from the server. */
        if (is_null($attributes) &&
            is_resource($this->_entry) &&
            is_resource($this->_link)) {
            /* Fetch schema. */
            if ($this->_ldap instanceof Horde_Ldap) {
                try {
                    $schema = $this->_ldap->schema();
                } catch (Horde_Ldap_Exception $e) {
                    $schema = null;
                }
            }

            /* Fetch attributes. */
            $attributes = array();
            for ($attr = @ldap_first_attribute($this->_link, $this->_entry);
                 $attr;
                 $attr = @ldap_next_attribute($this->_link, $this->_entry)) {
                /* Standard function to fetch value. */
                $func = 'ldap_get_values';

                /* Try to get binary values as binary data. */
                if ($schema instanceof Horde_Ldap_Schema &&
                    $schema->isBinary($attr)) {
                    $func = 'ldap_get_values_len';
                }

                /* Fetch attribute value (needs error checking?) . */
                $attributes[$attr] = $func($this->_link, $this->_entry, $attr);
            }
        }

        /* Set attribute data directly, if passed. */
        if (is_array($attributes) && count($attributes) > 0) {
            if (isset($attributes['count']) &&
                is_numeric($attributes['count'])) {
                unset($attributes['count']);
            }
            foreach ($attributes as $k => $v) {
                /* Attribute names should not be numeric. */
                if (is_numeric($k)) {
                    continue;
                }

                /* Map generic attribute name to real one. */
                $this->_map[Horde_String::lower($k)] = $k;

                /* Attribute values should be in an array. */
                if (false == is_array($v)) {
                    $v = array($v);
                }

                /* Remove the value count (comes from LDAP server). */
                if (isset($v['count'])) {
                    unset($v['count']);
                }
                $this->_attributes[$k] = $v;
            }
        }

        /* Save a copy for later use. */
        $this->_original = $this->_attributes;
    }

    /**
     * Returns the values of all attributes in a hash.
     *
     * The returned hash has the form
     * <code>
     * array('attributename' => 'single value',
     *       'attributename' => array('value1', value2', value3'))
     * </code>
     *
     * @return array Hash of all attributes with their values.
     * @throws Horde_Ldap_Exception
     */
    public function getValues()
    {
        $attrs = array();
        foreach (array_keys($this->_attributes) as $attr) {
            $attrs[$attr] = $this->getValue($attr);
        }
        return $attrs;
    }

    /**
     * Returns the value of a specific attribute.
     *
     * The first parameter is the name of the attribute.
     *
     * The second parameter influences the way the value is returned:
     * - 'single': only the first value is returned as string.
     * - 'all': all values are returned in an array.
     * In all other cases an attribute value with a single value is returned as
     * string, if it has multiple values it is returned as an array.
     *
     * @param string $attr   Attribute name.
     * @param string $option Option.
     *
     * @return string|array Attribute value(s).
     * @throws Horde_Ldap_Exception
     */
    public function getValue($attr, $option = null)
    {
        $attr = $this->_getAttrName($attr);

        if (!array_key_exists($attr, $this->_attributes)) {
            throw new Horde_Ldap_Exception('Unknown attribute (' . $attr . ') requested');
        }

        $value = $this->_attributes[$attr];

        if ($option == 'single' || (count($value) == 1 && $option != 'all')) {
            $value = array_shift($value);
        }

        return $value;
    }

    /**
     * Returns an array of attributes names.
     *
     * @return array Array of attribute names.
     */
    public function attributes()
    {
        return array_keys($this->_attributes);
    }

    /**
     * Returns whether an attribute exists or not.
     *
     * @param string $attr Attribute name.
     *
     * @return boolean True if the attribute exists.
     */
    public function exists($attr)
    {
        $attr = $this->_getAttrName($attr);
        return array_key_exists($attr, $this->_attributes);
    }

    /**
     * Adds new attributes or a new values to existing attributes.
     *
     * The paramter has to be an array of the form:
     * <code>
     * array('attributename' => 'single value',
     *       'attributename' => array('value1', 'value2'))
     * </code>
     *
     * When the attribute already exists the values will be added, otherwise
     * the attribute will be created. These changes are local to the entry and
     * do not affect the entry on the server until update() is called.
     *
     * You can add values of attributes that you haven't originally selected,
     * but if you do so, {@link getValue()} and {@link getValues()} will only
     * return the values you added, *NOT* all values present on the server. To
     * avoid this, just refetch the entry after calling {@link update()} or
     * select the attribute.
     *
     * @param array $attr Attributes to add.
     */
    public function add(array $attr = array())
    {
        foreach ($attr as $k => $v) {
            $k = $this->_getAttrName($k);
            if (!is_array($v)) {
                /* Do not add empty values. */
                if ($v == null) {
                    continue;
                } else {
                    $v = array($v);
                }
            }

            /* Add new values to existing attribute or add new attribute. */
            if ($this->exists($k)) {
                $this->_attributes[$k] = array_unique(array_merge($this->_attributes[$k], $v));
            } else {
                $this->_map[Horde_String::lower($k)] = $k;
                $this->_attributes[$k]      = $v;
            }

            /* Save changes for update(). */
            if (empty($this->_changes['add'][$k])) {
                $this->_changes['add'][$k] = array();
            }
            $this->_changes['add'][$k] = array_unique(array_merge($this->_changes['add'][$k], $v));
        }
    }

    /**
     * Deletes an attribute, a value or the whole entry.
     *
     * The parameter can be one of the following:
     *
     * - 'attributename': the attribute as a whole will be deleted.
     * - array('attributename1', 'attributename2'): all specified attributes
     *                                              will be deleted.
     * - array('attributename' => 'value'): the specified attribute value will
     *                                      be deleted.
     * - array('attributename' => array('value1', 'value2'): The specified
     *                                                       attribute values
     *                                                       will be deleted.
     * - null: the whole entry will be deleted.
     *
     * These changes are local to the entry and do not affect the entry on the
     * server until {@link update()} is called.
     *
     * You must select the attribute (at $ldap->search() for example) to be
     * able to delete values of it, Otherwise {@link update()} will silently
     * fail and remove nothing.
     *
     * @param string|array $attr Attributes to delete.
     */
    public function delete($attr = null)
    {
        if (is_null($attr)) {
            $this->_delete = true;
            return;
        }

        if (is_string($attr)) {
            $attr = array($attr);
        }

        /* Make the assumption that attribute names cannot be numeric,
         * therefore this has to be a simple list of attribute names to
         * delete. */
        reset($attr);
        if (is_numeric(key($attr))) {
            foreach ($attr as $name) {
                if (is_array($name)) {
                    /* Mixed modes (list mode but specific values given!). */
                    $del_attr_name = array_search($name, $attr);
                    $this->delete(array($del_attr_name => $name));
                } else {
                    /* Mark for update() if this attribute was not marked
                     before. */
                    $name = $this->_getAttrName($name);
                    if ($this->exists($name)) {
                        $this->_changes['delete'][$name] = null;
                        unset($this->_attributes[$name]);
                    }
                }
            }
        } else {
            /* We have a hash with 'attributename' => 'value to delete'. */
            foreach ($attr as $name => $values) {
                if (is_int($name)) {
                    /* Mixed modes and gave us just an attribute name. */
                    $this->delete($values);
                } else {
                    /* Mark for update() if this attribute was not marked
                     * before; this time it must consider the selected values
                     * too. */
                    $name = $this->_getAttrName($name);
                    if ($this->exists($name)) {
                        if (!is_array($values)) {
                            $values = array($values);
                        }
                        /* Save values to be deleted. */
                        if (empty($this->_changes['delete'][$name])) {
                            $this->_changes['delete'][$name] = array();
                        }
                        $this->_changes['delete'][$name] =
                            array_unique(array_merge($this->_changes['delete'][$name], $values));
                        foreach ($values as $value) {
                            /* Find the key for the value that should be
                             * deleted. */
                            $key = array_search($value, $this->_attributes[$name]);
                            if (false !== $key) {
                                /* Delete the value. */
                                unset($this->_attributes[$name][$key]);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Replaces attributes or their values.
     *
     * The parameter has to an array of the following form:
     * <code>
     * array('attributename' => 'single value',
     *       'attribute2name' => array('value1', 'value2'),
     *       'deleteme1' => null,
     *       'deleteme2' => '')
     * </code>
     *
     * If the attribute does not yet exist it will be added instead (see also
     * $force). If the attribue value is null, the attribute will de deleted.
     *
     * These changes are local to the entry and do not affect the entry on the
     * server until {@link update()} is called.
     *
     * In some cases you are not allowed to read the attributes value (for
     * example the ActiveDirectory attribute unicodePwd) but are allowed to
     * replace the value. In this case replace() would assume that the
     * attribute is not in the directory yet and tries to add it which will
     * result in an LDAP_TYPE_OR_VALUE_EXISTS error. To force replace mode
     * instead of add, you can set $force to true.
     *
     * @param array   $attr  Attributes to replace.
     * @param boolean $force Force replacing mode in case we can't read the
     *                       attribute value but are allowed to replace it.
     */
    public function replace(array $attr = array(), $force = false)
    {
        foreach ($attr as $k => $v) {
            $k = $this->_getAttrName($k);
            if (!is_array($v)) {
                /* Delete attributes with empty values; treat integers as
                 * string. */
                if (is_int($v)) {
                    $v = (string)$v;
                }
                if ($v == null) {
                    $this->delete($k);
                    continue;
                } else {
                    $v = array($v);
                }
            }
            /* Existing attributes will get replaced. */
            if ($this->exists($k) || $force) {
                $this->_changes['replace'][$k] = $v;
                $this->_attributes[$k]         = $v;
            } else {
                /* New ones just get added. */
                $this->add(array($k => $v));
            }
        }
    }

    /**
     * Updates the entry on the directory server.
     *
     * This will evaluate all changes made so far and send them to the
     * directory server.
     *
     * If you make changes to objectclasses wich have mandatory attributes set,
     * update() will currently fail. Remove the entry from the server and readd
     * it as new in such cases. This also will deal with problems with setting
     * structural object classes.
     *
     * @todo Entry rename with a DN containing special characters needs testing!
     *
     * @throws Horde_Ldap_Exception
     */
    public function update()
    {
        /* Ensure we have a valid LDAP object. */
        $ldap = $this->getLDAP();

        /* Get and check link. */
        $link = $ldap->getLink();
        if (!is_resource($link)) {
            throw new Horde_Ldap_Exception('Could not update entry: internal LDAP link is invalid');
        }

        /* Delete the entry. */
        if ($this->_delete) {
            return $ldap->delete($this);
        }

        /* New entry. */
        if ($this->_new) {
            $ldap->add($this);
            $this->_new                = false;
            $this->_changes['add']     = array();
            $this->_changes['delete']  = array();
            $this->_changes['replace'] = array();
            $this->_original           = $this->_attributes;
            return;
        }

        /* Rename/move entry. */
        if (!is_null($this->_newdn)) {
            if ($ldap->getVersion() != 3) {
                throw new Horde_Ldap_Exception('Renaming/Moving an entry is only supported in LDAPv3');
            }
            /* Make DN relative to parent (needed for LDAP rename). */
            $parent = Horde_Ldap_Util::explodeDN($this->_newdn, array('casefolding' => 'none', 'reverse' => false, 'onlyvalues' => false));
            $child = array_shift($parent);

            /* Maybe the DN consist of a multivalued RDN, we must build the DN
             * in this case because the $child RDN is an array. */
            if (is_array($child)) {
                $child = Horde_Ldap_Util::canonicalDN($child);
            }
            $parent = Horde_Ldap_Util::canonicalDN($parent);

            /* Rename/move. */
            if (!@ldap_rename($link, $this->_dn, $child, $parent, true)) {
                throw new Horde_Ldap_Exception('Entry not renamed: ' . @ldap_error($link), @ldap_errno($link));
            }

            /* Reflect changes to local copy. */
            $this->_dn    = $this->_newdn;
            $this->_newdn = null;
        }

        /* Carry out modifications to the entry. */
        foreach ($this->_changes['add'] as $attr => $value) {
            /* If attribute exists, add new values. */
            if ($this->exists($attr)) {
                if (!@ldap_mod_add($link, $this->dn(), array($attr => $value))) {
                    throw new Horde_Ldap_Exception('Could not add new values to attribute ' . $attr . ': ' . @ldap_error($link), @ldap_errno($link));
                }
            } else {
                /* New attribute. */
                if (!@ldap_modify($link, $this->dn(), array($attr => $value))) {
                    throw new Horde_Ldap_Exception('Could not add new attribute ' . $attr . ': ' . @ldap_error($link), @ldap_errno($link));
                }
            }
            unset($this->_changes['add'][$attr]);
        }

        foreach ($this->_changes['delete'] as $attr => $value) {
            /* In LDAPv3 you need to specify the old values for deleting. */
            if (is_null($value) && $ldap->getVersion() == 3) {
                $value = $this->_original[$attr];
            }
            if (!@ldap_mod_del($link, $this->dn(), array($attr => $value))) {
                throw new Horde_Ldap_Exception('Could not delete attribute ' . $attr . ': ' . @ldap_error($link), @ldap_errno($link));
            }
            unset($this->_changes['delete'][$attr]);
        }

        foreach ($this->_changes['replace'] as $attr => $value) {
            if (!@ldap_modify($link, $this->dn(), array($attr => $value))) {
                throw new Horde_Ldap_Exception('Could not replace attribute ' . $attr . ' values: ' . @ldap_error($link), @ldap_errno($link));
            }
            unset($this->_changes['replace'][$attr]);
        }

        /* All went well, so $_attributes (local copy) becomes $_original
         * (server). */
        $this->_original = $this->_attributes;
    }

    /**
     * Returns the right attribute name.
     *
     * @param string $attr Name of attribute.
     *
     * @return string The right name of the attribute
     */
    protected function _getAttrName($attr)
    {
        $name = Horde_String::lower($attr);
        return isset($this->_map[$name]) ? $this->_map[$name] : $attr;
    }

    /**
     * Returns a reference to the LDAP-Object of this entry.
     *
     * @return Horde_Ldap  Reference to the Horde_Ldap object (the connection).
     * @throws Horde_Ldap_Exception
     */
    public function getLDAP()
    {
        if (!($this->_ldap instanceof Horde_Ldap)) {
            throw new Horde_Ldap_Exception('ldap property is not a valid Horde_Ldap object');
        }
        return $this->_ldap;
    }

    /**
     * Sets a reference to the LDAP object of this entry.
     *
     * After setting a Horde_Ldap object, calling update() will use that object
     * for updating directory contents. Use this to dynamicly switch
     * directories.
     *
     * @param Horde_Ldap $ldap  Horde_Ldap object that this entry should be
     *                          connected to.
     *
     * @throws Horde_Ldap_Exception
     */
    public function setLDAP(Horde_Ldap $ldap)
    {
        $this->_ldap = $ldap;
    }

    /**
     * Marks the entry as new or existing.
     *
     * If an entry is marked as new, it will be added to the directory when
     * calling {@link update()}.
     *
     * If the entry is marked as old ($mark = false), then the entry is assumed
     * to be present in the directory server wich results in modification when
     * calling {@link update()}.
     *
     * @param boolean $mark Whether to mark the entry as new.
     */
    public function markAsNew($mark = true)
    {
        $this->_new = (bool)$mark;
    }

    /**
     * Applies a regular expression onto a single- or multi-valued attribute
     * (like preg_match()).
     *
     * This method behaves like PHP's preg_match() but with some exception.
     * Since it is possible to have multi valued attributes the $matches
     * array will have a additionally numerical dimension (one for each value):
     * <code>
     * $matches = array(
     *         0 => array (usual preg_match() returned array),
     *         1 => array (usual preg_match() returned array)
     * )
     * </code>
     * $matches will always be initialized to an empty array inside.
     *
     * Usage example:
     * <code>
     * try {
     *     if ($entry->pregMatch('/089(\d+)/', 'telephoneNumber', $matches)) {
     *         // Match of value 1, content of first bracket
     *         echo 'First match: ' . $matches[0][1];
     *     } else {
     *         echo 'No match found.';
     *     }
     * } catch (Horde_Ldap_Exception $e) {
     *     echo 'Error: ' . $e->getMessage();
     * }
     * </code>
     *
     * @param string $regex     The regular expression.
     * @param string $attr_name The attribute to search in.
     * @param array  $matches   Array to store matches in.
     *
     * @return boolean  True if we had a match in one of the values.
     * @throws Horde_Ldap_Exception
     */
    public function pregMatch($regex, $attr_name, &$matches = array())
    {
        /* Fetch attribute values. */
        $attr = $this->getValue($attr_name, 'all');
        unset($attr['count']);

        /* Perform preg_match() on all values. */
        $match = false;
        foreach ($attr as $thisvalue) {
            if (preg_match($regex, $thisvalue, $matches_int)) {
                $match = true;
                $matches[] = $matches_int;
            }
        }

        return $match;
    }

    /**
     * Returns whether the entry is considered new (not present in the server).
     *
     * This method doesn't tell you if the entry is really not present on the
     * server. Use {@link Horde_Ldap::exists()} to see if an entry is already
     * there.
     *
     * @return boolean  True if this is considered a new entry.
     */
    public function isNew()
    {
        return $this->_new;
    }

    /**
     * Is this entry going to be deleted once update() is called?
     *
     * @return boolean  True if this entry is going to be deleted.
     */
    public function willBeDeleted()
    {
        return $this->_delete;
    }

    /**
     * Is this entry going to be moved once update() is called?
     *
     * @return boolean  True if this entry is going to be move.
     */
    public function willBeMoved()
    {
        return $this->dn() !== $this->currentDN();
    }

    /**
     * Returns always the original DN.
     *
     * If an entry will be moved but {@link update()} was not called, {@link
     * dn()} will return the new DN. This method however, returns always the
     * current active DN.
     *
     * @return string  The current DN
     */
    public function currentDN()
    {
        return $this->_dn;
    }

    /**
     * Returns the attribute changes to be carried out once update() is called.
     *
     * @return array  The due changes.
     */
    public function getChanges()
    {
        return $this->_changes;
    }
}