Code

Fixed layout. Borders were to big in if elements.
[gosa.git] / include / sieve / class_My_Tree.inc
1 <?php
5 /* This class is inherited from the original 'Tree'
6  *  class written by Heiko Hund.
7  * It is partly rewritten to create a useable html interface 
8  *  for each single sieve token. 
9  * This gives us the ability to edit existing sieve filters. 
10  */
11 class My_Tree extends Tree
12 {
13   var $dumpFn_;
14   var $dump_;
16   var $mode_stack = array();
17   var $pap              = array();
18   var $parent = NULL;
20   function My_Tree(&$root,$parent)
21   {
22     $this->parent = $parent;
23     $this->_construct($root);
24   }
26   function execute()
27   {
28     return($this->dump());
29   }
31   /* Create a html interface for the current sieve filter 
32    */
33   function dump()
34   {
35     /**************
36      * Handle new elements 
37      **************/
39     /* Only parse the tokens once */
40     if(!count($this->pap)){
41       $this->dump_ = "";
42       $this->mode_stack = array();
43       $this->pap = array();
44       $this->doDump_(0, '', true);
46       /* Add left elements */
47       if(count($this->mode_stack)){
48         foreach($this->mode_stack as $element){
49           $this->handle_elements( $element,preg_replace("/[^0-9]/","",microtime()));
50         }
51       }
52     }
54     /* Create html results */
55     $smarty = get_smarty();
57     $block_indent_start = $smarty->fetch(get_template_path("templates/block_indent_start.tpl",TRUE,dirname(__FILE__)));
58     $block_indent_stop  = $smarty->fetch(get_template_path("templates/block_indent_stop.tpl",TRUE,dirname(__FILE__))); 
60     $this -> dump_ = "";
61     $ends = array();
62     $ends_complete_block = array();
64     foreach($this->pap as $key => $object){
65       if(is_object($object)){
67         $end = $this->get_block_end($key,false);
68         $end2 = $this->get_block_end($key);
69         if($end != $key && in_array(get_class($object),array("sieve_if"))){
70           $ends_complete_block[$end2] = $end2;
71           $this->dump_ .= "<div class='container_'>";
72         }
73         if(isset($ends[$key])){
74           $this->dump_  .= $block_indent_stop;
75         }
76         $this->dump_ .= preg_replace("/>/",">\n",$object->execute()); 
77         if($end != $key && in_array(get_class($object),array("sieve_if","sieve_else","sieve_elsif"))) {
78           $ends[$end] = $end;  
79           $this->dump_  .= $block_indent_start;
80         }
82         if(isset($ends_complete_block[$key])){
83           $this->dump_ .= "</div>";
84         }
85       }
86     }
87     
88     return($this->dump_);
89   }
92   /* This function walks through the object tree generated by the "Parse" class.
93    * All Commands will be resolved and grouped. So the Commands and their 
94    *  parameter are combined. Like "IF" and ":comparator"...
95    */  
96   function doDump_($node_id, $prefix, $last,$num = 1)
97   {
98     /* Indicates that current comman will only be valid for a single line. 
99      *  this command type will be removed from mode_stack after displaying it.
100      */
101     $rewoke_last = FALSE;
103     /* Get node */ 
104     $node = $this->nodes_[$node_id];
106     /* Get last element class and type */
107     $last_class = "";
108     $last_type  = "";
109     if(count($this->mode_stack)){
110       $key = key($this->mode_stack);
111       $tmp = array_reverse($this->mode_stack[$key]['ELEMENTS']);
112       $last_class = $tmp[key($tmp)]['class'];
113       
114       if(isset($this->mode_stack[$key]['TYPE'])){
115         $last_type  = $this->mode_stack[$key]['TYPE'];
116       }
117     }
119     /* This closes the last mode */
120     if($node['class'] == "block-start"){
121       $tmp = array_pop($this->mode_stack);
122       $this->handle_elements($tmp,$node_id);
123       $this->handle_elements(array("TYPE" => "block_start"),preg_replace("/[^0-9]/","",microtime()));
124     }
126     /* This closes the last mode */
127     if($node['class'] == "block-end"){
128       $tmp = array_pop($this->mode_stack);
129       $this->handle_elements($tmp,$node_id);
130       $this->handle_elements(array("TYPE" => "block_end"),preg_replace("/[^0-9]/","",microtime()));
131     }
133     /* Semicolon indicates a new command */
134     if($node['class'] == "semicolon"){
135       $tmp =array_pop($this->mode_stack);
136       $this->handle_elements($tmp,$node_id);
137     }
139     /* We can't handle comments within if tag right now */
140     if(!in_array_ics($last_type,array("if","elsif"))){
142       /* Comments require special attention.
143        * We do not want to create a single comment element 
144        *  foreach each  "#comment"  string found in the script. 
145        * Sometimes comments are used like this 
146        *   # This is a comment
147        *   #  and it still is a comment 
148        *   #  ...
149        * So we combine them to one single comment.
150        */
151       if($last_class != "comment" && $node['class'] == "comment"){
152         $tmp =array_pop($this->mode_stack);
153         $this->handle_elements($tmp,$node_id);
154         $this->mode_stack[] = array("TYPE" => $node['class']); 
155       }  
157       if($last_class == "comment" && $node['class'] != "comment"){
158         $tmp =array_pop($this->mode_stack);
159         $this->handle_elements($tmp,$node_id);
160       }
161     }
163     /* Handle identifiers */
164     $identifiers = array("else","if","elsif","end","reject","redirect","vacation","keep","discard","fileinto","require","stop");
165     if($node['class'] == "identifier" && in_array($node['text'],$identifiers)){
166       $this->mode_stack[] = array("TYPE" => $node['text']); 
167     }
169     if(!($last_type == "if" && $node['class'] == "comment")){
170       /* Add current node to current command stack */
171       end($this->mode_stack);
172       $key = key($this->mode_stack);
173       $this->mode_stack[$key]['ELEMENTS'][] = $node;
174     }
176     /* Remove last mode from mode stack, cause it was only valid for a single line */
177     if($rewoke_last){
178       $tmp =array_pop($this->mode_stack);
179       $this->handle_elements($tmp,$node_id);
180     }
182     /* If this is a sub element, just call this for all childs */       
183     if(isset($this->childs_[$node_id])){
184       $childs = $this->childs_[$node_id];
185       for ($i=0; $i<count($childs); ++$i)
186       {
187         $c_last = false;
188         if ($i+1 == count($childs))
189         {
190           $c_last = true;
191         }
192         $this->doDump_($childs[$i], "", $num);
193       }
194     }
195   }
198   /* Create a class for each resolved object.
199    * And append this class to a list of objects.
200    */
201   function handle_elements($data,$id)
202   {
203     if(!isset($data['TYPE'])){
204       return;
205     }
206     $type = $data['TYPE'];
207     
208     $class_name= "sieve_".$type ;
209     if(class_exists($class_name)){
210       $this->pap[] = new $class_name($data,$id,$this);
211     }else{
212       echo "<font color='red'>Missing : ".$class_name."</font>"."<br>";
213     }
214   }
216   function save_object()
217   {
218     reset($this->pap);
219     foreach($this->pap as $key => $obj){
221       if(in_array(get_class($obj),array("sieve_if",
222                                         "sieve_elsif",
223                                         "sieve_vacation",
224                                         "sieve_comment",
225                                         "sieve_reject",
226                                         "sieve_fileinto",
227                                         "sieve_require",
228                                         "sieve_redirect"))){
231         if(isset($this->pap[$key]) && method_exists($this->pap[$key],"save_object")){
232           $this->pap[$key]->save_object();
233         }
234       }
235     }
236   }
239   /* Remove the object at the given position */
240   function remove_object($key_id)
241   {
242     if(count($this->pap) == 1){
243       print_red(_("Can't remove last element."));
244       return;
245     }
247     if(!isset($this->pap[$key_id])){
248       trigger_error(_("Can't remove element with object_id=".$key_id.", there is no object with this identifier. Remove aborted."));
249       return(false);
250     }
252     $class = get_class($this->pap[$key_id]);
253     if(in_array($class,array("sieve_if","sieve_elsif","sieve_else"))){
254       $block_start= $key_id;
255       $block_end  = $this->get_block_end($key_id);
257       for($i = $block_start ; $i <= $block_end ; $i ++ ){
258         unset($this->pap[$i]);
259       }
260     }else{
261       unset($this->pap[$key_id]);
262     }
263     $tmp = array();
264     foreach($this->pap as $element){
265       $tmp[] = $element;
266     }
267     $this->pap = $tmp;
268   }
271   /* This function moves a given element to another position.
272    * Single elements like "keep;" will simply be moved one posisition down/up.
273    * Multiple elements like if-elsif-else will be moved as block. 
274    * 
275    *  $key_id     specified the element that should be moved.
276    *  $direction  specifies to move elements "up" or "down"
277    */
278   function move_up_down($key_id,$direction = "down")
279   {
280      
281     /* Get the current element to decide what to move. */ 
282     $e_class = get_class($this->pap[$key_id]);
283       
284     if(in_array($e_class,array("sieve_if"))){
285       $block_start= $key_id;
286       $block_end  = $this->get_block_end($key_id);
288       /* Depending on the direction move up down */
289       if($direction == "down"){
290         $next_free  = $this->_get_next_free_move_slot($block_end,$direction); 
291       }else{
292         $next_free  = $this->_get_next_free_move_slot($block_start,$direction); 
293       }
295       /* Move the given block */ 
296       $this->move_multiple_elements($block_start,$block_end,$next_free);
297     }
299     if(in_array($e_class,array( "sieve_stop",
300                                 "sieve_keep",
301                                 "sieve_require",
302                                 "sieve_comment",
303                                 "sieve_vacation",
304                                 "sieve_stop",   
305                                 "sieve_reject", 
306                                 "sieve_fileinto",
307                                 "sieve_redirect", 
308                                 "sieve_discard"))){
309       $this->move_single_element($key_id,$this->_get_next_free_move_slot($key_id,$direction));
310     }
311   }
313   
314   /* Move the given block to position */
315   function move_multiple_elements($start,$end,$to)
316   {
317     /* Use class names for testing */
318     $data = $this->pap;
320     /* Get block to move */
321     $block_to_move = array_slice($data,$start, ($end - $start +1));
323     /* We want do move this block up */
324     if($end > $to){
325       
326       /* Get start block */
327       $start_block = array_slice($data,0,$to);
329       /* Get Get all elements between the block to move 
330        *  and next free position 
331        */
332       $block_to_free = array_slice($data,$to ,$start - $to );  
333       $block_to_end = array_slice($data,$end+1);
334       $new = array();
335       foreach($start_block as $block){
336         $new[] = $block;
337       }
338       foreach($block_to_move as $block){
339         $new[] = $block;
340       }
341       foreach($block_to_free as $block){
342         $new[] = $block;
343       }
344       foreach($block_to_end as $block){
345         $new[] = $block;
346       }
347       $old = $this->pap;
348       $this->pap = $new;
349     }
350     
352     /* We want to move this block down. */
353     if($to > $end){
355       /* Get start block */
356       $start_block = array_slice($data,0,$start);
358       /* Get Get all elements between the block to move 
359        *  and next free position 
360        */
361       $block_to_free = array_slice($data,$end +1,($to - $end  ));  
363       /* Get the rest 
364        */
365       $block_to_end = array_slice($data,$to+1);
367       $new = array();
368       foreach($start_block as $block){
369         $new[] = $block;
370       }
371       foreach($block_to_free as $block){
372         $new[] = $block;
373       }
374       foreach($block_to_move as $block){
375         $new[] = $block;
376       }
377       foreach($block_to_end as $block){
378         $new[] = $block;
379       }
380       $old = $this->pap;
381       $this->pap = $new;
382     }
383   }  
385   
386   /* This function returns the id of the element 
387    *  where the current block ends  
388    */
389   function get_block_end($start,$complete = TRUE)
390   {
391     /* Only execute if this is a really a block element. 
392      * Block elements is only sieve_if
393      */
394     if(in_array(get_class($this->pap[$start]),array("sieve_if","sieve_elsif","sieve_else"))){
396       $class      = get_class($this->pap[$start]);
397       $next_class = get_class($this->pap[$start+1]);
398       $block_depth = 0;
400       $end = FALSE;
402       while(!$end && $start < count($this->pap)){
403  
404         if($class == "sieve_block_start"){
405           $block_depth ++;
406         }
408         if($class == "sieve_block_end"){
409           $block_depth --;
410         }
412         if($complete){
413           if( $block_depth == 0 && 
414               $class == "sieve_block_end" && 
415               !in_array($next_class,array("sieve_else","sieve_elsif"))){
416             $end = TRUE;
417             $start --;
418           }
419         }else{
421           if( $block_depth == 0 && 
422               $class == "sieve_block_end" ){ 
423             $end = TRUE;
424             $start --;
425           }
426         }
428         $start ++;       
429         $class      = get_class($this->pap[$start]);
430         
431         if(isset($this->pap[$start+1])){ 
432           $next_class = get_class($this->pap[$start+1]);
433         }else{
434           $next_class ="";
435         }
436       }
437     }
438     return($start);
439   }
442   /* This function moves the single element at 
443    *  position $from to position $to.
444    */
445   function move_single_element($from,$to)
446   {
447     if($from == $to) {
448       return;
449     }
451     $ret = array();
452     $tmp = $this->pap;
454     $begin = array();
455     $middle = array();
456     $end = array();
457     $element = $this->pap[$from];
459     if($from > $to ){
461       /* Get all element in fron to element to move */    
462       if($from  != 0){
463         $begin = array_slice($tmp,0,$to);
464       }
466       /* Get all elements between */
467       $middle = array_slice($tmp,$to , ($from - ($to) ));  
468     
469       /* Get the rest */ 
470       $end  = array_slice($tmp,$from+1);
471  
472       foreach($begin as $data){
473         $ret[] = $data;
474       }
475       $ret[] = $element;
476       foreach($middle as $data){
477         $ret[] = $data;
478       }
479       foreach($end as $data){
480         $ret[] = $data;
481       }
482       $this->pap = $ret;
483     }
484     if($from < $to ){
486       /* Get all element in fron to element to move */    
487       if($from  != 0){
488         $begin = array_slice($tmp,0,$from);
489       }
491       /* Get all elements between */
492       $middle = array_slice($tmp,$from+1 , ($to - ($from)));  
493     
494       /* Get the rest */ 
495       $end  = array_slice($tmp,$to+1);
496  
497       foreach($begin as $data){
498         $ret[] = $data;
499       }
500       foreach($middle as $data){
501         $ret[] = $data;
502       }
503       $ret[] = $element;
504       foreach($end as $data){
505         $ret[] = $data;
506       }
507       $this->pap = $ret;
508     }
509   }
512   /* Returns the next free position where we 
513    *  can add a new sinle element 
514    *    $key_id     = Current position
515    *    $direction  = Forward or backward.
516    */
517   function _get_next_free_move_slot($key_id,$direction,$include_self = FALSE)
518   {
519     $last_class = "";
520     $current_class ="";
521     $next_class = "";
523     /* After this elements we can add new elements 
524      *  without having any trouble.
525      */
526     $allowed_to_add_after = array("sieve_keep",
527                                   "sieve_require", 
528                                   "sieve_stop", 
529                                   "sieve_reject", 
530                                   "sieve_fileinto", 
531                                   "sieve_redirect", 
532                                   "sieve_discard",
533                                   "sieve_comment",
534                                   "sieve_block_start"
535                                  );
537     /* Before this elements we can add new elements 
538      *  without having any trouble.
539      */
540     $allowed_to_add_before = array("sieve_keep",
541                                   "sieve_require", 
542                                   "sieve_stop", 
543                                   "sieve_reject", 
544                                   "sieve_fileinto", 
545                                   "sieve_comment",
546                                   "sieve_redirect", 
547                                   "sieve_discard",
548                                   "sieve_if", 
549                                   "sieve_block_end"
550                                  );
552     if($direction == "down"){
553     
554       $test = $this->pap;
555       while($key_id < count($test)){
556         if(($key_id+1) == count($test)) {
557           return($key_id);
558         }
560         if(!$include_self){
561           $key_id ++;
562         }
563         $include_self = FALSE;
564         $current_class  = get_class($test[$key_id]);
565         if(in_array($current_class, $allowed_to_add_after)){
566           return($key_id);
567         } 
568       } 
569     }else{
570   
571       $test = $this->pap;
572       if($key_id == 0) {
573         return($key_id);
574       }
575       if(!$include_self){
576         $key_id --;
577       }
578       while($key_id >=0 ){
579         $current_class  = get_class($test[$key_id]);
580         if(in_array($current_class, $allowed_to_add_before)){
581           return($key_id);
582         } 
583         $key_id --;
584       }
585       return(0);
586     }
587   }
590   /* Need to be reviewed */
591   function get_sieve_script()
592   {
593     $tmp ="";
594     if(count($this->pap)){
595       $buffer = "";    
596       foreach($this->pap as $part)  {
597         if(get_class($part) == "sieve_block_end"){
598           $buffer = substr($buffer,0,strlen($buffer)-(strlen(SIEVE_INDENT_TAB)));
599         }
600         $tmp2 = $part->get_sieve_script_part();
602         $tmp3 = split("\n",$tmp2);
603         foreach($tmp3 as $str){
604           $str2 = trim($str);
606           /* If the current line only contains an '.'    
607            *  we must skip the line indent. 
608            * The text: statement uses a single '.' to mark the text end.
609            * This '.' must be the only char in the current line, no
610            *  whitespaces are allowed here.
611            */
612           if($str2 == "."){
613             $tmp.=$str."\n";
614           }else{
615             $tmp.= $buffer.$str."\n";
616           }
617         }
618         if(get_class($part) == "sieve_block_start"){
619           $buffer .= SIEVE_INDENT_TAB;
620         }
621       }
622     }
623     if(!preg_match("/Generated by GOsa - Gonicus System Administrator/",$tmp)){
624 #      $tmp = "#Generated by GOsa - Gonicus System Administrator \n ".$tmp;
625     }
626     return($tmp);
627   }
629   function check()
630   {
631     $msgs = array();
633     /* Some logical checks. 
634      *  like :  only sieve_comment can appear before require.
635      */
637     /* Ensure that there are no command before require 
638      *  - Get id of last require tag
639      *  - Collect object types in from of this tag. 
640      *  - Check if there are tags collected that are not allowed 
641      */
642     $last_found_at = -1; 
643     $objs = array();
644     foreach($this->pap as $key => $obj){
645       if(get_class($obj) == "sieve_require"){
646         $last_found_at = $key;
647       }
648     }
649     foreach($this->pap as $key => $obj){
650       if($key == $last_found_at) break;  
651       if(!in_array(get_class($obj),array("sieve_comment","sieve_require"))){
652         $objs[] = get_class($obj);
653       }
654     }
655     if(count($objs) && $last_found_at != -1){
656       $str = _("Require must be the first command in the script.");  
657       $msgs[] = $str;
658       print_red($str);;
659     }
661     foreach($this->pap as $obj){
662       $o_msgs = $obj->check();
663       foreach($o_msgs as $o_msg){
664         $msgs[] = $o_msg;
665       }
666     }
667     return($msgs);
668   }
671   /* We are forced to add a new require.
672    *  This function is called by the 
673    *  sieveElement_Classes->parent->add_require()  
674    */ 
675   function add_require($str)
676   {
677     $require_id = -1;
678     foreach($this->pap as $key => $obj){
679       if(get_class($obj) == "sieve_require"){
680         $require_id = $key;
681       }
682     }
684     /* No require found, add one */
685     if($require_id == -1){
686       $require = new sieve_require(NULL,preg_replace("/[^0-9]/","",microtime()),$this);
687       $require -> Add_Require($str);
688       $new = array();
689       $new[] = $require;
690       foreach($this->pap as $obj){
691         $new[] = $obj;
692       }
693       $this->pap = $new;
694     } else { 
695       $this->pap[$require_id]->Add_Require($str);
696     } 
697   }
701 /* Create valid sieve string/string-list 
702  *  out of a given array
703  */
704 function sieve_create_strings($data,$force_string = FALSE)
706   $ret = "";
707   if(is_array($data)){
708     if(count($data) == 1){
709       $ret = "\"";
710       foreach($data as $dat){
711         $ret .=$dat;
712       }
713       $ret.="\"";
714     }else{
715       foreach($data as $dat){
716         $ret.= "\"";
717         $ret.=$dat;
718         $ret.="\", ";
719       }
720       $ret = preg_replace("/,$/","",trim($ret));
721       $ret = "[".$ret."]";
722     }
723   }else{
725     $Multiline = preg_match("/\n/",$data);
726     $data = preg_replace("/\r/","",$data);;
728     if($Multiline && !$force_string){
729       $ret = "text: \r\n".$data."\r\n.\r\n";
730     }else{
731       $ret = "\"".$data."\"";
732     }
733   }
734   $ret = preg_replace("/\"\"/","\"",$ret);
735   $ret = preg_replace("/\n/","\r\n",$ret);
737   return($ret);
740 /* This checks if there is a string at the current position 
741  *  in the token array. 
742  * If there is a string list at the current position,
743  *  this function will return a complete list of all
744  *  strings used in this list.
745  * It also returns an offset of the last token position 
746  */
747 function sieve_get_strings($data,$id)
749   $ret = array();
750   if($data[$id]['class'] == "left-bracket"){
751     while(isset($data[$id]) && $data[$id]['class']  != "right-bracket" && $id < count($data)){
753       if($data[$id]['class'] == "quoted-string"){
754         $ret[] = $data[$id]['text'];
755       }
757       $id ++;
758     }
759   }elseif($data[$id]['class'] == "quoted-string"){
760     $ret[] = $data[$id]['text'];
761   }elseif($data[$id]['class'] == "number"){
762     $ret[] = $data[$id]['text'];
763   }elseif($data[$id]['class'] == "multi-line"){
764     $str = trim(preg_replace("/^text:/","",$data[$id]['text']));
765     $str = trim(preg_replace("/\.$/","",$str));
766     $ret[] = $str;
767   }
769   return(array("OFFSET" => $id, "STRINGS" => $ret));
772 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
773 ?>