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 var $add_new = FALSE;
20 var $add_new_id = 0;
21 var $add_type = "top";
22 var $add_element_type = "";
24 function execute()
25 {
26 return($this->dump());
27 }
29 /* Create a html interface for the current sieve filter
30 */
31 function dump()
32 {
33 error_reporting(E_ALL);
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"),$node_id);
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"),$node_id);
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);
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"))){
180 $block_start= $key_id;
181 $block_end = $this->get_block_end($key_id);
182 for($i = $block_start ; $i <= $block_end ; $i ++ ){
183 unset($this->pap[$i]);
184 }
185 }else{
186 unset($this->pap[$key_id]);
187 }
188 $tmp = array();
189 foreach($this->pap as $element){
190 $tmp[] = $element;
191 }
192 $this->pap = $tmp;
193 }
196 /* This function moves a given element to another position.
197 * Single elements like "keep;" will simply be moved one posisition down/up.
198 * Multiple elements like if-elsif-else will be moved as block.
199 *
200 * $key_id specified the element that should be moved.
201 * $direction specifies to move elements "up" or "down"
202 */
203 function move_up_down($key_id,$direction = "down")
204 {
206 /* Get the current element to decide what to move. */
207 $e_class = get_class($this->pap[$key_id]);
209 if(in_array($e_class,array("sieve_if"))){
210 $block_start= $key_id;
211 $block_end = $this->get_block_end($key_id);
213 /* Depending on the direction move up down */
214 if($direction == "down"){
215 $next_free = $this->_get_next_free_move_slot($block_end,$direction);
216 }else{
217 $next_free = $this->_get_next_free_move_slot($block_start,$direction);
218 }
220 /* Move the given block */
221 $this->move_multiple_elements($block_start,$block_end,$next_free);
222 }
224 if(in_array($e_class,array( "sieve_stop",
225 "sieve_keep",
226 "sieve_require",
227 "sieve_comment",
228 "sieve_vacation",
229 "sieve_stop",
230 "sieve_reject",
231 "sieve_fileinto",
232 "sieve_redirect",
233 "sieve_discard"))){
234 $this->move_single_element($key_id,$this->_get_next_free_move_slot($key_id,$direction));
235 }
236 }
239 /* Move the given block to position */
240 function move_multiple_elements($start,$end,$to)
241 {
242 /* Use class names for testing */
243 $data = $this->pap;
245 /* Get block to move */
246 $block_to_move = array_slice($data,$start, ($end - $start +1));
248 /* We want do move this block up */
249 if($end > $to){
251 /* Get start block */
252 $start_block = array_slice($data,0,$to);
254 /* Get Get all elements between the block to move
255 * and next free position
256 */
257 $block_to_free = array_slice($data,$to ,$start - $to );
258 $block_to_end = array_slice($data,$end+1);
259 $new = array();
260 foreach($start_block as $block){
261 $new[] = $block;
262 }
263 foreach($block_to_move as $block){
264 $new[] = $block;
265 }
266 foreach($block_to_free as $block){
267 $new[] = $block;
268 }
269 foreach($block_to_end as $block){
270 $new[] = $block;
271 }
272 $old = $this->pap;
273 $this->pap = $new;
274 }
277 /* We want to move this block down. */
278 if($to > $end){
280 /* Get start block */
281 $start_block = array_slice($data,0,$start);
283 /* Get Get all elements between the block to move
284 * and next free position
285 */
286 $block_to_free = array_slice($data,$end +1,($to - $end ));
288 /* Get the rest
289 */
290 $block_to_end = array_slice($data,$to+1);
292 $new = array();
293 foreach($start_block as $block){
294 $new[] = $block;
295 }
296 foreach($block_to_free as $block){
297 $new[] = $block;
298 }
299 foreach($block_to_move as $block){
300 $new[] = $block;
301 }
302 foreach($block_to_end as $block){
303 $new[] = $block;
304 }
305 $old = $this->pap;
306 $this->pap = $new;
307 }
308 }
311 /* This function returns the id of the element
312 * where the current block ends
313 */
314 function get_block_end($start)
315 {
316 /* Only execute if this is a really a block element.
317 * Block elements is only sieve_if
318 */
319 if(in_array(get_class($this->pap[$start]),array("sieve_if"))){
321 $class = get_class($this->pap[$start]);
322 $next_class = get_class($this->pap[$start+1]);
323 $block_depth = 0;
325 $end = FALSE;
327 while(!$end && $start < count($this->pap)){
329 if($class == "sieve_block_start"){
330 $block_depth ++;
331 }
333 if($class == "sieve_block_end"){
334 $block_depth --;
335 }
337 if( $block_depth == 0 &&
338 $class == "sieve_block_end" &&
339 !in_array($next_class,array("sieve_else","sieve_elsif"))){
340 $end = TRUE;
341 $start --;
342 }
343 $start ++;
344 $class = get_class($this->pap[$start]);
345 $next_class = get_class($this->pap[$start+1]);
346 }
347 }
348 return($start);
349 }
352 /* This function moves the single element at
353 * position $from to position $to.
354 */
355 function move_single_element($from,$to)
356 {
357 if($from == $to) {
358 return;
359 }
361 $ret = array();
362 $tmp = $this->pap;
364 $begin = array();
365 $middle = array();
366 $end = array();
367 $element = $this->pap[$from];
369 if($from > $to ){
371 /* Get all element in fron to element to move */
372 if($from != 0){
373 $begin = array_slice($tmp,0,$to);
374 }
376 /* Get all elements between */
377 $middle = array_slice($tmp,$to , ($from - ($to) ));
379 /* Get the rest */
380 $end = array_slice($tmp,$from+1);
382 foreach($begin as $data){
383 $ret[] = $data;
384 }
385 $ret[] = $element;
386 foreach($middle as $data){
387 $ret[] = $data;
388 }
389 foreach($end as $data){
390 $ret[] = $data;
391 }
392 $this->pap = $ret;
393 }
394 if($from < $to ){
396 /* Get all element in fron to element to move */
397 if($from != 0){
398 $begin = array_slice($tmp,0,$from);
399 }
401 /* Get all elements between */
402 $middle = array_slice($tmp,$from+1 , ($to - ($from)));
404 /* Get the rest */
405 $end = array_slice($tmp,$to+1);
407 foreach($begin as $data){
408 $ret[] = $data;
409 }
410 foreach($middle as $data){
411 $ret[] = $data;
412 }
413 $ret[] = $element;
414 foreach($end as $data){
415 $ret[] = $data;
416 }
417 $this->pap = $ret;
418 }
419 }
422 /* Returns the next free position where we
423 * can add a new sinle element
424 * $key_id = Current position
425 * $direction = Forward or backward.
426 */
427 function _get_next_free_move_slot($key_id,$direction)
428 {
429 $last_class = "";
430 $current_class ="";
431 $next_class = "";
433 /* After this elements we can add new elements
434 * without having any trouble.
435 */
436 $allowed_to_add_after = array("sieve_keep",
437 "sieve_require",
438 "sieve_stop",
439 "sieve_reject",
440 "sieve_fileinto",
441 "sieve_redirect",
442 "sieve_discard",
443 "sieve_comment",
444 "sieve_block_start"
445 );
447 /* Before this elements we can add new elements
448 * without having any trouble.
449 */
450 $allowed_to_add_before = array("sieve_keep",
451 "sieve_require",
452 "sieve_stop",
453 "sieve_reject",
454 "sieve_fileinto",
455 "sieve_comment",
456 "sieve_redirect",
457 "sieve_discard",
458 "sieve_if",
459 "sieve_block_end"
460 );
462 if($direction == "down"){
464 $test = $this->pap;
465 while($key_id < count($test)){
466 if(($key_id+1) == count($test)) {
467 return($key_id);
468 }
469 $key_id ++;
470 $current_class = get_class($test[$key_id]);
471 if(in_array($current_class, $allowed_to_add_after)){
472 return($key_id);
473 }
474 }
475 }else{
477 $test = $this->pap;
478 if($key_id == 0) {
479 return($key_id);
480 }
481 $key_id --;
482 while($key_id >=0 ){
483 $current_class = get_class($test[$key_id]);
484 if(in_array($current_class, $allowed_to_add_before)){
485 return($key_id);
486 }
487 $key_id --;
488 }
489 return(0);
490 }
491 }
494 /* Need to be reviewed */
495 function get_sieve_script()
496 {
497 $tmp ="";
498 if(count($this->pap)){
499 $buffer = "";
500 foreach($this->pap as $part) {
501 if(get_class($part) == "sieve_block_end"){
502 $buffer = substr($buffer,0,strlen($buffer)-(strlen(SIEVE_INDENT_TAB)));
503 }
504 $tmp2 = $part->get_sieve_script_part();
506 if(get_class($part) == "sieve_reject"){
507 $tmp.=$tmp2;
508 }else{
510 $tmp3 = split("\n",$tmp2);
511 foreach($tmp3 as $str){
512 $str2 = trim($str);
513 if(empty($str2)) continue;
514 $tmp.= $buffer.$str."\n";
515 }
516 }
517 if(get_class($part) == "sieve_block_start"){
518 $buffer .= SIEVE_INDENT_TAB;
519 }
520 }
521 }
522 if(!preg_match("/Generated by GOsa - Gonicus System Administrator/",$tmp)){
523 $tmp = "#Generated by GOsa - Gonicus System Administrator \n ".$tmp;
524 }
525 return($tmp);
526 }
528 function Add_Element()
529 {
530 $tmp = array("ELEMENTS" => array(array("class" => "qouted-string","text"=> "Bla bla, later more")));
531 $this->pap[] = new sieve_comment($tmp,rand(1000,100000));
532 }
534 function check()
535 {
536 $msgs = array();
538 /* Some logical checks.
539 * like : only sieve_comment can appear before require.
540 */
542 /* Ensure that there are no command before require
543 * - Get id of last require tag
544 * - Collect object types in from of this tag.
545 * - Check if there are tags collected that are not allowed
546 */
547 $last_found_at = -1;
548 $objs = array();
549 foreach($this->pap as $key => $obj){
550 if(get_class($obj) == "sieve_require"){
551 $last_found_at = $key;
552 }
553 }
554 foreach($this->pap as $key => $obj){
555 if($key == $last_found_at) break;
556 if(!in_array(get_class($obj),array("sieve_comment","sieve_require"))){
557 $objs[] = get_class($obj);
558 }
559 }
560 if(count($objs) && $last_found_at != -1){
561 $str = _("Require must be the first command in the script.");
562 $msgs[] = $str;
563 print_red($str);;
564 }
566 foreach($this->pap as $obj){
567 $o_msgs = $obj->check();
568 foreach($o_msgs as $o_msg){
569 $msgs[] = $o_msg;
570 }
571 }
572 return($msgs);
573 }
574 }
577 /* Create valid sieve string/string-list
578 * out of a given array
579 */
580 function sieve_create_strings($data,$force_string = FALSE)
581 {
582 $ret = "";
583 if(is_array($data)){
584 if(count($data) == 1){
585 $ret = "\"";
586 foreach($data as $dat){
587 $ret .=$dat;
588 }
589 $ret.="\"";
590 }else{
591 foreach($data as $dat){
592 $ret.= "\"";
593 $ret.=$dat;
594 $ret.="\", ";
595 }
596 $ret = preg_replace("/,$/","",trim($ret));
597 $ret = "[".$ret."]";
598 }
599 }else{
601 $Multiline = preg_match("/\n/",$data);
602 $data = preg_replace("/\r/","",$data);;
604 if($Multiline && !$force_string){
605 $ret = "text: \r\n".$data."\r\n.\r\n";
606 }else{
607 $ret = "\"".$data."\"";
608 }
609 }
610 $ret = preg_replace("/\"\"/","\"",$ret);
611 $ret = preg_replace("/\n/","\r\n",$ret);
613 return($ret);
614 }
616 /* This checks if there is a string at the current position
617 * in the token array.
618 * If there is a string list at the current position,
619 * this function will return a complete list of all
620 * strings used in this list.
621 * It also returns an offset of the last token position
622 */
623 function sieve_get_strings($data,$id)
624 {
625 $ret = array();
626 if($data[$id]['class'] == "left-bracket"){
627 while($data[$id]['class'] != "right-bracket" && $id < count($data)){
629 if($data[$id]['class'] == "quoted-string"){
630 $ret[] = $data[$id]['text'];
631 }
632 $id ++;
633 }
634 }elseif($data[$id]['class'] == "quoted-string"){
635 $ret[] = $data[$id]['text'];
636 }elseif($data[$id]['class'] == "number"){
637 $ret[] = $data[$id]['text'];
638 }elseif($data[$id]['class'] == "multi-line"){
639 $str = trim(preg_replace("/^text:/","",$data[$id]['text']));
640 $str = trim(preg_replace("/\.$/","",$str));
641 $ret[] = $str;
642 }
644 return(array("OFFSET" => $id, "STRINGS" => $ret));
645 }
647 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
648 ?>