LSldap :: update() : Fixed notice PHP
[ldapsaisie.git] / public_html / includes / class / class.LSldap.php
1 <?php
2 /*******************************************************************************
3  * Copyright (C) 2007 Easter-eggs
4  * http://ldapsaisie.labs.libre-entreprise.org
5  *
6  * Author: See AUTHORS file in top-level directory.
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License version 2
10  * as published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20
21 ******************************************************************************/
22
23 /**
24  * Gestion de l'accès à l'annaire Ldap
25  *
26  * Cette classe gère l'accès à l'annuaire ldap en s'appuyant sur PEAR :: Net_LDAP2
27  *
28  * @author Benjamin Renard <brenard@easter-eggs.com>
29  */
30 class LSldap {
31
32   private static $config;
33   private static $cnx = NULL;
34   
35   /**
36    * Défini la configuration
37    *
38    * Cette methode définis la configuration de l'accès à l'annuaire
39    *
40    * @author Benjamin Renard <brenard@easter-eggs.com>
41    *
42    * @param[in] $config array Tableau de configuration au formar Net_LDAP2
43    *
44    * @retval void
45    */
46   function setConfig ($config) {
47     self :: $config = $config;
48   }
49   
50   /**
51    * Connection
52    *
53    * Cette methode établie la connexion à l'annuaire Ldap
54    *
55    * @author Benjamin Renard <brenard@easter-eggs.com>
56    * 
57    * @param[in] $config array Tableau de configuration au formar Net_LDAP2
58    *
59    * @retval boolean true si la connection est établie, false sinon
60    */
61   public static function connect($config = null) {
62     if ($config) {
63       self :: setConfig($config);
64     }
65     self :: $cnx = Net_LDAP2::connect(self :: $config);
66     if (Net_LDAP2::isError(self :: $cnx)) {
67       LSerror :: addErrorCode('LSldap_01',self :: $cnx -> getMessage());
68       self :: $cnx = NULL;
69       return;
70     }
71     return true;
72   }
73   
74   /**
75    * Déconnection
76    *
77    * Cette methode clos la connexion à l'annuaire Ldap
78    *
79    * @author Benjamin Renard <brenard@easter-eggs.com>
80    *
81    * @retval void
82    */
83   public static function close() {
84     self :: $cnx -> done();
85   }
86   
87   /**
88    * Recherche dans l'annuaire
89    *
90    * Cette methode effectue une recherche dans l'annuaire et retourne le resultat
91    * de celle-ci sous la forme d'un tableau.
92    *
93    * @author Benjamin Renard <brenard@easter-eggs.com>
94    *
95    * @param[in] $filter [<b>required</b>] string Filtre de recherche Ldap
96    * @param[in] $basedn string DN de base pour la recherche
97    * @param[in] $params array Paramètres de recherche au format Net_LDAP2::search()
98    *
99    * @see Net_LDAP2::search()
100    *
101    * @retval array Retourne un tableau associatif contenant :
102    *               - ['dn'] : le DN de l'entré
103    *               - ['attrs'] : tableau associatif contenant les attributs (clé)
104    *                             et leur valeur (valeur).
105    */
106   public static function search ($filter,$basedn=NULL,$params = array()) {
107     $ret = self :: $cnx -> search($basedn,$filter,$params);
108     if (Net_LDAP2::isError($ret)) {
109       LSerror :: addErrorCode('LSldap_02',$ret -> getMessage());
110       return;
111     }
112     $retInfos=array();
113     foreach($ret -> entries() as $entry) {
114       $retInfos[]=array('dn' => $entry -> dn(), 'attrs' => $entry -> getValues());
115     }
116     return $retInfos;
117   }
118   
119   /**
120    * Compte le nombre de retour d'une recherche dans l'annuaire
121    *
122    * Cette methode effectue une recherche dans l'annuaire et retourne le nombre
123    * d'entrés trouvées.
124    *
125    * @author Benjamin Renard <brenard@easter-eggs.com>
126    *
127    * @param[in] $filter [<b>required</b>] string Filtre de recherche Ldap
128    * @param[in] $basedn string DN de base pour la recherche
129    * @param[in] $params array Paramètres de recherche au format Net_LDAP2::search()
130    *
131    * @see Net_LDAP2::search()
132    *
133    * @retval numeric Le nombre d'entré trouvées
134    */
135   public static function getNumberResult ($filter,$basedn=NULL,$params = array() ) {
136     if (empty($filter))
137       $filter=NULL;
138     $ret = self :: $cnx -> search($basedn,$filter,$params);
139     if (Net_LDAP2::isError($ret)) {
140       LSerror :: addErrorCode('LSldap_02',$ret -> getMessage());
141       return;
142     }
143     return $ret -> count();
144   }
145   
146   /**
147    * Charge les valeurs des attributs d'une entré de l'annuaire
148    *
149    * Cette methode recupère les valeurs des attributs d'une entrée de l'annaire
150    * et les retournes sous la forme d'un tableau.
151    *
152    * @author Benjamin Renard <brenard@easter-eggs.com>
153    *
154    * @param[in] $dn string DN de l'entré Ldap
155    *
156    * @retval array Tableau associatif des valeurs des attributs avec en clef, le nom de l'attribut.
157    */
158   public static function getAttrs($dn) {
159     $infos = ldap_explode_dn($dn,0);
160     if((!$infos)||($infos['count']==0))
161       return;
162     $basedn='';
163     for ($i=1;$i<$infos['count'];$i++) {
164       $sep=($basedn=='')?'':',';
165       $basedn.=$sep.$infos[$i];
166     }
167     $return=self :: search($infos[0],$basedn);
168     return $return[0]['attrs'];
169   }
170   
171   /**
172    * Retourne une entrée existante ou nouvelle
173    *
174    * @author Benjamin Renard <brenard@easter-eggs.com>
175    *
176    * @param[in] $object_type string Type de l'objet Ldap
177    * @param[in] $dn string DN de l'entré Ldap
178    *
179    * @retval ldapentry|array Un objet ldapentry (PEAR::Net_LDAP2)
180    *                         ou un tableau (si c'est une nouvelle entrée):
181    *                          Array (
182    *                            'entry' => ldapentry,
183    *                            'new' => true
184    *                          )
185    */
186   public static function getEntry($object_type,$dn) {
187     $obj_conf=LSconfig :: get('LSobjects.'.$object_type);
188     if(is_array($obj_conf)){
189       $entry = self :: getLdapEntry($dn);
190       if ($entry === false) {
191         $newentry = self :: getNewEntry($dn,$obj_conf['objectclass'],array());
192         
193         if (!$newentry) {
194           return;
195         }
196         return array('entry' => $newentry,'new' => true);
197       }
198       else {
199         return $entry;
200       }
201     }
202     else {
203       LSerror :: addErrorCode('LSldap_03');
204       return;
205     }
206   }
207   
208   /**
209    * Retourne un object NetLDAP d'une entree existante
210    *
211    * @author Benjamin Renard <brenard@easter-eggs.com>
212    *
213    * @param[in] $dn string DN de l'entré Ldap
214    *
215    * @retval ldapentry|boolean  Un objet ldapentry (PEAR::Net_LDAP2) ou false en
216    *                            cas de problème
217    */
218   public static function getLdapEntry($dn) {
219     $entry = self :: $cnx -> getEntry($dn);
220     if (Net_LDAP2::isError($entry)) {
221       return false;
222     }
223     else {
224       return $entry;
225     }
226   }
227   
228  /**
229   * Retourne une nouvelle entrée
230   * 
231   * @param[in] $dn string Le DN de l'objet
232   * @param[in] $objectClass array Un tableau contenant les objectClass de l'objet
233   * @param[in] $attrs array Un tabeau du type array('attr_name' => attr_value, ...)
234   * 
235   * @retval mixed Le nouvelle objet en cas de succès, false sinon
236   */
237   public static function getNewEntry($dn,$objectClass,$attrs,$add=false) {
238     $newentry = Net_LDAP2_Entry::createFresh($dn,array_merge(array('objectclass' =>$objectClass),(array)$attrs));
239     if(Net_LDAP2::isError($newentry)) {
240       return false;
241     }
242     if($add) {
243       if(!self :: $cnx -> add($newentry)) {
244         return;
245       }
246     }
247     return $newentry;
248   }
249   
250   /**
251    * Met à jour une entrée dans l'annuaire
252    * 
253    * Remarque : Supprime les valeurs vides de attributs et les attributs sans valeur.
254    *
255    * @author Benjamin Renard <brenard@easter-eggs.com>
256    *
257    * @param[in] $object_type string Type de l'objet Ldap
258    * @param[in] $dn string DN de l'entré Ldap
259    * @param[in] $change array Tableau des modifications à apporter
260    *
261    * @retval boolean true si l'objet a bien été mis à jour, false sinon
262    */
263   public static function update($object_type,$dn,$change) {
264     LSdebug($change);
265     $dropAttr=array();
266     $entry=self :: getEntry($object_type,$dn);
267     if (is_array($entry)) {
268       $new = $entry['new'];
269       $entry = $entry['entry'];
270     }
271     else {
272       $new = false;
273     }
274
275     if($entry) {
276       foreach($change as $attrName => $attrVal) {
277         $drop = true;
278         if (is_array($attrVal)) {
279           foreach($attrVal as $val) {
280             if (!empty($val)) {
281               $drop = false;
282               $changeData[$attrName][]=$val;
283             }
284           }
285         }
286         else {
287           if (!empty($attrVal)) {
288             $drop = false;
289             $changeData[$attrName][]=$attrVal;
290           }
291         }
292         if($drop) {
293           $dropAttr[] = $attrName;
294         }
295       }
296       if (isset($changeData)) {
297         $entry -> replace($changeData);
298         LSdebug('change : <pre>'.print_r($changeData,true).'</pre>');
299         LSdebug('drop : <pre>'.print_r($dropAttr,true).'</pre>');
300       }
301       else {
302         LSdebug('No change');
303       }
304
305       if ($new) {
306         LSdebug('LSldap :: add()');
307         $ret = self :: $cnx -> add($entry);
308       }
309       else {
310         LSdebug('LSldap :: update()');
311         $ret = $entry -> update();
312       }
313       
314       if (Net_LDAP2::isError($ret)) {
315         LSerror :: addErrorCode('LSldap_05',$dn);
316         LSerror :: addErrorCode(0,'NetLdap-Error : '.$ret->getMessage());
317       }
318       else {
319         if (!empty($dropAttr)) {
320           foreach($dropAttr as $attr) {
321             if(Net_LDAP2::isError($entry -> getValue($attr))) {
322               // Attribut n'existe pas dans l'annuaire
323               continue;
324             }
325             // Méthode buggé : suppression impossible de certain attribut
326             // exemple : jpegPhoto
327             // $entry -> delete($attr);
328             $entry -> replace(array($attr =>array()));
329           }
330           $ret = $entry -> update();
331           if (Net_LDAP2::isError($ret)) {
332             LSerror :: addErrorCode('LSldap_06');
333             LSerror :: addErrorCode(0,'NetLdap-Error : '.$ret->getMessage());
334           }
335         }
336         return true;
337       }
338     }
339     else {
340       LSerror :: addErrorCode('LSldap_04');
341       return;
342     }
343   }
344
345   /**
346    * Test de bind
347    *
348    * Cette methode établie une connexion à l'annuaire Ldap et test un bind
349    * avec un login et un mot de passe passé en paramètre
350    *
351    * @author Benjamin Renard <brenard@easter-eggs.com>
352    *
353    * @retval boolean true si la connection à réussi, false sinon
354    */
355   public static function checkBind($dn,$pwd) {
356     $config = self :: $config;
357     $config['binddn'] = $dn;
358     $config['bindpw'] = $pwd;
359     $cnx = Net_LDAP2::connect($config);
360     if (Net_LDAP2::isError($cnx)) {
361       return;
362     }
363     return true;
364   }
365
366   /**
367    * Retourne l'état de la connexion Ldap
368    *
369    * @retval boolean True si le serveur est connecté, false sinon.
370    */
371   public static function isConnected() {
372     return (self :: $cnx == NULL)?false:true;
373   }
374   
375   /**
376    * Supprime un objet de l'annuaire
377    *
378    * @param[in] string DN de l'objet à supprimer
379    * 
380    * @retval boolean True si l'objet à été supprimé, false sinon
381    */
382   public static function remove($dn) {
383     $ret = self :: $cnx -> delete($dn,array('recursive' => true));
384     if (Net_LDAP2::isError($ret)) {
385       LSerror :: addErrorCode(0,'NetLdap-Error : '.$ret->getMessage());
386       return;
387     }
388     return true;
389   }
390
391   /**
392    * Déplace un objet LDAP dans l'annuaire
393    * 
394    * @param[in] $old string Le DN actuel
395    * @param[in] $new string Le futur DN
396    * 
397    * @retval boolean True si l'objet a été déplacé, false sinon
398    */
399   public static function move($old,$new) {
400     $ret = self :: $cnx -> move($old,$new);
401     if (Net_LDAP2::isError($ret)) {
402       LSerror :: addErrorCode('LSldap_07');
403       LSerror :: addErrorCode(0,'NetLdap-Error : '.$ret->getMessage());
404       return;
405     }
406     return true;
407   }
408   
409   /**
410    * Combine LDAP Filters
411    * 
412    * @params array Array of LDAP filters
413    * 
414    * @retval Net_LDAP2_Filter | False The combined filter or False
415    **/
416   public static function combineFilters($op,$filters,$asStr=false) {
417     if (is_array($filters) && !empty($filters)) {
418       if (count($filters)==1) {
419         return $filters[0];
420       }
421       $filter=Net_LDAP2_Filter::combine($op,$filters);
422       if (!Net_LDAP2::isError($filter)) {
423         if ($asStr) {
424           return $filter->asString();
425         }
426         else {
427           return $filter;
428         }
429       }
430       else {
431         LSerror :: addErrorCode(0,$filter -> getMessage());
432       }
433     }
434     return;
435   }
436   
437   /**
438    * Check LDAP Filters String
439    * 
440    * @params string A LDAP filter as string
441    * 
442    * @retval boolean True only if the filter could be parsed
443    **/
444   public static function isValidFilter($filter) {
445     if (is_string($filter) && !empty($filter)) {
446       $filter=Net_LDAP2_Filter::parse($filter);
447       if (!Net_LDAP2::isError($filter)) {
448         return true;
449       }
450       else {
451         LSerror :: addErrorCode(0,$filter -> getMessage());
452       }
453     }
454     return;
455   }
456 }
457
458 /*
459  * Error Codes
460  */
461 LSerror :: defineError('LSldap_01',
462   _("LSldap : Error during the LDAP server connection (%{msg}).")
463 );
464 LSerror :: defineError('LSldap_02',
465   _("LSldap : Error during the LDAP search (%{msg}).")
466 );
467 LSerror :: defineError('LSldap_03',
468   _("LSldap : Object type unknown.")
469 );
470 LSerror :: defineError('LSldap_04',
471   _("LSldap : Error while fetching the LDAP entry.")
472 );
473 LSerror :: defineError('LSldap_05',
474   _("LSldap : Error while changing the LDAP entry (DN : %{dn}).")
475 );
476 LSerror :: defineError('LSldap_06',
477   _("LSldap : Error while deleting empty attributes.")
478 );
479 LSerror :: defineError('LSldap_07',
480   _("LSldap : Error while changing the DN of the object.")
481 );
482 ?>