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 $this -> dump_ = "";
51 foreach($this->pap as $key => $object){
52 if(is_object($object)){
53 $this->dump_ .= preg_replace("/>/",">\n",$object->execute());
54 }
55 }
57 return($this->dump_);
58 }
61 /* This function walks through the object tree generated by the "Parse" class.
62 * All Commands will be resolved and grouped. So the Commands and their
63 * parameter are combined. Like "IF" and ":comparator"...
64 */
65 function doDump_($node_id, $prefix, $last,$num = 1)
66 {
67 /* Indicates that current comman will only be valid for a single line.
68 * this command type will be removed from mode_stack after displaying it.
69 */
70 $rewoke_last = FALSE;
72 /* Get node */
73 $node = $this->nodes_[$node_id];
75 /* This closes the last mode */
76 if($node['class'] == "block-start"){
77 $tmp = array_pop($this->mode_stack);
78 $this->handle_elements($tmp,$node_id);
79 $this->handle_elements(array("TYPE" => "block_start"),preg_replace("/[^0-9]/","",microtime()));
80 }
82 /* This closes the last mode */
83 if($node['class'] == "block-end"){
84 $tmp = array_pop($this->mode_stack);
85 $this->handle_elements($tmp,$node_id);
86 $this->handle_elements(array("TYPE" => "block_end"),preg_replace("/[^0-9]/","",microtime()));
87 }
89 /* Semicolon indicates a new command */
90 if($node['class'] == "semicolon"){
91 $tmp =array_pop($this->mode_stack);
92 $this->handle_elements($tmp,$node_id);
93 }
95 /* Handle comments */
96 if($node['class'] == "comment"){
97 $this->mode_stack[] = array("TYPE" => $node['class']);
98 $rewoke_last = TRUE;
99 }
101 /* Handle identifiers */
102 $identifiers = array("else","if","elsif","end","reject","redirect","vacation","keep","discard","comment","fileinto","require","stop");
103 if($node['class'] == "identifier" && in_array($node['text'],$identifiers)){
104 $this->mode_stack[] = array("TYPE" => $node['text']);
105 }
107 /* Add current node to current command stack */
108 end($this->mode_stack);
109 $key = key($this->mode_stack);
110 $this->mode_stack[$key]['ELEMENTS'][] = $node;
112 /* Remove last mode from mode stack, cause it was only valid for a single line */
113 if($rewoke_last){
114 $tmp =array_pop($this->mode_stack);
115 $this->handle_elements($tmp,$node_id);
116 }
118 /* If this is a sub element, just call this for all childs */
119 if(isset($this->childs_[$node_id])){
120 $childs = $this->childs_[$node_id];
121 for ($i=0; $i<count($childs); ++$i)
122 {
123 $c_last = false;
124 if ($i+1 == count($childs))
125 {
126 $c_last = true;
127 }
128 $this->doDump_($childs[$i], "", $num);
129 }
130 }
131 }
134 /* Create a class for each resolved object.
135 * And append this class to a list of objects.
136 */
137 function handle_elements($data,$id)
138 {
139 if(!isset($data['TYPE'])){
140 return;
141 }
142 $type = $data['TYPE'];
144 $class_name= "sieve_".$type ;
145 if(class_exists($class_name)){
146 $this->pap[] = new $class_name($data,$id,$this);
147 }else{
148 echo "<font color='red'>Missing : ".$class_name."</font>"."<br>";
149 }
150 }
152 function save_object()
153 {
154 reset($this->pap);
155 foreach($this->pap as $key => $obj){
157 if(in_array(get_class($obj),array("sieve_if",
158 "sieve_elsif",
159 "sieve_vacation",
160 "sieve_comment",
161 "sieve_reject",
162 "sieve_fileinto",
163 "sieve_require",
164 "sieve_redirect"))){
167 if(isset($this->pap[$key]) && method_exists($this->pap[$key],"save_object")){
168 $this->pap[$key]->save_object();
169 }
170 }
171 }
172 }
175 /* Remove the object at the given position */
176 function remove_object($key_id)
177 {
178 $class = get_class($this->pap[$key_id]);
179 if(in_array($class,array("sieve_if","sieve_elsif","sieve_else"))){
180 $block_start= $key_id;
181 $block_end = $this->get_block_end($key_id);
183 for($i = $block_start ; $i <= $block_end ; $i ++ ){
184 unset($this->pap[$i]);
185 }
186 }else{
187 unset($this->pap[$key_id]);
188 }
189 $tmp = array();
190 foreach($this->pap as $element){
191 $tmp[] = $element;
192 }
193 $this->pap = $tmp;
194 }
197 /* This function moves a given element to another position.
198 * Single elements like "keep;" will simply be moved one posisition down/up.
199 * Multiple elements like if-elsif-else will be moved as block.
200 *
201 * $key_id specified the element that should be moved.
202 * $direction specifies to move elements "up" or "down"
203 */
204 function move_up_down($key_id,$direction = "down")
205 {
207 /* Get the current element to decide what to move. */
208 $e_class = get_class($this->pap[$key_id]);
210 if(in_array($e_class,array("sieve_if"))){
211 $block_start= $key_id;
212 $block_end = $this->get_block_end($key_id);
214 /* Depending on the direction move up down */
215 if($direction == "down"){
216 $next_free = $this->_get_next_free_move_slot($block_end,$direction);
217 }else{
218 $next_free = $this->_get_next_free_move_slot($block_start,$direction);
219 }
221 /* Move the given block */
222 $this->move_multiple_elements($block_start,$block_end,$next_free);
223 }
225 if(in_array($e_class,array( "sieve_stop",
226 "sieve_keep",
227 "sieve_require",
228 "sieve_comment",
229 "sieve_vacation",
230 "sieve_stop",
231 "sieve_reject",
232 "sieve_fileinto",
233 "sieve_redirect",
234 "sieve_discard"))){
235 $this->move_single_element($key_id,$this->_get_next_free_move_slot($key_id,$direction));
236 }
237 }
240 /* Move the given block to position */
241 function move_multiple_elements($start,$end,$to)
242 {
243 /* Use class names for testing */
244 $data = $this->pap;
246 /* Get block to move */
247 $block_to_move = array_slice($data,$start, ($end - $start +1));
249 /* We want do move this block up */
250 if($end > $to){
252 /* Get start block */
253 $start_block = array_slice($data,0,$to);
255 /* Get Get all elements between the block to move
256 * and next free position
257 */
258 $block_to_free = array_slice($data,$to ,$start - $to );
259 $block_to_end = array_slice($data,$end+1);
260 $new = array();
261 foreach($start_block as $block){
262 $new[] = $block;
263 }
264 foreach($block_to_move as $block){
265 $new[] = $block;
266 }
267 foreach($block_to_free as $block){
268 $new[] = $block;
269 }
270 foreach($block_to_end as $block){
271 $new[] = $block;
272 }
273 $old = $this->pap;
274 $this->pap = $new;
275 }
278 /* We want to move this block down. */
279 if($to > $end){
281 /* Get start block */
282 $start_block = array_slice($data,0,$start);
284 /* Get Get all elements between the block to move
285 * and next free position
286 */
287 $block_to_free = array_slice($data,$end +1,($to - $end ));
289 /* Get the rest
290 */
291 $block_to_end = array_slice($data,$to+1);
293 $new = array();
294 foreach($start_block as $block){
295 $new[] = $block;
296 }
297 foreach($block_to_free as $block){
298 $new[] = $block;
299 }
300 foreach($block_to_move as $block){
301 $new[] = $block;
302 }
303 foreach($block_to_end as $block){
304 $new[] = $block;
305 }
306 $old = $this->pap;
307 $this->pap = $new;
308 }
309 }
312 /* This function returns the id of the element
313 * where the current block ends
314 */
315 function get_block_end($start)
316 {
317 /* Only execute if this is a really a block element.
318 * Block elements is only sieve_if
319 */
320 if(in_array(get_class($this->pap[$start]),array("sieve_if","sieve_elsif","sieve_else"))){
322 $class = get_class($this->pap[$start]);
323 $next_class = get_class($this->pap[$start+1]);
324 $block_depth = 0;
326 $end = FALSE;
328 while(!$end && $start < count($this->pap)){
330 if($class == "sieve_block_start"){
331 $block_depth ++;
332 }
334 if($class == "sieve_block_end"){
335 $block_depth --;
336 }
338 if( $block_depth == 0 &&
339 $class == "sieve_block_end" &&
340 !in_array($next_class,array("sieve_else","sieve_elsif"))){
341 $end = TRUE;
342 $start --;
343 }
344 $start ++;
345 $class = get_class($this->pap[$start]);
347 if(isset($this->pap[$start+1])){
348 $next_class = get_class($this->pap[$start+1]);
349 }else{
350 $next_class ="";
351 }
352 }
353 }
354 return($start);
355 }
358 /* This function moves the single element at
359 * position $from to position $to.
360 */
361 function move_single_element($from,$to)
362 {
363 if($from == $to) {
364 return;
365 }
367 $ret = array();
368 $tmp = $this->pap;
370 $begin = array();
371 $middle = array();
372 $end = array();
373 $element = $this->pap[$from];
375 if($from > $to ){
377 /* Get all element in fron to element to move */
378 if($from != 0){
379 $begin = array_slice($tmp,0,$to);
380 }
382 /* Get all elements between */
383 $middle = array_slice($tmp,$to , ($from - ($to) ));
385 /* Get the rest */
386 $end = array_slice($tmp,$from+1);
388 foreach($begin as $data){
389 $ret[] = $data;
390 }
391 $ret[] = $element;
392 foreach($middle as $data){
393 $ret[] = $data;
394 }
395 foreach($end as $data){
396 $ret[] = $data;
397 }
398 $this->pap = $ret;
399 }
400 if($from < $to ){
402 /* Get all element in fron to element to move */
403 if($from != 0){
404 $begin = array_slice($tmp,0,$from);
405 }
407 /* Get all elements between */
408 $middle = array_slice($tmp,$from+1 , ($to - ($from)));
410 /* Get the rest */
411 $end = array_slice($tmp,$to+1);
413 foreach($begin as $data){
414 $ret[] = $data;
415 }
416 foreach($middle as $data){
417 $ret[] = $data;
418 }
419 $ret[] = $element;
420 foreach($end as $data){
421 $ret[] = $data;
422 }
423 $this->pap = $ret;
424 }
425 }
428 /* Returns the next free position where we
429 * can add a new sinle element
430 * $key_id = Current position
431 * $direction = Forward or backward.
432 */
433 function _get_next_free_move_slot($key_id,$direction,$include_self = FALSE)
434 {
435 $last_class = "";
436 $current_class ="";
437 $next_class = "";
439 /* After this elements we can add new elements
440 * without having any trouble.
441 */
442 $allowed_to_add_after = array("sieve_keep",
443 "sieve_require",
444 "sieve_stop",
445 "sieve_reject",
446 "sieve_fileinto",
447 "sieve_redirect",
448 "sieve_discard",
449 "sieve_comment",
450 "sieve_block_start"
451 );
453 /* Before this elements we can add new elements
454 * without having any trouble.
455 */
456 $allowed_to_add_before = array("sieve_keep",
457 "sieve_require",
458 "sieve_stop",
459 "sieve_reject",
460 "sieve_fileinto",
461 "sieve_comment",
462 "sieve_redirect",
463 "sieve_discard",
464 "sieve_if",
465 "sieve_block_end"
466 );
468 if($direction == "down"){
470 $test = $this->pap;
471 while($key_id < count($test)){
472 if(($key_id+1) == count($test)) {
473 return($key_id);
474 }
476 if(!$include_self){
477 $key_id ++;
478 }
479 $current_class = get_class($test[$key_id]);
480 if(in_array($current_class, $allowed_to_add_after)){
481 return($key_id);
482 }
483 }
484 }else{
486 $test = $this->pap;
487 if($key_id == 0) {
488 return($key_id);
489 }
490 if(!$include_self){
491 $key_id --;
492 }
493 while($key_id >=0 ){
494 $current_class = get_class($test[$key_id]);
495 if(in_array($current_class, $allowed_to_add_before)){
496 return($key_id);
497 }
498 $key_id --;
499 }
500 return(0);
501 }
502 }
505 /* Need to be reviewed */
506 function get_sieve_script()
507 {
508 $tmp ="";
509 if(count($this->pap)){
510 $buffer = "";
511 foreach($this->pap as $part) {
512 if(get_class($part) == "sieve_block_end"){
513 $buffer = substr($buffer,0,strlen($buffer)-(strlen(SIEVE_INDENT_TAB)));
514 }
515 $tmp2 = $part->get_sieve_script_part();
517 if(get_class($part) == "sieve_reject"){
518 $tmp.=$tmp2;
519 }else{
521 $tmp3 = split("\n",$tmp2);
522 foreach($tmp3 as $str){
523 $str2 = trim($str);
524 if(empty($str2)) continue;
525 $tmp.= $buffer.$str."\n";
526 }
527 }
528 if(get_class($part) == "sieve_block_start"){
529 $buffer .= SIEVE_INDENT_TAB;
530 }
531 }
532 }
533 if(!preg_match("/Generated by GOsa - Gonicus System Administrator/",$tmp)){
534 $tmp = "#Generated by GOsa - Gonicus System Administrator \n ".$tmp;
535 }
536 return($tmp);
537 }
539 function check()
540 {
541 $msgs = array();
543 /* Some logical checks.
544 * like : only sieve_comment can appear before require.
545 */
547 /* Ensure that there are no command before require
548 * - Get id of last require tag
549 * - Collect object types in from of this tag.
550 * - Check if there are tags collected that are not allowed
551 */
552 $last_found_at = -1;
553 $objs = array();
554 foreach($this->pap as $key => $obj){
555 if(get_class($obj) == "sieve_require"){
556 $last_found_at = $key;
557 }
558 }
559 foreach($this->pap as $key => $obj){
560 if($key == $last_found_at) break;
561 if(!in_array(get_class($obj),array("sieve_comment","sieve_require"))){
562 $objs[] = get_class($obj);
563 }
564 }
565 if(count($objs) && $last_found_at != -1){
566 $str = _("Require must be the first command in the script.");
567 $msgs[] = $str;
568 print_red($str);;
569 }
571 foreach($this->pap as $obj){
572 $o_msgs = $obj->check();
573 foreach($o_msgs as $o_msg){
574 $msgs[] = $o_msg;
575 }
576 }
577 return($msgs);
578 }
581 /* We are forced to add a new require.
582 * This function is called by the
583 * sieveElement_Classes->parent->add_require()
584 */
585 function add_require($str)
586 {
587 $require_id = -1;
588 foreach($this->pap as $key => $obj){
589 if(get_class($obj) == "sieve_require"){
590 $require_id = $key;
591 }
592 }
594 /* No require found, add one */
595 if($require_id == -1){
596 $require = new sieve_require(NULL,preg_replace("/[^0-9]/","",microtime()),$this);
597 $require -> Add_Require($str);
598 $new = array();
599 $new[] = $require;
600 foreach($this->pap as $obj){
601 $new[] = $obj;
602 }
603 $this->pap = $new;
604 } else {
605 $this->pap[$require_id]->Add_Require($str);
606 }
607 }
608 }
611 /* Create valid sieve string/string-list
612 * out of a given array
613 */
614 function sieve_create_strings($data,$force_string = FALSE)
615 {
616 $ret = "";
617 if(is_array($data)){
618 if(count($data) == 1){
619 $ret = "\"";
620 foreach($data as $dat){
621 $ret .=$dat;
622 }
623 $ret.="\"";
624 }else{
625 foreach($data as $dat){
626 $ret.= "\"";
627 $ret.=$dat;
628 $ret.="\", ";
629 }
630 $ret = preg_replace("/,$/","",trim($ret));
631 $ret = "[".$ret."]";
632 }
633 }else{
635 $Multiline = preg_match("/\n/",$data);
636 $data = preg_replace("/\r/","",$data);;
638 if($Multiline && !$force_string){
639 $ret = "text: \r\n".$data."\r\n.\r\n";
640 }else{
641 $ret = "\"".$data."\"";
642 }
643 }
644 $ret = preg_replace("/\"\"/","\"",$ret);
645 $ret = preg_replace("/\n/","\r\n",$ret);
647 return($ret);
648 }
650 /* This checks if there is a string at the current position
651 * in the token array.
652 * If there is a string list at the current position,
653 * this function will return a complete list of all
654 * strings used in this list.
655 * It also returns an offset of the last token position
656 */
657 function sieve_get_strings($data,$id)
658 {
659 $ret = array();
660 if($data[$id]['class'] == "left-bracket"){
661 while($data[$id]['class'] != "right-bracket" && $id < count($data)){
663 if($data[$id]['class'] == "quoted-string"){
664 $ret[] = $data[$id]['text'];
665 }
666 $id ++;
667 }
668 }elseif($data[$id]['class'] == "quoted-string"){
669 $ret[] = $data[$id]['text'];
670 }elseif($data[$id]['class'] == "number"){
671 $ret[] = $data[$id]['text'];
672 }elseif($data[$id]['class'] == "multi-line"){
673 $str = trim(preg_replace("/^text:/","",$data[$id]['text']));
674 $str = trim(preg_replace("/\.$/","",$str));
675 $ret[] = $str;
676 }
678 return(array("OFFSET" => $id, "STRINGS" => $ret));
679 }
681 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
682 ?>