LSsearch : improve and add doc for formaterLSformat extraDisplayedColumns's parameter
[ldapsaisie.git] / public_html / includes / class / class.LSsearch.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  * Object LSsearch
25  *
26  * @author Benjamin Renard <brenard@easter-eggs.com>
27  */
28 class LSsearch { 
29   
30   // The LdapObject type of search
31   private $LSobject=NULL;
32   
33   // The configuration of search
34   private $config;
35   
36   // The context of search
37   private $context;
38   
39   // The parameters of the search
40   private $params=array (
41     // Search params
42     'filter' => NULL,
43     'pattern' => NULL,
44     'predefinedFilter' => false,
45     'basedn' => NULL,
46     'subDn' => NULL,
47     'scope' => NULL,
48     'sizelimit' => 0,
49     'attronly' => false,    // If true, only attribute names are returned
50     'approx' => false,
51     'recursive' => false,
52     'attributes' => array(),
53     // Display params
54     'onlyAccessible' => NULL,
55     'sortDirection' => NULL,
56     'sortBy' => NULL,
57     'sortlimit' => 0,
58     'displaySubDn' => NULL,
59     'displayFormat' => NULL,
60     'nbObjectsByPage' => NB_LSOBJECT_LIST,
61     'nbPageLinkByPage' => 10,
62     'customInfos' => array(),
63     'withoutCache' => false,
64     'extraDisplayedColumns' => false,
65   );
66   
67   // The cache of search parameters
68   private $_searchParams = NULL;
69   
70   // The cache of the hash of the search parameters
71   private $_hash = NULL;
72   
73   // The result of the search
74   private $result=NULL;
75   
76   // Caches
77   private $_canCopy=NULL;
78   
79   /**
80    * Constructor
81    * 
82    * @param[in] $LSobject string The LdapObject type of search
83    * @param[in] $context string Context of search (LSrelation / LSldapObject/ ...)
84    * @param[in] $params array Parameters of search
85    * @param[in] $purgeParams boolean If params in session have to be purged
86    * 
87    **/
88   function LSsearch($LSobject,$context,$params=null,$purgeParams=false) {
89     if (!LSsession :: loadLSobject($LSobject)) {
90       return;
91     }
92     $this -> LSobject = $LSobject;
93     
94     $this -> loadConfig();
95     
96     if (isset($_REQUEST['LSsearchPurgeSession'])) {
97       $this -> purgeSession();
98     }
99     
100     $this -> context = $context;
101     
102     if (!$purgeParams) {
103       if (! $this -> loadParamsFromSession()) {
104         LSdebug('LSsearch : load default parameters');
105         $this -> loadDefaultParameters();
106       }
107     }
108     else {
109       $this -> purgeParams();
110       $this -> loadDefaultParameters();
111     }
112     
113     if (is_array($params)) {
114       $this -> setParams($params);
115     }
116     
117   }
118
119   /**
120    * Load configuration from LSconfig
121    * 
122    * @retval void
123    */
124   private function loadConfig() {
125     $this -> config = LSconfig::get("LSobjects.".$this -> LSobject.".LSsearch");
126     if (is_array($this -> config['predefinedFilters'])) {
127       foreach($this -> config['predefinedFilters'] as $filter => $label) {
128         if(!LSldap::isValidFilter($filter)) {
129           LSerror::addErrorCode('LSsearch_15',array('label' => $label, 'filter' => $filter, 'type' => $this -> LSobject));
130           unset($this -> config['predefinedFilters'][$key]);
131         }
132       }
133     }
134   }
135   
136   /**
137    * Load default search parameters from configuration
138    * 
139    * @retval boolean True on success or False
140    */
141   private function loadDefaultParameters() {
142     if (is_array($this -> config['params'])) {
143       return $this -> setParams($this -> config['params']);
144     }
145     return true;
146   }
147   
148   /**
149    * Load search parameters from session
150    * 
151    * @retval boolean True if params has been loaded from session or False
152    */
153   private function loadParamsFromSession() {
154     LSdebug('LSsearch : load context params session '.$this -> context);
155     if (isset($_SESSION['LSsession']['LSsearch'][$this -> LSobject]['params'][$this -> context]) && is_array($_SESSION['LSsession']['LSsearch'][$this -> LSobject]['params'][$this -> context])) {
156       $params = $_SESSION['LSsession']['LSsearch'][$this -> LSobject]['params'][$this -> context];
157       
158       if ($params['filter']) {
159         $params['filter'] = Net_LDAP2_Filter::parse($params['filter']);
160       }
161       
162       $this -> params = $params;
163       return true;
164     }
165     return;
166   }
167
168   /**
169    * Save search parameters in session
170    * 
171    * @retval void
172    */
173   private function saveParamsInSession() {
174     LSdebug('LSsearch : save context params session '.$this -> context);
175     $params = $this -> params;
176     if ($params['filter'] instanceof Net_LDAP2_Filter) {
177       $params['filter'] = $params['filter'] -> asString();
178     }
179     
180     foreach ($params as $param => $value) {
181       if ( !isset($_SESSION['LSsession']['LSsearch'][$this -> LSobject]['params'][$this -> context][$param]) || $_SESSION['LSsession']['LSsearch'][$this -> LSobject]['params'][$this -> context][$param]!=$value) {
182         LSdebug("S: $param => $value");
183         $_SESSION['LSsession']['LSsearch'][$this -> LSobject]['params'][$this -> context][$param]=$value;
184       }
185     }
186   }
187   
188   /**
189    * Purge parameters in session
190    * 
191    * @param[in] $LSobject string The LSobject type
192    * 
193    * @retval void
194    */
195   public function purgeParams($LSobject=NULL) {
196     if (is_null($LSobject)) {
197       $LSobject = $this -> LSobject;
198     }
199     unset($_SESSION['LSsession']['LSsearch'][$LSobject]['params']);
200   }
201   
202   /**
203    * Purge cache
204    * 
205    * @retval void
206    */
207   public function purgeCache($LSobject=NULL) {
208     if (is_null($LSobject))
209       $LSobject = $this -> LSobject;
210     unset($_SESSION['LSsession']['LSsearch'][$LSobject]);
211   }
212   
213   /**
214    * Purge session
215    * 
216    * @retval void
217    */
218   private function purgeSession() {
219     unset($_SESSION['LSsession']['LSsearch']);
220   }
221   
222   /**
223    * Define one search parameter
224    * 
225    * @param[in] $param string The parameter name
226    * @param[in] $value mixed The parameter value
227    * 
228    * @retval boolean True on success or False
229    */
230   public function setParam($param,$value) {
231     return $this -> setParams(array($param => $value));
232   }
233   
234   /**
235    * Define search parameters
236    * 
237    * @param[in] $params array Parameters of search
238    * 
239    * @retval boolean True on success or False
240    */
241   public function setParams($params) {
242     $OK=true;
243     
244     // Filter
245     if (isset($params['filter'])) {
246       if (is_string($params['filter'])) {
247         $filter = Net_LDAP2_Filter::parse($params['filter']);
248         if (!LSerror::isLdapError($filter)) {
249           $this -> params['filter'] = $filter;
250         }
251         else {
252           LSerror :: addErrorCode('LSsearch_01',$params['filter']);
253           $OK=false;
254         }
255       }
256       elseif($params['filter'] instanceof Net_LDAP2_Filter) {
257         $this -> params['filter'] =& $params['filter'];
258       }
259     }
260
261     // Approx
262     if (isset($params['approx'])) {
263       if (is_bool($params['approx']) || $params['approx']==0 || $params['approx']==1) {
264         $this -> params['approx'] = (bool)$params['approx'];
265       }
266       else {
267         LSerror :: addErrorCode('LSsearch_05','approx');
268         $OK=false;
269       }
270     }
271     
272     // Without Cache
273     if (isset($params['withoutCache'])) {
274       if (is_bool($params['withoutCache']) || $params['withoutCache']==0 || $params['withoutCache']==1) {
275         $this -> params['withoutCache'] = (bool)$params['withoutCache'];
276       }
277       else {
278         LSerror :: addErrorCode('LSsearch_05','withoutCache');
279         $OK=false;
280       }
281     }
282     
283     // Patterm
284     if (isset($params['pattern'])) {
285       if ($params['pattern']=="") {
286         $this -> params['pattern'] = NULL;
287         $this -> params['filter'] = NULL;
288       }
289       elseif (self :: isValidPattern($params['pattern'])) {
290         $this -> params['pattern'] = $params['pattern'];
291         if (!is_string($params['filter'])) {
292           $this -> params['filter']=NULL;
293         }
294       }
295     }
296     
297     
298     // BaseDN
299     if (isset($params['basedn']) && is_string($params['basedn'])) {
300       if (isCompatibleDNs(LSsession :: getRootDn(),$params['basedn'])) {
301         $this -> params['basedn'] = $params['basedn'];
302       }
303       else {
304         LSerror :: addErrorCode('LSsearch_02',$params['basedn']);
305         $OK=false;
306       }
307     }
308     
309     // subDn
310     if (isset($params['subDn']) && is_string($params['subDn'])) {
311       if (LSsession :: validSubDnLdapServer($params['subDn'])) {
312         $this -> params['subDn'] = $params['subDn'];
313       }
314       else {
315         LSerror :: addErrorCode('LSsearch_03','subDn');
316         $OK=false;
317       }
318     }
319     
320     // Scope
321     if (isset($params['scope']) && is_string($params['scope'])) {
322       if (in_array($params['scope'],array('sub','one','base'))) {
323         $this -> params['scope'] = $params['scope'];
324       }
325       else {
326         LSerror :: addErrorCode('LSsearch_03','scope');
327         $OK=false;
328       }
329     }
330     
331     // nbObjectsByPage
332     if (isset($params['nbObjectsByPage'])) {
333       if (((int)$params['nbObjectsByPage'])>1 ) {
334         $this -> params['nbObjectsByPage'] = (int)$params['nbObjectsByPage'];
335       }
336       else {
337         LSerror :: addErrorCode('LSsearch_03','nbObjectsByPage');
338         $OK=false;
339       }
340     }
341     
342     // Sort Limit
343     if (isset($params['sortlimit'])) {
344       if (is_int($params['sortlimit']) && $params['sortlimit']>=0 ) {
345         $this -> params['sortlimit'] = $params['sortlimit'];
346       }
347       elseif ((int)$params['sortlimit'] > 0) {
348         $this -> params['sortlimit'] = (int)$params['sortlimit'];
349       }
350       else {
351         LSerror :: addErrorCode('LSsearch_03','sortlimit');
352         $OK=false;
353       }
354     }
355     
356     // Sort Direction
357     if (isset($params['sortDirection']) && is_string($params['sortDirection'])) {
358       if (in_array($params['sortDirection'],array('ASC','DESC'))) {
359         $this -> params['sortDirection'] = $params['sortDirection'];
360       }
361       else {
362         LSerror :: addErrorCode('LSsearch_03','sortDirection');
363         $OK=false;
364       }
365     }
366     
367     // Sort By
368     if (isset($params['sortBy']) && is_string($params['sortBy'])) {
369       if (in_array($params['sortBy'],array('displayName','subDn')) || ($this ->extraDisplayedColumns && isset($this ->extraDisplayedColumns[$params['sortBy']]))) {
370         if ($this -> params['sortBy'] == $params['sortBy']) {
371           $this -> toggleSortDirection();
372         }
373         else {
374           $this -> params['sortBy'] = $params['sortBy'];
375           if (!is_string($params['sortDirection'])) {
376             $this -> params['sortDirection']='ASC';
377           }
378         }
379       }
380       else {
381         LSerror :: addErrorCode('LSsearch_03','sortBy');
382         $OK=false;
383       }
384     }
385     
386     // Size Limit
387     if (isset($params['sizelimit'])) {
388       if (((int)$params['sizelimit']) >= 0) {
389         $this -> params['sizelimit'] = $params['sizelimit'];
390       }
391       else {
392         LSerror :: addErrorCode('LSsearch_04');
393         $OK=false;
394       }
395     }
396     
397     // Attronly
398     if (isset($params['attronly'])) {
399       if (is_bool($params['attronly']) || $params['attronly']==0 || $params['attronly']==1) {
400         $this -> params['attronly'] = (bool)$params['attronly'];
401       }
402       else {
403         LSerror :: addErrorCode('LSsearch_05','attronly');
404         $OK=false;
405       }
406     }
407     
408     // Recursive
409     if (isset($params['recursive'])) {
410       if (is_bool($params['recursive']) || $params['recursive']==0 || $params['recursive']==1) {
411         $this -> params['recursive'] = (bool)$params['recursive'];
412       }
413       else {
414         LSerror :: addErrorCode('LSsearch_05','recursive');
415         $OK=false;
416       }
417     }
418     
419     // displaySubDn
420     if (isset($params['displaySubDn'])) {
421       if (! LSsession :: isSubDnLSobject($this -> LSobject) ) {
422         if (is_bool($params['displaySubDn']) || $params['displaySubDn']==0 || $params['displaySubDn']==1) {
423           $this -> params['displaySubDn'] = (bool)$params['displaySubDn'];
424         }
425         else {
426           LSerror :: addErrorCode('LSsearch_05','displaySubDn');
427           $OK=false;
428         }
429       }
430     }
431     
432     // Attributes
433     if (isset($params['attributes'])) {
434       if (is_string($params['attributes'])) {
435         $this -> params['attributes'] = array($params['attributes']);
436       }
437       elseif (is_array($params['attributes'])) {
438         $this -> params['attributes']=array();
439         foreach ($params['attributes'] as $attr) {
440           if (is_string($attr)) {
441             if (LSconfig::get("LSobjects.".$this -> LSobject.".attrs.$attr")) {;
442               $this -> params['attributes'][] = $attr;
443             }
444             else {
445               LSerror :: addErrorCode('LSsearch_11',$attr);
446             }
447           }
448         }
449       }
450       else {
451         LSerror :: addErrorCode('LSsearch_06');
452         $OK=false;
453       }
454     }
455
456     // Extra Columns
457     if (isset($params['extraDisplayedColumns'])) {
458       $this -> params['extraDisplayedColumns']=(bool)$params['extraDisplayedColumns'];
459     }
460
461     // predefinedFilter
462     if (isset($params['predefinedFilter'])) {
463       if (is_string($params['predefinedFilter'])) {
464         if (empty($params['predefinedFilter'])) {
465           $this->params['predefinedFilter']=false;
466         }
467         elseif(is_array($this -> config['predefinedFilters'])) {
468           if(isset($this->config['predefinedFilters'][$params['predefinedFilter']])) {
469             $this -> params['predefinedFilter'] = $params['predefinedFilter'];
470           }
471           else {
472             LSerror :: addErrorCode('LSsearch_03','predefinedFilter');
473             $OK=false;
474           }
475         }
476       }
477       else {
478         LSerror :: addErrorCode('LSsearch_03','predefinedFilter');
479         $OK=false;
480       }
481     }
482     
483     // Display Format
484     if (isset($params['displayFormat']) && is_string($params['displayFormat'])) {
485       $this -> params['displayFormat'] = $params['displayFormat'];
486     }
487     
488     // Custom Infos
489     if (isset($params['customInfos']) && is_array($params['customInfos'])) {
490       foreach($params['customInfos'] as $name => $data) {
491         if(is_array($data['function']) && is_string($data['function'][0])) {
492           LSsession::loadLSclass($data['function'][0]);
493         }
494         if (is_callable($data['function'])) {
495           $this -> params['customInfos'][$name] = array (
496             'function' => &$data['function'],
497             'args' => $data['args']
498           );
499         }
500         else {
501           LSerror :: addErrorCode('LSsearch_14',$name);
502         }
503       }
504     }
505
506     // Only Accessible objects
507     if (isset($params['onlyAccessible'])) {
508       $this -> params['onlyAccessible'] = (bool)$params['onlyAccessible'];
509     }
510
511     $this -> saveParamsInSession();
512     return $OK;
513   }
514
515   /**
516    * Return true only if the form is submited
517    * 
518    * @retval boolean True only if the is submited
519    **/
520   private function formIsSubmited() {
521     return isset($_REQUEST['LSsearch_submit']);
522   }
523
524   /**
525    * Define search parameters by reading Post Data ($_REQUEST)
526    * 
527    * @retval void
528    */
529   public function setParamsFormPostData() {
530     $data = $_REQUEST;
531     
532     if (self::formIsSubmited()) {
533       // Recursive 
534       if (is_null($data['recursive'])) {
535         $data['recursive']=false;
536       }
537       else {
538         $data['recursive']=true;
539       }
540       
541       // Approx 
542       if (is_null($data['approx'])) {
543         $data['approx']=false;
544       }
545       else {
546         $data['approx']=true;
547       }
548       
549       if (isset($data['ajax']) && !isset($data['pattern'])) {
550         $data['pattern']="";
551       }
552     }
553     
554     $this -> setParams($data);
555   }
556   
557   /**
558    * Toggle the sort direction
559    * 
560    * @retval void
561    **/
562   private function toggleSortDirection() {
563     if ($this -> params['sortDirection']=="ASC") {
564       $this -> params['sortDirection'] = "DESC";
565     }
566     else {
567       $this -> params['sortDirection'] = "ASC";
568     }
569   }
570   
571   /**
572    * Make a filter object with a pattern of search
573    *
574    * @param[in] $pattern The pattern of search. If is null, the pattern in params will be used.
575    * 
576    * @retval mixed Net_LDAP2_Filter on success or False
577    */ 
578   function getFilterFromPattern($pattern=NULL) {
579     if ($pattern==NULL) {
580       $pattern=$this -> params['pattern'];
581     }
582     if (self :: isValidPattern($pattern)) {
583       $attrsConfig=LSconfig::get("LSobjects.".$this -> LSobject.".LSsearch.attrs");
584       $attrsList=array();
585       if (!is_array($attrsConfig)) {
586         foreach(LSconfig::get("LSobjects.".$this -> LSobject.".attrs") as $attr => $config) {
587           $attrsList[$attr]=array();
588         }
589       }
590       else {
591         foreach($attrsConfig as $key => $val) {
592           if(is_int($key)) {
593             $attrsList[$val]=array();
594           }
595           else {
596             $attrsList[$key]=$val;
597           }
598         }
599       }
600       
601       if (empty($attrsList)) {
602         LSerror :: addErrorCode('LSsearch_07');
603         return;
604       }
605       
606       $filters=array();
607       foreach ($attrsList as $attr => $opts) {
608         if ($params['approx']) {
609           if (isset($opts['approxLSformat'])) {
610             $filter=Net_LDAP2_Filter::parse(getFData($opts['approxLSformat'],array('name'=>$attr,'pattern'=>$pattern)));
611           }
612           else {
613             $filter=Net_LDAP2_Filter::create($attr,'approx',$pattern);
614           }
615         }
616         else {
617           if (isset($opts['searchLSformat'])) {
618             $filter=Net_LDAP2_Filter::parse(getFData($opts['searchLSformat'],array('name'=>$attr,'pattern'=>$pattern)));
619           }
620           else {
621             $filter=Net_LDAP2_Filter::create($attr,'contains',$pattern);
622           }
623         }
624
625         if (!Net_LDAP2::isError($filter)) {
626           $filters[]=$filter;
627         }
628         else {
629           LSerror :: addErrorCode('LSsearch_08',array('attr' => $attr,'pattern' => $pattern));
630           return;
631         }
632       }
633       if(!empty($filters)) {
634         $filter=LSldap::combineFilters('or',$filters);
635         if ($filter) {
636           return $filter;
637         }
638         else {
639           LSerror :: addErrorCode('LSsearch_09');
640         }
641       }
642     }
643     else {
644       LSerror :: addErrorCode('LSsearch_10');
645     }
646     return;
647   }
648   
649   /**
650    * Check if search pattern is valid
651    * 
652    * @param[in] $pattern string The pattern
653    * 
654    * @retval boolean True if pattern is valid or False
655    **/
656   static function isValidPattern($pattern) {
657     return (is_string($pattern) && $pattern!= "" && $pattern!="*");
658   }
659   
660   /**
661    * Check if cache is enabled
662    * 
663    * @retval boolean True if cache is enabled or False
664    **/
665   public function cacheIsEnabled() {
666     if (isset($this -> config['cache'])) {
667       $conf=$this -> config['cache'];
668       if (is_bool($conf) || $conf==0 || $conf==1) {
669         return (bool)$conf;
670       }
671       else {
672         LSerror :: addErrorCode('LSsearch_03','cache');
673       }
674     }
675     return LSsession :: cacheSearch();
676   }
677   
678   /**
679    * Methode for parameters value access
680    * 
681    * @param[in] $key string The parameter name
682    * 
683    * @retval mixed The parameter value or NULL
684    **/
685   public function getParam($key) {
686     if(in_array($key,array_keys($this -> params))) {
687       return $this -> params[$key];
688     }
689     return NULL;
690   }
691   
692   /**
693    * Return hidden fileds to add in search form
694    * 
695    * @retval array The hield fields whith their values
696    **/
697   public function getHiddenFieldForm() {
698     return array (
699       'LSobject' => $this -> LSobject
700     );
701   }
702   
703   /**
704    * Generate an array with search parameters, only parameters whitch have to be
705    * passed to Net_LDAP2 for the LDAP search. This array will be store in 
706    * $this -> _searchParams private variable.
707    * 
708    * @retval void
709    **/
710   private function generateSearchParams() {
711     // Purge the cache of the hash
712     $this -> _hash = NULL;
713     
714     // Base
715     $retval = array(
716       'filter' => $this -> params['filter'],
717       'basedn' => $this -> params['basedn'],
718       'scope' => $this -> params['scope'],
719       'sizelimit' => $this -> params['sizelimit'],
720       'attronly' => $this -> params['attronly'],
721       'attributes' => $this -> params['attributes']
722     );
723     
724     // Pattern
725     if (!is_null($this -> params['pattern'])) {
726       $filter=$this ->getFilterFromPattern();
727       if (is_null($retval['filter'])) {
728         $retval['filter']=$filter;
729       }
730       else {
731         $retval['filter']=LSldap::combineFilters('and',array($retval['filter'],$filter));
732       }
733     }
734     
735     // predefinedFilter
736     if (is_string($this -> params['predefinedFilter'])) {
737       if (!is_null($retval['filter'])) {
738         $filter=LSldap::combineFilters('and',array($this -> params['predefinedFilter'],$retval['filter']));
739         if ($filter) {
740           $retval['filter']=$filter;
741         }
742       }
743       else {
744         $retval['filter']=$this -> params['predefinedFilter'];
745       }
746     }
747     
748     // Filter
749     $objFilter=LSldapObject::getObjectFilter($this -> LSobject);
750     if ($objFilter) {
751       if (!is_null($retval['filter'])) {
752         $filter=LSldap::combineFilters('and',array($objFilter,$retval['filter']));
753         if ($filter) {
754           $retval['filter']=$filter;
755         }
756       }
757       else {
758         $retval['filter']=$objFilter;
759       }
760     }
761     
762     // Recursive
763     if (is_null($retval['basedn'])) {
764       if (!is_null($this -> params['subDn'])) {
765         if ($this -> params['recursive']) {
766           $retval['basedn'] = $this -> params['subDn'];
767         }
768         else {
769           $retval['basedn'] = LSconfig::get("LSobjects.".$this -> LSobject.".container_dn").','.$this -> params['subDn'];
770         }
771       }
772       else {
773         if ($this -> params['recursive']) {
774           $retval['basedn'] = LSsession :: getTopDn();
775         }
776         else {
777           $retval['basedn'] = LSconfig::get("LSobjects.".$this -> LSobject.".container_dn").','.LSsession :: getTopDn();
778         }
779       }
780     }
781     if ($this -> params['recursive'] || !isset($retval['scope'])) {
782       $retval['scope'] = 'sub';
783     }
784     
785     if (is_null($this -> params['displayFormat'])) {
786       $this -> params['displayFormat']=LSconfig::get("LSobjects.".$this -> LSobject.".display_name_format");
787     }
788     
789     // Display Format
790     $attrs=getFieldInFormat($this -> params['displayFormat']);
791     if(is_array($retval['attributes'])) {
792       $retval['attributes']=array_merge($attrs,$retval['attributes']);
793     }
794     else {
795       $retval['attributes']=$attrs;
796     }
797
798     // Extra Columns
799     if ($this -> params['extraDisplayedColumns'] && is_array($this -> config['extraDisplayedColumns'])) {
800       foreach ($this -> config['extraDisplayedColumns'] as $id => $conf) {
801         $attrs=getFieldInFormat($conf['LSformat']);
802         if(is_array($conf['alternativeLSformats'])) {
803           foreach ($conf['alternativeLSformats'] as $format) {
804             $attrs=array_merge($attrs,getFieldInFormat($format));
805           }
806         }
807         else {
808           $attrs=array_merge($attrs,getFieldInFormat($conf['alternativeLSformats']));
809         }
810         if(isset($conf['formaterLSformat'])) {
811           $attrs=array_unique(array_merge($attrs,getFieldInFormat($conf['formaterLSformat'])));
812           if(($key = array_search('val', $attrs)) !== false) {
813             unset($attrs[$key]);
814           }
815         }
816         if(is_array($retval['attributes'])) {
817           $retval['attributes']=array_merge($attrs,$retval['attributes']);
818         }
819         else {
820           $retval['attributes']=$attrs;
821         }
822       }
823     }
824
825     if (is_array($retval['attributes'])) {
826       $retval['attributes']=array_unique($retval['attributes']);
827     }
828     
829     $this -> _searchParams = $retval;
830   }
831   
832   /**
833    * Run the search
834    *
835    * @param[in] $cache boolean Define if the cache can be used
836    * 
837    * @retval boolean True on success or False
838    */ 
839   public function run($cache=true) {
840     $this -> generateSearchParams();
841     if ($this -> _searchParams['filter'] instanceof Net_LDAP2_Filter) {
842       LSdebug('LSsearch : filter : '.$this -> _searchParams['filter']->asString());
843     }
844     LSdebug('LSsearch : basedn : '.$this -> _searchParams['basedn'].' - scope : '.$this -> _searchParams['scope']);
845     
846     if( $cache && (!isset($_REQUEST['refresh'])) && (!$this -> params['withoutCache']) ) {
847       LSdebug('LSsearch : with the cache');
848       $this -> result = $this -> getResultFromCache();
849     }
850     else {
851       LSdebug('LSsearch : without the cache');
852       $this -> setParam('withoutCache',false);
853     }
854     
855     if (!$this -> result) {
856       LSdebug('LSsearch : Not in cache');
857       $this -> result=array(
858         'sortBy' => NULL,
859         'sortDirection' => NULL
860       );
861
862       // Search in LDAP
863       $list = LSldap :: search(
864         $this -> _searchParams['filter'],
865         $this -> _searchParams['basedn'],
866         $this -> _searchParams
867       );
868
869       // Check result
870       if ($list === false) {
871         LSerror :: addErrorCode('LSsearch_12');
872         return;
873       }
874
875       if ($this -> getParam('onlyAccessible') && LSsession :: getLSuserObjectDn()) {
876         $this -> result['list']=array();
877
878         // Check user rights on objets
879         foreach($list as $id => $obj) {
880           if (LSsession :: canAccess($this -> LSobject,$obj['dn'])) {
881             $this -> result['list'][]=$obj;
882           }
883         }
884       }
885       else {
886         $this -> result['list']=$list;
887       }
888
889       $this -> addResultToCache();
890     }
891     
892     $this -> doSort();
893     
894     return true;
895   }
896   
897   /**
898    * Return an hash corresponding to the parameters of the search
899    * 
900    * @param[in] $searchParams array An optional search params array
901    * 
902    * @retval string The hash of the parameters of the search
903    **/  
904   public function getHash($searchParams=null) {
905     if(is_null($searchParams)) {
906       $searchParams=$this -> _searchParams;
907       if ($this -> _hash) {
908         return $this -> _hash;
909       }
910     }
911     if ($searchParams['filter'] instanceof Net_LDAP_Filter) {
912       $searchParams['filter']=$searchParams['filter']->asString();
913     }
914     return hash('md5',print_r($searchParams,true));
915   }
916   
917   /**
918    * Add the result of the search to cache of the session
919    * 
920    * @retval void
921    **/  
922   public function addResultToCache() {
923     if ($this -> cacheIsEnabled()) {
924       LSdebug('LSsearch : Save result in cache.');
925       $hash=$this->getHash();
926       $_SESSION['LSsession']['LSsearch'][$this -> LSobject][$hash]=$this->result;
927     }
928   }
929   
930   /**
931    * Get the result of the search from cache of the session
932    * 
933    * @retval array | False The array of the result of the search or False
934    **/  
935   private function getResultFromCache() {
936     if ($this -> cacheIsEnabled()) {
937       $hash=$this->getHash();
938       if (isset($_SESSION['LSsession']['LSsearch'][$this -> LSobject][$hash])) {
939         LSdebug('LSsearch : Load result from cache.');
940         return $_SESSION['LSsession']['LSsearch'][$this -> LSobject][$hash];
941       }
942     }
943     return;
944   }
945   
946   /**
947    * Get page informations to display
948    * 
949    * @param[in] $page integer The number of the page
950    * 
951    * @retval array The information of the page
952    **/
953   public function getPage($page=0) {
954     if (!LSsession::loadLSclass('LSsearchEntry')) {
955       LSerror::addErrorCode('LSsession_05',$this -> LSobject);
956       return;
957     }
958     $page = (int)$page;
959
960     $retval=array(
961       'nb' => $page,
962       'nbPages' => 1,
963       'list' => array(),
964       'total' => $this -> total
965     );
966     
967     if ($retval['total']>0) {
968       LSdebug('Total : '.$retval['total']);
969       
970       if (!$this->params['nbObjectsByPage']) {
971         $this->params['nbObjectsByPage']=NB_LSOBJECT_LIST;
972       }
973       $retval['nbPages']=ceil($retval['total']/$this->params['nbObjectsByPage']);
974       
975       $sortTable=$this -> getSortTable();
976       
977       $list = array_slice(
978         $sortTable,
979         ($page * $this->params['nbObjectsByPage']),
980         $this->params['nbObjectsByPage']
981       );
982       
983       foreach ($list as $key => $id) {
984         $retval['list'][]=new LSsearchEntry($this,$this -> LSobject,$this -> params,$this -> _hash,$this -> result['list'],$id);
985       }
986     }
987     return $retval;
988   }
989   
990   /**
991    * Get search entries
992    * 
993    * @retval array The entries
994    **/
995   public function getSearchEntries() {
996     if (!LSsession::loadLSclass('LSsearchEntry')) {
997       LSerror::addErrorCode('LSsession_05',$this -> LSobject);
998       return;
999     }
1000     $retval=array();
1001     if ($this -> total>0) {
1002       $sortTable=$this -> getSortTable();
1003       
1004       foreach ($sortTable as $key => $id) {
1005         $retval[]=new LSsearchEntry($this,$this -> LSobject,$this -> params,$this -> _hash,$this -> result['list'],$id);
1006       }
1007     }
1008     return $retval;
1009   }
1010   
1011   /**
1012    * Access to information of this object
1013    * 
1014    * @param[in] $key string The key of the info
1015    * 
1016    * @retval mixed The info
1017    **/
1018   public function __get($key) {
1019     $params = array (
1020       'basedn',
1021       'sortBy',
1022       'sortDirection'
1023     );
1024     if ($key=='LSobject') {
1025       return $this -> LSobject;
1026     }
1027     elseif (in_array($key,$params)) {
1028       return $this -> params[$key];
1029     }
1030     elseif ($key=='label_objectName') {
1031       return LSldapObject::getLabel($this -> LSobject);
1032     }
1033     elseif ($key=='label_level') {
1034       return LSsession :: getSubDnLabel();
1035     }
1036     elseif ($key=='label_actions') {
1037       return _('Actions');
1038     }
1039     elseif ($key=='label_no_result') {
1040       return _("This search didn't get any result.");
1041     }
1042     elseif ($key=='sort') {
1043       if (isset($this -> params['sortlimit']) && ($this -> params['sortlimit']>0)) {
1044         return ($this -> total < $this -> params['sortlimit']);
1045       }
1046       return true;
1047     }
1048     elseif ($key=='sortlimit') {
1049       return $this -> params['sortlimit'];
1050     }
1051     elseif ($key=='total') {
1052       return count($this -> result['list']);
1053     }
1054     elseif ($key=='label_total') {
1055       return $this -> total." ".$this -> label_objectName;
1056     }
1057     elseif ($key=='displaySubDn') {
1058       if (LSsession :: subDnIsEnabled()) {
1059         if (!is_null($this -> params[$key])) {
1060           return $this -> params[$key];
1061         }
1062         else {
1063           return (! LSsession :: isSubDnLSobject($this -> LSobject) );
1064         }
1065       }
1066       return false;
1067     }
1068     elseif ($key=='canCopy') {
1069       if (!is_null($this -> _canCopy))
1070         return $this -> _canCopy;
1071       $this -> _canCopy = LSsession :: canCreate($this -> LSobject);
1072       return $this -> _canCopy;
1073     }
1074     elseif ($key=='predefinedFilters') {
1075                         $retval=array();
1076                         if (is_array($this -> config['predefinedFilters'])) {
1077                                 foreach($this -> config['predefinedFilters'] as $filter => $label) {
1078                                         $retval[$filter]=__($label);
1079                                 }
1080                         }
1081       return $retval;
1082     }
1083     elseif ($key=='extraDisplayedColumns') {
1084       if ($this->params['extraDisplayedColumns'] && is_array($this -> config['extraDisplayedColumns'])) {
1085         return $this -> config['extraDisplayedColumns'];
1086       }
1087       else {
1088         return False;
1089       }
1090     }
1091     else {
1092       throw new Exception('Incorrect property !');
1093     }
1094   }
1095   
1096   /**
1097    * Function use with uasort to sort two entry
1098    * 
1099    * @param[in] $a array One line of result
1100    * @param[in] $b array One line of result
1101    * 
1102    * @retval int Value for uasort
1103    **/
1104   private function _sortTwoEntry(&$a,&$b) {
1105     $sortBy = $this -> params['sortBy'];
1106     $sortDirection = $this -> params['sortDirection'];
1107     if ($sortDirection=='ASC') {
1108       $dir = 1;
1109     }
1110     else {
1111       $dir = -1;
1112     }
1113     $oa = new LSsearchEntry($this,$this -> LSobject,$this -> params,$this -> _hash,$this -> result['list'],$a);
1114     $va = $oa->$sortBy;
1115     $ob = new LSsearchEntry($this,$this -> LSobject,$this -> params,$this -> _hash,$this -> result['list'],$b);
1116     $vb = $ob->$sortBy;
1117     
1118     if ($va == $vb) return 0;
1119     
1120     $val = strcoll(strtolower($va), strtolower($vb));
1121     return $val*$dir;
1122   }
1123   
1124   /**
1125    * Function to run after using the result. It's update the cache
1126    * 
1127    * IT'S FUNCTION IS VERY IMPORTANT !!!
1128    * 
1129    * @retval void
1130    **/
1131   function afterUsingResult() {
1132     $this -> addResultToCache();
1133   }
1134   
1135   /**
1136    * Redirect user to object view if the search have only one result
1137    * 
1138    * @retval boolean True only if user have been redirected
1139    **/
1140   function redirectWhenOnlyOneResult() {
1141     if ($this -> total == 1 && $this -> result && self::formIsSubmited()) {
1142       LSsession :: redirect('view.php?LSobject='.$this -> LSobject.'&dn='.urlencode($this -> result['list'][0]['dn']));
1143     }
1144     return;
1145   }
1146   
1147   /**
1148    * Run the sort if it's enabled and if the result is not in the cache
1149    * 
1150    * @retval boolean True on success or false
1151    **/
1152   function doSort() {
1153     if (!$this -> sort) {
1154       LSdebug('doSort : sort is disabled');
1155       return true;
1156     }
1157     if (is_null($this -> params['sortBy'])) {
1158       return;
1159     }
1160     if (is_null($this -> params['sortDirection'])) {
1161       $this -> params['sortDirection']='ASC';
1162     }
1163
1164     if ($this->total==0) {
1165       return true;
1166     }
1167     
1168     if (isset($this -> result['sort'][$this -> params['sortBy']][$this -> params['sortDirection']])) {
1169       LSdebug('doSort : from cache');
1170       return true;
1171     }
1172      
1173     LSdebug('doSort : '.$this -> params['sortBy'].' - '.$this -> params['sortDirection']);
1174     
1175     $this -> result['sort'][$this -> params['sortBy']][$this -> params['sortDirection']]=range(0,($this -> total-1));
1176     
1177     if (!LSsession :: loadLSClass('LSsearchEntry')) {
1178       LSerror::addErrorCode('LSsession_05','LSsearchEntry');
1179       return;
1180     }
1181     
1182     if (!uasort(
1183       $this -> result['sort'][$this -> params['sortBy']][$this -> params['sortDirection']],
1184       array($this,'_sortTwoEntry')
1185     )) {
1186       LSerror :: addErrorCode('LSsearch_13');
1187       return;
1188     }
1189     
1190     return true;
1191   }
1192   
1193   /**
1194    * Returns the id of table rows in the result sorted according to criteria 
1195    * defined in the parameters
1196    * 
1197    * @retval array The Table of id lines of results sorted
1198    **/
1199   function getSortTable() {
1200     if (isset($this -> result['sort'][$this -> params['sortBy']][$this -> params['sortDirection']])) {
1201       return $this -> result['sort'][$this -> params['sortBy']][$this -> params['sortDirection']];
1202     }
1203     return range(0,($this -> total-1));
1204   }
1205   
1206   /**
1207    * List objects name
1208    * 
1209    * @retval Array DN associate with name
1210    **/
1211   public function listObjectsName() {
1212     if (!LSsession::loadLSclass('LSsearchEntry')) {
1213       LSerror::addErrorCode('LSsession_05',$this -> LSobject);
1214       return;
1215     }
1216     
1217     $retval=array();
1218     
1219     if ($this -> total>0) {
1220       $sortTable=$this -> getSortTable();
1221       
1222       foreach ($sortTable as $key => $id) {
1223         $entry=new LSsearchEntry($this,$this -> LSobject,$this -> params,$this -> _hash,$this -> result['list'],$id);
1224         $retval[$entry->dn]=$entry->displayName;
1225       }
1226     }
1227     
1228     return $retval;
1229   }
1230   
1231   /**
1232    * List LSldapObjects 
1233    * 
1234    * @retval Array of LSldapObjects
1235    **/
1236   public function listObjects() {    
1237     $retval=array();
1238     
1239     if ($this -> total>0) {
1240       $sortTable=$this -> getSortTable();
1241
1242       $c=0;      
1243       foreach ($sortTable as $key => $id) {
1244         $retval[$c]=new $this -> LSobject();
1245         $retval[$c] -> loadData($this -> result['list'][$id]['dn']);
1246         $c++;
1247       }
1248     }
1249     
1250     return $retval;
1251   }
1252   
1253   /**
1254    * List objects dn
1255    * 
1256    * @retval Array of DN
1257    **/
1258   public function listObjectsDn() {    
1259     $retval=array();
1260     
1261     if ($this -> total>0) {
1262       $sortTable=$this -> getSortTable();
1263
1264       $c=0;      
1265       foreach ($sortTable as $key => $id) {
1266         $retval[$c] = $this -> result['list'][$id]['dn'];
1267         $c++;
1268       }
1269     }
1270     
1271     return $retval;
1272   }
1273   
1274 }
1275
1276 /**
1277  * Error Codes
1278  **/
1279 LSerror :: defineError('LSsearch_01',
1280 _("LSsearch : Invalid filter : %{filter}.")
1281 );
1282 LSerror :: defineError('LSsearch_02',
1283 _("LSsearch : Invalid basedn : %{basedn}.")
1284 );
1285 LSerror :: defineError('LSsearch_03',
1286 _("LSsearch : Invalid value for %{param} parameter.")
1287 );
1288 LSerror :: defineError('LSsearch_04',
1289 _("LSsearch : Invalid size limit. Must be an integer greater or equal to 0.")
1290 );
1291 LSerror :: defineError('LSsearch_05',
1292 _("LSsearch : Invalid parameter %{attr}. Must be an boolean.")
1293 );
1294 LSerror :: defineError('LSsearch_06',
1295 _("LSsearch : Invalid parameter attributes. Must be an string or an array of strings.")
1296 );
1297 LSerror :: defineError('LSsearch_07',
1298 _("LSsearch : Can't build attributes list for make filter.")
1299 );
1300 LSerror :: defineError('LSsearch_08',
1301 _("LSsearch : Error building filter with attribute '%{attr}' and pattern '%{pattern}'")
1302 );
1303 LSerror :: defineError('LSsearch_09',
1304 _("LSsearch : Error combining filters.")
1305 );
1306 LSerror :: defineError('LSsearch_10',
1307 _("LSsearch : Invalid pattern.")
1308 );
1309 LSerror :: defineError('LSsearch_11',
1310 _("LSsearch : Invalid attribute %{attr} in parameters.")
1311 );
1312 LSerror :: defineError('LSsearch_12',
1313 _("LSsearch : Error during the search.")
1314 );
1315 LSerror :: defineError('LSsearch_13',
1316 _("LSsearch : Error sorting the search.")
1317 );
1318 LSerror :: defineError('LSsearch_14',
1319 _("LSsearch : The function of the custum information %{name} is not callable.")
1320 );
1321 LSerror :: defineError('LSsearch_15',
1322 _("LSsearch : Invalid predefinedFilter for LSobject type %{type} : %{label} (filter : %{filter}).")
1323 );
1324 LSerror :: defineError('LSsearch_16',
1325 _("LSsearch : Error during execution of the custom action %{customAction}.")
1326 );