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"))){
173 $block_start= $key_id;
174 $block_end = $this->get_block_end($key_id);
175 for($i = $block_start ; $i <= $block_end ; $i ++ ){
176 unset($this->pap[$i]);
177 }
178 }else{
179 unset($this->pap[$key_id]);
180 }
181 $tmp = array();
182 foreach($this->pap as $element){
183 $tmp[] = $element;
184 }
185 $this->pap = $tmp;
186 }
189 /* This function moves a given element to another position.
190 * Single elements like "keep;" will simply be moved one posisition down/up.
191 * Multiple elements like if-elsif-else will be moved as block.
192 *
193 * $key_id specified the element that should be moved.
194 * $direction specifies to move elements "up" or "down"
195 */
196 function move_up_down($key_id,$direction = "down")
197 {
199 /* Get the current element to decide what to move. */
200 $e_class = get_class($this->pap[$key_id]);
202 if(in_array($e_class,array("sieve_if"))){
203 $block_start= $key_id;
204 $block_end = $this->get_block_end($key_id);
206 /* Depending on the direction move up down */
207 if($direction == "down"){
208 $next_free = $this->_get_next_free_move_slot($block_end,$direction);
209 }else{
210 $next_free = $this->_get_next_free_move_slot($block_start,$direction);
211 }
213 /* Move the given block */
214 $this->move_multiple_elements($block_start,$block_end,$next_free);
215 }
217 if(in_array($e_class,array( "sieve_stop",
218 "sieve_keep",
219 "sieve_require",
220 "sieve_comment",
221 "sieve_vacation",
222 "sieve_stop",
223 "sieve_reject",
224 "sieve_fileinto",
225 "sieve_redirect",
226 "sieve_discard"))){
227 $this->move_single_element($key_id,$this->_get_next_free_move_slot($key_id,$direction));
228 }
229 }
232 /* Move the given block to position */
233 function move_multiple_elements($start,$end,$to)
234 {
235 /* Use class names for testing */
236 $data = $this->pap;
238 /* Get block to move */
239 $block_to_move = array_slice($data,$start, ($end - $start +1));
241 /* We want do move this block up */
242 if($end > $to){
244 /* Get start block */
245 $start_block = array_slice($data,0,$to);
247 /* Get Get all elements between the block to move
248 * and next free position
249 */
250 $block_to_free = array_slice($data,$to ,$start - $to );
251 $block_to_end = array_slice($data,$end+1);
252 $new = array();
253 foreach($start_block as $block){
254 $new[] = $block;
255 }
256 foreach($block_to_move as $block){
257 $new[] = $block;
258 }
259 foreach($block_to_free as $block){
260 $new[] = $block;
261 }
262 foreach($block_to_end as $block){
263 $new[] = $block;
264 }
265 $old = $this->pap;
266 $this->pap = $new;
267 }
270 /* We want to move this block down. */
271 if($to > $end){
273 /* Get start block */
274 $start_block = array_slice($data,0,$start);
276 /* Get Get all elements between the block to move
277 * and next free position
278 */
279 $block_to_free = array_slice($data,$end +1,($to - $end ));
281 /* Get the rest
282 */
283 $block_to_end = array_slice($data,$to+1);
285 $new = array();
286 foreach($start_block as $block){
287 $new[] = $block;
288 }
289 foreach($block_to_free as $block){
290 $new[] = $block;
291 }
292 foreach($block_to_move as $block){
293 $new[] = $block;
294 }
295 foreach($block_to_end as $block){
296 $new[] = $block;
297 }
298 $old = $this->pap;
299 $this->pap = $new;
300 }
301 }
304 /* This function returns the id of the element
305 * where the current block ends
306 */
307 function get_block_end($start)
308 {
309 /* Only execute if this is a really a block element.
310 * Block elements is only sieve_if
311 */
312 if(in_array(get_class($this->pap[$start]),array("sieve_if"))){
314 $class = get_class($this->pap[$start]);
315 $next_class = get_class($this->pap[$start+1]);
316 $block_depth = 0;
318 $end = FALSE;
320 while(!$end && $start < count($this->pap)){
322 if($class == "sieve_block_start"){
323 $block_depth ++;
324 }
326 if($class == "sieve_block_end"){
327 $block_depth --;
328 }
330 if( $block_depth == 0 &&
331 $class == "sieve_block_end" &&
332 !in_array($next_class,array("sieve_else","sieve_elsif"))){
333 $end = TRUE;
334 $start --;
335 }
336 $start ++;
337 $class = get_class($this->pap[$start]);
338 $next_class = get_class($this->pap[$start+1]);
339 }
340 }
341 return($start);
342 }
345 /* This function moves the single element at
346 * position $from to position $to.
347 */
348 function move_single_element($from,$to)
349 {
350 if($from == $to) {
351 return;
352 }
354 $ret = array();
355 $tmp = $this->pap;
357 $begin = array();
358 $middle = array();
359 $end = array();
360 $element = $this->pap[$from];
362 if($from > $to ){
364 /* Get all element in fron to element to move */
365 if($from != 0){
366 $begin = array_slice($tmp,0,$to);
367 }
369 /* Get all elements between */
370 $middle = array_slice($tmp,$to , ($from - ($to) ));
372 /* Get the rest */
373 $end = array_slice($tmp,$from+1);
375 foreach($begin as $data){
376 $ret[] = $data;
377 }
378 $ret[] = $element;
379 foreach($middle as $data){
380 $ret[] = $data;
381 }
382 foreach($end as $data){
383 $ret[] = $data;
384 }
385 $this->pap = $ret;
386 }
387 if($from < $to ){
389 /* Get all element in fron to element to move */
390 if($from != 0){
391 $begin = array_slice($tmp,0,$from);
392 }
394 /* Get all elements between */
395 $middle = array_slice($tmp,$from+1 , ($to - ($from)));
397 /* Get the rest */
398 $end = array_slice($tmp,$to+1);
400 foreach($begin as $data){
401 $ret[] = $data;
402 }
403 foreach($middle as $data){
404 $ret[] = $data;
405 }
406 $ret[] = $element;
407 foreach($end as $data){
408 $ret[] = $data;
409 }
410 $this->pap = $ret;
411 }
412 }
415 /* Returns the next free position where we
416 * can add a new sinle element
417 * $key_id = Current position
418 * $direction = Forward or backward.
419 */
420 function _get_next_free_move_slot($key_id,$direction)
421 {
422 $last_class = "";
423 $current_class ="";
424 $next_class = "";
426 /* After this elements we can add new elements
427 * without having any trouble.
428 */
429 $allowed_to_add_after = array("sieve_keep",
430 "sieve_require",
431 "sieve_stop",
432 "sieve_reject",
433 "sieve_fileinto",
434 "sieve_redirect",
435 "sieve_discard",
436 "sieve_comment",
437 "sieve_block_start"
438 );
440 /* Before this elements we can add new elements
441 * without having any trouble.
442 */
443 $allowed_to_add_before = array("sieve_keep",
444 "sieve_require",
445 "sieve_stop",
446 "sieve_reject",
447 "sieve_fileinto",
448 "sieve_comment",
449 "sieve_redirect",
450 "sieve_discard",
451 "sieve_if",
452 "sieve_block_end"
453 );
455 if($direction == "down"){
457 $test = $this->pap;
458 while($key_id < count($test)){
459 if(($key_id+1) == count($test)) {
460 return($key_id);
461 }
462 $key_id ++;
463 $current_class = get_class($test[$key_id]);
464 if(in_array($current_class, $allowed_to_add_after)){
465 return($key_id);
466 }
467 }
468 }else{
470 $test = $this->pap;
471 if($key_id == 0) {
472 return($key_id);
473 }
474 $key_id --;
475 while($key_id >=0 ){
476 $current_class = get_class($test[$key_id]);
477 if(in_array($current_class, $allowed_to_add_before)){
478 return($key_id);
479 }
480 $key_id --;
481 }
482 return(0);
483 }
484 }
487 /* Need to be reviewed */
488 function get_sieve_script()
489 {
490 $tmp ="";
491 if(count($this->pap)){
492 $buffer = "";
493 foreach($this->pap as $part) {
494 if(get_class($part) == "sieve_block_end"){
495 $buffer = substr($buffer,0,strlen($buffer)-(strlen(SIEVE_INDENT_TAB)));
496 }
497 $tmp2 = $part->get_sieve_script_part();
499 if(get_class($part) == "sieve_reject"){
500 $tmp.=$tmp2;
501 }else{
503 $tmp3 = split("\n",$tmp2);
504 foreach($tmp3 as $str){
505 $str2 = trim($str);
506 if(empty($str2)) continue;
507 $tmp.= $buffer.$str."\n";
508 }
509 }
510 if(get_class($part) == "sieve_block_start"){
511 $buffer .= SIEVE_INDENT_TAB;
512 }
513 }
514 }
515 if(!preg_match("/Generated by GOsa - Gonicus System Administrator/",$tmp)){
516 $tmp = "#Generated by GOsa - Gonicus System Administrator \n ".$tmp;
517 }
518 return($tmp);
519 }
521 function check()
522 {
523 $msgs = array();
525 /* Some logical checks.
526 * like : only sieve_comment can appear before require.
527 */
529 /* Ensure that there are no command before require
530 * - Get id of last require tag
531 * - Collect object types in from of this tag.
532 * - Check if there are tags collected that are not allowed
533 */
534 $last_found_at = -1;
535 $objs = array();
536 foreach($this->pap as $key => $obj){
537 if(get_class($obj) == "sieve_require"){
538 $last_found_at = $key;
539 }
540 }
541 foreach($this->pap as $key => $obj){
542 if($key == $last_found_at) break;
543 if(!in_array(get_class($obj),array("sieve_comment","sieve_require"))){
544 $objs[] = get_class($obj);
545 }
546 }
547 if(count($objs) && $last_found_at != -1){
548 $str = _("Require must be the first command in the script.");
549 $msgs[] = $str;
550 print_red($str);;
551 }
553 foreach($this->pap as $obj){
554 $o_msgs = $obj->check();
555 foreach($o_msgs as $o_msg){
556 $msgs[] = $o_msg;
557 }
558 }
559 return($msgs);
560 }
561 }
564 /* Create valid sieve string/string-list
565 * out of a given array
566 */
567 function sieve_create_strings($data,$force_string = FALSE)
568 {
569 $ret = "";
570 if(is_array($data)){
571 if(count($data) == 1){
572 $ret = "\"";
573 foreach($data as $dat){
574 $ret .=$dat;
575 }
576 $ret.="\"";
577 }else{
578 foreach($data as $dat){
579 $ret.= "\"";
580 $ret.=$dat;
581 $ret.="\", ";
582 }
583 $ret = preg_replace("/,$/","",trim($ret));
584 $ret = "[".$ret."]";
585 }
586 }else{
588 $Multiline = preg_match("/\n/",$data);
589 $data = preg_replace("/\r/","",$data);;
591 if($Multiline && !$force_string){
592 $ret = "text: \r\n".$data."\r\n.\r\n";
593 }else{
594 $ret = "\"".$data."\"";
595 }
596 }
597 $ret = preg_replace("/\"\"/","\"",$ret);
598 $ret = preg_replace("/\n/","\r\n",$ret);
600 return($ret);
601 }
603 /* This checks if there is a string at the current position
604 * in the token array.
605 * If there is a string list at the current position,
606 * this function will return a complete list of all
607 * strings used in this list.
608 * It also returns an offset of the last token position
609 */
610 function sieve_get_strings($data,$id)
611 {
612 $ret = array();
613 if($data[$id]['class'] == "left-bracket"){
614 while($data[$id]['class'] != "right-bracket" && $id < count($data)){
616 if($data[$id]['class'] == "quoted-string"){
617 $ret[] = $data[$id]['text'];
618 }
619 $id ++;
620 }
621 }elseif($data[$id]['class'] == "quoted-string"){
622 $ret[] = $data[$id]['text'];
623 }elseif($data[$id]['class'] == "number"){
624 $ret[] = $data[$id]['text'];
625 }elseif($data[$id]['class'] == "multi-line"){
626 $str = trim(preg_replace("/^text:/","",$data[$id]['text']));
627 $str = trim(preg_replace("/\.$/","",$str));
628 $ret[] = $str;
629 }
631 return(array("OFFSET" => $id, "STRINGS" => $ret));
632 }
634 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
635 ?>