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 /* This closes the last mode */
91 if($node['class'] == "block-start"){
92 $tmp = array_pop($this->mode_stack);
93 $this->handle_elements($tmp,$node_id);
94 $this->handle_elements(array("TYPE" => "block_start"),preg_replace("/[^0-9]/","",microtime()));
95 }
97 /* This closes the last mode */
98 if($node['class'] == "block-end"){
99 $tmp = array_pop($this->mode_stack);
100 $this->handle_elements($tmp,$node_id);
101 $this->handle_elements(array("TYPE" => "block_end"),preg_replace("/[^0-9]/","",microtime()));
102 }
104 /* Semicolon indicates a new command */
105 if($node['class'] == "semicolon"){
106 $tmp =array_pop($this->mode_stack);
107 $this->handle_elements($tmp,$node_id);
108 }
110 /* Handle comments */
111 if($node['class'] == "comment"){
112 $this->mode_stack[] = array("TYPE" => $node['class']);
113 $rewoke_last = TRUE;
114 }
116 /* Handle identifiers */
117 $identifiers = array("else","if","elsif","end","reject","redirect","vacation","keep","discard","comment","fileinto","require","stop");
118 if($node['class'] == "identifier" && in_array($node['text'],$identifiers)){
119 $this->mode_stack[] = array("TYPE" => $node['text']);
120 }
122 /* Add current node to current command stack */
123 end($this->mode_stack);
124 $key = key($this->mode_stack);
125 $this->mode_stack[$key]['ELEMENTS'][] = $node;
127 /* Remove last mode from mode stack, cause it was only valid for a single line */
128 if($rewoke_last){
129 $tmp =array_pop($this->mode_stack);
130 $this->handle_elements($tmp,$node_id);
131 }
133 /* If this is a sub element, just call this for all childs */
134 if(isset($this->childs_[$node_id])){
135 $childs = $this->childs_[$node_id];
136 for ($i=0; $i<count($childs); ++$i)
137 {
138 $c_last = false;
139 if ($i+1 == count($childs))
140 {
141 $c_last = true;
142 }
143 $this->doDump_($childs[$i], "", $num);
144 }
145 }
146 }
149 /* Create a class for each resolved object.
150 * And append this class to a list of objects.
151 */
152 function handle_elements($data,$id)
153 {
154 if(!isset($data['TYPE'])){
155 return;
156 }
157 $type = $data['TYPE'];
159 $class_name= "sieve_".$type ;
160 if(class_exists($class_name)){
161 $this->pap[] = new $class_name($data,$id,$this);
162 }else{
163 echo "<font color='red'>Missing : ".$class_name."</font>"."<br>";
164 }
165 }
167 function save_object()
168 {
169 reset($this->pap);
170 foreach($this->pap as $key => $obj){
172 if(in_array(get_class($obj),array("sieve_if",
173 "sieve_elsif",
174 "sieve_vacation",
175 "sieve_comment",
176 "sieve_reject",
177 "sieve_fileinto",
178 "sieve_require",
179 "sieve_redirect"))){
182 if(isset($this->pap[$key]) && method_exists($this->pap[$key],"save_object")){
183 $this->pap[$key]->save_object();
184 }
185 }
186 }
187 }
190 /* Remove the object at the given position */
191 function remove_object($key_id)
192 {
193 $class = get_class($this->pap[$key_id]);
194 if(in_array($class,array("sieve_if","sieve_elsif","sieve_else"))){
195 $block_start= $key_id;
196 $block_end = $this->get_block_end($key_id);
198 for($i = $block_start ; $i <= $block_end ; $i ++ ){
199 unset($this->pap[$i]);
200 }
201 }else{
202 unset($this->pap[$key_id]);
203 }
204 $tmp = array();
205 foreach($this->pap as $element){
206 $tmp[] = $element;
207 }
208 $this->pap = $tmp;
209 }
212 /* This function moves a given element to another position.
213 * Single elements like "keep;" will simply be moved one posisition down/up.
214 * Multiple elements like if-elsif-else will be moved as block.
215 *
216 * $key_id specified the element that should be moved.
217 * $direction specifies to move elements "up" or "down"
218 */
219 function move_up_down($key_id,$direction = "down")
220 {
222 /* Get the current element to decide what to move. */
223 $e_class = get_class($this->pap[$key_id]);
225 if(in_array($e_class,array("sieve_if"))){
226 $block_start= $key_id;
227 $block_end = $this->get_block_end($key_id);
229 /* Depending on the direction move up down */
230 if($direction == "down"){
231 $next_free = $this->_get_next_free_move_slot($block_end,$direction);
232 }else{
233 $next_free = $this->_get_next_free_move_slot($block_start,$direction);
234 }
236 /* Move the given block */
237 $this->move_multiple_elements($block_start,$block_end,$next_free);
238 }
240 if(in_array($e_class,array( "sieve_stop",
241 "sieve_keep",
242 "sieve_require",
243 "sieve_comment",
244 "sieve_vacation",
245 "sieve_stop",
246 "sieve_reject",
247 "sieve_fileinto",
248 "sieve_redirect",
249 "sieve_discard"))){
250 $this->move_single_element($key_id,$this->_get_next_free_move_slot($key_id,$direction));
251 }
252 }
255 /* Move the given block to position */
256 function move_multiple_elements($start,$end,$to)
257 {
258 /* Use class names for testing */
259 $data = $this->pap;
261 /* Get block to move */
262 $block_to_move = array_slice($data,$start, ($end - $start +1));
264 /* We want do move this block up */
265 if($end > $to){
267 /* Get start block */
268 $start_block = array_slice($data,0,$to);
270 /* Get Get all elements between the block to move
271 * and next free position
272 */
273 $block_to_free = array_slice($data,$to ,$start - $to );
274 $block_to_end = array_slice($data,$end+1);
275 $new = array();
276 foreach($start_block as $block){
277 $new[] = $block;
278 }
279 foreach($block_to_move as $block){
280 $new[] = $block;
281 }
282 foreach($block_to_free as $block){
283 $new[] = $block;
284 }
285 foreach($block_to_end as $block){
286 $new[] = $block;
287 }
288 $old = $this->pap;
289 $this->pap = $new;
290 }
293 /* We want to move this block down. */
294 if($to > $end){
296 /* Get start block */
297 $start_block = array_slice($data,0,$start);
299 /* Get Get all elements between the block to move
300 * and next free position
301 */
302 $block_to_free = array_slice($data,$end +1,($to - $end ));
304 /* Get the rest
305 */
306 $block_to_end = array_slice($data,$to+1);
308 $new = array();
309 foreach($start_block as $block){
310 $new[] = $block;
311 }
312 foreach($block_to_free as $block){
313 $new[] = $block;
314 }
315 foreach($block_to_move as $block){
316 $new[] = $block;
317 }
318 foreach($block_to_end as $block){
319 $new[] = $block;
320 }
321 $old = $this->pap;
322 $this->pap = $new;
323 }
324 }
327 /* This function returns the id of the element
328 * where the current block ends
329 */
330 function get_block_end($start)
331 {
332 /* Only execute if this is a really a block element.
333 * Block elements is only sieve_if
334 */
335 if(in_array(get_class($this->pap[$start]),array("sieve_if","sieve_elsif","sieve_else"))){
337 $class = get_class($this->pap[$start]);
338 $next_class = get_class($this->pap[$start+1]);
339 $block_depth = 0;
341 $end = FALSE;
343 while(!$end && $start < count($this->pap)){
345 if($class == "sieve_block_start"){
346 $block_depth ++;
347 }
349 if($class == "sieve_block_end"){
350 $block_depth --;
351 }
353 if( $block_depth == 0 &&
354 $class == "sieve_block_end" &&
355 !in_array($next_class,array("sieve_else","sieve_elsif"))){
356 $end = TRUE;
357 $start --;
358 }
359 $start ++;
360 $class = get_class($this->pap[$start]);
362 if(isset($this->pap[$start+1])){
363 $next_class = get_class($this->pap[$start+1]);
364 }else{
365 $next_class ="";
366 }
367 }
368 }
369 return($start);
370 }
373 /* This function moves the single element at
374 * position $from to position $to.
375 */
376 function move_single_element($from,$to)
377 {
378 if($from == $to) {
379 return;
380 }
382 $ret = array();
383 $tmp = $this->pap;
385 $begin = array();
386 $middle = array();
387 $end = array();
388 $element = $this->pap[$from];
390 if($from > $to ){
392 /* Get all element in fron to element to move */
393 if($from != 0){
394 $begin = array_slice($tmp,0,$to);
395 }
397 /* Get all elements between */
398 $middle = array_slice($tmp,$to , ($from - ($to) ));
400 /* Get the rest */
401 $end = array_slice($tmp,$from+1);
403 foreach($begin as $data){
404 $ret[] = $data;
405 }
406 $ret[] = $element;
407 foreach($middle as $data){
408 $ret[] = $data;
409 }
410 foreach($end as $data){
411 $ret[] = $data;
412 }
413 $this->pap = $ret;
414 }
415 if($from < $to ){
417 /* Get all element in fron to element to move */
418 if($from != 0){
419 $begin = array_slice($tmp,0,$from);
420 }
422 /* Get all elements between */
423 $middle = array_slice($tmp,$from+1 , ($to - ($from)));
425 /* Get the rest */
426 $end = array_slice($tmp,$to+1);
428 foreach($begin as $data){
429 $ret[] = $data;
430 }
431 foreach($middle as $data){
432 $ret[] = $data;
433 }
434 $ret[] = $element;
435 foreach($end as $data){
436 $ret[] = $data;
437 }
438 $this->pap = $ret;
439 }
440 }
443 /* Returns the next free position where we
444 * can add a new sinle element
445 * $key_id = Current position
446 * $direction = Forward or backward.
447 */
448 function _get_next_free_move_slot($key_id,$direction,$include_self = FALSE)
449 {
450 $last_class = "";
451 $current_class ="";
452 $next_class = "";
454 /* After this elements we can add new elements
455 * without having any trouble.
456 */
457 $allowed_to_add_after = array("sieve_keep",
458 "sieve_require",
459 "sieve_stop",
460 "sieve_reject",
461 "sieve_fileinto",
462 "sieve_redirect",
463 "sieve_discard",
464 "sieve_comment",
465 "sieve_block_start"
466 );
468 /* Before this elements we can add new elements
469 * without having any trouble.
470 */
471 $allowed_to_add_before = array("sieve_keep",
472 "sieve_require",
473 "sieve_stop",
474 "sieve_reject",
475 "sieve_fileinto",
476 "sieve_comment",
477 "sieve_redirect",
478 "sieve_discard",
479 "sieve_if",
480 "sieve_block_end"
481 );
483 if($direction == "down"){
485 $test = $this->pap;
486 while($key_id < count($test)){
487 if(($key_id+1) == count($test)) {
488 return($key_id);
489 }
491 if(!$include_self){
492 $key_id ++;
493 }
494 $current_class = get_class($test[$key_id]);
495 if(in_array($current_class, $allowed_to_add_after)){
496 return($key_id);
497 }
498 }
499 }else{
501 $test = $this->pap;
502 if($key_id == 0) {
503 return($key_id);
504 }
505 if(!$include_self){
506 $key_id --;
507 }
508 while($key_id >=0 ){
509 $current_class = get_class($test[$key_id]);
510 if(in_array($current_class, $allowed_to_add_before)){
511 return($key_id);
512 }
513 $key_id --;
514 }
515 return(0);
516 }
517 }
520 /* Need to be reviewed */
521 function get_sieve_script()
522 {
523 $tmp ="";
524 if(count($this->pap)){
525 $buffer = "";
526 foreach($this->pap as $part) {
527 if(get_class($part) == "sieve_block_end"){
528 $buffer = substr($buffer,0,strlen($buffer)-(strlen(SIEVE_INDENT_TAB)));
529 }
530 $tmp2 = $part->get_sieve_script_part();
532 if(get_class($part) == "sieve_reject"){
533 $tmp.=$tmp2;
534 }else{
536 $tmp3 = split("\n",$tmp2);
537 foreach($tmp3 as $str){
538 $str2 = trim($str);
539 if(empty($str2)) continue;
540 $tmp.= $buffer.$str."\n";
541 }
542 }
543 if(get_class($part) == "sieve_block_start"){
544 $buffer .= SIEVE_INDENT_TAB;
545 }
546 }
547 }
548 if(!preg_match("/Generated by GOsa - Gonicus System Administrator/",$tmp)){
549 $tmp = "#Generated by GOsa - Gonicus System Administrator \n ".$tmp;
550 }
551 return($tmp);
552 }
554 function check()
555 {
556 $msgs = array();
558 /* Some logical checks.
559 * like : only sieve_comment can appear before require.
560 */
562 /* Ensure that there are no command before require
563 * - Get id of last require tag
564 * - Collect object types in from of this tag.
565 * - Check if there are tags collected that are not allowed
566 */
567 $last_found_at = -1;
568 $objs = array();
569 foreach($this->pap as $key => $obj){
570 if(get_class($obj) == "sieve_require"){
571 $last_found_at = $key;
572 }
573 }
574 foreach($this->pap as $key => $obj){
575 if($key == $last_found_at) break;
576 if(!in_array(get_class($obj),array("sieve_comment","sieve_require"))){
577 $objs[] = get_class($obj);
578 }
579 }
580 if(count($objs) && $last_found_at != -1){
581 $str = _("Require must be the first command in the script.");
582 $msgs[] = $str;
583 print_red($str);;
584 }
586 foreach($this->pap as $obj){
587 $o_msgs = $obj->check();
588 foreach($o_msgs as $o_msg){
589 $msgs[] = $o_msg;
590 }
591 }
592 return($msgs);
593 }
596 /* We are forced to add a new require.
597 * This function is called by the
598 * sieveElement_Classes->parent->add_require()
599 */
600 function add_require($str)
601 {
602 $require_id = -1;
603 foreach($this->pap as $key => $obj){
604 if(get_class($obj) == "sieve_require"){
605 $require_id = $key;
606 }
607 }
609 /* No require found, add one */
610 if($require_id == -1){
611 $require = new sieve_require(NULL,preg_replace("/[^0-9]/","",microtime()),$this);
612 $require -> Add_Require($str);
613 $new = array();
614 $new[] = $require;
615 foreach($this->pap as $obj){
616 $new[] = $obj;
617 }
618 $this->pap = $new;
619 } else {
620 $this->pap[$require_id]->Add_Require($str);
621 }
622 }
623 }
626 /* Create valid sieve string/string-list
627 * out of a given array
628 */
629 function sieve_create_strings($data,$force_string = FALSE)
630 {
631 $ret = "";
632 if(is_array($data)){
633 if(count($data) == 1){
634 $ret = "\"";
635 foreach($data as $dat){
636 $ret .=$dat;
637 }
638 $ret.="\"";
639 }else{
640 foreach($data as $dat){
641 $ret.= "\"";
642 $ret.=$dat;
643 $ret.="\", ";
644 }
645 $ret = preg_replace("/,$/","",trim($ret));
646 $ret = "[".$ret."]";
647 }
648 }else{
650 $Multiline = preg_match("/\n/",$data);
651 $data = preg_replace("/\r/","",$data);;
653 if($Multiline && !$force_string){
654 $ret = "text: \r\n".$data."\r\n.\r\n";
655 }else{
656 $ret = "\"".$data."\"";
657 }
658 }
659 $ret = preg_replace("/\"\"/","\"",$ret);
660 $ret = preg_replace("/\n/","\r\n",$ret);
662 return($ret);
663 }
665 /* This checks if there is a string at the current position
666 * in the token array.
667 * If there is a string list at the current position,
668 * this function will return a complete list of all
669 * strings used in this list.
670 * It also returns an offset of the last token position
671 */
672 function sieve_get_strings($data,$id)
673 {
674 $ret = array();
675 if($data[$id]['class'] == "left-bracket"){
676 while($data[$id]['class'] != "right-bracket" && $id < count($data)){
678 if($data[$id]['class'] == "quoted-string"){
679 $ret[] = $data[$id]['text'];
680 }
681 $id ++;
682 }
683 }elseif($data[$id]['class'] == "quoted-string"){
684 $ret[] = $data[$id]['text'];
685 }elseif($data[$id]['class'] == "number"){
686 $ret[] = $data[$id]['text'];
687 }elseif($data[$id]['class'] == "multi-line"){
688 $str = trim(preg_replace("/^text:/","",$data[$id]['text']));
689 $str = trim(preg_replace("/\.$/","",$str));
690 $ret[] = $str;
691 }
693 return(array("OFFSET" => $id, "STRINGS" => $ret));
694 }
696 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
697 ?>