Code

Added foot line
[gosa.git] / gosa-core / include / class_listing.inc
1 <?php
3 class listing {
5   var $xmlData;
6   var $entries;
7   var $departmentBrowser= false;
8   var $departmentRootVisible= false;
9   var $multiSelect= false;
10   var $template;
11   var $headline;
12   var $module;
13   var $header= array();
14   var $colprops= array();
15   var $filters= array();
16   var $pid;
17   var $objectTypes;
18   var $objectTypeCount= array();
21   function listing($filename)
22   {
23     global $config;
25     if (!$this->load($filename)) {
26       die("Cannot parse $filename!");
27     }
29     // Move footer information
30     $this->showFooter= ($config->get_cfg_value("listSummary") == "true");
32     // Register build in filters
33     $this->registerElementFilter("objectType", "listing::filterObjectType");
34     $this->registerElementFilter("link", "listing::filterLink");
35     $this->registerElementFilter("actions", "listing::filterActions");
37     // Initialize pid
38     $this->pid= preg_replace("/[^0-9]/", "", microtime(TRUE));
39   }
42   function registerElementFilter($name, $call)
43   {
44     if (!isset($this->filters[$name])) {
45       $this->filters[$name]= $call;
46       return true;
47     }
49     return false;
50   }
53   function load($filename)
54   {
55     $contents = file_get_contents($filename);
56     $this->xmlData= xml::xml2array($contents, 1);
58     if (!isset($this->xmlData['list'])) {
59       return false;
60     }
62     $this->xmlData= $this->xmlData["list"];
64     // Load some definition values
65     foreach (array("departmentBrowser", "departmentRootVisible", "multiSelect") as $token) {
66       if (isset($this->xmlData['definition'][$token]) &&
67           $this->xmlData['definition'][$token] == "true"){
68         $this->$token= true;
69       }
70     }
72     // Fill objectTypes
73     if (isset($this->xmlData['definition']['objectType'])) {
74       foreach ($this->xmlData['definition']['objectType'] as $index => $otype) {
75         $this->objectTypes[]= $this->xmlData['definition']['objectType'][$index];
76       }
77     }
79     // Parse layout per column
80     $this->colprops= $this->parseLayout($this->xmlData['table']['layout']);
82     // Prepare table headers
83     $this->header= array();
84     if (isset($this->xmlData['table']['column'])){
85       foreach ($this->xmlData['table']['column'] as $index => $config) {
86         if (isset($config['label'])) {
87           $this->header[$index]= "<td class='listheader' ".$this->colprops[$index].">"._($config['label'])."</td>";
88         } else {
89           $this->header[$index]= "<td class='listheader' ".$this->colprops[$index].">&nbsp;</td>";
90         }
91       }
92     }
94     // Assign headline/module
95     $this->headline= _($this->xmlData['definition']['label']);
96     $this->module= $this->xmlData['definition']['module'];
98     return true;  
99   }
102   function render()
103   {
104 echo "sorting, department browsing, filter base handling, copy'n paste handler, snapshot handler<br>";
106     // Initialize list
107     $result= "<input type='hidden' value='$this->pid' name='PID'>";
108     $result.= "<div class='contentboxb' id='listing_container' style='border-top:1px solid #B0B0B0;'>";
109     $result.= "<table summary='$this->headline' style='width:600px;height:450px;' cellspacing='0' id='t_scrolltable'>
110 <tr><td class='scrollhead'><table summary='' style='width:100%;' cellspacing='0' id='t_scrollhead'>";
111     $num_cols= count($this->colprops) + ($this->multiSelect?1:0);
113     // Build list header
114     $result.= "<tr>";
115     if ($this->multiSelect) {
116       $result.= "<td class='listheader' style='width:20px;'><input type='checkbox' id='select_all' name='select_all' title='"._("Select all")."' onClick='toggle_all_(\"listing_selected_[0-9]*$\",\"select_all\");' ></td>";
117     }
118     foreach ($this->header as $header) {
119       $result.= $header;
120     }
122     // Add 13px for scroller
123     $result.= "<td class='listheader' style='width:13px;border-right:0px;'>&nbsp;</td></table></td></tr>";
125     // New table for the real list contents
126     $result.= "<tr><td colspan='$num_cols' class='scrollbody'><div style='width:600px;height:430px;' id='d_scrollbody' class='scrollbody'><table summary='' style='height:100%;width:581px;' cellspacing='0' id='t_scrollbody'>";
128     // No results? Just take an empty colspanned row
129     if (count($this->entries) == 0) {
130       $result.= "<tr class='rowxp0'><td class='list1nohighlight' colspan='$num_cols' style='height:100%;border-right:0px;width:100%;'>&nbsp;</td></tr>";
131     }
133     // Fill with contents
134     foreach ($this->entries as $row => $entry){
135       $result.="<tr class='rowxp".($row&1)."'>";
137       // Render multi select if needed
138       if ($this->multiSelect) {
139         $result.="<td style='text-align:center;width:20px;' class='list0'><input type='checkbox' id='listing_selected_$row' name='listing_selected_$row'></td>";
140       }
142       foreach ($this->xmlData['table']['column'] as $index => $config) {
143         $result.="<td ".$this->colprops[$index]." class='list0'>".$this->renderCell($config['value'], $entry, $row)."</td>";
144       }
145       $result.="</tr>";
146     }
148     // Need to fill the list if it's not full (nobody knows why this is 22 ;-))
149     $emptyListStyle= (count($this->entries) == 0)?"border:0;":"";
150     if (count($this->entries) < 22) {
151       $result.= "<tr>";
152       for ($i= 0; $i<$num_cols; $i++) {
153         if ($i == 0) {
154           $result.= "<td class='list1nohighlight' style='$emptyListStyle height:100%;'>&nbsp;</td>";
155           continue;
156         }
157         if ($i != $num_cols-1) {
158           $result.= "<td class='list1nohighlight' style='$emptyListStyle'>&nbsp;</td>";
159         } else {
160           $result.= "<td class='list1nohighlight' style='border-right:1px solid #AAA;$emptyListStyle'>&nbsp;</td>";
161         }
162       }
163       $result.= "</tr>";
164     }
166     $result.= "</table></div></td></tr>";
168     // Add the footer if requested
169     if ($this->showFooter) {
170       $result.= "<tr><td class='scrollhead'><table summary='' style='width:100%' cellspacing='0' id='t_scrollfoot'><tr><td class='listfooter' style='border-bottom:0px;'>";
172       foreach ($this->objectTypes as $objectType) {
173         if (isset($this->objectTypeCount[$objectType['label']])) {
174           $label= _($objectType['label']);
175           $result.= "<img class='center' src='".$objectType['image']."' title='$label' alt='$label'>&nbsp;".$this->objectTypeCount[$objectType['label']]."&nbsp;&nbsp;&nbsp;&nbsp;";
176         }
177       }
179       $result.= "<td class='listfooter' style='width:13px;border-right:0px;'>&nbsp;</td></table></td></tr>";
180     }
182     $result.= "</table></div>";
184     $smarty= get_smarty();
185     $smarty->assign("FILTER", $this->filter->render());
186     $smarty->assign("LIST", $result);
188     // Assign navigation elements
189     $nav= $this->renderNavigation();
190     foreach ($nav as $key => $html) {
191       $smarty->assign($key, $html);
192     }
194     // Assign action menu
195     $smarty->assign("ACTIONS", $this->renderActionMenu());
197     // Assign separator
198     $smarty->assign("SEPARATOR", "<img src='images/lists/seperator.png' alt='-' align='middle' height='16' width='1' class='center'>");
200     // Assign summary
201     $smarty->assign("HEADLINE", $this->headline);
203     return ($smarty->fetch(get_template_path($this->xmlData['definition']['template'], true)));
204   }
207   function setFilter($filter)
208   {
209     $this->filter= &$filter;
210     $this->entries= $this->filter->query();
211   }
214   function update()
215   {
216     // Do not do anything if this is not our PID
217     # DISABLED because the object is not in the session
218     #if(isset($_REQUEST['PID']) && $_REQUEST['PID'] != $this->pid) {
219     #  return;
220     #}
222     $this->entries= $this->filter->query();
223   }
226   function parseLayout($layout)
227   {
228     $result= array();
229     $layout= preg_replace("/^\|/", "", $layout);
230     $layout= preg_replace("/\|$/", "", $layout);
231     $cols= split("\|", $layout);
232     foreach ($cols as $index => $config) {
233       if ($config != "") {
234         $components= split(';', $config);
235         $config= "";
236         foreach ($components as $part) {
237           if (preg_match("/^r$/", $part)) {
238             $config.= "text-align:right;";
239             continue;
240           }
241           if (preg_match("/^l$/", $part)) {
242             $config.= "text-align:left;";
243             continue;
244           }
245           if (preg_match("/^c$/", $part)) {
246             $config.= "text-align:center;";
247             continue;
248           }
249           if (preg_match("/^[0-9]+(|px|%)$/", $part)) {
250             $config.= "width:$part;";
251             continue;
252           }
253         }
255         $result[$index]= " style='$config' ";
256       } else {
257         $result[$index]= null;
258       }
259     }
261     return $result;
262   }
265   function renderCell($data, $config, $row)
266   {
267     // Replace flat attributes in data string
268     for ($i= 0; $i<$config['count']; $i++) {
269       $attr= $config[$i];
270       $value= "";
271       if (is_array($config[$attr])) {
272         $value= $config[$attr][0];
273       } else {
274         $value= $config[$attr];
275       }
276       $data= preg_replace("/%\{$attr\}/", $value, $data);
277     }
279     // Watch out for filters and prepare to execute them
280     $data= $this->processElementFilter($data, $config, $row);
282     return $data;
283   }
286   function processElementFilter($data, $config, $row)
287   {
288     preg_match_all("/%\{filter:([^(]+)\((.*)\)\}/", $data, $matches, PREG_SET_ORDER);
290     foreach ($matches as $match) {
291       if (!isset($this->filters[$match[1]])) {
292         continue;
293       }
294       $cl= preg_replace('/::.*$/', '', $this->filters[$match[1]]);
295       $method= preg_replace('/^.*::/', '', $this->filters[$match[1]]);
297       // Prepare params for function call
298       $params= array();
299       preg_match_all('/"[^"]+"|[^,]+/', $match[2], $parts);
300       foreach ($parts[0] as $param) {
302         // Row is replaced by the row number
303         if ($param == "row") {
304           $params[]= $row;
305         }
307         // pid is replaced by the current PID
308         if ($param == "pid") {
309           $params[]= $this->pid;
310         }
312         // Fixie with "" is passed directly
313         if (preg_match('/^".*"$/', $param)){
314           $params[]= preg_replace('/"/', '', $param);
315         }
317         // LDAP variables get replaced by their objects
318         for ($i= 0; $i<$config['count']; $i++) {
319           if ($param == $config[$i]) {
320             $values= $config[$config[$i]];
321             if (is_array($values)){
322               unset($values['count']);
323             }
324             $params[]= $values;
325           }
326         }
328         // Move dn if needed
329         if ($param == "dn") {
330           $params[]= LDAP::fix($config["dn"]);
331         }
332       }
334       // Replace information
335       if ($cl == "listing") {
336         // Non static call - seems to result in errors
337         $data= @preg_replace('/'.preg_quote($match[0]).'/', call_user_func_array(array($this, "$method"), $params), $data);
338       } else {
339         // Static call
340         $data= preg_replace('/'.preg_quote($match[0]).'/', call_user_func_array(array($cl, $method), $params), $data);
341       }
342     }
344     return $data;
345   }
348   function getObjectType($types, $classes)
349   {
350     // Walk thru types and see if there's something matching
351     foreach ($types as $objectType) {
352       $ocs= $objectType['objectClass'];
353       if (!is_array($ocs)){
354         $ocs= array($ocs);
355       }
357       $found= true;
358       foreach ($ocs as $oc){
359         if (preg_match('/^!(.*)$/', $oc, $match)) {
360           $oc= $match[1];
361           if (in_array($oc, $classes)) {
362             $found= false;
363           }
364         } else {
365           if (!in_array($oc, $classes)) {
366             $found= false;
367           }
368         }
369       }
371       if ($found) {
372         return $objectType;
373       }
374     }
376     return null;
377   }
380   function filterObjectType($dn, $classes)
381   {
382     // Walk thru classes and return on first match
383     $result= "&nbsp;";
385     $objectType= $this->getObjectType($this->objectTypes, $classes);
386     if ($objectType) {
387       $result= "<img class='center' title='".LDAP::fix($dn)."' src='".$objectType["image"]."'>";
388       if (!isset($this->objectTypeCount[$objectType['label']])) {
389         $this->objectTypeCount[$objectType['label']]= 0;
390       }
391       $this->objectTypeCount[$objectType['label']]++;
392     }
393     return $result;
394   }
397   function filterActions($dn, $row, $classes)
398   {
399     // Do nothing if there's no menu defined
400     if (!isset($this->xmlData['actiontriggers']['action'])) {
401       return "&nbsp;";
402     }
404     // Go thru all actions
405     $result= "";
406     $actions= $this->xmlData['actiontriggers']['action'];
407     foreach($actions as $action) {
408       // Skip the entry completely if there's no permission to execute it
409       if (!$this->hasActionPermission($action, $dn)) {
410         continue;
411       }
413       // If there's an objectclass definition and we don't have it
414       // add an empty picture here.
415       if (isset($action['objectclass'])){
416         $objectclass= $action['objectclass'];
417         if (preg_match('/^!(.*)$/', $objectclass, $m)){
418           $objectclass= $m[1];
419           if(in_array($objectclass, $classes)) {
420             $result.= "<img src='images/empty.png' alt=' ' class='center' style='padding:1px'>";
421             continue;
422           }
423         } else {
424           if(!in_array($objectclass, $classes)) {
425             $result.= "<img src='images/empty.png' alt=' ' class='center' style='padding:1px'>";
426             continue;
427           }
428         }
429       }
431       // Render normal entries as usual
432       if ($action['type'] == "entry") {
433         $label= $this->processElementFilter($action['label'], $this->entries[$row], $row);
434         $image= $this->processElementFilter($action['image'], $this->entries[$row], $row);
435         $result.="<input class='center' type='image' src='$image' title='$label' ".
436                  "name='listing_".$action['name']."_$row' style='padding:1px'>";
437       }
439       // Handle special types
440       if ($action['type'] == "snapshot") {
441         #TODO
442         #echo "actiontriggers: snapshot missing<br>";
443       }
444       if ($action['type'] == "copypaste") {
445         #TODO
446         #echo "actiontriggers: copypaste missing<br>";
447       }
448       if ($action['type'] == "daemon") {
449         #TODO
450         #echo "actiontriggers: daemon missing<br>";
451       }
453     }
455     return $result;
456   }
459   function filterLink()
460   {
461     $result= "&nbsp;";
463     $row= func_get_arg(0);
464     $pid= func_get_arg(1);
465     $dn= LDAP::fix(func_get_arg(2));
466     $params= array(func_get_arg(3));
468     // Collect sprintf params
469     for ($i = 4;$i < func_num_args();$i++) {
470       $val= func_get_arg($i);
471       if (is_array($val)){
472         $params[]= $val[0];
473         continue;
474       }
475       $params[]= $val;
476     }
478     $result= "&nbsp;";
479     $trans= call_user_func_array("sprintf", $params);
480     if ($trans != "") {
481       return("<a href='?plug=".$_GET['plug']."&amp;PID=$pid&amp;act=listing_edit_$row' title='$dn'>$trans</a>");
482     }
484     return $result;
485   }
488   function renderNavigation()
489   {
490     $result= array();
491     $enableBack = true;
492     $enableRoot = true;
493     $enableHome = true;
495     $ui = get_userinfo();
497     /* Check if base = first available base */
498     $deps = $ui->get_module_departments($this->module);
500     if(!count($deps) || $deps[0] == $this->filter->base){
501       $enableBack = false;
502       $enableRoot = false;
503     }
505     $listhead ="";
507     /* Check if we are in users home  department */
508     if(!count($deps) ||$this->filter->base == get_base_from_people($ui->dn)){
509       $enableHome = false;
510     }
512     /* Draw root button */
513     if($enableRoot){
514       $result["ROOT"]= "<input class='center' type='image' src='images/lists/root.png' align='middle' ".
515                        "title='"._("Go to root department")."' name='dep_root' alt='"._("Root")."'>";
516     }else{
517       $result["ROOT"]= "<img src='images/lists/root_grey.png' class='center' alt='"._("Root")."'>";
518     }
520     /* Draw back button */
521     if($enableBack){
522       $result["BACK"]= "<input class='center' type='image' align='middle' src='images/lists/back.png' ".
523                        "title='"._("Go up one department")."' alt='"._("Up")."' name='dep_back'>";
524     }else{
525       $result["BACK"]= "<img src='images/lists/back_grey.png' class='center' alt='"._("Up")."'>";
526     }
528     /* Draw home button */
529     if($enableHome){
530       $result["HOME"]= "<input class='center' type='image' align='middle' src='images/lists/home.png' ".
531                        "title='"._("Go to users department")."' alt='"._("Home")."' name='dep_home'>";
532     }else{
533       $result["HOME"]= "<img src='images/lists/home_grey.png' class='center' alt='"._("Home")."'>";
534     }
536     /* Draw reload button, this button is enabled everytime */
537     $result["RELOAD"]= "<input class='center' type='image' src='images/lists/reload.png' align='middle' ".
538                        "title='"._("Reload list")."' name='submit_department' alt='"._("Submit")."'>";
540     return ($result);
541   }
544   function getAction()
545   {
546     // Do not do anything if this is not our PID
547     # DISABLED because the object is not in the session
548     #if(isset($_REQUEST['PID']) && $_REQUEST['PID'] != $this->pid) {
549     #  return;
550     #}
552     $result= array("targets" => array(), "action" => "");
554     // Filter GET with "act" attributes
555     if (isset($_GET['act'])) {
556       $key= validate($_GET['act']);
557       $target= preg_replace('/^listing_[a-zA-Z_]+_([0-9]+)$/', '$1', $key);
558       if (isset($this->entries[$target]['dn'])) {
559         $result['action']= preg_replace('/^listing_([a-zA-Z_]+)_[0-9]+$/', '$1', $key);
560         $result['targets'][]= $this->entries[$target]['dn'];
561       }
563       // Drop targets if empty
564       if (count($result['targets']) == 0) {
565         unset($result['targets']);
566       }
567       return $result;
568     }
570     // Filter POST with "listing_" attributes
571     foreach ($_POST as $key => $prop) {
573       // Capture selections
574       if (preg_match('/^listing_selected_[0-9]+$/', $key)) {
575         $target= preg_replace('/^listing_selected_([0-9]+)$/', '$1', $key);
576         if (isset($this->entries[$target]['dn'])) {
577           $result['targets'][]= $this->entries[$target]['dn'];
578         }
579         continue;
580       }
582       // Capture action with target - this is a one shot
583       if (preg_match('/^listing_[a-zA-Z_]+_[0-9]+(|_x)$/', $key)) {
584         $target= preg_replace('/^listing_[a-zA-Z_]+_([0-9]+)(|_x)$/', '$1', $key);
585         if (isset($this->entries[$target]['dn'])) {
586           $result['action']= preg_replace('/^listing_([a-zA-Z_]+)_[0-9]+(|_x)$/', '$1', $key);
587           $result['targets']= array($this->entries[$target]['dn']);
588         }
589         break;
590       }
592       // Capture action without target
593       if (preg_match('/^listing_[a-zA-Z_]+(|_x)$/', $key)) {
594         $result['action']= preg_replace('/^listing_([a-zA-Z_]+)(|_x)$/', '$1', $key);
595         continue;
596       }
597     }
599     // Filter POST with "act" attributes -> posted from action menu
600     if (isset($_POST['act']) && $_POST['act'] != '') {
601       $result['action']= validate($_POST['act']);
602     }
604     // Drop targets if empty
605     if (count($result['targets']) == 0) {
606       unset($result['targets']);
607     }
608     return $result;
609   }
612   function renderActionMenu()
613   {
614     // Don't send anything if the menu is not defined
615     if (!isset($this->xmlData['actionmenu']['action'])){
616       return "";
617     }
619     // Load shortcut
620     $actions= &$this->xmlData['actionmenu']['action'];
621     $result= "<input type='hidden' name='act' id='actionmenu' value=''>".
622              "<ul class='level1' id='root'><li><a href='#'>Aktionen&nbsp;<img ".
623              "border=0 src='images/lists/sort-down.png'></a>";
625     // Build ul/li list
626     $result.= $this->recurseActions($actions);
628     return "<div id='pulldown'>".$result."</li></ul><div>";
629   }
632   function recurseActions($actions)
633   {
634     static $level= 2;
635     $result= "<ul class='level$level'>";
636     $separator= "";
638     foreach ($actions as $action) {
640       // Skip the entry completely if there's no permission to execute it
641       if (!$this->hasActionPermission($action, $this->filter->base)) {
642         continue;
643       }
645       // Fill image if set
646       $img= "";
647       if (isset($action['image'])){
648         $img= "<img border=0 src='".$action['image']."'>&nbsp;";
649       }
651       if ($action['type'] == "separator"){
652         $separator= " style='border-top:1px solid #AAA' ";
653         continue;
654       }
656       // Dive into subs
657       if ($action['type'] == "sub" && isset($action['action'])) {
658         $level++;
659         if (isset($action['label'])){
660           $result.= "<li$separator><a href='#'>$img"._($action['label'])."&nbsp;<img border='0' src='images/forward-arrow.png'></a>";
661         }
662         $result.= $this->recurseActions($action['action'])."</li>";
663         $level--;
664         $separator= "";
665         continue;
666       }
668       // Render entry elseways
669       if (isset($action['label'])){
670         $result.= "<li$separator><a href='#' onClick='document.getElementById(\"actionmenu\").value= \"".$action['name']."\";mainform.submit();'>$img"._($action['label'])."</a></li>";
671       }
673       // Check for special types
674       switch ($action['type']) {
675         case 'copypaste':
676           #TODO
677           #echo "actionmenu: copypaste missing<br>";
678           break;
680         case 'snapshot':
681           #TODO
682           #echo "actionmenu: snapshot missing<br>";
683           break;
685         case 'daemon':
686           #TODO
687           #echo "actionmenu: daemon missing<br>";
688           break;
689       }
691       $separator= "";
692     }
694     $result.= "</ul>";
695     return $result;
696   }
699   function hasActionPermission($action, $dn)
700   {
701     $ui= get_userinfo();
703     if (isset($action['acl'])) {
704       $acls= $action['acl'];
705       if (!is_array($acls)) {
706         $acls= array($acls);
707       }
709       // Every ACL has to pass
710       foreach ($acls as $acl) {
711         $module= $this->module;
712         $acllist= array();
714         // Split for category and plugins if needed
715         // match for "[rw]" style entries
716         if (preg_match('/^\[([rwcdm]+)\]$/', $acl, $match)){
717           $aclList= array($match[1]);
718         }
720         // match for "users[rw]" style entries
721         if (preg_match('/^([a-zA-Z0-9]+)\[([rwcdm]+)\]$/', $acl, $match)){
722           $module= $match[1];
723           $aclList= array($match[2]);
724         }
726         // match for "users/user[rw]" style entries
727         if (preg_match('/^([a-zA-Z0-9]+\/[a-zA-Z0-9]+)\[([rwcdm]+)\]$/', $acl, $match)){
728           $module= $match[1];
729           $aclList= array($match[2]);
730         }
732         // match "users/user[userPassword:rw(,...)*]" style entries
733         if (preg_match('/^([a-zA-Z0-9]+\/[a-zA-Z0-9]+)\[([a-zA-Z0-9]+:[rwcdm]+(,[a-zA-Z0-9]+:[rwcdm]+)*)\]$/', $acl, $match)){
734           $module= $match[1];
735           $aclList= split(',', $match[2]);
736         }
738         // Walk thru prepared ACL by using $module
739         foreach($aclList as $sAcl) {
740           $checkAcl= "";
742           // Category or detailed permission?
743           if (strpos('/', $module) === false) {
744             if (preg_match('/([a-zA-Z0-9]+):([rwcdm]+)/', $sAcl, $m) ) {
745               $checkAcl= $ui->get_permissions($dn, $module, $m[1]);
746               $sAcl= $m[2];
747             } else {
748               $checkAcl= $ui->get_permissions($dn, $module, '0');
749             }
750           } else {
751             $checkAcl= $ui->get_category_permissions($dn, $module);
752           }
754           // Split up remaining part of the acl and check if it we're
755           // allowed to do something...
756           $parts= str_split($sAcl);
757           foreach ($parts as $part) {
758             if (strpos($checkAcl, $part) === false){
759               return false;
760             }
761           }
763         }
764       }
765     }
767     return true;
768   }
772 ?>