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: