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 $user_prefix = "";
38 var $folder_prefix = "";
40 var $mailboxList = array("INBOX");
41 var $default_permissions = "none";
42 var $member_permissions = "post";
43 var $members = array();
44 var $admins = array();
45 var $vacations = array();
46 var $perms = array( "lrs" => "read",
47 "lrsp" => "post",
48 "lrsip" => "append",
49 "lrswipcd" => "write",
50 "lrswipcda" => "all" );
52 /* attribute list for save action */
53 var $attributes= array("mail", "gosaMailServer", "gosaMailQuota", "gosaMailMaxSize","gosaMailForwardingAddress",
54 "gosaMailDeliveryMode", "gosaSpamSortLevel", "gosaSpamMailbox","gosaMailAlternateAddress","gosaVacationStart","gosaVacationStop",
55 "gosaVacationMessage", "gosaMailAlternateAddress", "gosaMailForwardingAddress");
56 var $objectclasses= array("gosaMailAccount");
57 var $uid = "";
59 var $sieve_management = NULL;
60 var $multiple_support = TRUE;
62 /* constructor, if 'dn' is set, the node loads the given
63 'dn' from LDAP */
64 function mailAccount (&$config, $dn= NULL)
65 {
66 global $class_mapping;
68 $this->gosaVacationStart = time();
69 $this->gosaVacationStop = time();
71 /* Load bases attributes */
72 plugin::plugin($config, $dn);
74 /* Set uid */
75 if(isset($this->attrs['uid'])){
76 $this->uid = $this->attrs['uid'][0];
77 }
79 if(is_array($this->gosaMailServer) && isset($this->gosaMailServer[0])){
80 $this->gosaMailServer = $this->gosaMailServer[0];
81 }
83 /* Save initial account state */
84 $this->initially_was_account= $this->is_account;
86 /* Set mailMethod to the one defined in gosa.conf */
87 if ($this->config->get_cfg_value("mailmethod") != ""){
88 $method= $this->config->get_cfg_value("mailmethod");
90 $cls = get_correct_class_name("mailMethod$method");
91 if ($cls && class_exists($cls)){
92 $this->method= $cls;
93 } else {
94 msg_dialog::display(_("Configuration error"), sprintf(_("Mail method '%s' is unknown!"), $method), ERROR_DIALOG);
95 }
96 }
98 /* get user and folder prefix */
99 $method = new $this->method($this->config);
100 $this->user_prefix = $method->user_prefix;
101 $this->folder_prefix = $method->folder_prefix;
104 /* This is not a new account, parse additional attributes */
105 if (($dn !== NULL) && ($dn != "new") && $this->is_account){
107 /* Load attributes containing arrays */
108 foreach (array("gosaMailAlternateAddress", "gosaMailForwardingAddress") as $val){
109 $this->$val= array();
110 if (isset($this->attrs["$val"]["count"])){
111 for ($i= 0; $i<$this->attrs["$val"]["count"]; $i++){
112 array_push($this->$val, $this->attrs["$val"][$i]);
113 }
114 }
115 }
118 /* Only do IMAP actions if gosaMailServer attribute is set */
119 if (isset ($this->attrs["gosaMailServer"][0])){
121 $id = $method->uattrib;
123 /* Adapt attributes if needed */
124 $method->fixAttributesOnLoad($this);
126 /* FixAttributesOnLoad possibly creates an array out of gosaMailServer.
127 If the mail tab wasn't opened once before saving, the account can't be saved */
128 if(is_array($this->gosaMailServer)){
129 $this->gosaMailServer = $this->gosaMailServer[0];
130 }
132 if ($method->connect($this->attrs["gosaMailServer"][0])){
134 /* Update quota values */
135 $quota= $method->getQuota($this->user_prefix.$this->$id);
137 if($quota){
138 if ($quota['gosaMailQuota'] == 2147483647){
139 $this->quotaUsage = "";
140 $this->gosaMailQuota = "";
141 } else {
142 $this->quotaUsage = $quota['quotaUsage'];
143 $this->gosaMailQuota = $quota['gosaMailQuota'];
144 }
145 }else{
146 $this->quotaUsage = "";
147 $this->gosaMailQuota = "";
148 }
150 /* Get mailboxes / folder like INBOX ..*/
151 $this->mailboxList= $method->getMailboxList($this->user_prefix.$this->$id,$this->$id);
153 $method->disconnect();
154 }else{
155 /* Could not connect to ldap.
156 */
157 if (isset($this->attrs['gosaMailQuota'][0])){
158 $this->gosaMailQuota = $this->attrs['gosaMailQuota'][0];
159 }
160 }
161 }
162 }
164 /* Fill vacation array */
165 $this->vacation= array();
166 if ($this->config->get_cfg_value("vacationdir") != ""){
167 $dir= $this->config->get_cfg_value("vacationdir");
168 if (is_dir($dir) && is_readable($dir)){
170 /* Look for files and build the vacation array */
171 $dh= opendir($dir);
172 while ($file = readdir($dh)){
173 $description= $this->parse_vacation("$dir/$file");
174 if ($description != ""){
175 $this->vacation["$dir/$file"]= $description;
176 }
177 }
178 closedir($dh);
179 }
180 }
182 /* Create sieve management class */
183 $method = new $this->method($this->config);
184 $id = $method->uattrib;
185 $this->sieve_management = new sieveManagement($this->config,$this->dn,$this,$id);
187 /* Get global filter config */
188 if (!session::is_set("mailfilter")){
189 $ui= get_userinfo();
190 $base= get_base_from_people($ui->dn);
191 $mailfilter= array( "depselect" => $base,
192 "muser" => "",
193 "regex" => "*");
194 session::set("mailfilter", $mailfilter);
195 }
196 }
199 function parse_vacation($file)
200 {
201 $desc= "";
203 if (is_file($file)){
204 $fh = fopen($file, "r");
205 $line= fgets($fh, 256);
207 if (!preg_match('/^DESC:/', $line)){
208 msg_dialog::display(_("Configuration error"), sprintf(_("No DESC tag in vacation template '%s'!"), $file), ERROR_DIALOG);
209 return $desc;
210 }
211 fclose ($fh);
213 $desc= trim(preg_replace('/^DESC:\s*/', '', $line));
214 }
216 return $desc;
217 }
220 function execute()
221 {
222 /* Call parent execute */
223 plugin::execute();
225 /* Log view */
226 if($this->is_account && !$this->view_logged){
227 $this->view_logged = TRUE;
228 new log("view","users/".get_class($this),$this->dn);
229 }
231 /* Initialise vars */
233 /* Load templating engine */
234 $smarty= get_smarty();
235 $display= "";
237 /* Get available mailserver */
238 $mailserver= array();
239 $ui = get_userinfo();
240 foreach ($this->config->data['SERVERS']['IMAP'] as $key => $val){
241 if( $this->gosaMailServer == $key ||
242 preg_match("/r/",$ui->get_permissions($val['server_dn'],"server/goImapServer",""))){
243 $mailserver[]= $key;
244 }
245 }
247 /*
248 * Sieve Management
249 */
250 if(isset($_POST['sieveManagement'])
251 && preg_match("/C/",$this->gosaMailDeliveryMode)
252 && $this->acl_is_writeable("sieveManagement")) {
254 $this->dialog = $this->sieve_management;
255 }
257 /* Cancel sieve edit */
258 if(isset($_POST['sieve_cancel'])){
259 $this->dialog = FALSE;
260 }
262 /* Save sieve changes */
263 if(isset($_POST['sieve_finish'])){
264 $this->sieve_management = $this->dialog;
265 $this->dialog = FALSE;
266 }
268 if(is_object($this->dialog)){
269 $this->dialog->save_object();
270 return($this->dialog->execute());
271 }
274 /* Handle account state */
275 /* Do we need to flip is_account state? */
276 if(isset($_POST['modify_state'])){
277 if($this->is_account && $this->acl_is_removeable()){
278 $this->is_account= FALSE;
279 }elseif(!$this->is_account && $this->acl_is_createable()){
280 $this->is_account= TRUE;
281 }
282 }
284 /* Do we represent a valid account? */
285 if(!$this->multiple_support_active){
286 if (!$this->is_account && $this->parent === NULL){
287 $display= "<img alt=\"\" src=\"images/small-error.png\" align=\"middle\"> <b>".
288 msgPool::noValidExtension(_("Mail"))."</b>";
290 $display.= back_to_main();
291 return ($display);
292 }
294 /* Show tab dialog headers */
295 if ($this->parent !== NULL){
296 if ($this->is_account){
297 if($this->accountDelegationsConfigured()){
298 $display= $this->show_disable_header(msgPool::removeFeaturesButton(_("Mail")),
299 _("Mail settings cannot be removed while there are delegations configured!"),TRUE,TRUE);
300 }else{
301 $display= $this->show_disable_header(msgPool::removeFeaturesButton(_("Mail")),
302 msgPool::featuresEnabled(_("Mail")));
303 }
304 } else {
305 $display= $this->show_enable_header(msgPool::addFeaturesButton(_("Mail")),
306 msgPool::featuresDisabled(_("Mail")));
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 if (isset($_POST['local_list'])){
331 /* Check if we are able to write gosaMailForwardingAddress */
332 if($this->acl_is_writeable("gosaMailForwardingAddress")){
334 /* Walk through list of forwarders, ignore own addresses */
335 foreach ($_POST['local_list'] as $val){
336 if (!in_array ($val, $this->gosaMailAlternateAddress) &&
337 $val != $this->mail){
339 $this->addForwarder($val);
340 $this->is_modified= TRUE;
341 }
342 }
343 }
344 $this->forward_dialog= FALSE;
345 $this->dialog= FALSE;
346 } else {
347 msg_dialog::display(_("Error"), _("Please select an entry!"), ERROR_DIALOG);
348 }
349 }
351 /* Add forward email addresses */
352 if (isset($_POST['add_forwarder'])){
353 if ($_POST['forward_address'] != ""){
355 /* Valid email address specified? */
356 $address= $_POST['forward_address'];
357 $valid= FALSE;
358 if (!tests::is_email($address)){
359 if (!tests::is_email($address, TRUE)){
360 if ($this->is_template){
361 $valid= TRUE;
362 } else {
363 msg_dialog::display(_("Error"),
364 msgPool::invalid(_("Mail address"),"","","your-domain@your-domain.com"),
365 ERROR_DIALOG);
366 }
367 }
368 } elseif ($address == $this->mail
369 || in_array($address, $this->gosaMailAlternateAddress)) {
370 msg_dialog::display(_("Error"),_("Cannot add primary address to the list of forwarders!") , ERROR_DIALOG);
371 } else {
372 $valid= TRUE;
373 }
375 if ($valid){
377 /* Add it, if we are able to write gosaMailForwardingAddress */
378 if($this->acl_is_writeable("gosaMailForwardingAddress")){
379 $this->addForwarder ($address);
380 $this->is_modified= TRUE;
381 }
382 }
383 }
384 }
386 /* Delete forward email addresses */
387 if (isset($_POST['delete_forwarder'])){
388 $this->delForwarder ($_POST['forwarder_list']);
389 }
392 /* Add alternate email addresses */
393 if (isset($_POST['add_alternate'])){
395 $valid= FALSE;
396 if (!tests::is_email($_POST['alternate_address'])){
397 if ($this->is_template){
398 if (!(tests::is_email($_POST['alternate_address'], TRUE))){
399 msg_dialog::display(_("Error"),
400 msgPool::invalid(_("Mail address"),"","","your-domain@your-domain.com"),
401 ERROR_DIALOG);
403 } else {
404 $valid= TRUE;
405 }
406 } else {
407 msg_dialog::display(_("Error"),
408 msgPool::invalid(_("Mail address"),"","","your-domain@your-domain.com"),
409 ERROR_DIALOG);
410 }
412 } else {
413 $valid= TRUE;
414 }
416 if ($valid && ($user= $this->addAlternate ($_POST['alternate_address'])) != ""){
417 $ui= get_userinfo();
418 if ($user != $ui->username){
419 msg_dialog::display(_("Error"), msgPool::duplicated(_("Mail address"))." ".
420 sprintf(_("Address is already in use by user '%s'."), $user), ERROR_DIALOG);
421 }
422 }
423 }
425 /* Delete alternate email addresses */
426 if (isset($_POST['delete_alternate']) && isset($_POST['alternates_list'])){
427 $this->delAlternate ($_POST['alternates_list']);
428 }
431 /* Vacation message */
433 /* Import vacation message? */
434 if (isset($_POST["import_vacation"]) && isset($this->vacation[$_POST["vacation_template"]])){
437 /* Save message */
438 if($this->multiple_support_active){
439 $contents = file_get_contents($_POST["vacation_template"]);
440 }else{
441 $contents = $this->prepare_vacation_template(file_get_contents($_POST["vacation_template"]));
442 }
443 $this->gosaVacationMessage= htmlspecialchars($contents);
444 }
447 /* Display forward dialog if requested above */
449 /* Show forward add dialog */
450 if ($this->forward_dialog){
451 $ldap= $this->config->get_ldap_link();
453 /* Save data */
454 $mailfilter= session::get("mailfilter");
455 foreach( array("depselect", "muser", "regex") as $type){
456 if (isset($_POST[$type])){
457 $mailfilter[$type]= $_POST[$type];
458 }
459 }
460 if (isset($_GET['search'])){
461 $s= mb_substr($_GET['search'], 0, 1, "UTF8")."*";
462 if ($s == "**"){
463 $s= "*";
464 }
465 $mailfilter['regex']= $s;
466 }
467 session::set("mailfilter", $mailfilter);
469 /* Get actual list */
470 $mailusers= array ();
471 if ($mailfilter['regex'] != '*' && $mailfilter['regex'] != ""){
472 $regex= $mailfilter['regex'];
473 $filter= "(|(mail=$regex)(gosaMailAlternateAddress=$regex))";
474 } else {
475 $filter= "";
476 }
477 if ($mailfilter['muser'] != ""){
478 $user= $mailfilter['muser'];
479 $filter= "$filter(|(uid=$user)(cn=$user)(givenName=$user)(sn=$user))";
480 }
482 /* Add already present people to the filter */
483 $exclude= "";
484 foreach ($this->gosaMailForwardingAddress as $mail){
485 $exclude.= "(mail=$mail)";
486 }
487 if ($exclude != ""){
488 $filter.= "(!(|$exclude))";
489 }
491 $res= get_list("(&(objectClass=gosaMailAccount)$filter)", "users", $mailfilter['depselect'],
492 array("sn", "mail", "givenName"), GL_SIZELIMIT | GL_SUBSEARCH);
493 $ldap->cd($mailfilter['depselect']);
494 $ldap->search ("(&(objectClass=gosaMailAccount)$filter)", array("sn", "mail", "givenName"));
495 error_reporting (0);
496 while ($attrs= $ldap->fetch()){
497 if(preg_match('/%/', $attrs['mail'][0])){
498 continue;
499 }
500 $name= $this->make_name($attrs);
501 $mailusers[$attrs['mail'][0]]= $name."<".
502 $attrs['mail'][0].">";
503 }
504 error_reporting (E_ALL | E_STRICT);
505 natcasesort ($mailusers);
506 reset ($mailusers);
508 /* Show dialog */
509 $smarty->assign("search_image", get_template_path('images/lists/search.png'));
510 $smarty->assign("usearch_image", get_template_path('images/lists/search-user.png'));
511 $smarty->assign("tree_image", get_template_path('images/lists/search-subtree.png'));
512 $smarty->assign("infoimage", get_template_path('images/info.png'));
513 $smarty->assign("launchimage", get_template_path('images/lists/action.png'));
514 $smarty->assign("mailusers", $mailusers);
515 if (isset($_POST['depselect'])){
516 $smarty->assign("depselect", $_POST['depselect']);
517 }
518 $smarty->assign("deplist", $this->config->idepartments);
519 $smarty->assign("apply", apply_filter());
520 $smarty->assign("alphabet", generate_alphabet());
521 $smarty->assign("hint", print_sizelimit_warning());
522 foreach( array("depselect", "muser", "regex") as $type){
523 $smarty->assign("$type", $mailfilter[$type]);
524 }
525 $smarty->assign("hint", print_sizelimit_warning());
527 $display.= $smarty->fetch (get_template_path('mail_locals.tpl', TRUE, dirname(__FILE__)));
528 return ($display);
529 }
531 /* Display mail account tab */
533 $smarty->assign("mailServers", $mailserver);
534 $SkipWrite = (!isset($this->parent) || !$this->parent) && !session::is_set('edit');
536 $tmp = $this->plInfo();
537 foreach($tmp['plProvidedAcls'] as $name => $transl){
538 $smarty->assign("$name"."ACL", $this->getacl($name,$SkipWrite));
539 }
541 foreach(array("gosaMailServer", "gosaMailQuota", "perms", "mail",
542 "gosaMailAlternateAddress", "gosaMailForwardingAddress",
543 "gosaVacationMessage", "gosaMailDeliveryMode", "gosaVacationStart",
544 "gosaVacationStop", "gosaMailMaxSize", "gosaSpamSortLevel", "gosaSpamMailbox") as $val){
545 $smarty->assign("$val", $this->$val);
546 }
548 if (is_numeric($this->gosaMailQuota) && $this->gosaMailQuota != 0){
549 $smarty->assign("quotausage", progressbar(round(($this->quotaUsage * 100)/ $this->gosaMailQuota),100,15,true));
550 $smarty->assign("quotadefined", "true");
551 } else {
552 $smarty->assign("quotadefined", "false");
553 }
555 /* Disable mail field if needed */
556 $method= new $this->method($this->config);
557 if ($method->uattrib == "mail" && $this->initially_was_account){
558 $smarty->assign("mailACL", preg_replace("/w/","",$this->getacl("mail",$SkipWrite)));
559 }
561 /* Disable/Enable range select, but do not disable them twice
562 * if they are already diabled by "use own sieve script"
563 */
564 if (preg_match('/V/', $this->gosaMailDeliveryMode) || preg_match("/C/",$this->gosaMailDeliveryMode)){
565 $smarty->assign('rangeEnabled', "");
566 } else {
567 $smarty->assign('rangeEnabled', "disabled");
568 }
570 if (!preg_match("/L/", $this->gosaMailDeliveryMode)) {
571 $smarty->assign("only_local", "checked");
572 } else {
573 $smarty->assign("only_local", "");
574 }
576 $types = array(
577 "V"=>"use_vacation",
578 "S"=>"use_spam_filter",
579 "R"=>"use_mailsize_limit",
580 "I"=>"drop_own_mails",
581 "C"=>"own_script");
583 /* Fill checkboxes */
584 foreach($types as $option => $varname){
585 if (preg_match("/".$option."/", $this->gosaMailDeliveryMode)) {
586 $smarty->assign($varname, "checked");
587 } else {
588 $smarty->assign($varname, "");
589 }
590 }
592 /* Display mail account tab */
593 if($this->gosaVacationStart ==0){
594 $date= getdate(time());
595 }else{
596 $date= getdate($this->gosaVacationStart);
597 }
598 $days= array();
599 for($d= 1; $d<32; $d++){
600 $days[$d]= $d;
601 }
602 $years= array();
603 for($y= $date['year']-10; $y<$date['year']+10; $y++){
604 $years[]= $y;
605 }
606 $months= msgPool::months();
607 $smarty->assign("start_day", $date["mday"]);
608 $smarty->assign("days", $days);
609 $smarty->assign("months", $months);
610 $smarty->assign("start_month", $date["mon"]-1);
611 $smarty->assign("years", $years);
612 $smarty->assign("start_year", $date["year"]);
614 if($this->gosaVacationStop ==0){
615 $date= getdate(time());
616 $date["mday"]++;
617 }else{
618 $date= getdate($this->gosaVacationStop);
619 }
620 $smarty->assign("end_day", $date["mday"]);
621 $smarty->assign("end_month", $date["mon"]-1);
622 $smarty->assign("end_year", $date["year"]);
626 /* Have vacation templates? */
627 $smarty->assign("template", "");
628 if (count($this->vacation)){
629 $smarty->assign("show_templates", "true");
630 $smarty->assign("vacationtemplates", $this->vacation);
631 if (isset($_POST['vacation_template'])){
632 $smarty->assign("template", $_POST['vacation_template']);
633 }
634 } else {
635 $smarty->assign("show_templates", "false");
636 }
638 /* Fill spam selector */
639 $spamlevel= array();
640 for ($i= 0; $i<21; $i++){
641 $spamlevel[]= $i;
642 }
643 $smarty->assign("spamlevel", $spamlevel);
644 $smarty->assign("spambox", $this->mailboxList);
646 foreach($this->attributes as $attr){
647 $u_attr = "use_".$attr;
648 $smarty->assign($u_attr,in_array($attr,$this->multi_boxes));
649 }
651 foreach(array("only_local","gosaMailForwardingAddress","use_mailsize_limit","drop_own_mails","use_vacation","use_spam_filter") as $attr){
652 $u_attr = "use_".$attr;
653 $smarty->assign($u_attr,in_array($attr,$this->multi_boxes));
654 }
656 $smarty->assign("multiple_support",$this->multiple_support_active);
657 $display.= $smarty->fetch (get_template_path('generic.tpl', TRUE, dirname(__FILE__)));
658 return ($display);
659 }
662 /* remove object from parent */
663 function remove_from_parent()
664 {
665 /* Cancel if there's nothing to do here */
666 if (!$this->initially_was_account){
667 return;
668 }
670 /* include global link_info */
671 $ldap= $this->config->get_ldap_link();
673 /* Remove and write to LDAP */
674 plugin::remove_from_parent();
676 /* Zero arrays */
677 $this->attrs['gosaMailAlternateAddress']= array();
678 $this->attrs['gosaMailForwardingAddress']= array();
680 /* Adapt attributes if needed */
681 $method= new $this->method($this->config);
682 $method->fixAttributesOnRemove($this);
684 @DEBUG (DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__,$this->attributes, "Save");
685 $ldap->cd($this->dn);
686 $this->cleanup();
688 $ldap->modify ($this->attrs);
690 /* Add "view" to logging class */
691 new log("remove","users/".get_class($this),$this->dn,array_keys($this->attrs),$ldap->get_error());
693 if (!$ldap->success()){
694 msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $this->dn, LDAP_MOD, get_class()));
695 }
697 /* Connect to IMAP server for account deletion */
698 if ($this->gosaMailServer != ""){
699 $method= new $this->method($this->config);
700 $id= $method->uattrib;
701 if ($method->connect($this->gosaMailServer)){
703 /* Remove account from IMAP server */
704 $method->deleteMailbox($this->user_prefix.$this->$id);
705 $method->disconnect();
706 }
707 }
709 /* Update shared folder membership, ACL may need to be updated */
710 $this->updateSharedFolder();
712 /* Optionally execute a command after we're done */
713 $this->handle_post_events("remove",array("uid" => $this->uid));
714 }
717 /* check if we have some delegations configured, those delegations must be removed first */
718 function accountDelegationsConfigured()
719 {
720 /* We are in administrational edit mode.
721 Check tab configurations directly */
722 if(isset($this->attrs)){
723 $checkArray = array("kolabInvitationPolicy","unrestrictedMailSize", "calFBURL","kolabDelegate","kolabFreeBusyFuture");
724 foreach($checkArray as $index){
725 if(isset($this->attrs[$index])){
726 return(true);
727 }
728 }
729 }
730 return(false);
731 }
734 /* Save data to object */
735 function save_object()
736 {
737 if (isset($_POST['mailTab'])){
739 /* Save ldap attributes */
740 plugin::save_object();
743 if(isset($_POST['own_script'])){
745 if(!preg_match("/C/",$this->gosaMailDeliveryMode)){
746 $str= preg_replace("/[\[\]]/","",$this->gosaMailDeliveryMode);
747 $this->gosaMailDeliveryMode = "[".$str."C]";
748 }
750 }else{
752 /* Assemble mail delivery mode
753 The mode field in ldap consists of values between braces, this must
754 be called when 'mail' is set, because checkboxes may not be set when
755 we're in some other dialog.
757 Example for gosaMailDeliveryMode [LR ]
758 L: Local delivery
759 R: Reject when exceeding mailsize limit
760 S: Use spam filter
761 V: Use vacation message
762 C: Use custm sieve script
763 I: Only insider delivery */
765 $tmp= preg_replace("/[^a-z]/i","",$this->gosaMailDeliveryMode);
768 /* Handle delivery flags */
769 if($this->acl_is_writeable("gosaMailDeliveryModeL")){
770 if(!preg_match("/L/",$tmp) && !isset($_POST['only_local'])){
771 $tmp.="L";
772 }elseif(preg_match("/L/",$tmp) && isset($_POST['only_local'])){
773 $tmp = preg_replace("/L/","",$tmp);
774 }
775 }
777 $opts = array(
778 "R" => "use_mailsize_limit",
779 "S" => "use_spam_filter",
780 "V" => "use_vacation",
781 "C" => "own_script",
782 "I" => "drop_own_mails");
784 foreach($opts as $flag => $post){
785 if($this->acl_is_writeable("gosaMailDeliveryMode".$flag)){
786 if(!preg_match("/".$flag."/",$tmp) && isset($_POST[$post])){
787 $tmp.= $flag;
788 }elseif(preg_match("/".$flag."/",$tmp) && !isset($_POST[$post])){
789 $tmp = preg_replace("/".$flag."/","",$tmp);
790 }
791 }
792 }
794 $tmp= "[$tmp]";
795 if ($this->gosaMailDeliveryMode != $tmp){
796 $this->is_modified= TRUE;
797 }
798 $this->gosaMailDeliveryMode= $tmp;
801 if($this->acl_is_writeable("gosaVacationMessage") && preg_match("/V/",$this->gosaMailDeliveryMode)){
802 if(isset($_POST['gosaVacationStart'])){
803 $this->gosaVacationStart = $_POST['gosaVacationStart'];
804 }
805 if(isset($_POST['gosaVacationStop'])){
806 $this->gosaVacationStop = $_POST['gosaVacationStop'];
807 }
808 }
809 }
810 }
811 }
814 /* Save data to LDAP, depending on is_account we save or delete */
815 function save()
816 {
817 $ldap= $this->config->get_ldap_link();
819 /* Call parents save to prepare $this->attrs */
820 plugin::save();
822 /* Save arrays */
823 $this->attrs['gosaMailAlternateAddress']= $this->gosaMailAlternateAddress;
824 $this->attrs['gosaMailForwardingAddress']= $this->gosaMailForwardingAddress;
826 /* Adapt attributes if needed */
827 $method= new $this->method($this->config);
828 $id= $method->uattrib;
830 $method->fixAttributesOnStore($this);
832 /* Remove Mailquota if = "" or "0" */
833 if((isset($this->attrs['gosaMailQuota']))&&(!$this->attrs['gosaMailQuota'])) {
834 $this->attrs['gosaMailQuota']=0;
835 }
837 if(empty($this->attrs['gosaSpamMailbox'])){
838 unset($this->attrs['gosaSpamMailbox']);
839 }
841 $this->attrs['mail'] = strtolower($this->attrs['mail']);
843 /* Remove attributes - if not needed */
844 if (!preg_match('/V/', $this->gosaMailDeliveryMode)){
845 unset($this->attrs['gosaVacationStart']);
846 unset($this->attrs['gosaVacationStop']);
847 }
850 /* Remove attributes - if not needed */
851 if (!preg_match('/V/', $this->gosaMailDeliveryMode)){
852 unset($this->attrs['gosaVacationStart']);
853 unset($this->attrs['gosaVacationStop']);
854 }
856 /* Save data to LDAP */
857 $ldap->cd($this->dn);
858 $this->cleanup();
859 $ldap->modify ($this->attrs);
861 if (!$ldap->success()){
862 msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $this->dn, LDAP_MOD, get_class()));
863 }
865 /* Log last action */
866 if($this->initially_was_account){
867 new log("modify","users/".get_class($this),$this->dn,array_keys($this->attrs),$ldap->get_error());
868 }else{
869 new log("create","users/".get_class($this),$this->dn,array_keys($this->attrs),$ldap->get_error());
870 }
872 /* Only do IMAP actions if we are not a template */
873 if (!$this->is_template){
875 if ($method->connect($this->gosaMailServer)){
876 $method->updateMailbox($this->user_prefix.$this->$id);
878 $method->setQuota($this->user_prefix.$this->$id, $this->gosaMailQuota);
879 $method->disconnect();
881 /* Ensure that this is an existing account */
882 if(1==1 || $this->initially_was_account){
884 /* Write sieve information only if not in C mode */
885 if (!is_integer(strpos($this->gosaMailDeliveryMode, "C"))){
886 $method->configureFilter($this->$id,
887 $this->gosaMailDeliveryMode,
888 $this->mail,
889 $this->gosaMailAlternateAddress,
890 $this->gosaMailMaxSize,
891 $this->gosaSpamMailbox,
892 $this->gosaSpamSortLevel,
893 $this->gosaVacationMessage);
894 $this->is_modified = TRUE;
895 }else{
896 $this->sieve_management->save();
897 }
898 }
899 }
900 }
902 /* Optionally execute a command after we're done */
903 if ($this->initially_was_account == $this->is_account){
904 if ($this->is_modified){
905 $this->handle_post_events("modify", array("uid" => $this->uid));
906 }
907 } else {
908 $this->handle_post_events("add", array("uid" => $this->uid));
909 }
911 $this->updateSharedFolder();
912 }
915 /* Check formular input */
916 function check()
917 {
918 if(!$this->is_account) return(array());
920 $ldap= $this->config->get_ldap_link();
922 /* Call common method to give check the hook */
923 $message= plugin::check();
925 if(empty($this->gosaMailServer)){
926 $message[]= msgPool::noserver(_("Mail"));
927 }
929 /* must: mail */
930 if ($this->mail == ""){
931 $message[]= msgPool::required(_("Primary address"));
932 }
933 if ($this->is_template){
934 if (!tests::is_email($this->mail, TRUE)){
935 $message[]= msgPool::invalid(_("Mail address"),"","","%givenName.%sn@your-domain.com");
936 }
937 } else {
938 if (!tests::is_email($this->mail)){
939 $message[]= msgPool::invalid(_("Mail address"),"","","your-domain@your-domain.com");
940 }
941 }
943 $ldap->cd($this->config->current['BASE']);
944 $filter = "(&(!(objectClass=gosaUserTemplate))(!(uid=".$this->uid."))".
945 "(objectClass=gosaMailAccount)".
946 "(|(mail=".$this->mail.")(alias=".$this->mail.")(gosaMailAlternateAddress=".$this->mail.")))";
948 $ldap->search($filter,array("uid"));
949 if ($ldap->count() != 0){
950 $message[]= msgPool::duplicated(_("Mail address"));
951 }
954 /* Check quota */
955 if ($this->gosaMailQuota != '' && $this->acl_is_writeable("gosaMailQuota")){
956 if (!is_numeric($this->gosaMailQuota)) {
957 $message[]= msgPool::invalid(_("Quota size"),$this->gosaMailQuota,"/^[0-9]*/");
958 } else {
959 $this->gosaMailQuota= (int) $this->gosaMailQuota;
960 }
961 }
963 /* Check rejectsize for integer */
964 if ($this->gosaMailMaxSize != '' && $this->acl_is_writeable("gosaMailMaxSize")){
965 if (!is_numeric($this->gosaMailMaxSize)){
966 $message[]= msgPool::invalid(_("Mail reject size"),$this->gosaMailMaxSize,"/^[0-9]*/");
967 } else {
968 $this->gosaMailMaxSize= (int) $this->gosaMailMaxSize;
969 }
970 }
972 /* Need gosaMailMaxSize if use_mailsize_limit is checked */
973 if (is_integer(strpos($this->gosaMailDeliveryMode, "R")) && $this->gosaMailMaxSize == ""){
974 $message[]= msgPool::required(_("Mail reject size"));
975 }
977 if((preg_match("/S/", $this->gosaMailDeliveryMode))&&(empty($this->gosaSpamMailbox))) {
978 $message[]= msgPool::required(_("Spam folder"));
979 }
981 if (preg_match('/V/', $this->gosaMailDeliveryMode) && $this->gosaVacationStart >= $this->gosaVacationStop){
982 $message[]= msgPool::invalid(_("Vacation interval"));
983 }
985 return ($message);
986 }
989 /* Adapt from template, using 'dn' */
990 function adapt_from_template($dn, $skip= array())
991 {
992 plugin::adapt_from_template($dn, $skip);
994 foreach (array("gosaMailAlternateAddress", "gosaMailForwardingAddress") as $val){
996 if (in_array($val, $skip)){
997 continue;
998 }
1000 $this->$val= array();
1001 if (isset($this->attrs["$val"]["count"])){
1002 for ($i= 0; $i<$this->attrs["$val"]["count"]; $i++){
1003 $value= $this->attrs["$val"][$i];
1004 foreach (array("sn", "givenName", "uid") as $repl){
1005 if (preg_match("/%$repl/i", $value)){
1006 $value= preg_replace ("/%$repl/i", $this->parent->$repl, $value);
1007 }
1008 }
1009 array_push($this->$val, strtolower(rewrite($value)));
1010 }
1011 }
1012 }
1013 $this->mail= strtolower(rewrite($this->mail));
1014 }
1017 /* Add entry to forwarder list */
1018 function addForwarder($address)
1019 {
1020 if($this->acl_is_writeable("gosaMailForwardingAddress")){
1021 $this->gosaMailForwardingAddress[]= $address;
1022 $this->gosaMailForwardingAddress= array_unique ($this->gosaMailForwardingAddress);
1023 sort ($this->gosaMailForwardingAddress);
1024 reset ($this->gosaMailForwardingAddress);
1025 $this->is_modified= TRUE;
1026 }else{
1027 msg_dialog::display(_("Permission error"), _("You have no permission to modify these addresses!"), ERROR_DIALOG);
1028 }
1029 }
1032 /* Remove list of addresses from forwarder list */
1033 function delForwarder($addresses)
1034 {
1035 if($this->acl_is_writeable("gosaMailForwardingAddress")){
1036 $this->gosaMailForwardingAddress= array_remove_entries ($addresses, $this->gosaMailForwardingAddress);
1037 $this->is_modified= TRUE;
1038 }else{
1039 msg_dialog::display(_("Permission error"), _("You have no permission to modify these addresses!"), ERROR_DIALOG);
1040 }
1041 }
1044 /* Add given mail address to the list of alternate adresses ,
1045 check if this mal address is used, skip adding in this case */
1046 function addAlternate($address)
1047 {
1048 if($this->acl_is_writeable("gosaMailAlternateAddress")){
1049 $ldap= $this->config->get_ldap_link();
1050 $address= strtolower($address);
1052 /* Is this address already assigned in LDAP? */
1053 $ldap->cd ($this->config->current['BASE']);
1054 $ldap->search ("(&(!(objectClass=gosaUserTemplate))(objectClass=gosaMailAccount)(|(mail=$address)".
1055 "(alias=$address)(gosaMailAlternateAddress=$address)))", array("uid"));
1057 if ($ldap->count() > 0){
1058 $attrs= $ldap->fetch ();
1059 return ($attrs["uid"][0]);
1060 }
1062 /* Add to list of alternates */
1063 if (!in_array($address, $this->gosaMailAlternateAddress)){
1064 $this->gosaMailAlternateAddress[]= $address;
1065 $this->is_modified= TRUE;
1066 }
1068 sort ($this->gosaMailAlternateAddress);
1069 reset ($this->gosaMailAlternateAddress);
1070 return ("");
1071 }else{
1072 msg_dialog::display(_("Permission error"), _("You have no permission to modify these addresses!"), ERROR_DIALOG);
1073 }
1074 }
1077 function delAlternate($addresses)
1078 {
1079 if($this->acl_is_writeable("gosaMailAlternateAddress")){
1080 $this->gosaMailAlternateAddress= array_remove_entries ($addresses,$this->gosaMailAlternateAddress);
1081 $this->is_modified= TRUE;
1082 }else{
1083 msg_dialog::display(_("Permission error"), _("You have no permission to modify these addresses!"), ERROR_DIALOG);
1084 }
1085 }
1087 function make_name($attrs)
1088 {
1089 $name= "";
1090 if (isset($attrs['sn'][0])){
1091 $name= $attrs['sn'][0];
1092 }
1093 if (isset($attrs['givenName'][0])){
1094 if ($name != ""){
1095 $name.= ", ".$attrs['givenName'][0];
1096 } else {
1097 $name.= $attrs['givenName'][0];
1098 }
1099 }
1100 if ($name != ""){
1101 $name.= " ";
1102 }
1104 return ($name);
1105 }
1108 /* Create the mail part for the copy & paste dialog */
1109 function getCopyDialog()
1110 {
1111 if(!$this->is_account) return("");
1112 $smarty = get_smarty();
1113 $smarty->assign("mail",$this->mail);
1114 $smarty->assign("gosaMailAlternateAddress",$this->gosaMailAlternateAddress);
1115 $smarty->assign("gosaMailForwardingAddress",$this->gosaMailForwardingAddress);
1116 $str = $smarty->fetch(get_template_path("copypaste.tpl",TRUE, dirname(__FILE__)));
1118 $ret = array();
1119 $ret['status'] = "";
1120 $ret['string'] = $str;
1121 return($ret);
1122 }
1124 function saveCopyDialog()
1125 {
1126 if(!$this->is_account) return;
1128 /* Execute to save mailAlternateAddress && gosaMailForwardingAddress */
1129 $this->execute();
1131 if(isset($_POST['mail'])){
1132 $this->mail = $_POST['mail'];
1133 }
1135 }
1137 function allow_remove()
1138 {
1139 if ($this->config->get_cfg_value("mailmethod") != ""){
1140 $method= $this->config->get_cfg_value("mailmethod");
1141 if(preg_match("/olab/i",$method)){
1142 $ldap = $this->config->get_ldap_link();
1143 $ldap->cd($this->config->current['BASE']);
1144 $ldap->cat($this->dn);
1145 if($ldap->count()){
1146 $attrs = $ldap->fetch();
1147 if(isset($attrs['kolabDeleteFlag'])){
1148 return(_("Waiting for kolab to remove mail properties..."));
1149 }elseif(in_array("gosaMailAccount",$attrs['objectClass'])){
1150 return(_("Please remove the mail settings first to allow kolab to call its remove methods!"));
1151 }
1152 }
1153 }
1154 }
1155 }
1158 function PrepareForCopyPaste($source)
1159 {
1160 plugin::PrepareForCopyPaste($source);
1162 /* Reset alternate mail addresses */
1163 $this->gosaMailAlternateAddress = array();
1164 }
1167 static function plInfo()
1168 {
1169 return (array(
1170 "plShortName" => _("Mail"),
1171 "plDescription" => _("Mail settings"),
1172 "plSelfModify" => TRUE,
1173 "plDepends" => array("user"), // This plugin depends on
1174 "plPriority" => 4, // Position in tabs
1175 "plSection" => array("personal" => _("My account")),
1176 "plCategory" => array("users"),
1177 "plOptions" => array(),
1179 "plProvidedAcls" => array(
1180 "mail" => _("Mail address"),
1181 "gosaMailServer" => _("Mail server"),
1182 "gosaMailQuota" => _("Quota size"),
1184 "gosaMailDeliveryModeV" => _("Add vacation information"), // This is flag of gosaMailDeliveryMode
1185 "gosaVacationMessage" => _("Vacation message"),
1187 "gosaMailDeliveryModeS" => _("Use spam filter"), // This is flag of gosaMailDeliveryMode
1188 "gosaSpamSortLevel" => _("Spam level"),
1189 "gosaSpamMailbox" => _("Spam mail box"),
1191 "sieveManagement" => _("Sieve management"),
1193 "gosaMailDeliveryModeR" => _("Reject due to mailsize"), // This is flag of gosaMailDeliveryMode
1194 "gosaMailMaxSize" => _("Mail max size"),
1196 "gosaMailForwardingAddress" => _("Forwarding address"),
1197 "gosaMailDeliveryModeL" => _("Local delivery"), // This is flag of gosaMailDeliveryMode
1198 "gosaMailDeliveryModeI" => _("No delivery to own mailbox "), // This is flag of gosaMailDeliveryMode
1199 "gosaMailAlternateAddress" => _("Mail alternative addresses"),
1201 "gosaMailForwardingAddress" => _("Forwarding address"),
1202 "gosaMailDeliveryModeC" => _("Use custom sieve script")) // This is flag of gosaMailDeliveryMode
1203 ));
1204 }
1207 /*! \brief Prepare importet vacation string. \
1208 Replace placeholder like %givenName a.s.o.
1209 @param string Vacation string
1210 @return string Completed vacation string
1211 */
1212 private function prepare_vacation_template($contents)
1213 {
1214 /* Replace attributes */
1215 $attrs = array();
1216 $obj = NULL;
1217 if(isset($this->parent->by_object['user'])){
1218 $attrs = $this->parent->by_object['user']->attributes;
1219 $obj = $this->parent->by_object['user'];
1220 }else{
1221 $obj = new user($this->config,$this->dn);
1222 $attrs = $obj->attributes;
1223 }
1225 if($obj){
1226 foreach ($attrs as $val){
1227 if(preg_match("/dateOfBirth/",$val)){
1228 if($obj->use_dob){
1229 $contents= preg_replace("/%$val/",date("Y-d-m",$obj->dateOfBirth),$contents);
1230 }
1231 }else {
1232 $contents= preg_replace("/%$val/",
1233 $obj->$val, $contents);
1234 }
1236 /* Replace vacation start and end time */
1237 if(preg_match("/%start/",$contents)){
1238 $contents = preg_replace("/%start/",date("d.m.Y",$this->gosaVacationStart),$contents);
1239 }
1240 if(preg_match("/%end/",$contents)){
1241 $contents = preg_replace("/%end/",date("d.m.Y",$this->gosaVacationStop),$contents);
1242 }
1243 }
1244 }
1245 return($contents);
1246 }
1250 /* Upated shared folder ACLs
1251 */
1252 function updateSharedFolder()
1253 {
1254 $ldap = $this->config->get_ldap_link();
1255 $ldap->cd($this->config->current['BASE']);
1256 $ldap->search("(&(objectClass=posixGroup)(objectClass=gosaMailAccount)(memberUid=".$this->uid."))",array('dn','cn'));
1257 if(class_exists("grouptabs")){
1258 while($attrs = $ldap->fetch()){
1259 $tmp = new grouptabs($this->config, $this->config->data['TABS']['GROUPTABS'], $attrs['dn']);
1260 if(isset($tmp->by_object['mailgroup'])){
1261 $tmp->by_object['mailgroup']->members= $tmp->by_object['group']->memberUid;
1262 if(!$this->is_account){
1263 $tmp->by_object['mailgroup']->removeUserAcl($this->uid);
1264 $tmp->by_object['mailgroup']->removeUserAcl($this->mail);
1265 }
1266 $tmp->by_object['mailgroup']->save();
1267 }
1268 }
1269 }
1270 }
1272 /* Initialize plugin with given atribute arrays
1273 */
1274 function init_multiple_support($attrs,$all)
1275 {
1276 plugin::init_multiple_support($attrs,$all);
1278 if(isset($this->multi_attrs['gosaMailQuota'])){
1279 $this->gosaMailQuota = $this->multi_attrs['gosaMailQuota'];
1280 }
1281 }
1283 function get_multi_init_values()
1284 {
1285 $attrs = plugin::get_multi_init_values();
1286 $attrs['gosaMailQuota'] = $this->gosaMailQuota;
1287 return($attrs);
1288 }
1290 function multiple_execute()
1291 {
1292 return($this->execute());
1293 }
1295 function multiple_save_object()
1296 {
1297 plugin::multiple_save_object();
1299 $this->save_object();
1300 foreach(array("only_local","gosaMailForwardingAddress","use_mailsize_limit","drop_own_mails","use_vacation","use_spam_filter") as $attr){
1301 if(isset($_POST["use_".$attr])){
1302 $this->multi_boxes[] = $attr;
1303 }
1304 }
1305 }
1307 function get_multi_edit_values()
1308 {
1309 $ret = plugin::get_multi_edit_values();
1311 if(in_array("gosaMailQuota",$this->multi_boxes)){
1312 $ret['gosaMailQuota'] = $this->gosaMailQuota;
1313 }
1315 $flag_add = $flag_remove = array();
1316 $tmp= preg_replace("/[^a-z]/i","",$this->gosaMailDeliveryMode);
1317 $opts = array(
1318 "R" => "use_mailsize_limit",
1319 "S" => "use_spam_filter",
1320 "L" => "only_local",
1321 "V" => "use_vacation",
1322 "C" => "own_script",
1323 "I" => "drop_own_mails");
1325 foreach($opts as $flag => $post){
1326 if(in_array($post, $this->multi_boxes)){
1327 if(preg_match("/".$flag."/",$tmp)){
1328 $flag_add[] = $flag;
1329 }else{
1330 $flag_remove[] = $flag;
1331 }
1332 }
1333 }
1335 $ret['flag_add'] = $flag_add;
1336 $ret['flag_remove'] = $flag_remove;
1337 return($ret);
1338 }
1341 function multiple_check()
1342 {
1343 $message = plugin::multiple_check();
1345 if(empty($this->gosaMailServer) && in_array("gosaMailServer",$this->multi_boxes)){
1346 $message[]= msgPool::noserver(_("Mail"));
1347 }
1349 /* Check quota */
1350 if ($this->gosaMailQuota != '' && in_array("gosaMailQuota",$this->multi_boxes)){
1351 if (!is_numeric($this->gosaMailQuota)) {
1352 $message[]= msgPool::invalid(_("Quota size"),$this->gosaMailQuota,"/^[0-9]*/");
1353 } else {
1354 $this->gosaMailQuota= (int) $this->gosaMailQuota;
1355 }
1356 }
1358 /* Check rejectsize for integer */
1359 if ($this->gosaMailMaxSize != '' && in_array("gosaMailMaxSize",$this->multi_boxes)){
1360 if (!is_numeric($this->gosaMailMaxSize)){
1361 $message[]= msgPool::invalid(_("Mail reject size"),$this->gosaMailMaxSize,"/^[0-9]*/");
1362 } else {
1363 $this->gosaMailMaxSize= (int) $this->gosaMailMaxSize;
1364 }
1365 }
1367 if(empty($this->gosaSpamMailbox) && in_array("gosaSpamMailbox",$this->multi_boxes)){
1368 $message[]= msgPool::required(_("Spam folder"));
1369 }
1371 if ( in_array("use_vacation",$this->multi_boxes) &&
1372 preg_match('/V/', $this->gosaMailDeliveryMode) && $this->gosaVacationStart > $this->gosaVacationStop){
1373 $message[]= msgPool::invalid(_("Vacation interval"));
1374 }
1375 return($message);
1376 }
1379 function set_multi_edit_values($values)
1380 {
1381 plugin::set_multi_edit_values($values);
1382 $tmp= preg_replace("/[^a-z]/i","",$this->gosaMailDeliveryMode);
1383 if(isset($values['flag_add'])){
1384 foreach($values['flag_add'] as $flag){
1385 if(!preg_match("/".$flag."/",$tmp)){
1386 $tmp .= $flag;
1387 }
1388 }
1389 }
1390 if(isset($values['flag_remove'])){
1391 foreach($values['flag_remove'] as $flag){
1392 if(preg_match("/".$flag."/",$tmp)){
1393 $tmp = preg_replace("/".$flag."/","",$tmp);
1394 }
1395 }
1396 }
1397 $this->gosaMailDeliveryMode = "[".$tmp."]";
1399 /* Set vacation message and replace placeholder like %givenName
1400 */
1401 if(isset($values['gosaVacationMessage'])){
1402 $this->gosaVacationMessage = $this->prepare_vacation_template($values['gosaVacationMessage']);
1403 }
1404 }
1405 }
1407 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
1408 ?>