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 function execute()
20 {
21 return($this->dump());
22 }
24 /* Create a html interface for the current sieve filter
25 */
26 function dump()
27 {
28 /**************
29 * Handle new elements
30 **************/
32 /* Only parse the tokens once */
33 if(!count($this->pap)){
34 $this->dump_ = "";
35 $this->mode_stack = array();
36 $this->pap = array();
37 $this->doDump_(0, '', true);
38 }
40 /* Create html results */
41 $smarty = get_smarty();
43 $this -> dump_ = "";
44 foreach($this->pap as $key => $object){
45 if(is_object($object)){
46 $this->dump_ .= preg_replace("/>/",">\n",$object->execute());
47 }
48 }
50 return($this->dump_);
51 }
54 /* This function walks through the object tree generated by the "Parse" class.
55 * All Commands will be resolved and grouped. So the Commands and their
56 * parameter are combined. Like "IF" and ":comparator"...
57 */
58 function doDump_($node_id, $prefix, $last,$num = 1)
59 {
60 /* Indicates that current comman will only be valid for a single line.
61 * this command type will be removed from mode_stack after displaying it.
62 */
63 $rewoke_last = FALSE;
65 /* Get node */
66 $node = $this->nodes_[$node_id];
68 /* This closes the last mode */
69 if($node['class'] == "block-start"){
70 $tmp = array_pop($this->mode_stack);
71 $this->handle_elements($tmp,$node_id);
72 $this->handle_elements(array("TYPE" => "block_start"),preg_replace("/[^0-9]/","",microtime()));
73 }
75 /* This closes the last mode */
76 if($node['class'] == "block-end"){
77 $tmp = array_pop($this->mode_stack);
78 $this->handle_elements($tmp,$node_id);
79 $this->handle_elements(array("TYPE" => "block_end"),preg_replace("/[^0-9]/","",microtime()));
80 }
82 /* Semicolon indicates a new command */
83 if($node['class'] == "semicolon"){
84 $tmp =array_pop($this->mode_stack);
85 $this->handle_elements($tmp,$node_id);
86 }
88 /* Handle comments */
89 if($node['class'] == "comment"){
90 $this->mode_stack[] = array("TYPE" => $node['class']);
91 $rewoke_last = TRUE;
92 }
94 /* Handle identifiers */
95 $identifiers = array("else","if","elsif","end","reject","redirect","vacation","keep","discard","comment","fileinto","require","stop");
96 if($node['class'] == "identifier" && in_array($node['text'],$identifiers)){
97 $this->mode_stack[] = array("TYPE" => $node['text']);
98 }
100 /* Add current node to current command stack */
101 end($this->mode_stack);
102 $key = key($this->mode_stack);
103 $this->mode_stack[$key]['ELEMENTS'][] = $node;
105 /* Remove last mode from mode stack, cause it was only valid for a single line */
106 if($rewoke_last){
107 $tmp =array_pop($this->mode_stack);
108 $this->handle_elements($tmp,$node_id);
109 }
111 /* If this is a sub element, just call this for all childs */
112 if(isset($this->childs_[$node_id])){
113 $childs = $this->childs_[$node_id];
114 for ($i=0; $i<count($childs); ++$i)
115 {
116 $c_last = false;
117 if ($i+1 == count($childs))
118 {
119 $c_last = true;
120 }
121 $this->doDump_($childs[$i], "", $num);
122 }
123 }
124 }
127 /* Create a class for each resolved object.
128 * And append this class to a list of objects.
129 */
130 function handle_elements($data,$id)
131 {
132 if(!isset($data['TYPE'])){
133 return;
134 }
135 $type = $data['TYPE'];
137 $class_name= "sieve_".$type ;
138 if(class_exists($class_name)){
139 $this->pap[] = new $class_name($data,$id);
140 }else{
141 echo "<font color='red'>Missing : ".$class_name."</font>"."<br>";
142 }
143 }
145 function save_object()
146 {
147 reset($this->pap);
148 foreach($this->pap as $key => $obj){
150 if(in_array(get_class($obj),array("sieve_if",
151 "sieve_elsif",
152 "sieve_vacation",
153 "sieve_comment",
154 "sieve_reject",
155 "sieve_fileinto",
156 "sieve_require",
157 "sieve_redirect"))){
160 if(isset($this->pap[$key]) && method_exists($this->pap[$key],"save_object")){
161 $this->pap[$key]->save_object();
162 }
163 }
164 }
165 }
168 /* Remove the object at the given position */
169 function remove_object($key_id)
170 {
171 $class = get_class($this->pap[$key_id]);
172 if(in_array($class,array("sieve_if","sieve_elsif","sieve_else"))){
173 $block_start= $key_id;
174 $block_end = $this->get_block_end($key_id);
176 for($i = $block_start ; $i <= $block_end ; $i ++ ){
177 unset($this->pap[$i]);
178 }
179 }else{
180 unset($this->pap[$key_id]);
181 }
182 $tmp = array();
183 foreach($this->pap as $element){
184 $tmp[] = $element;
185 }
186 $this->pap = $tmp;
187 }
190 /* This function moves a given element to another position.
191 * Single elements like "keep;" will simply be moved one posisition down/up.
192 * Multiple elements like if-elsif-else will be moved as block.
193 *
194 * $key_id specified the element that should be moved.
195 * $direction specifies to move elements "up" or "down"
196 */
197 function move_up_down($key_id,$direction = "down")
198 {
200 /* Get the current element to decide what to move. */
201 $e_class = get_class($this->pap[$key_id]);
203 if(in_array($e_class,array("sieve_if"))){
204 $block_start= $key_id;
205 $block_end = $this->get_block_end($key_id);
207 /* Depending on the direction move up down */
208 if($direction == "down"){
209 $next_free = $this->_get_next_free_move_slot($block_end,$direction);
210 }else{
211 $next_free = $this->_get_next_free_move_slot($block_start,$direction);
212 }
214 /* Move the given block */
215 $this->move_multiple_elements($block_start,$block_end,$next_free);
216 }
218 if(in_array($e_class,array( "sieve_stop",
219 "sieve_keep",
220 "sieve_require",
221 "sieve_comment",
222 "sieve_vacation",
223 "sieve_stop",
224 "sieve_reject",
225 "sieve_fileinto",
226 "sieve_redirect",
227 "sieve_discard"))){
228 $this->move_single_element($key_id,$this->_get_next_free_move_slot($key_id,$direction));
229 }
230 }
233 /* Move the given block to position */
234 function move_multiple_elements($start,$end,$to)
235 {
236 /* Use class names for testing */
237 $data = $this->pap;
239 /* Get block to move */
240 $block_to_move = array_slice($data,$start, ($end - $start +1));
242 /* We want do move this block up */
243 if($end > $to){
245 /* Get start block */
246 $start_block = array_slice($data,0,$to);
248 /* Get Get all elements between the block to move
249 * and next free position
250 */
251 $block_to_free = array_slice($data,$to ,$start - $to );
252 $block_to_end = array_slice($data,$end+1);
253 $new = array();
254 foreach($start_block as $block){
255 $new[] = $block;
256 }
257 foreach($block_to_move as $block){
258 $new[] = $block;
259 }
260 foreach($block_to_free as $block){
261 $new[] = $block;
262 }
263 foreach($block_to_end as $block){
264 $new[] = $block;
265 }
266 $old = $this->pap;
267 $this->pap = $new;
268 }
271 /* We want to move this block down. */
272 if($to > $end){
274 /* Get start block */
275 $start_block = array_slice($data,0,$start);
277 /* Get Get all elements between the block to move
278 * and next free position
279 */
280 $block_to_free = array_slice($data,$end +1,($to - $end ));
282 /* Get the rest
283 */
284 $block_to_end = array_slice($data,$to+1);
286 $new = array();
287 foreach($start_block as $block){
288 $new[] = $block;
289 }
290 foreach($block_to_free as $block){
291 $new[] = $block;
292 }
293 foreach($block_to_move as $block){
294 $new[] = $block;
295 }
296 foreach($block_to_end as $block){
297 $new[] = $block;
298 }
299 $old = $this->pap;
300 $this->pap = $new;
301 }
302 }
305 /* This function returns the id of the element
306 * where the current block ends
307 */
308 function get_block_end($start)
309 {
310 /* Only execute if this is a really a block element.
311 * Block elements is only sieve_if
312 */
313 if(in_array(get_class($this->pap[$start]),array("sieve_if","sieve_elsif","sieve_else"))){
315 $class = get_class($this->pap[$start]);
316 $next_class = get_class($this->pap[$start+1]);
317 $block_depth = 0;
319 $end = FALSE;
321 while(!$end && $start < count($this->pap)){
323 if($class == "sieve_block_start"){
324 $block_depth ++;
325 }
327 if($class == "sieve_block_end"){
328 $block_depth --;
329 }
331 if( $block_depth == 0 &&
332 $class == "sieve_block_end" &&
333 !in_array($next_class,array("sieve_else","sieve_elsif"))){
334 $end = TRUE;
335 $start --;
336 }
337 $start ++;
338 $class = get_class($this->pap[$start]);
340 if(isset($this->pap[$start+1])){
341 $next_class = get_class($this->pap[$start+1]);
342 }else{
343 $next_class ="";
344 }
345 }
346 }
347 return($start);
348 }
351 /* This function moves the single element at
352 * position $from to position $to.
353 */
354 function move_single_element($from,$to)
355 {
356 if($from == $to) {
357 return;
358 }
360 $ret = array();
361 $tmp = $this->pap;
363 $begin = array();
364 $middle = array();
365 $end = array();
366 $element = $this->pap[$from];
368 if($from > $to ){
370 /* Get all element in fron to element to move */
371 if($from != 0){
372 $begin = array_slice($tmp,0,$to);
373 }
375 /* Get all elements between */
376 $middle = array_slice($tmp,$to , ($from - ($to) ));
378 /* Get the rest */
379 $end = array_slice($tmp,$from+1);
381 foreach($begin as $data){
382 $ret[] = $data;
383 }
384 $ret[] = $element;
385 foreach($middle as $data){
386 $ret[] = $data;
387 }
388 foreach($end as $data){
389 $ret[] = $data;
390 }
391 $this->pap = $ret;
392 }
393 if($from < $to ){
395 /* Get all element in fron to element to move */
396 if($from != 0){
397 $begin = array_slice($tmp,0,$from);
398 }
400 /* Get all elements between */
401 $middle = array_slice($tmp,$from+1 , ($to - ($from)));
403 /* Get the rest */
404 $end = array_slice($tmp,$to+1);
406 foreach($begin as $data){
407 $ret[] = $data;
408 }
409 foreach($middle as $data){
410 $ret[] = $data;
411 }
412 $ret[] = $element;
413 foreach($end as $data){
414 $ret[] = $data;
415 }
416 $this->pap = $ret;
417 }
418 }
421 /* Returns the next free position where we
422 * can add a new sinle element
423 * $key_id = Current position
424 * $direction = Forward or backward.
425 */
426 function _get_next_free_move_slot($key_id,$direction)
427 {
428 $last_class = "";
429 $current_class ="";
430 $next_class = "";
432 /* After this elements we can add new elements
433 * without having any trouble.
434 */
435 $allowed_to_add_after = array("sieve_keep",
436 "sieve_require",
437 "sieve_stop",
438 "sieve_reject",
439 "sieve_fileinto",
440 "sieve_redirect",
441 "sieve_discard",
442 "sieve_comment",
443 "sieve_block_start"
444 );
446 /* Before this elements we can add new elements
447 * without having any trouble.
448 */
449 $allowed_to_add_before = array("sieve_keep",
450 "sieve_require",
451 "sieve_stop",
452 "sieve_reject",
453 "sieve_fileinto",
454 "sieve_comment",
455 "sieve_redirect",
456 "sieve_discard",
457 "sieve_if",
458 "sieve_block_end"
459 );
461 if($direction == "down"){
463 $test = $this->pap;
464 while($key_id < count($test)){
465 if(($key_id+1) == count($test)) {
466 return($key_id);
467 }
468 $key_id ++;
469 $current_class = get_class($test[$key_id]);
470 if(in_array($current_class, $allowed_to_add_after)){
471 return($key_id);
472 }
473 }
474 }else{
476 $test = $this->pap;
477 if($key_id == 0) {
478 return($key_id);
479 }
480 $key_id --;
481 while($key_id >=0 ){
482 $current_class = get_class($test[$key_id]);
483 if(in_array($current_class, $allowed_to_add_before)){
484 return($key_id);
485 }
486 $key_id --;
487 }
488 return(0);
489 }
490 }
493 /* Need to be reviewed */
494 function get_sieve_script()
495 {
496 $tmp ="";
497 if(count($this->pap)){
498 $buffer = "";
499 foreach($this->pap as $part) {
500 if(get_class($part) == "sieve_block_end"){
501 $buffer = substr($buffer,0,strlen($buffer)-(strlen(SIEVE_INDENT_TAB)));
502 }
503 $tmp2 = $part->get_sieve_script_part();
505 if(get_class($part) == "sieve_reject"){
506 $tmp.=$tmp2;
507 }else{
509 $tmp3 = split("\n",$tmp2);
510 foreach($tmp3 as $str){
511 $str2 = trim($str);
512 if(empty($str2)) continue;
513 $tmp.= $buffer.$str."\n";
514 }
515 }
516 if(get_class($part) == "sieve_block_start"){
517 $buffer .= SIEVE_INDENT_TAB;
518 }
519 }
520 }
521 if(!preg_match("/Generated by GOsa - Gonicus System Administrator/",$tmp)){
522 $tmp = "#Generated by GOsa - Gonicus System Administrator \n ".$tmp;
523 }
524 return($tmp);
525 }
527 function check()
528 {
529 $msgs = array();
531 /* Some logical checks.
532 * like : only sieve_comment can appear before require.
533 */
535 /* Ensure that there are no command before require
536 * - Get id of last require tag
537 * - Collect object types in from of this tag.
538 * - Check if there are tags collected that are not allowed
539 */
540 $last_found_at = -1;
541 $objs = array();
542 foreach($this->pap as $key => $obj){
543 if(get_class($obj) == "sieve_require"){
544 $last_found_at = $key;
545 }
546 }
547 foreach($this->pap as $key => $obj){
548 if($key == $last_found_at) break;
549 if(!in_array(get_class($obj),array("sieve_comment","sieve_require"))){
550 $objs[] = get_class($obj);
551 }
552 }
553 if(count($objs) && $last_found_at != -1){
554 $str = _("Require must be the first command in the script.");
555 $msgs[] = $str;
556 print_red($str);;
557 }
559 foreach($this->pap as $obj){
560 $o_msgs = $obj->check();
561 foreach($o_msgs as $o_msg){
562 $msgs[] = $o_msg;
563 }
564 }
565 return($msgs);
566 }
567 }
570 /* Create valid sieve string/string-list
571 * out of a given array
572 */
573 function sieve_create_strings($data,$force_string = FALSE)
574 {
575 $ret = "";
576 if(is_array($data)){
577 if(count($data) == 1){
578 $ret = "\"";
579 foreach($data as $dat){
580 $ret .=$dat;
581 }
582 $ret.="\"";
583 }else{
584 foreach($data as $dat){
585 $ret.= "\"";
586 $ret.=$dat;
587 $ret.="\", ";
588 }
589 $ret = preg_replace("/,$/","",trim($ret));
590 $ret = "[".$ret."]";
591 }
592 }else{
594 $Multiline = preg_match("/\n/",$data);
595 $data = preg_replace("/\r/","",$data);;
597 if($Multiline && !$force_string){
598 $ret = "text: \r\n".$data."\r\n.\r\n";
599 }else{
600 $ret = "\"".$data."\"";
601 }
602 }
603 $ret = preg_replace("/\"\"/","\"",$ret);
604 $ret = preg_replace("/\n/","\r\n",$ret);
606 return($ret);
607 }
609 /* This checks if there is a string at the current position
610 * in the token array.
611 * If there is a string list at the current position,
612 * this function will return a complete list of all
613 * strings used in this list.
614 * It also returns an offset of the last token position
615 */
616 function sieve_get_strings($data,$id)
617 {
618 $ret = array();
619 if($data[$id]['class'] == "left-bracket"){
620 while($data[$id]['class'] != "right-bracket" && $id < count($data)){
622 if($data[$id]['class'] == "quoted-string"){
623 $ret[] = $data[$id]['text'];
624 }
625 $id ++;
626 }
627 }elseif($data[$id]['class'] == "quoted-string"){
628 $ret[] = $data[$id]['text'];
629 }elseif($data[$id]['class'] == "number"){
630 $ret[] = $data[$id]['text'];
631 }elseif($data[$id]['class'] == "multi-line"){
632 $str = trim(preg_replace("/^text:/","",$data[$id]['text']));
633 $str = trim(preg_replace("/\.$/","",$str));
634 $ret[] = $str;
635 }
637 return(array("OFFSET" => $id, "STRINGS" => $ret));
638 }
640 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
641 ?>