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= msgPool::months();
603 $smarty->assign("start_day", $date["mday"]);
604 $smarty->assign("days", $days);
605 $smarty->assign("months", $months);
606 $smarty->assign("start_month", $date["mon"]-1);
607 $smarty->assign("years", $years);
608 $smarty->assign("start_year", $date["year"]);
610 if($this->gosaVacationStop ==0){
611 $date= getdate(time());
612 $date["mday"]++;
613 }else{
614 $date= getdate($this->gosaVacationStop);
615 }
616 $smarty->assign("end_day", $date["mday"]);
617 $smarty->assign("end_month", $date["mon"]-1);
618 $smarty->assign("end_year", $date["year"]);
622 /* Have vacation templates? */
623 $smarty->assign("template", "");
624 if (count($this->vacation)){
625 $smarty->assign("show_templates", "true");
626 $smarty->assign("vacationtemplates", $this->vacation);
627 if (isset($_POST['vacation_template'])){
628 $smarty->assign("template", $_POST['vacation_template']);
629 }
630 } else {
631 $smarty->assign("show_templates", "false");
632 }
634 /* Fill spam selector */
635 $spamlevel= array();
636 for ($i= 0; $i<21; $i++){
637 $spamlevel[]= $i;
638 }
639 $smarty->assign("spamlevel", $spamlevel);
640 $smarty->assign("spambox", $this->mailboxList);
642 foreach($this->attributes as $attr){
643 $u_attr = "use_".$attr;
644 $smarty->assign($u_attr,in_array($attr,$this->multi_boxes));
645 }
647 foreach(array("only_local","gosaMailForwardingAddress","use_mailsize_limit","drop_own_mails","use_vacation","use_spam_filter") as $attr){
648 $u_attr = "use_".$attr;
649 $smarty->assign($u_attr,in_array($attr,$this->multi_boxes));
650 }
652 $smarty->assign("multiple_support",$this->multiple_support_active);
653 $display.= $smarty->fetch (get_template_path('generic.tpl', TRUE, dirname(__FILE__)));
654 return ($display);
655 }
658 /* remove object from parent */
659 function remove_from_parent()
660 {
661 /* Cancel if there's nothing to do here */
662 if (!$this->initially_was_account){
663 return;
664 }
666 /* include global link_info */
667 $ldap= $this->config->get_ldap_link();
669 /* Remove and write to LDAP */
670 plugin::remove_from_parent();
672 /* Zero arrays */
673 $this->attrs['gosaMailAlternateAddress']= array();
674 $this->attrs['gosaMailForwardingAddress']= array();
676 /* Adapt attributes if needed */
677 $method= new $this->method($this->config);
678 $method->fixAttributesOnRemove($this);
680 @DEBUG (DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__,$this->attributes, "Save");
681 $ldap->cd($this->dn);
682 $this->cleanup();
684 $ldap->modify ($this->attrs);
686 /* Add "view" to logging class */
687 new log("remove","users/".get_class($this),$this->dn,array_keys($this->attrs),$ldap->get_error());
689 if (!$ldap->success()){
690 msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $this->dn, LDAP_MOD, get_class()));
691 }
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 if (!$ldap->success()){
858 msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $this->dn, LDAP_MOD, get_class()));
859 }
861 /* Log last action */
862 if($this->initially_was_account){
863 new log("modify","users/".get_class($this),$this->dn,array_keys($this->attrs),$ldap->get_error());
864 }else{
865 new log("create","users/".get_class($this),$this->dn,array_keys($this->attrs),$ldap->get_error());
866 }
868 /* Only do IMAP actions if we are not a template */
869 if (!$this->is_template){
871 if ($method->connect($this->gosaMailServer)){
872 $method->updateMailbox($this->folder_prefix.$this->$id);
874 $method->setQuota($this->folder_prefix.$this->$id, $this->gosaMailQuota);
875 $method->disconnect();
877 /* Ensure that this is an existing account */
878 if(1==1 || $this->initially_was_account){
880 /* Write sieve information only if not in C mode */
881 if (!is_integer(strpos($this->gosaMailDeliveryMode, "C"))){
882 $method->configureFilter($this->$id,
883 $this->gosaMailDeliveryMode,
884 $this->mail,
885 $this->gosaMailAlternateAddress,
886 $this->gosaMailMaxSize,
887 $this->gosaSpamMailbox,
888 $this->gosaSpamSortLevel,
889 $this->gosaVacationMessage);
890 $this->is_modified = TRUE;
891 }else{
892 $this->sieve_management->save();
893 }
894 }
895 }
896 }
898 /* Optionally execute a command after we're done */
899 if ($this->initially_was_account == $this->is_account){
900 if ($this->is_modified){
901 $this->handle_post_events("modify", array("uid" => $this->uid));
902 }
903 } else {
904 $this->handle_post_events("add", array("uid" => $this->uid));
905 }
907 $this->updateSharedFolder();
908 }
911 /* Check formular input */
912 function check()
913 {
914 if(!$this->is_account) return(array());
916 $ldap= $this->config->get_ldap_link();
918 /* Call common method to give check the hook */
919 $message= plugin::check();
921 if(empty($this->gosaMailServer)){
922 $message[]= msgPool::noserver(_("Mail"));
923 }
925 /* must: mail */
926 if ($this->mail == ""){
927 $message[]= msgPool::required(_("Primary address"));
928 }
929 if ($this->is_template){
930 if (!tests::is_email($this->mail, TRUE)){
931 $message[]= msgPool::invalid(_("Mail address"),"","","%givenName.%sn@your-domain.com");
932 }
933 } else {
934 if (!tests::is_email($this->mail)){
935 $message[]= msgPool::invalid(_("Mail address"),"","","example@your-domain.com");
936 }
937 }
938 $ldap->cd($this->config->current['BASE']);
939 $ldap->search ("(&(!(objectClass=gosaUserTemplate))(objectClass=gosaMailAccount)(|(mail=".$this->mail.")(gosaMailAlternateAddress=".$this->mail."))(!(uid=".$this->uid."))(!(cn=".$this->uid.")))", array("uid"));
940 if ($ldap->count() != 0){
941 $message[]= msgPool::duplicated(_("Mail address"));
942 }
944 /* Check quota */
945 if ($this->gosaMailQuota != '' && $this->acl_is_writeable("gosaMailQuota")){
946 if (!is_numeric($this->gosaMailQuota)) {
947 $message[]= msgPool::invalid(_("Quota size"),$this->gosaMailQuota,"/^[0-9]*/");
948 } else {
949 $this->gosaMailQuota= (int) $this->gosaMailQuota;
950 }
951 }
953 /* Check rejectsize for integer */
954 if ($this->gosaMailMaxSize != '' && $this->acl_is_writeable("gosaMailMaxSize")){
955 if (!is_numeric($this->gosaMailMaxSize)){
956 $message[]= msgPool::invalid(_("Mail reject size"),$this->gosaMailMaxSize,"/^[0-9]*/");
957 } else {
958 $this->gosaMailMaxSize= (int) $this->gosaMailMaxSize;
959 }
960 }
962 /* Need gosaMailMaxSize if use_mailsize_limit is checked */
963 if (is_integer(strpos($this->gosaMailDeliveryMode, "R")) && $this->gosaMailMaxSize == ""){
964 $message[]= msgPool::required(_("Mail reject size"));
965 }
967 if((preg_match("/S/", $this->gosaMailDeliveryMode))&&(empty($this->gosaSpamMailbox))) {
968 $message[]= msgPool::required(_("Spam folder"));
969 }
971 if (preg_match('/V/', $this->gosaMailDeliveryMode) && $this->gosaVacationStart >= $this->gosaVacationStop){
972 $message[]= msgPool::invalid(_("Vacation interval"));
973 }
975 return ($message);
976 }
979 /* Adapt from template, using 'dn' */
980 function adapt_from_template($dn)
981 {
982 plugin::adapt_from_template($dn);
984 foreach (array("gosaMailAlternateAddress", "gosaMailForwardingAddress") as $val){
985 $this->$val= array();
986 if (isset($this->attrs["$val"]["count"])){
987 for ($i= 0; $i<$this->attrs["$val"]["count"]; $i++){
988 $value= $this->attrs["$val"][$i];
989 foreach (array("sn", "givenName", "uid") as $repl){
990 if (preg_match("/%$repl/i", $value)){
991 $value= preg_replace ("/%$repl/i", $this->parent->$repl, $value);
992 }
993 }
994 array_push($this->$val, strtolower(rewrite($value)));
995 }
996 }
997 }
998 $this->mail= strtolower(rewrite($this->mail));
999 }
1002 /* Add entry to forwarder list */
1003 function addForwarder($address)
1004 {
1005 if($this->acl_is_writeable("gosaMailForwardingAddress")){
1006 $this->gosaMailForwardingAddress[]= $address;
1007 $this->gosaMailForwardingAddress= array_unique ($this->gosaMailForwardingAddress);
1008 sort ($this->gosaMailForwardingAddress);
1009 reset ($this->gosaMailForwardingAddress);
1010 $this->is_modified= TRUE;
1011 }else{
1012 msg_dialog::display(_("Permission error"), _("You have no permission to modify these addresses!"), ERROR_DIALOG);
1013 }
1014 }
1017 /* Remove list of addresses from forwarder list */
1018 function delForwarder($addresses)
1019 {
1020 if($this->acl_is_writeable("gosaMailForwardingAddress")){
1021 $this->gosaMailForwardingAddress= array_remove_entries ($addresses, $this->gosaMailForwardingAddress);
1022 $this->is_modified= TRUE;
1023 }else{
1024 msg_dialog::display(_("Permission error"), _("You have no permission to modify these addresses!"), ERROR_DIALOG);
1025 }
1026 }
1029 /* Add given mail address to the list of alternate adresses ,
1030 check if this mal address is used, skip adding in this case */
1031 function addAlternate($address)
1032 {
1033 if($this->acl_is_writeable("gosaMailAlternateAddress")){
1034 $ldap= $this->config->get_ldap_link();
1035 $address= strtolower($address);
1037 /* Is this address already assigned in LDAP? */
1038 $ldap->cd ($this->config->current['BASE']);
1039 $ldap->search ("(&(objectClass=gosaMailAccount)(|(mail=$address)"."(gosaMailAlternateAddress=$address)))", array("uid"));
1040 $ldap->search ("(&(!(objectClass=gosaUserTemplate))(objectClass=gosaMailAccount)(|(mail=$address)"."(gosaMailAlternateAddress=$address)))", array("uid"));
1042 if ($ldap->count() > 0){
1043 $attrs= $ldap->fetch ();
1044 return ($attrs["uid"][0]);
1045 }
1047 /* Add to list of alternates */
1048 if (!in_array($address, $this->gosaMailAlternateAddress)){
1049 $this->gosaMailAlternateAddress[]= $address;
1050 $this->is_modified= TRUE;
1051 }
1053 sort ($this->gosaMailAlternateAddress);
1054 reset ($this->gosaMailAlternateAddress);
1055 return ("");
1056 }else{
1057 msg_dialog::display(_("Permission error"), _("You have no permission to modify these addresses!"), ERROR_DIALOG);
1058 }
1059 }
1062 function delAlternate($addresses)
1063 {
1064 if($this->acl_is_writeable("gosaMailAlternateAddress")){
1065 $this->gosaMailAlternateAddress= array_remove_entries ($addresses,$this->gosaMailAlternateAddress);
1066 $this->is_modified= TRUE;
1067 }else{
1068 msg_dialog::display(_("Permission error"), _("You have no permission to modify these addresses!"), ERROR_DIALOG);
1069 }
1070 }
1072 function make_name($attrs)
1073 {
1074 $name= "";
1075 if (isset($attrs['sn'][0])){
1076 $name= $attrs['sn'][0];
1077 }
1078 if (isset($attrs['givenName'][0])){
1079 if ($name != ""){
1080 $name.= ", ".$attrs['givenName'][0];
1081 } else {
1082 $name.= $attrs['givenName'][0];
1083 }
1084 }
1085 if ($name != ""){
1086 $name.= " ";
1087 }
1089 return ($name);
1090 }
1093 /* Create the mail part for the copy & paste dialog */
1094 function getCopyDialog()
1095 {
1096 if(!$this->is_account) return("");
1097 $smarty = get_smarty();
1098 $smarty->assign("mail",$this->mail);
1099 $smarty->assign("gosaMailAlternateAddress",$this->gosaMailAlternateAddress);
1100 $smarty->assign("gosaMailForwardingAddress",$this->gosaMailForwardingAddress);
1101 $str = $smarty->fetch(get_template_path("copypaste.tpl",TRUE, dirname(__FILE__)));
1103 $ret = array();
1104 $ret['status'] = "";
1105 $ret['string'] = $str;
1106 return($ret);
1107 }
1109 function saveCopyDialog()
1110 {
1111 if(!$this->is_account) return;
1113 /* Execute to save mailAlternateAddress && gosaMailForwardingAddress */
1114 $this->execute();
1116 if(isset($_POST['mail'])){
1117 $this->mail = $_POST['mail'];
1118 }
1120 }
1122 function allow_remove()
1123 {
1124 if (isset($this->config->current['MAILMETHOD'])){
1125 $method= $this->config->current['MAILMETHOD'];
1126 if(preg_match("/olab/i",$method)){
1127 $ldap = $this->config->get_ldap_link();
1128 $ldap->cd($this->config->current['BASE']);
1129 $ldap->cat($this->dn);
1130 if($ldap->count()){
1131 $attrs = $ldap->fetch();
1132 if(isset($attrs['kolabDeleteFlag'])){
1133 return(_("Waiting for kolab to remove mail properties..."));
1134 }elseif(in_array("gosaMailAccount",$attrs['objectClass'])){
1135 return(_("Please remove the mail settings first to allow kolab to call its remove methods!"));
1136 }
1137 }
1138 }
1139 }
1140 }
1143 function PrepareForCopyPaste($source)
1144 {
1145 plugin::PrepareForCopyPaste($source);
1147 /* Reset alternate mail addresses */
1148 $this->gosaMailAlternateAddress = array();
1149 }
1152 static function plInfo()
1153 {
1154 return (array(
1155 "plShortName" => _("Mail"),
1156 "plDescription" => _("Mail settings"),
1157 "plSelfModify" => TRUE,
1158 "plDepends" => array("user"), // This plugin depends on
1159 "plPriority" => 4, // Position in tabs
1160 "plSection" => array("personal" => _("My account")),
1161 "plCategory" => array("users"),
1162 "plOptions" => array(),
1164 "plProvidedAcls" => array(
1165 "mail" => _("Mail address"),
1166 "gosaMailServer" => _("Mail server"),
1167 "gosaMailQuota" => _("Quota size"),
1169 "gosaMailDeliveryModeV" => _("Add vacation information"), // This is flag of gosaMailDeliveryMode
1170 "gosaVacationMessage" => _("Vacation message"),
1172 "gosaMailDeliveryModeS" => _("Use spam filter"), // This is flag of gosaMailDeliveryMode
1173 "gosaSpamSortLevel" => _("Spam level"),
1174 "gosaSpamMailbox" => _("Spam mail box"),
1176 "sieveManagement" => _("Sieve management"),
1178 "gosaMailDeliveryModeR" => _("Reject due to mailsize"), // This is flag of gosaMailDeliveryMode
1179 "gosaMailMaxSize" => _("Mail max size"),
1181 "gosaMailForwardingAddress" => _("Forwarding address"),
1182 "gosaMailDeliveryModeL" => _("Local delivery"), // This is flag of gosaMailDeliveryMode
1183 "gosaMailDeliveryModeI" => _("No delivery to own mailbox "), // This is flag of gosaMailDeliveryMode
1184 "gosaMailAlternateAddress" => _("Mail alternative addresses"),
1186 "gosaMailForwardingAddress" => _("Forwarding address"),
1187 "gosaMailDeliveryModeC" => _("Use custom sieve script")) // This is flag of gosaMailDeliveryMode
1188 ));
1189 }
1192 /*! \brief Prepare importet vacation string. \
1193 Replace placeholder like %givenName a.s.o.
1194 @param string Vacation string
1195 @return string Completed vacation string
1196 */
1197 private function prepare_vacation_template($contents)
1198 {
1199 /* Replace attributes */
1200 $attrs = array();
1201 $obj = NULL;
1202 if(isset($this->parent->by_object['user'])){
1203 $attrs = $this->parent->by_object['user']->attributes;
1204 $obj = $this->parent->by_object['user'];
1205 }else{
1206 $obj = new user($this->config,$this->dn);
1207 $attrs = $obj->attributes;
1208 }
1210 if($obj){
1211 foreach ($attrs as $val){
1212 if(preg_match("/dateOfBirth/",$val)){
1213 if($obj->use_dob){
1214 $contents= preg_replace("/%$val/",date("Y-d-m",$obj->dateOfBirth),$contents);
1215 }
1216 }else {
1217 $contents= preg_replace("/%$val/",
1218 $obj->$val, $contents);
1219 }
1221 /* Replace vacation start and end time */
1222 if(preg_match("/%start/",$contents)){
1223 $contents = preg_replace("/%start/",date("d.m.Y",$this->gosaVacationStart),$contents);
1224 }
1225 if(preg_match("/%end/",$contents)){
1226 $contents = preg_replace("/%end/",date("d.m.Y",$this->gosaVacationStop),$contents);
1227 }
1228 }
1229 }
1230 return($contents);
1231 }
1235 /* Upated shared folder ACLs
1236 */
1237 function updateSharedFolder()
1238 {
1239 $ldap = $this->config->get_ldap_link();
1240 $ldap->cd($this->config->current['BASE']);
1241 $ldap->search("(&(objectClass=posixGroup)(objectClass=gosaMailAccount)(memberUid=".$this->uid."))",array('dn','cn'));
1242 if(class_exists("grouptabs")){
1243 while($attrs = $ldap->fetch()){
1244 $tmp = new grouptabs($this->config, $this->config->data['TABS']['GROUPTABS'], $attrs['dn']);
1245 if(isset($tmp->by_object['mailgroup'])){
1246 $tmp->by_object['mailgroup']->members= $tmp->by_object['group']->memberUid;
1247 if(!$this->is_account){
1248 $tmp->by_object['mailgroup']->removeUserAcl($this->uid);
1249 $tmp->by_object['mailgroup']->removeUserAcl($this->mail);
1250 }
1251 $tmp->by_object['mailgroup']->save();
1252 }
1253 }
1254 }
1255 }
1257 /* Initialize plugin with given atribute arrays
1258 */
1259 function init_multiple_support($attrs,$all)
1260 {
1261 plugin::init_multiple_support($attrs,$all);
1263 if(isset($this->multi_attrs['gosaMailQuota'])){
1264 $this->gosaMailQuota = $this->multi_attrs['gosaMailQuota'];
1265 }
1266 }
1268 function get_multi_init_values()
1269 {
1270 $attrs = plugin::get_multi_init_values();
1271 $attrs['gosaMailQuota'] = $this->gosaMailQuota;
1272 return($attrs);
1273 }
1275 function multiple_execute()
1276 {
1277 return($this->execute());
1278 }
1280 function multiple_save_object()
1281 {
1282 plugin::multiple_save_object();
1284 $this->save_object();
1285 foreach(array("only_local","gosaMailForwardingAddress","use_mailsize_limit","drop_own_mails","use_vacation","use_spam_filter") as $attr){
1286 if(isset($_POST["use_".$attr])){
1287 $this->multi_boxes[] = $attr;
1288 }
1289 }
1290 }
1292 function get_multi_edit_values()
1293 {
1294 $ret = plugin::get_multi_edit_values();
1296 if(in_array("gosaMailQuota",$this->multi_boxes)){
1297 $ret['gosaMailQuota'] = $this->gosaMailQuota;
1298 }
1300 $flag_add = $flag_remove = array();
1301 $tmp= preg_replace("/[^a-z]/i","",$this->gosaMailDeliveryMode);
1302 $opts = array(
1303 "R" => "use_mailsize_limit",
1304 "S" => "use_spam_filter",
1305 "L" => "only_local",
1306 "V" => "use_vacation",
1307 "C" => "own_script",
1308 "I" => "drop_own_mails");
1310 foreach($opts as $flag => $post){
1311 if(in_array($post, $this->multi_boxes)){
1312 if(preg_match("/".$flag."/",$tmp)){
1313 $flag_add[] = $flag;
1314 }else{
1315 $flag_remove[] = $flag;
1316 }
1317 }
1318 }
1320 $ret['flag_add'] = $flag_add;
1321 $ret['flag_remove'] = $flag_remove;
1322 return($ret);
1323 }
1326 function multiple_check()
1327 {
1328 $message = plugin::multiple_check();
1330 if(empty($this->gosaMailServer) && in_array("gosaMailServer",$this->multi_boxes)){
1331 $message[]= msgPool::noserver(_("Mail"));
1332 }
1334 /* Check quota */
1335 if ($this->gosaMailQuota != '' && in_array("gosaMailQuota",$this->multi_boxes)){
1336 if (!is_numeric($this->gosaMailQuota)) {
1337 $message[]= msgPool::invalid(_("Quota size"),$this->gosaMailQuota,"/^[0-9]*/");
1338 } else {
1339 $this->gosaMailQuota= (int) $this->gosaMailQuota;
1340 }
1341 }
1343 /* Check rejectsize for integer */
1344 if ($this->gosaMailMaxSize != '' && in_array("gosaMailMaxSize",$this->multi_boxes)){
1345 if (!is_numeric($this->gosaMailMaxSize)){
1346 $message[]= msgPool::invalid(_("Mail reject size"),$this->gosaMailMaxSize,"/^[0-9]*/");
1347 } else {
1348 $this->gosaMailMaxSize= (int) $this->gosaMailMaxSize;
1349 }
1350 }
1352 if(empty($this->gosaSpamMailbox) && in_array("gosaSpamMailbox",$this->multi_boxes)){
1353 $message[]= msgPool::required(_("Spam folder"));
1354 }
1356 if ( in_array("use_vacation",$this->multi_boxes) &&
1357 preg_match('/V/', $this->gosaMailDeliveryMode) && $this->gosaVacationStart > $this->gosaVacationStop){
1358 $message[]= msgPool::invalid(_("Vacation interval"));
1359 }
1360 return($message);
1361 }
1364 function set_multi_edit_values($values)
1365 {
1366 plugin::set_multi_edit_values($values);
1367 $tmp= preg_replace("/[^a-z]/i","",$this->gosaMailDeliveryMode);
1368 if(isset($values['flag_add'])){
1369 foreach($values['flag_add'] as $flag){
1370 if(!preg_match("/".$flag."/",$tmp)){
1371 $tmp .= $flag;
1372 }
1373 }
1374 }
1375 if(isset($values['flag_remove'])){
1376 foreach($values['flag_remove'] as $flag){
1377 if(preg_match("/".$flag."/",$tmp)){
1378 $tmp = preg_replace("/".$flag."/","",$tmp);
1379 }
1380 }
1381 }
1382 $this->gosaMailDeliveryMode = "[".$tmp."]";
1384 /* Set vacation message and replace placeholder like %givenName
1385 */
1386 if(isset($values['gosaVacationMessage'])){
1387 $this->gosaVacationMessage = $this->prepare_vacation_template($values['gosaVacationMessage']);
1388 }
1389 }
1390 }
1392 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
1393 ?>