Code

Updated mail group plugin
[gosa.git] / gosa-plugins / mail / admin / groups / mail / class_groupMail.inc
1 <?php
3 class mailgroup extends plugin
4 {
5   /* Multiple edit */
6   var $gosaMailForwardingAddress_Some  = array();  // Used in multiple edit 
8   /* Default values */
9   var $mail                       = "";           // Default mail address 
10   var $gosaMailAlternateAddress   = array();  // Set default Alternate Mail Adresses to empty array
11   var $gosaMailForwardingAddress  = array();  // Forwarding also empty
12   var $gosaMailServer             = "";       // Selected mailserver 
13   var $gosaMailQuota              = "";       // Defined Quota 
14   var $gosaVacationMessage        = "";       // Vocation message 
15   var $gosaSpamSortLevel          = "";     
16   var $gosaSpamMailbox            = "";
17   var $gosaSharedFolderTarget     ;
18   var $gosaMailDeliveryMode       = "[L        ]";   // 
19   var $gosaMailMaxSize            = "";       // 
20   var $FolderType                 = array("CAT" => '', "SUB_CAT" => '');
22   var $quotaUsage                 = -1;        // -1 Means undefined
24   /* Internal */
25   var $AclTypes                   = array();
26   var $members                    = array();  // Group members
27   var $mailusers                  = array();  // Group member with mail account
28   var $folder_acls                = array();
29   var $MailMethod = NULL; 
30   var $forward_dialog             = FALSE;    
31   var $remove_folder_from_imap    = true;
32   var $view_logged                = FALSE;
33   var $mailDomainPart             = "";
35   /* attribute list for save action */
36   var $attributes= array( "mail",   "gosaMailServer", "gosaMailQuota", "gosaMailMaxSize",
37       "gosaMailAlternateAddress", "gosaMailForwardingAddress",
38       "gosaMailDeliveryMode", "gosaSpamSortLevel", "gosaSpamMailbox",
39       "acl","gosaSharedFolderTarget", "gosaVacationMessage");
41   var $objectclasses= array("gosaMailAccount");
42   var $multiple_support = FALSE; // Not tested yet
44   var $uid = "";
45   var $cn ="";
46   var $orig_cn = "";
47   var $show_effective_memeber = FALSE;
49   function __construct (&$config, $dn= NULL, $base_object= NULL)
50   {
51     plugin::plugin($config, $dn);
53     /* Get attributes from parent object
54      */
55     foreach(array("uid","cn") as $attr){
56       if(isset($this->parent->by_object['group']) && isset($this->parent->by_object['group']->$attr)){
57         $this->$attr = &$this->parent->by_object['group']->$attr;
58       }elseif(isset($this->attrs[$attr])){
59         $this->$attr = $this->attrs[$attr][0];
60       }
61     }
62     $this->orig_cn = $this->uid = $this->cn;
64     /* Intialize the used mailMethod
65      */
66     $tmp = new mailMethod($config,$this,"group");
67     $this->mailMethod           = $tmp->get_method();
68     $this->mailMethod->fixAttributesOnLoad();
69     $this->mailDomainParts      = $this->mailMethod->getMailDomains();
70     $this->AvailableFolderTypes = $this->mailMethod->getAvailableFolderTypes();
71     $this->MailBoxes = array();
73     /* Remember account status
74      */
75     $this->initially_was_account = $this->is_account;
77     /* While we are not not allowed to modify the mail address
78      *  and this is a new mail account, preset the user part of the 
79      *  mail address with the accounts cn.
80      */ 
81     if(!$this->mailMethod->isModifyableMail() && !$this->initially_was_account){
82       $this->mail = &$base_object->cn;
83     }
85     /* Load folder_acls with defaults.
86       anyone -- The default acl, will be written to ldap.
87       member -- The ACL used for the members.
88      */ 
89     $this->folder_acls = $this->mailMethod->getDefaultACLs();
91     /* Load acls
92        The most used acl will be used as member acl, this
93         shortens the listed acls.        
94        This may be merged/overwritten by the mail methods.
95      */
96     $ldap = $this->config->get_ldap_link();
97     if(isset($this->attrs['acl'])){
98       for($i = 0; $i < $this->attrs['acl']['count'] ; $i++){
99         $str = trim($this->attrs['acl'][$i]);
101         /* Be carefull here, since kolab22 uses spaces in the acls (herbert read anon/post)
102          */
103         $name = trim(preg_replace("/^([^\s]*).*$/","\\1",$str));
104         $acl  = trim(preg_replace("/^[^\s]*+\s/","",$str));
105         if($name == "anyone") $name = "__anyone__";
106         $this->folder_acls[$name] = $acl;
107       }
108     }
110     /* Initialize configured values
111      */
112     if($this->is_account){
113       if($this->mailMethod->connect() && $this->mailMethod->account_exists()){
115         /* Read quota */
116         $this->gosaMailQuota = $this->mailMethod->getQuota($this->gosaMailQuota);
117         $this->quotaUsage = $this->mailMethod->getQuotaUsage($this->quotaUsage);
118         if($this->mailMethod->is_error()){
119           msg_dialog::display(_("Mail error"), sprintf(_("Cannot read quota settings: %s"),
120                 $this->mailMethod->get_error()), ERROR_DIALOG);
121         }
123         /* Read mailboxes */
124         $this->MailBoxes = $this->mailMethod->getMailboxList($this->MailBoxes);
125         if($this->mailMethod->is_error()){
126           msg_dialog::display(_("Mail error"), sprintf(_("Cannot get list of mailboxes: %s"),
127                 $this->mailMethod->get_error()), ERROR_DIALOG);
128         }
130         /* Receive folder types */
131         $this->FolderType = $this->mailMethod->getFolderType($this->FolderType);
132         if($this->mailMethod->is_error()){
133           msg_dialog::display(_("Mail error"), sprintf(_("Cannot receive folder types: %s"),
134                 $this->mailMethod->get_error()), ERROR_DIALOG);
135         }
137         /* Receive permissions */  
138         $this->folder_acls = $this->mailMethod->getFolderACLs($this->folder_acls);
139         if($this->mailMethod->is_error()){
140           msg_dialog::display(_("Mail error"), sprintf(_("Cannot receive folder permissions: %s"),
141                 $this->mailMethod->get_error()), ERROR_DIALOG);
142         }
144       }elseif(!$this->mailMethod->is_connected()){
145         msg_dialog::display(_("Mail error"), sprintf(_("Mail method cannot connect: %s"),
146               $this->mailMethod->get_error()), ERROR_DIALOG);
147       }elseif(!$this->mailMethod->account_exists()){
148         msg_dialog::display(_("Mail error"), sprintf(_("Mailbox '%s' doesn't exists on mail server: %s"),
149               $this->mailMethod->get_account_id(),$this->gosaMailServer), ERROR_DIALOG);
150       }
152       /* If the doamin part is selectable, we have to split the mail address
153        */
154       if(!(!$this->mailMethod->isModifyableMail() && $this->is_account)){
155         if($this->mailMethod->domainSelectionEnabled() || $this->mailMethod->mailEqualsCN()){
156           $this->mailDomainPart = preg_replace("/^[^@]*+@/","",$this->mail);
157           $this->mail = preg_replace("/@.*$/","\\1",$this->mail);
158           if(!in_array($this->mailDomainPart,$this->mailDomainParts)){
159             $this->mailDomainParts[] = $this->mailDomainPart;
160           }
161         }
162       }
164       /* Load attributes containing arrays */
165       foreach (array("gosaMailAlternateAddress", "gosaMailForwardingAddress") as $val){
166         $this->$val= array();
167         if (isset($this->attrs["$val"]["count"])){
168           for ($i= 0; $i<$this->attrs["$val"]["count"]; $i++){
169             array_push($this->$val, $this->attrs["$val"][$i]);
170           }
171         }
172       }
173     }
175     /* Disconnect mailMethod. Connect on demand later.
176      */
177     $this->mailMethod->disconnect();
178     $this->AclTypes = $this->mailMethod->getAclTypes();
180     /* Summarize most used ACLs as member acl 
181      */
182     if(count($this->folder_acls) > 2){
183       $acl_usage = array();
184       $most_acl = $this->folder_acls['__member__'];
185       $most_cnt = 0;
186       $member = $this->get_member();
187       foreach($this->folder_acls as $user => $acl){
188         if(preg_match("/^__/",$user)) continue;
189         if(!in_array($user,$member['mail'])) continue; 
190         if(!isset($acl_usage[$acl])) $acl_usage[$acl]=0;
191         $acl_usage[$acl] ++;
192         if($acl_usage[$acl] > $most_cnt){
193           $most_cnt = $acl_usage[$acl];
194           $most_acl = $acl;
195         }
196       }
197       $this->folder_acls['__member__'] = $most_acl;  
198       foreach($this->folder_acls as $name => $acl){
199         if(preg_match("/^__/",$name)) continue;
200         if($acl == $most_acl && in_array($name,$member['mail'])){
201           unset($this->folder_acls[$name]);
202         }
203       }
204     }
206     /* Get global filter config */
207     if (!session::is_set("gmailfilter")){
208       $ui= get_userinfo();
209       $base= get_base_from_people($ui->dn);
210       $gmailfilter= array( "depselect"       => $base,
211           "muser"            => "",
212           "regex"           => "*");
213       session::set("gmailfilter", $gmailfilter);
214     }
215   }
217  
218   /*! \brief  Returns all group members once with 'dn' and once with 'mail'.
219               This function is used to summarize ACLs by member acls.
220       @return Array   Containing all members, with mail and dn
221    */ 
222   function get_member()
223   {
224     $member = array('all' => array(), 'mail' => array());
225     $ldap = $this->config->get_ldap_link();
226     $ldap->cd($this->config->current['BASE']);
227     if(isset($this->parent->by_object['group'])){
228       foreach($this->parent->by_object['group']->memberUid as $uid){
229         $dn = $this->parent->by_object['group']->dnMapping[$uid];
230         $member['all'][$uid] = $uid;
231         if($ldap->object_match_filter($dn,"(&(objectClass=gosaMailAccount)(".$this->mailMethod->getUAttrib()."=*))")){
232           $ldap->cat($dn);
233           $attrs = $ldap->fetch();
234           $member['mail'][$uid] = $attrs[$this->mailMethod->getUAttrib()][0]; 
235         }
236       }
237     }else{
238       if(!isset($this->attrs['memberUid'])) return($member);
239       $uattrib = $this->mailMethod->getUAttrib();
240       $users = get_list("(&(objectClass=person)(objectClass=gosaAccount)(uid=*))",
241               "users",$this->config->current['BASE'],
242               array("uid","objectClass",$uattrib),GL_SUBSEARCH | GL_NO_ACL_CHECK);
243       foreach($users as $user){
244         $member['all'][$user['uid'][0]] = $user['dn'];
245         if(isset($user[$uattrib]) 
246             && in_array("gosaMailAccount",$user['objectClass']) 
247             && (in_array($user['uid'][0], $this->attrs['memberUid']))){
248           $member['mail'][$user['uid'][0]] = $user[$uattrib][0];
249         }
250       }
251     }
252     return($member);
253   }
256   function execute()
257   {
258     /* Call parent execute */
259     plugin::execute();
261     /* Log view */
262     if($this->is_account && !$this->view_logged){
263       $this->view_logged = TRUE;
264       new log("view","groups/".get_class($this),$this->dn);
265     }
266  
267     /****************
268       Account status
269      ****************/
271     if(!$this->multiple_support_active){
273       if(isset($_POST['modify_state'])){
274         if($this->is_account && $this->acl_is_removeable() && $this->mailMethod->accountRemoveAble()){
275           $this->is_account= FALSE;
276         }elseif(!$this->is_account && $this->acl_is_createable() && $this->mailMethod->accountCreateable()){
277           $this->is_account= TRUE;
278         }
279       }
281       if ($this->is_account){
282         $reason = "";
283         if(!$this->mailMethod->accountRemoveable($reason)){
284           $display= $this->show_disable_header(msgPool::removeFeaturesButton(_("Mail")),$reason ,TRUE,TRUE);
285         }else{
286           $display= $this->show_disable_header(msgPool::removeFeaturesButton(_("Mail")),msgPool::
287               featuresEnabled(_("Mail")));
288         }
289       } else {
290         $reason = "";
291         if(!$this->mailMethod->accountCreateable($reason)){
292           $display= $this->show_disable_header(msgPool::addFeaturesButton(_("Mail")),$reason ,TRUE,TRUE);
293         }else{
294           $display= $this->show_disable_header(msgPool::addFeaturesButton(_("Mail")),msgPool::
295               featuresDisabled(_("Mail")));
297           /* Show checkbox that allows us to remove imap entry too*/
298           if($this->initially_was_account){
299             $c = "";
300             if($this->remove_folder_from_imap){
301               $c= " checked ";
302             }
303             $display .= "<h2>Shared folder delete options</h2>
304               <input class='center' type='checkbox' name='remove_folder_from_imap' value='1' ".$c."
305               title='"._("Remove shared folder from mail server database when entry gets removed in LDAP")."'>";
306             $display .= _("Remove the shared folder and all its contents after saving this account");
307           }
308         }
309         return ($display);
310       }
311     }
313     
314     /****************
315       Preset mail attribute
316      ****************/
317     if(empty($this->mail) && $this->mailMethod->mailEqualsCN() && !$this->initially_was_account){
318       if($this->mailMethod->domainSelectionEnabled()){
319         $this->mail = &$this->parent->by_object['group']->cn;
320       }
321     }
324     /****************
325       Forward addresses
326      ****************/
327     if (isset($_POST['add_local_forwarder'])){
328       $this->forward_dialog= TRUE;
329       $this->dialog= TRUE;
330     }
331     if (isset($_POST['add_locals_cancel'])){
332       $this->forward_dialog= FALSE;
333       $this->dialog= FALSE;
334     }
335     if (isset($_POST['add_locals_finish'])){
336       if (isset($_POST['local_list'])){
337         if($this->acl_is_writeable("gosaMailForwardingAddress")){
338           foreach ($_POST['local_list'] as $val){
339             if (!in_array ($val, $this->gosaMailAlternateAddress) &&
340                 $val != $this->mail){
341               $this->addForwarder($val);
342               $this->is_modified= TRUE;
343             }
344           }
345         }
346         $this->forward_dialog= FALSE;
347         $this->dialog= FALSE;
348       } else {
349         msg_dialog::display(_("Error"), _("Please select an entry!"), ERROR_DIALOG);
350       }
351     }
353     if (isset($_POST['add_forwarder'])){
354       if ($_POST['forward_address'] != ""){
355         $address= $_POST['forward_address'];
356         $valid= FALSE;
357         if (!tests::is_email($address)){
358           if (!tests::is_email($address, TRUE)){
359             if ($this->is_template){
360               $valid= TRUE;
361             } else {
362               msg_dialog::display(_("Error"), msgPool::invalid(_("Mail address"),
363                     "","","your-address@your-domain.com"),ERROR_DIALOG);
364             }
365           }
366         } elseif ($address == $this->mail
367             || in_array($address, $this->gosaMailAlternateAddress)) {
368           msg_dialog::display(_("Error"),_("Cannot add primary address to the list of forwarders!") , ERROR_DIALOG);
369         } else {
370           $valid= TRUE;
371         }
372         if ($valid){
373           if($this->acl_is_writeable("gosaMailForwardingAddress")){
374             $this->addForwarder ($address);
375             $this->is_modified= TRUE;
376           }
377         }
378       }
379     }
380     if (isset($_POST['delete_forwarder'])){
381       $this->delForwarder ($_POST['forwarder_list']);
382     }
383     if ($this->forward_dialog){
384       return($this->display_forward_dialog());
385     } 
387  
388     /****************
389       Alternate addresses
390      ****************/
392     if (isset($_POST['add_alternate'])){
393       $valid= FALSE;
394       if (!tests::is_email($_POST['alternate_address'])){
395         if ($this->is_template){
396           if (!(tests::is_email($_POST['alternate_address'], TRUE))){
397             msg_dialog::display(_("Error"),msgPool::invalid(_("Mail address"),
398                   "","","your-domain@your-domain.com"),     ERROR_DIALOG);
399           } else {
400             $valid= TRUE;
401           }
402         } else {
403           msg_dialog::display(_("Error"),msgPool::invalid(_("Mail address"),
404                 "","","your-domain@your-domain.com"),       ERROR_DIALOG);
405         }
406       } else {
407         $valid= TRUE;
408       }
409       if ($valid && ($user= $this->addAlternate ($_POST['alternate_address'])) != ""){
410         $ui= get_userinfo();
411         if ($user != $ui->username){
412           msg_dialog::display(_("Error"), msgPool::duplicated(_("Mail address"))."&nbsp;".
413               sprintf(_("Address is already in use by user '%s'."), $user), ERROR_DIALOG);
414         }
415       }
416     }
417     if (isset($_POST['delete_alternate']) && isset($_POST['alternates_list'])){
418       $this->delAlternate ($_POST['alternates_list']);
419     }
422     /****************
423       SMARTY- Assign smarty variables
424      ****************/
426     /* Load templating engine */
427     $smarty= get_smarty();
428     $smarty->assign("initially_was_account", $this->initially_was_account);
429     $smarty->assign("isModifyableMail", $this->mailMethod->isModifyableMail());
430     $smarty->assign("isModifyableServer", $this->mailMethod->isModifyableServer());
431     $smarty->assign("mailEqualsCN", $this->mailMethod->mailEqualsCN());
432     $smarty->assign("folder_acls" , $this->postable_acls());
433     $smarty->assign("AclTypes" ,    $this->AclTypes);
434     $smarty->assign("Effective",    $this->get_effective_member_acls());
435     $smarty->assign("show_effective_memeber",    $this->show_effective_memeber);
436   
437     $smarty->assign("quotaEnabled", $this->mailMethod->quotaEnabled());
438     if($this->mailMethod->quotaEnabled()){
439       $smarty->assign("gosaMailQuota",$this->gosaMailQuota);
440       $smarty->assign("quotaUsage",   mailMethod::quota_to_image($this->quotaUsage,$this->gosaMailQuota)); 
441     }
443     $smarty->assign("MailDomains", $this->mailDomainParts);
444     $smarty->assign("MailDomain" , $this->mailDomainPart);
445     $smarty->assign("MailServers", $this->mailMethod->getMailServers());
446     $smarty->assign("allowSieveManagement", $this->mailMethod->allowSieveManagement());
448     $smarty->assign("domainSelectionEnabled", $this->mailMethod->domainSelectionEnabled());
449     $smarty->assign("folderTypesEnabled",$this->mailMethod->folderTypesEnabled());
450     $smarty->assign("AvailableFolderTypes", $this->AvailableFolderTypes);
451     $smarty->assign("FolderType", $this->FolderType);
452  
453     if (is_numeric($this->gosaMailQuota) && $this->gosaMailQuota != 0){
454       if($this->acl_is_readable("gosaMailQuota")){
455         $smarty->assign("quotausage", progressbar(round(($this->quotaUsage * 100)/ $this->gosaMailQuota),100,15,true));
456         $smarty->assign("quotadefined", "true");
457       }else{
458         $smarty->assign("quotadefined", "true");
459         $smarty->assign("quotausage", "-");
460       }
461     } else {
462       $smarty->assign("quotadefined", "false");
463     }
465     /* Assign acls */
466     $tmp = $this->plInfo();
467     foreach($tmp['plProvidedAcls'] as $name => $translation) {
468       $smarty->assign($name."ACL",$this->getacl($name));
469     }
470     foreach($this->attributes as $name){
471       $smarty->assign($name,$this->$name);
472     }
476     $smarty->assign("mailServers", $this->mailMethod->getMailServers());
477     if (preg_match("/I/", $this->gosaMailDeliveryMode)) {
478       $smarty->assign("only_local", "checked");
479     }else{
480       $smarty->assign("only_local", "");
481     }
484     /******
485       Multi edit support 
486      ******/
487     foreach($this->attributes as $attr){
488       if(in_array($attr,$this->multi_boxes)){
489         $smarty->assign("use_".$attr,TRUE);
490       }else{
491         $smarty->assign("use_".$attr,FALSE);
492       }
493     }
495     /* Multiple support handling */
496     foreach(array("kolabFolderType") as $attr){
497       if(in_array($attr,$this->multi_boxes)){
498         $smarty->assign("use_".$attr,TRUE);
499       }else{
500         $smarty->assign("use_".$attr,FALSE);
501       }
502     }
504     $smarty->assign("Forward_all",$this->gosaMailForwardingAddress);
505     $smarty->assign("Forward_some",$this->gosaMailForwardingAddress_Some);
506     $smarty->assign("multiple_support",$this->multiple_support_active);
508     $display.= $smarty->fetch (get_template_path('mail.tpl', TRUE, dirname(__FILE__)));
509     return ($display);
510   }
513   /* remove object from parent */
514   function remove_from_parent()
515   {
516     if(!$this->initially_was_account){
517       return;
518     }
519  
520     /* If domain part was selectable, contruct mail address */
521     if($this->mailMethod->domainSelectionEnabled() || $this->mailMethod->mailEqualsCN()){
522       $this->mail = $this->mail."@".$this->mailDomainPart;
523     }
525     /* Remove GOsa attributes */
526     plugin::remove_from_parent();
528     /* Zero arrays */
529     $this->attrs['gosaMailAlternateAddress'] = array();
530     $this->attrs['gosaMailForwardingAddress']= array();
532     $this->mailMethod->fixAttributesOnRemove();
533     $this->cleanup();
534     $ldap = $this->config->get_ldap_link();
535     $ldap->cd($this->dn);
536     $ldap->modify ($this->attrs); 
537     if (!$ldap->success()){
538       msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $this->dn, LDAP_MOD, get_class()));
539     }
541     new log("remove","groups/".get_class($this),$this->dn,array_keys($this->attrs),$ldap->get_error());
544     /* Let the mailMethod remove this mailbox, e.g. from imap and
545        update shared folder membership, ACL may need to be updated.
546      */
547     if (!$this->is_template && $this->remove_folder_from_imap){
549       if(!$this->mailMethod->connect()){
550         msg_dialog::display(_("Mail error"), sprintf(_("Mail method cannot connect: %s"),
551               $this->mailMethod->get_error()), ERROR_DIALOG);
552       }else{
553         if(!$this->mailMethod->deleteMailbox()){
554           msg_dialog::display(_("Mail error"), sprintf(_("Cannot remove mailbox: %s"),
555                 $this->mailMethod->get_error()), ERROR_DIALOG);
556         }
557         if(!$this->mailMethod->updateSharedFolder()){
558           msg_dialog::display(_("Mail error"), sprintf(_("Cannot update shared folder permissions: %s"),
559                 $this->mailMethod->get_error()), ERROR_DIALOG);
560         }
561       }
562     }
563     $this->mailMethod->disconnect();
566     /* Optionally execute a command after we're done */
567     $this->handle_post_events("remove");
568   }
571   /* Save data to object */
572   function save_object()
573   {
574     /* Check if user wants to remove the shared folder from imap too 
575      */
576     if($this->initially_was_account && !$this->is_account){
577       if(isset($_POST['remove_folder_from_imap'])){
578         $this->remove_folder_from_imap = true;
579       }else{
580         $this->remove_folder_from_imap = false;
581       }
582     }
583     if (isset($_POST['mailedit'])){
585       if(isset($_POST['show_effective_memeber'])){
586         $this->show_effective_memeber = !$this->show_effective_memeber;
587       }
589       $mail = $this->mail;
590       $server = $this->gosaMailServer;
591       plugin::save_object();
593       if(!$this->mailMethod->isModifyableServer() && $this->initially_was_account){
594         $this->gosaMailServer = $server;
595       }
596       if(!$this->mailMethod->isModifyableMail() && $this->initially_was_account){
597         $this->mail = $mail;
598       }else{
600         if($this->mailMethod->mailEqualsCN()){
601           $this->mail = &$this->parent->by_object['group']->cn;
602           if(isset($_POST['MailDomain'])){
603             $this->mailDomainPart = get_post('MailDomain');
604           }
605         }
607       /* Get posted mail domain part, if necessary
608        */
609       if($this->mailMethod->domainSelectionEnabled() && isset($_POST['MailDomain'])){
610         if(in_array(get_post('MailDomain'), $this->mailDomainParts)){
611           $this->mailDomainPart = get_post('MailDomain');
612         }
613       }
614     }
616       /* Get folder type 
617        */
618       if($this->mailMethod->folderTypesEnabled()){
619         if(isset($_POST['FolderTypeCAT'])){
620         $this->FolderType['CAT']     = get_post('FolderTypeCAT');
621         }
622         if(isset($_POST['FolderTypeSUB_CAT'])){
623           $this->FolderType['SUB_CAT'] = get_post('FolderTypeSUB_CAT');
624         }
625       }
627       /* Handle posted ACL changes. 
628          Add/del member acls.
629        */
630       if(isset($_POST['mail_acls_posted'])){
631         $new_acls = array();
632         foreach(array("__anyone__","__member__") as $attr){
633           $pname = base64_encode($attr);
634           if(get_post('acl_value_'.$pname)){ 
635             $new_acls[$attr] = get_post('acl_value_'.$pname);
636           }else{
637             $new_acls[$attr] = $this->folder_acls[$attr];
638           }
639         }
641         foreach($this->folder_acls as $user => $acl){
642           $pname = base64_encode($user);
643           if($user == "__member__" || $user == "__anyone__") continue;
644           if(isset($_POST['remove_acl_user_'.$pname])){
645           }elseif(isset($_POST['acl_user_'.$pname])){
646             if($user != get_post('acl_user_'.$pname)){
647               $new_acls[get_post('acl_user_'.$pname)] = get_post('acl_value_'.$pname);
648             }else{
649               $new_acls[$user] = get_post('acl_value_'.$pname);
650             }
651           }else{
652             $new_acls[$user] = $acl;
653           }
654         }
655         if(isset($_POST['add_acl_user'])){
656           $new_acls[_('New')] = $this->folder_acls['__anyone__'];
657         }
658         $this->folder_acls = $new_acls;
659       }
661       /* Handle GOsa mail delivery flags.
662        */
664     /* Assemble mail delivery mode
665        The mode field in ldap consists of values between braces, this must
666        be called when 'mail' is set, because checkboxes may not be set when
667        we're in some other dialog.
669        Example for gosaMailDeliveryMode [LR        ]
670        L - Local delivery
671        R - Reject when exceeding mailsize limit
672        S - Use spam filter
673        V - Use vacation message
674        C - Use custom sieve script
675        I - Only insider delivery */
676       $tmp= preg_replace("/[^a-z]/i","",$this->gosaMailDeliveryMode);
677       if($this->acl_is_writeable("gosaMailDeliveryModeL")){
678         if(!preg_match("/L/",$tmp) && !isset($_POST['drop_own_mails'])){
679           $tmp.="L";
680         }elseif(preg_match("/L/",$tmp) && isset($_POST['drop_own_mails'])){
681           $tmp = preg_replace("/L/","",$tmp);
682         }
683       }
685       $opts = array(
686           "R"   => "use_mailsize_limit",
687           "S"   => "use_spam_filter",
688           "V"   => "use_vacation",
689           "C"   => "own_script",
690           "I"   => "only_local");
692       foreach($opts as $flag => $post){
693         if($this->acl_is_writeable("gosaMailDeliveryMode".$flag)){
694           if(!preg_match("/".$flag."/",$tmp) && isset($_POST[$post])){
695             $tmp.= $flag;
696           }elseif(preg_match("/".$flag."/",$tmp) && !isset($_POST[$post])){
697             $tmp = preg_replace("/".$flag."/","",$tmp);
698           }
699         }
700       }
702       $tmp= "[$tmp]";
703       if ($this->gosaMailDeliveryMode != $tmp){
704         $this->is_modified= TRUE;
705       }
706       $this->gosaMailDeliveryMode= $tmp;
707     }
708   }
711   /* Save data to LDAP, depending on is_account we save or delete */
712   function save()
713   {
714     $ldap= $this->config->get_ldap_link();
716     /* If domain part was selectable, contruct mail address */
717     if(!(!$this->mailMethod->isModifyableMail() && $this->initially_was_account)){
719       if($this->mailMethod->domainSelectionEnabled() || $this->mailMethod->mailEqualsCN()){
720         $this->mail = $this->mail."@".$this->mailDomainPart;
721       }
722     }
724     /* Enforce lowercase mail address and trim whitespaces
725      */
726     $this->mail = trim(strtolower($this->mail));
727     
729     /* Create acls 
730      */
731     $this->acl = array("anyone ".$this->folder_acls['__anyone__']);
732     $member = $this->get_member();
733     $new_folder_acls = array("anyone" => $this->folder_acls['__anyone__']);
734     foreach($member['mail'] as $uid => $mail){
736       /* Do not save overridden acls */
737       if(isset($this->folder_acls[$mail])){
738         continue;
739       }
741       $this->acl[] = $mail." ".$this->folder_acls['__member__'];
742       $new_folder_acls[$mail]=$this->folder_acls['__member__'];
743     }
744     foreach($this->folder_acls as $user => $acls){
745       if(preg_match("/^__/",$user)) continue;
746       $this->acl[] = $user." ".$acls;
747       $new_folder_acls[$user]=$acls;
748     }
749     $this->folder_acls = $new_folder_acls;
750     $this->acl = array_unique($this->acl);
751      
752     /* Call parents save to prepare $this->attrs */
753     plugin::save();
755     /* Save arrays */
756     $this->attrs['gosaMailAlternateAddress'] = $this->gosaMailAlternateAddress;
757     $this->attrs['gosaMailForwardingAddress']= $this->gosaMailForwardingAddress;
759     /* Map method attributes */
760     $this->mailMethod->fixAttributesOnStore();
762     /* Save data to LDAP */
763     $ldap->cd($this->dn);
764     $this->cleanup();
765     $ldap->modify ($this->attrs); 
766     if (!$ldap->success()){
767       msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $this->dn, LDAP_MOD, get_class()));
768     }
769     
770     if($this->initially_was_account){
771       new log("modify","groups/".get_class($this),$this->dn,array_keys($this->attrs),$ldap->get_error());
772     }else{
773       new log("create","groups/".get_class($this),$this->dn,array_keys($this->attrs),$ldap->get_error());  
774     }
775     
776     /* Do imap/sieve actions,
777      */
778     $this->mailMethod->connect();
779     if(!$this->mailMethod->is_connected()){
780       msg_dialog::display(_("Mail error"), sprintf(_("Mail method cannot connect: %s"),
781             $this->mailMethod->get_error()), ERROR_DIALOG);
782     }else{
783       if(!$this->mailMethod->updateMailbox()){
784         msg_dialog::display(_("Mail error"), sprintf(_("Cannot update mailbox: %s"),
785               $this->mailMethod->get_error()), ERROR_DIALOG);
786       }
787       if(!$this->mailMethod->setQuota($this->gosaMailQuota)){
788         msg_dialog::display(_("Mail error"), sprintf(_("Cannot write quota settings: %s"),
789               $this->mailMethod->get_error()), ERROR_DIALOG);
790       }
791       /* Save Folder Types, if available 
792        */
793       if($this->mailMethod->folderTypesEnabled()){
794         $this->mailMethod->setFolderType($this->FolderType);
795       }
796       if(!$this->mailMethod->setFolderACLs($this->folder_acls)){
797         msg_dialog::display(_("Mail error"), sprintf(_("Cannot update shared folder permissions: %s"),
798               $this->mailMethod->get_error()), ERROR_DIALOG);
799       }
800     }
801     $this->mailMethod->disconnect();
803     /* Optionally execute a command after we're done */
804     if ($this->initially_was_account == $this->is_account){
805       if ($this->is_modified){
806         $this->handle_post_events("modify");
807       }
808     } else {
809       $this->handle_post_events("add");
810     }
811   }
815   /* Check formular input */
816   function check()
817   {
818     if(!$this->is_account) return array();
819     $ldap= $this->config->get_ldap_link();
822     /* Call common method to give check the hook */
823     $message= plugin::check();
825     /* Ensure that this group isn't renamed if the mailMethod enforces cn mailAttributes 
826      */
827     if($this->mailMethod->mailEqualsCN() && $this->initially_was_account){
828       if($this->cn != $this->orig_cn){
829         $message[] = sprintf(_("The group 'cn' has changed. It can't be changed due to the fact that mail method '%s' relies on it!")
830             ,get_class($this->mailMethod));
831       }
832     }
834     if(empty($this->gosaMailServer)){
835       $message[]= msgPool::noserver(_("Mail"));
836     }
838     /* Mail address checks */
839     $mail = $this->mail;
840     if(!(!$this->mailMethod->isModifyableMail() && $this->initially_was_account)){
841       if($this->mailMethod->domainSelectionEnabled() || $this->mailMethod->mailEqualsCN()){
842         $mail.= "@".$this->mailDomainPart;
843       }
844       if (empty($mail)){
845         $message[]= msgPool::required(_("Primary address"));
846       }elseif (!tests::is_email($mail)){
847         $message[]= msgPool::invalid(_("Mail address"),"","","your-address@your-domain.com");
848       }
849     }
850     
851     /* Check quota */
852     if ($this->gosaMailQuota != '' && $this->acl_is_writeable("gosaMailQuota")){
853       if (!is_numeric($this->gosaMailQuota)) {
854         $message[]= msgPool::invalid(_("Quota size"),$this->gosaMailQuota,"/[0-9]/");
855       } else {
856         $this->gosaMailQuota= (int) $this->gosaMailQuota;
857       }
858     }
860     /* Check if this mail address is already in use */
861     $ldap->cd($this->config->current['BASE']);
862     $filter = "(&(!(objectClass=gosaUserTemplate))(!(cn=".$this->cn."))".
863             "(objectClass=gosaMailAccount)".
864             "(|(mail=".$mail.")(alias=".$mail.")(gosaMailAlternateAddress=".$mail.")))";
865     $ldap->search($filter,array("cn"));
866     if ($ldap->count() != 0){
867             $message[]= msgPool::duplicated(_("Mail address"));
868     }
870     /* Check rejectsize for integer */
871     if ($this->gosaMailMaxSize != '' && $this->acl_is_writeable("gosaMailQuota")){
872       if (!is_numeric($this->gosaMailMaxSize)){
873         $message[]= msgPool::invalid(_("Mail max size"));
874       } else {
875         $this->gosaMailMaxSize= (int) $this->gosaMailMaxSize;
876       }
877     }
879     /* Need gosaMailMaxSize if use_mailsize_limit is checked */
880     if (is_integer(strpos($this->gosaMailDeliveryMode, "reject")) && $this->gosaMailMaxSize == ""){
881       $message[]= _("You need to set the maximum mail size in order to reject anything.");
882     }
884     if(empty($this->gosaMailServer)){
885       $message[] = msgPool::required(_("Mail server"));
886     }
888     return ($message);
889   }
891   /* Adapt from template, using 'dn' */
892   function adapt_from_template($dn, $skip= array())
893   {
894     plugin::adapt_from_template($dn, $skip);
896     foreach (array("gosaMailAlternateAddress", "gosaMailForwardingAddress") as $val){
897  
898       if (in_array($val, $skip)){
899         continue;
900       }
902       $this->$val= array();
903       if (isset($this->attrs["$val"]["count"])){
904         for ($i= 0; $i<$this->attrs["$val"]["count"]; $i++){
905           $value= $this->attrs["$val"][$i];
906           foreach (array("sn", "givenName", "uid") as $repl){
907             if (preg_match("/%$repl/i", $value)){
908               $value= preg_replace ("/%$repl/i", $this->parent->$repl, $value);
909             }
910           }
911           array_push($this->$val, $value);
912         }
913       }
914     }
915   }
919   function make_name($attrs)
920   {
921     $name= "";
922     if (isset($attrs['sn'][0])){
923       $name= $attrs['sn'][0];
924     }
925     if (isset($attrs['givenName'][0])){
926       if ($name != ""){
927         $name.= ", ".$attrs['givenName'][0];
928       } else {
929         $name.= $attrs['givenName'][0];
930       }
931     }
932     if ($name != ""){
933       $name.= " ";
934     }
936     return ($name);
937   }
939   function getCopyDialog()
940   {
941     if(!$this->is_account) return("");
943     $smarty = get_smarty();
944     $smarty->assign("gosaMailAlternateAddress",$this->gosaMailAlternateAddress);
945     $smarty->assign("gosaMailForwardingAddress",$this->gosaMailForwardingAddress);
946     $smarty->assign("mail",$this->mail);
947     $display= $smarty->fetch (get_template_path('paste_mail.tpl', TRUE, dirname(__FILE__)));
948     $ret = array();
949     $ret['string'] = $display;
950     $ret['status'] = "";
951     return($ret);
952   }
954   function saveCopyDialog()
955   {
956     if(!$this->is_account) return;
958     /* Perform ADD / REMOVE ... for mail alternate / mail forwarding addresses 
959     */
960     $this->execute();
961     if(isset($_POST['mail'])){
962       $this->mail = $_POST['mail'];
963     }
964   }
967   function PrepareForCopyPaste($source)
968   {
969     plugin::PrepareForCopyPaste($source);
970  
971     /* Reset alternate mail addresses */
972     $this->gosaMailAlternateAddress = array();
973   }
976   /* Return plugin informations for acl handling  */
977   static function plInfo()
978   {
979     return (array(
980           "plShortName"   => _("Mail"),
981           "plDescription" => _("Group mail"),
982           "plSelfModify"  => FALSE,
983           "plDepends"     => array(),
984           "plPriority"    => 10,
985           "plSection"     => array("administration"),
986           "plCategory"    => array("groups"), 
987           "plProvidedAcls"=> array(
988             "mail"                      => _("Mail address"),
989             "gosaMailQuota"             => _("Quota size"),
990             "gosaMailServer"            => _("Mail server"),
991             "kolabFolderType"           => _("Folder type")." ("._("Kolab").")",
992             "gosaMailAlternateAddress"  => _("Alternate addresses"),
993             "gosaMailForwardingAddress" => _("Forwarding addresses"),
994             "gosaMailDeliveryModeI"     => _("Only local"),
995             "acl"                       => _("Permissions"))
996           ));
997   }
999   
1000   /* Remove given ACL for given member (uid,mail) ..
1001    */
1002   function removeUserAcl($index )
1003   {
1004     if(isset($this->imapacl[$index])){
1005       unset($this->imapacl[$index]);
1006     }
1007   }
1009   function multiple_execute()
1010   {
1011     return($this->execute());
1012   }
1015   function init_multiple_support($attrs,$all)
1016   {
1017     plugin::init_multiple_support($attrs,$all);
1019     $this->gosaMailForwardingAddress = array();
1020     if(isset($attrs['gosaMailForwardingAddress'])){
1021       for($i = 0 ; $i < $attrs['gosaMailForwardingAddress']['count'] ; $i++){
1022         $this->gosaMailForwardingAddress[] = $attrs['gosaMailForwardingAddress'][$i];
1023       }
1024     }
1026     $this->gosaMailForwardingAddress_Some = array();
1027     if(isset($all['gosaMailForwardingAddress'])){
1028       for($i = 0 ; $i < $all['gosaMailForwardingAddress']['count'] ; $i++){
1029         if(!in_array($all['gosaMailForwardingAddress'][$i],$this->gosaMailForwardingAddress)){
1030           $this->gosaMailForwardingAddress_Some[] = $all['gosaMailForwardingAddress'][$i];
1031         }
1032       }
1033     }
1034   }
1036   function multiple_save_object()
1037   {
1038     if(isset($_POST['multiple_mail_group_posted'])){
1039       plugin::multiple_save_object();
1040       
1041       foreach(array("kolabFolderType") as $attr){
1042         if(isset($_POST['use_'.$attr])){
1043           $this->multi_boxes[] = $attr;
1044         }
1045       }
1047       /* Add special kolab attributes */
1048       if(preg_match("/olab/i",$this->config->get_cfg_value("mailmethod"))){
1049         if(isset($_POST['kolabFolderTypeType']) && $this->acl_is_writeable("kolabFolderType")){
1050           $this->kolabFolderTypeType = get_post("kolabFolderTypeType");
1051           $this->kolabFolderTypeSubType = get_post("kolabFolderTypeSubType");
1052         }
1053       }
1055       /* Collect data and re-assign it to the imapacl array */
1056       if ($this->acl_is_writeable("acl")){
1057         $this->imapacl= array();
1058         $this->imapacl['%members%']= $_POST['member_permissions'];
1059         $this->imapacl['anyone']= $_POST['default_permissions'];
1060         foreach ($this->indexed_user as $nr => $user){
1061           if (!isset($_POST["user_$nr"])){
1062             continue;
1063           }
1064           if ($_POST["user_$nr"] != $user ||
1065               $_POST["perm_$nr"] != $this->indexed_acl[$nr]){
1066             $this->is_modified= TRUE;
1067           }
1068           $this->imapacl[$_POST["user_$nr"]]= $_POST["perm_$nr"];
1069         }
1070       }
1071     }
1072   }
1073   
1074   
1075   /* Return selected values for multiple edit */
1076   function get_multi_edit_values()
1077   {
1078     $ret = plugin::get_multi_edit_values();
1079     $ret['Forward_some'] = $this->gosaMailForwardingAddress_Some;    
1080     $ret['Forward_all'] = $this->gosaMailForwardingAddress;    
1081     if(in_array('kolabFolderType',$this->multi_boxes)){
1082       $ret['kolabFolderTypeType'] = $this->kolabFolderTypeType;
1083       $ret['kolabFolderTypeSubType'] = $this->kolabFolderTypeSubType;
1084     }
1085     if(in_array("acl",$this->multi_boxes)){
1086       $ret['imapacl'] = $this->imapacl;
1087     }
1088     return($ret);
1089   }
1091   function set_multi_edit_values($attrs)
1092   {
1093     $forward = array();
1094     foreach($attrs['Forward_some'] as $addr){
1095       if(in_array($addr,$this->gosaMailForwardingAddress)){
1096         $forward[] = $addr;
1097       }
1098     }
1099     foreach($attrs['Forward_all'] as $addr){
1100       $forward[] = $addr;
1101     }
1102     plugin::set_multi_edit_values($attrs);
1103     $this->gosaMailForwardingAddress = $forward;
1104   }
1107   /*! \brief  Displays a dialog that allows mail address selection.
1108    */
1109   function display_forward_dialog()
1110   {
1111     restore_error_handler();
1113     $smarty = get_smarty();
1114     $ldap= $this->config->get_ldap_link();
1116     /* Save data */
1117     $mailfilter= session::get("mailfilter");
1118     foreach( array("depselect", "muser", "regex") as $type){
1119       if (isset($_POST[$type])){
1120         $mailfilter[$type]= $_POST[$type];
1121       }
1122     }
1123     if (isset($_GET['search'])){
1124       $s= mb_substr($_GET['search'], 0, 1, "UTF8")."*";
1125       if ($s == "**"){
1126         $s= "*";
1127       }
1128       $mailfilter['regex']= $s;
1129     }
1130     session::set("mailfilter", $mailfilter);
1132     /* Get actual list */
1133     $mailusers= array ();
1134     if ($mailfilter['regex'] != '*' && $mailfilter['regex'] != ""){
1135       $regex= $mailfilter['regex'];
1136       $filter= "(|(mail=$regex)(gosaMailAlternateAddress=$regex))";
1137     } else {
1138       $filter= "";
1139     }
1140     if ($mailfilter['muser'] != ""){
1141       $user= $mailfilter['muser'];
1142       $filter= "$filter(|(uid=$user)(cn=$user)(givenName=$user)(sn=$user))";
1143     }
1145     /* Add already present people to the filter */
1146     $exclude= "";
1147     foreach ($this->gosaMailForwardingAddress as $mail){
1148       $exclude.= "(mail=$mail)";
1149     }
1150     if ($exclude != ""){
1151       $filter.= "(!(|$exclude))";
1152     }
1153     $res= get_list("(&(objectClass=gosaMailAccount)$filter)", "users", $mailfilter['depselect'],
1154         array("sn", "mail", "givenName"), GL_SIZELIMIT | GL_SUBSEARCH);
1155     $ldap->cd($mailfilter['depselect']);
1156     $ldap->search ("(&(objectClass=gosaMailAccount)$filter)", array("sn", "mail", "givenName"));
1157     while ($attrs= $ldap->fetch()){
1158       if(preg_match('/%/', $attrs['mail'][0])){
1159         continue;
1160       }
1161       $name= $this->make_name($attrs);
1162       $mailusers[$attrs['mail'][0]]= $name."&lt;".
1163         $attrs['mail'][0]."&gt;";
1164     }
1165     natcasesort ($mailusers);
1166     reset ($mailusers);
1168     /* Show dialog */
1169     $smarty->assign("search_image", get_template_path('images/lists/search.png'));
1170     $smarty->assign("usearch_image", get_template_path('images/lists/search-user.png'));
1171     $smarty->assign("tree_image", get_template_path('images/lists/search-subtree.png'));
1172     $smarty->assign("infoimage", get_template_path('images/info.png'));
1173     $smarty->assign("launchimage", get_template_path('images/lists/action.png'));
1174     $smarty->assign("mailusers", $mailusers);
1175     if (isset($_POST['depselect'])){
1176       $smarty->assign("depselect", $_POST['depselect']);
1177     }
1178     $smarty->assign("deplist", $this->config->idepartments);
1179     $smarty->assign("apply", apply_filter());
1180     $smarty->assign("alphabet", generate_alphabet());
1181     $smarty->assign("hint", print_sizelimit_warning());
1182     foreach( array("depselect", "muser", "regex") as $type){
1183       $smarty->assign("$type", $mailfilter[$type]);
1184     }
1185     $smarty->assign("hint", print_sizelimit_warning());
1186     $display= $smarty->fetch (get_template_path('mail_locals.tpl', TRUE, dirname(__FILE__)));
1187     return ($display);
1188   }
1191   /*! \brief  Add given mail address to the list of forwarders.
1192    */
1193   function addForwarder($address)
1194   {
1195     if(empty($address)) return;
1196     $this->gosaMailForwardingAddress[]= $address;
1197     $this->gosaMailForwardingAddress= array_unique($this->gosaMailForwardingAddress);
1199     /* Update multiple edit values too */
1200     if($this->multiple_support_active){
1201       $this->gosaMailForwardingAddress_Some= 
1202         array_remove_entries (array($address),$this->gosaMailForwardingAddress_Some);
1203     }
1205     sort ($this->gosaMailForwardingAddress);
1206     reset ($this->gosaMailForwardingAddress);
1207     $this->is_modified= TRUE;
1208   }
1211   /*! \brief  Removes the given mail address from the forwarders 
1212    */
1213   function delForwarder($addresses)
1214   {
1215     if(empty($addresses)) return;
1216     $this->gosaMailForwardingAddress= array_remove_entries ($addresses,
1217         $this->gosaMailForwardingAddress);
1219     /* Update multiple edit values too */
1220     if($this->multiple_support_active){
1221       $this->gosaMailForwardingAddress_Some = array_remove_entries ($addresses,
1222           $this->gosaMailForwardingAddress_Some);
1223     }
1224     $this->is_modified= TRUE;
1225   }
1228   /*! \brief  Add given mail address to the list of alternate adresses ,
1229     .          check if this mal address is used, skip adding in this case
1230    */
1231   function addAlternate($address)
1232   {
1233     if(empty($address)) continue;
1234     $ldap= $this->config->get_ldap_link();
1236     $address= strtolower($address);
1238     /* Is this address already assigned in LDAP? */
1239     $ldap->cd ($this->config->current['BASE']);
1240     $ldap->search ("(&(objectClass=gosaMailAccount)(|(mail=$address)".
1241         "(gosaMailAlternateAddress=$address)))");
1243     if ($ldap->count() > 0){
1244       $attrs= $ldap->fetch ();
1245       return ($attrs["uid"][0]);
1246     }
1248     /* Add to list of alternates */
1249     if (!in_array($address, $this->gosaMailAlternateAddress)){
1250       $this->gosaMailAlternateAddress[]= $address;
1251     }
1253     sort ($this->gosaMailAlternateAddress);
1254     reset ($this->gosaMailAlternateAddress);
1255     $this->is_modified= TRUE;
1257     return ("");
1258   }
1261   /*! \brief  Removes the given mail address from the alternate addresses  
1262    */
1263   function delAlternate($addresses)
1264   {
1265     if(empty($address)) continue;
1266     $this->gosaMailAlternateAddress= array_remove_entries ($addresses,
1267         $this->gosaMailAlternateAddress);
1268     $this->is_modified= TRUE;
1269   }
1272   function postable_acls()
1273   {
1274     $ret = array();
1275     foreach($this->folder_acls as $name => $acl){
1276       $ret[$name] = array("name" => $name,"acl" => $acl,"post_name" => base64_encode($name));
1277     }
1278     return($ret);
1279   }
1282   function get_effective_member_acls()
1283   {
1284     $tmp = array();
1285     $member = $this->get_member();
1286     foreach($member['mail'] as $uid => $mail){
1288       /* Do not save overridden acls */
1289       if(isset($this->folder_acls[$mail])){
1290         continue;
1291       }
1293       
1294       $tmp[$mail]  = $this->folder_acls['__member__'];
1295     }
1296     return($tmp);
1297   }
1300   function allow_remove()
1301   {
1302     $resason = "";
1303     if(!$this->mailMethod->allow_remove($reason)){
1304       return($reason);
1305     }
1306     return("");
1307   }
1310   // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
1311 ?>