Code

224a8e38abff0c04402ddd060477d7b8146210f1
[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();
19   var $add_new          = FALSE;
20   var $add_new_id       = 0;
21   var $add_type         = "top";
22   var $add_element_type = "";
23   
24   var $Mode = "Structured";
26   function execute()
27   {
28     foreach($_POST as $name => $value){
29       if(preg_match("/Add_Test_Object_/",$name)) {
30         $name = preg_replace("/Add_Test_Object_/","",$name);
31         $name = preg_replace("/_(x|y)$/","",$name);
32     
33         $test_types_to_add = array(
34           "address" =>_("Address"),
35           "header"  =>_("Header"),
36           "envelope"=>_("Envelope"),
37           "size"    =>_("Size"),
38           "exists"  =>_("Exists"),
39           "allof"   =>_("All of"),
40           "anyof"   =>_("Any of"),
41           "true"    =>_("True"),
42           "false"   =>_("False"));
43  
44         $smarty = get_smarty();
45         $smarty->assign("ID",$name); 
46         $smarty->assign("test_types_to_add",$test_types_to_add); 
47         $ret = $smarty->fetch(get_template_path("templates/select_test_type.tpl",TRUE,dirname(__FILE__)));
48         return($ret);
49       }
50     }
52     return($this->dump());
53   }
55   /* Create a html interface for the current sieve filter 
56    */
57   function dump()
58   {
59     error_reporting(E_ALL);
61     /**************
62      * Handle new elements 
63      **************/
65     if(isset($_POST['select_new_element_type_cancel'])){
66       $this->add_new = FALSE;
67     }
69     if($this->add_new){
70   
71       $element_types= array(
72           "sieve_keep"      => _("Keep"),
73           "sieve_comment"   => _("Comment"),
74           "sieve_fileinto"  => _("File into"),
75           "sieve_keep"      => _("Keep"),
76           "sieve_discard"   => _("Discard"),
77           "sieve_redirect"  => _("Redirect"),
78           "sieve_reject"    => _("Reject"),
79           "sieve_require"   => _("Require"),
80           "sieve_stop"      => _("Stop"),
81           "sieve_vacation"  => _("Vacation message"),
82           "sieve_if"        => _("If"));
85       /* Element selected */
86       if(isset($_POST['element_type']) && isset($element_types[$_POST['element_type']])){
87         $this->add_element_type = $_POST['element_type'];
88       }
90       /* Create new element and add it at the selected position */
91       if(isset($_POST['select_new_element_type'])){
93         $ele[] = new $this->add_element_type(NULL, preg_replace("/[^0-9]/","",microtime()));
94         if($this->add_element_type == "sieve_if"){
95           $ele[] = new sieve_block_start(NULL,preg_replace("/[^0-9]/","",microtime()));
96           $ele[] = new sieve_block_end(NULL,preg_replace("/[^0-9]/","",microtime()));
97         }
98         $start = $end = array();
99         $found = false;  
101         /* Add above current element*/
102         if($this->add_type == "top"){
103           foreach($this->pap as $key => $obj){
104             if($obj->object_id == $this->add_new_id){
105               $found = true;
106             }
107             if(!$found){
108               $start[] = $obj;
109             }else{
110               $end[] = $obj;
111             }
112           }    
113         }else{
114         /* Add below current element */
115           foreach($this->pap as $key => $obj){
116             if(!$found){
117               $start[] = $obj;
118             }else{
119               $end[] = $obj;
120             }
121             if($obj->object_id == $this->add_new_id){
122               $found = true;
123             }
124           }    
125         }
127         /* Only add, if current element could be located */
128         if($found){
129           $new = array();
130           foreach($start as $obj){
131             $new[] = $obj;
132           }      
133           foreach($ele as $el){
134             $new[] = $el;
135           }
136           foreach($end as $obj){
137             $new[] = $obj;
138           }
139           $this->pap = $new;
140           $this->add_new = FALSE;
141         }else{
142           print_red(_("Something went wrong while adding a new entry."));
143         }
144       }
146     }
148     /* Only display select dialog if it is necessary */
149     if($this->add_new){  
150       $smarty = get_smarty();
151       $smarty->assign("element_types",$element_types );
152       $smarty->assign("element_type",$this->add_element_type);
153       $str = $smarty->fetch(get_template_path("templates/add_element.tpl",TRUE,dirname(__FILE__)));
154       return($str);
155     }
157     /* Only parse the tokens once */
158     if(!count($this->pap)){
159       $this->dump_ = "";
160       $this->mode_stack = array();
161       $this->pap = array();
162       $this->doDump_(0, '', true);
163     }
165     /* Create html results */
166     $smarty = get_smarty();
168     $this -> dump_ = "";
169     foreach($this->pap as $key => $object){
170       if(is_object($object)){
171         $this->dump_ .= preg_replace("/>/",">\n",$object->execute()); 
172       }
173     }
174     
175     return($this->dump_);
176   }
179   /* This function walks through the object tree generated by the "Parse" class.
180    * All Commands will be resolved and grouped. So the Commands and their 
181    *  parameter are combined. Like "IF" and ":comparator"...
182    */  
183   function doDump_($node_id, $prefix, $last,$num = 1)
184   {
185     /* Indicates that current comman will only be valid for a single line. 
186      *  this command type will be removed from mode_stack after displaying it.
187      */
188     $rewoke_last = FALSE;
190     /* Get node */ 
191     $node = $this->nodes_[$node_id];
193     /* This closes the last mode */
194     if($node['class'] == "block-start"){
195       $tmp = array_pop($this->mode_stack);
196       $this->handle_elements($tmp,$node_id);
197       $this->handle_elements(array("TYPE" => "block_start"),$node_id);
198     }
200     /* This closes the last mode */
201     if($node['class'] == "block-end"){
202       $tmp = array_pop($this->mode_stack);
203       $this->handle_elements($tmp,$node_id);
204       $this->handle_elements(array("TYPE" => "block_end"),$node_id);
205     }
207     /* Semicolon indicates a new command */
208     if($node['class'] == "semicolon"){
209       $tmp =array_pop($this->mode_stack);
210       $this->handle_elements($tmp,$node_id);
211     }
213     /* Handle comments */
214     if($node['class'] == "comment"){
215       $this->mode_stack[] = array("TYPE" => $node['class']);
216       $rewoke_last = TRUE;
217     }
219     /* Handle identifiers */
220     $identifiers = array("else","if","elsif","end","reject","redirect","vacation","keep","discard","comment","fileinto","require","stop");
221     if($node['class'] == "identifier" && in_array($node['text'],$identifiers)){
222       $this->mode_stack[] = array("TYPE" => $node['text']); 
223     }
225     /* Add current node to current command stack */
226     end($this->mode_stack);
227     $key = key($this->mode_stack);
228     $this->mode_stack[$key]['ELEMENTS'][] = $node;
230     /* Remove last mode from mode stack, cause it was only valid for a single line */
231     if($rewoke_last){
232       $tmp =array_pop($this->mode_stack);
233       $this->handle_elements($tmp,$node_id);
234     }
236     /* If this is a sub element, just call this for all childs */       
237     if(isset($this->childs_[$node_id])){
238       $childs = $this->childs_[$node_id];
239       for ($i=0; $i<count($childs); ++$i)
240       {
241         $c_last = false;
242         if ($i+1 == count($childs))
243         {
244           $c_last = true;
245         }
246         $this->doDump_($childs[$i], "", $num);
247       }
248     }
249   }
252   /* Create a class for each resolved object.
253    * And append this class to a list of objects.
254    */
255   function handle_elements($data,$id)
256   {
257     if(!isset($data['TYPE'])){
258       return;
259     }
260     $type = $data['TYPE'];
261     
262     $class_name= "sieve_".$type ;
263     if(class_exists($class_name)){
264       $this->pap[] = new $class_name($data,$id);
265     }else{
266       echo "<font color='red'>Missing : ".$class_name."</font>"."<br>";
267     }
268   }
270   function save_object()
271   {
272     reset($this->pap);
273     foreach($this->pap as $key => $obj){
275       if(in_array(get_class($obj),array("sieve_if",
276                                         "sieve_elsif",
277                                         "sieve_vacation",
278                                         "sieve_comment",
279                                         "sieve_reject",
280                                         "sieve_fileinto",
281                                         "sieve_require",
282                                         "sieve_redirect"))){
285         if(isset($this->pap[$key]) && method_exists($this->pap[$key],"save_object")){
286           $this->pap[$key]->save_object();
287         }
288       }
290       $once = TRUE;
291       foreach($_POST as $name => $value){
293         if(isset($obj->object_id) && preg_match("/^Remove_Object_".$obj->object_id."_/",$name) && $once){
294           $once = FALSE;
295           $this->remove_object($key);
296         }
297         if(isset($obj->object_id) && preg_match("/^Move_Up_Object_".$obj->object_id."_/",$name) && $once){
298           $this->move_up_down($key,"up");
299           $once = FALSE;
300         }
301         if(isset($obj->object_id) && preg_match("/^Move_Down_Object_".$obj->object_id."_/",$name) && $once){
302           $this->move_up_down($key,"down");
303           $once = FALSE;
304         }
305         if(isset($obj->object_id) && preg_match("/^Add_Object_Top_".$obj->object_id."_/",$name) && $once){
306           $this->add_new_object($obj->object_id,"top");
307           $once = FALSE;
308         }
309         if(isset($obj->object_id) && preg_match("/^Add_Object_Bottom_".$obj->object_id."_/",$name) && $once){
310           $this->add_new_object($obj->object_id,"bottom");
311           $once = FALSE;
312         }
313       }
314     }
315   }
318   /* Add a new object at the given position */
319   function add_new_object($id,$top_bottom = "bottom")
320   {
321     $this->add_new    = TRUE;
322     $this->add_new_id = $id;
323     $this->add_type   = $top_bottom;
324   }
327   /* Remove the object at the given position */
328   function remove_object($key_id)
329   {
330     $class = get_class($this->pap[$key_id]);
331     if(in_array($class,array("sieve_if"))){
332       $block_start= $key_id;
333       $block_end  = $this->get_block_end($key_id);
334       for($i = $block_start ; $i <= $block_end ; $i ++ ){
335         unset($this->pap[$i]);
336       }
337     }else{
338       unset($this->pap[$key_id]);
339     }
340     $tmp = array();
341     foreach($this->pap as $element){
342       $tmp[] = $element;
343     }
344     $this->pap = $tmp;
345   }
348   /* This function moves a given element to another position.
349    * Single elements like "keep;" will simply be moved one posisition down/up.
350    * Multiple elements like if-elsif-else will be moved as block. 
351    * 
352    *  $key_id     specified the element that should be moved.
353    *  $direction  specifies to move elements "up" or "down"
354    */
355   function move_up_down($key_id,$direction = "down")
356   {
357      
358     /* Get the current element to decide what to move. */ 
359     $e_class = get_class($this->pap[$key_id]);
360       
361     if(in_array($e_class,array("sieve_if"))){
362       $block_start= $key_id;
363       $block_end  = $this->get_block_end($key_id);
365       /* Depending on the direction move up down */
366       if($direction == "down"){
367         $next_free  = $this->_get_next_free_move_slot($block_end,$direction); 
368       }else{
369         $next_free  = $this->_get_next_free_move_slot($block_start,$direction); 
370       }
372       /* Move the given block */ 
373       $this->move_multiple_elements($block_start,$block_end,$next_free);
374     }
376     if(in_array($e_class,array( "sieve_stop",
377                                 "sieve_keep",
378                                 "sieve_require",
379                                 "sieve_comment",
380                                 "sieve_vacation",
381                                 "sieve_stop",   
382                                 "sieve_reject", 
383                                 "sieve_fileinto",
384                                 "sieve_redirect", 
385                                 "sieve_discard"))){
386       $this->move_single_element($key_id,$this->_get_next_free_move_slot($key_id,$direction));
387     }
388   }
390   
391   /* Move the given block to position */
392   function move_multiple_elements($start,$end,$to)
393   {
394     /* Use class names for testing */
395     $data = $this->pap;
397     /* Get block to move */
398     $block_to_move = array_slice($data,$start, ($end - $start +1));
400     /* We want do move this block up */
401     if($end > $to){
402       
403       /* Get start block */
404       $start_block = array_slice($data,0,$to);
406       /* Get Get all elements between the block to move 
407        *  and next free position 
408        */
409       $block_to_free = array_slice($data,$to ,$start - $to );  
410       $block_to_end = array_slice($data,$end+1);
411       $new = array();
412       foreach($start_block as $block){
413         $new[] = $block;
414       }
415       foreach($block_to_move as $block){
416         $new[] = $block;
417       }
418       foreach($block_to_free as $block){
419         $new[] = $block;
420       }
421       foreach($block_to_end as $block){
422         $new[] = $block;
423       }
424       $old = $this->pap;
425       $this->pap = $new;
426     }
427     
429     /* We want to move this block down. */
430     if($to > $end){
432       /* Get start block */
433       $start_block = array_slice($data,0,$start);
435       /* Get Get all elements between the block to move 
436        *  and next free position 
437        */
438       $block_to_free = array_slice($data,$end +1,($to - $end  ));  
440       /* Get the rest 
441        */
442       $block_to_end = array_slice($data,$to+1);
444       $new = array();
445       foreach($start_block as $block){
446         $new[] = $block;
447       }
448       foreach($block_to_free as $block){
449         $new[] = $block;
450       }
451       foreach($block_to_move as $block){
452         $new[] = $block;
453       }
454       foreach($block_to_end as $block){
455         $new[] = $block;
456       }
457       $old = $this->pap;
458       $this->pap = $new;
459     }
460   }  
462   
463   /* This function returns the id of the element 
464    *  where the current block ends  
465    */
466   function get_block_end($start)
467   {
468     /* Only execute if this is a really a block element. 
469      * Block elements is only sieve_if
470      */
471     if(in_array(get_class($this->pap[$start]),array("sieve_if"))){
473       $class      = get_class($this->pap[$start]);
474       $next_class = get_class($this->pap[$start+1]);
475       $block_depth = 0;
477       $end = FALSE;
479       while(!$end && $start < count($this->pap)){
480  
481         if($class == "sieve_block_start"){
482           $block_depth ++;
483         }
485         if($class == "sieve_block_end"){
486           $block_depth --;
487         }
489         if( $block_depth == 0 && 
490             $class == "sieve_block_end" && 
491             !in_array($next_class,array("sieve_else","sieve_elsif"))){
492           $end = TRUE;
493           $start --;
494         }
495         $start ++;       
496         $class      = get_class($this->pap[$start]);
497         $next_class = get_class($this->pap[$start+1]);
498       }
499     }
500     return($start);
501   }
504   /* This function moves the single element at 
505    *  position $from to position $to.
506    */
507   function move_single_element($from,$to)
508   {
509     if($from == $to) {
510       return;
511     }
513     $ret = array();
514     $tmp = $this->pap;
516     $begin = array();
517     $middle = array();
518     $end = array();
519     $element = $this->pap[$from];
521     if($from > $to ){
523       /* Get all element in fron to element to move */    
524       if($from  != 0){
525         $begin = array_slice($tmp,0,$to);
526       }
528       /* Get all elements between */
529       $middle = array_slice($tmp,$to , ($from - ($to) ));  
530     
531       /* Get the rest */ 
532       $end  = array_slice($tmp,$from+1);
533  
534       foreach($begin as $data){
535         $ret[] = $data;
536       }
537       $ret[] = $element;
538       foreach($middle as $data){
539         $ret[] = $data;
540       }
541       foreach($end as $data){
542         $ret[] = $data;
543       }
544       $this->pap = $ret;
545     }
546     if($from < $to ){
548       /* Get all element in fron to element to move */    
549       if($from  != 0){
550         $begin = array_slice($tmp,0,$from);
551       }
553       /* Get all elements between */
554       $middle = array_slice($tmp,$from+1 , ($to - ($from)));  
555     
556       /* Get the rest */ 
557       $end  = array_slice($tmp,$to+1);
558  
559       foreach($begin as $data){
560         $ret[] = $data;
561       }
562       foreach($middle as $data){
563         $ret[] = $data;
564       }
565       $ret[] = $element;
566       foreach($end as $data){
567         $ret[] = $data;
568       }
569       $this->pap = $ret;
570     }
571   }
574   /* Returns the next free position where we 
575    *  can add a new sinle element 
576    *    $key_id     = Current position
577    *    $direction  = Forward or backward.
578    */
579   function _get_next_free_move_slot($key_id,$direction)
580   {
581     $last_class = "";
582     $current_class ="";
583     $next_class = "";
585     /* After this elements we can add new elements 
586      *  without having any trouble.
587      */
588     $allowed_to_add_after = array("sieve_keep",
589                                   "sieve_require", 
590                                   "sieve_stop", 
591                                   "sieve_reject", 
592                                   "sieve_fileinto", 
593                                   "sieve_redirect", 
594                                   "sieve_discard",
595                                   "sieve_comment",
596                                   "sieve_block_start"
597                                  );
599     /* Before this elements we can add new elements 
600      *  without having any trouble.
601      */
602     $allowed_to_add_before = array("sieve_keep",
603                                   "sieve_require", 
604                                   "sieve_stop", 
605                                   "sieve_reject", 
606                                   "sieve_fileinto", 
607                                   "sieve_comment",
608                                   "sieve_redirect", 
609                                   "sieve_discard",
610                                   "sieve_if", 
611                                   "sieve_block_end"
612                                  );
614     if($direction == "down"){
615     
616       $test = $this->pap;
617       while($key_id < count($test)){
618         if(($key_id+1) == count($test)) {
619           return($key_id);
620         }
621         $key_id ++;
622         $current_class  = get_class($test[$key_id]);
623         if(in_array($current_class, $allowed_to_add_after)){
624           return($key_id);
625         } 
626       } 
627     }else{
628   
629       $test = $this->pap;
630       if($key_id == 0) {
631         return($key_id);
632       }
633       $key_id --;
634       while($key_id >=0 ){
635         $current_class  = get_class($test[$key_id]);
636         if(in_array($current_class, $allowed_to_add_before)){
637           return($key_id);
638         } 
639         $key_id --;
640       }
641       return(0);
642     }
643   }
646   /* Need to be reviewed */
647   function get_sieve_script()
648   {
649     $tmp ="";
650     if(count($this->pap)){
651       $buffer = "";    
652       foreach($this->pap as $part)  {
653         if(get_class($part) == "sieve_block_end"){
654           $buffer = substr($buffer,0,strlen($buffer)-(strlen(SIEVE_INDENT_TAB)));
655         }
656         $tmp2 = $part->get_sieve_script_part();
658         if(get_class($part) == "sieve_reject"){
659           $tmp.=$tmp2;
660         }else{
662           $tmp3 = split("\n",$tmp2);
663           foreach($tmp3 as $str){
664             $str2 = trim($str);
665             if(empty($str2)) continue;
666             $tmp.= $buffer.$str."\n";
667           }
668         }
669         if(get_class($part) == "sieve_block_start"){
670           $buffer .= SIEVE_INDENT_TAB;
671         }
672       }
673     }
674     if(!preg_match("/Generated by GOsa - Gonicus System Administrator/",$tmp)){
675       $tmp = "#Generated by GOsa - Gonicus System Administrator \n ".$tmp;
676     }
677     return($tmp);
678   }
680   function Add_Element()
681   {
682     $tmp = array("ELEMENTS" => array(array("class" => "qouted-string","text"=> "Bla bla, later more")));
683     $this->pap[] = new sieve_comment($tmp,rand(1000,100000));
684   }
686   function check()
687   {
688                 $msgs = array();
690     /* Some logical checks. 
691      *  like :  only sieve_comment can appear before require.
692      */
693     
694     /* Ensure that there are no command before require 
695      *  - Get id of last require tag
696      *  - Collect object types in from of this tag. 
697      *  - Check if there are tags collected that are not allowed 
698      */
699     $last_found_at = -1; 
700     $objs = array();
701     foreach($this->pap as $key => $obj){
702       if(get_class($obj) == "sieve_require"){
703         $last_found_at = $key;
704       }
705     }
706     foreach($this->pap as $key => $obj){
707       if($key == $last_found_at) break;  
708       if(!in_array(get_class($obj),array("sieve_comment","sieve_require"))){
709         $objs[] = get_class($obj);
710       }
711     }
712     if(count($objs) && $last_found_at != -1){
713       $str = _("Require must be the first command in the script.");  
714       $msgs[] = $str;
715       print_red($str);;
716     }
717     
718                 foreach($this->pap as $obj){
719                         $o_msgs = $obj->check();
720                         foreach($o_msgs as $o_msg){
721                                 $msgs[] = $o_msg;
722                         }
723                 }
724                 return($msgs);
725   }
729 /* Create valid sieve string/string-list 
730  *  out of a given array
731  */
732 function sieve_create_strings($data,$force_string = FALSE)
734   $ret = "";
735   if(is_array($data)){
736     if(count($data) == 1){
737       $ret = "\"";
738       foreach($data as $dat){
739         $ret .=$dat;
740       }
741       $ret.="\"";
742     }else{
743       foreach($data as $dat){
744         $ret.= "\"";
745         $ret.=$dat;
746         $ret.="\", ";
747       }
748       $ret = preg_replace("/,$/","",trim($ret));
749       $ret = "[".$ret."]";
750     }
751   }else{
753     $Multiline = preg_match("/\n/",$data);
754     $data = preg_replace("/\r/","",$data);;
756     if($Multiline && !$force_string){
757       $ret = "text: \r\n".$data."\r\n.\r\n";
758     }else{
759       $ret = "\"".$data."\"";
760     }
761   }
762   $ret = preg_replace("/\"\"/","\"",$ret);
763   $ret = preg_replace("/\n/","\r\n",$ret);
764   
765   return($ret);
768 /* This checks if there is a string at the current position 
769  *  in the token array. 
770  * If there is a string list at the current position,
771  *  this function will return a complete list of all
772  *  strings used in this list.
773  * It also returns an offset of the last token position 
774  */
775 function sieve_get_strings($data,$id)
777   $ret = array();
778   if($data[$id]['class'] == "left-bracket"){
779     while($data[$id]['class']  != "right-bracket" && $id < count($data)){
780       
781       if($data[$id]['class'] == "quoted-string"){
782         $ret[] = $data[$id]['text'];
783       }
784       $id ++;
785     }
786   }elseif($data[$id]['class'] == "quoted-string"){
787     $ret[] = $data[$id]['text'];
788   }elseif($data[$id]['class'] == "number"){
789     $ret[] = $data[$id]['text'];
790   }elseif($data[$id]['class'] == "multi-line"){
791     $str = trim(preg_replace("/^text:/","",$data[$id]['text']));
792     $str = trim(preg_replace("/\.$/","",$str));
793     $ret[] = $str;
794   }
795   
796   return(array("OFFSET" => $id, "STRINGS" => $ret));
799 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
800 ?>