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