LSsearch : add customAction feature
[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(is_array($retval['attributes'])) {
811           $retval['attributes']=array_merge($attrs,$retval['attributes']);
812         }
813         else {
814           $retval['attributes']=$attrs;
815         }
816       }
817     }
818
819     if (is_array($retval['attributes'])) {
820       $retval['attributes']=array_unique($retval['attributes']);
821     }
822     
823     $this -> _searchParams = $retval;
824   }
825   
826   /**
827    * Run the search
828    *
829    * @param[in] $cache boolean Define if the cache can be used
830    * 
831    * @retval boolean True on success or False
832    */ 
833   public function run($cache=true) {
834     $this -> generateSearchParams();
835     if ($this -> _searchParams['filter'] instanceof Net_LDAP2_Filter) {
836       LSdebug('LSsearch : filter : '.$this -> _searchParams['filter']->asString());
837     }
838     LSdebug('LSsearch : basedn : '.$this -> _searchParams['basedn'].' - scope : '.$this -> _searchParams['scope']);
839     
840     if( $cache && (!isset($_REQUEST['refresh'])) && (!$this -> params['withoutCache']) ) {
841       LSdebug('LSsearch : with the cache');
842       $this -> result = $this -> getResultFromCache();
843     }
844     else {
845       LSdebug('LSsearch : without the cache');
846       $this -> setParam('withoutCache',false);
847     }
848     
849     if (!$this -> result) {
850       LSdebug('LSsearch : Not in cache');
851       $this -> result=array(
852         'sortBy' => NULL,
853         'sortDirection' => NULL
854       );
855
856       // Search in LDAP
857       $list = LSldap :: search(
858         $this -> _searchParams['filter'],
859         $this -> _searchParams['basedn'],
860         $this -> _searchParams
861       );
862
863       // Check result
864       if ($list === false) {
865         LSerror :: addErrorCode('LSsearch_12');
866         return;
867       }
868
869       if ($this -> getParam('onlyAccessible') && LSsession :: getLSuserObjectDn()) {
870         $this -> result['list']=array();
871
872         // Check user rights on objets
873         foreach($list as $id => $obj) {
874           if (LSsession :: canAccess($this -> LSobject,$obj['dn'])) {
875             $this -> result['list'][]=$obj;
876           }
877         }
878       }
879       else {
880         $this -> result['list']=$list;
881       }
882
883       $this -> addResultToCache();
884     }
885     
886     $this -> doSort();
887     
888     return true;
889   }
890   
891   /**
892    * Return an hash corresponding to the parameters of the search
893    * 
894    * @param[in] $searchParams array An optional search params array
895    * 
896    * @retval string The hash of the parameters of the search
897    **/  
898   public function getHash($searchParams=null) {
899     if(is_null($searchParams)) {
900       $searchParams=$this -> _searchParams;
901       if ($this -> _hash) {
902         return $this -> _hash;
903       }
904     }
905     if ($searchParams['filter'] instanceof Net_LDAP_Filter) {
906       $searchParams['filter']=$searchParams['filter']->asString();
907     }
908     return hash('md5',print_r($searchParams,true));
909   }
910   
911   /**
912    * Add the result of the search to cache of the session
913    * 
914    * @retval void
915    **/  
916   public function addResultToCache() {
917     if ($this -> cacheIsEnabled()) {
918       LSdebug('LSsearch : Save result in cache.');
919       $hash=$this->getHash();
920       $_SESSION['LSsession']['LSsearch'][$this -> LSobject][$hash]=$this->result;
921     }
922   }
923   
924   /**
925    * Get the result of the search from cache of the session
926    * 
927    * @retval array | False The array of the result of the search or False
928    **/  
929   private function getResultFromCache() {
930     if ($this -> cacheIsEnabled()) {
931       $hash=$this->getHash();
932       if (isset($_SESSION['LSsession']['LSsearch'][$this -> LSobject][$hash])) {
933         LSdebug('LSsearch : Load result from cache.');
934         return $_SESSION['LSsession']['LSsearch'][$this -> LSobject][$hash];
935       }
936     }
937     return;
938   }
939   
940   /**
941    * Get page informations to display
942    * 
943    * @param[in] $page integer The number of the page
944    * 
945    * @retval array The information of the page
946    **/
947   public function getPage($page=0) {
948     if (!LSsession::loadLSclass('LSsearchEntry')) {
949       LSerror::addErrorCode('LSsession_05',$this -> LSobject);
950       return;
951     }
952     $page = (int)$page;
953
954     $retval=array(
955       'nb' => $page,
956       'nbPages' => 1,
957       'list' => array(),
958       'total' => $this -> total
959     );
960     
961     if ($retval['total']>0) {
962       LSdebug('Total : '.$retval['total']);
963       
964       if (!$this->params['nbObjectsByPage']) {
965         $this->params['nbObjectsByPage']=NB_LSOBJECT_LIST;
966       }
967       $retval['nbPages']=ceil($retval['total']/$this->params['nbObjectsByPage']);
968       
969       $sortTable=$this -> getSortTable();
970       
971       $list = array_slice(
972         $sortTable,
973         ($page * $this->params['nbObjectsByPage']),
974         $this->params['nbObjectsByPage']
975       );
976       
977       foreach ($list as $key => $id) {
978         $retval['list'][]=new LSsearchEntry($this,$this -> LSobject,$this -> params,$this -> _hash,$this -> result['list'],$id);
979       }
980     }
981     return $retval;
982   }
983   
984   /**
985    * Get search entries
986    * 
987    * @retval array The entries
988    **/
989   public function getSearchEntries() {
990     if (!LSsession::loadLSclass('LSsearchEntry')) {
991       LSerror::addErrorCode('LSsession_05',$this -> LSobject);
992       return;
993     }
994     $retval=array();
995     if ($this -> total>0) {
996       $sortTable=$this -> getSortTable();
997       
998       foreach ($sortTable as $key => $id) {
999         $retval[]=new LSsearchEntry($this,$this -> LSobject,$this -> params,$this -> _hash,$this -> result['list'],$id);
1000       }
1001     }
1002     return $retval;
1003   }
1004   
1005   /**
1006    * Access to information of this object
1007    * 
1008    * @param[in] $key string The key of the info
1009    * 
1010    * @retval mixed The info
1011    **/
1012   public function __get($key) {
1013     $params = array (
1014       'basedn',
1015       'sortBy',
1016       'sortDirection'
1017     );
1018     if ($key=='LSobject') {
1019       return $this -> LSobject;
1020     }
1021     elseif (in_array($key,$params)) {
1022       return $this -> params[$key];
1023     }
1024     elseif ($key=='label_objectName') {
1025       return LSldapObject::getLabel($this -> LSobject);
1026     }
1027     elseif ($key=='label_level') {
1028       return LSsession :: getSubDnLabel();
1029     }
1030     elseif ($key=='label_actions') {
1031       return _('Actions');
1032     }
1033     elseif ($key=='label_no_result') {
1034       return _("This search didn't get any result.");
1035     }
1036     elseif ($key=='sort') {
1037       if (isset($this -> params['sortlimit']) && ($this -> params['sortlimit']>0)) {
1038         return ($this -> total < $this -> params['sortlimit']);
1039       }
1040       return true;
1041     }
1042     elseif ($key=='sortlimit') {
1043       return $this -> params['sortlimit'];
1044     }
1045     elseif ($key=='total') {
1046       return count($this -> result['list']);
1047     }
1048     elseif ($key=='label_total') {
1049       return $this -> total." ".$this -> label_objectName;
1050     }
1051     elseif ($key=='displaySubDn') {
1052       if (LSsession :: subDnIsEnabled()) {
1053         if (!is_null($this -> params[$key])) {
1054           return $this -> params[$key];
1055         }
1056         else {
1057           return (! LSsession :: isSubDnLSobject($this -> LSobject) );
1058         }
1059       }
1060       return false;
1061     }
1062     elseif ($key=='canCopy') {
1063       if (!is_null($this -> _canCopy))
1064         return $this -> _canCopy;
1065       $this -> _canCopy = LSsession :: canCreate($this -> LSobject);
1066       return $this -> _canCopy;
1067     }
1068     elseif ($key=='predefinedFilters') {
1069                         $retval=array();
1070                         if (is_array($this -> config['predefinedFilters'])) {
1071                                 foreach($this -> config['predefinedFilters'] as $filter => $label) {
1072                                         $retval[$filter]=__($label);
1073                                 }
1074                         }
1075       return $retval;
1076     }
1077     elseif ($key=='extraDisplayedColumns') {
1078       if ($this->params['extraDisplayedColumns'] && is_array($this -> config['extraDisplayedColumns'])) {
1079         return $this -> config['extraDisplayedColumns'];
1080       }
1081       else {
1082         return False;
1083       }
1084     }
1085     else {
1086       throw new Exception('Incorrect property !');
1087     }
1088   }
1089   
1090   /**
1091    * Function use with uasort to sort two entry
1092    * 
1093    * @param[in] $a array One line of result
1094    * @param[in] $b array One line of result
1095    * 
1096    * @retval int Value for uasort
1097    **/
1098   private function _sortTwoEntry(&$a,&$b) {
1099     $sortBy = $this -> params['sortBy'];
1100     $sortDirection = $this -> params['sortDirection'];
1101     if ($sortDirection=='ASC') {
1102       $dir = -1;
1103     }
1104     else {
1105       $dir = 1;
1106     }
1107     $oa = new LSsearchEntry($this,$this -> LSobject,$this -> params,$this -> _hash,$this -> result['list'],$a);
1108     $va = $oa->$sortBy;
1109     $ob = new LSsearchEntry($this,$this -> LSobject,$this -> params,$this -> _hash,$this -> result['list'],$b);
1110     $vb = $ob->$sortBy;
1111     
1112     if ($va == $vb) return 0;
1113     
1114     $val = strcoll(strtolower($va), strtolower($vb));
1115     return $val*$dir;
1116   }
1117   
1118   /**
1119    * Function to run after using the result. It's update the cache
1120    * 
1121    * IT'S FUNCTION IS VERY IMPORTANT !!!
1122    * 
1123    * @retval void
1124    **/
1125   function afterUsingResult() {
1126     $this -> addResultToCache();
1127   }
1128   
1129   /**
1130    * Redirect user to object view if the search have only one result
1131    * 
1132    * @retval boolean True only if user have been redirected
1133    **/
1134   function redirectWhenOnlyOneResult() {
1135     if ($this -> total == 1 && $this -> result && self::formIsSubmited()) {
1136       LSsession :: redirect('view.php?LSobject='.$this -> LSobject.'&dn='.urlencode($this -> result['list'][0]['dn']));
1137     }
1138     return;
1139   }
1140   
1141   /**
1142    * Run the sort if it's enabled and if the result is not in the cache
1143    * 
1144    * @retval boolean True on success or false
1145    **/
1146   function doSort() {
1147     if (!$this -> sort) {
1148       LSdebug('doSort : sort is disabled');
1149       return true;
1150     }
1151     if (is_null($this -> params['sortBy'])) {
1152       return;
1153     }
1154     if (is_null($this -> params['sortDirection'])) {
1155       $this -> params['sortDirection']='ASC';
1156     }
1157
1158     if ($this->total==0) {
1159       return true;
1160     }
1161     
1162     if (isset($this -> result['sort'][$this -> params['sortBy']][$this -> params['sortDirection']])) {
1163       LSdebug('doSort : from cache');
1164       return true;
1165     }
1166      
1167     LSdebug('doSort : '.$this -> params['sortBy'].' - '.$this -> params['sortDirection']);
1168     
1169     $this -> result['sort'][$this -> params['sortBy']][$this -> params['sortDirection']]=range(0,($this -> total-1));
1170     
1171     if (!LSsession :: loadLSClass('LSsearchEntry')) {
1172       LSerror::addErrorCode('LSsession_05','LSsearchEntry');
1173       return;
1174     }
1175     
1176     if (!uasort(
1177       $this -> result['sort'][$this -> params['sortBy']][$this -> params['sortDirection']],
1178       array($this,'_sortTwoEntry')
1179     )) {
1180       LSerror :: addErrorCode('LSsearch_13');
1181       return;
1182     }
1183     
1184     return true;
1185   }
1186   
1187   /**
1188    * Returns the id of table rows in the result sorted according to criteria 
1189    * defined in the parameters
1190    * 
1191    * @retval array The Table of id lines of results sorted
1192    **/
1193   function getSortTable() {
1194     if (isset($this -> result['sort'][$this -> params['sortBy']][$this -> params['sortDirection']])) {
1195       return $this -> result['sort'][$this -> params['sortBy']][$this -> params['sortDirection']];
1196     }
1197     return range(0,($this -> total-1));
1198   }
1199   
1200   /**
1201    * List objects name
1202    * 
1203    * @retval Array DN associate with name
1204    **/
1205   public function listObjectsName() {
1206     if (!LSsession::loadLSclass('LSsearchEntry')) {
1207       LSerror::addErrorCode('LSsession_05',$this -> LSobject);
1208       return;
1209     }
1210     
1211     $retval=array();
1212     
1213     if ($this -> total>0) {
1214       $sortTable=$this -> getSortTable();
1215       
1216       foreach ($sortTable as $key => $id) {
1217         $entry=new LSsearchEntry($this,$this -> LSobject,$this -> params,$this -> _hash,$this -> result['list'],$id);
1218         $retval[$entry->dn]=$entry->displayName;
1219       }
1220     }
1221     
1222     return $retval;
1223   }
1224   
1225   /**
1226    * List LSldapObjects 
1227    * 
1228    * @retval Array of LSldapObjects
1229    **/
1230   public function listObjects() {    
1231     $retval=array();
1232     
1233     if ($this -> total>0) {
1234       $sortTable=$this -> getSortTable();
1235
1236       $c=0;      
1237       foreach ($sortTable as $key => $id) {
1238         $retval[$c]=new $this -> LSobject();
1239         $retval[$c] -> loadData($this -> result['list'][$id]['dn']);
1240         $c++;
1241       }
1242     }
1243     
1244     return $retval;
1245   }
1246   
1247   /**
1248    * List objects dn
1249    * 
1250    * @retval Array of DN
1251    **/
1252   public function listObjectsDn() {    
1253     $retval=array();
1254     
1255     if ($this -> total>0) {
1256       $sortTable=$this -> getSortTable();
1257
1258       $c=0;      
1259       foreach ($sortTable as $key => $id) {
1260         $retval[$c] = $this -> result['list'][$id]['dn'];
1261         $c++;
1262       }
1263     }
1264     
1265     return $retval;
1266   }
1267   
1268 }
1269
1270 /**
1271  * Error Codes
1272  **/
1273 LSerror :: defineError('LSsearch_01',
1274 _("LSsearch : Invalid filter : %{filter}.")
1275 );
1276 LSerror :: defineError('LSsearch_02',
1277 _("LSsearch : Invalid basedn : %{basedn}.")
1278 );
1279 LSerror :: defineError('LSsearch_03',
1280 _("LSsearch : Invalid value for %{param} parameter.")
1281 );
1282 LSerror :: defineError('LSsearch_04',
1283 _("LSsearch : Invalid size limit. Must be an integer greater or equal to 0.")
1284 );
1285 LSerror :: defineError('LSsearch_05',
1286 _("LSsearch : Invalid parameter %{attr}. Must be an boolean.")
1287 );
1288 LSerror :: defineError('LSsearch_06',
1289 _("LSsearch : Invalid parameter attributes. Must be an string or an array of strings.")
1290 );
1291 LSerror :: defineError('LSsearch_07',
1292 _("LSsearch : Can't build attributes list for make filter.")
1293 );
1294 LSerror :: defineError('LSsearch_08',
1295 _("LSsearch : Error building filter with attribute '%{attr}' and pattern '%{pattern}'")
1296 );
1297 LSerror :: defineError('LSsearch_09',
1298 _("LSsearch : Error combining filters.")
1299 );
1300 LSerror :: defineError('LSsearch_10',
1301 _("LSsearch : Invalid pattern.")
1302 );
1303 LSerror :: defineError('LSsearch_11',
1304 _("LSsearch : Invalid attribute %{attr} in parameters.")
1305 );
1306 LSerror :: defineError('LSsearch_12',
1307 _("LSsearch : Error during the search.")
1308 );
1309 LSerror :: defineError('LSsearch_13',
1310 _("LSsearch : Error sorting the search.")
1311 );
1312 LSerror :: defineError('LSsearch_14',
1313 _("LSsearch : The function of the custum information %{name} is not callable.")
1314 );
1315 LSerror :: defineError('LSsearch_15',
1316 _("LSsearch : Invalid predefinedFilter for LSobject type %{type} : %{label} (filter : %{filter}).")
1317 );
1318 LSerror :: defineError('LSsearch_16',
1319 _("LSsearch : Error during execution of the custom action %{customAction}.")
1320 );