LSldapobject / Custom Action : Fixed error message on custom action execution failed
[ldapsaisie.git] / public_html / includes / class / class.LSldapObject.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 LSsession :: loadLSclass('LSattribute');
24
25 /**
26  * Base d'un objet ldap
27  *
28  * Cette classe définis la base de tout objet ldap géré par LdapSaisie
29  *
30  * @author Benjamin Renard <brenard@easter-eggs.com>
31  */
32 class LSldapObject { 
33   
34   var $config = array();
35   var $type_name;
36   var $attrs = array();
37   var $forms;
38   var $view;
39   var $dn=false;
40   var $oldDn=false;
41   var $other_values=array();
42   var $submitError=true;
43   var $_whoami=NULL;
44   var $_LSrelationsCache=array();
45
46   var $_events=array();
47   var $_objectEvents=array();
48   
49   var $cache=array();
50   
51   /**
52    * Constructeur
53    *
54    * Cette methode construit l'objet et définis la configuration.
55    * Elle lance la construction du tableau d'attributs représentés par un objet LSattribute.
56    *
57    * @author Benjamin Renard <brenard@easter-eggs.com>
58    *
59    * @retval boolean true si l'objet a Ã©té construit, false sinon.
60    */ 
61   function LSldapObject() {
62     $this -> type_name = get_class($this);
63     $config = LSconfig :: get('LSobjects.'.$this -> type_name);
64     if(is_array($config)) {
65       $this -> config = $config;
66     }
67     else {
68       LSerror :: addErrorCode('LSldapObject_01');
69       return;
70     }
71     
72     foreach($this -> config['attrs'] as $attr_name => $attr_config) {
73       if(!$this -> attrs[$attr_name]=new LSattribute($attr_name,$attr_config,$this)) {
74         return;
75       }
76     }
77     
78     return true;
79   }
80   
81   /**
82    * Charge les données de l'objet
83    *
84    * Cette methode définis le DN de l'objet et charge les valeurs de attributs de l'objet
85    * Ã  partir de l'annuaire.
86    *
87    * @author Benjamin Renard <brenard@easter-eggs.com>
88    *
89    * @param[in] $dn string Le DN de l'objet.
90    *
91    * @retval boolean true si la chargement a réussi, false sinon.
92    */ 
93   function loadData($dn) {
94     $this -> dn = $dn;
95     $data = LSldap :: getAttrs($dn);
96     if(!empty($data)) {
97       foreach($this -> attrs as $attr_name => $attr) {
98         if( !$this -> attrs[$attr_name] -> loadData( (isset($data[$attr_name])?$data[$attr_name]:NULL) ) )
99           return;
100       }
101       $this->cache=array();
102       return true;
103     }
104     return;
105   }
106   
107   /**
108    * Recharge les données de l'objet
109    *
110    * @author Benjamin Renard <brenard@easter-eggs.com>
111    *
112    * @retval boolean true si la rechargement a réussi, false sinon.
113    */ 
114   function reloadData() {
115     $data = LSldap :: getAttrs($this -> dn);
116     foreach($this -> attrs as $attr_name => $attr) {
117       if(!$this -> attrs[$attr_name] -> reloadData( (isset($data[$attr_name])?$data[$attr_name]:NULL) ))
118         return;
119     }
120     return true;
121   }
122   
123   /**
124    * Retourne le format d'affichage de l'objet
125    *
126    * @author Benjamin Renard <brenard@easter-eggs.com>
127    *
128    * @retval string Format d'affichage de l'objet.
129    */ 
130   function getDisplayNameFormat() {
131     return $this -> config['display_name_format'];
132   }
133   
134   /**
135    * Retourne la valeur descriptive d'affichage de l'objet
136    * 
137    * Cette fonction retourne la valeur descriptive d'affichage de l'objet en fonction
138    * du format défini dans la configuration de l'objet ou spécifié en paramètre.
139    *
140    * @author Benjamin Renard <brenard@easter-eggs.com>
141    *
142    * @param[in] $spe [<i>optionnel</i>] string Format d'affichage de l'objet
143    * @param[in] $full [<i>optionnel</i>] boolean True pour afficher en plus le
144    *                                             subDnName
145    *
146    * @retval string Valeur descriptive d'affichage de l'objet
147    */ 
148   function getDisplayName($spe='',$full=false) {
149     if ($spe=='') {
150       $spe = $this -> getDisplayNameFormat();
151     }
152     $val = $this -> getFData($spe,&$this -> attrs,'getDisplayValue');
153     if (LSsession :: haveSubDn() && $full) {
154       $val.=' ('.$this -> subDnName.')';
155     }
156     return $val;
157   }
158   
159   /**
160    * Chaine formatée
161    * 
162    * Cette fonction retourne la valeur d'une chaine formatée en prennant les valeurs
163    * de l'objet.
164    *
165    * @author Benjamin Renard <brenard@easter-eggs.com>
166    *
167    * @param[in] $format string Format de la chaine
168    *
169    * @retval string Valeur d'une chaine formatée
170    */ 
171   function getFData($format) {
172     $format=getFData($format,$this,'getValue');
173     return $format;
174   }
175
176   /**
177    * Chaine formatee
178    *
179    * Cette fonction retourne la valeur d'une chaine formatee en prennant les valeurs
180    * d'affichage de l'objet.
181    *
182    * @author Benjamin Renard <brenard@easter-eggs.com>
183    *
184    * @param[in] $format string Format de la chaine
185    *
186    * @retval string Valeur d'une chaine formatee
187    */
188   function getDisplayFData($format) {
189     return getFData($format,$this,'getDisplayValue');
190   }
191   
192   /**
193    * Construit un formulaire de l'objet
194    * 
195    * Cette méthode construit un formulaire LSform Ã  partir de la configuration de l'objet
196    * et de chaque attribut.
197    *
198    * @param[in] $idForm [<b>required</b>] Identifiant du formulaire a créer
199    * @param[in] $load DN d'un objet similaire dont la valeur des attribut doit Ãªtre chargé dans le formulaire.
200    *
201    * @author Benjamin Renard <brenard@easter-eggs.com>
202    *
203    * @retval LSform Le formulaire crée
204    */ 
205   function getForm($idForm,$load=NULL) {
206     LSsession :: loadLSclass('LSform');
207     $LSform = new LSform($this,$idForm);
208     $this -> forms[$idForm] = array($LSform,$load);
209     
210     if ($load) {
211       $type = $this -> getType();
212       $loadObject = new $type();
213       if (!$loadObject -> loadData($load)) {
214         $load=false;
215       }
216     }
217     
218     if ($load) {
219       foreach($this -> attrs as $attr_name => $attr) {
220         if(!$this -> attrs[$attr_name] -> addToForm($LSform,$idForm,$this,$loadObject -> attrs[$attr_name] -> getFormVal())) {
221           $LSform -> can_validate = false;
222         }
223       }
224     }
225     else {
226       foreach($this -> attrs as $attr_name => $attr) {
227         if(!$this -> attrs[$attr_name] -> addToForm($LSform,$idForm,$this)) {
228           $LSform -> can_validate = false;
229         }
230       }      
231     }
232     return $LSform;
233   }
234   
235   /**
236    * Construit un formulaire de l'objet
237    * 
238    * Cette méthode construit un formulaire LSform Ã  partir de la configuration de l'objet
239    * et de chaque attribut.
240    *
241    * @param[in] $idForm [<b>required</b>] Identifiant du formulaire a créer
242    * @param[in] $config Configuration spécifique pour le formulaire
243    *
244    * @author Benjamin Renard <brenard@easter-eggs.com>
245    *
246    * @retval LSform Le formulaire crée
247    */ 
248   function getView() {
249     LSsession :: loadLSclass('LSform');
250     $this -> view = new LSform($this,'view');
251     foreach($this -> attrs as $attr_name => $attr) {
252       $this -> attrs[$attr_name] -> addToView($this -> view);
253     }
254     $this -> view -> can_validate = false;
255     return $this -> view;
256   }  
257   
258   /**
259    * Rafraichis le formulaire de l'objet
260    * 
261    * Cette méthode recharge les données d'un formulaire LSform.
262    *
263    * @param[in] $idForm [<b>required</b>] Identifiant du formulaire a créer
264    *
265    * @author Benjamin Renard <brenard@easter-eggs.com>
266    *
267    * @retval boolean true sile formulaire a Ã©té rafraichis, false sinon
268    */ 
269   function refreshForm($idForm) {
270     $LSform = $this -> forms[$idForm][0];
271     foreach($this -> attrs as $attr_name => $attr) {
272       if(!$this -> attrs[$attr_name] -> refreshForm($LSform,$idForm)) {
273         return;
274       }
275     }
276     return true;
277   }
278   
279   /**
280    * Met Ã  jour les données de l'objet Ã  partir d'un retour d'un formulaire.
281    *
282    * @param[in] $idForm Identifiant du formulaire d'origine
283    *
284    * @author Benjamin Renard <brenard@easter-eggs.com>
285    *
286    * @retval boolean true si la mise Ã  jour a réussi, false sinon
287    */ 
288   public function updateData($idForm=NULL) {
289     if($idForm!=NULL) {
290       if(isset($this -> forms[$idForm]))
291         $LSform = $this -> forms[$idForm][0];
292       else {
293         LSerror :: addErrorCode('LSldapObject_02',$this -> getType());
294         return;
295       }
296     }
297     else {
298       if(count($this -> forms) > 0) {
299         reset($this -> forms);
300         $idForm = key($this -> forms);
301         $LSform = current($this -> forms);
302         $config = $LSform[1];
303         $LSform = $LSform[0];
304       }
305       else {
306         LSerror :: addErrorCode('LSldapObject_03',$this -> getType());
307         return;
308       }
309     }
310     $new_data = $LSform -> exportValues();
311     return $this -> _updateData($new_data,$idForm);
312   }
313
314   /**
315    * Met Ã  jour les données de l'objet et de l'entré de l'annuaire
316    * 
317    * @param[in] $new_data Tableau des données de modification de l'objet
318    *
319    * @author Benjamin Renard <brenard@easter-eggs.com>
320    *
321    * @retval boolean true si la mise Ã  jour a réussi, false sinon
322    *
323    * @see validateAttrsData()
324    * @see submitChange()
325    */ 
326   private function _updateData($new_data,$idForm=null) {
327     if(!is_array($new_data)) {
328       return;
329     }
330     foreach($new_data as $attr_name => $attr_val) {
331       if(isset($this -> attrs[$attr_name])) {
332         $this -> attrs[$attr_name] -> setUpdateData($attr_val);
333       }
334     }
335     if($this -> validateAttrsData($idForm)) {
336       LSdebug("les données sont validées");
337       
338       if (!$this -> fireEvent('before_modify')) {
339         return;
340       }
341       
342       // $this -> attrs[ {inNewData} ] -> fireEvent('before_modify')
343       foreach($new_data as $attr_name => $attr_val) {
344         if (!$this -> attrs[$attr_name] -> fireEvent('before_modify')) {
345           return;
346         }
347       }
348       
349       if ($this -> submitChange($idForm)) {
350         LSdebug('Les modifications sont submitées');
351         $this -> submitError = false;
352         $this -> reloadData();
353         $this -> refreshForm($idForm);
354       }
355       else {
356         return;
357       }
358       
359       // Event After Modify
360       if(!$this -> submitError) {
361         $this -> fireEvent('after_modify');
362       }
363       
364       // $this -> attrs[*] => After Modify
365       foreach($new_data as $attr_name => $attr_val) {
366         $this -> attrs[$attr_name] -> fireEvent('after_modify');
367       }
368       return true;
369     }
370     else {
371       return;
372     }
373   }
374   
375   /**
376    * Valide les données retournées par un formulaire
377    *
378    * @param[in] $idForm Identifiant du formulaire d'origine
379    *
380    * @author Benjamin Renard <brenard@easter-eggs.com>
381    *
382    * @retval boolean true si les données sont valides, false sinon
383    */ 
384   function validateAttrsData($idForm=null) {
385     $retval = true;
386     if ($idForm) {
387       $LSform=$this -> forms[$idForm][0];
388     }
389     else {
390       $LSform=false;
391     }
392     foreach($this -> attrs as $attr) {
393       $attr_values = $attr -> getValue();
394       if (!$attr -> isValidate()) {
395         if($attr -> isUpdate()) {
396           if (!$this -> validateAttrData($LSform, $attr)) {
397             $retval = false;
398           }
399         }
400         else if( (empty($attr_values)) && ($attr -> isRequired()) ) { 
401           if ( $attr -> canBeGenerated()) {
402             if ($attr -> generateValue()) {
403               if (!$this -> validateAttrData($LSform, $attr)) {
404                 LSerror :: addErrorCode('LSattribute_08',$attr -> getLabel());
405                 $retval = false;
406               }
407             }
408             else {
409               LSerror :: addErrorCode('LSattribute_07',$attr -> getLabel());
410               $retval = false;
411             }
412           }
413           else {
414             LSerror :: addErrorCode('LSattribute_06',$attr -> getLabel());
415             $retval = false;
416           }
417         }
418       }
419     }
420     return $retval;
421   }
422
423    /**
424    * Valide les données d'un attribut
425    *
426    * @param[in] $LSForm Formulaire d'origine
427    * @param[in] &$attr Attribut Ã  valider
428    *
429    * @author Benjamin Renard <brenard@easter-eggs.com>
430    *
431    * @retval boolean true si les données sont valides, false sinon
432    */
433   function validateAttrData(&$LSform,&$attr) {
434     $retval = true;
435     
436     $vconfig=$attr -> getValidateConfig();
437
438     $data=$attr -> getUpdateData();
439     if(!is_array($data)) {
440       $data=array($data);
441     }
442
443     // Validation des valeurs de l'attribut
444     if(is_array($vconfig)) {
445       foreach($vconfig as $test) {
446         // Définition du basedn par défaut
447         if (!isset($test['basedn'])) {
448           $test['basedn']=LSsession :: getTopDn();
449         }
450
451         // Définition du message d'erreur
452         if (!empty($test['msg'])) {
453           $msg_error=getFData(__($test['msg']),$this,'getValue');
454         }
455         else {
456           $msg_error=getFData(_("The attribute %{attr} is not valid."),$attr -> getLabel());
457         }
458         foreach($data as $val) {
459           // validation par check LDAP
460           if((isset($test['filter'])||isset($test['basedn']))&&(isset($test['result']))) {
461             $sparams=(isset($test['scope']))?array('scope' => $test['scope']):array();
462             $this -> other_values['val']=$val;
463             $sfilter_user=(isset($test['filter']))?getFData($test['filter'],$this,'getValue'):NULL;
464             if(isset($test['object_type'])) {
465               $test_obj = new $test['object_type']();
466               $sfilter=$test_obj->getObjectFilter();
467               $sfilter='(&'.$sfilter;
468               if($sfilter_user[0]=='(') {
469                 $sfilter=$sfilter.$sfilter_user.')';
470               }
471               else {
472                 $sfilter=$sfilter.'('.$sfilter_user.'))';
473               }
474             }
475             else {
476               $sfilter=$sfilter_user;
477             }
478             $sbasedn=(isset($test['basedn']))?getFData($test['basedn'],$this,'getValue'):NULL;
479             $ret=LSldap :: getNumberResult ($sfilter,$sbasedn,$sparams);
480             if($test['result']==0) {
481               if($ret!=0) {
482                 if ($LSform) $LSform -> setElementError($attr,$msg_error);
483                 $retval = false;
484               }
485             }
486             else {
487               if($ret<0) {
488                 if ($LSform) $LSform -> setElementError($attr,$msg_error);
489                 $retval = false;
490               }
491             }
492           }
493           // Validation par fonction externe
494           else if(isset($test['function'])) {
495             if (function_exists($test['function'])) {
496               if(!$test['function']($this)) {
497                 if ($LSform) $LSform -> setElementError($attr,$msg_error);
498                 $retval = false;
499               }
500             }
501             else {
502               LSerror :: addErrorCode('LSldapObject_04',array('attr' => $attr->name,'obj' => $this->getType(),'func' => $test['function']));
503               $retval = false;
504             }
505           }
506           else {
507             LSerror :: addErrorCode('LSldapObject_05',array('attr' => $attr->name,'obj' => $this->getType()));
508             $retval = false;
509           }
510         }
511       }
512     }
513     // Génération des valeurs des attributs dépendants
514     $dependsAttrs=$attr->getDependsAttrs();
515     if (!empty($dependsAttrs)) {
516       foreach($dependsAttrs as $dependAttr) {
517         if(!isset($this -> attrs[$dependAttr])){
518           LSerror :: addErrorCode('LSldapObject_14',array('attr_depend' => $dependAttr, 'attr' => $attr -> getLabel()));
519           continue;
520         }
521         if($this -> attrs[$dependAttr] -> canBeGenerated()) {
522           if (!$this -> attrs[$dependAttr] -> generateValue()) {
523             LSerror :: addErrorCode('LSattribute_07',$this -> attrs[$dependAttr] -> getLabel());
524             $retval = false;
525           }
526         }
527         else {
528           LSerror :: addErrorCode('LSattribute_06',$this -> attrs[$dependAttr] -> getLabel());
529           $retval = false;
530         }
531       }
532     }
533
534     $attr -> validate();
535     unset($this -> other_values['val']);
536     return $retval;
537   }
538
539   /**
540    * Met Ã  jour les données modifiés dans l'annuaire
541    *
542    * @param[in] $idForm Identifiant du formulaire d'origine
543    *
544    * @author Benjamin Renard <brenard@easter-eggs.com>
545    *
546    * @retval boolean true si la mise Ã  jour a réussi, false sinon
547    */ 
548   function submitChange($idForm) {
549     $submitData=array();
550     $new = $this -> isNew();
551     foreach($this -> attrs as $attr) {
552       if(($attr -> isUpdate())&&($attr -> isValidate())) {
553         if(($attr -> name == $this -> config['rdn'])&&(!$new)) {
554           $new = true;
555           LSdebug('Rename');
556           if (!$this -> fireEvent('before_rename')) {
557             LSerror :: addErrorCode('LSldapObject_16');
558             return;
559           }
560           $oldDn = $this -> getDn();
561           $this -> dn = false;
562           $newDn = $this -> getDn();
563           if ($newDn) {
564             if (!LSldap :: move($oldDn,$newDn)) {
565               return;
566             }
567             $this -> dn = $newDn;
568             $this -> oldDn = $oldDn;
569             if (!$this -> fireEvent('after_rename')) {
570               LSerror :: addErrorCode('LSldapObject_17');
571               return;
572             }
573           }
574           else {
575             return;
576           }
577         }
578         else {
579           $submitData[$attr -> name] = $attr -> getUpdateData();
580         }
581       }
582     }
583     if(!empty($submitData)) {
584       $dn=$this -> getDn();
585       if($dn) {
586         $this -> dn=$dn;
587         LSdebug($submitData);
588         if ($new) {
589           if (!$this -> fireEvent('before_create')) {
590             LSerror :: addErrorCode('LSldapObject_20');
591             return;
592           }
593           foreach ($submitData as $attr_name => $attr) {
594             if (!$this -> attrs[$attr_name] -> fireEvent('before_create')) {
595               LSerror :: addErrorCode('LSldapObject_20');
596               return;
597             }
598           }
599         }
600         if (!LSldap :: update($this -> getType(),$dn, $submitData)) {
601           return;
602         }
603         if ($new) {
604           if (!$this -> fireEvent('after_create')) {
605             LSerror :: addErrorCode('LSldapObject_21');
606             return;
607           }
608           foreach ($submitData as $attr_name => $attr) {
609             if (!$this -> attrs[$attr_name] -> fireEvent('after_create')) {
610               LSerror :: addErrorCode('LSldapObject_21');
611               return;
612             }
613           }
614         }
615         return true;
616       }
617       else {
618         LSerror :: addErrorCode('LSldapObject_13');
619         return;
620       }
621     }
622     else {
623       return true;
624     }
625   }
626   
627   /**
628    * Retourne les informations issus d'un DN
629    *
630    * @param[in] $dn Un DN.
631    *
632    * @author Benjamin Renard <brenard@easter-eggs.com>
633    *
634    * @retval array Tableau : 
635    *                  - [0] : le premier paramètre
636    *                  - [1] : les paramètres suivants
637    */ 
638   function getDnInfos($dn) {
639     $infos=ldap_explode_dn($dn,0);
640     if(!$infos)
641       return;
642     $first=true;
643     for($i=1;$i<$infos['count'];$i++)
644       if($first) {
645         $basedn.=$infos[$i];
646         $first=false;
647       }
648       else
649         $basedn.=','.$infos[$i];
650     return array($infos[0],$basedn);
651   }
652   
653   /**
654    * Retourne le filtre correpondants aux objetcClass de l'objet
655    *
656    * @author Benjamin Renard <brenard@easter-eggs.com>
657    *
658    * @retval string le filtre ldap correspondant au type de l'objet
659    */ 
660   function getObjectFilter($type=null) {
661     if (is_null($type)) {
662       $type = $this -> type_name;
663     }
664     $oc=LSconfig::get("LSobjects.$type.objectclass");
665     if(!is_array($oc)) return;
666     $filters=array();
667     foreach ($oc as $class) {
668       $filters[]=Net_LDAP2_Filter::create('objectClass','equals',$class);
669     }
670     
671     $filter=LSconfig::get("LSobjects.$type.filter");
672     if ($filter) {
673       $filters[]=$filter;
674     }
675
676     $filter = LSldap::combineFilters('and',$filters,true);
677     if ($filter)
678       return $filter;
679     LSerror :: addErrorCode('LSldapObject_30',$type);
680     return;
681   }
682   
683   /**
684    * Retourne le filtre correpondants au pattern passé
685    * 
686    * @author Benjamin Renard <brenard@easter-eggs.com>
687    * 
688    * @param[in] $pattern string Le mot clé recherché
689    * @param[in] $approx booléen Booléen activant ou non la recherche approximative
690    *
691    * @retval string le filtre ldap correspondant
692    */ 
693   function getPatternFilter($pattern=null,$approx=null) {
694     if ($pattern!=NULL) {
695       if (is_array($this -> config['LSsearch']['attrs'])) {
696         $attrs=$this -> config['LSsearch']['attrs'];
697       }
698       else {
699         $attrs=array($this -> config['rdn']);
700       }
701       $pfilter='(|';
702       if ($approx) {
703         foreach ($attrs as $attr_name) {
704           $pfilter.='('.$attr_name.'~='.$pattern.')';
705         }
706       }
707       else {
708         foreach ($attrs as $attr_name) {
709           $pfilter.='('.$attr_name.'=*'.$pattern.'*)';
710         }
711       }
712       $pfilter.=')';
713       return $pfilter;
714     }
715     else {
716       return NULL;
717     }
718   }
719   
720   /**
721    * Retourne une liste d'objet du même type.
722    *
723    * Effectue une recherche en fonction des paramètres passé et retourne un
724    * tableau d'objet correspond au resultat de la recherche.
725    *
726    * @author Benjamin Renard <brenard@easter-eggs.com>
727    *
728    * @param[in] $filter array (ou string) Filtre de recherche Ldap / Tableau de filtres de recherche
729    * @param[in] $basedn string DN de base pour la recherche
730    * @param[in] $params array Paramètres de recherche au format Net_LDAP2::search()
731    *
732    * @retval array Tableau d'objets correspondant au resultat de la recherche
733    */ 
734   function listObjects($filter=NULL,$basedn=NULL,$params=array()) {
735     if (!LSsession :: loadLSclass('LSsearch')) {
736       LSerror::addErrorCode('LSsession_05','LSsearch');
737       return;
738     }
739     
740     $sparams = array(
741       'basedn' => $basedn,
742       'filter' => $filter,
743       'attributes' => array('dn')
744     );
745
746     if (is_array($params)) {    
747       $sparams=array_merge($sparams,$params);
748     }
749     $LSsearch = new LSsearch($this -> type_name,'LSldapObjet::listObjects',$sparams,true);
750     
751     $LSsearch -> run();
752     
753     return $LSsearch -> listObjects();
754     
755 /*
756     for($i=0;$i<count($ret);$i++) {
757       $retInfos[$i] = new $this -> type_name($this -> config);
758       $retInfos[$i] -> loadData($ret[$i]['dn']);
759     }
760     
761     return $retInfos;
762 */
763   }
764   
765   /**
766    * Recherche les objets du même type dans l'annuaire
767    *
768    * Effectue une recherche en fonction des paramètres passé et retourne un
769    * tableau array(dn => '', attrs => array()) d'objet correspondant au resultat*
770    * de la recherche.
771    *
772    * @author Benjamin Renard <brenard@easter-eggs.com>
773    *
774    * @param[in] $filter array (ou string) Filtre de recherche Ldap / Tableau de filtres de recherche
775    * @param[in] $basedn string DN de base pour la recherche
776    * @param[in] $params array Paramètres de recherche au format Net_LDAP2::search()
777    *
778    * @retval array Tableau d'objets correspondant au resultat de la recherche
779    */ 
780 /*
781   function search($filter='',$basedn=NULL,$params=array()) {
782     $retInfos=array();
783     $attrs=false;
784     $check_final_dn=false;
785
786     if(!is_array($filter))
787       $filter=array(array('filter' => $filter));
788     
789     $nbFilter=count($filter);
790
791     for($i=0;$i<$nbFilter;$i++) {
792       $new_attrs=array();
793       // Défintion des paramètres de base pour la recherche
794       $sbasedn=$basedn;
795       $sparams=$params;
796       $ret=array();
797       if (isset($filter[$i]['scope']))
798         $sparams["scope"]=$filter[$i]['scope'];
799       
800       // Definition des critères de recherche correspondant au type d'objet Ã  lister
801       if(($nbFilter==1)||(!isset($filter[$i]['attr']))) {
802         // Filtre sur l'objet souhaité
803         $sfilter='(&';
804         $sfilter.=$this -> getObjectFilter();
805         $sfilter_end=')';
806         $check_final_dn=true;
807       }
808       // Initialisation des critères d'une recherche intermédiaire
809       else {
810         if(isset($filter[$i]['object_type'])) {
811           $obj_tmp=new $filter[$i]['object_type']();
812           $obj_filter=$obj_tmp->getObjectFilter();
813           $sfilter='(&'.$obj_filter;
814           $sfilter_end=')';
815         }
816         else {
817           $sfilter='';
818           $sfilter_end='';
819         }
820         if(isset($filter[$i]['scope'])) {
821           $sparams['scope']=$filter[$i]['scope'];
822         }
823         if(isset($filter[$i]['basedn'])) {
824           $sbasedn=$filter[$i]['basedn'];
825         }
826       }
827       // Dans le cas d'une recherche intermédiaire ou finale
828       if($attrs!=false) {
829         // Initialisation des variables
830         $ret_gen=array();
831         $new_attrs=array();
832         
833         // Pour tout les attributs retournés
834         for($ii=0;$ii<count($attrs);$ii++) {
835           $sfilter_for='';
836           // Définition du filtre de recherche Ã  partir des paramètres utilisateurs et
837           // des paramètres de recherche de l'objet Ã  listé (dans le cas d'une recherche finale
838           if((isset($filter[$i]['filter']))&&(!empty($filter[$i]['filter']))) {
839             $sfilter_user=getFData($filter[$i]['filter'],$attrs[$ii]);
840             if($sfilter_user[0]=='(')
841               $sfilter_for=$sfilter.$sfilter_user;
842             else
843               $sfilter_for=$sfilter.'('.$sfilter_user.')';
844           }
845           else {
846             $sfilter_for=$sfilter;
847           }
848           
849           if(isset($filter[$i]['basedn'])) {
850             $sbasedn=getFData($filter[$i]['basedn'],$attrs[$ii]);
851             if ((!$this -> isCompatibleDNs($sbasedn,$basedn))&&($check_final_dn)) continue;
852           }
853         
854           // Vérification de la compatibilité du basedn de la recherche et du basedn générale
855           // Finalisation du filtre
856           $sfilter_for.=$sfilter_end;
857         
858         
859           // Attributes
860           if ($filter[$i]['attr']) {
861             $sparams['attributes'] = array($filter[$i]['attr']);
862           }
863           else if (!isset($sparams['attributes'])) {
864             $sparams['attributes'] = array($this -> config['rdn']);
865           }
866         
867           // Execution de la recherche
868           $ret=LSldap :: search ($sfilter_for,$sbasedn,$sparams);
869           
870           // Si il y un retour
871           if(isset($ret[0])) {
872             // si il ya une suite (recherche intermédiaire)
873             if($filter[$i]['attr']){
874               for($iii=0;$iii<count($ret);$iii++) {
875                 if(isset($ret[$iii]['attrs'][$filter[$i]['attr']])) {
876                   // cas de valeur multiple
877                   if(is_array($ret[$iii]['attrs'][$filter[$i]['attr']])) {
878                     foreach($ret[$iii]['attrs'][$filter[$i]['attr']] as $val_attr) {
879                       $new_attrs[]=$val_attr;
880                     }
881                   }
882                   // cas de valeur unique
883                   else {
884                     $new_attrs[]=$ret[$iii]['attrs'][$filter[$i]['attr']];
885                   }
886                 }
887               }
888             }
889           }
890         }
891         // cas du dernier filtre
892         if(!empty($ret_gen)) {
893           break;
894         }
895         // dans le cas d'une suite prévu mais d'un retour nul de la précédente recherche
896         else if(empty($new_attrs)) {
897             // retour vide et arrêt de la recherche
898             $ret=array();
899             break;
900         }
901         else {
902           $attrs=$new_attrs;
903         }
904       }
905       // Dans le cas de la recherche initiale
906       else {
907         // Déclaration du filtre de recherche
908         if((isset($filter[$i]['filter']))&&(!empty($filter[$i]['filter']))) {
909           if($filter[$i]['filter'][0]=='(') {
910             $sfilter.=$filter[$i]['filter'];
911           }
912           else {
913             $sfilter.='('.$filter[$i]['filter'].')';
914           }
915         }
916         // fermeture du filtre
917         $sfilter.=$sfilter_end;
918         
919         // Attributes
920         if (!isset($sparams['attributes'])) {
921           $sparams['attributes'] = array($this -> config['rdn']);
922         }
923         
924         // Lancement de la recherche
925         $ret=LSldap :: search ($sfilter,$sbasedn,$sparams);
926         
927         //Si filtre multiple => on recupère une liste d'attributs
928         if(isset($filter[$i]['attr'])) {
929           for($ii=0;$ii<count($ret);$ii++) {
930             if(isset($ret[$ii]['attrs'][$filter[$i]['attr']])) {
931               // cas de valeur multiple
932               if(is_array($ret[$ii]['attrs'][$filter[$i]['attr']])) {
933                 foreach($ret[$ii]['attrs'][$filter[$i]['attr']] as $val_attr) {
934                   $attrs[]=$val_attr;
935                 }
936               }
937               // cas de valeur unique
938               else {
939                 $attrs[]=$ret[$ii]['attrs'][$filter[$i]['attr']];
940               }
941             }
942           }
943           
944           // Si aucunne valeur n'est retournées
945           if(empty($attrs)){
946             // arrêt et retour Ã  zéro
947             $ret=array();
948             break;
949           }
950         }
951         // Si recherche unique
952         else {
953           // préparation du retour finale
954           if (!is_array($ret)) {
955             $ret=array();
956           }
957           break;
958         }
959       }
960     }
961     return $ret;
962   }
963 */
964   
965   /**
966    * Retourne une liste d'objet du même type et retourne leur noms
967    *
968    * Effectue une recherche en fonction des paramètres passé et retourne un
969    * tableau (dn => nom) correspondant au resultat de la recherche.
970    *
971    * @author Benjamin Renard <brenard@easter-eggs.com>
972    *
973    * @param[in] $filter string Filtre de recherche Ldap
974    * @param[in] $basedn string DN de base pour la recherche
975    * @param[in] $params array Paramètres de recherche au format Net_LDAP2::search()
976    * @param[in] $displayFormat string Format d'affichage du nom des objets
977    *
978    * @retval array Tableau dn => name correspondant au resultat de la recherche
979    */ 
980   function listObjectsName($filter=NULL,$sbasedn=NULL,$sparams=array(),$displayFormat=false,$cache=true) {
981     if (!LSsession :: loadLSclass('LSsearch')) {
982       LSerror::addErrorCode('LSsession_05','LSsearch');
983       return;
984     }
985     
986     if (!$displayFormat) {
987       $displayFormat = $this -> getDisplayNameFormat();
988     }
989     
990     $params = array(
991       'displayFormat' => $displayFormat,
992       'basedn' => $sbasedn,
993       'filter' => $filter
994     );
995
996     if (is_array($sparams)) {    
997       $params=array_merge($sparams,$params);
998     }
999     
1000     $LSsearch = new LSsearch($this -> type_name,'LSldapObject::listObjectsName',$params,true);
1001     
1002     $LSsearch -> run($cache);
1003     
1004     return $LSsearch -> listObjectsName();
1005   }
1006  
1007  
1008   /**
1009    * Recherche un objet à partir de la valeur exact de son RDN ou d'un filtre de
1010    * recherche LDAP sous la forme d'un LSformat qui sera construit avec la valeur
1011    * de $name.
1012    * 
1013    * @author Benjamin Renard <brenard@easter-eggs.com>
1014    * 
1015    * @param[in] $name string Valeur de son RDN ou de la valeur pour composer le filtre
1016    * @param[in] $basedn string Le DN de base de la recherche
1017    * @param[in] $filter string Le filtre de recherche de l'objet
1018    * @param[in] $params array Tableau de paramètres
1019    * 
1020    * @retval array Tableau d'objets correspondant au resultat de la recherche
1021    */
1022   function searchObject($name,$basedn=NULL,$filter=NULL,$params=NULL) {
1023     if (!$filter) {
1024       $filter = '('.$this -> config['rdn'].'='.$name.')';
1025     }
1026     else {
1027       $filter = getFData($filter,$name);
1028     }
1029     return $this -> listObjects($filter,$basedn,$params); 
1030   }
1031
1032   /**
1033    * Retourne une valeur de l'objet
1034    *
1035    * Retourne une valeur en fonction du paramètre. Si la valeur est inconnue, la valeur retourné est ' '.
1036    * tableau d'objet correspond au resultat de la recherche.
1037    *
1038    * Valeurs possibles :
1039    * - 'dn' ou '%{dn} : DN de l'objet
1040    * - [nom d'un attribut] : valeur de l'attribut
1041    * - [clef de $this -> other_values] : valeur de $this -> other_values
1042    *
1043    * @author Benjamin Renard <brenard@easter-eggs.com>
1044    *
1045    * @param[in] $val string Le nom de la valeur demandée
1046    *
1047    * @retval mixed la valeur demandé ou ' ' si celle-ci est inconnue.
1048    */ 
1049   function getValue($val) {
1050     if(($val=='dn')||($val=='%{dn}')) {
1051       return $this -> dn;
1052     }
1053     else if(($val=='rdn')||($val=='%{rdn}')) {
1054       return $this -> attrs[ $this -> config['rdn'] ] -> getValue();
1055     }
1056     else if(($val=='subDn')||($val=='%{subDn}')) {
1057       return $this -> subDnValue;
1058     }
1059     else if(($val=='subDnName')||($val=='%{subDnName}')) {
1060       return $this -> subDnName;
1061     }
1062     else if(isset($this ->  attrs[$val])){
1063       if (method_exists($this ->  attrs[$val],'getValue'))
1064         return $this -> attrs[$val] -> getValue();
1065       else
1066         return ' ';
1067     }
1068     else if(isset($this -> other_values[$val])){
1069       return $this -> other_values[$val];
1070     }
1071     else {
1072       return ' ';
1073     }
1074   }
1075
1076   /**
1077    * Retourne une valeur d'affichage de l'objet
1078    *
1079    * @author Benjamin Renard <brenard@easter-eggs.com>
1080    *
1081    * @param[in] $val string Le nom de la valeur demandee
1082    *
1083    * @retval mixed la valeur demandee ou ' ' si celle-ci est inconnue.
1084    */
1085   function getDisplayValue($val) {
1086     if(isset($this ->  attrs[$val])){
1087       if (method_exists($this ->  attrs[$val],'getDisplayValue'))
1088         return $this -> attrs[$val] -> getDisplayValue();
1089       else
1090         return ' ';
1091     }
1092     else {
1093       return $this -> getValue($val);
1094     }
1095   }
1096
1097   /**
1098    * Ajoute une valeur dans le tableau $this -> other_values
1099    *
1100    * @param[in] $name string Le nom de la valeur
1101    * @param[in] $value mixed La valeur
1102    *
1103    * @retval void
1104    **/
1105   function registerOtherValue($name,$value) {
1106     $this -> other_values[$name]=$value;
1107   }
1108
1109   /**
1110    * Retourn un tableau pour un select d'un objet du même type
1111    * 
1112    * @author Benjamin Renard <brenard@easter-eggs.com>
1113    *
1114    * @retval array('dn' => 'display')
1115    */
1116   function getSelectArray($pattern=NULL,$topDn=NULL,$displayFormat=NULL,$approx=false,$cache=true) {
1117     return $this -> listObjectsName(NULL,$topDn,array('pattern' => $pattern),$displayFormat,$cache);
1118   }
1119
1120   /**
1121    * Retourne le DN de l'objet
1122    *
1123    * Cette methode retourne le DN de l'objet. Si celui-ci n'existe pas, il le construit Ã  partir de la 
1124    * configuration de l'objet et la valeur de son attribut rdn.
1125    *
1126    * @author Benjamin Renard <brenard@easter-eggs.com>
1127    *
1128    * @retval string Le DN de l'objet
1129    */   
1130   function getDn() {
1131     if($this -> dn) {
1132       return $this -> dn;
1133     }
1134     else {
1135       $rdn_attr=$this -> config['rdn'];
1136       $topDn = LSsession :: getTopDn();
1137       if( (isset($this -> config['rdn'])) && (isset($this -> attrs[$rdn_attr])) && (isset($this -> config['container_dn'])) && ($topDn) ) {
1138         $rdn_val=$this -> attrs[$rdn_attr] -> getUpdateData();
1139         if (!empty($rdn_val)) {
1140           return $rdn_attr.'='.$rdn_val[0].','.$this -> config['container_dn'].','.$topDn;
1141         }
1142         else {
1143           LSerror :: addErrorCode('LSldapObject_12',$this -> config['rdn']);
1144           return;
1145         }
1146       }
1147       else {
1148         LSerror :: addErrorCode('LSldapObject_11',$this -> getType());
1149         return;
1150       }
1151     }
1152   }
1153
1154   /**
1155    * Retourne le type de l'objet
1156    *
1157    * @author Benjamin Renard <brenard@easter-eggs.com>
1158    * 
1159    * @retval string Le type de l'objet ($this -> type_name)
1160    */
1161   function getType() {
1162     return $this -> type_name;
1163   }
1164   
1165   /**
1166    * Retourne qui est l'utilisateur par rapport Ã  cet object
1167    *
1168    * @author Benjamin Renard <brenard@easter-eggs.com>
1169    * 
1170    * @retval string 'admin'/'self'/'user' pour Admin , l'utilisateur lui même ou un simple utilisateur
1171    */
1172   function whoami() {
1173     if (!$this -> _whoami)
1174       $this -> _whoami = LSsession :: whoami($this -> dn);
1175     return $this -> _whoami;
1176   }
1177   
1178   /**
1179    * Retourne le label de l'objet
1180    *
1181    * @author Benjamin Renard <brenard@easter-eggs.com>
1182    * 
1183    * @retval string Le label de l'objet ($this -> config['label'])
1184    */
1185   function getLabel($type=null) {
1186     if (is_null($type)) {
1187       $type = $this -> type_name;
1188     }
1189     return __(LSconfig::get("LSobjects.$type.label"));
1190   }
1191   
1192   
1193   /**
1194    * Supprime l'objet dans l'annuaire
1195    *
1196    * @author Benjamin Renard <brenard@easter-eggs.com>
1197    * 
1198    * @retval boolean True si l'objet Ã  Ã©té supprimé, false sinon
1199    */
1200   function remove() {
1201     if ($this -> fireEvent('before_delete')) {
1202       if (LSldap :: remove($this -> getDn())) {
1203         if ($this -> fireEvent('after_delete')) {
1204           return true;
1205         }
1206         LSerror :: addErrorCode('LSldapObject_19');
1207       }
1208     }
1209     else {
1210       LSerror :: addErrorCode('LSldapObject_18');
1211     }
1212     return;
1213   }
1214   
1215   /**
1216    * L'objet est-il nouveau
1217    * 
1218    * @author Benjamin Renard <brenard@easter-eggs.com>
1219    * 
1220    * @retval boolean True si l'objet est nouveau, false sinon
1221    */
1222   function isNew() {
1223     return (!$this -> dn);
1224   }
1225
1226   /**
1227    * Retourne la valeur (DN) du subDn de l'objet  
1228    * 
1229    * @parram[in] $dn string Un DN
1230    * 
1231    * @return string La valeur du subDn de l'object
1232    */
1233   public static function getSubDnValue($dn) {
1234     $subDn_value='';
1235     $subDnLdapServer = LSsession :: getSortSubDnLdapServer();
1236     foreach ($subDnLdapServer as $subDn => $subDn_name) {
1237       if (isCompatibleDNs($subDn,$dn)&&($subDn!=$dn)) {
1238         $subDn_value=$subDn;
1239         break;
1240       }
1241     }
1242     return $subDn_value;
1243   }
1244
1245   /**
1246    * Retourne la nom du subDn de l'objet  
1247    * 
1248    * @parram[in] $dn string Un DN
1249    * 
1250    * @return string Le nom du subDn de l'object
1251    */
1252   public static function getSubDnName($dn) {
1253     $subDnLdapServer = LSsession :: getSortSubDnLdapServer();
1254     return $subDnLdapServer[self :: getSubDnValue($dn)];
1255   }
1256   
1257   /**
1258    * Methode créant la liste des objets en relations avec l'objet courant et qui
1259    * la met en cache ($this -> _LSrelationsCache)
1260    * 
1261    * @retval True en cas de cas ce succès, False sinon.
1262    */
1263   function updateLSrelationsCache() {
1264     $this -> _LSrelationsCache=array();
1265     if (is_array($this->config['LSrelation'])) {
1266       $type = $this -> getType();
1267       $me = new $type();
1268       $me -> loadData($this -> getDn());
1269       foreach($this->config['LSrelation'] as $relation_name => $relation_conf) {
1270         if ( isset($relation_conf['list_function']) ) {
1271           if (LSsession :: loadLSobject($relation_conf['LSobject'])) {
1272             $obj = new $relation_conf['LSobject']();
1273             if ((method_exists($obj,$relation_conf['list_function']))&&(method_exists($obj,$relation_conf['getkeyvalue_function']))) {
1274               $list = $obj -> $relation_conf['list_function']($me);
1275               if (is_array($list)) {
1276                 // Key Value
1277                 $key = $obj -> $relation_conf['getkeyvalue_function']($me);
1278                 
1279                 $this -> _LSrelationsCache[$relation_name] = array(
1280                   'list' => $list,
1281                   'keyvalue' => $key
1282                 );
1283               }
1284               else {
1285                 LSdebug('Problème durant la mise en cache de la relation '.$relation_name);
1286                 return;
1287               }
1288             }
1289             else {
1290               LSdebug('Les méthodes de mise en cache de la relation '.$relation_name. ' ne sont pas toutes disponibles.');
1291               return;
1292             }
1293           }
1294           else {
1295             return;
1296           }
1297         }
1298       }
1299     }
1300     return true;
1301   }
1302   
1303   /**
1304    * Methode executant les actions nécéssaires avant le changement du DN de
1305    * l'objet.
1306    * 
1307    * Cette méthode n'est qu'un exemple et elle doit être certainement réécrite
1308    * pour les objets plus complexe.
1309    * 
1310    * @retval True en cas de cas ce succès, False sinon.
1311    */
1312   function beforeRename() {
1313     // LSrelations
1314     return $this -> updateLSrelationsCache();
1315   }
1316   
1317   /**
1318    * Methode executant les actions nécéssaires après le changement du DN de
1319    * l'objet.
1320    * 
1321    * Cette méthode n'est qu'un exemple et elle doit être certainement réécrite
1322    * pour les objets plus complexe.
1323    * 
1324    * @retval True en cas de cas ce succès, False sinon.
1325    */
1326   function afterRename() {
1327     $error = 0;
1328     
1329     // Change LSsession -> userObject Dn
1330     if(LSsession :: getLSuserObjectDn() == $this -> oldDn) {
1331       LSsession :: changeAuthUser($this);
1332     }
1333     
1334     // LSrelations
1335     foreach($this -> _LSrelationsCache as $relation_name => $objInfos) {
1336       if ((isset($this->config['LSrelation'][$relation_name]['rename_function']))&&(is_array($objInfos['list']))) {
1337         foreach($objInfos['list'] as $obj) {
1338           $meth = $this->config['LSrelation'][$relation_name]['rename_function'];
1339           if (method_exists($obj,$meth)) {
1340             if (!($obj -> $meth($this,$objInfos['keyvalue']))) {
1341               $error=1;
1342             }
1343           }
1344           else {
1345             $error=1;
1346           }
1347         }
1348       }
1349     }
1350     return !$error;
1351   }
1352   
1353   /**
1354    * Methode executant les actions nécéssaires avant la suppression de
1355    * l'objet.
1356    * 
1357    * Cette méthode n'est qu'un exemple et elle doit être certainement réécrite
1358    * pour les objets plus complexe.
1359    * 
1360    * @retval True en cas de cas ce succès, False sinon.
1361    */
1362   function beforeDelete() {
1363     $return = $this -> updateLSrelationsCache();
1364     
1365     foreach(array_keys($this -> attrs) as $attr_name) {
1366       if (!$this -> attrs[$attr_name] -> fireEvent('before_delete')) {
1367         $return = false;
1368       }
1369     }
1370     
1371     return $return;
1372   }
1373   
1374   /**
1375    * Methode executant les actions nécéssaires après la suppression de
1376    * l'objet.
1377    * 
1378    * Cette méthode n'est qu'un exemple et elle doit être certainement réécrite
1379    * pour les objets plus complexe.
1380    * 
1381    * @retval True en cas de cas ce succès, False sinon.
1382    */
1383   function afterDelete() {
1384     $error = 0;
1385     
1386     // LSrelations
1387     foreach($this -> _LSrelationsCache as $relation_name => $objInfos) {
1388       if ((isset($this->config['LSrelation'][$relation_name]['remove_function']))&&(is_array($objInfos['list']))) {
1389         foreach($objInfos['list'] as $obj) {
1390           $meth = $this->config['LSrelation'][$relation_name]['remove_function'];
1391           if (method_exists($obj,$meth)) {
1392             if (!($obj -> $meth($this))) {
1393               $error=1;
1394             }
1395           }
1396           else {
1397             $error=1;
1398           }
1399         }
1400       }
1401     }
1402     
1403     // Binding LSattributes
1404     foreach(array_keys($this -> attrs) as $attr_name) {
1405       if (!$this -> attrs[$attr_name] -> fireEvent('after_delete')) {
1406         $error = true;
1407       }
1408     }
1409     
1410     // LSsearch : Purge LSobject cache
1411     if (LSsession :: loadLSclass('LSsearch')) {
1412       LSsearch :: purgeCache($this -> type_name);
1413     }
1414     
1415     return !$error;
1416   }
1417   
1418   /**
1419    * Methode executant les actions nécéssaires après la création de
1420    * l'objet.
1421    * 
1422    * Cette méthode n'est qu'un exemple et elle doit être certainement réécrite
1423    * pour les objets plus complexe.
1424    * 
1425    * @retval True en cas de cas ce succès, False sinon.
1426    */
1427   function afterCreate() {
1428     LSdebug('after');
1429     $error = 0;
1430     
1431     // container_auto_create
1432     if (LSsession :: isSubDnLSobject($this -> getType())) {
1433       if (is_array(LSsession :: $ldapServer['subDn']['LSobject'][$this -> getType()]['LSobjects'])) {
1434         foreach(LSsession :: $ldapServer['subDn']['LSobject'][$this -> getType()]['LSobjects'] as $type) {
1435           if (LSsession :: loadLSobject($type)) {
1436             $conf_type=LSconfig :: get("LSobjects.$type");
1437             if (isset($conf_type['container_auto_create'])&&isset($conf_type['container_dn'])) {
1438               $dn = $conf_type['container_dn'].','.$this -> getDn();
1439               if(!LSldap :: getNewEntry($dn,$conf_type['container_auto_create']['objectclass'],$conf_type['container_auto_create']['attrs'],true)) {
1440                 LSdebug("Impossible de créer l'entrée fille : ".print_r(
1441                   array(
1442                     'dn' => $dn,
1443                     'objectClass' => $conf_type['container_auto_create']['objectclass'],
1444                     'attrs' => $conf_type['container_auto_create']['attrs']
1445                   )
1446                 ,true));
1447                 $error=1;
1448               }
1449             }
1450           }
1451           else {
1452             $error=1;
1453           }
1454         }
1455       }
1456     }
1457     
1458     // LSsearch : Purge LSobject cache
1459     if (LSsession :: loadLSclass('LSsearch')) {
1460       LSsearch :: purgeCache($this -> type_name);
1461     }
1462     
1463     return !$error;
1464   }
1465   
1466   /**
1467    * Methode executant les actions nécéssaires après la modification de
1468    * l'objet.
1469    * 
1470    * Cette méthode n'est qu'un exemple et elle doit être certainement réécrite
1471    * pour les objets plus complexe.
1472    * 
1473    * @retval True en cas de cas ce succès, False sinon.
1474    */
1475   function afterModify() {
1476     $error = 0;
1477     
1478     // LSsearch : Purge LSobject cache
1479     if (LSsession :: loadLSclass('LSsearch')) {
1480       LSsearch :: purgeCache($this -> type_name);
1481     }
1482     
1483     return !$error;
1484   }
1485   
1486   /**
1487    * Retourne la valeur clef d'un objet en relation
1488    * 
1489    * @param[in] $object Un object de type $objectType
1490    * @param[in] $objectType Le type d'objet en relation
1491    * @param[in] $value La valeur que doit avoir l'attribut :
1492    *                      - soit le dn (par defaut)
1493    *                      - soit la valeur [0] d'un attribut
1494    * 
1495    * @retval Mixed La valeur clef d'un objet en relation
1496    **/
1497   function getObjectKeyValueInRelation($object,$objectType,$attrValue='dn') {
1498     if (!$objectType) {
1499       LSerror :: addErrorCode('LSrelations_05','getObjectKeyValueInRelation');
1500       return;
1501     }
1502     if ($attrValue=='dn') {
1503       $val = $object -> getDn();
1504     }
1505     else {
1506       $val = $object -> getValue($attrValue);
1507       $val = $val[0];
1508     }
1509     return $val;
1510   }
1511   
1512   /**
1513    * Retourne la liste des relations pour l'objet en fonction de sa présence 
1514    * dans un des attributs
1515    * 
1516    * Retourne un tableau de d'objet (type : $objectType) correspondant à la 
1517    * relation entre l'objet $object et les objets de type $objectType. Cette relation
1518    * est établis par la présence de la valeur de référence à l'objet dans 
1519    * l'attribut des objets de type $objectType.
1520    * 
1521    * @param[in] $object Un object de type $objectType
1522    * @param[in] $attr L'attribut dans lequel l'objet doit apparaitre
1523    * @param[in] $objectType Le type d'objet en relation
1524    * @param[in] $value La valeur que doit avoir l'attribut :
1525    *                      - soit le dn (par defaut)
1526    *                      - soit la valeur [0] d'un attribut
1527    * 
1528    * @retval Array of $objectType Les objets en relations
1529    **/
1530   function listObjectsInRelation($object,$attr,$objectType,$attrValue='dn') {
1531     if ((!$attr)||(!$objectType)) {
1532       LSerror :: addErrorCode('LSrelations_05','listObjectsInRelation');
1533       return;
1534     }
1535     if ($attrValue=='dn') {
1536       $val = $object -> getDn();
1537     }
1538     else {
1539       $val = $object -> getValue($attrValue);
1540       $val = $val[0];
1541     }
1542     if ($val) {
1543       $filter = Net_LDAP2_Filter::create($attr,'equals',$val);
1544       return $this -> listObjects($filter,LSsession :: getRootDn(),array('scope' => 'sub','recursive' => true,'withoutCache'=>true));
1545     }
1546     return;
1547   }
1548
1549   /**
1550    * Ajoute un objet en relation dans l'attribut $attr de $this
1551    * 
1552    * @param[in] $object Un objet de type $objectType à ajouter
1553    * @param[in] $attr L'attribut dans lequel l'objet doit être ajouté
1554    * @param[in] $objectType Le type d'objet en relation
1555    * @param[in] $attrValue La valeur que doit avoir l'attribut :
1556    *                      - soit le dn (par defaut)
1557    *                      - soit la valeur [0] d'un attribut
1558    * @param[in] $canEditFunction  Le nom de la fonction pour vérifier que la
1559    *                              relation avec l'objet est éditable par le user
1560    * 
1561    * @retval boolean true si l'objet à été ajouté, False sinon
1562    **/  
1563   function addOneObjectInRelation($object,$attr,$objectType,$attrValue='dn',$canEditFunction=NULL) {
1564     if ((!$attr)||(!$objectType)) {
1565       LSerror :: addErrorCode('LSrelations_05','addOneObjectInRelation');
1566       return;
1567     }
1568     if ($object instanceof $objectType) {
1569       if ($canEditFunction) {
1570         if (!$this -> $canEditFunction()) {
1571           LSerror :: addErrorCode('LSsession_11');
1572           return;
1573         }
1574       }
1575       if ($this -> attrs[$attr] instanceof LSattribute) {
1576         if ($attrValue=='dn') {
1577           $val = $object -> getDn();
1578         }
1579         else {
1580           $val = $object -> getValue($attrValue);
1581           $val = $val[0];
1582         }
1583         $values = $this -> attrs[$attr] -> getValue();
1584         if ($this -> attrs[$attr] -> config['multiple']) {
1585           if (!is_array($values)) {
1586             $updateData = array($val);
1587           }
1588           else if (!in_array($val,$values)) {
1589             $values[]=$val;
1590             $updateData = $values;
1591           }
1592         }
1593         else {
1594           if (($values[0]!=$val)&&($values!=$val)) {
1595             $updateData = array($val);
1596           }
1597         }
1598         if (isset($updateData)) {
1599           return $this -> _updateData(array($attr => $updateData));
1600         }
1601         return true;
1602       }
1603     }
1604     return;
1605   }
1606   
1607   /**
1608    * Supprime un objet en relation dans l'attribut $attr de $this
1609    * 
1610    * @param[in] $object Un objet de type $objectType à supprimer
1611    * @param[in] $attr L'attribut dans lequel l'objet doit être supprimé
1612    * @param[in] $objectType Le type d'objet en relation
1613    * @param[in] $attrValue La valeur que doit avoir l'attribut :
1614    *                      - soit le dn (par defaut)
1615    *                      - soit la valeur [0] d'un attribut
1616    * @param[in] $canEditFunction  Le nom de la fonction pour vérifier que la
1617    *                              relation avec l'objet est éditable par le user
1618    * 
1619    * @retval boolean true si l'objet à été supprimé, False sinon
1620    **/  
1621   function deleteOneObjectInRelation($object,$attr,$objectType,$attrValue='dn',$canEditFunction=NULL) {
1622     if ((!$attr)||(!$objectType)) {
1623       LSerror :: addErrorCode('LSrelations_05','deleteOneObjectInRelation');
1624       return;
1625     }
1626     if ($object instanceof $objectType) {
1627       if ($canEditFunction) {
1628         if (!$this -> $canEditFunction()) {
1629           LSerror :: addErrorCode('LSsession_11');
1630           return;
1631         }
1632       }
1633       if ($this -> attrs[$attr] instanceof LSattribute) {
1634         if ($attrValue=='dn') {
1635           $val = $object -> getDn();
1636         }
1637         else {
1638           $val = $object -> getValue($attrValue);
1639           $val = $val[0];
1640         }
1641         $values = $this -> attrs[$attr] -> getValue();
1642         if ((!is_array($values)) && (!empty($values))) {
1643           $values = array($values);
1644         }
1645         if (is_array($values)) {
1646           $updateData=array();
1647           foreach($values as $value) {
1648             if ($value!=$val) {
1649               $updateData[]=$value;
1650             }
1651           }
1652           return $this -> _updateData(array($attr => $updateData));
1653         }
1654       }
1655     }
1656     return;
1657   }
1658   
1659  /**
1660   * Renome un objet en relation dans l'attribut $attr de $this
1661   * 
1662   * @param[in] $object Un objet de type $objectType à renomer
1663   * @param[in] $oldValue string L'ancienne valeur faisant référence à l'objet
1664   * @param[in] $attr L'attribut dans lequel l'objet doit être supprimé
1665   * @param[in] $objectType Le type d'objet en relation
1666   * @param[in] $attrValue La valeur que doit avoir l'attribut :
1667   *                      - soit le dn (par defaut)
1668   *                      - soit la valeur [0] d'un attribut
1669   *  
1670   * @retval boolean True en cas de succès, False sinon
1671   */
1672   function renameOneObjectInRelation($object,$oldValue,$attr,$objectType,$attrValue='dn') {
1673     if ((!$attr)||(!$objectType)) {
1674       LSerror :: addErrorCode('LSrelations_05','renameOneObjectInRelation');
1675       return;
1676     }
1677     if ($object instanceof $objectType) {
1678       if ($this -> attrs[$attr] instanceof LSattribute) {
1679         $values = $this -> attrs[$attr] -> getValue();
1680         if ((!is_array($values)) && (!empty($values))) {
1681           $values = array($values);
1682         }
1683         if (is_array($values)) {
1684           $updateData=array();
1685           foreach($values as $value) {
1686             if ($value!=$oldValue) {
1687               $updateData[] = $value;
1688             }
1689             else {
1690               if ($attrValue=='dn') {
1691                 $val = $object -> getDn();
1692               }
1693               else {
1694                 $val = $object -> getValue($attrValue);
1695                 $val = $val[0];
1696               }
1697               $updateData[] = $val;
1698             }
1699           }
1700           return $this -> _updateData(array($attr => $updateData));
1701         }
1702       }
1703     }
1704     return;
1705   }
1706   
1707   /**
1708    * Met à jour les objets du meme type que $this en relation avec l'objet $object
1709    * de type $objectType en modifiant la valeur de leur attribut $attr des objets
1710    * en relation
1711    * 
1712    * @param[in] $object Mixed Un object (type : $this -> userObjectType) : l'utilisateur
1713    * @param[in] $listDns Array(string) Un tableau des DNs des objets en relation
1714    * @param[in] $attr L'attribut dans lequel l'utilisateur doit apparaitre
1715    * @param[in] $objectType Le type d'objet en relation
1716    * @param[in] $attrValue La valeur que doit avoir l'attribut :
1717    *                      - soit le dn (par defaut)
1718    *                      - soit la valeur [0] d'un attribut
1719    * @param[in] $canEditFunction  Le nom de la fonction pour vérifier que la
1720    *                              relation avec l'objet est éditable par le user
1721    * 
1722    * @retval boolean true si tout c'est bien passé, False sinon
1723    **/  
1724   function updateObjectsInRelation($object,$listDns,$attr,$objectType,$attrValue='dn',$canEditFunction=NULL) {
1725     if ((!$attr)||(!$objectType)) {
1726       LSerror :: addErrorCode('LSrelations_05','updateObjectsInRelation');
1727       return;
1728     }
1729     $currentObjects = $this -> listObjectsInRelation($object,$attr,$objectType,$attrValue);
1730     $type=$this -> getType();
1731     if(is_array($currentObjects)) {
1732       if (is_array($listDns)) {
1733         $values=array();
1734         if ($attrValue!='dn') {
1735           $obj=new $objectType();
1736           foreach ($listDns as $dn) {
1737             $obj -> loadData($dn);
1738             $val = $obj -> getValue($attrValue);
1739             $values[$dn] = $val[0];
1740           }
1741         }
1742         else {
1743           foreach($listDns as $dn) {
1744             $values[$dn] = $dn;
1745           }
1746         }
1747         $dontDelete=array();
1748         $dontAdd=array();
1749         for ($i=0;$i<count($currentObjects);$i++) {
1750           if ($attrValue=='dn') {
1751             $val = $currentObjects[$i] -> getDn();
1752           }
1753           else {
1754             $val = $currentObjects[$i] -> getValue($attrValue);
1755             $val = $val[0];
1756           }
1757           if (in_array($val, $listDns)) {
1758             $dontDelete[$i]=true;
1759             $dontAdd[]=$val;
1760           }
1761         }
1762         
1763         for($i=0;$i<count($currentObjects);$i++) {
1764           if ($dontDelete[$i]) {
1765             continue;
1766           }
1767           else {
1768             if (!$currentObjects[$i] -> deleteOneObjectInRelation($object,$attr,$objectType,$attrValue,$canEditFunction)) {
1769               return;
1770             }
1771           }
1772         }
1773         
1774         foreach($values as $dn => $val) {
1775           if (in_array($val,$dontAdd)) {
1776             continue;
1777           }
1778           else {
1779             $obj = new $type();
1780             if ($obj -> loadData($dn)) {
1781               if (!$obj -> addOneObjectInRelation($object,$attr,$objectType,$attrValue,$canEditFunction)) {
1782                 return;
1783               }
1784             }
1785             else {
1786               return;
1787             }
1788           }
1789         }
1790         return true;
1791       }
1792     }
1793     else {
1794       if(!is_array($listDns)) {
1795         return true;
1796       }
1797       foreach($listDns as $dn) {
1798         $obj = new $type();
1799         if ($obj -> loadData($dn)) {
1800           if (!$obj -> addOneObjectInRelation($object,$attr,$objectType,$attrValue,$canEditFunction)) {
1801             return;
1802           }
1803         }
1804         else {
1805           return;
1806         }
1807       }
1808     }
1809   }
1810   
1811   /**
1812    * Ajouter une action lors d'un événement
1813    * 
1814    * @param[in] $event string Le nom de l'événement
1815    * @param[in] $fct string Le nom de la fonction à exectuer
1816    * @param[in] $param mixed Paramètre pour le lancement de la fonction
1817    * @param[in] $class Nom de la classe possèdant la méthode $fct à executer
1818    * 
1819    * @retval void
1820    */
1821   function addEvent($event,$fct,$param=NULL,$class=NULL) {
1822     $this -> _events[$event][] = array(
1823       'function'  => $fct,
1824       'param'    => $param,
1825       'class'     => $class
1826     );
1827   }
1828   
1829   /**
1830    * Ajouter une action sur un objet lors d'un événement
1831    * 
1832    * @param[in] $event string Le nom de l'événement
1833    * @param[in] $obj object L'objet dont la méthode doit être executé
1834    * @param[in] $meth string Le nom de la méthode
1835    * @param[in] $param mixed Paramètre d'execution de la méthode
1836    * 
1837    * @retval void
1838    */
1839   function addObjectEvent($event,&$obj,$meth,$param=NULL) {
1840     $this -> _objectEvents[$event][] = array(
1841       'obj'  => $obj,
1842       'meth'  => $meth,
1843       'param'    => $param
1844     );
1845   }
1846   
1847   /**
1848    * Lance les actions à executer lors d'un événement
1849    * 
1850    * @param[in] $event string Le nom de l'événement
1851    * 
1852    * @retval boolean True si tout c'est bien passé, false sinon
1853    */
1854   function fireEvent($event) {
1855     
1856     // Object event
1857     $return = $this -> fireObjectEvent($event);
1858     
1859     // Config
1860     if(isset($this -> config[$event])) {
1861       if (!is_array($this -> config[$event])) {
1862         $funcs = array($this -> config[$event]);
1863       }
1864       else {
1865         $funcs = $this -> config[$event];
1866       }
1867       foreach($funcs as $func) {
1868         if(function_exists($func)) {
1869           if(!$func($this)) {
1870             $return = false;
1871             LSerror :: addErrorCode('LSldapObject_07',array('func' => $func,'event' => $event));
1872           }
1873         }
1874         else {
1875           $return = false;
1876           LSerror :: addErrorCode('LSldapObject_06',array('func' => $func,'event' => $event));
1877         }
1878       }
1879     }
1880     
1881     // Binding via addEvent
1882     if (isset($this -> _events[$event]) && is_array($this -> _events[$event])) {
1883       foreach ($this -> _events[$event] as $e) {
1884         if ($e['class']) {
1885           if (class_exists($e['class'])) {
1886             $obj = new $e['class']();
1887             if (method_exists($obj,$e['fct'])) {
1888               try {
1889                 $obj -> $e['fct']($e['param']);
1890               }
1891               catch(Exception $er) {
1892                 LSerror :: addErrorCode('LSldapObject_10',array('class' => $e['class'],'meth' => $e['fct'],'event' => $event));
1893                 $return = false;
1894               }
1895             }
1896             else {
1897               LSerror :: addErrorCode('LSldapObject_09',array('class' => $e['class'],'meth' => $e['fct'],'event' => $event));
1898               $return = false;
1899             }
1900           }
1901           else {
1902             LSerror :: addErrorCode('LSldapObject_08',array('class' => $e['class'],'meth' => $e['fct'],'event' => $event));
1903             $return = false;
1904           }
1905         }
1906         else {
1907           if (function_exists($e['fct'])) {
1908             try {
1909               $e['fct']($e['param']);
1910             }
1911             catch(Exception $er) {
1912               LSerror :: addErrorCode('LSldapObject_27',array('func' => $e['fct'],'event' => $event));
1913               $return = false;
1914             }
1915           }
1916           else {
1917             LSerror :: addErrorCode('LSldapObject_26',array('func' => $e['fct'],'event' => $event));
1918             $return = false;
1919           }
1920         }
1921       }
1922     }
1923     
1924     // Binding via addObjectEvent
1925     if (isset($this -> _objectEvents[$event]) && is_array($this -> _objectEvents[$event])) {
1926       foreach ($this -> _objectEvents[$event] as $e) {
1927         if (method_exists($e['obj'],$e['meth'])) {
1928           try {
1929             $e['obj'] -> $e['meth']($e['param']);
1930           }
1931           catch(Exception $er) {
1932             LSerror :: addErrorCode('LSldapObject_29',array('meth' => $e['meth'],'event' => $event));
1933             $return = false;
1934           }
1935         }
1936         else {
1937           LSerror :: addErrorCode('LSldapObject_28',array('meth' => $e['meth'],'event' => $event));
1938           $return = false;
1939         }
1940       }
1941     }
1942     
1943     return $return;
1944   }
1945   
1946   /**
1947    * Lance les actions à executer lors d'un événement sur l'objet lui-même
1948    * 
1949    * @param[in] $event string Le nom de l'événement
1950    * 
1951    * @retval boolean True si tout c'est bien passé, false sinon
1952    **/
1953   function fireObjectEvent($event) {
1954     switch($event) {
1955       case 'after_create':
1956         return $this -> afterCreate();
1957       case 'after_delete':
1958         return $this -> afterDelete();
1959       case 'after_rename':
1960         return $this -> afterRename();
1961       case 'after_modify':
1962         return $this -> afterModify();
1963 /*
1964       case 'before_create':
1965         return $this -> beforeCreate();
1966 */
1967       case 'before_delete':
1968         return $this -> beforeDelete();
1969       case 'before_rename':
1970         return $this -> beforeRename();
1971 /*
1972       case 'before_modify':
1973         return $this -> beforeModify();
1974 */
1975     }
1976     return true;
1977   }
1978   
1979   /**
1980    * Access to infos of the object
1981    * 
1982    * @param[in] $key string The name of the value
1983    * 
1984    * @retval mixed The value
1985    **/
1986   function __get($key) {
1987     if ($key=='subDnValue') {
1988       if (isset($this -> cache['subDnValue'])) {
1989         return $this -> cache['subDnValue'];
1990       }
1991       $this -> cache['subDnValue'] = self :: getSubDnValue($this -> dn);
1992       return $this -> cache['subDnValue'];
1993     }
1994     if ($key=='subDnName') {
1995       if ($this -> cache['subDnName']) {
1996         return $this -> cache['subDnName'];
1997       }
1998       $this -> cache['subDnName'] = self :: getSubDnName($this -> dn);
1999       return $this -> cache['subDnName'];
2000     }
2001   }
2002   
2003 }
2004
2005 /**
2006  * Error Codes
2007  **/
2008 LSerror :: defineError('LSldapObject_01',
2009 _("LSldapObject : Object type unknown.")
2010 );
2011 LSerror :: defineError('LSldapObject_02',
2012 _("LSldapObject : Update form is not defined for the object %{obj}.")
2013 );
2014 LSerror :: defineError('LSldapObject_03',
2015 _("LSldapObject : No form exists for the object %{obj}.")
2016 );
2017 LSerror :: defineError('LSldapObject_04',
2018 _("LSldapObject : The function %{func} to validate the attribute %{attr} the object %{obj} is unknow.")
2019 );
2020 LSerror :: defineError('LSldapObject_05',
2021 _("LSldapObject : Configuration data are missing to validate the attribute %{attr} of the object %{obj}.")
2022 );
2023
2024 LSerror :: defineError('LSldapObject_06',
2025 _("LSldapObject : The function %{func} to be executed on the object event %{event} doesn't exist.")
2026 );
2027 LSerror :: defineError('LSldapObject_07',
2028 _("LSldapObject : The %{func} execution on the object event %{event} failed.")
2029 );
2030
2031 LSerror :: defineError('LSldapObject_08',
2032 _("LSldapObject : Class %{class}, which method %{meth} to be executed on the object event %{event}, doesn't exist.")
2033 );
2034 LSerror :: defineError('LSldapObject_09',
2035 _("LSldapObject : Method %{meth} within %{class} class to be executed on object event %{event}, doesn't exist.")
2036 );
2037 LSerror :: defineError('LSldapObject_10',
2038 _("LSldapObject : Error during execute %{meth} method within %{class} class, to be executed on object event %{event}.")
2039 );
2040
2041 LSerror :: defineError('LSldapObject_11',
2042 _("LSldapObject : Some configuration data of the object type %{obj} are missing to generate the DN of the new object.")
2043 );
2044 LSerror :: defineError('LSldapObject_12',
2045 _("LSldapObject : The attibute %{attr} of the object is not yet defined. Can't generate DN.")
2046 );
2047 LSerror :: defineError('LSldapObject_13',
2048 _("LSldapObject : Without DN, the object could not be changed.")
2049 );
2050 LSerror :: defineError('LSldapObject_14',
2051 _("LSldapObject : The attribute %{attr_depend} depending on the attribute %{attr} doesn't exist.")
2052 );
2053 LSerror :: defineError('LSldapObject_15',
2054 _("LSldapObject : Error during deleting the object %{objectname}.")
2055 );
2056
2057 LSerror :: defineError('LSldapObject_16',
2058 _("LSldapObject : Error during actions to be executed before renaming the objet.")
2059 );
2060 LSerror :: defineError('LSldapObject_17',
2061 _("LSldapObject : Error during actions to be executed after renaming the objet.")
2062 );
2063
2064 LSerror :: defineError('LSldapObject_18',
2065 _("LSldapObject : Error during actions to be executed before deleting the objet.")
2066 );
2067 LSerror :: defineError('LSldapObject_19',
2068 _("LSldapObject : Error during actions to be executed after deleting the objet.")
2069 );
2070
2071 LSerror :: defineError('LSldapObject_20',
2072 _("LSldapObject : Error during the actions to be executed before creating the object.")
2073 );
2074 LSerror :: defineError('LSldapObject_21',
2075 _("LSldapObject : Error during the actions to be executed after creating the object. It was created anyway.")
2076 );
2077
2078 LSerror :: defineError('LSldapObject_22',
2079 _("LSldapObject : The function %{func} to be executed before creating the object doesn't exist.")
2080 );
2081 LSerror :: defineError('LSldapObject_23',
2082 _("LSldapObject : Error executing the function %{func} to be execute after deleting the object.")
2083 );
2084 LSerror :: defineError('LSldapObject_24',
2085 _("LSldapObject : The function %{func} to be executed after deleting the object doesn't exist.")
2086 );
2087 LSerror :: defineError('LSldapObject_25',
2088 _("LSldapObject : Error executing the function %{func} to be execute after creating the object.")
2089 );
2090
2091 LSerror :: defineError('LSldapObject_26',
2092 _("LSldapObject : %{func} function, to be executed on object event %{event}, doesn't exist.")
2093 );
2094 LSerror :: defineError('LSldapObject_27',
2095 _("LSldapObject : Error during the execution of %{func} function on object event %{event}.")
2096 );
2097
2098 LSerror :: defineError('LSldapObject_28',
2099 _("LSldapObject : %{meth} method, to be executed on object event %{event}, doesn't exist.")
2100 );
2101 LSerror :: defineError('LSldapObject_29',
2102 _("LSldapObject : Error during execution of %{meth} method on object event %{event}.")
2103 );
2104 LSerror :: defineError('LSldapObject_30',
2105 _("LSldapObject : Error during generate LDAP filter for %{LSobject}.")
2106 );
2107
2108 LSerror :: defineError('LSldapObject_31',
2109 _("LSldapObject : Error during execution of the custom action %{customAction} on %{objectname}.")
2110 );
2111
2112 // LSrelation
2113 LSerror :: defineError('LSrelations_05',
2114 _("LSrelation : Some parameters are missing in the call of methods to handle standard relations (Method : %{meth}).")
2115 );
2116
2117 ?>