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]."> </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;'> </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%;'> </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%;'> </td>";
155 continue;
156 }
157 if ($i != $num_cols-1) {
158 $result.= "<td class='list1nohighlight' style='$emptyListStyle'> </td>";
159 } else {
160 $result.= "<td class='list1nohighlight' style='border-right:1px solid #AAA;$emptyListStyle'> </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'> ".$this->objectTypeCount[$objectType['label']]." ";
176 }
177 }
179 $result.= "<td class='listfooter' style='width:13px;border-right:0px;'> </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= " ";
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 " ";
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= " ";
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= " ";
479 $trans= call_user_func_array("sprintf", $params);
480 if ($trans != "") {
481 return("<a href='?plug=".$_GET['plug']."&PID=$pid&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 <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']."'> ";
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'])." <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 }
770 }
772 ?>