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-2017 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 */ 16class 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 $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