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