1 <?php
2 /*! \brief mail plugin
3 \author Cajus Pollmeier <pollmeier@gonicus.de>
4 \version 2.00
5 \date 24.07.2003
7 This class provides the functionality to read and write all attributes
8 relevant for gosaMailAccounts from/to the LDAP. It does syntax checking
9 and displays the formulars required.
10 */
12 class mailAccount extends plugin
13 {
14 /* Definitions */
15 var $plHeadline = "Mail";
16 var $plDescription = "This does something";
17 var $method = "mailMethod";
19 var $gosaVacationStart = 0;
20 var $gosaVacationStop = 0;
21 var $view_logged = FALSE;
23 /* plugin specific values */
24 var $mail = "";
25 var $gosaMailAlternateAddress = array();
26 var $gosaMailForwardingAddress = array();
27 var $gosaMailDeliveryMode = "[L ]";
28 var $gosaMailServer = "";
29 var $gosaMailQuota = "";
30 var $gosaMailMaxSize = "";
31 var $gosaVacationMessage = "";
32 var $gosaSpamSortLevel = "";
33 var $gosaSpamMailbox = "";
35 var $quotaUsage = 0;
36 var $forward_dialog = FALSE;
37 var $folder_prefix = "";
38 var $mailboxList = array("INBOX");
39 var $default_permissions = "none";
40 var $member_permissions = "post";
41 var $members = array();
42 var $admins = array();
43 var $vacations = array();
44 var $perms = array( "lrs" => "read",
45 "lrsp" => "post",
46 "lrsip" => "append",
47 "lrswipcd" => "write",
48 "lrswipcda" => "all" );
50 /* attribute list for save action */
51 var $attributes= array("mail", "gosaMailServer", "gosaMailQuota", "gosaMailMaxSize","gosaMailForwardingAddress",
52 "gosaMailDeliveryMode", "gosaSpamSortLevel", "gosaSpamMailbox","gosaMailAlternateAddress","gosaVacationStart","gosaVacationStop",
53 "gosaVacationMessage", "gosaMailAlternateAddress", "gosaMailForwardingAddress");
54 var $objectclasses= array("gosaMailAccount");
55 var $uid = "";
57 var $sieve_management = NULL;
58 var $multiple_support = TRUE;
60 /* constructor, if 'dn' is set, the node loads the given
61 'dn' from LDAP */
62 function mailAccount (&$config, $dn= NULL)
63 {
64 global $class_mapping;
66 $this->gosaVacationStart = time();
67 $this->gosaVacationStop = time();
69 /* Load bases attributes */
70 plugin::plugin($config, $dn);
72 /* Set uid */
73 if(isset($this->attrs['uid'])){
74 $this->uid = $this->attrs['uid'][0];
75 }
77 if(is_array($this->gosaMailServer) && isset($this->gosaMailServer[0])){
78 $this->gosaMailServer = $this->gosaMailServer[0];
79 }
81 /* Save initial account state */
82 $this->initially_was_account= $this->is_account;
84 /* Set mailMethod to the one defined in gosa.conf */
85 if (isset($this->config->current['MAILMETHOD'])){
86 $method= $this->config->current['MAILMETHOD'];
88 $cls = get_correct_class_name("mailMethod$method");
89 if ($cls && class_exists($cls)){
90 $this->method= $cls;
91 } else {
92 msg_dialog::display(_("Configuration error"), sprintf(_("Unkown mail method '%s' specified!"), $method), ERROR_DIALOG);
93 }
94 }
97 /* Create the account prefix user. user/
98 Preset folder prefix. Will change it later to respect
99 altnamespace. */
100 if (isset($this->config->current['CYRUSUNIXSTYLE']) && $this->config->current['CYRUSUNIXSTYLE'] == "true"){
101 $this->folder_prefix= "user/";
102 }elseif (isset($this->config->data['MAIN']['CYRUSUNIXSTYLE']) && $this->config->data['MAIN']['CYRUSUNIXSTYLE'] == "true"){
103 $this->folder_prefix= "user/";
104 } else {
105 $this->folder_prefix= "user.";
106 }
108 /* This is not a new account, parse additional attributes */
109 if (($dn !== NULL) && ($dn != "new") && $this->is_account){
111 /* Load attributes containing arrays */
112 foreach (array("gosaMailAlternateAddress", "gosaMailForwardingAddress") as $val){
113 $this->$val= array();
114 if (isset($this->attrs["$val"]["count"])){
115 for ($i= 0; $i<$this->attrs["$val"]["count"]; $i++){
116 array_push($this->$val, $this->attrs["$val"][$i]);
117 }
118 }
119 }
122 /* Only do IMAP actions if gosaMailServer attribute is set */
123 if (isset ($this->attrs["gosaMailServer"][0])){
125 $method = new $this->method($this->config);
126 $id = $method->uattrib;
128 /* Adapt attributes if needed */
129 $method->fixAttributesOnLoad($this);
131 /* FixAttributesOnLoad possibly creates an array out of gosaMailServer.
132 If the mail tab wasn't opened once before saving, the account can't be saved */
133 if(is_array($this->gosaMailServer)){
134 $this->gosaMailServer = $this->gosaMailServer[0];
135 }
137 if ($method->connect($this->attrs["gosaMailServer"][0])){
139 /* Update quota values */
140 $quota= $method->getQuota($this->folder_prefix.$this->$id);
142 if($quota){
143 if ($quota['gosaMailQuota'] == 2147483647){
144 $this->quotaUsage = "";
145 $this->gosaMailQuota = "";
146 } else {
147 $this->quotaUsage = $quota['quotaUsage'];
148 $this->gosaMailQuota = $quota['gosaMailQuota'];
149 }
150 }else{
151 $this->quotaUsage = "";
152 $this->gosaMailQuota = "";
153 }
155 /* Get mailboxes / folder like INBOX ..*/
156 $this->mailboxList= $method->getMailboxList($this->folder_prefix.$this->$id,$this->$id);
158 $method->disconnect();
159 }else{
160 /* Could not connect to ldap.
161 */
162 if (isset($this->attrs['gosaMailQuota'][0])){
163 $this->gosaMailQuota = $this->attrs['gosaMailQuota'][0];
164 }
165 }
166 }
167 }
169 /* Fill vacation array */
170 $this->vacation= array();
171 if (isset($this->config->current['VACATIONDIR'])){
172 $dir= $this->config->current['VACATIONDIR'];
173 if (is_dir($dir) && is_readable($dir)){
175 /* Look for files and build the vacation array */
176 $dh= opendir($dir);
177 while ($file = readdir($dh)){
178 $description= $this->parse_vacation("$dir/$file");
179 if ($description != ""){
180 $this->vacation["$dir/$file"]= $description;
181 }
182 }
183 closedir($dh);
184 }
185 }
187 /* Create sieve management class */
188 $method = new $this->method($this->config);
189 $id = $method->uattrib;
190 $this->sieve_management = new sieveManagement($this->config,$this->dn,$this,$id);
192 /* Get global filter config */
193 if (!session::is_set("mailfilter")){
194 $ui= get_userinfo();
195 $base= get_base_from_people($ui->dn);
196 $mailfilter= array( "depselect" => $base,
197 "muser" => "",
198 "regex" => "*");
199 session::set("mailfilter", $mailfilter);
200 }
201 }
204 function parse_vacation($file)
205 {
206 $desc= "";
208 if (is_file($file)){
209 $fh = fopen($file, "r");
210 $line= fgets($fh, 256);
212 if (!preg_match('/^DESC:/', $line)){
213 msg_dialog::display(_("Configuration error"), sprintf(_("No DESC tag in vacation template '%s'!"), $file), ERROR_DIALOG);
214 return $desc;
215 }
216 fclose ($fh);
218 $desc= trim(preg_replace('/^DESC:\s*/', '', $line));
219 }
221 return $desc;
222 }
225 function execute()
226 {
227 /* Call parent execute */
228 plugin::execute();
230 /* Log view */
231 if($this->is_account && !$this->view_logged){
232 $this->view_logged = TRUE;
233 new log("view","users/".get_class($this),$this->dn);
234 }
236 /* Initialise vars */
238 /* Load templating engine */
239 $smarty= get_smarty();
240 $display= "";
242 /* Get available mailserver */
243 $mailserver= array();
244 foreach ($this->config->data['SERVERS']['IMAP'] as $key => $val){
245 $mailserver[]= $key;
246 }
248 /*
249 * Sieve Management
250 */
251 if(isset($_POST['sieveManagement'])
252 && preg_match("/C/",$this->gosaMailDeliveryMode)
253 && $this->acl_is_writeable("sieveManagement")) {
255 $this->dialog = $this->sieve_management;
256 }
258 /* Cancel sieve edit */
259 if(isset($_POST['sieve_cancel'])){
260 $this->dialog = FALSE;
261 }
263 /* Save sieve changes */
264 if(isset($_POST['sieve_finish'])){
265 $this->sieve_management = $this->dialog;
266 $this->dialog = FALSE;
267 }
269 if(is_object($this->dialog)){
270 $this->dialog->save_object();
271 return($this->dialog->execute());
272 }
275 /* Handle account state */
276 /* Do we need to flip is_account state? */
277 if(isset($_POST['modify_state'])){
278 if($this->is_account && $this->acl_is_removeable()){
279 $this->is_account= FALSE;
280 }elseif(!$this->is_account && $this->acl_is_createable()){
281 $this->is_account= TRUE;
282 }
283 }
285 /* Do we represent a valid account? */
286 if(!$this->multiple_support_active){
287 if (!$this->is_account && $this->parent === NULL){
288 $display= "<img alt=\"\" src=\"images/stop.png\" align=\"middle\"> <b>".
289 _("This account has no mail extensions.")."</b>";
291 $display.= back_to_main();
292 return ($display);
293 }
295 /* Show tab dialog headers */
296 if ($this->parent !== NULL){
297 if ($this->is_account){
298 if($this->accountDelegationsConfigured()){
299 $display= $this->show_disable_header(_("Remove mail account"),
300 _("This account can't be removed while there are delegations configured. Remove those delegations first."),TRUE,TRUE);
301 }else{
302 $display= $this->show_disable_header(_("Remove mail account"),
303 _("This account has mail features enabled. You can disable them by clicking below."));
304 }
305 } else {
306 $display= $this->show_enable_header(_("Create mail account"), _("This account has mail features disabled. You can enable them by clicking below."));
307 return ($display);
308 }
309 }
310 }
312 /* Forwarder subdialog */
314 /* Trigger forward add dialog? */
315 if (isset($_POST['add_local_forwarder'])){
316 $this->forward_dialog= TRUE;
317 $this->dialog= TRUE;
318 }
320 /* Cancel forward add dialog? */
321 if (isset($_POST['add_locals_cancel'])){
322 $this->forward_dialog= FALSE;
323 $this->dialog= FALSE;
324 }
326 /* Finished adding of locals? */
327 if (isset($_POST['add_locals_finish'])){
329 /* Check if we are able to write gosaMailForwardingAddress */
330 if($this->acl_is_writeable("gosaMailForwardingAddress")){
332 /* Walk through list of forwarders, ignore own addresses */
333 foreach ($_POST['local_list'] as $val){
334 if (!in_array ($val, $this->gosaMailAlternateAddress) &&
335 $val != $this->mail){
337 $this->addForwarder($val);
338 $this->is_modified= TRUE;
339 }
340 }
341 }
342 $this->forward_dialog= FALSE;
343 $this->dialog= FALSE;
344 }
346 /* Add forward email addresses */
347 if (isset($_POST['add_forwarder'])){
348 if ($_POST['forward_address'] != ""){
350 /* Valid email address specified? */
351 $address= $_POST['forward_address'];
352 $valid= FALSE;
353 if (!tests::is_email($address)){
354 if (!tests::is_email($address, TRUE)){
355 if ($this->is_template){
356 $valid= TRUE;
357 } else {
358 msg_dialog::display(_("Error"),
359 msgPool::invalid(_("Mail address"),"","","example@your-domain.com"),
360 ERROR_DIALOG);
361 }
362 }
363 } elseif ($address == $this->mail
364 || in_array($address, $this->gosaMailAlternateAddress)) {
365 msg_dialog::display(_("Error"),_("Cannot add your primary address to the list of forwarders.") , ERROR_DIALOG);
366 } else {
367 $valid= TRUE;
368 }
370 if ($valid){
372 /* Add it, if we are able to write gosaMailForwardingAddress */
373 if($this->acl_is_writeable("gosaMailForwardingAddress")){
374 $this->addForwarder ($address);
375 $this->is_modified= TRUE;
376 }
377 }
378 }
379 }
381 /* Delete forward email addresses */
382 if (isset($_POST['delete_forwarder'])){
383 $this->delForwarder ($_POST['forwarder_list']);
384 }
387 /* Add alternate email addresses */
388 if (isset($_POST['add_alternate'])){
390 $valid= FALSE;
391 if (!tests::is_email($_POST['alternate_address'])){
392 if ($this->is_template){
393 if (!(tests::is_email($_POST['alternate_address'], TRUE))){
394 msg_dialog::display(_("Error"),
395 msgPool::invalid(_("Mail address"),"","","example@your-domain.com"),
396 ERROR_DIALOG);
398 } else {
399 $valid= TRUE;
400 }
401 } else {
402 msg_dialog::display(_("Error"),
403 msgPool::invalid(_("Mail address"),"","","example@your-domain.com"),
404 ERROR_DIALOG);
405 }
407 } else {
408 $valid= TRUE;
409 }
411 if ($valid && ($user= $this->addAlternate ($_POST['alternate_address'])) != ""){
412 $ui= get_userinfo();
413 if ($user != $ui->username){
414 msg_dialog::display(_("Error"), msgPool::duplicated(_("Mail address"))." ".
415 sprintf(_("Already used by user '%s'."), $user), ERROR_DIALOG);
416 }
417 }
418 }
420 /* Delete alternate email addresses */
421 if (isset($_POST['delete_alternate']) && isset($_POST['alternates_list'])){
422 $this->delAlternate ($_POST['alternates_list']);
423 }
426 /* Vacation message */
428 /* Import vacation message? */
429 if (isset($_POST["import_vacation"]) && isset($this->vacation[$_POST["vacation_template"]])){
432 /* Save message */
433 if($this->multiple_support_active){
434 $contents = file_get_contents($_POST["vacation_template"]);
435 }else{
436 $contents = $this->prepare_vacation_template(file_get_contents($_POST["vacation_template"]));
437 }
438 $this->gosaVacationMessage= htmlspecialchars($contents);
439 }
442 /* Display forward dialog if requested above */
444 /* Show forward add dialog */
445 if ($this->forward_dialog){
446 $ldap= $this->config->get_ldap_link();
448 /* Save data */
449 $mailfilter= session::get("mailfilter");
450 foreach( array("depselect", "muser", "regex") as $type){
451 if (isset($_POST[$type])){
452 $mailfilter[$type]= $_POST[$type];
453 }
454 }
455 if (isset($_GET['search'])){
456 $s= mb_substr($_GET['search'], 0, 1, "UTF8")."*";
457 if ($s == "**"){
458 $s= "*";
459 }
460 $mailfilter['regex']= $s;
461 }
462 session::set("mailfilter", $mailfilter);
464 /* Get actual list */
465 $mailusers= array ();
466 if ($mailfilter['regex'] != '*' && $mailfilter['regex'] != ""){
467 $regex= $mailfilter['regex'];
468 $filter= "(|(mail=$regex)(gosaMailAlternateAddress=$regex))";
469 } else {
470 $filter= "";
471 }
472 if ($mailfilter['muser'] != ""){
473 $user= $mailfilter['muser'];
474 $filter= "$filter(|(uid=$user)(cn=$user)(givenName=$user)(sn=$user))";
475 }
477 /* Add already present people to the filter */
478 $exclude= "";
479 foreach ($this->gosaMailForwardingAddress as $mail){
480 $exclude.= "(mail=$mail)";
481 }
482 if ($exclude != ""){
483 $filter.= "(!(|$exclude))";
484 }
486 $res= get_list("(&(objectClass=gosaMailAccount)$filter)", "users", $mailfilter['depselect'],
487 array("sn", "mail", "givenName"), GL_SIZELIMIT | GL_SUBSEARCH);
488 $ldap->cd($mailfilter['depselect']);
489 $ldap->search ("(&(objectClass=gosaMailAccount)$filter)", array("sn", "mail", "givenName"));
490 error_reporting (0);
491 while ($attrs= $ldap->fetch()){
492 if(preg_match('/%/', $attrs['mail'][0])){
493 continue;
494 }
495 $name= $this->make_name($attrs);
496 $mailusers[$attrs['mail'][0]]= $name."<".
497 $attrs['mail'][0].">";
498 }
499 error_reporting (E_ALL | E_STRICT);
500 natcasesort ($mailusers);
501 reset ($mailusers);
503 /* Show dialog */
504 $smarty->assign("search_image", get_template_path('images/search.png'));
505 $smarty->assign("usearch_image", get_template_path('images/search_user.png'));
506 $smarty->assign("tree_image", get_template_path('images/tree.png'));
507 $smarty->assign("infoimage", get_template_path('images/info.png'));
508 $smarty->assign("launchimage", get_template_path('images/small_filter.png'));
509 $smarty->assign("mailusers", $mailusers);
510 if (isset($_POST['depselect'])){
511 $smarty->assign("depselect", $_POST['depselect']);
512 }
513 $smarty->assign("deplist", $this->config->idepartments);
514 $smarty->assign("apply", apply_filter());
515 $smarty->assign("alphabet", generate_alphabet());
516 $smarty->assign("hint", print_sizelimit_warning());
517 foreach( array("depselect", "muser", "regex") as $type){
518 $smarty->assign("$type", $mailfilter[$type]);
519 }
520 $smarty->assign("hint", print_sizelimit_warning());
522 $display.= $smarty->fetch (get_template_path('mail_locals.tpl', TRUE, dirname(__FILE__)));
523 return ($display);
524 }
526 /* Display mail account tab */
528 $smarty->assign("mailServers", $mailserver);
529 $SkipWrite = (!isset($this->parent) || !$this->parent) && !session::is_set('edit');
531 $tmp = $this->plInfo();
532 foreach($tmp['plProvidedAcls'] as $name => $transl){
533 $smarty->assign("$name"."ACL", $this->getacl($name,$SkipWrite));
534 }
536 foreach(array("gosaMailServer", "gosaMailQuota", "perms", "mail",
537 "gosaMailAlternateAddress", "gosaMailForwardingAddress",
538 "gosaVacationMessage", "gosaMailDeliveryMode", "gosaVacationStart",
539 "gosaVacationStop", "gosaMailMaxSize", "gosaSpamSortLevel", "gosaSpamMailbox") as $val){
540 $smarty->assign("$val", $this->$val);
541 }
543 if (is_numeric($this->gosaMailQuota) && $this->gosaMailQuota != 0){
544 $smarty->assign("quotausage", progressbar(round(($this->quotaUsage * 100)/ $this->gosaMailQuota),100,15,true));
545 $smarty->assign("quotadefined", "true");
546 } else {
547 $smarty->assign("quotadefined", "false");
548 }
550 /* Disable mail field if needed */
551 $method= new $this->method($this->config);
552 if ($method->uattrib == "mail" && $this->initially_was_account){
553 $smarty->assign("mailACL", preg_replace("/w/","",$this->getacl("mail",$SkipWrite)));
554 }
556 /* Disable/Enable range select, but do not disable them twice
557 * if they are already diabled by "use own sieve script"
558 */
559 if (preg_match('/V/', $this->gosaMailDeliveryMode) || preg_match("/C/",$this->gosaMailDeliveryMode)){
560 $smarty->assign('rangeEnabled', "");
561 } else {
562 $smarty->assign('rangeEnabled', "disabled");
563 }
565 if (!preg_match("/L/", $this->gosaMailDeliveryMode)) {
566 $smarty->assign("only_local", "checked");
567 } else {
568 $smarty->assign("only_local", "");
569 }
571 $types = array(
572 "V"=>"use_vacation",
573 "S"=>"use_spam_filter",
574 "R"=>"use_mailsize_limit",
575 "I"=>"drop_own_mails",
576 "C"=>"own_script");
578 /* Fill checkboxes */
579 foreach($types as $option => $varname){
580 if (preg_match("/".$option."/", $this->gosaMailDeliveryMode)) {
581 $smarty->assign($varname, "checked");
582 } else {
583 $smarty->assign($varname, "");
584 }
585 }
587 /* Display mail account tab */
588 if($this->gosaVacationStart ==0){
589 $date= getdate(time());
590 }else{
591 $date= getdate($this->gosaVacationStart);
592 }
593 $days= array();
594 for($d= 1; $d<32; $d++){
595 $days[$d]= $d;
596 }
597 $years= array();
598 for($y= $date['year']-10; $y<$date['year']+10; $y++){
599 $years[]= $y;
600 }
601 $months= array(_("January"), _("February"), _("March"), _("April"),
602 _("May"), _("June"), _("July"), _("August"), _("September"),
603 _("October"), _("November"), _("December"));
604 $smarty->assign("start_day", $date["mday"]);
605 $smarty->assign("days", $days);
606 $smarty->assign("months", $months);
607 $smarty->assign("start_month", $date["mon"]-1);
608 $smarty->assign("years", $years);
609 $smarty->assign("start_year", $date["year"]);
611 if($this->gosaVacationStop ==0){
612 $date= getdate(time());
613 $date["mday"]++;
614 }else{
615 $date= getdate($this->gosaVacationStop);
616 }
617 $smarty->assign("end_day", $date["mday"]);
618 $smarty->assign("end_month", $date["mon"]-1);
619 $smarty->assign("end_year", $date["year"]);
623 /* Have vacation templates? */
624 $smarty->assign("template", "");
625 if (count($this->vacation)){
626 $smarty->assign("show_templates", "true");
627 $smarty->assign("vacationtemplates", $this->vacation);
628 if (isset($_POST['vacation_template'])){
629 $smarty->assign("template", $_POST['vacation_template']);
630 }
631 } else {
632 $smarty->assign("show_templates", "false");
633 }
635 /* Fill spam selector */
636 $spamlevel= array();
637 for ($i= 0; $i<21; $i++){
638 $spamlevel[]= $i;
639 }
640 $smarty->assign("spamlevel", $spamlevel);
641 $smarty->assign("spambox", $this->mailboxList);
643 foreach($this->attributes as $attr){
644 $u_attr = "use_".$attr;
645 $smarty->assign($u_attr,in_array($attr,$this->multi_boxes));
646 }
648 foreach(array("only_local","gosaMailForwardingAddress","use_mailsize_limit","drop_own_mails","use_vacation","use_spam_filter") as $attr){
649 $u_attr = "use_".$attr;
650 $smarty->assign($u_attr,in_array($attr,$this->multi_boxes));
651 }
653 $smarty->assign("multiple_support",$this->multiple_support_active);
654 $display.= $smarty->fetch (get_template_path('generic.tpl', TRUE, dirname(__FILE__)));
655 return ($display);
656 }
659 /* remove object from parent */
660 function remove_from_parent()
661 {
662 /* Cancel if there's nothing to do here */
663 if (!$this->initially_was_account){
664 return;
665 }
667 /* include global link_info */
668 $ldap= $this->config->get_ldap_link();
670 /* Remove and write to LDAP */
671 plugin::remove_from_parent();
673 /* Zero arrays */
674 $this->attrs['gosaMailAlternateAddress']= array();
675 $this->attrs['gosaMailForwardingAddress']= array();
677 /* Adapt attributes if needed */
678 $method= new $this->method($this->config);
679 $method->fixAttributesOnRemove($this);
681 @DEBUG (DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__,$this->attributes, "Save");
682 $ldap->cd($this->dn);
683 $this->cleanup();
685 $ldap->modify ($this->attrs);
687 /* Add "view" to logging class */
688 new log("remove","users/".get_class($this),$this->dn,array_keys($this->attrs),$ldap->get_error());
690 show_ldap_error($ldap->get_error(), sprintf(_("Removing of user/mail account with dn '%s' failed."),$this->dn));
692 /* Connect to IMAP server for account deletion */
693 if ($this->gosaMailServer != ""){
694 $method= new $this->method($this->config);
695 $id= $method->uattrib;
696 if ($method->connect($this->gosaMailServer)){
698 /* Remove account from IMAP server */
699 $method->deleteMailbox($this->folder_prefix.$this->$id);
700 $method->disconnect();
701 }
702 }
704 /* Update shared folder membership, ACL may need to be updated */
705 $this->updateSharedFolder();
707 /* Optionally execute a command after we're done */
708 $this->handle_post_events("remove",array("uid" => $this->uid));
709 }
712 /* check if we have some delegations configured, those delegations must be removed first */
713 function accountDelegationsConfigured()
714 {
715 /* We are in administrational edit mode.
716 Check tab configurations directly */
717 if(isset($this->attrs)){
718 $checkArray = array("kolabInvitationPolicy","unrestrictedMailSize", "calFBURL","kolabDelegate","kolabFreeBusyFuture");
719 foreach($checkArray as $index){
720 if(isset($this->attrs[$index])){
721 return(true);
722 }
723 }
724 }
725 return(false);
726 }
729 /* Save data to object */
730 function save_object()
731 {
732 if (isset($_POST['mailTab'])){
734 /* Save ldap attributes */
735 plugin::save_object();
738 if(isset($_POST['own_script'])){
740 if(!preg_match("/C/",$this->gosaMailDeliveryMode)){
741 $str= preg_replace("/[\[\]]/","",$this->gosaMailDeliveryMode);
742 $this->gosaMailDeliveryMode = "[".$str."C]";
743 }
745 }else{
747 /* Assemble mail delivery mode
748 The mode field in ldap consists of values between braces, this must
749 be called when 'mail' is set, because checkboxes may not be set when
750 we're in some other dialog.
752 Example for gosaMailDeliveryMode [LR ]
753 L: Local delivery
754 R: Reject when exceeding mailsize limit
755 S: Use spam filter
756 V: Use vacation message
757 C: Use custm sieve script
758 I: Only insider delivery */
760 $tmp= preg_replace("/[^a-z]/i","",$this->gosaMailDeliveryMode);
763 /* Handle delivery flags */
764 if($this->acl_is_writeable("gosaMailDeliveryModeL")){
765 if(!preg_match("/L/",$tmp) && !isset($_POST['only_local'])){
766 $tmp.="L";
767 }elseif(preg_match("/L/",$tmp) && isset($_POST['only_local'])){
768 $tmp = preg_replace("/L/","",$tmp);
769 }
770 }
772 $opts = array(
773 "R" => "use_mailsize_limit",
774 "S" => "use_spam_filter",
775 "V" => "use_vacation",
776 "C" => "own_script",
777 "I" => "drop_own_mails");
779 foreach($opts as $flag => $post){
780 if($this->acl_is_writeable("gosaMailDeliveryMode".$flag)){
781 if(!preg_match("/".$flag."/",$tmp) && isset($_POST[$post])){
782 $tmp.= $flag;
783 }elseif(preg_match("/".$flag."/",$tmp) && !isset($_POST[$post])){
784 $tmp = preg_replace("/".$flag."/","",$tmp);
785 }
786 }
787 }
789 $tmp= "[$tmp]";
790 if ($this->gosaMailDeliveryMode != $tmp){
791 $this->is_modified= TRUE;
792 }
793 $this->gosaMailDeliveryMode= $tmp;
796 if($this->acl_is_writeable("gosaVacationMessage") && preg_match("/V/",$this->gosaMailDeliveryMode)){
797 if(isset($_POST['gosaVacationStart'])){
798 $this->gosaVacationStart = $_POST['gosaVacationStart'];
799 }
800 if(isset($_POST['gosaVacationStop'])){
801 $this->gosaVacationStop = $_POST['gosaVacationStop'];
802 }
803 }
804 }
805 }
806 }
809 /* Save data to LDAP, depending on is_account we save or delete */
810 function save()
811 {
812 $ldap= $this->config->get_ldap_link();
814 /* Call parents save to prepare $this->attrs */
815 plugin::save();
817 /* Save arrays */
818 $this->attrs['gosaMailAlternateAddress']= $this->gosaMailAlternateAddress;
819 $this->attrs['gosaMailForwardingAddress']= $this->gosaMailForwardingAddress;
821 /* Adapt attributes if needed */
822 $method= new $this->method($this->config);
823 $id= $method->uattrib;
825 $method->fixAttributesOnStore($this);
827 /* Remove Mailquota if = "" or "0" */
828 if((isset($this->attrs['gosaMailQuota']))&&(!$this->attrs['gosaMailQuota'])) {
829 $this->attrs['gosaMailQuota']=0;
830 }
832 if(empty($this->attrs['gosaSpamMailbox'])){
833 unset($this->attrs['gosaSpamMailbox']);
834 }
836 $this->attrs['mail'] = strtolower($this->attrs['mail']);
838 /* Remove attributes - if not needed */
839 if (!preg_match('/V/', $this->gosaMailDeliveryMode)){
840 unset($this->attrs['gosaVacationStart']);
841 unset($this->attrs['gosaVacationStop']);
842 }
845 /* Remove attributes - if not needed */
846 if (!preg_match('/V/', $this->gosaMailDeliveryMode)){
847 unset($this->attrs['gosaVacationStart']);
848 unset($this->attrs['gosaVacationStop']);
849 }
851 /* Save data to LDAP */
852 $ldap->cd($this->dn);
853 $this->cleanup();
854 $ldap->modify ($this->attrs);
856 show_ldap_error($ldap->get_error(), sprintf(_("Saving of user/mail account with dn '%s' failed."),$this->dn));
858 /* Log last action */
859 if($this->initially_was_account){
860 new log("modify","users/".get_class($this),$this->dn,array_keys($this->attrs),$ldap->get_error());
861 }else{
862 new log("create","users/".get_class($this),$this->dn,array_keys($this->attrs),$ldap->get_error());
863 }
865 /* Only do IMAP actions if we are not a template */
866 if (!$this->is_template){
868 if ($method->connect($this->gosaMailServer)){
869 $method->updateMailbox($this->folder_prefix.$this->$id);
871 $method->setQuota($this->folder_prefix.$this->$id, $this->gosaMailQuota);
872 $method->disconnect();
874 /* Ensure that this is an existing account */
875 if(1==1 || $this->initially_was_account){
877 /* Write sieve information only if not in C mode */
878 if (!is_integer(strpos($this->gosaMailDeliveryMode, "C"))){
879 $method->configureFilter($this->$id,
880 $this->gosaMailDeliveryMode,
881 $this->mail,
882 $this->gosaMailAlternateAddress,
883 $this->gosaMailMaxSize,
884 $this->gosaSpamMailbox,
885 $this->gosaSpamSortLevel,
886 $this->gosaVacationMessage);
887 $this->is_modified = TRUE;
888 }else{
889 $this->sieve_management->save();
890 }
891 }
892 }
893 }
895 /* Optionally execute a command after we're done */
896 if ($this->initially_was_account == $this->is_account){
897 if ($this->is_modified){
898 $this->handle_post_events("modify", array("uid" => $this->uid));
899 }
900 } else {
901 $this->handle_post_events("add", array("uid" => $this->uid));
902 }
904 $this->updateSharedFolder();
905 }
908 /* Check formular input */
909 function check()
910 {
911 if(!$this->is_account) return(array());
913 $ldap= $this->config->get_ldap_link();
915 /* Call common method to give check the hook */
916 $message= plugin::check();
918 if(empty($this->gosaMailServer)){
919 $message[]= _("There is no valid mailserver specified, please add one in the system setup.");
920 }
922 /* must: mail */
923 if ($this->mail == ""){
924 $message[]= msgPool::required(_("Primary address"));
925 }
926 if ($this->is_template){
927 if (!tests::is_email($this->mail, TRUE)){
928 $message[]= msgPool::invalid(_("Mail address"),"","","%givenName.%sn@your-domain.com");
929 }
930 } else {
931 if (!tests::is_email($this->mail)){
932 $message[]= msgPool::invalid(_("Mail address"),"","","example@your-domain.com");
933 }
934 }
935 $ldap->cd($this->config->current['BASE']);
936 $ldap->search ("(&(!(objectClass=gosaUserTemplate))(objectClass=gosaMailAccount)(|(mail=".$this->mail.")(gosaMailAlternateAddress=".$this->mail."))(!(uid=".$this->uid."))(!(cn=".$this->uid.")))", array("uid"));
937 if ($ldap->count() != 0){
938 $message[]= msgPool::duplicated(_("Mail address"));
939 }
941 /* Check quota */
942 if ($this->gosaMailQuota != '' && $this->acl_is_writeable("gosaMailQuota")){
943 if (!is_numeric($this->gosaMailQuota)) {
944 $message[]= msgPool::invalid(_("Quota size"),$this->gosaMailQuota,"/^[0-9]*/");
945 } else {
946 $this->gosaMailQuota= (int) $this->gosaMailQuota;
947 }
948 }
950 /* Check rejectsize for integer */
951 if ($this->gosaMailMaxSize != '' && $this->acl_is_writeable("gosaMailMaxSize")){
952 if (!is_numeric($this->gosaMailMaxSize)){
953 $message[]= msgPool::invalid(_("Mail reject size"),$this->gosaMailMaxSize,"/^[0-9]*/");
954 } else {
955 $this->gosaMailMaxSize= (int) $this->gosaMailMaxSize;
956 }
957 }
959 /* Need gosaMailMaxSize if use_mailsize_limit is checked */
960 if (is_integer(strpos($this->gosaMailDeliveryMode, "R")) && $this->gosaMailMaxSize == ""){
961 $message[]= msgPool::required(_("Mail reject size"));
962 }
964 if((preg_match("/S/", $this->gosaMailDeliveryMode))&&(empty($this->gosaSpamMailbox))) {
965 $message[]= msgPool::required(_("Spam folder"));
966 }
968 if (preg_match('/V/', $this->gosaMailDeliveryMode) && $this->gosaVacationStart >= $this->gosaVacationStop){
969 $message[]= msgPool::invalid(_("Vacation interval"));
970 }
972 return ($message);
973 }
976 /* Adapt from template, using 'dn' */
977 function adapt_from_template($dn)
978 {
979 plugin::adapt_from_template($dn);
981 foreach (array("gosaMailAlternateAddress", "gosaMailForwardingAddress") as $val){
982 $this->$val= array();
983 if (isset($this->attrs["$val"]["count"])){
984 for ($i= 0; $i<$this->attrs["$val"]["count"]; $i++){
985 $value= $this->attrs["$val"][$i];
986 foreach (array("sn", "givenName", "uid") as $repl){
987 if (preg_match("/%$repl/i", $value)){
988 $value= preg_replace ("/%$repl/i", $this->parent->$repl, $value);
989 }
990 }
991 array_push($this->$val, strtolower(rewrite($value)));
992 }
993 }
994 }
995 $this->mail= strtolower(rewrite($this->mail));
996 }
999 /* Add entry to forwarder list */
1000 function addForwarder($address)
1001 {
1002 if($this->acl_is_writeable("gosaMailForwardingAddress")){
1003 $this->gosaMailForwardingAddress[]= $address;
1004 $this->gosaMailForwardingAddress= array_unique ($this->gosaMailForwardingAddress);
1005 sort ($this->gosaMailForwardingAddress);
1006 reset ($this->gosaMailForwardingAddress);
1007 $this->is_modified= TRUE;
1008 }else{
1009 msg_dialog::display(_("Permission error"), _("You have no permission to modify these addresses."), ERROR_DIALOG);
1010 }
1011 }
1014 /* Remove list of addresses from forwarder list */
1015 function delForwarder($addresses)
1016 {
1017 if($this->acl_is_writeable("gosaMailForwardingAddress")){
1018 $this->gosaMailForwardingAddress= array_remove_entries ($addresses, $this->gosaMailForwardingAddress);
1019 $this->is_modified= TRUE;
1020 }else{
1021 msg_dialog::display(_("Permission error"), _("You have no permission to modify these addresses."), ERROR_DIALOG);
1022 }
1023 }
1026 /* Add given mail address to the list of alternate adresses ,
1027 check if this mal address is used, skip adding in this case */
1028 function addAlternate($address)
1029 {
1030 if($this->acl_is_writeable("gosaMailAlternateAddress")){
1031 $ldap= $this->config->get_ldap_link();
1032 $address= strtolower($address);
1034 /* Is this address already assigned in LDAP? */
1035 $ldap->cd ($this->config->current['BASE']);
1036 $ldap->search ("(&(objectClass=gosaMailAccount)(|(mail=$address)"."(gosaMailAlternateAddress=$address)))", array("uid"));
1037 $ldap->search ("(&(!(objectClass=gosaUserTemplate))(objectClass=gosaMailAccount)(|(mail=$address)"."(gosaMailAlternateAddress=$address)))", array("uid"));
1039 if ($ldap->count() > 0){
1040 $attrs= $ldap->fetch ();
1041 return ($attrs["uid"][0]);
1042 }
1044 /* Add to list of alternates */
1045 if (!in_array($address, $this->gosaMailAlternateAddress)){
1046 $this->gosaMailAlternateAddress[]= $address;
1047 $this->is_modified= TRUE;
1048 }
1050 sort ($this->gosaMailAlternateAddress);
1051 reset ($this->gosaMailAlternateAddress);
1052 return ("");
1053 }else{
1054 msg_dialog::display(_("Permission error"), _("You have no permission to modify these addresses."), ERROR_DIALOG);
1055 }
1056 }
1059 function delAlternate($addresses)
1060 {
1061 if($this->acl_is_writeable("gosaMailAlternateAddress")){
1062 $this->gosaMailAlternateAddress= array_remove_entries ($addresses,$this->gosaMailAlternateAddress);
1063 $this->is_modified= TRUE;
1064 }else{
1065 msg_dialog::display(_("Permission error"), _("You have no permission to modify these addresses."), ERROR_DIALOG);
1066 }
1067 }
1069 function make_name($attrs)
1070 {
1071 $name= "";
1072 if (isset($attrs['sn'][0])){
1073 $name= $attrs['sn'][0];
1074 }
1075 if (isset($attrs['givenName'][0])){
1076 if ($name != ""){
1077 $name.= ", ".$attrs['givenName'][0];
1078 } else {
1079 $name.= $attrs['givenName'][0];
1080 }
1081 }
1082 if ($name != ""){
1083 $name.= " ";
1084 }
1086 return ($name);
1087 }
1090 /* Create the mail part for the copy & paste dialog */
1091 function getCopyDialog()
1092 {
1093 if(!$this->is_account) return("");
1094 $smarty = get_smarty();
1095 $smarty->assign("mail",$this->mail);
1096 $smarty->assign("gosaMailAlternateAddress",$this->gosaMailAlternateAddress);
1097 $smarty->assign("gosaMailForwardingAddress",$this->gosaMailForwardingAddress);
1098 $str = $smarty->fetch(get_template_path("copypaste.tpl",TRUE, dirname(__FILE__)));
1100 $ret = array();
1101 $ret['status'] = "";
1102 $ret['string'] = $str;
1103 return($ret);
1104 }
1106 function saveCopyDialog()
1107 {
1108 if(!$this->is_account) return;
1110 /* Execute to save mailAlternateAddress && gosaMailForwardingAddress */
1111 $this->execute();
1113 if(isset($_POST['mail'])){
1114 $this->mail = $_POST['mail'];
1115 }
1117 }
1119 function allow_remove()
1120 {
1121 if (isset($this->config->current['MAILMETHOD'])){
1122 $method= $this->config->current['MAILMETHOD'];
1123 if(preg_match("/olab/i",$method)){
1124 $ldap = $this->config->get_ldap_link();
1125 $ldap->cd($this->config->current['BASE']);
1126 $ldap->cat($this->dn);
1127 if($ldap->count()){
1128 $attrs = $ldap->fetch();
1129 if(isset($attrs['kolabDeleteFlag'])){
1130 return(_("Waiting for kolab to remove mail properties."));
1131 }elseif(in_array("gosaMailAccount",$attrs['objectClass'])){
1132 return(_("Please remove the mail account first, to allow kolab to call its remove methods."));
1133 }
1134 }
1135 }
1136 }
1137 }
1140 function PrepareForCopyPaste($source)
1141 {
1142 plugin::PrepareForCopyPaste($source);
1144 /* Reset alternate mail addresses */
1145 $this->gosaMailAlternateAddress = array();
1146 }
1149 static function plInfo()
1150 {
1151 return (array(
1152 "plShortName" => _("Mail"),
1153 "plDescription" => _("Mail settings"),
1154 "plSelfModify" => TRUE,
1155 "plDepends" => array("user"), // This plugin depends on
1156 "plPriority" => 4, // Position in tabs
1157 "plSection" => array("personal" => _("My account")),
1158 "plCategory" => array("users"),
1159 "plOptions" => array(),
1161 "plProvidedAcls" => array(
1162 "mail" => _("Mail address"),
1163 "gosaMailServer" => _("Mail server"),
1164 "gosaMailQuota" => _("Quota size"),
1166 "gosaMailDeliveryModeV" => _("Add vacation information"), // This is flag of gosaMailDeliveryMode
1167 "gosaVacationMessage" => _("Vacation message"),
1169 "gosaMailDeliveryModeS" => _("Use spam filter"), // This is flag of gosaMailDeliveryMode
1170 "gosaSpamSortLevel" => _("Spam level"),
1171 "gosaSpamMailbox" => _("Spam mail box"),
1173 "sieveManagement" => _("Sieve management"),
1175 "gosaMailDeliveryModeR" => _("Reject due to mailsize"), // This is flag of gosaMailDeliveryMode
1176 "gosaMailMaxSize" => _("Mail max size"),
1178 "gosaMailForwardingAddress" => _("Forwarding address"),
1179 "gosaMailDeliveryModeL" => _("Local delivery"), // This is flag of gosaMailDeliveryMode
1180 "gosaMailDeliveryModeI" => _("No delivery to own mailbox "), // This is flag of gosaMailDeliveryMode
1181 "gosaMailAlternateAddress" => _("Mail alternative addresses"),
1183 "gosaMailForwardingAddress" => _("Forwarding address"),
1184 "gosaMailDeliveryModeC" => _("Use custom sieve script")) // This is flag of gosaMailDeliveryMode
1185 ));
1186 }
1189 /*! \brief Prepare importet vacation string. \
1190 Replace placeholder like %givenName a.s.o.
1191 @param string Vacation string
1192 @return string Completed vacation string
1193 */
1194 private function prepare_vacation_template($contents)
1195 {
1196 /* Replace attributes */
1197 $attrs = array();
1198 $obj = NULL;
1199 if(isset($this->parent->by_object['user'])){
1200 $attrs = $this->parent->by_object['user']->attributes;
1201 $obj = $this->parent->by_object['user'];
1202 }else{
1203 $obj = new user($this->config,$this->dn);
1204 $attrs = $obj->attributes;
1205 }
1207 if($obj){
1208 foreach ($attrs as $val){
1209 if(preg_match("/dateOfBirth/",$val)){
1210 if($obj->use_dob){
1211 $contents= preg_replace("/%$val/",date("Y-d-m",$obj->dateOfBirth),$contents);
1212 }
1213 }else {
1214 $contents= preg_replace("/%$val/",
1215 $obj->$val, $contents);
1216 }
1218 /* Replace vacation start and end time */
1219 if(preg_match("/%start/",$contents)){
1220 $contents = preg_replace("/%start/",date("d.m.Y",$this->gosaVacationStart),$contents);
1221 }
1222 if(preg_match("/%end/",$contents)){
1223 $contents = preg_replace("/%end/",date("d.m.Y",$this->gosaVacationStop),$contents);
1224 }
1225 }
1226 }
1227 return($contents);
1228 }
1232 /* Upated shared folder ACLs
1233 */
1234 function updateSharedFolder()
1235 {
1236 $ldap = $this->config->get_ldap_link();
1237 $ldap->cd($this->config->current['BASE']);
1238 $ldap->search("(&(objectClass=posixGroup)(objectClass=gosaMailAccount)(memberUid=".$this->uid."))",array('dn','cn'));
1239 if(class_exists("grouptabs")){
1240 while($attrs = $ldap->fetch()){
1241 $tmp = new grouptabs($this->config, $this->config->data['TABS']['GROUPTABS'], $attrs['dn']);
1242 if(isset($tmp->by_object['mailgroup'])){
1243 $tmp->by_object['mailgroup']->members= $tmp->by_object['group']->memberUid;
1244 if(!$this->is_account){
1245 $tmp->by_object['mailgroup']->removeUserAcl($this->uid);
1246 $tmp->by_object['mailgroup']->removeUserAcl($this->mail);
1247 }
1248 $tmp->by_object['mailgroup']->save();
1249 }
1250 }
1251 }
1252 }
1254 /* Initialize plugin with given atribute arrays
1255 */
1256 function init_multiple_support($attrs,$all)
1257 {
1258 plugin::init_multiple_support($attrs,$all);
1260 if(isset($this->multi_attrs['gosaMailQuota'])){
1261 $this->gosaMailQuota = $this->multi_attrs['gosaMailQuota'];
1262 }
1263 }
1265 function get_multi_init_values()
1266 {
1267 $attrs = plugin::get_multi_init_values();
1268 $attrs['gosaMailQuota'] = $this->gosaMailQuota;
1269 return($attrs);
1270 }
1272 function multiple_execute()
1273 {
1274 return($this->execute());
1275 }
1277 function multiple_save_object()
1278 {
1279 plugin::multiple_save_object();
1281 $this->save_object();
1282 foreach(array("only_local","gosaMailForwardingAddress","use_mailsize_limit","drop_own_mails","use_vacation","use_spam_filter") as $attr){
1283 if(isset($_POST["use_".$attr])){
1284 $this->multi_boxes[] = $attr;
1285 }
1286 }
1287 }
1289 function get_multi_edit_values()
1290 {
1291 $ret = plugin::get_multi_edit_values();
1293 if(in_array("gosaMailQuota",$this->multi_boxes)){
1294 $ret['gosaMailQuota'] = $this->gosaMailQuota;
1295 }
1297 $flag_add = $flag_remove = array();
1298 $tmp= preg_replace("/[^a-z]/i","",$this->gosaMailDeliveryMode);
1299 $opts = array(
1300 "R" => "use_mailsize_limit",
1301 "S" => "use_spam_filter",
1302 "L" => "only_local",
1303 "V" => "use_vacation",
1304 "C" => "own_script",
1305 "I" => "drop_own_mails");
1307 foreach($opts as $flag => $post){
1308 if(in_array($post, $this->multi_boxes)){
1309 if(preg_match("/".$flag."/",$tmp)){
1310 $flag_add[] = $flag;
1311 }else{
1312 $flag_remove[] = $flag;
1313 }
1314 }
1315 }
1317 $ret['flag_add'] = $flag_add;
1318 $ret['flag_remove'] = $flag_remove;
1319 return($ret);
1320 }
1323 function multiple_check()
1324 {
1325 $message = plugin::multiple_check();
1327 if(empty($this->gosaMailServer) && in_array("gosaMailServer",$this->multi_boxes)){
1328 $message[]= _("There is no valid mailserver specified, please add one in the system setup.");
1329 }
1331 /* Check quota */
1332 if ($this->gosaMailQuota != '' && in_array("gosaMailQuota",$this->multi_boxes)){
1333 if (!is_numeric($this->gosaMailQuota)) {
1334 $message[]= msgPool::invalid(_("Quota size"),$this->gosaMailQuota,"/^[0-9]*/");
1335 } else {
1336 $this->gosaMailQuota= (int) $this->gosaMailQuota;
1337 }
1338 }
1340 /* Check rejectsize for integer */
1341 if ($this->gosaMailMaxSize != '' && in_array("gosaMailMaxSize",$this->multi_boxes)){
1342 if (!is_numeric($this->gosaMailMaxSize)){
1343 $message[]= msgPool::invalid(_("Mail reject size"),$this->gosaMailMaxSize,"/^[0-9]*/");
1344 } else {
1345 $this->gosaMailMaxSize= (int) $this->gosaMailMaxSize;
1346 }
1347 }
1349 if(empty($this->gosaSpamMailbox) && in_array("gosaSpamMailbox",$this->multi_boxes)){
1350 $message[]= msgPool::required(_("Spam folder"));
1351 }
1353 if ( in_array("use_vacation",$this->multi_boxes) &&
1354 preg_match('/V/', $this->gosaMailDeliveryMode) && $this->gosaVacationStart > $this->gosaVacationStop){
1355 $message[]= msgPool::invalid(_("Vacation interval"));
1356 }
1357 return($message);
1358 }
1361 function set_multi_edit_values($values)
1362 {
1363 plugin::set_multi_edit_values($values);
1364 $tmp= preg_replace("/[^a-z]/i","",$this->gosaMailDeliveryMode);
1365 if(isset($values['flag_add'])){
1366 foreach($values['flag_add'] as $flag){
1367 if(!preg_match("/".$flag."/",$tmp)){
1368 $tmp .= $flag;
1369 }
1370 }
1371 }
1372 if(isset($values['flag_remove'])){
1373 foreach($values['flag_remove'] as $flag){
1374 if(preg_match("/".$flag."/",$tmp)){
1375 $tmp = preg_replace("/".$flag."/","",$tmp);
1376 }
1377 }
1378 }
1379 $this->gosaMailDeliveryMode = "[".$tmp."]";
1381 /* Set vacation message and replace placeholder like %givenName
1382 */
1383 if(isset($values['gosaVacationMessage'])){
1384 $this->gosaVacationMessage = $this->prepare_vacation_template($values['gosaVacationMessage']);
1385 }
1386 }
1387 }
1389 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
1390 ?>