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 var $Mode = "Structured";
26 function execute()
27 {
28 foreach($_POST as $name => $value){
29 if(preg_match("/Add_Test_Object_/",$name)) {
30 $name = preg_replace("/Add_Test_Object_/","",$name);
31 $name = preg_replace("/_(x|y)$/","",$name);
33 $test_types_to_add = array(
34 "address" =>_("Address"),
35 "header" =>_("Header"),
36 "envelope"=>_("Envelope"),
37 "size" =>_("Size"),
38 "exists" =>_("Exists"),
39 "allof" =>_("All of"),
40 "anyof" =>_("Any of"),
41 "true" =>_("True"),
42 "false" =>_("False"));
44 $smarty = get_smarty();
45 $smarty->assign("ID",$name);
46 $smarty->assign("test_types_to_add",$test_types_to_add);
47 $ret = $smarty->fetch(get_template_path("templates/select_test_type.tpl",TRUE,dirname(__FILE__)));
48 return($ret);
49 }
50 }
52 return($this->dump());
53 }
55 /* Create a html interface for the current sieve filter
56 */
57 function dump()
58 {
59 error_reporting(E_ALL);
61 /**************
62 * Handle new elements
63 **************/
65 if(isset($_POST['select_new_element_type_cancel'])){
66 $this->add_new = FALSE;
67 }
69 if($this->add_new){
71 $element_types= array(
72 "sieve_keep" => _("Keep"),
73 "sieve_comment" => _("Comment"),
74 "sieve_fileinto" => _("File into"),
75 "sieve_keep" => _("Keep"),
76 "sieve_discard" => _("Discard"),
77 "sieve_redirect" => _("Redirect"),
78 "sieve_reject" => _("Reject"),
79 "sieve_require" => _("Require"),
80 "sieve_stop" => _("Stop"),
81 "sieve_vacation" => _("Vacation message"),
82 "sieve_if" => _("If"));
85 /* Element selected */
86 if(isset($_POST['element_type']) && isset($element_types[$_POST['element_type']])){
87 $this->add_element_type = $_POST['element_type'];
88 }
90 /* Create new element and add it at the selected position */
91 if(isset($_POST['select_new_element_type'])){
93 $ele[] = new $this->add_element_type(NULL, preg_replace("/[^0-9]/","",microtime()));
94 if($this->add_element_type == "sieve_if"){
95 $ele[] = new sieve_block_start(NULL,preg_replace("/[^0-9]/","",microtime()));
96 $ele[] = new sieve_block_end(NULL,preg_replace("/[^0-9]/","",microtime()));
97 }
98 $start = $end = array();
99 $found = false;
101 /* Add above current element*/
102 if($this->add_type == "top"){
103 foreach($this->pap as $key => $obj){
104 if($obj->object_id == $this->add_new_id){
105 $found = true;
106 }
107 if(!$found){
108 $start[] = $obj;
109 }else{
110 $end[] = $obj;
111 }
112 }
113 }else{
114 /* Add below current element */
115 foreach($this->pap as $key => $obj){
116 if(!$found){
117 $start[] = $obj;
118 }else{
119 $end[] = $obj;
120 }
121 if($obj->object_id == $this->add_new_id){
122 $found = true;
123 }
124 }
125 }
127 /* Only add, if current element could be located */
128 if($found){
129 $new = array();
130 foreach($start as $obj){
131 $new[] = $obj;
132 }
133 foreach($ele as $el){
134 $new[] = $el;
135 }
136 foreach($end as $obj){
137 $new[] = $obj;
138 }
139 $this->pap = $new;
140 $this->add_new = FALSE;
141 }else{
142 print_red(_("Something went wrong while adding a new entry."));
143 }
144 }
146 }
148 /* Only display select dialog if it is necessary */
149 if($this->add_new){
150 $smarty = get_smarty();
151 $smarty->assign("element_types",$element_types );
152 $smarty->assign("element_type",$this->add_element_type);
153 $str = $smarty->fetch(get_template_path("templates/add_element.tpl",TRUE,dirname(__FILE__)));
154 return($str);
155 }
157 /* Only parse the tokens once */
158 if(!count($this->pap)){
159 $this->dump_ = "";
160 $this->mode_stack = array();
161 $this->pap = array();
162 $this->doDump_(0, '', true);
163 }
165 /* Create html results */
166 $smarty = get_smarty();
168 $this -> dump_ = "";
169 foreach($this->pap as $key => $object){
170 if(is_object($object)){
171 $this->dump_ .= preg_replace("/>/",">\n",$object->execute());
172 }
173 }
175 return($this->dump_);
176 }
179 /* This function walks through the object tree generated by the "Parse" class.
180 * All Commands will be resolved and grouped. So the Commands and their
181 * parameter are combined. Like "IF" and ":comparator"...
182 */
183 function doDump_($node_id, $prefix, $last,$num = 1)
184 {
185 /* Indicates that current comman will only be valid for a single line.
186 * this command type will be removed from mode_stack after displaying it.
187 */
188 $rewoke_last = FALSE;
190 /* Get node */
191 $node = $this->nodes_[$node_id];
193 /* This closes the last mode */
194 if($node['class'] == "block-start"){
195 $tmp = array_pop($this->mode_stack);
196 $this->handle_elements($tmp,$node_id);
197 $this->handle_elements(array("TYPE" => "block_start"),$node_id);
198 }
200 /* This closes the last mode */
201 if($node['class'] == "block-end"){
202 $tmp = array_pop($this->mode_stack);
203 $this->handle_elements($tmp,$node_id);
204 $this->handle_elements(array("TYPE" => "block_end"),$node_id);
205 }
207 /* Semicolon indicates a new command */
208 if($node['class'] == "semicolon"){
209 $tmp =array_pop($this->mode_stack);
210 $this->handle_elements($tmp,$node_id);
211 }
213 /* Handle comments */
214 if($node['class'] == "comment"){
215 $this->mode_stack[] = array("TYPE" => $node['class']);
216 $rewoke_last = TRUE;
217 }
219 /* Handle identifiers */
220 $identifiers = array("else","if","elsif","end","reject","redirect","vacation","keep","discard","comment","fileinto","require","stop");
221 if($node['class'] == "identifier" && in_array($node['text'],$identifiers)){
222 $this->mode_stack[] = array("TYPE" => $node['text']);
223 }
225 /* Add current node to current command stack */
226 end($this->mode_stack);
227 $key = key($this->mode_stack);
228 $this->mode_stack[$key]['ELEMENTS'][] = $node;
230 /* Remove last mode from mode stack, cause it was only valid for a single line */
231 if($rewoke_last){
232 $tmp =array_pop($this->mode_stack);
233 $this->handle_elements($tmp,$node_id);
234 }
236 /* If this is a sub element, just call this for all childs */
237 if(isset($this->childs_[$node_id])){
238 $childs = $this->childs_[$node_id];
239 for ($i=0; $i<count($childs); ++$i)
240 {
241 $c_last = false;
242 if ($i+1 == count($childs))
243 {
244 $c_last = true;
245 }
246 $this->doDump_($childs[$i], "", $num);
247 }
248 }
249 }
252 /* Create a class for each resolved object.
253 * And append this class to a list of objects.
254 */
255 function handle_elements($data,$id)
256 {
257 if(!isset($data['TYPE'])){
258 return;
259 }
260 $type = $data['TYPE'];
262 $class_name= "sieve_".$type ;
263 if(class_exists($class_name)){
264 $this->pap[] = new $class_name($data,$id);
265 }else{
266 echo "<font color='red'>Missing : ".$class_name."</font>"."<br>";
267 }
268 }
270 function save_object()
271 {
272 reset($this->pap);
273 foreach($this->pap as $key => $obj){
275 if(in_array(get_class($obj),array("sieve_if",
276 "sieve_elsif",
277 "sieve_vacation",
278 "sieve_comment",
279 "sieve_reject",
280 "sieve_fileinto",
281 "sieve_require",
282 "sieve_redirect"))){
285 if(isset($this->pap[$key]) && method_exists($this->pap[$key],"save_object")){
286 $this->pap[$key]->save_object();
287 }
288 }
290 $once = TRUE;
291 foreach($_POST as $name => $value){
293 if(isset($obj->object_id) && preg_match("/^Remove_Object_".$obj->object_id."_/",$name) && $once){
294 $once = FALSE;
295 $this->remove_object($key);
296 }
297 if(isset($obj->object_id) && preg_match("/^Move_Up_Object_".$obj->object_id."_/",$name) && $once){
298 $this->move_up_down($key,"up");
299 $once = FALSE;
300 }
301 if(isset($obj->object_id) && preg_match("/^Move_Down_Object_".$obj->object_id."_/",$name) && $once){
302 $this->move_up_down($key,"down");
303 $once = FALSE;
304 }
305 if(isset($obj->object_id) && preg_match("/^Add_Object_Top_".$obj->object_id."_/",$name) && $once){
306 $this->add_new_object($obj->object_id,"top");
307 $once = FALSE;
308 }
309 if(isset($obj->object_id) && preg_match("/^Add_Object_Bottom_".$obj->object_id."_/",$name) && $once){
310 $this->add_new_object($obj->object_id,"bottom");
311 $once = FALSE;
312 }
313 }
314 }
315 }
318 /* Add a new object at the given position */
319 function add_new_object($id,$top_bottom = "bottom")
320 {
321 $this->add_new = TRUE;
322 $this->add_new_id = $id;
323 $this->add_type = $top_bottom;
324 }
327 /* Remove the object at the given position */
328 function remove_object($key_id)
329 {
330 $class = get_class($this->pap[$key_id]);
331 if(in_array($class,array("sieve_if"))){
332 $block_start= $key_id;
333 $block_end = $this->get_block_end($key_id);
334 for($i = $block_start ; $i <= $block_end ; $i ++ ){
335 unset($this->pap[$i]);
336 }
337 }else{
338 unset($this->pap[$key_id]);
339 }
340 $tmp = array();
341 foreach($this->pap as $element){
342 $tmp[] = $element;
343 }
344 $this->pap = $tmp;
345 }
348 /* This function moves a given element to another position.
349 * Single elements like "keep;" will simply be moved one posisition down/up.
350 * Multiple elements like if-elsif-else will be moved as block.
351 *
352 * $key_id specified the element that should be moved.
353 * $direction specifies to move elements "up" or "down"
354 */
355 function move_up_down($key_id,$direction = "down")
356 {
358 /* Get the current element to decide what to move. */
359 $e_class = get_class($this->pap[$key_id]);
361 if(in_array($e_class,array("sieve_if"))){
362 $block_start= $key_id;
363 $block_end = $this->get_block_end($key_id);
365 /* Depending on the direction move up down */
366 if($direction == "down"){
367 $next_free = $this->_get_next_free_move_slot($block_end,$direction);
368 }else{
369 $next_free = $this->_get_next_free_move_slot($block_start,$direction);
370 }
372 /* Move the given block */
373 $this->move_multiple_elements($block_start,$block_end,$next_free);
374 }
376 if(in_array($e_class,array( "sieve_stop",
377 "sieve_keep",
378 "sieve_require",
379 "sieve_comment",
380 "sieve_stop",
381 "sieve_reject",
382 "sieve_fileinto",
383 "sieve_redirect",
384 "sieve_discard"))){
385 $this->move_single_element($key_id,$this->_get_next_free_move_slot($key_id,$direction));
386 }
387 }
390 /* Move the given block to position */
391 function move_multiple_elements($start,$end,$to)
392 {
393 /* Use class names for testing */
394 $data = $this->pap;
396 /* Get block to move */
397 $block_to_move = array_slice($data,$start, ($end - $start +1));
399 /* We want do move this block up */
400 if($end > $to){
402 /* Get start block */
403 $start_block = array_slice($data,0,$to);
405 /* Get Get all elements between the block to move
406 * and next free position
407 */
408 $block_to_free = array_slice($data,$to ,$start - $to );
409 $block_to_end = array_slice($data,$end+1);
410 $new = array();
411 foreach($start_block as $block){
412 $new[] = $block;
413 }
414 foreach($block_to_move as $block){
415 $new[] = $block;
416 }
417 foreach($block_to_free as $block){
418 $new[] = $block;
419 }
420 foreach($block_to_end as $block){
421 $new[] = $block;
422 }
423 $old = $this->pap;
424 $this->pap = $new;
425 }
428 /* We want to move this block down. */
429 if($to > $end){
431 /* Get start block */
432 $start_block = array_slice($data,0,$start);
434 /* Get Get all elements between the block to move
435 * and next free position
436 */
437 $block_to_free = array_slice($data,$end +1,($to - $end ));
439 /* Get the rest
440 */
441 $block_to_end = array_slice($data,$to+1);
443 $new = array();
444 foreach($start_block as $block){
445 $new[] = $block;
446 }
447 foreach($block_to_free as $block){
448 $new[] = $block;
449 }
450 foreach($block_to_move as $block){
451 $new[] = $block;
452 }
453 foreach($block_to_end as $block){
454 $new[] = $block;
455 }
456 $old = $this->pap;
457 $this->pap = $new;
458 }
459 }
462 /* This function returns the id of the element
463 * where the current block ends
464 */
465 function get_block_end($start)
466 {
467 /* Only execute if this is a really a block element.
468 * Block elements is only sieve_if
469 */
470 if(in_array(get_class($this->pap[$start]),array("sieve_if"))){
472 $class = get_class($this->pap[$start]);
473 $next_class = get_class($this->pap[$start+1]);
474 $block_depth = 0;
476 $end = FALSE;
478 while(!$end && $start < count($this->pap)){
480 if($class == "sieve_block_start"){
481 $block_depth ++;
482 }
484 if($class == "sieve_block_end"){
485 $block_depth --;
486 }
488 if( $block_depth == 0 &&
489 $class == "sieve_block_end" &&
490 !in_array($next_class,array("sieve_else","sieve_elsif"))){
491 $end = TRUE;
492 $start --;
493 }
494 $start ++;
495 $class = get_class($this->pap[$start]);
496 $next_class = get_class($this->pap[$start+1]);
497 }
498 }
499 return($start);
500 }
503 /* This function moves the single element at
504 * position $from to position $to.
505 */
506 function move_single_element($from,$to)
507 {
508 if($from == $to) {
509 return;
510 }
512 $ret = array();
513 $tmp = $this->pap;
515 $begin = array();
516 $middle = array();
517 $end = array();
518 $element = $this->pap[$from];
520 if($from > $to ){
522 /* Get all element in fron to element to move */
523 if($from != 0){
524 $begin = array_slice($tmp,0,$to);
525 }
527 /* Get all elements between */
528 $middle = array_slice($tmp,$to , ($from - ($to) ));
530 /* Get the rest */
531 $end = array_slice($tmp,$from+1);
533 foreach($begin as $data){
534 $ret[] = $data;
535 }
536 $ret[] = $element;
537 foreach($middle as $data){
538 $ret[] = $data;
539 }
540 foreach($end as $data){
541 $ret[] = $data;
542 }
543 $this->pap = $ret;
544 }
545 if($from < $to ){
547 /* Get all element in fron to element to move */
548 if($from != 0){
549 $begin = array_slice($tmp,0,$from);
550 }
552 /* Get all elements between */
553 $middle = array_slice($tmp,$from+1 , ($to - ($from)));
555 /* Get the rest */
556 $end = array_slice($tmp,$to+1);
558 foreach($begin as $data){
559 $ret[] = $data;
560 }
561 foreach($middle as $data){
562 $ret[] = $data;
563 }
564 $ret[] = $element;
565 foreach($end as $data){
566 $ret[] = $data;
567 }
568 $this->pap = $ret;
569 }
570 }
573 /* Returns the next free position where we
574 * can add a new sinle element
575 * $key_id = Current position
576 * $direction = Forward or backward.
577 */
578 function _get_next_free_move_slot($key_id,$direction)
579 {
580 $last_class = "";
581 $current_class ="";
582 $next_class = "";
584 /* After this elements we can add new elements
585 * without having any trouble.
586 */
587 $allowed_to_add_after = array("sieve_keep",
588 "sieve_require",
589 "sieve_stop",
590 "sieve_reject",
591 "sieve_fileinto",
592 "sieve_redirect",
593 "sieve_discard",
594 "sieve_comment",
595 "sieve_block_start"
596 );
598 /* Before this elements we can add new elements
599 * without having any trouble.
600 */
601 $allowed_to_add_before = array("sieve_keep",
602 "sieve_require",
603 "sieve_stop",
604 "sieve_reject",
605 "sieve_fileinto",
606 "sieve_comment",
607 "sieve_redirect",
608 "sieve_discard",
609 "sieve_if",
610 "sieve_block_end"
611 );
613 if($direction == "down"){
615 $test = $this->pap;
616 while($key_id < count($test)){
617 if(($key_id+1) == count($test)) {
618 return($key_id);
619 }
620 $key_id ++;
621 $current_class = get_class($test[$key_id]);
622 if(in_array($current_class, $allowed_to_add_after)){
623 return($key_id);
624 }
625 }
626 }else{
628 $test = $this->pap;
629 if($key_id == 0) {
630 return($key_id);
631 }
632 $key_id --;
633 while($key_id >=0 ){
634 $current_class = get_class($test[$key_id]);
635 if(in_array($current_class, $allowed_to_add_before)){
636 return($key_id);
637 }
638 $key_id --;
639 }
640 return(0);
641 }
642 }
645 /* Need to be reviewed */
646 function get_sieve_script()
647 {
648 $tmp ="";
649 if(count($this->pap)){
650 $buffer = "";
651 foreach($this->pap as $part) {
652 if(get_class($part) == "sieve_block_end"){
653 $buffer = substr($buffer,0,strlen($buffer)-(strlen(SIEVE_INDENT_TAB)));
654 }
655 $tmp2 = $part->get_sieve_script_part();
657 if(get_class($part) == "sieve_reject"){
658 $tmp.=$tmp2;
659 }else{
661 $tmp3 = split("\n",$tmp2);
662 foreach($tmp3 as $str){
663 $str2 = trim($str);
664 if(empty($str2)) continue;
665 $tmp.= $buffer.$str."\n";
666 }
667 }
668 if(get_class($part) == "sieve_block_start"){
669 $buffer .= SIEVE_INDENT_TAB;
670 }
671 }
672 }
673 if(!preg_match("/Generated by GOsa - Gonicus System Administrator/",$tmp)){
674 $tmp = "#Generated by GOsa - Gonicus System Administrator \n ".$tmp;
675 }
676 return($tmp);
677 }
679 function Add_Element()
680 {
681 $tmp = array("ELEMENTS" => array(array("class" => "qouted-string","text"=> "Bla bla, later more")));
682 $this->pap[] = new sieve_comment($tmp,rand(1000,100000));
683 }
685 function check()
686 {
687 $msgs = array();
689 /* Some logical checks.
690 * like : only sieve_comment can appear before require.
691 */
693 /* Ensure that there are no command before require
694 * - Get id of last require tag
695 * - Collect object types in from of this tag.
696 * - Check if there are tags collected that are not allowed
697 */
698 $last_found_at = -1;
699 $objs = array();
700 foreach($this->pap as $key => $obj){
701 if(get_class($obj) == "sieve_require"){
702 $last_found_at = $key;
703 }
704 }
705 foreach($this->pap as $key => $obj){
706 if($key == $last_found_at) break;
707 if(!in_array(get_class($obj),array("sieve_comment","sieve_require"))){
708 $objs[] = get_class($obj);
709 }
710 }
711 if(count($objs) && $last_found_at != -1){
712 $str = _("Require must be the first command in the script.");
713 $msgs[] = $str;
714 print_red($str);;
715 }
717 foreach($this->pap as $obj){
718 $o_msgs = $obj->check();
719 foreach($o_msgs as $o_msg){
720 $msgs[] = $o_msg;
721 }
722 }
723 return($msgs);
724 }
725 }
728 /* Create valid sieve string/string-list
729 * out of a given array
730 */
731 function sieve_create_strings($data,$force_string = FALSE)
732 {
733 $ret = "";
734 if(is_array($data)){
735 if(count($data) == 1){
736 $ret = "\"";
737 foreach($data as $dat){
738 $ret .=$dat;
739 }
740 $ret.="\"";
741 }else{
742 foreach($data as $dat){
743 $ret.= "\"";
744 $ret.=$dat;
745 $ret.="\", ";
746 }
747 $ret = preg_replace("/,$/","",trim($ret));
748 $ret = "[".$ret."]";
749 }
750 }else{
752 $Multiline = preg_match("/\n/",$data);
753 $data = preg_replace("/\r/","",$data);;
755 if($Multiline && !$force_string){
756 $ret = "text: \r\n".$data."\r\n.\r\n";
757 }else{
758 $ret = "\"".$data."\"";
759 }
760 }
761 $ret = preg_replace("/\"\"/","\"",$ret);
762 $ret = preg_replace("/\n/","\r\n",$ret);
764 return($ret);
765 }
767 /* This checks if there is a string at the current position
768 * in the token array.
769 * If there is a string list at the current position,
770 * this function will return a complete list of all
771 * strings used in this list.
772 * It also returns an offset of the last token position
773 */
774 function sieve_get_strings($data,$id)
775 {
776 $ret = array();
777 if($data[$id]['class'] == "left-bracket"){
778 while($data[$id]['class'] != "right-bracket" && $id < count($data)){
780 if($data[$id]['class'] == "quoted-string"){
781 $ret[] = $data[$id]['text'];
782 }
783 $id ++;
784 }
785 }elseif($data[$id]['class'] == "quoted-string"){
786 $ret[] = $data[$id]['text'];
787 }elseif($data[$id]['class'] == "number"){
788 $ret[] = $data[$id]['text'];
789 }elseif($data[$id]['class'] == "multi-line"){
790 $str = trim(preg_replace("/^text:/","",$data[$id]['text']));
791 $str = trim(preg_replace("/\.$/","",$str));
792 $ret[] = $str;
793 }
795 return(array("OFFSET" => $id, "STRINGS" => $ret));
796 }
798 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
799 ?>