* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @version $Id$ */ /** * Mail worker class * -provides backend functionality for all classes in Mail * -provides classes that may be used by other apps too */ class mail_bo { /** * the current selected user profile * @var int */ var $profileID = 0; /** * the current display char set * @var string */ static $displayCharset; /** * Instance of bopreference * * @var bopreferences object */ var $bopreferences; /** * Active preferences * * @var array */ var $mailPreferences; /** * active html Options * * @var array */ var $htmlOptions; /** * Active incomming (IMAP) Server Object * * @var object */ var $icServer; /** * Active outgoing (smtp) Server Object * * @var object */ var $ogServer; /** * errorMessage * * @var string $errorMessage */ var $errorMessage; /** * switch to enable debug; sometimes debuging is quite handy, to see things. check with the error log to see results * @var boolean */ static $debug = false; //true; /** * static used to hold the mail Config values * @array */ static $mailConfig; /** * static used to configure tidy - if tidy is loadable, this config is used with tidy to straighten out html, instead of using purifiers tidy mode * * @array */ static $tidy_config = array('clean'=>true,'output-html'=>true,'join-classes'=>true,'join-styles'=>true,'show-body-only'=>"auto",'word-2000'=>true,'wrap'=>0); /** * static used to configure htmLawed, for use with emails * * @array */ static $htmLawed_config = array('comment'=>1, //remove comments 'make_tag_strict' => 3, // 3 is a new own config value, to indicate that transformation is to be performed, but don't transform font as size transformation of numeric sizes to keywords alters the intended result too much 'keep_bad'=>2, //remove tags but keep element content (4 and 6 keep element content only if text (pcdata) is valid in parent element as per specs, this may lead to textloss if balance is switched on) 'balance'=>1,//turn off tag-balancing (config['balance']=>0). That will not introduce any security risk; only standards-compliant tag nesting check/filtering will be turned off (basic tag-balance will remain; i.e., there won't be any unclosed tag, etc., after filtering) 'direct_list_nest' => 1, 'allow_for_inline' => array('table','div','li','p'),//block elements allowed for nesting when only inline is allowed; Example span does not allow block elements as table; table is the only element tested so far 'tidy'=>1, 'elements' => "* -script", 'deny_attribute' => 'on*', 'schemes'=>'href: file, ftp, http, https, mailto; src: cid, data, file, ftp, http, https; *:file, http, https, cid, src', 'hook_tag' =>"hl_email_tag_transform", ); /** * static used define abbrevations for common access rights * * @array */ static $aclShortCuts = array('' => array('label'=>'none','title'=>'The user has no rights whatsoever.'), 'lrs' => array('label'=>'readable','title'=>'Allows a user to read the contents of the mailbox.'), 'lprs' => array('label'=>'post','title'=>'Allows a user to read the mailbox and post to it through the delivery system by sending mail to the submission address of the mailbox.'), 'ilprs' => array('label'=>'append','title'=>'Allows a user to read the mailbox and append messages to it, either via IMAP or through the delivery system.'), 'cdilprsw' => array('label'=>'write','title'=>'Allows a user to read the maibox, post to it, append messages to it, and delete messages or the mailbox itself. The only right not given is the right to change the ACL of the mailbox.'), 'acdilprsw' => array('label'=>'all','title'=>'The user has all possible rights on the mailbox. This is usually granted to users only on the mailboxes they own.'), 'custom' => array('label'=>'custom','title'=>'User defined combination of rights for the ACL'), ); /** * Folders that get automatic created AND get translated to the users language * their creation is also controlled by users mailpreferences. if set to none / dont use folder * the folder will not be automatically created. This is controlled in mail_bo->getFolderObjects * so changing names here, must include a change of keywords there as well. Since these * foldernames are subject to translation, keep that in mind too, if you change names here. * ActiveSync: * Outbox is needed by Nokia Clients to be able to send Mails * @var array */ static $autoFolders = array('Drafts', 'Templates', 'Sent', 'Trash', 'Junk', 'Outbox'); /** * Array to cache the specialUseFolders, if existing * @var array */ static $specialUseFolders; /** * var to hold IDNA2 object * @var class object */ static $idna2; /** * Hold instances by profileID for getInstance() singleton * * @var array */ private static $instances = array(); /** * Singleton for mail_bo * * @param boolean $_restoreSession=true * @param int $_profileID=0 * @param boolean $_validate=true - flag wether the profileid should be validated or not, if validation is true, you may receive a profile * not matching the input profileID, if we can not find a profile matching the given ID * @return object instance of mail_bo */ public static function getInstance($_restoreSession=true, $_profileID=0, $_validate=true) { //error_log(__METHOD__.__LINE__.' RestoreSession:'.$_restoreSession.' ProfileId:'.$_profileID.' called from:'.function_backtrace()); if ($_profileID == 0) { if (isset($GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID']) && !empty($GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'])) { $profileID = (int)$GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID']; } else { $profileID = emailadmin_bo::getUserDefaultProfileID(); } if ($profileID!=$_profileID) $_restoreSession==false; $_profileID=$profileID; if (self::$debug) error_log(__METHOD__.__LINE__.' called with profileID==0 using '.$profileID.' instead->'.function_backtrace()); } if ($_profileID != 0 && $_validate) { $profileID = self::validateProfileID($_restoreSession, $_profileID); if ($profileID != $_profileID) { if (self::$debug) { error_log(__METHOD__.__LINE__.' Validation of profile with ID:'.$_profileID.' failed. Using '.$profileID.' instead.'); error_log(__METHOD__.__LINE__.' # Instance='.$GLOBALS['egw_info']['user']['domain'].', User='.$GLOBALS['egw_info']['user']['account_lid']); } $_profileID = $profileID; $GLOBALS['egw']->preferences->add('mail','ActiveProfileID',$_profileID,'user'); // save prefs $GLOBALS['egw']->preferences->save_repository(true); } egw_cache::setSession('mail','activeProfileID',$_profileID); } //error_log(__METHOD__.__LINE__.' RestoreSession:'.$_restoreSession.' ProfileId:'.$_profileID.' called from:'.function_backtrace()); if (!isset(self::$instances[$_profileID]) || $_restoreSession===false) { self::$instances[$_profileID] = new mail_bo('utf-8',$_restoreSession,$_profileID); } else { // make sure the prefs are up to date for the profile to load $loadfailed = false; self::$instances[$_profileID]->mailPreferences = self::$instances[$_profileID]->bopreferences->getPreferences(true,$_profileID); //error_log(__METHOD__.__LINE__." ReRead the Prefs for ProfileID ".$_profileID.' called from:'.function_backtrace()); if (self::$instances[$_profileID]->mailPreferences) { self::$instances[$_profileID]->icServer = self::$instances[$_profileID]->mailPreferences->getIncomingServer($_profileID); // if we do not get an icServer object, session restore failed on bopreferences->getPreferences if (!self::$instances[$_profileID]->icServer) $loadfailed=true; if ($_profileID != 0) self::$instances[$_profileID]->mailPreferences->setIncomingServer(self::$instances[$_profileID]->icServer,0); self::$instances[$_profileID]->ogServer = self::$instances[$_profileID]->mailPreferences->getOutgoingServer($_profileID); if ($_profileID != 0) self::$instances[$_profileID]->mailPreferences->setOutgoingServer(self::$instances[$_profileID]->ogServer,0); self::$instances[$_profileID]->htmlOptions = self::$instances[$_profileID]->mailPreferences->preferences['htmlOptions']; } else { $loadfailed=true; } if ($loadfailed) { error_log(__METHOD__.__LINE__." ReRead of the Prefs for ProfileID ".$_profileID.' failed for icServer; trigger new instance. called from:'.function_backtrace()); // restore session seems to provide an incomplete session self::$instances[$_profileID] = new mail_bo('utf-8',false,$_profileID); } } self::$instances[$_profileID]->profileID = $_profileID; if (!isset(self::$instances[$_profileID]->idna2)) self::$instances[$_profileID]->idna2 = new egw_idna; //if ($_profileID==0); error_log(__METHOD__.__LINE__.' RestoreSession:'.$_restoreSession.' ProfileId:'.$_profileID); if (is_null(self::$mailConfig)) self::$mailConfig = config::read('mail'); return self::$instances[$_profileID]; } /** * validate the given profileId to make sure it is valid for the active user * * @param boolean $_restoreSession=true - needed to pass on to getInstance * @param int $_profileID=0 * @return int validated profileID -> either the profileID given, or a valid one */ public static function validateProfileID($_restoreSession=true, $_profileID=0) { $identities = array(); $mail = mail_bo::getInstance($_restoreSession, $_profileID, $validate=false); // we need an instance of mail_bo $selectedID = $mail->getIdentitiesWithAccounts($identities); if (is_object($mail->mailPreferences)) $activeIdentity =& $mail->mailPreferences->getIdentity($_profileID, true); // if you use user defined accounts you may want to access the profile defined with the emailadmin available to the user // as we validate the profile in question and may need to return an emailadminprofile, we fetch this one all the time $boemailadmin = new emailadmin_bo(); $defaultProfile = $boemailadmin->getUserProfile() ; //error_log(__METHOD__.__LINE__.array2string($defaultProfile)); $identitys =& $defaultProfile->identities; $icServers =& $defaultProfile->ic_server; foreach ($identitys as $tmpkey => $identity) { if (empty($icServers[$tmpkey]->host)) continue; $identities[$identity->id] = $identity->realName.' '.$identity->organization.' <'.$identity->emailAddress.'>'; } //error_log(__METHOD__.__LINE__.array2string($identities)); if (array_key_exists($_profileID,$identities)) { // everything seems to be in order self::$profileID REMAINS UNCHANGED } else { if (array_key_exists($selectedID,$identities)) { $_profileID = $selectedID; } else { foreach (array_keys((array)$identities) as $k => $ident) { //error_log(__METHOD__.__LINE__.' Testing Identity with ID:'.$ident.' for being provided by emailadmin.'); if ($ident <0) $_profileID = $ident; } if (self::$debug) error_log(__METHOD__.__LINE__.' Profile Selected (after trying to fetch DefaultProfile):'.array2string($_profileID)); if (!array_key_exists($_profileID,$identities)) { // everything failed, try first profile found $keys = array_keys((array)$identities); if (count($keys)>0) $_profileID = array_shift($keys); else $_profileID = 0; } } } if (self::$debug) error_log(__METHOD__.'::'.__LINE__.' ProfileSelected:'.$_profileID.' -> '.$identities[$_profileID]); return $_profileID; } /** * Private constructor, use mail_bo::getInstance() instead * * @param string $_displayCharset='utf-8' * @param boolean $_restoreSession=true * @param int $_profileID=0 */ private function __construct($_displayCharset='utf-8',$_restoreSession=true, $_profileID=0) { if ($_restoreSession) { //error_log(__METHOD__." Session restore ".function_backtrace()); $this->restoreSessionData(); $lv_mailbox = $this->sessionData['mailbox']; $firstMessage = $this->sessionData['previewMessage']; } else { $this->restoreSessionData(); $lv_mailbox = $this->sessionData['mailbox']; $firstMessage = $this->sessionData['previewMessage']; $this->sessionData = array(); $this->forcePrefReload(); } $this->accountid = $GLOBALS['egw_info']['user']['account_id']; $this->bopreferences = CreateObject('mail.mail_bopreferences',$_restoreSession); $this->mailPreferences = $this->bopreferences->getPreferences(true,$this->profileID); //error_log(__METHOD__.__LINE__." ProfileID ".$this->profileID.' called from:'.function_backtrace()); if ($this->mailPreferences) { $this->icServer = $this->mailPreferences->getIncomingServer($this->profileID); if ($this->profileID != 0) $this->mailPreferences->setIncomingServer($this->icServer,0); $this->ogServer = $this->mailPreferences->getOutgoingServer($this->profileID); if ($this->profileID != 0) $this->mailPreferences->setOutgoingServer($this->ogServer,0); $this->htmlOptions = $this->mailPreferences->preferences['htmlOptions']; if (isset($this->icServer->ImapServerId) && !empty($this->icServer->ImapServerId)) { $_profileID = $this->profileID = $GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'] = $this->icServer->ImapServerId; } } if (is_null(self::$mailConfig)) self::$mailConfig = config::read('mail'); if (!isset(self::$idna2)) self::$idna2 = new egw_idna; } /** * forceEAProfileLoad * used to force the load of a specific emailadmin profile; we assume administrative use only (as of now) * @param int $_profile_id must be a value lower than 0 (emailadmin profile) * @return object instance of mail_bo (by reference) */ public static function &forceEAProfileLoad($_profile_id) { $mail = mail_bo::getInstance(false, $_profile_id,false); //_debug_array( $_profile_id); $mail->mailPreferences = $mail->bopreferences->getPreferences(false,$_profile_id,'mail',$_profile_id); $mail->icServer = $mail->mailPreferences->getIncomingServer($_profile_id); $mail->ogServer = $mail->mailPreferences->getOutgoingServer($_profile_id); return $mail; } /** * trigger the force of the reload of the SessionData by resetting the session to an empty array */ public static function forcePrefReload() { // unset the mail_preferences session object, to force the reload/rebuild $GLOBALS['egw']->session->appsession('mail_preferences','mail',serialize(array())); $GLOBALS['egw']->session->appsession('session_data','emailadmin',serialize(array())); } /** * restore the SessionData */ function restoreSessionData() { $this->sessionData = $GLOBALS['egw']->session->appsession('session_data','mail'); $this->sessionData['folderStatus'] = egw_cache::getCache(egw_cache::INSTANCE,'email','folderStatus'.trim($GLOBALS['egw_info']['user']['account_id']),$callback=null,$callback_params=array(),$expiration=60*60*1); } /** * saveSessionData saves session data */ function saveSessionData() { if (isset($this->sessionData['folderStatus']) && is_array($this->sessionData['folderStatus'])) { egw_cache::setCache(egw_cache::INSTANCE,'email','folderStatus'.trim($GLOBALS['egw_info']['user']['account_id']),$this->sessionData['folderStatus'], $expiration=60*60*1); unset($this->sessionData['folderStatus']); } $GLOBALS['egw']->session->appsession('session_data','mail',$this->sessionData); } /** * resets the various cache objects where connection error Objects may be cached * * @param int $_ImapServerId the profileID to look for */ static function resetConnectionErrorCache($_ImapServerId=null) { //error_log(__METHOD__.__LINE__.' for Profile:'.array2string($_ImapServerId) .' for user:'.trim($GLOBALS['egw_info']['user']['account_id'])); $account_id = $GLOBALS['egw_info']['user']['account_id']; if (is_array($_ImapServerId)) { // called via hook $account_id = $_ImapServerId['account_id']; unset($_ImapServerId); $_ImapServerId = null; } if (is_null($_ImapServerId)) { $buff = array(); $isConError = array(); $waitOnFailure = array(); } else { $buff = egw_cache::getCache(egw_cache::INSTANCE,'email','icServerIMAP_connectionError'.trim($account_id)); if (isset($buff[$_ImapServerId])) { unset($buff[$_ImapServerId]); } $isConError = egw_cache::getCache(egw_cache::INSTANCE,'email','icServerSIEVE_connectionError'.trim($account_id)); if (isset($isConError[$_ImapServerId])) { unset($isConError[$_ImapServerId]); } $waitOnFailure = egw_cache::getCache(egw_cache::INSTANCE,'email','ActiveSyncWaitOnFailure'.trim($GLOBALS['egw_info']['user']['account_id']),$callback=null,$callback_params=array(),$expiration=60*60*2); if (isset($waitOnFailure[$_ImapServerId])) { unset($waitOnFailure[$_ImapServerId]); } } egw_cache::setCache(egw_cache::INSTANCE,'email','icServerIMAP_connectionError'.trim($account_id),$buff,$expiration=60*15); egw_cache::setCache(egw_cache::INSTANCE,'email','icServerSIEVE_connectionError'.trim($account_id),$isConError,$expiration=60*15); egw_cache::setCache(egw_cache::INSTANCE,'email','ActiveSyncWaitOnFailure'.trim($GLOBALS['egw_info']['user']['account_id']),$waitOnFailure,$expiration=60*60*2); } /** * resets the various cache objects where Folder Objects may be cached * * @param int $_ImapServerId the profileID to look for */ static function resetFolderObjectCache($_ImapServerId=null) { //error_log(__METHOD__.__LINE__.' called for Profile:'.$_ImapServerId.'->'.function_backtrace()); if (is_null($_ImapServerId)) { $folders2return = array(); $folderInfo = array(); } else { $folders2return = egw_cache::getCache(egw_cache::INSTANCE,'email','folderObjects'.trim($GLOBALS['egw_info']['user']['account_id']),$callback=null,$callback_params=array(),$expiration=60*60*1); if (isset($folders2return[$_ImapServerId])) { unset($folders2return[$_ImapServerId]); } $folderInfo = egw_cache::getCache(egw_cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),$expiration=60*5); if (isset($folderInfo[$_ImapServerId])) { unset($folderInfo[$_ImapServerId]); } $lastFolderUsedForMove = egw_cache::getCache(egw_cache::INSTANCE,'email','lastFolderUsedForMove'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),$expiration=60*60*1); if (isset($lastFolderUsedForMove[$_ImapServerId])) { unset($lastFolderUsedForMove[$_ImapServerId]); } $folderBasicInfo = egw_cache::getCache(egw_cache::INSTANCE,'email','folderBasicInfo'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),$expiration=60*60*1); if (isset($folderBasicInfo[$_ImapServerId])) { unset($folderBasicInfo[$_ImapServerId]); } $_specialUseFolders = egw_cache::getCache(egw_cache::INSTANCE,'email','specialUseFolders'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),$expiration=60*60*12); if (isset($_specialUseFolders[$_ImapServerId])) { unset($_specialUseFolders[$_ImapServerId]); self::$specialUseFolders=null; } } egw_cache::setCache(egw_cache::INSTANCE,'email','folderObjects'.trim($GLOBALS['egw_info']['user']['account_id']),$folders2return, $expiration=60*60*1); egw_cache::setCache(egw_cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($GLOBALS['egw_info']['user']['account_id']),$folderInfo,$expiration=60*5); egw_cache::setCache(egw_cache::INSTANCE,'email','lastFolderUsedForMove'.trim($GLOBALS['egw_info']['user']['account_id']),$lastFolderUsedForMove,$expiration=60*60*1); egw_cache::setCache(egw_cache::INSTANCE,'email','folderBasicInfo'.trim($GLOBALS['egw_info']['user']['account_id']),$folderBasicInfo,$expiration=60*60*1); egw_cache::setCache(egw_cache::INSTANCE,'email','specialUseFolders'.trim($GLOBALS['egw_info']['user']['account_id']),$_specialUseFolders,$expiration=60*60*12); } /** * checks if the imap server supports a given capability * * @param string $_capability the name of the capability to check for * @return bool */ function hasCapability($_capability) { $rv = $this->icServer->hasCapability(strtoupper($_capability)); //error_log(__METHOD__.__LINE__." $_capability:".array2string($rv)); return $rv; } /** * getIdentitiesWithAccounts * * @param array reference to pass all identities back * @return the default Identity (active) or 0 */ function getIdentitiesWithAccounts(&$identities) { // account select box $selectedID = $this->profileID; if($this->mailPreferences->userDefinedAccounts) $allAccountData = $this->bopreferences->getAllAccountData($this->mailPreferences); if ($allAccountData) { foreach ($allAccountData as $tmpkey => $accountData) { $identity =& $accountData['identity']; $icServer =& $accountData['icServer']; //_debug_array($identity); //_debug_array($icServer); if (empty($icServer->host)) continue; $identities[$identity->id]=$identity->realName.' '.$identity->organization.' <'.$identity->emailAddress.'>'; if (!empty($identity->default)) $selectedID = $identity->id; } } return $selectedID; } /** * closes a connection on the active Server ($this->icServer) * * @return void */ function closeConnection() { //if ($icServer->_connected) error_log(__METHOD__.__LINE__.' disconnect from Server'); if ($icServer->_connected) $this->icServer->disconnect(); } /** * reopens a connection for the active Server ($this->icServer), and selects the folder given * * @param string $_foldername, folder to open/select * @return void */ function reopen($_foldername) { // TODO: trying to reduce traffic to the IMAP Server here, introduces problems with fetching the bodies of // eMails when not in "current-Folder" (folder that is selected by UI) static $folderOpened; //if (empty($folderOpened) || $folderOpened!=$_foldername) //{ //error_log( "------------------------reopen- $_foldername
"); //error_log(__METHOD__.__LINE__.' Connected with icServer for Profile:'.$this->profileID.'?'.print_r($this->icServer->_connected,true)); if (!($this->icServer->_connected == 1)) { $tretval = $this->openConnection($this->profileID,false); } if ($this->icServer->_connected == 1 && $this->folderIsSelectable($_foldername)) { $tretval = $this->icServer->selectMailbox($_foldername); } $folderOpened = $_foldername; //} } /** * openConnection * * @param int $_icServerID * @param boolean $_adminConnection * @return boolean true/false or PEAR_Error Object ((if available) on Failure) */ function openConnection($_icServerID=0, $_adminConnection=false) { static $isError; //error_log(__METHOD__.__LINE__.'->'.$_icServerID.' called from '.function_backtrace()); if (is_null($isError)) $isError = egw_cache::getCache(egw_cache::INSTANCE,'email','icServerIMAP_connectionError'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),$expiration=60*5); if ( isset($isError[$_icServerID]) || (($this->icServer instanceof defaultimap) && PEAR::isError($this->icServer->_connectionErrorObject))) { if (trim($isError[$_icServerID])==',' || trim($this->icServer->_connectionErrorObject->message) == ',') { //error_log(__METHOD__.__LINE__.' Connection seemed to have failed in the past, no real reason given, try to recover on our own.'); emailadmin_bo::unsetCachedObjects($_icServerID); } else { //error_log(__METHOD__.__LINE__.' failed for Reason:'.$isError[$_icServerID]); $this->errorMessage = ($isError[$_icServerID]?$isError[$_icServerID]:$this->icServer->_connectionErrorObject->message); return false; } } if (!is_object($this->mailPreferences)) { if (self::$debug) error_log(__METHOD__." No Object for MailPreferences found.". function_backtrace()); $this->errorMessage .= lang('No valid data to create MailProfile!!'); $isError[$_icServerID] = (($this->icServer instanceof defaultimap)?new PEAR_Error($this->errorMessage):$this->errorMessage); egw_cache::setCache(egw_cache::INSTANCE,'email','icServerIMAP_connectionError'.trim($GLOBALS['egw_info']['user']['account_id']),$isError,$expiration=60*15); return false; } if(!$this->icServer = $this->mailPreferences->getIncomingServer((int)$_icServerID)) { $this->errorMessage .= lang('No active IMAP server found!!'); $isError[$_icServerID] = $this->errorMessage; egw_cache::setCache(egw_cache::INSTANCE,'email','icServerIMAP_connectionError'.trim($GLOBALS['egw_info']['user']['account_id']),$isError,$expiration=60*15); return false; } //error_log(__METHOD__.__LINE__.'->'.array2string($this->icServer->ImapServerId)); if ($this->icServer && empty($this->icServer->host)) { $errormessage = lang('No IMAP server host configured!!'); if ($GLOBALS['egw_info']['user']['apps']['emailadmin']) { $errormessage .= "
".lang("Configure a valid IMAP Server in emailadmin for the profile you are using."); } else { $errormessage .= "
".lang('Please ask the administrator to correct the emailadmin IMAP Server Settings for you.'); } $this->icServer->_connectionErrorObject->message .= $this->errorMessage .= $errormessage; $isError[$_icServerID] = $this->errorMessage; egw_cache::setCache(egw_cache::INSTANCE,'email','icServerIMAP_connectionError'.trim($GLOBALS['egw_info']['user']['account_id']),$isError,$expiration=60*15); return false; } //error_log( "-------------------------->open connection ".function_backtrace()); //error_log(__METHOD__.__LINE__.' ->'.array2string($this->icServer)); if ($this->icServer->_connected == 1) { if (!empty($this->icServer->currentMailbox)) $tretval = $this->icServer->selectMailbox($this->icServer->currentMailbox); if ( PEAR::isError($tretval) ) $isError[$_icServerID] = $tretval->message; //error_log(__METHOD__." using existing Connection ProfileID:".$_icServerID.' Status:'.print_r($this->icServer->_connected,true)); } else { //error_log(__METHOD__.__LINE__."->open connection for Server with profileID:".$_icServerID.function_backtrace()); $timeout = mail_bo::getTimeOut(); $tretval = $this->icServer->openConnection($_adminConnection,$timeout); if ( PEAR::isError($tretval) || $tretval===false) { $isError[$_icServerID] = ($tretval?$tretval->message:$this->icServer->_connectionErrorObject->message); if (self::$debug) { error_log(__METHOD__.__LINE__." # failed to open new Connection ProfileID:".$_icServerID.' Status:'.print_r($this->icServer->_connected,true).' Message:'.$isError[$_icServerID].' called from '.function_backtrace()); error_log(__METHOD__.__LINE__.' # Instance='.$GLOBALS['egw_info']['user']['domain'].', User='.$GLOBALS['egw_info']['user']['account_lid']); } } if (!PEAR::isError($tretval) && isset($this->sessionData['mailbox']) && !empty($this->sessionData['mailbox'])) $smretval = $this->icServer->selectMailbox($this->sessionData['mailbox']);//may fail silently } if ( PEAR::isError($tretval) ) egw_cache::setCache(egw_cache::INSTANCE,'email','icServerIMAP_connectionError'.trim($GLOBALS['egw_info']['user']['account_id']),$isError,$expiration=60*15); //error_log(print_r($this->icServer->_connected,true)); //make sure we are working with the correct hierarchyDelimiter on the current connection, calling getHierarchyDelimiter with false to reset the cache $hD = $this->getHierarchyDelimiter(false); self::$specialUseFolders = $this->getSpecialUseFolders(); //error_log(__METHOD__.__LINE__.array2string($sUF)); return $tretval; } /** * getTimeOut * * @param string _use decide if the use is for IMAP or SIEVE, by now only the default differs * * @return int - timeout (either set or default 20/10) */ static function getTimeOut($_use='IMAP') { $timeout = $GLOBALS['egw_info']['user']['preferences']['mail']['connectionTimeout']; if (empty($timeout)) $timeout = ($_use=='SIEVE'?10:20); // this is the default value return $timeout; } /** * _getNameSpaces, fetch the namespace from icServer * @return array $nameSpace array(peronal=>array,others=>array, shared=>array) */ function _getNameSpaces() { static $nameSpace; $foldersNameSpace = array(); $delimiter = $this->getHierarchyDelimiter(); // TODO: cache by $this->icServer->ImapServerId if (is_null($nameSpace)) $nameSpace = $this->icServer->getNameSpaces(); if (is_array($nameSpace)) { foreach($nameSpace as $type => $singleNameSpace) { $prefix_present = false; if($type == 'personal' && ($singleNameSpace[2]['name'] == '#mh/' || count($nameSpace) == 1) && ($this->folderExists('Mail')||$this->folderExists('INBOX'))) { $foldersNameSpace[$type]['prefix_present'] = 'forced'; // uw-imap server with mailbox prefix or dovecot maybe $foldersNameSpace[$type]['prefix'] = ($this->folderExists('Mail')?'Mail':(!empty($singleNameSpace[0]['name'])?$singleNameSpace[0]['name']:'')); } elseif($type == 'personal' && ($singleNameSpace[2]['name'] == '#mh/' || count($nameSpace) == 1) && $this->folderExists('mail')) { $foldersNameSpace[$type]['prefix_present'] = 'forced'; // uw-imap server with mailbox prefix or dovecot maybe $foldersNameSpace[$type]['prefix'] = 'mail'; } else { $foldersNameSpace[$type]['prefix_present'] = true; $foldersNameSpace[$type]['prefix'] = $singleNameSpace[0]['name']; } $foldersNameSpace[$type]['delimiter'] = $delimiter; //echo "############## $type->".print_r($foldersNameSpace[$type],true)." ###################
"; } } //error_log(__METHOD__.__LINE__.array2string($foldersNameSpace)); return $foldersNameSpace; } /** * getFolderPrefixFromNamespace, wrapper to extract the folder prefix from folder compared to given namespace array * @var array $nameSpace * @var string $_folderName * @return string the prefix (may be an empty string) */ function getFolderPrefixFromNamespace($nameSpace, $folderName) { foreach($nameSpace as $type => $singleNameSpace) { //if (substr($singleNameSpace['prefix'],0,strlen($folderName))==$folderName) return $singleNameSpace['prefix']; if (substr($folderName,0,strlen($singleNameSpace['prefix']))==$singleNameSpace['prefix']) return $singleNameSpace['prefix']; } return ""; } /** * getHierarchyDelimiter * @var boolean $_useCache * @return string the hierarchyDelimiter */ function getHierarchyDelimiter($_useCache=true) { static $HierarchyDelimiter; if (is_null($HierarchyDelimiter)) $HierarchyDelimiter =& egw_cache::getSession('mail','HierarchyDelimiter'); if ($_useCache===false) unset($HierarchyDelimiter[$this->icServer->ImapServerId]); if (isset($HierarchyDelimiter[$this->icServer->ImapServerId])&&!empty($HierarchyDelimiter[$this->icServer->ImapServerId])) { $this->icServer->mailboxDelimiter = $HierarchyDelimiter[$this->icServer->ImapServerId]; return $HierarchyDelimiter[$this->icServer->ImapServerId]; } $HierarchyDelimiter[$this->icServer->ImapServerId] = '/'; if(($this->icServer instanceof defaultimap)) { $HierarchyDelimiter[$this->icServer->ImapServerId] = $this->icServer->getHierarchyDelimiter(); if (PEAR::isError($HierarchyDelimiter[$this->icServer->ImapServerId])) $HierarchyDelimiter[$this->icServer->ImapServerId] = '/'; } $this->icServer->mailboxDelimiter = $HierarchyDelimiter[$this->icServer->ImapServerId]; return $HierarchyDelimiter[$this->icServer->ImapServerId]; } /** * getSpecialUseFolders * @ToDo: could as well be static, when icServer is passed * @return mixed null/array */ function getSpecialUseFolders() { //error_log(__METHOD__.__LINE__.':'.$this->icServer->ImapServerId.' Connected:'.$this->icServer->_connected); static $_specialUseFolders; if (is_null($_specialUseFolders)||empty($_specialUseFolders)) $_specialUseFolders = egw_cache::getCache(egw_cache::INSTANCE,'email','specialUseFolders'.trim($GLOBALS['egw_info']['user']['account_id']),$callback=null,$callback_params=array(),$expiration=60*60*24*5); if (isset($_specialUseFolders[$this->icServer->ImapServerId]) &&!empty($_specialUseFolders[$this->icServer->ImapServerId])) { if(($this->icServer instanceof defaultimap)) { //error_log(__METHOD__.__LINE__.array2string($specialUseFolders[$this->icServer->ImapServerId])); // array('Drafts', 'Templates', 'Sent', 'Trash', 'Junk', 'Outbox'); if (empty($this->icServer->trashfolder) && ($f = array_search('Trash',(array)$_specialUseFolders[$this->icServer->ImapServerId]))) $this->icServer->trashfolder = $f; if (empty($this->icServer->draftfolder) && ($f = array_search('Drafts',(array)$_specialUseFolders[$this->icServer->ImapServerId]))) $this->icServer->draftfolder = $f; if (empty($this->icServer->sentfolder) && ($f = array_search('Sent',(array)$_specialUseFolders[$this->icServer->ImapServerId]))) $this->icServer->sentfolder = $f; if (empty($this->icServer->templatefolder) && ($f = array_search('Templates',(array)$_specialUseFolders[$this->icServer->ImapServerId]))) $this->icServer->templatefolder = $f; } //error_log(__METHOD__.__LINE__.array2string($_specialUseFolders[$this->icServer->ImapServerId])); self::$specialUseFolders = $_specialUseFolders[$this->icServer->ImapServerId]; // make sure this one is set on function call return $_specialUseFolders[$this->icServer->ImapServerId]; } if(($this->icServer instanceof defaultimap) && $this->icServer->_connected) { //error_log(__METHOD__.__LINE__); if(($this->hasCapability('SPECIAL-USE'))) { //error_log(__METHOD__.__LINE__); $ret = $this->icServer->getSpecialUseFolders(); if (PEAR::isError($ret)) { $_specialUseFolders[$this->icServer->ImapServerId]=array(); } else { foreach ($ret as $k => $f) { if (isset($f['ATTRIBUTES']) && !empty($f['ATTRIBUTES']) && !in_array('\\NonExistent',$f['ATTRIBUTES'])) { foreach (self::$autoFolders as $i => $n) // array('Drafts', 'Templates', 'Sent', 'Trash', 'Junk', 'Outbox'); { if (in_array('\\'.$n,$f['ATTRIBUTES'])) $_specialUseFolders[$this->icServer->ImapServerId][$f['MAILBOX']] = $n; } } } } egw_cache::setCache(egw_cache::INSTANCE,'email','specialUseFolders'.trim($GLOBALS['egw_info']['user']['account_id']),$_specialUseFolders, $expiration=60*60*24*5); } //error_log(__METHOD__.__LINE__.array2string($_specialUseFolders[$this->icServer->ImapServerId])); if (empty($this->icServer->trashfolder) && ($f = array_search('Trash',(array)$_specialUseFolders[$this->icServer->ImapServerId]))) $this->icServer->trashfolder = $f; if (empty($this->icServer->draftfolder) && ($f = array_search('Drafts',(array)$_specialUseFolders[$this->icServer->ImapServerId]))) $this->icServer->draftfolder = $f; if (empty($this->icServer->sentfolder) && ($f = array_search('Sent',(array)$_specialUseFolders[$this->icServer->ImapServerId]))) $this->icServer->sentfolder = $f; if (empty($this->icServer->templatefolder) && ($f = array_search('Templates',(array)$_specialUseFolders[$this->icServer->ImapServerId]))) $this->icServer->templatefolder = $f; } self::$specialUseFolders = $_specialUseFolders[$this->icServer->ImapServerId]; // make sure this one is set on function call //error_log(__METHOD__.__LINE__.array2string($_specialUseFolders[$this->icServer->ImapServerId])); return $_specialUseFolders[$this->icServer->ImapServerId]; } /** * get IMAP folder status regarding NoSelect * * returns true or false regarding the noselect attribute * * @param foldertoselect string the foldername * * @return boolean */ function folderIsSelectable($folderToSelect) { $retval = true; if($folderToSelect && ($folderStatus = $this->getFolderStatus($folderToSelect,false,true))) { if ($folderStatus instanceof PEAR_Error) return false; if (stripos(array2string($folderStatus['attributes']),'noselect')!==false) { $retval = false; } } return $retval; } /** * get IMAP folder status, wrapper to store results within a single request * * returns an array information about the imap folder * * @param folderName string the foldername * @param ignoreStatusCache bool ignore the cache used for counters * * @return array */ function _getStatus($folderName,$ignoreStatusCache=false) { static $folderStatus; if (!$ignoreStatusCache && isset($folderStatus[$this->icServer->ImapServerId][$folderName])) { //error_log(__METHOD__.__LINE__.' Using cache for status on Server:'.$this->icServer->ImapServerId.' for folder:'.$folderName.'->'.array2string($folderStatus[$this->icServer->ImapServerId][$folderName])); return $folderStatus[$this->icServer->ImapServerId][$folderName]; } $folderStatus[$this->icServer->ImapServerId][$folderName] = $this->icServer->getStatus($folderName); return $folderStatus[$this->icServer->ImapServerId][$folderName]; } /** * get IMAP folder status * * returns an array information about the imap folder, may be used as wrapper to retrieve results from cache * * @param _folderName string the foldername * @param ignoreStatusCache bool ignore the cache used for counters * @param basicInfoOnly bool retrieve only names and stuff returned by getMailboxes * * @return array */ function getFolderStatus($_folderName,$ignoreStatusCache=false,$basicInfoOnly=false) { if (self::$debug) error_log(__METHOD__." called with:".$_folderName); if (!is_string($_folderName) || empty($_folderName)) // something is wrong. Do not proceed { return false; } static $folderInfoCache; // reduce traffic on single request static $folderBasicInfo; if (is_null($folderBasicInfo)) { $folderBasicInfo = egw_cache::getCache(egw_cache::INSTANCE,'email','folderBasicInfo'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),$expiration=60*60*1); $folderInfoCache = $folderBasicInfo[$this->profileID]; } if (isset($folderInfoCache[$_folderName]) && $ignoreStatusCache==false && $basicInfoOnly) return $folderInfoCache[$_folderName]; $retValue = array(); $retValue['subscribed'] = false; if(!$icServer = $this->mailPreferences->getIncomingServer($this->profileID)) { if (self::$debug) error_log(__METHOD__." no Server found for Folder:".$_folderName); return false; } // does the folder exist??? if (is_null($folderInfoCache) || !isset($folderInfoCache[$_folderName])) $folderInfoCache[$_folderName] = $this->icServer->getMailboxes('', $_folderName, true); $folderInfo = $folderInfoCache[$_folderName]; //error_log(__METHOD__.__LINE__.array2string($folderInfo).'->'.function_backtrace()); if(($folderInfo instanceof PEAR_Error) || !is_array($folderInfo[0])) { if (self::$debug||$folderInfo instanceof PEAR_Error) error_log(__METHOD__." returned Info for folder $_folderName:".print_r($folderInfo->message,true)); if ( ($folderInfo instanceof PEAR_Error) || PEAR::isError($r = $this->_getStatus($_folderName)) || $r == 0) return false; if (!is_array($folderInfo[0])) { // no folder info, but there is a status returned for the folder: something is wrong, try to cope with it $folderInfo = array(0 => array('HIERACHY_DELIMITER'=>$this->getHierarchyDelimiter(), 'ATTRIBUTES' => '')); } } #if(!is_array($folderInfo[0])) { # return false; #} $retValue['delimiter'] = $folderInfo[0]['HIERACHY_DELIMITER']; $retValue['attributes'] = $folderInfo[0]['ATTRIBUTES']; $shortNameParts = explode($retValue['delimiter'], $_folderName); $retValue['shortName'] = array_pop($shortNameParts); $retValue['displayName'] = $this->encodeFolderName($_folderName); $retValue['shortDisplayName'] = $this->encodeFolderName($retValue['shortName']); if(strtoupper($retValue['shortName']) == 'INBOX') { $retValue['displayName'] = lang('INBOX'); $retValue['shortDisplayName'] = lang('INBOX'); } // translate the automatic Folders (Sent, Drafts, ...) like the INBOX elseif (in_array($retValue['shortName'],self::$autoFolders)) { $retValue['displayName'] = $retValue['shortDisplayName'] = lang($retValue['shortName']); } if (!($folderInfo instanceof PEAR_Error)) $folderBasicInfo[$this->profileID][$_folderName]=$retValue; egw_cache::setCache(egw_cache::INSTANCE,'email','folderBasicInfo'.trim($GLOBALS['egw_info']['user']['account_id']),$folderBasicInfo,$expiration=60*60*1); if ($basicInfoOnly || stripos(array2string($retValue['attributes']),'noselect')!==false) { return $retValue; } $subscribedFolders = $this->icServer->listsubscribedMailboxes('', $_folderName); if(is_array($subscribedFolders) && count($subscribedFolders) == 1) { $retValue['subscribed'] = true; } if ( PEAR::isError($folderStatus = $this->_getStatus($_folderName,$ignoreStatusCache)) ) { if (self::$debug) error_log(__METHOD__." returned folderStatus for Folder $_folderName:".print_r($folderStatus->message,true)); } else { $nameSpace = $this->_getNameSpaces(); if (isset($nameSpace['personal'])) unset($nameSpace['personal']); $prefix = $this->getFolderPrefixFromNamespace($nameSpace, $_folderName); $retValue['messages'] = $folderStatus['MESSAGES']; $retValue['recent'] = $folderStatus['RECENT']; $retValue['uidnext'] = $folderStatus['UIDNEXT']; $retValue['uidvalidity'] = $folderStatus['UIDVALIDITY']; $retValue['unseen'] = $folderStatus['UNSEEN']; if (//$retValue['unseen']==0 && (isset($this->mailPreferences->preferences['trustServersUnseenInfo']) && // some servers dont serve the UNSEEN information $this->mailPreferences->preferences['trustServersUnseenInfo']==false) || (isset($this->mailPreferences->preferences['trustServersUnseenInfo']) && $this->mailPreferences->preferences['trustServersUnseenInfo']==2 && $prefix != '' && stripos($_folderName,$prefix) !== false) ) { //error_log(__METHOD__." returned folderStatus for Folder $_folderName:".print_r($prefix,true).' TS:'.$this->mailPreferences->preferences['trustServersUnseenInfo']); // we filter for the combined status of unseen and undeleted, as this is what we show in list $sortResult = $this->getSortedList($_folderName, $_sort=0, $_reverse=1, $_filter=array('status'=>array('UNSEEN','UNDELETED')),$byUid=true,false); $retValue['unseen'] = count($sortResult); } } return $retValue; } /** * getHeaders * * this function is a wrapper function for getSortedList and populates the resultList thereof with headerdata * * @param string $_folderName, * @param int $_startMessage, * @param int $_numberOfMessages, number of messages to return * @param array $_sort, sort by criteria * @param boolean $_reverse, reverse sorting of the result array (may be switched, as it is passed to getSortedList by reference) * @param array $_filter, filter to apply to getSortedList * @param mixed $_thisUIDOnly=null, if given fetch the headers of this uid only (either one, or array of uids) * @param boolean $_cacheResult=true try touse the cache of getSortedList * @return array result as array(header=>array,total=>int,first=>int,last=>int) */ function getHeaders($_folderName, $_startMessage, $_numberOfMessages, $_sort, $_reverse, $_filter, $_thisUIDOnly=null, $_cacheResult=true) { //self::$debug=true; if (self::$debug) error_log(__METHOD__.__LINE__.function_backtrace()); if (self::$debug) error_log(__METHOD__.__LINE__."$_folderName,$_startMessage, $_numberOfMessages, $_sort, $_reverse, ".array2string($_filter).", $_thisUIDOnly"); $reverse = (bool)$_reverse; // get the list of messages to fetch if (self::$debug) $starttime = microtime (true); $this->reopen($_folderName); if (self::$debug) { $endtime = microtime(true) - $starttime; error_log(__METHOD__. " time used for reopen: ".$endtime.' for Folder:'.$_folderName); } //$currentFolder = $this->icServer->getCurrentMailbox(); //if ($currentFolder != $_folderName); $this->icServer->selectMailbox($_folderName); $rByUid = true; // try searching by uid. this var will be passed by reference to getSortedList, and may be set to false, if UID retrieval fails #print "
";
		#$this->icServer->setDebug(true);
		if ($_thisUIDOnly === null)
		{
			if (($_startMessage || $_numberOfMessages) && !isset($_filter['range']))
			{
				// this will not work we must calculate the range we want to retieve as e.g.: 0:20 retirieves the first 20 mails and sorts them
				// if sort capability is applied to the range fetched, not sort first and fetch the range afterwards
				$start = $_startMessage-1;
				$end = $_startMessage-1+$_numberOfMessages;
				//$_filter['range'] ="$start:$end";
				//$_filter['range'] ="$_startMessage:*";
			}
			if (self::$debug) error_log(__METHOD__.__LINE__."$_folderName, $_sort, $reverse, ".array2string($_filter).", $rByUid");
			if (self::$debug) $starttime = microtime (true);
			$sortResult = $this->getSortedList($_folderName, $_sort, $reverse, $_filter, $rByUid, $_cacheResult);
			if (self::$debug)
			{
				$endtime = microtime(true) - $starttime;
				error_log(__METHOD__. " time used for getSortedList: ".$endtime.' for Folder:'.$_folderName.' Filter:'.array2string($_filter).' Ids:'.array2string($_thisUIDOnly));
			}
			if (self::$debug) error_log(__METHOD__.__LINE__.array2string($sortResult));
			#$this->icServer->setDebug(false);
			#print "
"; // nothing found if(!is_array($sortResult) || empty($sortResult)) { $retValue = array(); $retValue['info']['total'] = 0; $retValue['info']['first'] = 0; $retValue['info']['last'] = 0; return $retValue; } $total = count($sortResult); #_debug_array($sortResult); #_debug_array(array_slice($sortResult, -5, -2)); //error_log("REVERSE: $reverse"); if($reverse === true) { if ($_startMessage<=$total) { $startMessage = $_startMessage-1; } else { //error_log(__METHOD__.__LINE__.' Start:'.$_startMessage.' NumberOfMessages:'.$_numberOfMessages.' Total:'.$total); if ($_startMessage+$_numberOfMessages>$total) { $numberOfMessages = $total%$_numberOfMessages; //$numberOfMessages = abs($_startMessage-$total-1); if ($numberOfMessages>0 && $numberOfMessages<=$_numberOfMessages) $_numberOfMessages = $numberOfMessages; //error_log(__METHOD__.__LINE__.' Start:'.$_startMessage.' NumberOfMessages:'.$_numberOfMessages.' Total:'.$total); } $startMessage=($total-$_numberOfMessages)-1; //$retValue['info']['first'] = $startMessage; //$retValue['info']['last'] = $total; } if ($startMessage+$_numberOfMessages>$total) { $_numberOfMessages = $_numberOfMessages-($total-($startMessage+$_numberOfMessages)); //$retValue['info']['first'] = $startMessage; //$retValue['info']['last'] = $total; } if($startMessage > 0) { if (self::$debug) error_log(__METHOD__.__LINE__.' StartMessage:'.(-($_numberOfMessages+$startMessage)).', '.-$startMessage.' Number of Messages:'.count($sortResult)); $sortResult = array_slice($sortResult, -($_numberOfMessages+$startMessage), -$startMessage); } else { if (self::$debug) error_log(__METHOD__.__LINE__.' StartMessage:'.(-($_numberOfMessages+($_startMessage-1))).', AllTheRest, Number of Messages:'.count($sortResult)); $sortResult = array_slice($sortResult, -($_numberOfMessages+($_startMessage-1))); } $sortResult = array_reverse($sortResult); } else { if (self::$debug) error_log(__METHOD__.__LINE__.' StartMessage:'.($_startMessage-1).', '.$_numberOfMessages.' Number of Messages:'.count($sortResult)); $sortResult = array_slice($sortResult, $_startMessage-1, $_numberOfMessages); } if (self::$debug) error_log(__METHOD__.__LINE__.array2string($sortResult)); } else { $sortResult = (is_array($_thisUIDOnly) ? $_thisUIDOnly:(array)$_thisUIDOnly); } $queryString = implode(',', $sortResult); // fetch the data for the selected messages if (self::$debug) $starttime = microtime(true); $headersNew = $this->icServer->getSummary($queryString, $rByUid); if (PEAR::isError($headersNew) && empty($queryString)) { $headersNew = array(); $sortResult = array(); } if ($headersNew == null && empty($_thisUIDOnly)) // -> if we request uids, do not try to look for messages with ids { if (self::$debug) error_log(__METHOD__.__LINE__."Uid->$queryString, ByUID? $rByUid"); // message retrieval via uid failed try one by one via message number $rByUid = false; foreach($sortResult as $k => $v) { if (self::$debug) error_log(__METHOD__.__LINE__.' Query:'.$v.':*'); $rv = $this->icServer->getSummary($v.':*', $rByUid); $headersNew[] = $rv[0]; } } if (self::$debug) { $endtime = microtime(true) - $starttime; error_log(__METHOD__. " time used for getSummary: ".$endtime.' for Folder:'.$_folderName.' Filter:'.array2string($_filter)); error_log(__METHOD__.__LINE__.' Query:'.$queryString.' Result:'.array2string($headersNew)); } $count = 0; foreach((array)$sortResult as $uid) { $sortOrder[$uid] = $count++; } $count = 0; if (is_array($headersNew)) { if (self::$debug) $starttime = microtime(true); foreach((array)$headersNew as $headerObject) { //if($count == 0) error_log(__METHOD__.array2string($headerObject)); if (empty($headerObject['UID'])) continue; $uid = ($rByUid ? $headerObject['UID'] : $headerObject['MSG_NUM']); // make dates like "Mon, 23 Apr 2007 10:11:06 UT" working with strtotime if(substr($headerObject['DATE'],-2) === 'UT') { $headerObject['DATE'] .= 'C'; } if(substr($headerObject['INTERNALDATE'],-2) === 'UT') { $headerObject['INTERNALDATE'] .= 'C'; } //error_log(__METHOD__.__LINE__.' '.$headerObject['SUBJECT'].'->'.$headerObject['DATE']); //error_log(__METHOD__.__LINE__.' '.$this->decode_subject($headerObject['SUBJECT']).'->'.$headerObject['DATE']); $retValue['header'][$sortOrder[$uid]]['subject'] = $this->decode_subject($headerObject['SUBJECT']); $retValue['header'][$sortOrder[$uid]]['size'] = $headerObject['SIZE']; $retValue['header'][$sortOrder[$uid]]['date'] = self::_strtotime(($headerObject['DATE']&&!($headerObject['DATE']=='NIL')?$headerObject['DATE']:$headerObject['INTERNALDATE']),'ts',true); $retValue['header'][$sortOrder[$uid]]['internaldate']= self::_strtotime($headerObject['INTERNALDATE'],'ts',true); $retValue['header'][$sortOrder[$uid]]['mimetype'] = $headerObject['MIMETYPE']; $retValue['header'][$sortOrder[$uid]]['id'] = $headerObject['MSG_NUM']; $retValue['header'][$sortOrder[$uid]]['uid'] = $headerObject['UID']; $retValue['header'][$sortOrder[$uid]]['priority'] = ($headerObject['PRIORITY']?$headerObject['PRIORITY']:3); if (is_array($headerObject['FLAGS'])) { $retValue['header'][$sortOrder[$uid]]['recent'] = in_array('\\Recent', $headerObject['FLAGS']); $retValue['header'][$sortOrder[$uid]]['flagged'] = in_array('\\Flagged', $headerObject['FLAGS']); $retValue['header'][$sortOrder[$uid]]['answered'] = in_array('\\Answered', $headerObject['FLAGS']); $retValue['header'][$sortOrder[$uid]]['forwarded'] = in_array('$Forwarded', $headerObject['FLAGS']); $retValue['header'][$sortOrder[$uid]]['deleted'] = in_array('\\Deleted', $headerObject['FLAGS']); $retValue['header'][$sortOrder[$uid]]['seen'] = in_array('\\Seen', $headerObject['FLAGS']); $retValue['header'][$sortOrder[$uid]]['draft'] = in_array('\\Draft', $headerObject['FLAGS']); $retValue['header'][$sortOrder[$uid]]['mdnsent'] = in_array('MDNSent', $headerObject['FLAGS']); $retValue['header'][$sortOrder[$uid]]['mdnnotsent'] = in_array('MDNnotSent', $headerObject['FLAGS']); if (is_array($headerObject['FLAGS'])) $headerFlags = array_map('strtolower',$headerObject['FLAGS']); if (!empty($headerFlags)) { $retValue['header'][$sortOrder[$uid]]['label1'] = in_array('$label1', $headerFlags); $retValue['header'][$sortOrder[$uid]]['label2'] = in_array('$label2', $headerFlags); $retValue['header'][$sortOrder[$uid]]['label3'] = in_array('$label3', $headerFlags); $retValue['header'][$sortOrder[$uid]]['label4'] = in_array('$label4', $headerFlags); $retValue['header'][$sortOrder[$uid]]['label5'] = in_array('$label5', $headerFlags); } } if(is_array($headerObject['FROM']) && is_array($headerObject['FROM'][0])) { if($headerObject['FROM'][0]['HOST_NAME'] != 'NIL') { $retValue['header'][$sortOrder[$uid]]['sender_address'] = self::decode_header($headerObject['FROM'][0]['EMAIL'],true); } else { $retValue['header'][$sortOrder[$uid]]['sender_address'] = self::decode_header($headerObject['FROM'][0]['MAILBOX_NAME'],true); } if($headerObject['FROM'][0]['PERSONAL_NAME'] != 'NIL') { $retValue['header'][$sortOrder[$uid]]['sender_name'] = self::decode_header($headerObject['FROM'][0]['PERSONAL_NAME']); } } if(is_array($headerObject['TO']) && is_array($headerObject['TO'][0])) { if($headerObject['TO'][0]['HOST_NAME'] != 'NIL') { $retValue['header'][$sortOrder[$uid]]['to_address'] = self::decode_header($headerObject['TO'][0]['EMAIL'],true); } else { $retValue['header'][$sortOrder[$uid]]['to_address'] = self::decode_header($headerObject['TO'][0]['MAILBOX_NAME'],true); } if($headerObject['TO'][0]['PERSONAL_NAME'] != 'NIL') { $retValue['header'][$sortOrder[$uid]]['to_name'] = self::decode_header($headerObject['TO'][0]['PERSONAL_NAME']); } if (count($headerObject['TO'])>1) { $ki=0; foreach($headerObject['TO'] as $k => $add) { if ($k==0) continue; //error_log(__METHOD__.__LINE__."-> $k:".array2string($add)); if($add['HOST_NAME'] != 'NIL') { $retValue['header'][$sortOrder[$uid]]['additional_to_addresses'][$ki]['address'] = self::decode_header($add['EMAIL'],true); } else { $retValue['header'][$sortOrder[$uid]]['additional_to_addresses'][$ki]['address'] = self::decode_header($add['MAILBOX_NAME'],true); } if($headerObject['TO'][$k]['PERSONAL_NAME'] != 'NIL') { $retValue['header'][$sortOrder[$uid]]['additional_to_addresses'][$ki]['name'] = self::decode_header($add['PERSONAL_NAME']); } //error_log(__METHOD__.__LINE__.array2string($retValue['header'][$sortOrder[$uid]]['additional_to_addresses'][$ki])); $ki++; } } } $count++; } if (self::$debug) { $endtime = microtime(true) - $starttime; error_log(__METHOD__. " time used for the rest: ".$endtime.' for Folder:'.$_folderName); } //self::$debug=false; // sort the messages to the requested displayorder if(is_array($retValue['header'])) { $countMessages = $total; if (isset($_filter['range'])) $countMessages = $this->sessionData['folderStatus'][$this->profileID][$_folderName]['messages']; ksort($retValue['header']); $retValue['info']['total'] = $total; //if ($_startMessage>$total) $_startMessage = $total-($count-1); $retValue['info']['first'] = $_startMessage; $retValue['info']['last'] = $_startMessage + $count - 1 ; return $retValue; } else { $retValue = array(); $retValue['info']['total'] = 0; $retValue['info']['first'] = 0; $retValue['info']['last'] = 0; return $retValue; } } else { if ($headersNew == null && empty($_thisUIDOnly)) error_log(__METHOD__." -> retrieval of Message Details to Query $queryString failed: ".print_r($headersNew,TRUE)); $retValue = array(); $retValue['info']['total'] = 0; $retValue['info']['first'] = 0; $retValue['info']['last'] = 0; return $retValue; } } /** * fetches a sorted list of messages from the imap server * private function * * @todo implement sort based on Net_IMAP * @param string $_folderName the name of the folder in which the messages get searched * @param integer $_sort the primary sort key * @param bool $_reverse sort the messages ascending or descending * @param array $_filter the search filter * @param bool $resultByUid if set to true, the result is to be returned by uid, if the server does not reply * on a query for uids, the result may be returned by IDs only, this will be indicated by this param * @param bool $setSession if set to true the session will be populated with the result of the query * @return mixed bool/array false or array of ids */ function getSortedList($_folderName, $_sort, &$_reverse, $_filter, &$resultByUid=true, $setSession=true) { //ToDo: FilterSpecific Cache if(PEAR::isError($folderStatus = $this->icServer->examineMailbox($_folderName))) { //if (stripos($folderStatus->message,'not connected') !== false); error_log(__METHOD__.__LINE__.$folderStatus->message); return false; } //error_log(__METHOD__.__LINE__.array2string($folderStatus)); //error_log(__METHOD__.__LINE__.' Filter:'.array2string($_filter)); $try2useCache = true; static $eMailListContainsDeletedMessages; if (is_null($eMailListContainsDeletedMessages)) $eMailListContainsDeletedMessages = egw_cache::getCache(egw_cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($GLOBALS['egw_info']['user']['account_id']),$callback=null,$callback_params=array(),$expiration=60*60*1); // this indicates, that there is no Filter set, and the returned set/subset should not contain DELETED Messages, nor filtered for UNDELETED if ($setSession==true && ((strpos(array2string($_filter), 'UNDELETED') === false && strpos(array2string($_filter), 'DELETED') === false))) { //$starttime = microtime(true); //$deletedMessages = $this->getSortedList($_folderName, $_sort=0, $_reverse=1, $_filter=array('status'=>array('DELETED')),$byUid=true,false); $deletedMessages = $this->getSortedList($_folderName, 0, $three=1, array('status'=>array('DELETED')),$five=true,false); //error_log(__METHOD__.__LINE__.array2string($deletedMessages)); $eMailListContainsDeletedMessages[$this->profileID][$_folderName] = count($deletedMessages); egw_cache::setCache(egw_cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($GLOBALS['egw_info']['user']['account_id']),$eMailListContainsDeletedMessages, $expiration=60*60*1); //$endtime = microtime(true); //$r = ($endtime-$starttime); //error_log(__METHOD__.__LINE__.' Profile:'.$this->profileID.' Folder:'.$_folderName.' -> EXISTS/SessStat:'.array2string($folderStatus['EXISTS']).'/'.$this->sessionData['folderStatus'][$this->profileID][$_folderName]['messages'].' ListContDelMsg/SessDeleted:'.$eMailListContainsDeletedMessages[$this->profileID][$_folderName].'/'.$this->sessionData['folderStatus'][$this->profileID][$_folderName]['deleted']); //error_log(__METHOD__.__LINE__.' Took:'.$r.'(s) setting eMailListContainsDeletedMessages for Profile:'.$this->profileID.' Folder:'.$_folderName.' to '.$eMailListContainsDeletedMessages[$this->profileID][$_folderName]); } if (self::$debug) { error_log(__METHOD__.__LINE__.' Profile:'.$this->profileID.' Folder:'.$_folderName.' -> EXISTS/SessStat:'.array2string($folderStatus['EXISTS']).'/'.$this->sessionData['folderStatus'][$this->profileID][$_folderName]['messages'].' ListContDelMsg/SessDeleted:'.$eMailListContainsDeletedMessages[$this->profileID][$_folderName].'/'.$this->sessionData['folderStatus'][$this->profileID][$_folderName]['deleted']); error_log(__METHOD__.__LINE__.' CachedFolderStatus:'.array2string($this->sessionData['folderStatus'][$this->profileID][$_folderName])); } if($try2useCache && (is_array($this->sessionData['folderStatus'][$this->profileID][$_folderName]) && $this->sessionData['folderStatus'][$this->profileID][$_folderName]['uidValidity'] === $folderStatus['UIDVALIDITY'] && $this->sessionData['folderStatus'][$this->profileID][$_folderName]['messages'] == $folderStatus['EXISTS'] && $this->sessionData['folderStatus'][$this->profileID][$_folderName]['deleted'] == $eMailListContainsDeletedMessages[$this->profileID][$_folderName] && $this->sessionData['folderStatus'][$this->profileID][$_folderName]['uidnext'] === $folderStatus['UIDNEXT'] && $this->sessionData['folderStatus'][$this->profileID][$_folderName]['filter'] === $_filter && $this->sessionData['folderStatus'][$this->profileID][$_folderName]['sort'] === $_sort && //$this->sessionData['folderStatus'][0][$_folderName]['reverse'] === $_reverse && !empty($this->sessionData['folderStatus'][$this->profileID][$_folderName]['sortResult'])) ) { if (self::$debug) error_log(__METHOD__." USE CACHE for Profile:". $this->profileID." Folder:".$_folderName.'->'.($setSession?'setSession':'checkrun').' Filter:'.array2string($_filter).function_backtrace()); $sortResult = $this->sessionData['folderStatus'][$this->profileID][$_folderName]['sortResult']; } else { $try2useCache = false; //error_log(__METHOD__." USE NO CACHE for Profile:". $this->profileID." Folder:".$_folderName.'->'.($setSession?'setSession':'checkrun')); if (self::$debug) error_log(__METHOD__." USE NO CACHE for Profile:". $this->profileID." Folder:".$_folderName." Filter:".array2string($_filter).function_backtrace()); $filter = $this->createIMAPFilter($_folderName, $_filter); //_debug_array($filter); if($this->icServer->hasCapability('SORT')) { if (self::$debug) error_log(__METHOD__." Mailserver has SORT Capability, SortBy: $_sort Reverse: $_reverse"); $sortOrder = $this->_getSortString($_sort, $_reverse); if ($_reverse && strpos($sortOrder,'REVERSE')!==false) $_reverse=false; // as we reversed the result already if (self::$debug) error_log(__METHOD__." Mailserver runs SORT: SortBy: $sortOrder Filter: $filter"); if (!empty(self::$displayCharset)) { $sortResult = $this->icServer->sort($sortOrder, strtoupper( self::$displayCharset ), $filter, $resultByUid); } if (PEAR::isError($sortResult) || empty(self::$displayCharset)) { $sortResult = $this->icServer->sort($sortOrder, 'US-ASCII', $filter, $resultByUid); // if there is an PEAR Error, we assume that the server is not capable of sorting if (PEAR::isError($sortResult)) { $advFilter = 'CHARSET '. strtoupper(self::$displayCharset) .' '.$filter; if (PEAR::isError($sortResult)) { $resultByUid = false; $sortResult = $this->icServer->search($filter, $resultByUid); if (PEAR::isError($sortResult)) { $sortResult = $this->sessionData['folderStatus'][$this->profileID][$_folderName]['sortResult']; } } } } if (self::$debug) error_log(__METHOD__.print_r($sortResult,true)); } else { if (self::$debug) error_log(__METHOD__." Mailserver has NO SORT Capability"); $advFilter = 'CHARSET '. strtoupper(self::$displayCharset) .' '.$filter; $sortResult = $this->icServer->search($advFilter, $resultByUid); if (PEAR::isError($sortResult)) { $sortResult = $this->icServer->search($filter, $resultByUid); if (PEAR::isError($sortResult)) { // some servers are not replying on a search for uids, so try this one $resultByUid = false; $sortResult = $this->icServer->search('*', $resultByUid); if (PEAR::isError($sortResult)) { error_log(__METHOD__.__LINE__.' PEAR_Error:'.array2string($sortResult->message)); $sortResult = null; } } } if(is_array($sortResult)) { sort($sortResult, SORT_NUMERIC); } if (self::$debug) error_log(__METHOD__." using Filter:".print_r($filter,true)." ->".print_r($sortResult,true)); } if ($setSession) { $this->sessionData['folderStatus'][$this->profileID][$_folderName]['uidValidity'] = $folderStatus['UIDVALIDITY']; $this->sessionData['folderStatus'][$this->profileID][$_folderName]['messages'] = $folderStatus['EXISTS']; $this->sessionData['folderStatus'][$this->profileID][$_folderName]['uidnext'] = $folderStatus['UIDNEXT']; $this->sessionData['folderStatus'][$this->profileID][$_folderName]['filter'] = $_filter; $this->sessionData['folderStatus'][$this->profileID][$_folderName]['sortResult'] = $sortResult; $this->sessionData['folderStatus'][$this->profileID][$_folderName]['sort'] = $_sort; } } if ($setSession) { // this indicates, that there should be no UNDELETED Messages in the returned set/subset if (((strpos(array2string($_filter), 'UNDELETED') === false && strpos(array2string($_filter), 'DELETED') === false))) { if ($try2useCache == false) $this->sessionData['folderStatus'][$this->profileID][$_folderName]['deleted'] = $eMailListContainsDeletedMessages[$this->profileID][$_folderName]; } $this->sessionData['folderStatus'][$this->profileID][$_folderName]['reverse'] = $_reverse; $this->saveSessionData(); } return $sortResult; } /** * convert the sort value from the gui(integer) into a string * * @param mixed _sort the integer sort order / or a valid and handeled SORTSTRING (right now: UID/ARRIVAL/INTERNALDATE (->ARRIVAL)) * @param bool _reverse wether to add REVERSE to the Sort String or not * @return the ascii sort string */ function _getSortString($_sort, $_reverse=false) { $_reverse=false; if (is_numeric($_sort)) { switch($_sort) { case 2: $retValue = 'FROM'; break; case 4: $retValue = 'TO'; break; case 3: $retValue = 'SUBJECT'; break; case 6: $retValue = 'SIZE'; break; case 0: default: $retValue = 'DATE'; //$retValue = 'ARRIVAL'; break; } } else { switch($_sort) { case 'UID': // should be equivalent to INTERNALDATE, which is ARRIVAL, which should be highest (latest) uid should be newest date case 'ARRIVAL': case 'INTERNALDATE': $retValue = 'ARRIVAL'; break; default: $retValue = 'DATE'; break; } } //error_log(__METHOD__.__LINE__.' '.($_reverse?'REVERSE ':'').$_sort.'->'.$retValue); return ($_reverse?'REVERSE ':'').$retValue; } /** * this function creates an IMAP filter from the criterias given * * @param string $_folder used to determine the search to TO or FROM on QUICK Search wether it is a send-folder or not * @param array $_criterias contains the search/filter criteria * @return string the IMAP filter */ function createIMAPFilter($_folder, $_criterias) { $all = 'ALL UNDELETED'; //'ALL' //_debug_array($_criterias); if (self::$debug) error_log(__METHOD__.__LINE__.' Criterias:'.(!is_array($_criterias)?" none -> returning $all":array2string($_criterias))); if(!is_array($_criterias)) { return $all; } #error_log(print_r($_criterias, true)); $imapFilter = ''; #foreach($_criterias as $criteria => $parameter) { if(!empty($_criterias['string'])) { $criteria = strtoupper($_criterias['type']); switch ($criteria) { case 'QUICK': if($this->isSentFolder($_folder)) { $imapFilter .= 'OR SUBJECT "'. $_criterias['string'] .'" TO "'. $_criterias['string'] .'" '; } else { $imapFilter .= 'OR SUBJECT "'. $_criterias['string'] .'" FROM "'. $_criterias['string'] .'" '; } break; case 'BCC': case 'BODY': case 'CC': case 'FROM': case 'KEYWORD': case 'SUBJECT': case 'TEXT': case 'TO': $imapFilter .= $criteria .' "'. $_criterias['string'] .'" '; break; case 'SINCE': case 'BEFORE': case 'ON': $imapFilter .= $criteria .' '. $_criterias['string'].' '; break; } } foreach((array)$_criterias['status'] as $k => $criteria) { $criteria = strtoupper($criteria); switch ($criteria) { case 'ANSWERED': case 'DELETED': case 'FLAGGED': case 'NEW': case 'OLD': case 'RECENT': case 'SEEN': case 'UNANSWERED': case 'UNDELETED': case 'UNFLAGGED': case 'UNSEEN': $imapFilter .= $criteria .' '; break; case 'KEYWORD1': case 'KEYWORD2': case 'KEYWORD3': case 'KEYWORD4': case 'KEYWORD5': $imapFilter .= "KEYWORD ".'$label'.substr(trim($criteria),strlen('KEYWORD')).' '; break; } } if (isset($_criterias['range']) && !empty($_criterias['range'])) { $imapFilter .= $_criterias['range'].' '; } if (self::$debug) error_log(__METHOD__.__LINE__." Filter: ".($imapFilter?$imapFilter:$all)); if($imapFilter == '') { return $all; } else { return trim($imapFilter); #return 'CHARSET '. strtoupper(self::$displayCharset) .' '. trim($imapFilter); } } /** * decode header (or envelope information) * if array given, note that only values will be converted * @param mixed $_string input to be converted, if array call decode_header recursively on each value * @param mixed/boolean $_tryIDNConversion (true/false AND FORCE): try IDN Conversion on domainparts of emailADRESSES * @return mixed - based on the input type */ static function decode_header($_string, $_tryIDNConversion=false) { if (is_array($_string)) { foreach($_string as $k=>$v) { $_string[$k] = self::decode_header($v, $_tryIDNConversion); } return $_string; } else { $_string = translation::decodeMailHeader($_string,self::$displayCharset); if ($_tryIDNConversion===true && stripos($_string,'@')!==false) { $rfcAddr = imap_rfc822_parse_adrlist($_string,''); if (!isset(self::$idna2)) self::$idna2 = new egw_idna; //$_string = str_replace($rfcAddr[0]->host,self::$idna2->decode($rfcAddr[0]->host),$_string); $_string = imap_rfc822_write_address($rfcAddr[0]->mailbox,self::$idna2->decode($rfcAddr[0]->host),$rfcAddr[0]->personal); } if ($_tryIDNConversion==='FORCE') { //error_log(__METHOD__.__LINE__.'->'.$_string.'='.self::$idna2->decode($_string)); $_string = self::$idna2->decode($_string); } return $_string; } } /** * decode subject * if array given, note that only values will be converted * @param mixed $_string input to be converted, if array call decode_header recursively on each value * @param boolean $decode try decoding * @return mixed - based on the input type */ function decode_subject($_string,$decode=true) { #$string = $_string; if($_string=='NIL') { return 'No Subject'; } if ($decode) $_string = self::decode_header($_string); return $_string; } /** * convert a mailboxname from utf7-imap to displaycharset * * @param string _folderName the foldername * @return string the converted string */ function encodeFolderName($_folderName) { return translation::convert($_folderName, 'UTF7-IMAP', self::$displayCharset); } /** * get IMAP folder objects * * returns an array of IMAP folder objects. Put INBOX folder in first * position. Preserves the folder seperator for later use. The returned * array is indexed using the foldername. Use cachedObjects when retrieving subscribedFolders * * @param boolean _subscribedOnly get subscribed or all folders * @param boolean _getCounters get get messages counters * @param boolean _alwaysGetDefaultFolders this triggers to ignore the possible notavailableautofolders - preference * as activeSync needs all folders like sent, trash, drafts, templates and outbox - if not present devices may crash * * @return array with folder objects. eg.: INBOX => {inbox object} */ function getFolderObjects($_subscribedOnly=false, $_getCounters=false, $_alwaysGetDefaultFolders=false) { if (self::$debug) error_log(__METHOD__.__LINE__.' '."subscribedOnly:$_subscribedOnly, getCounters:$_getCounters, alwaysGetDefaultFolders:$_alwaysGetDefaultFolders"); static $folders2return; if ($_subscribedOnly && $_getCounters===false) { if (is_null($folders2return)) $folders2return = egw_cache::getCache(egw_cache::INSTANCE,'email','folderObjects'.trim($GLOBALS['egw_info']['user']['account_id']),$callback=null,$callback_params=array(),$expiration=60*60*1); if (isset($folders2return[$this->icServer->ImapServerId]) && !empty($folders2return[$this->icServer->ImapServerId])) { //error_log(__METHOD__.__LINE__.' using Cached folderObjects'); return $folders2return[$this->icServer->ImapServerId]; } } $isUWIMAP = false; $delimiter = $this->getHierarchyDelimiter(); $inboxData = new stdClass; $inboxData->name = 'INBOX'; $inboxData->folderName = 'INBOX'; $inboxData->displayName = lang('INBOX'); $inboxData->delimiter = $delimiter; $inboxData->shortFolderName = 'INBOX'; $inboxData->shortDisplayName = lang('INBOX'); $inboxData->subscribed = true; if($_getCounters == true) { $inboxData->counter = self::getMailBoxCounters('INBOX'); } // force unsubscribed by preference showAllFoldersInFolderPane if ($_subscribedOnly == true && isset($this->mailPreferences->preferences['showAllFoldersInFolderPane']) && $this->mailPreferences->preferences['showAllFoldersInFolderPane']==1) { $_subscribedOnly = false; } #$inboxData->attributes = 64; $inboxFolderObject = array('INBOX' => $inboxData); #_debug_array($folders); //$nameSpace = $this->icServer->getNameSpaces(); $nameSpace = $this->_getNameSpaces(); //_debug_array($nameSpace); //_debug_array($delimiter); if(isset($nameSpace['#mh/'])) { // removed the uwimap code // but we need to reintroduce him later // uw imap does not return the attribute of a folder, when requesting subscribed folders only // dovecot has the same problem too } else { if (is_array($nameSpace)) { foreach($nameSpace as $type => $singleNameSpace) { $prefix_present = $nameSpace[$type]['prefix_present']; $foldersNameSpace[$type] = $nameSpace[$type]; if(is_array($singleNameSpace)) { // fetch and sort the subscribed folders $subscribedMailboxes = $this->icServer->listsubscribedMailboxes($foldersNameSpace[$type]['prefix']); if (empty($subscribedMailboxes) && $type == 'shared') { $subscribedMailboxes = $this->icServer->listsubscribedMailboxes('',0); } else { if ($prefix_present=='forced' && $type=='personal') // you cannot trust dovecots assumed prefix { $subscribedMailboxesAll = $this->icServer->listsubscribedMailboxes('',0); if( PEAR::isError($subscribedMailboxesAll) ) continue; foreach ($subscribedMailboxesAll as $ksMA => $sMA) if (!in_array($sMA,$subscribedMailboxes)) $subscribedMailboxes[] = $sMA; } } //echo "subscribedMailboxes";_debug_array($subscribedMailboxes); if( PEAR::isError($subscribedMailboxes) ) { continue; } $foldersNameSpace[$type]['subscribed'] = $subscribedMailboxes; if (is_array($foldersNameSpace[$type]['subscribed'])) sort($foldersNameSpace[$type]['subscribed']); //_debug_array($foldersNameSpace); if ($_subscribedOnly == true) { $foldersNameSpace[$type]['all'] = (is_array($foldersNameSpace[$type]['subscribed']) ? $foldersNameSpace[$type]['subscribed'] :array()); continue; } // only check for Folder in FolderMaintenance for Performance Reasons if(!$_subscribedOnly) { foreach ((array)$foldersNameSpace[$type]['subscribed'] as $folderName) { if ($foldersNameSpace[$type]['prefix'] == $folderName || $foldersNameSpace[$type]['prefix'] == $folderName.$foldersNameSpace[$type]['delimiter']) continue; //echo __METHOD__."Checking $folderName for existence
"; if (!self::folderExists($folderName,true)) { echo("eMail Folder $folderName failed to exist; should be unsubscribed; Trying ..."); error_log(__METHOD__."-> $folderName failed to be here; should be unsubscribed"); if (self::subscribe($folderName, false)) { echo " success."."
" ; } else { echo " failed."."
"; } } } } // fetch and sort all folders //echo $type.'->'.$foldersNameSpace[$type]['prefix'].'->'.($type=='shared'?0:2)."
"; $allMailboxesExt = $this->icServer->getMailboxes($foldersNameSpace[$type]['prefix'],2,true); if( PEAR::isError($allMailboxesExt) ) { error_log(__METHOD__.__LINE__.' Failed to retrieve all Boxes:'.$allMailboxesExt->message); $allMailboxesExt = array(); } if (empty($allMailboxesExt) && $type == 'shared') { $allMailboxesExt = $this->icServer->getMailboxes('',0,true); } else { if ($prefix_present=='forced' && $type=='personal') // you cannot trust dovecots assumed prefix { $allMailboxesExtAll = $this->icServer->getMailboxes('',0,true); foreach ($allMailboxesExtAll as $kaMEA => $aMEA) { if( PEAR::isError($aMEA) ) continue; if (!in_array($aMEA,$allMailboxesExt)) $allMailboxesExt[] = $aMEA; } } } if( PEAR::isError($allMailboxesExt) ) { #echo __METHOD__;_debug_array($allMailboxesExt); continue; } $allMailBoxesExtSorted = array(); foreach ($allMailboxesExt as $mbx) { //echo __METHOD__;_debug_array($mbx); //error_log(__METHOD__.__LINE__.array2string($mbx)); if (isset($allMailBoxesExtSorted[$mbx['MAILBOX']])|| isset($allMailBoxesExtSorted[$mbx['MAILBOX'].$foldersNameSpace[$type]['delimiter']])|| (substr($mbx['MAILBOX'],-1)==$foldersNameSpace[$type]['delimiter'] && isset($allMailBoxesExtSorted[substr($mbx['MAILBOX'],0,-1)])) ) continue; //echo '#'.$mbx['MAILBOX'].':'.array2string($mbx)."#
"; $allMailBoxesExtSorted[$mbx['MAILBOX']] = $mbx; } if (is_array($allMailBoxesExtSorted)) ksort($allMailBoxesExtSorted); //_debug_array($allMailBoxesExtSorted); $allMailboxes = array(); foreach ((array)$allMailBoxesExtSorted as $mbx) { //echo $mbx['MAILBOX']."
"; if (in_array('\HasChildren',$mbx["ATTRIBUTES"]) || in_array('\Haschildren',$mbx["ATTRIBUTES"])) { unset($buff); //$buff = $this->icServer->getMailboxes($mbx['MAILBOX'].$delimiter,0,false); if (!in_array($mbx['MAILBOX'],$allMailboxes)) $buff = self::getMailBoxesRecursive($mbx['MAILBOX'],$delimiter,$foldersNameSpace[$type]['prefix'],1); if( PEAR::isError($buff) ) { continue; } #_debug_array($buff); if (is_array($buff)) $allMailboxes = array_merge($allMailboxes,$buff); } if (!in_array($mbx['MAILBOX'],$allMailboxes)) $allMailboxes[] = $mbx['MAILBOX']; //echo "Result:";_debug_array($allMailboxes); } $foldersNameSpace[$type]['all'] = $allMailboxes; if (is_array($foldersNameSpace[$type]['all'])) sort($foldersNameSpace[$type]['all']); } } } // check for autocreated folders if(isset($foldersNameSpace['personal']['prefix'])) { $personalPrefix = $foldersNameSpace['personal']['prefix']; $personalDelimiter = $foldersNameSpace['personal']['delimiter']; if(!empty($personalPrefix)) { if(substr($personalPrefix, -1) != $personalDelimiter) { $folderPrefix = $personalPrefix . $personalDelimiter; } else { $folderPrefix = $personalPrefix; } } else { if(substr($personalPrefix, -1) != $personalDelimiter) { $folderPrefixAsInbox = 'INBOX' . $personalDelimiter; } else { $folderPrefixAsInbox = 'INBOX'; } } if (!$_alwaysGetDefaultFolders && $this->mailPreferences->preferences['notavailableautofolders'] && !empty($this->mailPreferences->preferences['notavailableautofolders'])) { $foldersToCheck = array_diff(self::$autoFolders,explode(',',$this->mailPreferences->preferences['notavailableautofolders'])); } else { $foldersToCheck = self::$autoFolders; } //error_log(__METHOD__.__LINE__." foldersToCheck:".array2string($foldersToCheck)); //error_log(__METHOD__.__LINE__." foldersToCheck:".array2string( $this->mailPreferences->preferences['sentFolder'])); foreach($foldersToCheck as $personalFolderName) { $folderName = (!empty($personalPrefix) ? $folderPrefix.$personalFolderName : $personalFolderName); //error_log(__METHOD__.__LINE__." foldersToCheck: $personalFolderName / $folderName"); if(!is_array($foldersNameSpace['personal']['all']) || !in_array($folderName, $foldersNameSpace['personal']['all'])) { $createfolder = true; switch($personalFolderName) { case 'Drafts': // => Entwürfe $draftFolder = $this->getDraftFolder(); if ($draftFolder && $draftFolder=='none') $createfolder=false; break; case 'Junk': //] => Spammails if ($this->mailPreferences->preferences['junkFolder'] && $this->mailPreferences->preferences['junkFolder']=='none') $createfolder=false; break; case 'Sent': //] => Gesendet // ToDo: we may need more sophistcated checking here $sentFolder = $this->getSentFolder(); if ($sentFolder && $sentFolder=='none') $createfolder=false; break; case 'Trash': //] => Papierkorb $trashFolder = $this->getTrashFolder(); if ($trashFolder && $trashFolder=='none') $createfolder=false; break; case 'Templates': //] => Vorlagen $templateFolder = $this->getTemplateFolder(); if ($templateFolder && $templateFolder=='none') $createfolder=false; break; case 'Outbox': // Nokia Outbox for activesync //if ($this->mailPreferences->preferences['outboxFolder'] && $this->mailPreferences->preferences['outboxFolder']=='none') $createfolder=false; if ($GLOBALS['egw_info']['user']['apps']['activesync']) $createfolder = true; break; } // check for the foldername as constructed with prefix (or not) if ($createfolder && self::folderExists($folderName)) { $createfolder = false; } // check for the folder as it comes (no prefix) if ($createfolder && $personalFolderName != $folderName && self::folderExists($personalFolderName)) { $createfolder = false; $folderName = $personalFolderName; } // check for the folder as it comes with INBOX prefixed $folderWithInboxPrefixed = $folderPrefixAsInbox.$personalFolderName; if ($createfolder && $folderWithInboxPrefixed != $folderName && self::folderExists($folderWithInboxPrefixed)) { $createfolder = false; $folderName = $folderWithInboxPrefixed; } // now proceed with the folderName that may be altered in the progress of testing for existence if ($createfolder === false && $_alwaysGetDefaultFolders) { if (!in_array($folderName,$foldersNameSpace['personal']['all'])) $foldersNameSpace['personal']['all'][] = $folderName; if (!in_array($folderName,$foldersNameSpace['personal']['subscribed'])) $foldersNameSpace['personal']['subscribed'][] = $folderName; } if($createfolder === true && $this->createFolder('', $folderName, true)) { $foldersNameSpace['personal']['all'][] = $folderName; $foldersNameSpace['personal']['subscribed'][] = $folderName; } else { #print "FOLDERNAME failed: $folderName
"; } } } } } //echo "
FolderNameSpace To Process:";_debug_array($foldersNameSpace); $autoFolderObjects = array(); foreach( array('personal', 'others', 'shared') as $type) { if(isset($foldersNameSpace[$type])) { if($_subscribedOnly) { if( !PEAR::isError($foldersNameSpace[$type]['subscribed']) ) $listOfFolders = $foldersNameSpace[$type]['subscribed']; } else { if( !PEAR::isError($foldersNameSpace[$type]['all'])) $listOfFolders = $foldersNameSpace[$type]['all']; } foreach((array)$listOfFolders as $folderName) { //echo "
FolderToCheck:$folderName
"; if($_subscribedOnly && !(in_array($folderName, $foldersNameSpace[$type]['all'])||in_array($folderName.$foldersNameSpace[$type]['delimiter'], $foldersNameSpace[$type]['all']))) { #echo "$folderName failed to be here
"; continue; } $folderParts = explode($delimiter, $folderName); $shortName = array_pop($folderParts); $folderObject = new stdClass; $folderObject->delimiter = $delimiter; $folderObject->folderName = $folderName; $folderObject->shortFolderName = $shortName; if(!$_subscribedOnly) { #echo $folderName."->".$type."
"; #_debug_array($foldersNameSpace[$type]['subscribed']); $folderObject->subscribed = in_array($folderName, $foldersNameSpace[$type]['subscribed']); } if($_getCounters == true) { $folderObject->counter = $this->getMailBoxCounters($folderName); } if(strtoupper($folderName) == 'INBOX') { $folderName = 'INBOX'; $folderObject->folderName = 'INBOX'; $folderObject->shortFolderName = 'INBOX'; $folderObject->displayName = lang('INBOX'); $folderObject->shortDisplayName = lang('INBOX'); $folderObject->subscribed = true; // translate the automatic Folders (Sent, Drafts, ...) like the INBOX } elseif (in_array($shortName,self::$autoFolders)) { $tmpfolderparts = explode($delimiter,$folderObject->folderName); array_pop($tmpfolderparts); $folderObject->displayName = implode($delimiter,$tmpfolderparts).$delimiter.lang($shortName); $folderObject->shortDisplayName = lang($shortName); unset($tmpfolderparts); } else { $folderObject->displayName = $this->encodeFolderName($folderObject->folderName); $folderObject->shortDisplayName = $this->encodeFolderName($shortName); } //$folderName = $folderName; if (in_array($shortName,self::$autoFolders)&&self::searchValueInFolderObjects($shortName,$autoFolderObjects)===false) { $autoFolderObjects[$folderName] = $folderObject; } else { $folders[$folderName] = $folderObject; } } } } if (is_array($autoFolderObjects) && !empty($autoFolderObjects)) { uasort($autoFolderObjects,array($this,"sortByAutoFolderPos")); } if (is_array($folders)) uasort($folders,array($this,"sortByDisplayName")); //$folders2return = array_merge($autoFolderObjects,$folders); //_debug_array($folders2return); #exit; $folders2return[$this->icServer->ImapServerId] = array_merge($inboxFolderObject,$autoFolderObjects,(array)$folders); if ($_subscribedOnly && $_getCounters===false) egw_cache::setCache(egw_cache::INSTANCE,'email','folderObjects'.trim($GLOBALS['egw_info']['user']['account_id']),$folders2return,$expiration=60*60*1); return $folders2return[$this->icServer->ImapServerId]; } /** * search Value In FolderObjects * * Helper function to search for a specific value within the foldertree objects * @param string $needle * @param array $haystack, array of folderobjects * @return MIXED false or key */ static function searchValueInFolderObjects($needle, $haystack) { $rv = false; foreach ($haystack as $k => $v) { foreach($v as $sk => $sv) if (trim($sv)==trim($needle)) return $k; } return $rv; } /** * sortByDisplayName * * Helper function to sort folder-objects by displayname * @param object $a * @param object $b, array of folderobjects * @return int expect values (0, 1 or -1) */ function sortByDisplayName($a,$b) { // 0, 1 und -1 return strcasecmp($a->displayName,$b->displayName); } /** * sortByAutoFolderPos * * Helper function to sort folder-objects by auto Folder Position * @param object $a * @param object $b, array of folderobjects * @return int expect values (0, 1 or -1) */ function sortByAutoFolderPos($a,$b) { // 0, 1 und -1 $pos1 = array_search(trim($a->shortFolderName),self::$autoFolders); $pos2 = array_search(trim($b->shortFolderName),self::$autoFolders); if ($pos1 == $pos2) return 0; return ($pos1 < $pos2) ? -1 : 1; } /** * getMailBoxCounters * * function to retrieve the counters for a given folder * @param string $folderName * @return mixed false or array of counters array(MESSAGES,UNSEEN,RECENT,UIDNEXT,UIDVALIDITY) */ function getMailBoxCounters($folderName) { $folderStatus = $this->_getStatus($folderName); error_log(__METHOD__.__LINE__." FolderStatus:".array2string($folderStatus)); if ( PEAR::isError($folderStatus)) { if (self::$debug) error_log(__METHOD__." returned FolderStatus for Folder $folderName:".print_r($folderStatus->message,true)); return false; } if(is_array($folderStatus)) { $status = new stdClass; $status->messages = $folderStatus['MESSAGES']; $status->unseen = $folderStatus['UNSEEN']; $status->recent = $folderStatus['RECENT']; $status->uidnext = $folderStatus['UIDNEXT']; $status->uidvalidity = $folderStatus['UIDVALIDITY']; return $status; } return false; } /** * getMailBoxesRecursive * * function to retrieve mailboxes recursively from given mailbox * @param string $_mailbox * @param string $delimiter * @param string $prefix * @param string $reclevel 0, counter to keep track of the current recursionlevel * @return array of mailboxes */ function getMailBoxesRecursive($_mailbox, $delimiter, $prefix, $reclevel=0) { #echo __METHOD__." retrieve SubFolders for $_mailbox$delimiter
"; $maxreclevel=25; if ($reclevel > $maxreclevel) { error_log( __METHOD__." Recursion Level Exeeded ($reclevel) while looking up $_mailbox$delimiter "); return array(); } $reclevel++; // clean up double delimiters $_mailbox = preg_replace('~'.($delimiter == '.' ? "\\".$delimiter:$delimiter).'+~s',$delimiter,$_mailbox); //get that mailbox in question $mbx = $this->icServer->getMailboxes($_mailbox,1,true); #_debug_array($mbx); if (is_array($mbx[0]["ATTRIBUTES"]) && (in_array('\HasChildren',$mbx[0]["ATTRIBUTES"]) || in_array('\Haschildren',$mbx[0]["ATTRIBUTES"]))) { // if there are children fetch them //echo $mbx[0]['MAILBOX']."
"; unset($buff); $buff = $this->icServer->getMailboxes($mbx[0]['MAILBOX'].($mbx[0]['MAILBOX'] == $prefix ? '':$delimiter),2,false); //$buff = $this->icServer->getMailboxes($mbx[0]['MAILBOX'],2,false); //_debug_array($buff); if( PEAR::isError($buff) ) { if (self::$debug) error_log(__METHOD__." Error while retrieving Mailboxes for:".$mbx[0]['MAILBOX'].$delimiter."."); return array(); } else { $allMailboxes = array(); foreach ($buff as $mbxname) { $mbxname = preg_replace('~'.($delimiter == '.' ? "\\".$delimiter:$delimiter).'+~s',$delimiter,$mbxname); #echo "About to recur in level $reclevel:".$mbxname."
"; if ( $mbxname != $mbx[0]['MAILBOX'] && $mbxname != $prefix && $mbxname != $mbx[0]['MAILBOX'].$delimiter) $allMailboxes = array_merge($allMailboxes, self::getMailBoxesRecursive($mbxname, $delimiter, $prefix, $reclevel)); } if (!(in_array('\NoSelect',$mbx[0]["ATTRIBUTES"]) || in_array('\Noselect',$mbx[0]["ATTRIBUTES"]))) $allMailboxes[] = $mbx[0]['MAILBOX']; return $allMailboxes; } } else { return array($_mailbox); } } /** * _getSpecialUseFolder * abstraction layer for getDraftFolder, getTemplateFolder, getTrashFolder and getSentFolder * @param string $type the type to fetch (Drafts|Template|Trash|Sent) * @param boolean $_checkexistance, trigger check for existance * @return mixed string or false */ function _getSpecialUseFolder($_type, $_checkexistance=TRUE) { static $types = array( 'Drafts'=>array('prefName'=>'draftFolder','profileKey'=>'draftfolder','autoFolderName'=>'Drafts'), 'Template'=>array('prefName'=>'templateFolder','profileKey'=>'templatefolder','autoFolderName'=>'Templates'), 'Trash'=>array('prefName'=>'trashFolder','profileKey'=>'trashfolder','autoFolderName'=>'Trash'), 'Sent'=>array('prefName'=>'sentFolder','profileKey'=>'sentfolder','autoFolderName'=>'Sent'), ); if (!isset($types[$_type])) { error_log(__METHOD__.__LINE__.' '.$_type.' not supported for '.__METHOD__); return false; } if (is_null(self::$specialUseFolders) || empty(self::$specialUseFolders)) self::$specialUseFolders = $this->getSpecialUseFolders(); //highest precedence $_folderName = $this->mailPreferences->ic_server[$this->profileID]->$types[$_type]['profileKey']; //check prefs next if (empty($_folderName)) $_folderName = $this->mailPreferences->preferences[$types[$_type]['prefName']]; // does the folder exist??? if ($_checkexistance && $_folderName !='none' && !self::folderExists($_folderName)) { $_folderName = false; } //no (valid) folder found yet; try specialUseFolders if (empty($_folderName) && is_array(self::$specialUseFolders) && ($f = array_search($_type,self::$specialUseFolders))) $_folderName = $f; //no specialUseFolder; try some Defaults if (empty($_folderName) && isset($types[$_type])) { $nameSpace = $this->_getNameSpaces(); $prefix=''; if (isset($nameSpace['personal'])) $prefix = $nameSpace['personal']['prefix']; if (self::folderExists($prefix.$types[$_type]['autoFolderName'])) $_folderName = $prefix.$types[$_type]['autoFolderName']; } return $_folderName; } /** * getDraftFolder wrapper for _getSpecialUseFolder Type Drafts * @param boolean $_checkexistance, trigger check for existance * @return mixed string or false */ function getDraftFolder($_checkexistance=TRUE) { return $this->_getSpecialUseFolder('Drafts', $_checkexistance); } /** * getTemplateFolder wrapper for _getSpecialUseFolder Type Template * @param boolean $_checkexistance, trigger check for existance * @return mixed string or false */ function getTemplateFolder($_checkexistance=TRUE) { return $this->_getSpecialUseFolder('Template', $_checkexistance); } /** * getTrashFolder wrapper for _getSpecialUseFolder Type Trash * @param boolean $_checkexistance, trigger check for existance * @return mixed string or false */ function getTrashFolder($_checkexistance=TRUE) { return $this->_getSpecialUseFolder('Trash', $_checkexistance); } /** * getSentFolder wrapper for _getSpecialUseFolder Type Sent * @param boolean $_checkexistance, trigger check for existance * @return mixed string or false */ function getSentFolder($_checkexistance=TRUE) { return $this->_getSpecialUseFolder('Sent', $_checkexistance); } /** * isSentFolder is the given folder the sent folder or at least a subfolder of it * @param string $_foldername, folder to perform the check on * @param boolean $_checkexistance, trigger check for existance * @return boolean */ function isSentFolder($_folderName, $_checkexistance=TRUE) { $sentFolder = $this->getSentFolder(); if(empty($sentFolder)) { return false; } // does the folder exist??? if ($_checkexistance && !self::folderExists($_folderName)) { return false; } if(false !== stripos($_folderName, $sentFolder)) { return true; } else { return false; } } /** * checks if the Outbox folder exists and is part of the foldername to be checked * @param string $_foldername, folder to perform the check on * @param boolean $_checkexistance, trigger check for existance * @return boolean */ function isOutbox($_folderName, $_checkexistance=TRUE) { if (stripos($_folderName, 'Outbox')===false) { return false; } // does the folder exist??? if ($_checkexistance && !self::folderExists($_folderName)) { return false; } return true; } /** * isDraftFolder is the given folder the sent folder or at least a subfolder of it * @param string $_foldername, folder to perform the check on * @param boolean $_checkexistance, trigger check for existance * @return boolean */ function isDraftFolder($_folderName, $_checkexistance=TRUE) { $draftFolder = $this->getDraftFolder(); if(empty($draftFolder)) { return false; } // does the folder exist??? if ($_checkexistance && !self::folderExists($_folderName)) { return false; } if(false !== stripos($_folderName, $draftFolder)) { return true; } else { return false; } } /** * isTrashFolder is the given folder the sent folder or at least a subfolder of it * @param string $_foldername, folder to perform the check on * @param boolean $_checkexistance, trigger check for existance * @return boolean */ function isTrashFolder($_folderName, $_checkexistance=TRUE) { $trashFolder = $this->getTrashFolder(); if(empty($trashFolder)) { return false; } // does the folder exist??? if ($_checkexistance && !self::folderExists($_folderName)) { return false; } if(false !== stripos($_folderName, $trashFolder)) { return true; } else { return false; } } /** * isTemplateFolder is the given folder the sent folder or at least a subfolder of it * @param string $_foldername, folder to perform the check on * @param boolean $_checkexistance, trigger check for existance * @return boolean */ function isTemplateFolder($_folderName, $_checkexistance=TRUE) { $templateFolder = $this->getTemplateFolder(); if(empty($templateFolder)) { return false; } // does the folder exist??? if ($_checkexistance && !self::folderExists($_folderName)) { return false; } if(false !== stripos($_folderName, $templateFolder)) { return true; } else { return false; } } /** * folderExists checks for existance of a given folder * @param string $_foldername, folder to perform the check on * @param boolean $_forceCheck, trigger check for existance on icServer * @return mixed string or false */ function folderExists($_folder, $_forceCheck=false) { static $folderInfo; $forceCheck = $_forceCheck; if (empty($_folder)) { // this error is more or less without significance, unless we force the check if ($_forceCheck===true) error_log(__METHOD__.__LINE__.' Called with empty Folder:'.$_folder.function_backtrace()); return false; } // reduce traffic within the Instance per User; Expire every 5 Minutes //error_log(__METHOD__.__LINE__.' Called with Folder:'.$_folder.function_backtrace()); if (is_null($folderInfo)) $folderInfo = egw_cache::getCache(egw_cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),$expiration=60*5); //error_log(__METHOD__.__LINE__.'Cached Info on Folder:'.$_folder.' for Profile:'.$this->profileID.($forceCheck?'(forcedCheck)':'').':'.array2string($folderInfo)); if (!empty($folderInfo) && isset($folderInfo[$this->profileID]) && isset($folderInfo[$this->profileID][$_folder]) && $forceCheck===false) { //error_log(__METHOD__.__LINE__.' Using cached Info on Folder:'.$_folder.' for Profile:'.$this->profileID); return $folderInfo[$this->profileID][$_folder]; } else { if ($forceCheck === false) { //error_log(__METHOD__.__LINE__.' No cached Info on Folder:'.$_folder.' for Profile:'.$this->profileID.' FolderExistsInfoCache:'.array2string($folderInfo[$this->profileID])); $forceCheck = true; // try to force the check, in case there is no connection, we may need that } } // does the folder exist??? //error_log(__METHOD__."->Connected?".$this->icServer->_connected.", ".$_folder.", ".($forceCheck?' forceCheck activated':'dont check on server')); if ((!($this->icServer->_connected == 1)) && $forceCheck || empty($folderInfo) || !isset($folderInfo[$this->profileID]) || !isset($folderInfo[$this->profileID][$_folder])) { //error_log(__METHOD__."->NotConnected and forceCheck with profile:".$this->profileID); //return false; //try to connect if (!$this->icServer->_connected) $this->openConnection($this->profileID,false); } if(($this->icServer instanceof defaultimap)) { $folderInfo[$this->profileID][$_folder] = $this->icServer->mailboxExist($_folder); //LIST Command, may return OK, but no attributes if ($folderInfo[$this->profileID][$_folder]==false) { // some servers dont serve the LIST command in certain cases; this is a ServerBUG and // we try to work around it here. if ((isset($this->mailPreferences->preferences['trustServersUnseenInfo']) && $this->mailPreferences->preferences['trustServersUnseenInfo']==false) || (isset($this->mailPreferences->preferences['trustServersUnseenInfo']) && $this->mailPreferences->preferences['trustServersUnseenInfo']==2) ) { $nameSpace = $this->_getNameSpaces(); if (isset($nameSpace['personal'])) unset($nameSpace['personal']); $prefix = $this->getFolderPrefixFromNamespace($nameSpace, $_folder); if ($prefix != '' && stripos($_folder,$prefix) !== false) { if(!PEAR::isError($r = $this->_getStatus($_folder)) && is_array($r)) $folderInfo[$this->profileID][$_folder] = true; } } } } //error_log(__METHOD__.__LINE__.' Folder Exists:'.$folderInfo[$this->profileID][$_folder].function_backtrace()); if(!empty($folderInfo) && isset($folderInfo[$this->profileID][$_folder]) && ($folderInfo[$this->profileID][$_folder] instanceof PEAR_Error) || $folderInfo[$this->profileID][$_folder] !== true) { $folderInfo[$this->profileID][$_folder] = false; // set to false, whatever it was (to have a valid returnvalue for the static return) } egw_cache::setCache(egw_cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($GLOBALS['egw_info']['user']['account_id']),$folderInfo,$expiration=60*5); return (!empty($folderInfo) && isset($folderInfo[$this->profileID][$_folder]) ? $folderInfo[$this->profileID][$_folder] : false); } /** * remove any messages which are marked as deleted or * remove any messages from the trashfolder * * @param string _folderName the foldername * @return nothing */ function compressFolder($_folderName = false) { $folderName = ($_folderName ? $_folderName : $this->sessionData['mailbox']); $deleteOptions = $GLOBALS['egw_info']['user']['preferences']['mail']['deleteOptions']; $trashFolder = $this->getTrashFolder(); $this->icServer->selectMailbox($folderName); if($folderName == $trashFolder && $deleteOptions == "move_to_trash") { $this->icServer->deleteMessages('1:*'); $this->icServer->expunge(); } else { $this->icServer->expunge(); } } /** * flag a Message * * @param string _flag (readable name) * @param mixed array/string _messageUID array of ids to flag, or 'all' * @param string _folder foldername * * @todo handle handle icserver->setFlags returnValue * * @return bool true, as we do not handle icserver->setFlags returnValue */ function flagMessages($_flag, $_messageUID,$_folder=NULL) { //error_log(__METHOD__.__LINE__.'->' .$_flag." ".array2string($_messageUID).",$_folder /".$this->sessionData['mailbox']); if(!is_array($_messageUID)) { #return false; if ($_messageUID=='all') { //all is an allowed value to be passed } else { $_messageUID=array($_messageUID); } } $this->icServer->selectMailbox(($_folder?$_folder:$this->sessionData['mailbox'])); switch($_flag) { case "undelete": $ret = $this->icServer->setFlags($_messageUID, '\\Deleted', 'remove', true); break; case "flagged": $ret = $this->icServer->setFlags($_messageUID, '\\Flagged', 'add', true); break; case "read": $ret = $this->icServer->setFlags($_messageUID, '\\Seen', 'add', true); break; case "forwarded": $ret = $this->icServer->setFlags($_messageUID, '$Forwarded', 'add', true); case "answered": $ret = $this->icServer->setFlags($_messageUID, '\\Answered', 'add', true); break; case "unflagged": $ret = $this->icServer->setFlags($_messageUID, '\\Flagged', 'remove', true); break; case "unread": $ret = $this->icServer->setFlags($_messageUID, '\\Seen', 'remove', true); $ret = $this->icServer->setFlags($_messageUID, '\\Answered', 'remove', true); $ret = $this->icServer->setFlags($_messageUID, '$Forwarded', 'remove', true); break; case "mdnsent": $ret = $this->icServer->setFlags($_messageUID, 'MDNSent', 'add', true); break; case "mdnnotsent": $ret = $this->icServer->setFlags($_messageUID, 'MDNnotSent', 'add', true); break; case "label1": case "labelone": $ret = $this->icServer->setFlags($_messageUID, '$label1', 'add', true); break; case "unlabel1": case "unlabelone": $this->icServer->setFlags($_messageUID, '$label1', 'remove', true); break; case "label2": case "labeltwo": $ret = $this->icServer->setFlags($_messageUID, '$label2', 'add', true); break; case "unlabel2": case "unlabeltwo": $ret = $this->icServer->setFlags($_messageUID, '$label2', 'remove', true); break; case "label3": case "labelthree": $ret = $this->icServer->setFlags($_messageUID, '$label3', 'add', true); break; case "unlabel3": case "unlabelthree": $ret = $this->icServer->setFlags($_messageUID, '$label3', 'remove', true); break; case "label4": case "labelfour": $ret = $this->icServer->setFlags($_messageUID, '$label4', 'add', true); break; case "unlabel4": case "unlabelfour": $ret = $this->icServer->setFlags($_messageUID, '$label4', 'remove', true); break; case "label5": case "labelfive": $ret = $this->icServer->setFlags($_messageUID, '$label5', 'add', true); break; case "unlabel5": case "unlabelfive": $ret = $this->icServer->setFlags($_messageUID, '$label5', 'remove', true); break; } if (PEAR::isError($ret)) { if (stripos($ret->message,'Too long argument')) { $c = count($_messageUID); $h =ceil($c/2); error_log(__METHOD__.__LINE__.$ret->message." $c messages given for flagging. Trying with chunks of $h"); $this->flagMessages($_flag, array_slice($_messageUID,0,$h),($_folder?$_folder:$this->sessionData['mailbox'])); $this->flagMessages($_flag, array_slice($_messageUID,$h),($_folder?$_folder:$this->sessionData['mailbox'])); } } $this->sessionData['folderStatus'][$this->profileID][$this->sessionData['mailbox']]['uidValidity'] = 0; $this->saveSessionData(); //error_log(__METHOD__.__LINE__.'->' .$_flag." ".array2string($_messageUID).",".($_folder?$_folder:$this->sessionData['mailbox'])); return true; // as we do not catch/examine setFlags returnValue } /** * Helper function to handle wrong or unrecognized timezones * returns the date as it is parseable by strtotime, or current timestamp if everything failes * @param string date to be parsed/formatted * @param string format string, if none is passed, use the users common dateformat supplemented by the time hour:minute:second * @return string returns the date as it is parseable by strtotime, or current timestamp if everything failes */ static function _strtotime($date='',$format=NULL,$convert2usertime=false) { if ($format==NULL) $format = $GLOBALS['egw_info']['user']['preferences']['common']['dateformat'].' '.($GLOBALS['egw_info']['user']['preferences']['common']['timeformat']==12?'h:i:s a':'H:i:s'); $date2return = ($convert2usertime ? egw_time::server2user($date,$format) : egw_time::to($date,$format)); if ($date2return==null) { $dtarr = explode(' ',$date); $test = null; while ($test===null && count($dtarr)>=1) { array_pop($dtarr); $test= ($convert2usertime ? egw_time::server2user(implode(' ',$dtarr),$format): egw_time::to(implode(' ',$dtarr),$format)); if ($test) $date2return = $test; } if ($test===null) $date2return = egw_time::to('now',$format); } return $date2return; } /** * htmlentities * helperfunction to cope with wrong encoding in strings * @param string $_string input to be converted * @param mixed $charset false or string -> Target charset, if false mail_bo displayCharset will be used * @return string */ static function htmlentities($_string, $_charset=false) { //setting the charset (if not given) if ($_charset===false) $_charset = self::$displayCharset; $_stringORG = $_string; $_string = @htmlentities($_string,ENT_QUOTES,$_charset, false); if (empty($_string) && !empty($_stringORG)) $_string = @htmlentities(translation::convert($_stringORG,self::detect_encoding($_stringORG),$_charset),ENT_QUOTES | ENT_IGNORE,$_charset, false); return $_string; } /** * Header and Bodystructure stuff */ /** * _getStructure * fetc the structure of a mail, represented by uid * @param string/int $_uid the messageuid, * @param boolean $byUid=true, is the messageuid given by UID or ID * @param boolean $_ignoreCache=false, use or disregard cache, when fetching * @param string $_folder='', if given search within that folder for the given $_uid, else use sessionData['mailbox'], or servers getCurrentMailbox * @return array an structured array of information about the mail */ function _getStructure($_uid, $byUid=true, $_ignoreCache=false, $_folder = '') { static $structure; if (empty($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox()); //error_log(__METHOD__.__LINE__.'User:'.trim($GLOBALS['egw_info']['user']['account_id'])." UID: $_uid, ".$this->icServer->ImapServerId.','.$_folder); if (is_null($structure)) $structure = egw_cache::getCache(egw_cache::INSTANCE,'email','structureCache'.trim($GLOBALS['egw_info']['user']['account_id']),$callback=null,$callback_params=array(),$expiration=60*60*1); //error_log(__METHOD__.__LINE__." UID: $_uid, ".$this->icServer->ImapServerId.','.$_folder.'->'.array2string(array_keys($structure))); if (isset($structure[$this->icServer->ImapServerId]) && !empty($structure[$this->icServer->ImapServerId]) && isset($structure[$this->icServer->ImapServerId][$_folder]) && !empty($structure[$this->icServer->ImapServerId][$_folder]) && isset($structure[$this->icServer->ImapServerId][$_folder][$_uid]) && !empty($structure[$this->icServer->ImapServerId][$_folder][$_uid])) { if ($_ignoreCache===false) { //error_log(__METHOD__.__LINE__.' Using cache for structure on Server:'.$this->icServer->ImapServerId.' for uid:'.$_uid." in Folder:".$_folder.'->'.array2string($structure[$this->icServer->ImapServerId][$_folder][$_uid])); return $structure[$this->icServer->ImapServerId][$_folder][$_uid]; } } $structure[$this->icServer->ImapServerId][$_folder][$_uid] = $this->icServer->getStructure($_uid, $byUid); egw_cache::setCache(egw_cache::INSTANCE,'email','structureCache'.trim($GLOBALS['egw_info']['user']['account_id']),$structure,$expiration=60*60*1); //error_log(__METHOD__.__LINE__.' Using query for structure on Server:'.$this->icServer->ImapServerId.' for uid:'.$_uid." in Folder:".$_folder.'->'.array2string($structure[$this->icServer->ImapServerId][$_folder][$_uid])); return $structure[$this->icServer->ImapServerId][$_folder][$_uid]; } /** * getMessageAttachments * parse the structure for attachments, it returns not the attachments itself, but an array of information about the attachment * @param string/int $_uid the messageuid, * @param string/int $_partID='' , the partID, may be omitted * @param array $_structure='', if given use structure for parsing * @param boolean $fetchEmbeddedImages=true, * @param boolean $fetchTextCalendar=false, * @param boolean $resolveTNEF=true * @return array an array of information about the attachment: array of array(name, size, mimeType, partID, encoding) */ function getMessageAttachments($_uid, $_partID='', $_structure='', $fetchEmbeddedImages=true, $fetchTextCalendar=false, $resolveTNEF=true) { if (self::$debug) echo __METHOD__."$_uid, $_partID
"; if(is_object($_structure)) { $structure = $_structure; } else { $structure = $this->_getStructure($_uid, true); if($_partID != '' && $_partID !=0) { $structure = $this->_getSubStructure($structure, $_partID); } } if (self::$debug) _debug_array($structure); $attachments = array(); // this kind of messages contain only the attachment and no body if($structure->type == 'APPLICATION' || $structure->type == 'AUDIO' || $structure->type == 'VIDEO' || $structure->type == 'IMAGE' || ($structure->type == 'TEXT' && $structure->disposition == 'ATTACHMENT') ) { $newAttachment = array(); $newAttachment['name'] = $this->getFileNameFromStructure($structure,$_uid,$structure->partID); $newAttachment['size'] = $structure->bytes; $newAttachment['mimeType'] = $structure->type .'/'. $structure->subType; $newAttachment['partID'] = $structure->partID; $newAttachment['encoding'] = $structure->encoding; // try guessing the mimetype, if we get the application/octet-stream if (strtolower($newAttachment['mimeType']) == 'application/octet-stream') $newAttachment['mimeType'] = mime_magic::filename2mime($newAttachment['name']); if(isset($structure->cid)) { $newAttachment['cid'] = $structure->cid; } # if the new attachment is a winmail.dat, we have to decode that first if ( $resolveTNEF && $newAttachment['name'] == 'winmail.dat' && ( $wmattachments = $this->decode_winmail( $_uid, $newAttachment['partID'] ) ) ) { $attachments = array_merge( $attachments, $wmattachments ); } elseif ( $resolveTNEF===false && $newAttachment['name'] == 'winmail.dat' ) { $attachments[] = $newAttachment; } else { $fetchit = $fetchEmbeddedImages; if ($fetchEmbeddedImages === false && (!in_array(strtoupper($structure->subtype),array('JPG','JPEG','GIF','PNG')))) $fetchit = true; if ( ($fetchit && isset($newAttachment['cid']) && strlen($newAttachment['cid'])>0) || !isset($newAttachment['cid']) || empty($newAttachment['cid'])) $attachments[] = $newAttachment; } //$attachments[] = $newAttachment; #return $attachments; } // outlook sometimes sends a TEXT/CALENDAR;REQUEST as plain ics, nothing more. if ($structure->type == 'TEXT' && $structure->subType == 'CALENDAR' && isset($structure->parameters['METHOD'] ) && strtoupper($structure->parameters['METHOD']) == 'REQUEST') { $newAttachment = array(); $newAttachment['name'] = 'event.ics'; $newAttachment['size'] = $structure->bytes; $newAttachment['mimeType'] = $structure->type .'/'. $structure->subType;//.';'.$structure->parameters['METHOD']; $newAttachment['partID'] = $structure->partID; $newAttachment['encoding'] = $structure->encoding; $newAttachment['method'] = $structure->parameters['METHOD']; $newAttachment['charset'] = $structure->parameters['CHARSET']; $attachments[] = $newAttachment; } // this kind of message can have no attachments if(($structure->type == 'TEXT' && !($structure->disposition == 'INLINE' && $structure->dparameters['FILENAME'])) || ($structure->type == 'MULTIPART' && $structure->subType == 'ALTERNATIVE' && !is_array($structure->subParts)) || !is_array($structure->subParts)) { if (count($attachments) == 0) return array(); } #$attachments = array(); foreach((array)$structure->subParts as $subPart) { // skip all non attachment parts if(($subPart->type == 'TEXT' && ($subPart->subType == 'PLAIN' || $subPart->subType == 'HTML') && ($subPart->disposition != 'ATTACHMENT' && !($subPart->disposition == 'INLINE' && $subPart->dparameters['FILENAME']))) || ($subPart->type == 'MULTIPART' && $subPart->subType == 'ALTERNATIVE') || ($subPart->type == 'MULTIPART' && $subPart->subType == 'APPLEFILE') || ($subPart->type == 'MESSAGE' && $subPart->subType == 'delivery-status')) { if ($subPart->type == 'MULTIPART' && $subPart->subType == 'ALTERNATIVE') { $attachments = array_merge($this->getMessageAttachments($_uid, '', $subPart, $fetchEmbeddedImages, $fetchTextCalendar, $resolveTNEF), $attachments); } if (!($subPart->type=='TEXT' && $subPart->disposition =='INLINE' && $subPart->filename)) continue; } // fetch the subparts for this part if($subPart->type == 'MULTIPART' && ($subPart->subType == 'RELATED' || $subPart->subType == 'MIXED' || $subPart->subType == 'SIGNED' || $subPart->subType == 'APPLEDOUBLE')) { $attachments = array_merge($this->getMessageAttachments($_uid, '', $subPart, $fetchEmbeddedImages,$fetchTextCalendar, $resolveTNEF), $attachments); } else { if (!$fetchTextCalendar && $subPart->type == 'TEXT' && $subPart->subType == 'CALENDAR' && $subPart->parameters['METHOD'] && $subPart->disposition !='ATTACHMENT') continue; $newAttachment = array(); $newAttachment['name'] = $this->getFileNameFromStructure($subPart,$_uid,$subPart->partID); $newAttachment['size'] = $subPart->bytes; $newAttachment['mimeType'] = $subPart->type .'/'. $subPart->subType; $newAttachment['partID'] = $subPart->partID; $newAttachment['encoding'] = $subPart->encoding; $newAttachment['method'] = $this->getMethodFromStructure($subPart,$_uid,$subPart->partID); $newAttachment['charset'] = $subPart->parameters['CHARSET']; if (isset($subPart->disposition) && !empty($subPart->disposition)) $newAttachment['disposition'] = $subPart->disposition; // try guessing the mimetype, if we get the application/octet-stream if (strtolower($newAttachment['mimeType']) == 'application/octet-stream') $newAttachment['mimeType'] = mime_magic::filename2mime($newAttachment['name']); if(isset($subPart->cid)) { $newAttachment['cid'] = $subPart->cid; } # if the new attachment is a winmail.dat, we have to decode that first if ( $resolveTNEF && $newAttachment['name'] == 'winmail.dat' && ( $wmattachments = $this->decode_winmail( $_uid, $newAttachment['partID'] ) ) ) { $attachments = array_merge( $attachments, $wmattachments ); } elseif ( $resolveTNEF===false && $newAttachment['name'] == 'winmail.dat' ) { $attachments[] = $newAttachment; } else { $fetchit = $fetchEmbeddedImages; if ($fetchEmbeddedImages === false && (!in_array(strtoupper($structure->subtype),array('JPG','JPEG','GIF','PNG')))) $fetchit = true; if ( ($fetchit && isset($newAttachment['cid']) && strlen($newAttachment['cid'])>0) || !isset($newAttachment['cid']) || empty($newAttachment['cid']) || $newAttachment['cid'] == 'NIL') { $attachments[] = $newAttachment; } else { // embedded images should be INLINE, so we check this too, 'cause we want to show/list non embedded images if ($fetchEmbeddedImages==false && isset($newAttachment['mimeType']) && !empty($newAttachment['mimeType']) && stripos($newAttachment['mimeType'],'IMAGE') !== false && isset($newAttachment['disposition']) && !empty($newAttachment['disposition']) && trim(strtoupper($newAttachment['disposition']))!='INLINE') $attachments[] = $newAttachment; } } //$attachments[] = $newAttachment; } } //_debug_array($attachments); exit; return $attachments; } /** * getFileNameFromStructure * parse the structure for the filename of an attachment * @param array $_structure='', structure used for parsing * @param string/int $_uid the messageuid, * @param string/int $_partID='', the partID, may be omitted * @return string a string representing the filename of an attachment */ function getFileNameFromStructure(&$structure, $_uid = false, $partID = false) { static $namecounter; if (is_null($namecounter)) $namecounter = 0; //if ( $_uid && $partID) error_log(__METHOD__.__LINE__.array2string($structure).' Uid:'.$_uid.' PartID:'.$partID.' -> '.array2string($this->icServer->getParsedHeaders($_uid, true, $partID, true))); if(isset($structure->parameters['NAME'])) { if (is_array($structure->parameters['NAME'])) $structure->parameters['NAME'] = implode(' ',$structure->parameters['NAME']); return rawurldecode(self::decode_header($structure->parameters['NAME'])); } elseif(isset($structure->dparameters['FILENAME'])) { return rawurldecode(self::decode_header($structure->dparameters['FILENAME'])); } elseif(isset($structure->dparameters['FILENAME*'])) { return rawurldecode(self::decode_header($structure->dparameters['FILENAME*'])); } elseif ( isset($structure->filename) && !empty($structure->filename) && $structure->filename != 'NIL') { return rawurldecode(self::decode_header($structure->filename)); } else { if ( $_uid && $partID) { $headers = $this->icServer->getParsedHeaders($_uid, true, $partID, true); if ($headers) { if (!PEAR::isError($headers)) { // simple parsing of the headers array for a usable name //error_log( __METHOD__.__LINE__.array2string($headers)); foreach(array('CONTENT-TYPE','CONTENT-DISPOSITION') as $k => $v) { $headers[$v] = rawurldecode(self::decode_header($headers[$v])); foreach(array('filename','name') as $sk => $n) { if (stripos($headers[$v],$n)!== false) { $buff = explode($n,$headers[$v]); //error_log(__METHOD__.__LINE__.array2string($buff)); $namepart = array_pop($buff); //error_log(__METHOD__.__LINE__.$namepart); $fp = strpos($namepart,'"'); //error_log(__METHOD__.__LINE__.' Start:'.$fp); if ($fp !== false) { $np = strpos($namepart,'"', $fp+1); //error_log(__METHOD__.__LINE__.' End:'.$np); if ($np !== false) { $name = trim(substr($namepart,$fp+1,$np-$fp-1)); if (!empty($name)) return $name; } } } } } } } } $namecounter++; return lang("unknown").$namecounter.($structure->subType ? ".".$structure->subType : ""); } } /** * getMethodFromStructure * parse the structure for the METHOD of an ics event (attachment) * @param array $_structure='', structure used for parsing * @param string/int $_uid the messageuid, * @param string/int $_partID='', the partID, may be omitted * @return string a string representing the filename of an attachment */ function getMethodFromStructure(&$structure, $_uid = false, $partID = false) { //if ( $_uid && $partID) error_log(__METHOD__.__LINE__.array2string($structure).' Uid:'.$_uid.' PartID:'.$partID.' -> '.array2string($this->icServer->getParsedHeaders($_uid, true, $partID, true))); if(isset($subPart->parameters['METHOD'])) { return $subPart->parameters['METHOD']; } else { if ( $_uid && $partID && $structure->type=='TEXT' && $structure->subType=='CALENDAR' && $structure->filename=='NIL') { $attachment = $this->getAttachment($_uid, $partID); if ($attachment['attachment']) { if (!PEAR::isError($attachment['attachment'])) { // simple parsing of the attachment for a usable method //error_log( __METHOD__.__LINE__.array2string($attachment['attachment'])); foreach(explode("\r\n",$attachment['attachment']) as $k => $v) { if (strpos($v,':') !== false) { list($first,$rest) = explode(':',$v,2); $first = trim($first); if ($first=='METHOD') { return trim($rest); } } } } } } } } /** * Hook stuff */ /** * hook to add account * * this function is a wrapper function for emailadmin * * @param _hookValues contains the hook values as array * @return nothing */ function addAccount($_hookValues) { if ($this->mailPreferences) { $icServer = $this->mailPreferences->getIncomingServer($this->profileID); if(($icServer instanceof defaultimap)) { // if not connected, try opening an admin connection if (!$icServer->_connected) $this->openConnection($this->profileID,true); $icServer->addAccount($_hookValues); if ($icServer->_connected) $this->closeConnection(); // close connection afterwards } $ogServer = $this->mailPreferences->getOutgoingServer($this->profileID); if(($ogServer instanceof emailadmin_smtp)) { $ogServer->addAccount($_hookValues); } } } /** * hook to delete account * * this function is a wrapper function for emailadmin * * @param _hookValues contains the hook values as array * @return nothing */ function deleteAccount($_hookValues) { if ($this->mailPreferences) { $icServer = $this->mailPreferences->getIncomingServer($this->profileID); if(($icServer instanceof defaultimap)) { //try to connect with admin rights, when not connected if (!$icServer->_connected) $this->openConnection($this->profileID,true); $icServer->deleteAccount($_hookValues); if ($icServer->_connected) $this->closeConnection(); // close connection } $ogServer = $this->mailPreferences->getOutgoingServer($this->profileID); if(($ogServer instanceof emailadmin_smtp)) { $ogServer->deleteAccount($_hookValues); } } } /** * hook to update account * * this function is a wrapper function for emailadmin * * @param _hookValues contains the hook values as array * @return nothing */ function updateAccount($_hookValues) { if (is_object($this->mailPreferences)) $icServer = $this->mailPreferences->getIncomingServer(0); if(($icServer instanceof defaultimap)) { $icServer->updateAccount($_hookValues); } if (is_object($this->mailPreferences)) $ogServer = $this->mailPreferences->getOutgoingServer(0); if(($ogServer instanceof emailadmin_smtp)) { $ogServer->updateAccount($_hookValues); } } }