forked from extern/egroupware
2168 lines
94 KiB
PHP
2168 lines
94 KiB
PHP
<?php
|
|
/**
|
|
* EGroupware - Mail - interface class for activesync implementation
|
|
*
|
|
* @link http://www.egroupware.org
|
|
* @package mail
|
|
* @author Stylite AG [info@stylite.de]
|
|
* @author Ralf Becker <rb@stylite.de>
|
|
* @author Philip Herbert <philip@knauber.de>
|
|
* @copyright (c) 2014 by Stylite AG <info-AT-stylite.de>
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
* @version $Id$
|
|
*/
|
|
|
|
/**
|
|
* mail eSync plugin
|
|
*
|
|
* Plugin creates a device specific file to map alphanumeric folder names to nummeric id's.
|
|
*/
|
|
class mail_activesync implements activesync_plugin_write, activesync_plugin_sendmail, activesync_plugin_meeting_response, activesync_plugin_search_mailbox
|
|
{
|
|
/**
|
|
* var BackendEGW
|
|
*/
|
|
private $backend;
|
|
|
|
/**
|
|
* Instance of mail_bo
|
|
*
|
|
* @var mail_bo
|
|
*/
|
|
private $mail;
|
|
|
|
/**
|
|
* Instance of uidisplay
|
|
* needed to use various bodyprocessing functions
|
|
*
|
|
* @var uidisplay
|
|
*/
|
|
//private $ui; // may not be needed after all
|
|
|
|
/**
|
|
* Integer id of trash folder
|
|
*
|
|
* @var mixed
|
|
*/
|
|
private $_wasteID = false;
|
|
|
|
/**
|
|
* Integer id of sent folder
|
|
*
|
|
* @var mixed
|
|
*/
|
|
private $_sentID = false;
|
|
|
|
/**
|
|
* Integer id of current mail account / connection
|
|
*
|
|
* @var int
|
|
*/
|
|
private $account;
|
|
|
|
private $folders;
|
|
|
|
private $messages;
|
|
|
|
static $profileID;
|
|
|
|
/**
|
|
* Integer waitOnFailureDefault how long (in seconds) to wait on connection failure
|
|
*
|
|
* @var int
|
|
*/
|
|
protected $waitOnFailureDefault = 120;
|
|
|
|
/**
|
|
* Integer waitOnFailureLimit how long (in seconds) to wait on connection failure until a 500 is raised
|
|
*
|
|
* @var int
|
|
*/
|
|
protected $waitOnFailureLimit = 7200;
|
|
/**
|
|
* debugLevel - enables more debug
|
|
*
|
|
* @var int
|
|
*/
|
|
private $debugLevel = 0;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param BackendEGW $backend
|
|
*/
|
|
public function __construct(BackendEGW $backend)
|
|
{
|
|
if ($GLOBALS['egw_setup']) return;
|
|
|
|
//$this->debugLevel=2;
|
|
$this->backend = $backend;
|
|
if (!isset($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID']))
|
|
{
|
|
if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' Noprefs set: using 0 as default');
|
|
// globals preferences add appname varname value
|
|
$GLOBALS['egw']->preferences->add('activesync','mail-ActiveSyncProfileID',0,'user');
|
|
// save prefs
|
|
$GLOBALS['egw']->preferences->save_repository(true);
|
|
}
|
|
if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' ActiveProfileID:'.array2string(self::$profileID));
|
|
|
|
if (is_null(self::$profileID))
|
|
{
|
|
if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' self::ProfileID isNUll:'.array2string(self::$profileID));
|
|
self::$profileID =& egw_cache::getSession('mail','activeSyncProfileID');
|
|
if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' ActiveProfileID (after reading Cache):'.array2string(self::$profileID));
|
|
}
|
|
if (isset($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID']))
|
|
{
|
|
if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' Pref for ProfileID:'.array2string($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID']));
|
|
if ($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID'] == 'G')
|
|
{
|
|
self::$profileID = 'G'; // this should trigger the fetch of the first negative profile (or if no negative profile is available the firstb there is)
|
|
}
|
|
else
|
|
{
|
|
self::$profileID = (int)$GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID'];
|
|
}
|
|
}
|
|
if ($this->debugLevel>1) error_log(__METHOD__.__LINE__.' Profile Selected (after reading Prefs):'.array2string(self::$profileID));
|
|
|
|
// verify we are on an existing profile, if not running in setup (settings can not be static according to interface!)
|
|
if (!isset($GLOBALS['egw_setup']))
|
|
{
|
|
try {
|
|
emailadmin_account::read(self::$profileID);
|
|
}
|
|
catch(Exception $e) {
|
|
unset($e);
|
|
self::$profileID = emailadmin_account::get_default_acc_id();
|
|
}
|
|
}
|
|
if ($this->debugLevel>0) error_log(__METHOD__.'::'.__LINE__.' ProfileSelected:'.self::$profileID);
|
|
//$this->debugLevel=0;
|
|
}
|
|
|
|
/**
|
|
* Populates $settings for the preferences
|
|
*
|
|
* @param array|string $hook_data
|
|
* @return array
|
|
*/
|
|
function settings($hook_data)
|
|
{
|
|
//error_log(__METHOD__.__LINE__.array2string($hook_data));
|
|
$identities = array();
|
|
if (!isset($hook_data['setup']) && in_array($hook_data['type'], array('user', 'group')))
|
|
{
|
|
$identities = iterator_to_array(emailadmin_account::search((int)$hook_data['account_id']));
|
|
}
|
|
$identities += array(
|
|
'G' => lang('Primary Profile'),
|
|
);
|
|
|
|
$settings['mail-ActiveSyncProfileID'] = array(
|
|
'type' => 'select',
|
|
'label' => 'eMail Account to sync',
|
|
'name' => 'mail-ActiveSyncProfileID',
|
|
'help' => 'eMail Account to sync ',
|
|
'values' => $identities,
|
|
'default'=> 'G',
|
|
'xmlrpc' => True,
|
|
'admin' => False,
|
|
);
|
|
$settings['mail-allowSendingInvitations'] = array(
|
|
'type' => 'select',
|
|
'label' => 'allow sending of calendar invitations using this profile?',
|
|
'name' => 'mail-allowSendingInvitations',
|
|
'help' => 'control the sending of calendar invitations while using this profile',
|
|
'values' => array(
|
|
'sendifnocalnotif'=>'only send if there is no notification in calendar',
|
|
'send'=>'yes, always send',
|
|
'nosend'=>'no, do not send',
|
|
),
|
|
'xmlrpc' => True,
|
|
'default' => 'sendifnocalnotif',
|
|
'admin' => False,
|
|
);
|
|
return $settings;
|
|
}
|
|
|
|
/**
|
|
* Verify preferences
|
|
*
|
|
* @param array|string $hook_data
|
|
* @return array with error-messages from all plugins
|
|
*/
|
|
function verify_settings($hook_data)
|
|
{
|
|
$errors = array();
|
|
|
|
// check if an eSync eMail profile is set (might not be set as default or forced!)
|
|
if (isset($hook_data['prefs']['mail-ActiveSyncProfileID']) || $hook_data['type'] == 'user')
|
|
{
|
|
// eSync and eMail translations are not (yet) loaded
|
|
translation::add_app('activesync');
|
|
translation::add_app('mail');
|
|
|
|
// inject preference to verify and call constructor
|
|
$GLOBALS['egw_info']['user']['preferences']['activesync']['mail-ActiveSyncProfileID'] =
|
|
$hook_data['prefs']['mail-ActiveSyncProfileID'];
|
|
$this->__construct($this->backend);
|
|
|
|
try {
|
|
$this->_connect(0,true);
|
|
$this->_disconnect();
|
|
|
|
if (!$this->_wasteID) $errors[] = lang('No valid %1 folder configured!', '<b>'.lang('trash').'</b>');
|
|
if (!$this->_sentID) $errors[] = lang('No valid %1 folder configured!', '<b>'.lang('send').'</b>');
|
|
}
|
|
catch(Exception $e) {
|
|
$errors[] = lang('Can not open IMAP connection').': '.$e->getMessage();
|
|
}
|
|
if ($errors)
|
|
{
|
|
$errors[] = '<b>'.lang('eSync will FAIL without a working eMail configuration!').'</b>';
|
|
}
|
|
}
|
|
//error_log(__METHOD__.'('.array2string($hook_data).') returning '.array2string($errors));
|
|
return $errors;
|
|
}
|
|
|
|
/**
|
|
* Open IMAP connection
|
|
*
|
|
* @param int $account integer id of account to use
|
|
* @param boolean $verify_mode mode used for verify_settings; we want the exception but not the header stuff
|
|
* @todo support different accounts
|
|
*/
|
|
private function _connect($account=0, $verify_mode=false)
|
|
{
|
|
static $waitOnFailure;
|
|
if (is_null($account)) $account = 0;
|
|
if ($this->mail && $this->account != $account) $this->_disconnect();
|
|
|
|
$hereandnow = egw_time::to('now','ts');
|
|
$this->_wasteID = false;
|
|
$this->_sentID = false;
|
|
|
|
$connectionFailed = false;
|
|
|
|
if ($verify_mode==false && (is_null($waitOnFailure)||empty($waitOnFailure[self::$profileID])||empty($waitOnFailure[self::$profileID][$this->backend->_devid])))
|
|
{
|
|
$waitOnFailure = egw_cache::getCache(egw_cache::INSTANCE,'email','ActiveSyncWaitOnFailure'.trim($GLOBALS['egw_info']['user']['account_id']),$callback=null,$callback_params=array(),$expiration=60*60*2);
|
|
}
|
|
if (isset($waitOnFailure[self::$profileID]) && !empty($waitOnFailure[self::$profileID]) && !empty($waitOnFailure[self::$profileID][$this->backend->_devid]) && isset($waitOnFailure[self::$profileID][$this->backend->_devid]['lastattempt']) && !empty($waitOnFailure[self::$profileID][$this->backend->_devid]['lastattempt']) && isset($waitOnFailure[self::$profileID][$this->backend->_devid]['howlong']) && !empty($waitOnFailure[self::$profileID][$this->backend->_devid]['howlong']))
|
|
{
|
|
if ($waitOnFailure[self::$profileID][$this->backend->_devid]['lastattempt']+$waitOnFailure[self::$profileID][$this->backend->_devid]['howlong']<$hereandnow)
|
|
{
|
|
if ($this->debugLevel>0); error_log(__METHOD__.__LINE__.'# Instance='.$GLOBALS['egw_info']['user']['domain'].', User='.$GLOBALS['egw_info']['user']['account_lid']." Refuse to open connection for Profile:".self::$profileID.' Device '.$this->backend->_devid.' should still wait '.array2string($waitOnFailure[self::$profileID][$this->backend->_devid]));
|
|
header("HTTP/1.1 503 Service Unavailable");
|
|
$hL = $waitOnFailure[self::$profileID][$this->backend->_devid]['lastattempt']+$waitOnFailure[self::$profileID][$this->backend->_devid]['howlong']-$hereandnow;
|
|
header("Retry-After: ".$hL);
|
|
exit;
|
|
}
|
|
}
|
|
if (!$this->mail)
|
|
{
|
|
$this->account = $account;
|
|
// todo: tell mail which account to use
|
|
//error_log(__METHOD__.__LINE__.' create object with ProfileID:'.array2string(self::$profileID));
|
|
try
|
|
{
|
|
$this->mail = mail_bo::getInstance(false,self::$profileID,true,false,true);
|
|
if (self::$profileID == 0 && isset($this->mail->icServer->ImapServerId) && !empty($this->mail->icServer->ImapServerId)) self::$profileID = $this->mail->icServer->ImapServerId;
|
|
$this->mail->openConnection(self::$profileID,false);
|
|
$connectionFailed = false;
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
$connectionFailed = true;
|
|
$errorMessage = $e->getMessage();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//error_log(__METHOD__.__LINE__." connect with profileID: ".self::$profileID);
|
|
if (self::$profileID == 0 && isset($this->mail->icServer->ImapServerId) && !empty($this->mail->icServer->ImapServerId)) self::$profileID = $this->mail->icServer->ImapServerId;
|
|
try
|
|
{
|
|
$this->mail->openConnection(self::$profileID,false);
|
|
$connectionFailed = false;
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
$connectionFailed = true;
|
|
$errorMessage = $e->getMessage();
|
|
}
|
|
}
|
|
if (empty($waitOnFailure[self::$profileID][$this->backend->_devid])) $waitOnFailure[self::$profileID][$this->backend->_devid] = array('howlong'=>$this->waitOnFailureDefault,'lastattempt'=>$hereandnow);
|
|
if ($connectionFailed)
|
|
{
|
|
// in verify_moode, we want the exeption, but not the exit
|
|
if ($verify_mode)
|
|
{
|
|
throw new egw_exception_not_found(__METHOD__.__LINE__."($account) can not open connection on Profile #".self::$profileID."!".$this->mail->getErrorMessage().' for Instance='.$GLOBALS['egw_info']['user']['domain']);
|
|
}
|
|
else
|
|
{
|
|
//error_log(__METHOD__.__LINE__."($account) could not open connection!".$errorMessage);
|
|
//error_log(date('Y-m-d H:i:s').' '.__METHOD__.__LINE__."($account) can not open connection!".$this->mail->getErrorMessage()."\n",3,'/var/lib/egroupware/esync-imap.log');
|
|
//error_log('# Instance='.$GLOBALS['egw_info']['user']['domain'].', User='.$GLOBALS['egw_info']['user']['account_lid'].', URL='.
|
|
// ($_SERVER['HTTPS']?'https://':'http://').$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']."\n\n",3,'/var/lib/egroupware/esync-imap.log');
|
|
if ($waitOnFailure[self::$profileID][$this->backend->_devid]['howlong'] > $this->waitOnFailureLimit )
|
|
{
|
|
$waitOnFailure[self::$profileID][$this->backend->_devid] = array('howlong'=>$this->waitOnFailureDefault,'lastattempt'=>$hereandnow);
|
|
egw_cache::setCache(egw_cache::INSTANCE,'email','ActiveSyncWaitOnFailure'.trim($GLOBALS['egw_info']['user']['account_id']),$waitOnFailure,$expiration=60*60*2);
|
|
header("HTTP/1.1 500 Internal Server Error");
|
|
throw new egw_exception_not_found(__METHOD__.__LINE__."($account) can not open connection on Profile #".self::$profileID."!".$errorMessage.' for Instance='.$GLOBALS['egw_info']['user']['domain'].', User='.$GLOBALS['egw_info']['user']['account_lid'].', Device:'.$this->backend->_devid);
|
|
}
|
|
else
|
|
{
|
|
//error_log(__METHOD__.__LINE__.'# Instance='.$GLOBALS['egw_info']['user']['domain'].', User='.$GLOBALS['egw_info']['user']['account_lid']." Can not open connection for Profile:".self::$profileID.' Device:'.$this->backend->_devid.' should wait '.array2string($waitOnFailure[self::$profileID][$this->backend->_devid]));
|
|
$waitaslongasthis = $waitOnFailure[self::$profileID][$this->backend->_devid]['howlong'];
|
|
$waitOnFailure[self::$profileID][$this->backend->_devid] = array('howlong'=>(empty($waitOnFailure[self::$profileID][$this->backend->_devid]['howlong'])?$this->waitOnFailureDefault:$waitOnFailure[self::$profileID][$this->backend->_devid]['howlong']) * 2,'lastattempt'=>$hereandnow);
|
|
egw_cache::setCache(egw_cache::INSTANCE,'email','ActiveSyncWaitOnFailure'.trim($GLOBALS['egw_info']['user']['account_id']),$waitOnFailure,$expiration=60*60*2);
|
|
header("HTTP/1.1 503 Service Unavailable");
|
|
header("Retry-After: ".$waitaslongasthis);
|
|
$ethrown = new egw_exception_not_found(__METHOD__.__LINE__."($account) can not open connection on Profile #".self::$profileID."!".$errorMessage.' for Instance='.$GLOBALS['egw_info']['user']['domain'].', User='.$GLOBALS['egw_info']['user']['account_lid'].', Device:'.$this->backend->_devid." Should wait for:".$waitaslongasthis.'(s)'.' WaitInfoStored2Cache:'.array2string($waitOnFailure));
|
|
_egw_log_exception($ethrown);
|
|
exit;
|
|
}
|
|
}
|
|
//die('Mail not or mis-configured!');
|
|
}
|
|
else
|
|
{
|
|
if (!empty($waitOnFailure[self::$profileID][$this->backend->_devid]))
|
|
{
|
|
$waitOnFailure[self::$profileID][$this->backend->_devid] = array();
|
|
egw_cache::setCache(egw_cache::INSTANCE,'email','ActiveSyncWaitOnFailure'.trim($GLOBALS['egw_info']['user']['account_id']),$waitOnFailure,$expiration=60*60*2);
|
|
}
|
|
}
|
|
$this->_wasteID = $this->mail->getTrashFolder(false);
|
|
//error_log(__METHOD__.__LINE__.' TrashFolder:'.$this->_wasteID);
|
|
$this->_sentID = $this->mail->getSentFolder(false);
|
|
$this->mail->getOutboxFolder(true);
|
|
//error_log(__METHOD__.__LINE__.' SentFolder:'.$this->_sentID);
|
|
//error_log(__METHOD__.__LINE__.' Connection Status for ProfileID:'.self::$profileID.'->'.$this->mail->icServer->_connected);
|
|
}
|
|
|
|
/**
|
|
* Close IMAP connection
|
|
*/
|
|
private function _disconnect()
|
|
{
|
|
debugLog(__METHOD__);
|
|
if ($this->mail) $this->mail->closeConnection();
|
|
|
|
unset($this->mail);
|
|
unset($this->account);
|
|
unset($this->folders);
|
|
}
|
|
|
|
/**
|
|
* GetFolderList
|
|
*
|
|
* @ToDo loop over available email accounts
|
|
*/
|
|
public function GetFolderList()
|
|
{
|
|
$folderlist = array();
|
|
//debugLog(__METHOD__.__LINE__);
|
|
/*foreach($available_accounts as $account)*/ $account = 0;
|
|
{
|
|
$this->_connect($account);
|
|
if (!isset($this->folders)) $this->folders = $this->mail->getFolderObjects(true,false,$_alwaysGetDefaultFolders=true);
|
|
//debugLog(__METHOD__.__LINE__.array2string($this->folders));
|
|
|
|
foreach ($this->folders as $folder => $folderObj) {
|
|
$folderlist[] = $f = array(
|
|
'id' => $this->createID($account,$folder),
|
|
'mod' => $folderObj->shortDisplayName,
|
|
'parent' => $this->getParentID($account,$folder),
|
|
);
|
|
if ($this->debugLevel>0) debugLog(__METHOD__."() returning ".array2string($f));
|
|
}
|
|
}
|
|
//debugLog(__METHOD__."() returning ".array2string($folderlist));
|
|
|
|
return $folderlist;
|
|
}
|
|
|
|
/**
|
|
* Sends a message which is passed as rfc822. You basically can do two things
|
|
* 1) Send the message to an SMTP server as-is
|
|
* 2) Parse the message yourself, and send it some other way
|
|
* It is up to you whether you want to put the message in the sent items folder. If you
|
|
* want it in 'sent items', then the next sync on the 'sent items' folder should return
|
|
* the new message as any other new message in a folder.
|
|
*
|
|
* @param string $rfc822 mail
|
|
* @param array $smartdata=array() values for keys:
|
|
* 'task': 'forward', 'new', 'reply'
|
|
* 'itemid': id of message if it's an reply or forward
|
|
* 'folderid': folder
|
|
* 'replacemime': false = send as is, false = decode and recode for whatever reason ???
|
|
* 'saveinsentitems': 1 or absent?
|
|
* @param boolean|double $protocolversion=false
|
|
* @return boolean true on success, false on error
|
|
*
|
|
* @see eg. BackendIMAP::SendMail()
|
|
* @todo implement either here or in mail backend
|
|
* (maybe sending here and storing to sent folder in plugin, as sending is supposed to always work in EGroupware)
|
|
*/
|
|
public function SendMail($rfc822, $smartdata=array(), $protocolversion = false)
|
|
{
|
|
//$this->debugLevel=3;
|
|
$ClientSideMeetingRequest = false;
|
|
$allowSendingInvitations = 'sendifnocalnotif';
|
|
if (isset($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-allowSendingInvitations']) &&
|
|
$GLOBALS['egw_info']['user']['preferences']['activesync']['mail-allowSendingInvitations']=='nosend')
|
|
{
|
|
$allowSendingInvitations = false;
|
|
}
|
|
elseif (isset($GLOBALS['egw_info']['user']['preferences']['activesync']['mail-allowSendingInvitations']) &&
|
|
$GLOBALS['egw_info']['user']['preferences']['activesync']['mail-allowSendingInvitations']!='nosend')
|
|
{
|
|
$allowSendingInvitations = $GLOBALS['egw_info']['user']['preferences']['activesync']['mail-allowSendingInvitations'];
|
|
}
|
|
|
|
if ($protocolversion < 14.0)
|
|
debugLog("IMAP-SendMail: " . (isset($rfc822) ? $rfc822 : ""). "task: ".(isset($smartdata['task']) ? $smartdata['task'] : "")." itemid: ".(isset($smartdata['itemid']) ? $smartdata['itemid'] : "")." folder: ".(isset($smartdata['folderid']) ? $smartdata['folderid'] : ""));
|
|
if ($this->debugLevel>0) debugLog("IMAP-Sendmail: Smartdata = ".array2string($smartdata));
|
|
//error_log("IMAP-Sendmail: Smartdata = ".array2string($smartdata));
|
|
// if we cannot decode the mail in question, fail
|
|
if (class_exists('Mail_mimeDecode',false)==false && (@include_once 'Mail/mimeDecode.php') === false)
|
|
{
|
|
debugLog("IMAP-SendMail: Could not find Mail_mimeDecode.");
|
|
return false;
|
|
}
|
|
// initialize our mail_bo
|
|
if (!isset($this->mail)) $this->mail = mail_bo::getInstance(false,self::$profileID,true,false,true);
|
|
$activeMailProfiles = $this->mail->getAccountIdentities(self::$profileID);
|
|
$activeMailProfile = array_shift($activeMailProfiles);
|
|
if ($this->debugLevel>2) debugLog(__METHOD__.__LINE__.' ProfileID:'.self::$profileID.' ActiveMailProfile:'.array2string($activeMailProfile));
|
|
|
|
// initialize the new egw_mailer object for sending
|
|
$mailObject = new egw_mailer();
|
|
$mailObject->CharSet = 'utf-8'; // set charset always to utf-8
|
|
// default, should this be forced?
|
|
$mailObject->IsSMTP();
|
|
$mailObject->Sender = $activeMailProfile['ident_email'];
|
|
$mailObject->From = $activeMailProfile['ident_email'];
|
|
$mailObject->FromName = $mailObject->EncodeHeader(mail_bo::generateIdentityString($activeMailProfile,false));
|
|
$mailObject->AddCustomHeader('X-Mailer: mail-Activesync');
|
|
|
|
$mimeParams = array('decode_headers' => true,
|
|
'decode_bodies' => false,
|
|
'include_bodies' => true,
|
|
'input' => $rfc822,
|
|
'crlf' => "\r\n",
|
|
'charset' => 'utf-8');
|
|
$mobj = new Mail_mimeDecode($mimeParams['input'], $mimeParams['crlf']);
|
|
$message = $mobj->decode($mimeParams, $mimeParams['crlf']);
|
|
//error_log(__METHOD__.__LINE__.array2string($message));
|
|
$mailObject->Priority = $message->headers['priority'];
|
|
$mailObject->Encoding = 'quoted-printable'; // we use this by default
|
|
|
|
if (isset($message->headers['date'])) $mailObject->RFCDateToSet = $message->headers['date'];
|
|
if (isset($message->headers['return-path'])) $mailObject->Sender = $message->headers['return-path'];
|
|
$mailObject->Subject = $message->headers['subject'];
|
|
$mailObject->MessageID = $message->headers['message-id'];
|
|
/* the client send garbage sometimes (blackberry->domain\username)
|
|
// from
|
|
$address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($message->headers['from']):$message->headers['from']),'');
|
|
foreach((array)$address_array as $addressObject) {
|
|
if ($addressObject->host == '.SYNTAX-ERROR.') continue;
|
|
if ($this->debugLevel>0) debugLog("Header Sentmail From: ".array2string($addressObject).' vs. '.array2string($message->headers['from']));
|
|
$mailObject->From = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : '');
|
|
$mailObject->FromName = $addressObject->personal;
|
|
}
|
|
*/
|
|
// prepare addressee list; moved the adding of addresses to the mailobject down
|
|
// to
|
|
$address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($message->headers["to"]):$message->headers["to"]),'');
|
|
foreach((array)$address_array as $addressObject) {
|
|
if ($addressObject->host == '.SYNTAX-ERROR.') continue;
|
|
if ($this->debugLevel>0) debugLog("Header Sentmail To: ".array2string($addressObject) );
|
|
//$mailObject->AddAddress($addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''),$addressObject->personal);
|
|
$toMailAddr[] = imap_rfc822_write_address($addressObject->mailbox, $addressObject->host, $addressObject->personal);
|
|
}
|
|
// CC
|
|
$address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($message->headers["cc"]):$message->headers["cc"]),'');
|
|
foreach((array)$address_array as $addressObject) {
|
|
if ($addressObject->host == '.SYNTAX-ERROR.') continue;
|
|
if ($this->debugLevel>0) debugLog("Header Sentmail CC: ".array2string($addressObject) );
|
|
//$mailObject->AddCC($addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''),$addressObject->personal);
|
|
$ccMailAddr[] = imap_rfc822_write_address($addressObject->mailbox, $addressObject->host, $addressObject->personal);
|
|
}
|
|
// BCC
|
|
$address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($message->headers["bcc"]):$message->headers["bcc"]),'');
|
|
foreach((array)$address_array as $addressObject) {
|
|
if ($addressObject->host == '.SYNTAX-ERROR.') continue;
|
|
if ($this->debugLevel>0) debugLog("Header Sentmail BCC: ".array2string($addressObject) );
|
|
//$mailObject->AddBCC($addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''),$addressObject->personal);
|
|
$bccMailAddr[] = imap_rfc822_write_address($addressObject->mailbox, $addressObject->host, $addressObject->personal);
|
|
}
|
|
/*
|
|
// AddReplyTo
|
|
$address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($message->headers['reply-to']):$message->headers['reply-to']),'');
|
|
foreach((array)$address_array as $addressObject) {
|
|
if ($addressObject->host == '.SYNTAX-ERROR.') continue;
|
|
if ($this->debugLevel>0) debugLog("Header Sentmail REPLY-TO: ".array2string($addressObject) );
|
|
$mailObject->AddReplyTo($addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''),$addressObject->personal);
|
|
}
|
|
*/
|
|
$addedfullname = false;
|
|
// save some headers when forwarding mails (content type & transfer-encoding)
|
|
$headers = "";
|
|
|
|
$use_orgbody = false;
|
|
|
|
// clean up the transmitted headers
|
|
// remove default headers because we are using our own mailer
|
|
//$returnPathSet = false;
|
|
//$body_base64 = false;
|
|
$org_charset = "";
|
|
foreach($message->headers as $k => $v) {
|
|
if ($k == "subject" ||
|
|
$k == "to" || $k == "cc" || $k == "bcc" || $k == "sender" || $k == "reply-to" || $k == 'from' || $k == 'return_path' ||
|
|
$k == "message-id" || $k == 'date')
|
|
continue; // already set
|
|
|
|
if ($this->debugLevel>0) debugLog("Header Sentmail original Header (filtered): " . $k. " = ".trim($v));
|
|
if ($k == "content-type") {
|
|
// if the message is a multipart message, then we should use the sent body
|
|
if (preg_match("/multipart/i", $v)) {
|
|
$use_orgbody = true;
|
|
$org_boundary = $message->ctype_parameters["boundary"];
|
|
}
|
|
|
|
// save the original content-type header for the body part when forwarding
|
|
if ($smartdata['task'] == 'forward' && $smartdata['itemid'] && !$use_orgbody) {
|
|
continue; // ignore
|
|
}
|
|
|
|
$org_charset = $v;
|
|
$v = preg_replace("/charset=([A-Za-z0-9-\"']+)/", "charset=\"utf-8\"", $v);
|
|
}
|
|
|
|
if ($k == "content-transfer-encoding") {
|
|
/* we do not use this, as we determine that by ourself by forcing Encoding=base64 on smartreply/forward
|
|
// if the content was base64 encoded, encode the body again when sending
|
|
if (trim($v) == "base64") $body_base64 = true;
|
|
|
|
// save the original encoding header for the body part when forwarding
|
|
if ($smartdata['task'] == 'forward' && $smartdata['itemid']) {
|
|
continue; // ignore
|
|
}
|
|
*/
|
|
}
|
|
|
|
// if the message is a multipart message, then we should use the sent body
|
|
if (($smartdata['task'] == 'new' || $smartdata['task'] == 'reply' || $smartdata['task'] == 'forward') &&
|
|
((isset($smartdata['replacemime']) && $smartdata['replacemime'] == true) ||
|
|
$k == "content-type" && preg_match("/multipart/i", $v))) {
|
|
$use_orgbody = true;
|
|
}
|
|
|
|
// all other headers stay, we probably dont use them, but we may add them with AddHeader/AddCustomHeader
|
|
//if ($headers) $headers .= "\n";
|
|
//$headers .= ucfirst($k) . ": ". trim($v);
|
|
}
|
|
// if this is a simple message, no structure at all
|
|
if ($message->ctype_primary=='text' && $message->body)
|
|
{
|
|
$mailObject->IsHTML($message->ctype_secondary=='html'?true:false);
|
|
// we decode the body ourself
|
|
$message->body = $this->mail->decodeMimePart($message->body,($message->headers['content-transfer-encoding']?$message->headers['content-transfer-encoding']:'WeDontKnowTheEncoding'));
|
|
$mailObject->Body = $body = $message->body;
|
|
$simpleBodyType = ($message->ctype_secondary=='html'?'text/html':'text/plain');
|
|
if ($this->debugLevel>0) debugLog("IMAP-Sendmail: fetched simple body as ".($message->ctype_secondary=='html'?'html':'text'));
|
|
}
|
|
//error_log(__METHOD__.__LINE__.array2string($mailObject));
|
|
// if this is a multipart message with a boundary, we must use the original body
|
|
$this->mail->createBodyFromStructure($mailObject, $message,NULL,$decode=true);
|
|
if ($this->debugLevel>2) debugLog(__METHOD__.__LINE__.' mailObject after Inital Parse:'.array2string($mailObject));
|
|
if ($use_orgbody) {
|
|
if ($this->debugLevel>0) debugLog("IMAP-Sendmail: use_orgbody = true");
|
|
$repl_body = $body = $mailObject->Body;
|
|
// if it is a ClientSideMeetingRequest, we report it as send at all times
|
|
if ($mailObject->AltExtendedContentType && stripos($mailObject->AltExtendedContentType,'text/calendar') !== false )
|
|
{
|
|
if ($this->debugLevel>0) debugLog("IMAP-Sendmail: we have a Client Side Meeting Request");
|
|
// try figuring out the METHOD -> [AltExtendedContentType] => text/calendar; name=meeting.ics; method=REQUEST
|
|
$tA = explode(' ',$mailObject->AltExtendedContentType);
|
|
foreach ((array)$tA as $k => $p) if (stripos($p,"method=")!==false) $cSMRMethod= trim(str_replace('METHOD=','',strtoupper($p)));
|
|
$ClientSideMeetingRequest = true;
|
|
}
|
|
}
|
|
else {
|
|
if ($this->debugLevel>0) debugLog("IMAP-Sendmail: use_orgbody = false");
|
|
$body = $mailObject->Body;
|
|
}
|
|
// now handle the addressee list
|
|
$toCount = 0;
|
|
//error_log(__METHOD__.__LINE__.array2string($toMailAddr));
|
|
foreach((array)$toMailAddr as $address) {
|
|
$address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($address):$address),'');
|
|
foreach((array)$address_array as $addressObject) {
|
|
$emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : '');
|
|
if ($ClientSideMeetingRequest === true && $allowSendingInvitations == 'sendifnocalnotif' && calendar_boupdate::email_update_requested($emailAddress,(isset($cSMRMethod)?$cSMRMethod:'REQUEST'))) continue;
|
|
$mailObject->AddAddress($emailAddress, $addressObject->personal);
|
|
$toCount++;
|
|
}
|
|
}
|
|
$ccCount = 0;
|
|
foreach((array)$ccMailAddr as $address) {
|
|
$address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($address):$address),'');
|
|
foreach((array)$address_array as $addressObject) {
|
|
$emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : '');
|
|
if ($ClientSideMeetingRequest === true && $allowSendingInvitations == 'sendifnocalnotif' && calendar_boupdate::email_update_requested($emailAddress)) continue;
|
|
$mailObject->AddCC($emailAddress, $addressObject->personal);
|
|
$ccCount++;
|
|
}
|
|
}
|
|
$bccCount = 0;
|
|
foreach((array)$bccMailAddr as $address) {
|
|
$address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($address):$address),'');
|
|
foreach((array)$address_array as $addressObject) {
|
|
$emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : '');
|
|
if ($ClientSideMeetingRequest === true && $allowSendingInvitations == 'sendifnocalnotif' && calendar_boupdate::email_update_requested($emailAddress)) continue;
|
|
$mailObject->AddBCC($emailAddress, $addressObject->personal);
|
|
$bccCount++;
|
|
}
|
|
}
|
|
if ($toCount+$ccCount+$bccCount == 0) return 0; // noone to send mail to
|
|
if ($ClientSideMeetingRequest === true && $allowSendingInvitations===false) return true;
|
|
if ($this->debugLevel>2) debugLog(__METHOD__.__LINE__.' MailAttachments:'.array2string($mailObject->GetAttachments()));
|
|
// as we use our mailer (phpmailer) it is detecting / setting the mimetype by itself while creating the mail
|
|
if (isset($smartdata['replacemime']) && $smartdata['replacemime'] == true &&
|
|
isset($message->ctype_primary)) {
|
|
//if ($headers) $headers .= "\n";
|
|
//$headers .= "Content-Type: ". $message->ctype_primary . "/" . $message->ctype_secondary .
|
|
// (isset($message->ctype_parameters['boundary']) ? ";\n\tboundary=".$message->ctype_parameters['boundary'] : "");
|
|
}
|
|
if ($this->debugLevel>2) debugLog(__METHOD__.__LINE__.' retrieved Body:'.$body);
|
|
$body = str_replace("\r",($mailObject->ContentType=='text/html'?'<br>':""),$body); // what is this for?
|
|
if ($this->debugLevel>2) debugLog(__METHOD__.__LINE__.' retrieved Body (modified):'.$body);
|
|
// reply ---------------------------------------------------------------------------
|
|
if ($smartdata['task'] == 'reply' && isset($smartdata['itemid']) &&
|
|
isset($smartdata['folderid']) && $smartdata['itemid'] && $smartdata['folderid'] &&
|
|
(!isset($smartdata['replacemime']) ||
|
|
(isset($smartdata['replacemime']) && $smartdata['replacemime'] == false)))
|
|
{
|
|
//remember Content type of org messageFolder
|
|
$orgMessageContentType = $mailObject->ContentType;
|
|
// now get on, and fetch the original mail
|
|
$uid = $smartdata['itemid'];
|
|
if ($this->debugLevel>0) debugLog("IMAP Smartreply is called with FolderID:".$smartdata['folderid'].' and ItemID:'.$smartdata['itemid']);
|
|
$this->splitID($smartdata['folderid'], $account, $folder);
|
|
|
|
$this->mail->reopen($folder);
|
|
// not needed, as the original header is always transmitted
|
|
/*
|
|
$headers = $this->mail->getMessageEnvelope($uid, $_partID, true, $folder);
|
|
if ($this->debugLevel>0) debugLog(__METHOD__.__LINE__." Headers of Message with UID:$uid ->".array2string($headers));
|
|
$body .= $this->mail->createHeaderInfoSection($headers,lang("original message"));
|
|
*/
|
|
$bodyStruct = $this->mail->getMessageBody($uid, 'html_only');
|
|
|
|
$bodyBUFF = $this->mail->getdisplayableBody($this->mail,$bodyStruct,true);
|
|
if ($this->debugLevel>3) debugLog(__METHOD__.__LINE__.' html_only:'.$bodyBUFF);
|
|
if ($bodyBUFF != "" && (is_array($bodyStruct) && $bodyStruct[0]['mimeType']=='text/html')) {
|
|
// may be html
|
|
if ($this->debugLevel>0) debugLog("MIME Body".' Type:html (fetched with html_only):'.$bodyBUFF);
|
|
$mailObject->IsHTML(true);
|
|
} else {
|
|
// plain text Message
|
|
if ($this->debugLevel>0) debugLog("MIME Body".' Type:plain, fetch text:');
|
|
// if the new part of the message is html, we must preserve it, and handle that the original mail is text/plain
|
|
if ($orgMessageContentType!='text/html') $mailObject->IsHTML(false);
|
|
$bodyStruct = $this->mail->getMessageBody($uid,'never_display');//'never_display');
|
|
$bodyBUFF = $this->mail->getdisplayableBody($this->mail,$bodyStruct);//$this->ui->getdisplayableBody($bodyStruct,false);
|
|
|
|
if ($this->debugLevel>0) debugLog("MIME Body ContentType ".$mailObject->ContentType);
|
|
$bodyBUFF = ($mailObject->ContentType=='text/html'?'<pre>':'').$bodyBUFF.($mailObject->ContentType=='text/html'?'</pre>':'');
|
|
}
|
|
|
|
if ($this->debugLevel>0) debugLog(__METHOD__.__LINE__.' Body -> '.$bodyBUFF);
|
|
if (isset($simpleBodyType) && $simpleBodyType == 'text/plain' && $mailObject->ContentType == 'text/html') $body=nl2br($body); // this is (should be) the same as $orgMessageContentType == 'text/plain' && $mailObject->ContentType == 'text/html'
|
|
// receive only body
|
|
$body .= $bodyBUFF;
|
|
$mailObject->Encoding = 'base64';
|
|
}
|
|
|
|
// how to forward and other prefs
|
|
$preferencesArray =& $GLOBALS['egw_info']['user']['preferences']['mail'];
|
|
|
|
// forward -------------------------------------------------------------------------
|
|
if ($smartdata['task'] == 'forward' && isset($smartdata['itemid']) &&
|
|
isset($smartdata['folderid']) && $smartdata['itemid'] && $smartdata['folderid'] &&
|
|
(!isset($smartdata['replacemime']) ||
|
|
(isset($smartdata['replacemime']) && $smartdata['replacemime'] == false)))
|
|
{
|
|
//force the default for the forwarding -> asmail
|
|
if (is_array($preferencesArray)) {
|
|
if (!array_key_exists('message_forwarding',$preferencesArray)
|
|
|| !isset($preferencesArray['message_forwarding'])
|
|
|| empty($preferencesArray['message_forwarding'])) $preferencesArray['message_forwarding'] = 'asmail';
|
|
} else {
|
|
$preferencesArray['message_forwarding'] = 'asmail';
|
|
}
|
|
// construct the uid of the message out of the itemid - seems to be the uid, no construction needed
|
|
$uid = $smartdata['itemid'];
|
|
if ($this->debugLevel>0) debugLog("IMAP Smartfordward is called with FolderID:".$smartdata['folderid'].' and ItemID:'.$smartdata['itemid']);
|
|
$this->splitID($smartdata['folderid'], $account, $folder);
|
|
|
|
$this->mail->reopen($folder);
|
|
// receive entire mail (header + body)
|
|
// get message headers for specified message
|
|
$headers = $this->mail->getMessageEnvelope($uid, $_partID, true, $folder);
|
|
|
|
// build a new mime message, forward entire old mail as file
|
|
if ($preferencesArray['message_forwarding'] == 'asmail')
|
|
{
|
|
$rawHeader='';
|
|
$rawHeader = $this->mail->getMessageRawHeader($smartdata['itemid'], $_partID,$folder);
|
|
$rawBody = $this->mail->getMessageRawBody($smartdata['itemid'], $_partID,$folder);
|
|
$mailObject->AddStringAttachment($rawHeader.$rawBody, $mailObject->EncodeHeader($headers['SUBJECT']), '7bit', 'message/rfc822');
|
|
}
|
|
else
|
|
{
|
|
/* ToDo - as it may double text
|
|
// This is for forwarding and using the original body as Client may only include parts of the original mail
|
|
if (!$use_orgbody)
|
|
$nbody = $body;
|
|
else
|
|
$nbody = $repl_body;
|
|
*/
|
|
//$body .= $this->mail->createHeaderInfoSection($headers,lang("original message"));
|
|
$bodyStruct = $this->mail->getMessageBody($uid, 'html_only');
|
|
$bodyBUFF = $this->mail->getdisplayableBody($this->mail,$bodyStruct,true);
|
|
if ($this->debugLevel>3) debugLog(__METHOD__.__LINE__.' html_only:'.$body);
|
|
if ($bodyBUFF != "" && (is_array($bodyStruct) && $bodyStruct[0]['mimeType']=='text/html')) {
|
|
// may be html
|
|
if ($this->debugLevel>0) debugLog("MIME Body".' Type:html (fetched with html_only)');
|
|
$mailObject->IsHTML(true);
|
|
} else {
|
|
// plain text Message
|
|
if ($this->debugLevel>0) debugLog("MIME Body".' Type:plain, fetch text:');
|
|
// as we glue together the send mail part, and the smartforward part, we stick to the ContentType of the to be sent-Mail
|
|
$mailObject->IsHTML($mailObject->ContentType=='text/html');
|
|
$bodyStruct = $this->mail->getMessageBody($uid,'never_display');//'never_display');
|
|
$bodyBUFF = $this->mail->getdisplayableBody($this->mail,$bodyStruct);//$this->ui->getdisplayableBody($bodyStruct,false);
|
|
|
|
if ($this->debugLevel>0) debugLog("MIME Body ContentType ".$mailObject->ContentType);
|
|
$bodyBUFF = ($mailObject->ContentType=='text/html'?'<pre>':'').$bodyBUFF.($mailObject->ContentType=='text/html'?'</pre>':'');
|
|
}
|
|
if ($this->debugLevel>0) debugLog(__METHOD__.__LINE__.' Body -> '.$bodyBUFF);
|
|
// receive only body
|
|
$body .= $bodyBUFF;
|
|
// get all the attachments and add them too.
|
|
// start handle Attachments
|
|
// $_uid, $_partID=null, Horde_Mime_Part $_structure=null, $fetchEmbeddedImages=true, $fetchTextCalendar=false, $resolveTNEF=true, $_folderName=''
|
|
$attachments = $this->mail->getMessageAttachments($uid, null, null, true, false, true , $folder);
|
|
$attachmentNames = false;
|
|
if (is_array($attachments) && count($attachments)>0)
|
|
{
|
|
if ($this->debugLevel>0) debugLog(__METHOD__.__LINE__.' gather Attachments for BodyCreation of/for MessageID:'.$uid.' found:'.count($attachments));
|
|
foreach((array)$attachments as $key => $attachment)
|
|
{
|
|
if ($this->debugLevel>0) debugLog(__METHOD__.__LINE__.' Key:'.$key.'->'.array2string($attachment));
|
|
$attachmentNames .= $attachment['name']."\n";
|
|
switch($attachment['type'])
|
|
{
|
|
case 'MESSAGE/RFC822':
|
|
$rawHeader = $rawBody = '';
|
|
$rawHeader = $this->mail->getMessageRawHeader($uid, $attachment['partID'],$folder);
|
|
$rawBody = $this->mail->getMessageRawBody($uid, $attachment['partID'],$folder);
|
|
$mailObject->AddStringAttachment($rawHeader.$rawBody, $mailObject->EncodeHeader($attachment['name']), '7bit', 'message/rfc822');
|
|
break;
|
|
default:
|
|
$attachmentData = '';
|
|
$attachmentData = $this->mail->getAttachment($uid, $attachment['partID'],0,false);
|
|
$mailObject->AddStringAttachment($attachmentData['attachment'], $mailObject->EncodeHeader($attachment['name']), 'base64', $attachment['mimeType']);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (isset($simpleBodyType) && $simpleBodyType == 'text/plain' && $mailObject->ContentType == 'text/html') $body=nl2br($body);
|
|
$mailObject->Encoding = 'base64';
|
|
} // end forward
|
|
|
|
// add signature!! -----------------------------------------------------------------
|
|
if ($this->debugLevel>2) debugLog(__METHOD__.__LINE__.' ActiveMailProfile:'.array2string($activeMailProfile));
|
|
try
|
|
{
|
|
$acc = emailadmin_account::read($this->mail->icServer->ImapServerId);
|
|
//error_log(__METHOD__.__LINE__.array2string($acc));
|
|
$_signature = emailadmin_account::read_identity($acc['ident_id'],true);
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
$_signature=array();
|
|
}
|
|
$signature = $_signature['ident_signature'];
|
|
if ((isset($preferencesArray['disableRulerForSignatureSeparation']) &&
|
|
$preferencesArray['disableRulerForSignatureSeparation']) ||
|
|
empty($signature) || trim(translation::convertHTMLToText($signature)) =='')
|
|
{
|
|
$disableRuler = true;
|
|
}
|
|
$before = "";
|
|
if ($disableRuler==false)
|
|
{
|
|
if($mailObject->ContentType=='text/html') {
|
|
$before = ($disableRuler ?' <br>':' <br><hr style="border:dotted 1px silver; width:90%; border:dotted 1px silver;">');
|
|
} else {
|
|
$before = ($disableRuler ?"\r\n\r\n":"\r\n\r\n-- \r\n");
|
|
}
|
|
}
|
|
$sigText = $this->mail->merge($signature,array($GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'],'person_id')));
|
|
if ($this->debugLevel>0) debugLog(__METHOD__.__LINE__.' Signature to use:'.$sigText);
|
|
$body .= $before.($mailObject->ContentType=='text/html'?$sigText:translation::convertHTMLToText($sigText));
|
|
//debugLog(__METHOD__.__LINE__.' -> '.$body);
|
|
// remove carriage-returns from body, set the body of the mailObject
|
|
if (trim($body) =='' && trim($mailObject->Body)==''/* && $attachmentNames*/) $body .= ($attachmentNames?$attachmentNames:lang('no text body supplied, check attachments for message text')); // to avoid failure on empty messages with attachments
|
|
//debugLog(__METHOD__.__LINE__.' -> '.$body);
|
|
$mailObject->Body = $body ;//= str_replace("\r\n", "\n", $body); // if there is a <pre> this needs \r\n so DO NOT replace them
|
|
if ($mailObject->ContentType=='text/html') $mailObject->AltBody = translation::convertHTMLToText($body);
|
|
|
|
//advanced debugging
|
|
if (strtolower($mailObject->CharSet) != 'utf-8')
|
|
{
|
|
debugLog(__METHOD__.__LINE__.' POSSIBLE PROBLEM: CharSet was changed during processing of the Body from:'.$mailObject->CharSet.'. Force back to UTF-8 now.');
|
|
$mailObject->CharSet = 'utf-8';
|
|
}
|
|
//debugLog("IMAP-SendMail: parsed message: ". print_r($message,1));
|
|
#_debug_array($ogServer);
|
|
$mailObject->Host = $this->mail->ogServer->host;
|
|
$mailObject->Port = $this->mail->ogServer->port;
|
|
// SMTP Auth??
|
|
if($this->mail->ogServer->smtpAuth) {
|
|
$mailObject->SMTPAuth = true;
|
|
// check if username contains a ; -> then a sender is specified (and probably needed)
|
|
list($username,$senderadress) = explode(';', $this->mail->ogServer->username,2);
|
|
if (isset($senderadress) && !empty($senderadress)) $mailObject->Sender = $senderadress;
|
|
$mailObject->Username = $username;
|
|
$mailObject->Password = $this->mail->ogServer->password;
|
|
}
|
|
if ($this->debugLevel>2) debugLog("IMAP-SendMail: MailObject:".array2string($mailObject));
|
|
if ($this->debugLevel>0 && $this->debugLevel<=2)
|
|
{
|
|
debugLog("IMAP-SendMail: MailObject (short):".array2string(array('host'=>$mailObject->Host,
|
|
'port'=>$mailObject->Port,
|
|
'username'=>$mailObject->Username,
|
|
'subject'=>$mailObject->Subject,
|
|
'CharSet'=>$mailObject->CharSet,
|
|
'Priority'=>$mailObject->Priority,
|
|
'Encoding'=>$mailObject->Encoding,
|
|
'ContentType'=>$mailObject->ContentType,
|
|
)));
|
|
}
|
|
if ($this->debugLevel>2) debugLog(__METHOD__.__LINE__.' MailAttachments:'.array2string($mailObject->GetAttachments()));
|
|
|
|
// set a higher timeout for big messages
|
|
@set_time_limit(120);
|
|
|
|
// send
|
|
$send = true;
|
|
try {
|
|
$mailObject->Send();
|
|
}
|
|
catch(phpmailerException $e) {
|
|
debugLog("The email could not be sent. Last-SMTP-error: ". $e->getMessage());
|
|
debugLog("IMAP-SendMail: MailObject (short):".array2string(array('host'=>$mailObject->Host,
|
|
'port'=>$mailObject->Port,
|
|
'username'=>$mailObject->Username,
|
|
'subject'=>$mailObject->Subject,
|
|
'CharSet'=>$mailObject->CharSet,
|
|
'Priority'=>$mailObject->Priority,
|
|
'Encoding'=>$mailObject->Encoding,
|
|
'ContentType'=>$mailObject->ContentType,
|
|
)));
|
|
$send = false;
|
|
}
|
|
|
|
if (( $smartdata['task'] == 'reply' || $smartdata['task'] == 'forward') && $send == true)
|
|
{
|
|
$uid = $smartdata['itemid'];
|
|
if ($this->debugLevel>0) debugLog(__METHOD__.__LINE__.' tASK:'.$smartdata['task']." FolderID:".$smartdata['folderid'].' and ItemID:'.$smartdata['itemid']);
|
|
$this->splitID($smartdata['folderid'], $account, $folder);
|
|
//error_log(__METHOD__.__LINE__.' Folder:'.$folder.' Uid:'.$uid);
|
|
$this->mail->reopen($folder);
|
|
// if the draft folder is a starting part of the messages folder, the draft message will be deleted after the send
|
|
// unless your templatefolder is a subfolder of your draftfolder, and the message is in there
|
|
if ($this->mail->isDraftFolder($folder) && !$this->mail->isTemplateFolder($folder))
|
|
{
|
|
$this->mail->deleteMessages(array($uid),$folder);
|
|
} else {
|
|
$this->mail->flagMessages("answered", array($uid),$folder);
|
|
if ($smartdata['task']== "forward")
|
|
{
|
|
$this->mail->flagMessages("forwarded", array($uid),$folder);
|
|
}
|
|
}
|
|
}
|
|
|
|
$asf = ($send ? true:false); // initalize accordingly
|
|
if (($smartdata['saveinsentitems']==1 || !isset($smartdata['saveinsentitems'])) && $send==true && $this->mail->mailPreferences['sendOptions'] != 'send_only')
|
|
{
|
|
$asf = false;
|
|
$sentFolder = $this->mail->getSentFolder();
|
|
if ($this->_sentID) {
|
|
$folderArray[] = $this->_sentID;
|
|
}
|
|
else if(isset($sentFolder) && $sentFolder != 'none')
|
|
{
|
|
$folderArray[] = $sentFolder;
|
|
}
|
|
// No Sent folder set, try defaults
|
|
else
|
|
{
|
|
debugLog("IMAP-SendMail: No Sent mailbox set");
|
|
// we dont try guessing
|
|
$asf = true;
|
|
}
|
|
if (count($folderArray) > 0) {
|
|
foreach((array)$bccMailAddr as $address) {
|
|
$address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($address):$address),'');
|
|
foreach((array)$address_array as $addressObject) {
|
|
$emailAddress = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : '');
|
|
$mailAddr[] = array($emailAddress, $addressObject->personal);
|
|
}
|
|
}
|
|
$BCCmail='';
|
|
if (count($mailAddr)>0) $BCCmail = $mailObject->AddrAppend("Bcc",$mailAddr);
|
|
foreach($folderArray as $folderName) {
|
|
if($this->mail->isSentFolder($folderName)) {
|
|
$flags = '\\Seen';
|
|
} elseif($this->mail->isDraftFolder($folderName)) {
|
|
$flags = '\\Draft';
|
|
} else {
|
|
$flags = '';
|
|
}
|
|
$asf = true;
|
|
//debugLog(__METHOD__.__LINE__.'->'.array2string($this->mail->icServer));
|
|
$this->mail->openConnection(self::$profileID,false);
|
|
if ($this->mail->folderExists($folderName)) {
|
|
try
|
|
{
|
|
$this->mail->appendMessage($folderName,
|
|
$BCCmail.$mailObject->getMessageHeader(),
|
|
$mailObject->getMessageBody(),
|
|
$flags);
|
|
}
|
|
catch (egw_exception_wrong_userinput $e)
|
|
{
|
|
//$asf = false;
|
|
debugLog(__METHOD__.__LINE__.'->'.lang("Import of message %1 failed. Could not save message to folder %2 due to: %3",$mailObject->Subject,$folderName,$e->getMessage()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//$asf = false;
|
|
debugLog(__METHOD__.__LINE__.'->'.lang("Import of message %1 failed. Destination Folder %2 does not exist.",$mailObject->Subject,$folderName));
|
|
}
|
|
debugLog("IMAP-SendMail: Outgoing mail saved in configured 'Sent' folder '".$folderName."': ". (($asf)?"success":"failed"));
|
|
}
|
|
//$this->mail->closeConnection();
|
|
}
|
|
}
|
|
// unset mimedecoder - free memory
|
|
unset($message);
|
|
unset($mobj);
|
|
|
|
//$this->debugLevel=0;
|
|
|
|
if ($send && $asf)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
debugLog(__METHOD__." returning ".($ClientSideMeetingRequest ? true : 120)." (MailSubmissionFailed)".($ClientSideMeetingRequest ?" is ClientSideMeetingRequest (we ignore the failure)":""));
|
|
return ($ClientSideMeetingRequest ? true : 120); //MAIL Submission failed, see MS-ASCMD
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
*
|
|
* For meeting requests (iCal attachments with method='request') we call calendar plugin with iCal to get SyncMeetingRequest object,
|
|
* and do NOT return the attachment itself!
|
|
*
|
|
* @see activesync_plugin_read::GetMessage()
|
|
*/
|
|
public function GetMessage($folderid, $id, $truncsize, $bodypreference=false, $optionbodypreference=false, $mimesupport = 0)
|
|
{
|
|
//$this->debugLevel=4;
|
|
if (!isset($this->mail)) $this->mail = mail_bo::getInstance(false,self::$profileID,true,false,true);
|
|
debugLog(__METHOD__.__LINE__.' FolderID:'.$folderid.' ID:'.$id.' TruncSize:'.$truncsize.' Bodypreference: '.array2string($bodypreference));
|
|
$rv = $this->splitID($folderid,$account,$_folderName,$xid);
|
|
$this->mail->reopen($_folderName);
|
|
$stat = $this->StatMessage($folderid, $id);
|
|
if ($this->debugLevel>3) debugLog(__METHOD__.__LINE__.array2string($stat));
|
|
// StatMessage should reopen the folder in question, so we dont need folderids in the following statements.
|
|
if ($stat)
|
|
{
|
|
debugLog(__METHOD__.__LINE__." Message $id with stat ".array2string($stat));
|
|
// initialize the object
|
|
$output = new SyncMail();
|
|
$headers = $this->mail->getMessageHeader($id,'',true,true,$_folderName);
|
|
if (empty($headers))
|
|
{
|
|
error_log(__METHOD__.__LINE__.' Retrieval of Headers Failed! for .'.$this->account.'/'.$GLOBALS['egw_info']['user']['account_lid'].' ServerID:'.self::$profileID.'FolderID:'.$folderid.'/'.$_folderName.' ID:'.$id.' TruncSize:'.$truncsize.' Bodypreference: '.array2string($bodypreference).' Stat was:'.array2string($stat));
|
|
return $output;//empty object
|
|
}
|
|
//$rawHeaders = $this->mail->getMessageRawHeader($id);
|
|
// simple style
|
|
// start AS12 Stuff (bodypreference === false) case = old behaviour
|
|
if ($this->debugLevel>0) debugLog(__METHOD__.__LINE__. ' for message with ID:'.$id.' with headers:'.array2string($headers));
|
|
if ($bodypreference === false) {
|
|
$bodyStruct = $this->mail->getMessageBody($id, 'only_if_no_text', '', null, true,$_folderName);
|
|
$body = $this->mail->getdisplayableBody($this->mail,$bodyStruct);
|
|
//$body = html_entity_decode($body,ENT_QUOTES,$this->mail->detect_encoding($body));
|
|
if (stripos($body,'<style')!==false) $body = preg_replace("/<style.*?<\/style>/is", "", $body); // in case there is only a html part
|
|
// remove all other html
|
|
$body = strip_tags($body);
|
|
if(strlen($body) > $truncsize) {
|
|
$body = utf8_truncate($body, $truncsize);
|
|
$output->bodytruncated = 1;
|
|
}
|
|
else
|
|
{
|
|
$output->bodytruncated = 0;
|
|
}
|
|
$output->bodysize = strlen($body);
|
|
$output->body = $body;
|
|
}
|
|
else // style with bodypreferences
|
|
{
|
|
if (isset($bodypreference[1]) && !isset($bodypreference[1]["TruncationSize"]))
|
|
$bodypreference[1]["TruncationSize"] = 1024*1024;
|
|
if (isset($bodypreference[2]) && !isset($bodypreference[2]["TruncationSize"]))
|
|
$bodypreference[2]["TruncationSize"] = 1024*1024;
|
|
if (isset($bodypreference[3]) && !isset($bodypreference[3]["TruncationSize"]))
|
|
$bodypreference[3]["TruncationSize"] = 1024*1024;
|
|
if (isset($bodypreference[4]) && !isset($bodypreference[4]["TruncationSize"]))
|
|
$bodypreference[4]["TruncationSize"] = 1024*1024;
|
|
// set the protocoll class
|
|
$output->airsyncbasebody = new SyncAirSyncBaseBody();
|
|
if ($this->debugLevel>0) debugLog("airsyncbasebody!");
|
|
// fetch the body (try to gather data only once)
|
|
$css ='';
|
|
$bodyStruct = $this->mail->getMessageBody($id, 'html_only', '', null, true,$_folderName);
|
|
if ($this->debugLevel>2) debugLog(__METHOD__.__LINE__.' html_only Struct:'.array2string($bodyStruct));
|
|
$body = $this->mail->getdisplayableBody($this->mail,$bodyStruct,true);//$this->ui->getdisplayableBody($bodyStruct,false);
|
|
if ($this->debugLevel>3) debugLog(__METHOD__.__LINE__.' html_only:'.$body);
|
|
if ($body != "" && (is_array($bodyStruct) && $bodyStruct[0]['mimeType']=='text/html')) {
|
|
// may be html
|
|
if ($this->debugLevel>0) debugLog("MIME Body".' Type:html (fetched with html_only)');
|
|
$css = $this->mail->getStyles($bodyStruct);
|
|
$output->airsyncbasenativebodytype=2;
|
|
} else {
|
|
// plain text Message
|
|
if ($this->debugLevel>0) debugLog("MIME Body".' Type:plain, fetch text (HTML, if no text available)');
|
|
$output->airsyncbasenativebodytype=1;
|
|
$bodyStruct = $this->mail->getMessageBody($id,'never_display', '', null, true,$_folderName); //'only_if_no_text');
|
|
if ($this->debugLevel>3) debugLog(__METHOD__.__LINE__.' plain text Struct:'.array2string($bodyStruct));
|
|
$body = $this->mail->getdisplayableBody($this->mail,$bodyStruct);//$this->ui->getdisplayableBody($bodyStruct,false);
|
|
if ($this->debugLevel>3) debugLog(__METHOD__.__LINE__.' never display html(plain text only):'.$body);
|
|
}
|
|
// whatever format decode (using the correct encoding)
|
|
if ($this->debugLevel>3) debugLog(__METHOD__.__LINE__."MIME Body".' Type:'.($output->airsyncbasenativebodytype==2?' html ':' plain ').$body);
|
|
//$body = html_entity_decode($body,ENT_QUOTES,$this->mail->detect_encoding($body));
|
|
// prepare plaintextbody
|
|
if ($output->airsyncbasenativebodytype == 2)
|
|
{
|
|
$bodyStructplain = $this->mail->getMessageBody($id,'never_display', '', null, true,$_folderName); //'only_if_no_text');
|
|
if($bodyStructplain[0]['error']==1)
|
|
{
|
|
$plainBody = translation::convertHTMLToText($body,true); // always display with preserved HTML
|
|
}
|
|
else
|
|
{
|
|
$plainBody = $this->mail->getdisplayableBody($this->mail,$bodyStructplain);//$this->ui->getdisplayableBody($bodyStruct,false);
|
|
}
|
|
}
|
|
//if ($this->debugLevel>0) debugLog("MIME Body".$body);
|
|
$plainBody = preg_replace("/<style.*?<\/style>/is", "", (strlen($plainBody)?$plainBody:$body));
|
|
// remove all other html
|
|
$plainBody = preg_replace("/<br.*>/is","\r\n",$plainBody);
|
|
$plainBody = strip_tags($plainBody);
|
|
if ($this->debugLevel>3 && $output->airsyncbasenativebodytype==1) debugLog(__METHOD__.__LINE__.' Plain Text:'.$plainBody);
|
|
//$body = str_replace("\n","\r\n", str_replace("\r","",$body)); // do we need that?
|
|
if (isset($bodypreference[4]) && ($mimesupport==2 || ($mimesupport ==1 && stristr($headers['CONTENT-TYPE'],'signed') !== false)))
|
|
{
|
|
debugLog(__METHOD__.__LINE__." bodypreference 4 requested");
|
|
$output->airsyncbasebody->type = 4;
|
|
//$rawBody = $this->mail->getMessageRawBody($id);
|
|
$mailObject = new egw_mailer();
|
|
// this try catch block is probably of no use anymore, as it was intended to catch exceptions thrown by parseRawMessageIntoMailObject
|
|
try
|
|
{
|
|
// we create a complete new rfc822 message here to pass a clean one to the client.
|
|
// this strips a lot of information, but ...
|
|
$Header = $Body = '';
|
|
if ($this->debugLevel>0) debugLog(__METHOD__.__LINE__." Creation of Mailobject.");
|
|
//if ($this->debugLevel>3) debugLog(__METHOD__.__LINE__." Using data from ".$rawHeaders.$rawBody);
|
|
//$this->mail->parseRawMessageIntoMailObject($mailObject,$rawHeaders.$rawBody,$Header,$Body);
|
|
//debugLog(__METHOD__.__LINE__.array2string($headers));
|
|
// now force UTF-8
|
|
$mailObject->IsSMTP(); // needed to ensure the to part of the Header is Created too, when CreatingHeader
|
|
$mailObject->CharSet = 'utf-8';
|
|
$mailObject->Priority = $headers['PRIORITY'];
|
|
$mailObject->Encoding = 'quoted-printable'; // we use this by default
|
|
|
|
$mailObject->RFCDateToSet = $headers['DATE'];
|
|
$mailObject->Sender = $headers['RETURN-PATH'];
|
|
$mailObject->Subject = $headers['SUBJECT'];
|
|
$mailObject->MessageID = $headers['MESSAGE-ID'];
|
|
// from
|
|
$address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($headers['FROM']):$headers['FROM']),'');
|
|
foreach((array)$address_array as $addressObject) {
|
|
//debugLog(__METHOD__.__LINE__.'Address to add (FROM):'.array2string($addressObject));
|
|
if ($addressObject->host == '.SYNTAX-ERROR.') continue;
|
|
$mailObject->From = $addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : '');
|
|
$mailObject->FromName = $addressObject->personal;
|
|
//error_log(__METHOD__.__LINE__.'Address to add (FROM):'.array2string($addressObject));
|
|
}
|
|
// to
|
|
$address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($headers['TO']):$headers['TO']),'');
|
|
foreach((array)$address_array as $addressObject) {
|
|
//debugLog(__METHOD__.__LINE__.'Address to add (TO):'.array2string($addressObject));
|
|
if ($addressObject->host == '.SYNTAX-ERROR.') continue;
|
|
$mailObject->AddAddress($addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''),$addressObject->personal);
|
|
}
|
|
// CC
|
|
$address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($headers['CC']):$headers['CC']),'');
|
|
foreach((array)$address_array as $addressObject) {
|
|
//debugLog(__METHOD__.__LINE__.'Address to add (CC):'.array2string($addressObject));
|
|
if ($addressObject->host == '.SYNTAX-ERROR.') continue;
|
|
$mailObject->AddCC($addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''),$addressObject->personal);
|
|
}
|
|
// AddReplyTo
|
|
$address_array = imap_rfc822_parse_adrlist((get_magic_quotes_gpc()?stripslashes($headers['REPLY-TO']):$headers['REPLY-TO']),'');
|
|
foreach((array)$address_array as $addressObject) {
|
|
//debugLog(__METHOD__.__LINE__.'Address to add (ReplyTO):'.array2string($addressObject));
|
|
if ($addressObject->host == '.SYNTAX-ERROR.') continue;
|
|
$mailObject->AddReplyTo($addressObject->mailbox. (!empty($addressObject->host) ? '@'.$addressObject->host : ''),$addressObject->personal);
|
|
}
|
|
$Header = $Body = ''; // we do not use Header and Body we use the MailObject
|
|
if ($this->debugLevel>0) debugLog(__METHOD__.__LINE__." Creation of Mailobject succeeded.");
|
|
}
|
|
catch (egw_exception_assertion_failed $e)
|
|
{
|
|
debugLog(__METHOD__.__LINE__." Creation of Mail failed.".$e->getMessage());
|
|
$Header = $Body = '';
|
|
}
|
|
|
|
if ($this->debugLevel>0) debugLog("MIME Body -> ".$body); // body is retrieved up
|
|
if ($output->airsyncbasenativebodytype==2) { //html
|
|
if ($this->debugLevel>0) debugLog("HTML Body with requested pref 4");
|
|
$mailObject->IsHTML(true);
|
|
$html = '<html>'.
|
|
'<head>'.
|
|
'<meta name="Generator" content="Z-Push">'.
|
|
'<meta http-equiv="Content-Type" content="text/html; charset=utf-8">'.
|
|
$css.
|
|
'</head>'.
|
|
'<body>'.
|
|
str_replace("\n","<BR>",str_replace("\r","", str_replace("\r\n","<BR>",$body))).
|
|
'</body>'.
|
|
'</html>';
|
|
$mailObject->Body = str_replace("\n","\r\n", str_replace("\r","",$html));
|
|
if ($this->debugLevel>2) debugLog(__METHOD__.__LINE__." MIME Body (constructed)-> ".$mailObject->Body);
|
|
$mailObject->AltBody = empty($plainBody)?strip_tags($body):$plainBody;
|
|
}
|
|
if ($output->airsyncbasenativebodytype==1) { //plain
|
|
if ($this->debugLevel>0) debugLog("Plain Body with requested pref 4");
|
|
$mailObject->IsHTML(false);
|
|
$mailObject->Body = $plainBody;
|
|
$mailObject->AltBody = '';
|
|
}
|
|
// we still need the attachments to be added ( if there are any )
|
|
// start handle Attachments
|
|
// $_uid, $_partID=null, Horde_Mime_Part $_structure=null, $fetchEmbeddedImages=true, $fetchTextCalendar=false, $resolveTNEF=true, $_folderName=''
|
|
$attachments = $this->mail->getMessageAttachments($id, null, null, true, false, true , $_folderName);
|
|
if (is_array($attachments) && count($attachments)>0)
|
|
{
|
|
debugLog(__METHOD__.__LINE__.' gather Attachments for BodyCreation of/for MessageID:'.$id.' found:'.count($attachments));
|
|
//error_log(__METHOD__.__LINE__.array2string($attachments));
|
|
foreach((array)$attachments as $key => $attachment)
|
|
{
|
|
if ($this->debugLevel>0) debugLog(__METHOD__.__LINE__.' Key:'.$key.'->'.array2string($attachment));
|
|
switch($attachment['type'])
|
|
{
|
|
case 'MESSAGE/RFC822':
|
|
$rawHeader = $rawBody = '';
|
|
if (isset($attachment['partID']))
|
|
{
|
|
$rawHeader = $this->mail->getMessageRawHeader($id, $attachment['partID'],$_folderName);
|
|
}
|
|
$rawBody = $this->mail->getMessageRawBody($id, $attachment['partID'],$_folderName);
|
|
$mailObject->AddStringAttachment($rawHeader.$rawBody, $mailObject->EncodeHeader($attachment['name']), '7bit', 'message/rfc822');
|
|
break;
|
|
default:
|
|
$attachmentData = '';
|
|
$attachmentData = $this->mail->getAttachment($id, $attachment['partID'],0,false);
|
|
$mailObject->AddStringAttachment($attachmentData['attachment'], $mailObject->EncodeHeader($attachment['name']), 'base64', $attachment['mimeType']);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
$mailObject->SetMessageType();
|
|
$Header = $mailObject->CreateHeader();
|
|
//debugLog(__METHOD__.__LINE__.' MailObject-Header:'.array2string($Header));
|
|
$Body = trim($mailObject->CreateBody()); // philip thinks this is needed, so lets try if it does any good/harm
|
|
if ($this->debugLevel>3) debugLog(__METHOD__.__LINE__.' MailObject:'.array2string($mailObject));
|
|
if ($this->debugLevel>2) debugLog(__METHOD__.__LINE__." Setting Mailobjectcontent to output:".$Header.$mailObject->LE.$mailObject->LE.$Body);
|
|
$output->airsyncbasebody->data = $Header.$mailObject->LE.$mailObject->LE.$Body;
|
|
}
|
|
else if (isset($bodypreference[2]))
|
|
{
|
|
if ($this->debugLevel>0) debugLog("HTML Body with requested pref 2");
|
|
// Send HTML if requested and native type was html
|
|
$output->airsyncbasebody->type = 2;
|
|
$htmlbody = '<html>'.
|
|
'<head>'.
|
|
'<meta name="Generator" content="Z-Push">'.
|
|
'<meta http-equiv="Content-Type" content="text/html; charset=utf-8">'.
|
|
$css.
|
|
'</head>'.
|
|
'<body>';
|
|
if ($output->airsyncbasenativebodytype==2)
|
|
{
|
|
// as we fetch html, and body is HTML, we may not need to handle this
|
|
$htmlbody .= $body;
|
|
}
|
|
else
|
|
{
|
|
// html requested but got only plaintext, so fake html
|
|
$htmlbody .= str_replace("\n","<BR>",str_replace("\r","<BR>", str_replace("\r\n","<BR>",$plainBody)));
|
|
}
|
|
$htmlbody .= '</body>'.
|
|
'</html>';
|
|
|
|
if(isset($bodypreference[2]["TruncationSize"]) && strlen($html) > $bodypreference[2]["TruncationSize"])
|
|
{
|
|
$htmlbody = utf8_truncate($htmlbody,$bodypreference[2]["TruncationSize"]);
|
|
$output->airsyncbasebody->truncated = 1;
|
|
}
|
|
$output->airsyncbasebody->data = $htmlbody;
|
|
}
|
|
else
|
|
{
|
|
// Send Plaintext as Fallback or if original body is plainttext
|
|
if ($this->debugLevel>0) debugLog("Plaintext Body:".$plainBody);
|
|
/* we use plainBody (set above) instead
|
|
$bodyStruct = $this->mail->getMessageBody($id,'only_if_no_text'); //'never_display');
|
|
$plain = $this->mail->getdisplayableBody($this->mail,$bodyStruct);//$this->ui->getdisplayableBody($bodyStruct,false);
|
|
$plain = html_entity_decode($plain,ENT_QUOTES,$this->mail->detect_encoding($plain));
|
|
$plain = strip_tags($plain);
|
|
//$plain = str_replace("\n","\r\n",str_replace("\r","",$plain));
|
|
*/
|
|
$output->airsyncbasebody->type = 1;
|
|
if(isset($bodypreference[1]["TruncationSize"]) &&
|
|
strlen($plainBody) > $bodypreference[1]["TruncationSize"])
|
|
{
|
|
$plainBody = utf8_truncate($plainBody, $bodypreference[1]["TruncationSize"]);
|
|
$output->airsyncbasebody->truncated = 1;
|
|
}
|
|
$output->airsyncbasebody->data = $plainBody;
|
|
}
|
|
// In case we have nothing for the body, send at least a blank...
|
|
// dw2412 but only in case the body is not rtf!
|
|
if ($output->airsyncbasebody->type != 3 && (!isset($output->airsyncbasebody->data) || strlen($output->airsyncbasebody->data) == 0))
|
|
{
|
|
$output->airsyncbasebody->data = " ";
|
|
}
|
|
// determine estimated datasize for all the above cases ...
|
|
$output->airsyncbasebody->estimateddatasize = strlen($output->airsyncbasebody->data);
|
|
}
|
|
// end AS12 Stuff
|
|
debugLog(__METHOD__.__LINE__.' gather Header info:'.$headers['SUBJECT'].' from:'.$headers['DATE']);
|
|
$output->read = $stat["flags"];
|
|
$output->subject = $this->messages[$id]['subject'];
|
|
$output->importance = ($this->messages[$id]['priority'] ? $this->messages[$id]['priority']:1) ;
|
|
$output->datereceived = $this->mail->_strtotime($headers['DATE'],'ts',true);
|
|
$output->displayto = ($headers['TO'] ? $headers['TO']:null); //$stat['FETCHED_HEADER']['to_name']
|
|
// $output->to = $this->messages[$id]['to_address']; //$stat['FETCHED_HEADER']['to_name']
|
|
// $output->from = $this->messages[$id]['sender_address']; //$stat['FETCHED_HEADER']['sender_name']
|
|
//error_log(__METHOD__.__LINE__.' To:'.$headers['TO']);
|
|
$output->to = $headers['TO'];
|
|
//error_log(__METHOD__.__LINE__.' From:'.$headers['FROM']);
|
|
$output->from = $headers['FROM'];
|
|
$output->cc = ($headers['CC'] ? $headers['CC']:null);
|
|
$output->reply_to = ($headers['REPLY_TO']?$headers['REPLY_TO']:null);
|
|
$output->messageclass = "IPM.Note";
|
|
if (stripos($this->messages[$id]['mimetype'],'multipart')!== false &&
|
|
stripos($this->messages[$id]['mimetype'],'signed')!== false)
|
|
{
|
|
$output->messageclass = "IPM.Note.SMIME.MultipartSigned";
|
|
}
|
|
// start AS12 Stuff
|
|
//$output->poommailflag = new SyncPoommailFlag();
|
|
|
|
if ($this->messages[$id]['flagged'] == 1)
|
|
{
|
|
$output->poommailflag = new SyncPoommailFlag();
|
|
$output->poommailflag->flagstatus = 2;
|
|
$output->poommailflag->flagtype = "Flag for Follow up";
|
|
}
|
|
|
|
$output->internetcpid = 65001;
|
|
$output->contentclass="urn:content-classes:message";
|
|
// end AS12 Stuff
|
|
|
|
// start handle Attachments (include text/calendar multipart alternative)
|
|
$attachments = $this->mail->getMessageAttachments($id, $_partID='', $_structure=null, $fetchEmbeddedImages=true, $fetchTextCalendar=true, true, $_folderName);
|
|
if (is_array($attachments) && count($attachments)>0)
|
|
{
|
|
debugLog(__METHOD__.__LINE__.' gather Attachments for MessageID:'.$id.' found:'.count($attachments));
|
|
//error_log(__METHOD__.__LINE__.array2string($attachments));
|
|
foreach ($attachments as $key => $attach)
|
|
{
|
|
if ($this->debugLevel>0) debugLog(__METHOD__.__LINE__.' Key:'.$key.'->'.array2string($attach));
|
|
|
|
// pass meeting requests to calendar plugin
|
|
if (strtolower($attach['mimeType']) == 'text/calendar' && strtolower($attach['method']) == 'request' &&
|
|
isset($GLOBALS['egw_info']['user']['apps']['calendar']) &&
|
|
($attachment = $this->mail->getAttachment($id, $attach['partID'],0,false)) &&
|
|
($output->meetingrequest = calendar_activesync::meetingRequest($attachment['attachment'])))
|
|
{
|
|
$output->messageclass = "IPM.Schedule.Meeting.Request";
|
|
continue; // do NOT add attachment as attachment
|
|
}
|
|
if(isset($output->_mapping['POOMMAIL:Attachments'])) {
|
|
$attachment = new SyncAttachment();
|
|
} else if(isset($output->_mapping['AirSyncBase:Attachments'])) {
|
|
$attachment = new SyncAirSyncBaseAttachment();
|
|
}
|
|
$attachment->attsize = $attach['size'];
|
|
$attachment->displayname = $attach['name'];
|
|
$attachment->attname = $folderid . ":" . $id . ":" . $attach['partID'];//$key;
|
|
//error_log(__METHOD__.__LINE__.'->'.$folderid . ":" . $id . ":" . $attach['partID']);
|
|
$attachment->attmethod = 1;
|
|
$attachment->attoid = "";//isset($part->headers['content-id']) ? trim($part->headers['content-id']) : "";
|
|
if (!empty($attach['cid']) && $attach['cid'] <> 'NIL' )
|
|
{
|
|
$attachment->isinline=true;
|
|
$attachment->attmethod=6;
|
|
$attachment->contentid= $attach['cid'];
|
|
// debugLog("'".$part->headers['content-id']."' ".$attachment->contentid);
|
|
$attachment->contenttype = trim($attach['mimeType']);
|
|
// debugLog("'".$part->headers['content-type']."' ".$attachment->contentid);
|
|
} else {
|
|
$attachment->attmethod=1;
|
|
}
|
|
|
|
if (isset($output->_mapping['POOMMAIL:Attachments'])) {
|
|
array_push($output->attachments, $attachment);
|
|
} else if(isset($output->_mapping['AirSyncBase:Attachments'])) {
|
|
array_push($output->airsyncbaseattachments, $attachment);
|
|
}
|
|
}
|
|
}
|
|
//$this->debugLevel=0;
|
|
// end handle Attachments
|
|
if ($this->debugLevel>3) debugLog(__METHOD__.__LINE__.array2string($output));
|
|
return $output;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Process response to meeting request
|
|
*
|
|
* mail plugin only extracts the iCal attachment and let's calendar plugin deal with adding it
|
|
*
|
|
* @see BackendDiff::MeetingResponse()
|
|
* @param string $folderid folder of meeting request mail
|
|
* @param int|string $requestid uid of mail with meeting request
|
|
* @param int $response 1=accepted, 2=tentative, 3=decline
|
|
* @return int|boolean id of calendar item, false on error
|
|
*/
|
|
function MeetingResponse($folderid, $requestid, $response)
|
|
{
|
|
if (!class_exists('calendar_activesync'))
|
|
{
|
|
debugLog(__METHOD__."(...) no EGroupware calendar installed!");
|
|
return null;
|
|
}
|
|
if (!($stat = $this->StatMessage($folderid, $requestid)))
|
|
{
|
|
debugLog(__METHOD__."($requestid, '$folderid', $response) returning FALSE (can NOT stat message)");
|
|
return false;
|
|
}
|
|
$ret = false;
|
|
foreach($this->mail->getMessageAttachments($requestid, $_partID='', $_structure=null, $fetchEmbeddedImages=true, $fetchTextCalendar=true) as $key => $attach)
|
|
{
|
|
if (strtolower($attach['mimeType']) == 'text/calendar' && strtolower($attach['method']) == 'request' &&
|
|
($attachment = $this->mail->getAttachment($requestid, $attach['partID'],0,false)))
|
|
{
|
|
debugLog(__METHOD__."($requestid, '$folderid', $response) iCal found, calling now backend->MeetingResponse('$attachment[attachment]')");
|
|
|
|
// calling backend again with iCal attachment, to let calendar add the event
|
|
if (($ret = $this->backend->MeetingResponse($attachment['attachment'],
|
|
$this->backend->createID('calendar',$GLOBALS['egw_info']['user']['account_id']),
|
|
$response, $calendarid)))
|
|
{
|
|
$ret = $calendarid;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
debugLog(__METHOD__."($requestid, '$folderid', $response) returning ".array2string($ret));
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* GetAttachmentData
|
|
* Should return attachment data for the specified attachment. The passed attachment identifier is
|
|
* the exact string that is returned in the 'AttName' property of an SyncAttachment. So, you should
|
|
* encode any information you need to find the attachment in that 'attname' property.
|
|
*
|
|
* @param string $fid - id
|
|
* @param string $attname - should contain (folder)id
|
|
* @return true, prints the content of the attachment
|
|
*/
|
|
function GetAttachmentData($fid,$attname) {
|
|
debugLog("getAttachmentData: $fid (attname: '$attname')");
|
|
//error_log(__METHOD__.__LINE__." Fid: $fid (attname: '$attname')");
|
|
list($folderid, $id, $part) = explode(":", $attname);
|
|
|
|
$this->splitID($folderid, $account, $folder);
|
|
|
|
if (!isset($this->mail)) $this->mail = mail_bo::getInstance(false,self::$profileID,true,false,true);
|
|
|
|
$this->mail->reopen($folder);
|
|
$attachment = $this->mail->getAttachment($id,$part,0,false);
|
|
print $attachment['attachment'];
|
|
unset($attachment);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* ItemOperationsGetAttachmentData
|
|
* Should return attachment data for the specified attachment. The passed attachment identifier is
|
|
* the exact string that is returned in the 'AttName' property of an SyncAttachment. So, you should
|
|
* encode any information you need to find the attachment in that 'attname' property.
|
|
*
|
|
* @param string $fid - id
|
|
* @param string $attname - should contain (folder)id
|
|
* @return SyncAirSyncBaseFileAttachment-object
|
|
*/
|
|
function ItemOperationsGetAttachmentData($fid,$attname) {
|
|
debugLog("ItemOperationsGetAttachmentData: (attname: '$attname')");
|
|
|
|
list($folderid, $id, $part) = explode(":", $attname);
|
|
|
|
$this->splitID($folderid, $account, $folder);
|
|
|
|
if (!isset($this->mail)) $this->mail = mail_bo::getInstance(false, self::$profileID,true,false,true);
|
|
|
|
$this->mail->reopen($folder);
|
|
$att = $this->mail->getAttachment($id,$part,0,false);
|
|
$attachment = new SyncAirSyncBaseFileAttachment();
|
|
/*
|
|
debugLog(__METHOD__.__LINE__.array2string($att));
|
|
if ($arr['filename']=='error.txt' && stripos($arr['attachment'], 'mail_bo::getAttachment failed') !== false)
|
|
{
|
|
return $attachment;
|
|
}
|
|
*/
|
|
if (is_array($att)) {
|
|
$attachment->_data = $att['attachment'];
|
|
$attachment->contenttype = trim($att['type']);
|
|
}
|
|
unset($att);
|
|
return $attachment;
|
|
}
|
|
|
|
/**
|
|
* StatMessage should return message stats, analogous to the folder stats (StatFolder). Entries are:
|
|
* 'id' => Server unique identifier for the message. Again, try to keep this short (under 20 chars)
|
|
* 'flags' => simply '0' for unread, '1' for read
|
|
* 'mod' => modification signature. As soon as this signature changes, the item is assumed to be completely
|
|
* changed, and will be sent to the PDA as a whole. Normally you can use something like the modification
|
|
* time for this field, which will change as soon as the contents have changed.
|
|
*
|
|
* @param string $folderid
|
|
* @param int|array $id event id or array or cal_id:recur_date for virtual exception
|
|
* @return array
|
|
*/
|
|
public function StatMessage($folderid, $id)
|
|
{
|
|
$messages = $this->fetchMessages($folderid, NULL, (array)$id);
|
|
$stat = array_shift($messages);
|
|
//debugLog (__METHOD__."('$folderid','$id') returning ".array2string($stat));
|
|
return $stat;
|
|
}
|
|
|
|
/**
|
|
* This function is called when a message has been changed on the PDA. You should parse the new
|
|
* message here and save the changes to disk. The return value must be whatever would be returned
|
|
* from StatMessage() after the message has been saved. This means that both the 'flags' and the 'mod'
|
|
* properties of the StatMessage() item may change via ChangeMessage().
|
|
* Note that this function will never be called on E-mail items as you can't change e-mail items, you
|
|
* can only set them as 'read'.
|
|
*/
|
|
function ChangeMessage($folderid, $id, $message)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* This function is called when the user moves an item on the PDA. You should do whatever is needed
|
|
* to move the message on disk. After this call, StatMessage() and GetMessageList() should show the items
|
|
* to have a new parent. This means that it will disappear from GetMessageList() will not return the item
|
|
* at all on the source folder, and the destination folder will show the new message
|
|
*
|
|
*/
|
|
function MoveMessage($folderid, $id, $newfolderid) {
|
|
debugLog("IMAP-MoveMessage: (sfid: '$folderid' id: '$id' dfid: '$newfolderid' )");
|
|
$this->splitID($folderid, $account, $srcFolder);
|
|
$this->splitID($newfolderid, $account, $destFolder);
|
|
debugLog("IMAP-MoveMessage: (SourceFolder: '$srcFolder' id: '$id' DestFolder: '$destFolder' )");
|
|
if (!isset($this->mail)) $this->mail = mail_bo::getInstance(false,self::$profileID,true,false,true);
|
|
$this->mail->reopen($destFolder);
|
|
$status = $this->mail->getFolderStatus($destFolder);
|
|
$uidNext = $status['uidnext'];
|
|
$this->mail->reopen($srcFolder);
|
|
|
|
// move message
|
|
$rv = $this->mail->moveMessages($destFolder,(array)$id,true,$srcFolder,true);
|
|
debugLog(__METHOD__.__LINE__.array2string($rv)); // this may be true, so try using the nextUID value by examine
|
|
// return the new id "as string""
|
|
return ($rv===true ? $uidNext : $rv[$id]) . "";
|
|
}
|
|
|
|
/**
|
|
* This function is analogous to GetMessageList.
|
|
*
|
|
* @ToDo loop over available email accounts
|
|
*/
|
|
public function GetMessageList($folderid, $cutoffdate=NULL)
|
|
{
|
|
static $cutdate;
|
|
if (!empty($cutoffdate) && $cutoffdate >0 && (empty($cutdate) || $cutoffdate != $cutdate)) $cutdate = $cutoffdate;
|
|
debugLog (__METHOD__.' for Folder:'.$folderid.' SINCE:'.$cutdate.'/'.date("d-M-Y", $cutdate));
|
|
if (empty($cutdate))
|
|
{
|
|
$cutdate = egw_time::to('now','ts')-(3600*24*28*3);
|
|
debugLog(__METHOD__.' Client set no truncationdate. Using 12 weeks.'.date("d-M-Y", $cutdate));
|
|
}
|
|
return $this->fetchMessages($folderid, $cutdate);
|
|
}
|
|
|
|
private function fetchMessages($folderid, $cutoffdate=NULL, $_id=NULL)
|
|
{
|
|
if ($this->debugLevel>1) $gstarttime = microtime (true);
|
|
//debugLog(__METHOD__.__LINE__);
|
|
$rv_messages = array();
|
|
// if the message is still available within the class, we use it instead of fetching it again
|
|
if (is_array($_id) && count($_id)==1 && is_array($this->messages) && isset($this->messages[$_id[0]]) && is_array($this->messages[$_id[0]]))
|
|
{
|
|
//debugLog(__METHOD__.__LINE__." the message ".$_id[0]." is still available within the class, we use it instead of fetching it again");
|
|
$rv_messages = array('header'=>array($this->messages[$_id[0]]));
|
|
}
|
|
if (empty($rv_messages))
|
|
{
|
|
if ($this->debugLevel>1) $starttime = microtime (true);
|
|
$this->_connect($this->account);
|
|
if ($this->debugLevel>1)
|
|
{
|
|
$endtime = microtime(true) - $starttime;
|
|
debugLog(__METHOD__. " connect took : ".$endtime.' for account:'.$this->account);
|
|
}
|
|
$messagelist = array();
|
|
// if not connected, any further action must fail
|
|
if (!empty($cutoffdate)) $_filter = array('status'=>array('UNDELETED'),'type'=>"SINCE",'string'=> date("d-M-Y", $cutoffdate));
|
|
if ($this->debugLevel>1) $starttime = microtime (true);
|
|
$rv = $this->splitID($folderid,$account,$_folderName,$id);
|
|
if ($this->debugLevel>1)
|
|
{
|
|
$endtime = microtime(true) - $starttime;
|
|
debugLog(__METHOD__. " splitID took : ".$endtime.' for FolderID:'.$folderid);
|
|
}
|
|
if ($this->debugLevel>1) debugLog(__METHOD__.' for Folder:'.$_folderName.' Filter:'.array2string($_filter).' Ids:'.array2string($_id).'/'.$id);
|
|
if ($this->debugLevel>1) $starttime = microtime (true);
|
|
$_numberOfMessages = (empty($cutoffdate)?250:99999);
|
|
$rv_messages = $this->mail->getHeaders($_folderName, $_startMessage=1, $_numberOfMessages, $_sort=0, $_reverse=false, $_filter, $_id);
|
|
if ($this->debugLevel>1)
|
|
{
|
|
$endtime = microtime(true) - $starttime;
|
|
debugLog(__METHOD__. " getHeaders call took : ".$endtime.' for FolderID:'.$_folderName);
|
|
}
|
|
}
|
|
if ($_id == NULL && $this->debugLevel>1) debugLog(__METHOD__." found :". count($rv_messages['header']));
|
|
//debugLog(__METHOD__.__LINE__.' Result:'.array2string($rv_messages));
|
|
foreach ((array)$rv_messages['header'] as $k => $vars)
|
|
{
|
|
if ($this->debugLevel>3) debugLog(__METHOD__.__LINE__.' ID to process:'.$vars['uid'].' Subject:'.$vars['subject']);
|
|
$this->messages[$vars['uid']] = $vars;
|
|
if ($this->debugLevel>3) debugLog(__METHOD__.__LINE__.' MailID:'.$k.'->'.array2string($vars));
|
|
if (!empty($vars['deleted'])) continue; // cut of deleted messages
|
|
if ($cutoffdate && $vars['date'] < $cutoffdate) continue; // message is out of range for cutoffdate, ignore it
|
|
if ($this->debugLevel>0) debugLog(__METHOD__.__LINE__.' ID to report:'.$vars['uid'].' Subject:'.$vars['subject']);
|
|
$mess["mod"] = $vars['date'];
|
|
$mess["id"] = $vars['uid'];
|
|
// 'seen' aka 'read' is the only flag we want to know about
|
|
$mess["flags"] = 0;
|
|
// outlook supports additional flags, set them to 0
|
|
$mess["olflags"] = 0;
|
|
if($vars["seen"]) $mess["flags"] = 1;
|
|
if($vars["flagged"]) $mess["olflags"] = 2;
|
|
if ($this->debugLevel>3) debugLog(__METHOD__.__LINE__.array2string($mess));
|
|
$messagelist[$vars['uid']] = $mess;
|
|
unset($mess);
|
|
}
|
|
|
|
if ($this->debugLevel>1)
|
|
{
|
|
$endtime = microtime(true) - $gstarttime;
|
|
debugLog(__METHOD__. " total time used : ".$endtime.' for Folder:'.$_folderName.' Filter:'.array2string($_filter).' Ids:'.array2string($_id).'/'.$id);
|
|
}
|
|
return $messagelist;
|
|
}
|
|
|
|
/**
|
|
* Search mailbox for a given pattern
|
|
*
|
|
* @param string $searchquery
|
|
* @return array with just rows (no values for keys rows, status or global_search_status!)
|
|
*/
|
|
public function getSearchResultsMailbox($searchquery)
|
|
{
|
|
if (!is_array($searchquery)) return array();
|
|
if ($this->debugLevel>0); debugLog(__METHOD__.__LINE__.array2string($searchquery));
|
|
// 19.10.2011 16:28:59 [24502] mail_activesync::getSearchResultsMailbox1408
|
|
//Array(
|
|
// [query] => Array(
|
|
// [0] => Array([op] => Search:And
|
|
// [value] => Array(
|
|
// [FolderType] => Email
|
|
// [FolderId] => 101000000000
|
|
// [Search:FreeText] => ttt
|
|
// [subquery] => Array(
|
|
// [0] => Array([op] => Search:GreaterThan
|
|
// [value] => Array(
|
|
// [POOMMAIL:DateReceived] => 1318975200))
|
|
// [1] => Array([op] => Search:LessThan
|
|
// [value] => Array(
|
|
// [POOMMAIL:DateReceived] => 1319034600))))))
|
|
// [rebuildresults] => 1
|
|
// [deeptraversal] =>
|
|
// [range] => 0-999)
|
|
if (isset($searchquery['rebuildresults'])) {
|
|
$rebuildresults = $searchquery['rebuildresults'];
|
|
} else {
|
|
$rebuildresults = false;
|
|
}
|
|
if ($this->debugLevel>0) debugLog( 'RebuildResults ['.$rebuildresults.']' );
|
|
|
|
if (isset($searchquery['deeptraversal'])) {
|
|
$deeptraversal = $searchquery['deeptraversal'];
|
|
} else {
|
|
$deeptraversal = false;
|
|
}
|
|
if ($this->debugLevel>0) debugLog( 'DeepTraversal ['.$deeptraversal.']' );
|
|
|
|
if (isset($searchquery['range'])) {
|
|
$range = explode("-",$searchquery['range']);
|
|
$limit = $range[1] - $range[0] + 1;
|
|
} else {
|
|
$range = false;
|
|
}
|
|
if ($this->debugLevel>0) debugLog( 'Range ['.print_r($range, true).']' );
|
|
|
|
//foreach($searchquery['query'] as $k => $value) {
|
|
// $query = $value;
|
|
//}
|
|
if (isset($searchquery['query'][0]['value']['FolderId'])) $folderid = $searchquery['query'][0]['value']['FolderId'];
|
|
// other types may be possible - we support quicksearch first (freeText in subject and from (or TO in Sent Folder))
|
|
if (is_null(emailadmin_imapbase::$supportsORinQuery) || !isset(emailadmin_imapbase::$supportsORinQuery[$this->mail->profileID]))
|
|
{
|
|
emailadmin_imapbase::$supportsORinQuery = egw_cache::getCache(egw_cache::INSTANCE,'email','supportsORinQuery'.trim($GLOBALS['egw_info']['user']['account_id']),$callback=null,$callback_params=array(),$expiration=60*60*10);
|
|
if (!isset(emailadmin_imapbase::$supportsORinQuery[$this->mail->profileID])) emailadmin_imapbase::$supportsORinQuery[$this->mail->profileID]=true;
|
|
}
|
|
|
|
if (isset($searchquery['query'][0]['value']['Search:FreeText']))
|
|
{
|
|
$type = (emailadmin_imapbase::$supportsORinQuery[$this->mail->profileID]?'quick':'subject');
|
|
$searchText = $searchquery['query'][0]['value']['Search:FreeText'];
|
|
}
|
|
if (!$folderid)
|
|
{
|
|
$_folderName = ($this->mail->sessionData['mailbox']?$this->mail->sessionData['mailbox']:'INBOX');
|
|
$folderid = $this->createID($account=0,$_folderName);
|
|
}
|
|
//if ($searchquery['query'][0]['value'][subquery][0][op]=='Search:GreaterThan');
|
|
//if (isset($searchquery['query'][0]['value'][subquery][0][value][POOMMAIL:DateReceived]));
|
|
//if ($searchquery['query'][0]['value'][subquery][1][op]=='Search:LessThan');
|
|
//if (isset($searchquery['query'][0]['value'][subquery][1][value][POOMMAIL:DateReceived]));
|
|
//$_filter = array('status'=>array('UNDELETED'),'type'=>"SINCE",'string'=> date("d-M-Y", $cutoffdate));
|
|
$rv = $this->splitID($folderid,$account,$_folderName,$id);
|
|
$_filter = array('type'=> (emailadmin_imapbase::$supportsORinQuery[$this->mail->profileID]?'quick':'subject'),
|
|
'string'=> $searchText,
|
|
'status'=>'any',
|
|
);
|
|
|
|
//$_filter[] = array('type'=>"SINCE",'string'=> date("d-M-Y", $cutoffdate));
|
|
if ($this->debugLevel>1) debugLog (__METHOD__.' for Folder:'.$_folderName.' Filter:'.array2string($_filter));
|
|
$rv_messages = $this->mail->getHeaders($_folderName, $_startMessage=1, $_numberOfMessages=($limit?$limit:9999999), $_sort=0, $_reverse=false, $_filter, $_id=NULL);
|
|
//debugLog(__METHOD__.__LINE__.array2string($rv_messages));
|
|
$list=array();
|
|
foreach((array)$rv_messages['header'] as $i => $vars)
|
|
{
|
|
$list[] = array(
|
|
"uniqueid" => $folderid.':'.$vars['uid'],
|
|
"item" => $vars['uid'],
|
|
//"parent" => ???,
|
|
"searchfolderid" => $folderid,
|
|
);
|
|
}
|
|
debugLog(__METHOD__.__LINE__.array2string($list));
|
|
return $list;//array();
|
|
}
|
|
|
|
/**
|
|
* Get ID of parent Folder or '0' for folders in root
|
|
*
|
|
* @param int $account
|
|
* @param string $folder
|
|
* @return string
|
|
*/
|
|
private function getParentID($account,$folder)
|
|
{
|
|
$this->_connect($account);
|
|
if (!isset($this->folders)) $this->folders = $this->mail->getFolderObjects(true,false);
|
|
|
|
$mailFolder = $this->folders[$folder];
|
|
if (!isset($mailFolder)) return false;
|
|
$delimiter = (isset($mailFolder->delimiter)?$mailFolder->delimiter:$this->mail->getHierarchyDelimiter());
|
|
$parent = explode($delimiter,$folder);
|
|
array_pop($parent);
|
|
$parent = implode($delimiter,$parent);
|
|
|
|
$id = $parent ? $this->createID($account, $parent) : '0';
|
|
if ($this->debugLevel>1) debugLog(__METHOD__."('$folder') --> parent=$parent --> $id");
|
|
return $id;
|
|
}
|
|
|
|
/**
|
|
* Get Information about a folder
|
|
*
|
|
* @param string $id
|
|
* @return SyncFolder|boolean false on error
|
|
*/
|
|
public function GetFolder($id)
|
|
{
|
|
static $last_id;
|
|
static $folderObj;
|
|
if (isset($last_id) && $last_id === $id) return $folderObj;
|
|
|
|
try {
|
|
$this->splitID($id, $account, $folder);
|
|
}
|
|
catch(Exception $e) {
|
|
debugLog(__METHOD__.__LINE__.' failed for '.$e->getMessage());
|
|
return $folderObj=false;
|
|
}
|
|
$this->_connect($account);
|
|
if (!isset($this->folders)) $this->folders = $this->mail->getFolderObjects(true,false);
|
|
|
|
$mailFolder = $this->folders[$folder];
|
|
if (!isset($mailFolder)) return $folderObj=false;
|
|
|
|
$folderObj = new SyncFolder();
|
|
$folderObj->serverid = $id;
|
|
$folderObj->parentid = $this->getParentID($account,$folder);
|
|
$folderObj->displayname = $mailFolder->shortDisplayName;
|
|
if ($this->debugLevel>1) debugLog(__METHOD__.__LINE__." ID: $id, Account:$account, Folder:$folder");
|
|
// get folder-type
|
|
foreach($this->folders as $inbox => $mailFolder) break;
|
|
if ($folder == $inbox)
|
|
{
|
|
$folderObj->type = SYNC_FOLDER_TYPE_INBOX;
|
|
}
|
|
elseif($this->mail->isDraftFolder($folder, false))
|
|
{
|
|
//debugLog(__METHOD__.' isDraft');
|
|
$folderObj->type = SYNC_FOLDER_TYPE_DRAFTS;
|
|
$folderObj->parentid = 0; // required by devices
|
|
}
|
|
elseif($this->mail->isTrashFolder($folder, false))
|
|
{
|
|
$folderObj->type = SYNC_FOLDER_TYPE_WASTEBASKET;
|
|
$this->_wasteID = $folder;
|
|
//error_log(__METHOD__.__LINE__.' TrashFolder:'.$this->_wasteID);
|
|
$folderObj->parentid = 0; // required by devices
|
|
}
|
|
elseif($this->mail->isSentFolder($folder, false))
|
|
{
|
|
$folderObj->type = SYNC_FOLDER_TYPE_SENTMAIL;
|
|
$folderObj->parentid = 0; // required by devices
|
|
$this->_sentID = $folder;
|
|
//error_log(__METHOD__.__LINE__.' SentFolder:'.$this->_sentID);
|
|
}
|
|
elseif($this->mail->isOutbox($folder, false))
|
|
{
|
|
//debugLog(__METHOD__.' isOutbox');
|
|
$folderObj->type = SYNC_FOLDER_TYPE_OUTBOX;
|
|
$folderObj->parentid = 0; // required by devices
|
|
}
|
|
else
|
|
{
|
|
//debugLog(__METHOD__.' isOther Folder'.$folder);
|
|
$folderObj->type = SYNC_FOLDER_TYPE_USER_MAIL;
|
|
}
|
|
|
|
if ($this->debugLevel>1) debugLog(__METHOD__."($id) --> $folder --> type=$folderObj->type, parentID=$folderObj->parentid, displayname=$folderObj->displayname");
|
|
return $folderObj;
|
|
}
|
|
|
|
/**
|
|
* Return folder stats. This means you must return an associative array with the
|
|
* following properties:
|
|
*
|
|
* "id" => The server ID that will be used to identify the folder. It must be unique, and not too long
|
|
* How long exactly is not known, but try keeping it under 20 chars or so. It must be a string.
|
|
* "parent" => The server ID of the parent of the folder. Same restrictions as 'id' apply.
|
|
* "mod" => This is the modification signature. It is any arbitrary string which is constant as long as
|
|
* the folder has not changed. In practice this means that 'mod' can be equal to the folder name
|
|
* as this is the only thing that ever changes in folders. (the type is normally constant)
|
|
*
|
|
* @return array with values for keys 'id', 'mod' and 'parent'
|
|
*/
|
|
public function StatFolder($id)
|
|
{
|
|
$folder = $this->GetFolder($id);
|
|
|
|
$stat = array(
|
|
'id' => $id,
|
|
'mod' => $folder->displayname,
|
|
'parent' => $folder->parentid,
|
|
);
|
|
|
|
return $stat;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return a changes array
|
|
*
|
|
* if changes occurr default diff engine computes the actual changes
|
|
*
|
|
* @param string $folderid
|
|
* @param string &$syncstate on call old syncstate, on return new syncstate
|
|
* @return array|boolean false if $folderid not found, array() if no changes or array(array("type" => "fakeChange"))
|
|
*/
|
|
function AlterPingChanges($folderid, &$syncstate)
|
|
{
|
|
debugLog(__METHOD__.' called with '.$folderid);
|
|
$this->splitID($folderid, $account, $folder);
|
|
if (is_numeric($account)) $type = 'mail';
|
|
if ($type != 'mail') return false;
|
|
|
|
if (!isset($this->mail)) $this->mail = mail_bo::getInstance(false,self::$profileID,true,false,true);
|
|
|
|
$changes = array();
|
|
debugLog("AlterPingChanges on $folderid ($folder) stat: ". $syncstate);
|
|
$this->mail->reopen($folder);
|
|
|
|
$status = $this->mail->getFolderStatus($folder,$ignoreStatusCache=true);
|
|
if (!$status) {
|
|
debugLog("AlterPingChanges: could not stat folder $folder ");
|
|
return false;
|
|
} else {
|
|
$newstate = "M:". $status['messages'] ."-R:". $status['recent'] ."-U:". $status['unseen']."-NUID:".$status['uidnext']."-UIDV:".$status['uidvalidity'];
|
|
|
|
// message number is different - change occured
|
|
if ($syncstate != $newstate) {
|
|
$syncstate = $newstate;
|
|
debugLog("AlterPingChanges: Change FOUND!");
|
|
// build a dummy change
|
|
$changes = array(array("type" => "fakeChange"));
|
|
}
|
|
}
|
|
//error_log(__METHOD__."('$folderid','$syncstate_was') syncstate='$syncstate' returning ".array2string($changes));
|
|
return $changes;
|
|
}
|
|
|
|
/**
|
|
* Should return a wastebasket folder if there is one. This is used when deleting
|
|
* items; if this function returns a valid folder ID, then all deletes are handled
|
|
* as moves and are sent to your backend as a move. If it returns FALSE, then deletes
|
|
* are always handled as real deletes and will be sent to your importer as a DELETE
|
|
*/
|
|
function GetWasteBasket()
|
|
{
|
|
debugLog(__METHOD__.__LINE__.' called.');
|
|
$this->_connect($this->account);
|
|
return $this->_wasteID;
|
|
}
|
|
|
|
/**
|
|
* This function is called when the user has requested to delete (really delete) a message. Usually
|
|
* this means just unlinking the file its in or somesuch. After this call has succeeded, a call to
|
|
* GetMessageList() should no longer list the message. If it does, the message will be re-sent to the PDA
|
|
* as it will be seen as a 'new' item. This means that if you don't implement this function, you will
|
|
* be able to delete messages on the PDA, but as soon as you sync, you'll get the item back
|
|
*/
|
|
function DeleteMessage($folderid, $id)
|
|
{
|
|
debugLog("IMAP-DeleteMessage: (fid: '$folderid' id: '$id' )");
|
|
/*
|
|
$this->imap_reopenFolder($folderid);
|
|
$s1 = @imap_delete ($this->_mbox, $id, FT_UID);
|
|
$s11 = @imap_setflag_full($this->_mbox, $id, "\\Deleted", FT_UID);
|
|
$s2 = @imap_expunge($this->_mbox);
|
|
*/
|
|
// we may have to split folderid
|
|
$this->splitID($folderid, $account, $folder);
|
|
debugLog(__METHOD__.__LINE__.' '.$folderid.'->'.$folder);
|
|
$_messageUID = (array)$id;
|
|
|
|
$this->_connect($this->account);
|
|
$this->mail->reopen($folder);
|
|
try
|
|
{
|
|
$rv = $this->mail->deleteMessages($_messageUID, $folder);
|
|
}
|
|
catch (egw_exception $e)
|
|
{
|
|
$error = $e->getMessage();
|
|
debugLog(__METHOD__.__LINE__." $_messageUID, $folder ->".$error);
|
|
// if the server thinks the message does not exist report deletion as success
|
|
if (stripos($error,'[NONEXISTENT]')!==false) return true;
|
|
return false;
|
|
}
|
|
|
|
// this may be a bit rude, it may be sufficient that GetMessageList does not list messages flagged as deleted
|
|
if ($this->mail->mailPreferences['deleteOptions'] == 'mark_as_deleted')
|
|
{
|
|
// ignore mark as deleted -> Expunge!
|
|
//$this->mail->icServer->expunge(); // do not expunge as GetMessageList does not List messages flagged as deleted
|
|
}
|
|
debugLog("IMAP-DeleteMessage: $rv");
|
|
|
|
return $rv;
|
|
}
|
|
|
|
/**
|
|
* This should change the 'read' flag of a message on disk. The $flags
|
|
* parameter can only be '1' (read) or '0' (unread). After a call to
|
|
* SetReadFlag(), GetMessageList() should return the message with the
|
|
* new 'flags' but should not modify the 'mod' parameter. If you do
|
|
* change 'mod', simply setting the message to 'read' on the PDA will trigger
|
|
* a full resync of the item from the server
|
|
*/
|
|
function SetReadFlag($folderid, $id, $flags)
|
|
{
|
|
// debugLog("IMAP-SetReadFlag: (fid: '$folderid' id: '$id' flags: '$flags' )");
|
|
$this->splitID($folderid, $account, $folder);
|
|
|
|
$_messageUID = (array)$id;
|
|
$this->_connect($this->account);
|
|
$rv = $this->mail->flagMessages((($flags) ? "read" : "unread"), $_messageUID,$folder);
|
|
debugLog("IMAP-SetReadFlag -> set ".array2string($_messageUID).' in Folder '.$folder." as " . (($flags) ? "read" : "unread") . "-->". $rv);
|
|
|
|
return $rv;
|
|
}
|
|
|
|
/**
|
|
* Creates or modifies a folder
|
|
*
|
|
* @param string $id of the parent folder
|
|
* @param string $oldid => if empty -> new folder created, else folder is to be renamed
|
|
* @param string $displayname => new folder name (to be created, or to be renamed to)
|
|
* @param string $type folder type, ignored in IMAP
|
|
*
|
|
* @return array|boolean stat array or false on error
|
|
*/
|
|
public function ChangeFolder($id, $oldid, $displayname, $type)
|
|
{
|
|
debugLog(__METHOD__."('$id', '$oldid', '$displayname', $type) NOT supported!");
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Deletes (really delete) a Folder
|
|
*
|
|
* @param string $parentid of the folder to delete
|
|
* @param string $id of the folder to delete
|
|
*
|
|
* @return
|
|
* @TODO check what is to be returned
|
|
*/
|
|
public function DeleteFolder($parentid, $id)
|
|
{
|
|
debugLog(__METHOD__."('$parentid', '$id') NOT supported!");
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* modify olflags (outlook style) flag of a message
|
|
*
|
|
* @param $folderid
|
|
* @param $id
|
|
* @param $flags
|
|
*
|
|
*
|
|
* @DESC The $flags parameter must contains the poommailflag Object
|
|
*/
|
|
function ChangeMessageFlag($folderid, $id, $flags)
|
|
{
|
|
$_messageUID = (array)$id;
|
|
$this->_connect($this->account);
|
|
$this->splitID($folderid, $account, $folder);
|
|
$rv = $this->mail->flagMessages((($flags->flagstatus == 2) ? "flagged" : "unflagged"), $_messageUID,$folder);
|
|
debugLog("IMAP-SetFlaggedFlag -> set ".array2string($_messageUID).' in Folder '.$folder." as " . (($flags->flagstatus == 2) ? "flagged" : "unflagged") . "-->". $rv);
|
|
|
|
return $rv;
|
|
}
|
|
|
|
/**
|
|
* Create a max. 32 hex letter ID, current 20 chars are used
|
|
*
|
|
* @param int $account mail account id
|
|
* @param string $folder
|
|
* @param int $id=0
|
|
* @return string
|
|
* @throws egw_exception_wrong_parameter
|
|
*/
|
|
private function createID($account,$folder,$id=0)
|
|
{
|
|
if (!is_numeric($folder))
|
|
{
|
|
// convert string $folder in numeric id
|
|
$folder = $this->folder2hash($account,$f=$folder);
|
|
}
|
|
|
|
$str = $this->backend->createID($account, $folder, $id);
|
|
|
|
if ($this->debugLevel>1) debugLog(__METHOD__."($account,'$f',$id) type=$account, folder=$folder --> '$str'");
|
|
|
|
return $str;
|
|
}
|
|
|
|
/**
|
|
* Split an ID string into $app, $folder and $id
|
|
*
|
|
* @param string $str
|
|
* @param int &$account mail account id
|
|
* @param string &$folder
|
|
* @param int &$id=null
|
|
* @throws egw_exception_wrong_parameter
|
|
*/
|
|
private function splitID($str,&$account,&$folder,&$id=null)
|
|
{
|
|
$this->backend->splitID($str, $account, $folder, $id);
|
|
|
|
// convert numeric folder-id back to folder name
|
|
$folder = $this->hash2folder($account,$f=$folder);
|
|
|
|
if ($this->debugLevel>1) debugLog(__METHOD__."('$str','$account','$folder',$id)");
|
|
}
|
|
|
|
/**
|
|
* Methods to convert (hierarchical) folder names to nummerical id's
|
|
*
|
|
* This is currently done by storing a serialized array in the device specific
|
|
* state directory.
|
|
*/
|
|
|
|
/**
|
|
* Convert folder string to nummeric hash
|
|
*
|
|
* @param int $account
|
|
* @param string $folder
|
|
* @return int
|
|
*/
|
|
private function folder2hash($account,$folder)
|
|
{
|
|
if(!isset($this->folderHashes)) $this->readFolderHashes();
|
|
|
|
if (($index = array_search($folder, (array)$this->folderHashes[$account])) === false)
|
|
{
|
|
// new hash
|
|
$this->folderHashes[$account][] = $folder;
|
|
$index = array_search($folder, (array)$this->folderHashes[$account]);
|
|
|
|
// maybe later storing in on class destruction only
|
|
$this->storeFolderHashes();
|
|
}
|
|
return $index;
|
|
}
|
|
|
|
/**
|
|
* Convert numeric hash to folder string
|
|
*
|
|
* @param int $account
|
|
* @param int $index
|
|
* @return string NULL if not used so far
|
|
*/
|
|
private function hash2folder($account,$index)
|
|
{
|
|
if(!isset($this->folderHashes)) $this->readFolderHashes();
|
|
|
|
return $this->folderHashes[$account][$index];
|
|
}
|
|
|
|
private $folderHashes;
|
|
|
|
/**
|
|
* Read hashfile from state dir
|
|
*/
|
|
private function readFolderHashes()
|
|
{
|
|
if (file_exists($file = $this->hashFile()) &&
|
|
($hashes = file_get_contents($file)))
|
|
{
|
|
$this->folderHashes = json_decode($hashes,true);
|
|
// fallback in case hashes have been serialized instead of being json-encoded
|
|
if (json_last_error()!=JSON_ERROR_NONE)
|
|
{
|
|
//error_log(__METHOD__.__LINE__." error decoding with json");
|
|
$this->folderHashes = unserialize($hashes);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$this->folderHashes = array();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Store hashfile in state dir
|
|
*
|
|
* return int|boolean false on error
|
|
*/
|
|
private function storeFolderHashes()
|
|
{
|
|
// make sure $this->folderHashes is an array otherwise json_encode may fail on decode for string,integer,float or boolean
|
|
return file_put_contents($this->hashFile(), json_encode((is_array($this->folderHashes)?$this->folderHashes:array($this->folderHashes))));
|
|
}
|
|
|
|
/**
|
|
* Get name of hashfile in state dir
|
|
*
|
|
* @throws egw_exception_assertion_failed
|
|
*/
|
|
private function hashFile()
|
|
{
|
|
if (!isset($this->backend->_devid))
|
|
{
|
|
throw new egw_exception_assertion_failed(__METHOD__."() called without this->_devid set!");
|
|
}
|
|
return STATE_DIR.'/'.strtolower($this->backend->_devid).'/'.$this->backend->_devid.'.hashes';
|
|
}
|
|
|
|
}
|