Code

Some 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();
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     /* Create html results */
176     $smarty = get_smarty();
177     $smarty->assign("Mode",$this->Mode);
178     if($this->Mode == "Structured"){
179       $smarty->assign("Contents",$this->dump_);
180     }else{
181       if(isset($_POST['script_contents'])){
182         $smarty->assign("Contents",stripslashes($_POST['script_contents']));
183       }else{
184         $smarty->assign("Contents",$this->get_sieve_script());
185       }
186     }
187     $ret = $smarty->fetch(get_template_path("templates/edit_frame_base.tpl",TRUE,dirname(__FILE__)));
188     return ($ret);
189   }
192   /* This function walks through the object tree generated by the "Parse" class.
193    * All Commands will be resolved and grouped. So the Commands and their 
194    *  parameter are combined. Like "IF" and ":comparator"...
195    */  
196   function doDump_($node_id, $prefix, $last,$num = 1)
197   {
198     /* Indicates that current comman will only be valid for a single line. 
199      *  this command type will be removed from mode_stack after displaying it.
200      */
201     $rewoke_last = FALSE;
203     /* Get node */ 
204     $node = $this->nodes_[$node_id];
206     /* This closes the last mode */
207     if($node['class'] == "block-start"){
208       $tmp = array_pop($this->mode_stack);
209       $this->handle_elements($tmp,$node_id);
210       $this->handle_elements(array("TYPE" => "block_start"),$node_id);
211     }
213     /* This closes the last mode */
214     if($node['class'] == "block-end"){
215       $tmp = array_pop($this->mode_stack);
216       $this->handle_elements($tmp,$node_id);
217       $this->handle_elements(array("TYPE" => "block_end"),$node_id);
218     }
220     /* Semicolon indicates a new command */
221     if($node['class'] == "semicolon"){
222       $tmp =array_pop($this->mode_stack);
223       $this->handle_elements($tmp,$node_id);
224     }
226     /* Handle comments */
227     if($node['class'] == "comment"){
228       $this->mode_stack[] = array("TYPE" => $node['class']);
229       $rewoke_last = TRUE;
230     }
232     /* Handle identifiers */
233     $identifiers = array("else","if","elsif","end","reject","redirect","vacation","keep","discard","comment","fileinto","require","stop");
234     if($node['class'] == "identifier" && in_array($node['text'],$identifiers)){
235       $this->mode_stack[] = array("TYPE" => $node['text']); 
236     }
238     /* Add current node to current command stack */
239     end($this->mode_stack);
240     $key = key($this->mode_stack);
241     $this->mode_stack[$key]['ELEMENTS'][] = $node;
243     /* Remove last mode from mode stack, cause it was only valid for a single line */
244     if($rewoke_last){
245       $tmp =array_pop($this->mode_stack);
246       $this->handle_elements($tmp,$node_id);
247     }
249     /* If this is a sub element, just call this for all childs */       
250     if(isset($this->childs_[$node_id])){
251       $childs = $this->childs_[$node_id];
252       for ($i=0; $i<count($childs); ++$i)
253       {
254         $c_last = false;
255         if ($i+1 == count($childs))
256         {
257           $c_last = true;
258         }
259         $this->doDump_($childs[$i], "", $num);
260       }
261     }
262   }
265   /* Create a class for each resolved object.
266    * And append this class to a list of objects.
267    */
268   function handle_elements($data,$id)
269   {
270     if(!isset($data['TYPE'])){
271       return;
272     }
273     $type = $data['TYPE'];
274     
275     $class_name= "sieve_".$type ;
276     if(class_exists($class_name)){
277       $this->pap[] = new $class_name($data,$id);
278     }else{
279       echo "<font color='red'>Missing : ".$class_name."</font>"."<br>";
280     }
281   }
283   function save_object()
284   {
285     reset($this->pap);
286     foreach($this->pap as $key => $obj){
288       if(in_array(get_class($obj),array("sieve_if",
289                                         "sieve_elsif",
290                                         "sieve_vacation",
291                                         "sieve_comment",
292                                         "sieve_reject",
293                                         "sieve_fileinto",
294                                         "sieve_require",
295                                         "sieve_redirect"))){
298         if(isset($this->pap[$key]) && method_exists($this->pap[$key],"save_object")){
299           $this->pap[$key]->save_object();
300         }
301       }
303       $once = TRUE;
304       foreach($_POST as $name => $value){
306         if(isset($obj->object_id) && preg_match("/^Remove_Object_".$obj->object_id."_/",$name) && $once){
307           $once = FALSE;
308           $this->remove_object($key);
309         }
310         if(isset($obj->object_id) && preg_match("/^Move_Up_Object_".$obj->object_id."_/",$name) && $once){
311           $this->move_up_down($key,"up");
312           $once = FALSE;
313         }
314         if(isset($obj->object_id) && preg_match("/^Move_Down_Object_".$obj->object_id."_/",$name) && $once){
315           $this->move_up_down($key,"down");
316           $once = FALSE;
317         }
318         if(isset($obj->object_id) && preg_match("/^Add_Object_Top_".$obj->object_id."_/",$name) && $once){
319           $this->add_new_object($obj->object_id,"top");
320           $once = FALSE;
321         }
322         if(isset($obj->object_id) && preg_match("/^Add_Object_Bottom_".$obj->object_id."_/",$name) && $once){
323           $this->add_new_object($obj->object_id,"bottom");
324           $once = FALSE;
325         }
326       }
327     }
328   }
331   /* Add a new object at the given position */
332   function add_new_object($id,$top_bottom = "bottom")
333   {
334     $this->add_new    = TRUE;
335     $this->add_new_id = $id;
336     $this->add_type   = $top_bottom;
337   }
340   /* Remove the object at the given position */
341   function remove_object($key_id)
342   {
343     $class = get_class($this->pap[$key_id]);
344     if(in_array($class,array("sieve_if"))){
345       $block_start= $key_id;
346       $block_end  = $this->get_block_end($key_id);
347       for($i = $block_start ; $i <= $block_end ; $i ++ ){
348         unset($this->pap[$i]);
349       }
350     }else{
351       unset($this->pap[$key_id]);
352     }
353     $tmp = array();
354     foreach($this->pap as $element){
355       $tmp[] = $element;
356     }
357     $this->pap = $tmp;
358   }
361   /* This function moves a given element to another position.
362    * Single elements like "keep;" will simply be moved one posisition down/up.
363    * Multiple elements like if-elsif-else will be moved as block. 
364    * 
365    *  $key_id     specified the element that should be moved.
366    *  $direction  specifies to move elements "up" or "down"
367    */
368   function move_up_down($key_id,$direction = "down")
369   {
370      
371     /* Get the current element to decide what to move. */ 
372     $e_class = get_class($this->pap[$key_id]);
373       
374     if(in_array($e_class,array("sieve_if"))){
375       $block_start= $key_id;
376       $block_end  = $this->get_block_end($key_id);
378       /* Depending on the direction move up down */
379       if($direction == "down"){
380         $next_free  = $this->_get_next_free_move_slot($block_end,$direction); 
381       }else{
382         $next_free  = $this->_get_next_free_move_slot($block_start,$direction); 
383       }
385       /* Move the given block */ 
386       $this->move_multiple_elements($block_start,$block_end,$next_free);
387     }
389     if(in_array($e_class,array( "sieve_stop",
390                                 "sieve_keep",
391                                 "sieve_require",
392                                 "sieve_comment",
393                                 "sieve_stop",   
394                                 "sieve_reject", 
395                                 "sieve_fileinto",
396                                 "sieve_redirect", 
397                                 "sieve_discard"))){
398       $this->move_single_element($key_id,$this->_get_next_free_move_slot($key_id,$direction));
399     }
400   }
402   
403   /* Move the given block to position */
404   function move_multiple_elements($start,$end,$to)
405   {
406     /* Use class names for testing */
407     $data = $this->pap;
409     /* Get block to move */
410     $block_to_move = array_slice($data,$start, ($end - $start +1));
412     /* We want do move this block up */
413     if($end > $to){
414       
415       /* Get start block */
416       $start_block = array_slice($data,0,$to);
418       /* Get Get all elements between the block to move 
419        *  and next free position 
420        */
421       $block_to_free = array_slice($data,$to ,$start - $to );  
422       $block_to_end = array_slice($data,$end+1);
423       $new = array();
424       foreach($start_block as $block){
425         $new[] = $block;
426       }
427       foreach($block_to_move as $block){
428         $new[] = $block;
429       }
430       foreach($block_to_free as $block){
431         $new[] = $block;
432       }
433       foreach($block_to_end as $block){
434         $new[] = $block;
435       }
436       $old = $this->pap;
437       $this->pap = $new;
438     }
439     
441     /* We want to move this block down. */
442     if($to > $end){
444       /* Get start block */
445       $start_block = array_slice($data,0,$start);
447       /* Get Get all elements between the block to move 
448        *  and next free position 
449        */
450       $block_to_free = array_slice($data,$end +1,($to - $end  ));  
452       /* Get the rest 
453        */
454       $block_to_end = array_slice($data,$to+1);
456       $new = array();
457       foreach($start_block as $block){
458         $new[] = $block;
459       }
460       foreach($block_to_free as $block){
461         $new[] = $block;
462       }
463       foreach($block_to_move as $block){
464         $new[] = $block;
465       }
466       foreach($block_to_end as $block){
467         $new[] = $block;
468       }
469       $old = $this->pap;
470       $this->pap = $new;
471     }
472   }  
474   
475   /* This function returns the id of the element 
476    *  where the current block ends  
477    */
478   function get_block_end($start)
479   {
480     /* Only execute if this is a really a block element. 
481      * Block elements is only sieve_if
482      */
483     if(in_array(get_class($this->pap[$start]),array("sieve_if"))){
485       $class      = get_class($this->pap[$start]);
486       $next_class = get_class($this->pap[$start+1]);
487       $block_depth = 0;
489       $end = FALSE;
491       while(!$end && $start < count($this->pap)){
492  
493         if($class == "sieve_block_start"){
494           $block_depth ++;
495         }
497         if($class == "sieve_block_end"){
498           $block_depth --;
499         }
501         if( $block_depth == 0 && 
502             $class == "sieve_block_end" && 
503             !in_array($next_class,array("sieve_else","sieve_elsif"))){
504           $end = TRUE;
505           $start --;
506         }
507         $start ++;       
508         $class      = get_class($this->pap[$start]);
509         $next_class = get_class($this->pap[$start+1]);
510       }
511     }
512     return($start);
513   }
516   /* This function moves the single element at 
517    *  position $from to position $to.
518    */
519   function move_single_element($from,$to)
520   {
521     if($from == $to) {
522       return;
523     }
525     $ret = array();
526     $tmp = $this->pap;
528     $begin = array();
529     $middle = array();
530     $end = array();
531     $element = $this->pap[$from];
533     if($from > $to ){
535       /* Get all element in fron to element to move */    
536       if($from  != 0){
537         $begin = array_slice($tmp,0,$to);
538       }
540       /* Get all elements between */
541       $middle = array_slice($tmp,$to , ($from - ($to) ));  
542     
543       /* Get the rest */ 
544       $end  = array_slice($tmp,$from+1);
545  
546       foreach($begin as $data){
547         $ret[] = $data;
548       }
549       $ret[] = $element;
550       foreach($middle as $data){
551         $ret[] = $data;
552       }
553       foreach($end as $data){
554         $ret[] = $data;
555       }
556       $this->pap = $ret;
557     }
558     if($from < $to ){
560       /* Get all element in fron to element to move */    
561       if($from  != 0){
562         $begin = array_slice($tmp,0,$from);
563       }
565       /* Get all elements between */
566       $middle = array_slice($tmp,$from+1 , ($to - ($from)));  
567     
568       /* Get the rest */ 
569       $end  = array_slice($tmp,$to+1);
570  
571       foreach($begin as $data){
572         $ret[] = $data;
573       }
574       foreach($middle as $data){
575         $ret[] = $data;
576       }
577       $ret[] = $element;
578       foreach($end as $data){
579         $ret[] = $data;
580       }
581       $this->pap = $ret;
582     }
583   }
586   /* Returns the next free position where we 
587    *  can add a new sinle element 
588    *    $key_id     = Current position
589    *    $direction  = Forward or backward.
590    */
591   function _get_next_free_move_slot($key_id,$direction)
592   {
593     $last_class = "";
594     $current_class ="";
595     $next_class = "";
597     /* After this elements we can add new elements 
598      *  without having any trouble.
599      */
600     $allowed_to_add_after = array("sieve_keep",
601                                   "sieve_require", 
602                                   "sieve_stop", 
603                                   "sieve_reject", 
604                                   "sieve_fileinto", 
605                                   "sieve_redirect", 
606                                   "sieve_discard",
607                                   "sieve_comment",
608                                   "sieve_block_start"
609                                  );
611     /* Before this elements we can add new elements 
612      *  without having any trouble.
613      */
614     $allowed_to_add_before = array("sieve_keep",
615                                   "sieve_require", 
616                                   "sieve_stop", 
617                                   "sieve_reject", 
618                                   "sieve_fileinto", 
619                                   "sieve_comment",
620                                   "sieve_redirect", 
621                                   "sieve_discard",
622                                   "sieve_if", 
623                                   "sieve_block_end"
624                                  );
626     if($direction == "down"){
627     
628       $test = $this->pap;
629       while($key_id < count($test)){
630         if(($key_id+1) == count($test)) {
631           return($key_id);
632         }
633         $key_id ++;
634         $current_class  = get_class($test[$key_id]);
635         if(in_array($current_class, $allowed_to_add_after)){
636           return($key_id);
637         } 
638       } 
639     }else{
640   
641       $test = $this->pap;
642       if($key_id == 0) {
643         return($key_id);
644       }
645       $key_id --;
646       while($key_id >=0 ){
647         $current_class  = get_class($test[$key_id]);
648         if(in_array($current_class, $allowed_to_add_before)){
649           return($key_id);
650         } 
651         $key_id --;
652       }
653       return(0);
654     }
655   }
658   /* Need to be reviewed */
659   function get_sieve_script()
660   {
661     $tmp ="";
662     if(count($this->pap)){
663       $buffer = "";    
664       foreach($this->pap as $part)  {
665         if(get_class($part) == "sieve_block_end"){
666           $buffer = substr($buffer,0,strlen($buffer)-(strlen(SIEVE_INDENT_TAB)));
667         }
668         $tmp2 = $part->get_sieve_script_part();
670         if(get_class($part) == "sieve_reject"){
671           $tmp.=$tmp2;
672         }else{
674           $tmp3 = split("\n",$tmp2);
675           foreach($tmp3 as $str){
676             $str2 = trim($str);
677             if(empty($str2)) continue;
678             $tmp.= $buffer.$str."\n";
679           }
680         }
681         if(get_class($part) == "sieve_block_start"){
682           $buffer .= SIEVE_INDENT_TAB;
683         }
684       }
685     }
686     if(!preg_match("/Generated by GOsa - Gonicus System Administrator/",$tmp)){
687       $tmp = "#Generated by GOsa - Gonicus System Administrator \n ".$tmp;
688     }
689     return($tmp);
690   }
692   function Add_Element()
693   {
694     $tmp = array("ELEMENTS" => array(array("class" => "qouted-string","text"=> "Bla bla, later more")));
695     $this->pap[] = new sieve_comment($tmp,rand(1000,100000));
696   }
698   function check()
699   {
700                 $msgs = array();
702     /* Some logical checks. 
703      *  like :  only sieve_comment can appear before require.
704      */
705     
706     /* Ensure that there are no command before require 
707      *  - Get id of last require tag
708      *  - Collect object types in from of this tag. 
709      *  - Check if there are tags collected that are not allowed 
710      */
711     $last_found_at = -1; 
712     $objs = array();
713     foreach($this->pap as $key => $obj){
714       if(get_class($obj) == "sieve_require"){
715         $last_found_at = $key;
716       }
717     }
718     foreach($this->pap as $key => $obj){
719       if($key == $last_found_at) break;  
720       if(!in_array(get_class($obj),array("sieve_comment","sieve_require"))){
721         $objs[] = get_class($obj);
722       }
723     }
724     if(count($objs) && $last_found_at != -1){
725       $str = _("Require must be the first command in the script.");  
726       $msgs[] = $str;
727       print_red($str);;
728     }
729     
730                 foreach($this->pap as $obj){
731                         $o_msgs = $obj->check();
732                         foreach($o_msgs as $o_msg){
733                                 $msgs[] = $o_msg;
734                         }
735                 }
736                 return($msgs);
737   }
741 /* Create valid sieve string/string-list 
742  *  out of a given array
743  */
744 function sieve_create_strings($data)
746   $ret = "";
747   if(is_array($data)){
748     if(count($data) == 1){
749       $ret = "\"";
750       foreach($data as $dat){
751         $ret .=$dat;
752       }
753       $ret.="\"";
754     }else{
755       foreach($data as $dat){
756         $ret.= "\"";
757         $ret.=$dat;
758         $ret.="\", ";
759       }
760       $ret = preg_replace("/,$/","",trim($ret));
761       $ret = "[".$ret."]";
762     }
763   }else{
765     $Multiline = preg_match("/\n/",$data);
766     $data = preg_replace("/\r/","",$data);;
768     if($Multiline){
769       $ret = "text: \r\n".$data."\r\n.\r\n";
770     }else{
771       $ret = "\"".$data."\"";
772     }
773   }
774   $ret = preg_replace("/\"\"/","\"",$ret);
775   $ret = preg_replace("/\n/","\r\n",$ret);
776   
777   return($ret);
780 /* This checks if there is a string at the current position 
781  *  in the token array. 
782  * If there is a string list at the current position,
783  *  this function will return a complete list of all
784  *  strings used in this list.
785  * It also returns an offset of the last token position 
786  */
787 function sieve_get_strings($data,$id)
789   $ret = array();
790   if($data[$id]['class'] == "left-bracket"){
791     while($data[$id]['class']  != "right-bracket" && $id < count($data)){
792       
793       if($data[$id]['class'] == "quoted-string"){
794         $ret[] = $data[$id]['text'];
795       }
796       $id ++;
797     }
798   }elseif($data[$id]['class'] == "quoted-string"){
799     $ret[] = $data[$id]['text'];
800   }elseif($data[$id]['class'] == "number"){
801     $ret[] = $data[$id]['text'];
802   }
803   return(array("OFFSET" => $id, "STRINGS" => $ret));
806 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
807 ?>