Code

c94d34cef8d7d8836ab52a657c303b6cec07bd8e
[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);
45     }
47     /* Create html results */
48     $smarty = get_smarty();
50     $block_indent_start = $smarty->fetch(get_template_path("templates/block_indent_start.tpl",TRUE,dirname(__FILE__)));
51     $block_indent_stop  = $smarty->fetch(get_template_path("templates/block_indent_stop.tpl",TRUE,dirname(__FILE__))); 
52   
53   
55     $this -> dump_ = "";
56     $ends = array();
57     foreach($this->pap as $key => $object){
58       if(is_object($object)){
59         $end = $this->get_block_end($key);
61         if($end != $key && get_class($object) == "sieve_if") {
62           $ends[$end] = $end;  
63           $this->dump_  .= $block_indent_start;
64         }
65         $this->dump_ .= preg_replace("/>/",">\n",$object->execute()); 
66         if(isset($ends[$key])){
67           $this->dump_  .= $block_indent_stop;
68         }
69       }
70     }
71     
72     return($this->dump_);
73   }
76   /* This function walks through the object tree generated by the "Parse" class.
77    * All Commands will be resolved and grouped. So the Commands and their 
78    *  parameter are combined. Like "IF" and ":comparator"...
79    */  
80   function doDump_($node_id, $prefix, $last,$num = 1)
81   {
82     /* Indicates that current comman will only be valid for a single line. 
83      *  this command type will be removed from mode_stack after displaying it.
84      */
85     $rewoke_last = FALSE;
87     /* Get node */ 
88     $node = $this->nodes_[$node_id];
90     /* Get last element class */
91     $last_class = "";
92     if(count($this->mode_stack)){
93       $key = key($this->mode_stack);
94       $tmp = array_reverse($this->mode_stack[$key]['ELEMENTS']);
95       $last_class = $tmp[key($tmp)]['class'];
96     }
98     /* This closes the last mode */
99     if($node['class'] == "block-start"){
100       $tmp = array_pop($this->mode_stack);
101       $this->handle_elements($tmp,$node_id);
102       $this->handle_elements(array("TYPE" => "block_start"),preg_replace("/[^0-9]/","",microtime()));
103     }
105     /* This closes the last mode */
106     if($node['class'] == "block-end"){
107       $tmp = array_pop($this->mode_stack);
108       $this->handle_elements($tmp,$node_id);
109       $this->handle_elements(array("TYPE" => "block_end"),preg_replace("/[^0-9]/","",microtime()));
110     }
112     /* Semicolon indicates a new command */
113     if($node['class'] == "semicolon"){
114       $tmp =array_pop($this->mode_stack);
115       $this->handle_elements($tmp,$node_id);
116     }
119     /* Comments require special attention.
120      * We do not want to create a single comment element 
121      *  foreach each  "#comment"  string found in the script. 
122      * Sometimes comments are used like this 
123      *   # This is a comment
124      *   #  and it still is a comment 
125      *   #  ...
126      * So we combine them to one single comment.
127      */
128     if($last_class != "comment" && $node['class'] == "comment"){
129       $tmp =array_pop($this->mode_stack);
130       $this->handle_elements($tmp,$node_id);
131       $this->mode_stack[] = array("TYPE" => $node['class']); 
132     }  
134     if($last_class == "comment" && $node['class'] != "comment"){
135       $tmp =array_pop($this->mode_stack);
136       $this->handle_elements($tmp,$node_id);
137     }
140     /* Handle identifiers */
141     $identifiers = array("else","if","elsif","end","reject","redirect","vacation","keep","discard","fileinto","require","stop");
142     if($node['class'] == "identifier" && in_array($node['text'],$identifiers)){
143       $this->mode_stack[] = array("TYPE" => $node['text']); 
144     }
146     /* Add current node to current command stack */
147     end($this->mode_stack);
148     $key = key($this->mode_stack);
149     $this->mode_stack[$key]['ELEMENTS'][] = $node;
151     /* Remove last mode from mode stack, cause it was only valid for a single line */
152     if($rewoke_last){
153       $tmp =array_pop($this->mode_stack);
154       $this->handle_elements($tmp,$node_id);
155     }
157     /* If this is a sub element, just call this for all childs */       
158     if(isset($this->childs_[$node_id])){
159       $childs = $this->childs_[$node_id];
160       for ($i=0; $i<count($childs); ++$i)
161       {
162         $c_last = false;
163         if ($i+1 == count($childs))
164         {
165           $c_last = true;
166         }
167         $this->doDump_($childs[$i], "", $num);
168       }
169     }
170   }
173   /* Create a class for each resolved object.
174    * And append this class to a list of objects.
175    */
176   function handle_elements($data,$id)
177   {
178     if(!isset($data['TYPE'])){
179       return;
180     }
181     $type = $data['TYPE'];
182     
183     $class_name= "sieve_".$type ;
184     if(class_exists($class_name)){
185       $this->pap[] = new $class_name($data,$id,$this);
186     }else{
187       echo "<font color='red'>Missing : ".$class_name."</font>"."<br>";
188     }
189   }
191   function save_object()
192   {
193     reset($this->pap);
194     foreach($this->pap as $key => $obj){
196       if(in_array(get_class($obj),array("sieve_if",
197                                         "sieve_elsif",
198                                         "sieve_vacation",
199                                         "sieve_comment",
200                                         "sieve_reject",
201                                         "sieve_fileinto",
202                                         "sieve_require",
203                                         "sieve_redirect"))){
206         if(isset($this->pap[$key]) && method_exists($this->pap[$key],"save_object")){
207           $this->pap[$key]->save_object();
208         }
209       }
210     }
211   }
214   /* Remove the object at the given position */
215   function remove_object($key_id)
216   {
217     $class = get_class($this->pap[$key_id]);
218     if(in_array($class,array("sieve_if","sieve_elsif","sieve_else"))){
219       $block_start= $key_id;
220       $block_end  = $this->get_block_end($key_id);
222       for($i = $block_start ; $i <= $block_end ; $i ++ ){
223         unset($this->pap[$i]);
224       }
225     }else{
226       unset($this->pap[$key_id]);
227     }
228     $tmp = array();
229     foreach($this->pap as $element){
230       $tmp[] = $element;
231     }
232     $this->pap = $tmp;
233   }
236   /* This function moves a given element to another position.
237    * Single elements like "keep;" will simply be moved one posisition down/up.
238    * Multiple elements like if-elsif-else will be moved as block. 
239    * 
240    *  $key_id     specified the element that should be moved.
241    *  $direction  specifies to move elements "up" or "down"
242    */
243   function move_up_down($key_id,$direction = "down")
244   {
245      
246     /* Get the current element to decide what to move. */ 
247     $e_class = get_class($this->pap[$key_id]);
248       
249     if(in_array($e_class,array("sieve_if"))){
250       $block_start= $key_id;
251       $block_end  = $this->get_block_end($key_id);
253       /* Depending on the direction move up down */
254       if($direction == "down"){
255         $next_free  = $this->_get_next_free_move_slot($block_end,$direction); 
256       }else{
257         $next_free  = $this->_get_next_free_move_slot($block_start,$direction); 
258       }
260       /* Move the given block */ 
261       $this->move_multiple_elements($block_start,$block_end,$next_free);
262     }
264     if(in_array($e_class,array( "sieve_stop",
265                                 "sieve_keep",
266                                 "sieve_require",
267                                 "sieve_comment",
268                                 "sieve_vacation",
269                                 "sieve_stop",   
270                                 "sieve_reject", 
271                                 "sieve_fileinto",
272                                 "sieve_redirect", 
273                                 "sieve_discard"))){
274       $this->move_single_element($key_id,$this->_get_next_free_move_slot($key_id,$direction));
275     }
276   }
278   
279   /* Move the given block to position */
280   function move_multiple_elements($start,$end,$to)
281   {
282     /* Use class names for testing */
283     $data = $this->pap;
285     /* Get block to move */
286     $block_to_move = array_slice($data,$start, ($end - $start +1));
288     /* We want do move this block up */
289     if($end > $to){
290       
291       /* Get start block */
292       $start_block = array_slice($data,0,$to);
294       /* Get Get all elements between the block to move 
295        *  and next free position 
296        */
297       $block_to_free = array_slice($data,$to ,$start - $to );  
298       $block_to_end = array_slice($data,$end+1);
299       $new = array();
300       foreach($start_block as $block){
301         $new[] = $block;
302       }
303       foreach($block_to_move as $block){
304         $new[] = $block;
305       }
306       foreach($block_to_free as $block){
307         $new[] = $block;
308       }
309       foreach($block_to_end as $block){
310         $new[] = $block;
311       }
312       $old = $this->pap;
313       $this->pap = $new;
314     }
315     
317     /* We want to move this block down. */
318     if($to > $end){
320       /* Get start block */
321       $start_block = array_slice($data,0,$start);
323       /* Get Get all elements between the block to move 
324        *  and next free position 
325        */
326       $block_to_free = array_slice($data,$end +1,($to - $end  ));  
328       /* Get the rest 
329        */
330       $block_to_end = array_slice($data,$to+1);
332       $new = array();
333       foreach($start_block as $block){
334         $new[] = $block;
335       }
336       foreach($block_to_free as $block){
337         $new[] = $block;
338       }
339       foreach($block_to_move as $block){
340         $new[] = $block;
341       }
342       foreach($block_to_end as $block){
343         $new[] = $block;
344       }
345       $old = $this->pap;
346       $this->pap = $new;
347     }
348   }  
350   
351   /* This function returns the id of the element 
352    *  where the current block ends  
353    */
354   function get_block_end($start)
355   {
356     /* Only execute if this is a really a block element. 
357      * Block elements is only sieve_if
358      */
359     if(in_array(get_class($this->pap[$start]),array("sieve_if","sieve_elsif","sieve_else"))){
361       $class      = get_class($this->pap[$start]);
362       $next_class = get_class($this->pap[$start+1]);
363       $block_depth = 0;
365       $end = FALSE;
367       while(!$end && $start < count($this->pap)){
368  
369         if($class == "sieve_block_start"){
370           $block_depth ++;
371         }
373         if($class == "sieve_block_end"){
374           $block_depth --;
375         }
377         if( $block_depth == 0 && 
378             $class == "sieve_block_end" && 
379             !in_array($next_class,array("sieve_else","sieve_elsif"))){
380           $end = TRUE;
381           $start --;
382         }
383         $start ++;       
384         $class      = get_class($this->pap[$start]);
385         
386         if(isset($this->pap[$start+1])){ 
387           $next_class = get_class($this->pap[$start+1]);
388         }else{
389           $next_class ="";
390         }
391       }
392     }
393     return($start);
394   }
397   /* This function moves the single element at 
398    *  position $from to position $to.
399    */
400   function move_single_element($from,$to)
401   {
402     if($from == $to) {
403       return;
404     }
406     $ret = array();
407     $tmp = $this->pap;
409     $begin = array();
410     $middle = array();
411     $end = array();
412     $element = $this->pap[$from];
414     if($from > $to ){
416       /* Get all element in fron to element to move */    
417       if($from  != 0){
418         $begin = array_slice($tmp,0,$to);
419       }
421       /* Get all elements between */
422       $middle = array_slice($tmp,$to , ($from - ($to) ));  
423     
424       /* Get the rest */ 
425       $end  = array_slice($tmp,$from+1);
426  
427       foreach($begin as $data){
428         $ret[] = $data;
429       }
430       $ret[] = $element;
431       foreach($middle as $data){
432         $ret[] = $data;
433       }
434       foreach($end as $data){
435         $ret[] = $data;
436       }
437       $this->pap = $ret;
438     }
439     if($from < $to ){
441       /* Get all element in fron to element to move */    
442       if($from  != 0){
443         $begin = array_slice($tmp,0,$from);
444       }
446       /* Get all elements between */
447       $middle = array_slice($tmp,$from+1 , ($to - ($from)));  
448     
449       /* Get the rest */ 
450       $end  = array_slice($tmp,$to+1);
451  
452       foreach($begin as $data){
453         $ret[] = $data;
454       }
455       foreach($middle as $data){
456         $ret[] = $data;
457       }
458       $ret[] = $element;
459       foreach($end as $data){
460         $ret[] = $data;
461       }
462       $this->pap = $ret;
463     }
464   }
467   /* Returns the next free position where we 
468    *  can add a new sinle element 
469    *    $key_id     = Current position
470    *    $direction  = Forward or backward.
471    */
472   function _get_next_free_move_slot($key_id,$direction,$include_self = FALSE)
473   {
474     $last_class = "";
475     $current_class ="";
476     $next_class = "";
478     /* After this elements we can add new elements 
479      *  without having any trouble.
480      */
481     $allowed_to_add_after = array("sieve_keep",
482                                   "sieve_require", 
483                                   "sieve_stop", 
484                                   "sieve_reject", 
485                                   "sieve_fileinto", 
486                                   "sieve_redirect", 
487                                   "sieve_discard",
488                                   "sieve_comment",
489                                   "sieve_block_start"
490                                  );
492     /* Before this elements we can add new elements 
493      *  without having any trouble.
494      */
495     $allowed_to_add_before = array("sieve_keep",
496                                   "sieve_require", 
497                                   "sieve_stop", 
498                                   "sieve_reject", 
499                                   "sieve_fileinto", 
500                                   "sieve_comment",
501                                   "sieve_redirect", 
502                                   "sieve_discard",
503                                   "sieve_if", 
504                                   "sieve_block_end"
505                                  );
507     if($direction == "down"){
508     
509       $test = $this->pap;
510       while($key_id < count($test)){
511         if(($key_id+1) == count($test)) {
512           return($key_id);
513         }
515         if(!$include_self){
516           $key_id ++;
517         }
518         $current_class  = get_class($test[$key_id]);
519         if(in_array($current_class, $allowed_to_add_after)){
520           return($key_id);
521         } 
522       } 
523     }else{
524   
525       $test = $this->pap;
526       if($key_id == 0) {
527         return($key_id);
528       }
529       if(!$include_self){
530         $key_id --;
531       }
532       while($key_id >=0 ){
533         $current_class  = get_class($test[$key_id]);
534         if(in_array($current_class, $allowed_to_add_before)){
535           return($key_id);
536         } 
537         $key_id --;
538       }
539       return(0);
540     }
541   }
544   /* Need to be reviewed */
545   function get_sieve_script()
546   {
547     $tmp ="";
548     if(count($this->pap)){
549       $buffer = "";    
550       foreach($this->pap as $part)  {
551         if(get_class($part) == "sieve_block_end"){
552           $buffer = substr($buffer,0,strlen($buffer)-(strlen(SIEVE_INDENT_TAB)));
553         }
554         $tmp2 = $part->get_sieve_script_part();
556         if(get_class($part) == "sieve_reject"){
557           $tmp.=$tmp2;
558         }else{
560           $tmp3 = split("\n",$tmp2);
561           foreach($tmp3 as $str){
562             $str2 = trim($str);
563             if(empty($str2)) continue;
564             $tmp.= $buffer.$str."\n";
565           }
566         }
567         if(get_class($part) == "sieve_block_start"){
568           $buffer .= SIEVE_INDENT_TAB;
569         }
570       }
571     }
572     if(!preg_match("/Generated by GOsa - Gonicus System Administrator/",$tmp)){
573       $tmp = "#Generated by GOsa - Gonicus System Administrator \n ".$tmp;
574     }
575     return($tmp);
576   }
578   function check()
579   {
580                 $msgs = array();
582     /* Some logical checks. 
583      *  like :  only sieve_comment can appear before require.
584      */
585     
586     /* Ensure that there are no command before require 
587      *  - Get id of last require tag
588      *  - Collect object types in from of this tag. 
589      *  - Check if there are tags collected that are not allowed 
590      */
591     $last_found_at = -1; 
592     $objs = array();
593     foreach($this->pap as $key => $obj){
594       if(get_class($obj) == "sieve_require"){
595         $last_found_at = $key;
596       }
597     }
598     foreach($this->pap as $key => $obj){
599       if($key == $last_found_at) break;  
600       if(!in_array(get_class($obj),array("sieve_comment","sieve_require"))){
601         $objs[] = get_class($obj);
602       }
603     }
604     if(count($objs) && $last_found_at != -1){
605       $str = _("Require must be the first command in the script.");  
606       $msgs[] = $str;
607       print_red($str);;
608     }
609     
610                 foreach($this->pap as $obj){
611                         $o_msgs = $obj->check();
612                         foreach($o_msgs as $o_msg){
613                                 $msgs[] = $o_msg;
614                         }
615                 }
616                 return($msgs);
617   }
620   /* We are forced to add a new require.
621    *  This function is called by the 
622    *  sieveElement_Classes->parent->add_require()  
623    */ 
624   function add_require($str)
625   {
626     $require_id = -1;
627     foreach($this->pap as $key => $obj){
628       if(get_class($obj) == "sieve_require"){
629         $require_id = $key;
630       }
631     }
632   
633     /* No require found, add one */
634     if($require_id == -1){
635       $require = new sieve_require(NULL,preg_replace("/[^0-9]/","",microtime()),$this);
636       $require -> Add_Require($str);
637       $new = array();
638       $new[] = $require;
639       foreach($this->pap as $obj){
640         $new[] = $obj;
641       }
642       $this->pap = $new;
643     } else { 
644       $this->pap[$require_id]->Add_Require($str);
645     } 
646   }
650 /* Create valid sieve string/string-list 
651  *  out of a given array
652  */
653 function sieve_create_strings($data,$force_string = FALSE)
655   $ret = "";
656   if(is_array($data)){
657     if(count($data) == 1){
658       $ret = "\"";
659       foreach($data as $dat){
660         $ret .=$dat;
661       }
662       $ret.="\"";
663     }else{
664       foreach($data as $dat){
665         $ret.= "\"";
666         $ret.=$dat;
667         $ret.="\", ";
668       }
669       $ret = preg_replace("/,$/","",trim($ret));
670       $ret = "[".$ret."]";
671     }
672   }else{
674     $Multiline = preg_match("/\n/",$data);
675     $data = preg_replace("/\r/","",$data);;
677     if($Multiline && !$force_string){
678       $ret = "text: \r\n".$data."\r\n.\r\n";
679     }else{
680       $ret = "\"".$data."\"";
681     }
682   }
683   $ret = preg_replace("/\"\"/","\"",$ret);
684   $ret = preg_replace("/\n/","\r\n",$ret);
685   
686   return($ret);
689 /* This checks if there is a string at the current position 
690  *  in the token array. 
691  * If there is a string list at the current position,
692  *  this function will return a complete list of all
693  *  strings used in this list.
694  * It also returns an offset of the last token position 
695  */
696 function sieve_get_strings($data,$id)
698   $ret = array();
699   if($data[$id]['class'] == "left-bracket"){
700     while($data[$id]['class']  != "right-bracket" && $id < count($data)){
701       
702       if($data[$id]['class'] == "quoted-string"){
703         $ret[] = $data[$id]['text'];
704       }
705       $id ++;
706     }
707   }elseif($data[$id]['class'] == "quoted-string"){
708     $ret[] = $data[$id]['text'];
709   }elseif($data[$id]['class'] == "number"){
710     $ret[] = $data[$id]['text'];
711   }elseif($data[$id]['class'] == "multi-line"){
712     $str = trim(preg_replace("/^text:/","",$data[$id]['text']));
713     $str = trim(preg_replace("/\.$/","",$str));
714     $ret[] = $str;
715   }
716   
717   return(array("OFFSET" => $id, "STRINGS" => $ret));
720 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
721 ?>