8a85b501663d3ea9b52ab643058e578bb7ed5675
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;
20 function listing($filename)
21 {
22 if (!$this->load($filename)) {
23 die("Cannot parse $filename!");
24 }
26 // Register build in filters
27 $this->registerElementFilter("objectType", "listing::filterObjectType");
28 $this->registerElementFilter("link", "listing::filterLink");
29 $this->registerElementFilter("actions", "listing::filterActions");
31 // Initialize pid
32 $this->pid= preg_replace("/[^0-9]/", "", microtime(TRUE));
33 }
36 function registerElementFilter($name, $call)
37 {
38 if (!isset($this->filters[$name])) {
39 $this->filters[$name]= $call;
40 return true;
41 }
43 return false;
44 }
47 function load($filename)
48 {
49 $contents = file_get_contents($filename);
50 $this->xmlData= xml::xml2array($contents, 1);
52 if (!isset($this->xmlData['list'])) {
53 return false;
54 }
56 $this->xmlData= $this->xmlData["list"];
58 // Load some definition values
59 foreach (array("departmentBrowser", "departmentRootVisible", "multiSelect") as $token) {
60 if (isset($this->xmlData['definition'][$token]) &&
61 $this->xmlData['definition'][$token] == "true"){
62 $this->$token= true;
63 }
64 }
66 // Fill objectTypes
67 if (isset($this->xmlData['definition']['objectType'])) {
68 foreach ($this->xmlData['definition']['objectType'] as $index => $otype) {
69 $this->objectTypes[$otype['objectClass']]= $this->xmlData['definition']['objectType'][$index];
70 }
71 }
73 // Parse layout per column
74 $this->colprops= $this->parseLayout($this->xmlData['table']['layout']);
76 // Prepare table headers
77 $this->header= array();
78 if (isset($this->xmlData['table']['column'])){
79 foreach ($this->xmlData['table']['column'] as $index => $config) {
80 if (isset($config['label'])) {
81 $this->header[$index]= "<td class='listheader' ".$this->colprops[$index].">"._($config['label'])."</td>";
82 } else {
83 $this->header[$index]= "<td class='listheader' ".$this->colprops[$index]."> </td>";
84 }
85 }
86 }
88 // Assign headline/module
89 $this->headline= _($this->xmlData['definition']['label']);
90 $this->module= $this->xmlData['definition']['module'];
92 return true;
93 }
96 function render()
97 {
98 echo "sorting, department browsing, filter base handling, bottom list info, copy'n paste handler, snapshot handler<br>";
100 // Initialize list
101 $result= "<input type='hidden' value='$this->pid' name='PID'>";
102 $result.= "<div class='contentboxb' id='listing_container' style='border-top:1px solid #B0B0B0;'>";
103 $result.= "<table summary='$this->headline' style='width:600px;height:450px;' cellspacing='0' id='t_scrolltable'>
104 <tr><td class='scrollhead'><table summary='' style='width:100%;' cellspacing='0' id='t_scrollhead'>";
105 $num_cols= count($this->colprops) + ($this->multiSelect?1:0);
107 // Build list header
108 $result.= "<tr>";
109 if ($this->multiSelect) {
110 $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>";
111 }
112 foreach ($this->header as $header) {
113 $result.= $header;
114 }
116 // Add 13px for scroller
117 $result.= "<td class='listheader' style='width:13px;border-right:0px;'> </td></table></td></tr>";
119 // New table for the real list contents
120 $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'>";
122 // No results? Just take an empty colspanned row
123 if (count($this->entries) == 0) {
124 $result.= "<tr class='rowxp0'><td class='list1nohighlight' colspan='$num_cols' style='height:100%;border-right:0px;width:100%;'> </td></tr>";
125 }
127 // Fill with contents
128 foreach ($this->entries as $row => $entry){
129 $result.="<tr class='rowxp".($row&1)."'>";
131 // Render multi select if needed
132 if ($this->multiSelect) {
133 $result.="<td style='text-align:center;width:20px;' class='list0'><input type='checkbox' id='listing_selected_$row' name='listing_selected_$row'></td>";
134 }
136 foreach ($this->xmlData['table']['column'] as $index => $config) {
137 $result.="<td ".$this->colprops[$index]." class='list0'>".$this->renderCell($config['value'], $entry, $row)."</td>";
138 }
139 $result.="</tr>";
140 }
142 // Need to fill the list if it's not full (nobody knows why this is 22 ;-))
143 if (count($this->entries) < 22) {
144 $result.= "<tr>";
145 for ($i= 0; $i<$num_cols; $i++) {
146 if ($i == 0) {
147 $result.= "<td class='list1nohighlight' style='height:100%;'> </td>";
148 continue;
149 }
150 if ($i != $num_cols-1) {
151 $result.= "<td class='list1nohighlight''> </td>";
152 } else {
153 $result.= "<td class='list1nohighlight' style='border-right:1px solid #AAA'> </td>";
154 }
155 }
156 $result.= "</tr>";
157 }
159 $result.= "</table></div></td></tr></table></div>";
161 $smarty= get_smarty();
162 $smarty->assign("FILTER", $this->filter->render());
163 $smarty->assign("LIST", $result);
165 // Assign navigation elements
166 $nav= $this->renderNavigation();
167 foreach ($nav as $key => $html) {
168 $smarty->assign($key, $html);
169 }
171 // Assign action menu
172 $smarty->assign("ACTIONS", $this->renderActionMenu());
174 // Assign separator
175 $smarty->assign("SEPARATOR", "<img src='images/lists/seperator.png' alt='-' align='middle' height='16' width='1' class='center'>");
177 // Assign summary
178 $smarty->assign("HEADLINE", $this->headline);
180 return ($smarty->fetch(get_template_path($this->xmlData['definition']['template'], true)));
181 }
184 function setFilter($filter)
185 {
186 $this->filter= &$filter;
187 $this->entries= $this->filter->query();
188 }
191 function update()
192 {
193 // Do not do anything if this is not our PID
194 # DISABLED because the object is not in the session
195 #if(isset($_REQUEST['PID']) && $_REQUEST['PID'] != $this->pid) {
196 # return;
197 #}
199 $this->entries= $this->filter->query();
200 }
203 function parseLayout($layout)
204 {
205 $result= array();
206 $layout= preg_replace("/^\|/", "", $layout);
207 $layout= preg_replace("/\|$/", "", $layout);
208 $cols= split("\|", $layout);
209 foreach ($cols as $index => $config) {
210 if ($config != "") {
211 $components= split(';', $config);
212 $config= "";
213 foreach ($components as $part) {
214 if (preg_match("/^r$/", $part)) {
215 $config.= "text-align:right;";
216 continue;
217 }
218 if (preg_match("/^l$/", $part)) {
219 $config.= "text-align:left;";
220 continue;
221 }
222 if (preg_match("/^c$/", $part)) {
223 $config.= "text-align:center;";
224 continue;
225 }
226 if (preg_match("/^[0-9]+(|px|%)$/", $part)) {
227 $config.= "width:$part;";
228 continue;
229 }
230 }
232 $result[$index]= " style='$config' ";
233 } else {
234 $result[$index]= null;
235 }
236 }
238 return $result;
239 }
242 function renderCell($data, $config, $row)
243 {
244 // Replace flat attributes in data string
245 for ($i= 0; $i<$config['count']; $i++) {
246 $attr= $config[$i];
247 $value= "";
248 if (is_array($config[$attr])) {
249 $value= $config[$attr][0];
250 } else {
251 $value= $config[$attr];
252 }
253 $data= preg_replace("/%\{$attr\}/", $value, $data);
254 }
256 // Watch out for filters and prepare to execute them
257 $data= $this->processElementFilter($data, $config, $row);
259 return $data;
260 }
263 function processElementFilter($data, $config, $row)
264 {
265 preg_match_all("/%\{filter:([^(]+)\((.*)\)\}/", $data, $matches, PREG_SET_ORDER);
267 foreach ($matches as $match) {
268 if (!isset($this->filters[$match[1]])) {
269 continue;
270 }
271 $cl= preg_replace('/::.*$/', '', $this->filters[$match[1]]);
272 $method= preg_replace('/^.*::/', '', $this->filters[$match[1]]);
274 // Prepare params for function call
275 $params= array();
276 preg_match_all('/"[^"]+"|[^,]+/', $match[2], $parts);
277 foreach ($parts[0] as $param) {
279 // Row is replaced by the row number
280 if ($param == "row") {
281 $params[]= $row;
282 }
284 // pid is replaced by the current PID
285 if ($param == "pid") {
286 $params[]= $this->pid;
287 }
289 // Fixie with "" is passed directly
290 if (preg_match('/^".*"$/', $param)){
291 $params[]= preg_replace('/"/', '', $param);
292 }
294 // LDAP variables get replaced by their objects
295 for ($i= 0; $i<$config['count']; $i++) {
296 if ($param == $config[$i]) {
297 $values= $config[$config[$i]];
298 if (is_array($values)){
299 unset($values['count']);
300 }
301 $params[]= $values;
302 }
303 }
305 // Move dn if needed
306 if ($param == "dn") {
307 $params[]= LDAP::fix($config["dn"]);
308 }
309 }
311 // Replace information
312 if ($cl == "listing") {
313 // Non static call - seems to result in errors
314 $data= @preg_replace('/'.preg_quote($match[0]).'/', call_user_func_array(array($this, "$method"), $params), $data);
315 } else {
316 // Static call
317 $data= preg_replace('/'.preg_quote($match[0]).'/', call_user_func_array(array($cl, $method), $params), $data);
318 }
319 }
321 return $data;
322 }
325 function filterObjectType($dn, $classes)
326 {
327 // Walk thru classes and return on first match
328 $result= " ";
329 $prio= 99;
330 foreach ($classes as $objectClass) {
331 if (isset($this->objectTypes[$objectClass])){
332 if (!isset($this->objectTypes[$objectClass]["priority"])){
333 $result= "<img class='center' title='".LDAP::fix($dn)."' src='".$this->objectTypes[$objectClass]["image"]."'>";
334 return $result;
335 }
337 if ($this->objectTypes[$objectClass]["priority"] < $prio){
338 $prio= $this->objectTypes[$objectClass]["priority"];
339 $result= "<img class='center' title='".LDAP::fix($dn)."' src='".$this->objectTypes[$objectClass]["image"]."'>";
340 }
341 }
342 }
344 return $result;
345 }
348 function filterActions($dn, $row, $classes)
349 {
350 // Do nothing if there's no menu defined
351 if (!isset($this->xmlData['actiontriggers']['action'])) {
352 return " ";
353 }
355 // Go thru all actions
356 $result= "";
357 $actions= $this->xmlData['actiontriggers']['action'];
358 foreach($actions as $action) {
359 // Skip the entry completely if there's no permission to execute it
360 if (!$this->hasActionPermission($action, $dn)) {
361 continue;
362 }
364 // If there's an objectclass definition and we don't have it
365 // add an empty picture here.
366 if (isset($action['objectclass'])){
367 $objectclass= $action['objectclass'];
368 if (preg_match('/^!(.*)$/', $objectclass, $m)){
369 $objectclass= $m[1];
370 if(in_array($objectclass, $classes)) {
371 $result.= "<img src='images/empty.png' alt=' ' class='center' style='padding:1px'>";
372 continue;
373 }
374 } else {
375 if(!in_array($objectclass, $classes)) {
376 $result.= "<img src='images/empty.png' alt=' ' class='center' style='padding:1px'>";
377 continue;
378 }
379 }
380 }
382 // Render normal entries as usual
383 if ($action['type'] == "entry") {
384 $label= $this->processElementFilter($action['label'], $this->entries[$row], $row);
385 $image= $this->processElementFilter($action['image'], $this->entries[$row], $row);
386 $result.="<input class='center' type='image' src='$image' title='$label' ".
387 "name='listing_".$action['name']."_$row' style='padding:1px'>";
388 }
390 // Handle special types
391 if ($action['type'] == "snapshot") {
392 #TODO
393 #echo "actiontriggers: snapshot missing<br>";
394 }
395 if ($action['type'] == "copypaste") {
396 #TODO
397 #echo "actiontriggers: copypaste missing<br>";
398 }
399 if ($action['type'] == "daemon") {
400 #TODO
401 #echo "actiontriggers: daemon missing<br>";
402 }
404 }
406 return $result;
407 }
410 function filterLink()
411 {
412 $result= " ";
414 $row= func_get_arg(0);
415 $pid= func_get_arg(1);
416 $dn= LDAP::fix(func_get_arg(2));
417 $params= array(func_get_arg(3));
419 // Collect sprintf params
420 for ($i = 4;$i < func_num_args();$i++) {
421 $val= func_get_arg($i);
422 if (is_array($val)){
423 $params[]= $val[0];
424 continue;
425 }
426 $params[]= $val;
427 }
429 $result= " ";
430 $trans= call_user_func_array("sprintf", $params);
431 if ($trans != "") {
432 return("<a href='?plug=".$_GET['plug']."&PID=$pid&act=listing_edit_$row' title='$dn'>$trans</a>");
433 }
435 return $result;
436 }
439 function renderNavigation()
440 {
441 $result= array();
442 $enableBack = true;
443 $enableRoot = true;
444 $enableHome = true;
446 $ui = get_userinfo();
448 /* Check if base = first available base */
449 $deps = $ui->get_module_departments($this->module);
451 if(!count($deps) || $deps[0] == $this->filter->base){
452 $enableBack = false;
453 $enableRoot = false;
454 }
456 $listhead ="";
458 /* Check if we are in users home department */
459 if(!count($deps) ||$this->filter->base == get_base_from_people($ui->dn)){
460 $enableHome = false;
461 }
463 /* Draw root button */
464 if($enableRoot){
465 $result["ROOT"]= "<input class='center' type='image' src='images/lists/root.png' align='middle' ".
466 "title='"._("Go to root department")."' name='dep_root' alt='"._("Root")."'>";
467 }else{
468 $result["ROOT"]= "<img src='images/lists/root_grey.png' class='center' alt='"._("Root")."'>";
469 }
471 /* Draw back button */
472 if($enableBack){
473 $result["BACK"]= "<input class='center' type='image' align='middle' src='images/lists/back.png' ".
474 "title='"._("Go up one department")."' alt='"._("Up")."' name='dep_back'>";
475 }else{
476 $result["BACK"]= "<img src='images/lists/back_grey.png' class='center' alt='"._("Up")."'>";
477 }
479 /* Draw home button */
480 if($enableHome){
481 $result["HOME"]= "<input class='center' type='image' align='middle' src='images/lists/home.png' ".
482 "title='"._("Go to users department")."' alt='"._("Home")."' name='dep_home'>";
483 }else{
484 $result["HOME"]= "<img src='images/lists/home_grey.png' class='center' alt='"._("Home")."'>";
485 }
487 /* Draw reload button, this button is enabled everytime */
488 $result["RELOAD"]= "<input class='center' type='image' src='images/lists/reload.png' align='middle' ".
489 "title='"._("Reload list")."' name='submit_department' alt='"._("Submit")."'>";
491 return ($result);
492 }
495 function getAction()
496 {
497 // Do not do anything if this is not our PID
498 # DISABLED because the object is not in the session
499 #if(isset($_REQUEST['PID']) && $_REQUEST['PID'] != $this->pid) {
500 # return;
501 #}
503 $result= array("targets" => array(), "action" => "");
505 // Filter GET with "act" attributes
506 if (isset($_GET['act'])) {
507 $key= validate($_GET['act']);
508 $target= preg_replace('/^listing_[a-zA-Z_]+_([0-9]+)$/', '$1', $key);
509 if (isset($this->entries[$target]['dn'])) {
510 $result['action']= preg_replace('/^listing_([a-zA-Z_]+)_[0-9]+$/', '$1', $key);
511 $result['targets'][]= $this->entries[$target]['dn'];
512 }
514 // Drop targets if empty
515 if (count($result['targets']) == 0) {
516 unset($result['targets']);
517 }
518 return $result;
519 }
521 // Filter POST with "listing_" attributes
522 foreach ($_POST as $key => $prop) {
524 // Capture selections
525 if (preg_match('/^listing_selected_[0-9]+$/', $key)) {
526 $target= preg_replace('/^listing_selected_([0-9]+)$/', '$1', $key);
527 if (isset($this->entries[$target]['dn'])) {
528 $result['targets'][]= $this->entries[$target]['dn'];
529 }
530 continue;
531 }
533 // Capture action with target - this is a one shot
534 if (preg_match('/^listing_[a-zA-Z_]+_[0-9]+(|_x)$/', $key)) {
535 $target= preg_replace('/^listing_[a-zA-Z_]+_([0-9]+)(|_x)$/', '$1', $key);
536 if (isset($this->entries[$target]['dn'])) {
537 $result['action']= preg_replace('/^listing_([a-zA-Z_]+)_[0-9]+(|_x)$/', '$1', $key);
538 $result['targets']= array($this->entries[$target]['dn']);
539 }
540 break;
541 }
543 // Capture action without target
544 if (preg_match('/^listing_[a-zA-Z_]+(|_x)$/', $key)) {
545 $result['action']= preg_replace('/^listing_([a-zA-Z_]+)(|_x)$/', '$1', $key);
546 continue;
547 }
548 }
550 // Filter POST with "act" attributes -> posted from action menu
551 if (isset($_POST['act']) && $_POST['act'] != '') {
552 $result['action']= validate($_POST['act']);
553 }
555 // Drop targets if empty
556 if (count($result['targets']) == 0) {
557 unset($result['targets']);
558 }
559 return $result;
560 }
563 function renderActionMenu()
564 {
565 // Don't send anything if the menu is not defined
566 if (!isset($this->xmlData['actionmenu']['action'])){
567 return "";
568 }
570 // Load shortcut
571 $actions= &$this->xmlData['actionmenu']['action'];
572 $result= "<input type='hidden' name='act' id='actionmenu' value=''>".
573 "<ul class='level1' id='root'><li><a href='#'>Aktionen <img ".
574 "border=0 src='images/lists/sort-down.png'></a>";
576 // Build ul/li list
577 $result.= $this->recurseActions($actions);
579 return "<div id='pulldown'>".$result."</li></ul><div>";
580 }
583 function recurseActions($actions)
584 {
585 static $level= 2;
586 $result= "<ul class='level$level'>";
587 $separator= "";
589 foreach ($actions as $action) {
591 // Skip the entry completely if there's no permission to execute it
592 if (!$this->hasActionPermission($action, $this->filter->base)) {
593 continue;
594 }
596 // Fill image if set
597 $img= "";
598 if (isset($action['image'])){
599 $img= "<img border=0 src='".$action['image']."'> ";
600 }
602 if ($action['type'] == "separator"){
603 $separator= " style='border-top:1px solid #AAA' ";
604 continue;
605 }
607 // Dive into subs
608 if ($action['type'] == "sub" && isset($action['action'])) {
609 $level++;
610 if (isset($action['label'])){
611 $result.= "<li$separator><a href='#'>$img"._($action['label'])." <img border='0' src='images/forward-arrow.png'></a>";
612 }
613 $result.= $this->recurseActions($action['action'])."</li>";
614 $level--;
615 $separator= "";
616 continue;
617 }
619 // Render entry elseways
620 if (isset($action['label'])){
621 $result.= "<li$separator><a href='#' onClick='document.getElementById(\"actionmenu\").value= \"".$action['name']."\";mainform.submit();'>$img"._($action['label'])."</a></li>";
622 }
624 // Check for special types
625 switch ($action['type']) {
626 case 'copypaste':
627 #TODO
628 #echo "actionmenu: copypaste missing<br>";
629 break;
631 case 'snapshot':
632 #TODO
633 #echo "actionmenu: snapshot missing<br>";
634 break;
636 case 'daemon':
637 #TODO
638 #echo "actionmenu: daemon missing<br>";
639 break;
640 }
642 $separator= "";
643 }
645 $result.= "</ul>";
646 return $result;
647 }
650 function hasActionPermission($action, $dn)
651 {
652 $ui= get_userinfo();
654 if (isset($action['acl'])) {
655 $acls= $action['acl'];
656 if (!is_array($acls)) {
657 $acls= array($acls);
658 }
660 // Every ACL has to pass
661 foreach ($acls as $acl) {
662 $module= $this->module;
663 $acllist= array();
665 // Split for category and plugins if needed
666 // match for "[rw]" style entries
667 if (preg_match('/^\[([rwcdm]+)\]$/', $acl, $match)){
668 $aclList= array($match[1]);
669 }
671 // match for "users[rw]" style entries
672 if (preg_match('/^([a-zA-Z0-9]+)\[([rwcdm]+)\]$/', $acl, $match)){
673 $module= $match[1];
674 $aclList= array($match[2]);
675 }
677 // match for "users/user[rw]" style entries
678 if (preg_match('/^([a-zA-Z0-9]+\/[a-zA-Z0-9]+)\[([rwcdm]+)\]$/', $acl, $match)){
679 $module= $match[1];
680 $aclList= array($match[2]);
681 }
683 // match "users/user[userPassword:rw(,...)*]" style entries
684 if (preg_match('/^([a-zA-Z0-9]+\/[a-zA-Z0-9]+)\[([a-zA-Z0-9]+:[rwcdm]+(,[a-zA-Z0-9]+:[rwcdm]+)*)\]$/', $acl, $match)){
685 $module= $match[1];
686 $aclList= split(',', $match[2]);
687 }
689 // Walk thru prepared ACL by using $module
690 foreach($aclList as $sAcl) {
691 $checkAcl= "";
693 // Category or detailed permission?
694 if (strpos('/', $module) === false) {
695 if (preg_match('/([a-zA-Z0-9]+):([rwcdm]+)/', $sAcl, $m) ) {
696 $checkAcl= $ui->get_permissions($dn, $module, $m[1]);
697 $sAcl= $m[2];
698 } else {
699 $checkAcl= $ui->get_permissions($dn, $module, '0');
700 }
701 } else {
702 $checkAcl= $ui->get_category_permissions($dn, $module);
703 }
705 // Split up remaining part of the acl and check if it we're
706 // allowed to do something...
707 $parts= str_split($sAcl);
708 foreach ($parts as $part) {
709 if (strpos($checkAcl, $part) === false){
710 return false;
711 }
712 }
714 }
715 }
716 }
718 return true;
719 }
721 }
723 ?>