* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$
*/
/**
* FeLaMiMail worker class
* -provides backend functionality for all classes in FeLaMiMail
* -provides classes that may be used by other apps too
*/
class felamimail_bo
{
var $public_functions = array
(
'flagMessages' => True,
);
static $debug = false; //true; // sometimes debuging is quite handy, to see things. check with the error log to see results
// define some constants
// message types
var $type = array("text", "multipart", "message", "application", "audio", "image", "video", "other");
/**
* static used to configure tidy - if tidy is loadable, this config is used with tidy to straighten out html, instead of using purifiers tidy mode
*
* @array
*/
static $tidy_config = array('clean'=>true,'output-html'=>true,'join-classes'=>true,'join-styles'=>true,'show-body-only'=>"auto",'word-2000'=>true,'wrap'=>0);
/**
* static used to configure htmLawed, for use with emails
*
* @array
*/
static $htmLawed_config = array('comment'=>1, //remove comments
'keep_bad'=>6,
'balance'=>1,//turn off tag-balancing (config['balance']=>0). That will not introduce any security risk; only standards-compliant tag nesting check/filtering will be turned off (basic tag-balance will remain; i.e., there won't be any unclosed tag, etc., after filtering)
'tidy'=>1,
'elements' => "* -script",
'schemes'=>'href: file, ftp, http, https, mailto; src: cid, data, file, ftp, http, https; *:file, http, https',
'hook_tag' =>"hl_email_tag_transform",
);
/**
* errorMessage
*
* @var string $errorMessage
*/
var $errorMessage;
// message encodings
var $encoding = array("7bit", "8bit", "binary", "base64", "quoted-printable", "other");
static $displayCharset;
/**
* Instance of bopreference
*
* @var bopreferences
*/
var $bopreferences;
/**
* Active preferences
*
* @var array
*/
var $mailPreferences;
// set to true, if php is compiled with multi byte string support
var $mbAvailable = FALSE;
// what type of mimeTypes do we want from the body(text/html, text/plain)
var $htmlOptions;
var $sessionData;
// the current selected user profile
var $profileID = 0;
/**
* Folders that get automatic created AND get translated to the users language
* their creation is also controlled by users mailpreferences. if set to none / dont use folder
* the folder will not be automatically created. This is controlled in bofelamimail->getFolderObjects
* so changing names here, must include a change of keywords there as well. Since these
* foldernames are subject to translation, keep that in mind too, if you change names here.
* ActiveSync:
* Outbox is needed by Nokia Clients to be able to send Mails
* @var array
*/
static $autoFolders = array('Drafts', 'Templates', 'Sent', 'Trash', 'Junk', 'Outbox');
/**
* Autoload classes from emailadmin, 'til they get autoloading conform names
*
* @param string $class
*/
static function autoload($class)
{
if (strlen($class)<100)
{
if (file_exists($file=EGW_INCLUDE_ROOT.'/emailadmin/inc/class.'.$class.'.inc.php'))
{
include_once($file);
//error_log(__METHOD__."($class) included $file");
}
elseif (file_exists($file=EGW_INCLUDE_ROOT.'/felamimail/inc/class.'.$class.'.inc.php'))
{
include_once($file);
}
else
{
#error_log(__METHOD__."($class) failed!");
}
}
}
/**
* Hold instances by profileID for getInstance() singleton
*
* @var array
*/
private static $instances = array();
/**
* Singleton for felamimail_bo
*
* @param boolean $_restoreSession=true
* @param int $_profileID=0
* @param boolean $_validate=true - flag wether the profileid should be validated or not, if validation is true, you may receive a profile
* not matching the input profileID, if we can not find a profile matching the given ID
* @return object instance of felamimail_bo
*/
public static function getInstance($_restoreSession=true, $_profileID=0, $_validate=true)
{
if ($_profileID != 0 && $_validate)
{
$profileID = self::validateProfileID($_restoreSession, $_profileID);
if ($profileID != $_profileID)
{
error_log(__METHOD__.__LINE__.' Validation of profile with ID:'.$_profileID.' failed. Using '.$profileID.' instead.');
error_log(__METHOD__.__LINE__.' # Instance='.$GLOBALS['egw_info']['user']['domain'].', User='.$GLOBALS['egw_info']['user']['account_lid']);
$_profileID = $profileID;
$GLOBALS['egw']->preferences->add('felamimail','ActiveProfileID',$_profileID,'user');
// save prefs
$GLOBALS['egw']->preferences->save_repository(true);
egw_cache::setSession('felamimail','activeProfileID',$_profileID);
}
}
//error_log(__METHOD__.__LINE__.' RestoreSession:'.$_restoreSession.' ProfileId:'.$_profileID.' called from:'.function_backtrace());
if (!isset(self::$instances[$_profileID]))
{
self::$instances[$_profileID] = new felamimail_bo('utf-8',$_restoreSession,$_profileID);
}
else
{
// make sure the prefs are up to date for the profile to load
$loadfailed = false;
self::$instances[$_profileID]->mailPreferences = self::$instances[$_profileID]->bopreferences->getPreferences(true,$_profileID);
//error_log(__METHOD__.__LINE__." ReRead the Prefs for ProfileID ".$_profileID.' called from:'.function_backtrace());
if (self::$instances[$_profileID]->mailPreferences) {
self::$instances[$_profileID]->icServer = self::$instances[$_profileID]->mailPreferences->getIncomingServer($_profileID);
// if we do not get an icServer object, session restore failed on bopreferences->getPreferences
if (!self::$instances[$_profileID]->icServer) $loadfailed=true;
if ($_profileID != 0) self::$instances[$_profileID]->mailPreferences->setIncomingServer(self::$instances[$_profileID]->icServer,0);
self::$instances[$_profileID]->ogServer = self::$instances[$_profileID]->mailPreferences->getOutgoingServer($_profileID);
if ($_profileID != 0) self::$instances[$_profileID]->mailPreferences->setOutgoingServer(self::$instances[$_profileID]->ogServer,0);
self::$instances[$_profileID]->htmlOptions = self::$instances[$_profileID]->mailPreferences->preferences['htmlOptions'];
}
else
{
$loadfailed=true;
}
if ($loadfailed)
{
error_log(__METHOD__.__LINE__." ReRead of the Prefs for ProfileID ".$_profileID.' failed for icServer; trigger new instance. called from:'.function_backtrace());
// restore session seems to provide an incomplete session
self::$instances[$_profileID] = new felamimail_bo('utf-8',false,$_profileID);
}
}
self::$instances[$_profileID]->profileID = $_profileID;
//error_log(__METHOD__.__LINE__.' RestoreSession:'.$_restoreSession.' ProfileId:'.$_profileID);
return self::$instances[$_profileID];
}
/**
* unset the private static instances by profileID
*
* @param int $_profileID=0
* @return void
*/
public static function unsetInstance($_profileID=0)
{
if (isset(self::$instances[$_profileID])) unset(self::$instances[$_profileID]);
}
/**
* validate the given profileId to make sure it is valid for the active user
*
* @param boolean $_restoreSession=true - needed to pass on to getInstance
* @param int $_profileID=0
* @return int validated profileID -> either the profileID given, or a valid one
*/
public static function validateProfileID($_restoreSession=true, $_profileID=0)
{
$identities = array();
$mail = felamimail_bo::getInstance($_restoreSession, $_profileID, $validate=false); // we need an instance of felamimail_bo
$selectedID = $mail->getIdentitiesWithAccounts($identities);
if (is_object($mail->mailPreferences)) $activeIdentity =& $mail->mailPreferences->getIdentity($_profileID, true);
// if you use user defined accounts you may want to access the profile defined with the emailadmin available to the user
// as we validate the profile in question and may need to return an emailadminprofile, we fetch this one all the time
$boemailadmin = new emailadmin_bo();
$defaultProfile = $boemailadmin->getUserProfile() ;
//error_log(__METHOD__.__LINE__.array2string($defaultProfile));
$identitys =& $defaultProfile->identities;
$icServers =& $defaultProfile->ic_server;
foreach ($identitys as $tmpkey => $identity)
{
if (empty($icServers[$tmpkey]->host)) continue;
$identities[$identity->id] = $identity->realName.' '.$identity->organization.' <'.$identity->emailAddress.'>';
}
//error_log(__METHOD__.__LINE__.array2string($identities));
if (array_key_exists($_profileID,$identities))
{
// everything seems to be in order self::$profileID REMAINS UNCHANGED
}
else
{
if (array_key_exists($selectedID,$identities))
{
$_profileID = $selectedID;
}
else
{
foreach (array_keys((array)$identities) as $k => $ident)
{
//error_log(__METHOD__.__LINE__.' Testing Identity with ID:'.$ident.' for being provided by emailadmin.');
if ($ident <0) $_profileID = $ident;
}
if (self::$debug) error_log(__METHOD__.__LINE__.' Profile Selected (after trying to fetch DefaultProfile):'.array2string($_profileID));
if (!array_key_exists($_profileID,$identities))
{
// everything failed, try first profile found
$keys = array_keys((array)$identities);
if (count($keys)>0) $_profileID = array_shift($keys);
else $_profileID = 0;
}
}
}
if (self::$debug) error_log(__METHOD__.'::'.__LINE__.' ProfileSelected:'.$_profileID.' -> '.$identities[$_profileID]);
return $_profileID;
}
/**
* Private constructor, use felamimail_bo::getInstance() instead
*
* @param string $_displayCharset='utf-8'
* @param boolean $_restoreSession=true
* @param int $_profileID=0
*/
private function __construct($_displayCharset='utf-8',$_restoreSession=true, $_profileID=0)
{
$this->profileID = $_profileID;
if ($_restoreSession)
{
//error_log(__METHOD__." Session restore ".function_backtrace());
$this->restoreSessionData();
$lv_mailbox = $this->sessionData['mailbox'];
$firstMessage = $this->sessionData['previewMessage'];
}
else
{
$this->restoreSessionData();
$lv_mailbox = $this->sessionData['mailbox'];
$firstMessage = $this->sessionData['previewMessage'];
$this->sessionData = array();
$this->forcePrefReload();
}
//error_log(array2string(array($firstMessage,$lv_mailbox)));
// FIXME: this->foldername seems to be unused
//$this->foldername = $this->sessionData['mailbox'];
$this->accountid = $GLOBALS['egw_info']['user']['account_id'];
$this->bopreferences = CreateObject('felamimail.bopreferences',$_restoreSession);
$this->mailPreferences = $this->bopreferences->getPreferences(true,$this->profileID);
//error_log(__METHOD__.__LINE__." ProfileID ".$this->profileID.' called from:'.function_backtrace());
if ($this->mailPreferences) {
$this->icServer = $this->mailPreferences->getIncomingServer($this->profileID);
if ($this->profileID != 0) $this->mailPreferences->setIncomingServer($this->icServer,0);
$this->ogServer = $this->mailPreferences->getOutgoingServer($this->profileID);
if ($this->profileID != 0) $this->mailPreferences->setOutgoingServer($this->ogServer,0);
$this->htmlOptions = $this->mailPreferences->preferences['htmlOptions'];
}
//_debug_array($this->mailPreferences->preferences);
$this->imapBaseDir = '';
self::$displayCharset = $_displayCharset;
if(function_exists(mb_decode_mimeheader)) {
mb_internal_encoding(self::$displayCharset);
}
// set some defaults
if(empty($this->sessionData))
{
// this should be under user preferences
// sessionData empty
// store active profileID
$this->sessionData['profileID'] = $_profileID;
// no filter active
$this->sessionData['activeFilter'] = "-1";
// default mailbox INBOX
$this->sessionData['mailbox'] = (($lv_mailbox && self::folderExists($lv_mailbox,true)) ? $lv_mailbox : "INBOX");
$this->sessionData['previewMessage'] = ($firstMessage >0 ? $firstMessage : 0);
// default start message
$this->sessionData['startMessage'] = 1;
// default mailbox for preferences pages
$this->sessionData['preferences']['mailbox'] = "INBOX";
$this->sessionData['messageFilter'] = array(
'string' => '',
'type' => 'quick',
'status' => 'any',
);
// default sorting
switch($GLOBALS['egw_info']['user']['preferences']['felamimail']['sortOrder']) {
case 1:
$this->sessionData['sort'] = SORTDATE;
$this->sessionData['sortReverse'] = false;
break;
case 2:
$this->sessionData['sort'] = SORTFROM;
$this->sessionData['sortReverse'] = true;
break;
case 3:
$this->sessionData['sort'] = SORTFROM;
$this->sessionData['sortReverse'] = false;
break;
case 4:
$this->sessionData['sort'] = SORTSUBJECT;
$this->sessionData['sortReverse'] = true;
break;
case 5:
$this->sessionData['sort'] = SORTSUBJECT;
$this->sessionData['sortReverse'] = false;
break;
case 6:
$this->sessionData['sort'] = SORTSIZE;
$this->sessionData['sortReverse'] = true;
break;
case 7:
$this->sessionData['sort'] = SORTSIZE;
$this->sessionData['sortReverse'] = false;
break;
default:
$this->sessionData['sort'] = SORTDATE;
$this->sessionData['sortReverse'] = true;
break;
}
$this->saveSessionData();
}
if (function_exists('mb_convert_encoding')) {
$this->mbAvailable = TRUE;
}
}
public static function forcePrefReload()
{
// unset the fm_preferences session object, to force the reload/rebuild
$GLOBALS['egw']->session->appsession('fm_preferences','felamimail',serialize(array()));
$GLOBALS['egw']->session->appsession('session_data','emailadmin',serialize(array()));
}
function _getNameSpaces()
{
static $nameSpace;
$foldersNameSpace = array();
$delimiter = $this->getHierarchyDelimiter();
// TODO: cache by $this->icServer->ImapServerId
if (is_null($nameSpace)) $nameSpace = $this->icServer->getNameSpaces();
if (is_array($nameSpace)) {
foreach($nameSpace as $type => $singleNameSpace) {
$prefix_present = false;
if($type == 'personal' && ($singleNameSpace[2]['name'] == '#mh/' || count($nameSpace) == 1) && ($this->folderExists('Mail')||$this->folderExists('INBOX')))
{
$foldersNameSpace[$type]['prefix_present'] = 'forced';
// uw-imap server with mailbox prefix or dovecot maybe
$foldersNameSpace[$type]['prefix'] = ($this->folderExists('Mail')?'Mail':'');
}
elseif($type == 'personal' && ($singleNameSpace[2]['name'] == '#mh/' || count($nameSpace) == 1) && $this->folderExists('mail'))
{
$foldersNameSpace[$type]['prefix_present'] = 'forced';
// uw-imap server with mailbox prefix or dovecot maybe
$foldersNameSpace[$type]['prefix'] = 'mail';
} else {
$foldersNameSpace[$type]['prefix_present'] = true;
$foldersNameSpace[$type]['prefix'] = $singleNameSpace[0]['name'];
}
$foldersNameSpace[$type]['delimiter'] = $delimiter;
//echo "############## $type->".print_r($foldersNameSpace[$type],true)." ###################
";
}
}
return $foldersNameSpace;
}
function getFolderPrefixFromNamespace($nameSpace, $folderName)
{
foreach($nameSpace as $type => $singleNameSpace)
{
if (substr($singleNameSpace['prefix'],0,strlen($folderName))==$folderName) return $singleNameSpace['prefix'];
}
return "";
}
function setACL($_folderName, $_accountName, $_acl, $_recursive=false)
{
//$_recursive=true;
//error_log(__METHOD__.__LINE__.'-> called with:'."$_folderName, $_accountName, $_acl, $_recursive");
if ( PEAR::isError($this->icServer->setACL($_folderName, $_accountName, $_acl)) ) {
return false;
}
if ($_recursive)
{
$delimiter = $this->getHierarchyDelimiter();
$nameSpace = $this->_getNameSpaces();
$prefix = $this->getFolderPrefixFromNamespace($nameSpace, $_folderName);
//error_log(__METHOD__.__LINE__.'->'."$_folderName, $delimiter, $prefix");
$subFolders = $this->getMailBoxesRecursive($_folderName, $delimiter, $prefix);
//error_log(__METHOD__.__LINE__.' Fetched Subfolders->'.array2string($subFolders));
foreach ($subFolders as $k => $folder)
{
// we do not monitor failure or success on subfolders
if ($folder <> $_folderName) $this->icServer->setACL($folder, $_accountName, $_acl);
}
}
return TRUE;
}
function deleteACL($_folderName, $_accountName, $_recursive=false)
{
//$_recursive=true;
//error_log(__METHOD__.__LINE__." calledv with: $_folderName, $_accountName, $_recursive");
if ( PEAR::isError($this->icServer->deleteACL($_folderName, $_accountName)) ) {
return false;
}
if ($_recursive)
{
$delimiter = $this->getHierarchyDelimiter();
$nameSpace = $this->_getNameSpaces();
$prefix = $this->getFolderPrefixFromNamespace($nameSpace, $_folderName);
//error_log(__METHOD__.__LINE__.'->'."$_folderName, $delimiter, $prefix");
$subFolders = $this->getMailBoxesRecursive($_folderName, $delimiter, $prefix);
//error_log(__METHOD__.__LINE__.' Fetched Subfolders->'.array2string($subFolders));
foreach ($subFolders as $k => $folder)
{
// we do not monitor failure or success on subfolders
if ($folder <> $_folderName) $this->icServer->deleteACL($folder, $_accountName);
}
}
return TRUE;
}
/**
* hook to add account
*
* this function is a wrapper function for emailadmin
*
* @param _hookValues contains the hook values as array
* @return nothing
*/
function addAccount($_hookValues)
{
if ($this->mailPreferences) {
$icServer = $this->mailPreferences->getIncomingServer($this->profileID);
if(($icServer instanceof defaultimap)) {
// if not connected, try opening an admin connection
if (!$icServer->_connected) $this->openConnection($this->profileID,true);
$icServer->addAccount($_hookValues);
if ($icServer->_connected) $this->closeConnection(); // close connection afterwards
}
$ogServer = $this->mailPreferences->getOutgoingServer($this->profileID);
if(($ogServer instanceof defaultsmtp)) {
$ogServer->addAccount($_hookValues);
}
}
}
/**
* save a message in folder
* throws exception on failure
* @todo set flags again
*
* @param string _folderName the foldername
* @param string _header the header of the message
* @param string _body the body of the message
* @param string _flags the imap flags to set for the saved message
*
* @return the id of the message appended or exception
*/
function appendMessage($_folderName, $_header, $_body, $_flags)
{
$header = ltrim(str_replace("\n","\r\n",$_header));
$body = str_replace("\n","\r\n",$_body);
$messageid = $this->icServer->appendMessage("$header"."$body", $_folderName, $_flags);
if ( PEAR::isError($messageid)) {
if (self::$debug) error_log("Could not append Message:".print_r($messageid->message,true));
throw new egw_exception_wrong_userinput(lang("Could not append Message:".array2string($messageid->message)));
//return false;
}
if ($messageid === true) // try to figure out the message uid
{
$list = $this->getHeaders($_folderName, $_startMessage=1, $_numberOfMessages=1, $_sort=0, $_reverse=true, $_filter=array());
if ($list)
{
if (self::$debug) error_log(__METHOD__.__LINE__.' MessageUid:'.$messageid.' but found:'.array2string($list));
$messageid = $list['header'][0]['uid'];
}
}
return $messageid;
}
function closeConnection() {
//if ($icServer->_connected) error_log(__METHOD__.__LINE__.' disconnect from Server');
if ($icServer->_connected) $this->icServer->disconnect();
}
/**
* remove any messages which are marked as deleted or
* remove any messages from the trashfolder
*
* @param string _folderName the foldername
* @return nothing
*/
function compressFolder($_folderName = false)
{
$folderName = ($_folderName ? $_folderName : $this->sessionData['mailbox']);
$deleteOptions = $GLOBALS['egw_info']['user']['preferences']['felamimail']['deleteOptions'];
$trashFolder = $this->mailPreferences->preferences['trashFolder']; //$GLOBALS['egw_info']['user']['preferences']['felamimail']['trashFolder'];
$this->icServer->selectMailbox($folderName);
if($folderName == $trashFolder && $deleteOptions == "move_to_trash") {
$this->icServer->deleteMessages('1:*');
$this->icServer->expunge();
} else {
$this->icServer->expunge();
}
}
/**
* create a new folder under given parent folder
*
* @param string _parent the parent foldername
* @param string _folderName the new foldername
* @param bool _subscribe subscribe to the new folder
*
* @return mixed name of the newly created folder or false on error
*/
function createFolder($_parent, $_folderName, $_subscribe=false)
{
if (self::$debug) error_log(__METHOD__.__LINE__."->"."$_parent, $_folderName, $_subscribe");
$parent = $this->_encodeFolderName($_parent);
$folderName = $this->_encodeFolderName($_folderName);
if(empty($parent)) {
$newFolderName = $folderName;
} else {
$HierarchyDelimiter = $this->getHierarchyDelimiter();
$newFolderName = $parent . $HierarchyDelimiter . $folderName;
}
if (self::$debug) error_log(__METHOD__.__LINE__.'->'.$newFolderName);
if (self::folderExists($newFolderName,true))
{
error_log(__METHOD__.__LINE__." Folder $newFolderName already exists.");
return $newFolderName;
}
$rv = $this->icServer->createMailbox($newFolderName);
if ( PEAR::isError($rv ) ) {
error_log(__METHOD__.__LINE__.' create Folder '.$newFolderName.'->'.$rv->message.' Namespace:'.array2string($this->icServer->getNameSpaces()));
return false;
}
$srv = $this->icServer->subscribeMailbox($newFolderName);
if ( PEAR::isError($srv ) ) {
error_log(__METHOD__.__LINE__.' subscribe to new folder '.$newFolderName.'->'.$srv->message);
return false;
}
return $newFolderName;
}
function createIMAPFilter($_folder, $_criterias)
{
$all = 'ALL UNDELETED'; //'ALL'
//_debug_array($_criterias);
if (self::$debug) error_log(__METHOD__.__LINE__.' Criterias:'.(!is_array($_criterias)?" none -> returning $all":array2string($_criterias)));
if(!is_array($_criterias)) {
return $all;
}
#error_log(print_r($_criterias, true));
$imapFilter = '';
#foreach($_criterias as $criteria => $parameter) {
if(!empty($_criterias['string'])) {
$criteria = strtoupper($_criterias['type']);
switch ($criteria) {
case 'QUICK':
if($this->isSentFolder($_folder)) {
$imapFilter .= 'OR SUBJECT "'. $_criterias['string'] .'" TO "'. $_criterias['string'] .'" ';
} else {
$imapFilter .= 'OR SUBJECT "'. $_criterias['string'] .'" FROM "'. $_criterias['string'] .'" ';
}
break;
case 'BCC':
case 'BODY':
case 'CC':
case 'FROM':
case 'KEYWORD':
case 'SUBJECT':
case 'TEXT':
case 'TO':
$imapFilter .= $criteria .' "'. $_criterias['string'] .'" ';
break;
case 'SINCE':
case 'BEFORE':
case 'ON':
$imapFilter .= $criteria .' '. $_criterias['string'].' ';
break;
}
}
foreach((array)$_criterias['status'] as $k => $criteria) {
$criteria = strtoupper($criteria);
switch ($criteria) {
case 'ANSWERED':
case 'DELETED':
case 'FLAGGED':
case 'NEW':
case 'OLD':
case 'RECENT':
case 'SEEN':
case 'UNANSWERED':
case 'UNDELETED':
case 'UNFLAGGED':
case 'UNSEEN':
$imapFilter .= $criteria .' ';
break;
case 'KEYWORD1':
case 'KEYWORD2':
case 'KEYWORD3':
case 'KEYWORD4':
case 'KEYWORD5':
$imapFilter .= "KEYWORD ".'$label'.substr(trim($criteria),strlen('KEYWORD')).' ';
break;
}
}
if (isset($_criterias['range']) && !empty($_criterias['range']))
{
$imapFilter .= $_criterias['range'].' ';
}
if (self::$debug) error_log(__METHOD__.__LINE__." Filter: ".($imapFilter?$imapFilter:$all));
if($imapFilter == '') {
return $all;
} else {
return trim($imapFilter);
#return 'CHARSET '. strtoupper(self::$displayCharset) .' '. trim($imapFilter);
}
}
/**
* convert a mailboxname from displaycharset to urf7-imap
*
* @param string _folderName the foldername
*
* @return string the converted foldername
*/
function decodeFolderName($_folderName)
{
return translation::convert($_folderName, self::$displayCharset, 'UTF7-IMAP');
}
function decodeMimePart($_mimeMessage, $_encoding, $_charset = '')
{
// decode the part
if (self::$debug) error_log(__METHOD__."() with $_encoding and $_charset:".print_r($_mimeMessage,true));
switch (strtoupper($_encoding))
{
case 'BASE64':
// use imap_base64 to decode, not any longer, as it is strict, and fails if it encounters invalid chars
return base64_decode($_mimeMessage); //imap_base64($_mimeMessage);
break;
case 'QUOTED-PRINTABLE':
// use imap_qprint to decode
return quoted_printable_decode($_mimeMessage);
break;
default:
// it is either not encoded or we don't know about it
return $_mimeMessage;
break;
}
}
/**
* decode header (or envelope information)
* if array given, note that only values will be converted
* @param mixed $_string input to be converted, if array call decode_header recursively on each value
* @return mixed - based on the input type
*/
static function decode_header($_string)
{
if (is_array($_string))
{
foreach($_string as $k=>$v)
{
$_string[$k] = self::decode_header($v);
}
return $_string;
}
else
{
return translation::decodeMailHeader($_string,self::$displayCharset);
}
}
function decode_subject($_string,$decode=true)
{
#$string = $_string;
if($_string=='NIL')
{
return 'No Subject';
}
if ($decode) $_string = self::decode_header($_string);
return $_string;
}
/**
* decodes winmail.dat attachments
*
* @param int $_uid
* @param string $_partID
* @param int $_filenumber
* @return array
*/
function decode_winmail( $_uid, $_partID, $_filenumber=0 )
{
$attachment = $this->getAttachment( $_uid, $_partID );
$dirname = $this->accountid.'_'.$this->profileID.'_'.$this->sessionData['mailbox'].'_'.$_uid.'_'.$_partID;
if (self::$debug) error_log(__METHOD__.__LINE__.' Dirname:'.$dirname);
$dirname = md5($dirname);
$dir = $GLOBALS['egw_info']['server']['temp_dir']."/fmail_winmail/$dirname";
if (self::$debug) error_log(__METHOD__.__LINE__.' Dir to save winmail.dat to:'.$dir);
$mime = CreateObject('phpgwapi.mime_magic');
if ( $attachment['type'] == 'APPLICATION/MS-TNEF' && $attachment['filename'] == 'winmail.dat' )
{
// decode winmail.dat
if ( !file_exists( "$dir/winmail.dat" ) )
{
@mkdir( $dir, 0700, true );
file_put_contents( "$dir/winmail.dat", $attachment['attachment'] );
}
//if (self::$debug) unset($attachment['attachment']);
if (self::$debug) error_log(__METHOD__.__LINE__." Saving Attachment to $dir. ->".array2string($attachment));
if (file_exists('/usr/bin/tnef'))
{
exec( "cd $dir && /usr/bin/tnef --save-body --overwrite -C $dir -f ./winmail.dat" );
}
elseif (exec("which tnef")) // use tnef if exsting, as it gives better results..
{
exec( "cd $dir && tnef --save-body --overwrite -C $dir -f ./winmail.dat" );
}
elseif (exec("which ytnef"))
{
exec( "cd $dir && ytnef -f . winmail.dat" );
}
// list contents
$files = scandir( $dir );
foreach ( $files as $num => $file )
{
if ( filetype( "$dir/$file" ) != 'file' || $file == 'winmail.dat' ) continue;
if ( $_filenumber > 0 && $_filenumber != $num ) continue;
$type = $mime->filename2mime($file);
$attachments[] = array(
'is_winmail' => $num,
'name' => self::decode_header($file),
'size' => filesize( "$dir/$file"),
'partID' => $_partID,
'mimeType' => $type,
'type' => $type,
'attachment' => $_filenumber > 0 ? file_get_contents("$dir/$file") : '',
);
unlink($dir."/".$file);
}
if (file_exists($dir."/winmail.dat")) unlink($dir."/winmail.dat");
if (file_exists($dir)) @rmdir($dir);
return $_filenumber > 0 ? $attachments[0] : $attachments;
}
return false;
}
function deleteAccount($_hookValues)
{
if ($this->mailPreferences) {
$icServer = $this->mailPreferences->getIncomingServer($this->profileID);
if(($icServer instanceof defaultimap)) {
//try to connect with admin rights, when not connected
if (!$icServer->_connected) $this->openConnection($this->profileID,true);
$icServer->deleteAccount($_hookValues);
if ($icServer->_connected) $this->closeConnection(); // close connection
}
$ogServer = $this->mailPreferences->getOutgoingServer($this->profileID);
if(($ogServer instanceof defaultsmtp)) {
$ogServer->deleteAccount($_hookValues);
}
}
}
/**
* delete a existing folder
*
* @param string _folderName the name of the folder to be deleted
*
* @return bool true on success, false on failure
*/
function deleteFolder($_folderName)
{
$folderName = $this->_encodeFolderName($_folderName);
$this->icServer->unsubscribeMailbox($folderName);
if ( PEAR::isError($this->icServer->deleteMailbox($folderName)) ) {
return false;
}
return true;
}
function deleteMessages($_messageUID, $_folder=NULL, $_forceDeleteMethod='no')
{
//error_log(__METHOD__.__LINE__.'->'.array2string($_messageUID).','.$_folder);
$msglist = '';
$oldMailbox = '';
if (is_null($_folder) || empty($_folder)) $_folder = $this->sessionData['mailbox'];
if(!is_array($_messageUID) || count($_messageUID) === 0)
{
if ($_messageUID=='all')
{
$_messageUID= null;
}
else
{
if (self::$debug) error_log(__METHOD__." no messages Message(s): ".implode(',',$_messageUID));
return false;
}
}
$deleteOptions = $_forceDeleteMethod; // use forceDeleteMethod if not "no", or unknown method
if ($_forceDeleteMethod === 'no' || !in_array($_forceDeleteMethod,array('move_to_trash',"mark_as_deleted","remove_immediately"))) $deleteOptions = $this->mailPreferences->preferences['deleteOptions'];
//error_log(__METHOD__.__LINE__.'->'.array2string($_messageUID).','.$_folder.'/'.$this->sessionData['mailbox'].' Option:'.$deleteOptions);
$trashFolder = $this->mailPreferences->preferences['trashFolder'];
$draftFolder = $this->mailPreferences->preferences['draftFolder']; //$GLOBALS['egw_info']['user']['preferences']['felamimail']['draftFolder'];
$templateFolder = $this->mailPreferences->preferences['templateFolder']; //$GLOBALS['egw_info']['user']['preferences']['felamimail']['templateFolder'];
if(($_folder == $trashFolder && $deleteOptions == "move_to_trash") ||
($_folder == $draftFolder)) {
$deleteOptions = "remove_immediately";
}
if($this->icServer->getCurrentMailbox() != $_folder) {
$oldMailbox = $this->icServer->getCurrentMailbox();
$this->icServer->selectMailbox($_folder);
}
$updateCache = false;
switch($deleteOptions) {
case "move_to_trash":
$updateCache = true;
if(!empty($trashFolder)) {
if (self::$debug) error_log(implode(' : ', $_messageUID));
if (self::$debug) error_log("$trashFolder <= ". $this->sessionData['mailbox']);
// copy messages
$retValue = $this->icServer->copyMessages($trashFolder, $_messageUID, $_folder, true);
if ( PEAR::isError($retValue) ) {
if (self::$debug) error_log(__METHOD__." failed to copy Message(s) to $trashFolder: ".implode(',',$_messageUID));
throw new egw_exception("failed to copy Message(s) to $trashFolder: ".implode(',',$_messageUID).' due to:'.array2string($retValue->message));
return false;
}
// mark messages as deleted
$retValue = $this->icServer->deleteMessages($_messageUID, true);
if ( PEAR::isError($retValue)) {
if (self::$debug) error_log(__METHOD__." failed to delete Message(s): ".implode(',',$_messageUID).' due to:'.$retValue->message);
throw new egw_exception("failed to delete Message(s): ".implode(',',$_messageUID).' due to:'.array2string($retValue->message));
return false;
}
// delete the messages finaly
$rv = $this->icServer->expunge();
if ( PEAR::isError($rv)) error_log(__METHOD__." failed to expunge Message(s) from Folder: ".$_folder.' due to:'.$rv->message);
}
break;
case "mark_as_deleted":
// mark messages as deleted
foreach((array)$_messageUID as $key =>$uid)
{
//flag messages, that are flagged for deletion as seen too
$this->flagMessages('read', $uid, $_folder);
$flags = $this->getFlags($uid);
//error_log(__METHOD__.__LINE__.array2string($flags));
if (strpos( array2string($flags),'Deleted')!==false) $undelete[] = $uid;
unset($flags);
}
$retValue = PEAR::isError($this->icServer->deleteMessages($_messageUID, true));
foreach((array)$undelete as $key =>$uid)
{
$this->flagMessages('undelete', $uid, $_folder);
}
if ( PEAR::isError($retValue)) {
if (self::$debug) error_log(__METHOD__." failed to mark as deleted for Message(s): ".implode(',',$_messageUID));
throw new egw_exception("failed to mark as deleted for Message(s): ".implode(',',$_messageUID).' due to:'.array2string($retValue->message));
return false;
}
break;
case "remove_immediately":
$updateCache = true;
// mark messages as deleted
$retValue = $this->icServer->deleteMessages($_messageUID, true);
if ( PEAR::isError($retValue)) {
if (self::$debug) error_log(__METHOD__." failed to remove immediately Message(s): ".implode(',',$_messageUID));
throw new egw_exception("failed to remove immediately Message(s): ".implode(',',$_messageUID).' due to:'.array2string($retValue->message));
return false;
}
// delete the messages finaly
$this->icServer->expunge();
break;
}
if ($updateCache)
{
$structure = egw_cache::getCache(egw_cache::INSTANCE,'email','structureCache'.trim($GLOBALS['egw_info']['user']['account_id']),$callback=null,$callback_params=array(),$expiration=60*60*1);
$cachemodified = false;
foreach ((array)$_messageUID as $k => $_uid)
{
if (isset($structure[$this->icServer->ImapServerId][$_folder][$_uid]) || $_uid=='all')
{
$cachemodified = true;
if ($_uid=='all')
unset($structure[$this->icServer->ImapServerId][$_folder]);
else
unset($structure[$this->icServer->ImapServerId][$_folder][$_uid]);
}
}
if ($cachemodified) egw_cache::setCache(egw_cache::INSTANCE,'email','structureCache'.trim($GLOBALS['egw_info']['user']['account_id']),$structure,$expiration=60*60*1);
}
if($oldMailbox != '') {
$this->icServer->selectMailbox($oldMailbox);
}
return true;
}
/**
* convert a mailboxname from utf7-imap to displaycharset
*
* @param string _folderName the foldername
*
* @return string the converted string
*/
function encodeFolderName($_folderName)
{
return translation::convert($_folderName, 'UTF7-IMAP', self::$displayCharset);
}
# function encodeHeader($_string, $_encoding='q')
# {
# switch($_encoding) {
# case "q":
# if(!preg_match("/[\x80-\xFF]/",$_string)) {
# // nothing to quote, only 7 bit ascii
# return $_string;
# }
#
# $string = imap_8bit($_string);
# $stringParts = explode("=\r\n",$string);
# while(list($key,$value) = each($stringParts)) {
# if(!empty($retString)) $retString .= " ";
# $value = str_replace(" ","_",$value);
# // imap_8bit does not convert "?"
# // it does not need, but it should
# $value = str_replace("?","=3F",$value);
# $retString .= "=?".strtoupper(self::$displayCharset). "?Q?". $value. "?=";
# }
# #exit;
# return $retString;
# break;
# default:
# return $_string;
# }
# }
function getFlags ($_messageUID) {
$flags = $this->icServer->getFlags($_messageUID, true);
if (PEAR::isError($flags)) {
error_log(__METHOD__.__LINE__.'Failed to retrieve Flags for Messages with UID(s)'.array2string($_messageUID).' Server returned:'.$flags->message);
return null;
}
return $flags;
}
function getNotifyFlags ($_messageUID, $flags=null)
{
if($flags===null) $flags = $this->icServer->getFlags($_messageUID, true);
if (self::$debug) error_log(__METHOD__.$_messageUID.' Flags:'.array2string($flags));
if (PEAR::isError($flags)) {
return null;
}
if ( stripos( array2string($flags),'MDNSent')!==false)
return true;
if ( stripos( array2string($flags),'MDNnotSent')!==false)
return false;
return null;
}
/**
* flag a Message
*
* @param string _flag (readable name)
* @param mixed array/string _messageUID array of ids to flag, or 'all'
* @param string _folder foldername
*
* @todo handle handle icserver->setFlags returnValue
*
* @return bool true, as we do not handle icserver->setFlags returnValue
*/
function flagMessages($_flag, $_messageUID,$_folder=NULL)
{
//error_log(__METHOD__.__LINE__.'->' .$_flag." ".array2string($_messageUID).",$_folder /".$this->sessionData['mailbox']);
if(!is_array($_messageUID)) {
#return false;
if ($_messageUID=='all')
{
//all is an allowed value to be passed
}
else
{
$_messageUID=array($_messageUID);
}
}
$this->icServer->selectMailbox(($_folder?$_folder:$this->sessionData['mailbox']));
switch($_flag) {
case "undelete":
$ret = $this->icServer->setFlags($_messageUID, '\\Deleted', 'remove', true);
break;
case "flagged":
$ret = $this->icServer->setFlags($_messageUID, '\\Flagged', 'add', true);
break;
case "read":
$ret = $this->icServer->setFlags($_messageUID, '\\Seen', 'add', true);
break;
case "forwarded":
$ret = $this->icServer->setFlags($_messageUID, '$Forwarded', 'add', true);
case "answered":
$ret = $this->icServer->setFlags($_messageUID, '\\Answered', 'add', true);
break;
case "unflagged":
$ret = $this->icServer->setFlags($_messageUID, '\\Flagged', 'remove', true);
break;
case "unread":
$ret = $this->icServer->setFlags($_messageUID, '\\Seen', 'remove', true);
$ret = $this->icServer->setFlags($_messageUID, '\\Answered', 'remove', true);
$ret = $this->icServer->setFlags($_messageUID, '$Forwarded', 'remove', true);
break;
case "mdnsent":
$ret = $this->icServer->setFlags($_messageUID, 'MDNSent', 'add', true);
break;
case "mdnnotsent":
$ret = $this->icServer->setFlags($_messageUID, 'MDNnotSent', 'add', true);
break;
case "label1":
$ret = $this->icServer->setFlags($_messageUID, '$label1', 'add', true);
break;
case "unlabel1":
$this->icServer->setFlags($_messageUID, '$label1', 'remove', true);
break;
case "label2":
$ret = $this->icServer->setFlags($_messageUID, '$label2', 'add', true);
break;
case "unlabel2":
$ret = $this->icServer->setFlags($_messageUID, '$label2', 'remove', true);
break;
case "label3":
$ret = $this->icServer->setFlags($_messageUID, '$label3', 'add', true);
break;
case "unlabel3":
$ret = $this->icServer->setFlags($_messageUID, '$label3', 'remove', true);
break;
case "label4":
$ret = $this->icServer->setFlags($_messageUID, '$label4', 'add', true);
break;
case "unlabel4":
$ret = $this->icServer->setFlags($_messageUID, '$label4', 'remove', true);
break;
case "label5":
$ret = $this->icServer->setFlags($_messageUID, '$label5', 'add', true);
break;
case "unlabel5":
$ret = $this->icServer->setFlags($_messageUID, '$label5', 'remove', true);
break;
}
if (PEAR::isError($ret))
{
if (stripos($ret->message,'Too long argument'))
{
$c = count($_messageUID);
$h =ceil($c/2);
error_log(__METHOD__.__LINE__.$ret->message." $c messages given for flagging. Trying with chunks of $h");
$this->flagMessages($_flag, array_slice($_messageUID,0,$h),($_folder?$_folder:$this->sessionData['mailbox']));
$this->flagMessages($_flag, array_slice($_messageUID,$h),($_folder?$_folder:$this->sessionData['mailbox']));
}
}
$this->sessionData['folderStatus'][$this->profileID][$this->sessionData['mailbox']]['uidValidity'] = 0;
$this->saveSessionData();
//error_log(__METHOD__.__LINE__.'->' .$_flag." ".array2string($_messageUID).",".($_folder?$_folder:$this->sessionData['mailbox']));
return true; // as we do not catch/examine setFlags returnValue
}
function _getStatus($folderName,$ignoreStatusCache=false)
{
static $folderStatus;
if (!$ignoreStatusCache && isset($folderStatus[$this->icServer->ImapServerId][$folderName]))
{
//error_log(__METHOD__.__LINE__.' Using cache for status on Server:'.$this->icServer->ImapServerId.' for folder:'.$folderName.'->'.array2string($folderStatus[$this->icServer->ImapServerId][$folderName]));
return $folderStatus[$this->icServer->ImapServerId][$folderName];
}
$folderStatus[$this->icServer->ImapServerId][$folderName] = $this->icServer->getStatus($folderName);
return $folderStatus[$this->icServer->ImapServerId][$folderName];
}
function _getStructure($_uid, $byUid=true, $_ignoreCache=false)
{
static $structure;
$_folder = $this->sessionData['mailbox'];
$_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox());
if (is_null($structure)) $structure = egw_cache::getCache(egw_cache::INSTANCE,'email','structureCache'.trim($GLOBALS['egw_info']['user']['account_id']),$callback=null,$callback_params=array(),$expiration=60*60*1);
if (isset($structure[$this->icServer->ImapServerId]) && !empty($structure[$this->icServer->ImapServerId]) &&
isset($structure[$this->icServer->ImapServerId][$_folder]) && !empty($structure[$this->icServer->ImapServerId][$_folder]) &&
isset($structure[$this->icServer->ImapServerId][$_folder][$_uid]) && !empty($structure[$this->icServer->ImapServerId][$_folder][$_uid]))
{
if ($_ignoreCache===false)
{
//error_log(__METHOD__.__LINE__.' Using cache for structure on Server:'.$this->icServer->ImapServerId.' for uid:'.$_uid." in Folder:".$_folder.'->'.array2string($structure[$this->icServer->ImapServerId][$_folder][$_uid]));
return $structure[$this->icServer->ImapServerId][$_folder][$_uid];
}
}
$structure[$this->icServer->ImapServerId][$_folder][$_uid] = $this->icServer->getStructure($_uid, $byUid);
egw_cache::setCache(egw_cache::INSTANCE,'email','structureCache'.trim($GLOBALS['egw_info']['user']['account_id']),$structure,$expiration=60*60*1);
//error_log(__METHOD__.__LINE__.' Using query for structure on Server:'.$this->icServer->ImapServerId.' for uid:'.$_uid." in Folder:".$_folder.'->'.array2string($structure[$this->icServer->ImapServerId][$_folder][$_uid]));
return $structure[$this->icServer->ImapServerId][$_folder][$_uid];
}
function _getSubStructure($_structure, $_partID)
{
$tempID = '';
$structure = $_structure;
if (empty($_partID)) $_partID=1;
$imapPartIDs = explode('.',$_partID);
#error_log(print_r($structure,true));
#error_log(print_r($_partID,true));
if($_partID != 1) {
foreach($imapPartIDs as $imapPartID) {
if(!empty($tempID)) {
$tempID .= '.';
}
$tempID .= $imapPartID;
#error_log(print_r( "TEMPID: $tempID
",true));
//_debug_array($structure);
if($structure->subParts[$tempID]->type == 'MESSAGE' && $structure->subParts[$tempID]->subType == 'RFC822' &&
count($structure->subParts[$tempID]->subParts) == 1 &&
$structure->subParts[$tempID]->subParts[$tempID]->type == 'MULTIPART' &&
($structure->subParts[$tempID]->subParts[$tempID]->subType == 'MIXED' ||
$structure->subParts[$tempID]->subParts[$tempID]->subType == 'ALTERNATIVE' ||
$structure->subParts[$tempID]->subParts[$tempID]->subType == 'RELATED' ||
$structure->subParts[$tempID]->subParts[$tempID]->subType == 'REPORT'))
{
$structure = $structure->subParts[$tempID]->subParts[$tempID];
} else {
$structure = $structure->subParts[$tempID];
}
}
}
if($structure->partID != $_partID) {
foreach($imapPartIDs as $imapPartID) {
if(!empty($tempID)) {
$tempID .= '.';
}
$tempID .= $imapPartID;
//print "TEMPID: $tempID
";
//_debug_array($structure);
if($structure->subParts[$tempID]->type == 'MESSAGE' && $structure->subParts[$tempID]->subType == 'RFC822' &&
count($structure->subParts[$tempID]->subParts) == 1 &&
$structure->subParts[$tempID]->subParts[$tempID]->type == 'MULTIPART' &&
($structure->subParts[$tempID]->subParts[$tempID]->subType == 'MIXED' ||
$structure->subParts[$tempID]->subParts[$tempID]->subType == 'ALTERNATIVE' ||
$structure->subParts[$tempID]->subParts[$tempID]->subType == 'RELATED' ||
$structure->subParts[$tempID]->subParts[$tempID]->subType == 'REPORT')) {
$structure = $structure->subParts[$tempID]->subParts[$tempID];
} else {
$structure = $structure->subParts[$tempID];
}
}
if($structure->partID != $_partID) {
error_log(__METHOD__."(". __LINE__ .") partID's don't match");
return false;
}
}
return $structure;
}
/*
* strip tags out of the message completely with their content
* param $_body is the text to be processed
* param $tag is the tagname which is to be removed. Note, that only the name of the tag is to be passed to the function
* without the enclosing brackets
* param $endtag can be different from tag but should be used only, if begin and endtag are known to be different e.g.:
*/
static function replaceTagsCompletley(&$_body,$tag,$endtag='',$addbracesforendtag=true)
{
translation::replaceTagsCompletley($_body,$tag,$endtag,$addbracesforendtag);
}
static function getCleanHTML(&$_html, $usepurify = false, $cleanTags=true)
{
// remove CRLF and TAB as it is of no use in HTML.
// but they matter in
, so we rather don't //$_html = str_replace("\r\n",' ',$_html); //$_html = str_replace("\t",' ',$_html); //error_log($_html); //repair doubleencoded ampersands, and some stuff htmLawed stumbles upon with balancing switched on $_html = str_replace(array('&','',"",''),array('&','
','
','
'),$_html); //$_html = str_replace(array('&'),array('&'),$_html); if (stripos($_html,'style')!==false) self::replaceTagsCompletley($_html,'style'); // clean out empty or pagewide style definitions / left over tags if (stripos($_html,'head')!==false) self::replaceTagsCompletley($_html,'head'); // Strip out stuff in head //if (stripos($_html,'![if')!==false && stripos($_html,'')!==false) self::replaceTagsCompletley($_html,'!\[if','',false); // Strip out stuff in ifs if (stripos($_html,'!--[if')!==false && stripos($_html,'')!==false) self::replaceTagsCompletley($_html,'!--\[if','',false); // Strip out stuff in ifs //error_log($_html); // force the use of kses, as it is still have the edge over purifier with some stuff $usepurify = true; if ($usepurify) { // we need a customized config, as we may allow external images, $GLOBALS['egw_info']['user']['preferences']['felamimail']['allowExternalIMGs'] if (get_magic_quotes_gpc() === 1) $_html = stripslashes($_html); // Strip out doctype in head, as htmlLawed cannot handle it TODO: Consider extracting it and adding it afterwards if (stripos($_html,'!doctype')!==false) self::replaceTagsCompletley($_html,'!doctype'); if (stripos($_html,'?xml:namespace')!==false) self::replaceTagsCompletley($_html,'\?xml:namespace','/>',false); if (stripos($_html,'?xml version')!==false) self::replaceTagsCompletley($_html,'\?xml version','\?>',false); if (strpos($_html,'!CURSOR')!==false) self::replaceTagsCompletley($_html,'!CURSOR'); // htmLawed filter only the 'body' //preg_match('`(]*>)(.+?)(