merge changes from update-zpush-2.3 branch into master

This commit is contained in:
Ralf Becker 2016-06-17 09:11:26 +02:00
commit 6a02f1c19a
7 changed files with 283 additions and 176 deletions

View File

@ -536,6 +536,7 @@ class Accounts
$underscore = '_';
}
if (!$domain) $domain = $GLOBALS['egw_info']['server']['mail_suffix'];
if (!$domain) $domain = $_SERVER['SERVER_NAME'];
$email = str_replace(array('first','last','initial','account','dot','underscore','-'),
array($first,$last,substr($first,0,1),$account,$dot,$underscore,''),
@ -736,9 +737,10 @@ class Accounts
*
* @param int|string $account_id numeric account_id or account_lid
* @param string $which ='account_lid' type to convert to: account_lid (default), account_email, ...
* @param boolean $generate_email =false true: generate an email address, if user has none
* @return string|boolean converted value or false on error ($account_id not found)
*/
static function id2name($account_id, $which='account_lid')
static function id2name($account_id, $which='account_lid', $generate_email=false)
{
if (!is_numeric($account_id) && !($account_id = self::getInstance()->name2id($account_id)))
{
@ -751,6 +753,10 @@ class Accounts
unset($e);
return false;
}
if ($generate_email && $which === 'account_email' && empty($data[$which]))
{
return self::email($data['account_firstname'], $data['account_lastname'], $data['account_lid']);
}
return $data[$which];
}

159
api/src/Db/Pdo.php Normal file
View File

@ -0,0 +1,159 @@
<?php
/**
* EGroupware API: PDO database connection
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage db
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2008-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @version $Id$
*/
namespace EGroupware\Api\Db;
/**
* PDO database connection
*/
class Pdo
{
/**
* Reference to the PDO object we use
*
* @var \PDO
*/
static protected $pdo;
/**
* PDO database type: mysql, pgsl
*
* @var string
*/
public static $pdo_type;
/**
* Case sensitive comparison operator, for mysql we use ' COLLATE utf8_bin ='
*
* @var string
*/
public static $case_sensitive_equal = '=';
/**
* Get active PDO connection
*
* @return \PDO
* @throws \PDOException when opening PDO connection fails
* @throws Exception when opening regular db-connection fails
*/
static public function connection()
{
if (!isset(self::$pdo))
{
self::reconnect();
}
return self::$pdo;
}
/**
* Reconnect to database
*/
static public function reconnect()
{
self::$pdo = self::_pdo();
}
/**
* Create pdo object / connection, as long as pdo is not generally used in eGW
*
* @return \PDO
*/
static protected function _pdo()
{
$egw_db = isset($GLOBALS['egw_setup']) ? $GLOBALS['egw_setup']->db : $GLOBALS['egw']->db;
switch($egw_db->Type)
{
case 'mysqli':
case 'mysqlt':
case 'mysql':
self::$case_sensitive_equal = '= BINARY ';
self::$pdo_type = 'mysql';
break;
default:
self::$pdo_type = $egw_db->Type;
break;
}
// get host used be egw_db
$egw_db->connect();
$host = $egw_db->get_host();
$dsn = self::$pdo_type.':dbname='.$egw_db->Database.($host ? ';host='.$host.($egw_db->Port ? ';port='.$egw_db->Port : '') : '');
// check once if pdo extension and DB specific driver is loaded or can be loaded
static $pdo_available=null;
if (is_null($pdo_available))
{
foreach(array('pdo','pdo_'.self::$pdo_type) as $ext)
{
check_load_extension($ext,true); // true = throw Exception
}
$pdo_available = true;
}
// set client charset of the connection
switch(self::$pdo_type)
{
case 'mysql':
$dsn .= ';charset=utf8';
break;
case 'pgsql':
$query = "SET NAMES 'utf-8'";
break;
}
try {
self::$pdo = new \PDO($dsn,$egw_db->User,$egw_db->Password,array(
\PDO::ATTR_ERRMODE=>\PDO::ERRMODE_EXCEPTION,
));
}
catch(\PDOException $e)
{
unset($e);
// Exception reveals password, so we ignore the exception and connect again without pw, to get the right exception without pw
self::$pdo = new \PDO($dsn,$egw_db->User,'$egw_db->Password');
}
if ($query)
{
self::$pdo->exec($query);
}
return self::$pdo;
}
/**
* Just a little abstration 'til I know how to organise stuff like that with PDO
*
* @param mixed $time
* @return string Y-m-d H:i:s
*/
static public function _pdo_timestamp($time)
{
if (is_numeric($time))
{
$time = date('Y-m-d H:i:s',$time);
}
return $time;
}
/**
* Just a little abstration 'til I know how to organise stuff like that with PDO
*
* @param boolean $val
* @return string '1' or '0' for mysql, 'true' or 'false' for everyone else
*/
static public function _pdo_boolean($val)
{
if (self::$pdo_type == 'mysql')
{
return $val ? '1' : '0';
}
return $val ? 'true' : 'false';
}
}

View File

@ -283,7 +283,7 @@ class Mail
}
else
{
throw new Exception(__METHOD__." failed to load the Profile for ProfileID for $_profileID with error:".$e->getMessage().($e->details?', '.$e->details:''));
throw new Exception(__METHOD__." failed to load the Profile for ProfileID for $_profileID with error:".$e->getMessage().($e->details?', '.$e->details:''));
}
}
self::storeActiveProfileIDToPref(self::$instances[$_profileID]->icServer, $_profileID, $_validate );

