3c2393106a411c9159383bce5213cf6011bda43d
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(_("Mail method '%s' is unknown!"), $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 msgPool::noValidExtension(_("Mail"))."</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(msgPool::removeFeaturesButton(_("Mail")),
300 _("Mail settings cannot be removed while there are delegations configured!"),TRUE,TRUE);
301 }else{
302 $display= $this->show_disable_header(msgPool::removeFeaturesButton(_("Mail")),
303 msgPool::featuresEnabled(_("Mail")));
304 }
305 } else {
306 $display= $this->show_enable_header(msgPool::addFeaturesButton(_("Mail")),
307 msgPool::featuresDisabled(_("Mail")));
308 return ($display);
309 }
310 }
311 }
313 /* Forwarder subdialog */
315 /* Trigger forward add dialog? */
316 if (isset($_POST['add_local_forwarder'])){
317 $this->forward_dialog= TRUE;
318 $this->dialog= TRUE;
319 }
321 /* Cancel forward add dialog? */
322 if (isset($_POST['add_locals_cancel'])){
323 $this->forward_dialog= FALSE;
324 $this->dialog= FALSE;
325 }
327 /* Finished adding of locals? */
328 if (isset($_POST['add_locals_finish'])){
330 /* Check if we are able to write gosaMailForwardingAddress */
331 if($this->acl_is_writeable("gosaMailForwardingAddress")){
333 /* Walk through list of forwarders, ignore own addresses */
334 foreach ($_POST['local_list'] as $val){
335 if (!in_array ($val, $this->gosaMailAlternateAddress) &&
336 $val != $this->mail){
338 $this->addForwarder($val);
339 $this->is_modified= TRUE;
340 }
341 }
342 }
343 $this->forward_dialog= FALSE;
344 $this->dialog= FALSE;
345 }
347 /* Add forward email addresses */
348 if (isset($_POST['add_forwarder'])){
349 if ($_POST['forward_address'] != ""){
351 /* Valid email address specified? */
352 $address= $_POST['forward_address'];
353 $valid= FALSE;
354 if (!tests::is_email($address)){
355 if (!tests::is_email($address, TRUE)){
356 if ($this->is_template){
357 $valid= TRUE;
358 } else {
359 msg_dialog::display(_("Error"),
360 msgPool::invalid(_("Mail address"),"","","example@your-domain.com"),
361 ERROR_DIALOG);
362 }
363 }
364 } elseif ($address == $this->mail
365 || in_array($address, $this->gosaMailAlternateAddress)) {
366 msg_dialog::display(_("Error"),_("Cannot add primary address to the list of forwarders!") , ERROR_DIALOG);
367 } else {
368 $valid= TRUE;
369 }
371 if ($valid){
373 /* Add it, if we are able to write gosaMailForwardingAddress */
374 if($this->acl_is_writeable("gosaMailForwardingAddress")){
375 $this->addForwarder ($address);
376 $this->is_modified= TRUE;
377 }
378 }
379 }
380 }
382 /* Delete forward email addresses */
383 if (isset($_POST['delete_forwarder'])){
384 $this->delForwarder ($_POST['forwarder_list']);
385 }
388 /* Add alternate email addresses */
389 if (isset($_POST['add_alternate'])){
391 $valid= FALSE;
392 if (!tests::is_email($_POST['alternate_address'])){
393 if ($this->is_template){
394 if (!(tests::is_email($_POST['alternate_address'], TRUE))){
395 msg_dialog::display(_("Error"),
396 msgPool::invalid(_("Mail address"),"","","example@your-domain.com"),
397 ERROR_DIALOG);
399 } else {
400 $valid= TRUE;
401 }
402 } else {
403 msg_dialog::display(_("Error"),
404 msgPool::invalid(_("Mail address"),"","","example@your-domain.com"),
405 ERROR_DIALOG);
406 }
408 } else {
409 $valid= TRUE;
410 }
412 if ($valid && ($user= $this->addAlternate ($_POST['alternate_address'])) != ""){
413 $ui= get_userinfo();
414 if ($user != $ui->username){
415 msg_dialog::display(_("Error"), msgPool::duplicated(_("Mail address"))." ".
416 sprintf(_("Address is already in use by user '%s'."), $user), ERROR_DIALOG);
417 }
418 }
419 }
421 /* Delete alternate email addresses */
422 if (isset($_POST['delete_alternate']) && isset($_POST['alternates_list'])){
423 $this->delAlternate ($_POST['alternates_list']);
424 }
427 /* Vacation message */
429 /* Import vacation message? */
430 if (isset($_POST["import_vacation"]) && isset($this->vacation[$_POST["vacation_template"]])){
433 /* Save message */
434 if($this->multiple_support_active){
435 $contents = file_get_contents($_POST["vacation_template"]);
436 }else{
437 $contents = $this->prepare_vacation_template(file_get_contents($_POST["vacation_template"]));
438 }
439 $this->gosaVacationMessage= htmlspecialchars($contents);
440 }
443 /* Display forward dialog if requested above */
445 /* Show forward add dialog */
446 if ($this->forward_dialog){
447 $ldap= $this->config->get_ldap_link();
449 /* Save data */
450 $mailfilter= session::get("mailfilter");
451 foreach( array("depselect", "muser", "regex") as $type){
452 if (isset($_POST[$type])){
453 $mailfilter[$type]= $_POST[$type];
454 }
455 }
456 if (isset($_GET['search'])){
457 $s= mb_substr($_GET['search'], 0, 1, "UTF8")."*";
458 if ($s == "**"){
459 $s= "*";
460 }
461 $mailfilter['regex']= $s;
462 }
463 session::set("mailfilter", $mailfilter);
465 /* Get actual list */
466 $mailusers= array ();
467 if ($mailfilter['regex'] != '*' && $mailfilter['regex'] != ""){
468 $regex= $mailfilter['regex'];
469 $filter= "(|(mail=$regex)(gosaMailAlternateAddress=$regex))";
470 } else {
471 $filter= "";
472 }
473 if ($mailfilter['muser'] != ""){
474 $user= $mailfilter['muser'];
475 $filter= "$filter(|(uid=$user)(cn=$user)(givenName=$user)(sn=$user))";
476 }
478 /* Add already present people to the filter */
479 $exclude= "";
480 foreach ($this->gosaMailForwardingAddress as $mail){
481 $exclude.= "(mail=$mail)";
482 }
483 if ($exclude != ""){
484 $filter.= "(!(|$exclude))";
485 }
487 $res= get_list("(&(objectClass=gosaMailAccount)$filter)", "users", $mailfilter['depselect'],
488 array("sn", "mail", "givenName"), GL_SIZELIMIT | GL_SUBSEARCH);
489 $ldap->cd($mailfilter['depselect']);
490 $ldap->search ("(&(objectClass=gosaMailAccount)$filter)", array("sn", "mail", "givenName"));
491 error_reporting (0);
492 while ($attrs= $ldap->fetch()){
493 if(preg_match('/%/', $attrs['mail'][0])){
494 continue;
495 }
496 $name= $this->make_name($attrs);
497 $mailusers[$attrs['mail'][0]]= $name."<".
498 $attrs['mail'][0].">";
499 }
500 error_reporting (E_ALL | E_STRICT);
501 natcasesort ($mailusers);
502 reset ($mailusers);
504 /* Show dialog */
505 $smarty->assign("search_image", get_template_path('images/search.png'));
506 $smarty->assign("usearch_image", get_template_path('images/search_user.png'));
507 $smarty->assign("tree_image", get_template_path('images/tree.png'));
508 $smarty->assign("infoimage", get_template_path('images/info.png'));
509 $smarty->assign("launchimage", get_template_path('images/small_filter.png'));
510 $smarty->assign("mailusers", $mailusers);
511 if (isset($_POST['depselect'])){
512 $smarty->assign("depselect", $_POST['depselect']);
513 }
514 $smarty->assign("deplist", $this->config->idepartments);
515 $smarty->assign("apply", apply_filter());
516 $smarty->assign("alphabet", generate_alphabet());
517 $smarty->assign("hint", print_sizelimit_warning());
518 foreach( array("depselect", "muser", "regex") as $type){
519 $smarty->assign("$type", $mailfilter[$type]);
520 }
521 $smarty->assign("hint", print_sizelimit_warning());
523 $display.= $smarty->fetch (get_template_path('mail_locals.tpl', TRUE, dirname(__FILE__)));
524 return ($display);
525 }
527 /* Display mail account tab */
529 $smarty->assign("mailServers", $mailserver);
530 $SkipWrite = (!isset($this->parent) || !$this->parent) && !session::is_set('edit');
532 $tmp = $this->plInfo();
533 foreach($tmp['plProvidedAcls'] as $name => $transl){
534 $smarty->assign("$name"."ACL", $this->getacl($name,$SkipWrite));
535 }
537 foreach(array("gosaMailServer", "gosaMailQuota", "perms", "mail",
538 "gosaMailAlternateAddress", "gosaMailForwardingAddress",
539 "gosaVacationMessage", "gosaMailDeliveryMode", "gosaVacationStart",
540 "gosaVacationStop", "gosaMailMaxSize", "gosaSpamSortLevel", "gosaSpamMailbox") as $val){
541 $smarty->assign("$val", $this->$val);
542 }
544 if (is_numeric($this->gosaMailQuota) && $this->gosaMailQuota != 0){
545 $smarty->assign("quotausage", progressbar(round(($this->quotaUsage * 100)/ $this->gosaMailQuota),100,15,true));
546 $smarty->assign("quotadefined", "true");
547 } else {
548 $smarty->assign("quotadefined", "false");
549 }
551 /* Disable mail field if needed */
552 $method= new $this->method($this->config);
553 if ($method->uattrib == "mail" && $this->initially_was_account){
554 $smarty->assign("mailACL", preg_replace("/w/","",$this->getacl("mail",$SkipWrite)));
555 }
557 /* Disable/Enable range select, but do not disable them twice
558 * if they are already diabled by "use own sieve script"
559 */
560 if (preg_match('/V/', $this->gosaMailDeliveryMode) || preg_match("/C/",$this->gosaMailDeliveryMode)){
561 $smarty->assign('rangeEnabled', "");
562 } else {
563 $smarty->assign('rangeEnabled', "disabled");
564 }
566 if (!preg_match("/L/", $this->gosaMailDeliveryMode)) {
567 $smarty->assign("only_local", "checked");
568 } else {
569 $smarty->assign("only_local", "");
570 }
572 $types = array(
573 "V"=>"use_vacation",
574 "S"=>"use_spam_filter",
575 "R"=>"use_mailsize_limit",
576 "I"=>"drop_own_mails",
577 "C"=>"own_script");
579 /* Fill checkboxes */
580 foreach($types as $option => $varname){
581 if (preg_match("/".$option."/", $this->gosaMailDeliveryMode)) {
582 $smarty->assign($varname, "checked");
583 } else {
584 $smarty->assign($varname, "");
585 }
586 }
588 /* Display mail account tab */
589 if($this->gosaVacationStart ==0){
590 $date= getdate(time());
591 }else{
592 $date= getdate($this->gosaVacationStart);
593 }
594 $days= array();
595 for($d= 1; $d<32; $d++){
596 $days[$d]= $d;
597 }
598 $years= array();
599 for($y= $date['year']-10; $y<$date['year']+10; $y++){
600 $years[]= $y;
601 }
602 $months= array(_("January"), _("February"), _("March"), _("April"),
603 _("May"), _("June"), _("July"), _("August"), _("September"),
604 _("October"), _("November"), _("December"));
605 $smarty->assign("start_day", $date["mday"]);
606 $smarty->assign("days", $days);
607 $smarty->assign("months", $months);
608 $smarty->assign("start_month", $date["mon"]-1);
609 $smarty->assign("years", $years);
610 $smarty->assign("start_year", $date["year"]);
612 if($this->gosaVacationStop ==0){
613 $date= getdate(time());
614 $date["mday"]++;
615 }else{
616 $date= getdate($this->gosaVacationStop);
617 }
618 $smarty->assign("end_day", $date["mday"]);
619 $smarty->assign("end_month", $date["mon"]-1);
620 $smarty->assign("end_year", $date["year"]);
624 /* Have vacation templates? */
625 $smarty->assign("template", "");
626 if (count($this->vacation)){
627 $smarty->assign("show_templates", "true");
628 $smarty->assign("vacationtemplates", $this->vacation);
629 if (isset($_POST['vacation_template'])){
630 $smarty->assign("template", $_POST['vacation_template']);
631 }
632 } else {
633 $smarty->assign("show_templates", "false");
634 }
636 /* Fill spam selector */
637 $spamlevel= array();
638 for ($i= 0; $i<21; $i++){
639 $spamlevel[]= $i;
640 }
641 $smarty->assign("spamlevel", $spamlevel);
642 $smarty->assign("spambox", $this->mailboxList);
644 foreach($this->attributes as $attr){
645 $u_attr = "use_".$attr;
646 $smarty->assign($u_attr,in_array($attr,$this->multi_boxes));
647 }
649 foreach(array("only_local","gosaMailForwardingAddress","use_mailsize_limit","drop_own_mails","use_vacation","use_spam_filter") as $attr){
650 $u_attr = "use_".$attr;
651 $smarty->assign($u_attr,in_array($attr,$this->multi_boxes));
652 }
654 $smarty->assign("multiple_support",$this->multiple_support_active);
655 $display.= $smarty->fetch (get_template_path('generic.tpl', TRUE, dirname(__FILE__)));
656 return ($display);
657 }
660 /* remove object from parent */
661 function remove_from_parent()
662 {
663 /* Cancel if there's nothing to do here */
664 if (!$this->initially_was_account){
665 return;
666 }
668 /* include global link_info */
669 $ldap= $this->config->get_ldap_link();
671 /* Remove and write to LDAP */
672 plugin::remove_from_parent();
674 /* Zero arrays */
675 $this->attrs['gosaMailAlternateAddress']= array();
676 $this->attrs['gosaMailForwardingAddress']= array();
678 /* Adapt attributes if needed */
679 $method= new $this->method($this->config);
680 $method->fixAttributesOnRemove($this);
682 @DEBUG (DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__,$this->attributes, "Save");
683 $ldap->cd($this->dn);
684 $this->cleanup();
686 $ldap->modify ($this->attrs);
688 /* Add "view" to logging class */
689 new log("remove","users/".get_class($this),$this->dn,array_keys($this->attrs),$ldap->get_error());
691 show_ldap_error($ldap->get_error(), sprintf(_("Removing of user/mail account with dn '%s' failed."),$this->dn));
693 /* Connect to IMAP server for account deletion */
694 if ($this->gosaMailServer != ""){
695 $method= new $this->method($this->config);
696 $id= $method->uattrib;
697 if ($method->connect($this->gosaMailServer)){
699 /* Remove account from IMAP server */
700 $method->deleteMailbox($this->folder_prefix.$this->$id);
701 $method->disconnect();
702 }
703 }
705 /* Update shared folder membership, ACL may need to be updated */
706 $this->updateSharedFolder();
708 /* Optionally execute a command after we're done */
709 $this->handle_post_events("remove",array("uid" => $this->uid));
710 }
713 /* check if we have some delegations configured, those delegations must be removed first */
714 function accountDelegationsConfigured()
715 {
716 /* We are in administrational edit mode.
717 Check tab configurations directly */
718 if(isset($this->attrs)){
719 $checkArray = array("kolabInvitationPolicy","unrestrictedMailSize", "calFBURL","kolabDelegate","kolabFreeBusyFuture");
720 foreach($checkArray as $index){
721 if(isset($this->attrs[$index])){
722 return(true);
723 }
724 }
725 }
726 return(false);
727 }
730 /* Save data to object */
731 function save_object()
732 {
733 if (isset($_POST['mailTab'])){
735 /* Save ldap attributes */
736 plugin::save_object();
739 if(isset($_POST['own_script'])){
741 if(!preg_match("/C/",$this->gosaMailDeliveryMode)){
742 $str= preg_replace("/[\[\]]/","",$this->gosaMailDeliveryMode);
743 $this->gosaMailDeliveryMode = "[".$str."C]";
744 }
746 }else{
748 /* Assemble mail delivery mode
749 The mode field in ldap consists of values between braces, this must
750 be called when 'mail' is set, because checkboxes may not be set when
751 we're in some other dialog.
753 Example for gosaMailDeliveryMode [LR ]
754 L: Local delivery
755 R: Reject when exceeding mailsize limit
756 S: Use spam filter
757 V: Use vacation message
758 C: Use custm sieve script
759 I: Only insider delivery */
761 $tmp= preg_replace("/[^a-z]/i","",$this->gosaMailDeliveryMode);
764 /* Handle delivery flags */
765 if($this->acl_is_writeable("gosaMailDeliveryModeL")){
766 if(!preg_match("/L/",$tmp) && !isset($_POST['only_local'])){
767 $tmp.="L";
768 }elseif(preg_match("/L/",$tmp) && isset($_POST['only_local'])){
769 $tmp = preg_replace("/L/","",$tmp);
770 }
771 }
773 $opts = array(
774 "R" => "use_mailsize_limit",
775 "S" => "use_spam_filter",
776 "V" => "use_vacation",
777 "C" => "own_script",
778 "I" => "drop_own_mails");
780 foreach($opts as $flag => $post){
781 if($this->acl_is_writeable("gosaMailDeliveryMode".$flag)){
782 if(!preg_match("/".$flag."/",$tmp) && isset($_POST[$post])){
783 $tmp.= $flag;
784 }elseif(preg_match("/".$flag."/",$tmp) && !isset($_POST[$post])){
785 $tmp = preg_replace("/".$flag."/","",$tmp);
786 }
787 }
788 }
790 $tmp= "[$tmp]";
791 if ($this->gosaMailDeliveryMode != $tmp){
792 $this->is_modified= TRUE;
793 }
794 $this->gosaMailDeliveryMode= $tmp;
797 if($this->acl_is_writeable("gosaVacationMessage") && preg_match("/V/",$this->gosaMailDeliveryMode)){
798 if(isset($_POST['gosaVacationStart'])){
799 $this->gosaVacationStart = $_POST['gosaVacationStart'];
800 }
801 if(isset($_POST['gosaVacationStop'])){
802 $this->gosaVacationStop = $_POST['gosaVacationStop'];
803 }
804 }
805 }
806 }
807 }
810 /* Save data to LDAP, depending on is_account we save or delete */
811 function save()
812 {
813 $ldap= $this->config->get_ldap_link();
815 /* Call parents save to prepare $this->attrs */
816 plugin::save();
818 /* Save arrays */
819 $this->attrs['gosaMailAlternateAddress']= $this->gosaMailAlternateAddress;
820 $this->attrs['gosaMailForwardingAddress']= $this->gosaMailForwardingAddress;
822 /* Adapt attributes if needed */
823 $method= new $this->method($this->config);
824 $id= $method->uattrib;
826 $method->fixAttributesOnStore($this);
828 /* Remove Mailquota if = "" or "0" */
829 if((isset($this->attrs['gosaMailQuota']))&&(!$this->attrs['gosaMailQuota'])) {
830 $this->attrs['gosaMailQuota']=0;
831 }
833 if(empty($this->attrs['gosaSpamMailbox'])){
834 unset($this->attrs['gosaSpamMailbox']);
835 }
837 $this->attrs['mail'] = strtolower($this->attrs['mail']);
839 /* Remove attributes - if not needed */
840 if (!preg_match('/V/', $this->gosaMailDeliveryMode)){
841 unset($this->attrs['gosaVacationStart']);
842 unset($this->attrs['gosaVacationStop']);
843 }
846 /* Remove attributes - if not needed */
847 if (!preg_match('/V/', $this->gosaMailDeliveryMode)){
848 unset($this->attrs['gosaVacationStart']);
849 unset($this->attrs['gosaVacationStop']);
850 }
852 /* Save data to LDAP */
853 $ldap->cd($this->dn);
854 $this->cleanup();
855 $ldap->modify ($this->attrs);
857 show_ldap_error($ldap->get_error(), sprintf(_("Saving of user/mail account with dn '%s' failed."),$this->dn));
859 /* Log last action */
860 if($this->initially_was_account){
861 new log("modify","users/".get_class($this),$this->dn,array_keys($this->attrs),$ldap->get_error());
862 }else{
863 new log("create","users/".get_class($this),$this->dn,array_keys($this->attrs),$ldap->get_error());
864 }
866 /* Only do IMAP actions if we are not a template */
867 if (!$this->is_template){
869 if ($method->connect($this->gosaMailServer)){
870 $method->updateMailbox($this->folder_prefix.$this->$id);
872 $method->setQuota($this->folder_prefix.$this->$id, $this->gosaMailQuota);
873 $method->disconnect();
875 /* Ensure that this is an existing account */
876 if(1==1 || $this->initially_was_account){
878 /* Write sieve information only if not in C mode */
879 if (!is_integer(strpos($this->gosaMailDeliveryMode, "C"))){
880 $method->configureFilter($this->$id,
881 $this->gosaMailDeliveryMode,
882 $this->mail,
883 $this->gosaMailAlternateAddress,
884 $this->gosaMailMaxSize,
885 $this->gosaSpamMailbox,
886 $this->gosaSpamSortLevel,
887 $this->gosaVacationMessage);
888 $this->is_modified = TRUE;
889 }else{
890 $this->sieve_management->save();
891 }
892 }
893 }
894 }
896 /* Optionally execute a command after we're done */
897 if ($this->initially_was_account == $this->is_account){
898 if ($this->is_modified){
899 $this->handle_post_events("modify", array("uid" => $this->uid));
900 }
901 } else {
902 $this->handle_post_events("add", array("uid" => $this->uid));
903 }
905 $this->updateSharedFolder();
906 }
909 /* Check formular input */
910 function check()
911 {
912 if(!$this->is_account) return(array());
914 $ldap= $this->config->get_ldap_link();
916 /* Call common method to give check the hook */
917 $message= plugin::check();
919 if(empty($this->gosaMailServer)){
920 $message[]= msgPool::noserver(_("Mail"));
921 }
923 /* must: mail */
924 if ($this->mail == ""){
925 $message[]= msgPool::required(_("Primary address"));
926 }
927 if ($this->is_template){
928 if (!tests::is_email($this->mail, TRUE)){
929 $message[]= msgPool::invalid(_("Mail address"),"","","%givenName.%sn@your-domain.com");
930 }
931 } else {
932 if (!tests::is_email($this->mail)){
933 $message[]= msgPool::invalid(_("Mail address"),"","","example@your-domain.com");
934 }
935 }
936 $ldap->cd($this->config->current['BASE']);
937 $ldap->search ("(&(!(objectClass=gosaUserTemplate))(objectClass=gosaMailAccount)(|(mail=".$this->mail.")(gosaMailAlternateAddress=".$this->mail."))(!(uid=".$this->uid."))(!(cn=".$this->uid.")))", array("uid"));
938 if ($ldap->count() != 0){
939 $message[]= msgPool::duplicated(_("Mail address"));
940 }
942 /* Check quota */
943 if ($this->gosaMailQuota != '' && $this->acl_is_writeable("gosaMailQuota")){
944 if (!is_numeric($this->gosaMailQuota)) {
945 $message[]= msgPool::invalid(_("Quota size"),$this->gosaMailQuota,"/^[0-9]*/");
946 } else {
947 $this->gosaMailQuota= (int) $this->gosaMailQuota;
948 }
949 }
951 /* Check rejectsize for integer */
952 if ($this->gosaMailMaxSize != '' && $this->acl_is_writeable("gosaMailMaxSize")){
953 if (!is_numeric($this->gosaMailMaxSize)){
954 $message[]= msgPool::invalid(_("Mail reject size"),$this->gosaMailMaxSize,"/^[0-9]*/");
955 } else {
956 $this->gosaMailMaxSize= (int) $this->gosaMailMaxSize;
957 }
958 }
960 /* Need gosaMailMaxSize if use_mailsize_limit is checked */
961 if (is_integer(strpos($this->gosaMailDeliveryMode, "R")) && $this->gosaMailMaxSize == ""){
962 $message[]= msgPool::required(_("Mail reject size"));
963 }
965 if((preg_match("/S/", $this->gosaMailDeliveryMode))&&(empty($this->gosaSpamMailbox))) {
966 $message[]= msgPool::required(_("Spam folder"));
967 }
969 if (preg_match('/V/', $this->gosaMailDeliveryMode) && $this->gosaVacationStart >= $this->gosaVacationStop){
970 $message[]= msgPool::invalid(_("Vacation interval"));
971 }
973 return ($message);
974 }
977 /* Adapt from template, using 'dn' */
978 function adapt_from_template($dn)
979 {
980 plugin::adapt_from_template($dn);
982 foreach (array("gosaMailAlternateAddress", "gosaMailForwardingAddress") as $val){
983 $this->$val= array();
984 if (isset($this->attrs["$val"]["count"])){
985 for ($i= 0; $i<$this->attrs["$val"]["count"]; $i++){
986 $value= $this->attrs["$val"][$i];
987 foreach (array("sn", "givenName", "uid") as $repl){
988 if (preg_match("/%$repl/i", $value)){
989 $value= preg_replace ("/%$repl/i", $this->parent->$repl, $value);
990 }
991 }
992 array_push($this->$val, strtolower(rewrite($value)));
993 }
994 }
995 }
996 $this->mail= strtolower(rewrite($this->mail));
997 }
1000 /* Add entry to forwarder list */
1001 function addForwarder($address)
1002 {
1003 if($this->acl_is_writeable("gosaMailForwardingAddress")){
1004 $this->gosaMailForwardingAddress[]= $address;
1005 $this->gosaMailForwardingAddress= array_unique ($this->gosaMailForwardingAddress);
1006 sort ($this->gosaMailForwardingAddress);
1007 reset ($this->gosaMailForwardingAddress);
1008 $this->is_modified= TRUE;
1009 }else{
1010 msg_dialog::display(_("Permission error"), _("You have no permission to modify these addresses!"), ERROR_DIALOG);
1011 }
1012 }
1015 /* Remove list of addresses from forwarder list */
1016 function delForwarder($addresses)
1017 {
1018 if($this->acl_is_writeable("gosaMailForwardingAddress")){
1019 $this->gosaMailForwardingAddress= array_remove_entries ($addresses, $this->gosaMailForwardingAddress);
1020 $this->is_modified= TRUE;
1021 }else{
1022 msg_dialog::display(_("Permission error"), _("You have no permission to modify these addresses!"), ERROR_DIALOG);
1023 }
1024 }
1027 /* Add given mail address to the list of alternate adresses ,
1028 check if this mal address is used, skip adding in this case */
1029 function addAlternate($address)
1030 {
1031 if($this->acl_is_writeable("gosaMailAlternateAddress")){
1032 $ldap= $this->config->get_ldap_link();
1033 $address= strtolower($address);
1035 /* Is this address already assigned in LDAP? */
1036 $ldap->cd ($this->config->current['BASE']);
1037 $ldap->search ("(&(objectClass=gosaMailAccount)(|(mail=$address)"."(gosaMailAlternateAddress=$address)))", array("uid"));
1038 $ldap->search ("(&(!(objectClass=gosaUserTemplate))(objectClass=gosaMailAccount)(|(mail=$address)"."(gosaMailAlternateAddress=$address)))", array("uid"));
1040 if ($ldap->count() > 0){
1041 $attrs= $ldap->fetch ();
1042 return ($attrs["uid"][0]);
1043 }
1045 /* Add to list of alternates */
1046 if (!in_array($address, $this->gosaMailAlternateAddress)){
1047 $this->gosaMailAlternateAddress[]= $address;
1048 $this->is_modified= TRUE;
1049 }
1051 sort ($this->gosaMailAlternateAddress);
1052 reset ($this->gosaMailAlternateAddress);
1053 return ("");
1054 }else{
1055 msg_dialog::display(_("Permission error"), _("You have no permission to modify these addresses!"), ERROR_DIALOG);
1056 }
1057 }
1060 function delAlternate($addresses)
1061 {
1062 if($this->acl_is_writeable("gosaMailAlternateAddress")){
1063 $this->gosaMailAlternateAddress= array_remove_entries ($addresses,$this->gosaMailAlternateAddress);
1064 $this->is_modified= TRUE;
1065 }else{
1066 msg_dialog::display(_("Permission error"), _("You have no permission to modify these addresses!"), ERROR_DIALOG);
1067 }
1068 }
1070 function make_name($attrs)
1071 {
1072 $name= "";
1073 if (isset($attrs['sn'][0])){
1074 $name= $attrs['sn'][0];
1075 }
1076 if (isset($attrs['givenName'][0])){
1077 if ($name != ""){
1078 $name.= ", ".$attrs['givenName'][0];
1079 } else {
1080 $name.= $attrs['givenName'][0];
1081 }
1082 }
1083 if ($name != ""){
1084 $name.= " ";
1085 }
1087 return ($name);
1088 }
1091 /* Create the mail part for the copy & paste dialog */
1092 function getCopyDialog()
1093 {
1094 if(!$this->is_account) return("");
1095 $smarty = get_smarty();
1096 $smarty->assign("mail",$this->mail);
1097 $smarty->assign("gosaMailAlternateAddress",$this->gosaMailAlternateAddress);
1098 $smarty->assign("gosaMailForwardingAddress",$this->gosaMailForwardingAddress);
1099 $str = $smarty->fetch(get_template_path("copypaste.tpl",TRUE, dirname(__FILE__)));
1101 $ret = array();
1102 $ret['status'] = "";
1103 $ret['string'] = $str;
1104 return($ret);
1105 }
1107 function saveCopyDialog()
1108 {
1109 if(!$this->is_account) return;
1111 /* Execute to save mailAlternateAddress && gosaMailForwardingAddress */
1112 $this->execute();
1114 if(isset($_POST['mail'])){
1115 $this->mail = $_POST['mail'];
1116 }
1118 }
1120 function allow_remove()
1121 {
1122 if (isset($this->config->current['MAILMETHOD'])){
1123 $method= $this->config->current['MAILMETHOD'];
1124 if(preg_match("/olab/i",$method)){
1125 $ldap = $this->config->get_ldap_link();
1126 $ldap->cd($this->config->current['BASE']);
1127 $ldap->cat($this->dn);
1128 if($ldap->count()){
1129 $attrs = $ldap->fetch();
1130 if(isset($attrs['kolabDeleteFlag'])){
1131 return(_("Waiting for kolab to remove mail properties..."));
1132 }elseif(in_array("gosaMailAccount",$attrs['objectClass'])){
1133 return(_("Please remove the mail settings first to allow kolab to call its remove methods!"));
1134 }
1135 }
1136 }
1137 }
1138 }
1141 function PrepareForCopyPaste($source)
1142 {
1143 plugin::PrepareForCopyPaste($source);
1145 /* Reset alternate mail addresses */
1146 $this->gosaMailAlternateAddress = array();
1147 }
1150 static function plInfo()
1151 {
1152 return (array(
1153 "plShortName" => _("Mail"),
1154 "plDescription" => _("Mail settings"),
1155 "plSelfModify" => TRUE,
1156 "plDepends" => array("user"), // This plugin depends on
1157 "plPriority" => 4, // Position in tabs
1158 "plSection" => array("personal" => _("My account")),
1159 "plCategory" => array("users"),
1160 "plOptions" => array(),
1162 "plProvidedAcls" => array(
1163 "mail" => _("Mail address"),
1164 "gosaMailServer" => _("Mail server"),
1165 "gosaMailQuota" => _("Quota size"),
1167 "gosaMailDeliveryModeV" => _("Add vacation information"), // This is flag of gosaMailDeliveryMode
1168 "gosaVacationMessage" => _("Vacation message"),
1170 "gosaMailDeliveryModeS" => _("Use spam filter"), // This is flag of gosaMailDeliveryMode
1171 "gosaSpamSortLevel" => _("Spam level"),
1172 "gosaSpamMailbox" => _("Spam mail box"),
1174 "sieveManagement" => _("Sieve management"),
1176 "gosaMailDeliveryModeR" => _("Reject due to mailsize"), // This is flag of gosaMailDeliveryMode
1177 "gosaMailMaxSize" => _("Mail max size"),
1179 "gosaMailForwardingAddress" => _("Forwarding address"),
1180 "gosaMailDeliveryModeL" => _("Local delivery"), // This is flag of gosaMailDeliveryMode
1181 "gosaMailDeliveryModeI" => _("No delivery to own mailbox "), // This is flag of gosaMailDeliveryMode
1182 "gosaMailAlternateAddress" => _("Mail alternative addresses"),
1184 "gosaMailForwardingAddress" => _("Forwarding address"),
1185 "gosaMailDeliveryModeC" => _("Use custom sieve script")) // This is flag of gosaMailDeliveryMode
1186 ));
1187 }
1190 /*! \brief Prepare importet vacation string. \
1191 Replace placeholder like %givenName a.s.o.
1192 @param string Vacation string
1193 @return string Completed vacation string
1194 */
1195 private function prepare_vacation_template($contents)
1196 {
1197 /* Replace attributes */
1198 $attrs = array();
1199 $obj = NULL;
1200 if(isset($this->parent->by_object['user'])){
1201 $attrs = $this->parent->by_object['user']->attributes;
1202 $obj = $this->parent->by_object['user'];
1203 }else{
1204 $obj = new user($this->config,$this->dn);
1205 $attrs = $obj->attributes;
1206 }
1208 if($obj){
1209 foreach ($attrs as $val){
1210 if(preg_match("/dateOfBirth/",$val)){
1211 if($obj->use_dob){
1212 $contents= preg_replace("/%$val/",date("Y-d-m",$obj->dateOfBirth),$contents);
1213 }
1214 }else {
1215 $contents= preg_replace("/%$val/",
1216 $obj->$val, $contents);
1217 }
1219 /* Replace vacation start and end time */
1220 if(preg_match("/%start/",$contents)){
1221 $contents = preg_replace("/%start/",date("d.m.Y",$this->gosaVacationStart),$contents);
1222 }
1223 if(preg_match("/%end/",$contents)){
1224 $contents = preg_replace("/%end/",date("d.m.Y",$this->gosaVacationStop),$contents);
1225 }
1226 }
1227 }
1228 return($contents);
1229 }
1233 /* Upated shared folder ACLs
1234 */
1235 function updateSharedFolder()
1236 {
1237 $ldap = $this->config->get_ldap_link();
1238 $ldap->cd($this->config->current['BASE']);
1239 $ldap->search("(&(objectClass=posixGroup)(objectClass=gosaMailAccount)(memberUid=".$this->uid."))",array('dn','cn'));
1240 if(class_exists("grouptabs")){
1241 while($attrs = $ldap->fetch()){
1242 $tmp = new grouptabs($this->config, $this->config->data['TABS']['GROUPTABS'], $attrs['dn']);
1243 if(isset($tmp->by_object['mailgroup'])){
1244 $tmp->by_object['mailgroup']->members= $tmp->by_object['group']->memberUid;
1245 if(!$this->is_account){
1246 $tmp->by_object['mailgroup']->removeUserAcl($this->uid);
1247 $tmp->by_object['mailgroup']->removeUserAcl($this->mail);
1248 }
1249 $tmp->by_object['mailgroup']->save();
1250 }
1251 }
1252 }
1253 }
1255 /* Initialize plugin with given atribute arrays
1256 */
1257 function init_multiple_support($attrs,$all)
1258 {
1259 plugin::init_multiple_support($attrs,$all);
1261 if(isset($this->multi_attrs['gosaMailQuota'])){
1262 $this->gosaMailQuota = $this->multi_attrs['gosaMailQuota'];
1263 }
1264 }
1266 function get_multi_init_values()
1267 {
1268 $attrs = plugin::get_multi_init_values();
1269 $attrs['gosaMailQuota'] = $this->gosaMailQuota;
1270 return($attrs);
1271 }
1273 function multiple_execute()
1274 {
1275 return($this->execute());
1276 }
1278 function multiple_save_object()
1279 {
1280 plugin::multiple_save_object();
1282 $this->save_object();
1283 foreach(array("only_local","gosaMailForwardingAddress","use_mailsize_limit","drop_own_mails","use_vacation","use_spam_filter") as $attr){
1284 if(isset($_POST["use_".$attr])){
1285 $this->multi_boxes[] = $attr;
1286 }
1287 }
1288 }
1290 function get_multi_edit_values()
1291 {
1292 $ret = plugin::get_multi_edit_values();
1294 if(in_array("gosaMailQuota",$this->multi_boxes)){
1295 $ret['gosaMailQuota'] = $this->gosaMailQuota;
1296 }
1298 $flag_add = $flag_remove = array();
1299 $tmp= preg_replace("/[^a-z]/i","",$this->gosaMailDeliveryMode);
1300 $opts = array(
1301 "R" => "use_mailsize_limit",
1302 "S" => "use_spam_filter",
1303 "L" => "only_local",
1304 "V" => "use_vacation",
1305 "C" => "own_script",
1306 "I" => "drop_own_mails");
1308 foreach($opts as $flag => $post){
1309 if(in_array($post, $this->multi_boxes)){
1310 if(preg_match("/".$flag."/",$tmp)){
1311 $flag_add[] = $flag;
1312 }else{
1313 $flag_remove[] = $flag;
1314 }
1315 }
1316 }
1318 $ret['flag_add'] = $flag_add;
1319 $ret['flag_remove'] = $flag_remove;
1320 return($ret);
1321 }
1324 function multiple_check()
1325 {
1326 $message = plugin::multiple_check();
1328 if(empty($this->gosaMailServer) && in_array("gosaMailServer",$this->multi_boxes)){
1329 $message[]= msgPool::noserver(_("Mail"));
1330 }
1332 /* Check quota */
1333 if ($this->gosaMailQuota != '' && in_array("gosaMailQuota",$this->multi_boxes)){
1334 if (!is_numeric($this->gosaMailQuota)) {
1335 $message[]= msgPool::invalid(_("Quota size"),$this->gosaMailQuota,"/^[0-9]*/");
1336 } else {
1337 $this->gosaMailQuota= (int) $this->gosaMailQuota;
1338 }
1339 }
1341 /* Check rejectsize for integer */
1342 if ($this->gosaMailMaxSize != '' && in_array("gosaMailMaxSize",$this->multi_boxes)){
1343 if (!is_numeric($this->gosaMailMaxSize)){
1344 $message[]= msgPool::invalid(_("Mail reject size"),$this->gosaMailMaxSize,"/^[0-9]*/");
1345 } else {
1346 $this->gosaMailMaxSize= (int) $this->gosaMailMaxSize;
1347 }
1348 }
1350 if(empty($this->gosaSpamMailbox) && in_array("gosaSpamMailbox",$this->multi_boxes)){
1351 $message[]= msgPool::required(_("Spam folder"));
1352 }
1354 if ( in_array("use_vacation",$this->multi_boxes) &&
1355 preg_match('/V/', $this->gosaMailDeliveryMode) && $this->gosaVacationStart > $this->gosaVacationStop){
1356 $message[]= msgPool::invalid(_("Vacation interval"));
1357 }
1358 return($message);
1359 }
1362 function set_multi_edit_values($values)
1363 {
1364 plugin::set_multi_edit_values($values);
1365 $tmp= preg_replace("/[^a-z]/i","",$this->gosaMailDeliveryMode);
1366 if(isset($values['flag_add'])){
1367 foreach($values['flag_add'] as $flag){
1368 if(!preg_match("/".$flag."/",$tmp)){
1369 $tmp .= $flag;
1370 }
1371 }
1372 }
1373 if(isset($values['flag_remove'])){
1374 foreach($values['flag_remove'] as $flag){
1375 if(preg_match("/".$flag."/",$tmp)){
1376 $tmp = preg_replace("/".$flag."/","",$tmp);
1377 }
1378 }
1379 }
1380 $this->gosaMailDeliveryMode = "[".$tmp."]";
1382 /* Set vacation message and replace placeholder like %givenName
1383 */
1384 if(isset($values['gosaVacationMessage'])){
1385 $this->gosaVacationMessage = $this->prepare_vacation_template($values['gosaVacationMessage']);
1386 }
1387 }
1388 }
1390 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
1391 ?>