Code

Some design changes
[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     /* This closes the last mode */
91     if($node['class'] == "block-start"){
92       $tmp = array_pop($this->mode_stack);
93       $this->handle_elements($tmp,$node_id);
94       $this->handle_elements(array("TYPE" => "block_start"),preg_replace("/[^0-9]/","",microtime()));
95     }
97     /* This closes the last mode */
98     if($node['class'] == "block-end"){
99       $tmp = array_pop($this->mode_stack);
100       $this->handle_elements($tmp,$node_id);
101       $this->handle_elements(array("TYPE" => "block_end"),preg_replace("/[^0-9]/","",microtime()));
102     }
104     /* Semicolon indicates a new command */
105     if($node['class'] == "semicolon"){
106       $tmp =array_pop($this->mode_stack);
107       $this->handle_elements($tmp,$node_id);
108     }
110     /* Handle comments */
111     if($node['class'] == "comment"){
112       $this->mode_stack[] = array("TYPE" => $node['class']);
113       $rewoke_last = TRUE;
114     }
116     /* Handle identifiers */
117     $identifiers = array("else","if","elsif","end","reject","redirect","vacation","keep","discard","comment","fileinto","require","stop");
118     if($node['class'] == "identifier" && in_array($node['text'],$identifiers)){
119       $this->mode_stack[] = array("TYPE" => $node['text']); 
120     }
122     /* Add current node to current command stack */
123     end($this->mode_stack);
124     $key = key($this->mode_stack);
125     $this->mode_stack[$key]['ELEMENTS'][] = $node;
127     /* Remove last mode from mode stack, cause it was only valid for a single line */
128     if($rewoke_last){
129       $tmp =array_pop($this->mode_stack);
130       $this->handle_elements($tmp,$node_id);
131     }
133     /* If this is a sub element, just call this for all childs */       
134     if(isset($this->childs_[$node_id])){
135       $childs = $this->childs_[$node_id];
136       for ($i=0; $i<count($childs); ++$i)
137       {
138         $c_last = false;
139         if ($i+1 == count($childs))
140         {
141           $c_last = true;
142         }
143         $this->doDump_($childs[$i], "", $num);
144       }
145     }
146   }
149   /* Create a class for each resolved object.
150    * And append this class to a list of objects.
151    */
152   function handle_elements($data,$id)
153   {
154     if(!isset($data['TYPE'])){
155       return;
156     }
157     $type = $data['TYPE'];
158     
159     $class_name= "sieve_".$type ;
160     if(class_exists($class_name)){
161       $this->pap[] = new $class_name($data,$id,$this);
162     }else{
163       echo "<font color='red'>Missing : ".$class_name."</font>"."<br>";
164     }
165   }
167   function save_object()
168   {
169     reset($this->pap);
170     foreach($this->pap as $key => $obj){
172       if(in_array(get_class($obj),array("sieve_if",
173                                         "sieve_elsif",
174                                         "sieve_vacation",
175                                         "sieve_comment",
176                                         "sieve_reject",
177                                         "sieve_fileinto",
178                                         "sieve_require",
179                                         "sieve_redirect"))){
182         if(isset($this->pap[$key]) && method_exists($this->pap[$key],"save_object")){
183           $this->pap[$key]->save_object();
184         }
185       }
186     }
187   }
190   /* Remove the object at the given position */
191   function remove_object($key_id)
192   {
193     $class = get_class($this->pap[$key_id]);
194     if(in_array($class,array("sieve_if","sieve_elsif","sieve_else"))){
195       $block_start= $key_id;
196       $block_end  = $this->get_block_end($key_id);
198       for($i = $block_start ; $i <= $block_end ; $i ++ ){
199         unset($this->pap[$i]);
200       }
201     }else{
202       unset($this->pap[$key_id]);
203     }
204     $tmp = array();
205     foreach($this->pap as $element){
206       $tmp[] = $element;
207     }
208     $this->pap = $tmp;
209   }
212   /* This function moves a given element to another position.
213    * Single elements like "keep;" will simply be moved one posisition down/up.
214    * Multiple elements like if-elsif-else will be moved as block. 
215    * 
216    *  $key_id     specified the element that should be moved.
217    *  $direction  specifies to move elements "up" or "down"
218    */
219   function move_up_down($key_id,$direction = "down")
220   {
221      
222     /* Get the current element to decide what to move. */ 
223     $e_class = get_class($this->pap[$key_id]);
224       
225     if(in_array($e_class,array("sieve_if"))){
226       $block_start= $key_id;
227       $block_end  = $this->get_block_end($key_id);
229       /* Depending on the direction move up down */
230       if($direction == "down"){
231         $next_free  = $this->_get_next_free_move_slot($block_end,$direction); 
232       }else{
233         $next_free  = $this->_get_next_free_move_slot($block_start,$direction); 
234       }
236       /* Move the given block */ 
237       $this->move_multiple_elements($block_start,$block_end,$next_free);
238     }
240     if(in_array($e_class,array( "sieve_stop",
241                                 "sieve_keep",
242                                 "sieve_require",
243                                 "sieve_comment",
244                                 "sieve_vacation",
245                                 "sieve_stop",   
246                                 "sieve_reject", 
247                                 "sieve_fileinto",
248                                 "sieve_redirect", 
249                                 "sieve_discard"))){
250       $this->move_single_element($key_id,$this->_get_next_free_move_slot($key_id,$direction));
251     }
252   }
254   
255   /* Move the given block to position */
256   function move_multiple_elements($start,$end,$to)
257   {
258     /* Use class names for testing */
259     $data = $this->pap;
261     /* Get block to move */
262     $block_to_move = array_slice($data,$start, ($end - $start +1));
264     /* We want do move this block up */
265     if($end > $to){
266       
267       /* Get start block */
268       $start_block = array_slice($data,0,$to);
270       /* Get Get all elements between the block to move 
271        *  and next free position 
272        */
273       $block_to_free = array_slice($data,$to ,$start - $to );  
274       $block_to_end = array_slice($data,$end+1);
275       $new = array();
276       foreach($start_block as $block){
277         $new[] = $block;
278       }
279       foreach($block_to_move as $block){
280         $new[] = $block;
281       }
282       foreach($block_to_free as $block){
283         $new[] = $block;
284       }
285       foreach($block_to_end as $block){
286         $new[] = $block;
287       }
288       $old = $this->pap;
289       $this->pap = $new;
290     }
291     
293     /* We want to move this block down. */
294     if($to > $end){
296       /* Get start block */
297       $start_block = array_slice($data,0,$start);
299       /* Get Get all elements between the block to move 
300        *  and next free position 
301        */
302       $block_to_free = array_slice($data,$end +1,($to - $end  ));  
304       /* Get the rest 
305        */
306       $block_to_end = array_slice($data,$to+1);
308       $new = array();
309       foreach($start_block as $block){
310         $new[] = $block;
311       }
312       foreach($block_to_free as $block){
313         $new[] = $block;
314       }
315       foreach($block_to_move as $block){
316         $new[] = $block;
317       }
318       foreach($block_to_end as $block){
319         $new[] = $block;
320       }
321       $old = $this->pap;
322       $this->pap = $new;
323     }
324   }  
326   
327   /* This function returns the id of the element 
328    *  where the current block ends  
329    */
330   function get_block_end($start)
331   {
332     /* Only execute if this is a really a block element. 
333      * Block elements is only sieve_if
334      */
335     if(in_array(get_class($this->pap[$start]),array("sieve_if","sieve_elsif","sieve_else"))){
337       $class      = get_class($this->pap[$start]);
338       $next_class = get_class($this->pap[$start+1]);
339       $block_depth = 0;
341       $end = FALSE;
343       while(!$end && $start < count($this->pap)){
344  
345         if($class == "sieve_block_start"){
346           $block_depth ++;
347         }
349         if($class == "sieve_block_end"){
350           $block_depth --;
351         }
353         if( $block_depth == 0 && 
354             $class == "sieve_block_end" && 
355             !in_array($next_class,array("sieve_else","sieve_elsif"))){
356           $end = TRUE;
357           $start --;
358         }
359         $start ++;       
360         $class      = get_class($this->pap[$start]);
361         
362         if(isset($this->pap[$start+1])){ 
363           $next_class = get_class($this->pap[$start+1]);
364         }else{
365           $next_class ="";
366         }
367       }
368     }
369     return($start);
370   }
373   /* This function moves the single element at 
374    *  position $from to position $to.
375    */
376   function move_single_element($from,$to)
377   {
378     if($from == $to) {
379       return;
380     }
382     $ret = array();
383     $tmp = $this->pap;
385     $begin = array();
386     $middle = array();
387     $end = array();
388     $element = $this->pap[$from];
390     if($from > $to ){
392       /* Get all element in fron to element to move */    
393       if($from  != 0){
394         $begin = array_slice($tmp,0,$to);
395       }
397       /* Get all elements between */
398       $middle = array_slice($tmp,$to , ($from - ($to) ));  
399     
400       /* Get the rest */ 
401       $end  = array_slice($tmp,$from+1);
402  
403       foreach($begin as $data){
404         $ret[] = $data;
405       }
406       $ret[] = $element;
407       foreach($middle as $data){
408         $ret[] = $data;
409       }
410       foreach($end as $data){
411         $ret[] = $data;
412       }
413       $this->pap = $ret;
414     }
415     if($from < $to ){
417       /* Get all element in fron to element to move */    
418       if($from  != 0){
419         $begin = array_slice($tmp,0,$from);
420       }
422       /* Get all elements between */
423       $middle = array_slice($tmp,$from+1 , ($to - ($from)));  
424     
425       /* Get the rest */ 
426       $end  = array_slice($tmp,$to+1);
427  
428       foreach($begin as $data){
429         $ret[] = $data;
430       }
431       foreach($middle as $data){
432         $ret[] = $data;
433       }
434       $ret[] = $element;
435       foreach($end as $data){
436         $ret[] = $data;
437       }
438       $this->pap = $ret;
439     }
440   }
443   /* Returns the next free position where we 
444    *  can add a new sinle element 
445    *    $key_id     = Current position
446    *    $direction  = Forward or backward.
447    */
448   function _get_next_free_move_slot($key_id,$direction,$include_self = FALSE)
449   {
450     $last_class = "";
451     $current_class ="";
452     $next_class = "";
454     /* After this elements we can add new elements 
455      *  without having any trouble.
456      */
457     $allowed_to_add_after = array("sieve_keep",
458                                   "sieve_require", 
459                                   "sieve_stop", 
460                                   "sieve_reject", 
461                                   "sieve_fileinto", 
462                                   "sieve_redirect", 
463                                   "sieve_discard",
464                                   "sieve_comment",
465                                   "sieve_block_start"
466                                  );
468     /* Before this elements we can add new elements 
469      *  without having any trouble.
470      */
471     $allowed_to_add_before = array("sieve_keep",
472                                   "sieve_require", 
473                                   "sieve_stop", 
474                                   "sieve_reject", 
475                                   "sieve_fileinto", 
476                                   "sieve_comment",
477                                   "sieve_redirect", 
478                                   "sieve_discard",
479                                   "sieve_if", 
480                                   "sieve_block_end"
481                                  );
483     if($direction == "down"){
484     
485       $test = $this->pap;
486       while($key_id < count($test)){
487         if(($key_id+1) == count($test)) {
488           return($key_id);
489         }
491         if(!$include_self){
492           $key_id ++;
493         }
494         $current_class  = get_class($test[$key_id]);
495         if(in_array($current_class, $allowed_to_add_after)){
496           return($key_id);
497         } 
498       } 
499     }else{
500   
501       $test = $this->pap;
502       if($key_id == 0) {
503         return($key_id);
504       }
505       if(!$include_self){
506         $key_id --;
507       }
508       while($key_id >=0 ){
509         $current_class  = get_class($test[$key_id]);
510         if(in_array($current_class, $allowed_to_add_before)){
511           return($key_id);
512         } 
513         $key_id --;
514       }
515       return(0);
516     }
517   }
520   /* Need to be reviewed */
521   function get_sieve_script()
522   {
523     $tmp ="";
524     if(count($this->pap)){
525       $buffer = "";    
526       foreach($this->pap as $part)  {
527         if(get_class($part) == "sieve_block_end"){
528           $buffer = substr($buffer,0,strlen($buffer)-(strlen(SIEVE_INDENT_TAB)));
529         }
530         $tmp2 = $part->get_sieve_script_part();
532         if(get_class($part) == "sieve_reject"){
533           $tmp.=$tmp2;
534         }else{
536           $tmp3 = split("\n",$tmp2);
537           foreach($tmp3 as $str){
538             $str2 = trim($str);
539             if(empty($str2)) continue;
540             $tmp.= $buffer.$str."\n";
541           }
542         }
543         if(get_class($part) == "sieve_block_start"){
544           $buffer .= SIEVE_INDENT_TAB;
545         }
546       }
547     }
548     if(!preg_match("/Generated by GOsa - Gonicus System Administrator/",$tmp)){
549       $tmp = "#Generated by GOsa - Gonicus System Administrator \n ".$tmp;
550     }
551     return($tmp);
552   }
554   function check()
555   {
556                 $msgs = array();
558     /* Some logical checks. 
559      *  like :  only sieve_comment can appear before require.
560      */
561     
562     /* Ensure that there are no command before require 
563      *  - Get id of last require tag
564      *  - Collect object types in from of this tag. 
565      *  - Check if there are tags collected that are not allowed 
566      */
567     $last_found_at = -1; 
568     $objs = array();
569     foreach($this->pap as $key => $obj){
570       if(get_class($obj) == "sieve_require"){
571         $last_found_at = $key;
572       }
573     }
574     foreach($this->pap as $key => $obj){
575       if($key == $last_found_at) break;  
576       if(!in_array(get_class($obj),array("sieve_comment","sieve_require"))){
577         $objs[] = get_class($obj);
578       }
579     }
580     if(count($objs) && $last_found_at != -1){
581       $str = _("Require must be the first command in the script.");  
582       $msgs[] = $str;
583       print_red($str);;
584     }
585     
586                 foreach($this->pap as $obj){
587                         $o_msgs = $obj->check();
588                         foreach($o_msgs as $o_msg){
589                                 $msgs[] = $o_msg;
590                         }
591                 }
592                 return($msgs);
593   }
596   /* We are forced to add a new require.
597    *  This function is called by the 
598    *  sieveElement_Classes->parent->add_require()  
599    */ 
600   function add_require($str)
601   {
602     $require_id = -1;
603     foreach($this->pap as $key => $obj){
604       if(get_class($obj) == "sieve_require"){
605         $require_id = $key;
606       }
607     }
608   
609     /* No require found, add one */
610     if($require_id == -1){
611       $require = new sieve_require(NULL,preg_replace("/[^0-9]/","",microtime()),$this);
612       $require -> Add_Require($str);
613       $new = array();
614       $new[] = $require;
615       foreach($this->pap as $obj){
616         $new[] = $obj;
617       }
618       $this->pap = $new;
619     } else { 
620       $this->pap[$require_id]->Add_Require($str);
621     } 
622   }
626 /* Create valid sieve string/string-list 
627  *  out of a given array
628  */
629 function sieve_create_strings($data,$force_string = FALSE)
631   $ret = "";
632   if(is_array($data)){
633     if(count($data) == 1){
634       $ret = "\"";
635       foreach($data as $dat){
636         $ret .=$dat;
637       }
638       $ret.="\"";
639     }else{
640       foreach($data as $dat){
641         $ret.= "\"";
642         $ret.=$dat;
643         $ret.="\", ";
644       }
645       $ret = preg_replace("/,$/","",trim($ret));
646       $ret = "[".$ret."]";
647     }
648   }else{
650     $Multiline = preg_match("/\n/",$data);
651     $data = preg_replace("/\r/","",$data);;
653     if($Multiline && !$force_string){
654       $ret = "text: \r\n".$data."\r\n.\r\n";
655     }else{
656       $ret = "\"".$data."\"";
657     }
658   }
659   $ret = preg_replace("/\"\"/","\"",$ret);
660   $ret = preg_replace("/\n/","\r\n",$ret);
661   
662   return($ret);
665 /* This checks if there is a string at the current position 
666  *  in the token array. 
667  * If there is a string list at the current position,
668  *  this function will return a complete list of all
669  *  strings used in this list.
670  * It also returns an offset of the last token position 
671  */
672 function sieve_get_strings($data,$id)
674   $ret = array();
675   if($data[$id]['class'] == "left-bracket"){
676     while($data[$id]['class']  != "right-bracket" && $id < count($data)){
677       
678       if($data[$id]['class'] == "quoted-string"){
679         $ret[] = $data[$id]['text'];
680       }
681       $id ++;
682     }
683   }elseif($data[$id]['class'] == "quoted-string"){
684     $ret[] = $data[$id]['text'];
685   }elseif($data[$id]['class'] == "number"){
686     $ret[] = $data[$id]['text'];
687   }elseif($data[$id]['class'] == "multi-line"){
688     $str = trim(preg_replace("/^text:/","",$data[$id]['text']));
689     $str = trim(preg_replace("/\.$/","",$str));
690     $ret[] = $str;
691   }
692   
693   return(array("OFFSET" => $id, "STRINGS" => $ret));
696 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
697 ?>