Entry.php 36 KB

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