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 /* Create a html interface for the current sieve filter
25 */
26 function dump()
27 {
28 error_reporting(E_ALL);
30 /**************
31 * Handle new elements
32 **************/
34 if($this->add_new){
36 $element_types= array(
37 "sieve_keep" => _("Keep"),
38 "sieve_comment" => _("Comment"),
39 "sieve_fileinto" => _("File into"),
40 "sieve_keep" => _("Keep"),
41 "sieve_discard" => _("Discard"),
42 "sieve_redirect" => _("Redirect"),
43 "sieve_reject" => _("Reject"),
44 "sieve_require" => _("Require"),
45 "sieve_stop" => _("Stop"),
46 "sieve_vacation" => _("Vacation message"),
47 "sieve_if" => _("If"));
50 /* Element selected */
51 if(isset($_POST['element_type']) && isset($element_types[$_POST['element_type']])){
52 $this->add_element_type = $_POST['element_type'];
53 }
55 /* Create new element and add it at the selected position */
56 if(isset($_POST['select_new_element_type'])){
58 $ele = new $this->add_element_type(NULL, preg_replace("/[^0-9]/","",microtime()));
59 $start = $end = array();
60 $found = false;
62 /* Add above current element*/
63 if($this->add_type == "top"){
64 foreach($this->pap as $key => $obj){
65 if($obj->object_id == $this->add_new_id){
66 $found = true;
67 }
68 if(!$found){
69 $start[] = $obj;
70 }else{
71 $end[] = $obj;
72 }
73 }
74 }else{
75 /* Add below current element */
76 foreach($this->pap as $key => $obj){
77 if(!$found){
78 $start[] = $obj;
79 }else{
80 $end[] = $obj;
81 }
82 if($obj->object_id == $this->add_new_id){
83 $found = true;
84 }
85 }
86 }
88 /* Only add, if current element could be located */
89 if($found){
90 $new = array();
91 foreach($start as $obj){
92 $new[] = $obj;
93 }
94 $new[] = $ele;
95 foreach($end as $obj){
96 $new[] = $obj;
97 }
98 $this->pap = $new;
99 $this->add_new = FALSE;
100 }else{
101 print_red(_("Something went wrong while adding a new entry."));
102 }
103 }
105 }
107 /* Only display select dialog if it is necessary */
108 if($this->add_new){
109 $smarty = get_smarty();
110 $smarty->assign("element_types",$element_types );
111 $smarty->assign("element_type",$this->add_element_type);
112 $str = $smarty->fetch(get_template_path("templates/add_element.tpl",TRUE,dirname(__FILE__)));
113 return($str);
114 }
116 /* Only parse the tokens once */
117 if(!count($this->pap)){
118 $this->dump_ = "";
119 $this->mode_stack = array();
120 $this->pap = array();
121 $this->doDump_(0, '', true);
122 }
124 /* Create html results */
125 $smarty = get_smarty();
127 $this -> dump_ = "";
128 foreach($this->pap as $key => $object){
129 if(is_object($object)){
130 $this->dump_ .= preg_replace("/>/",">\n",$object->execute());
131 }
132 }
134 /* Create html results */
135 $smarty = get_smarty();
136 $smarty->assign("Contents",$this->dump_);
137 $ret = $smarty->fetch(get_template_path("templates/edit_frame_base.tpl",TRUE,dirname(__FILE__)));
138 return ($ret);
139 }
142 /* This function walks through the object tree generated by the "Parse" class.
143 * All Commands will be resolved and grouped. So the Commands and their
144 * parameter are combined. Like "IF" and ":comparator"...
145 */
146 function doDump_($node_id, $prefix, $last,$num = 1)
147 {
148 /* Indicates that current comman will only be valid for a single line.
149 * this command type will be removed from mode_stack after displaying it.
150 */
151 $rewoke_last = FALSE;
153 /* Get node */
154 $node = $this->nodes_[$node_id];
156 /* This closes the last mode */
157 if($node['class'] == "block-start"){
158 $tmp = array_pop($this->mode_stack);
159 $this->handle_elements($tmp,$node_id);
160 $this->handle_elements(array("TYPE" => "block_start"),$node_id);
161 }
163 /* This closes the last mode */
164 if($node['class'] == "block-end"){
165 $tmp = array_pop($this->mode_stack);
166 $this->handle_elements($tmp,$node_id);
167 $this->handle_elements(array("TYPE" => "block_end"),$node_id);
168 }
170 /* Semicolon indicates a new command */
171 if($node['class'] == "semicolon"){
172 $tmp =array_pop($this->mode_stack);
173 $this->handle_elements($tmp,$node_id);
174 }
176 /* Handle comments */
177 if($node['class'] == "comment"){
178 $this->mode_stack[] = array("TYPE" => $node['class']);
179 $rewoke_last = TRUE;
180 }
182 /* Handle identifiers */
183 $identifiers = array("else","if","elsif","end","reject","redirect","vacation","keep","discard","comment","fileinto","require","stop");
184 if($node['class'] == "identifier" && in_array($node['text'],$identifiers)){
185 $this->mode_stack[] = array("TYPE" => $node['text']);
186 }
188 /* Add current node to current command stack */
189 end($this->mode_stack);
190 $key = key($this->mode_stack);
191 $this->mode_stack[$key]['ELEMENTS'][] = $node;
193 /* Remove last mode from mode stack, cause it was only valid for a single line */
194 if($rewoke_last){
195 $tmp =array_pop($this->mode_stack);
196 $this->handle_elements($tmp,$node_id);
197 }
199 /* If this is a sub element, just call this for all childs */
200 if(isset($this->childs_[$node_id])){
201 $childs = $this->childs_[$node_id];
202 for ($i=0; $i<count($childs); ++$i)
203 {
204 $c_last = false;
205 if ($i+1 == count($childs))
206 {
207 $c_last = true;
208 }
209 $this->doDump_($childs[$i], "", $num);
210 }
211 }
212 }
215 /* Create a class for each resolved object.
216 * And append this class to a list of objects.
217 */
218 function handle_elements($data,$id)
219 {
220 if(!isset($data['TYPE'])){
221 return;
222 }
223 $type = $data['TYPE'];
225 $class_name= "sieve_".$type ;
226 if(class_exists($class_name)){
227 $this->pap[] = new $class_name($data,$id);
228 }else{
229 echo "<font color='red'>Missing : ".$class_name."</font>"."<br>";
230 }
231 }
233 function save_object()
234 {
235 reset($this->pap);
236 foreach($this->pap as $key => $obj){
238 if(in_array(get_class($obj),array("sieve_if",
239 "sieve_elsif",
240 "sieve_vacation",
241 "sieve_comment",
242 "sieve_reject",
243 "sieve_fileinto",
244 "sieve_require",
245 "sieve_redirect"))){
248 if(isset($this->pap[$key]) && method_exists($this->pap[$key],"save_object")){
249 $this->pap[$key]->save_object();
250 }
251 }
253 $once = TRUE;
254 foreach($_POST as $name => $value){
256 if(isset($obj->object_id) && preg_match("/^Remove_Object_".$obj->object_id."_/",$name) && $once){
257 $once = FALSE;
258 $this->remove_object($key);
259 }
260 if(isset($obj->object_id) && preg_match("/^Move_Up_Object_".$obj->object_id."_/",$name) && $once){
261 $this->move_up_down($key,"up");
262 $once = FALSE;
263 }
264 if(isset($obj->object_id) && preg_match("/^Move_Down_Object_".$obj->object_id."_/",$name) && $once){
265 $this->move_up_down($key,"down");
266 $once = FALSE;
267 }
268 if(isset($obj->object_id) && preg_match("/^Add_Object_Top_".$obj->object_id."_/",$name) && $once){
269 $this->add_new_object($obj->object_id,"top");
270 $once = FALSE;
271 }
272 if(isset($obj->object_id) && preg_match("/^Add_Object_Bottom_".$obj->object_id."_/",$name) && $once){
273 $this->add_new_object($obj->object_id,"bottom");
274 $once = FALSE;
275 }
276 }
277 }
278 }
281 /* Add a new object at the given position */
282 function add_new_object($id,$top_bottom = "bottom")
283 {
284 $this->add_new = TRUE;
285 $this->add_new_id = $id;
286 $this->add_type = $top_bottom;
287 }
290 /* Remove the object at the given position */
291 function remove_object($key_id)
292 {
293 $class = get_class($this->pap[$key_id]);
294 if(in_array($class,array("sieve_if"))){
295 $block_start= $key_id;
296 $block_end = $this->get_block_end($key_id);
297 for($i = $block_start ; $i <= $block_end ; $i ++ ){
298 unset($this->pap[$i]);
299 }
300 }else{
301 unset($this->pap[$key_id]);
302 }
303 $tmp = array();
304 foreach($this->pap as $element){
305 $tmp[] = $element;
306 }
307 $this->pap = $tmp;
308 }
311 /* This function moves a given element to another position.
312 * Single elements like "keep;" will simply be moved one posisition down/up.
313 * Multiple elements like if-elsif-else will be moved as block.
314 *
315 * $key_id specified the element that should be moved.
316 * $direction specifies to move elements "up" or "down"
317 */
318 function move_up_down($key_id,$direction = "down")
319 {
321 /* Get the current element to decide what to move. */
322 $e_class = get_class($this->pap[$key_id]);
324 if(in_array($e_class,array("sieve_if"))){
325 $block_start= $key_id;
326 $block_end = $this->get_block_end($key_id);
328 /* Depending on the direction move up down */
329 if($direction == "down"){
330 $next_free = $this->_get_next_free_move_slot($block_end,$direction);
331 }else{
332 $next_free = $this->_get_next_free_move_slot($block_start,$direction);
333 }
335 /* Move the given block */
336 $this->move_multiple_elements($block_start,$block_end,$next_free);
337 }
339 if(in_array($e_class,array( "sieve_stop",
340 "sieve_keep",
341 "sieve_require",
342 "sieve_comment",
343 "sieve_stop",
344 "sieve_reject",
345 "sieve_fileinto",
346 "sieve_redirect",
347 "sieve_discard"))){
348 $this->move_single_element($key_id,$this->_get_next_free_move_slot($key_id,$direction));
349 }
350 }
353 /* Move the given block to position */
354 function move_multiple_elements($start,$end,$to)
355 {
356 /* Use class names for testing */
357 $data = $this->pap;
359 /* Get block to move */
360 $block_to_move = array_slice($data,$start, ($end - $start +1));
362 /* We want do move this block up */
363 if($end > $to){
365 /* Get start block */
366 $start_block = array_slice($data,0,$to);
368 /* Get Get all elements between the block to move
369 * and next free position
370 */
371 $block_to_free = array_slice($data,$to ,$start - $to );
372 $block_to_end = array_slice($data,$end+1);
373 $new = array();
374 foreach($start_block as $block){
375 $new[] = $block;
376 }
377 foreach($block_to_move as $block){
378 $new[] = $block;
379 }
380 foreach($block_to_free as $block){
381 $new[] = $block;
382 }
383 foreach($block_to_end as $block){
384 $new[] = $block;
385 }
386 $old = $this->pap;
387 $this->pap = $new;
388 }
391 /* We want to move this block down. */
392 if($to > $end){
394 /* Get start block */
395 $start_block = array_slice($data,0,$start);
397 /* Get Get all elements between the block to move
398 * and next free position
399 */
400 $block_to_free = array_slice($data,$end +1,($to - $end ));
402 /* Get the rest
403 */
404 $block_to_end = array_slice($data,$to+1);
406 $new = array();
407 foreach($start_block as $block){
408 $new[] = $block;
409 }
410 foreach($block_to_free as $block){
411 $new[] = $block;
412 }
413 foreach($block_to_move as $block){
414 $new[] = $block;
415 }
416 foreach($block_to_end as $block){
417 $new[] = $block;
418 }
419 $old = $this->pap;
420 $this->pap = $new;
421 }
422 }
425 /* This function returns the id of the element
426 * where the current block ends
427 */
428 function get_block_end($start)
429 {
430 /* Only execute if this is a really a block element.
431 * Block elements is only sieve_if
432 */
433 if(in_array(get_class($this->pap[$start]),array("sieve_if"))){
435 $class = get_class($this->pap[$start]);
436 $next_class = get_class($this->pap[$start+1]);
437 $block_depth = 0;
439 $end = FALSE;
441 while(!$end && $start < count($this->pap)){
443 if($class == "sieve_block_start"){
444 $block_depth ++;
445 }
447 if($class == "sieve_block_end"){
448 $block_depth --;
449 }
451 if( $block_depth == 0 &&
452 $class == "sieve_block_end" &&
453 !in_array($next_class,array("sieve_else","sieve_elsif"))){
454 $end = TRUE;
455 $start --;
456 }
457 $start ++;
458 $class = get_class($this->pap[$start]);
459 $next_class = get_class($this->pap[$start+1]);
460 }
461 }
462 return($start);
463 }
466 /* This function moves the single element at
467 * position $from to position $to.
468 */
469 function move_single_element($from,$to)
470 {
471 if($from == $to) {
472 return;
473 }
475 $ret = array();
476 $tmp = $this->pap;
478 $begin = array();
479 $middle = array();
480 $end = array();
481 $element = $this->pap[$from];
483 if($from > $to ){
485 /* Get all element in fron to element to move */
486 if($from != 0){
487 $begin = array_slice($tmp,0,$to);
488 }
490 /* Get all elements between */
491 $middle = array_slice($tmp,$to , ($from - ($to) ));
493 /* Get the rest */
494 $end = array_slice($tmp,$from+1);
496 foreach($begin as $data){
497 $ret[] = $data;
498 }
499 $ret[] = $element;
500 foreach($middle as $data){
501 $ret[] = $data;
502 }
503 foreach($end as $data){
504 $ret[] = $data;
505 }
506 $this->pap = $ret;
507 }
508 if($from < $to ){
510 /* Get all element in fron to element to move */
511 if($from != 0){
512 $begin = array_slice($tmp,0,$from);
513 }
515 /* Get all elements between */
516 $middle = array_slice($tmp,$from+1 , ($to - ($from)));
518 /* Get the rest */
519 $end = array_slice($tmp,$to+1);
521 foreach($begin as $data){
522 $ret[] = $data;
523 }
524 foreach($middle as $data){
525 $ret[] = $data;
526 }
527 $ret[] = $element;
528 foreach($end as $data){
529 $ret[] = $data;
530 }
531 $this->pap = $ret;
532 }
533 }
536 /* Returns the next free position where we
537 * can add a new sinle element
538 * $key_id = Current position
539 * $direction = Forward or backward.
540 */
541 function _get_next_free_move_slot($key_id,$direction)
542 {
543 $last_class = "";
544 $current_class ="";
545 $next_class = "";
547 /* After this elements we can add new elements
548 * without having any trouble.
549 */
550 $allowed_to_add_after = array("sieve_keep",
551 "sieve_require",
552 "sieve_stop",
553 "sieve_reject",
554 "sieve_fileinto",
555 "sieve_redirect",
556 "sieve_discard",
557 "sieve_comment",
558 "sieve_block_start"
559 );
561 /* Before this elements we can add new elements
562 * without having any trouble.
563 */
564 $allowed_to_add_before = array("sieve_keep",
565 "sieve_require",
566 "sieve_stop",
567 "sieve_reject",
568 "sieve_fileinto",
569 "sieve_comment",
570 "sieve_redirect",
571 "sieve_discard",
572 "sieve_if",
573 "sieve_block_end"
574 );
576 if($direction == "down"){
578 $test = $this->pap;
579 while($key_id < count($test)){
580 if(($key_id+1) == count($test)) {
581 return($key_id);
582 }
583 $key_id ++;
584 $current_class = get_class($test[$key_id]);
585 if(in_array($current_class, $allowed_to_add_after)){
586 return($key_id);
587 }
588 }
589 }else{
591 $test = $this->pap;
592 if($key_id == 0) {
593 return($key_id);
594 }
595 $key_id --;
596 while($key_id >=0 ){
597 $current_class = get_class($test[$key_id]);
598 if(in_array($current_class, $allowed_to_add_before)){
599 return($key_id);
600 }
601 $key_id --;
602 }
603 return(0);
604 }
605 }
608 /* Need to be reviewed */
609 function get_sieve_script()
610 {
611 $tmp ="";
612 if(count($this->pap)){
613 $buffer = "";
614 foreach($this->pap as $part) {
615 if(get_class($part) == "sieve_block_end"){
616 $buffer = substr($buffer,0,strlen($buffer)-(strlen(SIEVE_INDENT_TAB)));
617 }
618 $tmp2 = $part->get_sieve_script_part();
620 if(get_class($part) == "sieve_reject"){
621 $tmp.=$tmp2;
622 }else{
624 $tmp3 = split("\n",$tmp2);
625 foreach($tmp3 as $str){
626 $str2 = trim($str);
627 if(empty($str2)) continue;
628 $tmp.= $buffer.$str."\n";
629 }
630 }
631 if(get_class($part) == "sieve_block_start"){
632 $buffer .= SIEVE_INDENT_TAB;
633 }
634 }
635 }
636 if(!preg_match("/Generated by GOsa - Gonicus System Administrator/",$tmp)){
637 $tmp = "#Generated by GOsa - Gonicus System Administrator \n ".$tmp;
638 }
639 return($tmp);
640 }
642 function Add_Element()
643 {
644 $tmp = array("ELEMENTS" => array(array("class" => "qouted-string","text"=> "Bla bla, later more")));
645 $this->pap[] = new sieve_comment($tmp,rand(1000,100000));
646 }
648 function check()
649 {
650 $msgs = array();
651 foreach($this->pap as $obj){
653 $o_msgs = $obj->check();
654 foreach($o_msgs as $o_msg){
655 $msgs[] = $o_msg;
656 }
657 }
658 return($msgs);
659 }
660 }
663 /* Create valid sieve string/string-list
664 * out of a given array
665 */
666 function sieve_create_strings($data)
667 {
668 $ret = "";
669 if(is_array($data)){
670 if(count($data) == 1){
671 $ret = "\"";
672 foreach($data as $dat){
673 $ret .=$dat;
674 }
675 $ret.="\"";
676 }else{
677 foreach($data as $dat){
678 $ret.= "\"";
679 $ret.=$dat;
680 $ret.="\", ";
681 }
682 $ret = preg_replace("/,$/","",trim($ret));
683 $ret = "[".$ret."]";
684 }
685 }else{
687 $Multiline = preg_match("/\n/",$data);
688 $data = preg_replace("/\r/","",$data);;
690 if($Multiline){
691 $ret = "text: \r\n".$data."\r\n.\r\n";
692 }else{
693 $ret = "\"".$data."\"";
694 }
695 }
696 $ret = preg_replace("/\"\"/","\"",$ret);
697 $ret = preg_replace("/\n/","\r\n",$ret);
699 return($ret);
700 }
702 /* This checks if there is a string at the current position
703 * in the token array.
704 * If there is a string list at the current position,
705 * this function will return a complete list of all
706 * strings used in this list.
707 * It also returns an offset of the last token position
708 */
709 function sieve_get_strings($data,$id)
710 {
711 $ret = array();
712 if($data[$id]['class'] == "left-bracket"){
713 while($data[$id]['class'] != "right-bracket" && $id < count($data)){
715 if($data[$id]['class'] == "quoted-string"){
716 $ret[] = $data[$id]['text'];
717 }
718 $id ++;
719 }
720 }elseif($data[$id]['class'] == "quoted-string"){
721 $ret[] = $data[$id]['text'];
722 }elseif($data[$id]['class'] == "number"){
723 $ret[] = $data[$id]['text'];
724 }
725 return(array("OFFSET" => $id, "STRINGS" => $ret));
726 }
728 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
729 ?>