Overview

Packages

  • Ldap

Classes

  • Horde_Ldap
  • Horde_Ldap_Entry
  • Horde_Ldap_Exception
  • Horde_Ldap_Filter
  • Horde_Ldap_Ldif
  • Horde_Ldap_RootDse
  • Horde_Ldap_Schema
  • Horde_Ldap_Search
  • Horde_Ldap_Util
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * Horde_Ldap_Entry represents an LDAP entry.
  4:  *
  5:  * Copyright 2003-2007 Tarjej Huse, Jan Wagner, Benedikt Hallinger
  6:  * Copyright 2009-2012 Horde LLC (http://www.horde.org/)
  7:  *
  8:  * @package   Ldap
  9:  * @author    Jan Wagner <wagner@netsols.de>
 10:  * @author    Tarjej Huse <tarjei@bergfald.no>
 11:  * @author    Benedikt Hallinger <beni@php.net>
 12:  * @author    Ben Klang <ben@alkaloid.net>
 13:  * @author    Jan Schneider <jan@horde.org>
 14:  * @license   http://www.gnu.org/licenses/lgpl-3.0.html LGPL-3.0
 15:  */
 16: class Horde_Ldap_Entry
 17: {
 18:     /**
 19:      * Entry resource identifier.
 20:      *
 21:      * @var resource
 22:      */
 23:     protected $_entry;
 24: 
 25:     /**
 26:      * LDAP resource identifier.
 27:      *
 28:      * @var resource
 29:      */
 30:     protected $_link;
 31: 
 32:     /**
 33:      * Horde_Ldap object.
 34:      *
 35:      * This object will be used for updating and schema checking.
 36:      *
 37:      * @var Horde_Ldap
 38:      */
 39:     protected $_ldap;
 40: 
 41:     /**
 42:      * Distinguished name of the entry.
 43:      *
 44:      * @var string
 45:      */
 46:     protected $_dn;
 47: 
 48:     /**
 49:      * Attributes.
 50:      *
 51:      * @var array
 52:      */
 53:     protected $_attributes = array();
 54: 
 55:     /**
 56:      * Original attributes before any modification.
 57:      *
 58:      * @var array
 59:      */
 60:     protected $_original = array();
 61: 
 62:     /**
 63:      * Map of attribute names.
 64:      *
 65:      * @var array
 66:      */
 67:     protected $_map = array();
 68: 
 69:     /**
 70:      * Is this a new entry?
 71:      *
 72:      * @var boolean
 73:      */
 74:     protected $_new = true;
 75: 
 76:     /**
 77:      * New distinguished name.
 78:      *
 79:      * @var string
 80:      */
 81:     protected $_newdn;
 82: 
 83:     /**
 84:      * Shall the entry be deleted?
 85:      *
 86:      * @var boolean
 87:      */
 88:     protected $_delete = false;
 89: 
 90:     /**
 91:      * Map with changes to the entry.
 92:      *
 93:      * @var array
 94:      */
 95:     protected $_changes = array('add'     => array(),
 96:                                 'delete'  => array(),
 97:                                 'replace' => array());
 98: 
 99:     /**
100:      * Constructor.
101:      *
102:      * Sets up the distinguished name and the entries attributes.
103:      *
104:      * Use {@link Horde_Ldap_Entry::createFresh()} or {@link
105:      * Horde_Ldap_Entry::createConnected()} to create Horde_Ldap_Entry objects.
106:      *
107:      * @param Horde_Ldap|resource|array $ldap Horde_Ldap object, LDAP
108:      *                                        connection resource or
109:      *                                        array of attributes.
110:      * @param string|resource          $entry Either a DN or a LDAP entry
111:      *                                        resource.
112:      */
113:     protected function __construct($ldap, $entry = null)
114:     {
115:         /* Set up entry resource or DN. */
116:         if (is_resource($entry)) {
117:             $this->_entry = $entry;
118:         } else {
119:             $this->_dn = $entry;
120:         }
121: 
122:         /* Set up LDAP link. */
123:         if ($ldap instanceof Horde_Ldap) {
124:             $this->_ldap = $ldap;
125:             $this->_link = $ldap->getLink();
126:         } elseif (is_resource($ldap)) {
127:             $this->_link = $ldap;
128:         } elseif (is_array($ldap)) {
129:             /* Special case: here $ldap is an array of attributes, this means,
130:              * we have no link. This is a "virtual" entry.  We just set up the
131:              * attributes so one can work with the object as expected, but an
132:              * update() fails unless setLDAP() is called. */
133:             $this->_loadAttributes($ldap);
134:         }
135: 
136:         /* If this is an entry existing in the directory, then set up as old
137:          * and fetch attributes. */
138:         if (is_resource($this->_entry) && is_resource($this->_link)) {
139:             $this->_new = false;
140:             $this->_dn  = @ldap_get_dn($this->_link, $this->_entry);
141:             /* Fetch attributes from server. */
142:             $this->_loadAttributes();
143:         }
144:     }
145: 
146:     /**
147:      * Creates a fresh entry that may be added to the directory later.
148:      *
149:      * You should put a 'objectClass' attribute into the $attrs so the
150:      * directory server knows which object you want to create. However, you may
151:      * omit this in case you don't want to add this entry to a directory
152:      * server.
153:      *
154:      * The attributes parameter is as following:
155:      * <code>
156:      * $attrs = array('attribute1' => array('value1', 'value2'),
157:      *                'attribute2' => 'single value');
158:      * </code>
159:      *
160:      * @param string $dn    DN of the entry.
161:      * @param array  $attrs Attributes of the entry.
162:      *
163:      * @return Horde_Ldap_Entry
164:      * @throws Horde_Ldap_Exception
165:      */
166:     public static function createFresh($dn, array $attrs = array())
167:     {
168:         return new Horde_Ldap_Entry($attrs, $dn);
169:     }
170: 
171:     /**
172:      * Creates an entry object out of an LDAP entry resource.
173:      *
174:      * Use this method, if you want to initialize an entry object that is
175:      * already present in some directory and that you have read manually.
176:      *
177:      * @param Horde_Ldap $ldap Horde_Ldap object.
178:      * @param resource  $entry PHP LDAP entry resource.
179:      *
180:      * @return Horde_Ldap_Entry
181:      * @throws Horde_Ldap_Exception
182:      */
183:     public static function createConnected(Horde_Ldap $ldap, $entry)
184:     {
185:         if (!is_resource($entry)) {
186:             throw new Horde_Ldap_Exception('Unable to create connected entry: Parameter $entry needs to be a ldap entry resource!');
187:         }
188: 
189:         return new Horde_Ldap_Entry($ldap, $entry);
190:     }
191: 
192:     /**
193:      * Creates an entry object that is considered to exist already.
194:      *
195:      * Use this method, if you want to modify an already existing entry without
196:      * fetching it first.  In most cases however, it is better to fetch the
197:      * entry via Horde_Ldap::getEntry().
198:      *
199:      * You should take care if you construct entries manually with this because
200:      * you may get weird synchronisation problems.  The attributes and values
201:      * as well as the entry itself are considered existent which may produce
202:      * errors if you try to modify an entry which doesn't really exist or if
203:      * you try to overwrite some attribute with an value already present.
204:      *
205:      * The attributes parameter is as following:
206:      * <code>
207:      * $attrs = array('attribute1' => array('value1', 'value2'),
208:      *                'attribute2' => 'single value');
209:      * </code>
210:      *
211:      * @param string $dn    DN of the entry.
212:      * @param array  $attrs Attributes of the entry.
213:      *
214:      * @return Horde_Ldap_Entry
215:      * @throws Horde_Ldap_Exception
216:      */
217:     public static function createExisting($dn, array $attrs = array())
218:     {
219:         $entry = self::createFresh($dn, $attrs);
220:         $entry->markAsNew(false);
221:         return $entry;
222:     }
223: 
224:     /**
225:      * Returns or sets the distinguished name of the entry.
226:      *
227:      * If called without an argument the current (or the new DN if set) DN gets
228:      * returned.
229:      *
230:      * If you provide an DN, this entry is moved to the new location specified
231:      * if a DN existed.
232:      *
233:      * If the DN was not set, the DN gets initialized. Call {@link update()} to
234:      * actually create the new entry in the directory.
235:      *
236:      * To fetch the current active DN after setting a new DN but before an
237:      * update(), you can use {@link currentDN()} to retrieve the DN that is
238:      * currently active.
239:      *
240:      * @todo expect utf-8 data.
241:      * Please note that special characters (eg german umlauts) should be encoded using utf8_encode().
242:      * You may use {@link Horde_Ldap_Util::canonicalDN()} for properly encoding of the DN.
243:      *
244:      * @param string $dn New distinguished name.
245:      *
246:      * @return string Distinguished name.
247:      */
248:     public function dn($dn = null)
249:     {
250:         if (!is_null($dn)) {
251:             if (is_null($this->_dn)) {
252:                 $this->_dn = $dn;
253:             } else {
254:                 $this->_newdn = $dn;
255:             }
256:             return $dn;
257:         }
258:         return isset($this->_newdn) ? $this->_newdn : $this->currentDN();
259:     }
260: 
261:     /**
262:      * Sets the internal attributes array.
263:      *
264:      * This method fetches the values for the attributes from the server.  The
265:      * attribute syntax will be checked so binary attributes will be returned
266:      * as binary values.
267:      *
268:      * Attributes may be passed directly via the $attributes parameter to setup
269:      * this entry manually. This overrides attribute fetching from the server.
270:      *
271:      * @param array $attributes Attributes to set for this entry.
272:      */
273:     protected function _loadAttributes(array $attributes = null)
274:     {
275:         /* Fetch attributes from the server. */
276:         if (is_null($attributes) &&
277:             is_resource($this->_entry) &&
278:             is_resource($this->_link)) {
279:             /* Fetch schema. */
280:             if ($this->_ldap instanceof Horde_Ldap) {
281:                 try {
282:                     $schema = $this->_ldap->schema();
283:                 } catch (Horde_Ldap_Exception $e) {
284:                     $schema = null;
285:                 }
286:             }
287: 
288:             /* Fetch attributes. */
289:             $attributes = array();
290:             for ($attr = @ldap_first_attribute($this->_link, $this->_entry);
291:                  $attr;
292:                  $attr = @ldap_next_attribute($this->_link, $this->_entry)) {
293:                 /* Standard function to fetch value. */
294:                 $func = 'ldap_get_values';
295: 
296:                 /* Try to get binary values as binary data. */
297:                 if ($schema instanceof Horde_Ldap_Schema &&
298:                     $schema->isBinary($attr)) {
299:                     $func = 'ldap_get_values_len';
300:                 }
301: 
302:                 /* Fetch attribute value (needs error checking?) . */
303:                 $attributes[$attr] = $func($this->_link, $this->_entry, $attr);
304:             }
305:         }
306: 
307:         /* Set attribute data directly, if passed. */
308:         if (is_array($attributes) && count($attributes) > 0) {
309:             if (isset($attributes['count']) &&
310:                 is_numeric($attributes['count'])) {
311:                 unset($attributes['count']);
312:             }
313:             foreach ($attributes as $k => $v) {
314:                 /* Attribute names should not be numeric. */
315:                 if (is_numeric($k)) {
316:                     continue;
317:                 }
318: 
319:                 /* Map generic attribute name to real one. */
320:                 $this->_map[Horde_String::lower($k)] = $k;
321: 
322:                 /* Attribute values should be in an array. */
323:                 if (false == is_array($v)) {
324:                     $v = array($v);
325:                 }
326: 
327:                 /* Remove the value count (comes from LDAP server). */
328:                 if (isset($v['count'])) {
329:                     unset($v['count']);
330:                 }
331:                 $this->_attributes[$k] = $v;
332:             }
333:         }
334: 
335:         /* Save a copy for later use. */
336:         $this->_original = $this->_attributes;
337:     }
338: 
339:     /**
340:      * Returns the values of all attributes in a hash.
341:      *
342:      * The returned hash has the form
343:      * <code>
344:      * array('attributename' => 'single value',
345:      *       'attributename' => array('value1', value2', value3'))
346:      * </code>
347:      *
348:      * @return array Hash of all attributes with their values.
349:      * @throws Horde_Ldap_Exception
350:      */
351:     public function getValues()
352:     {
353:         $attrs = array();
354:         foreach (array_keys($this->_attributes) as $attr) {
355:             $attrs[$attr] = $this->getValue($attr);
356:         }
357:         return $attrs;
358:     }
359: 
360:     /**
361:      * Returns the value of a specific attribute.
362:      *
363:      * The first parameter is the name of the attribute.
364:      *
365:      * The second parameter influences the way the value is returned:
366:      * - 'single': only the first value is returned as string.
367:      * - 'all': all values are returned in an array.
368:      * In all other cases an attribute value with a single value is returned as
369:      * string, if it has multiple values it is returned as an array.
370:      *
371:      * @param string $attr   Attribute name.
372:      * @param string $option Option.
373:      *
374:      * @return string|array Attribute value(s).
375:      * @throws Horde_Ldap_Exception
376:      */
377:     public function getValue($attr, $option = null)
378:     {
379:         $attr = $this->_getAttrName($attr);
380: 
381:         if (!array_key_exists($attr, $this->_attributes)) {
382:             throw new Horde_Ldap_Exception('Unknown attribute (' . $attr . ') requested');
383:         }
384: 
385:         $value = $this->_attributes[$attr];
386: 
387:         if ($option == 'single' || (count($value) == 1 && $option != 'all')) {
388:             $value = array_shift($value);
389:         }
390: 
391:         return $value;
392:     }
393: 
394:     /**
395:      * Returns an array of attributes names.
396:      *
397:      * @return array Array of attribute names.
398:      */
399:     public function attributes()
400:     {
401:         return array_keys($this->_attributes);
402:     }
403: 
404:     /**
405:      * Returns whether an attribute exists or not.
406:      *
407:      * @param string $attr Attribute name.
408:      *
409:      * @return boolean True if the attribute exists.
410:      */
411:     public function exists($attr)
412:     {
413:         $attr = $this->_getAttrName($attr);
414:         return array_key_exists($attr, $this->_attributes);
415:     }
416: 
417:     /**
418:      * Adds new attributes or a new values to existing attributes.
419:      *
420:      * The paramter has to be an array of the form:
421:      * <code>
422:      * array('attributename' => 'single value',
423:      *       'attributename' => array('value1', 'value2'))
424:      * </code>
425:      *
426:      * When the attribute already exists the values will be added, otherwise
427:      * the attribute will be created. These changes are local to the entry and
428:      * do not affect the entry on the server until update() is called.
429:      *
430:      * You can add values of attributes that you haven't originally selected,
431:      * but if you do so, {@link getValue()} and {@link getValues()} will only
432:      * return the values you added, *NOT* all values present on the server. To
433:      * avoid this, just refetch the entry after calling {@link update()} or
434:      * select the attribute.
435:      *
436:      * @param array $attr Attributes to add.
437:      */
438:     public function add(array $attr = array())
439:     {
440:         foreach ($attr as $k => $v) {
441:             $k = $this->_getAttrName($k);
442:             if (!is_array($v)) {
443:                 /* Do not add empty values. */
444:                 if ($v == null) {
445:                     continue;
446:                 } else {
447:                     $v = array($v);
448:                 }
449:             }
450: 
451:             /* Add new values to existing attribute or add new attribute. */
452:             if ($this->exists($k)) {
453:                 $this->_attributes[$k] = array_unique(array_merge($this->_attributes[$k], $v));
454:             } else {
455:                 $this->_map[Horde_String::lower($k)] = $k;
456:                 $this->_attributes[$k]      = $v;
457:             }
458: 
459:             /* Save changes for update(). */
460:             if (empty($this->_changes['add'][$k])) {
461:                 $this->_changes['add'][$k] = array();
462:             }
463:             $this->_changes['add'][$k] = array_unique(array_merge($this->_changes['add'][$k], $v));
464:         }
465:     }
466: 
467:     /**
468:      * Deletes an attribute, a value or the whole entry.
469:      *
470:      * The parameter can be one of the following:
471:      *
472:      * - 'attributename': the attribute as a whole will be deleted.
473:      * - array('attributename1', 'attributename2'): all specified attributes
474:      *                                              will be deleted.
475:      * - array('attributename' => 'value'): the specified attribute value will
476:      *                                      be deleted.
477:      * - array('attributename' => array('value1', 'value2'): The specified
478:      *                                                       attribute values
479:      *                                                       will be deleted.
480:      * - null: the whole entry will be deleted.
481:      *
482:      * These changes are local to the entry and do not affect the entry on the
483:      * server until {@link update()} is called.
484:      *
485:      * You must select the attribute (at $ldap->search() for example) to be
486:      * able to delete values of it, Otherwise {@link update()} will silently
487:      * fail and remove nothing.
488:      *
489:      * @param string|array $attr Attributes to delete.
490:      */
491:     public function delete($attr = null)
492:     {
493:         if (is_null($attr)) {
494:             $this->_delete = true;
495:             return;
496:         }
497: 
498:         if (is_string($attr)) {
499:             $attr = array($attr);
500:         }
501: 
502:         /* Make the assumption that attribute names cannot be numeric,
503:          * therefore this has to be a simple list of attribute names to
504:          * delete. */
505:         reset($attr);
506:         if (is_numeric(key($attr))) {
507:             foreach ($attr as $name) {
508:                 if (is_array($name)) {
509:                     /* Mixed modes (list mode but specific values given!). */
510:                     $del_attr_name = array_search($name, $attr);
511:                     $this->delete(array($del_attr_name => $name));
512:                 } else {
513:                     /* Mark for update() if this attribute was not marked
514:                      before. */
515:                     $name = $this->_getAttrName($name);
516:                     if ($this->exists($name)) {
517:                         $this->_changes['delete'][$name] = null;
518:                         unset($this->_attributes[$name]);
519:                     }
520:                 }
521:             }
522:         } else {
523:             /* We have a hash with 'attributename' => 'value to delete'. */
524:             foreach ($attr as $name => $values) {
525:                 if (is_int($name)) {
526:                     /* Mixed modes and gave us just an attribute name. */
527:                     $this->delete($values);
528:                 } else {
529:                     /* Mark for update() if this attribute was not marked
530:                      * before; this time it must consider the selected values
531:                      * too. */
532:                     $name = $this->_getAttrName($name);
533:                     if ($this->exists($name)) {
534:                         if (!is_array($values)) {
535:                             $values = array($values);
536:                         }
537:                         /* Save values to be deleted. */
538:                         if (empty($this->_changes['delete'][$name])) {
539:                             $this->_changes['delete'][$name] = array();
540:                         }
541:                         $this->_changes['delete'][$name] =
542:                             array_unique(array_merge($this->_changes['delete'][$name], $values));
543:                         foreach ($values as $value) {
544:                             /* Find the key for the value that should be
545:                              * deleted. */
546:                             $key = array_search($value, $this->_attributes[$name]);
547:                             if (false !== $key) {
548:                                 /* Delete the value. */
549:                                 unset($this->_attributes[$name][$key]);
550:                             }
551:                         }
552:                     }
553:                 }
554:             }
555:         }
556:     }
557: 
558:     /**
559:      * Replaces attributes or their values.
560:      *
561:      * The parameter has to an array of the following form:
562:      * <code>
563:      * array('attributename' => 'single value',
564:      *       'attribute2name' => array('value1', 'value2'),
565:      *       'deleteme1' => null,
566:      *       'deleteme2' => '')
567:      * </code>
568:      *
569:      * If the attribute does not yet exist it will be added instead (see also
570:      * $force). If the attribue value is null, the attribute will de deleted.
571:      *
572:      * These changes are local to the entry and do not affect the entry on the
573:      * server until {@link update()} is called.
574:      *
575:      * In some cases you are not allowed to read the attributes value (for
576:      * example the ActiveDirectory attribute unicodePwd) but are allowed to
577:      * replace the value. In this case replace() would assume that the
578:      * attribute is not in the directory yet and tries to add it which will
579:      * result in an LDAP_TYPE_OR_VALUE_EXISTS error. To force replace mode
580:      * instead of add, you can set $force to true.
581:      *
582:      * @param array   $attr  Attributes to replace.
583:      * @param boolean $force Force replacing mode in case we can't read the
584:      *                       attribute value but are allowed to replace it.
585:      */
586:     public function replace(array $attr = array(), $force = false)
587:     {
588:         foreach ($attr as $k => $v) {
589:             $k = $this->_getAttrName($k);
590:             if (!is_array($v)) {
591:                 /* Delete attributes with empty values; treat integers as
592:                  * string. */
593:                 if (is_int($v)) {
594:                     $v = (string)$v;
595:                 }
596:                 if ($v == null) {
597:                     $this->delete($k);
598:                     continue;
599:                 } else {
600:                     $v = array($v);
601:                 }
602:             }
603:             /* Existing attributes will get replaced. */
604:             if ($this->exists($k) || $force) {
605:                 $this->_changes['replace'][$k] = $v;
606:                 $this->_attributes[$k]         = $v;
607:             } else {
608:                 /* New ones just get added. */
609:                 $this->add(array($k => $v));
610:             }
611:         }
612:     }
613: 
614:     /**
615:      * Updates the entry on the directory server.
616:      *
617:      * This will evaluate all changes made so far and send them to the
618:      * directory server.
619:      *
620:      * If you make changes to objectclasses wich have mandatory attributes set,
621:      * update() will currently fail. Remove the entry from the server and readd
622:      * it as new in such cases. This also will deal with problems with setting
623:      * structural object classes.
624:      *
625:      * @todo Entry rename with a DN containing special characters needs testing!
626:      *
627:      * @throws Horde_Ldap_Exception
628:      */
629:     public function update()
630:     {
631:         /* Ensure we have a valid LDAP object. */
632:         $ldap = $this->getLDAP();
633: 
634:         /* Get and check link. */
635:         $link = $ldap->getLink();
636:         if (!is_resource($link)) {
637:             throw new Horde_Ldap_Exception('Could not update entry: internal LDAP link is invalid');
638:         }
639: 
640:         /* Delete the entry. */
641:         if ($this->_delete) {
642:             return $ldap->delete($this);
643:         }
644: 
645:         /* New entry. */
646:         if ($this->_new) {
647:             $ldap->add($this);
648:             $this->_new                = false;
649:             $this->_changes['add']     = array();
650:             $this->_changes['delete']  = array();
651:             $this->_changes['replace'] = array();
652:             $this->_original           = $this->_attributes;
653:             return;
654:         }
655: 
656:         /* Rename/move entry. */
657:         if (!is_null($this->_newdn)) {
658:             if ($ldap->getVersion() != 3) {
659:                 throw new Horde_Ldap_Exception('Renaming/Moving an entry is only supported in LDAPv3');
660:             }
661:             /* Make DN relative to parent (needed for LDAP rename). */
662:             $parent = Horde_Ldap_Util::explodeDN($this->_newdn, array('casefolding' => 'none', 'reverse' => false, 'onlyvalues' => false));
663:             $child = array_shift($parent);
664: 
665:             /* Maybe the DN consist of a multivalued RDN, we must build the DN
666:              * in this case because the $child RDN is an array. */
667:             if (is_array($child)) {
668:                 $child = Horde_Ldap_Util::canonicalDN($child);
669:             }
670:             $parent = Horde_Ldap_Util::canonicalDN($parent);
671: 
672:             /* Rename/move. */
673:             if (!@ldap_rename($link, $this->_dn, $child, $parent, true)) {
674:                 throw new Horde_Ldap_Exception('Entry not renamed: ' . @ldap_error($link), @ldap_errno($link));
675:             }
676: 
677:             /* Reflect changes to local copy. */
678:             $this->_dn    = $this->_newdn;
679:             $this->_newdn = null;
680:         }
681: 
682:         /* Carry out modifications to the entry. */
683:         foreach ($this->_changes['add'] as $attr => $value) {
684:             /* If attribute exists, add new values. */
685:             if ($this->exists($attr)) {
686:                 if (!@ldap_mod_add($link, $this->dn(), array($attr => $value))) {
687:                     throw new Horde_Ldap_Exception('Could not add new values to attribute ' . $attr . ': ' . @ldap_error($link), @ldap_errno($link));
688:                 }
689:             } else {
690:                 /* New attribute. */
691:                 if (!@ldap_modify($link, $this->dn(), array($attr => $value))) {
692:                     throw new Horde_Ldap_Exception('Could not add new attribute ' . $attr . ': ' . @ldap_error($link), @ldap_errno($link));
693:                 }
694:             }
695:             unset($this->_changes['add'][$attr]);
696:         }
697: 
698:         foreach ($this->_changes['delete'] as $attr => $value) {
699:             /* In LDAPv3 you need to specify the old values for deleting. */
700:             if (is_null($value) && $ldap->getVersion() == 3) {
701:                 $value = $this->_original[$attr];
702:             }
703:             if (!@ldap_mod_del($link, $this->dn(), array($attr => $value))) {
704:                 throw new Horde_Ldap_Exception('Could not delete attribute ' . $attr . ': ' . @ldap_error($link), @ldap_errno($link));
705:             }
706:             unset($this->_changes['delete'][$attr]);
707:         }
708: 
709:         foreach ($this->_changes['replace'] as $attr => $value) {
710:             if (!@ldap_modify($link, $this->dn(), array($attr => $value))) {
711:                 throw new Horde_Ldap_Exception('Could not replace attribute ' . $attr . ' values: ' . @ldap_error($link), @ldap_errno($link));
712:             }
713:             unset($this->_changes['replace'][$attr]);
714:         }
715: 
716:         /* All went well, so $_attributes (local copy) becomes $_original
717:          * (server). */
718:         $this->_original = $this->_attributes;
719:     }
720: 
721:     /**
722:      * Returns the right attribute name.
723:      *
724:      * @param string $attr Name of attribute.
725:      *
726:      * @return string The right name of the attribute
727:      */
728:     protected function _getAttrName($attr)
729:     {
730:         $name = Horde_String::lower($attr);
731:         return isset($this->_map[$name]) ? $this->_map[$name] : $attr;
732:     }
733: 
734:     /**
735:      * Returns a reference to the LDAP-Object of this entry.
736:      *
737:      * @return Horde_Ldap  Reference to the Horde_Ldap object (the connection).
738:      * @throws Horde_Ldap_Exception
739:      */
740:     public function getLDAP()
741:     {
742:         if (!($this->_ldap instanceof Horde_Ldap)) {
743:             throw new Horde_Ldap_Exception('ldap property is not a valid Horde_Ldap object');
744:         }
745:         return $this->_ldap;
746:     }
747: 
748:     /**
749:      * Sets a reference to the LDAP object of this entry.
750:      *
751:      * After setting a Horde_Ldap object, calling update() will use that object
752:      * for updating directory contents. Use this to dynamicly switch
753:      * directories.
754:      *
755:      * @param Horde_Ldap $ldap  Horde_Ldap object that this entry should be
756:      *                          connected to.
757:      *
758:      * @throws Horde_Ldap_Exception
759:      */
760:     public function setLDAP(Horde_Ldap $ldap)
761:     {
762:         $this->_ldap = $ldap;
763:     }
764: 
765:     /**
766:      * Marks the entry as new or existing.
767:      *
768:      * If an entry is marked as new, it will be added to the directory when
769:      * calling {@link update()}.
770:      *
771:      * If the entry is marked as old ($mark = false), then the entry is assumed
772:      * to be present in the directory server wich results in modification when
773:      * calling {@link update()}.
774:      *
775:      * @param boolean $mark Whether to mark the entry as new.
776:      */
777:     public function markAsNew($mark = true)
778:     {
779:         $this->_new = (bool)$mark;
780:     }
781: 
782:     /**
783:      * Applies a regular expression onto a single- or multi-valued attribute
784:      * (like preg_match()).
785:      *
786:      * This method behaves like PHP's preg_match() but with some exception.
787:      * Since it is possible to have multi valued attributes the $matches
788:      * array will have a additionally numerical dimension (one for each value):
789:      * <code>
790:      * $matches = array(
791:      *         0 => array (usual preg_match() returned array),
792:      *         1 => array (usual preg_match() returned array)
793:      * )
794:      * </code>
795:      * $matches will always be initialized to an empty array inside.
796:      *
797:      * Usage example:
798:      * <code>
799:      * try {
800:      *     if ($entry->pregMatch('/089(\d+)/', 'telephoneNumber', $matches)) {
801:      *         // Match of value 1, content of first bracket
802:      *         echo 'First match: ' . $matches[0][1];
803:      *     } else {
804:      *         echo 'No match found.';
805:      *     }
806:      * } catch (Horde_Ldap_Exception $e) {
807:      *     echo 'Error: ' . $e->getMessage();
808:      * }
809:      * </code>
810:      *
811:      * @param string $regex     The regular expression.
812:      * @param string $attr_name The attribute to search in.
813:      * @param array  $matches   Array to store matches in.
814:      *
815:      * @return boolean  True if we had a match in one of the values.
816:      * @throws Horde_Ldap_Exception
817:      */
818:     public function pregMatch($regex, $attr_name, &$matches = array())
819:     {
820:         /* Fetch attribute values. */
821:         $attr = $this->getValue($attr_name, 'all');
822:         unset($attr['count']);
823: 
824:         /* Perform preg_match() on all values. */
825:         $match = false;
826:         foreach ($attr as $thisvalue) {
827:             if (preg_match($regex, $thisvalue, $matches_int)) {
828:                 $match = true;
829:                 array_push($matches, $matches_int);
830:             }
831:         }
832: 
833:         return $match;
834:     }
835: 
836:     /**
837:      * Returns whether the entry is considered new (not present in the server).
838:      *
839:      * This method doesn't tell you if the entry is really not present on the
840:      * server. Use {@link Horde_Ldap::exists()} to see if an entry is already
841:      * there.
842:      *
843:      * @return boolean  True if this is considered a new entry.
844:      */
845:     public function isNew()
846:     {
847:         return $this->_new;
848:     }
849: 
850:     /**
851:      * Is this entry going to be deleted once update() is called?
852:      *
853:      * @return boolean  True if this entry is going to be deleted.
854:      */
855:     public function willBeDeleted()
856:     {
857:         return $this->_delete;
858:     }
859: 
860:     /**
861:      * Is this entry going to be moved once update() is called?
862:      *
863:      * @return boolean  True if this entry is going to be move.
864:      */
865:     public function willBeMoved()
866:     {
867:         return $this->dn() !== $this->currentDN();
868:     }
869: 
870:     /**
871:      * Returns always the original DN.
872:      *
873:      * If an entry will be moved but {@link update()} was not called, {@link
874:      * dn()} will return the new DN. This method however, returns always the
875:      * current active DN.
876:      *
877:      * @return string  The current DN
878:      */
879:     public function currentDN()
880:     {
881:         return $this->_dn;
882:     }
883: 
884:     /**
885:      * Returns the attribute changes to be carried out once update() is called.
886:      *
887:      * @return array  The due changes.
888:      */
889:     public function getChanges()
890:     {
891:         return $this->_changes;
892:     }
893: }
894: 
API documentation generated by ApiGen