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 /* Create html results */
176 $smarty = get_smarty();
177 $smarty->assign("Mode",$this->Mode);
178 if($this->Mode == "Structured"){
179 $smarty->assign("Contents",$this->dump_);
180 }else{
181 if(isset($_POST['script_contents'])){
182 $smarty->assign("Contents",stripslashes($_POST['script_contents']));
183 }else{
184 $smarty->assign("Contents",$this->get_sieve_script());
185 }
186 }
187 $ret = $smarty->fetch(get_template_path("templates/edit_frame_base.tpl",TRUE,dirname(__FILE__)));
188 return ($ret);
189 }
192 /* This function walks through the object tree generated by the "Parse" class.
193 * All Commands will be resolved and grouped. So the Commands and their
194 * parameter are combined. Like "IF" and ":comparator"...
195 */
196 function doDump_($node_id, $prefix, $last,$num = 1)
197 {
198 /* Indicates that current comman will only be valid for a single line.
199 * this command type will be removed from mode_stack after displaying it.
200 */
201 $rewoke_last = FALSE;
203 /* Get node */
204 $node = $this->nodes_[$node_id];
206 /* This closes the last mode */
207 if($node['class'] == "block-start"){
208 $tmp = array_pop($this->mode_stack);
209 $this->handle_elements($tmp,$node_id);
210 $this->handle_elements(array("TYPE" => "block_start"),$node_id);
211 }
213 /* This closes the last mode */
214 if($node['class'] == "block-end"){
215 $tmp = array_pop($this->mode_stack);
216 $this->handle_elements($tmp,$node_id);
217 $this->handle_elements(array("TYPE" => "block_end"),$node_id);
218 }
220 /* Semicolon indicates a new command */
221 if($node['class'] == "semicolon"){
222 $tmp =array_pop($this->mode_stack);
223 $this->handle_elements($tmp,$node_id);
224 }
226 /* Handle comments */
227 if($node['class'] == "comment"){
228 $this->mode_stack[] = array("TYPE" => $node['class']);
229 $rewoke_last = TRUE;
230 }
232 /* Handle identifiers */
233 $identifiers = array("else","if","elsif","end","reject","redirect","vacation","keep","discard","comment","fileinto","require","stop");
234 if($node['class'] == "identifier" && in_array($node['text'],$identifiers)){
235 $this->mode_stack[] = array("TYPE" => $node['text']);
236 }
238 /* Add current node to current command stack */
239 end($this->mode_stack);
240 $key = key($this->mode_stack);
241 $this->mode_stack[$key]['ELEMENTS'][] = $node;
243 /* Remove last mode from mode stack, cause it was only valid for a single line */
244 if($rewoke_last){
245 $tmp =array_pop($this->mode_stack);
246 $this->handle_elements($tmp,$node_id);
247 }
249 /* If this is a sub element, just call this for all childs */
250 if(isset($this->childs_[$node_id])){
251 $childs = $this->childs_[$node_id];
252 for ($i=0; $i<count($childs); ++$i)
253 {
254 $c_last = false;
255 if ($i+1 == count($childs))
256 {
257 $c_last = true;
258 }
259 $this->doDump_($childs[$i], "", $num);
260 }
261 }
262 }
265 /* Create a class for each resolved object.
266 * And append this class to a list of objects.
267 */
268 function handle_elements($data,$id)
269 {
270 if(!isset($data['TYPE'])){
271 return;
272 }
273 $type = $data['TYPE'];
275 $class_name= "sieve_".$type ;
276 if(class_exists($class_name)){
277 $this->pap[] = new $class_name($data,$id);
278 }else{
279 echo "<font color='red'>Missing : ".$class_name."</font>"."<br>";
280 }
281 }
283 function save_object()
284 {
285 reset($this->pap);
286 foreach($this->pap as $key => $obj){
288 if(in_array(get_class($obj),array("sieve_if",
289 "sieve_elsif",
290 "sieve_vacation",
291 "sieve_comment",
292 "sieve_reject",
293 "sieve_fileinto",
294 "sieve_require",
295 "sieve_redirect"))){
298 if(isset($this->pap[$key]) && method_exists($this->pap[$key],"save_object")){
299 $this->pap[$key]->save_object();
300 }
301 }
303 $once = TRUE;
304 foreach($_POST as $name => $value){
306 if(isset($obj->object_id) && preg_match("/^Remove_Object_".$obj->object_id."_/",$name) && $once){
307 $once = FALSE;
308 $this->remove_object($key);
309 }
310 if(isset($obj->object_id) && preg_match("/^Move_Up_Object_".$obj->object_id."_/",$name) && $once){
311 $this->move_up_down($key,"up");
312 $once = FALSE;
313 }
314 if(isset($obj->object_id) && preg_match("/^Move_Down_Object_".$obj->object_id."_/",$name) && $once){
315 $this->move_up_down($key,"down");
316 $once = FALSE;
317 }
318 if(isset($obj->object_id) && preg_match("/^Add_Object_Top_".$obj->object_id."_/",$name) && $once){
319 $this->add_new_object($obj->object_id,"top");
320 $once = FALSE;
321 }
322 if(isset($obj->object_id) && preg_match("/^Add_Object_Bottom_".$obj->object_id."_/",$name) && $once){
323 $this->add_new_object($obj->object_id,"bottom");
324 $once = FALSE;
325 }
326 }
327 }
328 }
331 /* Add a new object at the given position */
332 function add_new_object($id,$top_bottom = "bottom")
333 {
334 $this->add_new = TRUE;
335 $this->add_new_id = $id;
336 $this->add_type = $top_bottom;
337 }
340 /* Remove the object at the given position */
341 function remove_object($key_id)
342 {
343 $class = get_class($this->pap[$key_id]);
344 if(in_array($class,array("sieve_if"))){
345 $block_start= $key_id;
346 $block_end = $this->get_block_end($key_id);
347 for($i = $block_start ; $i <= $block_end ; $i ++ ){
348 unset($this->pap[$i]);
349 }
350 }else{
351 unset($this->pap[$key_id]);
352 }
353 $tmp = array();
354 foreach($this->pap as $element){
355 $tmp[] = $element;
356 }
357 $this->pap = $tmp;
358 }
361 /* This function moves a given element to another position.
362 * Single elements like "keep;" will simply be moved one posisition down/up.
363 * Multiple elements like if-elsif-else will be moved as block.
364 *
365 * $key_id specified the element that should be moved.
366 * $direction specifies to move elements "up" or "down"
367 */
368 function move_up_down($key_id,$direction = "down")
369 {
371 /* Get the current element to decide what to move. */
372 $e_class = get_class($this->pap[$key_id]);
374 if(in_array($e_class,array("sieve_if"))){
375 $block_start= $key_id;
376 $block_end = $this->get_block_end($key_id);
378 /* Depending on the direction move up down */
379 if($direction == "down"){
380 $next_free = $this->_get_next_free_move_slot($block_end,$direction);
381 }else{
382 $next_free = $this->_get_next_free_move_slot($block_start,$direction);
383 }
385 /* Move the given block */
386 $this->move_multiple_elements($block_start,$block_end,$next_free);
387 }
389 if(in_array($e_class,array( "sieve_stop",
390 "sieve_keep",
391 "sieve_require",
392 "sieve_comment",
393 "sieve_stop",
394 "sieve_reject",
395 "sieve_fileinto",
396 "sieve_redirect",
397 "sieve_discard"))){
398 $this->move_single_element($key_id,$this->_get_next_free_move_slot($key_id,$direction));
399 }
400 }
403 /* Move the given block to position */
404 function move_multiple_elements($start,$end,$to)
405 {
406 /* Use class names for testing */
407 $data = $this->pap;
409 /* Get block to move */
410 $block_to_move = array_slice($data,$start, ($end - $start +1));
412 /* We want do move this block up */
413 if($end > $to){
415 /* Get start block */
416 $start_block = array_slice($data,0,$to);
418 /* Get Get all elements between the block to move
419 * and next free position
420 */
421 $block_to_free = array_slice($data,$to ,$start - $to );
422 $block_to_end = array_slice($data,$end+1);
423 $new = array();
424 foreach($start_block as $block){
425 $new[] = $block;
426 }
427 foreach($block_to_move as $block){
428 $new[] = $block;
429 }
430 foreach($block_to_free as $block){
431 $new[] = $block;
432 }
433 foreach($block_to_end as $block){
434 $new[] = $block;
435 }
436 $old = $this->pap;
437 $this->pap = $new;
438 }
441 /* We want to move this block down. */
442 if($to > $end){
444 /* Get start block */
445 $start_block = array_slice($data,0,$start);
447 /* Get Get all elements between the block to move
448 * and next free position
449 */
450 $block_to_free = array_slice($data,$end +1,($to - $end ));
452 /* Get the rest
453 */
454 $block_to_end = array_slice($data,$to+1);
456 $new = array();
457 foreach($start_block as $block){
458 $new[] = $block;
459 }
460 foreach($block_to_free as $block){
461 $new[] = $block;
462 }
463 foreach($block_to_move as $block){
464 $new[] = $block;
465 }
466 foreach($block_to_end as $block){
467 $new[] = $block;
468 }
469 $old = $this->pap;
470 $this->pap = $new;
471 }
472 }
475 /* This function returns the id of the element
476 * where the current block ends
477 */
478 function get_block_end($start)
479 {
480 /* Only execute if this is a really a block element.
481 * Block elements is only sieve_if
482 */
483 if(in_array(get_class($this->pap[$start]),array("sieve_if"))){
485 $class = get_class($this->pap[$start]);
486 $next_class = get_class($this->pap[$start+1]);
487 $block_depth = 0;
489 $end = FALSE;
491 while(!$end && $start < count($this->pap)){
493 if($class == "sieve_block_start"){
494 $block_depth ++;
495 }
497 if($class == "sieve_block_end"){
498 $block_depth --;
499 }
501 if( $block_depth == 0 &&
502 $class == "sieve_block_end" &&
503 !in_array($next_class,array("sieve_else","sieve_elsif"))){
504 $end = TRUE;
505 $start --;
506 }
507 $start ++;
508 $class = get_class($this->pap[$start]);
509 $next_class = get_class($this->pap[$start+1]);
510 }
511 }
512 return($start);
513 }
516 /* This function moves the single element at
517 * position $from to position $to.
518 */
519 function move_single_element($from,$to)
520 {
521 if($from == $to) {
522 return;
523 }
525 $ret = array();
526 $tmp = $this->pap;
528 $begin = array();
529 $middle = array();
530 $end = array();
531 $element = $this->pap[$from];
533 if($from > $to ){
535 /* Get all element in fron to element to move */
536 if($from != 0){
537 $begin = array_slice($tmp,0,$to);
538 }
540 /* Get all elements between */
541 $middle = array_slice($tmp,$to , ($from - ($to) ));
543 /* Get the rest */
544 $end = array_slice($tmp,$from+1);
546 foreach($begin as $data){
547 $ret[] = $data;
548 }
549 $ret[] = $element;
550 foreach($middle as $data){
551 $ret[] = $data;
552 }
553 foreach($end as $data){
554 $ret[] = $data;
555 }
556 $this->pap = $ret;
557 }
558 if($from < $to ){
560 /* Get all element in fron to element to move */
561 if($from != 0){
562 $begin = array_slice($tmp,0,$from);
563 }
565 /* Get all elements between */
566 $middle = array_slice($tmp,$from+1 , ($to - ($from)));
568 /* Get the rest */
569 $end = array_slice($tmp,$to+1);
571 foreach($begin as $data){
572 $ret[] = $data;
573 }
574 foreach($middle as $data){
575 $ret[] = $data;
576 }
577 $ret[] = $element;
578 foreach($end as $data){
579 $ret[] = $data;
580 }
581 $this->pap = $ret;
582 }
583 }
586 /* Returns the next free position where we
587 * can add a new sinle element
588 * $key_id = Current position
589 * $direction = Forward or backward.
590 */
591 function _get_next_free_move_slot($key_id,$direction)
592 {
593 $last_class = "";
594 $current_class ="";
595 $next_class = "";
597 /* After this elements we can add new elements
598 * without having any trouble.
599 */
600 $allowed_to_add_after = array("sieve_keep",
601 "sieve_require",
602 "sieve_stop",
603 "sieve_reject",
604 "sieve_fileinto",
605 "sieve_redirect",
606 "sieve_discard",
607 "sieve_comment",
608 "sieve_block_start"
609 );
611 /* Before this elements we can add new elements
612 * without having any trouble.
613 */
614 $allowed_to_add_before = array("sieve_keep",
615 "sieve_require",
616 "sieve_stop",
617 "sieve_reject",
618 "sieve_fileinto",
619 "sieve_comment",
620 "sieve_redirect",
621 "sieve_discard",
622 "sieve_if",
623 "sieve_block_end"
624 );
626 if($direction == "down"){
628 $test = $this->pap;
629 while($key_id < count($test)){
630 if(($key_id+1) == count($test)) {
631 return($key_id);
632 }
633 $key_id ++;
634 $current_class = get_class($test[$key_id]);
635 if(in_array($current_class, $allowed_to_add_after)){
636 return($key_id);
637 }
638 }
639 }else{
641 $test = $this->pap;
642 if($key_id == 0) {
643 return($key_id);
644 }
645 $key_id --;
646 while($key_id >=0 ){
647 $current_class = get_class($test[$key_id]);
648 if(in_array($current_class, $allowed_to_add_before)){
649 return($key_id);
650 }
651 $key_id --;
652 }
653 return(0);
654 }
655 }
658 /* Need to be reviewed */
659 function get_sieve_script()
660 {
661 $tmp ="";
662 if(count($this->pap)){
663 $buffer = "";
664 foreach($this->pap as $part) {
665 if(get_class($part) == "sieve_block_end"){
666 $buffer = substr($buffer,0,strlen($buffer)-(strlen(SIEVE_INDENT_TAB)));
667 }
668 $tmp2 = $part->get_sieve_script_part();
670 if(get_class($part) == "sieve_reject"){
671 $tmp.=$tmp2;
672 }else{
674 $tmp3 = split("\n",$tmp2);
675 foreach($tmp3 as $str){
676 $str2 = trim($str);
677 if(empty($str2)) continue;
678 $tmp.= $buffer.$str."\n";
679 }
680 }
681 if(get_class($part) == "sieve_block_start"){
682 $buffer .= SIEVE_INDENT_TAB;
683 }
684 }
685 }
686 if(!preg_match("/Generated by GOsa - Gonicus System Administrator/",$tmp)){
687 $tmp = "#Generated by GOsa - Gonicus System Administrator \n ".$tmp;
688 }
689 return($tmp);
690 }
692 function Add_Element()
693 {
694 $tmp = array("ELEMENTS" => array(array("class" => "qouted-string","text"=> "Bla bla, later more")));
695 $this->pap[] = new sieve_comment($tmp,rand(1000,100000));
696 }
698 function check()
699 {
700 $msgs = array();
702 /* Some logical checks.
703 * like : only sieve_comment can appear before require.
704 */
706 /* Ensure that there are no command before require
707 * - Get id of last require tag
708 * - Collect object types in from of this tag.
709 * - Check if there are tags collected that are not allowed
710 */
711 $last_found_at = -1;
712 $objs = array();
713 foreach($this->pap as $key => $obj){
714 if(get_class($obj) == "sieve_require"){
715 $last_found_at = $key;
716 }
717 }
718 foreach($this->pap as $key => $obj){
719 if($key == $last_found_at) break;
720 if(!in_array(get_class($obj),array("sieve_comment","sieve_require"))){
721 $objs[] = get_class($obj);
722 }
723 }
724 if(count($objs) && $last_found_at != -1){
725 $str = _("Require must be the first command in the script.");
726 $msgs[] = $str;
727 print_red($str);;
728 }
730 foreach($this->pap as $obj){
731 $o_msgs = $obj->check();
732 foreach($o_msgs as $o_msg){
733 $msgs[] = $o_msg;
734 }
735 }
736 return($msgs);
737 }
738 }
741 /* Create valid sieve string/string-list
742 * out of a given array
743 */
744 function sieve_create_strings($data)
745 {
746 $ret = "";
747 if(is_array($data)){
748 if(count($data) == 1){
749 $ret = "\"";
750 foreach($data as $dat){
751 $ret .=$dat;
752 }
753 $ret.="\"";
754 }else{
755 foreach($data as $dat){
756 $ret.= "\"";
757 $ret.=$dat;
758 $ret.="\", ";
759 }
760 $ret = preg_replace("/,$/","",trim($ret));
761 $ret = "[".$ret."]";
762 }
763 }else{
765 $Multiline = preg_match("/\n/",$data);
766 $data = preg_replace("/\r/","",$data);;
768 if($Multiline){
769 $ret = "text: \r\n".$data."\r\n.\r\n";
770 }else{
771 $ret = "\"".$data."\"";
772 }
773 }
774 $ret = preg_replace("/\"\"/","\"",$ret);
775 $ret = preg_replace("/\n/","\r\n",$ret);
777 return($ret);
778 }
780 /* This checks if there is a string at the current position
781 * in the token array.
782 * If there is a string list at the current position,
783 * this function will return a complete list of all
784 * strings used in this list.
785 * It also returns an offset of the last token position
786 */
787 function sieve_get_strings($data,$id)
788 {
789 $ret = array();
790 if($data[$id]['class'] == "left-bracket"){
791 while($data[$id]['class'] != "right-bracket" && $id < count($data)){
793 if($data[$id]['class'] == "quoted-string"){
794 $ret[] = $data[$id]['text'];
795 }
796 $id ++;
797 }
798 }elseif($data[$id]['class'] == "quoted-string"){
799 $ret[] = $data[$id]['text'];
800 }elseif($data[$id]['class'] == "number"){
801 $ret[] = $data[$id]['text'];
802 }
803 return(array("OFFSET" => $id, "STRINGS" => $ret));
804 }
806 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
807 ?>