Code

Readded spacer
[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__))); 
59   
60   
62     $this -> dump_ = "";
63     $ends = array();
64     $ends_complete_block = array();
66     foreach($this->pap as $key => $object){
67       if(is_object($object)){
69         $end = $this->get_block_end($key,false);
70         $end2 = $this->get_block_end($key);
71         if($end != $key && in_array(get_class($object),array("sieve_if"))){
72           $ends_complete_block[$end2] = $end2;
73           $this->dump_ .= "<div style='height:12px;'>&nbsp;</div>";
74           $this->dump_ .= "<div class='container_'>";
75         }
76         if(isset($ends[$key])){
77           $this->dump_  .= $block_indent_stop;
78         }
79         $this->dump_ .= preg_replace("/>/",">\n",$object->execute()); 
80         if($end != $key && in_array(get_class($object),array("sieve_if","sieve_else","sieve_elsif"))) {
81           $ends[$end] = $end;  
82           $this->dump_  .= $block_indent_start;
83         }
85         if(isset($ends_complete_block[$key])){
86           $this->dump_ .= "</div>";
87           $this->dump_ .= "<div style='height:12px;'>&nbsp;</div>";
88         }
89       }
90     }
91     
92     return($this->dump_);
93   }
96   /* This function walks through the object tree generated by the "Parse" class.
97    * All Commands will be resolved and grouped. So the Commands and their 
98    *  parameter are combined. Like "IF" and ":comparator"...
99    */  
100   function doDump_($node_id, $prefix, $last,$num = 1)
101   {
102     /* Indicates that current comman will only be valid for a single line. 
103      *  this command type will be removed from mode_stack after displaying it.
104      */
105     $rewoke_last = FALSE;
107     /* Get node */ 
108     $node = $this->nodes_[$node_id];
110     /* Get last element class and type */
111     $last_class = "";
112     $last_type  = "";
113     if(count($this->mode_stack)){
114       $key = key($this->mode_stack);
115       $tmp = array_reverse($this->mode_stack[$key]['ELEMENTS']);
116       $last_class = $tmp[key($tmp)]['class'];
117       
118       if(isset($this->mode_stack[$key]['TYPE'])){
119         $last_type  = $this->mode_stack[$key]['TYPE'];
120       }
121     }
123     /* This closes the last mode */
124     if($node['class'] == "block-start"){
125       $tmp = array_pop($this->mode_stack);
126       $this->handle_elements($tmp,$node_id);
127       $this->handle_elements(array("TYPE" => "block_start"),preg_replace("/[^0-9]/","",microtime()));
128     }
130     /* This closes the last mode */
131     if($node['class'] == "block-end"){
132       $tmp = array_pop($this->mode_stack);
133       $this->handle_elements($tmp,$node_id);
134       $this->handle_elements(array("TYPE" => "block_end"),preg_replace("/[^0-9]/","",microtime()));
135     }
137     /* Semicolon indicates a new command */
138     if($node['class'] == "semicolon"){
139       $tmp =array_pop($this->mode_stack);
140       $this->handle_elements($tmp,$node_id);
141     }
143     /* We can't handle comments within if tag right now */
144     if(!in_array_ics($last_type,array("if","elsif"))){
146       /* Comments require special attention.
147        * We do not want to create a single comment element 
148        *  foreach each  "#comment"  string found in the script. 
149        * Sometimes comments are used like this 
150        *   # This is a comment
151        *   #  and it still is a comment 
152        *   #  ...
153        * So we combine them to one single comment.
154        */
155       if($last_class != "comment" && $node['class'] == "comment"){
156         $tmp =array_pop($this->mode_stack);
157         $this->handle_elements($tmp,$node_id);
158         $this->mode_stack[] = array("TYPE" => $node['class']); 
159       }  
161       if($last_class == "comment" && $node['class'] != "comment"){
162         $tmp =array_pop($this->mode_stack);
163         $this->handle_elements($tmp,$node_id);
164       }
165     }
167     /* Handle identifiers */
168     $identifiers = array("else","if","elsif","end","reject","redirect","vacation","keep","discard","fileinto","require","stop");
169     if($node['class'] == "identifier" && in_array($node['text'],$identifiers)){
170       $this->mode_stack[] = array("TYPE" => $node['text']); 
171     }
173     if(!($last_type == "if" && $node['class'] == "comment")){
174       /* Add current node to current command stack */
175       end($this->mode_stack);
176       $key = key($this->mode_stack);
177       $this->mode_stack[$key]['ELEMENTS'][] = $node;
178     }
180     /* Remove last mode from mode stack, cause it was only valid for a single line */
181     if($rewoke_last){
182       $tmp =array_pop($this->mode_stack);
183       $this->handle_elements($tmp,$node_id);
184     }
186     /* If this is a sub element, just call this for all childs */       
187     if(isset($this->childs_[$node_id])){
188       $childs = $this->childs_[$node_id];
189       for ($i=0; $i<count($childs); ++$i)
190       {
191         $c_last = false;
192         if ($i+1 == count($childs))
193         {
194           $c_last = true;
195         }
196         $this->doDump_($childs[$i], "", $num);
197       }
198     }
199   }
202   /* Create a class for each resolved object.
203    * And append this class to a list of objects.
204    */
205   function handle_elements($data,$id)
206   {
207     if(!isset($data['TYPE'])){
208       return;
209     }
210     $type = $data['TYPE'];
211     
212     $class_name= "sieve_".$type ;
213     if(class_exists($class_name)){
214       $this->pap[] = new $class_name($data,$id,$this);
215     }else{
216       echo "<font color='red'>Missing : ".$class_name."</font>"."<br>";
217     }
218   }
220   function save_object()
221   {
222     reset($this->pap);
223     foreach($this->pap as $key => $obj){
225       if(in_array(get_class($obj),array("sieve_if",
226                                         "sieve_elsif",
227                                         "sieve_vacation",
228                                         "sieve_comment",
229                                         "sieve_reject",
230                                         "sieve_fileinto",
231                                         "sieve_require",
232                                         "sieve_redirect"))){
235         if(isset($this->pap[$key]) && method_exists($this->pap[$key],"save_object")){
236           $this->pap[$key]->save_object();
237         }
238       }
239     }
240   }
243   /* Remove the object at the given position */
244   function remove_object($key_id)
245   {
246     if(count($this->pap) == 1){
247       print_red(_("Can't remove last element."));
248       return;
249     }
251     if(!isset($this->pap[$key_id])){
252       trigger_error(_("Can't remove element with object_id=".$key_id.", there is no object with this identifier. Remove aborted."));
253       return(false);
254     }
256     $class = get_class($this->pap[$key_id]);
257     if(in_array($class,array("sieve_if","sieve_elsif","sieve_else"))){
258       $block_start= $key_id;
259       $block_end  = $this->get_block_end($key_id);
261       for($i = $block_start ; $i <= $block_end ; $i ++ ){
262         unset($this->pap[$i]);
263       }
264     }else{
265       unset($this->pap[$key_id]);
266     }
267     $tmp = array();
268     foreach($this->pap as $element){
269       $tmp[] = $element;
270     }
271     $this->pap = $tmp;
272   }
275   /* This function moves a given element to another position.
276    * Single elements like "keep;" will simply be moved one posisition down/up.
277    * Multiple elements like if-elsif-else will be moved as block. 
278    * 
279    *  $key_id     specified the element that should be moved.
280    *  $direction  specifies to move elements "up" or "down"
281    */
282   function move_up_down($key_id,$direction = "down")
283   {
284      
285     /* Get the current element to decide what to move. */ 
286     $e_class = get_class($this->pap[$key_id]);
287       
288     if(in_array($e_class,array("sieve_if"))){
289       $block_start= $key_id;
290       $block_end  = $this->get_block_end($key_id);
292       /* Depending on the direction move up down */
293       if($direction == "down"){
294         $next_free  = $this->_get_next_free_move_slot($block_end,$direction); 
295       }else{
296         $next_free  = $this->_get_next_free_move_slot($block_start,$direction); 
297       }
299       /* Move the given block */ 
300       $this->move_multiple_elements($block_start,$block_end,$next_free);
301     }
303     if(in_array($e_class,array( "sieve_stop",
304                                 "sieve_keep",
305                                 "sieve_require",
306                                 "sieve_comment",
307                                 "sieve_vacation",
308                                 "sieve_stop",   
309                                 "sieve_reject", 
310                                 "sieve_fileinto",
311                                 "sieve_redirect", 
312                                 "sieve_discard"))){
313       $this->move_single_element($key_id,$this->_get_next_free_move_slot($key_id,$direction));
314     }
315   }
317   
318   /* Move the given block to position */
319   function move_multiple_elements($start,$end,$to)
320   {
321     /* Use class names for testing */
322     $data = $this->pap;
324     /* Get block to move */
325     $block_to_move = array_slice($data,$start, ($end - $start +1));
327     /* We want do move this block up */
328     if($end > $to){
329       
330       /* Get start block */
331       $start_block = array_slice($data,0,$to);
333       /* Get Get all elements between the block to move 
334        *  and next free position 
335        */
336       $block_to_free = array_slice($data,$to ,$start - $to );  
337       $block_to_end = array_slice($data,$end+1);
338       $new = array();
339       foreach($start_block as $block){
340         $new[] = $block;
341       }
342       foreach($block_to_move as $block){
343         $new[] = $block;
344       }
345       foreach($block_to_free as $block){
346         $new[] = $block;
347       }
348       foreach($block_to_end as $block){
349         $new[] = $block;
350       }
351       $old = $this->pap;
352       $this->pap = $new;
353     }
354     
356     /* We want to move this block down. */
357     if($to > $end){
359       /* Get start block */
360       $start_block = array_slice($data,0,$start);
362       /* Get Get all elements between the block to move 
363        *  and next free position 
364        */
365       $block_to_free = array_slice($data,$end +1,($to - $end  ));  
367       /* Get the rest 
368        */
369       $block_to_end = array_slice($data,$to+1);
371       $new = array();
372       foreach($start_block as $block){
373         $new[] = $block;
374       }
375       foreach($block_to_free as $block){
376         $new[] = $block;
377       }
378       foreach($block_to_move as $block){
379         $new[] = $block;
380       }
381       foreach($block_to_end as $block){
382         $new[] = $block;
383       }
384       $old = $this->pap;
385       $this->pap = $new;
386     }
387   }  
389   
390   /* This function returns the id of the element 
391    *  where the current block ends  
392    */
393   function get_block_end($start,$complete = TRUE)
394   {
395     /* Only execute if this is a really a block element. 
396      * Block elements is only sieve_if
397      */
398     if(in_array(get_class($this->pap[$start]),array("sieve_if","sieve_elsif","sieve_else"))){
400       $class      = get_class($this->pap[$start]);
401       $next_class = get_class($this->pap[$start+1]);
402       $block_depth = 0;
404       $end = FALSE;
406       while(!$end && $start < count($this->pap)){
407  
408         if($class == "sieve_block_start"){
409           $block_depth ++;
410         }
412         if($class == "sieve_block_end"){
413           $block_depth --;
414         }
416         if($complete){
417           if( $block_depth == 0 && 
418               $class == "sieve_block_end" && 
419               !in_array($next_class,array("sieve_else","sieve_elsif"))){
420             $end = TRUE;
421             $start --;
422           }
423         }else{
425           if( $block_depth == 0 && 
426               $class == "sieve_block_end" ){ 
427             $end = TRUE;
428             $start --;
429           }
430         }
432         $start ++;       
433         $class      = get_class($this->pap[$start]);
434         
435         if(isset($this->pap[$start+1])){ 
436           $next_class = get_class($this->pap[$start+1]);
437         }else{
438           $next_class ="";
439         }
440       }
441     }
442     return($start);
443   }
446   /* This function moves the single element at 
447    *  position $from to position $to.
448    */
449   function move_single_element($from,$to)
450   {
451     if($from == $to) {
452       return;
453     }
455     $ret = array();
456     $tmp = $this->pap;
458     $begin = array();
459     $middle = array();
460     $end = array();
461     $element = $this->pap[$from];
463     if($from > $to ){
465       /* Get all element in fron to element to move */    
466       if($from  != 0){
467         $begin = array_slice($tmp,0,$to);
468       }
470       /* Get all elements between */
471       $middle = array_slice($tmp,$to , ($from - ($to) ));  
472     
473       /* Get the rest */ 
474       $end  = array_slice($tmp,$from+1);
475  
476       foreach($begin as $data){
477         $ret[] = $data;
478       }
479       $ret[] = $element;
480       foreach($middle as $data){
481         $ret[] = $data;
482       }
483       foreach($end as $data){
484         $ret[] = $data;
485       }
486       $this->pap = $ret;
487     }
488     if($from < $to ){
490       /* Get all element in fron to element to move */    
491       if($from  != 0){
492         $begin = array_slice($tmp,0,$from);
493       }
495       /* Get all elements between */
496       $middle = array_slice($tmp,$from+1 , ($to - ($from)));  
497     
498       /* Get the rest */ 
499       $end  = array_slice($tmp,$to+1);
500  
501       foreach($begin as $data){
502         $ret[] = $data;
503       }
504       foreach($middle as $data){
505         $ret[] = $data;
506       }
507       $ret[] = $element;
508       foreach($end as $data){
509         $ret[] = $data;
510       }
511       $this->pap = $ret;
512     }
513   }
516   /* Returns the next free position where we 
517    *  can add a new sinle element 
518    *    $key_id     = Current position
519    *    $direction  = Forward or backward.
520    */
521   function _get_next_free_move_slot($key_id,$direction,$include_self = FALSE)
522   {
523     $last_class = "";
524     $current_class ="";
525     $next_class = "";
527     /* After this elements we can add new elements 
528      *  without having any trouble.
529      */
530     $allowed_to_add_after = array("sieve_keep",
531                                   "sieve_require", 
532                                   "sieve_stop", 
533                                   "sieve_reject", 
534                                   "sieve_fileinto", 
535                                   "sieve_redirect", 
536                                   "sieve_discard",
537                                   "sieve_comment",
538                                   "sieve_block_start"
539                                  );
541     /* Before this elements we can add new elements 
542      *  without having any trouble.
543      */
544     $allowed_to_add_before = array("sieve_keep",
545                                   "sieve_require", 
546                                   "sieve_stop", 
547                                   "sieve_reject", 
548                                   "sieve_fileinto", 
549                                   "sieve_comment",
550                                   "sieve_redirect", 
551                                   "sieve_discard",
552                                   "sieve_if", 
553                                   "sieve_block_end"
554                                  );
556     if($direction == "down"){
557     
558       $test = $this->pap;
559       while($key_id < count($test)){
560         if(($key_id+1) == count($test)) {
561           return($key_id);
562         }
564         if(!$include_self){
565           $key_id ++;
566         }
567         $include_self = FALSE;
568         $current_class  = get_class($test[$key_id]);
569         if(in_array($current_class, $allowed_to_add_after)){
570           return($key_id);
571         } 
572       } 
573     }else{
574   
575       $test = $this->pap;
576       if($key_id == 0) {
577         return($key_id);
578       }
579       if(!$include_self){
580         $key_id --;
581       }
582       while($key_id >=0 ){
583         $current_class  = get_class($test[$key_id]);
584         if(in_array($current_class, $allowed_to_add_before)){
585           return($key_id);
586         } 
587         $key_id --;
588       }
589       return(0);
590     }
591   }
594   /* Need to be reviewed */
595   function get_sieve_script()
596   {
597     $tmp ="";
598     if(count($this->pap)){
599       $buffer = "";    
600       foreach($this->pap as $part)  {
601         if(get_class($part) == "sieve_block_end"){
602           $buffer = substr($buffer,0,strlen($buffer)-(strlen(SIEVE_INDENT_TAB)));
603         }
604         $tmp2 = $part->get_sieve_script_part();
606         $tmp3 = split("\n",$tmp2);
607         foreach($tmp3 as $str){
608           $str2 = trim($str);
610           /* If the current line only contains an '.'    
611            *  we must skip the line indent. 
612            * The text: statement uses a single '.' to mark the text end.
613            * This '.' must be the only char in the current line, no
614            *  whitespaces are allowed here.
615            */
616           if($str2 == "."){
617             $tmp.=$str."\n";
618           }else{
619             $tmp.= $buffer.$str."\n";
620           }
621         }
622         if(get_class($part) == "sieve_block_start"){
623           $buffer .= SIEVE_INDENT_TAB;
624         }
625       }
626     }
627     if(!preg_match("/Generated by GOsa - Gonicus System Administrator/",$tmp)){
628 #      $tmp = "#Generated by GOsa - Gonicus System Administrator \n ".$tmp;
629     }
630     return($tmp);
631   }
633   function check()
634   {
635                 $msgs = array();
637     /* Some logical checks. 
638      *  like :  only sieve_comment can appear before require.
639      */
640     
641     /* Ensure that there are no command before require 
642      *  - Get id of last require tag
643      *  - Collect object types in from of this tag. 
644      *  - Check if there are tags collected that are not allowed 
645      */
646     $last_found_at = -1; 
647     $objs = array();
648     foreach($this->pap as $key => $obj){
649       if(get_class($obj) == "sieve_require"){
650         $last_found_at = $key;
651       }
652     }
653     foreach($this->pap as $key => $obj){
654       if($key == $last_found_at) break;  
655       if(!in_array(get_class($obj),array("sieve_comment","sieve_require"))){
656         $objs[] = get_class($obj);
657       }
658     }
659     if(count($objs) && $last_found_at != -1){
660       $str = _("Require must be the first command in the script.");  
661       $msgs[] = $str;
662       print_red($str);;
663     }
664     
665                 foreach($this->pap as $obj){
666                         $o_msgs = $obj->check();
667                         foreach($o_msgs as $o_msg){
668                                 $msgs[] = $o_msg;
669                         }
670                 }
671                 return($msgs);
672   }
675   /* We are forced to add a new require.
676    *  This function is called by the 
677    *  sieveElement_Classes->parent->add_require()  
678    */ 
679   function add_require($str)
680   {
681     $require_id = -1;
682     foreach($this->pap as $key => $obj){
683       if(get_class($obj) == "sieve_require"){
684         $require_id = $key;
685       }
686     }
687   
688     /* No require found, add one */
689     if($require_id == -1){
690       $require = new sieve_require(NULL,preg_replace("/[^0-9]/","",microtime()),$this);
691       $require -> Add_Require($str);
692       $new = array();
693       $new[] = $require;
694       foreach($this->pap as $obj){
695         $new[] = $obj;
696       }
697       $this->pap = $new;
698     } else { 
699       $this->pap[$require_id]->Add_Require($str);
700     } 
701   }
705 /* Create valid sieve string/string-list 
706  *  out of a given array
707  */
708 function sieve_create_strings($data,$force_string = FALSE)
710   $ret = "";
711   if(is_array($data)){
712     if(count($data) == 1){
713       $ret = "\"";
714       foreach($data as $dat){
715         $ret .=$dat;
716       }
717       $ret.="\"";
718     }else{
719       foreach($data as $dat){
720         $ret.= "\"";
721         $ret.=$dat;
722         $ret.="\", ";
723       }
724       $ret = preg_replace("/,$/","",trim($ret));
725       $ret = "[".$ret."]";
726     }
727   }else{
729     $Multiline = preg_match("/\n/",$data);
730     $data = preg_replace("/\r/","",$data);;
732     if($Multiline && !$force_string){
733       $ret = "text: \r\n".$data."\r\n.\r\n";
734     }else{
735       $ret = "\"".$data."\"";
736     }
737   }
738   $ret = preg_replace("/\"\"/","\"",$ret);
739   $ret = preg_replace("/\n/","\r\n",$ret);
740   
741   return($ret);
744 /* This checks if there is a string at the current position 
745  *  in the token array. 
746  * If there is a string list at the current position,
747  *  this function will return a complete list of all
748  *  strings used in this list.
749  * It also returns an offset of the last token position 
750  */
751 function sieve_get_strings($data,$id)
753   $ret = array();
754   if($data[$id]['class'] == "left-bracket"){
755     while(isset($data[$id]) && $data[$id]['class']  != "right-bracket" && $id < count($data)){
756       
757       if($data[$id]['class'] == "quoted-string"){
758         $ret[] = $data[$id]['text'];
759       }
761       $id ++;
762     }
763   }elseif($data[$id]['class'] == "quoted-string"){
764     $ret[] = $data[$id]['text'];
765   }elseif($data[$id]['class'] == "number"){
766     $ret[] = $data[$id]['text'];
767   }elseif($data[$id]['class'] == "multi-line"){
768     $str = trim(preg_replace("/^text:/","",$data[$id]['text']));
769     $str = trim(preg_replace("/\.$/","",$str));
770     $ret[] = $str;
771   }
772   
773   return(array("OFFSET" => $id, "STRINGS" => $ret));
776 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
777 ?>