Code

Backport from trunk
[gosa.git] / gosa-plugins / mail / personal / mail / sieve / class_My_Tree.inc
1 <?php
3 /* This class is inherited from the original 'Tree'
4  *  class written by Heiko Hund.
5  * It is partly rewritten to create a useable html interface 
6  *  for each single sieve token. 
7  * This gives us the ability to edit existing sieve filters. 
8  */
9 class My_Tree extends Tree
10 {
11   var $dumpFn_;
12   var $dump_;
14   var $mode_stack = array();
15   var $pap              = array();
16   var $parent = NULL;
18   function My_Tree(&$root,$parent)
19   {
20     $this->parent = $parent;
21     $this->_construct($root);
22   }
25   function execute()
26   {
27     return($this->dump());
28   }
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_strict(get_class($object),array("sieve_if"))){
70           $ends_complete_block[$end2] = $end2;
71           $this->dump_ .= "<div style='height:10px;'></div>";
72           $this->dump_ .= "<div class='container_'>";
73         }
74         if(isset($ends[$key])){
75           $this->dump_  .= $block_indent_stop;
76         }
77         $this->dump_ .= preg_replace("/>/",">\n",$object->execute()); 
78         if($end != $key && in_array_strict(get_class($object),array("sieve_if","sieve_else","sieve_elsif"))) {
79           $ends[$end] = $end;  
80           $this->dump_  .= $block_indent_start;
81         }
83         if(isset($ends_complete_block[$key])){
84           $this->dump_ .= "</div>";
85           $this->dump_ .= "<div style='height:10px;'></div>";
86         }
87       }
88     }
89     
90     return($this->dump_);
91   }
94   /* This function walks through the object tree generated by the "Parse" class.
95    * All Commands will be resolved and grouped. So the Commands and their 
96    *  parameter are combined. Like "IF" and ":comparator"...
97    */  
98   function doDump_($node_id, $prefix, $last,$num = 1)
99   {
100     /* Indicates that current comman will only be valid for a single line. 
101      *  this command type will be removed from mode_stack after displaying it.
102      */
103     $rewoke_last = FALSE;
105     /* Get node */ 
106     $node = $this->nodes_[$node_id];
108     /* Get last element class and type */
109     $last_class = "";
110     $last_type  = "";
111     if(count($this->mode_stack)){
112       $key = key($this->mode_stack);
113       $tmp = array_reverse($this->mode_stack[$key]['ELEMENTS']);
114       $last_class = $tmp[key($tmp)]['class'];
115       
116       if(isset($this->mode_stack[$key]['TYPE'])){
117         $last_type  = $this->mode_stack[$key]['TYPE'];
118       }
119     }
121     /* This closes the last mode */
122     if($node['class'] == "block-start"){
123       $tmp = array_pop($this->mode_stack);
124       $this->handle_elements($tmp,$node_id);
125       $this->handle_elements(array("TYPE" => "block_start"),preg_replace("/[^0-9]/","",microtime()));
126     }
128     /* This closes the last mode */
129     if($node['class'] == "block-end"){
130       $tmp = array_pop($this->mode_stack);
131       $this->handle_elements($tmp,$node_id);
132       $this->handle_elements(array("TYPE" => "block_end"),preg_replace("/[^0-9]/","",microtime()));
133     }
135     /* Semicolon indicates a new command */
136     if($node['class'] == "semicolon"){
137       $tmp =array_pop($this->mode_stack);
138       $this->handle_elements($tmp,$node_id);
139     }
141     /* We can't handle comments within if tag right now */
142     if(!in_array_ics($last_type,array("if","elsif"))){
144       /* Comments require special attention.
145        * We do not want to create a single comment element 
146        *  foreach each  "#comment"  string found in the script. 
147        * Sometimes comments are used like this 
148        *   # This is a comment
149        *   #  and it still is a comment 
150        *   #  ...
151        * So we combine them to one single comment.
152        */
153       if($last_class != "comment" && $node['class'] == "comment"){
154         $tmp =array_pop($this->mode_stack);
155         $this->handle_elements($tmp,$node_id);
156         $this->mode_stack[] = array("TYPE" => $node['class']); 
157       }  
159       if($last_class == "comment" && $node['class'] != "comment"){
160         $tmp =array_pop($this->mode_stack);
161         $this->handle_elements($tmp,$node_id);
162       }
163     }
165     /* Handle identifiers */
166     $identifiers = array("else","if","elsif","end","reject","redirect","vacation","keep","discard","fileinto","require","stop");
167     if($node['class'] == "identifier" && in_array_strict($node['text'],$identifiers)){
168       $this->mode_stack[] = array("TYPE" => $node['text']); 
169     }
171     if(!($last_type == "if" && $node['class'] == "comment")){
172       /* Add current node to current command stack */
173       end($this->mode_stack);
174       $key = key($this->mode_stack);
175       $this->mode_stack[$key]['ELEMENTS'][] = $node;
176     }
178     /* Remove last mode from mode stack, cause it was only valid for a single line */
179     if($rewoke_last){
180       $tmp =array_pop($this->mode_stack);
181       $this->handle_elements($tmp,$node_id);
182     }
184     /* If this is a sub element, just call this for all childs */       
185     if(isset($this->childs_[$node_id])){
186       $childs = $this->childs_[$node_id];
187       for ($i=0; $i<count($childs); ++$i)
188       {
189         $c_last = false;
190         if ($i+1 == count($childs))
191         {
192           $c_last = true;
193         }
194         $this->doDump_($childs[$i], "", $num);
195       }
196     }
197   }
200   /* Create a class for each resolved object.
201    * And append this class to a list of objects.
202    */
203   function handle_elements($data,$id)
204   {
205     if(!isset($data['TYPE'])){
206       return;
207     }
208     $type = $data['TYPE'];
209     
210     $class_name= "sieve_".$type ;
211     if(class_exists($class_name)){
212       $this->pap[] = new $class_name($data,$id,$this);
213     }else{
214       echo "<font color='red'>Missing : ".$class_name."</font>"."<br>";
215     }
216   }
218   function save_object()
219   {
220     reset($this->pap);
221     foreach($this->pap as $key => $obj){
223       if(in_array_strict(get_class($obj),array("sieve_if",
224                                         "sieve_elsif",
225                                         "sieve_vacation",
226                                         "sieve_comment",
227                                         "sieve_reject",
228                                         "sieve_fileinto",
229                                         "sieve_require",
230                                         "sieve_redirect"))){
233         if(isset($this->pap[$key]) && method_exists($this->pap[$key],"save_object")){
234           $this->pap[$key]->save_object();
235         }
236       }
237     }
238   }
241   /* Remove the object at the given position */
242   function remove_object($key_id)
243   {
244     if(count($this->pap) == 1){
245       msg_dialog::display(_("Warning"), _("Cannot remove last element!"), ERROR_DIALOG);
246       return;
247     }
249     if(!isset($this->pap[$key_id])){
250       trigger_error("Can't remove element with object_id=".$key_id.", there is no object with this identifier. Remove aborted.");
251       return(false);
252     }
254     $class = get_class($this->pap[$key_id]);
255     if(in_array_strict($class,array("sieve_if","sieve_elsif","sieve_else"))){
256       $block_start= $key_id;
257       $block_end  = $this->get_block_end($key_id);
259       for($i = $block_start ; $i <= $block_end ; $i ++ ){
260         unset($this->pap[$i]);
261       }
262     }else{
263       unset($this->pap[$key_id]);
264     }
265     $tmp = array();
266     foreach($this->pap as $element){
267       $tmp[] = $element;
268     }
269     $this->pap = $tmp;
270   }
273   /* This function moves a given element to another position.
274    * Single elements like "keep;" will simply be moved one posisition down/up.
275    * Multiple elements like if-elsif-else will be moved as block. 
276    * 
277    *  $key_id     specified the element that should be moved.
278    *  $direction  specifies to move elements "up" or "down"
279    */
280   function move_up_down($key_id,$direction = "down")
281   {
282      
283     /* Get the current element to decide what to move. */ 
284     $e_class = get_class($this->pap[$key_id]);
285       
286     if(in_array_strict($e_class,array("sieve_if"))){
287       $block_start= $key_id;
288       $block_end  = $this->get_block_end($key_id);
290       /* Depending on the direction move up down */
291       if($direction == "down"){
292         $next_free  = $this->_get_next_free_move_slot($block_end,$direction); 
293       }else{
294         $next_free  = $this->_get_next_free_move_slot($block_start,$direction); 
295       }
297       /* Move the given block */ 
298       $this->move_multiple_elements($block_start,$block_end,$next_free);
299     }
301     if(in_array_strict($e_class,array( "sieve_stop",
302                                 "sieve_keep",
303                                 "sieve_require",
304                                 "sieve_comment",
305                                 "sieve_vacation",
306                                 "sieve_stop",   
307                                 "sieve_reject", 
308                                 "sieve_fileinto",
309                                 "sieve_redirect", 
310                                 "sieve_discard"))){
311       $this->move_single_element($key_id,$this->_get_next_free_move_slot($key_id,$direction));
312     }
313   }
315   
316   /* Move the given block to position */
317   function move_multiple_elements($start,$end,$to)
318   {
319     /* Use class names for testing */
320     $data = $this->pap;
322     /* Get block to move */
323     $block_to_move = array_slice($data,$start, ($end - $start +1));
325     /* We want do move this block up */
326     if($end > $to){
327       
328       /* Get start block */
329       $start_block = array_slice($data,0,$to);
331       /* Get Get all elements between the block to move 
332        *  and next free position 
333        */
334       $block_to_free = array_slice($data,$to ,$start - $to );  
335       $block_to_end = array_slice($data,$end+1);
336       $new = array();
337       foreach($start_block as $block){
338         $new[] = $block;
339       }
340       foreach($block_to_move as $block){
341         $new[] = $block;
342       }
343       foreach($block_to_free as $block){
344         $new[] = $block;
345       }
346       foreach($block_to_end as $block){
347         $new[] = $block;
348       }
349       $old = $this->pap;
350       $this->pap = $new;
351     }
352     
354     /* We want to move this block down. */
355     if($to > $end){
357       /* Get start block */
358       $start_block = array_slice($data,0,$start);
360       /* Get Get all elements between the block to move 
361        *  and next free position 
362        */
363       $block_to_free = array_slice($data,$end +1,($to - $end  ));  
365       /* Get the rest 
366        */
367       $block_to_end = array_slice($data,$to+1);
369       $new = array();
370       foreach($start_block as $block){
371         $new[] = $block;
372       }
373       foreach($block_to_free as $block){
374         $new[] = $block;
375       }
376       foreach($block_to_move as $block){
377         $new[] = $block;
378       }
379       foreach($block_to_end as $block){
380         $new[] = $block;
381       }
382       $old = $this->pap;
383       $this->pap = $new;
384     }
385   }  
387   
388   /* This function returns the id of the element 
389    *  where the current block ends  
390    */
391   function get_block_end($start,$complete = TRUE)
392   {
393     /* Only execute if this is a really a block element. 
394      * Block elements is only sieve_if
395      */
396     if(in_array_strict(get_class($this->pap[$start]),array("sieve_if","sieve_elsif","sieve_else"))){
398       $class      = get_class($this->pap[$start]);
399       $next_class = get_class($this->pap[$start+1]);
400       $block_depth = 0;
402       $end = FALSE;
404       while(!$end && $start < count($this->pap)){
405  
406         if($class == "sieve_block_start"){
407           $block_depth ++;
408         }
410         if($class == "sieve_block_end"){
411           $block_depth --;
412         }
414         if($complete){
415           if( $block_depth == 0 && 
416               $class == "sieve_block_end" && 
417               !in_array_strict($next_class,array("sieve_else","sieve_elsif"))){
418             $end = TRUE;
419             $start --;
420           }
421         }else{
423           if( $block_depth == 0 && 
424               $class == "sieve_block_end" ){ 
425             $end = TRUE;
426             $start --;
427           }
428         }
430         $start ++;       
431         $class      = get_class($this->pap[$start]);
432         
433         if(isset($this->pap[$start+1])){ 
434           $next_class = get_class($this->pap[$start+1]);
435         }else{
436           $next_class ="";
437         }
438       }
439     }
440     return($start);
441   }
444   /* This function moves the single element at 
445    *  position $from to position $to.
446    */
447   function move_single_element($from,$to)
448   {
449     if($from == $to) {
450       return;
451     }
453     $ret = array();
454     $tmp = $this->pap;
456     $begin = array();
457     $middle = array();
458     $end = array();
459     $element = $this->pap[$from];
461     if($from > $to ){
463       /* Get all element in fron to element to move */    
464       if($from  != 0){
465         $begin = array_slice($tmp,0,$to);
466       }
468       /* Get all elements between */
469       $middle = array_slice($tmp,$to , ($from - ($to) ));  
470     
471       /* Get the rest */ 
472       $end  = array_slice($tmp,$from+1);
473  
474       foreach($begin as $data){
475         $ret[] = $data;
476       }
477       $ret[] = $element;
478       foreach($middle as $data){
479         $ret[] = $data;
480       }
481       foreach($end as $data){
482         $ret[] = $data;
483       }
484       $this->pap = $ret;
485     }
486     if($from < $to ){
488       /* Get all element in fron to element to move */    
489       if($from  != 0){
490         $begin = array_slice($tmp,0,$from);
491       }
493       /* Get all elements between */
494       $middle = array_slice($tmp,$from+1 , ($to - ($from)));  
495     
496       /* Get the rest */ 
497       $end  = array_slice($tmp,$to+1);
498  
499       foreach($begin as $data){
500         $ret[] = $data;
501       }
502       foreach($middle as $data){
503         $ret[] = $data;
504       }
505       $ret[] = $element;
506       foreach($end as $data){
507         $ret[] = $data;
508       }
509       $this->pap = $ret;
510     }
511   }
514   /* Returns the next free position where we 
515    *  can add a new sinle element 
516    *    $key_id     = Current position
517    *    $direction  = Forward or backward.
518    */
519   function _get_next_free_move_slot($key_id,$direction,$include_self = FALSE)
520   {
521     $last_class = "";
522     $current_class ="";
523     $next_class = "";
525     /* After this elements we can add new elements 
526      *  without having any trouble.
527      */
528     $allowed_to_add_after = array("sieve_keep",
529                                   "sieve_require", 
530                                   "sieve_stop", 
531                                   "sieve_reject", 
532                                   "sieve_fileinto", 
533                                   "sieve_redirect", 
534                                   "sieve_discard",
535                                   "sieve_comment",
536                                   "sieve_block_start"
537                                  );
539     /* Before this elements we can add new elements 
540      *  without having any trouble.
541      */
542     $allowed_to_add_before = array("sieve_keep",
543                                   "sieve_require", 
544                                   "sieve_stop", 
545                                   "sieve_reject", 
546                                   "sieve_fileinto", 
547                                   "sieve_comment",
548                                   "sieve_redirect", 
549                                   "sieve_discard",
550                                   "sieve_if", 
551                                   "sieve_block_end"
552                                  );
554     if($direction == "down"){
555     
556       $test = $this->pap;
557       while($key_id < count($test)){
558         if(($key_id+1) == count($test)) {
559           return($key_id);
560         }
562         if(!$include_self){
563           $key_id ++;
564         }
565         $include_self = FALSE;
566         $current_class  = get_class($test[$key_id]);
567         if(in_array_strict($current_class, $allowed_to_add_after)){
568           return($key_id);
569         } 
570       } 
571     }else{
572   
573       $test = $this->pap;
574       if($key_id == 0) {
575         return($key_id);
576       }
577       if(!$include_self){
578         $key_id --;
579       }
580       while($key_id >=0 ){
581         $current_class  = get_class($test[$key_id]);
582         if(in_array_strict($current_class, $allowed_to_add_before)){
583           return($key_id);
584         } 
585         $key_id --;
586       }
587       return(0);
588     }
589   }
592   /* Need to be reviewed */
593   function get_sieve_script()
594   {
595     $tmp ="";
596     if(count($this->pap)){
597       $buffer = "";    
598       foreach($this->pap as $part)  {
599         if(get_class($part) == "sieve_block_end"){
600           $buffer = substr($buffer,0,strlen($buffer)-(strlen(SIEVE_INDENT_TAB)));
601         }
602         $tmp2 = $part->get_sieve_script_part();
604         $tmp3 = preg_split("/\n/",$tmp2);
605         foreach($tmp3 as $str){
606           $str2 = trim($str);
608           /* If the current line only contains an '.'    
609            *  we must skip the line indent. 
610            * The text: statement uses a single '.' to mark the text end.
611            * This '.' must be the only char in the current line, no
612            *  whitespaces are allowed here.
613            */
614           if($str2 == "."){
615             $tmp.=$str."\n";
616           }else{
617             $tmp.= $buffer.$str."\n";
618           }
619         }
620         if(get_class($part) == "sieve_block_start"){
621           $buffer .= SIEVE_INDENT_TAB;
622         }
623       }
624     }
625     if(!preg_match("/Generated by GOsa - Gonicus System Administrator/",$tmp)){
626 #      $tmp = "#Generated by GOsa - Gonicus System Administrator \n ".$tmp;
627     }
628     return($tmp);
629   }
631   function check()
632   {
633     $msgs = array();
635     /* Some logical checks. 
636      *  like :  only sieve_comment can appear before require.
637      */
639     /* Ensure that there are no command before require 
640      *  - Get id of last require tag
641      *  - Collect object types in from of this tag. 
642      *  - Check if there are tags collected that are not allowed 
643      */
644     $last_found_at = -1; 
645     $objs = array();
646     foreach($this->pap as $key => $obj){
647       if(get_class($obj) == "sieve_require"){
648         $last_found_at = $key;
649       }
650     }
651     foreach($this->pap as $key => $obj){
652       if($key == $last_found_at) break;  
653       if(!in_array_strict(get_class($obj),array("sieve_comment","sieve_require"))){
654         $objs[] = get_class($obj);
655       }
656     }
657     if(count($objs) && $last_found_at != -1){
658       $str = _("Require must be the first command in the script.");  
659       $msgs[] = $str;
660       msg_dialog::display(_("Error"), $str, ERROR_DIALOG);
661     }
663     foreach($this->pap as $obj){
664       $o_msgs = $obj->check();
665       foreach($o_msgs as $o_msg){
666         $msgs[] = $o_msg;
667       }
668     }
669     return($msgs);
670   }
673   /* We are forced to add a new require.
674    *  This function is called by the 
675    *  sieveElement_Classes->parent->add_require()  
676    */ 
677   function add_require($str)
678   {
679     $require_id = -1;
680     foreach($this->pap as $key => $obj){
681       if(get_class($obj) == "sieve_require"){
682         $require_id = $key;
683       }
684     }
686     /* No require found, add one */
687     if($require_id == -1){
688       $require = new sieve_require(NULL,preg_replace("/[^0-9]/","",microtime()),$this);
689       $require -> Add_Require($str);
690       $new = array();
691       $new[] = $require;
692       foreach($this->pap as $obj){
693         $new[] = $obj;
694       }
695       $this->pap = $new;
696     } else { 
697       $this->pap[$require_id]->Add_Require($str);
698     } 
699   }
703 /* Create valid sieve string/string-list 
704  *  out of a given array
705  */
706 function sieve_create_strings($data,$force_string = FALSE)
708   $ret = "";
709   if(is_array($data)){
710     if(count($data) == 1){
711       $ret = "\"";
712       foreach($data as $dat){
713         $ret .=$dat;
714       }
715       $ret.="\"";
716     }else{
717       foreach($data as $dat){
718         $ret.= "\"";
719         $ret.=$dat;
720         $ret.="\", ";
721       }
722       $ret = preg_replace("/,$/","",trim($ret));
723       $ret = "[".$ret."]";
724     }
725 #      $ret = preg_replace("/\"\"/","\"",$ret);
726   }else{
728     $Multiline = preg_match("/\n/",$data);
729     $data = preg_replace("/\r/","",$data);;
731     if($Multiline && !$force_string){
732       $ret = "text: \r\n".$data."\r\n.\r\n";
733     }else{
734       $ret = "\"".$data."\"";
735       $ret = preg_replace("/\"\"/","\"",$ret);
736     }
737   }
738   $ret = preg_replace("/\n/","\r\n",$ret);
740   return($ret);
743 /* This checks if there is a string at the current position 
744  *  in the token array. 
745  * If there is a string list at the current position,
746  *  this function will return a complete list of all
747  *  strings used in this list.
748  * It also returns an offset of the last token position 
749  */
750 function sieve_get_strings($data,$id)
752   $ret = array();
753   if($data[$id]['class'] == "left-bracket"){
754     while(isset($data[$id]) && $data[$id]['class']  != "right-bracket" && $id < count($data)){
756       if($data[$id]['class'] == "quoted-string"){
757         $text = $data[$id]['text']; 
758         $text= preg_replace("/^\"/","",$text);
759         $text= preg_replace("/\"$/","",$text);
760         $ret[] = $text;
761       }
763       $id ++;
764     }
765   }elseif($data[$id]['class'] == "quoted-string"){
766     $text = $data[$id]['text']; 
767     $text= preg_replace("/^\"/","",$text);
768     $text= preg_replace("/\"$/","",$text);
769     $ret[] = $text;
770   }elseif($data[$id]['class'] == "number"){
771     $ret[] = $data[$id]['text'];
772   }elseif($data[$id]['class'] == "multi-line"){
773     $str = trim(preg_replace("/^text:/","",$data[$id]['text']));
774     $str = trim(preg_replace("/\.$/","",$str));
775     $ret[] = $str;
776   }
778   return(array("OFFSET" => $id, "STRINGS" => $ret));
781 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
782 ?>