Rename trunk directory to public_html
[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         $attributes = array();
192         foreach($obj_conf['attrs'] as $attr_name => $attr_conf) {
193           if( isset($attr_conf['default_value']) ) {
194             $attributes[$attr_name]=$attr_conf['default_value'];
195           }
196         }
197         
198         $newentry = self :: getNewEntry($dn,$obj_conf['objectclass'],$attributes);
199         
200         if (!$newentry) {
201           return;
202         }
203         return array('entry' => $newentry,'new' => true);
204       }
205       else {
206         return $entry;
207       }
208     }
209     else {
210       LSerror :: addErrorCode('LSldap_03');
211       return;
212     }
213   }
214   
215   /**
216    * Retourne un object NetLDAP d'une entree existante
217    *
218    * @author Benjamin Renard <brenard@easter-eggs.com>
219    *
220    * @param[in] $dn string DN de l'entré Ldap
221    *
222    * @retval ldapentry|boolean  Un objet ldapentry (PEAR::Net_LDAP2) ou false en
223    *                            cas de problème
224    */
225   public static function getLdapEntry($dn) {
226     $entry = self :: $cnx -> getEntry($dn);
227     if (Net_LDAP2::isError($entry)) {
228       return false;
229     }
230     else {
231       return $entry;
232     }
233   }
234   
235  /**
236   * Retourne une nouvelle entrée
237   * 
238   * @param[in] $dn string Le DN de l'objet
239   * @param[in] $objectClass array Un tableau contenant les objectClass de l'objet
240   * @param[in] $attrs array Un tabeau du type array('attr_name' => attr_value, ...)
241   * 
242   * @retval mixed Le nouvelle objet en cas de succès, false sinon
243   */
244   public static function getNewEntry($dn,$objectClass,$attrs,$add=false) {
245     $newentry = Net_LDAP2_Entry::createFresh($dn,array_merge(array('objectclass' =>$objectClass),(array)$attrs));
246     if(Net_LDAP2::isError($newentry)) {
247       return false;
248     }
249     if($add) {
250       if(!self :: $cnx -> add($newentry)) {
251         return;
252       }
253     }
254     return $newentry;
255   }
256   
257   /**
258    * Met à jour une entrée dans l'annuaire
259    * 
260    * Remarque : Supprime les valeurs vides de attributs et les attributs sans valeur.
261    *
262    * @author Benjamin Renard <brenard@easter-eggs.com>
263    *
264    * @param[in] $object_type string Type de l'objet Ldap
265    * @param[in] $dn string DN de l'entré Ldap
266    * @param[in] $change array Tableau des modifications à apporter
267    *
268    * @retval boolean true si l'objet a bien été mis à jour, false sinon
269    */
270   public static function update($object_type,$dn,$change) {
271     LSdebug($change);
272     $dropAttr=array();
273     $entry=self :: getEntry($object_type,$dn);
274     if (is_array($entry)) {
275       $new = $entry['new'];
276       $entry = $entry['entry'];
277     }
278     else {
279       $new = false;
280     }
281
282     if($entry) {
283       foreach($change as $attrName => $attrVal) {
284         $drop = true;
285         if (is_array($attrVal)) {
286           foreach($attrVal as $val) {
287             if (!empty($val)) {
288               $drop = false;
289               $changeData[$attrName][]=$val;
290             }
291           }
292         }
293         else {
294           if (!empty($attrVal)) {
295             $drop = false;
296             $changeData[$attrName][]=$attrVal;
297           }
298         }
299         if($drop) {
300           $dropAttr[] = $attrName;
301         }
302       }
303       $entry -> replace($changeData);
304       LSdebug('change : <pre>'.print_r($changeData,true).'</pre>');
305       LSdebug('drop : <pre>'.print_r($dropAttr,true).'</pre>');
306
307       if ($new) {
308         LSdebug('LSldap :: add()');
309         $ret = self :: $cnx -> add($entry);
310       }
311       else {
312         LSdebug('LSldap :: update()');
313         $ret = $entry -> update();
314       }
315       
316       if (Net_LDAP2::isError($ret)) {
317         LSerror :: addErrorCode('LSldap_05',$dn);
318         LSerror :: addErrorCode(0,'NetLdap-Error : '.$ret->getMessage());
319       }
320       else {
321         if (!empty($dropAttr)) {
322           foreach($dropAttr as $attr) {
323             if(Net_LDAP2::isError($entry -> getValue($attr))) {
324               // Attribut n'existe pas dans l'annuaire
325               continue;
326             }
327             // Méthode buggé : suppression impossible de certain attribut
328             // exemple : jpegPhoto
329             // $entry -> delete($attr);
330             $entry -> replace(array($attr =>array()));
331           }
332           $ret = $entry -> update();
333           if (Net_LDAP2::isError($ret)) {
334             LSerror :: addErrorCode('LSldap_06');
335             LSerror :: addErrorCode(0,'NetLdap-Error : '.$ret->getMessage());
336           }
337         }
338         return true;
339       }
340     }
341     else {
342       LSerror :: addErrorCode('LSldap_04');
343       return;
344     }
345   }
346
347   /**
348    * Test de bind
349    *
350    * Cette methode établie une connexion à l'annuaire Ldap et test un bind
351    * avec un login et un mot de passe passé en paramètre
352    *
353    * @author Benjamin Renard <brenard@easter-eggs.com>
354    *
355    * @retval boolean true si la connection à réussi, false sinon
356    */
357   public static function checkBind($dn,$pwd) {
358     $config = self :: $config;
359     $config['binddn'] = $dn;
360     $config['bindpw'] = $pwd;
361     $cnx = Net_LDAP2::connect($config);
362     if (Net_LDAP2::isError($cnx)) {
363       return;
364     }
365     return true;
366   }
367
368   /**
369    * Retourne l'état de la connexion Ldap
370    *
371    * @retval boolean True si le serveur est connecté, false sinon.
372    */
373   public static function isConnected() {
374     return (self :: $cnx == NULL)?false:true;
375   }
376   
377   /**
378    * Supprime un objet de l'annuaire
379    *
380    * @param[in] string DN de l'objet à supprimer
381    * 
382    * @retval boolean True si l'objet à été supprimé, false sinon
383    */
384   public static function remove($dn) {
385     $ret = self :: $cnx -> delete($dn,array('recursive' => true));
386     if (Net_LDAP2::isError($ret)) {
387       LSerror :: addErrorCode(0,'NetLdap-Error : '.$ret->getMessage());
388       return;
389     }
390     return true;
391   }
392
393   /**
394    * Déplace un objet LDAP dans l'annuaire
395    * 
396    * @param[in] $old string Le DN actuel
397    * @param[in] $new string Le futur DN
398    * 
399    * @retval boolean True si l'objet a été déplacé, false sinon
400    */
401   public static function move($old,$new) {
402     $ret = self :: $cnx -> move($old,$new);
403     if (Net_LDAP2::isError($ret)) {
404       LSerror :: addErrorCode('LSldap_07');
405       LSerror :: addErrorCode(0,'NetLdap-Error : '.$ret->getMessage());
406       return;
407     }
408     return true;
409   }
410   
411   /**
412    * Combine LDAP Filters
413    * 
414    * @params array Array of LDAP filters
415    * 
416    * @retval Net_LDAP2_Filter | False The combined filter or False
417    **/
418   public static function combineFilters($op,$filters,$asStr=false) {
419     if (is_array($filters) && !empty($filters)) {
420       if (count($filters)==1) {
421         return $filters[0];
422       }
423       $filter=Net_LDAP2_Filter::combine($op,$filters);
424       if (!Net_LDAP2::isError($filter)) {
425         if ($asStr) {
426           return $filter->asString();
427         }
428         else {
429           return $filter;
430         }
431       }
432     }
433     return;
434   }
435 }
436
437 /*
438  * Error Codes
439  */
440 LSerror :: defineError('LSldap_01',
441   _("LSldap : Error during the LDAP server connection (%{msg}).")
442 );
443 LSerror :: defineError('LSldap_02',
444   _("LSldap : Error during the LDAP search (%{msg}).")
445 );
446 LSerror :: defineError('LSldap_03',
447   _("LSldap : Object type unknown.")
448 );
449 LSerror :: defineError('LSldap_04',
450   _("LSldap : Error while fetching the LDAP entry.")
451 );
452 LSerror :: defineError('LSldap_05',
453   _("LSldap : Error while changing the LDAP entry (DN : %{dn}).")
454 );
455 LSerror :: defineError('LSldap_06',
456   _("LSldap : Error while deleting empty attributes.")
457 );
458 LSerror :: defineError('LSldap_07',
459   _("LSldap : Error while changing the DN of the object.")
460 );
461 ?>