Code

Added department browsing
[gosa.git] / gosa-core / include / class_listing.inc
1 <?php
2 /*
3  * This code is part of GOsa (http://www.gosa-project.org)
4  * Copyright (C) 2003-2008 GONICUS GmbH
5  *
6  * ID: $$Id$$
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
23 class listing {
25   var $xmlData;
26   var $entries;
27   var $departments= array();
28   var $departmentBrowser= false;
29   var $departmentRootVisible= false;
30   var $multiSelect= false;
31   var $template;
32   var $headline;
33   var $module;
34   var $base;
35   var $sortDirection= null;
36   var $sortColumn= null;
37   var $sortAttribute;
38   var $sortType;
39   var $numColumns;
40   var $baseMode= false;
41   var $bases= array();
42   var $header= array();
43   var $colprops= array();
44   var $filters= array();
45   var $pid;
46   var $objectTypes= array();
47   var $objectTypeCount= array();
50   function listing($filename)
51   {
52     global $config;
54     // Initialize pid
55     $this->pid= preg_replace("/[^0-9]/", "", microtime(TRUE));
57     if (!$this->load($filename)) {
58       die("Cannot parse $filename!");
59     }
61     // Set base for filter
62     $this->base= session::global_get("CurrentMainBase");
63     if ($this->base == null) {
64       $this->base= $config->current['BASE'];
65     }
66     $this->refreshBasesList();
68     // Move footer information
69     $this->showFooter= ($config->get_cfg_value("listSummary") == "true");
71     // Register build in filters
72     $this->registerElementFilter("objectType", "listing::filterObjectType");
73     $this->registerElementFilter("departmentLink", "listing::filterDepartmentLink");
74     $this->registerElementFilter("link", "listing::filterLink");
75     $this->registerElementFilter("actions", "listing::filterActions");
76   }
79   function registerElementFilter($name, $call)
80   {
81     if (!isset($this->filters[$name])) {
82       $this->filters[$name]= $call;
83       return true;
84     }
86     return false;
87   }
90   function load($filename)
91   {
92     $contents = file_get_contents($filename);
93     $this->xmlData= xml::xml2array($contents, 1);
95     if (!isset($this->xmlData['list'])) {
96       return false;
97     }
99     $this->xmlData= $this->xmlData["list"];
101     // Load some definition values
102     foreach (array("departmentBrowser", "departmentRootVisible", "multiSelect", "baseMode") as $token) {
103       if (isset($this->xmlData['definition'][$token]) &&
104           $this->xmlData['definition'][$token] == "true"){
105         $this->$token= true;
106       }
107     }
109     // Fill objectTypes from departments and xml definition
110     $types = departmentManagement::get_support_departments();
111     foreach ($types as $class => $data) {
112       $this->objectTypes[]= array("label" => $data['TITLE'],
113                                   "objectClass" => $data['OC'],
114                                   "image" => $data['IMG']);
115     }
116     if (isset($this->xmlData['definition']['objectType'])) {
117       foreach ($this->xmlData['definition']['objectType'] as $index => $otype) {
118         $this->objectTypes[]= $this->xmlData['definition']['objectType'][$index];
119       }
120     }
122     // Parse layout per column
123     $this->colprops= $this->parseLayout($this->xmlData['table']['layout']);
125     // Prepare table headers
126     $this->renderHeader();
128     // Assign headline/module
129     $this->headline= _($this->xmlData['definition']['label']);
130     $this->module= $this->xmlData['definition']['module'];
132     return true;  
133   }
136   function renderHeader()
137   {
138     $this->header= array();
140     // Initialize sort?
141     $sortInit= false;
142     if (!$this->sortDirection) {
143       $this->sortColumn= 0;
144       if (isset($this->xmlData['definition']['defaultSortColumn'])){
145         $this->sortColumn= $this->xmlData['definition']['defaultSortColumn'];
146       } else {
147         $this->sortAttribute= "";
148       }
149       $this->sortDirection= array();
150       $sortInit= true;
151     }
153     if (isset($this->xmlData['table']['column'])){
154       foreach ($this->xmlData['table']['column'] as $index => $config) {
155         // Initialize everything to one direction
156         if ($sortInit) {
157           $this->sortDirection[$index]= false;
158         }
160         $sorter= "";
161         if ($index == $this->sortColumn && isset($config['sortAttribute']) &&
162             isset($config['sortType'])) {
163           $this->sortAttribute= $config['sortAttribute'];
164           $this->sortType= $config['sortType'];
165           $sorter= "&nbsp;<img border='0' title='".($this->sortDirection[$index]?_("Up"):_("Down"))."' src='images/lists/sort-".($this->sortDirection[$index]?"up":"down").".png' align='top'>";
166         }
167         $sortable= (isset($config['sortAttribute']));
169         $link= "href='?plug=".$_GET['plug']."&amp;PID=".$this->pid."&amp;act=SORT_$index'";
170         if (isset($config['label'])) {
171           if ($sortable) {
172             $this->header[$index]= "<td class='listheader' ".$this->colprops[$index]."><a $link>"._($config['label'])."$sorter</a></td>";
173           } else {
174             $this->header[$index]= "<td class='listheader' ".$this->colprops[$index].">"._($config['label'])."</td>";
175           }
176         } else {
177           if ($sortable) {
178             $this->header[$index]= "<td class='listheader' ".$this->colprops[$index]."><a $link>&nbsp;$sorter</a></td>";
179           } else {
180             $this->header[$index]= "<td class='listheader' ".$this->colprops[$index].">&nbsp;</td>";
181           }
182         }
183       }
184     }
185   }
187   function render()
188   {
189 echo "sizelimit, copypaste handler, snapshot handler, daemon handler<br>";
191     // Initialize list
192     $result= "<input type='hidden' value='$this->pid' name='PID'>";
193     $result.= "<div class='contentboxb' id='listing_container' style='border-top:1px solid #B0B0B0;'>";
194     $result.= "<table summary='$this->headline' style='width:600px;height:450px;' cellspacing='0' id='t_scrolltable'>
195 <tr><td class='scrollhead'><table summary='' style='width:100%;' cellspacing='0' id='t_scrollhead'>";
196     $this->numColumns= count($this->colprops) + ($this->multiSelect?1:0);
198     // Build list header
199     $result.= "<tr>";
200     if ($this->multiSelect) {
201       $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>";
202     }
203     foreach ($this->header as $header) {
204       $result.= $header;
205     }
207     // Add 13px for scroller
208     $result.= "<td class='listheader' style='width:13px;border-right:0px;'>&nbsp;</td></table></td></tr>";
210     // New table for the real list contents
211     $result.= "<tr><td colspan='$this->numColumns' 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'>";
213     // No results? Just take an empty colspanned row
214     if (count($this->entries) + count($this->departments) == 0) {
215       $result.= "<tr class='rowxp0'><td class='list1nohighlight' colspan='$this->numColumns' style='height:100%;border-right:0px;width:100%;'>&nbsp;</td></tr>";
216     }
218     // Line color alternation
219     $alt= 0;
220     $deps= 0;
222     // Draw department browser if configured and we're not in sub mode
223     if ($this->departmentBrowser && $this->filter->scope != "sub") {
224       // Fill with department browser if configured this way
225       $departmentIterator= new departmentSortIterator($this->departments, $this->sortDirection[$this->sortColumn]);
226       foreach ($departmentIterator as $row => $entry){
227         $result.="<tr class='rowxp".($alt&1)."'>";
229         // Render multi select if needed
230         if ($this->multiSelect) {
231           $result.="<td style='text-align:center;width:20px;' class='list1'>&nbsp;</td>";
232         }
234         // Render defined department columns, fill the rest with some stuff
235         foreach ($this->xmlData['table']['department'] as $index => $config) {
236           $result.="<td ".$this->colprops[$index]." class='list1'>".$this->renderCell($config['value'], $entry, $row)."</td>";
237         }
238         $last= count($this->xmlData['table']['department']) + 1;
239         $rest= $this->numColumns - $last;
240         for ($i= 0; $i<$rest; $i++){
241           $result.= "<td ".$this->colprops[$last+$i-1]." class='list1'>&nbsp;</td>";
242         }
243         $result.="</tr>";
245         $alt++;
246       }
247       $deps= $alt;
248     }
250     // Fill with contents, sort as configured
251     $entryIterator= new listingSortIterator($this->entries, $this->sortDirection[$this->sortColumn], $this->sortAttribute, $this->sortType);
252     foreach ($entryIterator as $row => $entry){
253       $result.="<tr class='rowxp".($alt&1)."'>";
255       // Render multi select if needed
256       if ($this->multiSelect) {
257         $result.="<td style='text-align:center;width:20px;' class='list0'><input type='checkbox' id='listing_selected_$row' name='listing_selected_$row'></td>";
258       }
260       foreach ($this->xmlData['table']['column'] as $index => $config) {
261         $result.="<td ".$this->colprops[$index]." class='list0'>".$this->renderCell($config['value'], $entry, $row)."</td>";
262       }
263       $result.="</tr>";
265       $alt++;
266     }
268     // Need to fill the list if it's not full (nobody knows why this is 22 ;-))
269     $emptyListStyle= (count($this->entries) + count($deps) == 0)?"border:0;":"";
270     if (count($this->entries) + count($deps) < 22) {
271       $result.= "<tr>";
272       for ($i= 0; $i<$this->numColumns; $i++) {
273         if ($i == 0) {
274           $result.= "<td class='list1nohighlight' style='$emptyListStyle height:100%;'>&nbsp;</td>";
275           continue;
276         }
277         if ($i != $this->numColumns-1) {
278           $result.= "<td class='list1nohighlight' style='$emptyListStyle'>&nbsp;</td>";
279         } else {
280           $result.= "<td class='list1nohighlight' style='border-right:1px solid #AAA;$emptyListStyle'>&nbsp;</td>";
281         }
282       }
283       $result.= "</tr>";
284     }
286     $result.= "</table></div></td></tr>";
288     // Add the footer if requested
289     if ($this->showFooter) {
290       $result.= "<tr><td class='scrollhead'><table summary='' style='width:100%' cellspacing='0' id='t_scrollfoot'><tr><td class='listfooter' style='border-bottom:0px;'>";
292       foreach ($this->objectTypes as $objectType) {
293         if (isset($this->objectTypeCount[$objectType['label']])) {
294           $label= _($objectType['label']);
295           $result.= "<img class='center' src='".$objectType['image']."' title='$label' alt='$label'>&nbsp;".$this->objectTypeCount[$objectType['label']]."&nbsp;&nbsp;&nbsp;&nbsp;";
296         }
297       }
299       $result.= "<td class='listfooter' style='width:13px;border-right:0px;'>&nbsp;</td></table></td></tr>";
300     }
302     $result.= "</table></div>";
304     $smarty= get_smarty();
305     $smarty->assign("FILTER", $this->filter->render());
306     $smarty->assign("LIST", $result);
308     // Assign navigation elements
309     $nav= $this->renderNavigation();
310     foreach ($nav as $key => $html) {
311       $smarty->assign($key, $html);
312     }
314     // Assign action menu / base
315     $smarty->assign("ACTIONS", $this->renderActionMenu());
316     $smarty->assign("BASE", $this->renderBase());
318     // Assign separator
319     $smarty->assign("SEPARATOR", "<img src='images/lists/seperator.png' alt='-' align='middle' height='16' width='1' class='center'>");
321     // Assign summary
322     $smarty->assign("HEADLINE", $this->headline);
324     return ($smarty->fetch(get_template_path($this->xmlData['definition']['template'], true)));
325   }
328   function setFilter($filter)
329   {
330     $this->filter= &$filter;
331     if ($this->departmentBrowser){
332       $this->departments= $this->getDepartments();
333     }
334     $this->filter->setBase($this->base);
335     $this->entries= $this->filter->query();
336   }
339   function update()
340   {
341     global $config;
342     $ui= get_userinfo();
344     // Do not do anything if this is not our PID
345     if(isset($_REQUEST['PID']) && $_REQUEST['PID'] != $this->pid) {
346       return;
347     }
349     // Save base
350     if (isset($_POST['BASE']) && $this->baseMode == true) {
351       $base= validate($_POST['BASE']);
352       if (isset($this->bases[$base])) {
353         $this->base= $base;
354       }
355     }
357     // Override the base if we got a message from the browser navigation
358     if ($this->departmentBrowser && isset($_GET['act'])) {
359       if (preg_match('/^department_([0-9]+)$/', validate($_GET['act']), $match)){
360         if (isset($this->departments[$match[1]])){
361           $this->base= $this->departments[$match[1]]['dn'];
362         }
363       }
364     }
366     // Filter GET with "act" attributes
367     if (isset($_GET['act'])) {
368       $key= validate($_GET['act']);
369       if (preg_match('/^SORT_([0-9]+)$/', $key, $match)) {
370         // Switch to new column or invert search order?
371         $column= $match[1];
372         if ($this->sortColumn != $column) {
373           $this->sortColumn= $column;
374         } else {
375           $this->sortDirection[$column]= !$this->sortDirection[$column];
376         }
378         // Allow header to update itself according to the new sort settings
379         $this->renderHeader();
380       }
381     }
383     // Override base if we got signals from the navigation elements
384     $action= "";
385     foreach ($_POST as $key => $value) {
386       if (preg_match('/^(ROOT|BACK|HOME)_x$/', $key, $match)) {
387         $action= $match[1];
388         break;
389       }
390     }
392     // Navigation handling
393     if ($action == 'ROOT') {
394       $deps= $ui->get_module_departments($this->module);
395       $this->base= $deps[0];
396     }
397     if ($action == 'BACK') {
398       $deps= $ui->get_module_departments($this->module);
399       $base= preg_replace("/^[^,]+,/", "", $this->base);
400       if(in_array_ics($base, $deps)){
401         $this->base= $base;
402       }
403     }
404     if ($action == 'HOME') {
405       $ui= get_userinfo();
406       $this->base= get_base_from_people($ui->dn);
407     }
409     // Reload departments
410     if ($this->departmentBrowser){
411       $this->departments= $this->getDepartments();
412     }
414     // Update filter and refresh entries
415     $this->filter->setBase($this->base);
416     $this->entries= $this->filter->query();
417   }
420   function parseLayout($layout)
421   {
422     $result= array();
423     $layout= preg_replace("/^\|/", "", $layout);
424     $layout= preg_replace("/\|$/", "", $layout);
425     $cols= split("\|", $layout);
426     foreach ($cols as $index => $config) {
427       if ($config != "") {
428         $components= split(';', $config);
429         $config= "";
430         foreach ($components as $part) {
431           if (preg_match("/^r$/", $part)) {
432             $config.= "text-align:right;";
433             continue;
434           }
435           if (preg_match("/^l$/", $part)) {
436             $config.= "text-align:left;";
437             continue;
438           }
439           if (preg_match("/^c$/", $part)) {
440             $config.= "text-align:center;";
441             continue;
442           }
443           if (preg_match("/^[0-9]+(|px|%)$/", $part)) {
444             $config.= "width:$part;";
445             continue;
446           }
447         }
449         $result[$index]= " style='$config' ";
450       } else {
451         $result[$index]= null;
452       }
453     }
455     // Save number of columns for later use
456     $this->numColumns= count($cols);
458     return $result;
459   }
462   function renderCell($data, $config, $row)
463   {
464     // Replace flat attributes in data string
465     for ($i= 0; $i<$config['count']; $i++) {
466       $attr= $config[$i];
467       $value= "";
468       if (is_array($config[$attr])) {
469         $value= $config[$attr][0];
470       } else {
471         $value= $config[$attr];
472       }
473       $data= preg_replace("/%\{$attr\}/", $value, $data);
474     }
476     // Watch out for filters and prepare to execute them
477     $data= $this->processElementFilter($data, $config, $row);
479     return $data;
480   }
483   function renderBase()
484   {
485     $result= "<select name='BASE' onChange='mainform.submit()' size='1'>";
486     $firstDN= null;
487     $found= false;
489     foreach ($this->bases as $key=>$value) {
490       // Keep first entry to fall back eventually
491       if(!$firstDN) {
492         $firstDN= $key;
493       }
495       // Prepare to render entry
496       $selected= "";
497       if ($key == $this->base) {
498         $selected= " selected";
499         $found= true;
500       }
501       $result.= "<option value='".$key."'$selected>".$value."</option>";
502     }
503     $result.= "</select>";
505     // Reset the currently used base to the first DN we found if there
506     // was no match.
507     if(!$found){
508       $this->base = $firstDN;
509     }
511     return $result;
512   }
515   function processElementFilter($data, $config, $row)
516   {
517     preg_match_all("/%\{filter:([^(]+)\((.*)\)\}/", $data, $matches, PREG_SET_ORDER);
519     foreach ($matches as $match) {
520       if (!isset($this->filters[$match[1]])) {
521         continue;
522       }
523       $cl= preg_replace('/::.*$/', '', $this->filters[$match[1]]);
524       $method= preg_replace('/^.*::/', '', $this->filters[$match[1]]);
526       // Prepare params for function call
527       $params= array();
528       preg_match_all('/"[^"]+"|[^,]+/', $match[2], $parts);
529       foreach ($parts[0] as $param) {
531         // Row is replaced by the row number
532         if ($param == "row") {
533           $params[]= $row;
534         }
536         // pid is replaced by the current PID
537         if ($param == "pid") {
538           $params[]= $this->pid;
539         }
541         // Fixie with "" is passed directly
542         if (preg_match('/^".*"$/', $param)){
543           $params[]= preg_replace('/"/', '', $param);
544         }
546         // LDAP variables get replaced by their objects
547         for ($i= 0; $i<$config['count']; $i++) {
548           if ($param == $config[$i]) {
549             $values= $config[$config[$i]];
550             if (is_array($values)){
551               unset($values['count']);
552             }
553             $params[]= $values;
554           }
555         }
557         // Move dn if needed
558         if ($param == "dn") {
559           $params[]= LDAP::fix($config["dn"]);
560         }
561       }
563       // Replace information
564       if ($cl == "listing") {
565         // Non static call - seems to result in errors
566         $data= @preg_replace('/'.preg_quote($match[0]).'/', call_user_func_array(array($this, "$method"), $params), $data);
567       } else {
568         // Static call
569         $data= preg_replace('/'.preg_quote($match[0]).'/', call_user_func_array(array($cl, $method), $params), $data);
570       }
571     }
573     return $data;
574   }
577   function getObjectType($types, $classes)
578   {
579     // Walk thru types and see if there's something matching
580     foreach ($types as $objectType) {
581       $ocs= $objectType['objectClass'];
582       if (!is_array($ocs)){
583         $ocs= array($ocs);
584       }
586       $found= true;
587       foreach ($ocs as $oc){
588         if (preg_match('/^!(.*)$/', $oc, $match)) {
589           $oc= $match[1];
590           if (in_array($oc, $classes)) {
591             $found= false;
592           }
593         } else {
594           if (!in_array($oc, $classes)) {
595             $found= false;
596           }
597         }
598       }
600       if ($found) {
601         return $objectType;
602       }
603     }
605     return null;
606   }
609   function filterObjectType($dn, $classes)
610   {
611     // Walk thru classes and return on first match
612     $result= "&nbsp;";
614     $objectType= $this->getObjectType($this->objectTypes, $classes);
615     if ($objectType) {
616       $result= "<img class='center' title='".LDAP::fix($dn)."' src='".$objectType["image"]."'>";
617       if (!isset($this->objectTypeCount[$objectType['label']])) {
618         $this->objectTypeCount[$objectType['label']]= 0;
619       }
620       $this->objectTypeCount[$objectType['label']]++;
621     }
622     return $result;
623   }
626   function filterActions($dn, $row, $classes)
627   {
628     // Do nothing if there's no menu defined
629     if (!isset($this->xmlData['actiontriggers']['action'])) {
630       return "&nbsp;";
631     }
633     // Go thru all actions
634     $result= "";
635     $actions= $this->xmlData['actiontriggers']['action'];
636     foreach($actions as $action) {
637       // Skip the entry completely if there's no permission to execute it
638       if (!$this->hasActionPermission($action, $dn)) {
639         continue;
640       }
642       // If there's an objectclass definition and we don't have it
643       // add an empty picture here.
644       if (isset($action['objectclass'])){
645         $objectclass= $action['objectclass'];
646         if (preg_match('/^!(.*)$/', $objectclass, $m)){
647           $objectclass= $m[1];
648           if(in_array($objectclass, $classes)) {
649             $result.= "<img src='images/empty.png' alt=' ' class='center' style='padding:1px'>";
650             continue;
651           }
652         } else {
653           if(!in_array($objectclass, $classes)) {
654             $result.= "<img src='images/empty.png' alt=' ' class='center' style='padding:1px'>";
655             continue;
656           }
657         }
658       }
660       // Render normal entries as usual
661       if ($action['type'] == "entry") {
662         $label= $this->processElementFilter($action['label'], $this->entries[$row], $row);
663         $image= $this->processElementFilter($action['image'], $this->entries[$row], $row);
664         $result.="<input class='center' type='image' src='$image' title='$label' ".
665                  "name='listing_".$action['name']."_$row' style='padding:1px'>";
666       }
668       // Handle special types
669       if ($action['type'] == "snapshot") {
670         #TODO
671         #echo "actiontriggers: snapshot missing<br>";
672       }
673       if ($action['type'] == "copypaste") {
674         #TODO
675         #echo "actiontriggers: copypaste missing<br>";
676       }
677       if ($action['type'] == "daemon") {
678         #TODO
679         #echo "actiontriggers: daemon missing<br>";
680       }
682     }
684     return $result;
685   }
688   function filterDepartmentLink($row, $dn, $description)
689   {
690     $attr= $this->departments[$row]['sort-attribute'];
691     $name= $this->departments[$row][$attr];
692     if (is_array($name)){
693       $name= $name[0];
694     }
695     $result= sprintf("%s [%s]", $name, $description[0]);
696     return("<a href='?plug=".$_GET['plug']."&amp;PID=$this->pid&amp;act=department_$row' title='$dn'>$result</a>");
697   }
700   function filterLink()
701   {
702     $result= "&nbsp;";
704     $row= func_get_arg(0);
705     $pid= $this->pid;
706     $dn= LDAP::fix(func_get_arg(1));
707     $params= array(func_get_arg(2));
709     // Collect sprintf params
710     for ($i = 3;$i < func_num_args();$i++) {
711       $val= func_get_arg($i);
712       if (is_array($val)){
713         $params[]= $val[0];
714         continue;
715       }
716       $params[]= $val;
717     }
719     $result= "&nbsp;";
720     $trans= call_user_func_array("sprintf", $params);
721     if ($trans != "") {
722       return("<a href='?plug=".$_GET['plug']."&amp;PID=$pid&amp;act=listing_edit_$row' title='$dn'>$trans</a>");
723     }
725     return $result;
726   }
729   function renderNavigation()
730   {
731     $result= array();
732     $enableBack = true;
733     $enableRoot = true;
734     $enableHome = true;
736     $ui = get_userinfo();
738     /* Check if base = first available base */
739     $deps = $ui->get_module_departments($this->module);
741     if(!count($deps) || $deps[0] == $this->filter->base){
742       $enableBack = false;
743       $enableRoot = false;
744     }
746     $listhead ="";
748     /* Check if we are in users home  department */
749     if(!count($deps) ||$this->filter->base == get_base_from_people($ui->dn)){
750       $enableHome = false;
751     }
753     /* Draw root button */
754     if($enableRoot){
755       $result["ROOT"]= "<input class='center' type='image' src='images/lists/root.png' align='middle' ".
756                        "title='"._("Go to root department")."' name='ROOT' alt='"._("Root")."'>";
757     }else{
758       $result["ROOT"]= "<img src='images/lists/root_grey.png' class='center' alt='"._("Root")."'>";
759     }
761     /* Draw back button */
762     if($enableBack){
763       $result["BACK"]= "<input class='center' type='image' align='middle' src='images/lists/back.png' ".
764                        "title='"._("Go up one department")."' alt='"._("Up")."' name='BACK'>";
765     }else{
766       $result["BACK"]= "<img src='images/lists/back_grey.png' class='center' alt='"._("Up")."'>";
767     }
769     /* Draw home button */
770     if($enableHome){
771       $result["HOME"]= "<input class='center' type='image' align='middle' src='images/lists/home.png' ".
772                        "title='"._("Go to users department")."' alt='"._("Home")."' name='HOME'>";
773     }else{
774       $result["HOME"]= "<img src='images/lists/home_grey.png' class='center' alt='"._("Home")."'>";
775     }
777     /* Draw reload button, this button is enabled everytime */
778     $result["RELOAD"]= "<input class='center' type='image' src='images/lists/reload.png' align='middle' ".
779                        "title='"._("Reload list")."' name='REFRESH' alt='"._("Submit")."'>";
781     return ($result);
782   }
785   function getAction()
786   {
787     // Do not do anything if this is not our PID
788     if(isset($_REQUEST['PID']) && $_REQUEST['PID'] != $this->pid) {
789       return;
790     }
792     $result= array("targets" => array(), "action" => "");
794     // Filter GET with "act" attributes
795     if (isset($_GET['act'])) {
796       $key= validate($_GET['act']);
797       $target= preg_replace('/^listing_[a-zA-Z_]+_([0-9]+)$/', '$1', $key);
798       if (isset($this->entries[$target]['dn'])) {
799         $result['action']= preg_replace('/^listing_([a-zA-Z_]+)_[0-9]+$/', '$1', $key);
800         $result['targets'][]= $this->entries[$target]['dn'];
801       }
803       // Drop targets if empty
804       if (count($result['targets']) == 0) {
805         unset($result['targets']);
806       }
807       return $result;
808     }
810     // Filter POST with "listing_" attributes
811     foreach ($_POST as $key => $prop) {
813       // Capture selections
814       if (preg_match('/^listing_selected_[0-9]+$/', $key)) {
815         $target= preg_replace('/^listing_selected_([0-9]+)$/', '$1', $key);
816         if (isset($this->entries[$target]['dn'])) {
817           $result['targets'][]= $this->entries[$target]['dn'];
818         }
819         continue;
820       }
822       // Capture action with target - this is a one shot
823       if (preg_match('/^listing_[a-zA-Z_]+_[0-9]+(|_x)$/', $key)) {
824         $target= preg_replace('/^listing_[a-zA-Z_]+_([0-9]+)(|_x)$/', '$1', $key);
825         if (isset($this->entries[$target]['dn'])) {
826           $result['action']= preg_replace('/^listing_([a-zA-Z_]+)_[0-9]+(|_x)$/', '$1', $key);
827           $result['targets']= array($this->entries[$target]['dn']);
828         }
829         break;
830       }
832       // Capture action without target
833       if (preg_match('/^listing_[a-zA-Z_]+(|_x)$/', $key)) {
834         $result['action']= preg_replace('/^listing_([a-zA-Z_]+)(|_x)$/', '$1', $key);
835         continue;
836       }
837     }
839     // Filter POST with "act" attributes -> posted from action menu
840     if (isset($_POST['act']) && $_POST['act'] != '') {
841       $result['action']= validate($_POST['act']);
842     }
844     // Drop targets if empty
845     if (count($result['targets']) == 0) {
846       unset($result['targets']);
847     }
848     return $result;
849   }
852   function renderActionMenu()
853   {
854     // Don't send anything if the menu is not defined
855     if (!isset($this->xmlData['actionmenu']['action'])){
856       return "";
857     }
859     // Load shortcut
860     $actions= &$this->xmlData['actionmenu']['action'];
861     $result= "<input type='hidden' name='act' id='actionmenu' value=''>".
862              "<ul class='level1' id='root'><li><a href='#'>Aktionen&nbsp;<img ".
863              "border=0 src='images/lists/sort-down.png'></a>";
865     // Build ul/li list
866     $result.= $this->recurseActions($actions);
868     return "<div id='pulldown'>".$result."</li></ul><div>";
869   }
872   function recurseActions($actions)
873   {
874     static $level= 2;
875     $result= "<ul class='level$level'>";
876     $separator= "";
878     foreach ($actions as $action) {
880       // Skip the entry completely if there's no permission to execute it
881       if (!$this->hasActionPermission($action, $this->filter->base)) {
882         continue;
883       }
885       // Fill image if set
886       $img= "";
887       if (isset($action['image'])){
888         $img= "<img border=0 src='".$action['image']."'>&nbsp;";
889       }
891       if ($action['type'] == "separator"){
892         $separator= " style='border-top:1px solid #AAA' ";
893         continue;
894       }
896       // Dive into subs
897       if ($action['type'] == "sub" && isset($action['action'])) {
898         $level++;
899         if (isset($action['label'])){
900           $result.= "<li$separator><a href='#'>$img"._($action['label'])."&nbsp;<img border='0' src='images/forward-arrow.png'></a>";
901         }
902         $result.= $this->recurseActions($action['action'])."</li>";
903         $level--;
904         $separator= "";
905         continue;
906       }
908       // Render entry elseways
909       if (isset($action['label'])){
910         $result.= "<li$separator><a href='#' onClick='document.getElementById(\"actionmenu\").value= \"".$action['name']."\";mainform.submit();'>$img"._($action['label'])."</a></li>";
911       }
913       // Check for special types
914       switch ($action['type']) {
915         case 'copypaste':
916           #TODO
917           #echo "actionmenu: copypaste missing<br>";
918           break;
920         case 'snapshot':
921           #TODO
922           #echo "actionmenu: snapshot missing<br>";
923           break;
925         case 'daemon':
926           #TODO
927           #echo "actionmenu: daemon missing<br>";
928           break;
929       }
931       $separator= "";
932     }
934     $result.= "</ul>";
935     return $result;
936   }
939   function hasActionPermission($action, $dn)
940   {
941     $ui= get_userinfo();
943     if (isset($action['acl'])) {
944       $acls= $action['acl'];
945       if (!is_array($acls)) {
946         $acls= array($acls);
947       }
949       // Every ACL has to pass
950       foreach ($acls as $acl) {
951         $module= $this->module;
952         $acllist= array();
954         // Split for category and plugins if needed
955         // match for "[rw]" style entries
956         if (preg_match('/^\[([rwcdm]+)\]$/', $acl, $match)){
957           $aclList= array($match[1]);
958         }
960         // match for "users[rw]" style entries
961         if (preg_match('/^([a-zA-Z0-9]+)\[([rwcdm]+)\]$/', $acl, $match)){
962           $module= $match[1];
963           $aclList= array($match[2]);
964         }
966         // match for "users/user[rw]" style entries
967         if (preg_match('/^([a-zA-Z0-9]+\/[a-zA-Z0-9]+)\[([rwcdm]+)\]$/', $acl, $match)){
968           $module= $match[1];
969           $aclList= array($match[2]);
970         }
972         // match "users/user[userPassword:rw(,...)*]" style entries
973         if (preg_match('/^([a-zA-Z0-9]+\/[a-zA-Z0-9]+)\[([a-zA-Z0-9]+:[rwcdm]+(,[a-zA-Z0-9]+:[rwcdm]+)*)\]$/', $acl, $match)){
974           $module= $match[1];
975           $aclList= split(',', $match[2]);
976         }
978         // Walk thru prepared ACL by using $module
979         foreach($aclList as $sAcl) {
980           $checkAcl= "";
982           // Category or detailed permission?
983           if (strpos('/', $module) === false) {
984             if (preg_match('/([a-zA-Z0-9]+):([rwcdm]+)/', $sAcl, $m) ) {
985               $checkAcl= $ui->get_permissions($dn, $module, $m[1]);
986               $sAcl= $m[2];
987             } else {
988               $checkAcl= $ui->get_permissions($dn, $module, '0');
989             }
990           } else {
991             $checkAcl= $ui->get_category_permissions($dn, $module);
992           }
994           // Split up remaining part of the acl and check if it we're
995           // allowed to do something...
996           $parts= str_split($sAcl);
997           foreach ($parts as $part) {
998             if (strpos($checkAcl, $part) === false){
999               return false;
1000             }
1001           }
1003         }
1004       }
1005     }
1007     return true;
1008   }
1011   function refreshBasesList()
1012   {
1013     global $config;
1014     $ui= get_userinfo();
1016     // Do some array munching to get it user friendly
1017     $ids= $config->idepartments;
1018     $d= $ui->get_module_departments($this->module);
1019     $k_ids= array_keys($ids);
1020     $deps= array_intersect($d,$k_ids);
1022     // Fill internal bases list
1023     $this->bases= array();
1024     foreach($k_ids as $department){
1025       $this->bases[$department] = $ids[$department];
1026     }
1027   }
1030   function getDepartments()
1031   {
1032     $departments= array();
1033     $ui= get_userinfo();
1035     // Get list of supported department types
1036     $types = departmentManagement::get_support_departments();
1038     // Load departments allowed by ACL
1039     $validDepartments = $ui->get_module_departments($this->module);
1041     // Build filter and look in the LDAP for possible sub departments
1042     // of current base
1043     $filter= "(&(objectClass=gosaDepartment)(|";
1044     $attrs= array("description", "objectClass");
1045     foreach($types as $name => $data){
1046       $filter.= "(objectClass=".$data['OC'].")";
1047       $attrs[]= $data['ATTR'];
1048     }
1049     $filter.= "))";
1050     $res= get_list($filter, $this->module, $this->base, $attrs, GL_NONE | GL_SIZELIMIT);
1052     // Analyze list of departments
1053     foreach ($res as $department) {
1054       if (!in_array($department['dn'], $validDepartments)) {
1055         continue;
1056       }
1058       // Add the attribute where we use for sorting
1059       $oc= null;
1060       foreach(array_keys($types) as $type) {
1061         if (in_array($type, $department['objectClass'])) {
1062           $oc= $type;
1063           break;
1064         }
1065       }
1066       $department['sort-attribute']= $types[$oc]['ATTR'];
1068       // Move to the result list
1069       $departments[]= $department;
1070     }
1072     return $departments;
1073   }
1077 ?>