View File

@ -34,7 +34,7 @@ use EGroupware\Api;
*
* @link http://www.php.net/manual/en/function.stream-wrapper-register.php
*/
class StreamWrapper implements Vfs\StreamWrapperIface
class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
{
/**
* Mime type of directories, the old vfs uses 'Directory', while eg. WebDAV uses 'httpd/unix-directory'
@ -141,12 +141,6 @@ class StreamWrapper implements Vfs\StreamWrapperIface
* @var array $path => info-array pairs
*/
static protected $stat_cache = array();
/**
* Reference to the PDO object we use
*
* @var \PDO
*/
static protected $pdo;
/**
* Array with filenames of dir opened with dir_opendir
*
@ -1624,116 +1618,6 @@ class StreamWrapper implements Vfs\StreamWrapperIface
return $stat;
}
public static $pdo_type;
/**
* Case sensitive comparison operator, for mysql we use ' COLLATE utf8_bin ='
*
* @var string
*/
public static $case_sensitive_equal = '=';
/**
* Reconnect to database
*/
static public function reconnect()
{
self::$pdo = self::_pdo();
}
/**
* Create pdo object / connection, as long as pdo is not generally used in eGW
*
* @return \PDO
*/
static protected function _pdo()
{
$egw_db = isset($GLOBALS['egw_setup']) ? $GLOBALS['egw_setup']->db : $GLOBALS['egw']->db;
switch($egw_db->Type)
{
case 'mysqli':
case 'mysqlt':
case 'mysql':
self::$case_sensitive_equal = '= BINARY ';
self::$pdo_type = 'mysql';
break;
default:
self::$pdo_type = $egw_db->Type;
break;
}
// get host used be egw_db
$egw_db->connect();
$host = $egw_db->get_host();
$dsn = self::$pdo_type.':dbname='.$egw_db->Database.($host ? ';host='.$host.($egw_db->Port ? ';port='.$egw_db->Port : '') : '');
// check once if pdo extension and DB specific driver is loaded or can be loaded
static $pdo_available=null;
if (is_null($pdo_available))
{
foreach(array('pdo','pdo_'.self::$pdo_type) as $ext)
{
check_load_extension($ext,true); // true = throw Exception
}
$pdo_available = true;
}
// set client charset of the connection
switch(self::$pdo_type)
{
case 'mysql':
$dsn .= ';charset=utf8';
break;
case 'pgsql':
$query = "SET NAMES 'utf-8'";
break;
}
try {
self::$pdo = new \PDO($dsn,$egw_db->User,$egw_db->Password,array(
\PDO::ATTR_ERRMODE=>\PDO::ERRMODE_EXCEPTION,
));
}
catch(\PDOException $e)
{
unset($e);
// Exception reveals password, so we ignore the exception and connect again without pw, to get the right exception without pw
self::$pdo = new \PDO($dsn,$egw_db->User,'$egw_db->Password');
}
if ($query)
{
self::$pdo->exec($query);
}
return self::$pdo;
}
/**
* Just a little abstration 'til I know how to organise stuff like that with PDO
*
* @param mixed $time
* @return string Y-m-d H:i:s
*/
static protected function _pdo_timestamp($time)
{
if (is_numeric($time))
{
$time = date('Y-m-d H:i:s',$time);
}
return $time;
}
/**
* Just a little abstration 'til I know how to organise stuff like that with PDO
*
* @param boolean $val
* @return string '1' or '0' for mysql, 'true' or 'false' for everyone else
*/
static protected function _pdo_boolean($val)
{
if (self::$pdo_type == 'mysql')
{
return $val ? '1' : '0';
}
return $val ? 'true' : 'false';
}
/**
* Maximum value for a single hash element (should be 10^N): 10, 100 (default), 1000, ...
*

View File

@ -1078,10 +1078,9 @@ class calendar_zpush implements activesync_plugin_write, activesync_plugin_meeti
$this->backend->note2messagenote($event['description'], $bodypreference, $message->asbody);
}
}
$message->md5body = md5($event['description']);
$message->organizername = $GLOBALS['egw']->accounts->id2name($event['owner'],'account_fullname');
$message->organizeremail = $GLOBALS['egw']->accounts->id2name($event['owner'],'account_email');
// at least iOS calendar crashes, if organizer has no email address (true = generate an email, if user has none)
$message->organizeremail = $GLOBALS['egw']->accounts->id2name($event['owner'], 'account_email', true);
$message->sensitivity = $event['public'] ? 0 : 2; // 0=normal, 1=personal, 2=private, 3=confidential
@ -1102,7 +1101,7 @@ class calendar_zpush implements activesync_plugin_write, activesync_plugin_meeti
if (is_numeric($uid))
{
$attendee->name = $GLOBALS['egw']->accounts->id2name($uid,'account_fullname');
$attendee->email = $GLOBALS['egw']->accounts->id2name($uid,'account_email');
$attendee->email = $GLOBALS['egw']->accounts->id2name($uid, 'account_email', true);
}
else
{
@ -1113,7 +1112,7 @@ class calendar_zpush implements activesync_plugin_write, activesync_plugin_meeti
if (!$info['email'] && $info['responsible'])
{
$info['email'] = $GLOBALS['egw']->accounts->id2name($info['responsible'],'account_email');
$info['email'] = $GLOBALS['egw']->accounts->id2name($info['responsible'], 'account_email', true);
}
$attendee->name = empty($info['cn']) ? $info['name'] : $info['cn'];
$attendee->email = $info['email'];

View File

@ -410,7 +410,12 @@ class infolog_zpush implements activesync_plugin_write
switch ($attr)
{
case 'info_des':
$infolog[$attr] = $this->backend->messagenote2note($message->body, $message->rtf, $message->airsyncbasebody);
// only change info_des, if one given, as iOS5 skips description in ChangeMessage
// --> we ignore empty / not set description, so description get no longer lost, but you cant empty it via eSync
if (($description = $this->backend->messagenote2note($message->body, $message->rtf, $message->asbody)))
{
$infolog[$attr] = $description;
}
break;
case 'info_cat':

View File

@ -172,6 +172,22 @@ class mail_zpush implements activesync_plugin_write, activesync_plugin_sendmail,
'default' => 'sendifnocalnotif',
'admin' => False,
);
/*
$settings['mail-useSignature'] = array(
'type' => 'select',
'label' => 'control if and which available signature is added to outgoing mails',
'name' => 'mail-useSignature',
'help' => 'control the use of signatures',
'values' => array(
'sendifnocalnotif'=>'only send if there is no notification in calendar',
'send'=>'yes, always add EGroupware signatures to outgoing mails',
'nosend'=>'no, never add EGroupware signatures to outgoing mails',
),
'xmlrpc' => True,
'default' => 'nosend',
'admin' => False,
);
*/
return $settings;
}
@ -369,8 +385,8 @@ class mail_zpush implements activesync_plugin_write, activesync_plugin_sendmail,
// Horde SMTP Class uses utf-8 by default. as we set charset always to utf-8
$mailObject->Sender = $activeMailProfile['ident_email'];
$mailObject->From = $activeMailProfile['ident_email'];
$mailObject->FromName = $mailObject->EncodeHeader(Mail::generateIdentityString($activeMailProfile,false));
$mailObject->AddCustomHeader('X-Mailer: mail-Activesync');
$mailObject->FromName = Mail::generateIdentityString($activeMailProfile,false);
$mailObject->addHeader('X-Mailer', 'mail-Activesync');
// prepare addressee list; moved the adding of addresses to the mailobject down
@ -665,7 +681,7 @@ class mail_zpush implements activesync_plugin_write, activesync_plugin_sendmail,
if ($this->debugLevel>0) ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' Key:'.$key.'->'.array2string($attachment));
$attachmentNames .= $attachment['name']."\n";
$attachmentData = $this->mail->getAttachment($uid, $attachment['partID'],0,false,false,$folder);
/*$x =*/ $mailObject->AddStringAttachment($attachmentData['attachment'], $mailObject->EncodeHeader($attachment['name']), $attachment['mimeType']);
/*$x =*/ $mailObject->AddStringAttachment($attachmentData['attachment'], $attachment['name'], $attachment['mimeType']);
ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' added part with number:'.$x);
}
}
@ -939,9 +955,12 @@ class mail_zpush implements activesync_plugin_write, activesync_plugin_sendmail,
ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__." bodypreference 4 requested");
$output->asbody->type = SYNC_BODYPREFERENCE_MIME;//4;
// use Api\Mailer::convert to convert charset of all text parts to utf-8, which is a z-push or AS requirement!
// ToDo: check if above is true for mime-message, otherwise with could use a stream without conversion
$Body = Api\Mailer::convert($this->mail->getMessageRawBody($id, '', $_folderName));
if ($this->debugLevel>2) ZLog::Write(LOGLEVEL_DEBUG, __METHOD__.__LINE__." Setting Mailobjectcontent to output:".$Body);
$output->asbody->data = $Body;
if ((string)$Body === '') $Body = ' ';
$output->asbody->data = StringStreamWrapper::Open($Body);
$output->asbody->estimatedDataSize = strlen($Body);
}
else if ($bpReturnType==2) //SYNC_BODYPREFERENCE_HTML
{
@ -975,7 +994,8 @@ class mail_zpush implements activesync_plugin_write, activesync_plugin_sendmail,
}
// output->nativebodytype is used as marker that the original message was of type ... but is now converted to, as type 2 is requested.
$output->nativebodytype = 2;
$output->asbody->data = $htmlbody;
$output->asbody->data = StringStreamWrapper::Open($htmlbody);
$output->asbody->estimatedDataSize = strlen($htmlbody);
}
else
{
@ -996,16 +1016,16 @@ class mail_zpush implements activesync_plugin_write, activesync_plugin_sendmail,
$plainBody = Utils::Utf8_truncate($plainBody, $truncsize);
$output->asbody->truncated = 1;
}
$output->asbody->data = $plainBody;
$output->asbody->data = StringStreamWrapper::Open((string)$plainBody !== '' ? $plainBody : ' ');
$output->asbody->estimatedDataSize = strlen($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->asbody->type != 3 && (!isset($output->asbody->data) || strlen($output->asbody->data) == 0))
if ($output->asbody->type != 3 && !isset($output->asbody->data))
{
$output->asbody->data = " ";
$output->asbody->data = StringStreamWrapper::Open(" ");
$output->asbody->estimatedDataSize = 1;
}
// determine estimated datasize for all the above cases ...
$output->asbody->estimatedDataSize = strlen($output->asbody->data);
}
// end AS12 Stuff
ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.__LINE__.' gather Header info:'.$headers['SUBJECT'].' from:'.$headers['DATE']);
@ -1186,29 +1206,11 @@ class mail_zpush implements activesync_plugin_write, activesync_plugin_sendmail,
*
* @param string $fid - id
* @param string $attname - should contain (folder)id
* @return true, prints the content of the attachment
* @return SyncItemOperationsAttachment-object
*/
function GetAttachmentData($fid,$attname) {
ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.": $fid (attname: '$attname')".function_backtrace());
//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::getInstance(false,self::$profileID,true,false,true);
$this->mail->reopen($folder);
$attachment = $this->mail->getAttachment($id,$part,0,false,true,$folder);
$SIOattachment = new SyncItemOperationsAttachment();
fseek($attachment['attachment'], 0, SEEK_SET); // z-push requires stream seeked to start
$SIOattachment->data = $attachment['attachment'];
//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.": $fid (attname: '$attname') Data:".$attachment['attachment']);
if (isset($attachment['type']) )
$SIOattachment->contenttype = $attachment['type'];
unset($attachment);
return $SIOattachment;
ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.": $fid (attname: '$attname')");
return $this->_GetAttachmentData($fid,$attname);
}
/**
@ -1222,6 +1224,21 @@ class mail_zpush implements activesync_plugin_write, activesync_plugin_sendmail,
* @return SyncItemOperationsAttachment-object
*/
function ItemOperationsGetAttachmentData($fid,$attname) {
ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.": $fid (attname: '$attname')");
return $this->_GetAttachmentData($fid,$attname);
}
/**
* _GetAttachmentData implements
* -ItemOperationsGetAttachmentData
* -GetAttachmentData
*
* @param string $fid - id
* @param string $attname - should contain (folder)id
* @return SyncItemOperationsAttachment-object
*/
private function _GetAttachmentData($fid,$attname)
{
ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.": $fid (attname: '$attname')".function_backtrace());
//error_log(__METHOD__.__LINE__." Fid: $fid (attname: '$attname')");
list($folderid, $id, $part) = explode(":", $attname);
@ -1232,15 +1249,16 @@ class mail_zpush implements activesync_plugin_write, activesync_plugin_sendmail,
$this->mail->reopen($folder);
$attachment = $this->mail->getAttachment($id,$part,0,false,true,$folder);
$SIOattachment = new SyncItemOperationsAttachment();
$SIOattachment = new SyncItemOperationsAttachment();
fseek($attachment['attachment'], 0, SEEK_SET); // z-push requires stream seeked to start
$SIOattachment->data = $attachment['attachment'];
if (isset($attachment['type']) )
$SIOattachment->contenttype = $attachment['type'];
$SIOattachment->data = $attachment['attachment'];
//ZLog::Write(LOGLEVEL_DEBUG,__METHOD__.": $fid (attname: '$attname') Data:".$attachment['attachment']);
if (isset($attachment['type']) )
$SIOattachment->contenttype = $attachment['type'];
unset($attachment);
return $SIOattachment;
return $SIOattachment;
}
/**
@ -1966,37 +1984,73 @@ class mail_zpush implements activesync_plugin_write, activesync_plugin_sendmail,
private $folderHashes;
/**
* Statemaschine instance used to store folders
*
* @var activesync_statemaschine
*/
private $fh_state_maschine;
/**
* state_type (and _key) used to store folder hashes
*/
const FOLDER_STATE_TYPE = 'folder_hashes';
/**
* Read hashfile from state dir
*/
private function readFolderHashes()
{
if ((file_exists($file = $this->hashFile()) || file_exists($file = $this->hashFile(true))) &&
($hashes = file_get_contents($file)))
if (!isset($this->fh_state_maschine))
{
$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);
}
$this->fh_state_maschine = new activesync_statemachine($this->backend);
}
else
{
$this->folderHashes = array();
try {
$this->folderHashes = $this->fh_state_maschine->getState(Request::GetDeviceID(),
self::FOLDER_STATE_TYPE, self::FOLDER_STATE_TYPE, 0);
}
catch (Exception $e) {
unset($e);
if ((file_exists($file = $this->hashFile()) || file_exists($file = $this->hashFile(true))) &&
($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);
}
// store folder-hashes to state
$this->storeFolderHashes();
}
else
{
$this->folderHashes = array();
}
}
}
/**
* Store hashfile in state dir
* Store hashfile via state-maschine
*
* 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))));
if (!isset($this->fh_state_maschine))
{
$this->fh_state_maschine = new activesync_statemachine($this->backend);
}
try {
$this->fh_state_maschine->setState($this->folderHashes, Request::GetDeviceID(),
self::FOLDER_STATE_TYPE, self::FOLDER_STATE_TYPE, 0);
}
catch (Exception $e) {
_egw_log_exception($e);
return false;
}
return true;
}
/**