1 <?php
3 class mailgroup extends plugin
4 {
5 var $uid = ""; // User id
6 var $cn = ""; // cn
7 var $orig_cn = ""; // cn
9 var $method = "mailMethod"; // Used Mail method
10 var $mmethod = ""; // Contains the gosa.conf MAILMETHOD
11 var $mail = ""; // Default mail address
13 var $gosaMailAlternateAddress = array(); // Set default Alternate Mail Adresses to empty array
14 var $gosaMailForwardingAddress = array(); // Forwarding also empty
16 var $gosaMailForwardingAddress_Some = array(); // Used in multiple edit
18 var $gosaMailServer = ""; // Selected mailserver
19 var $gosaMailQuota = ""; // Defined Quota
20 var $quotaUsage = 0; // Currently used quota
22 var $gosaVacationMessage = ""; // Vocation message
24 var $imapacl = array('anyone' => 'p', // Set acls for everyone
25 '%members%' => 'lrswp', // %members% are all group-members
26 '' => 'p'); // Every user added gets this right
28 var $kolabFolderTypeSubType = "";
29 var $kolabFolderTypeType = "";
31 var $gosaSpamSortLevel = "";
32 var $gosaSpamMailbox = "";
33 var $gosaSharedFolderTarget ;
35 var $forward_dialog = FALSE;
37 var $members = array(); // Group members
39 var $mailusers = array();
40 var $perms = array();
41 var $gosaMailDeliveryMode = "[L ]"; //
42 var $gosaMailMaxSize = ""; //
44 var $remove_folder_from_imap = true;
46 /* Helper */
47 var $indexed_acl= array();
48 var $indexed_user= array();
50 var $view_logged = FALSE;
52 /* attribute list for save action */
53 var $attributes= array( "mail", "gosaMailServer", "gosaMailQuota", "gosaMailMaxSize",
54 "gosaMailAlternateAddress", "gosaMailForwardingAddress",
55 "gosaMailDeliveryMode", "gosaSpamSortLevel", "gosaSpamMailbox",
56 "acl","gosaSharedFolderTarget", "gosaVacationMessage");
58 var $objectclasses= array("gosaMailAccount");
59 var $CopyPasteVars = array("quotaUsage","imapacl");
61 var $multiple_support = TRUE;
65 var $folder_prefix = "";
66 var $user_prefix = "";
68 var $account_name = "";
70 function mailgroup (&$config, $dn= NULL, $ui= NULL)
71 {
72 /* Initialise all available attributes ... if possible
73 */
74 plugin::plugin($config, $dn);
75 $this->orig_cn = $this->cn;
77 /* Set mailMethod to the one defined in gosa.conf
78 */
79 if ($this->config->get_cfg_value("mailmethod")){
80 $this->mmethod= $this->config->get_cfg_value("mailmethod");
81 }
83 /* Check if selected mail method exists
84 */
85 $cls = get_correct_class_name("mailMethod$this->mmethod");
86 if ($cls && class_exists($cls)){
87 $this->method= $cls;
88 } else {
89 msg_dialog::display(_("Configuration error"), sprintf(_("Cannot locate mail method '%s'!"), $this->mmethod), ERROR_DIALOG);
90 }
92 /* Load Mailserver
93 */
94 if(isset($this->attrs['gosaMailServer'][0])){
95 $this->gosaMailServer = $this->attrs['gosaMailServer'][0];
96 }
98 /* Get folder type */
99 if(preg_match("/olab/i",$this->config->get_cfg_value("mailmethod"))){
100 if(isset($this->attrs['kolabFolderType'])){
101 $tmp = split("\.",$this->attrs['kolabFolderType'][0]);
102 $this->kolabFolderTypeType = $tmp[0];
103 $this->kolabFolderTypeSubType = $tmp[1];
104 }
105 }
107 /* Create new instance of our defined mailclass
108 */
109 $method= new $this->method($this->config);
111 $this->folder_prefix = $method->folder_prefix;
112 $this->user_prefix = $method->user_prefix;
114 /* Build account name
115 */
116 $uattrib = $method->uattrib;
117 if (isset($this->attrs[$uattrib][0])){
118 $this->account_name= $this->attrs[$uattrib][0];
119 if(preg_match("/@/",$this->account_name)){
120 $cn = "";
121 if(isset($this->attrs['cn'][0])){
122 $cn = $this->attrs['cn'][0];
123 }
124 $this->account_name = preg_replace("/^[^@]*/",$cn,$this->account_name);
125 }
126 }
128 /* If this isn't a new mailgroup, read all required data from ldap
129 */
130 if (($dn != "new")&&($dn !== NULL)){
132 /* Load attributes which represent multiple entries
133 */
134 foreach (array("gosaMailAlternateAddress", "gosaMailForwardingAddress") as $val){
135 $this->$val = array();
136 if (isset($this->attrs["$val"]["count"])){
137 for ($i= 0; $i<$this->attrs["$val"]["count"]; $i++){
138 array_push($this->$val, $this->attrs["$val"][$i]);
139 }
140 }
141 }
143 /* Only do IMAP actions if gosaMailServer attribute is set
144 */
145 if (isset ($this->attrs["gosaMailServer"][0])){
147 if ($method->connect($this->attrs["gosaMailServer"][0])){
149 /* If we do NOT use kolab or equal methods,
150 read imap the acls from the mail method class.
151 They will be merged later with the ldap specified acls.
152 */
153 if(!preg_match("/olab/i",$this->mmethod) && !empty($this->mmethod)){
154 $this->imapacl= $method->getSharedFolderPermissions($this->folder_prefix.$this->account_name);
156 /* Need to filter what a member acl could be... */
157 $vote= array();
158 $peak= 0;
159 $leader= "";
160 foreach ($this->imapacl as $user => $acl){
162 if ($user != "anyone" ){
163 if (!isset($vote[$acl])){
164 $vote[$acl]= 1;
165 } else {
166 $vote[$acl]++;
167 }
168 if ($vote[$acl] > $peak){
169 $leader= $acl;
170 $peek= $vote[$acl];
171 }
172 }
173 }
174 /* Highest count wins as %members%, remove all members
175 with the same acl */
176 if(!empty($leader)){
177 $this->imapacl['%members%']= $leader;
178 }
179 foreach ($this->imapacl as $user => $acl){
180 if ($this->acl == $leader && in_array($user, $this->attrs['memberUid'])){
181 unset($this->imapacl[$user]);
182 }
183 }
184 }
186 /* Adapt attributes if needed */
187 $method->fixAttributesOnLoad($this);
189 /* get Quota */
190 $quota= $method->getQuota($this->folder_prefix.$this->account_name);
192 /* Update quota values */
193 if(is_array($quota)){
194 if ($quota['gosaMailQuota'] == 2147483647){
195 $this->quotaUsage= "";
196 $this->gosaMailQuota= "";
197 } else {
198 $this->quotaUsage= $quota['quotaUsage'];
199 $this->gosaMailQuota= $quota['gosaMailQuota'];
200 }
201 }else{
202 $this->quotaUsage = "";
203 $this->gosaMailQuota = "";
204 }
205 $method->disconnect();
206 } // ENDE $method->connect($this->attrs["gosaMailServer"][0])){
207 } // ENDE gosaMailServer
208 } // ENDE dn != "new"
211 /* Get global filter config */
212 if (!session::is_set("gmailfilter")){
213 $ui= get_userinfo();
214 $base= get_base_from_people($ui->dn);
215 $gmailfilter= array( "depselect" => $base,
216 "muser" => "",
217 "regex" => "*");
218 session::set("gmailfilter", $gmailfilter);
219 }
221 /* Load permissions */
222 $tmp = array();
223 $ldap = $this->config->get_ldap_link();
225 /* Read acls from ldap and merge them with
226 * acls read from mail method.
227 */
228 if (isset($this->attrs['acl'])){
230 for ($i= 0; $i<$this->attrs['acl']['count']; $i++){
231 list($user, $permission)= split(' ', $this->attrs['acl'][$i]);
233 /* Add to list */
234 $this->imapacl[$user]= $permission;
236 /* Get all user permissions sorted by acl, to detect the most used acl
237 This acl is then used for %members%
238 */
239 if ($user != "anyone" && $user != "%members%"){
240 $tmp[$permission][] = $user;
241 }
243 /* There is an entry in $this->imapacl like this this ...
244 $this->attrs['imapacl']['anyone'] = "p";
245 $this->attrs['imapacl']['%members%'] = "lprs";
246 $this->attrs['imapacl'][''] = ""; <------ This is used to diplay an empty
247 Field for special acls in our template.
248 If there is at least one special acl in out imapacl,
249 we don't need this entry anymore, because it is already displayed.
250 */
251 if ($user != "anyone" && $user != "%members%"){
252 unset($this->imapacl['']);
253 }
254 }
255 }
257 /**
258 * Detect group members which use the same acl
259 * as used for %members% and remove them.
260 **/
262 /* In this section we detect which acl is the most used.
263 This will be used as %members% acl.
264 */
265 $tmp2 = array();
266 foreach($tmp as $acl => $user){
267 $tmp2[count($tmp[$acl])]=$acl;
268 }
269 /* Most used at last
270 */
271 ksort($tmp2);
273 /* Assign last (most used acl) to %members% acl
274 */
275 $str = array_pop($tmp2);
276 if(!empty($str)) {
277 $this->imapacl['%members%']=$str;
278 }
279 if(!isset($this->imapacl['%members%'])){
280 $this->imapacl['%members%'] = "lrspw";
281 }
284 /* Open ldap connection
285 */
286 $ldap = $this->config->get_ldap_link();
287 $ldap->cd($this->config->current['BASE']);
289 /* Remove those users, that use %members% acl && are member of this group. */
290 foreach($this->imapacl as $mail => $permission){
291 $ldap->search("(&(objectClass=person)(|(mail=".$mail.")(uid=".$mail.")))",array("uid"));
292 $atr = $ldap->fetch();
293 if((isset($this->attrs['memberUid'])) && (is_array($this->attrs['memberUid']))){
294 if((isset($atr['uid'][0]))&&(in_array($atr['uid'][0],$this->attrs['memberUid']))&&($permission == $this->imapacl['%members%'])){
295 unset($this->imapacl[$mail]);
296 }
297 }
298 }
300 /**
301 * ENDE: Detect group member with same acl and replace them with %members%
302 **/
303 $this->indexed_user = array("%members%","anyone");
305 /* Append an empty entry, for special acl handling */
306 if(count($this->imapacl)==2){
307 $this->imapacl[''] ="lrsw";
308 $this->indexed_user[] = '';
309 }
311 /* Load Mailserver
312 */
313 if(isset($this->attrs['gosaMailServer'][0])){
314 $this->gosaMailServer = $this->attrs['gosaMailServer'][0];
315 }
316 /* Fill translations */
317 $this->perms["lrsw"]= _("read");
318 $this->perms["lrswp"]= _("post");
319 $this->perms["p"]= _("external post");
320 $this->perms["lrswip"]= _("append");
321 $this->perms["lrswipcd"]= _("write");
322 $this->perms["lrswipcda"]= _("admin");
323 $this->perms[""]= _("none");
324 }
328 function execute()
329 {
330 /* Call parent execute */
331 //plugin::execute();
332 $display = "";
334 /* Log view */
335 if($this->is_account && !$this->view_logged){
336 $this->view_logged = TRUE;
337 new log("view","groups/".get_class($this),$this->dn);
338 }
340 /* Load templating engine */
341 $smarty= get_smarty();
343 /* Assign acls */
344 $tmp = $this->plInfo();
345 foreach($tmp['plProvidedAcls'] as $name => $translation) {
346 $smarty->assign($name."ACL",$this->getacl($name));
347 }
349 if (session::get('js')==FALSE){
350 $smarty->assign("javascript", "false");
351 } else {
352 $smarty->assign("javascript", "true");
353 }
355 /* Handle actions should not be done, when
356 * editing multiple entries at once. e.g. account state
357 */
358 if(!$this->multiple_support_active){
360 /* Do we need to flip is_account state? */
361 if(isset($_POST['modify_state'])){
362 if($this->is_account && $this->acl_is_removeable()){
363 $this->is_account= FALSE;
364 }elseif(!$this->is_account && $this->acl_is_createable()){
365 $this->is_account= TRUE;
366 }
367 }
369 $display = "";
371 /* Do we represent a valid account? */
372 if (!$this->is_account && $this->parent === NULL){
374 $display.= "<img alt=\"\" src=\"images/small-error.png\" align=middle> <b>".msgPool::noValidExtension(_("mail"))."</b>";
375 return ($display);
376 }
378 /* Show tab dialog headers */
379 $display= "";
380 if ($this->parent !== NULL){
381 if ($this->is_account){
382 $display.= $this->show_disable_header(_("Remove mail account"),
383 msgPool::featuresEnabled(_("mail")));
384 } else {
385 $display.= $this->show_enable_header(_("Create mail account"),
386 msgPool::featuresDisabled(_("mail")));
388 /* Show checkbox that allows us to remove imap entry too*/
389 if($this->initially_was_account){
390 $c = "";
391 if($this->remove_folder_from_imap){
392 $c= " checked ";
393 }
394 $display .= "<h2>Shared folder delete options</h2>
395 <input class='center' type='checkbox' name='remove_folder_from_imap' value='1' ".$c."
396 title='"._("Remove shared folder from mail server database when entry gets removed in LDAP")."'>";
397 $display .= _("Remove the shared folder and all its contents after saving this account");
398 }
399 return ($display);
400 }
401 }
402 }
404 /* Add ACL? */
405 if($this->acl_is_writeable("acl")){
406 foreach ($this->indexed_user as $nr => $user){
407 if (isset($_POST["add_$nr"])){
408 $this->imapacl[""]= "l";
409 }
410 if (isset($_POST["del_$nr"])){
411 unset ($this->imapacl[$user]);
412 }
413 }
414 }
416 /* Trigger forward add dialog? */
417 if($this->acl_is_writeable("gosaMailForwardingAddress")){
418 if (isset($_POST['add_local_forwarder'])){
419 $this->forward_dialog= TRUE;
420 $this->dialog= TRUE;
421 }
422 }
424 /* Cancel forward add dialog? */
425 if($this->acl_is_writeable("gosaMailForwardingAddress")){
426 if (isset($_POST['add_locals_cancel'])){
427 $this->forward_dialog= FALSE;
428 $this->dialog= FALSE;
429 }
430 }
432 /* Finished adding of locals? */
433 if ((isset($_POST['add_locals_finish'])) && ($this->acl_is_writeable("gosaMailForwardingAddress"))) {
434 if (count ($_POST['local_list']) && $this->acl_is_writeable("gosaMailForwardingAddress")){
436 /* Walk through list of forwarders, ignore own addresses */
437 foreach ($_POST['local_list'] as $val){
438 if (!in_array ($val, $this->gosaMailAlternateAddress) &&
439 $val != $this->mail){
441 $this->addForwarder($val);
442 }
443 }
444 }
445 $this->forward_dialog= FALSE;
446 $this->dialog= FALSE;
447 }
449 /* Add forward email addresses */
450 if ((isset($_POST['add_forwarder'])) && ($this->acl_is_writeable("gosaMailForwardingAddress"))){
451 if ($_POST['forward_address'] != ""){
453 /* Valid email address specified? */
454 $address= $_POST['forward_address'];
455 if (!tests::is_email($address)){
456 msg_dialog::display(_("Error"), msgPool::invalid(_("forward address")), ERROR_DIALOG);
457 } elseif ($address == $this->mail
458 || in_array($address, $this->gosaMailAlternateAddress)) {
460 msg_dialog::display(_("Error"), _("Cannot forward to users own mail address!"), ERROR_DIALOG);
462 } else {
464 /* Add it */
465 if ($this->acl_is_writeable("gosaMailForwardingAddress")){
466 $this->addForwarder ($address);
467 }
469 }
470 }
471 }
473 /* Delete forward email addresses */
474 if (isset($_POST['delete_forwarder']) && ($this->acl_is_writeable("gosaMailForwardingAddress"))){
475 if (count($_POST['forwarder_list'])&& $this->acl_is_writeable("gosaMailForwardingAddress")){
477 $this->delForwarder ($_POST['forwarder_list']);
478 }
479 }
481 /* Add alternate email addresses */
482 if (isset($_POST['add_alternate'])){
483 if ($_POST['alternate_address'] != "" && $this->acl_is_writeable("gosaMailAlternateAddress")){
485 if (!tests::is_email($_POST['alternate_address'])){
486 msg_dialog::display(_("Error"),msgPool::invalid(_("Alternate address")), ERROR_DIALOG);
488 } elseif (($user= $this->addAlternate ($_POST['alternate_address'])) != ""){
489 $ui= get_userinfo();
490 if ($user != $ui->username){
491 msg_dialog::display(_("Error"),msgPool::duplicated(_("Mail address")), ERROR_DIALOG);
492 }
493 }
494 }
495 }
497 /* Delete alternate email addresses */
498 if($this->acl_is_writeable("gosaMailAlternateAddress")){
499 if (isset($_POST['delete_alternate']) && isset ($_POST['alternates_list'])){
500 if (count($_POST['alternates_list']) && $this->acl_is_writeable("gosaMailAlternateAddress")){
501 $this->delAlternate ($_POST['alternates_list']);
502 }
503 }
504 }
506 /* Show forward add dialog */
507 if ($this->forward_dialog){
508 $ldap= $this->config->get_ldap_link();
510 /* Save data */
511 $gmailfilter= session::get("gmailfilter");
512 foreach( array("depselect", "muser", "regex") as $type){
513 if (isset($_POST[$type])){
514 $gmailfilter[$type]= $_POST[$type];
515 }
516 }
517 if (isset($_GET['search'])){
518 $s= mb_substr($_GET['search'], 0, 1, "UTF8")."*";
519 if ($s == "**"){
520 $s= "*";
521 }
522 $gmailfilter['regex']= $s;
523 }
524 session::set("gmailfilter", $gmailfilter);
526 /* Get actual list */
527 $mailusers= array ();
528 if ($gmailfilter['regex'] != '*' && $gmailfilter['regex'] != ""){
529 $regex= $gmailfilter['regex'];
530 $filter= "(|(mail=$regex)(gosaMailAlternateAddress=$regex))";
531 } else {
532 $filter= "";
533 }
534 if ($gmailfilter['muser'] != ""){
535 $user= $gmailfilter['muser'];
536 $filter= "$filter(|(uid=$user)(cn=$user)(givenName=$user)(sn=$user))";
537 }
539 /* Add already present people to the filter */
540 $exclude= "";
541 foreach ($this->gosaMailForwardingAddress as $mail){
542 $exclude.= "(mail=$mail)";
543 }
544 if ($exclude != ""){
545 $filter.= "(!(|$exclude))";
546 }
548 $res= get_list("(&(objectClass=gosaMailAccount)$filter)", "users", $gmailfilter['depselect'],
549 array("sn", "mail", "givenName"), GL_SUBSEARCH | GL_SIZELIMIT);
550 $ldap->cd($gmailfilter['depselect']);
551 $ldap->search ("(&(objectClass=gosaMailAccount)$filter)", array("sn", "mail", "givenName"));
552 error_reporting (0);
553 while ($attrs= $ldap->fetch()){
554 if(preg_match('/%/', $attrs['mail'][0])){
555 continue;
556 }
557 $name= $this->make_name($attrs);
558 $mailusers[$attrs['mail'][0]]= $name."<".
559 $attrs['mail'][0].">";
560 }
561 error_reporting (E_ALL | E_STRICT);
562 natcasesort ($mailusers);
563 reset ($mailusers);
565 /* Show dialog */
566 $smarty->assign("search_image", get_template_path('images/lists/search.png'));
567 $smarty->assign("usearch_image", get_template_path('images/lists/search-user.png'));
568 $smarty->assign("tree_image", get_template_path('images/lists/search-subtree.png'));
569 $smarty->assign("infoimage", get_template_path('images/info.png'));
570 $smarty->assign("launchimage", get_template_path('images/lists/action.png'));
571 $smarty->assign("mailusers", $mailusers);
572 $smarty->assign("deplist", $this->config->idepartments);
573 $smarty->assign("apply", apply_filter());
574 $smarty->assign("alphabet", generate_alphabet());
575 $smarty->assign("hint", print_sizelimit_warning());
576 foreach( array("depselect", "muser", "regex") as $type){
577 $smarty->assign("$type", $gmailfilter[$type]);
578 }
579 $smarty->assign("hint", print_sizelimit_warning());
580 $display.= $smarty->fetch (get_template_path('mail_locals.tpl', TRUE, dirname(__FILE__)));
581 return ($display);
582 }
584 /* Assemble normal permissions */
585 if (isset($this->imapacl['anyone'])){
586 $smarty->assign("default_permissions", $this->imapacl['anyone']);
587 }
588 $smarty->assign("member_permissions", "lrsp");
589 if (isset($this->imapacl['%members%'])){
590 $smarty->assign("member_permissions", $this->imapacl['%members%']);
591 }
593 /* Assemble extra attributes */
594 $perm= $this->getacl( "acl");
595 $tmp= "";
596 $nr= 0;
597 $count= count($this->imapacl);
598 $this->indexed_user= array();
599 $this->indexed_acl= array();
600 foreach($this->imapacl as $user => $acl){
602 /* Add additional acl settings */
603 if ($user != "anyone" && $user != "%members%"){
605 $Dis = "";
606 if(!preg_match("/w/",$perm)){
607 $Dis = " disabled ";
608 }
610 /* Reset given Acls to ensure that nobody can read username and acls if not allwoed */
611 if(!preg_match("/r/",$perm)){
612 $user = "";
613 $nr = "none";
614 $key = "none";
615 }
617 $tmp.= "<tr>
618 <td>
619 <input name=\"user_$nr\" size=20 maxlength=60 value=\"$user\" ".$Dis.">
620 </td>
621 <td>
622 <select size=\"1\" name=\"perm_$nr\" ".$Dis.">";
624 /* Add acl options for this additional acl setting */
625 if(preg_match("/r/",$perm)){
626 foreach ($this->perms as $key => $value){
627 if ($acl == $key){
628 $tmp.= "<option value=\"$key\" selected>$value</option>";
629 } else {
630 $tmp.= "<option value=\"$key\">$value</option>";
631 }
632 }
633 }
634 $tmp.= "</select> ";
638 if ($nr == $count - 1){
639 if($this->acl_is_writeable("acl")){
640 $tmp.= "<input type=submit value=\""._("Add")."\" ".
641 "name=\"add_$nr\" >";
642 }
643 }
644 if ($count > 3){
645 if($this->acl_is_writeable("acl")){
646 $tmp.= "<input type=submit value=\""._("Remove")."\" ".
647 "name=\"del_$nr\" ></td></tr>";
648 }
649 }
650 }
651 $this->indexed_user[$nr]= $user;
652 $this->indexed_acl[$nr++]= $acl;
653 }
654 $smarty->assign("plusattributes", $tmp);
656 /* Show main page */
657 $mailserver= array();
658 $ui = get_userinfo();
659 foreach ($this->config->data['SERVERS']['IMAP'] as $key => $val){
660 if(!preg_match("/r/",$ui->get_category_permissions($val['server_dn'],"server"))) continue;
661 $mailserver[]= $key;
662 }
664 /* Append currently selected server if we are not allowed to view it.
665 */
666 if(isset($this->config->data['SERVERS']['IMAP'][$this->gosaMailServer]) &&
667 !in_array($this->gosaMailServer,$mailserver)){
668 $mailserver[] = $this->gosaMailServer;
669 }
671 $smarty->assign("mailServers", $mailserver);
672 foreach(array("gosaMailServer", "gosaMailQuota", "perms", "mail",
673 "gosaMailAlternateAddress", "gosaMailForwardingAddress") as $val){
674 $smarty->assign("$val", $this->$val);
675 }
676 if (is_numeric($this->gosaMailQuota) && $this->gosaMailQuota != 0){
677 if($this->acl_is_readable("gosaMailQuota")){
678 $smarty->assign("quotausage", progressbar(round(($this->quotaUsage * 100)/ $this->gosaMailQuota),100,15,true));
679 $smarty->assign("quotadefined", "true");
680 }else{
681 $smarty->assign("quotadefined", "true");
682 $smarty->assign("quotausage", "-");
683 }
684 } else {
685 $smarty->assign("quotadefined", "false");
686 }
688 if(preg_match("/olab/i",$this->config->get_cfg_value("mailmethod"))){
690 $smarty->assign("kolab", TRUE);
691 $smarty->assign("JS",session::get('js'));
692 $smarty->assign("kolabFolderTypeTypes", array ( '' => _('Unspecified'), 'mail' => _('Mails'),
693 'task' => _('Tasks') , 'journal' => _('Journals'),
694 'calendar' => _('Calendar'), 'contact' => _('Contacts'),
695 'note' => _('Notes')));
696 if($this->kolabFolderTypeType == "mail"){
697 $smarty->assign("kolabFolderTypeSubTypes", array(
698 '' => _('Unspecified'), 'inbox' => _("Inbox") ,
699 'drafts' => _("Drafts"), 'sentitems' => _("Sent items"),
700 'junkemail' => _("Junk mail")));
701 }else{
702 $smarty->assign("kolabFolderTypeSubTypes", array( 'default' => _("Default")));
703 }
704 $smarty->assign("kolabFolderTypeType", $this->kolabFolderTypeType);
705 $smarty->assign("kolabFolderTypeSubType", $this->kolabFolderTypeSubType);
706 }else{
707 $smarty->assign("kolab", FALSE);
708 }
710 /* Multiple support handling */
711 foreach($this->attributes as $attr){
712 if(in_array($attr,$this->multi_boxes)){
713 $smarty->assign("use_".$attr,TRUE);
714 }else{
715 $smarty->assign("use_".$attr,FALSE);
716 }
717 }
719 /* Multiple support handling */
720 foreach(array("kolabFolderType") as $attr){
721 if(in_array($attr,$this->multi_boxes)){
722 $smarty->assign("use_".$attr,TRUE);
723 }else{
724 $smarty->assign("use_".$attr,FALSE);
725 }
726 }
728 $smarty->assign("Forward_all",$this->gosaMailForwardingAddress);
729 $smarty->assign("Forward_some",$this->gosaMailForwardingAddress_Some);
731 $smarty->assign("multiple_support",$this->multiple_support_active);
732 $display.= $smarty->fetch (get_template_path('mail.tpl', TRUE, dirname(__FILE__)));
733 return ($display);
734 }
737 /* remove object from parent */
738 function remove_from_parent()
739 {
740 if(!$this->initially_was_account){
741 return;
742 }
744 /* Added these ObjectClass and Attributes, because they were not
745 removed correctly, only in case of kolab ...
746 */
747 if(preg_match("/olab/i",$this->config->get_cfg_value("mailmethod"))){
748 $this->attributes[]="acl";
749 $this->objectclasses[] = "kolabSharedFolder";
750 }
751 /* include global link_info */
752 $ldap= $this->config->get_ldap_link();
754 /* Remove and write to LDAP */
755 plugin::remove_from_parent();
757 /* Zero arrays */
758 $this->attrs['gosaMailAlternateAddress']= array();
759 $this->attrs['gosaMailForwardingAddress']= array();
760 $this->attrs['gosaSharedFolderTarget']= array();
762 /* Connect to IMAP server for account deletion */
763 if ($this->initially_was_account){
765 $method= new $this->method($this->config);
766 $method->fixAttributesOnRemove($this);
767 if ($method->connect($this->gosaMailServer) && $this->remove_folder_from_imap){
769 /* Remove account from IMAP server */
770 $method->deleteMailbox($this->folder_prefix.$this->account_name);
771 $method->disconnect();
772 }
773 }
774 /* Keep uid */
775 unset ($this->attrs['uid']);
777 $ldap->cd($this->dn);
778 $ldap->modify ($this->attrs);
779 if (!$ldap->success()){
780 msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $this->dn, LDAP_MOD, get_class()));
781 }
784 new log("remove","groups/".get_class($this),$this->dn,array_keys($this->attrs),$ldap->get_error());
786 /* Optionally execute a command after we're done */
787 $this->handle_post_events("remove");
788 }
791 /* Save data to object */
792 function save_object()
793 {
795 /* Add special kolab attributes */
796 if(preg_match("/olab/i",$this->config->get_cfg_value("mailmethod"))){
797 if(isset($_POST['kolabFolderTypeType']) && $this->acl_is_writeable("kolabFolderType")){
798 $this->kolabFolderTypeType = get_post("kolabFolderTypeType");
799 $this->kolabFolderTypeSubType = get_post("kolabFolderTypeSubType");
800 }
801 }
803 /* Check if user wants to remove the shared folder from imap too */
804 if($this->initially_was_account && !$this->is_account){
805 if(isset($_POST['remove_folder_from_imap'])){
806 $this->remove_folder_from_imap = true;
807 }else{
808 $this->remove_folder_from_imap = false;
809 }
810 }
812 /* Assemble mail delivery mode
813 The mode field in ldap consists of values between braces, this must
814 be called when 'mail' is set, because checkboxes may not be set when
815 we're in some other dialog.
817 Example for gosaMailDeliveryMode [LR ]
818 L: Local delivery
819 R: Reject when exceeding mailsize limit
820 S: Use spam filter
821 V: Use vacation message
822 C: Use custom sieve script
823 I: Only insider delivery */
824 if (isset($_POST['mailedit'])){
826 plugin::save_object();
828 $tmp= preg_replace("/[^a-z]/i","",$this->gosaMailDeliveryMode);
830 /* Handle delivery flags */
831 if($this->acl_is_writeable("gosaMailDeliveryModeL")){
832 if(!preg_match("/L/",$tmp) && !isset($_POST['drop_own_mails'])){
833 $tmp.="L";
834 }elseif(preg_match("/L/",$tmp) && isset($_POST['drop_own_mails'])){
835 $tmp = preg_replace("/L/","",$tmp);
836 }
837 }
839 $opts = array(
840 "R" => "use_mailsize_limit",
841 "S" => "use_spam_filter",
842 "V" => "use_vacation",
843 "C" => "own_script",
844 "I" => "only_local");
846 foreach($opts as $flag => $post){
847 if($this->acl_is_writeable("gosaMailDeliveryMode".$flag)){
848 if(!preg_match("/".$flag."/",$tmp) && isset($_POST[$post])){
849 $tmp.= $flag;
850 }elseif(preg_match("/".$flag."/",$tmp) && !isset($_POST[$post])){
851 $tmp = preg_replace("/".$flag."/","",$tmp);
852 }
853 }
854 }
856 $tmp= "[$tmp]";
857 if ($this->gosaMailDeliveryMode != $tmp){
858 $this->is_modified= TRUE;
859 }
860 $this->gosaMailDeliveryMode= $tmp;
862 /* Collect data and re-assign it to the imapacl array */
863 if ($this->acl_is_writeable("acl")){
864 $this->imapacl= array();
865 $this->imapacl['%members%']= $_POST['member_permissions'];
866 $this->imapacl['anyone']= $_POST['default_permissions'];
867 foreach ($this->indexed_user as $nr => $user){
868 if (!isset($_POST["user_$nr"])){
869 continue;
870 }
871 if ($_POST["user_$nr"] != $user ||
872 $_POST["perm_$nr"] != $this->indexed_acl[$nr]){
873 $this->is_modified= TRUE;
874 }
875 $this->imapacl[$_POST["user_$nr"]]= $_POST["perm_$nr"];
876 }
877 }
878 }
880 }
883 /* Save data to LDAP, depending on is_account we save or delete */
884 function save()
885 {
886 $ldap= $this->config->get_ldap_link();
887 $ldap->cd($this->config->current['BASE']);
889 /* Call parents save to prepare $this->attrs */
890 plugin::save();
892 /* Save arrays */
893 $this->attrs['gosaMailAlternateAddress'] = $this->gosaMailAlternateAddress;
894 $this->attrs['gosaMailForwardingAddress'] = $this->gosaMailForwardingAddress;
895 $this->attrs['gosaSharedFolderTarget'] = "share+".$this->account_name;
896 $this->attrs['acl']= array();
898 /* Prepare Mail server attribute */
899 if(preg_match("/olab/i",$this->mmethod)){
900 if (empty($this->gosaMailServer)||is_array($this->gosaMailServer)){
901 if(isset($this->attrs['gosaMailServer'][0])){
902 $this->gosaMailServer = $this->attrs['gosaMailServer'][0];
903 }
904 }
905 }
907 /* Exchange '%member%' pseudo entry */
908 $memberacl= $this->imapacl['%members%'];
909 foreach ($this->members as $user){
910 if (!isset($this->imapacl[$user])){
911 $this->imapacl[$user]= $memberacl;
912 }
913 }
915 /* Prepare kolab attributes to be written */
916 if(preg_match("/olab/i",$this->mmethod)){
917 if(!empty($this->kolabFolderTypeType)){
918 $this->attrs['kolabFolderType'] = $this->kolabFolderTypeType.".".$this->kolabFolderTypeSubType;
919 }else{
920 $this->attrs['kolabFolderType'] = array();
921 }
922 }
924 /* Get naming attribute for mail accounts */
925 $tmp = new $this->method($this->config);
926 $uattrib = $tmp->uattrib;
928 /* Create ACL array
929 What is done here.
931 1. Do not write acl entries for empty entries.
932 2. Check if given user is a valid GOsa user.
933 - If he is one, check if he has a valid mail extension
934 -If this is true, add the user to the ACL entry.
935 -If this is NOT true, skip ACL entries for this user.
936 - He is not a GOsa Account, so write the ACL. (Manually entered ACL)
937 3. In case of "olab" mail method, remove the entry from the $this->imapacl array
938 because the kolab deamon will set the acls for us.
940 */
941 $acls_set_for = array();
942 foreach ($this->imapacl as $user => $acl){
944 /* Skip empty entries */
945 if (empty($user) || $user == ""){
946 unset($this->imapacl[$user]);
947 }
949 /* Skip placeholder */
950 if (empty($user) || $user == "" || preg_match("/%members%/",$user)){
951 continue;
952 }
954 /* Check if your is a real GOsa user
955 * If user is a real GOsa user but do not have an email address - SKIP adding acls
956 * If user is a real GOsa user with an email address - add acls
957 */
958 $ldap->search("(&(objectClass=person)(|(uid=".$user.")(mail=".$user.")))",array("mail","uid"));
959 if($ldap->count()){
961 /* Has the user a valid mail account? */
962 $attrs = $ldap->fetch();
963 if(isset($attrs['mail'][0])){
965 $name = $attrs[$uattrib][0];
967 /* Do not overwrite manually set ACLs with group member acls
968 */
969 if(!in_array($name,$acls_set_for)){
970 $this->attrs['acl'][]= $name." ".$acl;
971 }
972 $acls_set_for[] = $name;
975 /* Do not write imap acl directly i nkolab mode, let the kolab deamon do this. */
976 unset($this->imapacl[$user]);
977 if(!preg_match("/olab/i",$this->mmethod)){
978 $this->imapacl[$name] = $acl;
979 }
981 }else{
983 /* User is a valid GOsa account, but he has no mail extension. Skip ACLs */
984 unset($this->imapacl[$user]);
985 }
986 }else{
988 /* Seems to be a manually a added acl
989 * Write this acl.
990 */
992 /* Do not overwrite manually set ACLs with group member acls
993 */
994 if(!in_array($user,$acls_set_for)){
995 $this->attrs['acl'][]= $user." ".$acl;
996 $acls_set_for[] = $user;
997 }
999 /* In case of kolab methods, let the deamon add the imap acls */
1000 if(preg_match("/olab/i",$this->mmethod)){
1001 unset($this->imapacl[$user]);
1002 }
1003 }
1004 }
1006 /* Handle mail method actions, set acls, quota ...*/
1007 if ((!$this->is_template)&&(!empty($this->gosaMailServer))){
1008 $method= new $this->method($this->config);
1009 $method->fixAttributesOnStore($this);
1010 if (($method->connect($this->gosaMailServer))){
1011 $method->updateMailbox($this->folder_prefix.$this->account_name);
1012 $method->setQuota($this->folder_prefix.$this->account_name, $this->gosaMailQuota);
1014 /* Only write imap acls directly if we are not
1015 * using a kolab like mail method.
1016 */
1017 if(!preg_match("/olab/i",$this->mmethod)){
1018 $method->setSharedFolderPermissions($this->folder_prefix.$this->account_name, $this->imapacl);
1019 }
1020 $method->disconnect();
1021 }
1022 }
1024 /* Save data to LDAP */
1025 $ldap->cd($this->dn);
1026 $this->cleanup();
1027 $ldap->modify ($this->attrs);
1028 if (!$ldap->success()){
1029 msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $this->dn, LDAP_MOD, get_class()));
1030 }
1032 if($this->initially_was_account){
1033 new log("modify","groups/".get_class($this),$this->dn,array_keys($this->attrs),$ldap->get_error());
1034 }else{
1035 new log("create","groups/".get_class($this),$this->dn,array_keys($this->attrs),$ldap->get_error());
1036 }
1039 /* Optionally execute a command after we're done */
1040 if ($this->initially_was_account == $this->is_account){
1041 if ($this->is_modified){
1042 $this->handle_post_events("modify");
1043 }
1044 } else {
1045 $this->handle_post_events("add");
1046 }
1047 }
1051 /* Check formular input */
1052 function check()
1053 {
1054 $ldap= $this->config->get_ldap_link();
1056 /* Call common method to give check the hook */
1057 $message= plugin::check();
1059 if(!$this->is_account) return array();
1061 //$message[] = $str;
1063 /* must: mail */
1064 if ($this->mail == ""){
1065 $message[]= msgPool::required(_("Mail address"));
1066 }
1067 if (!tests::is_email($this->mail)){
1068 $message[]= msgPool::invalid(_("Mail address"),"","",_("your-name@your-domain.com"));
1069 }
1070 $ldap->cd($this->config->current['BASE']);
1071 $ldap->search ("(&(!(objectClass=gosaUserTemplate))(objectClass=gosaMailAccount)(|(mail=".$this->mail.")(gosaMailAlternateAddress=".
1072 $this->mail."))(!(uid=".$this->orig_cn."))(!(cn=".$this->orig_cn.")))");
1073 if ($ldap->count() != 0){
1074 $message[]= msgPool::duplicated(_("Mail address"));
1075 }
1077 /* Check quota */
1078 if ($this->gosaMailQuota != '' && $this->acl_is_writeable("gosaMailQuota")){
1079 if (!is_numeric($this->gosaMailQuota)) {
1080 $message[]= msgPool::invalid(_("Quota size"),$this->gosaMailQuota,"/[0-9]/");
1081 } else {
1082 $this->gosaMailQuota= (int) $this->gosaMailQuota;
1083 }
1084 }
1086 /* Check rejectsize for integer */
1087 if ($this->gosaMailMaxSize != '' && $this->acl_is_writeable("gosaMailQuota")){
1088 if (!is_numeric($this->gosaMailMaxSize)){
1089 $message[]= msgPool::invalid(_("Mail max size"));
1090 } else {
1091 $this->gosaMailMaxSize= (int) $this->gosaMailMaxSize;
1092 }
1093 }
1095 /* Need gosaMailMaxSize if use_mailsize_limit is checked */
1096 if (is_integer(strpos($this->gosaMailDeliveryMode, "reject")) && $this->gosaMailMaxSize == ""){
1097 $message[]= _("You need to set the maximum mail size in order to reject anything.");
1098 }
1100 if(ord($this->imapacl['anyone'][0])==194){
1101 $message[] = _("Please choose valid permission settings. Default permission can't be emtpy.");
1102 }
1104 if(empty($this->gosaMailServer)){
1105 $message[] = msgPool::required(_("Mail server"));
1106 }
1108 return ($message);
1109 }
1111 /* Adapt from template, using 'dn' */
1112 function adapt_from_template($dn, $skip= array())
1113 {
1114 plugin::adapt_from_template($dn, $skip);
1116 foreach (array("gosaMailAlternateAddress", "gosaMailForwardingAddress") as $val){
1118 if (in_array($val, $skip)){
1119 continue;
1120 }
1122 $this->$val= array();
1123 if (isset($this->attrs["$val"]["count"])){
1124 for ($i= 0; $i<$this->attrs["$val"]["count"]; $i++){
1125 $value= $this->attrs["$val"][$i];
1126 foreach (array("sn", "givenName", "uid") as $repl){
1127 if (preg_match("/%$repl/i", $value)){
1128 $value= preg_replace ("/%$repl/i", $this->parent->$repl, $value);
1129 }
1130 }
1131 array_push($this->$val, $value);
1132 }
1133 }
1134 }
1135 }
1137 /* Add entry to forwarder list */
1138 function addForwarder($address)
1139 {
1140 $this->gosaMailForwardingAddress[]= $address;
1141 $this->gosaMailForwardingAddress= array_unique($this->gosaMailForwardingAddress);
1143 /* Update multiple edit values too */
1144 if($this->multiple_support_active){
1145 $this->gosaMailForwardingAddress_Some=
1146 array_remove_entries (array($address),$this->gosaMailForwardingAddress_Some);
1147 }
1149 sort ($this->gosaMailForwardingAddress);
1150 reset ($this->gosaMailForwardingAddress);
1151 $this->is_modified= TRUE;
1152 }
1154 /* Remove list of addresses from forwarder list */
1155 function delForwarder($addresses)
1156 {
1157 $this->gosaMailForwardingAddress= array_remove_entries ($addresses,
1158 $this->gosaMailForwardingAddress);
1160 /* Update multiple edit values too */
1161 if($this->multiple_support_active){
1162 $this->gosaMailForwardingAddress_Some = array_remove_entries ($addresses,
1163 $this->gosaMailForwardingAddress_Some);
1164 }
1165 $this->is_modified= TRUE;
1166 }
1170 function addAlternate($address)
1171 {
1172 $ldap= $this->config->get_ldap_link();
1174 $address= strtolower($address);
1176 /* Is this address already assigned in LDAP? */
1177 $ldap->cd ($this->config->current['BASE']);
1178 $ldap->search ("(&(objectClass=gosaMailAccount)(|(mail=$address)".
1179 "(gosaMailAlternateAddress=$address)))");
1181 if ($ldap->count() > 0){
1182 $attrs= $ldap->fetch ();
1183 return ($attrs["uid"][0]);
1184 }
1186 /* Add to list of alternates */
1187 if (!in_array($address, $this->gosaMailAlternateAddress)){
1188 $this->gosaMailAlternateAddress[]= $address;
1189 }
1191 sort ($this->gosaMailAlternateAddress);
1192 reset ($this->gosaMailAlternateAddress);
1193 $this->is_modified= TRUE;
1195 return ("");
1196 }
1199 function delAlternate($addresses)
1200 {
1201 $this->gosaMailAlternateAddress= array_remove_entries ($addresses,
1202 $this->gosaMailAlternateAddress);
1203 $this->is_modified= TRUE;
1204 }
1207 function make_name($attrs)
1208 {
1209 $name= "";
1210 if (isset($attrs['sn'][0])){
1211 $name= $attrs['sn'][0];
1212 }
1213 if (isset($attrs['givenName'][0])){
1214 if ($name != ""){
1215 $name.= ", ".$attrs['givenName'][0];
1216 } else {
1217 $name.= $attrs['givenName'][0];
1218 }
1219 }
1220 if ($name != ""){
1221 $name.= " ";
1222 }
1224 return ($name);
1225 }
1227 function getCopyDialog()
1228 {
1229 if(!$this->is_account) return("");
1231 $smarty = get_smarty();
1232 $smarty->assign("gosaMailAlternateAddress",$this->gosaMailAlternateAddress);
1233 $smarty->assign("gosaMailForwardingAddress",$this->gosaMailForwardingAddress);
1234 $smarty->assign("mail",$this->mail);
1235 $display= $smarty->fetch (get_template_path('paste_mail.tpl', TRUE, dirname(__FILE__)));
1236 $ret = array();
1237 $ret['string'] = $display;
1238 $ret['status'] = "";
1239 return($ret);
1240 }
1242 function saveCopyDialog()
1243 {
1244 if(!$this->is_account) return;
1246 /* Perform ADD / REMOVE ... for mail alternate / mail forwarding addresses
1247 */
1248 $this->execute();
1249 if(isset($_POST['mail'])){
1250 $this->mail = $_POST['mail'];
1251 }
1252 }
1255 function PrepareForCopyPaste($source)
1256 {
1257 plugin::PrepareForCopyPaste($source);
1259 /* Reset alternate mail addresses */
1260 $this->gosaMailAlternateAddress = array();
1261 }
1264 /* Return plugin informations for acl handling */
1265 static function plInfo()
1266 {
1267 return (array(
1268 "plShortName" => _("Mail"),
1269 "plDescription" => _("Group mail"),
1270 "plSelfModify" => FALSE,
1271 "plDepends" => array(),
1272 "plPriority" => 10,
1273 "plSection" => array("administration"),
1274 "plCategory" => array("groups"),
1275 "plProvidedAcls"=> array(
1276 "mail" => _("Mail address"),
1277 "gosaMailQuota" => _("Quota size"),
1278 "gosaMailServer" => _("Mail server"),
1279 "kolabFolderType" => _("Folder type")." ("._("Kolab").")",
1280 "gosaMailAlternateAddress" => _("Alternate addresses"),
1281 "gosaMailForwardingAddress" => _("Forwarding addresses"),
1282 "acl" => _("Permissions"))
1283 ));
1284 }
1287 /* Remove given ACL for given member (uid,mail) ..
1288 */
1289 function removeUserAcl($index )
1290 {
1291 if(isset($this->imapacl[$index])){
1292 unset($this->imapacl[$index]);
1293 }
1294 }
1296 function multiple_execute()
1297 {
1298 return($this->execute());
1299 }
1302 function init_multiple_support($attrs,$all)
1303 {
1304 plugin::init_multiple_support($attrs,$all);
1306 $this->gosaMailForwardingAddress = array();
1307 if(isset($attrs['gosaMailForwardingAddress'])){
1308 for($i = 0 ; $i < $attrs['gosaMailForwardingAddress']['count'] ; $i++){
1309 $this->gosaMailForwardingAddress[] = $attrs['gosaMailForwardingAddress'][$i];
1310 }
1311 }
1313 $this->gosaMailForwardingAddress_Some = array();
1314 if(isset($all['gosaMailForwardingAddress'])){
1315 for($i = 0 ; $i < $all['gosaMailForwardingAddress']['count'] ; $i++){
1316 if(!in_array($all['gosaMailForwardingAddress'][$i],$this->gosaMailForwardingAddress)){
1317 $this->gosaMailForwardingAddress_Some[] = $all['gosaMailForwardingAddress'][$i];
1318 }
1319 }
1320 }
1321 }
1323 function multiple_save_object()
1324 {
1325 if(isset($_POST['multiple_mail_group_posted'])){
1326 plugin::multiple_save_object();
1328 foreach(array("kolabFolderType") as $attr){
1329 if(isset($_POST['use_'.$attr])){
1330 $this->multi_boxes[] = $attr;
1331 }
1332 }
1334 /* Add special kolab attributes */
1335 if(preg_match("/olab/i",$this->config->get_cfg_value("mailmethod"))){
1336 if(isset($_POST['kolabFolderTypeType']) && $this->acl_is_writeable("kolabFolderType")){
1337 $this->kolabFolderTypeType = get_post("kolabFolderTypeType");
1338 $this->kolabFolderTypeSubType = get_post("kolabFolderTypeSubType");
1339 }
1340 }
1342 /* Collect data and re-assign it to the imapacl array */
1343 if ($this->acl_is_writeable("acl")){
1344 $this->imapacl= array();
1345 $this->imapacl['%members%']= $_POST['member_permissions'];
1346 $this->imapacl['anyone']= $_POST['default_permissions'];
1347 foreach ($this->indexed_user as $nr => $user){
1348 if (!isset($_POST["user_$nr"])){
1349 continue;
1350 }
1351 if ($_POST["user_$nr"] != $user ||
1352 $_POST["perm_$nr"] != $this->indexed_acl[$nr]){
1353 $this->is_modified= TRUE;
1354 }
1355 $this->imapacl[$_POST["user_$nr"]]= $_POST["perm_$nr"];
1356 }
1357 }
1358 }
1359 }
1362 /* Return selected values for multiple edit */
1363 function get_multi_edit_values()
1364 {
1365 $ret = plugin::get_multi_edit_values();
1366 $ret['Forward_some'] = $this->gosaMailForwardingAddress_Some;
1367 $ret['Forward_all'] = $this->gosaMailForwardingAddress;
1368 if(in_array('kolabFolderType',$this->multi_boxes)){
1369 $ret['kolabFolderTypeType'] = $this->kolabFolderTypeType;
1370 $ret['kolabFolderTypeSubType'] = $this->kolabFolderTypeSubType;
1371 }
1372 if(in_array("acl",$this->multi_boxes)){
1373 $ret['imapacl'] = $this->imapacl;
1374 }
1375 return($ret);
1376 }
1378 function set_multi_edit_values($attrs)
1379 {
1380 $forward = array();
1381 foreach($attrs['Forward_some'] as $addr){
1382 if(in_array($addr,$this->gosaMailForwardingAddress)){
1383 $forward[] = $addr;
1384 }
1385 }
1386 foreach($attrs['Forward_all'] as $addr){
1387 $forward[] = $addr;
1388 }
1389 plugin::set_multi_edit_values($attrs);
1390 $this->gosaMailForwardingAddress = $forward;
1391 }
1392 }
1394 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
1395 ?>