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_vacation",
381 "sieve_stop",
382 "sieve_reject",
383 "sieve_fileinto",
384 "sieve_redirect",
385 "sieve_discard"))){
386 $this->move_single_element($key_id,$this->_get_next_free_move_slot($key_id,$direction));
387 }
388 }
391 /* Move the given block to position */
392 function move_multiple_elements($start,$end,$to)
393 {
394 /* Use class names for testing */
395 $data = $this->pap;
397 /* Get block to move */
398 $block_to_move = array_slice($data,$start, ($end - $start +1));
400 /* We want do move this block up */
401 if($end > $to){
403 /* Get start block */
404 $start_block = array_slice($data,0,$to);
406 /* Get Get all elements between the block to move
407 * and next free position
408 */
409 $block_to_free = array_slice($data,$to ,$start - $to );
410 $block_to_end = array_slice($data,$end+1);
411 $new = array();
412 foreach($start_block as $block){
413 $new[] = $block;
414 }
415 foreach($block_to_move as $block){
416 $new[] = $block;
417 }
418 foreach($block_to_free as $block){
419 $new[] = $block;
420 }
421 foreach($block_to_end as $block){
422 $new[] = $block;
423 }
424 $old = $this->pap;
425 $this->pap = $new;
426 }
429 /* We want to move this block down. */
430 if($to > $end){
432 /* Get start block */
433 $start_block = array_slice($data,0,$start);
435 /* Get Get all elements between the block to move
436 * and next free position
437 */
438 $block_to_free = array_slice($data,$end +1,($to - $end ));
440 /* Get the rest
441 */
442 $block_to_end = array_slice($data,$to+1);
444 $new = array();
445 foreach($start_block as $block){
446 $new[] = $block;
447 }
448 foreach($block_to_free as $block){
449 $new[] = $block;
450 }
451 foreach($block_to_move as $block){
452 $new[] = $block;
453 }
454 foreach($block_to_end as $block){
455 $new[] = $block;
456 }
457 $old = $this->pap;
458 $this->pap = $new;
459 }
460 }
463 /* This function returns the id of the element
464 * where the current block ends
465 */
466 function get_block_end($start)
467 {
468 /* Only execute if this is a really a block element.
469 * Block elements is only sieve_if
470 */
471 if(in_array(get_class($this->pap[$start]),array("sieve_if"))){
473 $class = get_class($this->pap[$start]);
474 $next_class = get_class($this->pap[$start+1]);
475 $block_depth = 0;
477 $end = FALSE;
479 while(!$end && $start < count($this->pap)){
481 if($class == "sieve_block_start"){
482 $block_depth ++;
483 }
485 if($class == "sieve_block_end"){
486 $block_depth --;
487 }
489 if( $block_depth == 0 &&
490 $class == "sieve_block_end" &&
491 !in_array($next_class,array("sieve_else","sieve_elsif"))){
492 $end = TRUE;
493 $start --;
494 }
495 $start ++;
496 $class = get_class($this->pap[$start]);
497 $next_class = get_class($this->pap[$start+1]);
498 }
499 }
500 return($start);
501 }
504 /* This function moves the single element at
505 * position $from to position $to.
506 */
507 function move_single_element($from,$to)
508 {
509 if($from == $to) {
510 return;
511 }
513 $ret = array();
514 $tmp = $this->pap;
516 $begin = array();
517 $middle = array();
518 $end = array();
519 $element = $this->pap[$from];
521 if($from > $to ){
523 /* Get all element in fron to element to move */
524 if($from != 0){
525 $begin = array_slice($tmp,0,$to);
526 }
528 /* Get all elements between */
529 $middle = array_slice($tmp,$to , ($from - ($to) ));
531 /* Get the rest */
532 $end = array_slice($tmp,$from+1);
534 foreach($begin as $data){
535 $ret[] = $data;
536 }
537 $ret[] = $element;
538 foreach($middle as $data){
539 $ret[] = $data;
540 }
541 foreach($end as $data){
542 $ret[] = $data;
543 }
544 $this->pap = $ret;
545 }
546 if($from < $to ){
548 /* Get all element in fron to element to move */
549 if($from != 0){
550 $begin = array_slice($tmp,0,$from);
551 }
553 /* Get all elements between */
554 $middle = array_slice($tmp,$from+1 , ($to - ($from)));
556 /* Get the rest */
557 $end = array_slice($tmp,$to+1);
559 foreach($begin as $data){
560 $ret[] = $data;
561 }
562 foreach($middle as $data){
563 $ret[] = $data;
564 }
565 $ret[] = $element;
566 foreach($end as $data){
567 $ret[] = $data;
568 }
569 $this->pap = $ret;
570 }
571 }
574 /* Returns the next free position where we
575 * can add a new sinle element
576 * $key_id = Current position
577 * $direction = Forward or backward.
578 */
579 function _get_next_free_move_slot($key_id,$direction)
580 {
581 $last_class = "";
582 $current_class ="";
583 $next_class = "";
585 /* After this elements we can add new elements
586 * without having any trouble.
587 */
588 $allowed_to_add_after = array("sieve_keep",
589 "sieve_require",
590 "sieve_stop",
591 "sieve_reject",
592 "sieve_fileinto",
593 "sieve_redirect",
594 "sieve_discard",
595 "sieve_comment",
596 "sieve_block_start"
597 );
599 /* Before this elements we can add new elements
600 * without having any trouble.
601 */
602 $allowed_to_add_before = array("sieve_keep",
603 "sieve_require",
604 "sieve_stop",
605 "sieve_reject",
606 "sieve_fileinto",
607 "sieve_comment",
608 "sieve_redirect",
609 "sieve_discard",
610 "sieve_if",
611 "sieve_block_end"
612 );
614 if($direction == "down"){
616 $test = $this->pap;
617 while($key_id < count($test)){
618 if(($key_id+1) == count($test)) {
619 return($key_id);
620 }
621 $key_id ++;
622 $current_class = get_class($test[$key_id]);
623 if(in_array($current_class, $allowed_to_add_after)){
624 return($key_id);
625 }
626 }
627 }else{
629 $test = $this->pap;
630 if($key_id == 0) {
631 return($key_id);
632 }
633 $key_id --;
634 while($key_id >=0 ){
635 $current_class = get_class($test[$key_id]);
636 if(in_array($current_class, $allowed_to_add_before)){
637 return($key_id);
638 }
639 $key_id --;
640 }
641 return(0);
642 }
643 }
646 /* Need to be reviewed */
647 function get_sieve_script()
648 {
649 $tmp ="";
650 if(count($this->pap)){
651 $buffer = "";
652 foreach($this->pap as $part) {
653 if(get_class($part) == "sieve_block_end"){
654 $buffer = substr($buffer,0,strlen($buffer)-(strlen(SIEVE_INDENT_TAB)));
655 }
656 $tmp2 = $part->get_sieve_script_part();
658 if(get_class($part) == "sieve_reject"){
659 $tmp.=$tmp2;
660 }else{
662 $tmp3 = split("\n",$tmp2);
663 foreach($tmp3 as $str){
664 $str2 = trim($str);
665 if(empty($str2)) continue;
666 $tmp.= $buffer.$str."\n";
667 }
668 }
669 if(get_class($part) == "sieve_block_start"){
670 $buffer .= SIEVE_INDENT_TAB;
671 }
672 }
673 }
674 if(!preg_match("/Generated by GOsa - Gonicus System Administrator/",$tmp)){
675 $tmp = "#Generated by GOsa - Gonicus System Administrator \n ".$tmp;
676 }
677 return($tmp);
678 }
680 function Add_Element()
681 {
682 $tmp = array("ELEMENTS" => array(array("class" => "qouted-string","text"=> "Bla bla, later more")));
683 $this->pap[] = new sieve_comment($tmp,rand(1000,100000));
684 }
686 function check()
687 {
688 $msgs = array();
690 /* Some logical checks.
691 * like : only sieve_comment can appear before require.
692 */
694 /* Ensure that there are no command before require
695 * - Get id of last require tag
696 * - Collect object types in from of this tag.
697 * - Check if there are tags collected that are not allowed
698 */
699 $last_found_at = -1;
700 $objs = array();
701 foreach($this->pap as $key => $obj){
702 if(get_class($obj) == "sieve_require"){
703 $last_found_at = $key;
704 }
705 }
706 foreach($this->pap as $key => $obj){
707 if($key == $last_found_at) break;
708 if(!in_array(get_class($obj),array("sieve_comment","sieve_require"))){
709 $objs[] = get_class($obj);
710 }
711 }
712 if(count($objs) && $last_found_at != -1){
713 $str = _("Require must be the first command in the script.");
714 $msgs[] = $str;
715 print_red($str);;
716 }
718 foreach($this->pap as $obj){
719 $o_msgs = $obj->check();
720 foreach($o_msgs as $o_msg){
721 $msgs[] = $o_msg;
722 }
723 }
724 return($msgs);
725 }
726 }
729 /* Create valid sieve string/string-list
730 * out of a given array
731 */
732 function sieve_create_strings($data,$force_string = FALSE)
733 {
734 $ret = "";
735 if(is_array($data)){
736 if(count($data) == 1){
737 $ret = "\"";
738 foreach($data as $dat){
739 $ret .=$dat;
740 }
741 $ret.="\"";
742 }else{
743 foreach($data as $dat){
744 $ret.= "\"";
745 $ret.=$dat;
746 $ret.="\", ";
747 }
748 $ret = preg_replace("/,$/","",trim($ret));
749 $ret = "[".$ret."]";
750 }
751 }else{
753 $Multiline = preg_match("/\n/",$data);
754 $data = preg_replace("/\r/","",$data);;
756 if($Multiline && !$force_string){
757 $ret = "text: \r\n".$data."\r\n.\r\n";
758 }else{
759 $ret = "\"".$data."\"";
760 }
761 }
762 $ret = preg_replace("/\"\"/","\"",$ret);
763 $ret = preg_replace("/\n/","\r\n",$ret);
765 return($ret);
766 }
768 /* This checks if there is a string at the current position
769 * in the token array.
770 * If there is a string list at the current position,
771 * this function will return a complete list of all
772 * strings used in this list.
773 * It also returns an offset of the last token position
774 */
775 function sieve_get_strings($data,$id)
776 {
777 $ret = array();
778 if($data[$id]['class'] == "left-bracket"){
779 while($data[$id]['class'] != "right-bracket" && $id < count($data)){
781 if($data[$id]['class'] == "quoted-string"){
782 $ret[] = $data[$id]['text'];
783 }
784 $id ++;
785 }
786 }elseif($data[$id]['class'] == "quoted-string"){
787 $ret[] = $data[$id]['text'];
788 }elseif($data[$id]['class'] == "number"){
789 $ret[] = $data[$id]['text'];
790 }elseif($data[$id]['class'] == "multi-line"){
791 $str = trim(preg_replace("/^text:/","",$data[$id]['text']));
792 $str = trim(preg_replace("/\.$/","",$str));
793 $ret[] = $str;
794 }
796 return(array("OFFSET" => $id, "STRINGS" => $ret));
797 }
799 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
800 ?>