* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$
*/
namespace EGroupware\Api;
use Horde_Imap_Client;
use Horde_Imap_Client_Ids;
use Horde_Imap_Client_Fetch_Query;
use Horde_Imap_Client_Data_Fetch;
use Horde_Mime_Part;
use Horde_Imap_Client_Search_Query;
use Horde_Idna;
use Horde_Imap_Client_DateTime;
use Horde_Mime_Headers;
use Horde_Compress;
use Horde_Mime_Magic;
use Horde_Mail_Rfc822;
use Horde_Mail_Rfc822_List;
use Horde_Mime_Mdn;
use tidy;
/**
* Mail worker class
* -provides backend functionality for all classes in Mail
* -provides classes that may be used by other apps too
*
* @link https://github.com/horde/horde/blob/master/imp/lib/Contents.php
*/
class Mail
{
/**
* the current selected user profile
* @var int
*/
var $profileID = 0;
/**
* delimiter - used to separate acc_id from mailbox / folder-tree-structure
*
* @var string
*/
const DELIMITER = '::';
/**
* the current display char set
* @var string
*/
static $displayCharset;
static $activeFolderCache;
static $folderStatusCache;
static $supportsORinQuery;
/**
* Active preferences
*
* @var array
*/
var $mailPreferences;
/**
* active html Options
*
* @var array
*/
var $htmlOptions;
/**
* Active mimeType
*
* @var string
*/
var $activeMimeType;
/**
* Active incomming (IMAP) Server Object
*
* @var Api\Mail\Imap
*/
var $icServer;
/**
* Active outgoing (smtp) Server Object
*
* @var Api\Mail\Smtp
*/
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 $debugTimes = 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'=>false,'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)
// we switch the balance off because of some broken html mails contents get removed like (td in table), and let browser deal with it
'balance'=>0,//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 eats away even some wanted whitespace, so we switch it off;
// we used it for its compacting and beautifying capabilities, which resulted in better html for further processing
'tidy'=>0,
'elements' => "* -script",
'deny_attribute' => 'on*',
'schemes'=>'href: file, ftp, http, https, mailto, phone; 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->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.
* lang('Drafts'), lang('Templates'), lang('Sent'), lang('Trash'), lang('Junk'), lang('Outbox')
* 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;
/**
* Hold instances by profileID for getInstance() singleton
*
* @var array
*/
private static $instances = array();
private static $profileDefunct = array();
/**
* Singleton for Mail
*
* @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
* @param mixed boolean/object $_icServerObject - if object, return instance with object set as icServer
* immediately, if boolean === true use oldImapServer in constructor
* @param boolean $_reuseCache = null if null it is set to the value of $_restoreSession
* @return Mail
*/
public static function getInstance($_restoreSession=true, &$_profileID=0, $_validate=true, $_oldImapServerObject=false, $_reuseCache=null)
{
//$_restoreSession=false;
if (is_null($_reuseCache)) $_reuseCache = $_restoreSession;
//error_log(__METHOD__.' ('.__LINE__.') '.' RestoreSession:'.$_restoreSession.' ProfileId:'.$_profileID.'/'.Mail\Account::get_default_acc_id().' for user:'.$GLOBALS['egw_info']['user']['account_lid'].' called from:'.function_backtrace());
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_oldImapServerObject));
self::$profileDefunct = Cache::getCache(Cache::INSTANCE,'email','profileDefunct'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),5*1);
if (isset(self::$profileDefunct[$_profileID]) && strlen(self::$profileDefunct[$_profileID]))
{
throw new Exception(__METHOD__." failed to instanciate Mail for Profile #$_profileID Reason:".self::$profileDefunct[$_profileID]);
}
if ($_oldImapServerObject instanceof Mail\Imap)
{
if (!is_object(self::$instances[$_profileID]))
{
self::$instances[$_profileID] = new Mail('utf-8',false,$_profileID,false,$_reuseCache);
}
self::$instances[$_profileID]->icServer = $_oldImapServerObject;
self::$instances[$_profileID]->accountid= $_oldImapServerObject->ImapServerId;
self::$instances[$_profileID]->profileID= $_oldImapServerObject->ImapServerId;
self::$instances[$_profileID]->mailPreferences = $GLOBALS['egw_info']['user']['preferences']['mail'];
self::$instances[$_profileID]->htmlOptions = self::$instances[$_profileID]->mailPreferences['htmlOptions'];
return self::$instances[$_profileID];
}
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 = Mail\Account::get_default_acc_id();
}
if ($profileID!=$_profileID) $_restoreSession==false;
$_profileID=$profileID;
if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' called with profileID==0 using '.$profileID.' instead->'.function_backtrace());
}
// no validation or restoreSession for old ImapServer Object, just fetch it and return it
if ($_oldImapServerObject===true)
{
return new Mail('utf-8',false,$_profileID,true,$_reuseCache);
}
if ($_profileID != 0 && $_validate)
{
$profileID = self::validateProfileID($_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);
}
//Cache::setSession('mail','activeProfileID',$_profileID);
}
//error_log(__METHOD__.' ('.__LINE__.') '.' RestoreSession:'.$_restoreSession.' ProfileId:'.$_profileID.' called from:'.function_backtrace());
if ($_profileID && (!isset(self::$instances[$_profileID]) || $_restoreSession===false))
{
self::$instances[$_profileID] = new Mail('utf-8',$_restoreSession,$_profileID,false,$_reuseCache);
}
else
{
//refresh objects
try
{
self::$instances[$_profileID]->icServer = Mail\Account::read($_profileID)->imapServer();
self::$instances[$_profileID]->ogServer = Mail\Account::read($_profileID)->smtpServer();
// TODO: merge mailprefs into userprefs, for easy treatment
self::$instances[$_profileID]->mailPreferences = $GLOBALS['egw_info']['user']['preferences']['mail'];
self::$instances[$_profileID]->htmlOptions = self::$instances[$_profileID]->mailPreferences['htmlOptions'];
} catch (\Exception $e)
{
$newprofileID = Mail\Account::get_default_acc_id();
// try loading the default profile for the user
error_log(__METHOD__.' ('.__LINE__.') '." Loading the Profile for ProfileID ".$_profileID.' failed for icServer; '.$e->getMessage().' Trigger new instance for Default-Profile '.$newprofileID.'. called from:'.function_backtrace());
if ($newprofileID)
{
self::$instances[$newprofileID] = new Mail('utf-8',false,$newprofileID,false,$_reuseCache);
$_profileID = $newprofileID;
}
else
{
throw new Exception(__METHOD__." failed to load the Profile for ProfileID for $_profileID with error:".$e->getMessage().($e->details?', '.$e->details:''));
}
}
self::storeActiveProfileIDToPref(self::$instances[$_profileID]->icServer, $_profileID, $_validate );
}
self::$instances[$_profileID]->profileID = $_profileID;
if (!isset(self::$instances[$_profileID]->idna2)) self::$instances[$_profileID]->idna2 = new Horde_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];
}
/**
* This method tries to fix alias address lacking domain part
* by trying to add domain part extracted from given reference address
*
* @param string $refrence email address to be used for domain extraction
* @param string $address alias address
*
* @return string returns alias address with appended default domain
*/
public static function fixInvalidAliasAddress($refrence, $address)
{
$parts = explode('@', $refrence);
if (!strpos($address,'@') && !empty($parts[1])) $address .= '@'.$parts[1];
return $address;
}
/**
* store given ProfileID to Session and pref
*
* @param int $_profileID = 0
* @param boolean $_testConnection = 0
* @return mixed $_profileID or false on failed ConnectionTest
*/
public static function storeActiveProfileIDToPref($_icServerObject, $_profileID=0, $_testConnection=true)
{
if (isset($GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID']) && !empty($GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID']))
{
$oldProfileID = (int)$GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'];
}
if ($_testConnection)
{
try
{
$_icServerObject->getCurrentMailbox();
}
catch (\Exception $e)
{
if ($_profileID != Mail\Account::get_default_acc_id()) $_profileID = Mail\Account::get_default_acc_id();
error_log(__METHOD__.__LINE__.' '.$e->getMessage());
return false;
}
}
if ($oldProfileID != $_profileID)
{
if ($oldProfileID && $_profileID==0) $_profileID = $oldProfileID;
$GLOBALS['egw']->preferences->add('mail','ActiveProfileID',$_profileID,'user');
// save prefs
$GLOBALS['egw']->preferences->save_repository(true);
$GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'] = $_profileID;
Cache::setSession('mail','activeProfileID',$_profileID);
}
return $_profileID;
}
/**
* Validate given account acc_id to make sure account is valid for current user
*
* Validation checks:
* - non-empty imap-host
* - non-empty imap-username
*
* @param int $_acc_id = 0
* @return int validated acc_id -> either acc_id given, or first valid one
*/
public static function validateProfileID($_acc_id=0)
{
if ($_acc_id)
{
try {
$account = Mail\Account::read($_acc_id);
if ($account->is_imap())
{
return $_acc_id;
}
if (self::$debug) error_log(__METHOD__."($_acc_id) account NOT valid, no imap-host!");
}
catch (\Exception $e) {
unset($e);
if (self::$debug) error_log(__METHOD__."($_acc_id) account NOT found!");
}
}
// no account specified or specified account not found or not valid
// --> search existing account for first valid one and return that
foreach(Mail\Account::search($only_current_user=true, 'acc_imap_host') as $acc_id => $imap_host)
{
if (!empty($imap_host) && ($account = Mail\Account::read($acc_id)) && $account->is_imap())
{
if (self::$debug && $_acc_id) error_log(__METHOD__."($_acc_id) using $acc_id instead");
return $acc_id;
}
}
if (self::$debug) error_log(__METHOD__."($_acc_id) NO valid account found!");
return 0;
}
/**
* Private constructor, use Mail::getInstance() instead
*
* @param string $_displayCharset = 'utf-8'
* @param boolean $_restoreSession = true
* @param int $_profileID = 0 if not nummeric, we assume we only want an empty class object
* @param boolean $_oldImapServerObject = false
* @param boolean $_reuseCache = null if null it is set to the value of $_restoreSession
*/
private function __construct($_displayCharset='utf-8',$_restoreSession=true, $_profileID=0, $_oldImapServerObject=false, $_reuseCache=null)
{
if (is_null($_reuseCache)) $_reuseCache = $_restoreSession;
if (!empty($_displayCharset)) self::$displayCharset = $_displayCharset;
// not nummeric, we assume we only want an empty class object
if (!is_numeric($_profileID)) return true;
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();
}
if (!$_reuseCache) $this->forcePrefReload($_profileID,!$_reuseCache);
try
{
$this->profileID = self::validateProfileID($_profileID);
$this->accountid = $GLOBALS['egw_info']['user']['account_id'];
//error_log(__METHOD__.' ('.__LINE__.') '." ProfileID ".$this->profileID.' called from:'.function_backtrace());
$acc = Mail\Account::read($this->profileID);
}
catch (\Exception $e)
{
throw new Exception(__METHOD__." failed to instanciate Mail for $_profileID / ".$this->profileID." with error:".$e->getMessage());
}
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($acc->imapServer()));
$this->icServer = ($_oldImapServerObject?$acc->oldImapServer():$acc->imapServer());
$this->ogServer = $acc->smtpServer();
// TODO: merge mailprefs into userprefs, for easy treatment
$this->mailPreferences = $GLOBALS['egw_info']['user']['preferences']['mail'];
$this->htmlOptions = $this->mailPreferences['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');
}
/**
* forceEAProfileLoad
* used to force the load of a specific emailadmin profile; we assume administrative use only (as of now)
* @param int $_profile_id
* @return object instance of Mail (by reference)
*/
public static function &forceEAProfileLoad($_profile_id)
{
self::unsetCachedObjects($_profile_id);
$mail = self::getInstance(false, $_profile_id,false);
//_debug_array( $_profile_id);
$mail->icServer = Mail\Account::read($_profile_id)->imapServer();
$mail->ogServer = Mail\Account::read($_profile_id)->smtpServer();
return $mail;
}
/**
* trigger the force of the reload of the SessionData by resetting the session to an empty array
* @param int $_profile_id
* @param boolean $_resetFolderObjects
*/
public static function forcePrefReload($_profile_id=null,$_resetFolderObjects=true)
{
// unset the mail_preferences session object, to force the reload/rebuild
Cache::setSession('mail','mail_preferences',serialize(array()));
Cache::setSession('emailadmin','session_data',serialize(array()));
if ($_resetFolderObjects) self::resetFolderObjectCache($_profile_id);
}
/**
* restore the SessionData
*/
function restoreSessionData()
{
$this->sessionData = array();//Cache::getCache(Cache::SESSION,'mail','session_data',$callback=null,$callback_params=array(),$expiration=60*60*1);
self::$activeFolderCache = Cache::getCache(Cache::INSTANCE,'email','activeMailbox'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*10);
if (!empty(self::$activeFolderCache[$this->profileID])) $this->sessionData['mailbox'] = self::$activeFolderCache[$this->profileID];
}
/**
* saveSessionData saves session data
*/
function saveSessionData()
{
//error_log(__METHOD__.' ('.__LINE__.') '.array2string(array_keys($this->sessionData)));
if (!empty($this->sessionData['mailbox'])) self::$activeFolderCache[$this->profileID]=$this->sessionData['mailbox'];
if (isset(self::$activeFolderCache) && is_array(self::$activeFolderCache))
{
Cache::setCache(Cache::INSTANCE,'email','activeMailbox'.trim($GLOBALS['egw_info']['user']['account_id']),self::$activeFolderCache, 60*60*10);
}
// no need to block session any longer
$GLOBALS['egw']->session->commit_session();
}
/**
* unset certain CachedObjects for the given profile id, unsets the profile for default ID=0 as well
*
* 1) icServerIMAP_connectionError
* 2) icServerSIEVE_connectionError
* 3) INSTANCE OF MAIL_BO
* 4) HierarchyDelimiter
* 5) VacationNotice
*
* @param int $_profileID = null default profile of user as returned by getUserDefaultProfileID
* @return void
*/
static function unsetCachedObjects($_profileID=null)
{
if (is_null($_profileID)) $_profileID = Mail\Account::get_default_acc_id();
if (is_array($_profileID) && $_profileID['account_id']) $account_id = $_profileID['account_id'];
//error_log(__METHOD__.__LINE__.' called with ProfileID:'.array2string($_profileID).' from '.function_backtrace());
if (!is_array($_profileID) && (is_numeric($_profileID) || !(stripos($_profileID,'tracker_')===false)))
{
self::resetConnectionErrorCache($_profileID);
$rawHeadersCache = Cache::getCache(Cache::INSTANCE,'email','rawHeadersCache'.trim($account_id),$callback=null,$callback_params=array(),$expiration=60*60*1);
if (isset($rawHeadersCache[$_profileID]))
{
unset($rawHeadersCache[$_profileID]);
Cache::setCache(Cache::INSTANCE,'email','rawHeadersCache'.trim($account_id),$rawHeadersCache, $expiration=60*60*1);
}
$HierarchyDelimiterCache = Cache::getCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($account_id),$callback=null,$callback_params=array(),$expiration=60*60*24*5);
if (isset($HierarchyDelimiterCache[$_profileID]))
{
unset($HierarchyDelimiterCache[$_profileID]);
Cache::setCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($account_id),$HierarchyDelimiterCache, $expiration=60*60*24*5);
}
//reset folderObject cache, to trigger reload
self::resetFolderObjectCache($_profileID);
//reset counter of deleted messages per folder
$eMailListContainsDeletedMessages = Cache::getCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($account_id),$callback=null,$callback_params=array(),$expiration=60*60*1);
if (isset($eMailListContainsDeletedMessages[$_profileID]))
{
unset($eMailListContainsDeletedMessages[$_profileID]);
Cache::setCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($account_id),$eMailListContainsDeletedMessages, $expiration=60*60*1);
}
$vacationCached = Cache::getCache(Cache::INSTANCE, 'email', 'vacationNotice'.trim($account_id),$callback=null,$callback_params=array(),$expiration=60*60*24*1);
if (isset($vacationCached[$_profileID]))
{
unset($vacationCached[$_profileID]);
Cache::setCache(Cache::INSTANCE,'email','vacationNotice'.trim($account_id),$vacationCached, $expiration=60*60*24*1);
}
if (isset(self::$instances[$_profileID])) unset(self::$instances[$_profileID]);
}
if (is_array($_profileID) && $_profileID['location'] == 'clear_cache')
{
// called via hook
foreach($GLOBALS['egw']->accounts->search(array('type' => 'accounts','order' => 'account_lid')) as $account)
{
//error_log(__METHOD__.__LINE__.array2string($account));
$account_id = $account['account_id'];
$_profileID = null;
self::resetConnectionErrorCache($_profileID,$account_id);
self::resetFolderObjectCache($_profileID,$account_id);
Cache::setCache(Cache::INSTANCE,'email','rawHeadersCache'.trim($account_id),array(), 60*60*1);
Cache::setCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($account_id),array(), 60*60*24*5);
Cache::setCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($account_id),array(), 60*60*1);
Cache::setCache(Cache::INSTANCE,'email','vacationNotice'.trim($account_id),array(), 60*60*24*1);
}
}
}
/**
* resets the various cache objects where connection error Objects may be cached
*
* @param int $_ImapServerId the profileID to look for
* @param int $account_id the egw account to look for
*/
static function resetConnectionErrorCache($_ImapServerId=null,$account_id=null)
{
//error_log(__METHOD__.' ('.__LINE__.') '.' for Profile:'.array2string($_ImapServerId) .' for user:'.trim($account_id));
if (is_null($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))
{
$isConError = array();
$waitOnFailure = array();
}
else
{
$isConError = Cache::getCache(Cache::INSTANCE,'email','icServerSIEVE_connectionError'.trim($account_id));
if (isset($isConError[$_ImapServerId]))
{
unset($isConError[$_ImapServerId]);
}
$waitOnFailure = Cache::getCache(Cache::INSTANCE,'email','ActiveSyncWaitOnFailure'.trim($account_id),null,array(),60*60*2);
if (isset($waitOnFailure[$_ImapServerId]))
{
unset($waitOnFailure[$_ImapServerId]);
}
}
Cache::setCache(Cache::INSTANCE,'email','icServerSIEVE_connectionError'.trim($account_id),$isConError,60*15);
Cache::setCache(Cache::INSTANCE,'email','ActiveSyncWaitOnFailure'.trim($account_id),$waitOnFailure,60*60*2);
}
/**
* resets the various cache objects where Folder Objects may be cached
*
* @param int $_ImapServerId the profileID to look for
* @param int $account_id the egw account to look for
*/
static function resetFolderObjectCache($_ImapServerId=null,$account_id=null)
{
//error_log(__METHOD__.' ('.__LINE__.') '.' called for Profile:'.array2string($_ImapServerId).'->'.function_backtrace());
if (is_null($account_id)) $account_id = $GLOBALS['egw_info']['user']['account_id'];
// on [location] => verify_settings we coud either use [prefs] => Array([ActiveProfileID] => 9, .. as $_ImapServerId
// or treat it as not given. we try that path
if (is_null($_ImapServerId)||is_array($_ImapServerId))
{
$folders2return = array();
$folderInfo = array();
$folderBasicInfo = array();
$_specialUseFolders = array();
}
else
{
$folders2return = Cache::getCache(Cache::INSTANCE,'email','folderObjects'.trim($account_id),null,array(),60*60*1);
if (!empty($folders2return) && isset($folders2return[$_ImapServerId]))
{
unset($folders2return[$_ImapServerId]);
}
$folderInfo = Cache::getCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($account_id),null,array(),60*60*5);
if (!empty($folderInfo) && isset($folderInfo[$_ImapServerId]))
{
unset($folderInfo[$_ImapServerId]);
}
/*
$lastFolderUsedForMove = Cache::getCache(Cache::INSTANCE,'email','lastFolderUsedForMove'.trim($account_id),null,array(),$expiration=60*60*1);
if (isset($lastFolderUsedForMove[$_ImapServerId]))
{
unset($lastFolderUsedForMove[$_ImapServerId]);
}
*/
$folderBasicInfo = Cache::getCache(Cache::INSTANCE,'email','folderBasicInfo'.trim($account_id),null,array(),60*60*1);
if (!empty($folderBasicInfo) && isset($folderBasicInfo[$_ImapServerId]))
{
unset($folderBasicInfo[$_ImapServerId]);
}
$_specialUseFolders = Cache::getCache(Cache::INSTANCE,'email','specialUseFolders'.trim($account_id),null,array(),60*60*12);
if (!empty($_specialUseFolders) && isset($_specialUseFolders[$_ImapServerId]))
{
unset($_specialUseFolders[$_ImapServerId]);
self::$specialUseFolders=null;
}
}
Cache::setCache(Cache::INSTANCE,'email','folderObjects'.trim($account_id),$folders2return, 60*60*1);
Cache::setCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($account_id),$folderInfo,60*60*5);
//Cache::setCache(Cache::INSTANCE,'email','lastFolderUsedForMove'.trim($account_id),$lastFolderUsedForMove,$expiration=60*60*1);
Cache::setCache(Cache::INSTANCE,'email','folderBasicInfo'.trim($account_id),$folderBasicInfo,60*60*1);
Cache::setCache(Cache::INSTANCE,'email','specialUseFolders'.trim($account_id),$_specialUseFolders,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;
}
/**
* getUserEMailAddresses - function to gather the emailadresses connected to the current mail-account
* @param string $_profileID the ID of the mailaccount to check for identities, if null current mail-account is used
* @return array - array(email=>realname)
*/
function getUserEMailAddresses($_profileID=null)
{
$acc = Mail\Account::read((!empty($_profileID)?$_profileID:$this->profileID));
//error_log(__METHOD__.' ('.__LINE__.') '.':'.array2string($acc));
$identities = Mail\Account::identities($acc);
$userEMailAdresses = array($acc['ident_email']=>$acc['ident_realname']);
foreach($identities as $ik => $ident) {
//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($ident));
$identity = Mail\Account::read_identity($ik);
if (!empty($identity['ident_email']) && !isset($userEMailAdresses[$identity['ident_email']])) $userEMailAdresses[$identity['ident_email']] = $identity['ident_realname'];
}
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($userEMailAdresses));
return $userEMailAdresses;
}
/**
* getAllIdentities - function to gather the identities connected to the current user
* @param string/int $_accountToSearch = null if set search accounts for user specified
* @param boolean $resolve_placeholders wether or not resolve possible placeholders in identities
* @return array - array(email=>realname)
*/
static function getAllIdentities($_accountToSearch=null,$resolve_placeholders=false)
{
$userEMailAdresses = array();
foreach(Mail\Account::search($only_current_user=($_accountToSearch?$_accountToSearch:true), $just_name=true) as $acc_id => $identity_name)
{
$acc = Mail\Account::read($acc_id,($_accountToSearch?$_accountToSearch:null));
if (!$resolve_placeholders) $userEMailAdresses[$acc['ident_id']] = array('acc_id'=>$acc_id,'ident_id'=>$acc['ident_id'],'ident_email'=>$acc['ident_email'],'ident_org'=>$acc['ident_org'],'ident_realname'=>$acc['ident_realname'],'ident_signature'=>$acc['ident_signature'],'ident_name'=>$acc['ident_name']);
foreach(Mail\Account::identities($acc) as $ik => $ident) {
//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($ident));
$identity = Mail\Account::read_identity($ik,$resolve_placeholders);
//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($identity));
if (!isset($userEMailAdresses[$identity['ident_id']])) $userEMailAdresses[$identity['ident_id']] = array('acc_id'=>$acc_id,'ident_id'=>$identity['ident_id'],'ident_email'=>$identity['ident_email'],'ident_org'=>$identity['ident_org'],'ident_realname'=>$identity['ident_realname'],'ident_signature'=>$identity['ident_signature'],'ident_name'=>$identity['ident_name']);
}
}
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($userEMailAdresses));
return $userEMailAdresses;
}
/**
* Get all identities of given mailaccount
*
* @param int|Mail\Account $account account-object or acc_id
* @return array - array(email=>realname)
*/
function getAccountIdentities($account)
{
if (!$account instanceof Mail\Account)
{
$account = Mail\Account::read($account);
}
$userEMailAdresses = array();
foreach(Mail\Account::identities($account, true, 'params') as $ik => $ident) {
//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($ident));
$identity = Mail\Account::read_identity($ik,true,null,$account);
//error_log(__METHOD__.' ('.__LINE__.') '.':'.$ik.'->'.array2string($identity));
// standardIdentity has ident_id==acc_id (as it is done within account->identities)
if (empty($identity['ident_id'])) $identity['ident_id'] = $identity['acc_id'];
if (!isset($userEMailAdresses[$identity['ident_id']]))
{
$userEMailAdresses[$identity['ident_id']] = array('acc_id'=>$identity['acc_id'],
'ident_id'=>$identity['ident_id'],
'ident_email'=>$identity['ident_email'],
'ident_org'=>$identity['ident_org'],
'ident_realname'=>$identity['ident_realname'],
'ident_signature'=>$identity['ident_signature'],
'ident_name'=>$identity['ident_name']);
}
}
return $userEMailAdresses;
}
/**
* Function to gather the default identitiy connected to the current mailaccount
*
* @return int - id of the identity
*/
function getDefaultIdentity()
{
// retrieve the signature accociated with the identity
$id = $this->getIdentitiesWithAccounts($_accountData=array());
foreach(Mail\Account::identities($_accountData[$this->profileID] ?
$this->profileID : $_accountData[$id],false,'ident_id') as $accountData)
{
return $accountData;
}
}
/**
* 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;
$allAccountData = Mail\Account::search($only_current_user=true, false, null);
if ($allAccountData) {
$rememberFirst=$selectedFound=null;
foreach ($allAccountData as $tmpkey => $icServers)
{
if (is_null($rememberFirst)) $rememberFirst = $tmpkey;
if ($tmpkey == $selectedID) $selectedFound=true;
//error_log(__METHOD__.' ('.__LINE__.') '.' Key:'.$tmpkey.'->'.array2string($icServers->acc_imap_host));
$host = $icServers->acc_imap_host;
if (empty($host)) continue;
$identities[$icServers->acc_id] = $icServers['ident_realname'].' '.$icServers['ident_org'].' <'.$icServers['ident_email'].'>';
//error_log(__METHOD__.' ('.__LINE__.') '.' Key:'.$tmpkey.'->'.array2string($identities[$icServers->acc_id]));
}
}
return ($selectedFound?$selectedID:$rememberFirst);
}
/**
* construct the string representing an Identity passed by $identity
*
* @var array/object $identity, identity object that holds realname, organization, emailaddress and signatureid
* @var boolean $fullString full or false=NamePart only is returned
* @return string - constructed of identity object data as defined in mailConfig
*/
static function generateIdentityString($identity, $fullString=true)
{
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($identity));
//if (is_null(self::$mailConfig)) self::$mailConfig = Config::read('mail');
// not set? -> use default, means full display of all available data
//if (!isset(self::$mailConfig['how2displayIdentities'])) self::$mailConfig['how2displayIdentities']='';
$how2displayIdentities = '';
switch ($how2displayIdentities)
{
case 'email';
//$retData = str_replace('@',' ',$identity->emailAddress).($fullString===true?' <'.$identity->emailAddress.'>':'');
$retData = $identity['ident_email'].($fullString===true?' <'.$identity['ident_email'].'>':'');
break;
case 'nameNemail';
$retData = (!empty($identity['ident_realname'])?$identity['ident_realname']:substr_replace($identity['ident_email'],'',strpos($identity['ident_email'],'@'))).($fullString===true?' <'.$identity['ident_email'].'>':'');
break;
case 'orgNemail';
$retData = (!empty($identity['ident_org'])?$identity['ident_org']:substr_replace($identity['ident_email'],'',0,strpos($identity['ident_email'],'@')+1)).($fullString===true?' <'.$identity['ident_email'].'>':'');
break;
default:
$retData = $identity['ident_realname'].(!empty($identity['ident_org'])?' '.$identity['ident_org']:'').($fullString===true?' <'.$identity['ident_email'].'>':'');
}
return $retData;
}
/**
* closes a connection on the active Server ($this->icServer)
*
* @return void
*/
function closeConnection()
{
//if ($icServer->_connected) error_log(__METHOD__.' ('.__LINE__.') '.' disconnect from Server');
//error_log(__METHOD__."() ".function_backtrace());
$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)
{
if (self::$debugTimes) $starttime = microtime (true);
//error_log(__METHOD__.' ('.__LINE__.') '."('$_foldername') ".function_backtrace());
// 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( __METHOD__.' ('.__LINE__.') '." $_foldername ".function_backtrace());
//error_log(__METHOD__.' ('.__LINE__.') '.' Connected with icServer for Profile:'.$this->profileID.'?'.print_r($this->icServer->_connected,true));
if ($this->folderIsSelectable($_foldername)) {
$this->icServer->openMailbox($_foldername);
}
$folderOpened = $_foldername;
//}
if (self::$debugTimes) self::logRunTimes($starttime,null,'Folder:'.$_foldername,__METHOD__.' ('.__LINE__.') ');
}
/**
* openConnection
*
* @param int $_icServerID = 0
* @throws Horde_Imap_Client_Exception on connection error or authentication failure
* @throws InvalidArgumentException on missing credentials
*/
function openConnection($_icServerID=0)
{
//error_log( "-------------------------->open connection ".function_backtrace());
//error_log(__METHOD__.' ('.__LINE__.') '.' ->'.array2string($this->icServer));
if (self::$debugTimes) $starttime = microtime (true);
$mailbox=null;
try
{
if(isset($this->sessionData['mailbox'])&&$this->folderExists($this->sessionData['mailbox'])) $mailbox = $this->sessionData['mailbox'];
if (empty($mailbox)) $mailbox = $this->icServer->getCurrentMailbox();
/*
if (isset(Mail\Imap::$supports_keywords[$_icServerID]))
{
$this->icServer->openMailbox($mailbox);
}
else
{
$this->icServer->examineMailbox($mailbox);
}
*/
// the above should detect if there is a known information about supporting KEYWORDS
// but does not work as expected :-(
$this->icServer->examineMailbox($mailbox);
//error_log(__METHOD__." using existing Connection ProfileID:".$_icServerID.' Status:'.print_r($this->icServer->_connected,true));
//error_log(__METHOD__.' ('.__LINE__.') '."->open connection for Server with profileID:".$_icServerID.function_backtrace());
//make sure we are working with the correct hierarchyDelimiter on the current connection, calling getHierarchyDelimiter with false to reset the cache
$this->getHierarchyDelimiter(false);
self::$specialUseFolders = $this->getSpecialUseFolders();
}
catch (\Exception $e)
{
error_log(__METHOD__.' ('.__LINE__.') '."->open connection for Server with profileID:".$_icServerID." trying to examine ($mailbox) failed!".$e->getMessage());
throw new Exception(__METHOD__." failed to ".__METHOD__." on Profile to $_icServerID while trying to examine $mailbox:".$e->getMessage());
}
if (self::$debugTimes) self::logRunTimes($starttime,null,'ProfileID:'.$_icServerID,__METHOD__.' ('.__LINE__.') ');
}
/**
* getQuotaRoot
* return the qouta of the users INBOX
*
* @return mixed array/boolean
*/
function getQuotaRoot()
{
static $quota;
if (isset($quota)) return $quota;
if (isset(self::$profileDefunct[$this->profileID]) && strlen(self::$profileDefunct[$this->profileID]))
{
// something is wrong. Do not proceed. either no folder or profile is marked as defunct for this request
return false;
}
try
{
$this->icServer->getCurrentMailbox();
if(!$this->icServer->hasCapability('QUOTA')) {
$quota = false;
return false;
}
$quota = $this->icServer->getStorageQuotaRoot('INBOX');
}
catch (Exception $e)
{
//error_log(__METHOD__.array2string($e));
//error_log(__METHOD__." failed to fetch quota on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:'')/*.function_backtrace()*/);
if ($e->getCode()==102)
{
self::$profileDefunct[$this->profileID]=$e->getMessage().($e->details?', '.$e->details:'');
Cache::setCache(Cache::INSTANCE,'email','profileDefunct'.trim($GLOBALS['egw_info']['user']['account_id']),self::$profileDefunct, $expiration=5*1);
throw new Exception(__METHOD__." failed to fetch quota on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:''));
}
}
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($quota));
if(is_array($quota)) {
$quota = array(
'usage' => $quota['USED'],
'limit' => $quota['QMAX'],
);
} else {
$quota = false;
}
return $quota;
}
/**
* 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;
}
/**
* Fetch the namespace from icServer
*
* An IMAPServer may present several namespaces under each key:
* so we return an array of namespacearrays for our needs
*
* @return array array(prefix_present=>mixed (bool/string) ,prefix=>string,delimiter=>string,type=>string (personal|others|shared))
*/
function _getNameSpaces()
{
static $nameSpace = null;
$foldersNameSpace = array();
$delimiter = $this->getHierarchyDelimiter();
// TODO: cache by $this->icServer->ImapServerId
if (is_null($nameSpace)) $nameSpace = $this->icServer->getNameSpaceArray();
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($nameSpace));
if (is_array($nameSpace)) {
foreach($nameSpace as $type => $singleNameSpaceArray)
{
foreach ($singleNameSpaceArray as $singleNameSpace)
{
$_foldersNameSpace = array();
if($type == 'personal' && $singleNameSpace['name'] == '#mh/' && ($this->folderExists('Mail')||$this->folderExists('INBOX')))
{
$_foldersNameSpace['prefix_present'] = 'forced';
// uw-imap server with mailbox prefix or dovecot maybe
$_foldersNameSpace['prefix'] = ($this->folderExists('Mail')?'Mail':(!empty($singleNameSpace['name'])?$singleNameSpace['name']:''));
}
elseif($type == 'personal' && ($singleNameSpace['name'] == '#mh/') && $this->folderExists('mail'))
{
$_foldersNameSpace['prefix_present'] = 'forced';
// uw-imap server with mailbox prefix or dovecot maybe
$_foldersNameSpace['prefix'] = 'mail';
} else {
$_foldersNameSpace['prefix_present'] = !empty($singleNameSpace['name']);
$_foldersNameSpace['prefix'] = $singleNameSpace['name'];
}
$_foldersNameSpace['delimiter'] = ($singleNameSpace['delimiter']?$singleNameSpace['delimiter']:$delimiter);
$_foldersNameSpace['type'] = $type;
$foldersNameSpace[] =$_foldersNameSpace;
}
//echo "############## $type->".print_r($foldersNameSpace[$type],true)." ###################
";
}
}
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($foldersNameSpace));
return $foldersNameSpace;
}
/**
* Wrapper to extract the folder prefix from folder compared to given namespace array
*
* @param array $nameSpace
* @paam string $_folderName
* @return string the prefix (may be an empty string)
*/
function getFolderPrefixFromNamespace($nameSpace, $folderName)
{
foreach($nameSpace as &$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 = null;
if (is_null($HierarchyDelimiter)) $HierarchyDelimiter = Cache::getCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*24*5);
if ($_useCache===false) unset($HierarchyDelimiter[$this->icServer->ImapServerId]);
if (isset($HierarchyDelimiter[$this->icServer->ImapServerId])&&!empty($HierarchyDelimiter[$this->icServer->ImapServerId]))
{
return $HierarchyDelimiter[$this->icServer->ImapServerId];
}
$HierarchyDelimiter[$this->icServer->ImapServerId] = '/';
try
{
$this->icServer->getCurrentMailbox();
$HierarchyDelimiter[$this->icServer->ImapServerId] = $this->icServer->getDelimiter();
}
catch(\Exception $e)
{
if ($e->getCode()==102)
{
self::$profileDefunct[$this->profileID]=$e->getMessage().($e->details?', '.$e->details:'');
Cache::setCache(Cache::INSTANCE,'email','profileDefunct'.trim($GLOBALS['egw_info']['user']['account_id']),self::$profileDefunct, $expiration=5*1);
}
unset($e);
$HierarchyDelimiter[$this->icServer->ImapServerId] = '/';
}
Cache::setCache(Cache::INSTANCE,'email','HierarchyDelimiter'.trim($GLOBALS['egw_info']['user']['account_id']),$HierarchyDelimiter, 60*60*24*5);
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 = null;
if (is_null($_specialUseFolders)||empty($_specialUseFolders)) $_specialUseFolders = Cache::getCache(Cache::INSTANCE,'email','specialUseFolders'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*24*5);
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($this->icServer->acc_folder_trash));
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($this->icServer->acc_folder_sent));
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($this->icServer->acc_folder_draft));
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($this->icServer->acc_folder_template));
self::$specialUseFolders = $_specialUseFolders[$this->icServer->ImapServerId];
if (isset($_specialUseFolders[$this->icServer->ImapServerId]) && !empty($_specialUseFolders[$this->icServer->ImapServerId]))
return $_specialUseFolders[$this->icServer->ImapServerId];
$_specialUseFolders[$this->icServer->ImapServerId]=array();
//if (!empty($this->icServer->acc_folder_trash) && !isset($_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_trash]))
$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_trash]='Trash';
//if (!empty($this->icServer->acc_folder_draft) && !isset($_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_draft]))
$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_draft]='Drafts';
//if (!empty($this->icServer->acc_folder_sent) && !isset($_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_sent]))
$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_sent]='Sent';
//if (!empty($this->icServer->acc_folder_template) && !isset($_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_template]))
$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_template]='Templates';
$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_junk]='Junk';
$_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_archive]='Archive';
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_specialUseFolders));//.'<->'.array2string($this->icServer));
self::$specialUseFolders = $_specialUseFolders[$this->icServer->ImapServerId];
Cache::setCache(Cache::INSTANCE,'email','specialUseFolders'.trim($GLOBALS['egw_info']['user']['account_id']),$_specialUseFolders, 60*60*24*5);
return $_specialUseFolders[$this->icServer->ImapServerId];
}
/**
* get IMAP folder status regarding NoSelect
*
* @param foldertoselect string the foldername
*
* @return boolean true or false regarding the noselect attribute
*/
function folderIsSelectable($folderToSelect)
{
$retval = true;
if($folderToSelect && ($folderStatus = $this->getFolderStatus($folderToSelect,false,true))) {
if (!empty($folderStatus['attributes']) && 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
*
* @throws Exception
*/
function _getStatus($folderName,$ignoreStatusCache=false)
{
static $folderStatus = null;
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];
}
try
{
$folderStatus[$this->icServer->ImapServerId][$folderName] = $this->icServer->getStatus($folderName,$ignoreStatusCache);
}
catch (\Exception $e)
{
throw new Exception(__METHOD__.' ('.__LINE__.') '." failed for $folderName with error:".$e->getMessage().($e->details?', '.$e->details:''));
}
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
* @param fetchSubscribedInfo bool fetch Subscribed Info on folder
* @return array
*/
function getFolderStatus($_folderName,$ignoreStatusCache=false,$basicInfoOnly=false,$fetchSubscribedInfo=true)
{
if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '." called with:$_folderName,$ignoreStatusCache,$basicInfoOnly");
if (!is_string($_folderName) || empty($_folderName)||(isset(self::$profileDefunct[$this->profileID]) && strlen(self::$profileDefunct[$this->profileID])))
{
// something is wrong. Do not proceed. either no folder or profile is marked as defunct for this request
return false;
}
static $folderInfoCache = null; // reduce traffic on single request
static $folderBasicInfo = null;
if (isset($folderBasicInfo[$this->profileID]))
{
$folderInfoCache = $folderBasicInfo[$this->profileID];
}
if (isset($folderInfoCache[$_folderName]) && $ignoreStatusCache==false && $basicInfoOnly) return $folderInfoCache[$_folderName];
$retValue = array();
$retValue['subscribed'] = false;
/*
if(!$icServer = Mail\Account::read($this->profileID)) {
if (self::$debug) error_log(__METHOD__." no Server found for Folder:".$_folderName);
return false;
}
*/
//error_log(__METHOD__.' ('.__LINE__.') '.$_folderName.' '.array2string(array_keys($folderInfoCache)));
// does the folder exist???
if (is_null($folderInfoCache) || !isset($folderInfoCache[$_folderName]))
{
try
{
$ret = $this->icServer->getMailboxes($_folderName, 1, true);
}
catch (\Exception $e)
{
//error_log(__METHOD__.array2string($e));
//error_log(__METHOD__." failed to fetch Mailbox $_folderName on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:'')/*.function_backtrace()*/);
self::$profileDefunct[$this->profileID]=$e->getMessage().($e->details?', '.$e->details:'');
Cache::setCache(Cache::INSTANCE,'email','profileDefunct'.trim($GLOBALS['egw_info']['user']['account_id']),self::$profileDefunct, $expiration=5*1);
throw new Exception(__METHOD__." failed to fetch Mailbox $_folderName on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:''));
}
//error_log(__METHOD__.' ('.__LINE__.') '.$_folderName.' '.array2string($ret));
if (is_array($ret))
{
$retkeys = array_keys($ret);
if ($retkeys[0]==$_folderName) $folderInfoCache[$_folderName] = $ret[$retkeys[0]];
}
else
{
$folderInfoCache[$_folderName]=false;
}
}
$folderInfo = $folderInfoCache[$_folderName];
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($folderInfo).'->'.function_backtrace());
if($ignoreStatusCache||!$folderInfo|| !is_array($folderInfo)) {
try
{
$folderInfo = $this->_getStatus($_folderName,$ignoreStatusCache);
}
catch (\Exception $e)
{
//error_log(__METHOD__.array2string($e));
error_log(__METHOD__." failed to fetch status for $_folderName on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:'')/*.function_backtrace()*/);
self::$profileDefunct[$this->profileID]=$e->getMessage().($e->details?', '.$e->details:'');
Cache::setCache(Cache::INSTANCE,'email','profileDefunct'.trim($GLOBALS['egw_info']['user']['account_id']),self::$profileDefunct, $expiration=5*1);
//throw new Exception(__METHOD__." failed to fetch status for $_folderName on ".$this->profileID.' Reason:'.$e->getMessage().($e->details?', '.$e->details:''));
$folderInfo=null;
}
if (!is_array($folderInfo))
{
// no folder info, but there is a status returned for the folder: something is wrong, try to cope with it
$folderInfo = is_array($folderInfo)?$folderInfo:array('HIERACHY_DELIMITER'=>$this->getHierarchyDelimiter(),
'ATTRIBUTES' => '');
if (!isset($folderInfo['HIERACHY_DELIMITER']) || empty($folderInfo['HIERACHY_DELIMITER']) || (isset($folderInfo['delimiter']) && empty($folderInfo['delimiter'])))
{
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($folderInfo));
$folderInfo['HIERACHY_DELIMITER'] = $this->getHierarchyDelimiter();
}
}
}
#if(!is_array($folderInfo)) {
# return false;
#}
$retValue['delimiter'] = (isset($folderInfo['HIERACHY_DELIMITER']) && $folderInfo['HIERACHY_DELIMITER']?$folderInfo['HIERACHY_DELIMITER']:$folderInfo['delimiter']);
$retValue['attributes'] = (isset($folderInfo['ATTRIBUTES']) && $folderInfo['ATTRIBUTES']?$folderInfo['ATTRIBUTES']:$folderInfo['attributes']);
$shortNameParts = explode($retValue['delimiter'], $_folderName);
$retValue['shortName'] = array_pop($shortNameParts);
$retValue['displayName'] = $_folderName;
$retValue['shortDisplayName'] = $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) $folderBasicInfo[$this->profileID][$_folderName]=$retValue;
//error_log(__METHOD__.' ('.__LINE__.') '.' '.$_folderName.array2string($retValue['attributes']));
if ($basicInfoOnly || (isset($retValue['attributes']) && stripos(array2string($retValue['attributes']),'noselect')!==false))
{
return $retValue;
}
// fetch all in one go for one request, instead of querying them one by one
// cache it for a minute 60*60*1
// this should reduce communication to the imap server
static $subscribedFolders = null;
static $nameSpace = null;
static $prefix = null;
if (is_null($nameSpace) || empty($nameSpace[$this->profileID])) $nameSpace[$this->profileID] = $this->_getNameSpaces();
if (!empty($nameSpace[$this->profileID]))
{
$nsNoPersonal=array();
foreach($nameSpace[$this->profileID] as &$ns)
{
if ($ns['type']!='personal') $nsNoPersonal[]=$ns;
}
$nameSpace[$this->profileID]=$nsNoPersonal;
}
if (is_null($prefix) || empty($prefix[$this->profileID]) || empty($prefix[$this->profileID][$_folderName])) $prefix[$this->profileID][$_folderName] = $this->getFolderPrefixFromNamespace($nameSpace[$this->profileID], $_folderName);
if ($fetchSubscribedInfo && is_null($subscribedFolders) || empty($subscribedFolders[$this->profileID]))
{
$subscribedFolders[$this->profileID] = $this->icServer->listSubscribedMailboxes();
}
if($fetchSubscribedInfo && is_array($subscribedFolders[$this->profileID]) && in_array($_folderName,$subscribedFolders[$this->profileID])) {
$retValue['subscribed'] = true;
}
try
{
//$folderStatus = $this->_getStatus($_folderName,$ignoreStatusCache);
$folderStatus = $this->getMailBoxCounters($_folderName,false);
$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['trustServersUnseenInfo']) && // some servers dont serve the UNSEEN information
$this->mailPreferences['trustServersUnseenInfo']==false) ||
(isset($this->mailPreferences['trustServersUnseenInfo']) &&
$this->mailPreferences['trustServersUnseenInfo']==2 &&
$prefix[$this->profileID][$_folderName] != '' && stripos($_folderName,$prefix[$this->profileID][$_folderName]) !== false)
)
{
//error_log(__METHOD__." returned folderStatus for Folder $_folderName:".print_r($prefix,true).' TS:'.$this->mailPreferences['trustServersUnseenInfo']);
// we filter for the combined status of unseen and undeleted, as this is what we show in list
try
{
$byUid=true;
$_reverse=1;
$sortResult = $this->getSortedList($_folderName, $_sort=0, $_reverse, array('status'=>array('UNSEEN','UNDELETED')),$byUid,false);
$retValue['unseen'] = $sortResult['count'];
}
catch (\Exception $ee)
{
if (self::$debug) error_log(__METHOD__." could not fetch/calculate unseen counter for $_folderName Reason:'".$ee->getMessage()."' but requested.");
}
}
}
catch (\Exception $e)
{
if (self::$debug) error_log(__METHOD__." returned folderStatus for Folder $_folderName:".print_r($e->getMessage(),true));
}
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
* @param mixed $_fetchPreviews = false (boolean/int) fetch part of the body of the messages requested (if integer the number is assumed to be the number of chars to be returned for preview; if set to true 300 chars are returned (when available))
* @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, $_fetchPreviews=false)
{
//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
$this->reopen($_folderName);
//$currentFolder = $this->icServer->getCurrentMailbox();
//if ($currentFolder != $_folderName); $this->icServer->openMailbox($_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);
$total=0;
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||self::$debugTimes) $starttime = microtime (true);
//see this example below for a 12 week datefilter (since)
//$_filter = array('status'=>array('UNDELETED'),'type'=>"SINCE",'string'=> date("d-M-Y", $starttime-(3600*24*7*12)));
$_sortResult = $this->getSortedList($_folderName, $_sort, $reverse, $_filter, $rByUid, $_cacheResult);
$sortResult = $_sortResult['match']->ids;
//$modseq = $_sortResult['modseq'];
//error_log(__METHOD__.' ('.__LINE__.') '.'Modsequence:'.$modseq);
if (self::$debug||self::$debugTimes) self::logRunTimes($starttime,null,' call getSortedList for Folder:'.$_folderName.' Filter:'.array2string($_filter).' Ids:'.array2string($_thisUIDOnly),__METHOD__.' ('.__LINE__.') ');
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 = $_sortResult['count'];
#_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);
}
// fetch the data for the selected messages
if (self::$debug||self::$debugTimes) $starttime = microtime(true);
try
{
$uidsToFetch = new Horde_Imap_Client_Ids();
$uidsToFetch->add($sortResult);
$fquery = new Horde_Imap_Client_Fetch_Query();
// Pre-cache the headers we want, 'fetchHeaders' is a label into the cache
$fquery->headers('fetchHeaders',array(
'DISPOSITION-NOTIFICATION-TO','RETURN-RECEIPT-TO','X-CONFIRM-READING-TO',
'DATE','SUBJECT','FROM','TO','CC','REPLY-TO',
'X-PRIORITY'
),array(
// Cache headers, we'll look at them below
'cache' => true,//$_cacheResult,
// Set peek so messages are not flagged as read
'peek' => true
));
$fquery->size();
$fquery->structure();
$fquery->flags();
$fquery->imapDate();// needed to ensure getImapDate fetches the internaldate, not the current time
// if $_fetchPreviews is activated fetch part of the messages too
if ($_fetchPreviews) $fquery->fullText(array('peek'=>true,'length'=>((int)$_fetchPreviews<5000?5000:$_fetchPreviews),'start'=>0));
$headersNew = $this->icServer->fetch($_folderName, $fquery, array(
'ids' => $uidsToFetch,
));
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($headersNew->ids()));
}
catch (\Exception $e)
{
$headersNew = array();
$sortResult = array();
}
if (self::$debug||self::$debugTimes)
{
self::logRunTimes($starttime,null,'HordeFetch: for Folder:'.$_folderName.' Filter:'.array2string($_filter),__METHOD__.' ('.__LINE__.') ');
if (self::$debug)
{
$queryString = implode(',', $sortResult);
error_log(__METHOD__.' ('.__LINE__.') '.' Query:'.$queryString.' Result:'.array2string($headersNew));
}
}
$cnt = 0;
foreach((array)$sortResult as $uid) {
$sortOrder[$uid] = $cnt++;
}
$count = 0;
if (is_object($headersNew)) {
if (self::$debug||self::$debugTimes) $starttime = microtime(true);
foreach($headersNew->ids() as $id) {
$_headerObject = $headersNew->get($id);
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_headerObject));
$headerObject = array();
$bodyPreview = null;
$uid = $headerObject['UID']= ($_headerObject->getUid()?$_headerObject->getUid():$id);
$headerObject['MSG_NUM'] = $_headerObject->getSeq();
$headerObject['SIZE'] = $_headerObject->getSize();
$headerObject['INTERNALDATE'] = $_headerObject->getImapDate();
// Get already cached headers, 'fetchHeaders' is a label matchimg above
$headerForPrio = $_headerObject->getHeaders('fetchHeaders',Horde_Imap_Client_Data_Fetch::HEADER_PARSE)->toArray();
// Try to fetch header with key='' as some servers might have no fetchHeaders index. e.g. yandex.com
if (empty($headerForPrio)) $headerForPrio = $_headerObject->getHeaders('',Horde_Imap_Client_Data_Fetch::HEADER_PARSE)->toArray();
//fetch the fullMsg part if all conditions match to be available in case $_headerObject->getHeaders returns
//nothing worthwhile (as it does for googlemail accounts, when preview is switched on
if ($_fetchPreviews)
{
// on enabled preview $bodyPreview is needed lateron. fetched here, for fallback-reasons
// in case of failed Header-Retrieval
$bodyPreview = $_headerObject->getFullMsg();
if (empty($headerForPrio)||(is_array($headerForPrio)&&count($headerForPrio)===1&&$headerForPrio['']))
{
$length = strpos($bodyPreview, Horde_Mime_Part::RFC_EOL.Horde_Mime_Part::RFC_EOL);
if ($length===false) $length = strlen($bodyPreview);
$headerForPrio = Horde_Mime_Headers::parseHeaders(substr($bodyPreview, 0,$length))->toArray();
}
}
$headerForPrio = array_change_key_case($headerForPrio, CASE_UPPER);
if (self::$debug) {
error_log(__METHOD__.' ('.__LINE__.') '.array2string($_headerObject).'UID:'.$_headerObject->getUid().' Size:'.$_headerObject->getSize().' Date:'.$_headerObject->getImapDate().'/'.DateTime::to($_headerObject->getImapDate(),'Y-m-d H:i:s'));
error_log(__METHOD__.' ('.__LINE__.') '.array2string($headerForPrio));
}
// message deleted from server but cache still reporting its existence ; may happen on QRESYNC with No permanent modsequences
if (empty($headerForPrio))
{
$total--;
continue;
}
if ( isset($headerForPrio['DISPOSITION-NOTIFICATION-TO']) ) {
$headerObject['DISPOSITION-NOTIFICATION-TO'] = self::decode_header(trim($headerForPrio['DISPOSITION-NOTIFICATION-TO']));
} else if ( isset($headerForPrio['RETURN-RECEIPT-TO']) ) {
$headerObject['DISPOSITION-NOTIFICATION-TO'] = self::decode_header(trim($headerForPrio['RETURN-RECEIPT-TO']));
} else if ( isset($headerForPrio['X-CONFIRM-READING-TO']) ) {
$headerObject['DISPOSITION-NOTIFICATION-TO'] = self::decode_header(trim($headerForPrio['X-CONFIRM-READING-TO']));
} /*else $sent_not = "";*/
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($headerObject));
$headerObject['DATE'] = $headerForPrio['DATE'];
$headerObject['SUBJECT'] = (is_array($headerForPrio['SUBJECT'])?$headerForPrio['SUBJECT'][0]:$headerForPrio['SUBJECT']);
$headerObject['FROM'] = (array)($headerForPrio['FROM']?$headerForPrio['FROM']:($headerForPrio['REPLY-TO']?$headerForPrio['REPLY-TO']:$headerForPrio['RETURN-PATH']));
$headerObject['TO'] = (array)$headerForPrio['TO'];
$headerObject['CC'] = isset($headerForPrio['CC'])?(array)$headerForPrio['CC']:array();
$headerObject['REPLY-TO'] = isset($headerForPrio['REPLY-TO'])?(array)$headerForPrio['REPLY-TO']:array();
$headerObject['PRIORITY'] = isset($headerForPrio['X-PRIORITY'])?$headerForPrio['X-PRIORITY']:null;
foreach (array('FROM','TO','CC','REPLY-TO') as $key)
{
$address = array();
foreach ($headerObject[$key] as $k => $ad)
{
//the commented section below IS a simplified version of the section "make sure ..."
/*
if (stripos($ad,'@')===false)
{
$remember=$k;
}
else
{
$address[] = (!is_null($remember)?$headerObject[$key][$remember].' ':'').$ad;
$remember=null;
}
*/
// make sure addresses are real emailaddresses one by one in the array as expected
$rfcAddr = self::parseAddressList($ad); // does some fixing of known problems too
foreach ($rfcAddr as $_rfcAddr)
{
if (!$_rfcAddr->valid) continue; // skip. not a valid address
$address[] = imap_rfc822_write_address($_rfcAddr->mailbox,$_rfcAddr->host,$_rfcAddr->personal);
}
}
$headerObject[$key] = $address;
}
$headerObject['FLAGS'] = $_headerObject->getFlags();
$headerObject['BODYPREVIEW']=null;
// this section fetches part of the message-body (if enabled) for some kind of preview
// if we fail to succeed, we fall back to the retrieval of the message-body with
// fetchPartContents (see below, when we iterate over the structure to determine the
// existance (and the details) for attachments)
if ($_fetchPreviews)
{
// $bodyPreview is populated at the beginning of the loop, as it may be
// needed to parse the Headers of the Message
if (empty($bodyPreview)) $bodyPreview = $_headerObject->getFullMsg();
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($bodyPreview));
$base = Horde_Mime_Part::parseMessage($bodyPreview);
foreach($base->partIterator() as $part)
{
//error_log(__METHOD__.__LINE__.'Part:'.$part->getPrimaryType());
if (empty($headerObject['BODYPREVIEW'])&&$part->getPrimaryType()== 'text')
{
$charset = $part->getContentTypeParameter('charset');
$buffer = Mail\Html::convertHTMLToText($part->toString(array(
'encode' => Horde_Mime_Part::ENCODE_BINARY, // otherwise we cant recode charset
)), $charset, 'utf-8');
$headerObject['BODYPREVIEW']=trim(str_replace(array("\r\n","\r","\n"),' ',mb_substr(Translation::convert_jsonsafe($buffer),0,((int)$_fetchPreviews<300?300:$_fetchPreviews))));
} elseif (empty($headerObject['BODYPREVIEW'])&&$part->getPrimaryType()== 'multipart')
{
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($part));
}
}
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($headerObject['BODYPREVIEW']));
}
$mailStructureObject = $_headerObject->getStructure();
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($headerObject));
//error_log(__METHOD__.' ('.__LINE__.') '.' MimeMap:'.array2string($mailStructureObject->contentTypeMap()));
//foreach ($_headerObject->getStructure()->getParts() as $p => $part)
$headerObject['ATTACHMENTS']=null;
$skipParts=array();
$messageMimeType='';
foreach ($mailStructureObject->contentTypeMap() as $mime_id => $mime_type)
{
if ($mime_id==0 || $messageMimeType==='') $messageMimeType = $mime_type;
$part = $mailStructureObject->getPart($mime_id);
$partdisposition = $part->getDisposition();
$partPrimaryType = $part->getPrimaryType();
// this section fetches the body for the purpose of previewing a few lines
// drawback here it is talking to the mailserver for each mail thus consuming
// more time than expected; so we call this section only when there is no
// bodypreview could be found (multipart/....)
if ($_fetchPreviews && empty($headerObject['BODYPREVIEW'])&&($partPrimaryType == 'text') &&
((intval($mime_id) === 1) || !$mime_id) &&
($partdisposition !== 'attachment')) {
$_structure=$part;
$this->fetchPartContents($uid, $_structure, false,true);
$headerObject['BODYPREVIEW']=trim(str_replace(array("\r\n","\r","\n"),' ',mb_substr(Mail\Html::convertHTMLToText($_structure->getContents()),0,((int)$_fetchPreviews<300?300:$_fetchPreviews))));
$charSet=Translation::detect_encoding($headerObject['BODYPREVIEW']);
// add line breaks to $bodyParts
//error_log(__METHOD__.' ('.__LINE__.') '.' Charset:'.$bodyParts[$i]['charSet'].'->'.$bodyParts[$i]['body']);
$headerObject['BODYPREVIEW'] = Translation::convert_jsonsafe($headerObject['BODYPREVIEW'], $charSet);
//error_log(__METHOD__.__LINE__.$headerObject['BODYPREVIEW']);
}
//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.'->'.$mime_id.' Disp:'.$partdisposition.' Type:'.$partPrimaryType);
$cid = $part->getContentId();
if (empty($partdisposition) && $partPrimaryType != 'multipart' && $partPrimaryType != 'text')
{
// the presence of an cid does not necessarily indicate its inline. it may lack the needed
// link to show the image. Considering this: we "list" everything that matches the above criteria
// as attachment in order to not loose/miss information on our data
$partdisposition='attachment';//($partPrimaryType == 'image'&&!empty($cid)?'inline':'attachment');
}
if ($mime_type=='message/rfc822')
{
//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.'->'.$mime_id.':'.array2string($part->contentTypeMap()));
foreach($part->contentTypeMap() as $sub_id => $sub_type) { if ($sub_id != $mime_id) $skipParts[$sub_id] = $sub_type;}
}
//error_log(__METHOD__.' ('.__LINE__.') '.' Uid:'.$uid.'->'.$mime_id.' Disp:'.$partdisposition.' Type:'.$partPrimaryType.' Skip:'.array2string($skipParts));
if (array_key_exists($mime_id,$skipParts)) continue;
if ($partdisposition=='attachment' ||
($partdisposition=='inline'&&$partPrimaryType == 'image'&&$mime_type=='image/tiff') || // as we are not able to display tiffs
($partdisposition=='inline'&&$partPrimaryType == 'image'&&empty($cid)) ||
($partdisposition=='inline' && $partPrimaryType != 'image' && $partPrimaryType != 'multipart' && $partPrimaryType != 'text'))
{
$headerObject['ATTACHMENTS'][$mime_id]=$part->getAllDispositionParameters();
$headerObject['ATTACHMENTS'][$mime_id]['mimeType']=$mime_type;
$headerObject['ATTACHMENTS'][$mime_id]['uid']=$uid;
$headerObject['ATTACHMENTS'][$mime_id]['cid'] = $cid;
$headerObject['ATTACHMENTS'][$mime_id]['partID']=$mime_id;
if (!isset($headerObject['ATTACHMENTS'][$mime_id]['name']))$headerObject['ATTACHMENTS'][$mime_id]['name']=$part->getName();
if (!strcasecmp($headerObject['ATTACHMENTS'][$mime_id]['name'],'winmail.dat') ||
$headerObject['ATTACHMENTS'][$mime_id]['mimeType']=='application/ms-tnef')
{
$headerObject['ATTACHMENTS'][$mime_id]['is_winmail'] = true;
}
//error_log(__METHOD__.' ('.__LINE__.') '.' PartDisposition:'.$mime_id.'->'.array2string($part->getName()));
//error_log(__METHOD__.' ('.__LINE__.') '.' PartDisposition:'.$mime_id.'->'.array2string($part->getAllDispositionParameters()));
//error_log(__METHOD__.' ('.__LINE__.') '.' Attachment:'.$mime_id.'->'.array2string($headerObject['ATTACHMENTS'][$mime_id]));
}
}
//error_log(__METHOD__.' ('.__LINE__.') '.' FindBody (plain):'.array2string($mailStructureObject->findBody('plain')));
//error_log(__METHOD__.' ('.__LINE__.') '.' FindBody (html):'.array2string($mailStructureObject->findBody('html')));
//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'].'<->'.$headerObject['INTERNALDATE'] .'#');
//error_log(__METHOD__.' ('.__LINE__.') '.' '.$this->decode_subject($headerObject['SUBJECT']).'->'.$headerObject['DATE']);
if (isset($headerObject['ATTACHMENTS']) && count($headerObject['ATTACHMENTS'])) foreach ($headerObject['ATTACHMENTS'] as &$a) { $retValue['header'][$sortOrder[$uid]]['attachments'][]=$a;}
$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'] = $messageMimeType;
$retValue['header'][$sortOrder[$uid]]['id'] = $headerObject['MSG_NUM'];
$retValue['header'][$sortOrder[$uid]]['uid'] = $headerObject['UID'];
$retValue['header'][$sortOrder[$uid]]['bodypreview'] = $headerObject['BODYPREVIEW'];
$retValue['header'][$sortOrder[$uid]]['priority'] = ($headerObject['PRIORITY']?$headerObject['PRIORITY']:3);
//error_log(__METHOD__.' ('.__LINE__.') '.' '.array2string($retValue['header'][$sortOrder[$uid]]));
if (isset($headerObject['DISPOSITION-NOTIFICATION-TO'])) $retValue['header'][$sortOrder[$uid]]['disposition-notification-to'] = $headerObject['DISPOSITION-NOTIFICATION-TO'];
if (is_array($headerObject['FLAGS'])) {
$retValue['header'][$sortOrder[$uid]] = array_merge($retValue['header'][$sortOrder[$uid]],self::prepareFlagsArray($headerObject));
}
//error_log(__METHOD__.' ('.__LINE__.') '.$headerObject['SUBJECT'].'->'.array2string($_headerObject->getEnvelope()->__get('from')));
if(is_array($headerObject['FROM']) && $headerObject['FROM'][0]) {
$retValue['header'][$sortOrder[$uid]]['sender_address'] = self::decode_header($headerObject['FROM'][0],true);
}
if(is_array($headerObject['REPLY-TO']) && $headerObject['REPLY-TO'][0]) {
$retValue['header'][$sortOrder[$uid]]['reply_to_address'] = self::decode_header($headerObject['REPLY-TO'][0],true);
}
if(is_array($headerObject['TO']) && $headerObject['TO'][0]) {
$retValue['header'][$sortOrder[$uid]]['to_address'] = self::decode_header($headerObject['TO'][0],true);
if (count($headerObject['TO'])>1)
{
$ki=0;
foreach($headerObject['TO'] as $k => $add)
{
if ($k==0) continue;
//error_log(__METHOD__.' ('.__LINE__.') '."-> $k:".array2string($add));
$retValue['header'][$sortOrder[$uid]]['additional_to_addresses'][$ki] = self::decode_header($add,true);
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retValue['header'][$sortOrder[$uid]]['additional_to_addresses'][$ki]));
$ki++;
}
}
}
if(is_array($headerObject['CC']) && count($headerObject['CC'])>0) {
$ki=0;
foreach($headerObject['CC'] as $k => $add)
{
//error_log(__METHOD__.' ('.__LINE__.') '."-> $k:".array2string($add));
$retValue['header'][$sortOrder[$uid]]['cc_addresses'][$ki] = self::decode_header($add,true);
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retValue['header'][$sortOrder[$uid]]['additional_to_addresses'][$ki]));
$ki++;
}
}
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($retValue['header'][$sortOrder[$uid]]));
$count++;
}
if (self::$debug||self::$debugTimes) self::logRunTimes($starttime,null,' fetching Headers and stuff for Folder:'.$_folderName,__METHOD__.' ('.__LINE__.') ');
//self::$debug=false;
// sort the messages to the requested displayorder
if(is_array($retValue['header'])) {
$countMessages = $total;
if (isset($_filter['range'])) $countMessages = self::$folderStatusCache[$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;
}
}
/**
* static function prepareFlagsArray
* prepare headerObject to return some standardized array to tell which flags are set for a message
* @param array $headerObject - array to process, a full return array from icServer->getSummary
* @return array array of flags
*/
static function prepareFlagsArray($headerObject)
{
if (is_array($headerObject['FLAGS'])) $headerFlags = array_map('strtolower',$headerObject['FLAGS']);
$retValue = array();
$retValue['recent'] = in_array('\\recent', $headerFlags);
$retValue['flagged'] = in_array('\\flagged', $headerFlags);
$retValue['answered'] = in_array('\\answered', $headerFlags);
$retValue['forwarded'] = in_array('$forwarded', $headerFlags);
$retValue['deleted'] = in_array('\\deleted', $headerFlags);
$retValue['seen'] = in_array('\\seen', $headerFlags);
$retValue['draft'] = in_array('\\draft', $headerFlags);
$retValue['mdnsent'] = in_array('$mdnsent', $headerFlags)||in_array('mdnsent', $headerFlags);
$retValue['mdnnotsent'] = in_array('$mdnnotsent', $headerFlags)||in_array('mdnnotsent', $headerFlags);
$retValue['label1'] = in_array('$label1', $headerFlags);
$retValue['label2'] = in_array('$label2', $headerFlags);
$retValue['label3'] = in_array('$label3', $headerFlags);
$retValue['label4'] = in_array('$label4', $headerFlags);
$retValue['label5'] = in_array('$label5', $headerFlags);
//error_log(__METHOD__.' ('.__LINE__.') '.$headerObject['SUBJECT'].':'.array2string($retValue));
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)
{
static $cachedFolderStatus = null;
// in the past we needed examineMailbox to figure out if the server with the serverID support keywords
// this information is filled/provided by examineMailbox; but caching within one request seems o.k.
if (is_null($cachedFolderStatus) || !isset($cachedFolderStatus[$this->profileID][$_folderName]) )
{
$folderStatus = $cachedFolderStatus[$this->profileID][$_folderName] = $this->icServer->examineMailbox($_folderName);
}
else
{
$folderStatus = $cachedFolderStatus[$this->profileID][$_folderName];
}
//error_log(__METHOD__.' ('.__LINE__.') '.' F:'.$_folderName.' S:'.array2string($folderStatus));
//error_log(__METHOD__.' ('.__LINE__.') '.' Filter:'.array2string($_filter));
$try2useCache = true;
static $eMailListContainsDeletedMessages = null;
if (is_null($eMailListContainsDeletedMessages)) $eMailListContainsDeletedMessages = Cache::getCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),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)))
{
if (self::$debugTimes) $starttime = microtime(true);
if (is_null($eMailListContainsDeletedMessages) || empty($eMailListContainsDeletedMessages[$this->profileID]) || empty($eMailListContainsDeletedMessages[$this->profileID][$_folderName])) $eMailListContainsDeletedMessages = Cache::getCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*1);
$five=true;
$dReverse=1;
$deletedMessages = $this->getSortedList($_folderName, 0, $dReverse, array('status'=>array('DELETED')),$five,false);
if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') Found DeletedMessages:'.array2string($eMailListContainsDeletedMessages));
$eMailListContainsDeletedMessages[$this->profileID][$_folderName] =$deletedMessages['count'];
Cache::setCache(Cache::INSTANCE,'email','eMailListContainsDeletedMessages'.trim($GLOBALS['egw_info']['user']['account_id']),$eMailListContainsDeletedMessages, 60*60*1);
if (self::$debugTimes) self::logRunTimes($starttime,null,'setting eMailListContainsDeletedMessages for Profile:'.$this->profileID.' Folder:'.$_folderName.' to '.$eMailListContainsDeletedMessages[$this->profileID][$_folderName],__METHOD__.' ('.__LINE__.') '); //error_log(__METHOD__.' ('.__LINE__.') '.' Profile:'.$this->profileID.' Folder:'.$_folderName.' -> EXISTS/SessStat:'.array2string($folderStatus['MESSAGES']).'/'.self::$folderStatusCache[$this->profileID][$_folderName]['messages'].' ListContDelMsg/SessDeleted:'.$eMailListContainsDeletedMessages[$this->profileID][$_folderName].'/'.self::$folderStatusCache[$this->profileID][$_folderName]['deleted']);
}
$try2useCache = false;
//self::$supportsORinQuery[$this->profileID]=true;
if (is_null(self::$supportsORinQuery) || !isset(self::$supportsORinQuery[$this->profileID]))
{
self::$supportsORinQuery = Cache::getCache(Cache::INSTANCE,'email','supportsORinQuery'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*10);
if (!isset(self::$supportsORinQuery[$this->profileID])) self::$supportsORinQuery[$this->profileID]=true;
}
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($_filter).' SupportsOrInQuery:'.self::$supportsORinQuery[$this->profileID]);
$filter = $this->createIMAPFilter($_folderName, $_filter,self::$supportsORinQuery[$this->profileID]);
if (self::$debug)
{
$query_str = $filter->build();
error_log(__METHOD__.' ('.__LINE__.') '.' '.$query_str['query']);
}
//_debug_array($filter);
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($filter).'#'.array2string($this->icServer->capability()));
if($this->icServer->hasCapability('SORT')) {
// when using an orQuery and we sort by date. sort seems to fail on certain servers => ZIMBRA with Horde_Imap_Client
// thus we translate the search request from date to Horde_Imap_Client::SORT_SEQUENCE (which should be the same, if
// there is no messing with the dates)
//if (self::$supportsORinQuery[$this->profileID]&&$_sort=='date'&&$_filter['type']=='quick'&&!empty($_filter['string']))$_sort='INTERNALDATE';
if (self::$debug) error_log(__METHOD__." Mailserver has SORT Capability, SortBy: ".array2string($_sort)." Reverse: $_reverse");
$sortOrder = $this->_getSortString($_sort, $_reverse);
if ($_reverse && in_array(Horde_Imap_Client::SORT_REVERSE,$sortOrder)) $_reverse=false; // as we reversed the result already
if (self::$debug) error_log(__METHOD__." Mailserver runs SORT: SortBy:".array2string($_sort)."->".array2string($sortOrder)." Filter: ".array2string($filter));
try
{
$sortResult = $this->icServer->search($_folderName, $filter, array(
'sort' => $sortOrder,));
// Attempt another search without sorting filter if first try failed with
// no result, as may some servers do not coupe well with sort option
// eventhough they claim to support SORT capability.
if (!isset($sortResult['count'])) $sortResult = $this->icServer->search($_folderName, $filter);
// if there is an Error, we assume that the server is not capable of sorting
}
catch(\Exception $e)
{
//error_log(__METHOD__.'('.__LINE__.'):'.$e->getMessage());
$resultByUid = false;
$sortOrder = array(Horde_Imap_Client::SORT_SEQUENCE);
if ($_reverse) array_unshift($sortOrder,Horde_Imap_Client::SORT_REVERSE);
try
{
$sortResult = $this->icServer->search($_folderName, $filter, array(
'sort' => $sortOrder));
}
catch(\Exception $e)
{
error_log(__METHOD__.'('.__LINE__.'):'.$e->getMessage());
$sortResult = self::$folderStatusCache[$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");
//$sortOrder = array(Horde_Imap_Client::SORT_SEQUENCE);
//if ($_reverse) array_unshift($sortOrder,Horde_Imap_Client::SORT_REVERSE);
try
{
$sortResult = $this->icServer->search($_folderName, $filter, array()/*array(
'sort' => $sortOrder)*/);
}
catch(\Exception $e)
{
//error_log(__METHOD__.'('.__LINE__.'):'.$e->getMessage());
// possible error OR Query. But Horde gives no detailed Info :-(
self::$supportsORinQuery[$this->profileID]=false;
Cache::setCache(Cache::INSTANCE,'email','supportsORinQuery'.trim($GLOBALS['egw_info']['user']['account_id']),self::$supportsORinQuery,60*60*10);
if (self::$debug) error_log(__METHOD__.__LINE__." Mailserver seems to have NO OR Capability for Search:".$sortResult->message);
$filter = $this->createIMAPFilter($_folderName, $_filter, self::$supportsORinQuery[$this->profileID]);
try
{
$sortResult = $this->icServer->search($_folderName, $filter, array()/*array(
'sort' => $sortOrder)*/);
}
catch(\Exception $e)
{
}
}
if(is_array($sortResult['match'])) {
// not sure that this is going so succeed as $sortResult['match'] is a hordeObject
sort($sortResult['match'], SORT_NUMERIC);
}
if (self::$debug) error_log(__METHOD__." using Filter:".print_r($filter,true)." ->".print_r($sortResult,true));
}
if ($setSession)
{
self::$folderStatusCache[$this->profileID][$_folderName]['uidValidity'] = $folderStatus['UIDVALIDITY'];
self::$folderStatusCache[$this->profileID][$_folderName]['messages'] = $folderStatus['MESSAGES'];
self::$folderStatusCache[$this->profileID][$_folderName]['deleted'] = $eMailListContainsDeletedMessages[$this->profileID][$_folderName];
self::$folderStatusCache[$this->profileID][$_folderName]['uidnext'] = $folderStatus['UIDNEXT'];
self::$folderStatusCache[$this->profileID][$_folderName]['filter'] = $_filter;
self::$folderStatusCache[$this->profileID][$_folderName]['sortResult'] = $sortResult;
self::$folderStatusCache[$this->profileID][$_folderName]['sort'] = $_sort;
}
//error_log(__METHOD__." using Filter:".print_r($filter,true)." ->".print_r($sortResult,true));
//_debug_array($sortResult['match']->ids);
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 sort sequence for horde search
*/
function _getSortString($_sort, $_reverse=false)
{
$_reverse=false;
if (is_numeric($_sort))
{
switch($_sort) {
case 2:
$retValue = array(Horde_Imap_Client::SORT_FROM);
break;
case 4:
$retValue = array(Horde_Imap_Client::SORT_TO);
break;
case 3:
$retValue = array(Horde_Imap_Client::SORT_SUBJECT);
break;
case 6:
$retValue = array(Horde_Imap_Client::SORT_SIZE);
break;
case 0:
default:
$retValue = array(Horde_Imap_Client::SORT_DATE);
//$retValue = 'ARRIVAL';
break;
}
}
else
{
switch(strtoupper($_sort)) {
case 'FROMADDRESS':
$retValue = array(Horde_Imap_Client::SORT_FROM);
break;
case 'TOADDRESS':
$retValue = array(Horde_Imap_Client::SORT_TO);
break;
case 'SUBJECT':
$retValue = array(Horde_Imap_Client::SORT_SUBJECT);
break;
case 'SIZE':
$retValue = array(Horde_Imap_Client::SORT_SIZE);
break;
case 'ARRIVAL':
$retValue = array(Horde_Imap_Client::SORT_ARRIVAL);
break;
case 'UID': // should be equivalent to INTERNALDATE, which is ARRIVAL, which should be highest (latest) uid should be newest date
case 'INTERNALDATE':
$retValue = array(Horde_Imap_Client::SORT_SEQUENCE);
break;
case 'DATE':
default:
$retValue = array(Horde_Imap_Client::SORT_DATE);
break;
}
}
if ($_reverse) array_unshift($retValue,Horde_Imap_Client::SORT_REVERSE);
//error_log(__METHOD__.' ('.__LINE__.') '.' '.($_reverse?'REVERSE ':'').$_sort.'->'.$retValue);
return $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
* @param boolean $_supportsOrInQuery wether to use the OR Query on QuickSearch
* @return Horde_Imap_Client_Search_Query the IMAP filter
*/
function createIMAPFilter($_folder, $_criterias, $_supportsOrInQuery=true)
{
$imapFilter = new Horde_Imap_Client_Search_Query();
$imapFilter->charset('UTF-8');
//_debug_array($_criterias);
if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' Criterias:'.(!is_array($_criterias)?" none -> returning":array2string($_criterias)));
if((!is_array($_criterias) || $_criterias['status']=='any') &&
(!isset($_criterias['string']) || empty($_criterias['string'])) &&
(!isset($_criterias['range'])|| empty($_criterias['range']) ||
( !empty($_criterias['range'])&& ($_criterias['range']!='BETWEEN' && empty($_criterias['date'])||
($_criterias['range']=='BETWEEN' && empty($_criterias['since'])&& empty($_criterias['before']))))))
{
//error_log(__METHOD__.' ('.__LINE__.') returning early Criterias:'.print_r($_criterias, true));
$imapFilter->flag('DELETED', $set=false);
return $imapFilter;
}
$queryValid = false;
// statusQuery MUST be placed first, as search for subject/mailbody and such is
// depending on charset. flagSearch is not BUT messes the charset if called afterwards
$statusQueryValid = false;
foreach((array)$_criterias['status'] as $k => $criteria) {
$imapStatusFilter = new Horde_Imap_Client_Search_Query();
$imapStatusFilter->charset('UTF-8');
$criteria = strtoupper($criteria);
switch ($criteria) {
case 'ANSWERED':
case 'DELETED':
case 'FLAGGED':
case 'RECENT':
case 'SEEN':
$imapStatusFilter->flag($criteria, $set=true);
$queryValid = $statusQueryValid =true;
break;
case 'READ':
$imapStatusFilter->flag('SEEN', $set=true);
$queryValid = $statusQueryValid =true;
break;
case 'LABEL1':
case 'KEYWORD1':
case 'LABEL2':
case 'KEYWORD2':
case 'LABEL3':
case 'KEYWORD3':
case 'LABEL4':
case 'KEYWORD4':
case 'LABEL5':
case 'KEYWORD5':
$imapStatusFilter->flag(str_ireplace('KEYWORD','$LABEL',$criteria), $set=true);
$queryValid = $statusQueryValid =true;
break;
case 'NEW':
$imapStatusFilter->flag('RECENT', $set=true);
$imapStatusFilter->flag('SEEN', $set=false);
$queryValid = $statusQueryValid =true;
break;
case 'OLD':
$imapStatusFilter->flag('RECENT', $set=false);
$queryValid = $statusQueryValid =true;
break;
// operate only on system flags
// $systemflags = array(
// 'ANSWERED', 'DELETED', 'DRAFT', 'FLAGGED', 'RECENT', 'SEEN'
// );
case 'UNANSWERED':
$imapStatusFilter->flag('ANSWERED', $set=false);
$queryValid = $statusQueryValid =true;
break;
case 'UNDELETED':
$imapFilter->flag('DELETED', $set=false);
$queryValid = true;
break;
case 'UNFLAGGED':
$imapStatusFilter->flag('FLAGGED', $set=false);
$queryValid = $statusQueryValid =true;
break;
case 'UNREAD':
case 'UNSEEN':
$imapStatusFilter->flag('SEEN', $set=false);
$queryValid = $statusQueryValid =true;
break;
case 'UNLABEL1':
case 'UNKEYWORD1':
case 'UNLABEL2':
case 'UNKEYWORD2':
case 'UNLABEL3':
case 'UNKEYWORD3':
case 'UNLABEL4':
case 'UNKEYWORD4':
case 'UNLABEL5':
case 'UNKEYWORD5':
$imapStatusFilter->flag(str_ireplace(array('UNKEYWORD','UNLABEL'),'$LABEL',$criteria), $set=false);
$queryValid = $statusQueryValid =true;
break;
default:
$statusQueryValid = false;
}
if ($statusQueryValid)
{
$imapFilter->andSearch($imapStatusFilter);
}
}
//error_log(__METHOD__.' ('.__LINE__.') '.print_r($_criterias, true));
$imapSearchFilter = new Horde_Imap_Client_Search_Query();
$imapSearchFilter->charset('UTF-8');
if(!empty($_criterias['string'])) {
$criteria = strtoupper($_criterias['type']);
switch ($criteria) {
case 'BYDATE':
case 'QUICK':
case 'QUICKWITHCC':
$imapSearchFilter->headerText('SUBJECT', $_criterias['string'], $not=false);
//$imapSearchFilter->charset('UTF-8');
$imapFilter2 = new Horde_Imap_Client_Search_Query();
$imapFilter2->charset('UTF-8');
if($this->isSentFolder($_folder)) {
$imapFilter2->headerText('TO', $_criterias['string'], $not=false);
} else {
$imapFilter2->headerText('FROM', $_criterias['string'], $not=false);
}
if ($_supportsOrInQuery)
{
$imapSearchFilter->orSearch($imapFilter2);
}
else
{
$imapSearchFilter->andSearch($imapFilter2);
}
if ($_supportsOrInQuery && $criteria=='QUICKWITHCC')
{
$imapFilter3 = new Horde_Imap_Client_Search_Query();
$imapFilter3->charset('UTF-8');
$imapFilter3->headerText('CC', $_criterias['string'], $not=false);
$imapSearchFilter->orSearch($imapFilter3);
}
$queryValid = true;
break;
case 'LARGER':
case 'SMALLER':
if (strlen(trim($_criterias['string'])) != strlen((float) trim($_criterias['string'])))
{
//examine string to evaluate size
$unit = strtoupper(trim(substr(trim($_criterias['string']),strlen((float) trim($_criterias['string'])))));
$multipleBy = array('KB'=>1024,'K'=>1024,
'MB'=>1024*1000,'M'=>1024*1000,
'GB'=>1024*1000*1000,'G'=>1024*1000*1000,
'TB'=>1024*1000*1000*1000,'T'=>1024*1000*1000*1000);
$numberinBytes=(float)$_criterias['string'];
if (isset($multipleBy[$unit])) $numberinBytes=(float)$_criterias['string']*$multipleBy[$unit];
//error_log(__METHOD__.__LINE__.'#'.$_criterias['string'].'->'.(float)$_criterias['string'].'#'.$unit.' ='.$numberinBytes);
$_criterias['string']=$numberinBytes;
}
$imapSearchFilter->size( $_criterias['string'], ($criteria=='LARGER'?true:false), $not=false);
//$imapSearchFilter->charset('UTF-8');
$queryValid = true;
break;
case 'FROM':
case 'TO':
case 'CC':
case 'BCC':
case 'SUBJECT':
$imapSearchFilter->headerText($criteria, $_criterias['string'], $not=false);
//$imapSearchFilter->charset('UTF-8');
$queryValid = true;
break;
case 'BODY':
case 'TEXT':
$imapSearchFilter->text($_criterias['string'],($criteria=='BODY'?true:false), $not=false);
//$imapSearchFilter->charset('UTF-8');
$queryValid = true;
break;
case 'SINCE':
$imapSearchFilter->dateSearch(new DateTime($_criterias['string']), Horde_Imap_Client_Search_Query::DATE_SINCE, $header=true, $not=false);
$queryValid = true;
break;
case 'BEFORE':
$imapSearchFilter->dateSearch(new DateTime($_criterias['string']), Horde_Imap_Client_Search_Query::DATE_BEFORE, $header=true, $not=false);
$queryValid = true;
break;
case 'ON':
$imapSearchFilter->dateSearch(new DateTime($_criterias['string']), Horde_Imap_Client_Search_Query::DATE_ON, $header=true, $not=false);
$queryValid = true;
break;
}
}
if ($statusQueryValid && !$queryValid) $queryValid=true;
if ($queryValid) $imapFilter->andSearch($imapSearchFilter);
if (isset($_criterias['range']) && !empty($_criterias['range']))
{
$rangeValid = false;
$imapRangeFilter = new Horde_Imap_Client_Search_Query();
$imapRangeFilter->charset('UTF-8');
$criteria = strtoupper($_criterias['range']);
if ($_criterias['range'] == "BETWEEN" && isset($_criterias['since']) && isset($_criterias['before']) && $_criterias['since']==$_criterias['before'])
{
$_criterias['date']=$_criterias['since'];
unset($_criterias['since']);
unset($_criterias['before']);
$criteria=$_criterias['range']='ON';
}
switch ($criteria) {
case 'BETWEEN':
//try to be smart about missing
//enddate
if ($_criterias['since'])
{
$imapRangeFilter->dateSearch(new DateTime($_criterias['since']), Horde_Imap_Client_Search_Query::DATE_SINCE, $header=true, $not=false);
$rangeValid = true;
}
//startdate
if ($_criterias['before'])
{
$imapRangeFilter2 = new Horde_Imap_Client_Search_Query();
$imapRangeFilter2->charset('UTF-8');
//our before (startdate) is inklusive, as we work with "d-M-Y", we must add a day
$_criterias['before'] = date("d-M-Y",DateTime::to($_criterias['before'],'ts')+(3600*24));
$imapRangeFilter2->dateSearch(new DateTime($_criterias['before']), Horde_Imap_Client_Search_Query::DATE_BEFORE, $header=true, $not=false);
$imapRangeFilter->andSearch($imapRangeFilter2);
$rangeValid = true;
}
break;
case 'SINCE'://enddate
$imapRangeFilter->dateSearch(new DateTime(($_criterias['since']?$_criterias['since']:$_criterias['date'])), Horde_Imap_Client_Search_Query::DATE_SINCE, $header=true, $not=false);
$rangeValid = true;
break;
case 'BEFORE'://startdate
//our before (startdate) is inklusive, as we work with "d-M-Y", we must add a day
$_criterias['before'] = date("d-M-Y",DateTime::to(($_criterias['before']?$_criterias['before']:$_criterias['date']),'ts')+(3600*24));
$imapRangeFilter->dateSearch(new DateTime($_criterias['before']), Horde_Imap_Client_Search_Query::DATE_BEFORE, $header=true, $not=false);
$rangeValid = true;
break;
case 'ON':
$imapRangeFilter->dateSearch(new DateTime($_criterias['date']), Horde_Imap_Client_Search_Query::DATE_ON, $header=true, $not=false);
$rangeValid = true;
break;
}
if ($rangeValid && !$queryValid) $queryValid=true;
if ($rangeValid) $imapFilter->andSearch($imapRangeFilter);
}
if (self::$debug)
{
//$imapFilter->charset('UTF-8');
$query_str = $imapFilter->build();
//error_log(__METHOD__.' ('.__LINE__.') '.' '.$query_str['query'].' created by Criterias:'.(!is_array($_criterias)?" none -> returning":array2string($_criterias)));
}
if($queryValid==false) {
$imapFilter->flag('DELETED', $set=false);
return $imapFilter;
} else {
return $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 = Mail\Html::decodeMailHeader($_string,self::$displayCharset);
$test = @json_encode($_string);
//error_log(__METHOD__.__LINE__.' ->'.strlen($singleBodyPart['body']).' Error:'.json_last_error().'<- BodyPart:#'.$test.'#');
if (($test=="null" || $test === false || is_null($test)) && strlen($_string)>0)
{
// try to fix broken utf8
$x = utf8_encode($_string);
$test = @json_encode($x);
if (($test=="null" || $test === false || is_null($test)) && strlen($_string)>0)
{
// this should not be needed, unless something fails with charset detection/ wrong charset passed
$_string = (function_exists('mb_convert_encoding')?mb_convert_encoding($_string,'UTF-8','UTF-8'):(function_exists('iconv')?@iconv("UTF-8","UTF-8//IGNORE",$_string):$_string));
}
else
{
$_string = $x;
}
}
if ($_tryIDNConversion===true && stripos($_string,'@')!==false)
{
$rfcAddr = self::parseAddressList($_string);
$stringA = array();
//$_string = str_replace($rfcAddr[0]->host,Horde_Idna::decode($rfcAddr[0]->host),$_string);
foreach ($rfcAddr as $_rfcAddr)
{
if (!$_rfcAddr->valid)
{
$stringA = array();
break; // skip idna conversion if we encounter an error here
}
$stringA[] = imap_rfc822_write_address($_rfcAddr->mailbox,Horde_Idna::decode($_rfcAddr->host),$_rfcAddr->personal);
}
if (!empty($stringA)) $_string = implode(',',$stringA);
}
if ($_tryIDNConversion==='FORCE')
{
//error_log(__METHOD__.' ('.__LINE__.') '.'->'.$_string.'='.Horde_Idna::decode($_string));
$_string = Horde_Idna::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);
// make sure its utf-8
$test = @json_encode($_string);
if (($test=="null" || $test === false || is_null($test)) && strlen($_string)>0)
{
$_string = utf8_encode($_string);
}
return $_string;
}
/**
* decodeEntityFolderName - remove html entities
* @param string _folderName the foldername
* @return string the converted string
*/
function decodeEntityFolderName($_folderName)
{
return html_entity_decode($_folderName, ENT_QUOTES, self::$displayCharset);
}
/**
* 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);
}
/**
* convert the foldername from display charset to UTF-7
*
* @param string _parent the parent foldername
* @return ISO-8859-1 / UTF7-IMAP encoded string
*/
function _encodeFolderName($_folderName) {
return Translation::convert($_folderName, self::$displayCharset, 'ISO-8859-1');
#return Translation::convert($_folderName, self::$displayCharset, 'UTF7-IMAP');
}
/**
* create a new folder under given parent folder
*
* @param string _parent the parent foldername
* @param string _folderName the new foldername
* @param string _error pass possible error back to caller
*
* @return mixed name of the newly created folder or false on error
*/
function createFolder($_parent, $_folderName, &$_error)
{
if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '."->"."$_parent, $_folderName called from:".function_backtrace());
$parent = $_parent;//$this->_encodeFolderName($_parent);
$folderName = $_folderName;//$this->_encodeFolderName($_folderName);
if(empty($parent)) {
$newFolderName = $folderName;
} else {
$HierarchyDelimiter = $this->getHierarchyDelimiter();
$newFolderName = $parent . $HierarchyDelimiter . $folderName;
}
if (empty($newFolderName)) return false;
if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.'->'.$newFolderName);
if ($this->folderExists($newFolderName,true))
{
if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '." Folder $newFolderName already exists.");
return $newFolderName;
}
try
{
$opts = array();
// if new created folder is a specal-use-folder, mark it as such, so other clients know to use it too
if (isset(self::$specialUseFolders[$newFolderName]))
{
$opts['special_use'] = self::$specialUseFolders[$newFolderName];
}
$this->icServer->createMailbox($newFolderName, $opts);
}
catch (\Exception $e)
{
$_error = lang('Could not create Folder %1 Reason: %2',$newFolderName,$e->getMessage());
error_log(__METHOD__.' ('.__LINE__.') '.' create Folder '.$newFolderName.'->'.$e->getMessage().' ('.$e->details.') Namespace:'.array2string($this->icServer->getNameSpaces()).function_backtrace());
return false;
}
try
{
$this->icServer->subscribeMailbox($newFolderName);
}
catch (\Exception $e)
{
error_log(__METHOD__.' ('.__LINE__.') '.' subscribe to new folder '.$newFolderName.'->'.$e->getMessage().' ('.$e->details);
return false;
}
return $newFolderName;
}
/**
* rename a folder
*
* @param string _oldFolderName the old foldername
* @param string _parent the parent foldername
* @param string _folderName the new foldername
*
* @return mixed name of the newly created folder or false on error
* @throws Exception
*/
function renameFolder($_oldFolderName, $_parent, $_folderName)
{
$oldFolderName = $_oldFolderName;//$this->_encodeFolderName($_oldFolderName);
$parent = $_parent;//$this->_encodeFolderName($_parent);
$folderName = $_folderName;//$this->_encodeFolderName($_folderName);
if(empty($parent)) {
$newFolderName = $folderName;
} else {
$HierarchyDelimiter = $this->getHierarchyDelimiter();
$newFolderName = $parent . $HierarchyDelimiter . $folderName;
}
if (self::$debug) error_log("create folder: $newFolderName");
try
{
$this->icServer->renameMailbox($oldFolderName, $newFolderName);
}
catch (\Exception $e)
{
throw new Exception(__METHOD__." failed for $oldFolderName (rename to: $newFolderName) with error:".$e->getMessage());;
}
// clear FolderExistsInfoCache
Cache::setCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($GLOBALS['egw_info']['user']['account_id']),$folderInfo,60*60*5);
return $newFolderName;
}
/**
* delete an existing folder
*
* @param string _folderName the name of the folder to be deleted
*
* @return bool true on success, PEAR Error on failure
* @throws Exception
*/
function deleteFolder($_folderName)
{
//$folderName = $this->_encodeFolderName($_folderName);
try
{
$this->icServer->subscribeMailbox($_folderName,false);
$this->icServer->deleteMailbox($_folderName);
}
catch (\Exception $e)
{
throw new Exception("Deleting Folder $_folderName failed! Error:".$e->getMessage());;
}
// clear FolderExistsInfoCache
Cache::setCache(Cache::INSTANCE,'email','icServerFolderExistsInfo'.trim($GLOBALS['egw_info']['user']['account_id']),$folderInfo,60*60*5);
return true;
}
/**
* fetchUnSubscribedFolders: get unsubscribed IMAP folder list
*
* returns an array of unsubscribed IMAP folder names.
*
* @return array with folder names. eg.: 1 => INBOX/TEST
*/
function fetchUnSubscribedFolders()
{
$unSubscribedMailboxes = $this->icServer->listUnSubscribedMailboxes();
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($unSubscribedMailboxes));
return $unSubscribedMailboxes;
}
/**
* 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
* -> autoFolders should be created if needed / accessed (if possible and configured)
* @param boolean _useCacheIfPossible - if set to false cache will be ignored and reinitialized
*
* @return array with folder objects. eg.: INBOX => {inbox object}
*/
function getFolderObjects($_subscribedOnly=false, $_getCounters=false, $_alwaysGetDefaultFolders=false,$_useCacheIfPossible=true)
{
if (self::$debug) error_log(__METHOD__.' ('.__LINE__.') '.' ServerID:'.$this->icServer->ImapServerId.", subscribedOnly:$_subscribedOnly, getCounters:$_getCounters, alwaysGetDefaultFolders:$_alwaysGetDefaultFolders, _useCacheIfPossible:$_useCacheIfPossible");
if (self::$debugTimes) $starttime = microtime (true);
static $folders2return;
//$_subscribedOnly=false;
// always use static on single request if info is available;
// so if you require subscribed/unsubscribed results on a single request you MUST
// set $_useCacheIfPossible to false !
if ($_useCacheIfPossible && isset($folders2return[$this->icServer->ImapServerId]) && !empty($folders2return[$this->icServer->ImapServerId]))
{
if (self::$debugTimes) self::logRunTimes($starttime,null,'using static',__METHOD__.' ('.__LINE__.') ');
return $folders2return[$this->icServer->ImapServerId];
}
if ($_subscribedOnly && $_getCounters===false)
{
if (is_null($folders2return)) $folders2return = Cache::getCache(Cache::INSTANCE,'email','folderObjects'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*1);
if ($_useCacheIfPossible && isset($folders2return[$this->icServer->ImapServerId]) && !empty($folders2return[$this->icServer->ImapServerId]))
{
//error_log(__METHOD__.' ('.__LINE__.') '.' using Cached folderObjects'.array2string($folders2return[$this->icServer->ImapServerId]));
if (self::$debugTimes) self::logRunTimes($starttime,null,'from Cache',__METHOD__.' ('.__LINE__.') ');
return $folders2return[$this->icServer->ImapServerId];
}
}
// use $folderBasicInfo for holding attributes and other basic folderinfo $folderBasicInfo[$this->icServer->ImapServerId]
static $folderBasicInfo;
if (is_null($folderBasicInfo)||!isset($folderBasicInfo[$this->icServer->ImapServerId])) $folderBasicInfo = Cache::getCache(Cache::INSTANCE,'email','folderBasicInfo'.trim($GLOBALS['egw_info']['user']['account_id']),null,array(),60*60*1);
//error_log(__METHOD__.' ('.__LINE__.') '.array2string(array_keys($folderBasicInfo[$this->icServer->ImapServerId])));
$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 = $this->getMailBoxCounters('INBOX');
}
// force unsubscribed by preference showAllFoldersInFolderPane
if ($_subscribedOnly == true &&
isset($this->mailPreferences['showAllFoldersInFolderPane']) &&
$this->mailPreferences['showAllFoldersInFolderPane']==1)
{
$_subscribedOnly = false;
}
$inboxFolderObject = array('INBOX' => $inboxData);
//$nameSpace = $this->icServer->getNameSpaces();
$nameSpace = $this->_getNameSpaces();
$fetchedAllInOneGo = false;
$subscribedFoldersForCache = $foldersNameSpace = array();
//error_log(__METHOD__.__LINE__.array2string($nameSpace));
if (is_array($nameSpace))
{
foreach($nameSpace as $k => $singleNameSpace) {
$type = $singleNameSpace['type'];
// the following line (assumption that for the same namespace the delimiter should be equal) may be wrong
$foldersNameSpace[$type]['delimiter'] = $singleNameSpace['delimiter'];
if(is_array($singleNameSpace)&&$fetchedAllInOneGo==false) {
// fetch and sort the subscribed folders
// we alway fetch the subscribed, as this provides the only way to tell
// if a folder is subscribed or not
if ($_subscribedOnly == true)
{
try
{
$subscribedMailboxes = $this->icServer->listSubscribedMailboxes('',0,true);
if (!empty($subscribedMailboxes))
{
$fetchedAllInOneGo = true;
}
else
{
$subscribedMailboxes = $this->icServer->listSubscribedMailboxes($singleNameSpace['prefix'],0,true);
}
}
catch(Exception $e)
{
continue;
}
//echo "subscribedMailboxes";_debug_array($subscribedMailboxes);
$subscribedFoldersPerNS = (!empty($subscribedMailboxes)?array_keys($subscribedMailboxes):array());
//if (is_array($foldersNameSpace[$type]['subscribed'])) sort($foldersNameSpace[$type]['subscribed']);
//_debug_array($foldersNameSpace);
//error_log(__METHOD__.__LINE__.array2string($singleNameSpace).':#:'.array2string($subscribedFoldersPerNS));
if (!empty($subscribedFoldersPerNS) && !empty($subscribedMailboxes))
{
//error_log(__METHOD__.' ('.__LINE__.') '." $type / subscribed:". array2string($subscribedMailboxes));
foreach ($subscribedMailboxes as $k => $finfo)
{
//error_log(__METHOD__.__LINE__.$k.':#:'.array2string($finfo));
$subscribedFoldersForCache[$this->icServer->ImapServerId][$k]=
$folderBasicInfo[$this->icServer->ImapServerId][$k]=array(
'MAILBOX'=>$finfo['MAILBOX'],
'ATTRIBUTES'=>$finfo['ATTRIBUTES'],
'delimiter'=>$finfo['delimiter'],//lowercase for some reason???
'SUBSCRIBED'=>$finfo['SUBSCRIBED'],//seeded by getMailboxes
);
if (empty($foldersNameSpace[$type]['subscribed']) || !in_array($k,$foldersNameSpace[$type]['subscribed']))
{
$foldersNameSpace[$type]['subscribed'][] = $k;
}
if (empty($foldersNameSpace[$type]['all']) || !in_array($k,$foldersNameSpace[$type]['all']))
{
$foldersNameSpace[$type]['all'][] = $k;
}
}
}
//error_log(__METHOD__.' ('.__LINE__.') '.' '.$type.'->'.array2string($foldersNameSpace[$type]['subscribed']));
if (!is_array($foldersNameSpace[$type]['all'])) $foldersNameSpace[$type]['all'] = array();
if ($_subscribedOnly == true && !empty($foldersNameSpace[$type]['subscribed'])) {
continue;
}
}
// fetch and sort all folders
//echo $type.'->'.$singleNameSpace['prefix'].'->'.($type=='shared'?0:2).", so we rather don't
//$_html = str_replace("\r\n",' ',$_html);
//$_html = str_replace("\t",' ',$_html);
//error_log(__METHOD__.__LINE__.':'.$_html);
//repair doubleencoded ampersands, and some stuff htmLawed stumbles upon with balancing switched on
$_html = str_replace(array('&','
'," ",' ','','
',' ',' ','',' '),
array('&', '
', '
', '
', '','', '', '', '', ''),$_html);
//$_html = str_replace(array('&'),array('&'),$_html);
if (stripos($_html,'style')!==false) Mail\Html::replaceTagsCompletley($_html,'style'); // clean out empty or pagewide style definitions / left over tags
if (stripos($_html,'head')!==false) Mail\Html::replaceTagsCompletley($_html,'head'); // Strip out stuff in head
//if (stripos($_html,'![if')!==false && stripos($_html,'')!==false) Mail\Html::replaceTagsCompletley($_html,'!\[if','',false); // Strip out stuff in ifs
//if (stripos($_html,'!--[if')!==false && stripos($_html,'')!==false) Mail\Html::replaceTagsCompletley($_html,'!--\[if','',false); // Strip out stuff in ifs
//error_log(__METHOD__.' ('.__LINE__.') '.$_html);
if (get_magic_quotes_gpc() === 1) $_html = stripslashes($_html);
// Strip out doctype in head, as htmlLawed cannot handle it TODO: Consider extracting it and adding it afterwards
if (stripos($_html,'!doctype')!==false) Mail\Html::replaceTagsCompletley($_html,'!doctype');
if (stripos($_html,'?xml:namespace')!==false) Mail\Html::replaceTagsCompletley($_html,'\?xml:namespace','/>',false);
if (stripos($_html,'?xml version')!==false) Mail\Html::replaceTagsCompletley($_html,'\?xml version','\?>',false);
if (strpos($_html,'!CURSOR')!==false) Mail\Html::replaceTagsCompletley($_html,'!CURSOR');
// htmLawed filter only the 'body'
//preg_match('`(]*>)(.+?)(