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__)));
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 }
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'];
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 {
246 /* Get the current element to decide what to move. */
247 $e_class = get_class($this->pap[$key_id]);
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 }
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){
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 }
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 }
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)){
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]);
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) ));
424 /* Get the rest */
425 $end = array_slice($tmp,$from+1);
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)));
449 /* Get the rest */
450 $end = array_slice($tmp,$to+1);
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"){
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{
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 */
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 }
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 }
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 }
647 }
650 /* Create valid sieve string/string-list
651 * out of a given array
652 */
653 function sieve_create_strings($data,$force_string = FALSE)
654 {
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);
686 return($ret);
687 }
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)
697 {
698 $ret = array();
699 if($data[$id]['class'] == "left-bracket"){
700 while($data[$id]['class'] != "right-bracket" && $id < count($data)){
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 }
717 return(array("OFFSET" => $id, "STRINGS" => $ret));
718 }
720 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
721 ?>