Merge SyncML Extensions with 1.6

This commit is contained in:
Jörg Lehrke 2009-11-16 08:04:18 +00:00
parent 781048414b
commit 18ff8a6fa7
92 changed files with 12311 additions and 6648 deletions

View File

@ -5,6 +5,7 @@
* @link http://www.egroupware.org
* @author Cornelius Weiss <egw@von-und-zu-weiss.de>
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @author Joerg Lehrke <jlehrke@noc.de>
* @package addressbook
* @copyright (c) 2005-8 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2005/6 by Cornelius Weiss <egw@von-und-zu-weiss.de>
@ -246,6 +247,49 @@ class addressbook_bo extends addressbook_so
}
}
/**
* Get the availible addressbooks of the user
*
* @param int $required=EGW_ACL_READ required rights on the addressbook or multiple rights or'ed together,
* to return only addressbooks fullfilling all the given rights
* @param string $extra_label first label if given (already translated)
* @return array with owner => label pairs
*/
function get_addressbooks($required=EGW_ACL_READ,$extra_label=null)
{
//echo "uicontacts::get_addressbooks($required,$include_all) grants="; _debug_array($this->grants);
$addressbooks = array();
if ($extra_label) $addressbooks[''] = $extra_label;
$addressbooks[$this->user] = lang('Personal');
// add all group addressbooks the user has the necessary rights too
foreach($this->grants as $uid => $rights)
{
if (($rights & $required) == $required && $GLOBALS['egw']->accounts->get_type($uid) == 'g')
{
$addressbooks[$uid] = lang('Group %1',$GLOBALS['egw']->accounts->id2name($uid));
}
}
if (($this->grants[0] & $required) == $required && !$GLOBALS['egw_info']['user']['preferences']['addressbook']['hide_accounts'])
{
$addressbooks[0] = lang('Accounts');
}
// add all other user addressbooks the user has the necessary rights too
foreach($this->grants as $uid => $rights)
{
if ($uid != $this->user && ($rights & $required) == $required && $GLOBALS['egw']->accounts->get_type($uid) == 'u')
{
$addressbooks[$uid] = $GLOBALS['egw']->common->grab_owner_name($uid);
}
}
if ($this->private_addressbook)
{
$addressbooks[$this->user.'p'] = lang('Private');
}
//_debug_array($addressbooks);
return $addressbooks;
}
/**
* calculate the file_as string from the contact and the file_as type
*
@ -265,8 +309,8 @@ class addressbook_bo extends addressbook_so
$contact['n_fn'],$contact['org_name'],$contact['org_unit'],$contact['adr_one_locality']),$type);
// removing empty delimiters, caused by empty contact fields
$fileas = str_replace(array(', , : ',', : ',': , ',', , ',': : '),array(': ',': ',': ',', ',': '),$fileas);
while ($fileas{0} == ':' || $fileas{0} == ',') $fileas = substr($fileas,2);
$fileas = str_replace(array(', , : ',', : ',': , ',', , ',': : ',' ()'),array(': ',': ',': ',', ',': ',''),$fileas);
while ($fileas[0] == ':' || $fileas[0] == ',') $fileas = substr($fileas,2);
while (substr($fileas,-2) == ': ' || substr($fileas,-2) == ', ') $fileas = substr($fileas,0,-2);
//echo "<p align=right>bocontacts::fileas(,$type)='$fileas'</p>\n";
@ -327,6 +371,70 @@ class addressbook_bo extends addressbook_so
return $options;
}
/**
* Set n_fileas (and n_fn) in contacts of all users (called by Admin >> Addressbook >> Site configuration (Admin only)
*
* If $all all fileas fields will be set, if !$all only empty ones
*
* @param string $fileas_type '' or type of $this->fileas_types
* @param int $all=false update all contacts or only ones with empty values
* @param int &$errors=null on return number of errors
* @return int|boolean number of contacts updated, false for wrong fileas type
*/
function set_all_fileas($fileas_type,$all=false,&$errors=null,$ignore_acl=false)
{
if ($type != '' && !in_array($type,$this->fileas_types))
{
return false;
}
if ($ignore_acl)
{
unset($this->somain->grants); // to NOT limit search to contacts readable by current user
}
// to be able to work on huge contact repositories we read the contacts in chunks of 100
for($n = $updated = $errors = 0; ($contacts = parent::search($all ? array() : array(
'n_fileas IS NULL',
"n_fileas=''",
'n_fn IS NULL',
"n_fn=''",
),false,'','','',false,'OR',array($n*100,100))); ++$n)
{
foreach($contacts as $contact)
{
$old_fn = $contact['n_fn'];
$old_fileas = $contact['n_fileas'];
$contact['n_fn'] = $this->fullname($contact);
// only update fileas if type is given AND (all should be updated or n_fileas is empty)
if ($fileas_type && ($all || empty($contact['n_fileas'])))
{
$contact['n_fileas'] = $this->fileas($contact,$fileas_type);
}
if ($old_fileas != $contact['n_fileas'] || $old_fn != $contact['n_fn'])
{
//echo "<p>('$old_fileas' != '{$contact['n_fileas']}' || '$old_fn' != '{$contact['n_fn']}')=".array2string($old_fileas != $contact['n_fileas'] || $old_fn != $contact['n_fn'])."</p>\n";
// only specify/write updated fields plus "keys"
$contact = array_intersect_key($contact,array(
'id' => true,
'owner' => true,
'private' => true,
'account_id' => true,
'uid' => true,
)+($old_fileas != $contact['n_fileas'] ? array('n_fileas' => true) : array())+($old_fn != $contact['n_fn'] ? array('n_fn' => true) : array()));
if ($this->save($contact,$ignore_acl))
{
$updated++;
}
else
{
$errors++;
}
}
}
}
return $updated;
}
/**
* get full name from the name-parts
*
@ -335,8 +443,13 @@ class addressbook_bo extends addressbook_so
*/
function fullname($contact)
{
if (empty($contact['n_family']) && empty($contact['n_given'])) {
$cpart = array('org_name');
} else {
$cpart = array('n_prefix','n_given','n_middle','n_family','n_suffix');
}
$parts = array();
foreach(array('n_prefix','n_given','n_middle','n_family','n_suffix') as $n)
foreach($cpart as $n)
{
if ($contact[$n]) $parts[] = $contact[$n];
}
@ -498,14 +611,15 @@ class addressbook_bo extends addressbook_so
return false;
}
// convert categories
if (is_array($contact['cat_id'])) {
if (is_array($contact['cat_id']))
{
$contact['cat_id'] = implode(',',$contact['cat_id']);
}
// last modified
$contact['modifier'] = $this->user;
$contact['modified'] = $this->now_su;
// set full name and fileas from the content
if (isset($contact['n_family']) && isset($contact['n_given']))
if (!isset($contact['n_fn']))
{
$contact['n_fn'] = $this->fullname($contact);
if (isset($contact['org_name'])) $contact['n_fileas'] = $this->fileas($contact);
@ -610,7 +724,7 @@ class addressbook_bo extends addressbook_so
function read_org($org_id)
{
if (!$org_id) return false;
if (strpos($org_id,'*AND*')!== false) $org_id = str_replace('*AND*','&',$org_id);
$org = array();
foreach(explode('|||',$org_id) as $part)
{
@ -985,7 +1099,7 @@ class addressbook_bo extends addressbook_so
{
foreach($event['participants'] as $uid => $status)
{
if ($uid{0} != 'c' || ($status == 'R' && !$GLOBALS['egw_info']['user']['preferences']['calendar']['show_rejected']))
if ($uid[0] != 'c' || ($status == 'R' && !$GLOBALS['egw_info']['user']['preferences']['calendar']['show_rejected']))
{
continue;
}
@ -1326,7 +1440,7 @@ class addressbook_bo extends addressbook_so
{
$cat_name = substr($cat_name, 2);
}
$cat_id = $this->categories->add(array('name' => $cat_name,'descr' => $cat_name));
$cat_id = $this->categories->add(array('name' => $cat_name, 'descr' => $cat_name, 'access' => 'private'));
}
if ($cat_id)
@ -1357,9 +1471,9 @@ class addressbook_bo extends addressbook_so
$cat_list = array();
foreach($cat_id_list as $cat_id)
{
if ($cat_data = $this->categories->return_single($cat_id))
if ($cat_id && ($cat_name = $this->categories->id2name($cat_id)) && $cat_name != '--')
{
$cat_list[] = $cat_data[0]['name'];
$cat_list[] = $cat_name;
}
}
@ -1368,15 +1482,276 @@ class addressbook_bo extends addressbook_so
function fixup_contact(&$contact)
{
if (!isset($contact['n_fn']) || empty($contact['n_fn']))
if (empty($contact['n_fn']))
{
$contact['n_fn'] = $this->fullname($contact);
}
if (!isset($contact['n_fileas']) || empty($contact['n_fileas']))
if (empty($contact['n_fileas']))
{
$contact['n_fileas'] = $this->fileas($contact);
}
}
function all_empty(&$_contact, &$fields)
{
$retval = true;
foreach ($fields as $field) {
if (isset($_contact[$field]) && !empty($_contact[$field])) {
$retval = false;
break;
}
}
return $retval;
}
/**
* Try to find a matching db entry
*
* @param array $contact the contact data we try to find
* @param boolean $relax=false if asked to relax, we only match against some key fields
* @return the contact_id of the matching entry or false (if none matches)
*/
function find_contact($contact, $relax=false)
{
if ($contact['id'] && ($found = $this->read($contact['id'])))
{
// We only do a simple consistency check
if ((empty($found['n_family']) || $found['n_family'] == $contact['n_family'])
&& (empty($found['n_given']) || $found['n_given'] == $contact['n_given'])
&& (empty($found['org_name']) || $found['org_name'] == $contact['org_name']))
{
return $found['id'];
}
}
unset($contact['id']);
$columns_to_search = array('contact_id',
'n_family', 'n_given', 'n_middle', 'n_prefix', 'n_suffix',
'bday', 'org_name', 'org_unit', 'title', 'role',
'email', 'email_home');
$tolerance_fields = array('contact_id',
'n_middle', 'n_prefix', 'n_suffix',
'bday', 'org_unit', 'title', 'role',
'email', 'email_home');
$addr_one_fields = array('adr_one_street',
'adr_one_locality', 'adr_one_region',
'adr_one_postalcode', 'adr_one_countryname');
$addr_two_fields = array('adr_two_street',
'adr_two_locality', 'adr_two_region',
'adr_two_postalcode', 'adr_two_countryname');
$no_addr_one = array();
$no_addr_two = array();
if (!empty($contact['owner']))
{
$columns_to_search += array('owner');
}
$backend =& $this->get_backend();
// define filter for empty address one
foreach ($addr_one_fields as $field)
{
if (!($db_col = array_search($field, $backend->db_cols)))
{
$db_col = $field;
}
$no_addr_one[] = "(" . $db_col . " IS NULL OR " . $db_col . " = '')";
}
// define filter for empty address two
foreach ($addr_two_fields as $field)
{
if (!($db_col = array_search($field, $backend->db_cols)))
{
$db_col = $field;
}
$no_addr_two[] = "(" . $db_col . " IS NULL OR " . $db_col . " = '')";
}
$result = false;
$criteria = array();
$empty_columns = array();
foreach ($columns_to_search as $field)
{
if (!isset($contact[$field]) || empty($contact[$field])) {
// Not every device supports all fields
if (!in_array($field, $tolerance_fields))
{
if (!($db_col = array_search($field, $backend->db_cols)))
{
$db_col = $field;
}
$empty_columns[] = "(" . $db_col . " IS NULL OR " . $db_col . " = '')";
}
}
else
{
if (!$relax || !in_array($field, $tolerance_fields))
{
$criteria[$field] = $contact[$field];
}
}
}
$filter = $empty_columns;
if (!$relax)
{
// We use addresses only for strong matching
if ($this->all_empty($contact, $addr_one_fields))
{
$filter = $filter + $no_addr_one;
}
else
{
foreach ($addr_one_fields as $field)
{
if (!isset($contact[$field]) || empty($contact[$field]))
{
if (!($db_col = array_search($field, $backend->db_cols)))
{
$db_col = $field;
}
$filter[] = "(" . $db_col . " IS NULL OR " . $db_col . " = '')";
}
else
{
$criteria[$field] = $contact[$field];
}
}
}
if ($this->all_empty($contact, $addr_two_fields))
{
$filter = $filter + $no_addr_two;
}
else
{
foreach ($addr_two_fields as $field)
{
if (!isset($contact[$field]) || empty($contact[$field]))
{
if (!($db_col = array_search($field, $backend->db_cols)))
{
$db_col = $field;
}
$filter[] = "(" . $db_col . " IS NULL OR " . $db_col . " = '')";
}
else
{
$criteria[$field] = $contact[$field];
}
}
}
}
Horde::logMessage("Addressbook find step 1:\n" . print_r($criteria, true) .
"filter:\n" . print_r($filter, true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
// first try full match
if (($foundContacts = parent::search($criteria, true, '', '', '', False, 'AND', false, $filter)))
{
$result = $foundContacts[0]['id'];
}
// No need for more searches for relaxed matching
if (!$relax && !$result && !$this->all_empty($contact, $addr_one_fields)
&& $this->all_empty($contact, $addr_two_fields))
{
// try given address and ignore the second one in EGW
$filter = array_diff($filter, $no_addr_two);
Horde::logMessage("Addressbook find step 2:\n" . print_r($criteria, true) .
"filter:\n" . print_r($filter, true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
if (($foundContacts = parent::search($criteria, true, '', '', '', False, 'AND', false, $filter)))
{
$result = $foundContacts[0]['id'];
}
else
{
// try address as home address -- some devices don't qualify addresses
$filter = $empty_columns;
foreach ($addr_two_fields as $key => $field)
{
if (isset($criteria[$addr_one_fields[$key]]))
{
$criteria[$field] = $criteria[$addr_one_fields[$key]];
unset($criteria[$addr_one_fields[$key]]);
}
else
{
if (!($db_col = array_search($field,$backend->db_cols)))
{
$db_col = $field;
}
$filter[] = "(" . $db_col . " IS NULL OR " . $db_col . " = '')";
}
}
$filter = $filter + $no_addr_one;
Horde::logMessage("Addressbook find step 3:\n" . print_r($criteria, true) .
"filter:\n" . print_r($filter, true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
if (($foundContacts = parent::search($criteria, true, '', '', '', False, 'AND', false, $filter)))
{
$result = $foundContacts[0]['id'];
}
}
}
elseif (!$relax && !$result)
{ // No more searches for relaxed matching, try again after address swap
$filter = $empty_columns;
foreach ($addr_one_fields as $key => $field)
{
$_temp_set = false;
if (isset($criteria[$field]))
{
$_temp = $criteria[$field];
$_temp_set = true;
unset($criteria[$field]);
}
if (isset($criteria[$addr_two_fields[$key]]))
{
$criteria[$field] = $criteria[$addr_two_fields[$key]];
unset($criteria[$addr_two_fields[$key]]);
}
else
{
if (!($db_col = array_search($field,$backend->db_cols)))
{
$db_col = $field;
}
$filter[] = "(" . $db_col . " IS NULL OR " . $db_col . " = '')";
}
if ($_temp_set)
{
$criteria[$addr_two_fields[$key]] = $_temp;
}
else
{
if (!($db_col = array_search($addr_two_fields[$key],$backend->db_cols)))
{
$db_col = $field;
}
$filter[] = "(" . $db_col . " IS NULL OR " . $db_col . " = '')";
}
}
Horde::logMessage("Addressbook find step 4:\n" . print_r($criteria, true) .
"filter:\n" . print_r($filter, true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
if(($foundContacts = parent::search($criteria, true, '', '', '', False, 'AND', false, $filter)))
{
$result = $foundContacts[0]['id'];
}
}
return $result;
}
}

View File

@ -117,7 +117,7 @@ class addressbook_merge // extends bo_merge
switch($field['type'])
{
case 'select-account':
if ($value) $value = $GLOBALS['egw']->common->grab_owner_name($value);
if ($value) $value = common::grab_owner_name($value);
break;
case 'select':
@ -139,8 +139,8 @@ class addressbook_merge // extends bo_merge
{
$format = $field['len'] ? $field['len'] : ($field['type'] == 'date' ? 'Y-m-d' : 'Y-m-d H:i:s');
$date = array_combine(preg_split('/[\\/. :-]/',$format),preg_split('/[\\/. :-]/',$value));
$value = $GLOBALS['egw']->common->dateformatorder($date['Y'],$date['m'],$date['d'],true);
if (isset($date['H'])) $value .= ' '.$GLOBALS['egw']->common->formattime($date['H'],$date['i']);
$value = common::dateformatorder($date['Y'],$date['m'],$date['d'],true);
if (isset($date['H'])) $value .= ' '.common::formattime($date['H'],$date['i']);
}
break;
}

View File

@ -5,6 +5,7 @@
* @link http://www.egroupware.org
* @author Lars Kneschke <lkneschke@egroupware.org>
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @author Joerg Lehrke <jlehrke@noc.de>
* @package addressbook
* @subpackage export
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
@ -94,6 +95,10 @@ class addressbook_sif extends addressbook_bo
'Folder' => '',
);
// standard headers
const xml_decl = '<?xml version="1.0" encoding="UTF-8"?>';
const SIF_decl = '<SIFVersion>1.1</SIFVersion>';
function startElement($_parser, $_tag, $_attributes) {
}
@ -108,13 +113,12 @@ class addressbook_sif extends addressbook_bo
$this->sifData .= $_data;
}
function siftoegw($_sifdata) {
$sifData = base64_decode($_sifdata);
function siftoegw($sifData) {
#$tmpfname = tempnam('/tmp/sync/contents','sifc_');
#$handle = fopen($tmpfname, "w");
#fwrite($handle, $sifdata);
#fwrite($handle, $sifData);
#fclose($handle);
$this->xml_parser = xml_parser_create('UTF-8');
@ -131,6 +135,7 @@ class addressbook_sif extends addressbook_bo
}
foreach($this->contact as $key => $value) {
$value = preg_replace('/<\!\[CDATA\[(.+)\]\]>/Usim', '$1', $value);
$value = $GLOBALS['egw']->translation->convert($value, 'utf-8');
switch($key) {
case 'cat_id':
@ -164,25 +169,17 @@ class addressbook_sif extends addressbook_bo
* @param string $_sifdata
* @return boolean/int/string contact-id or false, if not found
*/
function search($_sifdata,$contentID=null)
function search($_sifdata, $contentID=null, $relax=false)
{
if(!$contact = $this->siftoegw($_sifdata))
{
return false;
}
if ($contentID) {
$contact['contact_id'] = $contentID;
}
// patch from Di Guest says: we need to ignore the n_fileas
unset($contact['n_fileas']);
// we probably need to ignore even more as we do in vcaladdressbook
$result = false;
if(($foundContacts = addressbook_bo::search($contact)))
{
error_log(print_r($foundContacts,true));
return $foundContacts[0]['id'];
if($contact = $this->siftoegw($_sifdata)) {
if ($contentID) {
$contact['contact_id'] = $contentID;
}
$result = $this->find_contact($contact, $relax);
}
return false;
return $result;
}
/**
@ -191,8 +188,9 @@ class addressbook_sif extends addressbook_bo
* @return int contact id
* @param string $_vcard the vcard
* @param int/string $_abID=null the internal addressbook id or !$_abID for a new enty
* @param boolean $merge=false merge data with existing entry
*/
function addSIF($_sifdata, $_abID=null)
function addSIF($_sifdata, $_abID=null, $merge=false)
{
#error_log('ABID: '.$_abID);
#error_log(base64_decode($_sifdata));
@ -209,24 +207,25 @@ class addressbook_sif extends addressbook_bo
}
/**
* return a vcard
* return a sifc
*
* @param int $_id the id of the contact
* @param int $_vcardProfile profile id for mapping from vcard values to egw addressbook
* @return string containing the vcard
*/
function getSIF($_id)
{
$sysCharSet = $GLOBALS['egw']->translation->charset();
$fields = array_unique(array_values($this->sifMapping));
sort($fields);
if(!($entry = $this->read($_id)))
{
if(!($entry = $this->read($_id))) {
return false;
}
$sifContact = '<contact>';
$sifContact = self::xml_decl . "\n<contact>" . self::SIF_decl;
#error_log(print_r($entry,true));
$sysCharSet = $GLOBALS['egw']->translation->charset();
// fillup some defaults such as n_fn and n_fileas is needed
$this->fixup_contact($entry);
@ -242,15 +241,6 @@ class addressbook_sif extends addressbook_bo
switch($sifField)
{
// TODO handle multiple categories
case 'Categories':
if(!empty($value)) {
$value = implode("; ", $this->get_categories($value));
$value = $GLOBALS['egw']->translation->convert($value, $sysCharSet, 'utf-8');
}
$sifContact .= "<$sifField>$value</$sifField>";
break;
case 'Sensitivity':
$value = 2 * $value; // eGW private is 0 (public) or 1 (private)
$sifContact .= "<$sifField>$value</$sifField>";
@ -261,13 +251,22 @@ class addressbook_sif extends addressbook_bo
#$sifContact .= "<$sifField>/</$sifField>";
break;
case 'Categories':
if(!empty($value)) {
$value = implode("; ", $this->get_categories($value));
$value = $GLOBALS['egw']->translation->convert($value, $sysCharSet, 'utf-8');
} else {
break;
}
default:
$sifContact .= "<$sifField>".trim($value)."</$sifField>";
$value = @htmlspecialchars(trim($value), ENT_QUOTES, 'utf-8');
$sifContact .= "<$sifField>$value</$sifField>";
break;
}
}
$sifContact .= "</contact>";
return base64_encode($sifContact);
return $sifContact;
}
}

View File

@ -872,7 +872,8 @@ class addressbook_so
/**
* Get the availible distribution lists for a user
*
* @param int $required=EGW_ACL_READ required rights on the list
* @param int $required=EGW_ACL_READ required rights on the list or multiple rights or'ed together,
* to return only lists fullfilling all the given rights
* @param string $extra_labels=null first labels if given (already translated)
* @return array with id => label pairs or false if backend does not support lists
*/
@ -883,7 +884,7 @@ class addressbook_so
$uids = array();
foreach($this->grants as $uid => $rights)
{
if ($rights & $required)
if (($rights & $required) == $required)
{
$uids[] = $uid;
}

View File

@ -30,6 +30,16 @@ class addressbook_sql extends so_sql
var $contact_repository = 'sql';
var $grants;
/**
* join to show only active account (and not already expired ones)
*/
const ACOUNT_ACTIVE_JOIN = ' LEFT JOIN egw_accounts ON egw_addressbook.account_id=egw_accounts.account_id';
/**
* filter to show only active account (and not already expired ones)
* UNIX_TIMESTAMP(NOW()) gets replaced with value of time() in the code!
*/
const ACOUNT_ACTIVE_FILTER = '(account_expires IS NULL OR account_expires = -1 OR account_expires > UNIX_TIMESTAMP(NOW()))';
/**
* internal name of the id, gets mapped to uid
*
@ -277,7 +287,7 @@ class addressbook_sql extends so_sql
{
foreach($criteria as $col => $val)
{
if ($col[0] == '#') // search for a value in a certain custom field
if ($col[0] === '#') // search for a value in a certain custom field
{
$valarray=array();
# val may be a list of values, constructed by multiple select fields, to be able to do the contains feature of adv-search
@ -298,12 +308,12 @@ class addressbook_sql extends so_sql
}
$search_customfields = true;
}
elseif($col == 'cat_id') // search in comma-sep. cat-column
elseif($col === 'cat_id') // search in comma-sep. cat-column
{
$criteria = array_merge($criteria,$this->_cat_search($val));
unset($criteria[$col]);
}
elseif($col == 'contact_value')
elseif($col === 'contact_value')
{
if ($order_by[0] == '#')
{
@ -390,12 +400,20 @@ class addressbook_sql extends so_sql
unset($criteria['owner']);
}
// postgres requires that expressions in order by appear in the columns of a distinct select
if ($this->db->Type != 'mysql' && preg_match("/(\w+<>'')/",$order_by,$matches))
if ($this->db->Type != 'mysql' && preg_match("/([a-zA-Z_.]+)<>''/",$order_by,$matches))
{
if (!is_array($extra_cols)) $extra_cols = $extra_cols ? explode(',',$extra_cols) : array();
$extra_cols[] = $matches[1];
$extra_cols[] = $matches[1]."<>''";
}
}
// add join to show only active accounts (only if accounts are shown and in sql and we not already join the accounts table, eg. used by admin)
if (!$owner && substr($this->account_repository,0,3) == 'sql' &&
strpos($join,$GLOBALS['egw']->accounts->backend->table) === false && !array_key_exists('account_id',$filter))
{
$join .= self::ACOUNT_ACTIVE_JOIN;
$filter[] = str_replace('UNIX_TIMESTAMP(NOW())',time(),self::ACOUNT_ACTIVE_FILTER);
}
$rows =& parent::search($criteria,$only_keys,$order_by,$extra_cols,$wildcard,$empty,$op,$start,$filter,$join,$need_full_no_count);
if ($start === false) $this->total = is_array($rows) ? count($rows) : 0; // so_sql sets total only for $start !== false!
@ -589,11 +607,23 @@ class addressbook_sql extends so_sql
*/
function read($keys,$extra_cols='',$join='')
{
if (isset($GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length'])) {
$minimum_uid_length = $GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length'];
} else {
$minimum_uid_length = 8;
}
if (!is_array($keys) && !is_numeric($keys))
{
$keys = array('contact_uid' => $keys);
}
return parent::read($keys,$extra_cols,$join);
$contact = parent::read($keys,$extra_cols,$join);
// enforce a minium uid strength
if (is_array($contact) && (!isset($contact['uid'])
|| strlen($contact['uid']) < $minimum_uid_length)) {
parent::update(array('uid' => common::generate_uid('addressbook',$contact['id'])));
}
return $contact;
}
/**
@ -605,6 +635,12 @@ class addressbook_sql extends so_sql
*/
function save($keys=null)
{
if (isset($GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length'])) {
$minimum_uid_length = $GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length'];
} else {
$minimum_uid_length = 8;
}
if (is_array($keys) && count($keys)) $this->data_merge($keys);
$new_entry = !$this->data['id'];
@ -631,8 +667,8 @@ class addressbook_sql extends so_sql
}
}
// enforce a minium uid strength
if (!$err && ($new_entry || isset($this->data['uid'])) && (strlen($this->data['uid']) < 20 || is_numeric($this->data['uid'])))
{
if (!$err && (!isset($this->data['uid'])
|| strlen($this->data['uid']) < $minimum_uid_length)) {
parent::update(array('uid' => common::generate_uid('addressbook',$this->data['id'])));
//echo "<p>set uid={$this->data['uid']}, etag={$this->data['etag']}</p>";
}

View File

@ -5,7 +5,7 @@
* @link www.egroupware.org
* @author Cornelius Weiss <egw@von-und-zu-weiss.de>
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2005-8 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2005-9 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2005/6 by Cornelius Weiss <egw@von-und-zu-weiss.de>
* @package addressbook
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
@ -71,7 +71,7 @@ class addressbook_ui extends addressbook_bo
$this->config =& $GLOBALS['egw_info']['server'];
// check if a contact specific export limit is set, if yes use it also for etemplate's csv export
if (!$this->config['contact_export_limit'])
if ($this->config['contact_export_limit'])
{
$this->config['export_limit'] = $this->config['contact_export_limit'];
}
@ -260,6 +260,9 @@ class addressbook_ui extends addressbook_bo
'vcard' => lang('Export as VCard'), // ToDo: move this to importexport framework
);
}
// if there is any export limit set, pass it on to the nextmatch, to be evaluated by the export
if (isset($this->config['contact_export_limit']) && (int)$this->config['contact_export_limit']) $content['nm']['export_limit']=$this->config['contact_export_limit'];
$sel_options['action'] += array(
'merge' => lang('Merge into first or account, deletes all other!'),
'cat_add' => lang('Add or delete Categoies'), // add a categirie to multible addresses
@ -318,6 +321,7 @@ class addressbook_ui extends addressbook_bo
if (!isset($sel_options['org_view'][(string) $content['nm']['org_view']]))
{
$org_name = array();
if (strpos($content['nm']['org_view'],'*AND*')!== false) $content['nm']['org_view'] = str_replace('*AND*','&',$content['nm']['org_view']);
foreach(explode('|||',$content['nm']['org_view']) as $part)
{
list(,$name) = explode(':',$part,2);
@ -397,6 +401,7 @@ class addressbook_ui extends addressbook_bo
if (count($checked) > 1) // use a nicely formatted org-name as title in infolog
{
$parts = array();
if (strpos($org,'*AND*')!== false) $org = str_replace('*AND*','&',$org);
foreach(explode('|||',$org) as $part)
{
list(,$part) = explode(':',$part,2);
@ -422,7 +427,7 @@ class addressbook_ui extends addressbook_bo
$query['filter2'] = (int)$list;
$this->action($email_type,array(),true,$success,$failed,$action_msg,$query,$msg);
$response =& new xajaxResponse();
$response = new xajaxResponse();
if ($success) $response->addScript($GLOBALS['egw']->js->body['onLoad']);
@ -884,6 +889,7 @@ class addressbook_ui extends addressbook_bo
}
if ($query['org_view']) // view the contacts of one organisation only
{
if (strpos($query['org_view'],'*AND*')!== false) $query['org_view'] = str_replace('*AND*','&',$query['org_view']);
foreach(explode('|||',$query['org_view']) as $part)
{
list($name,$value) = explode(':',$part,2);
@ -1072,11 +1078,8 @@ class addressbook_ui extends addressbook_bo
if (($row['addr_format'] = $this->addr_format_by_country($row['adr_one_countryname']))=='postcode_city') unset($row['adr_one_region']);
if (($row['addr_format2'] = $this->addr_format_by_country($row['adr_two_countryname']))=='postcode_city') unset($row['adr_two_region']);
}
if ($show_distributionlist) {
$readonlys['no_distrib_lists'] =true;
} else {
$readonlys['no_distrib_lists'] =false;
}
$readonlys['no_distrib_lists'] = (bool)$show_distributionlist;
if (!$this->prefs['no_auto_hide'])
{
// disable photo column, if view contains no photo(s)
@ -1089,6 +1092,9 @@ class addressbook_ui extends addressbook_bo
// disable customfields column, if we have no customefield(s)
if (!$this->customfields/* || !$this->prefs['no_auto_hide'] && !$customfields*/) $rows['no_customfields'] = true;
// disable filemanger icon if user has no access to filemanager
$readonlys['filemanager/navbar'] = !isset($GLOBALS['egw_info']['user']['apps']['filemanager']);
$rows['order'] = $order;
$rows['call_popup'] = $this->config['call_popup'];
$rows['customfields'] = array_values($this->customfields);
@ -1109,7 +1115,7 @@ class addressbook_ui extends addressbook_bo
}
if($query['advanced_search'])
{
$GLOBALS['egw_info']['flags']['app_header'] .= ': '.lang('Advanced search');
$GLOBALS['egw_info']['flags']['app_header'] .= ': '.lang('Advanced search');
}
if ($query['cat_id'])
{
@ -1167,7 +1173,7 @@ class addressbook_ui extends addressbook_bo
}
/**
* Get the availible addressbooks of the user
* Get the available addressbooks of the user
*
* @param int $required=EGW_ACL_READ required rights on the addressbook
* @param string $extra_label first label if given (already translated)
@ -1244,6 +1250,12 @@ class addressbook_ui extends addressbook_bo
{
$old_org_entry = $this->read($content['id']);
}
if (isset($contact['n_family']) && isset($contact['n_given'])
&& $contact['n_family'] != $old_org_entry['n_family']
&& $contact['n_given'] != $old_org_entry['n_given'])
{
unset($content['n_fn']);
}
if ($this->save($content))
{
$content['msg'] = lang('Contact saved');
@ -1549,7 +1561,7 @@ class addressbook_ui extends addressbook_bo
'n_suffix' => $n_suffix,
'org_name' => $org_name,
);
$response =& new xajaxResponse();
$response = new xajaxResponse();
$response->addScript("setOptions('".addslashes(implode("\b",$this->fileas_options($names)))."');");
return $response->getXML();
@ -2025,7 +2037,7 @@ $readonlys['button[vcard]'] = true;
return lang("Document '%1' does not exist or is not readable for you!",$document);
}
require_once(EGW_INCLUDE_ROOT.'/addressbook/inc/class.addressbook_merge.inc.php');
$document_merge =& new addressbook_merge();
$document_merge = new addressbook_merge();
return $document_merge->download($document,$ids);
}

File diff suppressed because it is too large Load Diff

View File

@ -487,7 +487,7 @@ abstract class admin_cmd
* @param mixed $value
* @return mixed
*/
function __set($property,$value)
protected function __set($property,$value)
{
$this->data[$property] = $value;
}
@ -497,7 +497,7 @@ abstract class admin_cmd
*
* @param string $property
*/
function __unset($property)
protected function __unset($property)
{
unset($this->data[$property]);
}
@ -795,7 +795,7 @@ abstract class admin_cmd
return admin_cmd::run_queued_jobs();
}
include_once(EGW_API_INC.'/class.asyncservice.inc.php');
$async = new asyncservice();
$async =& new asyncservice();
// we cant use this class as callback, as it's abstract and ExecMethod used by the async service instanciated the class!
list($app) = explode('_',$class=$next['type']);

View File

@ -183,6 +183,8 @@
$this->template->set_var('sort_name',$this->nextmatchs->show_sort_order($this->sort,'cat_name',$this->order,'/index.php',lang('Name'),$link_data));
$this->template->set_var('sort_description',$this->nextmatchs->show_sort_order($this->sort,'cat_description',$this->order,'/index.php',lang('Description'),$link_data));
$accountId = $GLOBALS['egw_info']['user']['account_id'];
foreach($cats as $cat)
{
$data = unserialize($cat['data']);
@ -192,8 +194,6 @@
$this->template->set_var('td_color',$data['color']);
$gray = (hexdec(substr($data['color'],1,2))+hexdec(substr($data['color'],3,2))+hexdec(substr($data['color'],5,2)))/3;
}
else
{
$this->template->set_var('td_color','');
@ -206,7 +206,7 @@
$this->nextmatchs->template_alternate_row_color($this->template);
$gray = 255;
// }
$this->template->set_var('color',$gray < 128 ? 'style="color: white;"' : '');
$id = $cat['id'];
@ -233,6 +233,10 @@
{
$appendix = '&lt;' . lang('Global') . '&gt;';
}
elseif ($cat['owner'] != $accountId)
{
$appendix = '&lt;' . $GLOBALS['egw']->accounts->id2name($cat['owner'], 'account_fullname') . '&gt;';
}
else
{
$appendix = '';
@ -291,7 +295,7 @@
$data = unserialize($cat['data']);
$icon = $data['icon'];
$dir_img = $GLOBALS['egw_info']['server']['webserver_url'] . SEP . 'phpgwapi' . SEP . 'images' . SEP;
if (strlen($icon) > 0)
$this->template->set_var('icon', "<img src='". $dir_img . $icon ."'>");
else
@ -318,7 +322,7 @@
$cat_description = $_POST['cat_description'];
$cat_data = $_POST['cat_data'];
$old_parent = (int)$_POST['old_parent'];
if ($new_parent)
{
$cat_parent = $new_parent;
@ -396,7 +400,7 @@
'<input type="hidden" name="old_parent" value="' . $cat['parent'] . '">' . "\n";
$link_data['menuaction'] = 'admin.uicategories.edit';
$link_data['cat_id'] = $this->cat_id;
$link_data['cat_id'] = $this->cat_id;
$this->template->set_var('action_url',$GLOBALS['egw']->link('/index.php',$link_data));
if ($this->acl_delete)
@ -518,7 +522,7 @@
$cats = $this->bo->cats->return_single($this->cat_id);
$this->template->set_var('cat_name',$cat['name']);
if ($apps_cats)
{
$this->template->set_block('category_delete','delete','deletehandle');

View File

@ -35,7 +35,7 @@ class bocalendar
function __construct()
{
$this->cal =& new calendar_boupdate();
$this->cal = new calendar_boupdate();
if (is_object($GLOBALS['server']) && $GLOBALS['server']->simpledate)
{

View File

@ -301,7 +301,7 @@
// reading the holidayfile from egroupware.org via network::gethttpsocketfile contains all the headers!
foreach($lines as $n => $line)
{
$fields = split("[\t\n ]+",$line);
$fields = preg_split("/[\t\n ]+/",$line);
if ($fields[0] == 'charset' && $fields[1])
{

View File

@ -52,7 +52,7 @@ class calendar_ajax {
$conflicts=$this->calendar->update($event);
$response =& new xajaxResponse();
$response = new xajaxResponse();
if(!is_array($conflicts))
{
$response->addRedirect('');

View File

@ -5,6 +5,7 @@
* @link http://www.egroupware.org
* @package calendar
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @author Joerg Lehrke <jlehrke@noc.de>
* @copyright (c) 2004-8 by RalfBecker-At-outdoor-training.de
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$
@ -21,9 +22,10 @@ define('WEEK_s',7*DAY_s);
/**
* Gives read access to the calendar, but all events the user is not participating are private!
* Used be the addressbook.
* Used by addressbook.
*/
define('EGW_ACL_READ_FOR_PARTICIPANTS',EGW_ACL_CUSTOM_1);
define('EGW_ACL_FREEBUSY',EGW_ACL_CUSTOM_2);
/**
* Required (!) include, as we use the MCAL_* constants, BEFORE instanciating (and therefore autoloading) the class
@ -61,7 +63,7 @@ class calendar_bo
var $debug=false;
/**
* @var int $tz_offset_s offset in secconds between user and server-time,
* @var int $tz_offset_s offset in seconds between user and server-time,
* it need to be add to a server-time to get the user-time or substracted from a user-time to get the server-time
*/
var $tz_offset_s;
@ -124,6 +126,17 @@ class calendar_bo
MCAL_M_SATURDAY => 'Saturday',
MCAL_M_SUNDAY => 'Sunday',
);
/**
* Standard iCal attendee roles
*
* @var array
*/
var $roles = array(
'REQ-PARTICIPANT' => 'Requested',
'CHAIR' => 'Chair',
'OPT-PARTICIPANT' => 'Optional',
'NON-PARTICIPANT' => 'None',
);
/**
* @var array $resources registered scheduling resources of the calendar (gets chached in the session for performance reasons)
*/
@ -133,6 +146,7 @@ class calendar_bo
*/
protected static $cached_event = array();
protected static $cached_event_date_format = false;
protected static $cached_event_date = 0;
/**
* @var array $cached_holidays holidays plus birthdays (gets cached in the session for performance reasons)
*/
@ -140,7 +154,7 @@ class calendar_bo
/**
* Instance of the socal class
*
* @var socal
* @var calendar_so
*/
var $so;
/**
@ -200,7 +214,7 @@ class calendar_bo
/**
* returns info about email addresses as participants
*
* @param int/array $ids single contact-id or array of id's
* @param int|array $ids single contact-id or array of id's
* @return array
*/
static function email_info($ids)
@ -259,13 +273,13 @@ class calendar_bo
/**
* Searches / lists calendar entries, including repeating ones
*
* @param params array with the following keys
* @param array $params array with the following keys
* start date startdate of the search/list, defaults to today
* end date enddate of the search/list, defaults to start + one day
* users mixed integer user-id or array of user-id's to use, defaults to the current user
* cat_id mixed category-id or array of cat-id's, defaults to all if unset, 0 or False
* Please note: only a single cat-id, will include all sub-cats (if the common-pref 'cats_no_subs' is False)
* filter string filter-name, atm. 'all' or 'hideprivate'
* filter string all (not rejected), accepted, unknown, tentative, rejected or hideprivate
* query string pattern so search for, if unset or empty all matching entries are returned (no search)
* Please Note: a search never returns repeating events more then once AND does not honor start+end date !!!
* dayswise boolean on True it returns an array with YYYYMMDD strings as keys and an array with events
@ -277,16 +291,29 @@ class calendar_bo
* otherwise the original recuring event (with the first start- + enddate) is returned
* num_rows int number of entries to return, default or if 0, max_entries from the prefs
* order column-names plus optional DESC|ASC separted by comma
* show_rejected if set rejected invitation are shown only when true, otherwise it depends on the cal-pref or a running query
* ignore_acl if set and true no check_perms for a general EGW_ACL_READ grants is performed
* enum_groups boolean if set and true, group-members will be added as participants with status 'G'
* @return array of events or array with YYYYMMDD strings / array of events pairs (depending on $daywise param)
* or false if there are no read-grants from _any_ of the requested users
* cols string|array columns to select, if set an iterator will be returned
* append string to append to the query, eg. GROUP BY
* cfs array if set, query given custom fields or all for empty array, none are returned, if not set (default)
* @return iterator|array|boolean array of events or array with YYYYMMDD strings / array of events pairs (depending on $daywise param)
* or false if there are no read-grants from _any_ of the requested users or iterator/recordset if cols are given
*/
function &search($params)
{
$params_in = $params;
unset($params['sql_filter']); // dont allow to set it via UI or xmlrpc
// check if any resource wants to hook into
foreach($this->resources as $app => $data)
{
if (isset($data['search_filter']))
{
$params = ExecMethod($data['search_filter'],$params);
}
}
if (!isset($params['users']) || !$params['users'] ||
count($params['users']) == 1 && isset($params['users'][0]) && !$params['users'][0]) // null or '' casted to an array
{
@ -301,7 +328,7 @@ class calendar_bo
$users = array();
foreach($params['users'] as $user)
{
if ($params['ignore_acl'] || $this->check_perms(EGW_ACL_READ|EGW_ACL_READ_FOR_PARTICIPANTS,0,$user))
if ($params['ignore_acl'] || $this->check_perms(EGW_ACL_READ|EGW_ACL_READ_FOR_PARTICIPANTS|EGW_ACL_FREEBUSY,0,$user))
{
if ($user && !in_array($user,$users)) // already added?
{
@ -325,7 +352,7 @@ class calendar_bo
{
// use only members which gave the user a read-grant
if (!in_array($member['account_id'],$users) &&
($params['ignore_acl'] || $this->check_perms(EGW_ACL_READ,0,$member['account_id'])))
($params['ignore_acl'] || $this->check_perms(EGW_ACL_READ|EGW_ACL_FREEBUSY,0,$member['account_id'])))
{
$users[] = $member['account_id'];
}
@ -373,7 +400,12 @@ class calendar_bo
}
// date2ts(,true) converts to server time, db2data converts again to user-time
$events =& $this->so->search(isset($start) ? $this->date2ts($start,true) : null,isset($end) ? $this->date2ts($end,true) : null,
$users,$cat_id,$filter,$params['query'],$offset,(int)$params['num_rows'],$params['order'],$show_rejected);
$users,$cat_id,$filter,$params['query'],$offset,(int)$params['num_rows'],$params['order'],$show_rejected,$params['cols'],$params['append'],$params['cfs']);
if (isset($params['cols']))
{
return $events;
}
$this->total = $this->so->total;
$this->db2data($events,isset($params['date_format']) ? $params['date_format'] : 'ts');
@ -484,8 +516,8 @@ class calendar_bo
'title' => lang('private'),
'participants' => array_intersect_key($event['participants'],array_flip($allowed_participants)),
'public'=> 0,
'category' => $event['category'], // category is visible anyway, eg. by using planner by cat
'non_blocking' => $event['non_blocking'],
'category' => $event['category'], // category is visible anyway, eg. by using planner by cat
'non_blocking' => $event['non_blocking'],
);
}
@ -534,7 +566,7 @@ class calendar_bo
}
}
// update the horizont
$config =& CreateObject('phpgwapi.config','calendar');
$config = CreateObject('phpgwapi.config','calendar');
$config->save_value('horizont',$this->config['horizont'],'calendar');
if ($this->debug == 'check_move_horizont') $this->debug_message('bocal::check_move_horizont(%1) new horizont=%2, exiting',true,$new_horizont,$this->config['horizont']);
@ -562,10 +594,18 @@ class calendar_bo
$events = array();
$this->insert_all_repetitions($event,$start,$this->date2ts($this->config['horizont'],true),$events,null);
$days = $this->so->get_recurrence_exceptions($event); // content of array is in server-time!
$days = is_array($days) ? $days : array();
//error_log('set_recurrences: days=' . array2string($days) );
foreach($events as $event)
{
$this->so->recurrence($event['id'],$this->date2ts($event['start'],true),$this->date2ts($event['end'],true),$event['participants']);
$start_servertime = $this->date2ts($event['start'],true);
if (in_array($start_servertime, (array)$days))
{
// we don't change the stati of recurrence exceptions
$event['participants'] = array();
}
$this->so->recurrence($event['id'],$start_servertime,$this->date2ts($event['end'],true),$event['participants']);
}
}
@ -578,29 +618,29 @@ class calendar_bo
function db2data(&$events,$date_format='ts')
{
if (!is_array($events)) echo "<p>bocal::db2data(\$events,$date_format) \$events is no array<br />\n".function_backtrace()."</p>\n";
foreach($events as $id => $event)
foreach($events as &$event)
{
// we convert here from the server-time timestamps to user-time and (optional) to a different date-format!
foreach(array('start','end','modified','recur_enddate') as $ts)
foreach(array('start','end','modified','recur_enddate','reference') as $ts)
{
if (empty($event[$ts])) continue;
$events[$id][$ts] = $this->date2usertime($event[$ts],$date_format);
$event[$ts] = $this->date2usertime($event[$ts],$date_format);
}
// same with the recur exceptions
if (isset($event['recur_exception']) && is_array($event['recur_exception']))
{
foreach($event['recur_exception'] as $n => $date)
foreach($event['recur_exception'] as &$date)
{
$events[$id]['recur_exception'][$n] = $this->date2usertime($date,$date_format);
$date = $this->date2usertime($date,$date_format);
}
}
// same with the alarms
if (isset($event['alarm']) && is_array($event['alarm']))
{
foreach($event['alarm'] as $n => $alarm)
foreach($event['alarm'] as &$alarm)
{
$events[$id]['alarm'][$n]['time'] = $this->date2usertime($alarm['time'],$date_format);
$alarm['time'] = $this->date2usertime($alarm['time'],$date_format);
}
}
}
@ -650,7 +690,7 @@ class calendar_bo
{
if (is_array($ids) || !isset(self::$cached_event['id']) || self::$cached_event['id'] != $ids ||
self::$cached_event_date_format != $date_format ||
self::$cached_event['recur_type'] != MCAL_RECUR_NONE && !is_null($date) && (!$date || self::$cached_event['start'] < $date))
self::$cached_event['recur_type'] != MCAL_RECUR_NONE && !is_null($date) && self::$cached_event_date != $date || (!$date || self::$cached_event['start'] < $date))
{
$events = $this->so->read($ids,$date ? $this->date2ts($date,true) : 0);
@ -666,6 +706,7 @@ class calendar_bo
{
self::$cached_event = array_shift($events);
self::$cached_event_date_format = $date_format;
self::$cached_event_date = $date;
$return =& self::$cached_event;
}
}
@ -745,6 +786,8 @@ class calendar_bo
{
$search_date_ymd = (int)$this->date2string($ts);
//error_log('insert_all_repetitions search_date = ' . $search_date_ymd . ' => ' . print_r($recur_exceptions, true));
$have_exception = !is_null($recur_exceptions) && isset($recur_exceptions[$search_date_ymd]);
if (!$have_exception) // no execption by an edited event => check the deleted ones
@ -910,7 +953,7 @@ class calendar_bo
* We do some caching here, as the resource itself might not do it.
*
* @param string $uid string with one-letter resource-type and numerical resource-id, eg. "r19"
* @return array/boolean array with keys res_id,cat_id,name,useable (name definied by max_quantity in $this->resources),rights,responsible or false if $uid is not found
* @return array|boolean array with keys res_id,cat_id,name,useable (name definied by max_quantity in $this->resources),rights,responsible or false if $uid is not found
*/
function resource_info($uid)
{
@ -924,7 +967,7 @@ class calendar_bo
'res_id' => $uid,
'email' => $GLOBALS['egw']->accounts->id2name($uid,'account_email'),
'name' => trim($GLOBALS['egw']->accounts->id2name($uid,'account_firstname'). ' ' .
$GLOBALS['egw']->accounts->id2name($uid,'account_lastname')),
$GLOBALS['egw']->accounts->id2name($uid,'account_lastname')),
'type' => $GLOBALS['egw']->accounts->get_type($uid),
);
}
@ -970,7 +1013,6 @@ class calendar_bo
if ($other && !is_numeric($other))
{
$resource = $this->resource_info($other);
return $needed & $resource['rights'];
}
if (is_int($event) && $event == 0)
@ -981,7 +1023,7 @@ class calendar_bo
{
if (!is_array($event))
{
$event = $this->read($event,$date_to_read,True,$date_format); // = no ACL check !!!
$event = $this->read($event,$date_to_read,true,$date_format); // = no ACL check !!!
}
if (!is_array($event))
{
@ -994,9 +1036,7 @@ class calendar_bo
$owner = $event['owner'];
$private = !$event['public'];
}
$user = $GLOBALS['egw_info']['user']['account_id'];
$grants = $this->grants[$owner];
if (is_array($event) && $needed == EGW_ACL_READ)
{
// Check if the $user is one of the participants or has a read-grant from one of them
@ -1006,7 +1046,7 @@ class calendar_bo
{
foreach($event['participants'] as $uid => $accept)
{
if ($uid == $user || $uid < 0 && in_array($user,$GLOBALS['egw']->accounts->members($uid,true)))
if ($uid == $this->user || $uid < 0 && in_array($this->user,$GLOBALS['egw']->accounts->members($uid,true)))
{
// if we are a participant, we have an implicite READ and PRIVAT grant
$grants |= EGW_ACL_READ | EGW_ACL_PRIVATE;
@ -1031,14 +1071,13 @@ class calendar_bo
error_log(__METHOD__." no participants for event:".print_r($event,true));
}
}
if ($GLOBALS['egw']->accounts->get_type($owner) == 'g' && $needed == EGW_ACL_ADD)
{
$access = False; // a group can't be the owner of an event
$access = false; // a group can't be the owner of an event
}
else
{
$access = $user == $owner || $grants & $needed && (!$private || $grants & EGW_ACL_PRIVATE);
$access = ($this->user == $owner) || $grants & $needed && (!$private || $grants & EGW_ACL_PRIVATE);
}
if ($this->debug && ($this->debug > 2 || $this->debug == 'check_perms'))
{
@ -1063,54 +1102,54 @@ class calendar_bo
switch(gettype($date))
{
case 'string': // YYYYMMDD or iso8601 YYYY-MM-DDThh:mm:ss[Z|[+-]hh:mm] string
if (is_numeric($date) && $date > 21000000)
if (is_numeric($date) && $date > 21000000)
{
$date = (int) $date; // this is already as timestamp
break;
}
// evaluate evtl. added timezone
if (strlen($date) > 12)
{
if (substr($date,-1) == 'Z')
{
$date = (int) $date; // this is already as timestamp
break;
$time_offset = date('Z');
}
// evaluate evtl. added timezone
if (strlen($date) > 12)
elseif(preg_match('/([+-]{1})([0-9]{2}):?([0-9]{2})$/',$date,$matches))
{
if (substr($date,-1) == 'Z')
{
$time_offset = date('Z');
}
elseif(preg_match('/([+-]{1})([0-9]{2}):?([0-9]{2})$/',$date,$matches))
{
$time_offset = date('Z')-($matches[1] == '+' ? 1 : -1)*(3600*$matches[2]+60*$matches[3]);
}
$time_offset = date('Z')-($matches[1] == '+' ? 1 : -1)*(3600*$matches[2]+60*$matches[3]);
}
// removing all non-nummerical chars, gives YYYYMMDDhhmmss, independent of the iso8601 format
$date = str_replace(array('-',':','T','Z',' ','+'),'',$date);
$date = array(
'year' => (int) substr($date,0,4),
'month' => (int) substr($date,4,2),
'day' => (int) substr($date,6,2),
'hour' => (int) substr($date,8,2),
'minute' => (int) substr($date,10,2),
'second' => (int) substr($date,12,2),
);
// fall-through
}
// removing all non-nummerical chars, gives YYYYMMDDhhmmss, independent of the iso8601 format
$date = str_replace(array('-',':','T','Z',' ','+'),'',$date);
$date = array(
'year' => (int) substr($date,0,4),
'month' => (int) substr($date,4,2),
'day' => (int) substr($date,6,2),
'hour' => (int) substr($date,8,2),
'minute' => (int) substr($date,10,2),
'second' => (int) substr($date,12,2),
);
// fall-through
case 'array': // day, month and year keys
if (isset($date['raw']) && $date['raw']) // we already have a timestamp
{
$date = $date['raw'];
break;
}
if (!isset($date['year']) && isset($date['full']))
{
$date['year'] = (int) substr($date['full'],0,4);
$date['month'] = (int) substr($date['full'],4,2);
$date['day'] = (int) substr($date['full'],6,2);
}
$date = adodb_mktime((int)$date['hour'],(int)$date['minute'],(int)$date['second'],(int)$date['month'],
(int) (isset($date['day']) ? $date['day'] : $date['mday']),(int)$date['year']);
if (isset($date['raw']) && $date['raw']) // we already have a timestamp
{
$date = $date['raw'];
break;
}
if (!isset($date['year']) && isset($date['full']))
{
$date['year'] = (int) substr($date['full'],0,4);
$date['month'] = (int) substr($date['full'],4,2);
$date['day'] = (int) substr($date['full'],6,2);
}
$date = adodb_mktime((int)$date['hour'],(int)$date['minute'],(int)$date['second'],(int)$date['month'],
(int) (isset($date['day']) ? $date['day'] : $date['mday']),(int)$date['year']);
break;
case 'integer': // already a timestamp
break;
break;
default: // eg. boolean, means now in user-time (!)
$date = $this->now_su;
break;
$date = $this->now_su;
break;
}
if ($time_offset)
{
@ -1261,6 +1300,7 @@ class calendar_bo
EGW_ACL_EDIT => 'ACL_EDIT',
EGW_ACL_DELETE => 'ACL_DELETE',
EGW_ACL_PRIVATE => 'ACL_PRIVATE',
EGW_ACL_FREEBUSY => 'ACL_FREEBUSY',
);
for($i = 2; $i < func_num_args(); ++$i)
{
@ -1430,7 +1470,7 @@ class calendar_bo
/**
* Converts a participant into a (readable) user- or resource-name
*
* @param $id string/int id of user or resource
* @param $id string|int id of user or resource
* @return string with name
*/
function participant_name($id,$use_type=false)
@ -1471,11 +1511,13 @@ class calendar_bo
$names = array();
foreach($event['participants'] as $id => $status)
{
calendar_so::split_status($status,$quantity,$role);
if ($status == 'G' && !$show_group_invitation) continue; // dont show group-invitation
if (!$long_status)
{
switch($status)
switch($status[0])
{
case 'A': // accepted
$status = html::image('calendar','agt_action_success',$this->verbose_status[$status]);
@ -1499,7 +1541,26 @@ class calendar_bo
{
$status = '('.$this->verbose_status[$status].')';
}
$names[$id] = $this->participant_name($id).' '.$status;
$names[$id] = $this->participant_name($id).($quantity > 1 ? ' ('.$quantity.')' : '').' '.$status;
// add role, if not a regular participant
if ($role != 'REQ-PARTICIPANT')
{
if (isset($this->roles[$role]))
{
$role = lang($this->roles[$role]);
}
// allow to use cats as roles (beside regular iCal ones)
elseif (substr($role,0,6) == 'X-CAT-' && ($cat_id = (int)substr($role,6)) > 0)
{
$role = $GLOBALS['egw']->categories->id2name($cat_id);
}
else
{
$role = lang(str_replace('X-','',$role));
}
$names[$id] .= ' '.$role;
}
}
return $names;
}
@ -1518,7 +1579,7 @@ class calendar_bo
$color = 0;
if (!is_object($this->cats))
{
$this->cats =& CreateObject('phpgwapi.categories','','calendar');
$this->cats = CreateObject('phpgwapi.categories','','calendar');
}
foreach(explode(',',$category) as $cat_id)
{
@ -1540,7 +1601,9 @@ class calendar_bo
return $cats;
}
/* This is called only by list_cals(). It was moved here to remove fatal error in php5 beta4 */
/**
* This is called only by list_cals(). It was moved here to remove fatal error in php5 beta4
*/
function _list_cals_add($id,&$users,&$groups)
{
$name = $GLOBALS['egw']->common->grab_owner_name($id);
@ -1560,9 +1623,10 @@ class calendar_bo
}
/**
* generate list of user- / group-calendars for the selectbox in the header
* @return alphabeticaly sorted array with groups first and then users
*/
* generate list of user- / group-calendars for the selectbox in the header
*
* @return array alphabeticaly sorted array with groups first and then users: $name => array('grantor'=>$id,'value'=>['g_'.]$id,'name'=>$name)
*/
function list_cals()
{
$users = $groups = array();
@ -1667,7 +1731,7 @@ class calendar_bo
{
if (!is_object($this->holidays))
{
$this->holidays =& CreateObject('calendar.boholiday');
$this->holidays = CreateObject('calendar.boholiday');
}
$this->holidays->prepare_read_holidays($year);
$this->cached_holidays[$year] = $this->holidays->read_holiday();
@ -1675,7 +1739,7 @@ class calendar_bo
// search for birthdays
if ($GLOBALS['egw_info']['server']['hide_birthdays'] != 'yes')
{
$contacts =& CreateObject('phpgwapi.contacts');
$contacts = CreateObject('phpgwapi.contacts');
$bdays =& $contacts->read(0,0,array('id','n_family','n_given','n_prefix','n_middle','bday'),'',"bday=!'',n_family=!''",'ASC','bday');
if ($bdays)
{
@ -1831,4 +1895,20 @@ class calendar_bo
$GLOBALS['egw_info']['server']['webserver_url'].'/calendar/freebusy.php?user='.urlencode($user).
($pw ? '&password='.urlencode($pw) : '');
}
/**
* Check if the event is the whole day
*
* @param event
* @return boolean true for whole day events
*/
function isWholeDay($event)
{
// check if the event is the whole day
$start = $this->date2array($event['start']);
$end = $this->date2array($event['end']);
$result = (!$start['hour'] && !$start['minute']
&& $end['hour'] == 23 && $end['minute'] == 59);
return $result;
}
}

View File

@ -5,12 +5,13 @@
* @link http://www.egroupware.org
* @package calendar
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2005-8 by RalfBecker-At-outdoor-training.de
* @author Joerg Lehrke <jlehrke@noc.de>
* @copyright (c) 2005-9 by RalfBecker-At-outdoor-training.de
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$
*/
// types of messsages send by bocalupdate::send_update
// types of messsages send by calendar_boupdate::send_update
define('MSG_DELETED',0);
define('MSG_MODIFIED',1);
define('MSG_ADDED',2);
@ -51,7 +52,7 @@ class calendar_boupdate extends calendar_bo
var $debug;
/**
* @var string/boolean $log_file filename to enable the login or false for no update-logging
* @var string|boolean $log_file filename to enable the login or false for no update-logging
*/
var $log_file = false;
@ -60,11 +61,11 @@ class calendar_boupdate extends calendar_bo
*/
function __construct()
{
if ($this->debug > 0) $this->debug_message('bocalupdate::bocalupdate() started',True);
if ($this->debug > 0) $this->debug_message('calendar_boupdate::__construct() started',True);
parent::__construct(); // calling the parent constructor
if ($this->debug > 0) $this->debug_message('bocalupdate::bocalupdate() finished',True);
if ($this->debug > 0) $this->debug_message('calendar_boupdate::__construct() finished',True);
}
/**
@ -73,16 +74,17 @@ class calendar_boupdate extends calendar_bo
* @param array &$event event-array, on return some values might be changed due to set defaults
* @param boolean $ignore_conflicts=false just ignore conflicts or do a conflict check and return the conflicting events
* @param boolean $touch_modified=true touch modificatin time and set modifing user, default true=yes
* @param boolean $ignore_acl=flase should we ignore the acl
* @param boolean $ignore_acl=false should we ignore the acl
* @param boolean $updateTS=true update the content history of the event
* @return mixed on success: int $cal_id > 0, on error false or array with conflicting events (only if $check_conflicts)
* Please note: the events are not garantied to be readable by the user (no read grant or private)!
*/
function update(&$event,$ignore_conflicts=false,$touch_modified=true,$ignore_acl=false)
function update(&$event,$ignore_conflicts=false,$touch_modified=true,$ignore_acl=false,$updateTS=true)
{
//error_log(__METHOD__."(".array2string($event).",$ignore_conflicts,$touch_modified,$ignore_acl)");
if ($this->debug > 1 || $this->debug == 'update')
{
$this->debug_message('bocalupdate::update(%1,ignore_conflict=%2,touch_modified=%3,ignore_acl=%4)',
$this->debug_message('calendar_boupdate::update(%1,ignore_conflict=%2,touch_modified=%3,ignore_acl=%4)',
false,$event,$ignore_conflicts,$touch_modified,$ignore_acl);
}
// check some minimum requirements:
@ -100,14 +102,14 @@ class calendar_boupdate extends calendar_bo
// if no owner given, set user to owner
if (!$event['owner']) $event['owner'] = $this->user;
// set owner as participant if none is given
if (!$event['id'] && (!is_array($event['participants']) || !count($event['participants'])))
if (!is_array($event['participants']) || !count($event['participants']))
{
$event['participants'][$event['owner']] = 'U';
$event['participants'] = array($event['owner'] => 'U');
}
// set the status of the current user to 'A' = accepted
if (isset($event['participants'][$this->user]) && $event['participants'][$this->user] != 'A')
if (isset($event['participants'][$this->user]) && $event['participants'][$this->user][0] != 'A')
{
$event['participants'][$this->user] = 'A';
$event['participants'][$this->user][0] = 'A';
}
}
// check if user has the permission to update / create the event
@ -130,6 +132,7 @@ class calendar_boupdate extends calendar_bo
$quantity = $users = array();
foreach($event['participants'] as $uid => $status)
{
calendar_so::split_status($status,$q,$r);
if ($status[0] == 'R') continue; // ignore rejected participants
if ($uid < 0) // group, check it's members too
@ -140,7 +143,7 @@ class calendar_boupdate extends calendar_bo
$users[] = $uid;
if (in_array($uid[0],$types_with_quantity))
{
$quantity[$uid] = max(1,(int) substr($status,2));
$quantity[$uid] = $q;
}
}
$overlapping_events =& $this->search(array(
@ -152,7 +155,7 @@ class calendar_boupdate extends calendar_bo
));
if ($this->debug > 2 || $this->debug == 'update')
{
$this->debug_message('bocalupdate::update() checking for potential overlapping events for users %1 from %2 to %3',false,$users,$event['start'],$event['end']);
$this->debug_message('calendar_boupdate::update() checking for potential overlapping events for users %1 from %2 to %3',false,$users,$event['start'],$event['end']);
}
$max_quantity = $possible_quantity_conflicts = $conflicts = array();
foreach((array) $overlapping_events as $k => $overlap)
@ -165,7 +168,7 @@ class calendar_boupdate extends calendar_bo
}
if ($this->debug > 3 || $this->debug == 'update')
{
$this->debug_message('bocalupdate::update() checking overlapping event %1',false,$overlap);
$this->debug_message('calendar_boupdate::update() checking overlapping event %1',false,$overlap);
}
// check if the overlap is with a rejected participant or within the allowed quantity
$common_parts = array_intersect($users,array_keys($overlap['participants']));
@ -198,7 +201,7 @@ class calendar_boupdate extends calendar_bo
{
if ($this->debug > 3 || $this->debug == 'update')
{
$this->debug_message('bocalupdate::update() conflicts with the following participants found %1',false,$common_parts);
$this->debug_message('calendar_boupdate::update() conflicts with the following participants found %1',false,$common_parts);
}
$conflicts[$overlap['id'].'-'.$this->date2ts($overlap['start'])] =& $overlapping_events[$k];
}
@ -221,12 +224,13 @@ class calendar_boupdate extends calendar_bo
{
foreach($conflicts as $key => $conflict)
{
$conflict['participants'] = array_intersect_key($conflict['participants'],$event['participants']);
if (!$this->check_perms(EGW_ACL_READ,$conflict))
{
$conflicts[$key] = array(
'id' => $conflict['id'],
'title' => lang('busy'),
'participants' => array_intersect_key($conflict['participants'],$event['participants']),
'participants' => $conflict['participants'],
'start' => $conflict['start'],
'end' => $conflict['end'],
);
@ -234,7 +238,7 @@ class calendar_boupdate extends calendar_bo
}
if ($this->debug > 2 || $this->debug == 'update')
{
$this->debug_message('bocalupdate::update() %1 conflicts found %2',false,count($conflicts),$conflicts);
$this->debug_message('calendar_boupdate::update() %1 conflicts found %2',false,count($conflicts),$conflicts);
}
return $conflicts;
}
@ -253,10 +257,11 @@ class calendar_boupdate extends calendar_bo
if (!isset($event['participants'])) $event['participants'] = $old_event['participants'];
//echo "old $event[id]="; _debug_array($old_event);
}
//echo "saving $event[id]="; _debug_array($event);
$event2save = $event;
if (!($cal_id = $this->save($event)))
if (!($cal_id = $this->save($event, $ignore_acl, $updateTS)))
{
return $cal_id;
}
@ -285,7 +290,7 @@ class calendar_boupdate extends calendar_bo
}
/**
* Check for added, modified or deleted participants
* Check for added, modified or deleted participants AND notify them
*
* @param array $new_event the updated event
* @param array $old_event the event before the update
@ -294,7 +299,7 @@ class calendar_boupdate extends calendar_bo
{
$modified = $added = $deleted = array();
//echo "<p>bocalupdate::check4update() new participants = ".print_r($new_event['participants'],true).", old participants =".print_r($old_event['participants'],true)."</p>\n";
//echo "<p>calendar_boupdate::check4update() new participants = ".print_r($new_event['participants'],true).", old participants =".print_r($old_event['participants'],true)."</p>\n";
// Find modified and deleted participants ...
foreach($old_event['participants'] as $old_userid => $old_status)
@ -316,7 +321,7 @@ class calendar_boupdate extends calendar_bo
$added[$new_userid] = 'U';
}
}
//echo "<p>bocalupdate::check4update() added=".print_r($added,true).", modified=".print_r($modified,true).", deleted=".print_r($deleted,true)."</p>\n";
//echo "<p>calendar_boupdate::check4update() added=".print_r($added,true).", modified=".print_r($modified,true).", deleted=".print_r($deleted,true)."</p>\n";
if(count($added) || count($modified) || count($deleted))
{
if(count($added))
@ -387,7 +392,7 @@ class calendar_boupdate extends calendar_bo
case 'no':
break;
}
//echo "<p>bocalupdate::update_requested(user=$userid,pref=".$part_prefs['calendar']['receive_updates'] .",msg_type=$msg_type,".($old_event?$old_event['title']:'False').",".($old_event?$old_event['title']:'False').") = $want_update</p>\n";
//echo "<p>calendar_boupdate::update_requested(user=$userid,pref=".$part_prefs['calendar']['receive_updates'] .",msg_type=$msg_type,".($old_event?$old_event['title']:'False').",".($old_event?$old_event['title']:'False').") = $want_update</p>\n";
return $want_update > 0;
}
@ -403,6 +408,7 @@ class calendar_boupdate extends calendar_bo
*/
function send_update($msg_type,$to_notify,$old_event,$new_event=null,$user=0)
{
//echo "<p>".__METHOD__."($msg_type,".array2string($to_notify).",,$new_event[title],$user)</p>\n";
if (!is_array($to_notify))
{
$to_notify = array();
@ -527,7 +533,7 @@ class calendar_boupdate extends calendar_bo
}
if($userid != $GLOBALS['egw_info']['user']['account_id'] || $msg_type == MSG_ALARM)
{
$preferences =& CreateObject('phpgwapi.preferences',$userid);
$preferences = CreateObject('phpgwapi.preferences',$userid);
$part_prefs = $preferences->read_repository();
if (!$this->update_requested($userid,$part_prefs,$msg_type,$old_event,$new_event))
@ -553,7 +559,18 @@ class calendar_boupdate extends calendar_bo
switch($part_prefs['calendar']['update_format'])
{
case 'extended':
case 'ical':
if ($method == 'REQUEST')
{
$ics = ExecMethod2('calendar.calendar_ical.exportVCal',$event['id'],'2.0',$method);
$attachment = array( 'string' => $ics,
'filename' => 'cal.ics',
'encoding' => '8bit',
'type' => 'text/calendar; method='.$method,
);
}
// fall through
case 'extended':
$body .= "\n\n".lang('Event Details follow').":\n";
foreach($event_arr as $key => $val)
{
@ -570,18 +587,6 @@ class calendar_boupdate extends calendar_bo
}
}
break;
case 'ical':
$ics = ExecMethod2('calendar.calendar_ical.exportVCal',$event['id'],'2.0',$method,false);
if ($method == 'REQUEST')
{
$attachment = array( 'string' => $ics,
'filename' => 'cal.ics',
'encoding' => '8bit',
'type' => 'text/calendar; method='.$method,
);
}
break;
}
// send via notification_app
if($GLOBALS['egw_info']['apps']['notifications']['enabled']) {
@ -661,20 +666,23 @@ class calendar_boupdate extends calendar_bo
}
/**
* saves an event to the database, does NOT do any notifications, see bocalupdate::update for that
* saves an event to the database, does NOT do any notifications, see calendar_boupdate::update for that
*
* This methode converts from user to server time and handles the insertion of users and dates of repeating events
*
* @param array $event
* @return int/boolean $cal_id > 0 or false on error (eg. permission denied)
* @param boolean $ignore_acl=false should we ignore the acl
* @param boolean $updateTS=true update the content history of the event
* @return int|boolean $cal_id > 0 or false on error (eg. permission denied)
*/
function save($event)
function save($event,$ignore_acl=false,$updateTS=true)
{
//error_log(__METHOD__."(".str_replace(array("\n",' '),'',print_r($event,true)).",$etag)");
//echo '<p>'.__METHOD__.'('.array2string($event).",$ignore_acl)</p>\n";
//error_log(__METHOD__.'('.array2string($event).",$etag)");
// check if user has the permission to update / create the event
if ($event['id'] && !$this->check_perms(EGW_ACL_EDIT,$event['id']) ||
if (!$ignore_acl && ($event['id'] && !$this->check_perms(EGW_ACL_EDIT,$event['id']) ||
!$event['id'] && !$this->check_perms(EGW_ACL_EDIT,0,$event['owner']) &&
!$this->check_perms(EGW_ACL_ADD,0,$event['owner']))
!$this->check_perms(EGW_ACL_ADD,0,$event['owner'])))
{
return false;
}
@ -684,7 +692,7 @@ class calendar_boupdate extends calendar_bo
$save_event = $event;
// we run all dates through date2ts, to adjust to server-time and the possible date-formats
foreach(array('start','end','modified','recur_enddate') as $ts)
foreach(array('start','end','modified','recur_enddate','reference') as $ts)
{
// we convert here from user-time to timestamps in server-time!
if (isset($event[$ts])) $event[$ts] = $event[$ts] ? $this->date2ts($event[$ts],true) : 0;
@ -705,12 +713,16 @@ class calendar_boupdate extends calendar_bo
$event['alarm'][$id]['time'] = $this->date2ts($alarm['time'],true);
}
}
if (($cal_id = $this->so->save($event,$set_recurrences,NULL,$event['etag'])) && $set_recurrences && $event['recur_type'] != MCAL_RECUR_NONE)
$set_recurrences = false;
$set_recurrences_start = 0;
if (($cal_id = $this->so->save($event,$set_recurrences,$set_recurrences_start,0,$event['etag'])) && $set_recurrences && $event['recur_type'] != MCAL_RECUR_NONE)
{
$save_event['id'] = $cal_id;
$this->set_recurrences($save_event);
// unset participants to enforce the default stati for all added recurrences
unset($save_event['participants']);
$this->set_recurrences($save_event, $set_recurrences_start);
}
$GLOBALS['egw']->contenthistory->updateTimeStamp('calendar',$cal_id,$event['id'] ? 'modify' : 'add',time());
if ($updateTS) $GLOBALS['egw']->contenthistory->updateTimeStamp('calendar',$cal_id,$event['id'] ? 'modify' : 'add',time());
return $cal_id;
}
@ -720,8 +732,8 @@ class calendar_boupdate extends calendar_bo
*
* For contacts we use edit rights of the owner of the event (aka. edit rights of the event).
*
* @param int/string $uid account_id or 1-char type-identifer plus id (eg. c15 for addressbook entry #15)
* @param array/int $event event array or id of the event
* @param int|string $uid account_id or 1-char type-identifer plus id (eg. c15 for addressbook entry #15)
* @param array|int $event event array or id of the event
* @return boolean
*/
function check_status_perms($uid,$event)
@ -745,23 +757,25 @@ class calendar_boupdate extends calendar_bo
/**
* set the status of one participant for a given recurrence or for all recurrences since now (includes recur_date=0)
*
* @param int/array $event event-array or id of the event
* @param string/int $uid account_id or 1-char type-identifer plus id (eg. c15 for addressbook entry #15)
* @param int/char $status numeric status (defines) or 1-char code: 'R', 'U', 'T' or 'A'
* @param int|array $event event-array or id of the event
* @param string|int $uid account_id or 1-char type-identifer plus id (eg. c15 for addressbook entry #15)
* @param int|char $status numeric status (defines) or 1-char code: 'R', 'U', 'T' or 'A'
* @param int $recur_date=0 date to change, or 0 = all since now
* @param boolean $ignore_acl=false do not check the permisions for the $uid, if true
* @param boolean $updateTS=true update the content history of the event
* @return int number of changed recurrences
*/
function set_status($event,$uid,$status,$recur_date=0)
function set_status($event,$uid,$status,$recur_date=0,$ignore_acl=false,$updateTS=true)
{
$cal_id = is_array($event) ? $event['id'] : $event;
//echo "<p>bocalupdate::set_status($cal_id,$uid,$status,$recur_date)</p>\n";
if (!$cal_id || !$this->check_status_perms($uid,$event))
//echo "<p>calendar_boupdate::set_status($cal_id,$uid,$status,$recur_date)</p>\n";
if (!$cal_id || (!$ignore_acl && !$this->check_status_perms($uid,$event)))
{
return false;
}
if (($Ok = $this->so->set_status($cal_id,is_numeric($uid)?'u':$uid[0],is_numeric($uid)?$uid:substr($uid,1),$status,$recur_date ? $this->date2ts($recur_date,true) : 0)))
{
$GLOBALS['egw']->contenthistory->updateTimeStamp('calendar',$cal_id,'modify',time());
if ($updateTS) $GLOBALS['egw']->contenthistory->updateTimeStamp('calendar',$cal_id,'modify',time());
static $status2msg = array(
'R' => MSG_REJECTED,
@ -783,14 +797,15 @@ class calendar_boupdate extends calendar_bo
*
* @param int $cal_id id of the event to delete
* @param int $recur_date=0 if a single event from a series should be deleted, its date
* @param boolean $ignore_acl=false true for no ACL check, default do ACL check
* @return boolean true on success, false on error (usually permission denied)
*/
function delete($cal_id,$recur_date=0)
function delete($cal_id,$recur_date=0,$ignore_acl=false)
{
$event = $this->read($cal_id,$recur_date);
if (!($event = $this->read($cal_id,$recur_date)) ||
!$this->check_perms(EGW_ACL_DELETE,$event))
!$ignore_acl && !$this->check_perms(EGW_ACL_DELETE,$event))
{
return false;
}
@ -811,6 +826,10 @@ class calendar_boupdate extends calendar_bo
unset($event['end']);
$this->save($event); // updates the content-history
}
if ($event['reference'])
{
// evtl. delete recur_exception $event['recurrence'] from event with cal_id=$event['reference']
}
return true;
}
@ -1006,7 +1025,9 @@ class calendar_boupdate extends calendar_bo
}
$alarm['time'] = $this->date2ts($alarm['time'],true); // user to server-time
return $this->so->save_alarm($cal_id,$alarm);
$GLOBALS['egw']->contenthistory->updateTimeStamp('calendar',$cal_id, 'modify', time());
return $this->so->save_alarm($cal_id,$alarm, $this->now_su);
}
/**
@ -1023,7 +1044,10 @@ class calendar_boupdate extends calendar_bo
{
return false; // no rights to delete the alarm
}
return $this->so->delete_alarm($id);
$GLOBALS['egw']->contenthistory->updateTimeStamp('calendar',$cal_id, 'modify', time());
return $this->so->delete_alarm($id, $this->now_su);
}
var $categories;
@ -1032,7 +1056,7 @@ class calendar_boupdate extends calendar_bo
{
if (!is_object($this->categories))
{
$this->categories =& CreateObject('phpgwapi.categories',$GLOBALS['egw_info']['user']['account_id'],'calendar');
$this->categories = CreateObject('phpgwapi.categories',$this->user,'calendar');
}
$cat_id_list = array();
@ -1046,7 +1070,7 @@ class calendar_boupdate extends calendar_bo
{
$cat_name = substr($cat_name, 2);
}
$cat_id = $this->categories->add(array('name' => $cat_name,'descr' => $cat_name));
$cat_id = $this->categories->add(array('name' => $cat_name, 'descr' => $cat_name, 'access' => 'private'));
}
if ($cat_id)
@ -1067,7 +1091,7 @@ class calendar_boupdate extends calendar_bo
{
if (!is_object($this->categories))
{
$this->categories =& CreateObject('phpgwapi.categories',$GLOBALS['egw_info']['user']['account_id'],'calendar');
$this->categories = CreateObject('phpgwapi.categories',$this->user,'calendar');
}
if (!is_array($cat_id_list))
@ -1086,4 +1110,110 @@ class calendar_boupdate extends calendar_bo
return $cat_list;
}
}
/**
* Try to find a matching db entry
*
* @param array $event the vCalendar data we try to find
* @param boolean $relax=false if asked to relax, we only match against some key fields
* @return the calendar_id of the matching entry or false (if none matches)
*/
function find_event($event, $relax=false)
{
$query = array(
'cal_start='.$event['start'],
'cal_end='.$event['end'],
);
foreach (array('title', 'location',
'public', 'non_blocking', 'category') as $key)
{
if (!empty($event[$key])) $query['cal_'.$key] = $event[$key];
}
if ($event['uid'] && ($uidmatch = $this->read($event['uid'])))
{
if ($event['reference'])
{
// Let's try to find a real exception first
$query['cal_uid'] = $event['uid'];
$query['cal_reference'] = $event['reference'];
if ($foundEvents = parent::search(array(
'query' => $query,
)))
{
if(is_array($foundEvents))
{
$event = array_shift($foundEvents);
return $event['id'];
}
}
// Let's try the "status only" (pseudo) exceptions now
if (($egw_event = $this->read($uidmatch['id'], $event['reference'])))
{
// Do we work with a pseudo exception here?
$match = true;
foreach (array('start', 'end', 'title', 'description', 'priority',
'location', 'public', 'non_blocking') as $key)
{
if (isset($event[$key])
&& $event[$key] != $egw_event[$key])
{
$match = false;
break;
}
}
if ($match && is_array($event['participants']))
{
foreach ($event['participants'] as $attendee => $status)
{
if (!isset($egw_event['participants'][$attendee])
|| $egw_event['participants'][$attendee] != $status)
{
$match = false;
break;
}
else
{
unset($egw_event['participants'][$attendee]);
}
}
if ($match && !empty($egw_event['participants'])) $match = false;
}
if ($match) return ($uidmatch['id'] . ':' . $event['reference']);
return false; // We need to create a new pseudo exception
}
}
else
{
return $uidmatch['id'];
}
}
if ($event['id'] && ($found = $this->read($event['id'])))
{
// We only do a simple consistency check
if ($found['title'] == $event['title']
&& $found['start'] == $event['start']
&& $found['end'] == $event['end'])
{
return $found['id'];
}
}
unset($event['id']);
if($foundEvents = parent::search(array(
'query' => $query,
)))
{
if(is_array($foundEvents))
{
$event = array_shift($foundEvents);
return $event['id'];
}
}
return false;
}
}

View File

@ -7,7 +7,7 @@
* @package calendar
* @subpackage groupdav
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2007/8 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2007-9 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @version $Id$
*/
@ -33,7 +33,7 @@ class calendar_groupdav extends groupdav_handler
//'RDATE' => 'cal_start',
//'EXRULE'
//'EXDATE'
//'RECURRENCE-ID'
'RECURRENCE-ID' => 'cal_reference',
);
/**
@ -43,11 +43,11 @@ class calendar_groupdav extends groupdav_handler
* @param int $debug=null debug-level to set
* @param string $base_uri=null base url of handler
*/
function __construct($app,$debug=null,$base_uri=null)
function __construct($app,$debug=null, $base_uri=null)
{
parent::__construct($app,$debug,$base_uri);
$this->bo =& new calendar_boupdate();
$this->bo = new calendar_boupdate();
}
const PATH_ATTRIBUTE = 'id';
@ -85,18 +85,20 @@ class calendar_groupdav extends groupdav_handler
function propfind($path,$options,&$files,$user,$id='')
{
if ($this->debug) error_log(__METHOD__."($path,".array2string($options).",,$user,$id)");
//error_log(__METHOD__."($path,".array2string($options).",,$user,$id)");//njv:
// ToDo: add parameter to only return id & etag
//error_log( __FILE__ . __METHOD__ ." :$user ". print_r($options,true));
$st = microtime(true);
$cal_filters = array(
'users' => $user,
'start' => time()-30*24*3600, // default one month back
'end' => time()+365*24*3600, // default one year into the future
'start' => time()-100*24*3600, // default one month back -30 breaks all sync recurrences
'end' => time()+365*24*3600, // default one year into the future +365
'enum_recuring' => false,
'daywise' => false,
'date_format' => 'server',
);
if ($this->debug > 1) error_log(__METHOD__."($path,,,$user,$id) cal_filters=".array2string($cal_filters));
//error_log(__METHOD__."($path,,,$user,$id) cal_filters=".array2string($cal_filters));//njv
// process REPORT filters or multiget href's
if (($id || $options['root']['name'] != 'propfind') && !$this->_report_filters($options,$cal_filters,$id))
{
@ -114,6 +116,7 @@ class calendar_groupdav extends groupdav_handler
}
}
}
//error_log(__FILE__ . __METHOD__ ."Filters:" .print_r($cal_filters,true));
if (($events = $this->bo->search($cal_filters)))
{
foreach($events as $event)
@ -127,6 +130,7 @@ class calendar_groupdav extends groupdav_handler
HTTP_WebDAV_Server::mkprop('getlastmodified', $event['modified']),
HTTP_WebDAV_Server::mkprop('resourcetype',''), // iPhone requires that attribute!
);
//error_log(__FILE__ . __METHOD__ . "Calendar Data : $calendar_data");
if ($calendar_data)
{
if (is_null($handler)) $handler = $this->_get_handler();
@ -144,6 +148,8 @@ class calendar_groupdav extends groupdav_handler
);
}
}
$end = microtime(true) - $st;
if ($this->debug) error_log(__FILE__ . __METHOD__ . "Function took : $end");
return true;
}
@ -170,6 +176,7 @@ class calendar_groupdav extends groupdav_handler
{
case 'comp-filter':
if ($this->debug > 1) error_log(__METHOD__."($path,...) comp-filter='{$filter['attrs']['name']}'");
switch($filter['attrs']['name'])
{
case 'VTODO':
@ -178,7 +185,7 @@ class calendar_groupdav extends groupdav_handler
$infolog_handler = new groupdav_infolog();
return $infolog_handler->propfind($path,$options,$files,$user,$method);
case 'VCALENDAR':
CASE 'VEVENT':
case 'VEVENT':
break; // that's our default anyway
}
break;
@ -202,7 +209,7 @@ class calendar_groupdav extends groupdav_handler
if ($this->debug) error_log(__METHOD__."($path,...) param-filter='{$filter['attrs']['name']}' not (yet) implemented!");
break;
case 'time-range':
if ($this->debug > 1) error_log(__METHOD__."($path,...) time-range={$filter['attrs']['start']}-{$filter['attrs']['end']}");
if ($this->debug > 1) error_log(__FILE__ . __METHOD__."($path,...) time-range={$filter['attrs']['start']}-{$filter['attrs']['end']}");
$cal_filters['start'] = $filter['attrs']['start'];
$cal_filters['end'] = $filter['attrs']['end'];
break;
@ -218,6 +225,7 @@ class calendar_groupdav extends groupdav_handler
}
}
// multiget or propfind on a given id
//error_log(__FILE__ . __METHOD__ . "multiget of propfind:");
if ($options['root']['name'] == 'calendar-multiget' || $id)
{
// no standard time-range!
@ -253,7 +261,8 @@ class calendar_groupdav extends groupdav_handler
{
$cal_filters['query'][] = 'egw_cal.cal_id IN ('.implode(',',array_map(create_function('$n','return (int)$n;'),$ids)).')';
}
if ($this->debug) error_log(__METHOD__."($path,,,$user,$id) calendar-multiget: ids=".implode(',',$ids));
if ($this->debug > 1) error_log(__FILE__ . __METHOD__ ."($path,,,$user,$id) calendar-multiget: ids=".implode(',',$ids));
}
return true;
}
@ -289,10 +298,13 @@ class calendar_groupdav extends groupdav_handler
*/
function put(&$options,$id,$user=null)
{
if($this->debug) error_log(__METHOD__."($id, $user)".print_r($options,true));
$return_no_access=true; // as handled by importVCal anyway and allows it to set the status for participants
$event = $this->_common_get_put_delete('PUT',$options,$id,$return_no_access);
if (!is_null($event) && !is_array($event))
{
if ($this->debug) error_log(__METHOD__.print_r($event,true).function_backtrace());
return $event;
}
$handler = $this->_get_handler();
@ -347,6 +359,8 @@ class calendar_groupdav extends groupdav_handler
*/
function read($id)
{
//$cal_read = $this->bo->read($id,null,false,'server');//njv: do we actually get anything
if ($this->debug > 1) error_log("bo-ical read :$id:");//njv:
return $this->bo->read($id,null,false,'server');
}
@ -363,12 +377,15 @@ class calendar_groupdav extends groupdav_handler
{
$entry = $this->read($entry);
}
if (!$entry['id'] || !isset($entry['etag']) || !isset($entry['participants'])) error_log(__METHOD__."($e_in): id=$entry[id], etag=$entry[etag], isset(participants)=".(int)isset($entry['participants']).", title=$entry[title]: id, etag or participants not set!!!");
if (!$entry['id'] || !isset($entry['etag']) || !isset($entry['participants']))
{
if ($this->debug > 1) error_log(__FILE__ . __METHOD__."($e_in): id=$entry[id], etag=$entry[etag], isset(participants)=".(int)isset($entry['participants']).", title=$entry[title]: id, etag or participants not set!!!");
}
$etag = $entry['id'].':'.$entry['etag'];
// add a hash over the participants and their stati
ksort($entry['participants']); // create a defined order
$etag .= ':'.md5(serialize($entry['participants']));
//error_log(__METHOD__."($entry[id] ($entry[etag]): $entry[title] --> etag=$etag");
//error_log(__FILE__ .__METHOD__ . "($entry[id] ($entry[etag]): $entry[title] --> etag=$etag");
return $etag;
}
@ -412,9 +429,10 @@ class calendar_groupdav extends groupdav_handler
*/
private function _get_handler()
{
$handler =& new calendar_ical();
$handler = new calendar_ical();
$handler->setSupportedFields('GroupDAV',$this->agent);
if ($this->debug > 1) error_log("ical Handler called:" . $this->agent);
return $handler;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -4,13 +4,14 @@
*
* @link http://www.egroupware.org
* @author Lars Kneschke <lkneschke@egroupware.org>
* @author Joerg Lehrke <jlehrke@noc.de>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package calendar
* @subpackage export
* @version $Id$
*/
require_once EGW_SERVER_ROOT.'/phpgwapi/inc/horde/Horde/iCalendar.php';
require_once EGW_SERVER_ROOT.'/phpgwapi/inc/horde/lib/core.php';
/**
* SIF Parser for SyncML
@ -21,6 +22,7 @@ class calendar_sif extends calendar_boupdate
'Start' => 'start',
'End' => 'end',
'AllDayEvent' => 'alldayevent',
'Attendees' => '',
'BillingInformation' => '',
'Body' => 'description',
'BusyStatus' => '',
@ -33,6 +35,11 @@ class calendar_sif extends calendar_boupdate
'Mileage' => '',
'ReminderMinutesBeforeStart' => 'reminderstart',
'ReminderSet' => 'reminderset',
'ReminderSoundFile' => '',
'ReminderOptions' => '',
'ReminderInterval' => '',
'ReminderRepeatCount' => '',
'Exceptions' => '',
'ReplyTime' => '',
'Sensitivity' => 'public',
'Subject' => 'title',
@ -51,6 +58,11 @@ class calendar_sif extends calendar_boupdate
// the calendar event array
var $event;
// device specific settings
var $productName = 'mozilla plugin';
var $productSoftwareVersion = '0.3';
var $uidExtension = false;
// constants for recurence type
const olRecursDaily = 0;
const olRecursWeekly = 1;
@ -68,33 +80,41 @@ class calendar_sif extends calendar_boupdate
const olFriday = 32;
const olSaturday = 64;
function startElement($_parser, $_tag, $_attributes) {
// standard headers
const xml_decl = '<?xml version="1.0" encoding="UTF-8"?>';
const SIF_decl = '<SIFVersion>1.1</SIFVersion>';
function startElement($_parser, $_tag, $_attributes)
{
}
function endElement($_parser, $_tag) {
function endElement($_parser, $_tag)
{
//error_log('endElem: ' . $_tag .' => '. trim($this->sifData));
if(!empty($this->sifMapping[$_tag])) {
if(!empty($this->sifMapping[$_tag]))
{
$this->event[$this->sifMapping[$_tag]] = trim($this->sifData);
}
unset($this->sifData);
}
function characterData($_parser, $_data) {
function characterData($_parser, $_data)
{
$this->sifData .= $_data;
}
function siftoegw($_sifdata) {
$vcal = &new Horde_iCalendar;
function siftoegw($sifData)
{
$vcal = new Horde_iCalendar;
$finalEvent = array();
$sysCharSet = $GLOBALS['egw']->translation->charset();
$sifData = base64_decode($_sifdata);
#error_log($sifData);
$tmpfname = tempnam('/tmp/sync/contents','sife_');
#$tmpfname = tempnam('/tmp/sync/contents','sife_');
$handle = fopen($tmpfname, "w");
fwrite($handle, $sifData);
fclose($handle);
#$handle = fopen($tmpfname, "w");
#fwrite($handle, $sifData);
#fclose($handle);
$this->xml_parser = xml_parser_create('UTF-8');
xml_set_object($this->xml_parser, $this);
@ -102,7 +122,8 @@ class calendar_sif extends calendar_boupdate
xml_set_element_handler($this->xml_parser, "startElement", "endElement");
xml_set_character_data_handler($this->xml_parser, "characterData");
$this->strXmlData = xml_parse($this->xml_parser, $sifData);
if(!$this->strXmlData) {
if (!$this->strXmlData)
{
error_log(sprintf("XML error: %s at line %d",
xml_error_string(xml_get_error_code($this->xml_parser)),
xml_get_current_line_number($this->xml_parser)));
@ -110,16 +131,29 @@ class calendar_sif extends calendar_boupdate
}
#error_log(print_r($this->event, true));
foreach($this->event as $key => $value) {
foreach ($this->event as $key => $value)
{
$value = preg_replace('/<\!\[CDATA\[(.+)\]\]>/Usim', '$1', $value);
$value = $GLOBALS['egw']->translation->convert($value, 'utf-8', $sysCharSet);
#error_log("$key => $value");
switch($key) {
switch ($key)
{
case 'alldayevent':
if($value == 1) {
if ($value == 1)
{
$finalEvent['whole_day'] = true;
$startParts = explode('-',$this->event['start']);
$finalEvent['start'] = mktime(0, 0, 0, $startParts[1], $startParts[2], $startParts[0]);
$finalEvent['start']['hour'] = $finalEvent['start']['minute'] = $finalEvent['start']['second'] = 0;
$finalEvent['start']['year'] = $startParts[0];
$finalEvent['start']['month'] = $startParts[1];
$finalEvent['start']['day'] = $startParts[2];
$finalEvent['start'] = $this->date2ts($finalEvent['start']);
$endParts = explode('-',$this->event['end']);
$finalEvent['end'] = mktime(23, 59, 59, $endParts[1], $endParts[2], $endParts[0]);
$finalEvent['end']['hour'] = 23; $finalEvent['end']['minute'] = $finalEvent['end']['second'] = 59;
$finalEvent['end']['year'] = $endParts[0];
$finalEvent['end']['month'] = $endParts[1];
$finalEvent['end']['day'] = $endParts[2];
$finalEvent['end'] = $this->date2ts($finalEvent['end']);
}
break;
@ -128,26 +162,31 @@ class calendar_sif extends calendar_boupdate
break;
case 'category':
if(!empty($value)) {
if (!empty($value))
{
$finalEvent[$key] = implode(',',$this->find_or_add_categories(explode(';', $value)));
}
break;
case 'end':
case 'start':
if($this->event['alldayevent'] < 1) {
if ($this->event['alldayevent'] < 1)
{
$finalEvent[$key] = $vcal->_parseDateTime($value);
error_log("event ".$key." val=".$value.", parsed=".$finalEvent[$key]);
}
break;
case 'isrecurring':
if($value == 1) {
if ($value == 1)
{
$finalEvent['recur_interval'] = $this->event['recur_interval'];
if($this->event['recur_noenddate'] == 0) {
if ($this->event['recur_noenddate'] == 0)
{
$finalEvent['recur_enddate'] = $vcal->_parseDateTime($this->event['recur_enddate']);
}
switch($this->event['recur_type']) {
switch ($this->event['recur_type'])
{
case self::olRecursDaily:
$finalEvent['recur_type'] = MCAL_RECUR_DAILY;
break;
@ -174,11 +213,12 @@ class calendar_sif extends calendar_boupdate
break;
case 'priority':
$finalEvent[$key] = $value+1;
$finalEvent[$key] = $value + 1;
break;
case 'reminderset':
if($value == 1) {
if ($value == 1)
{
$finalEvent['alarm'] = $this->event['reminderstart'];
}
break;
@ -191,6 +231,12 @@ class calendar_sif extends calendar_boupdate
// do nothing, get's handled in isrecuring clause
break;
case 'description':
if (preg_match('/\s*\[UID:(.+)?\]/Usm', $value, $matches))
{
$finalEvent['uid'] = $matches[1];
}
default:
$finalEvent[$key] = $value;
break;
@ -205,97 +251,74 @@ class calendar_sif extends calendar_boupdate
return $finalEvent;
}
function search($_sifdata, $contentID=null) {
if(!$event = $this->siftoegw($_sifdata)) {
return false;
}
function search($_sifdata, $contentID=null, $relax=false)
{
$result = false;
$query = array(
'cal_start='.$this->date2ts($event['start'],true), // true = Server-time
'cal_end='.$this->date2ts($event['end'],true),
);
if ($contentID) {
$query[] = 'egw_cal.cal_id='.(int)$contentID;
}
#foreach(array('title','location','priority','public','non_blocking') as $name) {
foreach(array('title','location','public','non_blocking') as $name) {
if (isset($event[$name])) $query['cal_'.$name] = $event[$name];
}
if($foundEvents = parent::search(array(
'user' => $this->user,
'query' => $query,
))) {
if(is_array($foundEvents)) {
$event = array_shift($foundEvents);
return $event['id'];
if ($event = $this->siftoegw($_sifdata))
{
if ($contentID) {
$event['id'] = $contentID;
}
$result = $this->find_event($event, $relax);
}
return false;
$search['start'] = $event['start'];
$search['end'] = $event['end'];
unset($event['description']);
unset($event['start']);
unset($event['end']);
foreach($event as $key => $value) {
if (substr($key,0,6) != 'recur_' && substr($key,0,5) != 'alarm') {
$search['query']['cal_'.$key] = $value;
} else {
#$search['query'][$key] = $value;
}
}
if($foundEvents = parent::search($search)) {
if(is_array($foundEvents)) {
$event = array_shift($foundEvents);
return $event['id'];
}
}
return false;
return $result;
}
/**
* @return int contact id
* @param string $_vcard the vcard
* @param int $_abID the internal addressbook id
* @param boolean $merge=false merge data with existing entry
* @desc import a vard into addressbook
*/
function addSIF($_sifdata, $_calID)
function addSIF($_sifdata, $_calID, $merge=false)
{
$state = &$_SESSION['SyncML.state'];
$deviceInfo = $state->getClientDeviceInfo();
$calID = false;
#error_log('ABID: '.$_abID);
#error_log(base64_decode($_sifdata));
if(!$event = $this->siftoegw($_sifdata)) {
if (!$event = $this->siftoegw($_sifdata))
{
return false;
}
if(isset($event['alarm'])) {
if (isset($event['alarm']))
{
$alarm = $event['alarm'];
unset($event['alarm']);
}
if($_calID > 0)
if ($_calID > 0)
{
// update entry
$event['id'] = $_calID;
}
else
{
if (isset($event['whole_day']) && $event['whole_day']
&& isset ($deviceInfo) && is_array($deviceInfo)
&& isset($deviceInfo['nonBlockingAllday'])
&& $deviceInfo['nonBlockingAllday'])
{
$event['non_blocking'] = '1';
}
}
if($eventID = $this->update($event, TRUE)) {
if ($eventID = $this->update($event, TRUE))
{
$updatedEvent = $this->read($eventID);
foreach($updatedEvent['alarm'] as $alarmID => $alarmData)
foreach ($updatedEvent['alarm'] as $alarmID => $alarmData)
{
$this->delete_alarm($alarmID);
}
if(isset($alarm)) {
if (isset($alarm))
{
$alarmData['time'] = $event['start'] - ($alarm*60);
$alarmData['offset'] = $alarm*60;
$alarmData['all'] = 1;
@ -308,46 +331,48 @@ class calendar_sif extends calendar_boupdate
}
/**
* return a vcard
* return a sife
*
* @param int $_id the id of the contact
* @param int $_vcardProfile profile id for mapping from vcard values to egw addressbook
* @param int $_id the id of the event
* @return string containing the vcard
*/
function getSIF($_id)
{
$sysCharSet = $GLOBALS['egw']->translation->charset();
$fields = array_unique(array_values($this->sifMapping));
sort($fields);
#$event = $this->read($_id,null,false,'server');
#error_log("FOUND EVENT: ". print_r($event, true));
if($event = $this->read($_id,null,false,'server')) {
$sysCharSet = $GLOBALS['egw']->translation->charset();
$vcal = &new Horde_iCalendar;
if (($event = $this->read($_id,null,false,'server')))
{
$sifEvent = '<appointment>';
foreach($this->sifMapping as $sifField => $egwField)
if ($this->uidExtension)
{
if(empty($egwField)) continue;
if (!preg_match('/\[UID:.+\]/m', $event['description']))
{
$event['description'] .= "\n[UID:" . $event['uid'] . "]";
}
}
$vcal = new Horde_iCalendar('1.0');
$sifEvent = self::xml_decl . "\n<appointment>" . self::SIF_decl;
foreach ($this->sifMapping as $sifField => $egwField)
{
if (empty($egwField)) continue;
#error_log("$sifField => $egwField");
#error_log('VALUE1: '.$event[$egwField]);
$value = $GLOBALS['egw']->translation->convert($event[$egwField], $sysCharSet, 'utf-8');
#error_log('VALUE2: '.$value);
switch($sifField)
switch ($sifField)
{
case 'Categories':
if(!empty($value)) {
$value = implode('; ', $this->get_categories(explode(',',$value)));
$value = $GLOBALS['egw']->translation->convert($value, $sysCharSet, 'utf-8');
}
$sifEvent .= "<$sifField>$value</$sifField>";
break;
case 'Importance':
$value = $value-1;
$sifEvent .= "<$sifField>$value</$sifField>";
@ -362,10 +387,26 @@ class calendar_sif extends calendar_boupdate
break;
case 'IsRecurring':
switch($event['recur_type']) {
case MCAL_RECUR_NONE:
$sifEvent .= "<$sifField>0</$sifField>";
break;
if ($event['recur_type'] == MCAL_RECUR_NONE)
{
$sifEvent .= "<$sifField>0</$sifField>";
break;
}
if ($event['recur_enddate'] == 0)
{
$sifEvent .= '<NoEndDate>1</NoEndDate>';
}
else
{
$recurEndDate = mktime(24 , 0, 0,
date('m',$event['recur_enddate']),
date('d', $event['recur_enddate']),
date('Y', $event['recur_enddate']));
$sifEvent .= '<NoEndDate>0</NoEndDate>';
$sifEvent .= '<PatternEndDate>'. $vcal->_exportDateTime($recurEndDate) .'</PatternEndDate>';
}
switch ($event['recur_type'])
{
case MCAL_RECUR_DAILY:
$eventInterval = ($event['recur_interval'] > 1 ? $event['recur_interval'] : 1);
@ -375,13 +416,8 @@ class calendar_sif extends calendar_boupdate
$sifEvent .= '<RecurrenceType>'. self::olRecursDaily .'</RecurrenceType>';
$sifEvent .= '<Interval>'. $eventInterval .'</Interval>';
$sifEvent .= '<PatternStartDate>'. $vcal->_exportDateTime($recurStartDate) .'</PatternStartDate>';
if($event['recur_enddate'] == 0) {
$sifEvent .= '<NoEndDate>1</NoEndDate>';
} else {
$recurEndDate = mktime(24,0,0,date('m',$event['recur_enddate']), date('d', $event['recur_enddate']), date('Y', $event['recur_enddate']));
$sifEvent .= '<NoEndDate>0</NoEndDate>';
$sifEvent .= '<PatternEndDate>'. $vcal->_exportDateTime($recurEndDate) .'</PatternEndDate>';
if ($event['recur_enddate'])
{
$totalDays = ($recurEndDate - $recurStartDate) / 86400;
$occurrences = ceil($totalDays / $eventInterval);
$sifEvent .= '<Occurrences>'. $occurrences .'</Occurrences>';
@ -397,46 +433,73 @@ class calendar_sif extends calendar_boupdate
$sifEvent .= '<Interval>'. $eventInterval .'</Interval>';
$sifEvent .= '<PatternStartDate>'. $vcal->_exportDateTime($recurStartDate) .'</PatternStartDate>';
$sifEvent .= '<DayOfWeekMask>'. $event['recur_data'] .'</DayOfWeekMask>';
if($event['recur_enddate'] == 0) {
$sifEvent .= '<NoEndDate>1</NoEndDate>';
} else {
$recurEndDate = mktime(24, 0, 0, date('m',$event['recur_enddate']), date('d', $event['recur_enddate']), date('Y', $event['recur_enddate']));
if ($event['recur_enddate'])
{
$daysPerWeek = substr_count(decbin($event['recur_data']),'1');
$sifEvent .= '<NoEndDate>0</NoEndDate>';
$sifEvent .= '<PatternEndDate>'. $vcal->_exportDateTime($recurEndDate) .'</PatternEndDate>';
$totalWeeks = floor(($recurEndDate - $recurStartDate) / (86400*7));
#error_log("AAA: $daysPerWeek $totalWeeks");
$occurrences = ($totalWeeks / $eventInterval) * $daysPerWeek;
for($i = $recurEndDate; $i > $recurStartDate + ($totalWeeks * 86400*7); $i = $i - 86400) {
switch(date('w', $i-1)) {
for($i = $recurEndDate; $i > $recurStartDate + ($totalWeeks * 86400*7); $i = $i - 86400)
{
switch (date('w', $i-1))
{
case 0:
if($event['recur_data'] & 1) $occurrences++;
if ($event['recur_data'] & 1) $occurrences++;
break;
// monday
case 1:
if($event['recur_data'] & 2) $occurrences++;
if ($event['recur_data'] & 2) $occurrences++;
break;
case 2:
if($event['recur_data'] & 4) $occurrences++;
if ($event['recur_data'] & 4) $occurrences++;
break;
case 3:
if($event['recur_data'] & 8) $occurrences++;
if ($event['recur_data'] & 8) $occurrences++;
break;
case 4:
if($event['recur_data'] & 16) $occurrences++;
if ($event['recur_data'] & 16) $occurrences++;
break;
case 5:
if($event['recur_data'] & 32) $occurrences++;
if ($event['recur_data'] & 32) $occurrences++;
break;
case 6:
if($event['recur_data'] & 64) $occurrences++;
if ($event['recur_data'] & 64) $occurrences++;
break;
}
}
$sifEvent .= '<Occurrences>'. $occurrences .'</Occurrences>';
}
break;
case MCAL_RECUR_MONTHLY_MDAY:
$eventInterval = ($event['recur_interval'] > 1 ? $event['recur_interval'] : 1);
$recurStartDate = mktime(0,0,0,date('m',$event['start']), date('d', $event['start']), date('Y', $event['start']));
$sifEvent .= "<$sifField>1</$sifField>";
$sifEvent .= '<RecurrenceType>'. self::olRecursMonthly .'</RecurrenceType>';
$sifEvent .= '<Interval>'. $eventInterval .'</Interval>';
$sifEvent .= '<PatternStartDate>'. $vcal->_exportDateTime($recurStartDate) .'</PatternStartDate>';
break;
case MCAL_RECUR_MONTHLY_WDAY:
$weekMaskMap = array('Sun' => self::olSunday, 'Mon' => self::olMonday, 'Tue' => self::olTuesday,
'Wed' => self::olWednesday, 'Thu' => self::olThursday, 'Fri' => self::olFriday,
'Sat' => self::olSaturday);
$eventInterval = ($event['recur_interval'] > 1 ? $event['recur_interval'] : 1);
$recurStartDate = mktime(0,0,0,date('m',$event['start']), date('d', $event['start']), date('Y', $event['start']));
$sifEvent .= "<$sifField>1</$sifField>";
$sifEvent .= '<RecurrenceType>'. self::olRecursMonthNth .'</RecurrenceType>';
$sifEvent .= '<Interval>'. $eventInterval .'</Interval>';
$sifEvent .= '<PatternStartDate>'. $vcal->_exportDateTime($recurStartDate) .'</PatternStartDate>';
$sifEvent .= '<Instance>' . (1 + (int) ((date('d',$event['start'])-1) / 7)) . '</Instance>';
$sifEvent .= '<DayOfWeekMask>' . $weekMaskMap[date('D',$event['start'])] . '</DayOfWeekMask>';
break;
case MCAL_RECUR_YEARLY:
$sifEvent .= "<$sifField>1</$sifField>";
$sifEvent .= '<RecurrenceType>'. self::olRecursYearly .'</RecurrenceType>';
break;
}
break;
@ -456,12 +519,16 @@ class calendar_sif extends calendar_boupdate
break;
case 'Start':
if($event['end'] - $event['start'] == 86399 && date('Y-m-d', $event['end']) == date('Y-m-d', $event['start'])) {
if ($this->isWholeDay($event))
{
$value = date('Y-m-d', $event['start']);
$sifEvent .= "<Start>$value</Start>";
$vaule = date('Y-m-d', $event['end']);
$sifEvent .= "<End>$value</End>";
$sifEvent .= "<AllDayEvent>1</AllDayEvent>";
} else {
}
else
{
$value = $vcal->_exportDateTime($event['start']);
$sifEvent .= "<Start>$value</Start>";
$value = $vcal->_exportDateTime($event['end']);
@ -474,27 +541,42 @@ class calendar_sif extends calendar_boupdate
break;
case 'ReminderSet':
if(count((array)$event['alarm']) > 0) {
if (count((array)$event['alarm']) > 0)
{
$sifEvent .= "<$sifField>1</$sifField>";
foreach($event['alarm'] as $alarmID => $alarmData)
foreach ($event['alarm'] as $alarmID => $alarmData)
{
$sifEvent .= '<ReminderMinutesBeforeStart>'. $alarmData['offset']/60 .'</ReminderMinutesBeforeStart>';
// lets take only the first alarm
break;
}
} else {
}
else
{
$sifEvent .= "<$sifField>0</$sifField>";
}
break;
case 'Categories':
if (!empty($value))
{
$value = implode('; ', $this->get_categories(explode(',',$value)));
$value = $GLOBALS['egw']->translation->convert($value, $sysCharSet, 'utf-8');
}
else
{
break;
}
default:
$value = @htmlspecialchars($value, ENT_QUOTES, 'utf-8');
$sifEvent .= "<$sifField>$value</$sifField>";
break;
}
}
$sifEvent .= '</appointment>';
return base64_encode($sifEvent);
return $sifEvent;
}
if($this->xmlrpc)
@ -504,4 +586,35 @@ class calendar_sif extends calendar_boupdate
return False;
}
/**
* Set the supported fields
*
* Currently we only store name and version, manucfacturer is always Funambol
*
* @param string $_productName
* @param string $_productSoftwareVersion
*/
function setSupportedFields($_productName='', $_productSoftwareVersion='')
{
$state = &$_SESSION['SyncML.state'];
$deviceInfo = $state->getClientDeviceInfo();
if (isset($deviceInfo) && is_array($deviceInfo))
{
if (isset($deviceInfo['uidExtension']) &&
$deviceInfo['uidExtension'])
{
$this->uidExtension = true;
}
}
// store product name and version, to be able to use it elsewhere
if ($_productName)
{
$this->productName = strtolower($_productName);
if (preg_match('/^[^\d]*(\d+\.?\d*)[\.|\d]*$/', $_productSoftwareVersion, $matches))
{
$this->productSoftwareVersion = $matches[1];
}
}
}
}

View File

@ -5,7 +5,9 @@
* @link http://www.egroupware.org
* @package calendar
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2005-8 by RalfBecker-At-outdoor-training.de
* @author Christian Binder <christian-AT-jaytraxx.de>
* @author Joerg Lehrke <jlehrke@noc.de>
* @copyright (c) 2005-9 by RalfBecker-At-outdoor-training.de
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$
*/
@ -13,7 +15,7 @@
/**
* some necessary defines used by the calendar
*/
if(extension_loaded('mcal') == False)
if(!extension_loaded('mcal'))
{
define('MCAL_RECUR_NONE',0);
define('MCAL_RECUR_DAILY',1);
@ -43,6 +45,10 @@ define('NO_RESPONSE',1);
define('TENTATIVE',2);
define('ACCEPTED',3);
define('HOUR_s',60*60);
define('DAY_s',24*HOUR_s);
define('WEEK_s',7*DAY_s);
/**
* Class to store all calendar data (storage object)
*
@ -67,7 +73,7 @@ class calendar_so
var $extra_table,$repeats_table,$user_table,$dates_table,$all_tables;
/**
* internal copy of the global db-object
* reference to global db-object
*
* @var egw_db
*/
@ -105,12 +111,21 @@ class calendar_so
*
* All times (start, end and modified) are returned as timesstamps in servertime!
*
* @param int/array/string $ids id or array of id's of the entries to read, or string with a single uid
* @param int|array|string $ids id or array of id's of the entries to read, or string with a single uid
* @param int $recur_date=0 if set read the next recurrance at or after the timestamp, default 0 = read the initital one
* @return array/boolean array with id => data pairs or false if entry not found
* @return array|boolean array with id => data pairs or false if entry not found
*/
function read($ids,$recur_date=0)
{
if (isset($GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length']))
{
$minimum_uid_length = $GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length'];
}
else
{
$minimum_uid_length = 8;
}
//echo "<p>socal::read(".print_r($ids,true).",$recur_date)<br />\n".function_backtrace()."<p>\n";
$table_def = $this->db->get_table_definitions('calendar',$this->cal_table);
@ -131,7 +146,9 @@ class calendar_so
}
else
{
// We want only the parents to match
$where['cal_uid'] = $ids;
$where['cal_reference'] = 0;
}
if ((int) $recur_date)
{
@ -153,6 +170,17 @@ class calendar_so
}
if (!$events) return false;
foreach ($events as &$event)
{
if (!isset($event['uid']) || strlen($event['uid']) < $minimum_uid_length)
{
// event (without uid), not strong enough uid => create new uid
$event['uid'] = $GLOBALS['egw']->common->generate_uid('calendar',$event['id']);
$this->db->update($this->cal_table, array('cal_uid' => $event['uid']),
array('cal_id' => $event['id']),__LINE__,__FILE__,'calendar');
}
}
// check if we have a real recurance, if not set $recur_date=0
if (is_array($ids) || $events[(int)$ids]['recur_type'] == MCAL_RECUR_NONE)
{
@ -173,20 +201,12 @@ class calendar_so
'cal_recur_date' => $recur_date,
),__LINE__,__FILE__,false,'ORDER BY cal_user_type DESC,'.self::STATUS_SORT,'calendar') as $row) // DESC puts users before resources and contacts
{
// if the type is not an ordinary user (eg. contact or resource)...
if ($row['cal_user_type'] && $row['cal_user_type'] != 'u')
{
// prefix the id with the type
$user_id = $row['cal_user_type'].$row['cal_user_id'];
// and append quantity
$row['cal_status'] .= $row['cal_quantity'] == 1 ? '' : $row['cal_quantity'];
}
else
{
$user_id = (int) $row['cal_user_id'];
}
$events[$row['cal_id']]['participants'][$user_id] = $row['cal_status'];
$events[$row['cal_id']]['participant_types'][$row['cal_user_type']][$row['cal_user_id']] = $row['cal_status'];
// combine all participant data in uid and status values
$uid = self::combine_user($row['cal_user_type'],$row['cal_user_id']);
$status = self::combine_status($row['cal_status'],$row['cal_quantity'],$row['cal_role']);
$events[$row['cal_id']]['participants'][$uid] = $status;
$events[$row['cal_id']]['participant_types'][$row['cal_user_type']][$row['cal_user_id']] = $status;
}
// custom fields
@ -214,7 +234,7 @@ class calendar_so
/**
* generate SQL to filter after a given category (evtl. incl. subcategories)
*
* @param array/int $cat_id cat-id or array of cat-ids, or !$cat_id for none
* @param array|int $cat_id cat-id or array of cat-ids, or !$cat_id for none
* @return string SQL to include in the query
*/
function cat_filter($cat_id)
@ -247,25 +267,35 @@ class calendar_so
*
* @param int $start startdate of the search/list (servertime)
* @param int $end enddate of the search/list (servertime)
* @param int/array $users user-id or array of user-id's, !$users means all entries regardless of users
* @param int|array $users user-id or array of user-id's, !$users means all entries regardless of users
* @param int $cat_id=0 mixed category-id or array of cat-id's, default 0 = all
* Please note: only a single cat-id, will include all sub-cats (if the common-pref 'cats_no_subs' is False)
* @param string $filter='' string filter-name, atm. all or hideprivate
* @param string $filter='all' string filter-name: all (not rejected), accepted, unknown, tentative, rejected or hideprivate (handled elsewhere!)
* @param string $query='' pattern so search for, if unset or empty all matching entries are returned (no search)
* Please Note: a search never returns repeating events more then once AND does not honor start+end date !!!
* @param int/bool $offset=False offset for a limited query or False (default)
* @param int|boolean $offset=False offset for a limited query or False (default)
* @param int $num_rows=0 number of rows to return if offset set, default 0 = use default in user prefs
* @param string $order='cal_start' column-names plus optional DESC|ASC separted by comma
* @param boolean $show_rejected=true should the search return rejected invitations
* @param string $sql_filter='' sql to be and'ed into query (fully quoted)
* @param string|array $_cols=null what to select, default "$this->repeats_table.*,$this->cal_table.*,cal_start,cal_end,cal_recur_date",
* if specified and not false an iterator for the rows is returned
* @param string $append='' SQL to append to the query before $order, eg. for a GROUP BY clause
* @param array $cfs=null custom fields to query, null = none, array() = all, or array with cfs names
* @return array of cal_ids, or false if error in the parameters
*
* ToDo: search custom-fields too
*/
function &search($start,$end,$users,$cat_id=0,$filter='',$query='',$offset=False,$num_rows=0,$order = 'cal_start',$show_rejected=true)
function &search($start,$end,$users,$cat_id=0,$filter='all',$query='',$offset=False,$num_rows=0,$order='cal_start',$sql_filter='',$_cols=null,$append='',$cfs=null)
{
//echo '<p>socal::search('.($start ? date('Y-m-d H:i',$start) : '').','.($end ? date('Y-m-d H:i',$end) : '').','.print_r($users,true).','.print_r($cat_id,true).",'$filter',".print_r($query,true).",$offset,$num_rows)</p>\n";
//echo '<p>'.__METHOD__.'('.($start ? date('Y-m-d H:i',$start) : '').','.($end ? date('Y-m-d H:i',$end) : '').','.array2string($users).','.array2string($cat_id).",'$filter',".array2string($query).",$offset,$num_rows,$order,$show_rejected,".array2string($_cols).",$append,".array2string($cfs).")</p>\n";
$cols = !is_null($_cols) ? $_cols : "$this->repeats_table.*,$this->cal_table.*,cal_start,cal_end,cal_recur_date";
$where = array();
if (!empty($sql_filter) && is_string($sql_filter))
{
$where[] = $sql_filter;
}
if (is_array($query))
{
$where = $query;
@ -281,7 +311,7 @@ class calendar_so
if ($users)
{
$users_by_type = array();
foreach(is_array($users) ? $users : array($users) as $user)
foreach((array)$users as $user)
{
if (is_numeric($user))
{
@ -289,7 +319,7 @@ class calendar_so
}
elseif (is_numeric(substr($user,1)))
{
$users_by_type[$user{0}][] = (int) substr($user,1);
$users_by_type[$user[0]][] = (int) substr($user,1);
}
}
$to_or = array();
@ -300,10 +330,31 @@ class calendar_so
'cal_user_type' => $type,
'cal_user_id' => $ids,
));
if ($type == 'u' && $filter == 'owner')
{
$cal_table_def = $this->db->get_table_definitions('calendar',$this->cal_table);
$to_or[] = $this->db->expression($cal_table_def,array('cal_owner' => $ids));
}
}
$where[] = '('.implode(' OR ',$to_or).')';
if (!$show_rejected) $where[] = "cal_status != 'R'";
switch($filter)
{
case 'unknown':
$where[] = "cal_status='U'"; break;
case 'accepted':
$where[] = "cal_status='A'"; break;
case 'tentative':
$where[] = "cal_status='T'"; break;
case 'rejected':
$where[] = "cal_status='R'"; break;
case 'all':
break;
default:
//if (!$show_rejected) // not longer used
$where[] = "cal_status != 'R'";
break;
}
}
if ($cat_id)
{
@ -320,9 +371,10 @@ class calendar_so
$select = array(
'table' => $this->cal_table,
'join' => "JOIN $this->dates_table ON $this->cal_table.cal_id=$this->dates_table.cal_id JOIN $this->user_table ON $this->cal_table.cal_id=$this->user_table.cal_id LEFT JOIN $this->repeats_table ON $this->cal_table.cal_id=$this->repeats_table.cal_id",
'cols' => "$this->repeats_table.*,$this->cal_table.*,cal_start,cal_end,cal_recur_date",
'cols' => $cols,
'where' => $where,
'app' => 'calendar',
'append'=> $append,
);
$selects = array($select,$select);
$selects[0]['where'][] = 'recur_type IS NULL AND cal_recur_date=0';
@ -337,6 +389,7 @@ class calendar_so
$selects[0]['cols'] = $selects[1]['cols'] = $select['cols']; // restore the original cols
}
// error_log("calendar_so_search:\n" . print_r($selects, true));
$rs = $this->db->union($selects,__LINE__,__FILE__,$order,$offset,$num_rows);
}
else // MsSQL oder MySQL 3.23
@ -351,11 +404,14 @@ class calendar_so
$where,__LINE__,__FILE__,false,'','calendar',0,
"JOIN $this->dates_table ON $this->cal_table.cal_id=$this->dates_table.cal_id JOIN $this->user_table ON $this->cal_table.cal_id=$this->user_table.cal_id LEFT JOIN $this->repeats_table ON $this->cal_table.cal_id=$this->repeats_table.cal_id")->NumRows();
}
$rs = $this->db->select($this->cal_table,($this->db->capabilities['distinct_on_text'] ? 'DISTINCT ' : '').
"$this->repeats_table.*,$this->cal_table.*,cal_start,cal_end,cal_recur_date",
$where,__LINE__,__FILE__,$offset,'ORDER BY '.$order,'calendar',$num_rows,
$rs = $this->db->select($this->cal_table,($this->db->capabilities['distinct_on_text'] ? 'DISTINCT ' : '').$cols,
$where,__LINE__,__FILE__,$offset,$append.' ORDER BY '.$order,'calendar',$num_rows,
"JOIN $this->dates_table ON $this->cal_table.cal_id=$this->dates_table.cal_id JOIN $this->user_table ON $this->cal_table.cal_id=$this->user_table.cal_id LEFT JOIN $this->repeats_table ON $this->cal_table.cal_id=$this->repeats_table.cal_id");
}
if (!is_null($_cols))
{
return $rs; // if colums are specified we return the recordset / iterator
}
$events = $ids = $recur_dates = $recur_ids = array();
foreach($rs as $row)
{
@ -375,33 +431,41 @@ class calendar_so
// now ready all users with the given cal_id AND (cal_recur_date=0 or the fitting recur-date)
// This will always read the first entry of each recuring event too, we eliminate it later
$recur_dates[] = 0;
foreach($this->db->select($this->user_table,'*',array(
'cal_id' => array_unique($ids),
'cal_recur_date' => $recur_dates,
),__LINE__,__FILE__,false,'ORDER BY cal_id,cal_user_type DESC,'.self::STATUS_SORT,'calendar') as $row) // DESC puts users before resources and contacts
$utcal_id_view = " (SELECT * FROM ".$this->user_table." WHERE cal_id IN (".implode(',',array_unique($ids)).")) utcalid ";
//$utrecurdate_view = " (select * from ".$this->user_table." where cal_recur_date in (".implode(',',array_unique($recur_dates)).")) utrecurdates ";
foreach($this->db->select($utcal_id_view,'*',array(
//'cal_id' => array_unique($ids),
'cal_recur_date' => $recur_dates,
),__LINE__,__FILE__,false,'ORDER BY cal_id,cal_user_type DESC,'.self::STATUS_SORT,'calendar',$num_rows=0,$join='',
$this->db->get_table_definitions('calendar',$this->user_table)) as $row) // DESC puts users before resources and contacts
{
$id = $row['cal_id'];
if ($row['cal_recur_date']) $id .= '-'.$row['cal_recur_date'];
if (!in_array($id,(array)$recur_ids[$row['cal_id']])) $recur_ids[$row['cal_id']][] = $id;
if (!isset($events[$id])) continue; // not needed first entry of recuring event
$events[$id]['participants'][$this->combine_user($row['cal_user_type'],$row['cal_user_id'])] = $row['cal_status'];
// combine all participant data in uid and status values
$events[$id]['participants'][self::combine_user($row['cal_user_type'],$row['cal_user_id'])] =
self::combine_status($row['cal_status'],$row['cal_quantity'],$row['cal_role']);
}
/* custom fields are not shown in the regular views, so we can ignore them here for the moment
foreach($this->db->select($this->extra_table,'*',array('cal_id'=>$ids),__LINE__,__FILE__,false,'','calendar') as $row)
//custom fields are not shown in the regular views, so we only query them, if explicitly required
if (!is_null($cfs))
{
$set_ids = array($row['cal_id']);
if (isset($recur_ids[$row['cal_id']])) $set_ids += $recur_ids[$row['cal_id']];
foreach($set_ids as $id)
$where = array('cal_id' => $ids);
if ($cfs) $where['cal_extra_name'] = $cfs;
foreach($this->db->select($this->extra_table,'*',$where,
__LINE__,__FILE__,false,'','calendar') as $row)
{
if (isset($events[$cal_id]))
foreach((array)$recur_ids[$row['cal_id']] as $id)
{
$events[$id]['#'.$row['cal_extra_name']] = $row['cal_extra_value'];
if (isset($events[$id]))
{
$events[$id]['#'.$row['cal_extra_name']] = $row['cal_extra_value'];
}
}
}
}
*/
// alarms, atm. we read all alarms in the system, as this can be done in a single query
foreach((array)$this->async->read('cal'.(is_array($ids) ? '' : ':'.(int)$ids).':%') as $id => $job)
{
@ -468,14 +532,24 @@ ORDER BY cal_user_type, cal_usre_id
*
* @param array $event
* @param boolean &$set_recurrences on return: true if the recurrences need to be written, false otherwise
* @param int &$set_recurrences_start=0 on return: time from which on the recurrences should be rebuilt, default 0=all
* @param int $change_since=0 time from which on the repetitions should be changed, default 0=all
* @param int &$etag etag=null etag to check or null, on return new etag
* @return boolean/int false on error, 0 if etag does not match, cal_id otherwise
* @return boolean|int false on error, 0 if etag does not match, cal_id otherwise
*/
function save($event,&$set_recurrences,$change_since=0,&$etag=null)
function save($event,&$set_recurrences,&$set_recurrences_start=0,$change_since=0,&$etag=null)
{
//echo "<p>socal::save(,$change_since) event="; _debug_array($event);
//error_log(__METHOD__."(".str_replace(array("\n",' '),'',print_r($event,true)).",$set_recurrences,$change_since,$etag)");
if (isset($GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length']))
{
$minimum_uid_length = $GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length'];
}
else
{
$minimum_uid_length = 8;
}
//echo '<p>'.__METHOD__.'('.array2string($event).",$change_since) event="; _debug_array($event);
//error_log(__METHOD__.'('.array2string($event).",$set_recurrences,$change_since,$etag)");
$cal_id = (int) $event['id'];
unset($event['id']);
@ -484,7 +558,7 @@ ORDER BY cal_user_type, cal_usre_id
// add colum prefix 'cal_' if there's not already a 'recur_' prefix
foreach($event as $col => $val)
{
if ($col{0} != '#' && substr($col,0,6) != 'recur_' && $col != 'alarm')
if ($col[0] != '#' && substr($col,0,6) != 'recur_' && $col != 'alarm')
{
$event['cal_'.$col] = $val;
unset($event[$col]);
@ -510,6 +584,7 @@ ORDER BY cal_user_type, cal_usre_id
}
else
{
// new event
if (!$event['cal_owner']) $event['cal_owner'] = $GLOBALS['egw_info']['user']['account_id'];
if (!$event['cal_id'] && !isset($event['cal_uid'])) $event['cal_uid'] = ''; // uid is NOT NULL!
@ -519,45 +594,102 @@ ORDER BY cal_user_type, cal_usre_id
{
return false;
}
// new event (without uid), not strong enough uid or new created referencing event => create new uid
if (strlen($event['cal_uid']) < 20 || is_numeric($event['cal_uid']) ||
$event['cal_reference'] && strpos($event['cal_uid'],'cal-'.$event['calreference'].'-') !== false)
{
$event['cal_uid'] = $GLOBALS['egw']->common->generate_uid('calendar',$cal_id);
$this->db->update($this->cal_table,array('cal_uid' => $event['cal_uid']),array('cal_id' => $cal_id),__LINE__,__FILE__,'calendar');
}
$etag = 0;
// new events need to have at least one participant, default to the owner
if (!isset($event['cal_participants']))
{
$event['cal_participants'] = array($event['cal_owner'] => 'A');
}
}
if (!isset($event['cal_uid']) || strlen($event['cal_uid']) < $minimum_uid_length)
{
// event (without uid), not strong enough uid
$event['cal_uid'] = $GLOBALS['egw']->common->generate_uid('calendar',$cal_id);
$this->db->update($this->cal_table, array('cal_uid' => $event['cal_uid']),
array('cal_id' => $event['cal_id']),__LINE__,__FILE__,'calendar');
}
// write information about recuring event, if recur_type is present in the array
if (isset($event['recur_type']))
{
if (isset($event['recur_exception']) && is_array($event['recur_exception']) && count($event['recur_exception']))
{
// delete execeptions from the user and dates table, it could be the first time
$this->db->delete($this->user_table,array('cal_id' => $cal_id,'cal_recur_date' => $event['recur_exception']),__LINE__,__FILE__,'calendar');
$this->db->delete($this->dates_table,array('cal_id' => $cal_id,'cal_start' => $event['recur_exception']),__LINE__,__FILE__,'calendar');
// fetch information about the currently saved (old) event
$old_min = (int) $this->db->select($this->dates_table,'MIN(cal_start)',array('cal_id'=>$cal_id),__LINE__,__FILE__,false,'','calendar')->fetchColumn();
$old_duration = (int) $this->db->select($this->dates_table,'MIN(cal_end)',array('cal_id'=>$cal_id),__LINE__,__FILE__,false,'','calendar')->fetchColumn() - $old_min;
$old_repeats = $this->db->select($this->repeats_table,'*',array('cal_id' => $cal_id),__LINE__,__FILE__,false,'','calendar')->fetch();
$old_exceptions = $old_repeats['recur_exception'] ? explode(',',$old_repeats['recur_exception']) : array();
$event['recur_exception'] = implode(',',$event['recur_exception']);
$event['recur_exception'] = is_array($event['recur_exception']) ? $event['recur_exception'] : array();
// re-check: did so much recurrence data change that we have to rebuild it from scratch?
if (!$set_recurrences)
{
$set_recurrences = (isset($event['cal_start']) && (int)$old_min != (int) $event['cal_start']) ||
$event['recur_type'] != $old_repeats['recur_type'] || $event['recur_data'] != $old_repeats['recur_data'] ||
(int)$event['recur_interval'] != (int)$old_repeats['recur_interval'];
}
if ($set_recurrences)
{
// too much recurrence data has changed, we have to do a rebuild from scratch
// delete all, but the lowest dates record
$this->db->delete($this->dates_table,array(
'cal_id' => $cal_id,
'cal_start > '.(int)$old_min,
),__LINE__,__FILE__,'calendar');
// delete all user-records, with recur-date != 0
$this->db->delete($this->user_table,array(
'cal_id' => $cal_id,
'cal_recur_date != 0',
),__LINE__,__FILE__,'calendar');
}
else
{
$event['recur_exception'] = null;
}
if (!$set_recurrences)
{
// check if the recure-information changed
$old_recur = $this->db->select($this->repeats_table,'*',array('cal_id' => $cal_id),__LINE__,__FILE__,false,'','calendar')->fetch();
$old_exceptions = $old_recur['recur_exception'] ? explode(',',$old_recur['recur_exception']) : array();
$exceptions = $event['recur_exception'] ? explode(',',$event['recur_exception']) : array();
$set_recurrences = $event['recur_type'] != $old_recur['recur_type'] || $event['recur_data'] != $old_recur['recur_data'] ||
$event['recur_interval'] != $old_recur['recur_interval'] || $event['recur_enddate'] != $old_recur['recur_enddate'] ||
count(array_diff($old_exceptions,$exceptions)); // exception deleted or added
// we adjust some possibly changed recurrences manually
// deleted exceptions: re-insert recurrences into the user and dates table
if(count($deleted_exceptions = array_diff($old_exceptions,$event['recur_exception'])))
{
foreach($deleted_exceptions as $id => $deleted_exception)
{
// rebuild participants for the re-inserted recurrence
$participants = array();
$participants_only = $this->get_participants($cal_id); // participants without states
foreach($participants_only as $id => $participant_only)
{
$states = $this->get_recurrences($cal_id, $participant_only['uid']);
$participants[$participant_only['uid']] = $states[0]; // insert main status as default
}
$this->recurrence($cal_id, $deleted_exception, $deleted_exception + $old_duration, $participants);
}
}
// check if recurrence enddate was adjusted
if(isset($event['recur_enddate']))
{
// recurrences need to be truncated
if((int)$event['recur_enddate'] > 0 &&
((int)$old_repeats['recur_enddate'] == 0 || (int)$old_repeats['recur_enddate'] > (int)$event['recur_enddate'])
)
{
$this->db->delete($this->user_table,array('cal_id' => $cal_id,'cal_recur_date > '.($event['recur_enddate'] + 1*DAY_s)),__LINE__,__FILE__,'calendar');
$this->db->delete($this->dates_table,array('cal_id' => $cal_id,'cal_start > '.($event['recur_enddate'] + 1*DAY_s)),__LINE__,__FILE__,'calendar');
}
// recurrences need to be expanded
if(((int)$event['recur_enddate'] == 0 && (int)$old_repeats['recur_enddate'] > 0)
|| ((int)$event['recur_enddate'] > 0 && (int)$old_repeats['recur_enddate'] > 0 && (int)$old_repeats['recur_enddate'] < (int)$event['recur_enddate'])
)
{
$set_recurrences = true;
$set_recurrences_start = ($old_repeats['recur_enddate'] + 1*DAY_s);
}
}
// truncate recurrences by given exceptions
if (count($event['recur_exception']))
{
// added and existing exceptions: delete the execeptions from the user and dates table, it could be the first time
$this->db->delete($this->user_table,array('cal_id' => $cal_id,'cal_recur_date' => $event['recur_exception']),__LINE__,__FILE__,'calendar');
$this->db->delete($this->dates_table,array('cal_id' => $cal_id,'cal_start' => $event['recur_exception']),__LINE__,__FILE__,'calendar');
}
}
// write the repeats table
$event['recur_exception'] = empty($event['recur_exception']) ? null : implode(',',$event['recur_exception']);
unset($event[0]); // unset the 'etag=etag+1', as it's not in the repeats table
if($event['recur_type'] != MCAL_RECUR_NONE)
{
@ -567,21 +699,6 @@ ORDER BY cal_user_type, cal_usre_id
{
$this->db->delete($this->repeats_table,array('cal_id' => $cal_id),__LINE__,__FILE__,'calendar');
}
if ($set_recurrences)
{
// delete all, but the lowest dates record
$min = (int) $this->db->select($this->dates_table,'MIN(cal_start)',array('cal_id'=>$cal_id),__LINE__,__FILE__,false,'','calendar')->fetchSingle();
$this->db->delete($this->dates_table,array(
'cal_id' => $cal_id,
'cal_start > '.(int)$min,
),__LINE__,__FILE__,'calendar');
// delete all user-records, with recur-date != 0
$this->db->delete($this->user_table,array(
'cal_id' => $cal_id,
'cal_recur_date != 0',
),__LINE__,__FILE__,'calendar');
}
}
// update start- and endtime if present in the event-array, evtl. we need to move all recurrences
if (isset($event['cal_start']) && isset($event['cal_end']))
@ -634,17 +751,17 @@ ORDER BY cal_user_type, cal_usre_id
}
//pgoerzen: don't add an alarm if it is before the current date.
if ($event['recur_type'] && ($tmp_event = $this->read($eventID, time() + $alarm['offset'])))
/*if ($event['recur_type'] && ($tmp_event = $this->read($eventID, time() + $alarm['offset'])))
{
$alarm['time'] = $tmp_event['cal_start'] - $alarm['offset'];
}
} */
$this->save_alarm($cal_id,$alarm);
}
}
if (is_null($etag))
{
$etag = $this->db->select($this->cal_table,'cal_etag',array('cal_id' => $cal_id),__LINE__,__FILE__,false,'','calendar')->fetchSingle();
$etag = $this->db->select($this->cal_table,'cal_etag',array('cal_id' => $cal_id),__LINE__,__FILE__,false,'','calendar')->fetchColumn();
}
return $cal_id;
}
@ -655,10 +772,10 @@ ORDER BY cal_user_type, cal_usre_id
* @param int $cal_id
* @param int $start new starttime
* @param int $end new endtime
* @param int/boolean $change_since=0 false=new entry, > 0 time from which on the repetitions should be changed, default 0=all
* @param int|boolean $change_since=0 false=new entry, > 0 time from which on the repetitions should be changed, default 0=all
* @param int $old_start=0 old starttime or (default) 0, to query it from the db
* @param int $old_end=0 old starttime or (default) 0
* @return int/boolean number of moved recurrences or false on error
* @return int|boolean number of moved recurrences or false on error
*/
function move($cal_id,$start,$end,$change_since=0,$old_start=0,$old_end=0)
{
@ -713,7 +830,7 @@ ORDER BY cal_user_type, cal_usre_id
* @param string|int $user_id id
* @return string|int combined id
*/
function combine_user($user_type,$user_id)
static function combine_user($user_type,$user_id)
{
if (!$user_type || $user_type == 'u')
{
@ -729,7 +846,7 @@ ORDER BY cal_user_type, cal_usre_id
* @param string &$user_type 1-char type: 'u' = user, ...
* @param string|int &$user_id id
*/
function split_user($uid,&$user_type,&$user_id)
static function split_user($uid,&$user_type,&$user_id)
{
if (is_numeric($uid))
{
@ -743,23 +860,61 @@ ORDER BY cal_user_type, cal_usre_id
}
}
/**
* Combine status, quantity and role into one value
*
* @param string $status
* @param int $quantity=1
* @param string $role='REQ-PARTICIPANT'
* @return string
*/
static function combine_status($status,$quantity=1,$role='REQ-PARTICIPANT')
{
if ((int)$quantity > 1) $status .= (int)$quantity;
//if ($role != 'REQ-PARTICIPANT') $status .= $role;
return $status;
}
/**
* splits the combined status, quantity and role
*
* @param string &$status I: combined value, O: status letter: U, T, A, R
* @param int &$quantity only O: quantity
* @param string &$role only O: role
*/
static function split_status(&$status,&$quantity,&$role)
{
$quantity = 1;
$role = 'REQ-PARTICIPANT';
if (strlen($status) > 1 && preg_match('/^.([0-9]*)(.*)$/',$status,$matches))
{
if ((int)$matches[1] > 0) $quantity = (int)$matches[1];
if ($matches[2]) $role = $matches[2];
$status = $status[0];
}
}
/**
* updates the participants of an event, taken into account the evtl. recurrences of the event(!)
* this method just adds new participants or removes not longer set participants
* this method does never overwrite existing entries (except for delete)
*
* @param int $cal_id
* @param array $participants id => status pairs
* @param int/boolean $change_since=0 false=new entry, > 0 time from which on the repetitions should be changed, default 0=all
* @param int $recur_date=0 time of which repetitions should be updated, default 0=all
* @return int/boolean number of updated recurrences or false on error
* @param array $participants uid => status pairs
* @param int|boolean $change_since=0, false=new entry, 0=all, > 0 time from which on the repetitions should be changed
* @param boolean $add_only=false
* false = add AND delete participants if needed (full list of participants required in $participants)
* true = only add participants if needed, no participant will be deleted (participants to check/add required in $participants)
* @return int|boolean number of updated recurrences or false on error
*/
function participants($cal_id,$participants,$change_since=0)
function participants($cal_id,$participants,$change_since=0,$add_only=false)
{
//echo "<p>socal::participants($cal_id,".print_r($participants,true).",$change_since)</p>\n";
// remove group-invitations, they are NOT stored in the db
foreach($participants as $uid => $status)
{
if ($status == 'G')
if ($status[0] == 'G')
{
unset($participants[$uid]);
}
@ -768,27 +923,40 @@ ORDER BY cal_user_type, cal_usre_id
if ((int) $change_since)
{
$where[] = '(cal_recur_date=0 OR cal_recur_date >= '.(int)$change_since.')';
$where[0] = '(cal_recur_date=0 OR cal_recur_date >= '.(int)$change_since.')';
}
if ($change_since !== false) // existing entries only
if ($change_since !== false) // update existing entries
{
// delete not longer set participants
$deleted = array();
foreach($this->db->select($this->user_table,'DISTINCT cal_user_type,cal_user_id,cal_quantity',$where,
__LINE__,__FILE__,false,'','calendar') as $row)
$existing_entries = $this->db->select($this->user_table,'DISTINCT cal_user_type,cal_user_id',$where,__LINE__,__FILE__,false,'','calendar');
// create a full list of participants which already exist in the db
$old_participants = array();
foreach($existing_entries as $row)
{
$uid = $this->combine_user($row['cal_user_type'],$row['cal_user_id']);
if (!isset($participants[$uid])) // delete group-invitations
$old_participants[self::combine_user($row['cal_user_type'],$row['cal_user_id'])] = true;
}
// tag participants which should be deleted
if($add_only === false)
{
$deleted = array();
foreach($existing_entries as $row)
{
$deleted[$row['cal_user_type']][] = $row['cal_user_id'];
}
elseif($row['cal_quantity'] == (substr($participants[$uid],1) ? substr($participants[$uid],1) : 1))
{
unset($participants[$uid]); // we dont touch them
$uid = self::combine_user($row['cal_user_type'],$row['cal_user_id']);
// delete not longer set participants
if (!isset($participants[$uid]))
{
$deleted[$row['cal_user_type']][] = $row['cal_user_id'];
}
}
}
if (count($deleted))
// only keep added participants for further steps - we do not touch existing ones
$participants = array_diff_key($participants,$old_participants);
// delete participants tagged for delete
if ($add_only === false && count($deleted))
{
$to_or = array();
$table_def = $this->db->get_table_definitions('calendar',$this->user_table);
@ -799,14 +967,17 @@ ORDER BY cal_user_type, cal_usre_id
'cal_user_id' => $ids,
));
}
$this->db->delete($this->user_table,$where + array('('.implode(' OR ',$to_or).')'),__LINE__,__FILE__,'calendar');
$where[1] = '('.implode(' OR ',$to_or).')';
$this->db->delete($this->user_table,$where,__LINE__,__FILE__,'calendar');
unset($where[1]);
}
}
if (count($participants)) // these are NEW participants now
if (count($participants)) // participants which need to be added
{
// find all recurrences, as they all need the new parts to be added
$recurrences = array();
if ($change_since !== false) // existing entries only
if ($change_since !== false) // existing entries
{
foreach($this->db->select($this->user_table,'DISTINCT cal_recur_date',$where,__LINE__,__FILE__,false,'','calendar') as $row)
{
@ -815,15 +986,20 @@ ORDER BY cal_user_type, cal_usre_id
}
if (!count($recurrences)) $recurrences[] = 0; // insert the default one
// update participants
foreach($participants as $uid => $status)
{
$this->split_user($uid,$type,$id);
$id = null;
self::split_user($uid,$type,$id);
self::split_status($status,$quantity,$role);
$set = array(
'cal_status' => $status,
'cal_quantity' => $quantity,
'cal_role' => $role,
);
foreach($recurrences as $recur_date)
{
$this->db->insert($this->user_table,array(
'cal_status' => $status !== true ? $status{0} : 'U',
'cal_quantity' => substr($status,1) ? substr($status,1) : 1,
),array(
$this->db->insert($this->user_table,$set,array(
'cal_id' => $cal_id,
'cal_recur_date' => $recur_date,
'cal_user_type' => $type,
@ -841,11 +1017,12 @@ ORDER BY cal_user_type, cal_usre_id
* @param int $cal_id
* @param char $user_type 'u' regular user, 'r' resource, 'c' contact
* @param int $user_id
* @param int/char $status numeric status (defines) or 1-char code: 'R', 'U', 'T' or 'A'
* @param int|char $status numeric status (defines) or 1-char code: 'R', 'U', 'T' or 'A'
* @param int $recur_date=0 date to change, or 0 = all since now
* @param string $role=null role to set if !is_null($role)
* @return int number of changed recurrences
*/
function set_status($cal_id,$user_type,$user_id,$status,$recur_date=0)
function set_status($cal_id,$user_type,$user_id,$status,$recur_date=0,$role=null)
{
static $status_code_short = array(
REJECTED => 'R',
@ -873,15 +1050,20 @@ ORDER BY cal_user_type, cal_usre_id
{
$where[] = '(cal_recur_date=0 OR cal_recur_date >= '.time().')';
}
// check if the user has any status database entries and create the default set if needed
// a status update before having the necessary entries happens on e.g. group invitations
$this->participants($cal_id,array(self::combine_user($user_type,$user_id) => 'U'),0,true);
if ($status == 'G') // remove group invitations, as we dont store them in the db
{
$this->db->delete($this->user_table,$where,__LINE__,__FILE__,'calendar');
}
else
{
$this->db->insert($this->user_table,array(
'cal_status' => $status,
),$where,__LINE__,__FILE__,'calendar');
$set = array('cal_status' => $status);
if (!is_null($role)) $set['cal_role'] = $role;
$this->db->insert($this->user_table,$set,$where,__LINE__,__FILE__,'calendar');
}
$ret = $this->db->affected_rows();
//error_log(__METHOD__."($cal_id,$user_type,$user_id,$status,$recur_date) = $ret");
@ -910,9 +1092,11 @@ ORDER BY cal_user_type, cal_usre_id
{
if ($status == 'G') continue; // dont save group-invitations
$type = '';
$id = null;
$this->split_user($uid,$type,$id);
$this->db->insert($this->user_table,array(
'cal_status' => $status !== true ? $status{0} : 'U',
'cal_status' => $status !== true ? $status[0] : 'U',
'cal_quantity' => substr($status,1) ? substr($status,1) : 1,
),array(
'cal_id' => $cal_id,
@ -1011,9 +1195,10 @@ ORDER BY cal_user_type, cal_usre_id
*
* @param int $cal_id Id of the calendar-entry
* @param array $alarm array with fields: text, owner, enabled, ..
* @param timestamp $now_su=0 timestamp for modification of related event
* @return string id of the alarm
*/
function save_alarm($cal_id,$alarm)
function save_alarm($cal_id, $alarm, $now_su = 0)
{
//echo "<p>save_alarm(cal_id=$cal_id, alarm="; print_r($alarm); echo ")</p>\n";
if (!($id = $alarm['id']))
@ -1037,6 +1222,14 @@ ORDER BY cal_user_type, cal_usre_id
{
return False;
}
// update the modification information of the related event
$datetime = $GLOBALS['egw']->datetime;
$now = ($now_su ? $now_su : time() + $datetime->this->tz_offset);
$modifier = $GLOBALS['egw_info']['user']['account_id'];
$this->db->update($this->cal_table, array('cal_modified' => $now, 'cal_modifier' => $modifier),
array('cal_id' => $cal_id), __LINE__, __FILE__, 'calendar');
return $id;
}
@ -1061,10 +1254,21 @@ ORDER BY cal_user_type, cal_usre_id
* delete one alarms identified by its id
*
* @param string $id alarm-id is a string of 'cal:'.$cal_id.':'.$alarm_nr, it is used as the job-id too
* @param timestamp $now_su=0 timestamp for modification of related event
* @return int number of alarms deleted
*/
function delete_alarm($id)
function delete_alarm($id, $now_su = 0)
{
// update the modification information of the related event
list(,$cal_id) = explode(':',$id);
if ($cal_id)
{
$datetime = $GLOBALS['egw']->datetime;
$now = ($now_su ? $now_su : time() + $datetime->this->tz_offset);
$modifier = $GLOBALS['egw_info']['user']['account_id'];
$this->db->update($this->cal_table, array('cal_modified' => $now, 'cal_modifier' => $modifier),
array('cal_id' => $cal_id), __LINE__, __FILE__, 'calendar');
}
return $this->async->cancel_timer($id);
}
@ -1074,7 +1278,7 @@ ORDER BY cal_user_type, cal_usre_id
* @param array|int $old_user integer old user or array with keys 'account_id' and 'new_owner' as the deleteaccount hook uses it
* @param int $new_user=null
*/
function deleteaccount($data)
function deleteaccount($old_user, $newuser=null)
{
if (is_array($old_user))
{
@ -1083,6 +1287,8 @@ ORDER BY cal_user_type, cal_usre_id
}
if (!(int)$new_user)
{
$user_type = '';
$user_id = null;
$this->split_user($old_user,$user_type,$user_id);
if ($user_type == 'u') // only accounts can be owners of events
@ -1131,4 +1337,133 @@ ORDER BY cal_user_type, cal_usre_id
),__LINE__,__FILE__,'calendar');
}
}
/**
* get stati of all recurrences of an event for a specific participant
*
* @param int $cal_id
* @param int $uid participant uid
* @return array recur_date => status pairs (index 0 => main status)
*/
function get_recurrences($cal_id, $uid)
{
$user_type = $user_id = null;
$this->split_user($uid, $user_type, $user_id);
$participant_status = array();
$where = array('cal_id' => $cal_id);
foreach($this->db->select($this->user_table,'DISTINCT cal_recur_date',$where,__LINE__,__FILE__,false,'','calendar') as $row)
{
// inititalize the array
$participant_status[$row['cal_recur_date']] = null;
}
$where = array(
'cal_id' => $cal_id,
'cal_user_type' => $user_type ? $user_type : 'u',
'cal_user_id' => $user_id,
);
foreach ($this->db->select($this->user_table,'cal_recur_date,cal_status',$where,
__LINE__,__FILE__,false,'','calendar') as $row)
{
$participant_status[$row['cal_recur_date']] = $row['cal_status'];
}
return $participant_status;
}
/**
* get all participants of an event
*
* @param int $cal_id
* @param int $recur_date=0 gives participants of this recurrence, default 0=all
*
* @return array participants
*/
function get_participants($cal_id, $recur_date=0)
{
$participants = array();
$where = array('cal_id' => $cal_id);
if ($recur_date)
{
$where['cal_recur_date'] = $recur_date;
}
foreach ($this->db->select($this->user_table,'DISTINCT cal_user_type,cal_user_id', $where,
__LINE__,__FILE__,false,'','calendar') as $row)
{
$uid = $this->combine_user($row['cal_user_type'], $row['cal_user_id']);
$id = $row['cal_user_type'] . $row['cal_user_id'];
$participants[$id]['type'] = $row['cal_user_type'];
$participants[$id]['id'] = $row['cal_user_id'];
$participants[$id]['uid'] = $uid;
}
return $participants;
}
/**
* get all releated events
*
* @param int $uid UID of the series
*
* @return array of event exception ids for all events which share $uid
*/
function get_related($uid)
{
$where = array(
'cal_uid' => $uid,
);
$related = array();
foreach ($this->db->select($this->cal_table,'cal_id,cal_reference',$where,
__LINE__,__FILE__,false,'','calendar') as $row)
{
if ($row['cal_reference'])
{
// not the series entry itself
$related[$row['cal_id']] = $row['cal_reference'];
}
}
return $related;
}
/**
* Gets the exception days of a given recurring event caused by
* irregular participant stati
*
* @param array $event Recurring Event.
*
* @return array Array of exception days (false for non-recurring events).
*/
function get_recurrence_exceptions(&$event)
{
$cal_id = (int) $event['id'];
if (!$cal_id || $event['recur_type'] == MCAL_RECUR_NONE) return false;
$days = array();
$participants = $this->get_participants($event['id'], 0);
// Check if the stati for all participants are identical for all recurrences
foreach ($participants as $uid => $attendee)
{
switch ($attendee['type'])
{
case 'u': // account
case 'c': // contact
case 'e': // email address
$recurrences = $this->get_recurrences($event['id'], $uid);
foreach ($recurrences as $recur_date => $recur_status)
{
if ($recur_date && $recur_status != $recurrences[0])
{
// Every distinct status results in an exception
$days[] = $recur_date;
}
}
break;
default: // We don't handle the rest
break;
}
}
$days = array_unique($days);
sort($days);
return $days;
}
}

View File

@ -123,7 +123,7 @@ class calendar_ui
/**
* @var array $states_to_save all states that will be saved to the user prefs
*/
var $states_to_save = array('owner');
var $states_to_save = array('owner','filter');
/**
* Constructor
@ -188,13 +188,13 @@ class calendar_ui
foreach($GLOBALS['egw']->accounts->member($owner) as $member)
{
$member = $member['account_id'];
if (!$this->bo->check_perms(EGW_ACL_READ|EGW_ACL_READ_FOR_PARTICIPANTS,0,$member))
if (!$this->bo->check_perms(EGW_ACL_READ|EGW_ACL_READ_FOR_PARTICIPANTS|EGW_ACL_FREEBUSY,0,$member))
{
$no_access_group[$member] = $this->bo->participant_name($member);
}
}
}
elseif (!$this->bo->check_perms(EGW_ACL_READ|EGW_ACL_READ_FOR_PARTICIPANTS,0,$owner))
elseif (!$this->bo->check_perms(EGW_ACL_READ|EGW_ACL_READ_FOR_PARTICIPANTS|EGW_ACL_FREEBUSY,0,$owner))
{
$no_access[$owner] = $this->bo->participant_name($owner);
}
@ -247,7 +247,7 @@ class calendar_ui
* - filter: the used filter: all or hideprivate
* - sortby: category or user of planner
* - view: the actual view, where dialogs should return to or which they refresh
* @param set_states array to manualy set / change one of the states, default NULL = use $_REQUEST
* @param array $set_states array to manualy set / change one of the states, default NULL = use $_REQUEST
*/
function manage_states($set_states=NULL)
{
@ -381,16 +381,19 @@ class calendar_ui
// save defined states into the user-prefs
if(!empty($states) && is_array($states))
{
$saved_states = array_intersect_key($states,array_flip($this->states_to_save));
$GLOBALS['egw']->preferences->add('calendar','saved_states',serialize($saved_states));
$GLOBALS['egw']->preferences->save_repository(false,'user',false);
$saved_states = serialize(array_intersect_key($states,array_flip($this->states_to_save)));
if ($saved_states != $this->cal_prefs['saved_states'])
{
$GLOBALS['egw']->preferences->add('calendar','saved_states',$saved_states);
$GLOBALS['egw']->preferences->save_repository(false,'user',false);
}
}
}
/**
* gets the icons displayed for a given event
*
* @param $event array
* @param array $event
* @return array of 'img' / 'title' pairs
*/
function event_icons($event)
@ -509,8 +512,8 @@ class calendar_ui
function popup($link,$target='_blank',$width=750,$height=410,$Link_confirm_abort='',$Link_confirm_text='')
{
//Handle Exception for Calandar
if (($Link_confirm_abort) && ($Link_confirm_text))
{
if ($Link_confirm_abort && $Link_confirm_text)
{
$returnvalue = 'javascript:var check=confirm(\''.$Link_confirm_text.'\');';
$returnvalue .=' if (check==true) {';
// open confirm =0kay
@ -524,14 +527,9 @@ class calendar_ui
$returnvalue .= '}';
return $returnvalue;
}
else {
return 'egw_openWindowCentered2('.($link == 'this.href' ? $link : "'".$link."'").','.
($target == 'this.target' ? $target : "'".$target."'").",$width,$height,'yes')";
}
return 'egw_openWindowCentered2('.($link == 'this.href' ? $link : "'".$link."'").','.
($target == 'this.target' ? $target : "'".$target."'").",$width,$height,'yes')";
}
/**
@ -692,10 +690,21 @@ class calendar_ui
$this->cats->formatted_list('select','all',$this->cat_id,'True'),$baseurl ? $baseurl.'&cat_id=' : '');
// Filter all or hideprivate
$file[] = $this->_select_box('Filter','filter',
'<option value="all"'.($this->filter=='all'?' selected="selected"':'').'>'.lang('No filter').'</option>'."\n".
'<option value="hideprivate"'.($this->filter=='hideprivate'?' selected="selected"':'').'>'.lang('Hide private infos').'</option>'."\n",
$baseurl ? $baseurl.'&filter=' : '');
$options = '';
foreach(array(
'default' => lang('Not rejected'),
'accepted' => lang('Accepted'),
'unknown' => lang('Invitations'),
'tentative' => lang('Tentative'),
'rejected' => lang('Rejected'),
'owner' => lang('Owner too'),
'all' => lang('All incl. rejected'),
'hideprivate' => lang('Hide private infos'),
) as $value => $label)
{
$options .= '<option value="'.$value.'"'.($this->filter == $value ? ' selected="selected"' : '').'>'.$label.'</options>'."\n";
}
$file[] = $this->_select_box('Filter','filter',$options,$baseurl ? $baseurl.'&filter=' : '');
// Calendarselection: User or Group
if(count($this->bo->grants) > 0 && $this->accountsel->account_selection != 'none')

View File

@ -25,7 +25,7 @@
class calendar_uiforms extends calendar_ui
{
var $public_functions = array(
'freetimesearch' => True,
'freetimesearch' => true,
'edit' => true,
'export' => true,
'import' => true,
@ -52,6 +52,15 @@ class calendar_uiforms extends calendar_ui
*/
var $locktime_default=1;
/**
* Set Logging
*
* @var boolean
*/
var $log = false;
var $logfile="/tmp/calendar-edit";
/**
* Constructor
*/
@ -63,6 +72,7 @@ class calendar_uiforms extends calendar_ui
{
$this->durations[$n*60] = sprintf('%d:%02d',$n/60,$n%60);
}
if ($this->log) $this->logfile = $GLOBALS['egw_info']['server']['temp_dir'].'/calendar-edit';
}
/**
@ -123,19 +133,17 @@ class calendar_uiforms extends calendar_ui
if (is_numeric($uid))
{
$participants[$uid] = $participant_types['u'][$uid] = $uid == $this->user ? 'A' : 'U';
$participants[$uid] = $participant_types['u'][$uid] =
calendar_so::combine_status($uid == $this->user ? 'A' : 'U',1,$uid == $this->user ? 'CHAIR' : 'REQ-PARTICIPANT');
}
elseif (is_array($this->bo->resources[$uid[0]]))
{
$res_data = $this->bo->resources[$uid[0]];
list($id,$quantity) = explode(':',substr($uid,1));
$participants[$uid] = $participant_types[$uid[0]][$id] = ($res_data['new_status'] ? ExecMethod($res_data['new_status'],$id) : 'U').
((int) $quantity > 1 ? (int)$quantity : '');
// if new_status == 'x', resource is not bookable
if(strpos($participant_types[$uid[0]][$id],'x') !== false)
if (($status = $res_data['new_status'] ? ExecMethod($res_data['new_status'],$id) : 'U'))
{
unset($participant_types[$uid[0]][$id]);
unset($participants[$uid]);
$participants[$uid] = $participant_types[$uid[0]][$id] =
calendar_so::combine_status($status,$quantity,'REQ-PARTICIPANT');
}
}
}
@ -158,6 +166,7 @@ class calendar_uiforms extends calendar_ui
*/
function process_edit($content)
{
if ($this->log) error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n".array2string($content)."\n",3,$this->logfile);
$referer=$this->view_menuaction;
list($button) = @each($content['button']);
if (!$button && $content['action']) $button = $content['action']; // action selectbox
@ -233,12 +242,14 @@ class calendar_uiforms extends calendar_ui
$event['recur_data'] = 1 << (int)date('w',$event['start']);
}
$event['participants'] = $event['participant_types'] = array();
foreach($content['participants'] as $key => $data)
{
switch($key)
{
case 'delete': // handled in default
case 'quantity': // handled in new_resource
case 'role': // handled in add, account or resource
case 'cal_resources':
break;
@ -248,21 +259,22 @@ class calendar_uiforms extends calendar_ui
if (($email = $_POST['exec']['participants']['resource']['query']) &&
(preg_match('/^(.*<)?([a-z0-9_.-]+@[a-z0-9_.-]{5,})>?$/i',$email,$matches)))
{
$status = calendar_so::combine_status('U',$content['participants']['quantity'],$content['participants']['role']);
// check if email belongs to account or contact --> prefer them over just emails
if (($data = $GLOBALS['egw']->accounts->name2id($matches[2],'account_email')))
{
$event['participants'][$data] = $event['participant_types']['u'][$data] = 'U';
$event['participants'][$data] = $event['participant_types']['u'][$data] = $status;
}
elseif ((list($data) = ExecMethod2('addressbook.addressbook_bo.search',array(
'email' => $matches[2],
'email_home' => $matches[2],
),true,'','','',false,'OR')))
{
$event['participants']['c'.$data['id']] = $event['participant_types']['c'][$data['id']] = 'U';
$event['participants']['c'.$data['id']] = $event['participant_types']['c'][$data['id']] = $status;
}
else
{
$event['participants']['e'.$email] = $event['participant_types']['e'][$email] = 'U';
$event['participants']['e'.$email] = $event['participant_types']['e'][$email] = $status;
}
}
elseif (!$content['participants']['account'] && !$content['participants']['resource'])
@ -305,7 +317,7 @@ class calendar_uiforms extends calendar_ui
break;
}
}
if ($uid && $id)
if ($uid && $id)
{
$status = isset($this->bo->resources[$type]['new_status']) ? ExecMethod($this->bo->resources[$type]['new_status'],$id) : 'U';
$event['participants'][$uid] = $event['participant_types'][$type][$id] = $status.((int) $quantity > 1 ? (int)$quantity : '');
@ -320,16 +332,20 @@ class calendar_uiforms extends calendar_ui
case 'account':
foreach(is_array($data) ? $data : explode(',',$data) as $uid)
{
if ($uid) $event['participants'][$uid] = $event['participant_types']['u'][$uid] =
$uid == $this->bo->user ? 'A' : 'U';
if ($uid)
{
$event['participants'][$uid] = $event['participant_types']['u'][$uid] =
calendar_so::combine_status($uid == $this->bo->user ? 'A' : 'U',1,$content['participants']['role']);
}
}
break;
default: // existing participant row
foreach(array('uid','status','status_recurrence','quantity') as $name)
foreach(array('uid','status','status_recurrence','quantity','role') as $name)
{
$$name = $data[$name];
}
if (!$status) $status = $data['old_status']; // fix etemplate bug
if ($content['participants']['delete'][$uid])
{
$uid = false; // entry has been deleted
@ -348,10 +364,33 @@ class calendar_uiforms extends calendar_ui
}
if ($data['old_status'] != $status)
{
if ($this->bo->set_status($event['id'],$uid,$status,$event['recur_type'] != MCAL_RECUR_NONE && !$status_recurrence ? $content['participants']['status_date'] : 0))
if ($content['edit_single'] ||
$event['recur_type'] != MCAL_RECUR_NONE && !$status_recurrence )
{
$recur_date = $content['participants']['status_date'];
}
else $recur_date = 0;
if ($this->bo->set_status($event['id'],$uid,$status,$recur_date))
{
// refreshing the calendar-view with the changed participant-status
$msg = lang('Status changed');
if($event['recur_type'] != MCAL_RECUR_NONE)
{
$msg = lang('Status for all future scheduled days changed');
}
else
{
if(isset($content['edit_single']))
{
$msg = lang('Status for this particular day changed');
// prevent accidentally creating a real exception afterwards
$view = true;
$hide_delete = true;
}
else
{
$msg = lang('Status changed');
}
}
if (!$preserv['no_popup'])
{
$js = 'opener.location.href=\''.addslashes($GLOBALS['egw']->link('/index.php',array(
@ -364,7 +403,7 @@ class calendar_uiforms extends calendar_ui
if ($uid && $status != 'G')
{
$event['participants'][$uid] = $event['participant_types'][$type][$id] =
$status.((int) $quantity > 1 ? (int)$quantity : '');
calendar_so::combine_status($status,$quantity,$role);
}
}
break;
@ -373,6 +412,7 @@ class calendar_uiforms extends calendar_ui
}
$preserv = array(
'view' => $view,
'hide_delete' => $hide_delete,
'edit_single' => $content['edit_single'],
'actual_date' => $content['actual_date'],
'referer' => $referer,
@ -408,21 +448,38 @@ class calendar_uiforms extends calendar_ui
// fall through
case 'mail':
case 'save':
//case 'print':
case 'apply':
if ($event['id'] && !$this->bo->check_perms(EGW_ACL_EDIT,$event))
unset($old_event);
if ($event['id'])
{
switch ($button)
$old_event = $this->bo->read($event['id']);
if (!$this->bo->check_perms(EGW_ACL_EDIT, $old_event))
{
case 'mail': // just mail without edit-rights is ok
if ($button == 'mail') // just mail without edit-rights is ok
{
$js = $this->custom_mail($event,false);
break 2;
case 'print': // just print without edit-rights is ok
break;
}
if ($button == 'print') // just print without edit-rights is ok
{
$js = $this->custom_print($event,false);
break 2;
break;
}
// now we can handle a possible status update
$uid = $this->bo->user;
$recur_date = ($content['edit_single'] ? $content['edit_single'] : 0);
$old_status = $this->bo->so->get_recurrences($event['id'], $uid);
if (isset($old_status[$recur_date]) && $old_status[$recur_date] == $event['participants'][$uid]) break;
$msg = lang('Status updated');
$this->bo->set_status($event['id'], $uid, $event['participants'][$uid][0], $recur_date);
$js = 'opener.location.href=\''.addslashes($GLOBALS['egw']->link('/index.php',array(
'menuaction' => $referer,
'msg' => $msg,
))).'\';';
break;
}
$msg = lang('Permission denied');
$button = '';
break;
}
if ($event['start'] > $event['end'])
{
@ -457,30 +514,99 @@ class calendar_uiforms extends calendar_ui
}
if ($content['edit_single']) // we edited a single event from a series
{
$event['reference'] = $event['id'];
unset($event['id']);
unset($event['uid']);
$conflicts = $this->bo->update($event,$ignore_conflicts);
if (!is_array($conflicts) && $conflicts)
// let's compare with the original instance
$old_event = $this->bo->read($event['id'], $content['edit_single']);
$event['non_blocking'] = empty($event['non_blocking']) ? 0: $event['non_blocking'];
$categories = $event['category'];
$event['category'] = implode(',', $event['category']);
$participants = $event['participants']; // save the status information
$unchanged = true;
foreach (array('start','end','owner','title','description','category',
'alarm','location','priority','public','special','non_blocking') as $key)
{
//error_log('edit_event test ' . $key . ': '. $old_event[$key] . ' == ' .$event[$key]);
if ((isset($old_event[$key]) || isset($event[$key]))
&& $old_event[$key] != $event[$key]) $unchanged = false;
}
// only update the stati of the exception
if ($unchanged)
{
foreach ($participants as $uid => $status)
{
if (!isset($old_event['participants'][$uid])
|| !$old_event['participants'][$uid]
|| $old_event['participants'][$uid][0] != $status)
{
$this->bo->set_status($old_event['id'], $uid, $status, $content['edit_single']);
$unchanged = false;
}
unset($old_event['participants'][$uid]);
}
foreach ($old_event['participants'] as $uid => $status)
{
// delete the removed participants
$this->bo->set_status($old_event['id'], $uid, 'G', $content['edit_single']);
$unchanged = false;
}
if (!$unchanged)
{
$msg = lang('Status updated');
$js = 'opener.location.href=\''.addslashes($GLOBALS['egw']->link('/index.php',array(
'menuaction' => $referer,
'msg' => $msg,
))).'\';';
}
break;
}
// now we make a true exception
$event['category'] = $categories;
$event['reference'] = $content['edit_single'];
$id = 1;
$alarms = array();
foreach ($event['alarm'] as $alarm)
{
// get a temporary non-conflicting, numeric id
$alarm['id'] = $id;
$alarm['time'] = $event['start'] - $alarm['offset'];
unset($alarm['cal_id']);
$alarms[$id] = $alarm;
$id++;
}
$event['alarm'] = $alarms;
unset($event['id']);
$conflicts = $this->bo->update($event, $ignore_conflicts);
if (!is_array($conflicts) && $conflicts ||
count($conflicts) == 1 && ($conflict = array_pop($conflicts))
&& $conflict['id'] == $old_event['id'])
{
if (is_array($conflicts))
{
$this->bo->update($event, true);
}
// now we need to add the original start as recur-execption to the series
$recur_event = $this->bo->read($event['reference']);
$recur_event['recur_exception'][] = $content['edit_single'];
$recur_event = $this->bo->read($old_event['id']);
$recur_event['recur_exception'][] = $event['reference'];
unset($recur_event['start']); unset($recur_event['end']); // no update necessary
$this->bo->update($recur_event,true); // no conflict check here
$this->bo->update($recur_event, true); // no conflict check here
unset($recur_event);
unset($event['edit_single']); // if we further edit it, it's just a single event
unset($preserv['edit_single']);
$conflicts = $event['id'];
unset($old_event);
}
else // conflict or error, we need to reset everything to the state befor we tried to save it
{
$event['id'] = $event['reference'];
$event['id'] = $old_event['id'];
$event['uid'] = $old_event['uid'];
unset($event['reference']);
$event['uid'] = $content['uid'];
}
}
else // we edited a non-reccuring event or the whole series
{
$participants = $event['participants']; // save the status information
$conflicts = $this->bo->update($event,$ignore_conflicts);
unset($event['ignore']);
}
@ -489,7 +615,7 @@ class calendar_uiforms extends calendar_ui
$event['button_was'] = $button; // remember for ignore
return $this->conflicts($event,$conflicts,$preserv);
}
elseif ($conflicts ===0)
elseif ($conflicts === 0)
{
$msg .= ($msg ? ', ' : '') .lang('Error: the entry has been updated since you opened it for editing!').'<br />'.
lang('Copy your changes to the clipboard, %1reload the entry%2 and merge them.','<a href="'.
@ -501,8 +627,53 @@ class calendar_uiforms extends calendar_ui
$noerror=false;
}
elseif ($conflicts>0)
elseif ($conflicts > 0)
{
if (isset($old_event))
{
if (is_array($old_event['alarm']))
{
// remove deleted alarms
foreach ($old_event['alarm'] as $id => $alarm)
{
if (!isset($content['alarm'][$id]) ||
$alarm != $content['alarm'][$id])
{
$this->bo->delete_alarm($id);
}
}
}
// Update the stati
foreach ($participants as $uid => $status)
{
if (!isset($old_event['participants'][$uid])
|| !$old_event['participants'][$uid]
|| $old_event['participants'][$uid][0] != $status)
{
$this->bo->set_status($old_event['id'], $uid, $status, $content['edit_single']);
}
unset($old_event['participants'][$uid]);
}
foreach ($old_event['participants'] as $uid => $status)
{
// delete the removed participants
$this->bo->set_status($old_event['id'], $uid, 'G', $content['edit_single']);
}
if ($event['recur_type'] != MCAL_RECUR_NONE)
{
// remove deleted exception
$recur_exceptions = $this->bo->so->get_related($event['uid']);
foreach ($recur_exceptions as $id => $recur_date)
{
if (!in_array($recur_date, $event['recur_exception']))
{
$this->bo->delete($id);
}
}
}
}
$msg .= ($msg ? ', ' : '') . lang('Event saved');
// writing links for new entry, existing ones are handled by the widget itself
@ -515,6 +686,10 @@ class calendar_uiforms extends calendar_ui
'msg' => $msg,
))).'\';';
if ($button == 'print')
{
$js = $this->custom_print($event,!$content['id'])."\n".$js; // first open the new window and then update the view
}
if ($button == 'mail')
{
$js = $this->custom_mail($event,!$content['id'])."\n".$js; // first open the new window and then update the view
@ -527,7 +702,7 @@ class calendar_uiforms extends calendar_ui
break;
case 'cancel':
if($content['cancel_needs_refresh'])
if ($content['cancel_needs_refresh'])
{
$js = 'opener.location.href=\''.addslashes($GLOBALS['egw']->link('/index.php',array(
'menuaction' => $referer,
@ -539,6 +714,15 @@ class calendar_uiforms extends calendar_ui
case 'delete':
if ($this->bo->delete($event['id'],(int)$content['edit_single']))
{
if (!$content['edit_single'])
{
// We may delete a whole series
$recur_exceptions = $this->bo->so->get_related($event['uid']);
foreach ($recur_exceptions as $id => $recur_date)
{
$this->bo->delete($id);
}
}
$msg = lang('Event deleted');
$js = 'opener.location.href=\''.addslashes($GLOBALS['egw']->link('/index.php',array(
'menuaction' => $referer,
@ -607,7 +791,7 @@ class calendar_uiforms extends calendar_ui
}
$js .= 'window.close();';
echo "<html><body onload=\"$js\"></body></html>\n";
$GLOBALS['egw']->common->egw_exit();
common::egw_exit();
}
return $this->edit($event,$preserv,$msg,$js,$event['id'] ? $event['id'] : $content['link_to']['to_id']);
}
@ -646,6 +830,8 @@ class calendar_uiforms extends calendar_ui
foreach($event['participants'] as $uid => $status)
{
$toadd = '';
if ($status == 'R' || $uid == $this->user) continue;
if (is_numeric($uid) && $GLOBALS['egw']->accounts->get_type($uid) == 'u')
@ -654,7 +840,8 @@ class calendar_uiforms extends calendar_ui
$GLOBALS['egw']->accounts->get_account_name($uid,$lid,$firstname,$lastname);
$to[] = $firstname.' '.$lastname.' <'.$email.'>';
$toadd = $firstname.' '.$lastname.' <'.$email.'>';
if (!in_array($toadd,$to)) $to[] = $toadd;
}
elseif ($uid < 0)
{
@ -664,7 +851,9 @@ class calendar_uiforms extends calendar_ui
$GLOBALS['egw']->accounts->get_account_name($uid,$lid,$firstname,$lastname);
$to[] = $firstname.' '.$lastname.' <'.$email.'>';
$toadd = $firstname.' '.$lastname.' <'.$email.'>';
// dont add groupmembers if they already rejected the event, or are the current user
if (!in_array($toadd,$to) && ($event['participants'][$uid] !== 'R' && $uid != $this->user)) $to[] = $toadd;
}
}
elseif(($info = $this->bo->resource_info($uid)))
@ -675,7 +864,7 @@ class calendar_uiforms extends calendar_ui
list($subject,$body) = $this->bo->get_update_message($event,$added ? MSG_ADDED : MSG_MODIFIED); // update-message is in TZ of the user
$boical = new calendar_ical();
$ics = $boical->exportVCal(array($event),'2.0','request',false);
$ics = $boical->exportVCal(array($event),'2.0','REQUEST');
$ics_file = tempnam($GLOBALS['egw_info']['server']['temp_dir'],'ics');
if(($f = fopen($ics_file,'w')))
@ -696,6 +885,23 @@ class calendar_uiforms extends calendar_ui
return "window.open('".$GLOBALS['egw']->link('/index.php',$vars)."','_blank','width=700,height=700,scrollbars=yes,status=no');";
}
/**
* return javascript to open compose window to print the event
*
* @param array $event
* @param boolean $added
* @return string javascript window.open command
*/
function custom_print($event,$added)
{
$vars = array(
'menuaction' => 'calendar.calendar_uiforms.edit',
'cal_id' => $event['id'],
'print' => true,
);
return "window.open('".$GLOBALS['egw']->link('/index.php',$vars)."','_blank','width=700,height=700,scrollbars=yes,status=no');";
}
/**
* Edit a calendar event
*
@ -711,7 +917,8 @@ class calendar_uiforms extends calendar_ui
*/
function edit($event=null,$preserv=null,$msg='',$js = 'window.focus();',$link_to_id='')
{
$etpl =& CreateObject('etemplate.etemplate','calendar.edit');
$template = $_REQUEST['print'] ? 'calendar.print' : 'calendar.edit';
$etpl =& CreateObject('etemplate.etemplate',$template);
$sel_options = array(
'recur_type' => &$this->bo->recur_types,
'status' => $this->bo->verbose_status,
@ -719,6 +926,7 @@ class calendar_uiforms extends calendar_ui
'action' => array(
'copy' => array('label' => 'Copy', 'title' => 'Copy this event'),
'ical' => array('label' => 'Export', 'title' => 'Download this event as iCal'),
//'print' => array('label' => 'Print', 'title' => 'Print this event'),
'mail' => array('label' => 'Mail all participants', 'title' => 'compose a mail to all participants after the event is saved'),
),
'status_recurrence' => array('' => 'for this event', 'A' => 'for all future events'),
@ -766,9 +974,7 @@ class calendar_uiforms extends calendar_ui
if(isset($_GET['start'])) { $event['start'] = $_GET['start']; }
if(isset($_GET['end'])) { $event['end'] = $_GET['end']; }
// check if the event is the whole day
$start = $this->bo->date2array($event['start']);
$end = $this->bo->date2array($event['end']);
$event['whole_day'] = !$start['hour'] && !$start['minute'] && $end['hour'] == 23 && $end['minute'] == 59;
$event['whole_day'] = $this->bo->isWholeDay($event);
$link_to_id = $event['id'];
if (!$add_link && !$event['id'] && isset($_GET['link_app']) && isset($_GET['link_id']) &&
@ -874,7 +1080,11 @@ class calendar_uiforms extends calendar_ui
'uid' => $member,
'status' => 'G',
);
// read access is enought to invite participants, but you edit rights to change status
$readonlys[$row.'[quantity]'] = $readonlys["delete[$member]"] = true;
$content['participants'][$row++]['title'] =
$GLOBALS['egw']->common->grab_owner_name($member);
// read access is enought to invite participants
// but you edit rights to change status
if (!$this->bo->check_perms(EGW_ACL_EDIT,0,$member))
{
$readonlys[$row.'[quantity]'] = $readonlys["delete[$member]"] =$readonlys[$row]['status']= true;
@ -883,7 +1093,6 @@ class calendar_uiforms extends calendar_ui
{
$readonlys[$row.'[quantity]'] = $readonlys["delete[$member]"] = true;
}
$content['participants'][$row++]['title'] = $GLOBALS['egw']->common->grab_owner_name($member);
}
}
}
@ -970,7 +1179,18 @@ class calendar_uiforms extends calendar_ui
// the call to set_style_by_class has to be in onload, to make sure the function and the element is already created
$GLOBALS['egw']->js->set_onload("set_style_by_class('table','end_hide','visibility','".($content['duration'] && isset($sel_options['duration'][$content['duration']]) ? 'hidden' : 'visible')."');");
$readonlys['recur_exception'] = !count($content['recur_exception']); // otherwise we get a delete button
if ($content['reference'])
{
// recurrence exceptions can not be recurring events
foreach (array('recur_enddate','recur_interval','recur_exception','recur_type','recur_date','recur_data') as $name)
{
$readonlys[$name] = true;
}
}
else
{
$readonlys['recur_exception'] = !count($content['recur_exception']); // otherwise we get a delete button
}
}
// disabling the custom fields tab, if there are none
$readonlys[$this->tabs] = array(
@ -1107,7 +1327,7 @@ class calendar_uiforms extends calendar_ui
*/
function freetimesearch($content = null)
{
$etpl =& CreateObject('etemplate.etemplate','calendar.freetimesearch');
$etpl = new etemplate('calendar.freetimesearch');
$sel_options['search_window'] = array(
7*DAY_s => lang('one week'),
@ -1412,15 +1632,17 @@ class calendar_uiforms extends calendar_ui
/**
* Export events as vCalendar version 2.0 files (iCal)
*
* @param int/array $content=0 numeric cal_id or submitted content from etempalte::exec
* @param int|array $content=0 numeric cal_id or submitted content from etempalte::exec
* @param boolean $return_error=false should an error-msg be returned or a regular page with it generated (default)
* @return string error-msg if $return_error
*/
function export($content=0,$return_error=false)
{
$boical = new calendar_ical();
#error_log(__METHOD__.print_r($content,true));
if (is_numeric($cal_id = $content ? $content : $_REQUEST['cal_id']))
{
if (!($ical =& ExecMethod2('calendar.calendar_ical.exportVCal',$cal_id,'2.0','PUBLISH',false)))
if (!($ical =& $boical->exportVCal(array($cal_id),'2.0','PUBLISH')))
{
$msg = lang('Permission denied');
@ -1428,9 +1650,9 @@ class calendar_uiforms extends calendar_ui
}
else
{
$GLOBALS['egw']->browser->content_header('event.ics','text/calendar',bytes($ical));
html::content_header('event.ics','text/calendar',bytes($ical));
echo $ical;
$GLOBALS['egw']->common->egw_exit();
common::egw_exit();
}
}
if (is_array($content))
@ -1449,10 +1671,10 @@ class calendar_uiforms extends calendar_ui
}
else
{
$ical =& ExecMethod2('calendar.calendar_ical.exportVCal',$events,'2.0'/*$content['version']*/,'PUBLISH',false);
$GLOBALS['egw']->browser->content_header($content['file'] ? $content['file'] : 'event.ics','text/calendar',bytes($ical));
$ical =& $boical->exportVCal($events,'2.0','PUBLISH');
html::content_header($content['file'] ? $content['file'] : 'event.ics','text/calendar',bytes($ical));
echo $ical;
$GLOBALS['egw']->common->egw_exit();
common::egw_exit();
}
}
if (!is_array($content))
@ -1469,7 +1691,7 @@ class calendar_uiforms extends calendar_ui
$content['msg'] = $msg;
$GLOBALS['egw_info']['flags']['app_header'] = lang('calendar') . ' - ' . lang('iCal Export');
$etpl =& CreateObject('etemplate.etemplate','calendar.export');
$etpl = new etemplate('calendar.export');
$etpl->exec('calendar.calendar_uiforms.export',$content);
}
@ -1503,8 +1725,8 @@ class calendar_uiforms extends calendar_ui
'msg' => $msg,
);
$GLOBALS['egw_info']['flags']['app_header'] = lang('calendar') . ' - ' . lang('iCal Import');
$etpl =& CreateObject('etemplate.etemplate','calendar.import');
$etpl = new etemplate('calendar.import');
$etpl->exec('calendar.calendar_uiforms.import',$content);
}
}
}

View File

@ -5,7 +5,7 @@
* @link http://www.egroupware.org
* @package calendar
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2005-8 by RalfBecker-At-outdoor-training.de
* @copyright (c) 2005-9 by RalfBecker-At-outdoor-training.de
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$
*/
@ -82,7 +82,7 @@ class calendar_uilist extends calendar_ui
'multiple' => 0,
'view' => $this->bo->cal_prefs['defaultcalendar'],
));
$GLOBALS['egw']->session->appsession('calendar_list','calendar',''); // in case there's already something set
egw_session::appsession('calendar_list','calendar',''); // in case there's already something set
return $this->listview(null,'',true);
}
@ -95,7 +95,7 @@ class calendar_uilist extends calendar_ui
if ($_GET['msg']) $msg .= $_GET['msg'];
if ($this->group_warning) $msg .= $this->group_warning;
$etpl =& CreateObject('etemplate.etemplate','calendar.list');
$etpl = new etemplate('calendar.list');
if (is_array($content) && $content['nm']['rows']['delete'])
{
@ -120,7 +120,7 @@ class calendar_uilist extends calendar_ui
}
}
$content = array(
'nm' => $GLOBALS['egw']->session->appsession('calendar_list','calendar'),
'nm' => egw_session::appsession('calendar_list','calendar'),
'msg' => $msg,
);
if (!is_array($content['nm']))
@ -201,7 +201,7 @@ class calendar_uilist extends calendar_ui
$this->manage_states(array('date' => $this->bo->date2string($params['startdate'])));
}
}
$old_params = $GLOBALS['egw']->session->appsession('calendar_list','calendar');
$old_params = egw_session::appsession('calendar_list','calendar');
if ($old_params['filter'] && $old_params['filter'] != $params['filter']) // filter changed => order accordingly
{
$params['order'] = 'cal_start';
@ -211,8 +211,18 @@ class calendar_uilist extends calendar_ui
{
$this->adjust_for_search($params['search'],$params);
}
$GLOBALS['egw']->session->appsession('calendar_list','calendar',$params);
if (!$params['csv_export']) egw_session::appsession('calendar_list','calendar',$params);
// do we need to query custom fields and which
$select_cols = explode(',',$params['selectcols']);
if (in_array('cfs',$select_cols))
{
$cfs = array();
foreach($select_cols as $col)
{
if ($col[0] == '#') $cfs[] = substr($col,1);
}
}
$search_params = array(
'cat_id' => $this->cat_id,
'filter' => $this->filter,
@ -220,6 +230,7 @@ class calendar_uilist extends calendar_ui
'offset' => (int) $params['start'],
'num_rows'=> $params['num_rows'],
'order' => $params['order'] ? $params['order'].' '.$params['sort'] : 'cal_start',
'cfs' => $params['csv_explort'] ? array() : $cfs,
);
switch($params['filter'])
{
@ -289,11 +300,19 @@ class calendar_uilist extends calendar_ui
$readonlys['view['.$event['id'].']'] = !($readonlys['edit['.$event['id'].']'] = !$this->bo->check_perms(EGW_ACL_EDIT,$event));
$readonlys['delete['.$event['id'].']'] = !$this->bo->check_perms(EGW_ACL_DELETE,$event);
$event['parts'] = implode(",\n",$this->bo->participants($event,true));
$event['recure'] = $this->bo->recure2string($event);
$event['date'] = $this->bo->date2string($event['start']);
if ($params['csv_export'])
{
$event['participants'] = implode(",\n",$this->bo->participants($event,true));
}
else
{
$event['parts'] = implode(",\n",$this->bo->participants($event,true));
$event['date'] = $this->bo->date2string($event['start']);
}
if (empty($event['description'])) $event['description'] = ' '; // no description screws the titles horz. alignment
if (empty($event['location'])) $event['location'] = ' '; // no location screws the owner horz. alignment
$rows[] = $event;
}
$wv=0;
@ -310,7 +329,7 @@ class calendar_uilist extends calendar_ui
$rows['format'] = '16';
$dv=1;
}
if ($wv&&$dv)
if ($wv && $dv)
{
$rows['format'] = '64';
}

View File

@ -5,7 +5,7 @@
* @link http://www.egroupware.org
* @package calendar
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2004-8 by RalfBecker-At-outdoor-training.de
* @copyright (c) 2004-9 by RalfBecker-At-outdoor-training.de
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$
*/
@ -115,6 +115,12 @@ class calendar_uiviews extends calendar_ui
*/
var $wholeDayPosCounter=1;
/**
* Switch to disable private data and possibility to view and edit events
* in case of a public view (sitemgr)
*/
var $allowEdit = true;
/**
* Constructor
*
@ -155,7 +161,7 @@ class calendar_uiviews extends calendar_ui
$this->dragdrop = new dragdrop();
// if the object would auto-disable itself unset object
// to avoid unneccesary dragdrop calls later
if(!$this->dragdrop->validateBrowser()) { $this->dragdrop = false; }
if(!$this->dragdrop->validateBrowser()) $this->dragdrop = false;
}
}
@ -344,7 +350,10 @@ class calendar_uiviews extends calendar_ui
'date' => $this->bo->date2string($week_start),
);
$title = lang('Wk').' '.adodb_date('W',$week_start);
$title = html::a_href($title,$week_view,'',' title="'.lang('Weekview').'"');
if ($this->allowEdit)
{
$title = html::a_href($title,$week_view,'',' title="'.lang('Weekview').'"');
}
$content .= $this->timeGridWidget($this->tagWholeDayOnTop($week),$weeks == 2 ? 30 : 60,200,'',$title,0,$week_start+WEEK_s >= $this->last);
}
@ -452,19 +461,19 @@ class calendar_uiviews extends calendar_ui
# temporarly disabled, because it collides with the title for the website
#
# // add navigation for previous and next
# // prev. week
# $GLOBALS['egw_info']['flags']['app_header'] = html::a_href(html::image('phpgwpai','first',lang('previous'),$options=' alt="<<"'),array(
# 'menuaction' => $this->view_menuaction,
# 'date' => date('Ymd',$this->first-$days*DAY_s),
# )) . ' &nbsp; <b>'.$GLOBALS['egw_info']['flags']['app_header'];
# // next week
# $GLOBALS['egw_info']['flags']['app_header'] .= '</b> &nbsp; '.html::a_href(html::image('phpgwpai','last',lang('next'),$options=' alt=">>"'),array(
# 'menuaction' => $this->view_menuaction,
# 'date' => date('Ymd',$this->last+$days*DAY_s),
# ));
#
# $class = $class == 'row_on' ? 'th' : 'row_on';
# // add navigation for previous and next
# // prev. week
# $GLOBALS['egw_info']['flags']['app_header'] = html::a_href(html::image('phpgwapi','first',lang('previous'),$options=' alt="<<"'),array(
# 'menuaction' => $this->view_menuaction,
# 'date' => date('Ymd',$this->first-$days*DAY_s),
# )) . ' &nbsp; <b>'.$GLOBALS['egw_info']['flags']['app_header'];
# // next week
# $GLOBALS['egw_info']['flags']['app_header'] .= '</b> &nbsp; '.html::a_href(html::image('phpgwapi','last',lang('next'),$options=' alt=">>"'),array(
# 'menuaction' => $this->view_menuaction,
# 'date' => date('Ymd',$this->last+$days*DAY_s),
# ));
#
# $class = $class == 'row_on' ? 'th' : 'row_on';
//echo "<p>weekdaystarts='".$this->cal_prefs['weekdaystarts']."', get_weekday_start($this->year,$this->month,$this->day)=".date('l Y-m-d',$wd_start).", first=".date('l Y-m-d',$this->first)."</p>\n";
$search_params = array(
@ -933,16 +942,33 @@ class calendar_uiviews extends calendar_ui
if ($short_title === true)
{
$title = html::a_href($title,$day_view,'',
!isset($this->holidays[$day_ymd])?' title="'.lang('Dayview').'"':'');
if ($this->allowEdit)
{
$title = html::a_href($title,$day_view,'',
!isset($this->holidays[$day_ymd])?' title="'.lang('Dayview').'"':'');
}
}
elseif ($short_title === false)
{
// add arrows to go to the previous and next day (dayview only)
$day_view['date'] = $this->bo->date2string($ts -= 12*HOUR_s);
$title = html::a_href(html::image('phpgwapi','left',$this->bo->long_date($ts)),$day_view).' &nbsp; '.$title;
if ($this->allowEdit)
{
$title = html::a_href(html::image('phpgwapi','left',$this->bo->long_date($ts)),$day_view).' &nbsp; '.$title;
}
else
{
$title = $day_view.' &nbsp; '.$title;
}
$day_view['date'] = $this->bo->date2string($ts += 48*HOUR_s);
$title .= ' &nbsp; '.html::a_href(html::image('phpgwapi','right',$this->bo->long_date($ts)),$day_view);
if ($this->allowEdit)
{
$title .= ' &nbsp; '.html::a_href(html::image('phpgwapi','right',$this->bo->long_date($ts)),$day_view);
}
else
{
$title .= ' &nbsp; '.$day_view;
}
}
$html .= $indent."\t".'<div style="height: '. $this->rowHeight .'%;" class="calDayColHeader '.$class.'"'.($holidays ? ' title="'.$holidays.'"':'').'>'.
$title."</div>\n";
@ -978,8 +1004,12 @@ class calendar_uiviews extends calendar_ui
$droppableID='drop_'.$droppableDateTime.'_O'.$owner;
$html .= $indent."\t".'<div id="' . $droppableID . '" style="height:'. $this->rowHeight .'%; top: '. $i*$this->rowHeight .
'%;" class="calAddEvent" onclick="'.$this->popup($GLOBALS['egw']->link('/index.php',$linkData)).';return false;"></div>'."\n";
'%;" class="calAddEvent"';
if ($this->allowEdit)
{
$html .= ' onclick="'.$this->popup($GLOBALS['egw']->link('/index.php',$linkData)).';return false;"';
}
$html .= '></div>'."\n";
if(is_object($this->dragdrop) && $dropPermission)
{
$this->dragdrop->addDroppable(
@ -1098,7 +1128,7 @@ class calendar_uiviews extends calendar_ui
{
if ($this->debug > 1 || $this->debug==='eventWidget') $this->bo->debug_message('uiviews::eventWidget(%1,width=%2)',False,$event,$width);
if($this->use_time_grid && $event['whole_day_on_top']) { $block='event_widget_wholeday_on_top'; }
if($this->use_time_grid && $event['whole_day_on_top']) $block = 'event_widget_wholeday_on_top';
static $tpl = False;
if (!$tpl)
@ -1147,21 +1177,24 @@ class calendar_uiviews extends calendar_ui
// seperate each participant types
$part_array = array();
foreach($this->bo->participants($event) as $part_key => $participant)
{
if(is_numeric($part_key))
{
$part_array[lang('Participants')][$part_key] = $participant;
}
elseif(isset($this->bo->resources[$part_key{0}]))
{
$part_array[((isset($this->bo->resources[$part_key{0}]['participants_header'])) ? $this->bo->resources[$part_key{0}]['participants_header'] : lang($this->bo->resources[$part_key{0}]['app']))][$part_key] = $participant;
}
}
foreach($part_array as $part_group => $participant)
{
$participants .= $this->add_nonempty($participant,$part_group,True,False);
}
if ($this->allowEdit)
{
foreach($this->bo->participants($event) as $part_key => $participant)
{
if(is_numeric($part_key))
{
$part_array[lang('Participants')][$part_key] = $participant;
}
elseif(isset($this->bo->resources[$part_key[0]]))
{
$part_array[((isset($this->bo->resources[$part_key[0]]['participants_header'])) ? $this->bo->resources[$part_key[0]]['participants_header'] : lang($this->bo->resources[$part_key[0]]['app']))][$part_key] = $participant;
}
}
foreach($part_array as $part_group => $participant)
{
$participants .= $this->add_nonempty($participant,$part_group,True,False);
}
}
// as we only deal with percentual widht, we consider only the full dayview (1 colum) as NOT small
$small = $this->view != 'day' || $width < 50;
// $small = $width <= $small_trigger_width
@ -1231,12 +1264,11 @@ class calendar_uiviews extends calendar_ui
{
$view_link_confirm_abort = $GLOBALS['egw']->link('/index.php',array('menuaction'=>'calendar.calendar_uiforms.edit','cal_id'=>$event['id'],'date'=>$this->bo->date2string($event['start']),'exception'=>1));
$view_link_confirm_text=lang('do you want to edit serialevent als exception? - Ok = Edit Exception, Abort = Edit Serial');
$popup = $is_private ? '' : ' onclick="'.$this->popup($view_link_confirm_abort,null,750,410,$view_link,$view_link_confirm_text).'; return false;"';
$popup = ($is_private || ! $this->allowEdit) ? '' : ' onclick="'.$this->popup($view_link_confirm_abort,null,750,410,$view_link,$view_link_confirm_text).'; return false;"';
}
else
{
$popup = $is_private ? '' : ' onclick="'.$this->popup($view_link).'; return false;"';
$popup = ($is_private || ! $this->allowEdit) ? '' : ' onclick="'.$this->popup($view_link).'; return false;"';
}
//_debug_array($event);
//echo $event['id']."?<br>";
@ -1602,11 +1634,11 @@ class calendar_uiviews extends calendar_ui
if ($this->day >= 15) $prev = $t_arr; // we stay in the same month
$prev['day'] = $this->day < 15 ? 15 : 1;
$half = $this->bo->date2string($prev);
$title = html::a_href(html::image('phpgwpai','first',lang('back one month'),$options=' alt="<<"'),array(
$title = html::a_href(html::image('phpgwapi','first',lang('back one month'),$options=' alt="<<"'),array(
'menuaction' => $this->view_menuaction,
'date' => $full,
)) . ' &nbsp; '.
html::a_href(html::image('phpgwpai','left',lang('back half a month'),$options=' alt="<"'),array(
html::a_href(html::image('phpgwapi','left',lang('back half a month'),$options=' alt="<"'),array(
'menuaction' => $this->view_menuaction,
'date' => $half,
)) . ' &nbsp; '.$title;
@ -1628,11 +1660,11 @@ class calendar_uiviews extends calendar_ui
if ($this->day < 15) $next = $t_arr; // we stay in the same month
$next['day'] = $this->day < 15 ? 15 : 1;
$half = $this->bo->date2string($next);
$title .= ' &nbsp; '.html::a_href(html::image('phpgwpai','right',lang('forward half a month'),$options=' alt=">>"'),array(
$title .= ' &nbsp; '.html::a_href(html::image('phpgwapi','right',lang('forward half a month'),$options=' alt=">>"'),array(
'menuaction' => $this->view_menuaction,
'date' => $half,
)). ' &nbsp; '.
html::a_href(html::image('phpgwpai','last',lang('forward one month'),$options=' alt=">>"'),array(
html::a_href(html::image('phpgwapi','last',lang('forward one month'),$options=' alt=">>"'),array(
'menuaction' => $this->view_menuaction,
'date' => $full,
));
@ -1678,12 +1710,12 @@ class calendar_uiviews extends calendar_ui
else
{
// prev. week
$title = html::a_href(html::image('phpgwpai','first',lang('previous'),$options=' alt="<<"'),array(
$title = html::a_href(html::image('phpgwapi','first',lang('previous'),$options=' alt="<<"'),array(
'menuaction' => $this->view_menuaction,
'date' => date('Ymd',$t-7*DAY_s),
)) . ' &nbsp; <b>'.$title;
// next week
$title .= '</b> &nbsp; '.html::a_href(html::image('phpgwpai','last',lang('next'),$options=' alt=">>"'),array(
$title .= '</b> &nbsp; '.html::a_href(html::image('phpgwapi','last',lang('next'),$options=' alt=">>"'),array(
'menuaction' => $this->view_menuaction,
'date' => date('Ymd',$t+7*DAY_s),
));
@ -1737,14 +1769,14 @@ class calendar_uiviews extends calendar_ui
{
if (!$i) // prev. day only for the first day
{
$title = html::a_href(html::image('phpgwpai','first',lang('previous'),$options=' alt="<<"'),array(
$title = html::a_href(html::image('phpgwapi','first',lang('previous'),$options=' alt="<<"'),array(
'menuaction' => $this->view_menuaction,
'date' => date('Ymd',$start-DAY_s),
)) . ' &nbsp; '.$title;
}
if ($i == $days-1) // next day only for the last day
{
$title .= ' &nbsp; '.html::a_href(html::image('phpgwpai','last',lang('next'),$options=' alt=">>"'),array(
$title .= ' &nbsp; '.html::a_href(html::image('phpgwapi','last',lang('next'),$options=' alt=">>"'),array(
'menuaction' => $this->view_menuaction,
'date' => date('Ymd',$start+DAY_s),
));

View File

@ -265,7 +265,7 @@
"group" => "Group public"
);
if (ereg(",", $selected))
if (strpos($selected,",") !== false)
{
$selected = "group";
}

View File

@ -192,7 +192,7 @@ class soholiday
echo 'HOLIDAY_TOTAL : '.$where.'<br>'."\n";
}
$retval = $this->db->select($this->table,'count(*)',$where,__LINE__,__FILE__,false,'','calendar')->fetchSingle();
$retval = $this->db->select($this->table,'count(*)',$where,__LINE__,__FILE__,false,'','calendar')->fetchColumn();
if($this->debug)
{

View File

@ -45,14 +45,14 @@
$this->bo =& CreateObject('calendar.boholiday');
$this->bo->check_admin();
$this->base_url = $this->bo->base_url;
$this->template_dir = $GLOBALS['egw']->common->get_tpl_dir('calendar');
$this->template_dir = common::get_tpl_dir('calendar');
$this->sb =& CreateObject('calendar.sbox');
// calendar does not work with hidden sidebox atm.
unset($GLOBALS['egw_info']['user']['preferences']['common']['auto_hide_sidebox']);
$GLOBALS['egw_info']['flags']['app_header'] = $GLOBALS['egw_info']['apps']['calendar']['title'].' - '.lang('Holiday Management');
$GLOBALS['egw']->template->set_var('help_msg',lang('<b>Please note</b>: The calendar use the holidays of your country, which is set to %1. You can change it in your %2.<br />Holidays are %3 automatic installed from %4. You can changed it in %5.',
'<b>'.$GLOBALS['egw_info']['user']['preferences']['common']['country'].'</b>','<a href="'.$GLOBALS['egw']->link('/index.php',array(
'menuaction' => 'preferences.uisettings.index',
@ -71,7 +71,7 @@
unset($GLOBALS['egw_info']['flags']['noheader']);
unset($GLOBALS['egw_info']['flags']['nonavbar']);
$GLOBALS['egw_info']['flags']['noappfooter'] = True;
$GLOBALS['egw']->common->egw_header();
common::egw_header();
$p = &$GLOBALS['egw']->template;
$p->set_file(Array('locales'=>'locales.tpl'));
@ -157,7 +157,7 @@
unset($GLOBALS['egw_info']['flags']['noheader']);
unset($GLOBALS['egw_info']['flags']['nonavbar']);
$GLOBALS['egw_info']['flags']['noappfooter'] = True;
$GLOBALS['egw']->common->egw_header();
common::egw_header();
$p =& $GLOBALS['egw']->template;
$p->set_file(Array('locale'=>'locales.tpl'));
$p->set_block('locale','list','list');
@ -210,7 +210,7 @@
{
$holidays[$i]['name'] = '&nbsp;';
}
$var = Array(
'tr_color' => $tr_color,
'header_delete'=> lang('Delete'),
@ -268,7 +268,7 @@
unset($GLOBALS['egw_info']['flags']['nonavbar']);
$GLOBALS['egw_info']['flags']['noappfooter'] = True;
$GLOBALS['egw_info']['flags']['app_header'] = $GLOBALS['egw_info']['apps']['calendar']['title'].' - '.($this->bo->id ? lang('Edit') : lang('Add')).' '.lang('Holiday');
$GLOBALS['egw']->common->egw_header();
common::egw_header();
$t = &$GLOBALS['egw']->template;
$t->set_file(Array('holiday'=>'holiday.tpl','form_button'=>'form_button_script.tpl'));
@ -277,13 +277,13 @@
if (@count($error))
{
$message = $GLOBALS['egw']->common->error_list($error);
$message = common::error_list($error);
}
else
{
$message = '';
}
$var = Array(
'title_holiday' => ($this->bo->id ? lang('Edit') : lang('Add')).' '.lang('Holiday'),
'message' => $message,
@ -300,7 +300,7 @@
$this->display_item($t,lang('title'),'<input name="holiday[name]" size="60" maxlength="50" value="'.$holiday['name'].'">');
// Date
$this->display_item($t,lang('Date'),$GLOBALS['egw']->common->dateformatorder($this->sb->getYears('holiday[year]',$holiday['occurence']>1900?$holiday['occurence']:0),$this->sb->getMonthText('holiday[month_num]',$holiday['month']),$this->sb->getDays('holiday[mday]',$holiday['day'])).
$this->display_item($t,lang('Date'),common::dateformatorder($this->sb->getYears('holiday[year]',$holiday['occurence']>1900?$holiday['occurence']:0),$this->sb->getMonthText('holiday[month_num]',$holiday['month']),$this->sb->getDays('holiday[mday]',$holiday['day'])).
'&nbsp;'.lang('Set a Year only for one-time / non-regular holidays.'));
// Occurence
@ -357,7 +357,7 @@
'menuaction' => 'calendar.uiholiday.admin'
);
}
$t->set_var(Array(
'action_url_button' => $GLOBALS['egw']->link($this->base_url,$link_params),
'action_text_button' => lang('Cancel'),
@ -365,7 +365,7 @@
'action_extra_field' => ''
));
$t->parse('cancel_button','form_button');
if ($this->bo->id)
{
$link_params = Array(
@ -396,7 +396,7 @@
$p =& CreateObject('phpgwapi.Template',$this->template_dir);
$p->set_file(Array('form'=>'delete_common.tpl','form_button'=>'form_button_script.tpl'));
$p->set_var('messages',lang('Are you sure you want to delete this Country ?')."<br>".$this->bo->locales[0]);
$var = Array(
@ -428,15 +428,15 @@
{
return $this->edit_locale();
}
unset($GLOBALS['egw_info']['flags']['noheader']);
unset($GLOBALS['egw_info']['flags']['nonavbar']);
$GLOBALS['egw_info']['flags']['noappfooter'] = True;
$GLOBALS['egw']->common->egw_header();
common::egw_header();
$p =& CreateObject('phpgwapi.Template',$this->template_dir);
$p->set_file(Array('form'=>'delete_common.tpl','form_button'=>'form_button_script.tpl'));
$p->set_var('messages',lang('Are you sure you want to delete this holiday ?')."<br>".$holiday['name'].' ('.$this->bo->locales[0].') '.$this->bo->rule_string($holiday));
$var = Array(
@ -468,7 +468,7 @@
}
$this->bo->year = 0; // for a complete list with all years
$holidays = $this->bo->get_holiday_list();
if (!is_array($holidays) || !count($holidays))
{
$this->admin();
@ -479,11 +479,9 @@
if (isset($_GET['download']))
{
$locale = $this->bo->locales[0];
$browser =& CreateObject('phpgwapi.browser');
$browser->content_header("holidays.$locale.csv",'text/text');
unset($browser);
html::content_header("holidays.$locale.csv",'text/text');
echo "charset\t".$GLOBALS['egw']->translation->charset()."\n";
echo "charset\t".translation::charset()."\n";
$last_year = -1;
foreach($holidays as $holiday)
{
@ -495,7 +493,7 @@
}
echo "$locale\t$holiday[name]\t$holiday[day]\t$holiday[month]\t$holiday[occurence]\t$holiday[dow]\t$holiday[observance_rule]\n";
}
$GLOBALS['egw']->common->egw_exit();
common::egw_exit();
}
if($this->debug)
{
@ -508,13 +506,13 @@
$GLOBALS['egw_info']['flags']['noappheader'] = True;
$GLOBALS['egw_info']['flags']['noappfooter'] = True;
$GLOBALS['egw_info']['flags']['nofooter'] = True;
$GLOBALS['egw']->common->egw_header();
common::egw_header();
echo '<body onLoad="document.submitform.submit()">'."\n";
echo '<form action="'.$action.'" method="post" name="submitform">'."\n";
echo '<input type="hidden" name="locale" value="'.$this->bo->locales[0].'">'."\n";
echo '<input type="hidden" name="charset" value="'.$GLOBALS['egw']->translation->charset().'">'."\n";
echo '<input type="hidden" name="charset" value="'.translation::charset().'">'."\n";
foreach($holidays as $holiday)
{
echo '<input type="hidden" name="name[]" value="'.htmlspecialchars($holiday['name']).'">'."\n"

View File

@ -1,31 +1,17 @@
<?php
/**************************************************************************\
* eGroupWare - calendar: rounded corners *
* http://www.egroupware.org *
* Written by RalfBecker@outdoor-training.de *
* -------------------------------------------- *
* This program is free software; you can redistribute it and/or modify it *
* under the terms of the GNU General Public License as published by the *
* Free Software Foundation; either version 2 of the License, or (at your *
* option) any later version. *
\**************************************************************************/
/**
* eGroupWare Calendar: rounded corners
*
* @link http://www.egroupware.org
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @package calendar
* @copyright (c) 2004-9 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$
*/
/* $Id$ */
// some constanst for pre php4.3
if (!defined('PHP_SHLIB_SUFFIX'))
{
define('PHP_SHLIB_SUFFIX',strtoupper(substr(PHP_OS, 0,3)) == 'WIN' ? 'dll' : 'so');
}
if (!defined('PHP_SHLIB_PREFIX'))
{
define('PHP_SHLIB_PREFIX',PHP_SHLIB_SUFFIX == 'dll' ? 'php_' : '');
}
if (!extension_loaded('gd') && !@dl(PHP_SHLIB_PREFIX.'gd.'.PHP_SHLIB_SUFFIX))
{
die("Can't load the needed php-extension 'gd' !!!");
}
include_once('../../phpgwapi/inc/common_functions.inc.php');
check_load_extension('gd',true); // true = throw exception if not loadable
foreach(array('width'=>1,'height'=>1,'color1'=>'000080','color2'=>'ffffff') as $name => $default)
{
@ -84,4 +70,3 @@ else
imagepng($image);
}
imagedestroy($image);
?>

View File

@ -1,31 +1,17 @@
<?php
/**************************************************************************\
* eGroupWare - calendar: rounded corners *
* http://www.egroupware.org *
* Written by RalfBecker@outdoor-training.de *
* -------------------------------------------- *
* This program is free software; you can redistribute it and/or modify it *
* under the terms of the GNU General Public License as published by the *
* Free Software Foundation; either version 2 of the License, or (at your *
* option) any later version. *
\**************************************************************************/
/**
* eGroupWare Calendar: rounded corners
*
* @link http://www.egroupware.org
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @package calendar
* @copyright (c) 2004-9 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$
*/
/* $Id$ */
// some constanst for pre php4.3
if (!defined('PHP_SHLIB_SUFFIX'))
{
define('PHP_SHLIB_SUFFIX',strtoupper(substr(PHP_OS, 0,3)) == 'WIN' ? 'dll' : 'so');
}
if (!defined('PHP_SHLIB_PREFIX'))
{
define('PHP_SHLIB_PREFIX',PHP_SHLIB_SUFFIX == 'dll' ? 'php_' : '');
}
if (!extension_loaded('gd') && !@dl(PHP_SHLIB_PREFIX.'gd.'.PHP_SHLIB_SUFFIX))
{
die("Can't load the needed php-extension 'gd' !!!");
}
include_once('../../phpgwapi/inc/common_functions.inc.php');
check_load_extension('gd',true); // true = throw exception if not loadable
foreach(array('width'=>-20,'height'=>40,'border'=>1,'color'=>'000080','bgcolor'=>'0000FF') as $name => $default)
{
@ -85,4 +71,3 @@ else
imagepng($image);
}
imagedestroy($image);
?>

View File

@ -22,7 +22,7 @@
*/
class select_widget
{
/**
/**
* exported methods of this class
* @var array
*/
@ -95,7 +95,7 @@
*
* @param string $name form-name of the control
* @param mixed &$value value / existing content, can be modified
* @param array &$cell array with the widget, can be modified for ui-independent widgets
* @param array &$cell array with the widget, can be modified for ui-independent widgets
* @param array &$readonlys names of widgets as key, to be made readonly
* @param mixed &$extension_data data the extension can store persisten between pre- and post-process
* @param object &$tmpl reference to the template we belong too
@ -184,6 +184,7 @@
{
$categories =& new categories('',$type3);
}
$accountId = $GLOBALS['egw_info']['user']['account_id'];
foreach((array)$categories->return_sorted_array(0,False,'','','',!$type) as $cat)
{
$s = str_repeat('&nbsp;',$cat['level']) . stripslashes($cat['name']);
@ -192,6 +193,14 @@
{
$s .= ' &#9830;';
}
elseif ($cat['owner'] != $accountId)
{
$s .= '&lt;' . $GLOBALS['egw']->accounts->id2name($cat['owner'], 'account_fullname') . '&gt;';
}
elseif ($cat['access'] == 'private')
{
$s .= ' &#9829;';
}
if (!$tmpl->xslt)
{
$cell['sel_options'][$cat['id']] = $s; // 0.9.14 only
@ -212,7 +221,7 @@
case 'select-account': // options: #rows,{accounts(default)|both|groups|owngroups},{0(=lid)|1(default=name)|2(=lid+name),expand-multiselect-rows,not-to-show-accounts,...)}
//echo "<p>select-account widget: name=$cell[name], type='$type', rows=$rows, readonly=".(int)($cell['readonly'] || $readonlys)."</p>\n";
if($type == 'owngroups')
if($type == 'owngroups')
{
$type = 'groups';
$owngroups = true;
@ -298,7 +307,7 @@
$cell['sel_options'] = $this->monthnames;
$value = intval($value);
break;
case 'select-dow': // options: rows[,0=summaries befor days, 1=summaries after days, 2=no summaries[,extraStyleMultiselect]]
if (!defined('MCAL_M_SUNDAY'))
{
@ -309,14 +318,14 @@
define('MCAL_M_THURSDAY',16);
define('MCAL_M_FRIDAY',32);
define('MCAL_M_SATURDAY',64);
define('MCAL_M_WEEKDAYS',62);
define('MCAL_M_WEEKEND',65);
define('MCAL_M_ALLDAYS',127);
}
$weekstart = $GLOBALS['egw_info']['user']['preferences']['calendar']['weekdaystarts'];
$cell['sel_options'] = array();
if ($rows >= 2 && !$type)
if ($rows >= 2 && !$type)
{
$cell['sel_options'] = array(
MCAL_M_ALLDAYS => 'all days',
@ -335,7 +344,7 @@
);
if ($weekstart != 'Saturday') $cell['sel_options'][MCAL_M_SATURDAY] = 'saturday';
if ($weekstart == 'Monday') $cell['sel_options'][MCAL_M_SUNDAY] = 'sunday';
if ($rows >= 2 && $type == 1)
if ($rows >= 2 && $type == 1)
{
$cell['sel_options'] += array(
MCAL_M_ALLDAYS => 'all days',
@ -350,13 +359,13 @@
if (($value_in & $val) == $val)
{
$value[] = $val;
if ($val == MCAL_M_ALLDAYS ||
if ($val == MCAL_M_ALLDAYS ||
$val == MCAL_M_WEEKDAYS && $value_in == MCAL_M_WEEKDAYS ||
$val == MCAL_M_WEEKEND && $value_in == MCAL_M_WEEKEND)
{
break; // dont set the others
}
}
}
}
if (!$readonly)
@ -371,7 +380,7 @@
$type2 = 31;
$type3 = 1;
// fall-through
case 'select-number': // options: rows,min,max,decrement,suffix
$type = $type === '' ? 1 : intval($type); // min
$type2 = $type2 === '' ? 10 : intval($type2); // max
@ -392,17 +401,17 @@
}
$cell['no_lang'] = True;
break;
case 'select-hour':
for ($h = 0; $h <= 23; ++$h)
{
$cell['sel_options'][$h] = $GLOBALS['egw_info']['user']['preferences']['common']['timeformat'] == 12 ?
(($h % 12 ? $h % 12 : 12).' '.($h < 12 ? lang('am') : lang('pm'))) :
(($h % 12 ? $h % 12 : 12).' '.($h < 12 ? lang('am') : lang('pm'))) :
sprintf('%02d',$h);
}
$cell['no_lang'] = True;
break;
case 'select-app': // type2: ''=users enabled apps, 'installed', 'all' = not installed ones too
$apps = array();
foreach ($GLOBALS['egw_info']['apps'] as $app => $data)
@ -468,7 +477,7 @@
}
}
$info = $show_type ? '('.$acc['account_type'].') ' : '';
if ($acc['account_type'] == 'g')
{
$longnames = 1;
@ -492,7 +501,7 @@
}
return $info;
}
/**
* postprocessing method, called after the submission of the form
*

View File

@ -15,7 +15,7 @@
*/
class tree_widget
{
/**
/**
* exported methods of this class
* @var array
*/
@ -49,7 +49,7 @@ class tree_widget
*
* @param string $name form-name of the control
* @param mixed &$value value / existing content, can be modified
* @param array &$cell array with the widget, can be modified for ui-independent widgets
* @param array &$cell array with the widget, can be modified for ui-independent widgets
* @param array &$readonlys names of widgets as key, to be made readonly
* @param mixed &$extension_data data the extension can store persisten between pre- and post-process
* @param etemplate &$tmpl reference to the template we belong too
@ -83,6 +83,7 @@ class tree_widget
{
$categories =& new categories('',$type3);
}
$accountId = $GLOBALS['egw_info']['user']['account_id'];
$cat2path=array();
foreach((array)$categories->return_sorted_array(0,False,'','','',!$type) as $cat)
{
@ -92,6 +93,13 @@ class tree_widget
{
$s .= ' &#9830;';
}
elseif ($cat['owner'] != $accountId)
{
$s .= '&lt;' . $GLOBALS['egw']->accounts->id2name($cat['owner'], 'account_fullname') . '&gt;';
}
elseif ($cat['access'] == 'private') {
$s .= ' &#9829;';
}
$cat2path[$cat['id']] = $path = ($cat['parent'] ? $cat2path[$cat['parent']].'/' : '').(string)$cat['id'];
$cell['sel_options'][$path] = $s;
}
@ -134,10 +142,10 @@ class tree_widget
$onCheck = 'onCheck_'.$tree_id;
$script .= "
function $onCheck(id) {
document.getElementsByName('$name')[0].value=$tree_id.getAllChecked();
document.getElementsByName('$name')[0].value=$tree_id.getAllChecked();
$onclick;
}
function $onNodeSelect(id) {
function $onNodeSelect(id) {
$tree_id.setCheck(id,$tree_id.isItemChecked(id) ? 0 : 1);
$onCheck(id);
}
@ -146,7 +154,7 @@ class tree_widget
else // single selection
{
$script .= "
function $onNodeSelect(id) {
function $onNodeSelect(id) {
document.getElementsByName('$name')[0].value=id;
$onclick;
}
@ -161,7 +169,7 @@ class tree_widget
return True; // extra Label Ok
}
/**
* postprocessing method, called after the submission of the form
*
@ -182,7 +190,7 @@ class tree_widget
function post_process($name,&$value,&$extension_data,&$loop,&$tmpl,$value_in)
{
//echo "value_in"; _debug_array($value_in);
if (!preg_match('/^[0-9\\/'.($extension_data['multiple']?',':'').']*$/',$value_in)) return false; // guard against xss and other malious content
$value = $extension_data['multiple'] ? explode(',',$value_in) : $value_in;

View File

@ -4,6 +4,7 @@
*
* @link http://www.egroupware.org
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @author Joerg Lehrke <jlehrke@noc.de>
* @package infolog
* @copyright (c) 2003-8 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
@ -95,6 +96,31 @@ class infolog_bo
*/
var $max_line_chars = 40;
/**
* Available filters
*
* @var array filter => label pairs
*/
var $filters = array(
'none' => 'no Filter',
'done' => 'done',
'responsible' => 'responsible',
'responsible-open-today' => 'responsible open',
'responsible-open-overdue' => 'responsible overdue',
'responsible-upcoming' => 'responsible upcoming',
'delegated' => 'delegated',
'delegated-open-today' => 'delegated open',
'delegated-open-overdue' => 'delegated overdue',
'delegated-upcoming' => 'delegated upcomming',
'own' => 'own',
'own-open-today' => 'own open',
'own-open-overdue' => 'own overdue',
'own-upcoming' => 'own upcoming',
'open-today' => 'open',
'open-overdue' => 'overdue',
'upcoming' => 'upcoming',
);
/**
* Constructor Infolog BO
*
@ -129,7 +155,7 @@ class infolog_bo
'billed' => 'billed', // --> DONE
'template' => 'template', // --> cancelled
'nonactive' => 'nonactive', // --> cancelled
'archive' => 'archive' ), // --> cancelled
'archive' => 'archive' ), // --> cancelled
'phone' => array(
'not-started' => 'call', // iCal NEEDS-ACTION
'ongoing' => 'will-call', // iCal IN-PROCESS
@ -211,7 +237,7 @@ class infolog_bo
$this->user_time_now = time() + $this->tz_offset_s;
$this->grants = $GLOBALS['egw']->acl->get_grants('infolog',$this->group_owners ? $this->group_owners : true);
$this->so =& new infolog_so($this->grants);
$this->so = new infolog_so($this->grants);
if ($info_id)
{
@ -488,10 +514,9 @@ class infolog_bo
$GLOBALS['egw']->contenthistory->updateTimeStamp('infolog_'.$info['info_type'], $info_id, 'delete', time());
// send email notifications and do the history logging
require_once(EGW_INCLUDE_ROOT.'/infolog/inc/class.infolog_tracking.inc.php');
if (!is_object($this->tracking))
{
$this->tracking =& new infolog_tracking($this);
$this->tracking = new infolog_tracking($this);
}
$this->tracking->track($deleted,$info,$this->user,true);
}
@ -508,7 +533,7 @@ class infolog_bo
* @param boolean $touch_modified=true touch the modification data and sets the modiefier's user-id
* @return int/boolean info_id on a successfull write or false
*/
function write(&$values,$check_defaults=True,$touch_modified=True)
function write(&$values, $check_defaults=True, $touch_modified=True)
{
//echo "boinfolog::write()values="; _debug_array($values);
if ($status_only = $values['info_id'] && !$this->check_access($values['info_id'],EGW_ACL_EDIT))
@ -522,9 +547,9 @@ class infolog_bo
{
$responsible =& $values['info_responsible'];
}
if (!($status_only = in_array($this->user, $responsible))) // responsible has implicit right to change status
if (!($status_only = in_array($this->user, (array)$responsible))) // responsible has implicit right to change status
{
$status_only = !!array_intersect($responsible,array_keys($GLOBALS['egw']->accounts->memberships($this->user)));
$status_only = !!array_intersect((array)$responsible,array_keys($GLOBALS['egw']->accounts->memberships($this->user)));
}
if (!$status_only && $values['info_status'] != 'deleted')
{
@ -553,8 +578,24 @@ class infolog_bo
if ($set_completed)
{
$values['info_datecompleted'] = $this->user_time_now;
$values['info_percent'] = '100%';
if (!in_array($values['info_status'],array('done','billed','cancelled'))) $values['info_status'] = 'done';
$values['info_percent'] = 100;
$forcestatus = true;
$status = 'done';
if (isset($values['info_type']) && !in_array($values['info_status'],array('done','billed','cancelled'))) {
$forcestatus = false;
echo "set_completed:"; _debug_array($this->status[$values['info_type']]);
if (isset($this->status[$values['info_type']]['done'])) {
$forcestatus = true;
$status = 'done';
} elseif (isset($this->status[$values['info_type']]['billed'])) {
$forcestatus = true;
$status = 'billed';
} elseif (isset($this->status[$values['info_type']]['cancelled'])) {
$forcestatus = true;
$status = 'cancelled';
}
}
if ($forcestatus && !in_array($values['info_status'],array('done','billed','cancelled'))) $values['info_status'] = $status;
}
$check_defaults = False;
}
@ -567,11 +608,26 @@ class infolog_bo
}
if (in_array($values['info_status'],array('done','billed')))
{
$values['info_percent'] == '100%';
$values['info_percent'] = 100;
}
if ((int)$values['info_percent'] == 100 && !in_array($values['info_status'],array('done','billed','cancelled')))
{
$values['info_status'] = 'done';
//echo "check_defaults:"; _debug_array($this->status[$values['info_type']]);
//$values['info_status'] = 'done';
$status = 'done';
if (isset($values['info_type'])) {
if (isset($this->status[$values['info_type']]['done'])) {
$status = 'done';
} elseif (isset($this->status[$values['info_type']]['billed'])) {
$status = 'billed';
} elseif (isset($this->status[$values['info_type']]['cancelled'])) {
$status = 'cancelled';
} else {
// since the comlete stati above do not exist for that type, dont change it
$status = $values['info_status'];
}
}
$values['info_status'] = $status;
}
if ($values['info_responsible'] && $values['info_status'] == 'offer')
{
@ -603,7 +659,7 @@ class infolog_bo
// Should only an entry be updated which includes the original modification date?
// Used in the web-GUI to check against a modification by an other user while editing the entry.
// It's now disabled for xmlrpc, as otherwise the xmlrpc code need to be changed!
$xmprpc = is_object($GLOBALS['server']) && $GLOBALS['server']->last_method;
$xmlrpc = is_object($GLOBALS['server']) && $GLOBALS['server']->last_method;
$check_modified = $values['info_datemodified'] && !$xmlrpc ? $values['info_datemodified']-$this->tz_offset_s : false;
$values['info_datemodified'] = $this->user_time_now;
}
@ -611,6 +667,7 @@ class infolog_bo
{
$values['info_modifier'] = $this->so->user;
}
//_debug_array($values);
$to_write = $values;
if ($status_only && !$undelete) $values = array_merge($backup_values,$values);
// convert user- to system-time
@ -663,7 +720,7 @@ class infolog_bo
// send email notifications and do the history logging
if (!is_object($this->tracking))
{
$this->tracking =& new infolog_tracking($this);
$this->tracking = new infolog_tracking($this);
}
$this->tracking->track($values,$old,$this->user,$values['info_status'] == 'deleted' || $old['info_status'] == 'deleted');
}
@ -796,7 +853,12 @@ class infolog_bo
{
foreach ($_attachments as $attachment)
{
if(is_readable($attachment['tmp_name']))
$is_vfs = false;
if (parse_url($attachment['tmp_name'],PHP_URL_SCHEME) == 'vfs' && egw_vfs::is_readable($attachment['tmp_name']))
{
$is_vfs = true;
}
if(is_readable($attachment['tmp_name']) || $is_vfs)
{
egw_link::link('infolog',$info['link_to']['to_id'],'file',$attachment);
}
@ -1018,7 +1080,7 @@ class infolog_bo
$cat_id = $this->categories->name2id($cat_name, 'X-');
if (!$cat_id)
{
$cat_id = $this->categories->add(array('name' => $cat_name,'descr' => $cat_name));
$cat_id = $this->categories->add(array('name' => $cat_name, 'descr' => $cat_name, 'access' => 'private'));
}
if ($cat_id)
@ -1089,7 +1151,7 @@ class infolog_bo
$GLOBALS['egw']->acl->acl($user);
$GLOBALS['egw']->acl->read_repository();
$this->grants = $GLOBALS['egw']->acl->get_grants('infolog',$this->group_owners ? $this->group_owners : true);
$this->so =& new infolog_so($this->grants); // so caches it's filters
$this->so = new infolog_so($this->grants); // so caches it's filters
$notified_info_ids = array();
foreach(array(
@ -1110,32 +1172,32 @@ class infolog_bo
// check if we already send a notification for that infolog entry, eg. starting and due on same day
if (in_array($info['info_id'],$notified_info_ids)) continue;
if (is_null($tracking) || $tracking->user != $user)
if (is_null($this->tracking) || $this->tracking->user != $user)
{
require_once(EGW_INCLUDE_ROOT.'/infolog/inc/class.infolog_tracking.inc.php');
$tracking = new infolog_tracking($this);
$this->tracking = new infolog_tracking($this);
}
switch($pref)
{
case 'notify_due_responsible':
$info['message'] = lang('%1 you are responsible for is due at %2',$this->enums['type'][$info['info_type']],
$tracking->datetime($info['info_enddate']-$this->tz_offset_s,false));
$this->tracking->datetime($info['info_enddate']-$this->tz_offset_s,false));
break;
case 'notify_due_delegated':
$info['message'] = lang('%1 you delegated is due at %2',$this->enums['type'][$info['info_type']],
$tracking->datetime($info['info_enddate']-$this->tz_offset_s,false));
$this->tracking->datetime($info['info_enddate']-$this->tz_offset_s,false));
break;
case 'notify_start_responsible':
$info['message'] = lang('%1 you are responsible for is starting at %2',$this->enums['type'][$info['info_type']],
$tracking->datetime($info['info_startdate']-$this->tz_offset_s,null));
$this->tracking->datetime($info['info_startdate']-$this->tz_offset_s,null));
break;
case 'notify_start_delegated':
$info['message'] = lang('%1 you delegated is starting at %2',$this->enums['type'][$info['info_type']],
$tracking->datetime($info['info_startdate']-$this->tz_offset_s,null));
$this->tracking->datetime($info['info_startdate']-$this->tz_offset_s,null));
break;
}
error_log("notifiying $user($email) about $info[info_subject]: $info[message]");
$tracking->send_notification($info,null,$email,$user,$pref);
$this->tracking->send_notification($info,null,$email,$user,$pref);
$notified_info_ids[] = $info['info_id'];
}
@ -1159,6 +1221,8 @@ class infolog_bo
'template' => 'CANCELLED',
'nonactive' => 'CANCELLED',
'archive' => 'CANCELLED',
'deferred' => 'NEEDS-ACTION',
'waiting' => 'IN-PROCESS',
);
/** conversion of vtodo status to infolog status
@ -1167,7 +1231,9 @@ class infolog_bo
*/
var $_vtodo2status = array(
'NEEDS-ACTION' => 'not-started',
'NEEDS ACTION' => 'not-started',
'IN-PROCESS' => 'ongoing',
'IN PROCESS' => 'ongoing',
'COMPLETED' => 'done',
'CANCELLED' => 'cancelled',
);
@ -1223,4 +1289,128 @@ class infolog_bo
}
return 'ongoing';
}
/**
* Get the Parent ID of an InfoLog entry
*
* @param string $_guid
* @return string parentID
*/
function getParentID($_guid)
{
#Horde::logMessage("getParentID($_guid)", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$parentID = False;
$myfilter = array('col_filter' => array('info_uid'=>$_guid)) ;
if ($_guid && ($found=$this->search($myfilter)) && ($uidmatch = array_shift($found))) {
$parentID = $uidmatch['info_id'];
};
return $parentID;
}
/**
* Try to find a matching db entry
*
* @param array $egwData the vTODO data we try to find
* @param boolean $relax=false if asked to relax, we only match against some key fields
* @return the infolog_id of the matching entry or false (if none matches)
*/
function findVTODO($egwData, $relax=false)
{
$myfilter = array('col_filter' => array('info_uid'=>$egwData['info_uid'])) ;
if ($egwData['info_uid']
&& ($found = $this->search($myfilter))
&& ($uidmatch = array_shift($found)))
{
return $uidmatch['info_id'];
};
unset($egwData['info_uid']);
$filter = array();
$description = '';
if (!empty($egwData['info_des'])) {
$description = trim(preg_replace("/\r?\n?\\[[A-Z_]+:.*\\]/i", '', $egwData['info_des']));
unset($egwData['info_des']);
// Avoid quotation problems
$description = preg_replace("/[^\x20-\x7F].*/", '', $description);
if (strlen($description)) {
$filter['search'] = $description;
}
}
if ($egwData['info_id']
&& ($found = $this->read($egwData['info_id'])))
{
// We only do a simple consistency check
if ($found['info_subject'] == $egwData['info_subject']
&& strpos($found['info_des'], $description) === 0)
{
return $found['info_id'];
}
}
unset($egwData['info_id']);
// priority does not need to match
unset($egwData['info_priority']);
$filter['col_filter'] = $egwData;
if($foundItems = $this->search($filter)) {
if(count($foundItems) > 0) {
$itemIDs = array_keys($foundItems);
return $itemIDs[0];
}
}
$filter = array();
if (!$relax && strlen($description)) {
$filter['search'] = $description;
}
$filter['col_filter'] = $egwData;
// search for date only match
unset($filter['col_filter']['info_startdate']);
unset($filter['col_filter']['info_datecompleted']);
// try tasks without category
unset($filter['col_filter']['info_cat']);
#Horde::logMessage("findVTODO Filter\n"
# . print_r($filter, true),
# __FILE__, __LINE__, PEAR_LOG_DEBUG);
foreach ($this->search($filter) as $itemID => $taskData) {
# Horde::logMessage("findVTODO Trying\n"
# . print_r($taskData, true),
# __FILE__, __LINE__, PEAR_LOG_DEBUG);
if (isset($egwData['info_cat'])
&& isset($taskData['info_cat']) && $taskData['info_cat']
&& $egwData['info_cat'] != $taskData['info_cat']) continue;
if (isset($egwData['info_startdate'])
&& isset($taskData['info_startdate']) && $taskData['info_startdate']) {
$parts = @getdate($taskData['info_startdate']);
$startdate = @mktime(0, 0, 0, $parts['mon'], $parts['mday'], $parts['year']);
if ($egwData['info_startdate'] != $startdate) continue;
}
// some clients don't support DTSTART
if (isset($egwData['info_startdate'])
&& (!isset($taskData['info_startdate']) || !$taskData['info_startdate'])
&& !$relax) continue;
if (isset($egwData['info_datecompleted'])
&& isset($taskData['info_datecompleted']) && $taskData['info_datecompleted']) {
$parts = @getdate($taskData['info_datecompleted']);
$enddate = @mktime(0, 0, 0, $parts['mon'], $parts['mday'], $parts['year']);
if ($egwData['info_datecompleted'] != $enddate) continue;
}
if ((isset($egwData['info_datecompleted'])
&& (!isset($taskData['info_datecompleted']) || !$taskData['info_datecompleted'])) ||
(!isset($egwData['info_datecompleted'])
&& isset($taskData['info_datecompleted']) && $taskData['info_datecompleted'])
&& !$relax) continue;
return($itemID);
}
return false;
}
}

View File

@ -5,7 +5,7 @@
* @link http://www.egroupware.org
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @package infolog
* @copyright (c) 2003-6 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2003-9 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$
*/
@ -111,15 +111,17 @@ class infolog_hooks
}
/**
* populates $GLOBALS['settings'] for the preferences
* populates $settings for the preferences
*
* @return array
*/
static function settings()
{
/* Setup some values to fill the array of this app's settings below */
$ui = new infolog_ui(); // need some labels from
$info = new infolog_bo(); // need some labels from
$filters = $show_home = array();
$show_home[] = lang("DON'T show InfoLog");
foreach($ui->filters as $key => $label)
foreach($info->filters as $key => $label)
{
$show_home[$key] = $filters[$key] = lang($label);
}
@ -144,7 +146,7 @@ class infolog_hooks
);
/* Settings array for this app */
$GLOBALS['settings'] = array(
$settings = array(
'defaultFilter' => array(
'type' => 'select',
'label' => 'Default Filter for InfoLog',
@ -206,6 +208,15 @@ class infolog_hooks
'xmlrpc' => True,
'admin' => False
),
'limit_des_lines' => array(
'type' => 'input',
'size' => 5,
'label' => 'Limit number of description lines (default 5, 0 for no limit)',
'name' => 'limit_des_lines',
'help' => 'How many describtion lines should be directly visible. Further lines are available via a scrollbar.',
'xmlrpc' => True,
'admin' => False
),
'set_start' => array(
'type' => 'select',
'label' => 'Startdate for new entries',
@ -223,14 +234,14 @@ class infolog_hooks
'type' => 'multiselect',
'label' => 'Which types should the calendar show',
'name' => 'cal_show',
'values' => $ui->bo->enums['type'],
'values' => $info->enums['type'],
'help' => 'Can be used to show further InfoLog types in the calendar or limit it to show eg. only tasks.',
'xmlrpc' => True,
'admin' => False
),
'cat_add_default' => array(
'type' => 'select',
'label' => 'Default category for new Infolog entries',
'label' => 'Default categorie for new Infolog entries',
'name' => 'cat_add_default',
'values' => self::all_cats(),
'help' => 'You can choose a categorie to be preselected, when you create a new Infolog entry',
@ -241,7 +252,7 @@ class infolog_hooks
);
// notification preferences
$GLOBALS['settings']['notify_creator'] = array(
$settings['notify_creator'] = array(
'type' => 'check',
'label' => 'Receive notifications about own items',
'name' => 'notify_creator',
@ -249,7 +260,7 @@ class infolog_hooks
'xmlrpc' => True,
'admin' => False,
);
$GLOBALS['settings']['notify_assigned'] = array(
$settings['notify_assigned'] = array(
'type' => 'select',
'label' => 'Receive notifications about items assigned to you',
'name' => 'notify_assigned',
@ -272,7 +283,7 @@ class infolog_hooks
'2d' => lang('%1 days in advance',2),
'3d' => lang('%1 days in advance',3),
);
$GLOBALS['settings']['notify_due_delegated'] = array(
$settings['notify_due_delegated'] = array(
'type' => 'select',
'label' => 'Receive notifications about due entries you delegated',
'name' => 'notify_due_delegated',
@ -281,7 +292,7 @@ class infolog_hooks
'xmlrpc' => True,
'admin' => False,
);
$GLOBALS['settings']['notify_due_responsible'] = array(
$settings['notify_due_responsible'] = array(
'type' => 'select',
'label' => 'Receive notifications about due entries you are responsible for',
'name' => 'notify_due_responsible',
@ -290,7 +301,7 @@ class infolog_hooks
'xmlrpc' => True,
'admin' => False,
);
$GLOBALS['settings']['notify_start_delegated'] = array(
$settings['notify_start_delegated'] = array(
'type' => 'select',
'label' => 'Receive notifications about starting entries you delegated',
'name' => 'notify_start_delegated',
@ -299,7 +310,7 @@ class infolog_hooks
'xmlrpc' => True,
'admin' => False,
);
$GLOBALS['settings']['notify_start_responsible'] = array(
$settings['notify_start_responsible'] = array(
'type' => 'select',
'label' => 'Receive notifications about starting entries you are responsible for',
'name' => 'notify_start_responsible',
@ -309,7 +320,7 @@ class infolog_hooks
'admin' => False,
);
return true; // otherwise prefs say it cant find the file ;-)
return $settings;
}
/**
@ -320,6 +331,7 @@ class infolog_hooks
private static function all_cats()
{
$categories = new categories('','infolog');
$accountId = $GLOBALS['egw_info']['user']['account_id'];
foreach((array)$categories->return_sorted_array(0,False,'','','',true) as $cat)
{
@ -329,6 +341,14 @@ class infolog_hooks
{
$s .= ' &#9830;';
}
elseif ($cat['owner'] != $accountId)
{
$s .= '&lt;' . $GLOBALS['egw']->accounts->id2name($cat['owner'], 'account_fullname') . '&gt;';
}
elseif ($cat['access'] == 'private')
{
$s .= ' &#9829;';
}
$sel_options[$cat['id']] = $s; // 0.9.14 only
}
return $sel_options;
@ -346,10 +366,7 @@ class infolog_hooks
if ($data['prefs']['notify_due_delegated'] || $data['prefs']['notify_due_responsible'] ||
$data['prefs']['notify_start_delegated'] || $data['prefs']['notify_start_responsible'])
{
require_once(EGW_API_INC.'/class.asyncservice.inc.php');
$async =& new asyncservice();
//$async->cancel_timer('infolog-async-notification');
$async = new asyncservice();
if (!$async->read('infolog-async-notification'))
{

View File

@ -4,13 +4,14 @@
*
* @link http://www.egroupware.org
* @author Lars Kneschke <lkneschke@egroupware.org>
* @author Joerg Lehrke <jlehrke@noc.de>
* @package infolog
* @subpackage syncml
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$
*/
require_once EGW_API_INC.'/horde/Horde/iCalendar.php';
require_once EGW_SERVER_ROOT.'/phpgwapi/inc/horde/lib/core.php';
/**
* InfoLog: Create and parse iCal's
@ -18,17 +19,24 @@ require_once EGW_API_INC.'/horde/Horde/iCalendar.php';
*/
class infolog_ical extends infolog_bo
{
/**
* @var array conversion of the priority egw => ical
*/
var $egw_priority2vcal_priority = array(
0 => 3,
1 => 2,
2 => 1,
3 => 1,
0 => 9, // low
1 => 5, // normal
2 => 3, // high
3 => 1, // urgent
);
/**
* @var array conversion of the priority ical => egw
*/
var $vcal_priority2egw_priority = array(
1 => 2,
2 => 1,
3 => 0,
9 => 0, 8 => 0, 7 => 0, // low
6 => 1, 5 => 1, 4 => 1, 0 => 1, // normal
3 => 2, 2 => 2, // high
1 => 3, // urgent
);
/**
@ -39,6 +47,32 @@ class infolog_ical extends infolog_bo
var $productManufacturer = 'file';
var $productName = '';
/**
* Shall we use the UID extensions of the description field?
*
* @var boolean
*/
var $uidExtension = false;
/**
* Client CTCap Properties
*
* @var array
*/
var $clientProperties;
/**
* Constructor
*
* @param array $_clientProperties client properties
*/
function __construct(&$_clientProperties = array())
{
parent::__construct();
$this->clientProperties = $_clientProperties;
}
/**
* Exports one InfoLog tast to an iCalendar VTODO
*
@ -51,53 +85,137 @@ class infolog_ical extends infolog_bo
{
$taskData = $this->read($_taskID);
$taskData = $GLOBALS['egw']->translation->convert($taskData, $GLOBALS['egw']->translation->charset(), 'UTF-8');
if ($taskData['info_id_parent'])
{
$parent = $this->read($taskData['info_id_parent']);
$taskData['info_id_parent'] = $parent['info_uid'];
}
else
{
$taskData['info_id_parent'] = '';
}
$taskGUID = $GLOBALS['egw']->common->generate_uid('infolog_task',$_taskID);
if ($this->uidExtension)
{
if (!preg_match('/\[UID:.+\]/m', $taskData['info_des']))
{
$taskData['info_des'] .= "\n[UID:" . $taskData['info_uid'] . "]";
if ($taskData['info_id_parent'] != '')
{
$taskData['info_des'] .= "\n[PARENT_UID:" . $taskData['info_id_parent'] . "]";
}
}
}
$vcal = &new Horde_iCalendar;
if (!empty($taskData['info_cat']))
{
$cats = $this->get_categories(array($taskData['info_cat']));
$taskData['info_cat'] = $cats[0];
}
$taskData = $GLOBALS['egw']->translation->convert($taskData,
$GLOBALS['egw']->translation->charset(), 'UTF-8');
$vcal = new Horde_iCalendar;
$vcal->setAttribute('VERSION',$_version);
$vcal->setAttribute('METHOD',$_method);
$vevent = Horde_iCalendar::newComponent('VTODO',$vcal);
// set fields that may contain non-ascii chars and encode them if necessary
foreach(array(
'SUMMARY' => $taskData['info_subject'],
'DESCRIPTION' => $taskData['info_des'],
'LOCATION' => $taskData['info_location'],
) as $field => $value)
if (!isset($this->clientProperties['SUMMARY']['Size']))
{
$vevent->setAttribute($field,$value);
$options = array();
if($this->productManufacturer != 'GroupDAV' && preg_match('/([\000-\012\015\016\020-\037\075])/',$value))
// make SUMMARY a required field
$this->clientProperties['SUMMARY']['Size'] = 0xFFFF;
$this->clientProperties['SUMMARY']['NoTruncate'] = false;
}
// set fields that may contain non-ascii chars and encode them if necessary
foreach (array(
'SUMMARY' => $taskData['info_subject'],
'DESCRIPTION' => $taskData['info_des'],
'LOCATION' => $taskData['info_location'],
'RELATED-TO' => $taskData['info_id_parent'],
'UID' => $taskData['info_uid'],
'CATEGORIES' => $taskData['info_cat'],
) as $field => $value)
{
if (isset($this->clientProperties[$field]['Size']))
{
$options['ENCODING'] = 'QUOTED-PRINTABLE';
$size = $this->clientProperties[$field]['Size'];
$noTruncate = $this->clientProperties[$field]['NoTruncate'];
#Horde::logMessage("VTODO $field Size: $size, NoTruncate: " .
# ($noTruncate ? 'TRUE' : 'FALSE'), __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
if($this->productManufacturer != 'GroupDAV' && preg_match('/([\177-\377])/',$value))
else
{
$size = -1;
$noTruncate = false;
}
$cursize = strlen($value);
if (($size > 0) && $cursize > $size)
{
if ($noTruncate)
{
Horde::logMessage("VTODO $field omitted due to maximum size $size",
__FILE__, __LINE__, PEAR_LOG_WARNING);
continue; // skip field
}
// truncate the value to size
$value = substr($value, 0, $size -1);
#Horde::logMessage("VTODO $field truncated to maximum size $size",
# __FILE__, __LINE__, PEAR_LOG_INFO);
}
if (empty($value) && ($size < 0 || $noTruncate)) continue;
if ($field == 'RELATED-TO')
{
$options = array('RELTYPE' => 'PARENT');
}
else
{
$options = array();
}
/*if(preg_match('/([\000-\012\015\016\020-\037\075])/', $value)) {
$options['ENCODING'] = 'QUOTED-PRINTABLE';
}*/
if ($this->productManufacturer != 'groupdav'
&& preg_match('/([\177-\377])/',$value))
{
$options['CHARSET'] = 'UTF-8';
}
if ($options) $vevent->setParameter($field, $options);
$vevent->setAttribute($field, $value, $options);
}
$dateOnly = false;
if ($taskData['info_startdate'])
{
self::setDateOrTime($vevent,'DTSTART',$taskData['info_startdate']);
$dateOnly = self::setDateOrTime($vevent, 'DTSTART', $taskData['info_startdate']);
}
if ($taskData['info_enddate'])
{
self::setDateOrTime($vevent,'DUE',$taskData['info_enddate']);
$parts = @getdate($taskData['info_enddate']);
if ($dateOnly)
{
$value = @mktime(0, 0, 0, $parts['mon'], $parts['mday'], $parts['year']);
}
else
{
$value = @mktime(12, 0, 0, $parts['mon'], $parts['mday'], $parts['year']);
}
self::setDateOrTime($vevent, 'DUE', $taskData['info_enddate']);
}
if ($taskData['info_datecompleted'])
{
self::setDateOrTime($vevent,'COMPLETED',$taskData['info_datecompleted']);
self::setDateOrTime($vevent, 'COMPLETED', $taskData['info_datecompleted']);
}
$vevent->setAttribute('DTSTAMP',time());
$vevent->setAttribute('CREATED',$GLOBALS['egw']->contenthistory->getTSforAction('infolog_task',$_taskID,'add'));
$vevent->setAttribute('LAST-MODIFIED',$GLOBALS['egw']->contenthistory->getTSforAction('infolog_task',$_taskID,'modify'));
$vevent->setAttribute('UID',$taskData['info_uid']);
$vevent->setAttribute('CLASS',$taskData['info_access'] == 'public' ? 'PUBLIC' : 'PRIVATE');
$vevent->setAttribute('STATUS',$this->status2vtodo($taskData['info_status']));
// we try to preserv the original infolog status as X-INFOLOG-STATUS, so we can restore it, if the user does not modify STATUS
@ -105,38 +223,12 @@ class infolog_ical extends infolog_bo
$vevent->setAttribute('PERCENT-COMPLETE',$taskData['info_percent']);
$vevent->setAttribute('PRIORITY',$this->egw_priority2vcal_priority[$taskData['info_priority']]);
if (!empty($taskData['info_cat']))
{
$cats = $this->get_categories(array($taskData['info_cat']));
$vevent->setAttribute('CATEGORIES', $cats[0]);
}
$vcal->addComponent($vevent);
return $vcal->exportvCalendar();
}
/**
* Check if use set a date or date+time and export it as such
*
* @param Horde_iCalendar_* $vevent
* @param string $attr attribute name
* @param int $value timestamp
*/
static function setDateOrTime($vevent,$attr,$value)
{
// check if use set only a date --> export it as such
if (date('H:i',$value) == '00:00')
{
$vevent->setAttribute($attr,array(
'year' => date('Y',$value),
'month' => date('m',$value),
'mday' => date('d',$value),
),array('VALUE' => 'DATE'));
}
else
{
$vevent->setAttribute($attr,$value);
}
$retval = $vcal->exportvCalendar();
Horde::logMessage("exportVTODO:\n" . print_r($retval, true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
return $retval;
}
/**
@ -144,102 +236,143 @@ class infolog_ical extends infolog_bo
*
* @param string $_vcalData
* @param int $_taskID=-1 info_id, default -1 = new entry
* @param boolean $merge=false merge data with existing entry
* @return int|boolean integer info_id or false on error
*/
function importVTODO(&$_vcalData, $_taskID=-1)
function importVTODO(&$_vcalData, $_taskID=-1, $merge=false)
{
if(!$taskData = $this->vtodotoegw($_vcalData,$_taskID))
{
return false;
}
if (!$taskData = $this->vtodotoegw($_vcalData,$_taskID)) return false;
// we suppose that a not set status in a vtodo means that the task did not started yet
if(empty($taskData['info_status']))
if (empty($taskData['info_status']))
{
$taskData['info_status'] = 'not-started';
}
if (empty($taskData['info_datecompleted']))
{
$taskData['info_datecompleted'] = 0;
}
return $this->write($taskData);
}
function searchVTODO($_vcalData, $contentID=null)
{
if(!$egwData = $this->vtodotoegw($_vcalData)) {
return false;
}
/**
* Search a matching infolog entry for the VTODO data
*
* @param string $_vcalData VTODO
* @param int $contentID=null infolog_id (or null, if unkown)
* @param boolean $relax=false if true, a weaker match algorithm is used
* @return infolog_id of a matching entry or false, if nothing was found
*/
function searchVTODO($_vcalData, $contentID=null, $relax=false) {
$result = false;
$myfilter = array('col_filter' => array('info_uid'=>$egwData['info_uid'])) ;
if ($egwData['info_uid'] && ($found=parent::search($myfilter)) && ($uidmatch = array_shift($found)))
if (($egwData = $this->vtodotoegw($_vcalData)))
{
return $uidmatch['info_id'];
};
unset($egwData['info_uid']);
if ($contentID) {
$egwData['info_id'] = $contentID;
}
#unset($egwData['info_priority']);
$filter = array('col_filter' => $egwData);
if($foundItems = $this->search($filter)) {
if(count($foundItems) > 0) {
$itemIDs = array_keys($foundItems);
return $itemIDs[0];
if ($contentID)
{
$egwData['info_id'] = $contentID;
}
$result = $this->findVTODO($egwData, $relax);
}
return false;
return $result;
}
function vtodotoegw($_vcalData,$_taskID=-1)
/**
* Convert VTODO into a eGW infolog entry
*
* @param string $_vcalData VTODO data
* @param int $_taskID=-1 infolog_id of the entry
* @return array infolog entry or false on error
*/
function vtodotoegw($_vcalData, $_taskID=-1)
{
$vcal = &new Horde_iCalendar;
if(!$vcal->parsevCalendar($_vcalData))
$vcal = new Horde_iCalendar;
if (!($vcal->parsevCalendar($_vcalData))) return false;
if (isset($GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length']))
{
return FALSE;
$minimum_uid_length = $GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length'];
}
else
{
$minimum_uid_length = 8;
}
$components = $vcal->getComponents();
foreach($components as $component)
foreach ($components as $component)
{
if(is_a($component, 'Horde_iCalendar_vtodo'))
if (is_a($component, 'Horde_iCalendar_vtodo'))
{
$taskData = array();
if($_taskID > 0)
$taskData['info_type'] = 'task';
if ($_taskID > 0)
{
$taskData['info_id'] = $_taskID;
}
foreach($component->_attributes as $attributes)
foreach ($component->_attributes as $attributes)
{
switch($attributes['name'])
//$attributes['value'] = trim($attributes['value']);
if (empty($attributes['value'])) continue;
switch ($attributes['name'])
{
case 'CLASS':
$taskData['info_access'] = strtolower($attributes['value']);
$taskData['info_access'] = strtolower($attributes['value']);
break;
case 'DESCRIPTION':
$taskData['info_des'] = $attributes['value'];
$value = $attributes['value'];
if (preg_match('/\s*\[UID:(.+)?\]/Usm', $value, $matches))
{
if (!isset($taskData['info_uid'])
&& strlen($matches[1]) >= $minimum_uid_length)
{
$taskData['info_uid'] = $matches[1];
}
//$value = str_replace($matches[0], '', $value);
}
if (preg_match('/\s*\[PARENT_UID:(.+)?\]/Usm', $value, $matches))
{
if (!isset($taskData['info_id_parent'])
&& strlen($matches[1]) >= $minimum_uid_length)
{
$taskData['info_id_parent'] = $this->getParentID($matches[1]);
}
//$value = str_replace($matches[0], '', $value);
}
$taskData['info_des'] = $value;
break;
case 'LOCATION':
$taskData['info_location'] = $attributes['value'];
$taskData['info_location'] = $attributes['value'];
break;
case 'DUE':
$taskData['info_enddate'] = $attributes['value'];
// eGroupWare uses date only
$parts = @getdate($attributes['value']);
$value = @mktime(0, 0, 0, $parts['mon'], $parts['mday'], $parts['year']);
$taskData['info_enddate'] = $value;
break;
case 'COMPLETED':
$taskData['info_datecompleted'] = $attributes['value'];
break;
case 'DTSTART':
$taskData['info_startdate'] = $attributes['value'];
$taskData['info_startdate'] = $attributes['value'];
break;
case 'PRIORITY':
if (1 <= $attributes['value'] && $attributes['value'] <= 3)
{
$taskData['info_priority'] = $this->vcal_priority2egw_priority[$attributes['value']];
}
else
{
$taskData['info_priority'] = 1; // default = normal
if (1 <= $attributes['value'] && $attributes['value'] <= 3) {
$taskData['info_priority'] = $this->vcal_priority2egw_priority[$attributes['value']];
} else {
$taskData['info_priority'] = 1; // default = normal
}
break;
case 'STATUS':
// check if we (still) have X-INFOLOG-STATUS set AND it would give an unchanged status (no change by the user)
foreach($component->_attributes as $attr)
@ -249,28 +382,26 @@ class infolog_ical extends infolog_bo
$taskData['info_status'] = $this->vtodo2status($attributes['value'],
$attr['name'] == 'X-INFOLOG-STATUS' ? $attr['value'] : null);
break;
case 'SUMMARY':
$taskData['info_subject'] = $attributes['value'];
$taskData['info_subject'] = $attributes['value'];
break;
case 'RELATED-TO':
$taskData['info_id_parent'] = $this->getParentID($attributes['value']);
break;
case 'CATEGORIES':
$cats = $this->find_or_add_categories(explode(',', $attributes['value']));
$taskData['info_cat'] = $cats[0];
break;
case 'UID':
$taskData['info_uid'] = $attributes['value'];
if ($_taskID <= 0 && !empty($attributes['value']) && ($uid_task = $this->read($attributes['value'])))
{
$taskData['info_id'] = $uid_task['id'];
unset($uid_task);
}
// not use weak uids that might come from syncml clients
if (isset($event['uid']) && (strlen($event['uid']) < 20 || is_numeric($event['uid'])))
{
unset ($event['uid']);
}
case 'UID':
if (strlen($attributes['value']) >= $minimum_uid_length) {
$taskData['info_uid'] = $attributes['value'];
}
break;
case 'PERCENT-COMPLETE':
$taskData['info_percent'] = (int) $attributes['value'];
break;
@ -280,18 +411,28 @@ class infolog_ical extends infolog_bo
# do NOT convert here
#$taskData = $GLOBALS['egw']->translation->convert($taskData, 'UTF-8');
Horde::logMessage("vtodotoegw:\n" . print_r($taskData, true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
return $taskData;
}
}
return FALSE;
return false;
}
/**
* Export an infolog entry as VNOTE
*
* @param int $_noteID the infolog_id of the entry
* @param string $_type content type (e.g. text/plain)
* @return string VNOTE representation of the infolog entry
*/
function exportVNOTE($_noteID, $_type)
{
$note = $this->read($_noteID);
$note = $GLOBALS['egw']->translation->convert($note, $GLOBALS['egw']->translation->charset(), 'UTF-8');
$note = $GLOBALS['egw']->translation->convert($note,
$GLOBALS['egw']->translation->charset(), 'UTF-8');
switch($_type)
switch ($_type)
{
case 'text/plain':
$txt = $note['info_subject']."\n\n".$note['info_des'];
@ -299,66 +440,128 @@ class infolog_ical extends infolog_bo
break;
case 'text/x-vnote':
$noteGUID = $GLOBALS['egw']->common->generate_uid('infolog_note',$_noteID);
$vnote = &new Horde_iCalendar_vnote();
$vnote = new Horde_iCalendar_vnote();
$options = array('CHARSET' => 'UTF-8');
$vNote->setAttribute('VERSION', '1.1');
$vnote->setAttribute('SUMMARY',$note['info_subject']);
$vnote->setAttribute('BODY',$note['info_des']);
if($note['info_startdate'])
foreach (array( 'SUMMARY' => $note['info_subject'],
'BODY' => $note['info_des'],
) as $field => $value)
{
$vnote->setAttribute($field, $value);
if ($this->productManufacturer != 'groupdav'
&& preg_match('/([\177-\377])/', $value))
{
$vevent->setParameter($field, $options);
}
}
if ($note['info_startdate'])
{
$vnote->setAttribute('DCREATED',$note['info_startdate']);
}
$vnote->setAttribute('DCREATED',$GLOBALS['egw']->contenthistory->getTSforAction('infolog_note',$_noteID,'add'));
$vnote->setAttribute('LAST-MODIFIED',$GLOBALS['egw']->contenthistory->getTSforAction('infolog_note',$_noteID,'modify'));
if (!empty($note['info_cat']))
{
$cats = $this->get_categories(array($note['info_cat']));
$vnote->setAttribute('CATEGORIES', $cats[0]);
$value = $cats[0];
$vnote->setAttribute('CATEGORIES', $value);
if ($this->productManufacturer != 'groupdav'
&& preg_match('/([\177-\377])/', $value))
{
$vevent->setParameter('CATEGORIES', $options);
}
}
#$vnote->setAttribute('UID',$noteGUID);
#$vnote->setAttribute('CLASS',$taskData['info_access'] == 'public' ? 'PUBLIC' : 'PRIVATE');
#$options = array('CHARSET' => 'UTF-8','ENCODING' => 'QUOTED-PRINTABLE');
#$vnote->setParameter('SUMMARY', $options);
#$vnote->setParameter('DESCRIPTION', $options);
return $vnote->exportvCalendar();
break;
}
return false;
}
function importVNOTE(&$_vcalData, $_type, $_noteID = -1)
/**
* Check whether to export a date or date+time
*
* @param Horde_iCalendar_* $vevent
* @param string $attr attribute name
* @param int $value timestamp
* @return boolean true, if date only
*/
static function setDateOrTime($vevent,$attr,$value)
{
if(!$note = $this->vnotetoegw($_vcalData, $_type))
if (date('Hi',$value) == '0000')
{
return false;
$vevent->setAttribute($attr, array(
'year' => date('Y',$value),
'month' => date('m',$value),
'mday' => date('d',$value),
), array('VALUE' => 'DATE'));
return true;
}
if($_noteID > 0)
else
{
$note['info_id'] = $_noteID;
$vevent->setAttribute($attr, $value);
}
return false;
}
if(empty($note['info_status'])) {
$note['info_status'] = 'done';
}
/**
* Import a VNOTE component of an iCal
*
* @param string $_vcalData
* @param string $_type content type (eg.g text/plain)
* @param int $_taskID=-1 info_id, default -1 = new entry
* @param boolean $merge=false merge data with existing entry
* @return int|boolean integer info_id or false on error
*/
function importVNOTE(&$_vcalData, $_type, $_noteID = -1, $merge=false)
{
if (!($note = $this->vnotetoegw($_vcalData, $_type))) return false;
if($_noteID > 0) $note['info_id'] = $_noteID;
if (empty($note['info_status'])) $note['info_status'] = 'done';
#_debug_array($taskData);exit;
return $this->write($note);
}
/**
* Search a matching infolog entry for the VNOTE data
*
* @param string $_vcalData VNOTE
* @param int $contentID=null infolog_id (or null, if unkown)
* @return infolog_id of a matching entry or false, if nothing was found
*/
function searchVNOTE($_vcalData, $_type, $contentID=null)
{
if(!$note = $this->vnotetoegw($_vcalData,$_type)) {
return false;
}
if ($contentID) {
$note['info_id'] = $contentID;
if (!($note = $this->vnotetoegw($_vcalData,$_type))) return false;
if ($contentID) $note['info_id'] = $contentID;
unset($note['info_startdate']);
$filter = array();
if (!empty($note['info_des']))
{
$description = trim(preg_replace("/\r?\n?\\[[A-Z_]+:.*\\]/i", '', $note['info_des']));
unset($note['info_des']);
if (strlen($description))
{
$filter['search'] = $description;
}
}
$filter = array('col_filter' => $note);
if($foundItems = $this->search($filter)) {
if(count($foundItems) > 0) {
$filter['col_filter'] = $note;
if (($foundItems = $this->search($filter)))
{
if (count($foundItems) > 0)
{
$itemIDs = array_keys($foundItems);
return $itemIDs[0];
}
@ -367,9 +570,16 @@ class infolog_ical extends infolog_bo
return false;
}
/**
* Convert VTODO into a eGW infolog entry
*
* @param string $_data VNOTE data
* @param string $_type content type (eg.g text/plain)
* @return array infolog entry or false on error
*/
function vnotetoegw($_data, $_type)
{
switch($_type)
switch ($_type)
{
case 'text/plain':
$note = array();
@ -394,35 +604,32 @@ class infolog_ical extends infolog_bo
break;
case 'text/x-vnote':
$vnote = &new Horde_iCalendar;
if (!$vcal->parsevCalendar($_data))
{
return FALSE;
}
$vnote = new Horde_iCalendar;
if (!$vcal->parsevCalendar($_data)) return false;
$components = $vnote->getComponent();
if(count($components) > 0)
foreach ($components as $component)
{
$component = $components[0];
if(is_a($component, 'Horde_iCalendar_vnote'))
if (is_a($component, 'Horde_iCalendar_vnote'))
{
$note = array();
$note['info_type'] = 'note';
foreach($component->_attributes as $attribute)
foreach ($component->_attributes as $attribute)
{
switch ($attribute['name'])
{
case 'BODY':
$note['info_des'] = $attribute['value'];
break;
case 'SUMMARY':
$note['info_subject'] = $attribute['value'];
break;
case 'CATEGORIES':
{
$cats = $this->find_or_add_categories(explode(',', $attribute['value']));
$note['info_cat'] = $cats[0];
}
$cats = $this->find_or_add_categories(explode(',', $attribute['value']));
$note['info_cat'] = $cats[0];
break;
}
}
@ -430,7 +637,7 @@ class infolog_ical extends infolog_bo
return $note;
}
}
return FALSE;
return false;
}
/**
@ -441,11 +648,41 @@ class infolog_ical extends infolog_bo
* @param string $_productManufacturer
* @param string $_productName
*/
function setSupportedFields($_productManufacturer='file', $_productName='')
function setSupportedFields($_productManufacturer='', $_productName='')
{
// save them vor later use
$this->productManufacturer = $_productManufacturer;
$this->productName = $_productName;
$state = &$_SESSION['SyncML.state'];
if (isset($state))
{
$deviceInfo = $state->getClientDeviceInfo();
}
// store product manufacturer and name, to be able to use it elsewhere
if ($_productManufacturer)
{
$this->productManufacturer = strtolower($_productManufacturer);
$this->productName = strtolower($_productName);
}
if (isset($deviceInfo) && is_array($deviceInfo))
{
if (!isset($this->productManufacturer)
|| $this->productManufacturer == ''
|| $this->productManufacturer == 'file')
{
$this->productManufacturer = strtolower($deviceInfo['manufacturer']);
}
if (!isset($this->productName) || $this->productName == '')
{
$this->productName = strtolower($deviceInfo['model']);
}
if (isset($deviceInfo['uidExtension'])
&& $deviceInfo['uidExtension'])
{
$this->uidExtension = true;
}
}
Horde::logMessage('setSupportedFields(' . $this->productManufacturer . ', ' . $this->productName .')', __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
}

View File

@ -4,13 +4,14 @@
*
* @link http://www.egroupware.org
* @author Lars Kneschke <lkneschke@egroupware.org>
* @author Joerg Lehrke <jlehrke@noc.de>
* @package infolog
* @subpackage syncml
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$
*/
require_once EGW_API_INC.'/horde/Horde/iCalendar.php';
require_once EGW_SERVER_ROOT.'/phpgwapi/inc/horde/lib/core.php';
/**
* InfoLog: Create and parse SIF
@ -38,58 +39,88 @@ class infolog_sif extends infolog_bo
// mappings for SIFTask to InfologTask
var $_sifTaskMapping = array(
'ActualWork' => '',
'ActualWork' => '',
'BillingInformation' => '',
'Body' => 'info_des',
'Categories' => 'info_cat',
'Companies' => '',
'Complete' => '',
'DateCompleted' => 'info_datecompleted',
'DueDate' => 'info_enddate',
'Importance' => 'info_priority',
'IsRecurring' => '',
'Mileage' => '',
'PercentComplete' => 'info_percent',
'ReminderSet' => '',
'ReminderTime' => '',
'Sensitivity' => 'info_access',
'StartDate' => 'info_startdate',
'Status' => 'info_status',
'Subject' => 'info_subject',
'TeamTask' => '',
'TotalWork' => '',
'RecurrenceType' => '',
'Interval' => '',
'MonthOfYear' => '',
'DayOfMonth' => '',
'DayOfWeekMask' => '',
'Instance' => '',
'PatternStartDate' => '',
'NoEndDate' => '',
'PatternEndDate' => '',
'Occurrences' => '',
'Body' => 'info_des',
'Categories' => 'info_cat',
'Companies' => '',
'Complete' => 'complete',
'DateCompleted' => 'info_datecompleted',
'DueDate' => 'info_enddate',
'Importance' => 'info_priority',
'IsRecurring' => '',
'Mileage' => '',
'PercentComplete' => 'info_percent',
'ReminderSet' => '',
'ReminderTime' => '',
'Sensitivity' => 'info_access',
'StartDate' => 'info_startdate',
'Status' => 'info_status',
'Subject' => 'info_subject',
'TeamTask' => '',
'TotalWork' => '',
'RecurrenceType' => '',
'Interval' => '',
'MonthOfYear' => '',
'DayOfMonth' => '',
'DayOfWeekMask' => '',
'Instance' => '',
'PatternStartDate' => '',
'NoEndDate' => '',
'PatternEndDate' => '',
'Occurrences' => '',
);
// standard headers
const xml_decl = '<?xml version="1.0" encoding="UTF-8"?>';
const SIF_decl = '<SIFVersion>1.1</SIFVersion>';
/**
* name and version of the sync-client
*
* @var string
*/
var $productName = 'mozilla plugin';
var $productSoftwareVersion = '0.3';
/**
* Shall we use the UID extensions of the description field?
*
* @var boolean
*/
var $uidExtension = false;
function startElement($_parser, $_tag, $_attributes) {
function startElement($_parser, $_tag, $_attributes)
{
// nothing to do
}
function endElement($_parser, $_tag) {
error_log("infolog: tag=$_tag data=".trim($this->sifData));
if(!empty($this->_currentSIFMapping[$_tag])) {
function endElement($_parser, $_tag)
{
#error_log("infolog: tag=$_tag data=".trim($this->sifData));
if (!empty($this->_currentSIFMapping[$_tag]))
{
$this->_extractedSIFData[$this->_currentSIFMapping[$_tag]] = trim($this->sifData);
}
unset($this->sifData);
}
function characterData($_parser, $_data) {
function characterData($_parser, $_data)
{
$this->sifData .= $_data;
}
function siftoegw($_sifData, $_sifType) {
/**
* Convert SIF data into a eGW infolog entry
*
* @param string $sifData the SIF data
* @param string $_sifType type (note/task)
* @return array infolog entry or false on error
*/
function siftoegw($sifData, $_sifType)
{
$sysCharSet = $GLOBALS['egw']->translation->charset();
$sifData = base64_decode($_sifData);
#$tmpfname = tempnam('/tmp/sync/contents','sift_');
@ -115,29 +146,44 @@ class infolog_sif extends infolog_bo
xml_set_element_handler($this->xml_parser, "startElement", "endElement");
xml_set_character_data_handler($this->xml_parser, "characterData");
$this->strXmlData = xml_parse($this->xml_parser, $sifData);
if(!$this->strXmlData) {
if (!$this->strXmlData)
{
error_log(sprintf("XML error: %s at line %d",
xml_error_string(xml_get_error_code($this->xml_parser)),
xml_get_current_line_number($this->xml_parser)));
return false;
}
if(!array($this->_extractedSIFData)) {
return false;
}
if (!array($this->_extractedSIFData)) return false;
switch($_sifType) {
switch ($_sifType)
{
case 'task':
$taskData = array();
$vcal = &new Horde_iCalendar;
$vcal = new Horde_iCalendar;
$taskData['info_type'] = 'task';
$taskData['info_status'] = 'not-started';
foreach($this->_extractedSIFData as $key => $value) {
if (isset($GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length']))
{
$minimum_uid_length = $GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length'];
}
else
{
$minimum_uid_length = 8;
}
foreach ($this->_extractedSIFData as $key => $value)
{
$value = preg_replace('/<\!\[CDATA\[(.+)\]\]>/Usim', '$1', $value);
$value = $GLOBALS['egw']->translation->convert($value, 'utf-8', $sysCharSet);
error_log("infolog key=$key => value=$value");
#error_log("infolog key=$key => value=$value");
if (empty($value)) continue;
switch($key) {
switch($key)
{
case 'info_access':
$taskData[$key] = ((int)$value > 0) ? 'private' : 'public';
break;
@ -145,19 +191,18 @@ class infolog_sif extends infolog_bo
case 'info_datecompleted':
case 'info_enddate':
case 'info_startdate':
if(!empty($value)) {
if (!empty($value))
{
$taskData[$key] = $vcal->_parseDateTime($value);
// somehow the client always deliver a timestamp about 3538 seconds, when no startdate set.
if($taskData[$key] < 10000)
$taskData[$key] = '';
} else {
$taskData[$key] = '';
if ($taskData[$key] < 10000) unset($taskData[$key]);
}
break;
case 'info_cat':
if (!empty($value)) {
if (!empty($value))
{
$categories = $this->find_or_add_categories(explode(';', $value));
$taskData['info_cat'] = $categories[0];
}
@ -168,8 +213,8 @@ class infolog_sif extends infolog_bo
break;
case 'info_status':
$taskData[$key] = ((int)$value == 2) ? 'done' : 'ongoing';
switch($value) {
switch ($value)
{
case '0':
$taskData[$key] = 'not-started';
break;
@ -178,9 +223,20 @@ class infolog_sif extends infolog_bo
break;
case '2':
$taskData[$key] = 'done';
$taskData['info_percent'] = 100;
break;
case '3':
$taskData[$key] = 'waiting';
break;
case '4':
$taskData[$key] = 'cancelled';
if ($this->productName == 'blackberry plug-in')
{
$taskData[$key] = 'deferred';
}
else
{
$taskData[$key] = 'cancelled';
}
break;
default:
$taskData[$key] = 'ongoing';
@ -188,11 +244,35 @@ class infolog_sif extends infolog_bo
}
break;
case 'complete':
$taskData['info_status'] = 'done';
$taskData['info_percent'] = 100;
break;
case 'info_des':
// extract our UID and PARENT_UID information
if (preg_match('/\s*\[UID:(.+)?\]/Usm', $value, $matches))
{
if (strlen($matches[1]) >= $minimum_uid_length)
{
$taskData['info_uid'] = $matches[1];
}
//$value = str_replace($matches[0], '', $value);
}
if (preg_match('/\s*\[PARENT_UID:(.+)?\]/Usm', $value, $matches))
{
if (strlen($matches[1]) >= $minimum_uid_length)
{
$taskData['info_id_parent'] = $this->getParentID($matches[1]);
}
//$value = str_replace($matches[0], '', $value);
}
default:
$taskData[$key] = $value;
break;
}
error_log("infolog task key=$key => value=".$taskData[$key]);
#error_log("infolog task key=$key => value=" . $taskData[$key]);
}
return $taskData;
@ -201,30 +281,34 @@ class infolog_sif extends infolog_bo
case 'note':
$noteData = array();
$noteData['info_type'] = 'note';
$vcal = &new Horde_iCalendar;
$vcal = new Horde_iCalendar;
foreach($this->_extractedSIFData as $key => $value)
foreach ($this->_extractedSIFData as $key => $value)
{
$value = preg_replace('/<\!\[CDATA\[(.+)\]\]>/Usim', '$1', $value);
$value = $GLOBALS['egw']->translation->convert($value, 'utf-8', $sysCharSet);
error_log("infolog client key=$key => value=".$value);
#error_log("infolog client key=$key => value=" . $value);
switch ($key)
{
case 'info_startdate':
if(!empty($value)) {
if (!empty($value))
{
$noteData[$key] = $vcal->_parseDateTime($value);
// somehow the client always deliver a timestamp about 3538 seconds, when no startdate set.
if($noteData[$key] < 10000)
$noteData[$key] = '';
} else {
if ($noteData[$key] < 10000) $noteData[$key] = '';
}
else
{
$noteData[$key] = '';
}
break;
case 'info_cat':
if (!empty($value)) {
if (!empty($value))
{
$categories = $this->find_or_add_categories(explode(';', $value));
$taskData['info_cat'] = $categories[0];
$noteData['info_cat'] = $categories[0];
}
break;
@ -232,7 +316,7 @@ class infolog_sif extends infolog_bo
$noteData[$key] = $value;
break;
}
error_log("infolog note key=$key => value=".$noteData[$key]);
#error_log("infolog note key=$key => value=".$noteData[$key]);
}
return $noteData;
break;
@ -243,17 +327,33 @@ class infolog_sif extends infolog_bo
}
}
function searchSIF($_sifData, $_sifType, $contentID=null) {
if(!$egwData = $this->siftoegw($_sifData, $_sifType)) {
return false;
}
if ($contentID) {
$egwData['info_id'] = $contentID;
}
/**
* Search for SIF data a matching infolog entry
*
* @param string $sifData the SIF data
* @param string $_sifType type (note/task)
* @param int $contentID=null infolog_id (or null, if unkown)
* @param boolean $relax=false if true, a weaker match algorithm is used
* @return infolog_id of a matching entry or false, if nothing was found
*/
function searchSIF($_sifData, $_sifType, $contentID=null, $relax=false)
{
if (!($egwData = $this->siftoegw($_sifData, $_sifType))) return false;
$filter = array('col_filter' => $egwData);
if($foundItems = $this->search($filter)) {
if(count($foundItems) > 0) {
if ($contentID) $egwData['info_id'] = $contentID;
if ($_sifType == 'task') return $this->findVTODO($egwData, $relax);
if ($_sifType == 'note') unset($egwData['info_startdate']);
$filter = array();
$filter['col_filter'] = $egwData;
if ($foundItems = $this->search($filter))
{
if (count($foundItems) > 0)
{
$itemIDs = array_keys($foundItems);
return $itemIDs[0];
}
@ -262,46 +362,119 @@ class infolog_sif extends infolog_bo
return false;
}
function addSIF($_sifData, $_id, $_sifType) {
if(!$egwData = $this->siftoegw($_sifData, $_sifType)) {
return false;
}
/**
* Add SIF data entry
*
* @param string $sifData the SIF data
* @param string $_sifType type (note/task)
* @param boolean $merge=false reserved for future use
* @return infolog_id of the new entry or false, for errors
*/
function addSIF($_sifData, $_id, $_sifType, $merge=false)
{
if (!($egwData = $this->siftoegw($_sifData, $_sifType))) return false;
if($_id > 0)
$egwData['info_id'] = $_id;
if ($_id > 0) $egwData['info_id'] = $_id;
if (empty($taskData['info_datecompleted']))
{
$taskData['info_datecompleted'] = 0;
}
$egwID = $this->write($egwData, false);
return $egwID;
}
function getSIF($_id, $_sifType) {
switch($_sifType) {
/**
* Export an infolog entry as SIF data
*
* @param int $_id the infolog_id of the entry
* @param string $_sifType type (note/task)
* @return string SIF representation of the infolog entry
*/
function getSIF($_id, $_sifType)
{
$sysCharSet = $GLOBALS['egw']->translation->charset();
switch($_sifType)
{
case 'task':
if($taskData = $this->read($_id)) {
$sysCharSet = $GLOBALS['egw']->translation->charset();
$vcal = &new Horde_iCalendar;
if (($taskData = $this->read($_id)))
{
$vcal = new Horde_iCalendar('1.0');
$sifTask = '<task>';
foreach($this->_sifTaskMapping as $sifField => $egwField)
if ($taskData['info_id_parent'])
{
if(empty($egwField)) continue;
$parent = $this->read($taskData['info_id_parent']);
$taskData['info_id_parent'] = $parent['info_uid'];
}
else
{
$taskData['info_id_parent'] = '';
}
if (!preg_match('/\[UID:.+\]/m', $taskData['info_des']))
{
$taskData['info_des'] .= "\r\n[UID:" . $taskData['info_uid'] . "]";
if ($taskData['info_id_parent'] != '')
{
$taskData['info_des'] .= "\r\n[PARENT_UID:" . $taskData['info_id_parent'] . "]";
}
}
$sifTask = self::xml_decl . "\n<task>" . self::SIF_decl;
foreach ($this->_sifTaskMapping as $sifField => $egwField)
{
if (empty($egwField)) continue;
$value = $GLOBALS['egw']->translation->convert($taskData[$egwField], $sysCharSet, 'utf-8');
switch($sifField) {
switch ($sifField)
{
case 'Complete':
// is handled with DateCompleted
break;
case 'DateCompleted':
case 'DueDate':
case 'StartDate':
if(!empty($value)) {
$value = $vcal->_exportDateTime($value);
if ($taskData[info_status] == 'done')
{
$sifTask .= "<Complete>1</Complete>";
}
else
{
$sifTask .= "<DateCompleted></DateCompleted><Complete>0</Complete>";
continue;
}
case 'DueDate':
if (!empty($value))
{
$hdate = new Horde_Date($value);
$value = $vcal->_exportDate($hdate, '000000Z');
$sifTask .= "<$sifField>$value</$sifField>";
}
else
{
$sifTask .= "<$sifField></$sifField>";
}
break;
case 'StartDate':
if (!empty($value))
{
$value = $vcal->_exportDateTime($value);
$sifTask .= "<$sifField>$value</$sifField>";
}
else
{
$sifTask .= "<$sifField></$sifField>";
}
$sifTask .= "<$sifField>$value</$sifField>";
break;
case 'Importance':
if($value > 3) $value = 3;
if ($value > 3) $value = 3;
$sifTask .= "<$sifField>$value</$sifField>";
break;
@ -311,20 +484,26 @@ class infolog_sif extends infolog_bo
break;
case 'Status':
switch($value) {
switch ($value)
{
case 'cancelled':
case 'deferred':
$value = '4';
break;
case 'waiting':
case 'nonactive':
$value = '3';
break;
case 'done':
case 'archive':
case 'billed':
$value = '2';
break;
case 'not-started':
case 'template':
$value = '0';
break;
case 'ongoing':
$value = '1';
break;
default:
default: //ongoing
$value = 1;
break;
}
@ -332,102 +511,69 @@ class infolog_sif extends infolog_bo
break;
case 'Categories':
if (!empty($value))
if (!empty($value) && $value)
{
$value = implode('; ', $this->get_categories(array($value)));
$value = $GLOBALS['egw']->translation->convert($value, $sysCharSet, 'utf-8');
}
$sifTask .= "<$sifField>$value</$sifField>";
break;
else
{
break;
}
default:
$value = @htmlspecialchars($value, ENT_QUOTES, 'utf-8');
$sifTask .= "<$sifField>$value</$sifField>";
break;
}
}
$sifTask .= '<ActualWork>0</ActualWork><IsRecurring>0</IsRecurring>';
return base64_encode($sifTask);
/* return base64_encode("<task>
<ActualWork>0</ActualWork>
<BillingInformation></BillingInformation>
<Body></Body>
<Categories></Categories>
<Companies></Companies>
<Complete>0</Complete>
<DateCompleted></DateCompleted>
<DueDate></DueDate>
<Importance>1</Importance>
<IsRecurring>0</IsRecurring>
<Mileage></Mileage>
<PercentComplete>0</PercentComplete>
<ReminderSet>0</ReminderSet>
<ReminderTime></ReminderTime>
<Sensitivity>0</Sensitivity>
<StartDate>45001231T230000Z</StartDate>
<Status>3</Status>
<Subject>TARAAA3</Subject>
<TeamTask>0</TeamTask>
<TotalWork>0</TotalWork>
<RecurrenceType>1</RecurrenceType>
<Interval>1</Interval>
<MonthOfYear>0</MonthOfYear>
<DayOfMonth>0</DayOfMonth>
<DayOfWeekMask>4</DayOfWeekMask>
<Instance>0</Instance>
<PatternStartDate>20060320T230000Z</PatternStartDate>
<NoEndDate>1</NoEndDate>
<PatternEndDate></PatternEndDate>
<Occurrences>10</Occurrences>
</task>
"); */
$sifTask .= '<ActualWork>0</ActualWork><IsRecurring>0</IsRecurring></task>';
return $sifTask;
}
break;
case 'note':
if($taskData = $this->read($_id)) {
$sysCharSet = $GLOBALS['egw']->translation->charset();
$vcal = &new Horde_iCalendar;
if (($taskData = $this->read($_id)))
{
$vcal = new Horde_iCalendar('1.0');
$sifNote = '<note>';
$sifNote = self::xml_decl . "\n<note>" . self::SIF_decl;
foreach($this->_sifNoteMapping as $sifField => $egwField)
foreach ($this->_sifNoteMapping as $sifField => $egwField)
{
if(empty($egwField)) continue;
$value = $GLOBALS['egw']->translation->convert($taskData[$egwField], $sysCharSet, 'utf-8');
switch($sifField) {
switch ($sifField)
{
case 'Date':
if(!empty($value)) {
if (!empty($value))
{
$value = $vcal->_exportDateTime($value);
}
$sifNote .= "<$sifField>$value</$sifField>";
break;
case 'Body':
$value = $GLOBALS['egw']->translation->convert($taskData['info_subject'], $sysCharSet, 'utf-8') . "\n" . $value;
$sifNote .= "<$sifField>$value</$sifField>";
break;
case 'Categories':
if (!empty($value))
{
$value = implode('; ', $this->get_categories(array($value)));
$value = $GLOBALS['egw']->translation->convert($value, $sysCharSet, 'utf-8');
}
$sifNote .= "<$sifField>$value</$sifField>";
break;
else
{
break;
}
default:
$value = @htmlspecialchars($value, ENT_QUOTES, 'utf-8');
$sifNote .= "<$sifField>$value</$sifField>";
break;
}
}
return base64_encode($sifNote);
$sifNote .= '</note>';
return $sifNote;
}
break;
@ -437,116 +583,35 @@ class infolog_sif extends infolog_bo
}
function exportVTODO($_taskID, $_version)
/**
* Set the supported fields
*
* Currently we only store name and version, manucfacturer is always Funambol
*
* @param string $_productName
* @param string $_productSoftwareVersion
*/
function setSupportedFields($_productName='', $_productSoftwareVersion='')
{
$taskData = $this->read($_taskID);
$state = &$_SESSION['SyncML.state'];
$deviceInfo = $state->getClientDeviceInfo();
$taskData = $GLOBALS['egw']->translation->convert($taskData,$GLOBALS['egw']->translation->charset(),'UTF-8');
//_debug_array($taskData);
$taskGUID = $GLOBALS['phpgw']->common->generate_uid('infolog_task',$_taskID);
$vcal = &new Horde_iCalendar;
$vcal->setAttribute('VERSION',$_version);
$vcal->setAttribute('METHOD','PUBLISH');
$vevent = Horde_iCalendar::newComponent('VTODO',$vcal);
$options = array();
$vevent->setAttribute('SUMMARY',$taskData['info_subject']);
$vevent->setAttribute('DESCRIPTION',$taskData['info_des']);
if($taskData['info_startdate'])
$vevent->setAttribute('DTSTART',$taskData['info_startdate']);
if($taskData['info_enddate'])
$vevent->setAttribute('DUE',$taskData['info_enddate']);
$vevent->setAttribute('DTSTAMP',time());
$vevent->setAttribute('CREATED',$GLOBALS['phpgw']->contenthistory->getTSforAction('infolog_task',$_taskID,'add'));
$vevent->setAttribute('LAST-MODIFIED',$GLOBALS['phpgw']->contenthistory->getTSforAction('infolog_task',$_taskID,'modify'));
$vevent->setAttribute('UID',$taskGUID);
$vevent->setAttribute('CLASS',(($taskData['info_access'] == 'public')?'PUBLIC':'PRIVATE'));
$vevent->setAttribute('STATUS',(($taskData['info_status'] == 'completed')?'COMPLETED':'NEEDS-ACTION'));
// 3=urgent => 1, 2=high => 2, 1=normal => 3, 0=low => 4
$vevent->setAttribute('PRIORITY',4-$taskData['info_priority']);
#$vevent->setAttribute('TRANSP','OPAQUE');
# status
# ATTENDEE
$options = array('CHARSET' => 'UTF-8','ENCODING' => 'QUOTED-PRINTABLE');
$vevent->setParameter('SUMMARY', $options);
$vevent->setParameter('DESCRIPTION', $options);
$vcal->addComponent($vevent);
#print "<pre>";
#print $vcal->exportvCalendar();
#print "</pre>";
return $vcal->exportvCalendar();
}
function importVTODO(&$_vcalData, $_taskID=-1)
{
$botranslation = CreateObject('phpgwapi.translation');
$vcal = &new Horde_iCalendar;
if(!$vcal->parsevCalendar($_vcalData))
if (isset($deviceInfo) && is_array($deviceInfo))
{
return FALSE;
}
$components = $vcal->getComponents();
if(count($components) > 0)
{
$component = $components[0];
if(is_a($component, 'Horde_iCalendar_vtodo'))
if (isset($deviceInfo['uidExtension']) &&
$deviceInfo['uidExtension'])
{
if($_taskID>0)
$taskData['info_id'] = $_taskID;
foreach($component->_attributes as $attributes)
{
#print $attributes['name'].' - '.$attributes['value'].'<br>';
#$attributes['value'] = $GLOBALS['egw']->translation->convert($attributes['value'],'UTF-8');
switch($attributes['name'])
{
case 'CLASS':
$taskData['info_access'] = strtolower($attributes['value']);
break;
case 'DESCRIPTION':
$taskData['info_des'] = $attributes['value'];
break;
case 'DUE':
$taskData['info_enddate'] = $attributes['value'];
break;
case 'DTSTART':
$taskData['info_startdate'] = $attributes['value'];
break;
case 'PRIORITY':
// 1 => 3=urgent, 2 => 2=high, 3 => 1=normal, 4 => 0=low
if (1 <= $attributes['value'] && $attributes['value'] <= 4)
{
$taskData['info_priority'] = 4 - $attributes['value'];
}
else
{
$taskData['info_priority'] = 1; // default = normal
}
break;
case 'STATUS':
$taskData['info_status'] = (strtolower($attributes['value']) == 'completed') ? 'done' : 'ongoing';
break;
case 'SUMMARY':
$taskData['info_subject'] = $attributes['value'];
break;
}
}
#_debug_array($eventData);exit;
return $this->write($taskData);
$this->uidExtension = true;
}
}
// store product name and version, to be able to use it elsewhere
if ($_productName)
{
$this->productName = strtolower($_productName);
if (preg_match('/^[^\d]*(\d+\.?\d*)[\.|\d]*$/', $_productSoftwareVersion, $matches))
{
$this->productSoftwareVersion = $matches[1];
}
}
return FALSE;
}
}

View File

@ -91,13 +91,13 @@ class infolog_so
$user_and_memberships = $GLOBALS['egw']->accounts->memberships($this->user,true);
$user_and_memberships[] = $this->user;
}
return $info['info_responsible'] && array_intersect($info['info_responsible'],$user_and_memberships);
return $info['info_responsible'] && array_intersect((array)$info['info_responsible'],$user_and_memberships);
}
/**
* checks if user has the $required_rights to access $info_id (private access is handled too)
*
* @param array/int $info data or info_id of InfoLog entry
* @param array|int $info data or info_id of InfoLog entry
* @param int $required_rights EGW_ACL_xyz anded together
* @param boolean $implicit_edit=false responsible has only implicit read and add rigths, unless this is set to true
* @return boolean True if access is granted else False
@ -284,7 +284,7 @@ class infolog_so
preg_match('/(upcoming|today|overdue|date|enddate)([-\\/.0-9]*)/',$filter,$vars);
$filter = $vars[1];
if (isset($vars[2]) && !empty($vars[2]) && ($date = split('[-/.]',$vars[2])))
if (isset($vars[2]) && !empty($vars[2]) && ($date = preg_split('/[-\\/.]/',$vars[2])))
{
$today = mktime(-$this->tz_offset,0,0,intval($date[1]),intval($date[2]),intval($date[0]));
$tomorrow = mktime(-$this->tz_offset,0,0,intval($date[1]),intval($date[2])+1,intval($date[0]));
@ -339,11 +339,17 @@ class infolog_so
*
* some cacheing is done to prevent multiple reads of the same entry
*
* @param $info_id id or uid of entry
* @return array/boolean the entry as array or False on error (eg. entry not found)
* @param int|string $info_id id or uid of entry
* @return array|boolean the entry as array or False on error (eg. entry not found)
*/
function read($info_id) // did _not_ ensure ACL
{
if (isset($GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length'])) {
$minimum_uid_length = $GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length'];
} else {
$minimum_uid_length = 8;
}
//echo "<p>read($info_id) ".function_backtrace()."</p>\n";
if ($info_id && ((int)$info_id == $this->data['info_id'] || $info_id == $this->data['info_uid']))
{
@ -356,6 +362,14 @@ class infolog_so
$this->init( );
return False;
}
if (!$this->data['info_uid'] || strlen($this->data['info_uid']) < $minimum_uid_length) {
// entry without uid --> create one based on our info_id and save it
$this->data['info_uid'] = $GLOBALS['egw']->common->generate_uid('infolog', $info_id);
$this->db->update($this->info_table,
array('info_uid' => $this->data['info_uid']),
array('info_id' => $this->data['info_id']), __LINE__,__FILE__);
}
if (!is_array($this->data['info_responsible']))
{
$this->data['info_responsible'] = $this->data['info_responsible'] ? explode(',',$this->data['info_responsible']) : array();
@ -399,7 +413,7 @@ class infolog_so
* delete InfoLog entry $info_id AND the links to it
*
* @param int $info_id id of log-entry
* @param bool $delete_children delete the children, if not set there parent-id to $new_parent
* @param boolean $delete_children delete the children, if not set there parent-id to $new_parent
* @param int $new_parent new parent-id to set for subs
*/
function delete($info_id,$delete_children=True,$new_parent=0) // did _not_ ensure ACL
@ -495,10 +509,16 @@ class infolog_so
*
* @param array $values with the data of the log-entry
* @param int $check_modified=0 old modification date to check before update (include in WHERE)
* @return int/boolean info_id, false on error or 0 if the entry has been updated in the meantime
* @return int|boolean info_id, false on error or 0 if the entry has been updated in the meantime
*/
function write($values,$check_modified=0) // did _not_ ensure ACL
{
if (isset($GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length'])) {
$minimum_uid_length = $GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length'];
} else {
$minimum_uid_length = 8;
}
//echo "soinfolog::write(,$check_modified) values="; _debug_array($values);
$info_id = (int) $values['info_id'];
@ -540,12 +560,17 @@ class infolog_so
$this->db->insert($this->info_table,$to_write,false,__LINE__,__FILE__);
$info_id = $this->data['info_id'] = $this->db->get_last_insert_id($this->info_table,'info_id');
if (!$this->data['info_uid']) // new entry without uid --> create one based on our info_id and save it
{
$this->data['info_uid'] = $GLOBALS['egw']->common->generate_uid('infolog',$info_id);
$this->db->update($this->info_table,array('info_uid'=>$this->data['info_uid']),array('info_id'=>$info_id),__LINE__,__FILE__);
}
}
if (!$this->data['info_uid'] || strlen($this->data['info_uid']) < $minimum_uid_length) {
// entry without uid --> create one based on our info_id and save it
$this->data['info_uid'] = $GLOBALS['egw']->common->generate_uid('infolog', $info_id);
$this->db->update($this->info_table,
array('info_uid' => $this->data['info_uid']),
array('info_id' => $info_id), __LINE__,__FILE__);
}
//echo "<p>soinfolog.write values= "; _debug_array($values);
// write customfields now
@ -616,17 +641,19 @@ class infolog_so
/**
* searches InfoLog for a certain pattern in $query
*
* @param $query[order] column-name to sort after
* @param $query[sort] sort-order DESC or ASC
* @param $query[filter] string with combination of acl-, date- and status-filters, eg. 'own-open-today' or ''
* @param $query[cat_id] category to use or 0 or unset
* @param $query[search] pattern to search, search is done in info_from, info_subject and info_des
* @param $query[action] / $query[action_id] if only entries linked to a specified app/entry show be used
* @param &$query[start], &$query[total] nextmatch-parameters will be used and set if query returns less entries
* @param $query[col_filter] array with column-name - data pairs, data == '' means no filter (!)
* @param $query[subs] boolean return subs or not, if unset the user preference is used
* @param $query[num_rows] number of rows to return if $query[start] is set, default is to use the value from the general prefs
* @return array with id's as key of the matching log-entries
* @param string $query[order] column-name to sort after
* @param string $query[sort] sort-order DESC or ASC
* @param string $query[filter] string with combination of acl-, date- and status-filters, eg. 'own-open-today' or ''
* @param int $query[cat_id] category to use or 0 or unset
* @param string $query[search] pattern to search, search is done in info_from, info_subject and info_des
* @param string $query[action] / $query[action_id] if only entries linked to a specified app/entry show be used
* @param int &$query[start], &$query[total] nextmatch-parameters will be used and set if query returns less entries
* @param array $query[col_filter] array with column-name - data pairs, data == '' means no filter (!)
* @param boolean $query[subs] return subs or not, if unset the user preference is used
* @param int $query[num_rows] number of rows to return if $query[start] is set, default is to use the value from the general prefs
* @param string|array $query[cols]=null what to query, if set the recordset / iterator get's returned
* @param string $query[append]=null get's appended to sql query, eg. for GROUP BY
* @return array|iterator with id's as key of the matching log-entries or recordset/iterator if cols is set
*/
function search(&$query)
{
@ -661,7 +688,9 @@ class infolog_so
}
else
{
$val = (substr($val,0,5) != 'info_' ? 'info_' : '').$val;
static $table_def;
if (is_null($table_def)) $table_def = $this->db->get_table_definitions('infolog',$this->info_table);
if (substr($val,0,5) != 'info_' && isset($table_def['fd']['info_'.$val])) $val = 'info_'.$val;
if ($val == 'info_des' && $this->db->capabilities['order_on_text'] !== true)
{
if (!$this->db->capabilities['order_on_text']) continue;
@ -685,6 +714,11 @@ class infolog_so
{
foreach($query['col_filter'] as $col => $data)
{
if (is_int($col))
{
$filtermethod .= ' AND '.$data;
continue;
}
if (substr($col,0,5) != 'info_' && substr($col,0,1)!='#') $col = 'info_'.$col;
if (!empty($data) && preg_match('/^[a-z_0-9]+$/i',$col))
{
@ -766,6 +800,7 @@ class infolog_so
if ($action == '' || $action == 'sp' || count($links))
{
$sql_query = "FROM $this->info_table main $join WHERE ($filtermethod $pid $sql_query) $link_extra";
#error_log("infolog.so.search:\n" . print_r($sql_query, true));
if ($this->db->Type == 'mysql' && $this->db->ServerInfo['version'] >= 4.0)
{
@ -788,7 +823,9 @@ class infolog_so
{
$query['start'] = 0;
}
$rs = $this->db->query($sql="SELECT $mysql_calc_rows $distinct main.* $info_customfield $sql_query $ordermethod",__LINE__,__FILE__,
$cols = isset($query['cols']) ? $query['cols'] : 'main.*';
if (is_array($cols)) $cols = implode(',',$cols);
$rs = $this->db->query($sql='SELECT '.$mysql_calc_rows.' '.$distinct.' '.$cols.' '.$info_customfield.' '.$sql_query.$query['append'].' '.$ordermethod,__LINE__,__FILE__,
(int) $query['start'],isset($query['start']) ? (int) $query['num_rows'] : -1,false,egw_db::FETCH_ASSOC);
//echo "<p>db::query('$sql',,,".(int)$query['start'].','.(isset($query['start']) ? (int) $query['num_rows'] : -1).")</p>\n";
@ -800,6 +837,10 @@ class infolog_so
// check if start is behind total --> loop to set start=0
while (isset($query['start']) && $query['start'] > $query['total']);
if (isset($query['cols']))
{
return $rs;
}
foreach($rs as $info)
{
$info['info_responsible'] = $info['info_responsible'] ? explode(',',$info['info_responsible']) : array();

View File

@ -65,7 +65,8 @@ class infolog_ui
'edit' => 'edit.gif', 'edit_alt' => 'Edit',
'addfile' => 'addfile.gif', 'addfile_alt' => 'Add a file',
'delete' => 'delete.gif', 'delete_alt' => 'Delete',
'close' => 'done.gif', 'close_alt' => 'Close' ),
'close' => 'done.gif', 'close_alt' => 'Close' ,
'close_all' => 'done_all.gif', 'close_all_alt' => 'Close' ),
'status' => array(
'billed' => 'billed.gif', 'billed_alt' => 'billed',
'done' => 'done.gif', 'done_alt' => 'done',
@ -74,25 +75,7 @@ class infolog_ui
'ongoing' => 'ongoing.gif', 'ongoing_alt' => 'ongoing',
'offer' => 'offer.gif', 'offer_alt' => 'offer' )
);
var $filters = array(
'none' => 'no Filter',
'done' => 'done',
'responsible' => 'responsible',
'responsible-open-today' => 'responsible open',
'responsible-open-overdue' => 'responsible overdue',
'responsible-upcoming' => 'responsible upcoming',
'delegated' => 'delegated',
'delegated-open-today' => 'delegated open',
'delegated-open-overdue' => 'delegated overdue',
'delegated-upcoming' => 'delegated upcomming',
'own' => 'own',
'own-open-today' => 'own open',
'own-open-overdue' => 'own overdue',
'own-upcoming' => 'own upcoming',
'open-today' => 'open',
'open-overdue' => 'overdue',
'upcoming' => 'upcoming',
);
var $filters;
var $messages = array(
'edit' => 'InfoLog - Edit',
'add' => 'InfoLog - New',
@ -109,7 +92,7 @@ class infolog_ui
function __construct()
{
if ($GLOBALS['egw_info']['flags']['currentapp'] != 'infolog') $GLOBALS['egw']->translation->add_app('infolog');
$this->bo =& new infolog_bo();
$this->bo = new infolog_bo();
$this->tmpl = new etemplate();
@ -124,6 +107,7 @@ class infolog_ui
$this->duration_format = str_replace(',','',$pm_config['duration_units']).','.$pm_config['hours_per_workday'];
unset($pm_config);
}
$this->filters =& $this->bo->filters;
/* these are just for testing of the notifications
for($i = -1; $i <= 3; ++$i)
{
@ -163,7 +147,10 @@ class infolog_ui
$info = $this->bo->read($info);
}
$id = $info['info_id'];
$done = $info['info_status'] == 'done' || $info['info_status'] == 'billed';
$done = $info['info_status'] == 'done' || $info['info_status'] == 'billed' || $info['info_status'] == 'cancelled'; //cancelled is regarded as a completed status as well in bo
// regard an infolog as done/billed/cancelled if its percentage is 100% when there is to status like the above for that type
if (!$done && !isset($this->bo->status[$info['info_type']]['done']) && !isset($this->bo->status[$info['info_type']]['billed']) &&
!isset($this->bo->status[$info['info_type']]['cancelled']) && (int)$info['info_percent']==100) $done = true ;
$info['sub_class'] = $this->bo->enums['priority'][$info['info_priority']] . ($done ? '_done' : '');
if (!$done && $info['info_enddate'] < $this->bo->user_time_now)
{
@ -172,13 +159,18 @@ class infolog_ui
if (!isset($info['info_anz_subs'])) $info['info_anz_subs'] = $this->bo->anzSubs($id);
$this->bo->link_id2from($info,$action,$action_id); // unset from for $action:$action_id
$info['info_percent'] = (int) $info['info_percent'].'%';
$readonlys["edit[$id]"] = !($this->bo->check_access($info,EGW_ACL_EDIT) || // edit rights or more then standard responsible rights
$this->bo->is_responsible($info) && array_diff($this->bo->responsible_edit,array('info_status','info_percent','info_datecompleted')));
$editrights = $this->bo->check_access($info,EGW_ACL_EDIT);
$isresposible = $this->bo->is_responsible($info);
$readonlys["edit[$id]"] = !($editrights || // edit rights or more then standard responsible rights
$isresposible && array_diff($this->bo->responsible_edit,array('info_status','info_percent','info_datecompleted')));
$readonlys["close[$id]"] = $done || ($readonlys["edit_status[$id]"] =
!($this->bo->check_access($info,EGW_ACL_EDIT) || $this->bo->is_responsible($info)));
!($editrights || $isresposible));
$readonlys["close_all[$id]"] = ($done) || !$info['info_anz_subs'] || ($readonlys["edit_status[$id]"] =
!($editrights || $isresposible)); // this one is supressed, when you are not allowed to edit, or not responsible, or the entry is closed
// and has no children. If you want that this one is shown if there are children regardless of the status of the current or its childs,
// then modify ($done) to ($done && !$info['info_anz_subs'])
$readonlys["edit_status[$id]"] = $readonlys["edit_percent[$id]"] =
!$this->bo->check_access($info,EGW_ACL_EDIT) && !$this->bo->is_responsible($info) &&
!$editrights && !$isresposible &&
!$this->bo->check_access($info,EGW_ACL_UNDELETE); // undelete is handled like status edit
$readonlys["delete[$id]"] = !$this->bo->check_access($info,EGW_ACL_DELETE);
$readonlys["sp[$id]"] = !$this->bo->check_access($info,EGW_ACL_ADD);
@ -187,6 +179,7 @@ class infolog_ui
$readonlys["timesheet[$id]"] = !isset($GLOBALS['egw_info']['user']['apps']['timesheet']);
if (!$show_links) $show_links = $this->prefs['show_links'];
if (($show_links != 'none' && $show_links != 'no_describtion' ||
$this->prefs['show_times'] || isset($GLOBALS['egw_info']['user']['apps']['timesheet'])) &&
(isset($info['links']) || ($info['links'] = egw_link::get_links('infolog',$info['info_id']))))
@ -310,7 +303,7 @@ class infolog_ui
unset($query['custom_fields']);
if ($query['col_filter']['info_type'])
{
$tpl =& new etemplate;
$tpl = new etemplate;
if ($tpl->read('infolog.index.rows.'.$query['col_filter']['info_type']))
{
$query['template'] =& $tpl;
@ -320,7 +313,7 @@ class infolog_ui
}
// do we need to read the custom fields, depends on the column is enabled and customfields exist, prefs are filter specific
// so we have to check that as well
$details = $query['filter2'] == 'all';
$details = $query['filter2'] == 'all';
$columselection = $this->prefs['nextmatch-infolog.index.rows'.($details?'-details':'')];
//_debug_array($columselection);
if ($columselection)
@ -345,15 +338,15 @@ class infolog_ui
if ($details)
{
$query['columnselection_pref'] = (is_object($query['template'])?$query['template']->name:'infolog.index.rows').'-details';
$query['default_cols'] = '!cat_id,info_used_time_info_planned_time_info_replanned_time,info_id';
$query['default_cols'] = '!cat_id,info_used_time_info_planned_time,info_used_time_info_planned_time_info_replanned_time,info_id';
}
else
{
$query['columnselection_pref'] = 'infolog.index.rows';
$query['default_cols'] = '!cat_id,info_datemodified,info_used_time_info_planned_time_info_replanned_time,info_id';
$query['default_cols'] = '!cat_id,info_datemodified,info_used_time_info_planned_time,info_used_time_info_planned_time_info_replanned_time,info_id';
}
// set old show_times pref, that get_info calculates the cumulated time of the timesheets (we only check used&planned to work for both time cols)
$this->prefs['show_times'] = strpos($this->prefs['nextmatch-'.$query['columnselection_pref']],'info_used_time_info_planned_time') !== false;
// set old show_times pref, that get_info calculates the cumulated time of the timesheets
$this->prefs['show_times'] = strpos($this->prefs['nextmatch-'.$query['columnselection_pref']],'info_used_time_info_planned_time_info_replanned_time') !== false;
// query all links and sub counts in one go
if ($infos && !$query['csv_export'])
@ -428,6 +421,9 @@ class infolog_ui
$GLOBALS['egw_info']['flags']['app_header'] .= ': '.$title;
}
}
// disable filemanager icon, if user has no access to it
$readonlys['filemanager/navbar'] = !isset($GLOBALS['egw_info']['user']['apps']['filemanager']);
return $query['total'];
}
@ -553,7 +549,9 @@ class infolog_ui
}
break;
case 'close':
$this->close($do_id,$called_as);
$closesingle=true;
case 'close_all':
$this->close($do_id,$called_as,$closesingle);
break;
case 'sp':
return $this->edit(0,'sp',$do_id,'',$called_as);
@ -639,6 +637,13 @@ class infolog_ui
{
$values['css'] = '<style type="text/css">@import url('.$GLOBALS['egw_info']['server']['webserver_url'].'/infolog/templates/default/app.css);'."</style>";
}
// add scrollbar to long describtion, if user choose so in his prefs
if ($this->prefs['limit_des_lines'] > 0 || (string)$this->prefs['limit_des_lines'] == '')
{
$values['css'] .= '<style type="text/css">@media screen { .infoDes { max-height: '.
(($this->prefs['limit_des_lines'] ? $this->prefs['limit_des_lines'] : 5) * 1.35). // dono why em is not real lines
'em; overflow: auto; }}</style>';
}
return $this->tmpl->exec('infolog.infolog_ui.index',$values,array(
'info_type' => $this->bo->enums['type'],
),$readonlys,$persist,$return_html ? -1 : 0);
@ -649,29 +654,45 @@ class infolog_ui
*
* @param int|array $values=0 info_id (default _GET[info_id])
* @param string $referer=''
* @param boolean $closesingle=false
*/
function close($values=0,$referer='')
function close($values=0,$referer='',$closesingle=false)
{
//echo "<p>".__METHOD__."($values,$referer)</p>\n";
//echo "<p>".__METHOD__."($values,$referer,$closeall)</p>\n";
$info_id = (int) (is_array($values) ? $values['info_id'] : ($values ? $values : $_GET['info_id']));
$referer = is_array($values) ? $values['referer'] : $referer;
if ($info_id)
{
$info = $this->bo->read($info_id);
#_debug_array($info);
$status = $info['info_status'];
// closed stati assumed array('done','billed','cancelled')
if (isset($this->bo->status[$info['info_type']]['done'])) {
$status ='done';
} elseif (isset($this->bo->status[$info['info_type']]['billed'])) {
$status ='billed';
} elseif (isset($this->bo->status[$info['info_type']]['cancelled'])) {
$status ='cancelled';
}
#_debug_array($status);
$values = array(
'info_id' => $info_id,
'info_status' => 'done',
'info_type' => $info['info_type'],
'info_status' => $status,
'info_percent'=> 100,
'info_datecompleted' => $this->bo->now_su,
);
$this->bo->write($values);
$query = array('action'=>'sp','action_id'=>$info_id);
foreach((array)$this->bo->search($query) as $info)
{
if ($info['info_id_parent'] == $info_id) // search also returns linked entries!
if (!$closesingle) {
foreach((array)$this->bo->search($query) as $info)
{
$this->close($info['info_id'],$referer); // we call ourselfs recursive to process subs from subs too
if ($info['info_id_parent'] == $info_id) // search also returns linked entries!
{
$this->close($info['info_id'],$referer,$closeall); // we call ourselfs recursive to process subs from subs too
}
}
}
}
@ -953,6 +974,7 @@ class infolog_ui
}
$parent = $this->bo->so->data;
$content['info_id'] = $info_id = 0;
$content['info_uid'] = ''; // ensure that we have our own UID
$content['info_owner'] = $this->user;
$content['info_id_parent'] = $parent['info_id'];
/*
@ -1281,10 +1303,9 @@ class infolog_ui
if ($_POST['responsible_edit'])
{
$extra = array_intersect((array)$_POST['responsible_edit'],array_keys($fields));
$this->bo->responsible_edit = array_merge($this->bo->responsible_edit,$extra);
$extra = array_intersect($_POST['responsible_edit'],array_keys($fields));
config::save_value('responsible_edit',$this->bo->responsible_edit = array_merge($this->bo->responsible_edit,$extra),'infolog');
}
config::save_value('responsible_edit',$this->bo->responsible_edit,'infolog');
config::save_value('implicit_rights',$this->bo->implicit_rights = $_POST['implicit_rights'] == 'edit' ? 'edit' : 'read','infolog');
config::save_value('history',$this->bo->history = $_POST['history'],'infolog');
}
@ -1413,7 +1434,6 @@ class infolog_ui
#continue;
}
$message .= $bofelamimail->wordwrap($value,75,"\n");
#$message .= $bodyAppend;
}
}
@ -1496,7 +1516,7 @@ class infolog_ui
$GLOBALS['egw']->translation->add_app('infolog');
$GLOBALS['egw_info']['etemplate']['hooked'] = True;
$GLOBALS['egw_info']['etemplate']['hooked'] = true;
$this->index(0,$app,$args[$view_id],array(
'menuaction' => $view,
isset($view_id2) ? $view_id2 : $view_id => $args[$view_id]

View File

@ -148,11 +148,11 @@ class categories
if (!empty($order) && preg_match('/^[a-zA-Z_(), ]+$/',$order) && (empty($sort) || preg_match('/^(ASC|DESC|asc|desc)$/',$sort)))
{
$ordermethod = 'ORDER BY '.$order.' '.$sort;
$ordermethod = 'ORDER BY '.$order.' '.$sort . ', cat_access ASC';
}
else
{
$ordermethod = 'ORDER BY cat_main, cat_level, cat_name ASC';
$ordermethod = 'ORDER BY cat_main, cat_level, cat_name, cat_access ASC';
}
if ($this->account_id == '-1')
@ -167,7 +167,7 @@ class categories
}
else
{
$grant_cats = ' cat_owner=' . $this->account_id . ' OR cat_owner=-1 ';
$grant_cats = ' (cat_owner=' . $this->account_id . ' OR cat_owner=-1) ';
}
}
@ -235,11 +235,11 @@ class categories
if (!empty($order) && preg_match('/^[a-zA-Z_, ]+$/',$order) && (empty($sort) || preg_match('/^(ASC|DESC|asc|desc)$/',$sort)))
{
$ordermethod = 'ORDER BY '.$order.' '.$sort;
$ordermethod = 'ORDER BY '.$order.' '.$sort . ', cat_access ASC';
}
else
{
$ordermethod = ' ORDER BY cat_name ASC';
$ordermethod = ' ORDER BY cat_name ASC, cat_access ASC';
}
if ($this->account_id == '-1')
@ -254,7 +254,7 @@ class categories
}
else
{
$grant_cats = " cat_owner='" . $this->account_id . "' OR cat_owner='-1' ";
$grant_cats = " (cat_owner='" . $this->account_id . "' OR cat_owner='-1') ";
}
}
$parent_select = ' AND cat_parent=' . (int)$parent_id;
@ -403,6 +403,14 @@ class categories
{
$s .= ' &#9830;';
}
elseif ($cat['owner'] != $this->account_id)
{
$s .= '&lt;' . $GLOBALS['egw']->accounts->id2name($cat['owner'], 'account_fullname') . '&gt;';
}
elseif ($cat['access'] == 'private')
{
$s .= ' &#9829;';
}
$s .= '</option>' . "\n";
}
break;
@ -494,6 +502,7 @@ class categories
if ($modify_subs)
{
$new_parent = $this->id2name($cat_id,'parent');
$new_main = 0;
foreach ((array) $this->return_sorted_array('',False,'','','',False, $cat_id) as $cat)
{
@ -683,17 +692,18 @@ class categories
/**
* check if a category id and/or name exists, if id AND name are given the check is for a category with same name and different id (!)
*
* @param string $type subs or mains
* @param string $cat_name='' category name
* @param int $cat_id=0 category id
* @param int $parent=0 category id of parent
* @param string $type subs or mains
* @param string $cat_name='' category name
* @param int $cat_id=0 category id
* @param int $parent=0 category id of parent
* @param boolean $private=false limit to private categories
* @return int/boolean cat_id or false if cat not exists
*/
function exists($type,$cat_name = '',$cat_id = 0,$parent = 0)
function exists($type,$cat_name = '',$cat_id = 0,$parent = 0, $private = false)
{
static $cache = array(); // a litle bit of caching
if (isset($cache[$type][$cat_name][$cat_id])) return $cache[$type][$cat_name][$cat_id];
if (isset($cache[$type][$cat_name][$cat_id][$private])) return $cache[$type][$cat_name][$cat_id][$private];
$where = array($this->filter($type));
@ -707,9 +717,21 @@ class categories
{
$where['cat_id'] = $cat_id;
}
if ($parent) $where['cat_parent'] = $parent;
return $cache[$type][$cat_name][$cat_id] = $this->db->select($this->table,'cat_id',$where,__LINE__,__FILE__)->fetchSingle();
if ($parent){
$where['cat_parent'] = $parent;
}
$grant_cats = "(";
if ($private) {
$grant_cats .= "cat_owner='" . $this->account_id . "' AND cat_access='private'";
} else {
$grant_cats .= "cat_owner='" . $this->account_id
. "' OR cat_owner='-1' OR cat_access='public'";
if (is_array($this->grants)) {
$grant_cats .= " AND cat_owner IN (" . implode(',',array_keys($this->grants)) . ")";
}
}
$where[] = $grant_cats . ")";
return $cache[$type][$cat_name][$cat_id][$private] = $this->db->select($this->table,'cat_id',$where,__LINE__,__FILE__)->fetchSingle();
}
/**

View File

@ -1,7 +1,7 @@
<?php
/**
* eGW API - content history class
*
*
* @link http://www.egroupware.org
* @author Lars Kneschke [lkneschke@linux-at-work.de]
* @copyright Lars Kneschke 2005
@ -30,7 +30,7 @@ class contenthistory
{
$this->db = $GLOBALS['egw']->db;
}
/**
* mark mapping as expired
*
@ -39,7 +39,7 @@ class contenthistory
*
* @param string $_appName the appname example: infolog_notes
* @param int $_id the internal egwapp content id
* @return bool
* @return bool
*/
function expireMapping($_appName, $_id)
{
@ -57,13 +57,13 @@ class contenthistory
*
* @param string$_appName the appname example: infolog_notes
* @param string $_action can be modify, add or delete
* @param string $_ts timestamp where to start searching from
* @param string $_ts timestamp where to start searching from
* @return array containing contentIds with changes
*/
function getHistory($_appName, $_action, $_ts)
{
$where = array('sync_appname' => $_appName);
switch($_action)
{
case 'modify':
@ -84,10 +84,10 @@ class contenthistory
{
$idList[] = $row['sync_contentid'];
}
return $idList;
}
/**
* when got a entry last added/modified/deleted
*
@ -122,14 +122,14 @@ class contenthistory
}
return $ts;
}
/**
* update a timestamp for action
*
* @param string $_appName the appname example: infolog_notes
* @param int $_id the app internal content id
* @param string $_action can be modify, add or delete
* @param string $_ts timestamp where to start searching from
* @param string $_ts timestamp where to start searching from
* @return boolean returns allways true
*/
function updateTimeStamp($_appName, $_id, $_action, $_ts)
@ -146,7 +146,7 @@ class contenthistory
case 'add':
$this->db->insert(self::TABLE,$newData,array(),__LINE__,__FILE__);
break;
case 'modify':
case 'delete':
// first check that this entry got ever added to database already
@ -163,7 +163,7 @@ class contenthistory
// now update the time stamp
$newData = array (
'sync_changedby' => $GLOBALS['egw_info']['user']['account_id'],
$_action == 'modify' ? 'sync_modified' : 'sync_deleted' => $_ts ,
$_action == 'delete' ? 'sync_deleted' : 'sync_modified' => $this->db->to_timestamp($_ts),
);
$this->db->update(self::TABLE, $newData, $where,__LINE__,__FILE__);
break;

View File

@ -1453,13 +1453,14 @@ class egw_session
* @param int $start
* @param string $sort='session_dla' session_lid, session_id, session_started, session_logintime, session_action, or (default) session_dla
* @param string $order='DESC' ASC or DESC
* @param boolean $all_no_sort=false skip sorting and limiting to maxmatchs if set to true
* @return array with sessions (values for keys as in $sort) or array() if not supported by session-handler
*/
public static function session_list($start,$sort='DESC',$order='session_dla')
{
if (method_exists(self::$session_handler,'session_list'))
{
return call_user_func(array(self::$session_handler,'session_list'),$start,$sort,$order);
return call_user_func(array(self::$session_handler,'session_list'),$start,$sort,$order,$all_no_sort);
}
return array();
}

View File

@ -34,9 +34,10 @@ class egw_session_files
* @param int $start
* @param string $sort='session_dla' session_lid, session_id, session_started, session_logintime, session_action, or (default) session_dla
* @param string $order='DESC' ASC or DESC
* @param boolean $all_no_sort=false skip sorting and limiting to maxmatchs if set to true
* @return array with sessions (values for keys as in $sort) or array() if not supported by session-handler
*/
public static function session_list($start,$sort='DESC',$order='session_dla',$all_no_sort = False)
public static function session_list($start,$sort='DESC',$order='session_dla',$all_no_sort=false)
{
if (session_module_name() != 'files')
{

View File

@ -289,14 +289,13 @@ class Browser_imode {
var $_manufacturer;
var $_httpversion;
var $_cache = 5;
var $_extra;
/**
* Does not handle bogus user_agents or most of the other error
* situation properly yet.
*
* Example usage:
* $ua = &new Browser_imode($_SERVEr['HTTP_USER_AGENT']);
* $ua = new Browser_imode($_SERVEr['HTTP_USER_AGENT']);
*
* @param string $input The user agent to match.
*/

View File

@ -0,0 +1,769 @@
<?php
define('HORDE_DATE_SUNDAY', 0);
define('HORDE_DATE_MONDAY', 1);
define('HORDE_DATE_TUESDAY', 2);
define('HORDE_DATE_WEDNESDAY', 3);
define('HORDE_DATE_THURSDAY', 4);
define('HORDE_DATE_FRIDAY', 5);
define('HORDE_DATE_SATURDAY', 6);
define('HORDE_DATE_MASK_SUNDAY', 1);
define('HORDE_DATE_MASK_MONDAY', 2);
define('HORDE_DATE_MASK_TUESDAY', 4);
define('HORDE_DATE_MASK_WEDNESDAY', 8);
define('HORDE_DATE_MASK_THURSDAY', 16);
define('HORDE_DATE_MASK_FRIDAY', 32);
define('HORDE_DATE_MASK_SATURDAY', 64);
define('HORDE_DATE_MASK_WEEKDAYS', 62);
define('HORDE_DATE_MASK_WEEKEND', 65);
define('HORDE_DATE_MASK_ALLDAYS', 127);
define('HORDE_DATE_MASK_SECOND', 1);
define('HORDE_DATE_MASK_MINUTE', 2);
define('HORDE_DATE_MASK_HOUR', 4);
define('HORDE_DATE_MASK_DAY', 8);
define('HORDE_DATE_MASK_MONTH', 16);
define('HORDE_DATE_MASK_YEAR', 32);
define('HORDE_DATE_MASK_ALLPARTS', 63);
/**
* Horde Date wrapper/logic class, including some calculation
* functions.
*
* $Horde: framework/Date/Date.php,v 1.8.10.18 2008/09/17 08:46:04 jan Exp $
*
* @package Horde_Date
*/
class Horde_Date {
/**
* Year
*
* @var integer
*/
var $year;
/**
* Month
*
* @var integer
*/
var $month;
/**
* Day
*
* @var integer
*/
var $mday;
/**
* Hour
*
* @var integer
*/
var $hour = 0;
/**
* Minute
*
* @var integer
*/
var $min = 0;
/**
* Second
*
* @var integer
*/
var $sec = 0;
/**
* Internally supported strftime() specifiers.
*
* @var string
*/
var $_supportedSpecs = '%CdDeHImMnRStTyY';
/**
* Build a new date object. If $date contains date parts, use them to
* initialize the object.
*
* Recognized formats:
* - arrays with keys 'year', 'month', 'mday', 'day' (since Horde 3.2),
* 'hour', 'min', 'minute' (since Horde 3.2), 'sec'
* - objects with properties 'year', 'month', 'mday', 'hour', 'min', 'sec'
* - yyyy-mm-dd hh:mm:ss (since Horde 3.1)
* - yyyymmddhhmmss (since Horde 3.1)
* - yyyymmddThhmmssZ (since Horde 3.1.4)
* - unix timestamps
*/
function Horde_Date($date = null)
{
if (function_exists('nl_langinfo')) {
$this->_supportedSpecs .= 'bBpxX';
}
if (is_array($date) || is_object($date)) {
foreach ($date as $key => $val) {
if (in_array($key, array('year', 'month', 'mday', 'hour', 'min', 'sec'))) {
$this->$key = (int)$val;
}
}
// If $date['day'] is present and numeric we may have been passed
// a Horde_Form_datetime array.
if (is_array($date) && isset($date['day']) &&
is_numeric($date['day'])) {
$this->mday = (int)$date['day'];
}
// 'minute' key also from Horde_Form_datetime
if (is_array($date) && isset($date['minute'])) {
$this->min = $date['minute'];
}
} elseif (!is_null($date)) {
// Match YYYY-MM-DD HH:MM:SS, YYYYMMDDHHMMSS and YYYYMMDD'T'HHMMSS'Z'.
if (preg_match('/(\d{4})-?(\d{2})-?(\d{2})T? ?(\d{2}):?(\d{2}):?(\d{2})Z?/', $date, $parts)) {
$this->year = (int)$parts[1];
$this->month = (int)$parts[2];
$this->mday = (int)$parts[3];
$this->hour = (int)$parts[4];
$this->min = (int)$parts[5];
$this->sec = (int)$parts[6];
} else {
// Try as a timestamp.
$parts = @getdate($date);
if ($parts) {
$this->year = $parts['year'];
$this->month = $parts['mon'];
$this->mday = $parts['mday'];
$this->hour = $parts['hours'];
$this->min = $parts['minutes'];
$this->sec = $parts['seconds'];
}
}
}
}
/**
* @static
*/
function isLeapYear($year)
{
if (strlen($year) != 4 || preg_match('/\D/', $year)) {
return false;
}
return (($year % 4 == 0 && $year % 100 != 0) || $year % 400 == 0);
}
/**
* Returns the day of the year (1-366) that corresponds to the
* first day of the given week.
*
* TODO: with PHP 5.1+, see http://derickrethans.nl/calculating_start_and_end_dates_of_a_week.php
*
* @param integer $week The week of the year to find the first day of.
* @param integer $year The year to calculate for.
*
* @return integer The day of the year of the first day of the given week.
*/
function firstDayOfWeek($week, $year)
{
$jan1 = new Horde_Date(array('year' => $year, 'month' => 1, 'mday' => 1));
$start = $jan1->dayOfWeek();
if ($start > HORDE_DATE_THURSDAY) {
$start -= 7;
}
return (($week * 7) - (7 + $start)) + 1;
}
/**
* @static
*/
function daysInMonth($month, $year)
{
if ($month == 2) {
if (Horde_Date::isLeapYear($year)) {
return 29;
} else {
return 28;
}
} elseif ($month == 4 || $month == 6 || $month == 9 || $month == 11) {
return 30;
} else {
return 31;
}
}
/**
* Return the day of the week (0 = Sunday, 6 = Saturday) of this
* object's date.
*
* @return integer The day of the week.
*/
function dayOfWeek()
{
if ($this->month > 2) {
$month = $this->month - 2;
$year = $this->year;
} else {
$month = $this->month + 10;
$year = $this->year - 1;
}
$day = (floor((13 * $month - 1) / 5) +
$this->mday + ($year % 100) +
floor(($year % 100) / 4) +
floor(($year / 100) / 4) - 2 *
floor($year / 100) + 77);
return (int)($day - 7 * floor($day / 7));
}
/**
* Returns the day number of the year (1 to 365/366).
*
* @return integer The day of the year.
*/
function dayOfYear()
{
$monthTotals = array(0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334);
$dayOfYear = $this->mday + $monthTotals[$this->month - 1];
if (Horde_Date::isLeapYear($this->year) && $this->month > 2) {
++$dayOfYear;
}
return $dayOfYear;
}
/**
* Returns the week of the month.
*
* @since Horde 3.2
*
* @return integer The week number.
*/
function weekOfMonth()
{
return ceil($this->mday / 7);
}
/**
* Returns the week of the year, first Monday is first day of first week.
*
* @return integer The week number.
*/
function weekOfYear()
{
return $this->format('W');
}
/**
* Return the number of weeks in the given year (52 or 53).
*
* @static
*
* @param integer $year The year to count the number of weeks in.
*
* @return integer $numWeeks The number of weeks in $year.
*/
function weeksInYear($year)
{
// Find the last Thursday of the year.
$day = 31;
$date = new Horde_Date(array('year' => $year, 'month' => 12, 'mday' => $day, 'hour' => 0, 'min' => 0, 'sec' => 0));
while ($date->dayOfWeek() != HORDE_DATE_THURSDAY) {
--$date->mday;
}
return $date->weekOfYear();
}
/**
* Set the date of this object to the $nth weekday of $weekday.
*
* @param integer $weekday The day of the week (0 = Sunday, etc).
* @param integer $nth The $nth $weekday to set to (defaults to 1).
*/
function setNthWeekday($weekday, $nth = 1)
{
if ($weekday < HORDE_DATE_SUNDAY || $weekday > HORDE_DATE_SATURDAY) {
return false;
}
$this->mday = 1;
$first = $this->dayOfWeek();
if ($weekday < $first) {
$this->mday = 8 + $weekday - $first;
} else {
$this->mday = $weekday - $first + 1;
}
$this->mday += 7 * $nth - 7;
$this->correct();
return true;
}
function dump($prefix = '')
{
echo ($prefix ? $prefix . ': ' : '') . $this->year . '-' . $this->month . '-' . $this->mday . "<br />\n";
}
/**
* Is the date currently represented by this object a valid date?
*
* @return boolean Validity, counting leap years, etc.
*/
function isValid()
{
if ($this->year < 0 || $this->year > 9999) {
return false;
}
return checkdate($this->month, $this->mday, $this->year);
}
/**
* Correct any over- or underflows in any of the date's members.
*
* @param integer $mask We may not want to correct some overflows.
*/
function correct($mask = HORDE_DATE_MASK_ALLPARTS)
{
if ($mask & HORDE_DATE_MASK_SECOND) {
while ($this->sec < 0) {
--$this->min;
$this->sec += 60;
}
while ($this->sec > 59) {
++$this->min;
$this->sec -= 60;
}
}
if ($mask & HORDE_DATE_MASK_MINUTE) {
while ($this->min < 0) {
--$this->hour;
$this->min += 60;
}
while ($this->min > 59) {
++$this->hour;
$this->min -= 60;
}
}
if ($mask & HORDE_DATE_MASK_HOUR) {
while ($this->hour < 0) {
--$this->mday;
$this->hour += 24;
}
while ($this->hour > 23) {
++$this->mday;
$this->hour -= 24;
}
}
if ($mask & HORDE_DATE_MASK_MONTH) {
while ($this->month > 12) {
++$this->year;
$this->month -= 12;
}
while ($this->month < 1) {
--$this->year;
$this->month += 12;
}
}
if ($mask & HORDE_DATE_MASK_DAY) {
while ($this->mday > Horde_Date::daysInMonth($this->month, $this->year)) {
$this->mday -= Horde_Date::daysInMonth($this->month, $this->year);
++$this->month;
$this->correct(HORDE_DATE_MASK_MONTH);
}
while ($this->mday < 1) {
--$this->month;
$this->correct(HORDE_DATE_MASK_MONTH);
$this->mday += Horde_Date::daysInMonth($this->month, $this->year);
}
}
}
/**
* Compare this date to another date object to see which one is
* greater (later). Assumes that the dates are in the same
* timezone.
*
* @param mixed $date The date to compare to.
*
* @return integer == 0 if the dates are equal
* >= 1 if this date is greater (later)
* <= -1 if the other date is greater (later)
*/
function compareDate($date)
{
if (!is_a($date, 'Horde_Date')) {
$date = new Horde_Date($date);
}
if ($this->year != $date->year) {
return $this->year - $date->year;
}
if ($this->month != $date->month) {
return $this->month - $date->month;
}
return $this->mday - $date->mday;
}
/**
* Compare this to another date object by time, to see which one
* is greater (later). Assumes that the dates are in the same
* timezone.
*
* @param mixed $date The date to compare to.
*
* @return integer == 0 if the dates are equal
* >= 1 if this date is greater (later)
* <= -1 if the other date is greater (later)
*/
function compareTime($date)
{
if (!is_a($date, 'Horde_Date')) {
$date = new Horde_Date($date);
}
if ($this->hour != $date->hour) {
return $this->hour - $date->hour;
}
if ($this->min != $date->min) {
return $this->min - $date->min;
}
return $this->sec - $date->sec;
}
/**
* Compare this to another date object, including times, to see
* which one is greater (later). Assumes that the dates are in the
* same timezone.
*
* @param mixed $date The date to compare to.
*
* @return integer == 0 if the dates are equal
* >= 1 if this date is greater (later)
* <= -1 if the other date is greater (later)
*/
function compareDateTime($date)
{
if (!is_a($date, 'Horde_Date')) {
$date = new Horde_Date($date);
}
if ($diff = $this->compareDate($date)) {
return $diff;
}
return $this->compareTime($date);
}
/**
* Get the time offset for local time zone.
*
* @param boolean $colon Place a colon between hours and minutes?
*
* @return string Timezone offset as a string in the format +HH:MM.
*/
function tzOffset($colon = true)
{
$secs = $this->format('Z');
if ($secs < 0) {
$sign = '-';
$secs = -$secs;
} else {
$sign = '+';
}
$colon = $colon ? ':' : '';
$mins = intval(($secs + 30) / 60);
return sprintf('%s%02d%s%02d',
$sign, $mins / 60, $colon, $mins % 60);
}
/**
* Return the unix timestamp representation of this date.
*
* @return integer A unix timestamp.
*/
function timestamp()
{
if (class_exists('DateTime')) {
return $this->format('U');
} else {
return Horde_Date::_mktime($this->hour, $this->min, $this->sec, $this->month, $this->mday, $this->year);
}
}
/**
* Return the unix timestamp representation of this date, 12:00am.
*
* @return integer A unix timestamp.
*/
function datestamp()
{
if (class_exists('DateTime')) {
$dt = new DateTime();
$dt->setDate($this->year, $this->month, $this->mday);
$dt->setTime(0, 0, 0);
return $dt->format('U');
} else {
return Horde_Date::_mktime(0, 0, 0, $this->month, $this->mday, $this->year);
}
}
/**
* Format time using the specifiers available in date() or in the DateTime
* class' format() method.
*
* @since Horde 3.3
*
* @param string $format
*
* @return string Formatted time.
*/
function format($format)
{
if (class_exists('DateTime')) {
$dt = new DateTime();
$dt->setDate($this->year, $this->month, $this->mday);
$dt->setTime($this->hour, $this->min, $this->sec);
return $dt->format($format);
} else {
return date($format, $this->timestamp());
}
}
/**
* Format time in ISO-8601 format. Works correctly since Horde 3.2.
*
* @return string Date and time in ISO-8601 format.
*/
function iso8601DateTime()
{
return $this->rfc3339DateTime() . $this->tzOffset();
}
/**
* Format time in RFC 2822 format.
*
* @return string Date and time in RFC 2822 format.
*/
function rfc2822DateTime()
{
return $this->format('D, j M Y H:i:s') . ' ' . $this->tzOffset(false);
}
/**
* Format time in RFC 3339 format.
*
* @since Horde 3.1
*
* @return string Date and time in RFC 3339 format. The seconds part has
* been added with Horde 3.2.
*/
function rfc3339DateTime()
{
return $this->format('Y-m-d\TH:i:s');
}
/**
* Format time to standard 'ctime' format.
*
* @return string Date and time.
*/
function cTime()
{
return $this->format('D M j H:i:s Y');
}
/**
* Format date and time using strftime() format.
*
* @since Horde 3.1
*
* @return string strftime() formatted date and time.
*/
function strftime($format)
{
if (preg_match('/%[^' . $this->_supportedSpecs . ']/', $format)) {
return strftime($format, $this->timestamp());
} else {
return $this->_strftime($format);
}
}
/**
* Format date and time using a limited set of the strftime() format.
*
* @return string strftime() formatted date and time.
*/
function _strftime($format)
{
if (preg_match('/%[bBpxX]/', $format)) {
require_once 'Horde/NLS.php';
}
return preg_replace(
array('/%b/e',
'/%B/e',
'/%C/e',
'/%d/e',
'/%D/e',
'/%e/e',
'/%H/e',
'/%I/e',
'/%m/e',
'/%M/e',
'/%n/',
'/%p/e',
'/%R/e',
'/%S/e',
'/%t/',
'/%T/e',
'/%x/e',
'/%X/e',
'/%y/e',
'/%Y/',
'/%%/'),
array('$this->_strftime(NLS::getLangInfo(constant(\'ABMON_\' . (int)$this->month)))',
'$this->_strftime(NLS::getLangInfo(constant(\'MON_\' . (int)$this->month)))',
'(int)($this->year / 100)',
'sprintf(\'%02d\', $this->mday)',
'$this->_strftime(\'%m/%d/%y\')',
'sprintf(\'%2d\', $this->mday)',
'sprintf(\'%02d\', $this->hour)',
'sprintf(\'%02d\', $this->hour == 0 ? 12 : ($this->hour > 12 ? $this->hour - 12 : $this->hour))',
'sprintf(\'%02d\', $this->month)',
'sprintf(\'%02d\', $this->min)',
"\n",
'$this->_strftime(NLS::getLangInfo($this->hour < 12 ? AM_STR : PM_STR))',
'$this->_strftime(\'%H:%M\')',
'sprintf(\'%02d\', $this->sec)',
"\t",
'$this->_strftime(\'%H:%M:%S\')',
'$this->_strftime(NLS::getLangInfo(D_FMT))',
'$this->_strftime(NLS::getLangInfo(T_FMT))',
'substr(sprintf(\'%04d\', $this->year), -2)',
(int)$this->year,
'%'),
$format);
}
/**
* mktime() implementation that supports dates outside of 1970-2038,
* from http://phplens.com/phpeverywhere/adodb_date_library.
*
* @TODO remove in Horde 4
*
* This does NOT work with pre-1970 daylight saving times.
*
* @static
*/
function _mktime($hr, $min, $sec, $mon = false, $day = false,
$year = false, $is_dst = false, $is_gmt = false)
{
if ($mon === false) {
return $is_gmt
? @gmmktime($hr, $min, $sec)
: @mktime($hr, $min, $sec);
}
if ($year > 1901 && $year < 2038 &&
($year >= 1970 || version_compare(PHP_VERSION, '5.0.0', '>='))) {
return $is_gmt
? @gmmktime($hr, $min, $sec, $mon, $day, $year)
: @mktime($hr, $min, $sec, $mon, $day, $year);
}
$gmt_different = $is_gmt
? 0
: (mktime(0, 0, 0, 1, 2, 1970, 0) - gmmktime(0, 0, 0, 1, 2, 1970, 0));
$mon = intval($mon);
$day = intval($day);
$year = intval($year);
if ($mon > 12) {
$y = floor($mon / 12);
$year += $y;
$mon -= $y * 12;
} elseif ($mon < 1) {
$y = ceil((1 - $mon) / 12);
$year -= $y;
$mon += $y * 12;
}
$_day_power = 86400;
$_hour_power = 3600;
$_min_power = 60;
$_month_table_normal = array('', 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
$_month_table_leaf = array('', 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
$_total_date = 0;
if ($year >= 1970) {
for ($a = 1970; $a <= $year; $a++) {
$leaf = Horde_Date::isLeapYear($a);
if ($leaf == true) {
$loop_table = $_month_table_leaf;
$_add_date = 366;
} else {
$loop_table = $_month_table_normal;
$_add_date = 365;
}
if ($a < $year) {
$_total_date += $_add_date;
} else {
for ($b = 1; $b < $mon; $b++) {
$_total_date += $loop_table[$b];
}
}
}
return ($_total_date + $day - 1) * $_day_power + $hr * $_hour_power + $min * $_min_power + $sec + $gmt_different;
}
for ($a = 1969 ; $a >= $year; $a--) {
$leaf = Horde_Date::isLeapYear($a);
if ($leaf == true) {
$loop_table = $_month_table_leaf;
$_add_date = 366;
} else {
$loop_table = $_month_table_normal;
$_add_date = 365;
}
if ($a > $year) {
$_total_date += $_add_date;
} else {
for ($b = 12; $b > $mon; $b--) {
$_total_date += $loop_table[$b];
}
}
}
$_total_date += $loop_table[$mon] - $day;
$_day_time = $hr * $_hour_power + $min * $_min_power + $sec;
$_day_time = $_day_power - $_day_time;
$ret = -($_total_date * $_day_power + $_day_time - $gmt_different);
if ($ret < -12220185600) {
// If earlier than 5 Oct 1582 - gregorian correction.
return $ret + 10 * 86400;
} elseif ($ret < -12219321600) {
// If in limbo, reset to 15 Oct 1582.
return -12219321600;
} else {
return $ret;
}
}
}

View File

@ -206,7 +206,7 @@ class Horde_RPC {
}
$class = 'Horde_RPC_' . $driver;
if (class_exists($class)) {
$rpc = &new $class($params);
$rpc = new $class($params);
} else {
require_once 'PEAR.php';
$rpc = PEAR::raiseError('Class definition of ' . $class . ' not found.');

View File

@ -89,7 +89,7 @@ class Horde_RPC_syncml extends Horde_RPC {
}
require_once 'XML/WBXML/ContentHandler.php';
$this->_output = &new XML_WBXML_ContentHandler();
$this->_output = new XML_WBXML_ContentHandler();
$this->_parse($request);
$response = $this->_output->getOutput();
@ -200,7 +200,7 @@ class Horde_RPC_syncml extends Horde_RPC {
// Either <SyncML><SyncHdr> or <SyncML><SyncBody>
if (!isset($this->_contentHandler)) {
// If not defined then create SyncHdr.
$this->_contentHandler = &new Horde_SyncML_SyncmlHdr();
$this->_contentHandler = new Horde_SyncML_SyncmlHdr();
$this->_contentHandler->setOutput($this->_output);
}
@ -233,7 +233,7 @@ class Horde_RPC_syncml extends Horde_RPC {
unset($this->_contentHandler);
$this->_contentHandler = &new Horde_SyncML_SyncmlBody();
$this->_contentHandler = new Horde_SyncML_SyncmlBody();
$this->_contentHandler->setOutput($this->_output);
} else {
// No longer used.

View File

@ -51,8 +51,8 @@ class Horde_RPC_syncml_wbxml extends Horde_RPC_syncml {
}
$decoder = &new XML_WBXML_Decoder();
$this->_output = &new XML_WBXML_Encoder();
$decoder = new XML_WBXML_Decoder();
$this->_output = new XML_WBXML_Encoder();
$decoder->setContentHandler($this);

View File

@ -1,16 +1,14 @@
<?php
require_once 'Horde/Util.php';
$GLOBALS['_HORDE_STRING_CHARSET'] = 'iso-8859-1';
/**
* The String:: class provides static methods for charset and locale safe
* string manipulation.
*
* $Horde: framework/Util/String.php,v 1.50 2005/02/10 17:09:44 jan Exp $
* $Horde: framework/Util/String.php,v 1.43.6.31 2008/10/23 21:28:38 jan Exp $
*
* Copyright 2003-2005 Jan Schneider <jan@horde.org>
* Copyright 2003-2008 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
@ -21,18 +19,40 @@ $GLOBALS['_HORDE_STRING_CHARSET'] = 'iso-8859-1';
*/
class String {
/**
* Caches the result of extension_loaded() calls.
*
* @param string $ext The extension name.
*
* @return boolean Is the extension loaded?
*
* @see Util::extensionExists()
*/
function extensionExists($ext)
{
static $cache = array();
if (!isset($cache[$ext])) {
$cache[$ext] = extension_loaded($ext);
}
return $cache[$ext];
}
/**
* Sets a default charset that the String:: methods will use if none is
* explicitely specified.
* explicitly specified.
*
* @param string $charset The charset to use as the default one.
*/
function setDefaultCharset($charset)
{
$GLOBALS['_HORDE_STRING_CHARSET'] = $charset;
if (Util::extensionExists('mbstring') &&
if (String::extensionExists('mbstring') &&
function_exists('mb_regex_encoding')) {
@mb_regex_encoding($charset);
$old_error = error_reporting(0);
mb_regex_encoding(String::_mbstringCharset($charset));
error_reporting($old_error);
}
}
@ -44,44 +64,53 @@ class String {
* The original string is returned if conversion failed or none
* of the extensions were available.
*
* @param mixed $input The data to be converted. If $input is an an
* array, the array's values get converted
* recursively.
* @param string $from The string's current charset.
* @param string $to The charset to convert the string to. If not
* specified, the global variable
* $_HORDE_STRING_CHARSET will be used.
* @param bool $recursion Internally used.
* @param mixed $input The data to be converted. If $input is an an array,
* the array's values get converted recursively.
* @param string $from The string's current charset.
* @param string $to The charset to convert the string to. If not
* specified, the global variable
* $_HORDE_STRING_CHARSET will be used.
*
* @return string The converted string.
* @return mixed The converted input data.
*/
function convertCharset($input, $from, $to = null, $recursion = false)
function convertCharset($input, $from, $to = null)
{
/* Don't bother converting numbers. */
if (is_numeric($input)) {
return $input;
}
/* Get the user's default character set if none passed in. */
if (is_null($to)) {
$to = $GLOBALS['_HORDE_STRING_CHARSET'];
}
/* If the from and to character sets are identical, return now. */
if (!$recursion) {
$from = String::lower($from);
$to = String::lower($to);
}
$from = String::lower($from);
$to = String::lower($to);
if ($from == $to) {
return $input;
}
if (is_array($input)) {
$tmp = array();
foreach ($input as $key => $val) {
$tmp[String::convertCharset($key, $from, $to, true)] = String::convertCharset($val, $from, $to, true);
while (list($key, $val) = each($input)) {
$tmp[String::_convertCharset($key, $from, $to)] = String::convertCharset($val, $from, $to);
}
return $tmp;
}
if (is_object($input)) {
// PEAR_Error objects are almost guaranteed to contain recursion,
// which will cause a segfault in PHP. We should never reach
// this line, but add a check and a log message to help the devs
// track down and fix this issue.
if (is_a($input, 'PEAR_Error')) {
Horde::logMessage('Called convertCharset() on a PEAR_Error object. ' . print_r($input, true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
return '';
}
$vars = get_object_vars($input);
foreach ($vars as $key => $val) {
$input->$key = String::convertCharset($val, $from, $to, true);
while (list($key, $val) = each($vars)) {
$input->$key = String::convertCharset($val, $from, $to);
}
return $input;
}
@ -90,43 +119,62 @@ class String {
return $input;
}
$output = false;
return String::_convertCharset($input, $from, $to);
}
/* Use utf8_[en|de]code() if possible. */
/**
* Internal function used to do charset conversion.
*
* @access private
*
* @param string $input See String::convertCharset().
* @param string $from See String::convertCharset().
* @param string $to See String::convertCharset().
*
* @return string The converted string.
*/
function _convertCharset($input, $from, $to)
{
$output = '';
$from_check = (($from == 'iso-8859-1') || ($from == 'us-ascii'));
if ($from_check && ($to == 'utf-8')) {
return utf8_encode($input);
}
$to_check = (($to == 'iso-8859-1') || ($to == 'us-ascii'));
if (($from == 'utf-8') && $to_check) {
return utf8_decode($input);
/* Use utf8_[en|de]code() if possible and if the string isn't too
* large (less than 16 MB = 16 * 1024 * 1024 = 16777216 bytes) - these
* functions use more memory. */
if (strlen($input) < 16777216 || !(String::extensionExists('iconv') || String::extensionExists('mbstring'))) {
if ($from_check && ($to == 'utf-8')) {
return utf8_encode($input);
}
if (($from == 'utf-8') && $to_check) {
return utf8_decode($input);
}
}
/* First try iconv with transliteration. */
if ($from != 'utf7-imap' &&
$to != 'utf7-imap' &&
Util::extensionExists('iconv')) {
ini_set('track_errors', 1);
/* We need to tack an extra character temporarily because
* of a bug in iconv() if the last character is not a 7
* bit ASCII character. */
if (($from != 'utf7-imap') &&
($to != 'utf7-imap') &&
String::extensionExists('iconv')) {
/* We need to tack an extra character temporarily because of a bug
* in iconv() if the last character is not a 7 bit ASCII
* character. */
$oldTrackErrors = ini_set('track_errors', 1);
unset($php_errormsg);
$output = @iconv($from, $to . '//TRANSLIT', $input . 'x');
if (isset($php_errormsg)) {
$output = false;
} else {
$output = String::substr($output, 0, -1, $to);
}
ini_restore('track_errors');
$output = (isset($php_errormsg)) ? false : String::substr($output, 0, -1, $to);
ini_set('track_errors', $oldTrackErrors);
}
/* Next try mbstring. */
if (!$output && Util::extensionExists('mbstring')) {
$output = @mb_convert_encoding($input, $to, $from);
if (!$output && String::extensionExists('mbstring')) {
$old_error = error_reporting(0);
$output = mb_convert_encoding($input, $to, String::_mbstringCharset($from));
error_reporting($old_error);
}
/* At last try imap_utf7_[en|de]code if appropriate. */
if (!$output && Util::extensionExists('imap')) {
if (!$output && String::extensionExists('imap')) {
if ($from_check && ($to == 'utf7-imap')) {
return @imap_utf7_encode($input);
}
@ -135,7 +183,7 @@ class String {
}
}
return !$output ? $input : $output;
return (!$output) ? $input : $output;
}
/**
@ -155,12 +203,14 @@ class String {
if ($locale) {
/* The existence of mb_strtolower() depends on the platform. */
if (Util::extensionExists('mbstring') &&
if (String::extensionExists('mbstring') &&
function_exists('mb_strtolower')) {
if (is_null($charset)) {
$charset = $GLOBALS['_HORDE_STRING_CHARSET'];
}
$ret = @mb_strtolower($string, $charset);
$old_error = error_reporting(0);
$ret = mb_strtolower($string, String::_mbstringCharset($charset));
error_reporting($old_error);
if (!empty($ret)) {
return $ret;
}
@ -173,7 +223,7 @@ class String {
}
if (!isset($lowers[$string])) {
$language = setlocale(LC_CTYPE, 0);
setlocale(LC_CTYPE, 'en_US');
setlocale(LC_CTYPE, 'C');
$lowers[$string] = strtolower($string);
setlocale(LC_CTYPE, $language);
}
@ -203,7 +253,9 @@ class String {
if (is_null($charset)) {
$charset = $GLOBALS['_HORDE_STRING_CHARSET'];
}
$ret = @mb_strtoupper($string, $charset);
$old_error = error_reporting(0);
$ret = mb_strtoupper($string, String::_mbstringCharset($charset));
error_reporting($old_error);
if (!empty($ret)) {
return $ret;
}
@ -216,7 +268,7 @@ class String {
}
if (!isset($uppers[$string])) {
$language = setlocale(LC_CTYPE, 0);
setlocale(LC_CTYPE, 'en_US');
setlocale(LC_CTYPE, 'C');
$uppers[$string] = strtoupper($string);
setlocale(LC_CTYPE, $language);
}
@ -251,31 +303,34 @@ class String {
/**
* Returns part of a string.
*
* @param string $string The string to be converted.
* @param int $start The part's start position, zero based.
* @param int $length The part's length.
* @param string $charset The charset to use when calculating the part's
* position and length, defaults to current charset.
* @param string $string The string to be converted.
* @param integer $start The part's start position, zero based.
* @param integer $length The part's length.
* @param string $charset The charset to use when calculating the part's
* position and length, defaults to current
* charset.
*
* @return string The string's part.
*/
function substr($string, $start, $length = null, $charset = null)
{
if (Util::extensionExists('mbstring')) {
if (is_null($length)) {
$length = String::length($string, $charset) - $start;
}
if ($length == 0) {
return '';
}
if (String::extensionExists('mbstring')) {
if (is_null($charset)) {
$charset = $GLOBALS['_HORDE_STRING_CHARSET'];
}
if (is_null($length)) {
$length = String::length($string, $charset);
}
$ret = @mb_substr($string, $start, $length, $charset);
$old_error = error_reporting(0);
$ret = mb_substr($string, $start, $length, String::_mbstringCharset($charset));
error_reporting($old_error);
if (!empty($ret)) {
return $ret;
}
}
if (is_null($length)) {
$length = String::length($string);
}
return substr($string, $start, $length);
}
@ -290,11 +345,17 @@ class String {
*/
function length($string, $charset = null)
{
if (Util::extensionExists('mbstring')) {
if (is_null($charset)) {
$charset = $GLOBALS['_HORDE_STRING_CHARSET'];
}
$ret = @mb_strlen($string, $charset);
if (is_null($charset)) {
$charset = $GLOBALS['_HORDE_STRING_CHARSET'];
}
$charset = String::lower($charset);
if ($charset == 'utf-8' || $charset == 'utf8') {
return strlen(utf8_decode($string));
}
if (String::extensionExists('mbstring')) {
$old_error = error_reporting(0);
$ret = mb_strlen($string, String::_mbstringCharset($charset));
error_reporting($old_error);
if (!empty($ret)) {
return $ret;
}
@ -308,22 +369,24 @@ class String {
*
* @param string $haystack The string to search through.
* @param string $needle The string to search for.
* @param int $offset Allows to specify which character in haystack
* @param integer $offset Allows to specify which character in haystack
* to start searching.
* @param string $charset The charset to use when searching for the
* $needle string.
*
* @return int The position of first occurrence.
* @return integer The position of first occurrence.
*/
function pos($haystack, $needle, $offset = 0, $charset = null)
{
if (Util::extensionExists('mbstring')) {
if (String::extensionExists('mbstring')) {
if (is_null($charset)) {
$charset = $GLOBALS['_HORDE_STRING_CHARSET'];
}
ini_set('track_errors', 1);
$ret = @mb_strpos($haystack, $needle, $offset, $charset);
ini_restore('track_errors');
$track_errors = ini_set('track_errors', 1);
$old_error = error_reporting(0);
$ret = mb_strpos($haystack, $needle, $offset, String::_mbstringCharset($charset));
error_reporting($old_error);
ini_set('track_errors', $track_errors);
if (!isset($php_errormsg)) {
return $ret;
}
@ -337,7 +400,7 @@ class String {
* This method behaves exactly like str_pad but is multibyte safe.
*
* @param string $input The string to be padded.
* @param int $length The length of the resulting string.
* @param integer $length The length of the resulting string.
* @param string $pad The string to pad the input string with. Must
* be in the same charset like the input string.
* @param const $type The padding type. One of STR_PAD_LEFT,
@ -388,22 +451,89 @@ class String {
/**
* Wraps the text of a message.
*
* @todo Make multibyte-save.
* @since Horde 3.2
*
* @access public
* @param string $string String containing the text to wrap.
* @param integer $width Wrap the string at this number of
* characters.
* @param string $break Character(s) to use when breaking lines.
* @param boolean $cut Whether to cut inside words if a line
* can't be wrapped.
* @param string $charset Character set to use when breaking lines.
* @param boolean $line_folding Whether to apply line folding rules per
* RFC 822 or similar. The correct break
* characters including leading whitespace
* have to be specified too.
*
* @param string $text String containing the text to wrap.
* @param optional integer $length Wrap $text at this number of
* characters.
* @param optional string $break_char Character(s) to use when breaking
* lines.
* @param optional string $charset Character set to use when breaking
* lines.
* @param optional boolean $quote Ignore lines that are wrapped with
* the '>' character (RFC 2646)? If
* true, we don't remove any padding
* whitespace at the end of the
* string.
* @return string String containing the wrapped text.
*/
function wordwrap($string, $width = 75, $break = "\n", $cut = false,
$charset = null, $line_folding = false)
{
/* Get the user's default character set if none passed in. */
if (is_null($charset)) {
$charset = $GLOBALS['_HORDE_STRING_CHARSET'];
}
$charset = String::_mbstringCharset($charset);
$string = String::convertCharset($string, $charset, 'utf-8');
$wrapped = '';
while (String::length($string, 'utf-8') > $width) {
$line = String::substr($string, 0, $width, 'utf-8');
$string = String::substr($string, String::length($line, 'utf-8'), null, 'utf-8');
// Make sure didn't cut a word, unless we want hard breaks anyway.
if (!$cut && preg_match('/^(.+?)(\s|\r?\n)/u', $string, $match)) {
$line .= $match[1];
$string = String::substr($string, String::length($match[1], 'utf-8'), null, 'utf-8');
}
// Wrap at existing line breaks.
if (preg_match('/^(.*?)(\r?\n)(.*)$/u', $line, $match)) {
$wrapped .= $match[1] . $match[2];
$string = $match[3] . $string;
continue;
}
// Wrap at the last colon or semicolon followed by a whitespace if
// doing line folding.
if ($line_folding &&
preg_match('/^(.*?)(;|:)(\s+.*)$/u', $line, $match)) {
$wrapped .= $match[1] . $match[2] . $break;
$string = $match[3] . $string;
continue;
}
// Wrap at the last whitespace of $line.
if ($line_folding) {
$sub = '(.+[^\s])';
} else {
$sub = '(.*)';
}
if (preg_match('/^' . $sub . '(\s+)(.*)$/u', $line, $match)) {
$wrapped .= $match[1] . $break;
$string = ($line_folding ? $match[2] : '') . $match[3] . $string;
continue;
}
// Hard wrap if necessary.
if ($cut) {
$wrapped .= String::substr($line, 0, $width, 'utf-8') . $break;
$string = String::substr($line, $width, null, 'utf-8') . $string;
continue;
}
$wrapped .= $line;
}
return String::convertCharset($wrapped . $string, 'utf-8', $charset);
}
/**
* Wraps the text of a message.
*
* @param string $text String containing the text to wrap.
* @param integer $length Wrap $text at this number of characters.
* @param string $break_char Character(s) to use when breaking lines.
* @param string $charset Character set to use when breaking lines.
* @param boolean $quote Ignore lines that are wrapped with the '>'
* character (RFC 2646)? If true, we don't
* remove any padding whitespace at the end of
* the string.
*
* @return string String containing the wrapped text.
*/
@ -422,7 +552,7 @@ class String {
if ($input != '-- ') {
$input = rtrim($input);
}
$line = wordwrap($input, $length, $break_char);
$line = String::wordwrap($input, $length, $break_char, false, $charset);
}
$paragraphs[] = $line;
@ -432,9 +562,8 @@ class String {
}
/**
* Returns true if the every character in the parameter is an
* alphabetic character. This method doesn't work with any charset
* other than the current charset yet.
* Returns true if the every character in the parameter is an alphabetic
* character.
*
* @param $string The string to test.
* @param $charset The charset to use when testing the string.
@ -443,26 +572,30 @@ class String {
*/
function isAlpha($string, $charset = null)
{
if (Util::extensionExists('mbstring')) {
$old_charset = mb_regex_encoding();
if ($charset != $old_charset) {
@mb_regex_encoding($charset);
}
$alpha = !mb_ereg_match('[^[:alpha:]]', $string);
if ($charset != $old_charset) {
@mb_regex_encoding($old_charset);
}
return $alpha;
if (!String::extensionExists('mbstring')) {
return ctype_alpha($string);
}
return ctype_alpha($string);
$charset = String::_mbstringCharset($charset);
$old_charset = mb_regex_encoding();
$old_error = error_reporting(0);
if ($charset != $old_charset) {
mb_regex_encoding($charset);
}
$alpha = !mb_ereg_match('[^[:alpha:]]', $string);
if ($charset != $old_charset) {
mb_regex_encoding($old_charset);
}
error_reporting($old_error);
return $alpha;
}
/**
* Returns true if every character in the parameter is a lowercase
* letter in the current locale.
*
* @access public
* Returns true if ever character in the parameter is a lowercase letter in
* the current locale.
*
* @param $string The string to test.
* @param $charset The charset to use when testing the string.
@ -476,10 +609,8 @@ class String {
}
/**
* Returns true if every character in the parameter is an
* uppercase letter in the current locale.
*
* @access public
* Returns true if every character in the parameter is an uppercase letter
* in the current locale.
*
* @param string $string The string to test.
* @param string $charset The charset to use when testing the string.
@ -493,63 +624,59 @@ class String {
}
/**
* Performs a regex match search on the text provided. Will correctly
* handle text with multibyte characters if the mbstring extensions and
* the mbregex functions are available. Will use the preg_match()
* function if possible or if the mbregex ereg function is not available.
* Performs a multibyte safe regex match search on the text provided.
*
* @access public
* @since Horde 3.1
*
* @param string $text The text to search.
* @param array $regex The regular expressions to use. These
* expressions should conform to ereg() rules -
* extended perl rules are NOT supported.
* Additionally, do NOT add perl regex delimiters
* (e.g. '/' or '|') to the beginning/end.
* @param array $regex The regular expressions to use, without perl
* regex delimiters (e.g. '/' or '|').
* @param string $charset The character set of the text.
*
* @return array The matches array from the first regex that matches.
*/
function regexMatch($text, $regex, $charset = null)
{
static $mbregex;
if (!isset($mbregex)) {
$mbregex = function_exists('mb_ereg');
}
$use_mb = false;
if ($mbregex && !is_null($charset) &&
(String::lower($charset) != 'utf-8')) {
$old_charset = mb_regex_encoding();
if ($charset != $old_charset) {
@mb_regex_encoding($charset);
} else {
unset($old_charset);
}
$use_mb = true;
if (!empty($charset)) {
$regex = String::convertCharset($regex, $charset, 'utf-8');
$text = String::convertCharset($text, $charset, 'utf-8');
}
$matches = array();
foreach ($regex as $val) {
if ($use_mb) {
if (mb_ereg($val, $text, $matches)) {
break;
}
} else {
if (preg_match('/' . $val . '/u', $text, $matches)) {
break;
}
if (preg_match('/' . $val . '/u', $text, $matches)) {
break;
}
}
if (isset($old_charset)) {
@mb_regex_encoding($old_charset);
if (!empty($charset)) {
$matches = String::convertCharset($matches, 'utf-8', $charset);
}
return $matches;
}
/**
* Workaround charsets that don't work with mbstring functions.
*
* @access private
*
* @param string $charset The original charset.
*
* @return string The charset to use with mbstring functions.
*/
function _mbstringCharset($charset)
{
/* mbstring functions do not handle the 'ks_c_5601-1987' &
* 'ks_c_5601-1989' charsets. However, these charsets are used, for
* example, by various versions of Outlook to send Korean characters.
* Use UHC (CP949) encoding instead. See, e.g.,
* http://lists.w3.org/Archives/Public/ietf-charsets/2001AprJun/0030.html */
if (in_array(String::lower($charset), array('ks_c_5601-1987', 'ks_c_5601-1989'))) {
$charset = 'UHC';
}
return $charset;
}
}

View File

@ -1,32 +1,32 @@
<?php
/**
* eGroupWare - SyncML based on Horde 3.0
*
* The Horde_SyncML_SyncHdr and Horde_SyncML_SyncBody classes provides
* a SyncHdr and SyncBody in SyncML Representation Protocol, version
* 1.1 5.2.2 and 5.2.3. Most of the work is passed on to
* Horde_SyncML_Command_Alert and Horde_SyncML_Command_Sync.
*
* Using the PEAR Log class (which need to be installed!)
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage horde
* @author Anthony Mills <amills@pyramid6.com>
* @author Karsten Fourmont <fourmont@gmx.de>
* @author Joerg Lehrke <jlehrke@noc.de>
* @copyright (c) The Horde Project (http://www.horde.org/)
* @version $Id$
*/
include_once 'Horde/SyncML/Command.php';
include_once 'Horde/SyncML/Command/Status.php';
include_once 'Horde/SyncML/Command/Alert.php';
include_once 'Horde/SyncML/Command/Final.php';
include_once 'Horde/SyncML/Command/Sync.php';
include_once 'Horde/SyncML/Sync.php';
include_once 'Horde/SyncML/Command/Sync/SyncElementItem.php';
/**
* The Horde_SyncML_SyncHdr and Horde_SyncML_SyncBody classes provides
* a SyncHdr and SyncBody in SyncML Representation Protocol, version
* 1.1 5.2.2 and 5.2.3. Most of the work is passed on to
* Horde_SyncML_Command_Alert and Horde_SyncML_Command_Sync.
*
* $Horde: framework/SyncML/SyncML.php,v 1.21 2004/07/21 19:26:36 karsten Exp $
*
* Copyright 2003-2004 Anthony Mills <amills@pyramid6.com>
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Anthony Mills <amills@pyramid6.com>
* @author Karsten Fourmont <fourmont@gmx.de>
*
* @version $Revision$
* @since Horde 3.0
* @package Horde_SyncML
*/
class Horde_SyncML_ContentHandler {
/**
@ -108,7 +108,7 @@ class Horde_SyncML_SyncMLHdr extends Horde_SyncML_ContentHandler {
/**
* Defined in SyncML Representation Protocol, version 1.1. Must
* be 1.0 (0) or 1.1 (1).
* be 1.0 (0), 1.1 (1) or 1.2(2).
*
* @var string $_version
*/
@ -144,41 +144,52 @@ class Horde_SyncML_SyncMLHdr extends Horde_SyncML_ContentHandler {
var $_credType;
var $_maxMsgSize;
function getStateFromSession($sourceURI, $locName, $sessionID)
{
// Remove any existing session since we'll be contructing a
// custom session id.
session_regenerate_id();
session_destroy();
// we need to (re-)load the eGW session-handler, as session_destroy unloads custom session-handlers
if (function_exists('init_session_handler'))
{
init_session_handler();
if (function_exists('init_session_handler')) {
init_session_handler();
}
// Reload the Horde SessionHandler if necessary.
// Reload the Horde SessionHandler if necessary.
Horde::setupSessionHandler();
// It would seem multisync does not send the user name once it
// has been authorized. Make sure we have a valid session id.
if(!empty($_GET['syncml_sessionid'])) {
session_id($_GET['syncml_sessionid']);
Horde::logMessage('SyncML['. session_id() .']: reusing existing session', __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde::logMessage('SyncML['. session_id() .']: reusing existing session',
__FILE__, __LINE__, PEAR_LOG_DEBUG);
} else {
#session_id('syncml' . preg_replace('/[^a-zA-Z0-9]/', '', $sourceURI . $sessionID));
session_id('syncml-' . md5(uniqid(rand(), true)));
Horde::logMessage('SyncML['. session_id() .']: starting new session for '.$this->_locName, __FILE__, __LINE__, PEAR_LOG_INFO);
Horde::logMessage('SyncML['. session_id() .']: starting new session for '
. $this->_locName, __FILE__, __LINE__, PEAR_LOG_INFO);
}
@session_start();
if (!isset($_SESSION['SyncML.state'])) {
// Create a new state if one does not already exist.
Horde::logMessage('SyncML['. session_id() .']: create new session state variable', __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde::logMessage('SyncML['. session_id() .']: create new session state variable for '
. $sourceURI, __FILE__, __LINE__, PEAR_LOG_DEBUG);
# LK $_SESSION['SyncML.state'] = &new Horde_SyncML_State($sourceURI, $locName, $sessionID);
$_SESSION['SyncML.state'] = &new EGW_SyncML_State($sourceURI, $locName, $sessionID);
}
#if($_SESSION['SyncML.state']->_isAuthorized)
# Horde::logMessage('SyncML['. session_id() .']: is session authorized', __FILE__, __LINE__, PEAR_LOG_DEBUG);
$_SESSION['SyncML.state'] = new EGW_SyncML_State($sourceURI, $locName, $sessionID);
}
if($_SESSION['SyncML.state']->isAuthorized()) {
Horde::logMessage('SyncML['. session_id() .']: session is authorized',
__FILE__, __LINE__, PEAR_LOG_DEBUG);
}
#Horde::logMessage('SyncML['. session_id() . "]:\n" . print_r($_SESSION['SyncML.state'], true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
return $_SESSION['SyncML.state'];
}
@ -216,18 +227,26 @@ class Horde_SyncML_SyncMLHdr extends Horde_SyncML_ContentHandler {
#Horde::logMessage('SymcML: SyncHdr done. Try to load state from session.', __FILE__, __LINE__, PEAR_LOG_DEBUG);
$state = $this->getStateFromSession($this->_sourceURI, $this->_locName, $this->_sessionID);
Horde::logMessage('SyncML['. session_id() .']: package '.$this->_msgID.' +++++++++++++++++++++ started', __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde::logMessage('SyncML['. session_id() .']: package '
. $this->_msgID.' +++++++++++++++++++++ started',
__FILE__, __LINE__, PEAR_LOG_DEBUG);
$state->setVersion($this->_version);
$state->setMsgID($this->_msgID);
$state->setTargetURI($this->_targetURI);
$state->setWBXML(is_a($this->_output, 'XML_WBXML_Encoder'));
if(isset($this->_credData) && isset($this->_locName) && !$state->isAuthorized())
{
if(isset($this->_credData)
&& isset($this->_locName)
&& !$state->isAuthorized()) {
$state->setPassword($this->_credData);
$state->setLocName($this->_locName);
}
if (isset($this->_maxMsgSize)) {
$state->setMaxMsgSize($this->_maxMsgSize);
}
#$str = 'authorized=' . $state->isAuthorized();
#$str .= ' version=' . $state->getVersion();
#$str .= ' msgid=' . $state->getMsgID();
@ -246,11 +265,17 @@ class Horde_SyncML_SyncMLHdr extends Horde_SyncML_ContentHandler {
case 3:
if ($element == 'VerProto') {
// </VerProto></SyncHdr></SyncML>
if (trim($this->_chars) == 'SyncML/1.1') {
$this->_version = 1;
} else {
$this->_version = 0;
}
switch (strtolower(trim($this->_chars))) {
case 'syncml/1.2':
$this->_version = 2;
break;
case 'syncml/1.1':
$this->_version = 1;
break;
default:
$this->_version = 0;
break;
}
} elseif ($element == 'SessionID') {
// </SessionID></SyncHdr></SyncML>
$this->_sessionID = trim($this->_chars);
@ -269,10 +294,9 @@ class Horde_SyncML_SyncMLHdr extends Horde_SyncML_ContentHandler {
$this->_credData = base64_decode($this->_credData);
//}
$tmp = split(':', $this->_credData, 2);
$tmp = explode(':', $this->_credData, 2);
// set only if not set by LocName already
if(!isset($this->_locName))
{
if(!isset($this->_locName)) {
$this->_locName = $tmp[0];
}
$this->_credData = $tmp[1];
@ -282,26 +306,34 @@ class Horde_SyncML_SyncMLHdr extends Horde_SyncML_ContentHandler {
break;
case 4:
if ($element == 'LocURI') {
if ($this->_isSource) {
// </LocURI></Source></SyncHdr></SyncML>
$this->_sourceURI = trim($this->_chars);
} else {
// </LocURI></Target></SyncHdr></SyncML>
$this->_targetURI = trim($this->_chars);
}
} elseif ($element == 'LocName') {
if ($this->_isSource) {
// </LocName></Source></SyncHdr></SyncML>
$this->_locName = trim($this->_chars);
}
} elseif ($element == 'Data') {
// </Data></Cred></SyncHdr></SyncML>
if ($this->_isCred) {
$this->_credData = trim($this->_chars);
}
}
break;
switch ($element) {
case 'LocURI':
if ($this->_isSource) {
// </LocURI></Source></SyncHdr></SyncML>
$this->_sourceURI = trim($this->_chars);
} else {
// </LocURI></Target></SyncHdr></SyncML>
$this->_targetURI = trim($this->_chars);
}
break;
case 'LocName':
if ($this->_isSource) {
// </LocName></Source></SyncHdr></SyncML>
$this->_locName = trim($this->_chars);
}
break;
case 'Data':
// </Data></Cred></SyncHdr></SyncML>
if ($this->_isCred) {
$this->_credData = trim($this->_chars);
}
break;
case 'MaxMsgSize':
//</MaxMsgSize></Meta></SyncHdr></SyncML>
$this->_maxMsgSize = trim($this->_chars);
break;
}
break;
case 5:
if ($this->_isCred) {
@ -323,19 +355,31 @@ class Horde_SyncML_SyncMLHdr extends Horde_SyncML_ContentHandler {
{
$attrs = array();
$state = $_SESSION['SyncML.state'];
$state = &$_SESSION['SyncML.state'];
$uri = $state->getURI();
$uriMeta = $state->getURIMeta();
$output->startElement($uri, 'SyncHdr', $attrs);
$output->startElement($uri, 'VerDTD', $attrs);
$chars = ($this->_version == 1) ? '1.1' : '1.0';
if ($this->_version == 2) {
$chars = '1.2';
} elseif ($this->_version == 1) {
$chars = '1.1';
} else {
$chars = '1.0';
}
$output->characters($chars);
$output->endElement($uri, 'VerDTD');
$output->startElement($uri, 'VerProto', $attrs);
$chars = ($this->_version == 1) ? 'SyncML/1.1' : 'SyncML/1.0';
if ($this->_version == 2) {
$chars = 'SyncML/1.2';
} elseif ($this->_version == 1) {
$chars = 'SyncML/1.1';
} else {
$chars = 'SyncML/1.0';
}
$output->characters($chars);
$output->endElement($uri, 'VerProto');
@ -359,37 +403,40 @@ class Horde_SyncML_SyncMLHdr extends Horde_SyncML_ContentHandler {
$output->endElement($uri, 'LocURI');
$output->endElement($uri, 'Source');
if(session_id() != '' && !strpos($this->_targetURI,'syncml_sessionid')) {
$output->startElement($uri, 'RespURI', $attrs);
if (session_id() != '' && !strpos($this->_targetURI,'syncml_sessionid')) {
$output->startElement($uri, 'RespURI', $attrs);
// some clients don't send the whole URL as targetURI
if (strpos($this->_targetURI,$_SERVER['PHP_SELF']) === false) {
$output->characters($this->_targetURI . $_SERVER['PHP_SELF'] . '?syncml_sessionid=' . session_id());
} else {
$output->characters($this->_targetURI . '?syncml_sessionid=' . session_id());
// some clients don't send the whole URL as targetURI
if (strpos($this->_targetURI,$_SERVER['PHP_SELF']) === false) {
$output->characters($this->_targetURI . $_SERVER['PHP_SELF'] . '?syncml_sessionid=' . session_id());
} else {
$output->characters($this->_targetURI . '?syncml_sessionid=' . session_id());
}
$output->endElement($uri, 'RespURI');
}
$output->endElement($uri, 'RespURI');
}
/*
$output->startElement($uri, 'Meta', $attrs);
// Dummy Max MsqSize, this is just put in to make the packet
// work, it is not a real value.
if (!($maxMsgSize = $state->getMaxMsgSizeClient())) {
// Dummy MaxMsqSize, this is just put in to make the packet
// work, it is not our real value.
$maxMsgSize = 50000;
}
$output->startElement($uriMeta, 'MaxMsgSize', $attrs);
$chars = '50000';
$output->characters($chars);
$output->characters($maxMsgSize);
$output->endElement($uriMeta, 'MaxMsgSize');
// Dummy MaxObjSize, this is just put in to make the packet
// work, it is not a real value.
$output->startElement($uriMeta, 'MaxObjSize', $attrs);
$chars = '4000000';
$output->characters($chars);
$output->endElement($uriMeta, 'MaxObjSize');
#// Dummy MaxObjSize, this is just put in to make the packet
#// work, it is not our real value.
#if ($this->_version > 0) {
# // Don't send this to old devices
# $output->startElement($uriMeta, 'MaxObjSize', $attrs);
# $output->characters('4000000');
# $output->endElement($uriMeta, 'MaxObjSize');
#}
$output->endElement($uri, 'Meta');
*/
$output->endElement($uri, 'SyncHdr');
}
@ -448,33 +495,35 @@ class Horde_SyncML_SyncMLBody extends Horde_SyncML_ContentHandler {
function startElement($uri, $element, $attrs)
{
parent::startElement($uri, $element, $attrs);
$state = &$_SESSION['SyncML.state'];
switch ($this->_xmlStack) {
case 2:
$state = & $_SESSION['SyncML.state'];
$this->_actionCommands = false; // so far, we have not seen commands that require action from our side
$state->_sendFinal = false;
// <SyncML><SyncBody>
$this->_output->startElement($uri, $element, $attrs);
if($state->getLocName())
{
// Right our status about the header.
$status = &new Horde_SyncML_Command_Status(($state->isAuthorized()) ?
RESPONSE_AUTHENTICATION_ACCEPTED : RESPONSE_INVALID_CREDENTIALS, 'SyncHdr');
}
else
{
if($state->getLocName()) {
if($state->isAuthConfirmed()) {
// Right our status about the header
$status = new Horde_SyncML_Command_Status(($state->isAuthorized()) ?
RESPONSE_OK : RESPONSE_INVALID_CREDENTIALS, 'SyncHdr');
} else {
// Right our status about the header.
$status = new Horde_SyncML_Command_Status(($state->isAuthorized()) ?
RESPONSE_AUTHENTICATION_ACCEPTED : RESPONSE_INVALID_CREDENTIALS, 'SyncHdr');
}
} else {
// Request credentials if not sent so far
$status = &new Horde_SyncML_Command_Status(RESPONSE_MISSING_CREDENTIALS, 'SyncHdr');
$status = new Horde_SyncML_Command_Status(RESPONSE_MISSING_CREDENTIALS, 'SyncHdr');
}
$status->setSourceRef($state->getSourceURI());
$status->setTargetRef($state->getTargetURI());
$status->setCmdRef(0);
$state->clearNumberOfElements();
/*$str = 'authorized=' . $state->isAuthorized();
$str .= ' version=' . $state->getVersion();
@ -483,11 +532,12 @@ class Horde_SyncML_SyncMLBody extends Horde_SyncML_ContentHandler {
$str .= ' target=' . $state->getTargetURI();
*/
$this->_currentCmdID = $status->output($this->_currentCmdID, $this->_output);
if ($state->isAuthorized()) {
$state->AuthConfirmed();
}
break;
case 3:
$state = & $_SESSION['SyncML.state'];
// <SyncML><SyncBody><[Command]>
#Horde::logMessage('SyncML['. session_id() ."]: found command $element ", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$this->_currentCommand = Horde_SyncML_Command::factory($element);
@ -497,7 +547,7 @@ class Horde_SyncML_SyncMLBody extends Horde_SyncML_ContentHandler {
// We've got to do something! This can't be the last
// packet.
$this->_actionCommands = true;
Horde::logMessage('SyncML['. session_id() ."]: found action commands <$element> " . $this->_actionCommands, __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde::logMessage('SyncML['. session_id() ."]: found action commands <$element> ", __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
switch($element)
@ -516,43 +566,49 @@ class Horde_SyncML_SyncMLBody extends Horde_SyncML_ContentHandler {
}
}
function endElement($uri, $element) {
function endElement($uri, $element)
{
$state = &$_SESSION['SyncML.state'];
switch ($this->_xmlStack) {
case 2:
// </SyncBody></SyncML>
$state = & $_SESSION['SyncML.state'];
Horde::logMessage('SyncML['. session_id() .']: package ----------------------- done', __FILE__, __LINE__, PEAR_LOG_DEBUG);
if($state->getSyncStatus() == CLIENT_SYNC_FINNISHED && $state->getAlert222Received() == true) {
$state->setSyncStatus(CLIENT_SYNC_ACKNOWLEDGED);
if ($state->getAlert222Received() == true) {
// the Funambol specialty
if ($state->getSyncStatus() == CLIENT_SYNC_FINNISHED) {
$state->setSyncStatus(CLIENT_SYNC_ACKNOWLEDGED);
}
$state->setAlert222Received(false);
}
// send the sync reply
// we do still have some data to send OR
// we should reply to the Sync command
if($state->getSyncStatus() >= CLIENT_SYNC_ACKNOWLEDGED && $state->getSyncStatus() < SERVER_SYNC_FINNISHED) {
$sync = &new Horde_SyncML_Command_Sync();
if($state->getSyncStatus() > CLIENT_SYNC_FINNISHED && $state->getSyncStatus() < SERVER_SYNC_FINNISHED) {
$sync = new Horde_SyncML_Command_Sync();
$this->_currentCmdID = $sync->syncToClient($this->_currentCmdID, $this->_output);
}
// send the Final tag if possible
#if($state->getSyncStatus() != SERVER_SYNC_DATA_PENDING && $state->getSyncStatus() != CLIENT_SYNC_STARTED) {
if($state->getSyncStatus() >= SERVER_SYNC_FINNISHED || $state->_sendFinal) {
$final = &new Horde_SyncML_Command_Final();
$final = new Horde_SyncML_Command_Final();
$this->_currentCmdID = $final->output($this->_currentCmdID, $this->_output);
}
$this->_output->endElement($uri, $element);
Horde::logMessage('SyncML['. session_id() .']: syncStatus ' . $state->getSyncStatus() .'actionCommands: '.$this->_actionCommands, __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde::logMessage('SyncML['. session_id() .']: syncStatus = '. $state->getSyncStatus() .', actionCommands = '.
($this->_actionCommands ? 'True' : 'False'), __FILE__, __LINE__, PEAR_LOG_DEBUG);
if (!$this->_actionCommands && $state->getSyncStatus() == SERVER_SYNC_FINNISHED) {
// this packet did not contain any real actions, just status and map.
// This means, we're through! The session can be closed and
// the Anchors saved for the next Sync
$state = & $_SESSION['SyncML.state'];
Horde::logMessage('SyncML['. session_id() .']: sync' . session_id() . ' completed successfully!', __FILE__, __LINE__, PEAR_LOG_INFO);
$state->writeSyncSummary();
$log = $state->getLog();
@ -560,6 +616,9 @@ class Horde_SyncML_SyncMLBody extends Horde_SyncML_ContentHandler {
foreach($log as $k => $v) {
$s .= " $k=$v";
}
if (strlen(trim($s)) == 0) {
$s = ' Both parties were already in sync';
}
Horde::logMessage('SyncML['. session_id() .']: summary:' . $s, __FILE__, __LINE__, PEAR_LOG_INFO);
# Horde::logMessage('SyncML['. session_id() .']: destroying sync session '.session_id(), __FILE__, __LINE__, PEAR_LOG_INFO);
# // session can be closed here!
@ -571,7 +630,6 @@ class Horde_SyncML_SyncMLBody extends Horde_SyncML_ContentHandler {
// this packet did not contain any real actions, just status and map.
// This means, we're through! The session can be closed and
// the Anchors saved for the next Sync
$state = & $_SESSION['SyncML.state'];
Horde::logMessage('SyncML['. session_id() .']: sync' . session_id() . ' completed successfully!', __FILE__, __LINE__, PEAR_LOG_INFO);
$state->writeSyncSummary();
$log = $state->getLog();
@ -579,6 +637,9 @@ class Horde_SyncML_SyncMLBody extends Horde_SyncML_ContentHandler {
foreach($log as $k => $v) {
$s .= " $k=$v";
}
if (strlen(trim($s)) == 0) {
$s = ' Both parties were already in sync';
}
Horde::logMessage('SyncML['. session_id() .']: summary:' . $s, __FILE__, __LINE__, PEAR_LOG_INFO);
Horde::logMessage('SyncML['. session_id() .']: destroying sync session '.session_id(), __FILE__, __LINE__, PEAR_LOG_INFO);
@ -586,75 +647,53 @@ class Horde_SyncML_SyncMLBody extends Horde_SyncML_ContentHandler {
session_unset();
session_destroy();
}
if($state->getSyncStatus() == CLIENT_SYNC_FINNISHED) {
$state->setSyncStatus(CLIENT_SYNC_ACKNOWLEDGED);
Horde::logMessage('SyncML['. session_id() .']: syncStatus(client sync acknowledged) '.$state->getSyncStatus(), __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
break;
case 3:
// </[Command]></SyncBody></SyncML>
$state = & $_SESSION['SyncML.state'];
// this should be moved to case 2:
if($element == 'Final')
{
// make sure that we request devinfo, if we not have them already
/* if(!$state->getClientDeviceInfo())
{
$attrs = array();
$this->_output->startElement($state->getURI(), 'Get', $attrs);
$this->_output->startElement($state->getURI(), 'CmdID', $attrs);
$this->_output->characters($this->_currentCmdID);
$this->_currentCmdID++;
$this->_output->endElement($state->getURI(), 'CmdID');
$this->_output->startElement($state->getURI(), 'Meta', $attrs);
$this->_output->startElement($state->getURIMeta(), 'Type', $attrs);
if(is_a($this->_output, 'XML_WBXML_Encoder'))
$this->_output->characters('application/vnd.syncml-devinf+wbxml');
else
$this->_output->characters('application/vnd.syncml-devinf+xml');
$this->_output->endElement($state->getURIMeta(), 'Type');
$this->_output->endElement($state->getURI(), 'Meta');
$this->_output->startElement($state->getURI(), 'Item', $attrs);
$this->_output->startElement($state->getURI(), 'Target', $attrs);
$this->_output->startElement($state->getURI(), 'LocURI', $attrs);
$this->_output->characters(($state->getVersion() == 0) ? './devinf10' : './devinf11');
$this->_output->endElement($state->getURI(), 'LocURI');
$this->_output->endElement($state->getURI(), 'Target');
$this->_output->endElement($state->getURI(), 'Item');
$this->_output->endElement($state->getURI(), 'Get');
} */
}
$this->_currentCommand->endElement($uri, $element);
switch($element) {
case 'Final':
if($state->getSyncStatus() == CLIENT_SYNC_STARTED) {
$state->setSyncStatus(CLIENT_SYNC_FINNISHED);
Horde::logMessage('SyncML['. session_id() .']: syncStatus(client sync finnished) ' . $state->getSyncStatus(), __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
case 'Final':
$this->_actionCommands = false;
$deviceInfo = $state->getClientDeviceInfo();
if($state->getSyncStatus() == SERVER_SYNC_FINNISHED) {
$state->setSyncStatus(SERVER_SYNC_ACKNOWLEDGED);
Horde::logMessage('SyncML['. session_id() .']: syncStatus(server sync acknowledged) ' . $state->getSyncStatus(), __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
if ($state->getSyncStatus() == CLIENT_SYNC_STARTED) {
if (strtolower($deviceInfo['manufacturer']) == 'funambol'
&& isset($deviceInfo['softwareVersion'])) {
$swversion = $deviceInfo['softwareVersion'];
if ($swversion < 1.0) {
// e.g. Mozilla plugin uses this range
$swversion = $swversion * 10;
}
if (3.0 < $swversion && $swversion < 7.0) {
// We wait for a ALERT_NEXT_MESSAGE from Funambol old clients
Horde::logMessage('SyncML['. session_id()
. "]: Special treatment for Funambol version $swversion activated",
__FILE__, __LINE__, PEAR_LOG_DEBUG);
$state->setSyncStatus(CLIENT_SYNC_FINNISHED);
} else {
$state->setSyncStatus(CLIENT_SYNC_ACKNOWLEDGED);
}
} else {
$state->setSyncStatus(CLIENT_SYNC_ACKNOWLEDGED);
}
}
$this->_clientSentFinal = true;
#Horde::logMessage('SyncML['. session_id() .']: Sync _syncTag = '. $state->getSyncStatus(), __FILE__, __LINE__, PEAR_LOG_INFO);
if ($state->getSyncStatus() == SERVER_SYNC_FINNISHED) {
$state->setSyncStatus(SERVER_SYNC_ACKNOWLEDGED);
}
break;
$this->_clientSentFinal = true;
Horde::logMessage('SyncML['. session_id() .']: syncStatus(server sync acknowledged) '
. $state->getSyncStatus(), __FILE__, __LINE__, PEAR_LOG_DEBUG);
break;
default:
$this->_currentCmdID = $this->_currentCommand->output($this->_currentCmdID, $this->_output);
break;
}
default:
$this->_currentCmdID = $this->_currentCommand->output($this->_currentCmdID, $this->_output);
break;
}
unset($this->_currentCommand);
break;

View File

@ -1,80 +1,166 @@
<?php
include_once 'Horde/SyncML/State.php';
/**
* The Horde_SyncML_Command class provides a super class fo SyncBody commands.
* eGroupWare - SyncML based on Horde 3
*
* $Horde: framework/SyncML/SyncML/Command.php,v 1.4 2004/07/03 15:26:46 chuck Exp $
* The SyncML_Command class provides a base class for handling all <SyncBody>
* commands.
*
* Copyright 2003-2004 Anthony Mills <amills@pyramid6.com>
* A SyncML command is a protocol primitive. Each SyncML command specifies to
* a recipient an individual operation that is to be performed.
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
* The SyncML_Command objects are hooked into the XML parser of the
* SyncML_ContentHandler class and are reponsible for parsing a single command
* inside the SyncBody section of a SyncML message. All actions that must be
* executed for a single SyncML command are handled by these objects, by means
* of the handleCommand() method.
*
* @author Anthony Mills <amills@pyramid6.com>
* @version $Revision$
* @since Horde 3.0
* @package Horde_SyncML
*
* Using the PEAR Log class (which need to be installed!)
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage horde
* @author Anthony Mills <amills@pyramid6.com>
* @author Jan Schneider <jan@horde.org>
* @author Joerg Lehrke <jlehrke@noc.de>
* @copyright (c) The Horde Project (http://www.horde.org/)
* @version $Id$
*/
include_once 'Horde/SyncML/State_egw.php';
class Horde_SyncML_Command {
/**
* Name of the command, like 'Put'.
*
* Must be overwritten by a sub class.
*
* @var string
*/
var $_cmdName;
/**
* The command ID (<CmdID>).
*
* @var integer
*/
var $_cmdID;
var $_xmlStack;
/**
* Stack for holding the XML elements during creation of the object from
* the XML event flow.
*
* @var array
*/
var $_stack = array();
var $_chars;
/**
* Buffer for the parsed character data.
*
* @var string
*/
var $_chars = '';
function &factory($command, $params = null)
/**
* Start element handler for the XML parser, delegated from
* SyncML_ContentHandler::startElement().
*
* @param string $uri The namespace URI of the element.
* @param string $element The element tag name.
* @param array $attrs A hash with the element's attributes.
*/
function startElement($uri, $element, $attrs)
{
include_once 'Horde/SyncML/Command/' . $command . '.php';
$class = 'Horde_SyncML_Command_' . $command;
if (class_exists($class)) {
return $cmd = &new $class($params);
} else {
Horde::logMessage('SyncML: Class definition of ' . $class . ' not found.', __FILE__, __LINE__, PEAR_LOG_ERR);
require_once 'PEAR.php';
return PEAR::raiseError('Class definition of ' . $class . ' not found.');
}
}
function output($currentCmdID, $output)
{
}
function startElement($uri, $localName, $attrs)
{
$this->_xmlStack++;
$this->_stack[] = $element;
}
/**
* End element handler for the XML parser, delegated from
* SyncML_ContentHandler::endElement().
*
* @param string $uri The namespace URI of the element.
* @param string $element The element tag name.
*/
function endElement($uri, $element)
{
switch ($this->_xmlStack) {
case 2:
if ($element == 'CmdID') {
$this->_cmdID = intval(trim($this->_chars));
}
break;
if (count($this->_stack) == 2 &&
$element == 'CmdID') {
$this->_cmdID = intval(trim($this->_chars));
}
if (isset($this->_chars)) {
unset($this->_chars);
if (strlen($this->_chars)) {
$this->_chars = '';
}
$this->_xmlStack--;
array_pop($this->_stack);
}
/**
* Character data handler for the XML parser, delegated from
* SyncML_ContentHandler::characters().
*
* @param string $str The data string.
*/
function characters($str)
{
$tempValue = trim($str);
if(empty($tempValue)) return;
if (isset($this->_chars)) {
$this->_chars = $this->_chars . $str;
$this->_chars .= $str;
} else {
$this->_chars = $str;
}
}
/**
* Returns the command name this instance is reponsible for.
*
* @return string The command name this object is handling.
*/
function getCommandName()
{
return $this->_cmdName;
}
/**
* This method is supposed to implement the actual business logic of the
* command once the XML parsing is complete.
*
* @abstract
*/
function output($currentCmdID, &$output)
{
}
/**
* Attempts to return a concrete Horde_SyncML_Command instance based on
* $command.
*
* @param string $command The type of the concrete
* SyncML_Comment subclass to
* return.
* @param $params Optional Parameter.
*
* @return SyncML_Command The newly created concrete SyncML_Command
* instance, or false on error.
*/
function &factory($command, $params = null)
{
$command = basename($command);
$class = 'Horde_SyncML_Command_' . $command;
if (!class_exists($class)) {
include_once 'Horde/SyncML/Command/' . $command . '.php';
}
if (class_exists($class)) {
$cmd = new $class($params);
} else {
$msg = 'SyncML: Class definition of ' . $class . ' not found.';
Horde::logMessage($msg, __FILE__, __LINE__, PEAR_LOG_ERR);
require_once 'PEAR.php';
$cmd = PEAR::raiseError($msg);
}
return $cmd;
}
}

View File

@ -1,56 +1,87 @@
<?php
include_once 'Horde/SyncML/State.php';
include_once 'Horde/SyncML/Command.php';
/**
* eGroupWare - SyncML based on Horde 3
*
* The Horde_SyncML_Alert class provides a SyncML implementation of
* the Alert command as defined in SyncML Representation Protocol,
* version 1.1 5.5.2.
*
* $Horde: framework/SyncML/SyncML/Command/Alert.php,v 1.18 2004/07/03 15:21:14 chuck Exp $
*
* Copyright 2003-2004 Anthony Mills <amills@pyramid6.com>
* Using the PEAR Log class (which need to be installed!)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Anthony Mills <amills@pyramid6.com>
* @version $Revision$
* @since Horde 3.0
* @package Horde_SyncML
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage horde
* @author Anthony Mills <amills@pyramid6.com>
* @author Joerg Lehrke <jlehrke@noc.de>
* @copyright (c) The Horde Project (http://www.horde.org/)
* @version $Id$
*/
include_once 'Horde/SyncML/State_egw.php';
include_once 'Horde/SyncML/Command.php';
class Horde_SyncML_Command_Alert extends Horde_SyncML_Command {
/**
* @var integer $_alert
* Name of the command.
*
* @var string
*/
var $_cmdName = 'Alert';
/**
* The alert type. Should be one of the ALERT_* constants.
*
* @var integer
*/
var $_alert;
/**
* @var string $_sourceURI
* Source database of the Alert command.
*
* @var string
*/
var $_sourceLocURI;
/**
* @var string $_targetURI
* Target database of the Alert command.
*
* @var string
*/
var $_targetLocURI;
/**
* @var string $_metaAnchorNext
* Optional parameter for the Target database.
*
* @var string
*/
var $_targetLocURIParameters;
/**
* The current time this synchronization happens, from the <Meta><Next>
* element.
*
* @var string
*/
var $_metaAnchorNext;
/**
* @var integer $_metaAnchorLast
* The last time when synchronization happened, from the <Meta><Last>
* element.
*
* @var integer
*/
var $_metaAnchorLast;
/**
* Use in xml tag.
* The filter expression the client provided
* (e.g. the time range for calendar synchronization)
*
* @var string
*/
var $_isInSource;
var $_filterExpression = '';
/**
* Creates a new instance of Alert.
@ -64,335 +95,403 @@ class Horde_SyncML_Command_Alert extends Horde_SyncML_Command {
function output($currentCmdID, &$output)
{
global $registry;
$attrs = array();
$state = &$_SESSION['SyncML.state'];
// Handle unauthorized first.
if (!$state->isAuthorized()) {
$status = &new Horde_SyncML_Command_Status(RESPONSE_INVALID_CREDENTIALS, 'Alert');
$status = new Horde_SyncML_Command_Status(RESPONSE_INVALID_CREDENTIALS, 'Alert');
$status->setCmdRef($this->_cmdID);
$currentCmdID = $status->output($currentCmdID, $output);
return $currentCmdID;
}
$type = $this->_targetLocURI;
$clientAnchorNext = $this->_metaAnchorNext;
if($this->_alert < ALERT_RESULT_ALERT) {
if ($this->_alert == ALERT_TWO_WAY ||
$this->_alert == ALERT_ONE_WAY_FROM_CLIENT ||
$this->_alert == ALERT_ONE_WAY_FROM_SERVER) {
// Check if we have information about previous sync.
$info = $state->getSyncSummary($this->_targetLocURI);
if (is_a($info, 'DataTreeObject')) {
$x = $info->get('ClientAnchor');
$clientlast = $x[$type];
$x = $info->get('ServerAnchor');
$serverAnchorLast = $x[$type];
} elseif (is_array($info)) {
$clientlast = $info['ClientAnchor'];
$serverAnchorLast = $info['ServerAnchor'];
} else {
$clientlast = false;
$serverAnchorLast = 0;
}
$state->setServerAnchorLast($type, $serverAnchorLast);
$type = $this->_targetLocURI;
if ($clientlast !== false){
// Info about previous successful sync sessions found.
Horde::logMessage('SyncML: Previous sync found for target ' . $type
. '; client timestamp: ' . $clientlast,
__FILE__, __LINE__, PEAR_LOG_DEBUG);
// Store client's Next Anchor in State. After successful sync
// this is then written to persistence for negotiation of
// further syncs.
$state->setClientAnchorNext($type, $this->_metaAnchorNext);
// Check if anchor sent from client matches our own stored
// data.
if ($clientlast == $this->_metaAnchorLast) {
// Last sync anchors matche, TwoWaySync will do.
$anchormatch = true;
Horde::logMessage('SyncML: Anchor timestamps match, TwoWaySync possible. Syncing data since '
. date('Y-m-d H:i:s', $serverAnchorLast),
__FILE__, __LINE__, PEAR_LOG_DEBUG);
} else {
// Server and client have different anchors, enforce
// SlowSync/RefreshSync
Horde::logMessage('SyncML: Client requested sync with anchor timestamp '
. $this->_metaAnchorLast
. ' but server has recorded timestamp '
. $clientlast . '. Enforcing SlowSync',
__FILE__, __LINE__, PEAR_LOG_INFO);
$anchormatch = false;
$clientlast = 0;
}
} else {
// No info about previous sync, use SlowSync or RefreshSync.
Horde::logMessage('SyncML: No info about previous syncs found for device ' .
$state->getSourceURI() . ' and target ' . $type,
__FILE__, __LINE__, PEAR_LOG_DEBUG);
$clientlast = 0;
$serverAnchorLast = 0;
$anchormatch = false;
}
} else {
// SlowSync requested, no anchor check required.
$anchormatch = true;
}
$info = $state->getSyncSummary($this->_targetLocURI);
#Horde::logMessage("SyncML: Anchor match, TwoWaySync sinceee " . $clientlast, __FILE__, __LINE__, PEAR_LOG_DEBUG);
if (is_a($info, 'DataTreeObject')) {
$x = $info->get('ClientAnchor');
$clientlast = $x[$type];
$x = $info->get('ServerAnchor');
$state->setServerAnchorLast($type, $x[$type]);
} elseif (is_array($info)) {
$clientlast = $info['ClientAnchor'];
$state->setServerAnchorLast($type, $info['ServerAnchor']);
} else {
$clientlast = false;
$state->setServerAnchorLast($type, 0);
}
Horde::logMessage('SyncML: checking anchor targetLocURI: clientlast: ' . $clientlast .' / '. $this->_metaAnchorLast, __FILE__, __LINE__, PEAR_LOG_DEBUG);
// Set Server Anchor for this sync to current time.
$state->setServerAnchorNext($type,time());
if ($clientlast !== false && $clientlast == $this->_metaAnchorLast) {
// Last Sync Anchor matches, TwoWaySync will do.
$code = RESPONSE_OK;
Horde::logMessage("SyncML: Anchor match, TwoWaySync since " . $clientlast, __FILE__, __LINE__, PEAR_LOG_DEBUG);
} else {
Horde::logMessage("SyncML: Anchor mismatch, enforcing SlowSync clientlast $clientlast serverlast ".$this->_metaAnchorLast, __FILE__, __LINE__, PEAR_LOG_DEBUG);
// Mismatch, enforce slow sync. 508=RESPONSE_REFRESH_REQUIRED 201=ALERT_SLOW_SYNC
$this->_alert = 201;
$code = 508;
// create new synctype
$sync = &Horde_SyncML_Sync::factory($this->_alert);
$sync->_targetLocURI = $this->_targetLocURI;
$sync->_sourceLocURI = $this->_sourceLocURI;
if(isset($this->_targetLocURIParameters))
$sync->_targetLocURIParameters = $this->_targetLocURIParameters;
$state->setSync($this->_targetLocURI, $sync);
// PH : no longer delete entire content_map entries for client before SlowSync,
// use content_map to verify if content is mapped correctly
//$state->removeAllUID($this->_targetLocURI);
}
// Determine sync type and status response code.
Horde::logMessage("SyncML: Alert " . $this->_alert, __FILE__, __LINE__, PEAR_LOG_DEBUG);
switch ($this->_alert) {
case ALERT_NEXT_MESSAGE:
$state->setAlert222Received(true);
case ALERT_RESULT_ALERT:
case ALERT_NO_END_OF_DATA:
// Nothing to do on our side
$status = new Horde_SyncML_Command_Status(RESPONSE_OK, 'Alert');
$status->setCmdRef($this->_cmdID);
if ($this->_sourceLocURI != null) {
$status->setSourceRef($this->_sourceLocURI);
}
if ($this->_targetLocURI != null) {
$status->setTargetRef((isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI));
}
if ($this->_alert == ALERT_NEXT_MESSAGE) {
if ($this->_sourceLocURI != null) {
$status->setItemSourceLocURI($this->_sourceLocURI);
}
if ($this->_targetLocURI != null) {
$status->setItemTargetLocURI(isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI);
}
}
$currentCmdID = $status->output($currentCmdID, $output);
return $currentCmdID;
case ALERT_TWO_WAY:
if ($anchormatch) {
$synctype = ALERT_TWO_WAY;
$response = RESPONSE_OK;
} else {
$synctype = ALERT_SLOW_SYNC;
$response = RESPONSE_REFRESH_REQUIRED;
}
break;
$status = &new Horde_SyncML_Command_Status($code, 'Alert');
$status->setCmdRef($this->_cmdID);
if ($this->_sourceLocURI != null) {
$status->setSourceRef($this->_sourceLocURI);
}
if ($this->_targetLocURI != null) {
$status->setTargetRef((isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI));
}
case ALERT_SLOW_SYNC:
$synctype = ALERT_SLOW_SYNC;
$response = $anchormatch ? RESPONSE_OK : RESPONSE_REFRESH_REQUIRED;
break;
// Mirror Next Anchor from client back to client.
if (isset($this->_metaAnchorNext)) {
$status->setItemDataAnchorNext($this->_metaAnchorNext);
}
case ALERT_ONE_WAY_FROM_CLIENT:
if ($anchormatch) {
$synctype = ALERT_ONE_WAY_FROM_CLIENT;
$response = RESPONSE_OK;
} else {
$synctype = ALERT_REFRESH_FROM_CLIENT;
$response = RESPONSE_REFRESH_REQUIRED;
}
break;
// Mirror Last Anchor from client back to client.
if (isset($this->_metaAnchorLast)) {
$status->setItemDataAnchorLast($this->_metaAnchorLast);
}
case ALERT_REFRESH_FROM_CLIENT:
$synctype = ALERT_REFRESH_FROM_CLIENT;
$response = $anchormatch ? RESPONSE_OK : RESPONSE_REFRESH_REQUIRED;
$currentCmdID = $status->output($currentCmdID, $output);
// We will erase the current server content,
// then we can add the client's contents.
if ($state->isAuthorized()) {
$output->startElement($state->getURI(), 'Alert', $attrs);
$hordeType = $state->getHordeType($this->_targetLocURI);
$output->startElement($state->getURI(), 'CmdID', $attrs);
$chars = $currentCmdID;
$output->characters($chars);
$output->endElement($state->getURI(), 'CmdID');
$state->setTargetURI($this->_targetLocURI);
$deletes = $state->getClientItems();
if (is_array($deletes)) {
foreach ($deletes as $delete) {
$registry->call($hordeType . '/delete', array($delete));
}
Horde::logMessage("SyncML: RefreshFromClient " . count($deletes) . " entries deleted for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
$anchormatch = false;
break;
$output->startElement($state->getURI(), 'Data', $attrs);
$chars = $this->_alert;
$output->characters($chars);
$output->endElement($state->getURI(), 'Data');
case ALERT_ONE_WAY_FROM_SERVER:
if ($anchormatch) {
$synctype = ALERT_ONE_WAY_FROM_SERVER;
$response = RESPONSE_OK;
} else {
$synctype = ALERT_REFRESH_FROM_SERVER;
$response = RESPONSE_REFRESH_REQUIRED;
}
break;
$output->startElement($state->getURI(), 'Item', $attrs);
case ALERT_REFRESH_FROM_SERVER:
$synctype = ALERT_REFRESH_FROM_SERVER;
$response = $anchormatch ? RESPONSE_OK : RESPONSE_REFRESH_REQUIRED;
break;
if ($this->_sourceLocURI != null) {
$output->startElement($state->getURI(), 'Target', $attrs);
$output->startElement($state->getURI(), 'LocURI', $attrs);
$chars = $this->_sourceLocURI;
$output->characters($chars);
$output->endElement($state->getURI(), 'LocURI');
$output->endElement($state->getURI(), 'Target');
}
if ($this->_targetLocURI != null) {
$output->startElement($state->getURI(), 'Source', $attrs);
$output->startElement($state->getURI(), 'LocURI', $attrs);
$chars = (isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI);
$output->characters($chars);
$output->endElement($state->getURI(), 'LocURI');
$output->endElement($state->getURI(), 'Source');
}
case ALERT_RESUME:
// @TODO: Suspend and Resume is not supported yet
$synctype = ALERT_SLOW_SYNC;
$response = RESPONSE_REFRESH_REQUIRED;
break;
$output->startElement($state->getURI(), 'Meta', $attrs);
default:
// We can't handle this one
Horde::logMessage('SyncML: Unknown sync type ' . $this->_alert,
__FILE__, __LINE__, PEAR_LOG_ERR);
$status = new Horde_SyncML_Command_Status(RESPONSE_BAD_REQUEST, 'Alert');
$status->setCmdRef($this->_cmdID);
if ($this->_sourceLocURI != null) {
$status->setSourceRef($this->_sourceLocURI);
}
if ($this->_targetLocURI != null) {
$status->setTargetRef((isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI));
}
$currentCmdID = $status->output($currentCmdID, $output);
return $currentCmdID;
}
$output->startElement($state->getURIMeta(), 'Anchor', $attrs);
// Store client's Next Anchor in State and
// set server's Next Anchor. After successful sync
// this is then written to persistence for negotiation of
// further syncs.
$state->setClientAnchorNext($type, $this->_metaAnchorNext);
$serverAnchorNext = time();
$state->setServerAnchorNext($type, $serverAnchorNext);
$output->startElement($state->getURIMeta(), 'Last', $attrs);
$chars = $state->getServerAnchorLast($type);
$output->characters($chars);
$output->endElement($state->getURIMeta(), 'Last');
// Now set interval to retrieve server changes from, defined by
// ServerAnchor [Last,Next]
if ($synctype != ALERT_TWO_WAY &&
$synctype != ALERT_ONE_WAY_FROM_CLIENT &&
$synctype != ALERT_ONE_WAY_FROM_SERVER) {
$serverAnchorLast = 0;
#if (!$anchormatch) {
// Erase existing map:
$state->removeAllUID($this->_targetLocURI);
#}
}
// Now create the actual SyncML_Sync object, if it doesn't exist yet.
$sync = &$state->getSync($this->_targetLocURI);
if (!$sync) {
Horde::logMessage('SyncML: Creating SyncML_Sync object for target '
. $this->_targetLocURI . '; sync type ' . $synctype,
__FILE__, __LINE__, PEAR_LOG_DEBUG);
$sync = &Horde_SyncML_Sync::factory($synctype);
$state->clearConflictItems($this->_targetLocURI);
}
$sync->setTargetLocURI($this->_targetLocURI);
$sync->setSourceLocURI($this->_sourceLocURI);
$sync->setLocName($state->getLocName()); // We need it for conflict handling
$sync->setsyncType($synctype);
$sync->setFilterExpression($this->_filterExpression);
$state->setSync($this->_targetLocURI, $sync);
$output->startElement($state->getURIMeta(), 'Next', $attrs);
$chars = $state->getServerAnchorNext($type);
$output->characters($chars);
$output->endElement($state->getURIMeta(), 'Next');
$status = new Horde_SyncML_Command_Status($response, 'Alert');
$status->setCmdRef($this->_cmdID);
if ($this->_sourceLocURI != null) {
$status->setSourceRef($this->_sourceLocURI);
}
if ($this->_targetLocURI != null) {
$status->setTargetRef((isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI));
}
$output->endElement($state->getURIMeta(), 'Anchor');
$output->endElement($state->getURI(), 'Meta');
$output->endElement($state->getURI(), 'Item');
$output->endElement($state->getURI(), 'Alert');
// Mirror Next Anchor from client back to client.
if (isset($this->_metaAnchorNext)) {
$status->setItemDataAnchorNext($this->_metaAnchorNext);
}
// still needed? lars
$state->_sendFinal = true;
$currentCmdID++;
// Mirror Last Anchor from client back to client.
if (isset($this->_metaAnchorLast)) {
$status->setItemDataAnchorLast($this->_metaAnchorLast);
}
if($state->_devinfoRequested == false &&
$this->_sourceLocURI != null &&
is_a($state->getPreferedContentTypeClient($this->_sourceLocURI), 'PEAR_Error')) {
$output->startElement($state->getURI(), 'Get', $attrs);
$currentCmdID = $status->output($currentCmdID, $output);
$output->startElement($state->getURI(), 'CmdID', $attrs);
$output->characters($currentCmdID);
$currentCmdID++;
$output->endElement($state->getURI(), 'CmdID');
$output->startElement($state->getURI(), 'Meta', $attrs);
$output->startElement($state->getURIMeta(), 'Type', $attrs);
if(is_a($output, 'XML_WBXML_Encoder')) {
$output->characters('application/vnd.syncml-devinf+wbxml');
} else {
$output->characters('application/vnd.syncml-devinf+xml');
}
$output->endElement($state->getURIMeta(), 'Type');
$output->endElement($state->getURI(), 'Meta');
$output->startElement($state->getURI(), 'Item', $attrs);
$output->startElement($state->getURI(), 'Target', $attrs);
$output->startElement($state->getURI(), 'LocURI', $attrs);
$output->characters(($state->getVersion() == 0) ? './devinf10' : './devinf11');
$output->endElement($state->getURI(), 'LocURI');
$output->endElement($state->getURI(), 'Target');
$output->endElement($state->getURI(), 'Item');
$output->endElement($state->getURI(), 'Get');
$state->_devinfoRequested = true;
}
}
$output->startElement($state->getURI(), 'Alert', $attrs);
} elseif ($this->_alert == ALERT_NEXT_MESSAGE) {
$status = &new Horde_SyncML_Command_Status(RESPONSE_OK, 'Alert');
$status->setCmdRef($this->_cmdID);
if ($this->_targetLocURI != null) {
$status->setTargetRef((isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI));
}
if ($this->_sourceLocURI != null) {
$status->setSourceRef($this->_sourceLocURI);
}
$status->setItemSourceLocURI($this->_sourceLocURI);
$status->setItemTargetLocURI(isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI);
$currentCmdID = $status->output($currentCmdID, $output);
$output->startElement($state->getURI(), 'CmdID', $attrs);
$chars = $currentCmdID;
$output->characters($chars);
$output->endElement($state->getURI(), 'CmdID');
$state->setAlert222Received(true);
$output->startElement($state->getURI(), 'Data', $attrs);
$chars = $synctype;
$output->characters($chars);
$output->endElement($state->getURI(), 'Data');
} else {
$status = &new Horde_SyncML_Command_Status(RESPONSE_OK, 'Alert');
$status->setCmdRef($this->_cmdID);
if ($this->_sourceLocURI != null) {
$status->setSourceRef($this->_sourceLocURI);
}
if ($this->_targetLocURI != null) {
$status->setTargetRef((isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI));
}
$output->startElement($state->getURI(), 'Item', $attrs);
$currentCmdID = $status->output($currentCmdID, $output);
}
if ($this->_sourceLocURI != null) {
$output->startElement($state->getURI(), 'Target', $attrs);
$output->startElement($state->getURI(), 'LocURI', $attrs);
$chars = $this->_sourceLocURI;
$output->characters($chars);
$output->endElement($state->getURI(), 'LocURI');
$output->endElement($state->getURI(), 'Target');
}
if ($this->_targetLocURI != null) {
$output->startElement($state->getURI(), 'Source', $attrs);
$output->startElement($state->getURI(), 'LocURI', $attrs);
$chars = (isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI);
$output->characters($chars);
$output->endElement($state->getURI(), 'LocURI');
$output->endElement($state->getURI(), 'Source');
}
$output->startElement($state->getURI(), 'Meta', $attrs);
$output->startElement($state->getURIMeta(), 'Anchor', $attrs);
$output->startElement($state->getURIMeta(), 'Last', $attrs);
$chars = $state->getServerAnchorLast($type);
$output->characters($chars);
$output->endElement($state->getURIMeta(), 'Last');
$output->startElement($state->getURIMeta(), 'Next', $attrs);
$chars = $state->getServerAnchorNext($type);
$output->characters($chars);
$output->endElement($state->getURIMeta(), 'Next');
$output->endElement($state->getURIMeta(), 'Anchor');
$output->endElement($state->getURI(), 'Meta');
$output->endElement($state->getURI(), 'Item');
$output->endElement($state->getURI(), 'Alert');
// Final packet of this message
$state->_sendFinal = true;
$currentCmdID++;
if ($state->_devinfoRequested == false &&
$this->_sourceLocURI != null &&
is_a($state->getPreferedContentTypeClient($this->_sourceLocURI), 'PEAR_Error')) {
Horde::logMessage("SyncML: PreferedContentTypeClient missing, sending <Get>", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$output->startElement($state->getURI(), 'Get', $attrs);
$output->startElement($state->getURI(), 'CmdID', $attrs);
$output->characters($currentCmdID);
$currentCmdID++;
$output->endElement($state->getURI(), 'CmdID');
$output->startElement($state->getURI(), 'Meta', $attrs);
$output->startElement($state->getURIMeta(), 'Type', $attrs);
if (is_a($output, 'XML_WBXML_Encoder')) {
$output->characters('application/vnd.syncml-devinf+wbxml');
} else {
$output->characters('application/vnd.syncml-devinf+xml');
}
$output->endElement($state->getURIMeta(), 'Type');
$output->endElement($state->getURI(), 'Meta');
$output->startElement($state->getURI(), 'Item', $attrs);
$output->startElement($state->getURI(), 'Target', $attrs);
$output->startElement($state->getURI(), 'LocURI', $attrs);
if ($state->getVersion() == 2) {
$output->characters('./devinf12');
} elseif ($state->getVersion() == 1) {
$output->characters('./devinf11');
} else {
$output->characters('./devinf10');
}
$output->endElement($state->getURI(), 'LocURI');
$output->endElement($state->getURI(), 'Target');
$output->endElement($state->getURI(), 'Item');
$output->endElement($state->getURI(), 'Get');
$state->_devinfoRequested = true;
}
return $currentCmdID;
}
/**
* Setter for property sourceURI.
* End element handler for the XML parser, delegated from
* SyncML_ContentHandler::endElement().
*
* @param string $sourceURI New value of property sourceURI.
* @param string $uri The namespace URI of the element.
* @param string $element The element tag name.
*/
function setSourceLocURI($sourceURI)
function endElement($uri, $element)
{
$this->_sourceLocURI = $sourceURI;
}
function getTargetLocURI()
{
return $this->_targetURI;
}
/**
* Setter for property targetURI.
*
* @param string $targetURI New value of property targetURI.
*/
// is this function still used???
function setTargetURI($targetURI)
{
$this->_targetLocURI = $targetURI;
}
/**
* Setter for property targetURI.
*
* @param string $targetURI New value of property targetURI.
*/
function setTargetLocURI($targetURI)
{
$this->_targetLocURI = $targetURI;
}
function startElement($uri, $element, $attrs)
{
parent::startElement($uri, $element, $attrs);
switch ($this->_xmlStack) {
case 3:
if ($element == 'Target') {
$this->_isInSource = false;
} else {
$this->_isInSource = true;
switch (count($this->_stack)) {
case 2:
if ($element == 'Data') {
$this->_alert = intval(trim($this->_chars));
}
break;
case 4:
if ($element == 'LocURI') {
switch ($this->_stack[2]) {
case 'Source':
$this->_sourceLocURI = trim($this->_chars);
break;
case 'Target':
$targetLocURIData = explode('?/',trim($this->_chars));
$this->_targetLocURI = $targetLocURIData[0];
if (isset($targetLocURIData[1])) {
$this->_targetLocURIParameters = $targetLocURIData[1];
}
break;
}
}
break;
case 5:
switch ($element) {
case 'Next':
$this->_metaAnchorNext = trim($this->_chars);
break;
case 'Last':
$this->_metaAnchorLast = trim($this->_chars);
break;
}
break;
case 7:
if ($element == 'Data'
&& $this->_stack[2] == 'Target'
&& $this->_stack[3] == 'Filter'
&& $this->_stack[4] == 'Record'
&& $this->_stack[5] == 'Item') {
$this->_filterExpression = trim($this->_chars);
}
break;
}
}
function endElement($uri, $element)
{
switch ($this->_xmlStack) {
case 1:
$state = & $_SESSION['SyncML.state'];
$sync = $state->getSync($this->_targetLocURI);
if (!$sync && $this->_alert < ALERT_RESULT_ALERT) {
Horde::logMessage('SyncML: create new sync for ' . $this->_targetLocURI . ' ' . $this->_alert, __FILE__, __LINE__, PEAR_LOG_DEBUG);
$sync = &Horde_SyncML_Sync::factory($this->_alert);
$sync->_targetLocURI = $this->_targetLocURI;
$sync->_sourceLocURI = $this->_sourceLocURI;
if(isset($this->_targetLocURIParameters)) {
$sync->_targetLocURIParameters = $this->_targetLocURIParameters;
}
$state->setSync($this->_targetLocURI, $sync);
if($this->_alert == ALERT_SLOW_SYNC || $this->_alert == ALERT_REFRESH_FROM_SERVER) {
$state->removeAllUID($this->_targetLocURI);
}
}
break;
case 2:
if ($element == 'Data') {
$this->_alert = intval(trim($this->_chars));
}
break;
case 4:
if ($element == 'LocURI') {
if ($this->_isInSource) {
$this->_sourceLocURI = trim($this->_chars);
} else {
$targetLocURIData = explode('?/',trim($this->_chars));
$this->_targetLocURI = $targetLocURIData[0];
if(isset($targetLocURIData[1])) {
$this->_targetLocURIParameters = $targetLocURIData[1];
}
}
}
break;
case 5:
if ($element == 'Next') {
$this->_metaAnchorNext = trim($this->_chars);
} else if ($element == 'Last') {
$this->_metaAnchorLast = trim($this->_chars);
}
break;
}
parent::endElement($uri, $element);
}
function getAlert()
{
return $this->_alert;
}
function setAlert($alert)
{
$this->_alert = $alert;
parent::endElement($uri, $element);
}
}

View File

@ -1,24 +1,31 @@
<?php
include_once 'Horde/SyncML/Command.php';
/**
* eGroupWare - SyncML based on Horde 3
*
* The Horde_SyncML_Command_Final class.
*
* $Horde: framework/SyncML/SyncML/Command/Final.php,v 1.10 2004/05/26 17:41:30 chuck Exp $
*
* Copyright 2003-2004 Anthony Mills <amills@pyramid6.com>
* Using the PEAR Log class (which need to be installed!)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Anthony Mills <amills@pyramid6.com>
* @version $Revision$
* @since Horde 3.0
* @package Horde_SyncML
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage horde
* @author Anthony Mills <amills@pyramid6.com>
* @copyright (c) The Horde Project (http://www.horde.org/)
* @version $Id$
*/
include_once 'Horde/SyncML/Command.php';
class Horde_SyncML_Command_Final extends Horde_SyncML_Command {
/**
* Name of the command.
*
* @var string
*/
var $_cmdName = 'Final';
function output($currentCmdID, &$output)
{
$state = $_SESSION['SyncML.state'];

View File

@ -1,34 +1,39 @@
<?php
/**
* eGroupWare - SyncML based on Horde 3
*
*
* Using the PEAR Log class (which need to be installed!)
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage horde
* @author Anthony Mills <amills@pyramid6.com>
* @author Karsten Fourmont <fourmont@gmx.de>
* @author Joerg Lehrke <jlehrke@noc.de>
* @copyright (c) The Horde Project (http://www.horde.org/)
* @version $Id$
*/
include_once 'Horde/SyncML/State.php';
include_once 'Horde/SyncML/Command.php';
include_once 'Horde/SyncML/Command/Results.php';
/**
* The Horde_SyncML_Command_Get class.
*
* $Horde: framework/SyncML/SyncML/Command/Get.php,v 1.14 2004/07/02 19:24:44 chuck Exp $
*
* Copyright 2003-2004 Anthony Mills <amills@pyramid6.com>
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Anthony Mills <amills@pyramid6.com>
* @author Karsten Fourmont <fourmont@gmx.de>
* @version $Revision$
* @since Horde 3.0
* @package Horde_SyncML
*/
class Horde_SyncML_Command_Get extends Horde_SyncML_Command {
function output($currentCmdID, &$output)
{
$state = $_SESSION['SyncML.state'];
$ref = ($state->getVersion() == 0) ? './devinf10' : './devinf11';
if ($state->getVersion() == 2) {
$ref = './devinf12';
} elseif ($state->getVersion() == 1) {
$ref = './devinf11';
} else {
$ref = './devinf10';
}
$status = &new Horde_SyncML_Command_Status((($state->isAuthorized()) ? RESPONSE_OK : RESPONSE_INVALID_CREDENTIALS), 'Get');
$status = new Horde_SyncML_Command_Status((($state->isAuthorized()) ? RESPONSE_OK : RESPONSE_INVALID_CREDENTIALS), 'Get');
$status->setCmdRef($this->_cmdID);
$status->setTargetRef($ref);
$currentCmdID = $status->output($currentCmdID, $output);
@ -74,7 +79,13 @@ class Horde_SyncML_Command_Get extends Horde_SyncML_Command {
$output->startElement($state->getURIDevInf() , 'DevInf', $attrs);
$output->startElement($state->getURIDevInf() , 'VerDTD', $attrs);
$output->characters(($state->getVersion() == 0) ? '1.0' : '1.1');
if ($state->getVersion() == 2) {
$output->characters('1.2');
} elseif($state->getVersion() == 1) {
$output->characters('1.1');
} else {
$output->characters('1.0');
}
$output->endElement($state->getURIDevInf() , 'VerDTD', $attrs);
$output->startElement($state->getURIDevInf() , 'Man', $attrs);
$output->characters('www.egroupware.org');
@ -85,12 +96,24 @@ class Horde_SyncML_Command_Get extends Horde_SyncML_Command {
$output->startElement($state->getURIDevInf() , 'DevTyp', $attrs);
$output->characters('server');
$output->endElement($state->getURIDevInf() , 'DevTyp', $attrs);
$output->startElement($state->getURIDevInf() , 'UTC', $attrs);
$output->endElement($state->getURIDevInf() , 'UTC', $attrs);
$output->startElement($state->getURIDevInf() , 'SupportNumberOfChanges', $attrs);
$output->endElement($state->getURIDevInf() , 'SupportNumberOfChanges', $attrs);
$output->startElement($state->getURIDevInf() , 'SupportLargeObjs', $attrs);
$output->endElement($state->getURIDevInf() , 'SupportLargeObjs', $attrs);
$this->_writeDataStore('./notes', 'text/x-vnote', '1.1', $output,
array('text/plain' => '1.0'));
$this->_writeDataStore('./contacts', 'text/x-vcard', '2.1', $output);
$this->_writeDataStore('./tasks', 'text/x-vcalendar', '1.0', $output);
$this->_writeDataStore('./calendar', 'text/x-vcalendar', '1.0', $output);
$this->_writeDataStore('./caltasks', 'text/x-vcalendar', '1.0', $output);
$this->_writeDataStore('./contacts', 'text/vcard', '3.0', $output,
array('text/x-vcard' => '2.1'));
$this->_writeDataStore('./tasks', 'text/calendar', '2.0', $output,
array('text/x-vcalendar' => '1.0'));
$this->_writeDataStore('./calendar', 'text/calendar', '2.0', $output,
array('text/x-vcalendar' => '1.0'));
$this->_writeDataStore('./events', 'text/calendar', '2.0', $output,
array('text/x-vcalendar' => '1.0'));
$this->_writeDataStore('./caltasks', 'text/calendar', '2.0', $output,
array('text/x-vcalendar' => '1.0'));
$output->endElement($state->getURIDevInf() , 'DevInf', $attrs);
$output->endElement($state->getURI(), 'Data');
@ -111,7 +134,7 @@ class Horde_SyncML_Command_Get extends Horde_SyncML_Command {
* @param string $version: data for &lt;(R|T)x-Pref&gt;&lt;VerCT&gt;
* @param string &$output contenthandler that will received the output.
* @param array $additionaltypes: array of additional types for Tx and Rx;
* format array('text/vcard' => '2.0')
* format array('text/vcard' => '3.0')
*/
function _writeDataStore($sourceref, $mimetype, $version, &$output,
$additionaltypes = false)
@ -124,6 +147,9 @@ class Horde_SyncML_Command_Get extends Horde_SyncML_Command {
$output->startElement($state->getURIDevInf() , 'SourceRef', $attrs);
$output->characters($sourceref);
$output->endElement($state->getURIDevInf() , 'SourceRef', $attrs);
$output->startElement($state->getURIDevInf() , 'MaxGUIDSize', $attrs);
$output->characters(255);
$output->endElement($state->getURIDevInf() , 'MaxGUIDSize', $attrs);
$output->startElement($state->getURIDevInf() , 'Rx-Pref', $attrs);
$output->startElement($state->getURIDevInf() , 'CTType', $attrs);
@ -170,12 +196,13 @@ class Horde_SyncML_Command_Get extends Horde_SyncML_Command {
}
$output->startElement($state->getURIDevInf() , 'SyncCap', $attrs);
$output->startElement($state->getURIDevInf() , 'SyncType', $attrs);
$output->characters('1');
$output->endElement($state->getURIDevInf() , 'SyncType', $attrs);
$output->startElement($state->getURIDevInf() , 'SyncType', $attrs);
$output->characters('2');
$output->endElement($state->getURIDevInf() , 'SyncType', $attrs);
// We support all sync Types from 1-6: two way, slow, refresh|update
// from client|server
for ($i = 1; $i <= 6; ++$i) {
$output->startElement($state->getURIDevInf(), 'SyncType', $attrs);
$output->characters($i);
$output->endElement($state->getURIDevInf(), 'SyncType', $attrs);
}
$output->endElement($state->getURIDevInf() , 'SyncCap', $attrs);
$output->endElement($state->getURIDevInf() , 'DataStore', $attrs);
}

View File

@ -1,43 +1,60 @@
<?php
include_once 'Horde/SyncML/State.php';
include_once 'Horde/SyncML/Command.php';
/**
* eGroupWare - SyncML based on Horde 3
*
* The Horde_SyncML_Map class provides a SyncML implementation of
* the Map command as defined in SyncML Representation Protocol,
* version 1.0.1 5.5.8.
*
* $Horde: framework/SyncML/SyncML/Command/Map.php,v 1.1 2004/07/02 19:24:44 chuck Exp $
*
* Copyright 2004 Karsten Fourmont <fourmont@gmx.de>
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
* Using the PEAR Log class (which need to be installed!)
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage horde
* @author Karsten Fourmont <fourmont@gmx.de>
* @version $Revision$
* @since Horde 3.0
* @package Horde_SyncML
* @copyright (c) The Horde Project (http://www.horde.org/)
* @version $Id$
*/
include_once 'Horde/SyncML/State.php';
include_once 'Horde/SyncML/Command.php';
class Horde_SyncML_Command_Map extends Horde_SyncML_Command {
/**
* @var string $_sourceURI
* Name of the command.
*
* @var string
*/
var $_cmdName = 'Map';
/**
* Source database of the Map command.
*
* @var string
*/
var $_sourceLocURI;
/**
* @var string $_targetURI
* Target database of the Map command.
*
* @var string
*/
var $_targetLocURI;
/**
* Use in xml tag.
* Recipient map item specifier.
*
* @var string
*/
var $_isInSource;
var $_mapTarget;
/**
* Originator map item specifier.
*
* @var string
*/
var $_mapSource;
function output($currentCmdID, &$output)
@ -46,7 +63,7 @@ class Horde_SyncML_Command_Map extends Horde_SyncML_Command {
$state = $_SESSION['SyncML.state'];
$status = &new Horde_SyncML_Command_Status($state->isAuthorized() ? RESPONSE_OK : RESPONSE_INVALID_CREDENTIALS, 'Map');
$status = new Horde_SyncML_Command_Status($state->isAuthorized() ? RESPONSE_OK : RESPONSE_INVALID_CREDENTIALS, 'Map');
$status->setCmdRef($this->_cmdID);
if ($this->_sourceLocURI != null) {
$status->setSourceRef($this->_sourceLocURI);
@ -60,76 +77,25 @@ class Horde_SyncML_Command_Map extends Horde_SyncML_Command {
return $currentCmdID;
}
/**
* Setter for property sourceURI.
*
* @param string $sourceURI New value of property sourceURI.
*/
function setSourceLocURI($sourceURI)
{
$this->_sourceURI = $sourceURI;
}
function getTargetLocURI()
{
return $this->_targetURI;
}
/**
* Setter for property targetURI.
*
* @param string $targetURI New value of property targetURI.
*/
function setTargetURI($targetURI)
{
$this->_targetURI = $targetURI;
}
function startElement($uri, $element, $attrs)
{
parent::startElement($uri, $element, $attrs);
switch ($this->_xmlStack) {
case 2:
if ($element == 'Target') {
$this->_isInSource = false;
}
if ($element == 'Source') {
$this->_isInSource = true;
}
if ($element == 'MapItem') {
unset($this->_mapTarget);
unset($this->_mapSource);
}
break;
case 3:
if ($element == 'Target') {
$this->_isInSource = false;
}
if ($element == 'Source') {
$this->_isInSource = true;
}
break;
if (count($this->_stack) == 2 &&
$element == 'MapItem') {
unset($this->_mapTarget);
unset($this->_mapSource);
}
}
function endElement($uri, $element)
{
switch ($this->_xmlStack) {
case 1:
$state = $_SESSION['SyncML.state'];
$sync = $state->getSync($this->_targetLocURI);
if (!$sync) {
}
$_SESSION['SyncML.state'] = $state;
break;
$state = &$_SESSION['SyncML.state'];
switch (count($this->_stack)) {
case 2:
if ($element == 'MapItem') {
$state = $_SESSION['SyncML.state'];
$sync = $state->getSync($this->_targetLocURI);
if (!$state->isAuthorized()) {
Horde::logMessage('SyncML: Not Authorized in the middle of MapItem!', __FILE__, __LINE__, PEAR_LOG_ERR);
@ -149,26 +115,20 @@ class Horde_SyncML_Command_Map extends Horde_SyncML_Command {
case 3:
if ($element == 'LocURI') {
if ($this->_isInSource) {
if ($this->_stack[1] == 'Source') {
$this->_sourceLocURI = trim($this->_chars);
} else {
} elseif ($this->_stack[1] == 'Target') {
$targetLocURIData = explode('?/',trim($this->_chars));
$this->_targetLocURI = $targetLocURIData[0];
if(isset($targetLocURIData[1]))
{
$this->_targetLocURIParameters = $targetLocURIData[1];
}
$this->_targetLocURI = $targetLocURIData[0];
}
}
break;
case 4:
if ($element == 'LocURI') {
if ($this->_isInSource) {
if ($this->_stack[2] == 'Source') {
$this->_mapSource = trim($this->_chars);
} else {
} elseif ($this->_stack[2] == 'Target') {
$this->_mapTarget = trim($this->_chars);
}
}

View File

@ -1,23 +1,31 @@
<?php
/**
* eGroupWare - SyncML based on Horde 3
*
*
* Using the PEAR Log class (which need to be installed!)
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage horde
* @author Anthony Mills <amills@pyramid6.com>
* @author Joerg Lehrke <jlehrke@noc.de>
* @copyright (c) The Horde Project (http://www.horde.org/)
* @version $Id$
*/
include_once 'Horde/SyncML/State.php';
include_once 'Horde/SyncML/Command.php';
/**
* $Horde: framework/SyncML/SyncML/Command/Put.php,v 1.12 2004/07/02 19:24:44 chuck Exp $
*
* Copyright 2003-2004 Anthony Mills <amills@pyramid6.com>
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Anthony Mills <amills@pyramid6.com>
* @version $Revision$
* @since Horde 3.0
* @package Horde_SyncML
*/
class Horde_SyncML_Command_Put extends Horde_SyncML_Command {
/**
* Name of the command.
*
* @var string
*/
var $_cmdName = 'Put';
/**
* @var string $_manufacturer
*/
@ -36,6 +44,12 @@ class Horde_SyncML_Command_Put extends Horde_SyncML_Command {
var $_oem;
/**
* @var array $_deviceInfo
*/
var $_deviceInfo;
/**
* @var string $_softwareVersion
*/
@ -43,62 +57,63 @@ class Horde_SyncML_Command_Put extends Horde_SyncML_Command {
var $_softwareVersion;
function endElement($uri, $element) {
switch ($this->_xmlStack) {
switch (count($this->_stack)) {
case 5:
switch($element) {
switch ($element) {
case 'DataStore':
$this->_deviceInfo['dataStore'][$this->_sourceReference] = array(
$this->_deviceInfo['dataStore'][$this->_sourceReference] = array (
'maxGUIDSize' => $this->_maxGUIDSize,
'rxPreference' => $this->_rxPreference,
'txPreference' => $this->_txPreference,
'syncCapabilities' => $this->_syncCapabilities,
'properties' => $this->_properties,
);
break;
case 'DevID':
$this->_deviceInfo['deviceID'] = trim($this->_chars);
break;
case 'DevTyp':
$this->_deviceInfo['deviceType'] = trim($this->_chars);
break;
case 'FwV':
$this->_deviceInfo['firmwareVersion'] = trim($this->_chars);
break;
case 'HwV':
$this->_deviceInfo['hardwareVersion'] = trim($this->_chars);
break;
case 'Man':
$this->_deviceInfo['manufacturer'] = trim($this->_chars);
break;
case 'Mod':
$this->_deviceInfo['model'] = trim($this->_chars);
break;
case 'OEM':
$this->_deviceInfo['oem'] = trim($this->_chars);
break;
case 'SwV':
$this->_deviceInfo['softwareVersion'] = trim($this->_chars);
break;
case 'SupportLargeObjs':
$this->_deviceInfo['supportLargeObjs'] = true;
break;
case 'SupportNumberOfChanges':
$this->_deviceInfo['supportNumberOfChanges'] = true;
break;
case 'UTC':
$this->_deviceInfo['UTC'] = true;
break;
case 'VerDTD':
$this->_deviceInfo['DTDVersion'] = trim($this->_chars);
break;
@ -109,18 +124,18 @@ class Horde_SyncML_Command_Put extends Horde_SyncML_Command {
case 'MaxGUIDSize':
$this->_maxGUIDSize = trim($this->_chars);
break;
case 'Rx-Pref':
$this->_rxPreference = array(
'contentType' => $this->_contentType,
'contentVersion' => $this->_contentVersion,
);
break;
case 'SourceRef':
$this->_sourceReference = trim($this->_chars);
break;
case 'Tx-Pref':
$this->_txPreference = array(
'contentType' => $this->_contentType,
@ -129,7 +144,7 @@ class Horde_SyncML_Command_Put extends Horde_SyncML_Command {
break;
}
break;
case 7:
switch($element) {
case 'CTType':
@ -178,87 +193,165 @@ class Horde_SyncML_Command_Put extends Horde_SyncML_Command {
}
}
break;
case 'SyncType':
$this->_syncCapabilities[] = trim($this->_chars);
break;
case 'VerCT':
$this->_contentVersion = trim($this->_chars);
break;
case 'Property':
if (isset($this->_PropName)) {
$this->_properties[$this->_contentType][$this->_contentVersion][$this->_PropName] = array(
'Size' => $this->_PropSize,
'NoTruncate' => $this->_PropNoTruncate,
);
}
break;
}
break;
case 8:
switch($element) {
case 'PropName':
$this->_PropName = trim($this->_chars);
break;
case 'Size':
$this->_PropSize = trim($this->_chars);
break;
case 'NoTruncate':
$this->_PropNoTruncate = true;
break;
}
beak;
}
parent::endElement($uri, $element);
}
function finalizeDeviceInfo()
{
// get some more information about the device from out of band data
$ua = $_SERVER['HTTP_USER_AGENT'];
if (preg_match("/^\s*Funambol (.*) (\d+\.\d+\.\d+)\s*$/i", $ua, $matches))
{
if (!isset($this->_deviceInfo['manufacturer']))
$this->_deviceInfo['manufacturer'] = 'Funambol';
if (!isset($this->_deviceInfo['model']))
$this->_deviceInfo['model'] = 'Funambol ' . trim($matches[1]);
if (!isset($this->_deviceInfo['softwareVersion']))
$this->_deviceInfo['softwareVersion'] = $matches[2];
if (preg_match("/^\s*Funambol (.*) [^\d]*(\d+\.?\d*)[\.|\d]*\s*$/i", $ua, $matches)) {
// Funambol uses the hardware Manufacturer we don't care about
$this->_deviceInfo['manufacturer'] = 'Funambol';
$this->_deviceInfo['model'] = trim($matches[1]);
$this->_deviceInfo['softwareVersion'] = floatval($matches[2]);
if (!isset($this->_deviceInfo['deviceType']))
{
switch (strtolower(trim($matches[1])))
{
case 'outlook plug-in':
default:
$this->_deviceInfo['deviceType'] = 'workstation';
break;
if (!isset($this->_deviceInfo['deviceType'])) {
switch (strtolower(trim($matches[1]))) {
case 'pocket pc plug-in':
$this->_deviceInfo['deviceType'] = 'windowsmobile';
break;
case 'outlook plug-in':
default:
$this->_deviceInfo['deviceType'] = 'workstation';
break;
}
}
}
$devid = $this->_deviceInfo['deviceID'];
switch (strtolower($devid))
{
switch (strtolower($this->_deviceInfo['deviceID'])) {
case 'fmz-thunderbird-plugin':
if (empty($this->_devinceInfo['manufacturer']))
if (empty($this->_devinceInfo['manufacturer'])) {
$this->_deviceInfo['manufacturer'] = 'Funambol';
if (empty($this->_devinceInfo['model']))
}
if (empty($this->_devinceInfo['model'])) {
$this->_deviceInfo['model'] = 'ThunderBird';
if (empty($this->_devinceInfo['softwareVersion']))
$this->_deviceInfo['softwareVersion'] = '0.3';
}
if (empty($this->_devinceInfo['softwareVersion'])) {
$this->_deviceInfo['softwareVersion'] = '3.0';
}
break;
}
if (preg_match('/Funambol.*/i', $this->_deviceInfo['manufacturer'])) {
$this->_deviceInfo['supportLargeObjs'] = true;
}
switch (strtolower($this->_deviceInfo['manufacturer'])) {
case 'sonyericsson':
case 'sony ericsson':
if (strtolower($this->_deviceInfo['model']) == 'w890i') {
$this->_deviceInfo['supportLargeObjs'] = false;
}
break;
case 'synthesis ag':
foreach ($this->_deviceInfo['dataStore'] as &$ctype) {
$ctype['maxGUIDSize'] = 255;
}
break;
}
}
function output($currentCmdID, &$output ) {
$state = &$_SESSION['SyncML.state'];
$status = &new Horde_SyncML_Command_Status((($state->isAuthorized()) ? RESPONSE_OK : RESPONSE_INVALID_CREDENTIALS), 'Put');
$status = new Horde_SyncML_Command_Status((($state->isAuthorized()) ? RESPONSE_OK : RESPONSE_INVALID_CREDENTIALS), $this->_cmdName);
$status->setCmdRef($this->_cmdID);
$ref = ($state->getVersion() == 0) ? './devinf10' : './devinf11';
if ($state->getVersion() == 2) {
$ref = './devinf12';
} elseif ($state->getVersion() == 1) {
$ref = './devinf11';
} else {
$ref = './devinf10';
}
$status->setSourceRef($ref);
if($state->isAuthorized()) {
$this->finalizeDeviceInfo();
if(count((array)$this->_deviceInfo) > 0) {
$state->setClientDeviceInfo($this->_deviceInfo);
$devInfo = $state->getClientDeviceInfo();
if (is_array($devInfo['dataStore'])
&& $devInfo['softwareVersion'] == $this->_deviceInfo['softwareVersion']) {
// merge with existing information
$devInfo['dataStore'] =
array_merge($devInfo['dataStore'],
$this->_deviceInfo['dataStore']);
} else {
// new device
$devInfo = $this->_deviceInfo;
}
#Horde::logMessage("SyncML: Put DeviceInfo:\n" . print_r($this->_deviceInfo, true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
$state->setClientDeviceInfo($devInfo);
$state->writeClientDeviceInfo();
}
}
return $status->output($currentCmdID, $output);
}
function startElement($uri, $element, $attrs) {
#Horde::logMessage("SyncML: startElement[" . count($this->_stack) . "] $uri $element", __FILE__, __LINE__, PEAR_LOG_DEBUG);
switch (count($this->_stack)) {
case 4:
switch ($element) {
case 'DataStore':
$this->_properties = array();
break;
}
break;
case 6:
switch ($element) {
case 'Property':
unset($this->_PropName);
$this->_PropSize = -1;
$this->_PropNoTruncate = false;
break;
}
break;
}
parent::startElement($uri, $element, $attrs);
}

View File

@ -1,22 +1,29 @@
<?php
/**
* eGroupWare - SyncML based on Horde 3
*
*
* Using the PEAR Log class (which need to be installed!)
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage horde
* @author Anthony Mills <amills@pyramid6.com>
* @copyright (c) The Horde Project (http://www.horde.org/)
* @version $Id$
*/
include_once 'Horde/SyncML/Command.php';
/**
* $Horde: framework/SyncML/SyncML/Command/Replace.php,v 1.7 2004/05/26 17:41:30 chuck Exp $
*
* Copyright 2003-2004 Anthony Mills <amills@pyramid6.com>
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Anthony Mills <amills@pyramid6.com>
* @version $Revision$
* @since Horde 3.0
* @package Horde_SyncML
*/
class Horde_SyncML_Command_Final extends Horde_SyncML_Command {
/**
* Name of the command.
*
* @var string
*/
var $_cmdName = 'Replace';
function output($currentCmdID, &$output)
{
return $currentCmdID;

View File

@ -1,291 +1,36 @@
<?php
include_once 'Horde/SyncML/State.php';
include_once 'Horde/SyncML/Command.php';
/**
* $Horde: framework/SyncML/SyncML/Command/Results.php,v 1.11 2004/07/02 19:24:44 chuck Exp $
* eGroupWare - SyncML based on Horde 3
*
* Copyright 2003-2004 Anthony Mills <amills@pyramid6.com>
* The SyncML_Command_Results class provides a SyncML implementation of the
* Results command as defined in SyncML Representation Protocol, version 1.1,
* section 5.5.12.
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
* The Results command is used to return the results of a Search or Get
* command. Currently SyncML_Command_Results behaves the same as
* SyncML_Command_Put. The only results we get is the same DevInf as for the
* Put command.
*
* @author Anthony Mills <amills@pyramid6.com>
* @version $Revision$
* @since Horde 3.0
* @package Horde_SyncML
*
* Using the PEAR Log class (which need to be installed!)
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage horde
* @author Anthony Mills <amills@pyramid6.com>
* @copyright (c) The Horde Project (http://www.horde.org/)
* @version $Id$
*/
class Horde_SyncML_Command_Results extends Horde_SyncML_Command {
include_once 'Horde/SyncML/Command/Put.php';
var $_cmdRef;
var $_type;
var $_data;
var $_locSourceURI;
var $_deviceInfo;
function endElement($uri, $element) {
#Horde::logMessage('SyncML: put endelement ' . $element . ' chars ' . $this->_chars, __FILE__, __LINE__, PEAR_LOG_DEBUG);
switch ($this->_xmlStack) {
case 5:
switch($element) {
case 'DataStore':
$this->_deviceInfo['dataStore'][$this->_sourceReference] = array (
'maxGUIDSize' => $this->_maxGUIDSize,
'rxPreference' => $this->_rxPreference,
'txPreference' => $this->_txPreference,
'syncCapabilities' => $this->_syncCapabilities,
);
break;
case 'DevID':
$this->_deviceInfo['deviceID'] = trim($this->_chars);
break;
case 'DevTyp':
$this->_deviceInfo['deviceType'] = trim($this->_chars);
break;
case 'FwV':
$this->_deviceInfo['firmwareVersion'] = trim($this->_chars);
break;
case 'HwV':
$this->_deviceInfo['hardwareVersion'] = trim($this->_chars);
break;
case 'Man':
$this->_deviceInfo['manufacturer'] = trim($this->_chars);
break;
case 'Mod':
$this->_deviceInfo['model'] = trim($this->_chars);
break;
case 'OEM':
$this->_deviceInfo['oem'] = trim($this->_chars);
break;
case 'SwV':
$this->_deviceInfo['softwareVersion'] = trim($this->_chars);
break;
case 'SupportLargeObjs':
$this->_deviceInfo['supportLargeObjs'] = true;
break;
case 'SupportNumberOfChanges':
$this->_deviceInfo['supportNumberOfChanges'] = true;
break;
case 'UTC':
$this->_deviceInfo['UTC'] = true;
break;
case 'VerDTD':
$this->_deviceInfo['DTDVersion'] = trim($this->_chars);
break;
}
break;
class Horde_SyncML_Command_Results extends Horde_SyncML_Command_Put {
case 6:
switch($element) {
case 'MaxGUIDSize':
$this->_maxGUIDSize = trim($this->_chars);
break;
case 'Rx-Pref':
$this->_rxPreference = array (
'contentType' => $this->_contentType,
'contentVersion' => $this->_contentVersion,
);
break;
case 'SourceRef':
$this->_sourceReference = trim($this->_chars);
break;
case 'Tx-Pref':
$this->_txPreference = array(
'contentType' => $this->_contentType,
'contentVersion' => $this->_contentVersion,
);
break;
}
break;
case 7:
switch($element) {
case 'CTType':
$this->_contentType = trim($this->_chars);
break;
case 'SyncType':
$this->_syncCapabilities[] = trim($this->_chars);
break;
case 'VerCT':
$this->_contentVersion = trim($this->_chars);
break;
}
break;
}
parent::endElement($uri, $element);
}
/**
* Name of the command.
*
* @var string
*/
var $_cmdName = 'Results';
function finalizeDeviceInfo()
{
// get some more information about the device from out of band data
$ua = $_SERVER['HTTP_USER_AGENT'];
if (preg_match("/^\s*Funambol (.*) (\d+\.\d+\.\d+)\s*$/i", $ua, $matches))
{
if (!isset($this->_deviceInfo['manufacturer']))
$this->_deviceInfo['manufacturer'] = 'Funambol';
if (!isset($this->_deviceInfo['model']))
$this->_deviceInfo['model'] = 'Funambol ' . trim($matches[1]);
if (!isset($this->_deviceInfo['softwareVersion']))
$this->_deviceInfo['softwareVersion'] = $matches[2];
if (!isset($this->_deviceInfo['deviceType']))
{
switch (strtolower(trim($matches[1])))
{
case 'outlook plug-in':
default:
$this->_deviceInfo['deviceType'] = 'workstation';
break;
case 'pocket pc plug-in':
$this->_deviceInfo['deviceType'] = 'windowsmobile';
break;
}
}
}
$devid = $this->_deviceInfo['deviceID'];
switch (strtolower($devid))
{
case 'fmz-thunderbird-plugin':
if (empty($this->_devinceInfo['manufacturer']))
$this->_deviceInfo['manufacturer'] = 'Funambol';
if (empty($this->_devinceInfo['model']))
$this->_deviceInfo['model'] = 'ThunderBird';
if (empty($this->_devinceInfo['softwareVersion']))
$this->_deviceInfo['softwareVersion'] = '0.3';
break;
}
}
function output($currentCmdID, &$output) {
if(!isset($this->_locSourceURI)) {
#Horde::logMessage('SyncML: BIG TODO!!!!!!!!!!!!!!!!!! parse reply', __FILE__, __LINE__, PEAR_LOG_DEBUG);
$state = &$_SESSION['SyncML.state'];
$status = &new Horde_SyncML_Command_Status((($state->isAuthorized()) ? RESPONSE_OK : RESPONSE_INVALID_CREDENTIALS), 'Results');
$status->setCmdRef($this->_cmdID);
$ref = ($state->getVersion() == 0) ? './devinf10' : './devinf11';
$status->setSourceRef($ref);
if($state->isAuthorized()) {
$this->finalizeDeviceInfo();
if(count((array)$this->_deviceInfo) > 0) {
$state->setClientDeviceInfo($this->_deviceInfo);
$state->writeClientDeviceInfo();
}
}
return $status->output($currentCmdID, $output);
} else {
#Horde::logMessage('SyncML: BIG TODO!!!!!!!!!!!!!!!!!! generate reponse', __FILE__, __LINE__, PEAR_LOG_DEBUG);
$state = $_SESSION['SyncML.state'];
$attrs = array();
$output->startElement($state->getURI(), 'Results', $attrs);
$output->startElement($state->getURI(), 'CmdID', $attrs);
$chars = $currentCmdID;
$output->characters($chars);
$output->endElement($state->getURI(), 'CmdID');
$output->startElement($state->getURI(), 'MsgRef', $attrs);
$chars = $state->getMsgID();
$output->characters($chars);
$output->endElement($state->getURI(), 'MsgRef');
$output->startElement($state->getURI(), 'CmdRef', $attrs);
$chars = $this->_cmdRef;
$output->characters($chars);
$output->endElement($state->getURI(), 'CmdRef');
$output->startElement($state->getURI(), 'Meta', $attrs);
$output->startElement($state->getURIMeta(), 'Type', $attrs);
$output->characters($this->_type);
$output->endElement($state->getURIMeta(), 'Type');
$output->endElement($state->getURI(), 'Meta');
$output->startElement($state->getURI(), 'Item', $attrs);
$output->startElement($state->getURI(), 'Source', $attrs);
$output->startElement($state->getURI(), 'LocURI', $attrs);
$chars = $this->_locSourceURI;
$output->characters($chars);
$output->endElement($state->getURI(), 'LocURI');
$output->endElement($state->getURI(), 'Source');
$output->startElement($state->getURI(), 'Data', $attrs);
// Need to send this information as opaque data so the WBXML
// will understand it.
$output->opaque($this->_data);
$output->endElement($state->getURI(), 'Data');
$output->endElement($state->getURI(), 'Item');
$output->endElement($state->getURI(), 'Results');
$currentCmdID++;
return $currentCmdID;
}
}
/**
* Setter for property cmdRef.
*
* @param string $cmdRef New value of property cmdRef.
*/
function setCmdRef($cmdRef) {
$this->_cmdRef = $cmdRef;
}
/**
* Setter for property Type.
*
* @param string $type New value of property type.
*/
function setType($type) {
$this->_type = $type;
}
/**
* Setter for property data.
*
* @param string $data New value of property data.
*/
function setData($data) {
$this->_data = $data;
}
/**
* Setter for property locSourceURI.
*
* @param string $locSourceURI New value of property locSourceURI.
*/
function setlocSourceURI($locSourceURI) {
$this->_locSourceURI = $locSourceURI;
}
}

View File

@ -1,37 +1,76 @@
<?php
/**
* eGroupWare - SyncML based on Horde 3
*
*
* Using the PEAR Log class (which need to be installed!)
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage horde
* @author Anthony Mills <amills@pyramid6.com>
* @author Joerg Lehrke <jlehrke@noc.de>
* @copyright (c) The Horde Project (http://www.horde.org/)
* @version $Id$
*/
include_once 'Horde/SyncML/State.php';
include_once 'Horde/SyncML/Command.php';
/**
* $Horde: framework/SyncML/SyncML/Command/Status.php,v 1.15 2004/07/02 19:24:44 chuck Exp $
*
* Copyright 2003-2004 Anthony Mills <amills@pyramid6.com>
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Anthony Mills <amills@pyramid6.com>
* @version $Revision$
* @since Horde 3.0
* @package Horde_SyncML
*/
class Horde_SyncML_Command_Status extends Horde_SyncML_Command {
/**
* Name of the command.
*
* @var string
*/
var $_cmdName = 'Status';
/**
* The Response code of the command sent to the client, that this
* Status response refers to.
*
* @var integer
*/
var $_response;
/**
* The command ID (CmdID) of the command sent to the client, that this
* Status response refers to.
*
* @var integer
*/
var $_cmdRef;
/**
* Must be present.
* The command (Add, Replace, etc) sent to the client, that this Status
* response refers to.
*
* @var string
*/
var $_cmd;
/**
* Must if not null (what does this mean?).
* The server ID of the sent object, that this Status response refers to.
*
* This element is optional. If specified, Status response refers to a
* single Item in the command sent to the client. It refers to all Items in
* the sent command otherwise.
*
* @var string
*/
var $_sourceRef;
/**
* The client ID of the sent object, that this Status response refers to.
*
* This element is optional. If specified, Status response refers to a
* single Item in the command sent to the client. It refers to all Items in
* the sent command otherwise.
*
* @var string
*/
var $_targetRef;
var $_chalMetaFormat;
@ -48,6 +87,15 @@ class Horde_SyncML_Command_Status extends Horde_SyncML_Command {
var $_itemSourceLocURI;
var $_syncItems;
/**
* Constructor.
*
* @param integer $response The response code.
* @param string $cmd The command sent to the client,
* that this Status response refers to.
*/
function Horde_SyncML_Command_Status($response = null, $cmd = null)
{
if ($response != null) {
@ -61,12 +109,11 @@ class Horde_SyncML_Command_Status extends Horde_SyncML_Command {
function output($currentCmdID, &$output)
{
$state = &$_SESSION['SyncML.state'];
$attrs = array();
$state = $_SESSION['SyncML.state'];
if ($this->_cmd != null) {
$attrs = array();
$output->startElement($state->getURI(), 'Status', $attrs);
$output->startElement($state->getURI(), 'CmdID', $attrs);
@ -174,61 +221,41 @@ class Horde_SyncML_Command_Status extends Horde_SyncML_Command {
$output->endElement($state->getURI(), 'Item');
}
if (isset($this->_itemTargetLocURI) && isset($this->_itemSourceLocURI)) {
$output->startElement($state->getURI(), 'Item', $attrs);
if (isset($this->_syncItems)) {
// Support multible items per command
foreach ($this->_syncItems as $locURI => &$syncItem) {
$output->startElement($state->getURI(), 'Item', $attrs);
$output->startElement($state->getURI(), 'Source', $attrs);
$output->startElement($state->getURI(), 'LocURI', $attrs);
$output->characters($locURI);
$output->endElement($state->getURI(), 'LocURI');
$output->endElement($state->getURI(), 'Source');
$output->endElement($state->getURI(), 'Item');
}
} elseif (isset($this->_itemTargetLocURI) || isset($this->_itemSourceLocURI)) {
$output->startElement($state->getURI(), 'Item', $attrs);
$output->startElement($state->getURI(), 'Target', $attrs);
$output->startElement($state->getURI(), 'LocURI', $attrs);
$output->characters($this->_itemTargetLocURI);
$output->endElement($state->getURI(), 'LocURI');
$output->endElement($state->getURI(), 'Target');
$output->startElement($state->getURI(), 'Source', $attrs);
$output->startElement($state->getURI(), 'LocURI', $attrs);
$output->characters($this->_itemSourceLocURI);
$output->endElement($state->getURI(), 'LocURI');
$output->endElement($state->getURI(), 'Source');
$output->endElement($state->getURI(), 'Item');
}
if (isset($this->_itemTargetLocURI)) {
$output->startElement($state->getURI(), 'Target', $attrs);
$output->startElement($state->getURI(), 'LocURI', $attrs);
$output->characters($this->_itemTargetLocURI);
$output->endElement($state->getURI(), 'LocURI');
$output->endElement($state->getURI(), 'Target');
}
if (isset($this->_itemSourceLocURI)) {
$output->startElement($state->getURI(), 'Source', $attrs);
$output->startElement($state->getURI(), 'LocURI', $attrs);
$output->characters($this->_itemSourceLocURI);
$output->endElement($state->getURI(), 'LocURI');
$output->endElement($state->getURI(), 'Source');
}
$output->endElement($state->getURI(), 'Item');
}
$output->endElement($state->getURI(), 'Status');
$currentCmdID++;
// moredata pending request them
/* if($this->_response == RESPONSE_CHUNKED_ITEM_ACCEPTED_AND_BUFFERED) {
$output->startElement($state->getURI(), 'Alert', $attrs);
$output->startElement($state->getURI(), 'CmdID', $attrs);
$chars = $currentCmdID;
$output->characters($chars);
$output->endElement($state->getURI(), 'CmdID');
$output->startElement($state->getURI(), 'Data', $attrs);
$output->characters(ALERT_NEXT_MESSAGE);
$output->endElement($state->getURI(), 'Data');
if (isset($this->_itemTargetLocURI) && isset($this->_itemSourceLocURI)) {
$output->startElement($state->getURI(), 'Item', $attrs);
$output->startElement($state->getURI(), 'Target', $attrs);
$output->startElement($state->getURI(), 'LocURI', $attrs);
$output->characters($this->_itemTargetLocURI);
$output->endElement($state->getURI(), 'LocURI');
$output->endElement($state->getURI(), 'Target');
$output->startElement($state->getURI(), 'Source', $attrs);
$output->startElement($state->getURI(), 'LocURI', $attrs);
$output->characters($this->_itemSourceLocURI);
$output->endElement($state->getURI(), 'LocURI');
$output->endElement($state->getURI(), 'Source');
}
$output->endElement($state->getURI(), 'Alert');
$currentCmdID++;
} */
}
return $currentCmdID;
@ -323,4 +350,14 @@ class Horde_SyncML_Command_Status extends Horde_SyncML_Command {
{
$this->_itemTargetLocURI = $itemTargetLocURI;
}
/**
* Setter for the the list of handled SyncItems
*
* @param array $syncItems The Items of the command
*/
function setSyncItems(&$syncItems)
{
$this->_syncItems = $syncItems;
}
}

View File

@ -1,220 +1,259 @@
<?php
/**
* eGroupWare - SyncML based on Horde 3
*
*
* Using the PEAR Log class (which need to be installed!)
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage horde
* @author Anthony Mills <amills@pyramid6.com>
* @author Joerg Lehrke <jlehrke@noc.de>
* @copyright (c) The Horde Project (http://www.horde.org/)
* @version $Id$
*/
include_once 'Horde/SyncML/State.php';
include_once 'Horde/SyncML/Command.php';
include_once 'Horde/SyncML/Command/Sync/SyncElement.php';
include_once 'Horde/SyncML/Sync/TwoWaySync.php';
include_once 'Horde/SyncML/Sync/SlowSync.php';
include_once 'Horde/SyncML/Sync/OneWayFromServerSync.php';
include_once 'Horde/SyncML/Sync/OneWayFromClientSync.php';
include_once 'Horde/SyncML/Sync/RefreshFromServerSync.php';
include_once 'Horde/SyncML/Sync/RefreshFromClientSync.php';
/**
* $Horde: framework/SyncML/SyncML/Command/Sync.php,v 1.17 2004/07/03 15:21:14 chuck Exp $
*
* Copyright 2003-2004 Anthony Mills <amills@pyramid6.com>
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Anthony Mills <amills@pyramid6.com>
* @version $Revision$
* @since Horde 3.0
* @package Horde_SyncML
*/
class Horde_SyncML_Command_Sync extends Horde_Syncml_Command {
class Horde_SyncML_Command_Sync extends Horde_SyncML_Command {
var $_isInSource;
var $_currentSyncElement;
var $_syncElements = array();
function output($currentCmdID, &$output) {
$state = &$_SESSION['SyncML.state'];
$attrs = array();
Horde::logMessage('SyncML: $this->_targetURI = ' . $this->_targetURI, __FILE__, __LINE__, PEAR_LOG_DEBUG);
$status = &new Horde_SyncML_Command_Status(RESPONSE_OK, 'Sync');
// $status->setState($state);
$status->setCmdRef($this->_cmdID);
if ($this->_targetURI != null) {
$status->setTargetRef((isset($this->_targetURIParameters) ? $this->_targetURI.'?/'.$this->_targetURIParameters : $this->_targetURI));
}
if ($this->_sourceURI != null) {
$status->setSourceRef($this->_sourceURI);
}
$currentCmdID = $status->output($currentCmdID, $output);
if($sync = $state->getSync($this->_targetURI)) {
$currentCmdID = $sync->startSync($currentCmdID, $output);
foreach ($this->_syncElements as $element) {
$currentCmdID = $sync->nextSyncCommand($currentCmdID, $element, $output);
}
}
/**
* Name of the command.
*
* @var string
*/
var $_cmdName = 'Sync';
/**
* Source database of the <Sync> command.
*
* @var string
*/
var $_sourceURI;
/**
* Target database of the <Sync> command.
*
* @var string
*/
var $_targetURI;
/**
* Optional parameter for the Target.
*
* @var string
*/
var $_targetURIParameters;
/**
* SyncML_SyncElement object for the currently parsed sync command.
*
* @var SyncML_SyncElement
*/
var $_curItem;
/**
* List of all SyncML_SyncElement objects that have parsed.
*
* @var array
*/
var $_syncElements = array();
function output($currentCmdID, &$output)
{
$state = &$_SESSION['SyncML.state'];
Horde::logMessage('SyncML: $this->_targetURI = ' . $this->_targetURI, __FILE__, __LINE__, PEAR_LOG_DEBUG);
$status = new Horde_SyncML_Command_Status(RESPONSE_OK, 'Sync');
$status->setCmdRef($this->_cmdID);
if ($this->_targetURI != null) {
$status->setTargetRef((isset($this->_targetURIParameters) ? $this->_targetURI.'?/'.$this->_targetURIParameters : $this->_targetURI));
}
if ($this->_sourceURI != null) {
$status->setSourceRef($this->_sourceURI);
}
$currentCmdID = $status->output($currentCmdID, $output);
if ($this->_targetURI != "configuration" && // Fix Funambol issue
($sync = &$state->getSync($this->_targetURI))) {
$currentCmdID = $sync->startSync($currentCmdID, $output);
foreach ($this->_syncElements as $element) {
$currentCmdID = $sync->nextSyncCommand($currentCmdID, $element, $output);
}
}
return $currentCmdID;
}
return $currentCmdID;
}
function getTargetURI() {
return $this->_targetURI;
}
function startElement($uri, $element, $attrs)
{
parent::startElement($uri, $element, $attrs);
switch ($this->_xmlStack) {
switch (count($this->_stack)) {
case 2:
if ($element == 'Replace' || $element == 'Add' || $element == 'Delete') {
$this->_currentSyncElement = &Horde_SyncML_Command_Sync_SyncElement::factory($element);
// $this->_currentSyncElement->setVersion($this->_version);
// $this->_currentSyncElement->setCmdRef($this->_cmdID);
// $this->_currentSyncElement->setMsgID($this->_msgID);
} elseif ($element == 'Target') {
$this->_isInSource = false;
} else {
$this->_isInSource = true;
if ($element == 'Replace' ||
$element == 'Add' ||
$element == 'Delete') {
Horde::logMessage("SyncML: sync element $element found", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$this->_curItem = &Horde_SyncML_Command_Sync_SyncElement::factory($element);
}
break;
}
if (isset($this->_currentSyncElement)) {
$this->_currentSyncElement->startElement($uri, $element, $attrs);
if (isset($this->_curItem)) {
$this->_curItem->startElement($uri, $element, $attrs);
}
}
// We create a seperate Sync Element for the Sync Data sent
// from the Server to the client as we want to process the
// client sync information before.
function syncToClient($currentCmdID, &$output)
{
Horde::logMessage('SyncML: starting sync to client', __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde::logMessage('SyncML: starting sync to client', __FILE__, __LINE__, PEAR_LOG_DEBUG);
$state = $_SESSION['SyncML.state'];
if($state->getSyncStatus() >= CLIENT_SYNC_ACKNOWLEDGED && $state->getSyncStatus() < SERVER_SYNC_FINNISHED)
{
$deviceInfo = $state->getClientDeviceInfo();
$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
$targets = $state->getTargets();
Horde::logMessage('SyncML: starting sync to client '.$targets[0], __FILE__, __LINE__, PEAR_LOG_DEBUG);
$attrs = array();
$state = &$_SESSION['SyncML.state'];
if($state->getSyncStatus() >= CLIENT_SYNC_FINNISHED && $state->getSyncStatus() < SERVER_SYNC_FINNISHED)
{
$deviceInfo = $state->getClientDeviceInfo();
$targets = $state->getTargets();
foreach($targets as $target)
{
$sync = $state->getSync($target);
// make sure that the state reflects what is currently being done
$state->_currentSourceURI = $sync->_sourceLocURI;
$state->_currentTargetURI = $sync->_targetLocURI;
$output->startElement($state->getURI(), 'Sync', $attrs);
$output->startElement($state->getURI(), 'CmdID', $attrs);
$output->characters($currentCmdID);
$currentCmdID++;
$output->endElement($state->getURI(), 'CmdID');
$output->startElement($state->getURI(), 'Target', $attrs);
$output->startElement($state->getURI(), 'LocURI', $attrs);
$chars = $sync->_sourceLocURI;
$output->characters($chars);
$output->endElement($state->getURI(), 'LocURI');
$output->endElement($state->getURI(), 'Target');
$output->startElement($state->getURI(), 'Source', $attrs);
$output->startElement($state->getURI(), 'LocURI', $attrs);
#$chars = $sync->_targetLocURI;
$chars = (isset($sync->_targetLocURIParameters) ? $sync->_targetLocURI.'?/'.$sync->_targetLocURIParameters : $sync->_targetLocURI);
$output->characters($chars);
$output->endElement($state->getURI(), 'LocURI');
$output->endElement($state->getURI(), 'Source');
if(!$sync->_syncDataLoaded)
{
$numberOfItems = $sync->loadData();
if($deviceInfo['supportNumberOfChanges'])
$sync = &$state->getSync($target);
Horde::logMessage('SyncML['. session_id() .']: sync alerttype '. $sync->_syncType .' found for target ' . $target, __FILE__, __LINE__, PEAR_LOG_DEBUG);
if ($sync->_syncType == ALERT_ONE_WAY_FROM_CLIENT ||
$sync->_syncType == ALERT_REFRESH_FROM_CLIENT) {
Horde::logMessage('SyncML['. session_id() .']: From client Sync, no sync of '. $target .' to client', __FILE__, __LINE__, PEAR_LOG_DEBUG);
$state->clearSync($target);
} else if ($state->getSyncStatus() >= CLIENT_SYNC_ACKNOWLEDGED) {
Horde::logMessage("SyncML: starting sync to client $target", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$attrs = array();
$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
$output->startElement($state->getURI(), 'Sync', $attrs);
$output->startElement($state->getURI(), 'CmdID', $attrs);
$output->characters($currentCmdID);
$currentCmdID++;
$output->endElement($state->getURI(), 'CmdID');
$output->startElement($state->getURI(), 'Target', $attrs);
$output->startElement($state->getURI(), 'LocURI', $attrs);
$chars = $sync->_sourceLocURI;
$output->characters($chars);
$output->endElement($state->getURI(), 'LocURI');
$output->endElement($state->getURI(), 'Target');
$output->startElement($state->getURI(), 'Source', $attrs);
$output->startElement($state->getURI(), 'LocURI', $attrs);
$chars = (isset($sync->_targetLocURIParameters) ? $sync->_targetLocURI.'?/'.$sync->_targetLocURIParameters : $sync->_targetLocURI);
$output->characters($chars);
$output->endElement($state->getURI(), 'LocURI');
$output->endElement($state->getURI(), 'Source');
if(!$sync->_syncDataLoaded)
{
$output->startElement($state->getURI(), 'NumberOfChanged', $attrs);
$output->characters($numberOfItems);
$output->endElement($state->getURI(), 'NumberOfChanged');
$numberOfItems = $sync->loadData();
if($deviceInfo['supportNumberOfChanges'])
{
$output->startElement($state->getURI(), 'NumberOfChanges', $attrs);
$output->characters($numberOfItems);
$output->endElement($state->getURI(), 'NumberOfChanges');
}
}
$currentCmdID = $sync->endSync($currentCmdID, $output);
$output->endElement($state->getURI(), 'Sync');
if (isset($state->curSyncItem) ||
$state->getNumberOfElements() === false) {
break;
}
} else {
Horde::logMessage("SyncML: Waiting for client ACKNOWLEDGE for $target", __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
$currentCmdID = $sync->endSync($currentCmdID, $output);
$output->endElement($state->getURI(), 'Sync');
break;
}
// no syncs left
if($state->getTargets() === FALSE)
if($state->getTargets() === FALSE &&
!isset($state->curSyncItem)) {
$state->setSyncStatus(SERVER_SYNC_FINNISHED);
Horde::logMessage('SyncML: syncStatus(server_sync_finnished) '. $state->getSyncStatus, __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
}
Horde::logMessage('SyncML: syncStatus(syncToClient) = '. $state->getSyncStatus(), __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
return $currentCmdID;
}
function endElement($uri, $element)
{
if (isset($this->_currentSyncElement)) {
$this->_currentSyncElement->endElement($uri, $element);
if (isset($this->_curItem)) {
$this->_curItem->endElement($uri, $element);
}
switch ($this->_xmlStack) {
switch (count($this->_stack)) {
case 2:
if ($element == 'Replace' || $element == 'Add' || $element == 'Delete') {
$this->_syncElements[] = $this->_currentSyncElement;
unset($this->_currentSyncElement);
if ($element == 'Replace' ||
$element == 'Add' ||
$element == 'Delete') {
$this->_syncElements[] = &$this->_curItem;
unset($this->_curItem);
}
break;
case 3:
$state = & $_SESSION['SyncML.state'];
if ($element == 'LocURI' && !isset($this->_currentSyncElement)) {
if ($this->_isInSource) {
if ($element == 'LocURI' && !isset($this->_curItem)) {
if ($this->_stack[1] == 'Source') {
$this->_sourceURI = trim($this->_chars);
$state->_currentSourceURI = $this->_sourceURI;
} else {
$this->_targetURI = trim($this->_chars);
} elseif ($this->_stack[1] == 'Target') {
$targetURIData = explode('?/',trim($this->_chars));
$this->_targetURI = $targetURIData[0];
$state->_currentTargetURI = $this->_targetURI;
if(isset($targetURIData[1]))
{
$this->_targetURIParameters = $targetURIData[1];
$state->_currentTargetURIParameters = $this->_targetURIParameters;
}
$this->_targetURI = $targetURIData[0];
if (isset($targetURIData[1])) {
$this->_targetURIParameters = $targetURIData[1];
}
}
}
break;
}
parent::endElement($uri, $element);
}
function characters($str)
{
if (isset($this->_currentSyncElement)) {
$this->_currentSyncElement->characters($str);
if (isset($this->_curItem)) {
$this->_curItem->characters($str);
} else {
if (isset($this->_chars)) {
$this->_chars = $this->_chars . $str;
$this->_chars .= $str;
} else {
$this->_chars = $str;
}

View File

@ -1,32 +1,33 @@
<?php
/**
* eGroupWare - SyncML based on Horde 3
*
*
* Using the PEAR Log class (which need to be installed!)
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage horde
* @author Anthony Mills <amills@pyramid6.com>
* @copyright (c) The Horde Project (http://www.horde.org/)
* @version $Id$
*/
include_once 'Horde/SyncML/Command/Sync/SyncElement.php';
/**
* $Horde: framework/SyncML/SyncML/Command/Sync/Add.php,v 1.10 2004/07/02 19:24:44 chuck Exp $
*
* Copyright 2003-2004 Anthony Mills <amills@pyramid6.com>
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Anthony Mills <amills@pyramid6.com>
* @version $Revision$
* @since Horde 3.0
* @package Horde_SyncML
*/
class Horde_SyncML_Command_Sync_Add extends Horde_SyncML_Command_Sync_SyncElement {
var $_status = RESPONSE_ITEM_ADDED;
function output($currentCmdID, &$output)
{
$status = &new Horde_SyncML_Command_Status($this->_status, 'Add');
$status = new Horde_SyncML_Command_Status($this->_status, 'Add');
$status->setCmdRef($this->_cmdID);
if (isset($this->_luid)) {
$status->setSourceRef($this->_luid);
if (!empty($this->_items)) {
$status->setSyncItems($this->_items);
}
return $status->output($currentCmdID, $output);
}

View File

@ -1,106 +1,44 @@
<?php
include_once 'Horde/SyncML/State.php';
include_once 'Horde/SyncML/Command/Sync/SyncElement.php';
/**
* $Horde: framework/SyncML/SyncML/Command/Sync/ContentSyncElement.php,v 1.12 2004/07/02 19:24:44 chuck Exp $
* eGroupWare - SyncML based on Horde 3
*
* Copyright 2003-2004 Anthony Mills <amills@pyramid6.com>
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
* Using the PEAR Log class (which need to be installed!)
*
* @author Anthony Mills <amills@pyramid6.com>
* @version $Revision$
* @since Horde 3.0
* @package Horde_SyncML
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage horde
* @author Anthony Mills <amills@pyramid6.com>
* @author Joerg Lehrke <jlehrke@noc.de>
* @copyright (c) The Horde Project (http://www.horde.org/)
* @version $Id$
*/
class Horde_SyncML_Command_Sync_ContentSyncElement extends Horde_SyncML_Command_Sync_SyncElement {
include_once 'Horde/SyncML/State.php';
include_once 'Horde/SyncML/Command/Sync/SyncElementItem.php';
/**
* The content: vcard data, etc.
*/
var $_content;
/**
* Local to server: our Horde guid.
*/
var $_locURI;
var $_targetURI;
var $_contentType;
function setSourceURI($uri)
{
$this->_locURI = $uri;
}
function getSourceURI()
{
return $this->_locURI;
}
function setTargetURI($uri)
{
$this->_targetURI = $uri;
}
function getTargetURI()
{
return $this->_targetURI;
}
function setContentType($c)
{
$this->_contentType = $c;
}
function setContentFormat($_format)
{
$this->_contentFormat = $_format;
}
function getContentType()
{
return $this->_contentType;
}
function getContent()
{
return $this->_content;
}
function setContent($content)
{
$this->_content = $content;
}
function endElement($uri, $element)
{
switch ($this->_xmlStack) {
case 2:
if ($element == 'Data') {
$this->_content = trim($this->_chars);
}
break;
}
parent::endElement($uri, $element);
}
class Horde_SyncML_Command_Sync_ContentSyncElement extends Horde_SyncML_Command_Sync_SyncElementItem {
function outputCommand($currentCmdID, &$output, $command)
{
$state = $_SESSION['SyncML.state'];
$maxMsgSize = $state->getMaxMsgSizeClient();
$maxGUIDSize = $state->getMaxGUIDSizeClient();
if ($this->_moreData) {
$command = $this->_command;
} else {
$this->_command = $command;
}
$attrs = array();
$output->startElement($state->getURI(), $command, $attrs);
$output->startElement($state->getURI(), 'CmdID', $attrs);
$chars = $currentCmdID;
$output->characters($chars);
$output->characters($currentCmdID);
$output->endElement($state->getURI(), 'CmdID');
/*
if (isset($this->_contentType)) {
$output->startElement($state->getURI(), 'Meta', $attrs);
$output->startElement($state->getURIMeta(), 'Type', $attrs);
@ -108,43 +46,100 @@ class Horde_SyncML_Command_Sync_ContentSyncElement extends Horde_SyncML_Command_
$output->endElement($state->getURIMeta(), 'Type');
$output->endElement($state->getURI(), 'Meta');
}
*/
if (isset($this->_content) && !$this->_moreData) {
$this->_content = trim($this->_content);
$this->_contentSize = strlen($this->_content);
if (strtolower($this->_contentFormat) == 'b64') {
$this->_content = base64_encode($this->_content);
}
} else {
$this->_contentSize = 0;
}
if (isset($this->_content)
|| isset($this->_locURI) || isset($this->targetURI)) {
// <command><Meta>
if ($this->_contentSize || isset($this->_contentType) || isset($this->_contentFormat)) {
$output->startElement($state->getURI(), 'Meta', $attrs);
if (isset($this->_contentType)) {
$output->startElement($state->getURIMeta(), 'Type', $attrs);
$output->characters($this->_contentType);
$output->endElement($state->getURIMeta(), 'Type');
}
if (isset($this->_contentFormat)) {
$output->startElement($state->getURIMeta(), 'Format', $attrs);
$output->characters($this->_contentFormat);
$output->endElement($state->getURIMeta(), 'Format');
}
if ($this->_contentSize) {
$output->startElement($state->getURIMeta(), 'Size', $attrs);
$output->characters(($this->_contentSize));
$output->endElement($state->getURIMeta(), 'Size');
}
$output->endElement($state->getURI(), 'Meta');
}
if (isset($this->_content) || isset($this->_luid) || isset($this->_guid)) {
$output->startElement($state->getURI(), 'Item', $attrs);
// send only when sending adds
if ($this->_locURI != null && (strtolower($command) == 'add')) {
// <command><Item><Source><LocURI>
if (isset($this->_guid)) {
$output->startElement($state->getURI(), 'Source', $attrs);
$output->startElement($state->getURI(), 'LocURI', $attrs);
$chars = substr($this->_locURI,0,39);
$state->setUIDMapping($this->_locURI, $chars);
$chars = substr($this->_guid, 0, $maxGUIDSize);
$state->setUIDMapping($this->_guid, $chars);
$output->characters($chars);
$output->endElement($state->getURI(), 'LocURI');
$output->endElement($state->getURI(), 'Source');
}
if(isset($this->_contentFormat)) {
$output->startElement($state->getURI(), 'Meta', $attrs);
$output->startElement($state->getURIMeta(), 'Format', $attrs);
$output->characters($this->_contentFormat);
$output->endElement($state->getURIMeta(), 'Format');
$output->endElement($state->getURI(), 'Meta');
}
if ($this->_targetURI != null) {
// <command><Item><Target><LocURI>
if (isset($this->_luid)) {
$output->startElement($state->getURI(), 'Target', $attrs);
$output->startElement($state->getURI(), 'LocURI', $attrs);
$chars = $this->_targetURI;
$output->characters($chars);
$output->characters($this->_luid);
$output->endElement($state->getURI(), 'LocURI');
$output->endElement($state->getURI(), 'Target');
}
// <command><Item><Data>
if (isset($this->_content)) {
$output->startElement($state->getURI(), 'Data', $attrs);
#$chars = '<![CDATA['.$this->_content.']]>';
$chars = $this->_content;
$output->characters($chars);
$currentSize = $output->getOutputSize();
Horde::logMessage("SyncML: $command: current = $currentSize, max = $maxMsgSize", __FILE__, __LINE__, PEAR_LOG_DEBUG);
if (!$maxMsgSize ||
(($currentSize + MIN_MSG_LEFT + $this->_contentSize) <= $maxMsgSize)) {
$chars = $this->_content;
unset($this->_content);
$this->_moreData = false;
} else {
$sizeLeft = $maxMsgSize - $currentSize - MIN_MSG_LEFT;
if ($sizeLeft < 0) {
Horde::logMessage("SyncML: $command: split with $currentSize for $maxMsgSize, increase MIN_MSG_LEFT!", __FILE__, __LINE__, PEAR_LOG_WARNING);
$sizeLeft = 0;
}
// don't let us loose characters by trimming
while (($this->_contentSize > $sizeLeft) &&
(strlen(trim(substr($this->_content, $sizeLeft - 1, 2))) < 2)) {
Horde::logMessage("SyncML: $command: split at $sizeLeft hit WS!", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$sizeLeft++;
}
$chars = substr($this->_content, 0, $sizeLeft);
$this->_content = substr($this->_content, $sizeLeft, $this->_contentSize - $sizeLeft);
Horde::logMessage("SyncML: $command: "
. $this->_contentSize . " split at $sizeLeft:\n"
. $chars, __FILE__, __LINE__, PEAR_LOG_DEBUG);
$this->_moreData = true;
}
$output->characters($chars);
$output->endElement($state->getURI(), 'Data');
// <command><Item><MoreData/>
if ($this->_moreData) {
$output->startElement($state->getURI(), 'MoreData', $attrs);
$output->endElement($state->getURI(), 'MoreData');
}
}
$output->endElement($state->getURI(), 'Item');
}

View File

@ -1,29 +1,29 @@
<?php
/**
* eGroupWare - SyncML based on Horde 3
*
*
* Using the PEAR Log class (which need to be installed!)
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage horde
* @author Anthony Mills <amills@pyramid6.com>
* @copyright (c) The Horde Project (http://www.horde.org/)
* @version $Id$
*/
include_once 'Horde/SyncML/Command/Sync/SyncElement.php';
/**
* $Horde: framework/SyncML/SyncML/Command/Sync/Delete.php,v 1.9 2004/07/02 19:24:44 chuck Exp $
*
* Copyright 2003-2004 Anthony Mills <amills@pyramid6.com>
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Anthony Mills <amills@pyramid6.com>
* @version $Revision$
* @since Horde 3.0
* @package Horde_SyncML
*/
class Horde_SyncML_Command_Sync_Delete extends Horde_SyncML_Command_Sync_SyncElement {
function output($currentCmdID, &$output)
{
$status = &new Horde_SyncML_Command_Status($this->_status, 'Delete');
$status = new Horde_SyncML_Command_Status($this->_status, 'Delete');
$status->setCmdRef($this->_cmdID);
if (isset($this->_luid)) {
$status->setSourceRef($this->_luid);
if (!empty($this->_items)) {
$status->setSyncItems($this->_items);
}
return $status->output($currentCmdID, $output);

View File

@ -1,32 +1,30 @@
<?php
/**
* eGroupWare - SyncML based on Horde 3
*
*
* Using the PEAR Log class (which need to be installed!)
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage horde
* @author Anthony Mills <amills@pyramid6.com>
* @copyright (c) The Horde Project (http://www.horde.org/)
* @version $Id$
*/
include_once 'Horde/SyncML/Command/Sync/SyncElement.php';
/**
* $Horde: framework/SyncML/SyncML/Command/Sync/Replace.php,v 1.9 2004/07/02 19:24:44 chuck Exp $
*
* Copyright 2003-2004 Anthony Mills <amills@pyramid6.com>
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Anthony Mills <amills@pyramid6.com>
* @version $Revision$
* @since Horde 3.0
* @package Horde_SyncML
*/
class Horde_SyncML_Command_Sync_Replace extends Horde_SyncML_Command_Sync_SyncElement {
function output($currentCmdID, &$output) {
$status = &new Horde_SyncML_Command_Status($this->_status, 'Replace');
$status = new Horde_SyncML_Command_Status($this->_status, 'Replace');
$status->setCmdRef($this->_cmdID);
if (isset($this->_luid)) {
$status->setSourceRef($this->_luid);
}
#$status->setItemSourceLocURI($this->_sourceLocURI);
#$status->setItemTargetLocURI(isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI);
if (!empty($this->_items)) {
$status->setSyncItems($this->_items);
}
return $status->output($currentCmdID, $output);
}

View File

@ -1,40 +1,43 @@
<?php
/**
* eGroupWare - SyncML based on Horde 3
*
*
* Using the PEAR Log class (which need to be installed!)
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage horde
* @author Anthony Mills <amills@pyramid6.com>
* @author Joerg Lehrke <jlehrke@noc.de>
* @copyright (c) The Horde Project (http://www.horde.org/)
* @version $Id$
*/
include_once 'Horde/SyncML/Command.php';
/**
* $Horde: framework/SyncML/SyncML/Command/Sync/SyncElement.php,v 1.11 2004/07/02 19:24:44 chuck Exp $
*
* Copyright 2003-2004 Anthony Mills <amills@pyramid6.com>
* Copyright 2005-2006 Lars Kneschke <l.kneschke@metaways.de>
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Anthony Mills <amills@pyramid6.com>
* @version $Revision$
* @since Horde 3.0
* @package Horde_SyncML
*/
class Horde_SyncML_Command_Sync_SyncElement extends Horde_SyncML_Command {
var $_luid;
var $_guid;
var $_isSource;
var $_content;
var $_contentSize;
var $_contentType;
var $_contentFormat;
var $_status = RESPONSE_OK;
var $_items;
var $_curItem;
var $_items = array();
var $_moreData = false;
var $_command = false;
function &factory($command, $params = null) {
include_once 'Horde/SyncML/Command/Sync/SyncElementItem.php';
@include_once 'Horde/SyncML/Command/Sync/' . $command . '.php';
$class = 'Horde_SyncML_Command_Sync_' . $command;
if (class_exists($class)) {
#Horde::logMessage('SyncML: Class definition of ' . $class . ' found in SyncElement::factory.', __FILE__, __LINE__, PEAR_LOG_DEBUG);
return $element = &new $class($params);
return $element = new $class($params);
} else {
Horde::logMessage('SyncML: Class definition of ' . $class . ' not found in SyncElement::factory.', __FILE__, __LINE__, PEAR_LOG_DEBUG);
require_once 'PEAR.php';
@ -44,78 +47,122 @@ class Horde_SyncML_Command_Sync_SyncElement extends Horde_SyncML_Command {
function startElement($uri, $element, $attrs) {
parent::startElement($uri, $element, $attrs);
switch ($this->_xmlStack) {
case 3:
if ($element == 'Source') {
$this->_isSource = true;
$state = &$_SESSION['SyncML.state'];
switch (count($this->_stack)) {
case 1:
$this->_command = $element;
break;
case 2:
if ($element == 'Item') {
if (isset($state->curSyncItem)) {
// Copy from state in case of <MoreData>.
$this->_curItem = &$state->curSyncItem;
if (isset($this->_luid) &&
($this->_luid != $this->_curItem->_luid)) {
Horde::logMessage('SyncML: moreData mismatch for LocURI ' .
$this->_curItem->_luid . ' (' . $this->_luid . ')', __FILE__, __LINE__, PEAR_LOG_ERROR);
} else {
Horde::logMessage('SyncML: moreData item found for LocURI ' . $this->_curItem->_luid, __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
unset($state->curSyncItem);
} else {
$this->_curItem = new Horde_SyncML_Command_Sync_SyncElementItem();
}
$this->_moreData = false;
}
break;
}
}
function endElement($uri, $element) {
$state = &$_SESSION['SyncML.state'];
$search = array('/ *\n/','/ *$/m');
$replace = array('','');
switch ($this->_xmlStack) {
switch (count($this->_stack)) {
case 1:
$this->_command = false;
// Need to add sync elements to the Sync method?
#error_log('total # of items: '.count($this->_items));
#error_log(print_r($this->_items[10], true));
break;
case 2;
if($element == 'Item') {
$item = new Horde_SyncML_Command_Sync_SyncElementItem();
if($this->_luid) {
$item->setLocURI($this->_luid);
$item->setContent($this->_content);
$item->setContentType($this->_contentType);
$this->_curItem->setLocURI($this->_luid);
$this->_curItem->setContentType($this->_contentType);
$this->_curItem->setContentFormat($this->_contentFormat);
$this->_curItem->setCommand($this->_command);
if($this->_contentSize)
$item->setContentType($this->_contentSize);
if($this->_moreData)
$item->setMoreData($this->_moreData);
$this->_items[$this->_luid] = $item;
$this->_curItem->setContentSize($this->_contentSize);
if($this->_moreData) {
$state->curSyncItem = &$this->_curItem;
Horde::logMessage('SyncML: moreData item saved for LocURI ' . $this->_curItem->_luid, __FILE__, __LINE__, PEAR_LOG_DEBUG);
} else {
if (strtolower($this->_curItem->getContentFormat()) == 'b64') {
$content = $this->_curItem->getContent();
$content = ($content ? base64_decode($content) : '');
$this->_curItem->setContent($content);
#Horde::logMessage('SyncML: BASE64 encoded item for LocURI '
# . $this->_curItem->_luid . ":\n $content", __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
$this->_items[$this->_luid] = $this->_curItem;
}
}
unset($this->_content);
unset($this->_contentSize);
unset($this->_luid);
}
break;
case 3:
if ($element == 'Source') {
$this->_isSource = false;
} elseif ($element == 'Data') {
$this->_content = $this->_chars;
} elseif ($element == 'MoreData') {
$this->_moreData = TRUE;
} elseif ($element == 'Type') {
if(empty($this->_contentType))
$this->_contentType = trim($this->_chars);
switch ($element) {
case 'Data':
$this->_curItem->_content .= $this->_chars;
break;
case 'MoreData':
$this->_moreData = true;
break;
case 'Type':
if(empty($this->_contentType)) {
$this->_contentType = trim($this->_chars);
}
break;
case 'Format':
$this->_contentFormat = strtolower(trim($this->_chars));
break;
case 'Size':
$this->_contentSize = $this->_chars;
break;
}
break;
case 4:
if ($element == 'LocURI' && $this->_isSource) {
$this->_luid = trim($this->_chars);
} elseif ($element == 'Type') {
$this->_contentType = trim($this->_chars);
} elseif ($element == 'Size') {
$this->_contentSize = trim($this->_chars);
switch ($element) {
case 'LocURI':
if ($this->_stack[2] == 'Source') {
$this->_luid = trim($this->_chars);
}
break;
case 'Type':
$this->_contentType = trim($this->_chars);
break;
case 'Format':
$this->_contentFormat = strtolower(trim($this->_chars));
break;
case 'Size':
$this->_contentSize = trim($this->_chars);
break;
}
break;
}
parent::endElement($uri, $element);
}
function getSyncElementItems() {
return (array)$this->_items;
}
function getSyncElementItems() {
return (array)$this->_items;
}
function getLocURI()
{
@ -149,14 +196,17 @@ class Horde_SyncML_Command_Sync_SyncElement extends Horde_SyncML_Command {
function getContent()
{
return $this->_content;
if ($this->_curItem) {
return $this->_curItem->getcontent();
}
return false;
}
function setContent($content)
function hasMoreData()
{
$this->_content = $content;
return $this->_moreData;
}
function setStatus($_status)
{
$this->_status = $_status;

View File

@ -1,67 +1,95 @@
<?php
/**
* eGroupWare - SyncML based on Horde 3
*
*
* Using the PEAR Log class (which need to be installed!)
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage horde
* @author Anthony Mills <amills@pyramid6.com>
* @copyright (c) The Horde Project (http://www.horde.org/)
* @version $Id$
*/
include_once 'Horde/SyncML/Command.php';
/**
* $Horde: framework/SyncML/SyncML/Command/Sync/SyncElement.php,v 1.11 2004/07/02 19:24:44 chuck Exp $
*
* Copyright 2003-2004 Anthony Mills <amills@pyramid6.com>
* Copyright 2005-2006 Lars Kneschke <l.kneschke@metaways.de>
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Anthony Mills <amills@pyramid6.com>
* @version $Revision$
* @since Horde 3.0
* @package Horde_SyncML
*/
class Horde_SyncML_Command_Sync_SyncElementItem {
var $_luid;
var $_guid;
var $_content;
var $_content = '';
var $_contentSize;
var $_contentType;
var $_contentFormat;
var $_command;
var $_moreData = false;
function getLocURI() {
return $this->_luid;
}
function getGUID() {
return $this->_guid;
}
function getContentType() {
return $this->_contentType;
}
function getContentFormat() {
return $this->_contentFormat;
}
function getContent() {
return $this->_content;
}
function getContentSize() {
if (isset($this->_contentSize)) {
return $this->_contentSize;
}
return false;
}
function getCommand() {
return $this->_command;
}
function setLocURI($luid) {
$this->_luid = $luid;
}
function setGUID($guid) {
$this->_guid = $guid;
}
function setContent($content) {
$this->_content = $content;
function setContent($_content) {
$this->_content = $_content;
}
function setContentSize($_size) {
$this->_contentSize = $_size;
}
function setContentType($_contentType) {
$this->_contentType = $_contentType;
function setContentType($_type) {
$this->_contentType = $_type;
}
function setContentFormat($_format) {
$this->_contentFormat = $_format;
}
function setMoreData($_status) {
$this->_moreData = $_status;
}
function hasMoreData() {
return $this->_moreData;
}
function setCommand($_command) {
$this->_command = $_command;
}
}

View File

@ -1,4 +1,18 @@
<?php
/**
* eGroupWare - SyncML based on Horde 3
*
*
* Using the PEAR Log class (which need to be installed!)
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage horde
* @author Joerg Lehrke <jlehrke@noc.de>
* @copyright (c) The Horde Project (http://www.horde.org/)
* @version $Id$
*/
define('ALERT_DISPLAY', 100);
@ -21,6 +35,10 @@ define('ALERT_RESULT_ALERT', 221);
define('ALERT_NEXT_MESSAGE', 222);
define('ALERT_NO_END_OF_DATA', 223);
// Not (really) implemented.
define('ALERT_SUSPEND', 224); // New in SyncML 1.2
define('ALERT_RESUME', 225); // New in SyncML 1.2
define('MIME_SYNCML_XML', 'application/vnd.syncml+xml');
define('MIME_SYNCML_WBXML', 'application/vnd.syncml+wbxml');
@ -107,7 +125,7 @@ define('RESPONSE_COMMAND_FAILED', 500);
// define('RESPONSE_COMMAND_FAILED', 505);
// define('RESPONSE_COMMAND_FAILED', 506);
// define('RESPONSE_COMMAND_FAILED', 507);
// define('RESPONSE_COMMAND_FAILED', 508);
define('RESPONSE_REFRESH_REQUIRED', 508);
// define('RESPONSE_COMMAND_FAILED', 509);
// define('RESPONSE_COMMAND_FAILED', 510);
// define('RESPONSE_COMMAND_FAILED', 511);
@ -117,12 +135,15 @@ define('RESPONSE_COMMAND_FAILED', 500);
// define('RESPONSE_COMMAND_FAILED', 515);
define('RESPONSE_ATOMIC_ROLL_BACK_FAILED', 516);
define('NAME_SPACE_URI_SYNCML', 'syncml:syncml1.0');
define('NAME_SPACE_URI_SYNCML_1_0', 'syncml:syncml1.0');
define('NAME_SPACE_URI_SYNCML_1_1', 'syncml:syncml1.1');
define('NAME_SPACE_URI_METINF', 'syncml:metinf');
define('NAME_SPACE_URI_SYNCML_1_2', 'syncml:syncml1.2');
define('NAME_SPACE_URI_METINF_1_0', 'syncml:metinf1.0');
define('NAME_SPACE_URI_METINF_1_1', 'syncml:metinf1.1');
define('NAME_SPACE_URI_DEVINF', 'syncml:devinf');
define('NAME_SPACE_URI_METINF_1_2', 'syncml:metinf1.2');
define('NAME_SPACE_URI_DEVINF_1_0', 'syncml:devinf1.0');
define('NAME_SPACE_URI_DEVINF_1_1', 'syncml:devinf1.1');
define('NAME_SPACE_URI_DEVINF_1_2', 'syncml:devinf1.2');
define('CLIENT_SYNC_STARTED', 1);
define('CLIENT_SYNC_FINNISHED', 2);
@ -131,8 +152,18 @@ define('SERVER_SYNC_DATA_PENDING', 4);
define('SERVER_SYNC_FINNISHED', 5);
define('SERVER_SYNC_ACKNOWLEDGED', 6);
// conflict management
define('CONFLICT_CLIENT_WINNING', 0);
define('CONFLICT_SERVER_WINNING', 1);
define('CONFLICT_MERGE_DATA', 2);
define('CONFLICT_RESOLVED_WITH_DUPLICATE', 3);
define('CONFLICT_CLIENT_CHANGES_IGNORED', 4);
define('CONFLICT_CLIENT_REFRESH_ENFORCED', 5);
define('MAX_DATA', 19);
define('MAX_ENTRIES', 10);
define('MAX_ENTRIES', 10); // default
define('MAX_GUID_SIZE', 64);
define('MIN_MSG_LEFT', 200); // Overhead
/**
* The Horde_SyncML_State class provides a SyncML state object.
@ -148,6 +179,7 @@ define('MAX_ENTRIES', 10);
* @version $Revision$
* @since Horde 3.0
* @package Horde_SyncML
* @modified Joerg Lehrke <jlehrke@noc.de> 2009/01/20, support all syn types
*/
class Horde_SyncML_State {
@ -157,6 +189,10 @@ class Horde_SyncML_State {
var $_msgID;
var $_maxMsgSize;
var $_maxGUIDSize;
var $_targetURI;
var $_sourceURI;
@ -169,6 +205,8 @@ class Horde_SyncML_State {
var $_isAuthorized;
var $_AuthConfirmed;
var $_uri;
var $_uriMeta;
@ -181,7 +219,7 @@ class Horde_SyncML_State {
var $_serverAnchorNext = array(); // written to db after successful sync
var $_clientDeviceInfo = array();
var $_clientDeviceInfo;
// array list of changed items, which need to be synced to the client
var $_changedItems;
@ -192,8 +230,11 @@ class Horde_SyncML_State {
// array list of added items, which need to be synced to the client
var $_addedItems;
// bool flag that we need to more data
var $_syncStatus;
// array list of items, which need to be refreshed at the client
var $_conflictItems;
// current session status
var $_syncStatus = 0;
var $_log = array();
@ -208,6 +249,22 @@ class Horde_SyncML_State {
*/
var $_uidMappings = array();
/**
* Current sync element sent from client.
*
* Stored in state if one element is split into multiple message packets.
*
* @var SyncML_SyncElement
*/
var $curSyncItem;
/**
* Number of sync elements sent to client within current message.
*
* @var _numberOfElements
*/
var $_numberOfElements;
/**
* Creates a new instance of Horde_SyncML_State.
*/
@ -220,7 +277,8 @@ class Horde_SyncML_State {
$this->setPassword($password);
}
$this->isAuthorized = false;
$this->_isAuthorized = false;
$this->_isAuthConfirmed = false;
}
/**
@ -234,7 +292,7 @@ class Horde_SyncML_State {
* retrieve the real egw uid for a given send uid
*/
function getUIDMapping($_sentEgwUid) {
if(isset($this->_uidMappings[$_sentEgwUid])) {
if(strlen("$_sentEgwUid") && isset($this->_uidMappings[$_sentEgwUid])) {
return $this->_uidMappings[$_sentEgwUid];
}
@ -311,6 +369,16 @@ class Horde_SyncML_State {
return false;
}
function &getConflictItems($_type)
{
if(isset($this->_conflictItems[$_type]))
{
return $this->_conflictItems[$_type];
}
return false;
}
function getMoreDataPending()
{
return $this->_moreDataPending;
@ -321,6 +389,14 @@ class Horde_SyncML_State {
return $this->_msgID;
}
function getMaxMsgSizeClient()
{
if (isset($this->_maxMsgSize)) {
return $this->_maxMsgSize;
}
return false;
}
function setWBXML($wbxml)
{
$this->_wbxml = $wbxml;
@ -341,11 +417,39 @@ class Horde_SyncML_State {
$this->_addedItems[$_type] = $_addedItems;
}
function mergeAddedItems($_type, $_addedItems)
{
if (is_array($this->_addedItems[$_type])) {
$this->_addedItems[$_type] = array_merge($this->_addedItems[$_type], $_addedItems);
} else {
$this->_addedItems[$_type] = $_addedItems;
}
}
function pushAddedItem($_type, $_addedItem)
{
$this->_addedItems[$_type][] = $_addedItem;
}
function setChangedItems($_type, $_changedItems)
{
$this->_changedItems[$_type] = $_changedItems;
}
function mergeChangedItems($_type, $_changedItems)
{
if (is_array($this->_changedItems[$_type])) {
$this->_changedItems[$_type] = array_merge($this->_changedItems[$_type], $_changedItems);
} else {
$this->_changedItems[$_type] = $_changedItems;
}
}
function pushChangedItem($_type, $_changedItem)
{
$this->_changedItems[$_type][] = $_changedItem;
}
function setClientDeviceInfo($clientDeviceInfo)
{
$this->_clientDeviceInfo = $clientDeviceInfo;
@ -356,9 +460,14 @@ class Horde_SyncML_State {
$this->_deletedItems[$_type] = $_deletedItems;
}
function setMoreDataPending($_state)
function addConflictItem($_type, $_conflict)
{
$this->_moreDataPending = $_state;
$this->_conflictItems[$_type][] = $_conflict;
}
function clearConflictItems($_type)
{
$this->_conflictItems[$_type] = array();
}
/**
@ -370,6 +479,15 @@ class Horde_SyncML_State {
$this->_msgID = $msgID;
}
/**
* Setter for property maxMsgSize.
* @param size New value of property maxMsgSize.
*/
function setMaxMsgSize($size)
{
$this->_maxMsgSize = $size;
}
/**
* Setter for property locName.
* @param locName New value of property locName.
@ -408,15 +526,20 @@ class Horde_SyncML_State {
{
$this->_version = $version;
if ($version == 0) {
$this->_uri = NAME_SPACE_URI_SYNCML;
$this->_uriMeta = NAME_SPACE_URI_METINF;
$this->_uriDevInf = NAME_SPACE_URI_DEVINF;
} else {
if ($version == 2) {
$this->_uri = NAME_SPACE_URI_SYNCML_1_2;
$this->_uriMeta = NAME_SPACE_URI_METINF_1_2;
$this->_uriDevInf = NAME_SPACE_URI_DEVINF_1_2;
} elseif ($version == 1) {
$this->_uri = NAME_SPACE_URI_SYNCML_1_1;
$this->_uriMeta = NAME_SPACE_URI_METINF_1_1;
$this->_uriDevInf = NAME_SPACE_URI_DEVINF_1_1;
}
} else {
$this->_uri = NAME_SPACE_URI_SYNCML_1_0;
$this->_uriMeta = NAME_SPACE_URI_METINF_1_0;
$this->_uriDevInf = NAME_SPACE_URI_DEVINF_1_0;
}
}
function setSessionID($sessionID)
@ -457,6 +580,16 @@ class Horde_SyncML_State {
return $this->_isAuthorized;
}
function isAuthConfirmed()
{
return $this->_AuthConfirmed;
}
function AuthConfirmed()
{
$this->_AuthConfirmed = true;
}
function clearSync($target)
{
unset($this->_syncs[$target]);
@ -486,6 +619,9 @@ class Horde_SyncML_State {
$targets[] = $target;
}
// Make sure we keep the order
sort($targets);
return $targets;
}
@ -502,6 +638,7 @@ class Horde_SyncML_State {
return '';
}
}
function getURIMeta()
{
return $this->_uriMeta;
@ -548,16 +685,18 @@ class Horde_SyncML_State {
* this->_locName . $this->_sourceURI . $type . $locid so you can
* have different syncs with different devices. If an entry
* already exists, it is overwritten.
* Expired entries can be deleted at the next session start.
*/
function setUID($type, $locid, $guid, $ts=0)
function setUID($type, $locid, $guid, $ts=0, $expired=0)
{
$dt = &$this->getDataTree();
// Set $locid.
$gid = &new DataTreeObject($this->_locName . $this->_sourceURI . $type . $guid);
$gid = new DataTreeObject($this->_locName . $this->_sourceURI . $type . $guid);
$gid->set('type', $type);
$gid->set('locid', $locid);
$gid->set('ts', $ts);
$gid->set('expired', $expired);
$r = $dt->add($gid);
if (is_a($r, 'PEAR_Error')) {
@ -567,7 +706,7 @@ class Horde_SyncML_State {
$this->dieOnError($r, __FILE__, __LINE__);
// Set $globaluid
$lid = &new DataTreeObject($this->_locName . $this->_sourceURI . $type . $locid);
$lid = new DataTreeObject($this->_locName . $this->_sourceURI . $type . $locid);
$lid->set('globaluid', $guid);
$r = $dt->add($lid);
if (is_a($r, 'PEAR_Error')) {
@ -657,54 +796,41 @@ class Horde_SyncML_State {
*/
function adjustContentType($type, $target = null)
{
$ctype;
if (is_array($type))
{
if (is_array($type)) {
$ctype = $type['ContentType'];
$res = $type;
}
else
{
} else {
$ctype = $type;
$res = array();
$res['ContentType'] = $ctype;
}
$deviceInfo = $this->getClientDeviceInfo();
$deviceInfo = $this->getClientDeviceInfo();
$manufacturer = isset($deviceInfo['manufacturer']) ? strtolower($deviceInfo['manufacturer']) : 'unknown';
switch ($manufacturer) {
case 'funambol':
switch (strtolower($deviceInfo['model'])) {
case 'thunderbird':
case 'mozilla plugin':
$res['mayFragment'] = 1;
break;
default:
if (isset($deviceInfo['softwareVersion'])
&& $deviceInfo['softwareVersion'] < 4.0) {
$res['mayFragment'] = 0;
} else {
$res['mayFragment'] = 1;
}
break;
}
break;
default:
$res['mayFragment'] = 1;
break;
}
if (isset($deviceInfo['manufacturer']))
{
switch (strtolower($deviceInfo['manufacturer']))
{
case 'funambol':
if (strtolower($deviceInfo['model']) == 'thunderbird')
{
$res['mayFragment'] = 1;
}
if (isset($deviceInfo['softwareVersion'])
&& $deviceInfo['softwareVersion']{0} == '3')
{
// anything beyond 6.0 supports fragmentation
$res['mayFragment'] = 0;
}
else
{
$res['mayFragment'] = 1;
}
break;
}
}
if (!isset($res['mayFragment']))
{
$res['mayFragment'] = 1;
}
// the funambol specific types need to be encoded in base 64
switch(strtolower($ctype))
{
// the funambol specific types need to be encoded in base64
switch (strtolower($ctype)) {
case 'text/x-s4j-sifc':
case 'text/x-s4j-sife':
case 'text/x-s4j-sift':
@ -712,17 +838,15 @@ class Horde_SyncML_State {
$res['ContentFormat'] = 'b64';
break;
}
return $res;
}
function getPreferedContentType($type)
{
$_type = str_replace('./','',$type);
switch(strtolower($_type))
{
switch (strtolower($_type)) {
case 'contacts':
return 'text/x-vcard';
return 'text/vcard';
break;
case 'notes':
@ -730,9 +854,10 @@ class Horde_SyncML_State {
break;
case 'calendar':
case 'events':
case 'tasks':
case 'caltasks':
return 'text/x-vcalendar';
return 'text/calendar';
break;
case 'sifcalendar':
@ -778,6 +903,7 @@ class Horde_SyncML_State {
return 'tasks';
break;
case 'events':
case 'calendar':
return 'calendar';
break;
@ -815,7 +941,6 @@ class Horde_SyncML_State {
}
}
/**
/**
* Returns the preferred contenttype of the client for the given
* sync data type (database).
@ -826,12 +951,33 @@ class Horde_SyncML_State {
function getPreferedContentTypeClient($_sourceLocURI, $_targetLocURI = null) {
$deviceInfo = $this->getClientDeviceInfo();
if(isset($deviceInfo['dataStore'][$_sourceLocURI]['rxPreference']['contentType']))
{
return $this->adjustContentType($deviceInfo['dataStore'][$_sourceLocURI]['rxPreference']['contentType'], $_targetLocURI);
if(isset($deviceInfo['dataStore'][$_sourceLocURI]['maxGUIDSize']['contentType'])) {
$this->_maxGUIDSize = $deviceInfo['dataStore'][$this->_sourceURI]['maxGUIDSize']['contentType'];
}
Horde::logMessage('SyncML: sourceLocURI ' . $_sourceLocURI .' not found', __FILE__, __LINE__, PEAR_LOG_DEBUG);
if(isset($deviceInfo['dataStore'][$_sourceLocURI]['rxPreference']['contentType']))
{
$ctype = $deviceInfo['dataStore'][$_sourceLocURI]['rxPreference']['contentType'];
$cvers = $deviceInfo['dataStore'][$_sourceLocURI]['rxPreference']['contentVersion'];
$cfrmt = $deviceInfo['dataStore'][$_sourceLocURI]['rxPreference']['contentFormat'];
$cprops = $deviceInfo['dataStore'][$_sourceLocURI]['properties'][$ctype][$cvers];
if (isset($deviceInfo['dataStore'][$_sourceLocURI]['maxGUIDSize'])) {
// get UID properties from maxGUIDSize
$cprops['UID']['Size'] = $deviceInfo['dataStore'][$_sourceLocURI]['maxGUIDSize'];
$cprops['UID']['NoTruncate'] = true;
}
$clientPrefs = array(
'ContentType' => $ctype,
'ContentFormat' => $cfrmt,
'mayFragment' => 1,
'Properties' => $cprops,
);
#Horde::logMessage('SyncML: sourceLocURI ' . $_sourceLocURI . " clientPrefs:\n"
# . print_r($clientPrefs, true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
return $this->adjustContentType($clientPrefs, $_targetLocURI);
}
Horde::logMessage('SyncML: sourceLocURI ' . $_sourceLocURI . " not found:\n" . print_r($deviceInfo, true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
if ($_targetLocURI != null)
{
@ -841,6 +987,19 @@ class Horde_SyncML_State {
return PEAR::raiseError(_('sourceLocURI not found'));
}
/**
* Returns the MaxGUIDSize of the client
*/
function getMaxGUIDSizeClient() {
$maxGUIDSize = MAX_GUID_SIZE;
if (isset($this->_maxGUIDSize)) {
$maxGUIDSize = $this->_maxGUIDSize;
}
return $maxGUIDSize;
}
function setClientAnchorNext($type, $a)
{
$this->_clientAnchorNext[$type] = $a;
@ -900,6 +1059,11 @@ class Horde_SyncML_State {
*/
function getClientDeviceInfo()
{
if (isset($this->_clientDeviceInfo) && is_array($this->_clientDeviceInfo)) {
// use cached information
return $this->_clientDeviceInfo;
}
$dt = &$this->getDataTree();
$id = $dt->getId($this->_locName . $this->_sourceURI . 'deviceInfo');
@ -907,7 +1071,7 @@ class Horde_SyncML_State {
return false;
}
$info = $dt->getObjectById($id);
$info = $dt->getObjectById($id);
return $info->get('ClientDeviceInfo');
}
@ -926,7 +1090,7 @@ class Horde_SyncML_State {
$s = $this->_locName . $this->_sourceURI . 'deviceInfo';
// Set $locid.
$info = &new DataTreeObject($s);
$info = new DataTreeObject($s);
$info->set('ClientDeviceInfo', $this->_clientDeviceInfo);
$r = $dt->add($info);
if (is_a($r, 'PEAR_Error')) {
@ -952,7 +1116,7 @@ class Horde_SyncML_State {
$s = $this->_locName . $this->_sourceURI . $type . 'syncSummary';
// Set $locid.
$info = &new DataTreeObject($s);
$info = new DataTreeObject($s);
$info->set('ClientAnchor', $this->_clientAnchorNext);
$info->set('ServerAnchor', $this->_serverAnchorNext);
$r = $dt->add($info);
@ -1070,4 +1234,23 @@ class Horde_SyncML_State {
$this->_receivedAlert222 = (bool)$_status;
}
function incNumberOfElements() {
$this->_numberOfElements++;
}
function clearNumberOfElements() {
$this->_numberOfElements = 0;
}
function getNumberOfElements() {
if (isset($this->_numberOfElements)) {
return $this->_numberOfElements;
} else {
return false;
}
}
function maxNumberOfElements() {
unset($this->_numberOfElements);
}
}

View File

@ -1,14 +1,18 @@
<?php
/**
* eGroupWare SyncML
* eGroupWare - SyncML based on Horde 3
*
*
* Using the PEAR Log class (which need to be installed!)
*
* @link http://www.egroupware.org
* @author Lars Kneschke
* @package syncml
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage horde
* @author Lars Kneschke <lkneschke@egroupware.org>
* @author Joerg Lehrke <jlehrke@noc.de>
* @version $Id$
*/
include_once dirname(__FILE__).'/State.php';
/**
@ -17,45 +21,42 @@ include_once dirname(__FILE__).'/State.php';
class EGW_SyncML_State extends Horde_SyncML_State
{
var $table_devinfo = 'egw_syncmldevinfo';
/*
* store the mappings of egw uids to client uids
*/
var $uidMappings = array();
/**
* get the local content id from a syncid
*
* @param sting $_syncid id used in syncml
* @return int local egw content id
*/
function get_egwID($_syncid)
{
/**
* get the local content id from a syncid
*
* @param sting $_syncid id used in syncml
* @return int local egw content id
*/
function get_egwID($_syncid) {
$syncIDParts = explode('-',$_syncid);
array_shift($syncIDParts);
$_id = implode ('', $syncIDParts);
return $_id;
}
/**
* when got a entry last added/modified/deleted
*
* @param $_syncid containing appName-contentid
* @param $_action string can be add, delete or modify
* @return string the last timestamp
*/
function getSyncTSforAction($_syncid, $_action)
{
}
/**
* when got a entry last added/modified/deleted
*
* @param $_syncid containing appName-contentid
* @param $_action string can be add, delete or modify
* @return string the last timestamp
*/
function getSyncTSforAction($_syncid, $_action) {
$syncIDParts = explode('-',$_syncid);
$_appName = array_shift($syncIDParts);
$_id = implode ('', $syncIDParts);
$ts = $GLOBALS['egw']->contenthistory->getTSforAction($_appName, $_id, $_action);
return $ts;
}
}
/**
* get the timestamp for action
*
@ -63,57 +64,94 @@ class EGW_SyncML_State extends Horde_SyncML_State
*
* @param string$_appName the appname example: infolog_notes
* @param string $_action can be modify, add or delete
* @param string $_ts timestamp where to start searching from
* @param string $_ts timestamp where to start searching from
* @return array containing syncIDs with changes
*/
function getHistory($_appName, $_action, $_ts)
{
function getHistory($_appName, $_action, $_ts) {
$guidList = array ();
$syncIdList = array ();
$idList = $GLOBALS['egw']->contenthistory->getHistory($_appName, $_action, $_ts);
foreach ($idList as $idItem)
{
$syncIdList[] = $_appName . '-' . $idItem;
if ($idItem) // ignore inconsistent entries
{
$syncIdList[] = $_appName . '-' . $idItem;
}
}
return $syncIdList;
return $syncIdList;
}
/**
* Returns the timestamp (if set) of the last change to the
* obj:guid, that was caused by the client. This is stored to
* avoid mirroring these changes back to the client.
*/
function getChangeTS($type, $guid)
{
$mapID = $this->_locName . $this->_sourceURI . $type;
#Horde::logMessage('SyncML: getChangeTS for ' . $mapID .' / '. $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG);
if ($ts = $GLOBALS['egw']->db->select('egw_contentmap', 'map_timestamp', array(
'map_id' => $mapID,
'map_guid' => $guid,
), __LINE__, __FILE__, false, '', 'syncml')->fetchSingle())
{
#Horde::logMessage('SyncML: getChangeTS changets is ' . $GLOBALS['egw']->db->from_timestamp($ts), __FILE__, __LINE__, PEAR_LOG_DEBUG);
return $GLOBALS['egw']->db->from_timestamp($ts);
}
return false;
}
/**
* Retrieves information about the clients device info if any. Returns
* false if no info found or a DateTreeObject with at least the
* following attributes:
*
* a array containing all available infos about the device
*/
function getClientDeviceInfo()
{
if(($deviceID = $GLOBALS['egw']->db->select('egw_syncmldeviceowner', 'owner_devid',array (
'owner_locname' => $this->_locName,
'owner_deviceid' => $this->_sourceURI,
), __LINE__, __FILE__, false, '', 'syncml')->fetchSingle()))
* Returns the timestamp (if set) of the last change to the
* obj:guid, that was caused by the client. This is stored to
* avoid mirroring these changes back to the client.
*/
function getChangeTS($type, $guid) {
$mapID = $this->_locName . $this->_sourceURI . $type;
#Horde::logMessage('SyncML: getChangeTS for ' . $mapID
# . ' / '. $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG);
if ($ts = $GLOBALS['egw']->db->select('egw_contentmap', 'map_timestamp',
array(
'map_id' => $mapID,
'map_guid' => $guid,
), __LINE__, __FILE__, false, '', 'syncml')->fetchColumn()) {
#Horde::logMessage('SyncML: getChangeTS changets is '
# . $GLOBALS['egw']->db->from_timestamp($ts),
# __FILE__, __LINE__, PEAR_LOG_DEBUG);
return $GLOBALS['egw']->db->from_timestamp($ts);
}
return false;
}
/**
* Returns the exceptions for a GUID which the client knows of
*/
function getGUIDExceptions($type, $guid) {
$mapID = $this->_locName . $this->_sourceURI . $type;
#Horde::logMessage('SyncML: getChangeTS for ' . $mapID
# . ' / '. $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG);
$guid_exceptions = array();
$where = array ('map_id' => $mapID,);
$where[] = "map_guid LIKE '$guid" . ":%'";
// Fetch all exceptions which the client knows of
foreach ($GLOBALS['egw']->db->select('egw_contentmap', 'map_guid', $where,
__LINE__,__FILE__, false, '', 'syncml') as $row)
{
$parts = preg_split('/:/', $row['map_guid']);
$Id = $parts[0];
$extension = $parts[1];
$guid_exceptions[$extension] = $row['map_guid'];
}
return $guid_exceptions;
}
/**
* Retrieves information about the clients device info if any. Returns
* false if no info found or a DateTreeObject with at least the
* following attributes:
*
* a array containing all available infos about the device
*/
function getClientDeviceInfo() {
#Horde::logMessage("SyncML: getClientDeviceInfo " . $this->_locName
# . ", " . $this->_sourceURI, __FILE__, __LINE__, PEAR_LOG_DEBUG);
if (isset($this->_clientDeviceInfo)
&& is_array($this->_clientDeviceInfo)) {
// use cached information
return $this->_clientDeviceInfo;
}
if (($deviceID = $GLOBALS['egw']->db->select('egw_syncmldeviceowner',
'owner_devid',
array (
'owner_locname' => $this->_locName,
'owner_deviceid' => $this->_sourceURI,
), __LINE__, __FILE__, false, '', 'syncml')->fetchColumn())) {
$cols = array(
'dev_dtdversion',
'dev_numberofchanges',
@ -129,156 +167,161 @@ class EGW_SyncML_State extends Horde_SyncML_State
'dev_utc',
);
#Horde::logMessage("SyncML: getClientDeviceInfo $deviceID", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$where = array(
'dev_id' => $deviceID,
'dev_id' => $deviceID,
);
if (($row = $GLOBALS['egw']->db->select('egw_syncmldevinfo', $cols, $where, __LINE__, __FILE__, false, '', 'syncml')->fetch()))
{
return array (
'DTDVersion' => $row['dev_dtdversion'],
'supportNumberOfChanges'=> $row['dev_numberofchanges'],
'supportLargeObjs' => $row['dev_largeobjs'],
'UTC' => $row['dev_utc'],
'softwareVersion' => $row['dev_swversion'],
'hardwareVersion' => $row['dev_hwversion'],
'firmwareVersion' => $row['dev_fwversion'],
'oem' => $row['dev_oem'],
'model' => $row['dev_model'],
'manufacturer' => $row['dev_manufacturer'],
'deviceType' => $row['dev_devicetype'],
'dataStore' => unserialize($row['dev_datastore']),
if (($row = $GLOBALS['egw']->db->select('egw_syncmldevinfo',
$cols, $where, __LINE__, __FILE__, false, '', 'syncml')->fetch())) {
$deviceMaxEntries = 'maxEntries-' . $this->_sourceURI;
$deviceUIDExtension = 'uidExtension-' . $this->_sourceURI;
$deviceNonBlockingAllday = 'nonBlockingAllday-' . $this->_sourceURI;
$deviceTimezone = 'tzid-' . $this->_sourceURI;
$syncml_prefs = $GLOBALS['egw_info']['user']['preferences']['syncml'];
$this->_clientDeviceInfo = array (
'DTDVersion' => $row['dev_dtdversion'],
'supportNumberOfChanges' => $row['dev_numberofchanges'],
'supportLargeObjs' => $row['dev_largeobjs'],
'UTC' => $row['dev_utc'],
'softwareVersion' => $row['dev_swversion'],
'hardwareVersion' => $row['dev_hwversion'],
'firmwareVersion' => $row['dev_fwversion'],
'oem' => $row['dev_oem'],
'model' => $row['dev_model'],
'manufacturer' => $row['dev_manufacturer'],
'deviceType' => $row['dev_devicetype'],
'maxMsgSize' => $this->_maxMsgSize,
'maxEntries' => $syncml_prefs[$deviceMaxEntries],
'uidExtension' => $syncml_prefs[$deviceUIDExtension],
'nonBlockingAllday' => $syncml_prefs[$deviceNonBlockingAllday],
'tzid' => $syncml_prefs[$deviceTimezone],
'dataStore' => unserialize($row['dev_datastore']),
);
return $this->_clientDeviceInfo;
}
}
return false;
}
/**
* returns GUIDs of all client items
*/
function _getClientItems($type)
{
$mapID = $this->_locName . $this->_sourceURI . $type;
* returns GUIDs of all client items
*/
function getClientItems() {
$mapID = $this->_locName . $this->_sourceURI . $this->_targetURI;
$guids = array();
foreach($GLOBALS['egw']->db->select('egw_contentmap', 'map_guid', array(
'map_id' => $mapID,
'map_expired' => 0,
), __LINE__, __FILE__, false, '', 'syncml') as $row)
{
$guids[] = $row['map_guid'];
}
return $guids ? $guids : false;
$guids = array();
foreach($GLOBALS['egw']->db->select('egw_contentmap', 'map_guid', array(
'map_id' => $mapID,
'map_expired' => false,
), __LINE__, __FILE__, false, '', 'syncml') as $row) {
$guids[] = $row['map_guid'];
}
return $guids ? $guids : false;
}
/**
* Retrieves the Horde server guid (like
* kronolith:0d1b415fc124d3427722e95f0e926b75) for a given client
* locid. Returns false if no such id is stored yet.
*
* Opposite of getLocId which returns the locid for a given guid.
*/
function getGlobalUID($type, $locid)
{
$mapID = $this->_locName . $this->_sourceURI . $type;
/**
* Retrieves the Horde server guid (like
* kronolith:0d1b415fc124d3427722e95f0e926b75) for a given client
* locid. Returns false if no such id is stored yet.
*
* Opposite of getLocId which returns the locid for a given guid.
*/
function getGlobalUID($type, $locid) {
$mapID = $this->_locName . $this->_sourceURI . $type;
#Horde::logMessage('SyncML: search GlobalUID for ' . $mapID .' / '.$locid, __FILE__, __LINE__, PEAR_LOG_DEBUG);
#Horde::logMessage('SyncML: search GlobalUID for ' . $mapID .' / '.$locid, __FILE__, __LINE__, PEAR_LOG_DEBUG);
return $GLOBALS['egw']->db->select('egw_contentmap', 'map_guid', array(
'map_id' => $mapID,
'map_locuid' => $locid,
'map_expired' => 0,
), __LINE__, __FILE__, false, '', 'syncml')->fetchSingle();
}
return $GLOBALS['egw']->db->select('egw_contentmap', 'map_guid',
array(
'map_id' => $mapID,
'map_locuid' => $locid,
'map_expired' => false,
), __LINE__, __FILE__, false, '', 'syncml')->fetchColumn();
}
/**
* Converts a EGW GUID (like
* kronolith:0d1b415fc124d3427722e95f0e926b75) to a client ID as
* used by the sync client (like 12) returns false if no such id
* is stored yet.
*/
function getLocID($type, $guid)
{
$mapID = $this->_locName . $this->_sourceURI . $type;
/**
* Converts a EGW GUID (like
* kronolith:0d1b415fc124d3427722e95f0e926b75) to a client ID as
* used by the sync client (like 12) returns false if no such id
* is stored yet.
*/
function getLocID($type, $guid) {
$mapID = $this->_locName . $this->_sourceURI . $type;
Horde::logMessage('SyncML: search LocID for ' . $mapID .' / '.$guid, __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde::logMessage('SyncML: search LocID for ' . $mapID . ' / ' . $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG);
if (($locuid = $GLOBALS['egw']->db->select('egw_contentmap', 'map_locuid', array(
'map_id' => $mapID,
'map_guid' => $guid
), __LINE__, __FILE__, false, '', 'syncml')->fetchSingle()))
{
Horde::logMessage('SyncML: found LocID: '.$locuid, __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
return $locuid;
}
if (($locuid = $GLOBALS['egw']->db->select('egw_contentmap', 'map_locuid', array(
'map_id' => $mapID,
'map_guid' => $guid
), __LINE__, __FILE__, false, '', 'syncml')->fetchColumn())) {
Horde::logMessage('SyncML: found LocID: '.$locuid, __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
return $locuid;
}
/**
* Retrieves information about the previous sync if any. Returns
* false if no info found or a DateTreeObject with at least the
* following attributes:
*
* ClientAnchor: the clients Next Anchor of the previous sync.
* ServerAnchor: the Server Next Anchor of the previous sync.
*/
function getSyncSummary($type)
{
/**
* Retrieves information about the previous sync if any. Returns
* false if no info found or a DateTreeObject with at least the
* following attributes:
*
* ClientAnchor: the clients Next Anchor of the previous sync.
* ServerAnchor: the Server Next Anchor of the previous sync.
*/
function getSyncSummary($type) {
$deviceID = $this->_locName . $this->_sourceURI;
#Horde::logMessage("SyncML: get SYNCSummary for $deviceID", __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde::logMessage("SyncML: getSyncSummary for $deviceID", __FILE__, __LINE__, PEAR_LOG_DEBUG);
if (($row = $GLOBALS['egw']->db->select('egw_syncmlsummary', array('sync_serverts','sync_clientts'), array(
'dev_id' => $deviceID,
'sync_path' => $type
), __LINE__, __FILE__, false, '', 'syncml')->fetch()))
{
#Horde::logMessage("SyncML: get SYNCSummary for $deviceID serverts: ".$row['sync_serverts']." clients: ".$row['sync_clientts'], __FILE__, __LINE__, PEAR_LOG_DEBUG);
return array(
'ClientAnchor' => $row['sync_clientts'],
'ServerAnchor' => $row['sync_serverts'],
);
}
return false;
}
if (($row = $GLOBALS['egw']->db->select('egw_syncmlsummary', array('sync_serverts','sync_clientts'), array(
'dev_id' => $deviceID,
'sync_path' => $type
), __LINE__, __FILE__, false, '', 'syncml')->fetch())) {
Horde::logMessage("SyncML: getSyncSummary for $deviceID serverts: ".$row['sync_serverts']." clients: ".$row['sync_clientts'], __FILE__, __LINE__, PEAR_LOG_DEBUG);
return array(
'ClientAnchor' => $row['sync_clientts'],
'ServerAnchor' => $row['sync_serverts'],
);
}
return false;
}
function isAuthorized()
{
if (!$this->_isAuthorized)
{
if(!isset($this->_locName) && !isset($this->_password))
{
function isAuthorized() {
if (!$this->_isAuthorized) {
if(!isset($this->_locName) && !isset($this->_password)) {
Horde::logMessage('SyncML: Authentication not yet possible currently. Username and password not available' , __FILE__, __LINE__, PEAR_LOG_DEBUG);
return FALSE;
}
if(!isset($this->_password))
{
if(!isset($this->_password)) {
Horde::logMessage('SyncML: Authentication not yet possible currently. Password not available' , __FILE__, __LINE__, PEAR_LOG_DEBUG);
return FALSE;
}
if(strpos($this->_locName,'@') === False)
{
if(strpos($this->_locName,'@') === False) {
$this->_locName .= '@'.$GLOBALS['egw_info']['server']['default_domain'];
} else {
$parts = explode('@',$this->_locName);
$GLOBALS['egw_info']['user']['domain'] = array_pop($parts);
}
#Horde::logMessage('SyncML: authenticate with username: ' . $this->_locName . ' and password: ' . $this->_password, __FILE__, __LINE__, PEAR_LOG_DEBUG);
if($GLOBALS['sessionid'] = $GLOBALS['egw']->session->create($this->_locName,$this->_password,'text'))
{
$this->_isAuthorized = true;
Horde::logMessage('SyncML_EGW: Authentication of ' . $this->_locName . '/' . $GLOBALS['sessionid'] . ' succeded' , __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
else
{
if($GLOBALS['sessionid'] = $GLOBALS['egw']->session->create($this->_locName,$this->_password,'text')) {
if ($GLOBALS['egw_info']['user']['apps']['syncml']) {
$this->_isAuthorized = true;
Horde::logMessage('SyncML_EGW: Authentication of ' . $this->_locName . '/' . $GLOBALS['sessionid'] . ' succeded' , __FILE__, __LINE__, PEAR_LOG_DEBUG);
} else {
$this->_isAuthorized = false;
Horde::logMessage('SyncML is not enabled for this user', __FILE__, __LINE__, PEAR_LOG_ERROR);
}
} else {
$this->_isAuthorized = false;
Horde::logMessage('SyncML: Authentication of ' . $this->_locName . ' failed' , __FILE__, __LINE__, PEAR_LOG_INFO);
}
}
else
{
} else {
// store sessionID in a variable, because ->verify maybe resets that value
$sessionID = session_id();
if(!$GLOBALS['egw']->session->verify($sessionID, 'staticsyncmlkp3')) {
@ -290,11 +333,10 @@ class EGW_SyncML_State extends Horde_SyncML_State
}
/**
* Removes all locid<->guid mappings for the given type.
* Returns always true.
*/
function removeAllUID($type)
{
* Removes all locid<->guid mappings for the given type.
* Returns always true.
*/
function removeAllUID($type) {
$mapID = $this->_locName . $this->_sourceURI . $type;
Horde::logMessage("SyncML: state->removeAllUID(type=$type)", __FILE__, __LINE__, PEAR_LOG_DEBUG);
@ -302,17 +344,16 @@ class EGW_SyncML_State extends Horde_SyncML_State
$GLOBALS['egw']->db->delete('egw_contentmap', array('map_id' => $mapID), __LINE__, __FILE__, 'syncml');
return true;
}
}
/**
* Used in SlowSync
* Removes all locid<->guid mappings for the given type,
* that are older than $ts.
*
* Returns always true.
*/
function removeOldUID($type, $ts)
{
* Used in SlowSync
* Removes all locid<->guid mappings for the given type,
* that are older than $ts.
*
* Returns always true.
*/
function removeOldUID($type, $ts) {
$mapID = $this->_locName . $this->_sourceURI . $type;
$where[] = "map_id = '".$mapID."' AND map_timestamp < '".$GLOBALS['egw']->db->to_timestamp($ts)."'";
@ -324,102 +365,151 @@ class EGW_SyncML_State extends Horde_SyncML_State
}
/**
* Removes the locid<->guid mapping for the given locid. Returns
* the guid that was removed or false if no mapping entry was
* found.
*/
function removeUID($type, $locid)
{
* Used at session end to cleanup expired entries
* Removes all locid<->guid mappings for the given type,
* that are marked as expired and older than $ts.
*
* Returns always true.
*/
function removeExpiredUID($type, $ts) {
$mapID = $this->_locName . $this->_sourceURI . $type;
$where['map_id'] = $mapID;
$where['map_expired'] = true;
$where[] = "map_timestamp <= '".$GLOBALS['egw']->db->to_timestamp($ts)."'";
Horde::logMessage("SyncML: state->removeExpiredUID(type=$type)",
__FILE__, __LINE__, PEAR_LOG_DEBUG);
$GLOBALS['egw']->db->delete('egw_contentmap', $where,
__LINE__, __FILE__, 'syncml');
return true;
}
/**
* Check if an entry is already expired
*
* Returns true for expired mappings.
*/
function isExpiredUID($type, $locid) {
$mapID = $this->_locName . $this->_sourceURI . $type;
$expired = false;
$where = array(
'map_id' => $mapID,
'map_locuid' => $locid,
);
if (($expired = $GLOBALS['egw']->db->select('egw_contentmap', 'map_expired',
$where, __LINE__, __FILE__, false, '', 'syncml')->fetchColumn())) {
Horde::logMessage('SyncML: found LocID: '. $locid,
__FILE__, __LINE__, PEAR_LOG_DEBUG);
}
return $expired;
}
/**
* Removes the locid<->guid mapping for the given locid. Returns
* the guid that was removed or false if no mapping entry was
* found.
*/
function removeUID($type, $locid) {
$mapID = $this->_locName . $this->_sourceURI . $type;
$where = array (
'map_id' => $mapID,
'map_locuid' => $locid
);
if (!($guid = $GLOBALS['egw']->db->select('egw_contentmap', 'map_guid', $where, __LINE__, __FILE__, false, '', 'syncml')->fetchSingle()))
{
Horde::logMessage("SyncML: state->removeUID(type=$type,locid=$locid) : nothing to remove", __FILE__, __LINE__, PEAR_LOG_INFO);
if (!($guid = $GLOBALS['egw']->db->select('egw_contentmap', 'map_guid', $where,
__LINE__, __FILE__, false, '', 'syncml')->fetchColumn())) {
Horde::logMessage("SyncML: state->removeUID(type=$type,locid=$locid)"
. " nothing to remove", __FILE__, __LINE__, PEAR_LOG_INFO);
return false;
}
Horde::logMessage("SyncML: state->removeUID(type=$type,locid=$locid) : removing guid:$guid", __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde::logMessage("SyncML: state->removeUID(type=$type,locid=$locid): "
. "removing guid $guid", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$GLOBALS['egw']->db->delete('egw_contentmap', $where, __LINE__, __FILE__, 'syncml');
return $guid;
}
/**
* Puts a given client $locid and Horde server $guid pair into the
* map table to allow mapping between the client's and server's
* IDs. Actually there are two maps: from the localid to the guid
* and vice versa. The localid is converted to a key as follows:
* this->_locName . $this->_sourceURI . $type . $locid so you can
* have different syncs with different devices. If an entry
* already exists, it is overwritten.
*/
function setUID($type, $locid, $_guid, $ts=0)
{
#Horde::logMessage("SyncML: setUID $type, $locid, $guid, $ts ", __FILE__, __LINE__, PEAR_LOG_DEBUG);
#Horde::logMessage("SyncML: setUID ". $this->getUIDMapping($guid), __FILE__, __LINE__, PEAR_LOG_DEBUG);
// problem: entries created from client, come here with the (long) server guid,
// but getUIDMapping does not know them and can not map server-guid <--> client guid
$guid = $this->getUIDMapping($_guid);
if($guid === false)
{
// this message is not really usefull here because setUIDMapping is only called when adding content to the client,
// however setUID is called also when adding content from the client. So in all other conditions this
// message will be logged.
Horde::logMessage("SyncML: setUID $type, $locid, $guid something went wrong!!! Mapping not found.", __FILE__, __LINE__, PEAR_LOG_INFO);
$guid = $_guid;
//return false;
}
Horde::logMessage("SyncML: setUID $_guid => $guid", __FILE__, __LINE__, PEAR_LOG_DEBUG);
/**
* Puts a given client $locid and Horde server $guid pair into the
* map table to allow mapping between the client's and server's
* IDs. Actually there are two maps: from the localid to the guid
* and vice versa. The localid is converted to a key as follows:
* this->_locName . $this->_sourceURI . $type . $locid so you can
* have different syncs with different devices. If an entry
* already exists, it is overwritten.
* Expired entries can be deleted at the next session start.
*/
function setUID($type, $locid, $_guid, $ts=0, $expired=false) {
#Horde::logMessage("SyncML: setUID $type, $locid, $_guid, $ts ", __FILE__, __LINE__, PEAR_LOG_DEBUG);
if(!$ts) $ts = time();
if (!strlen("$_guid")) {
// We can't handle this case otherwise
return;
}
Horde::logMessage("SyncML: setUID $type, $locid, $guid, $ts ", __FILE__, __LINE__, PEAR_LOG_DEBUG);
// problem: entries created from client, come here with the (long) server guid,
// but getUIDMapping does not know them and can not map server-guid <--> client guid
$guid = $this->getUIDMapping($_guid);
if($guid === false) {
// this message is not really usefull here because setUIDMapping is only called when adding content to the client,
// however setUID is called also when adding content from the client. So in all other conditions this
// message will be logged.
//Horde::logMessage("SyncML: setUID $type, $locid, $guid something went wrong!!! Mapping not found.", __FILE__, __LINE__, PEAR_LOG_INFO);
$guid = $_guid;
//return false;
}
#Horde::logMessage("SyncML: setUID $_guid => $guid", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$mapID = $this->_locName . $this->_sourceURI . $type;
if(!$ts) $ts = time();
// delete all client id's
$where = array(
'map_id' => $mapID,
'map_locuid' => $locid,
);
$GLOBALS['egw']->db->delete('egw_contentmap', $where, __LINE__, __FILE__, 'syncml');
Horde::logMessage("SyncML: setUID $type, $locid, $guid, $ts ",
__FILE__, __LINE__, PEAR_LOG_DEBUG);
// delete all egw id's
$where = array(
'map_id' => $mapID,
'map_guid' => $guid,
);
$GLOBALS['egw']->db->delete('egw_contentmap', $where, __LINE__, __FILE__, 'syncml');
$mapID = $this->_locName . $this->_sourceURI . $type;
$data = $where + array(
'map_locuid' => $locid,
'map_timestamp' => $ts,
'map_expired' => 0,
);
$GLOBALS['egw']->db->insert('egw_contentmap', $data, $where, __LINE__, __FILE__, 'syncml');
// expire all client id's
$where = array(
'map_id' => $mapID,
'map_locuid' => $locid,
);
$data = array (
'map_expired' => true,
);
$GLOBALS['egw']->db->delete('egw_contentmap', $where,
__LINE__, __FILE__, 'syncml');
#Horde::logMessage("SyncML: setUID $type, $locid, $guid, $ts $mapID", __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
// delete all egw id's
$where = array(
'map_id' => $mapID,
'map_guid' => $guid,
);
$GLOBALS['egw']->db->delete('egw_contentmap', $where,
__LINE__, __FILE__, 'syncml');
$data = $where + array(
'map_locuid' => $locid,
'map_timestamp' => $ts,
'map_expired' => ($expired ? true : false),
);
$GLOBALS['egw']->db->insert('egw_contentmap', $data, $where,
__LINE__, __FILE__, 'syncml');
}
/**
* writes clients deviceinfo into database
*/
function writeClientDeviceInfo()
{
if (!isset($this->_clientDeviceInfo) || !is_array($this->_clientDeviceInfo))
{
* writes clients deviceinfo into database
*/
function writeClientDeviceInfo() {
if (!isset($this->_clientDeviceInfo)
|| !is_array($this->_clientDeviceInfo)) {
return false;
}
if(!isset($this->size_dev_hwversion))
{
if(!isset($this->size_dev_hwversion)) {
$tableDefDevInfo = $GLOBALS['egw']->db->get_table_definitions('syncml',$this->table_devinfo);
$this->size_dev_hwversion = $tableDefDevInfo['fd']['dev_hwversion']['precision'];
unset($tableDefDevInfo);
@ -437,77 +527,89 @@ class EGW_SyncML_State extends Horde_SyncML_State
'dev_fwversion' => $firmwareVersion,
);
if (($deviceID = $GLOBALS['egw']->db->select('egw_syncmldevinfo', 'dev_id', $where, __LINE__, __FILE__, false, '', 'syncml')->fetchSingle()))
{
if (($deviceID = $GLOBALS['egw']->db->select('egw_syncmldevinfo', 'dev_id', $where,
__LINE__, __FILE__, false, '', 'syncml')->fetchColumn())) {
$data = array (
'dev_datastore' => serialize($this->_clientDeviceInfo['dataStore']),
);
$GLOBALS['egw']->db->update('egw_syncmldevinfo', $data, $where, __LINE__, __FILE__, 'syncml');
}
else
{
$GLOBALS['egw']->db->update('egw_syncmldevinfo', $data, $where,
__LINE__, __FILE__, 'syncml');
} else {
$data = array (
'dev_dtdversion' => $this->_clientDeviceInfo['DTDVersion'],
'dev_numberofchanges' => $this->_clientDeviceInfo['supportNumberOfChanges'] ? true : false,
'dev_largeobjs' => $this->_clientDeviceInfo['supportLargeObjs'] ? true : false,
'dev_utc' => $this->_clientDeviceInfo['UTC'] ? true : false,
'dev_swversion' => $softwareVersion,
'dev_hwversion' => $hardwareVersion,
'dev_fwversion' => $firmwareVersion,
'dev_oem' => $this->_clientDeviceInfo['oem'],
'dev_model' => $this->_clientDeviceInfo['model'],
'dev_manufacturer' => $this->_clientDeviceInfo['manufacturer'],
'dev_devicetype' => $this->_clientDeviceInfo['deviceType'],
'dev_datastore' => serialize($this->_clientDeviceInfo['dataStore']),
'dev_dtdversion' => $this->_clientDeviceInfo['DTDVersion'],
'dev_numberofchanges' => ($this->_clientDeviceInfo['supportNumberOfChanges'] ? true : false),
'dev_largeobjs' => ($this->_clientDeviceInfo['supportLargeObjs'] ? true : false),
'dev_utc' => ($this->_clientDeviceInfo['UTC'] ? true : false),
'dev_swversion' => $softwareVersion,
'dev_hwversion' => $hardwareVersion,
'dev_fwversion' => $firmwareVersion,
'dev_oem' => $this->_clientDeviceInfo['oem'],
'dev_model' => $this->_clientDeviceInfo['model'],
'dev_manufacturer' => $this->_clientDeviceInfo['manufacturer'],
'dev_devicetype' => $this->_clientDeviceInfo['deviceType'],
'dev_datastore' => serialize($this->_clientDeviceInfo['dataStore']),
);
$GLOBALS['egw']->db->insert('egw_syncmldevinfo', $data, $where, __LINE__, __FILE__, 'syncml');
$deviceID = $GLOBALS['egw']->db->get_last_insert_id('egw_syncmldevinfo', 'dev_id');
}
$data = array (
'owner_locname' => $this->_locName,
'owner_deviceid' => $this->_sourceURI,
'owner_devid' => $deviceID,
);
$where = array (
'owner_locname' => $this->_locName,
'owner_deviceid' => $this->_sourceURI,
);
$GLOBALS['egw']->db->insert('egw_syncmldeviceowner', $data, $where, __LINE__, __FILE__, 'syncml');
if ($GLOBALS['egw']->db->select('egw_syncmldeviceowner', 'owner_devid', $where,
__LINE__, __FILE__, false, '', 'syncml')->fetchColumn()) {
$data = array (
'owner_devid' => $deviceID,
);
$GLOBALS['egw']->db->update('egw_syncmldeviceowner', $data, $where,
__LINE__, __FILE__, 'syncml');
} else {
$data = array (
'owner_locname' => $this->_locName,
'owner_deviceid' => $this->_sourceURI,
'owner_devid' => $deviceID,
);
$GLOBALS['egw']->db->insert('egw_syncmldeviceowner', $data, $where,
__LINE__, __FILE__, 'syncml');
}
}
/**
* After a successful sync, the client and server's Next Anchors
* are written to the database so they can be used to negotiate
* upcoming syncs.
*/
function writeSyncSummary()
{
#parent::writeSyncSummary();
/**
* After a successful sync, the client and server's Next Anchors
* are written to the database so they can be used to negotiate
* upcoming syncs.
*/
function writeSyncSummary() {
#parent::writeSyncSummary();
if (!isset($this->_serverAnchorNext) || !is_array($this->_serverAnchorNext))
{
return;
}
if (!isset($this->_serverAnchorNext)
|| !is_array($this->_serverAnchorNext)) {
return;
}
$deviceID = $this->_locName . $this->_sourceURI;
$deviceID = $this->_locName . $this->_sourceURI;
foreach((array)$this->_serverAnchorNext as $type => $a)
{
Horde::logMessage("SyncML: write SYNCSummary for $deviceID $type serverts: $a clients: ".$this->_clientAnchorNext[$type], __FILE__, __LINE__, PEAR_LOG_DEBUG);
foreach((array)$this->_serverAnchorNext as $type => $a) {
Horde::logMessage("SyncML: write SYNCSummary for $deviceID "
. "$type serverts: $a clients: "
. $this->_clientAnchorNext[$type],
__FILE__, __LINE__, PEAR_LOG_DEBUG);
$where = array(
'dev_id' => $deviceID,
'sync_path' => $type,
);
$where = array(
'dev_id' => $deviceID,
'sync_path' => $type,
);
$data = array(
'sync_serverts' => $a,
'sync_clientts' => $this->_clientAnchorNext[$type]
);
$data = array(
'sync_serverts' => $a,
'sync_clientts' => $this->_clientAnchorNext[$type]
);
$GLOBALS['egw']->db->insert('egw_syncmlsummary', $data, $where, __LINE__, __FILE__, 'syncml');
}
}
$GLOBALS['egw']->db->insert('egw_syncmlsummary', $data, $where,
__LINE__, __FILE__, 'syncml');
}
}
}

View File

@ -1,230 +1,466 @@
<?php
/**
* $Horde: framework/SyncML/SyncML/Sync.php,v 1.7 2004/09/14 04:27:05 chuck Exp $
* eGroupWare - SyncML based on Horde 3
*
* Copyright 2003-2004 Anthony Mills <amills@pyramid6.com>
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
* Using the PEAR Log class (which need to be installed!)
*
* @author Anthony Mills <amills@pyramid6.com>
* @version $Revision$
* @since Horde 3.0
* @package Horde_SyncML
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage horde
* @author Anthony Mills <amills@pyramid6.com>
* @author Joerg Lehrke <jlehrke@noc.de>
* @copyright (c) The Horde Project (http://www.horde.org/)
* @version $Id$
*/
class Horde_SyncML_Sync {
/**
* Target, either contacts, notes, events,
*/
var $_targetLocURI;
/**
* Target, either contacts, notes, events,
*/
var $_targetLocURI;
var $_sourceLocURI;
var $_sourceLocURI;
/**
* Return if all commands success.
*/
var $globalSuccess;
/**
* This is the content type to use to export data.
*/
var $preferedContentType;
/**
* Do have the sync data loaded from the database already?
*/
var $syncDataLoaded;
function &factory($alert)
{
Horde::logMessage('SyncML: new sync for alerttype ' . $alert, __FILE__, __LINE__, PEAR_LOG_DEBUG);
switch ($alert) {
case ALERT_TWO_WAY:
include_once 'Horde/SyncML/Sync/TwoWaySync.php';
return $sync = &new Horde_SyncML_Sync_TwoWaySync();
case ALERT_SLOW_SYNC:
include_once 'Horde/SyncML/Sync/SlowSync.php';
return $sync = &new Horde_SyncML_Sync_SlowSync();
case ALERT_ONE_WAY_FROM_CLIENT:
include_once 'Horde/SyncML/Sync/OneWayFromClientSync.php';
return $sync = &new Horde_SyncML_Sync_OneWayFromClientSync();
case ALERT_REFRESH_FROM_CLIENT:
include_once 'Horde/SyncML/Sync/RefreshFromClientSync.php';
return $sync = &new Horde_SyncML_Sync_RefreshFromClientSync();
case ALERT_ONE_WAY_FROM_SERVER:
include_once 'Horde/SyncML/Sync/OneWayFromServerSync.php';
return $sync = &new Horde_SyncML_Sync_OneWayFromServerSync();
case ALERT_REFRESH_FROM_SERVER:
include_once 'Horde/SyncML/Sync/RefreshFromServerSync.php';
return $sync = &new Horde_SyncML_Sync_RefreshFromServerSync();
}
require_once 'PEAR.php';
return PEAR::raiseError('Alert ' . $alert . ' not found.');
}
function nextSyncCommand($currentCmdID, &$syncCommand, &$output)
{
$result = $this->runSyncCommand($syncCommand);
return $syncCommand->output($currentCmdID, $output);
}
function startSync($currentCmdID, &$output)
{
return $currentCmdID;
}
function endSync($currentCmdID, &$output)
{
return $currentCmdID;
}
var $_locName;
/**
* Here's where the actual processing of a client-sent Sync
* Command takes place. Entries are added, deleted or replaced
* from the server database by using Horde API (Registry) calls.
*/
* The synchronization method, one of the ALERT_* constants.
*
* @var integer
*/
var $_syncType;
/**
* Return if all commands success.
*/
var $globalSuccess;
/**
* This is the content type to use to export data.
*/
var $preferedContentType;
/**
* Optional filter expression for this content.
*
* @var string
*/
var $_filterExpression = '';
/**
* Do have the sync data loaded from the database already?
*/
var $syncDataLoaded;
function &factory($alert) {
Horde::logMessage('SyncML: new sync for alerttype ' . $alert, __FILE__, __LINE__, PEAR_LOG_DEBUG);
switch ($alert) {
case ALERT_TWO_WAY:
include_once 'Horde/SyncML/Sync/TwoWaySync.php';
return $sync = new Horde_SyncML_Sync_TwoWaySync();
case ALERT_SLOW_SYNC:
include_once 'Horde/SyncML/Sync/SlowSync.php';
return $sync = new Horde_SyncML_Sync_SlowSync();
case ALERT_ONE_WAY_FROM_CLIENT:
include_once 'Horde/SyncML/Sync/OneWayFromClientSync.php';
return $sync = new Horde_SyncML_Sync_OneWayFromClientSync();
case ALERT_REFRESH_FROM_CLIENT:
include_once 'Horde/SyncML/Sync/RefreshFromClientSync.php';
return $sync = new Horde_SyncML_Sync_RefreshFromClientSync();
case ALERT_ONE_WAY_FROM_SERVER:
include_once 'Horde/SyncML/Sync/OneWayFromServerSync.php';
return $sync = new Horde_SyncML_Sync_OneWayFromServerSync();
case ALERT_REFRESH_FROM_SERVER:
include_once 'Horde/SyncML/Sync/RefreshFromServerSync.php';
return $sync = new Horde_SyncML_Sync_RefreshFromServerSync();
}
require_once 'PEAR.php';
return PEAR::raiseError('Alert ' . $alert . ' not found.');
}
function nextSyncCommand($currentCmdID, &$syncCommand, &$output) {
$result = $this->runSyncCommand($syncCommand);
return $syncCommand->output($currentCmdID, $output);
}
function startSync($currentCmdID, &$output) {
return $currentCmdID;
}
function endSync($currentCmdID, &$output) {
return $currentCmdID;
}
/**
* Setter for property sourceURI.
*
* @param string $sourceURI New value of property sourceLocURI.
*/
function setSourceLocURI($sourceURI) {
$this->_sourceLocURI = $sourceURI;
}
/**
* Setter for property targetURI.
*
* @param string $targetURI New value of property targetLocURI.
*/
function setTargetLocURI($targetURI) {
$this->_targetLocURI = $targetURI;
}
/**
* Setter for property syncType.
*
* @param integer $syncType New value of property syncType.
*/
function setSyncType($syncType) {
$this->_syncType = $syncType;
}
/**
* Setter for property locName.
*
* @param string $locName New value of property locName.
*/
function setLocName($locName) {
$this->_locName = $locName;
}
/**
* Setter for property filterExpression.
*
* @param string $expression New value of property filterExpression.
*/
function setFilterExpression($expression) {
$this->_filterExpression = $expression;
}
/**
* Here's where the actual processing of a client-sent Sync
* Command takes place. Entries are added, deleted or replaced
* from the server database by using Horde API (Registry) calls.
*/
function runSyncCommand(&$command) {
#Horde::logMessage('SyncML: content type is ' . $command->getContentType() .' moreData '. $command->_moreData, __FILE__, __LINE__, PEAR_LOG_DEBUG);
global $registry;
$history = $GLOBALS['egw']->contenthistory;
$state = &$_SESSION['SyncML.state'];
if(isset($state->_moreData['luid'])) {
if(($command->_luid == $state->_moreData['luid'])) {
Horde::logMessage('SyncML: got next moreData chunk '.$command->getContent(), __FILE__, __LINE__, PEAR_LOG_DEBUG);
$lastChunks = implode('',$state->_moreData['chunks']);
$command->_content = $lastChunks.$command->_content;
$stringlen1 = strlen($lastChunks);
$stringlen2 = strlen($command->_content);
if(!$command->_moreData && strlen($command->_content) != $state->_moreData['contentSize']) {
$command->_status = RESPONSE_SIZE_MISMATCH;
$state->_moreData = array();
return;
} elseif(!$command->_moreData && strlen($command->_content) == $state->_moreData['contentSize']) {
$state->_moreData = array();
Horde::logMessage('SyncML: chunk ended successful type is ' . $command->getContentType() .' content is '. $command->getContent(), __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
} else {
// alert 223 needed too
#$command->_status = ALERT_NO_END_OF_DATA;
$state->_moreData = array();
return;
if ($command->hasMoreData()) {
Horde::logMessage('SyncML: moreData: TRUE', __FILE__, __LINE__, PEAR_LOG_DEBUG);
$command->setStatus(RESPONSE_CHUNKED_ITEM_ACCEPTED_AND_BUFFERED);
return true;
}
$type = $this->_targetLocURI;
$syncml_prefs = $GLOBALS['egw_info']['user']['preferences']['syncml'];
if (isset($syncml_prefs[$type])) {
$sync_conflicts = $syncml_prefs[$type];
} else {
$sync_conflicts = CONFLICT_SERVER_WINNING;
}
$state->setLocName($this->_locName);
$locName = $state->getLocName();
$sourceURI = $state->getSourceURI();
$hordeType = $state->getHordeType($type);
$serverAnchorLast = $state->getServerAnchorLast($type);
$state->setTargetURI($type);
$changes = array();
// First we get all changes done after the previous sync start
foreach ($registry->call($hordeType. '/listBy',
array('action' => 'modify',
'timestamp' => $serverAnchorLast,
'type' => $type,
'filter' => $this->_filterExpression)) as $change) {
// now we have to remove the ones
// that came from the last sync with this client
$guid_ts = $state->getSyncTSforAction($change, 'modify');
$sync_ts = $state->getChangeTS($type, $change);
if ($sync_ts && $sync_ts == $guid_ts) {
// Change was done by us upon request of client.
Horde::logMessage("SyncML: change: $change ignored, " .
"came from client", __FILE__, __LINE__, PEAR_LOG_DEBUG);
continue;
}
$changes[] = $change;
}
// don't add/replace the data currently, they are not yet complete
if($command->_moreData == TRUE) {
$state->_moreData['chunks'][] = $command->_content;
$state->_moreData['luid'] = $command->_luid;
// gets only set with the first chunk of data
if(isset($command->_contentSize))
$state->_moreData['contentSize'] = $command->_contentSize;
$command->_status = RESPONSE_CHUNKED_ITEM_ACCEPTED_AND_BUFFERED;
Horde::logMessage('SyncML: added moreData chunk '.$command->getContent(), __FILE__, __LINE__, PEAR_LOG_DEBUG);
return;
}
$hordeType = $type = $this->_targetLocURI;
$hordeType = $state->getHordeType($hordeType);
Horde::logMessage('SyncML: runSyncCommand found ' . count($changes) .
" possible conflicts for $type", __FILE__, __LINE__, PEAR_LOG_DEBUG);
if(!$contentType = $command->getContentType()) {
$contentType = $state->getPreferedContentType($type);
}
if (($contentType == 'text/x-vcalendar' || $contentType == 'text/calendar')
&& strpos($command->getContent(), 'BEGIN:VTODO') !== false)
{
&& strpos($command->getContent(), 'BEGIN:VTODO') !== false) {
$hordeType = 'tasks';
}
$syncElementItems = $command->getSyncElementItems();
foreach($syncElementItems as $syncItem) {
$guid = false;
$contentSize = strlen($syncItem->_content);
if ((($size = $syncItem->getContentSize()) !== false) &&
($contentSize != $size) &&
($contentSize + 1 != $size)) {
Horde::logMessage('SyncML: content size missmatch for LocURI '
. $syncItem->getLocURI() . ": $contentSize ($size)",
__FILE__, __LINE__, PEAR_LOG_ERROR);
$command->setStatus(RESPONSE_SIZE_MISMATCH);
return false;
}
if (is_a($command, 'Horde_SyncML_Command_Sync_Add')) {
if ($sync_conflicts > CONFLICT_RESOLVED_WITH_DUPLICATE) {
// We enforce the client not to change anything
if ($sync_conflicts > CONFLICT_CLIENT_CHANGES_IGNORED) {
// delete this item from client
Horde::logMessage('SyncML: Server RO! REMOVE '
. $syncItem->getLocURI() . ' from client',
__FILE__, __LINE__, PEAR_LOG_WARNING);
$state->addConflictItem($type, $syncItem->getLocURI());
} else {
Horde::logMessage('SyncML: Server RO! '
. 'REJECT all client changes',
__FILE__, __LINE__, PEAR_LOG_WARNING);
$state->log('Client-AddReplaceIgnored');
}
continue;
}
$guid = $registry->call($hordeType . '/import',
array($state->convertClient2Server($syncItem->getContent(), $contentType), $contentType));
if (!is_a($guid, 'PEAR_Error') && $guid != false) {
$ts = $state->getSyncTSforAction($guid, 'add');
$state->setUID($type, $syncItem->getLocURI(), $guid, $ts);
$state->log("Client-Add");
Horde::logMessage('SyncML: added client entry as ' . $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG);
$state->log('Client-Add');
Horde::logMessage('SyncML: added client entry as '
. $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG);
} else {
$state->log("Client-AddFailure");
Horde::logMessage('SyncML: Error in adding client entry:' . $guid->message, __FILE__, __LINE__, PEAR_LOG_ERR);
$state->log('Client-AddFailure');
Horde::logMessage('SyncML: Error in adding client entry: '
. $guid->message, __FILE__, __LINE__, PEAR_LOG_ERR);
}
} elseif (is_a($command, 'Horde_SyncML_Command_Sync_Delete')) {
// We can't remove the mapping entry as we need to keep
// the timestamp information.
$guid = $state->removeUID($type, $syncItem->getLocURI());
#$guid = $state->getGlobalUID($type, $syncItem->getLocURI());
Horde::logMessage('SyncML: about to delete entry ' . $type .' / '. $guid . ' due to client request '.$syncItem->getLocURI(), __FILE__, __LINE__, PEAR_LOG_DEBUG);
if (!$guid) {
// the entry is no longer on the server
$state->log('Client-DeleteFailure');
Horde::logMessage('SyncML: Failure deleting client entry, '
. 'gone already on server!',
__FILE__, __LINE__, PEAR_LOG_ERR);
continue;
}
if ($sync_conflicts > CONFLICT_RESOLVED_WITH_DUPLICATE) {
// We enforce the client not to change anything
if ($sync_conflicts > CONFLICT_CLIENT_CHANGES_IGNORED) {
Horde::logMessage('SyncML: Server RO! ADD '
. $guid . ' to client again',
__FILE__, __LINE__, PEAR_LOG_WARNING);
$state->pushAddedItem($type, $guid);
} else {
Horde::logMessage('SyncML: '.
'Server RO! REJECT all client changes',
__FILE__, __LINE__, PEAR_LOG_WARNING);
}
$state->log('Client-DeleteIgnored');
continue;
}
elseif ($sync_conflicts == CONFLICT_RESOLVED_WITH_DUPLICATE &&
in_array($guid, $changes))
{
Horde::logMessage('SyncML: '.
'Server has updated version to keep',
__FILE__, __LINE__, PEAR_LOG_WARNING);
$state->log('Client-DeleteIgnored');
continue;
}
elseif ($sync_conflicts == CONFLICT_MERGE_DATA)
{
Horde::logMessage('SyncML: Server Merge Only: ADD '
. $guid . ' to client again',
__FILE__, __LINE__, PEAR_LOG_WARNING);
$state->pushAddedItem($type, $guid);
$state->log('Client-DeleteIgnored');
continue;
}
Horde::logMessage('SyncML: about to delete entry '
. $type .' / '. $guid . ' due to client request '
. $syncItem->getLocURI(), __FILE__, __LINE__, PEAR_LOG_DEBUG);
if (!is_a($guid, 'PEAR_Error') && $guid != false) {
$registry->call($hordeType . '/delete', array($guid));
#$ts = $state->getSyncTSforAction($guid, 'delete');
#$state->setUID($type, $syncItem->getLocURI(), $guid, $ts);
$state->log("Client-Delete");
Horde::logMessage('SyncML: deleted entry ' . $guid . ' due to client request', __FILE__, __LINE__, PEAR_LOG_DEBUG);
$ts = $state->getSyncTSforAction($guid, 'delete');
$state->setUID($type, $syncItem->getLocURI(), $guid, $ts, 1);
$state->log('Client-Delete');
Horde::logMessage('SyncML: deleted entry '
. $guid . ' due to client request',
__FILE__, __LINE__, PEAR_LOG_DEBUG);
} else {
$state->log("Client-DeleteFailure");
Horde::logMessage('SyncML: Failure deleting client entry, maybe gone already on server. msg:'. $guid->message, __FILE__, __LINE__, PEAR_LOG_ERR);
$state->log('Client-DeleteFailure');
Horde::logMessage('SyncML: Failure deleting client entry, '
. 'maybe gone already on server. msg: '
. $guid->message, __FILE__, __LINE__, PEAR_LOG_ERR);
}
} elseif (is_a($command, 'Horde_SyncML_Command_Sync_Replace')) {
$guid = $state->getGlobalUID($type, $syncItem->getLocURI());
$replace = true;
$ok = false;
if ($guid) {
Horde::logMessage('SyncML: locuri'. $syncItem->getLocURI() . ' guid ' . $guid , __FILE__, __LINE__, PEAR_LOG_ERR);
// Entry exists: replace current one.
$ok = $registry->call($hordeType . '/replace',
array($guid, $state->convertClient2Server($syncItem->getContent(), $contentType), $contentType));
if (!is_a($ok, 'PEAR_Error')) {
$ts = $state->getSyncTSforAction($guid, 'modify');
$state->setUID($type, $syncItem->getLocURI(), $guid, $ts);
Horde::logMessage('SyncML: replaced entry due to client request guid: ' .$guid. ' ts: ' .$ts, __FILE__, __LINE__, PEAR_LOG_DEBUG);
$state->log("Client-Replace");
$ok = true;
} else {
// Entry may have been deleted; try adding it.
$ok = false;
$merge = false;
if ($guid)
{
Horde::logMessage('SyncML: locuri '. $syncItem->getLocURI() . ' guid ' . $guid , __FILE__, __LINE__, PEAR_LOG_DEBUG);
if (($sync_conflicts > CONFLICT_RESOLVED_WITH_DUPLICATE) || in_array($guid, $changes))
{
Horde::logMessage('SyncML: CONFLICT for locuri '. $syncItem->getLocURI() . ' guid ' . $guid , __FILE__, __LINE__, PEAR_LOG_WARNING);
switch ($sync_conflicts)
{
case CONFLICT_CLIENT_WINNING:
$command->setStatus(RESPONSE_CONFLICT_RESOLVED_WITH_CLIENT_WINNING);
break;
case CONFLICT_SERVER_WINNING:
Horde::logMessage('SyncML: REJECT client change for locuri ' .
$syncItem->getLocURI() . ' guid ' . $guid ,
__FILE__, __LINE__, PEAR_LOG_WARNING);
$ok = true;
$replace = false;
$state->log('Client-AddReplaceIgnored');
break;
case CONFLICT_MERGE_DATA:
Horde::logMessage('SyncML: Merge server and client data for locuri ' .
$syncItem->getLocURI() . ' guid ' . $guid ,
__FILE__, __LINE__, PEAR_LOG_WARNING);
$merge = true;
break;
case CONFLICT_RESOLVED_WITH_DUPLICATE:
$replace = false;
break;
case CONFLICT_CLIENT_CHANGES_IGNORED:
Horde::logMessage('SyncML: Server RO! REJECT client change for locuri ' .
$syncItem->getLocURI() . ' guid ' . $guid ,
__FILE__, __LINE__, PEAR_LOG_WARNING);
$ok = true;
$replace = false;
$ts = $state->getSyncTSforAction($guid, 'modify');
$state->setUID($type, $syncItem->getLocURI(), $guid, $ts);
$state->log('Client-AddReplaceIgnored');
break;
default: // We enforce our data on client
Horde::logMessage('SyncML: Server RO! UNDO client change for locuri ' .
$syncItem->getLocURI() . ' guid ' . $guid ,
__FILE__, __LINE__, PEAR_LOG_WARNING);
$state->pushChangedItem($type, $guid);
$ok = true;
$replace = false;
}
}
elseif ($sync_conflicts == CONFLICT_MERGE_DATA)
{
Horde::logMessage('SyncML: Merge server and client data for locuri ' .
$syncItem->getLocURI() . ' guid ' . $guid ,
__FILE__, __LINE__, PEAR_LOG_WARNING);
$merge = true;
}
if ($replace)
{
// Entry exists: replace/merge with current one.
$ok = $registry->call($hordeType . '/replace',
array($guid, $state->convertClient2Server($syncItem->getContent(),
$contentType), $contentType, $type, $merge));
if (!is_a($ok, 'PEAR_Error') && $ok != false)
{
$ts = $state->getSyncTSforAction($guid, 'modify');
$state->setUID($type, $syncItem->getLocURI(), $guid, $ts);
if ($merge)
{
Horde::logMessage('SyncML: Merged entry due to client request guid: ' .
$guid . ' ts: ' . $ts, __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
else
{
Horde::logMessage('SyncML: replaced entry due to client request guid: ' .
$guid . ' ts: ' . $ts, __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
$state->log('Client-Replace');
$ok = true;
}
else
{
// Entry may have been deleted; try adding it.
$ok = false;
}
}
}
if (!$ok) {
// Entry does not exist in map or database: add a new one.
Horde::logMessage('SyncML: try to add contentype ' . $contentType, __FILE__, __LINE__, PEAR_LOG_DEBUG);
// Entry does either not exist in map or database, or should be added due to a conflict
if ($sync_conflicts > CONFLICT_RESOLVED_WITH_DUPLICATE) {
// We enforce the client not to change anything
if ($sync_conflicts > CONFLICT_CLIENT_CHANGES_IGNORED) {
// delete this item from client
Horde::logMessage('SyncML: Server RO! REMOVE ' . $syncItem->getLocURI() . ' from client',
__FILE__, __LINE__, PEAR_LOG_WARNING);
$state->addConflictItem($type, $syncItem->getLocURI());
} else {
Horde::logMessage('SyncML: Server RO! REJECT all client changes',
__FILE__, __LINE__, PEAR_LOG_WARNING);
}
continue;
}
Horde::logMessage('SyncML: try to add contentype '
. $contentType . ' for locuri ' . $syncItem->getLocURI(),
__FILE__, __LINE__, PEAR_LOG_DEBUG);
//continue;
$oguid = $guid;
$guid = $registry->call($hordeType . '/import',
array($state->convertClient2Server($syncItem->getContent(), $contentType), $contentType));
array($state->convertClient2Server($syncItem->getContent(), $contentType), $contentType, $guid));
if (!is_a($guid, 'PEAR_Error')) {
$ts = $state->getSyncTSforAction($guid, 'add');
if ($oguid != $guid) {
// We add a new entry
$ts = $state->getSyncTSforAction($guid, 'add');
Horde::logMessage('SyncML: added entry '
. $guid . ' from client',
__FILE__, __LINE__, PEAR_LOG_DEBUG);
$state->log('Client-Add');
} else {
// We replaced an entry
$ts = $state->getSyncTSforAction($guid, 'modify');
Horde::logMessage('SyncML: replaced entry '
. $guid . ' from client',
__FILE__, __LINE__, PEAR_LOG_DEBUG);
$state->log('Client-Replace');
}
$state->setUID($type, $syncItem->getLocURI(), $guid, $ts);
$state->log("Client-AddReplace");
Horde::logMessage('SyncML: r/ added client entry as ' . $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG);
} else {
Horde::logMessage('SyncML: Error in replacing/add client entry:' . $guid->message, __FILE__, __LINE__, PEAR_LOG_ERR);
$state->log("Client-AddFailure");
Horde::logMessage('SyncML: Error in replacing/'
. 'add client entry:' . $guid->message,
__FILE__, __LINE__, PEAR_LOG_ERR);
$state->log('Client-AddFailure');
}
}
}
}
return $guid;
}
}

View File

@ -1,6 +1,6 @@
<?php
include_once 'Horde/SyncML/Sync.php';
include_once 'Horde/SyncML/Sync/SlowSync.php';
/**
* $Horde: framework/SyncML/SyncML/Sync/RefreshFromClientSync.php,v 1.8 2004/09/14 04:27:06 chuck Exp $
@ -15,20 +15,9 @@ include_once 'Horde/SyncML/Sync.php';
* @since Horde 3.0
* @package Horde_SyncML
*/
class Horde_SyncML_Sync_RefreshFromClientSync extends Horde_SyncML_Sync {
class Horde_SyncML_Sync_RefreshFromClientSync extends Horde_SyncML_Sync_SlowSync {
/**
* We need to erase the current server contents, then we can add
* We needed to erase the current server contents, then we can add
* the client's contents.
*/
function startSync($currentCmdID, &$output)
{
$deletes = $registry->call($this->targetLocURI, '/list', array());
foreach ($delete as $deletes) {
$registry->call($this->targetLocURI . '/delete', array($delete));
}
return $currentCmdID;
}
}

View File

@ -1,87 +1,167 @@
<?php
/**
* eGroupWare - SyncML based on Horde 3
*
*
* Using the PEAR Log class (which need to be installed!)
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage horde
* @author Anthony Mills <amills@pyramid6.com>
* @author Joerg Lehrke <jlehrke@noc.de>
* @copyright (c) The Horde Project (http://www.horde.org/)
* @version $Id$
*/
include_once 'Horde/SyncML/Sync.php';
/**
* $Horde: framework/SyncML/SyncML/Sync/RefreshFromServerSync.php,v 1.9 2004/07/03 15:21:15 chuck Exp $
*
* Copyright 2003-2004 Anthony Mills <amills@pyramid6.com>
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Anthony Mills <amills@pyramid6.com>
* @version $Revision$
* @since Horde 3.0
* @package Horde_SyncML
*/
class Horde_SyncML_Sync_RefreshFromServerSync extends Horde_SyncML_Sync_TwoWaySync {
function handleSync($currentCmdID, $hordeType, $syncType, &$output, $refts) {
global $registry;
$state = &$_SESSION['SyncML.state'];
$adds = &$state->getAddedItems($hordeType);
Horde::logMessage("SyncML: ".count($adds). ' added items found for '.$hordeType , __FILE__, __LINE__, PEAR_LOG_DEBUG);
$maxMsgSize = $state->getMaxMsgSizeClient();
$deviceInfo = $state->getClientDeviceInfo();
if (isset($deviceInfo['maxEntries'])) {
$maxEntries = $deviceInfo['maxEntries'];
if (!$maxMsgSize && !$maxEntries) {
// fallback to default
$maxEntries = MAX_ENTRIES;
}
} else {
$maxEntries = MAX_ENTRIES;
}
$serverAnchorNext = $state->getServerAnchorNext($syncType);
$counter = 0;
if (isset($state->curSyncItem)) {
// Finish the pending sync item
$cmd = &$state->curSyncItem;
unset($state->curSyncItem);
$currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Sync');
// moreData split; save in session state and end current message
if ($cmd->hasMoreData()) {
$state->curSyncItem = &$cmd;
$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
return $currentCmdID;
}
$state->incNumberOfElements();
}
$adds = &$state->getAddedItems($syncType);
Horde::logMessage("SyncML: ".count($adds).
' added items found for '.$syncType ,
__FILE__, __LINE__, PEAR_LOG_DEBUG);
if(is_array($adds)) {
while($guid = array_shift($adds)) {
$currentSize = $output->getOutputSize();
// return if we have to much data
if (($maxEntries && ($state->getNumberOfElements() >= $maxEntries)
&& isset($contentType['mayFragment'])
&& $contentType['mayFragment'])
|| ($maxMsgSize
&& (($currentSize + MIN_MSG_LEFT * 2) > $maxMsgSize))) {
// put the item back in the queue
$adds[] = $guid;
$state->maxNumberOfElements();
$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
return $currentCmdID;
}
if ($locID = $state->getLocID($syncType, $guid)) {
Horde::logMessage("SyncML: RefreshFromServerSync add to client: $guid ignored, already at client($locID)", __FILE__, __LINE__, PEAR_LOG_DEBUG);
continue;
}
$guid_ts = $state->getSyncTSforAction($guid, 'add');
if ($guid_ts > $serverAnchorNext) {
// Change was made after we started this sync.
// Don't sent this now to the client.
Horde::logMessage("SyncML: RefreshFromServerSync add $guid is in our future", __FILE__, __LINE__, PEAR_LOG_DEBUG);
continue;
}
$contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI, $this->_targetLocURI);
$cmd = &new Horde_SyncML_Command_Sync_ContentSyncElement();
$c = $registry->call($hordeType . '/export', array('guid' => $guid, 'contentType' => $contentType));
Horde::logMessage("SyncML: slowsync add $guid to client ". print_r($c, true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
if (!is_a($c, 'PEAR_Error')) {
$cmd->setContent($c);
$cmd->setContentType($contentType['ContentType']);
if (isset($contentType['ContentFormat']))
{
$cmd->setContentFormat($contentType['ContentFormat']);
}
$cmd->setSourceURI($guid);
$currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Add');
$state->log('Server-Add');
// return if we have to much data
if(++$counter >= MAX_ENTRIES
&& isset($contentType['mayFragment'])
&& $contentType['mayFragment'])
{
$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
return $currentCmdID;
}
if (is_a($c, 'PEAR_Error')) {
Horde::logMessage("SyncML: refresh failed to export guid $guid:\n" . print_r($c, true), __FILE__, __LINE__, PEAR_LOG_WARNING);
$state->log("Server-ExportFailed");
continue;
}
$size = strlen($c);
// return if we have to much data
if ($maxMsgSize && !$deviceInfo['supportLargeObjs']) {
if (($size + MIN_MSG_LEFT * 2) > $maxMsgSize) {
Horde::logMessage("SyncML: refresh failed to export guid $guid due to size $size", __FILE__, __LINE__, PEAR_LOG_ERROR);
$state->log("Server-ExportFailed");
continue;
}
if (($currentSize + $size + MIN_MSG_LEFT * 2) > $maxMsgSize) {
// put the item back in the queue
$adds[] = $guid;
$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
return $currentCmdID;
}
}
Horde::logMessage("SyncML: refresh add $guid to client\n$c", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$cmd = new Horde_SyncML_Command_Sync_ContentSyncElement();
$cmd->setContent($c);
$cmd->setContentType($contentType['ContentType']);
if (isset($contentType['ContentFormat'])) {
$cmd->setContentFormat($contentType['ContentFormat']);
}
$cmd->setGUID($guid);
$currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Add');
$state->log('Server-Add');
// moreData split; put the guid back in the list and return
if ($cmd->hasMoreData()) {
$state->curSyncItem = &$cmd;
$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
return $currentCmdID;
}
$state->incNumberOfElements();
}
}
#Horde::logMessage("SyncML: handling sync ".$currentCmdID, __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde::logMessage("SyncML: All items handled for sync $syncType", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$state->removeExpiredUID($syncType, $serverAnchorNext);
$state->clearSync($syncType);
return $currentCmdID;
}
function loadData() {
global $registry;
$state = &$_SESSION['SyncML.state'];
$syncType = $this->_targetLocURI;
$hordeType = $state->getHordeType($syncType);
$state->setTargetURI($syncType);
$future = $state->getServerAnchorNext($syncType);
$delta_add = 0;
Horde::logMessage("SyncML: reading added items from database for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$state->setAddedItems($hordeType, $registry->call($hordeType. '/list', array()));
$adds = &$state->getAddedItems($hordeType);
$state->setAddedItems($syncType, $registry->call($hordeType. '/listBy',
array('action' => 'add',
'timestamp' => $future,
'type' => $syncType,
'filter' => $this->_filterExpression)));
$delta_add = count($state->getAddedItems($hordeType));
$state->setAddedItems($syncType, $registry->call($hordeType. '/list', array('filter' => $this->_filterExpression)));
$this->_syncDataLoaded = TRUE;
return count($state->getAddedItems($hordeType));
return count($state->getAddedItems($syncType)) - $delta_add;
}
}

View File

@ -1,194 +1,282 @@
<?php
include_once 'Horde/SyncML/Sync/TwoWaySync.php';
/**
* eGroupWare - SyncML based on Horde 3
*
* Slow sync may just work; I think most of the work is going to be
* done by the API.
*
* $Horde: framework/SyncML/SyncML/Sync/SlowSync.php,v 1.7 2004/05/26 17:32:50 chuck Exp $
*
* Copyright 2003-2004 Anthony Mills <amills@pyramid6.com>
* Using the PEAR Log class (which need to be installed!)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Anthony Mills <amills@pyramid6.com>
* @version $Revision$
* @since Horde 3.0
* @package Horde_SyncML
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage horde
* @author Anthony Mills <amills@pyramid6.com>
* @author Joerg Lehrke <jlehrke@noc.de>
* @copyright (c) The Horde Project (http://www.horde.org/)
* @version $Id$
*/
include_once 'Horde/SyncML/Sync/TwoWaySync.php';
class Horde_SyncML_Sync_SlowSync extends Horde_SyncML_Sync_TwoWaySync {
function handleSync($currentCmdID, $hordeType, $syncType, &$output, $refts) {
global $registry;
$history = $GLOBALS['egw']->contenthistory;
$state = &$_SESSION['SyncML.state'];
$maxMsgSize = $state->getMaxMsgSizeClient();
$deviceInfo = $state->getClientDeviceInfo();
if (isset($deviceInfo['maxEntries'])) {
$maxEntries = $deviceInfo['maxEntries'];
if (!$maxMsgSize && !$maxEntries) {
// fallback to default
$maxEntries = MAX_ENTRIES;
}
} else {
$maxEntries = MAX_ENTRIES;
}
$serverAnchorNext = $state->getServerAnchorNext($syncType);
// now we remove all UID from contentmap that have not been verified in this slowsync
$state->removeOldUID($syncType, $serverAnchorNext);
$adds = &$state->getAddedItems($hordeType);
Horde::logMessage("SyncML: ".count($adds). ' added items found for '.$hordeType , __FILE__, __LINE__, PEAR_LOG_DEBUG);
$counter = 0;
if (isset($state->curSyncItem)) {
// Finish the pending sync item
$cmd = &$state->curSyncItem;
unset($state->curSyncItem);
$currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Sync');
// moreData split; save in session state and end current message
if ($cmd->hasMoreData()) {
$state->curSyncItem = &$cmd;
$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
return $currentCmdID;
}
$state->incNumberOfElements();
}
$adds = &$state->getAddedItems($syncType);
Horde::logMessage("SyncML: ".count($adds). ' added items found for '.$syncType, __FILE__, __LINE__, PEAR_LOG_DEBUG);
if(is_array($adds)) {
while($guid = array_shift($adds)) {
$currentSize = $output->getOutputSize();
// return if we have to much data
if (($maxEntries && ($state->getNumberOfElements() >= $maxEntries)
&& isset($contentType['mayFragment'])
&& $contentType['mayFragment']) ||
($maxMsgSize && (($currentSize + MIN_MSG_LEFT * 2) > $maxMsgSize))) {
// put the item back in the queue
$adds[] = $guid;
$state->maxNumberOfElements();
$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
return $currentCmdID;
}
if ($locID = $state->getLocID($syncType, $guid)) {
Horde::logMessage("SyncML: slowsync add to client: $guid ignored, already at client($locID)", __FILE__, __LINE__, PEAR_LOG_DEBUG);
continue;
}
$guid_ts = $state->getSyncTSforAction($guid, 'add');
if ($guid_ts > $serverAnchorNext) {
// Change was made after we started this sync.
// Don't sent this now to the client.
Horde::logMessage("SyncML: slowsync add $guid is in our future", __FILE__, __LINE__, PEAR_LOG_DEBUG);
continue;
}
$contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI, $this->_targetLocURI);
$cmd = &new Horde_SyncML_Command_Sync_ContentSyncElement();
$c = $registry->call($hordeType . '/export', array('guid' => $guid, 'contentType' => $contentType));
#Horde::logMessage("SyncML: slowsync add guid $guid to client ". print_r($c, true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
if (!is_a($c, 'PEAR_Error')) {
$cmd->setContent($c);
$cmd->setContentType($contentType['ContentType']);
if (isset($contentType['ContentFormat']))
{
$cmd->setContentFormat($contentType['ContentFormat']);
if (is_a($c, 'PEAR_Error')) {
Horde::logMessage("SyncML: slowsync failed to export guid $guid:\n" . print_r($c, true), __FILE__, __LINE__, PEAR_LOG_WARNING);
continue;
}
$size = strlen($c);
// return if we have to much data
if ($maxMsgSize && !$deviceInfo['supportLargeObjs']) {
if (($size + MIN_MSG_LEFT * 2) > $maxMsgSize) {
Horde::logMessage("SyncML: slowsync failed to export guid $guid due to size $size", __FILE__, __LINE__, PEAR_LOG_ERROR);
continue;
}
$cmd->setSourceURI($guid);
$currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Add');
$state->log('Server-Add');
// return if we have to much data
if(++$counter >= MAX_ENTRIES
&& isset($contentType['mayFragment'])
&& $contentType['mayFragment'])
{
if (($currentSize + $size + MIN_MSG_LEFT * 2) > $maxMsgSize) {
// put the item back in the queue
$adds[] = $guid;
$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
return $currentCmdID;
}
}
Horde::logMessage("SyncML: slowsync add guid $guid to client\n$c", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$cmd = new Horde_SyncML_Command_Sync_ContentSyncElement();
$cmd->setContent($c);
$cmd->setContentType($contentType['ContentType']);
if (isset($contentType['ContentFormat'])) {
$cmd->setContentFormat($contentType['ContentFormat']);
}
$cmd->setGUID($guid);
$currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Add');
$state->log('Server-Add');
// moreData split; save in session state and end current message
if ($cmd->hasMoreData()) {
$state->curSyncItem = &$cmd;
$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
return $currentCmdID;
}
$state->incNumberOfElements();
}
}
#Horde::logMessage("SyncML: handling sync ".$currentCmdID, __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde::logMessage("SyncML: All items handled for sync $syncType", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$state->removeExpiredUID($syncType, $serverAnchorNext);
$state->clearSync($syncType);
return $currentCmdID;
}
/**
* Here's where the actual processing of a client-sent Sync
* Command takes place. Entries are added or replaced
* from the server database by using Horde API (Registry) calls.
*/
function runSyncCommand(&$command) {
#Horde::logMessage('SyncML: content type is ' . $command->getContentType() .' moreData '. $command->_moreData, __FILE__, __LINE__, PEAR_LOG_DEBUG);
global $registry;
$history = $GLOBALS['egw']->contenthistory;
$state = &$_SESSION['SyncML.state'];
if(isset($state->_moreData['luid'])) {
if(($command->_luid == $state->_moreData['luid'])) {
Horde::logMessage('SyncML: got next moreData chunk '.$command->getContent(), __FILE__, __LINE__, PEAR_LOG_DEBUG);
$lastChunks = implode('',$state->_moreData['chunks']);
$command->_content = $lastChunks.$command->_content;
$stringlen1 = strlen($lastChunks);
$stringlen2 = strlen($command->_content);
if(!$command->_moreData && strlen($command->_content) != $state->_moreData['contentSize']) {
$command->_status = RESPONSE_SIZE_MISMATCH;
$state->_moreData = array();
return;
} elseif(!$command->_moreData && strlen($command->_content) == $state->_moreData['contentSize']) {
$state->_moreData = array();
Horde::logMessage('SyncML: chunk ended successful type is ' . $command->getContentType() .' content is '. $command->getContent(), __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
} else {
// alert 223 needed too
#$command->_status = ALERT_NO_END_OF_DATA;
$state->_moreData = array();
if ($command->hasMoreData()) {
Horde::logMessage('SyncML: moreData: TRUE', __FILE__, __LINE__, PEAR_LOG_DEBUG);
$command->setStatus(RESPONSE_CHUNKED_ITEM_ACCEPTED_AND_BUFFERED);
return true;
}
$type = $this->_targetLocURI;
$syncml_prefs = $GLOBALS['egw_info']['user']['preferences']['syncml'];
if (isset($syncml_prefs[$type])) {
$sync_conflicts = $syncml_prefs[$type];
} else {
$sync_conflicts = CONFLICT_SERVER_WINNING;
}
$hordeType = $state->getHordeType($type);
$syncElementItems = $command->getSyncElementItems();
foreach($syncElementItems as $syncItem) {
$contentSize = strlen($syncItem->_content);
if ((($size = $syncItem->getContentSize()) !== false) &&
($contentSize != $size) &&
($contentSize + 1 != $size)) {
Horde::logMessage('SyncML: content size missmatch for LocURI ' . $syncItem->_luid .
": $contentSize ($size)", __FILE__, __LINE__, PEAR_LOG_ERROR);
$command->setStatus(RESPONSE_SIZE_MISMATCH);
return;
}
}
// don't add/replace the data currently, they are not yet complete
if($command->_moreData == TRUE) {
$state->_moreData['chunks'][] = $command->_content;
$state->_moreData['luid'] = $command->_luid;
// gets only set with the first chunk of data
if(isset($command->_contentSize))
$state->_moreData['contentSize'] = $command->_contentSize;
$command->_status = RESPONSE_CHUNKED_ITEM_ACCEPTED_AND_BUFFERED;
Horde::logMessage('SyncML: added moreData chunk '.$command->getContent(), __FILE__, __LINE__, PEAR_LOG_DEBUG);
return;
}
$type = $this->_targetLocURI;
$hordeType = $state->getHordeType($type);
$syncElementItems = $command->getSyncElementItems();
foreach($syncElementItems as $syncItem) {
if(!$contentType = $syncItem->getContentType()) {
$contentType = $state->getPreferedContentType($type);
}
if (($contentType == 'text/x-vcalendar' || $contentType == 'text/calendar')
&& strpos($syncItem->getContent(), 'BEGIN:VTODO') !== false)
{
&& strpos($syncItem->getContent(), 'BEGIN:VTODO') !== false) {
$hordeType = 'tasks';
}
$guid = false;
$guid = $registry->call($hordeType . '/search',
array($state->convertClient2Server($syncItem->getContent(), $contentType), $contentType, $state->getGlobalUID($type, $syncItem->getLocURI()) ));
if ($guid) {
# entry exists in database already. Just update the mapping
Horde::logMessage('SyncML: adding mapping for locuri:'. $syncItem->getLocURI() . ' and guid:' . $guid , __FILE__, __LINE__, PEAR_LOG_DEBUG);
$state->setUID($type, $syncItem->getLocURI(), $guid, mktime());
$state->log("Client-Replace");
} else {
# Entry does not exist in database: add a new one.
$state->removeUID($type, $syncItem->getLocURI());
Horde::logMessage('SyncML: try to add contentype ' . $contentType .' to '. $hordeType, __FILE__, __LINE__, PEAR_LOG_DEBUG);
$guid = $registry->call($hordeType . '/import',
array($state->convertClient2Server($syncItem->getContent(), $contentType), $contentType));
if (!is_a($guid, 'PEAR_Error') && $guid != false) {
$ts = $state->getSyncTSforAction($guid, 'add');
$state->setUID($type, $syncItem->getLocURI(), $guid, $ts);
$state->log("Client-AddReplace");
Horde::logMessage('SyncML: r/ added client entry as ' . $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG);
// Check if the found entry came from the client
$guid_ts = $state->getSyncTSforAction($guid, 'add');
$sync_ts = $state->getChangeTS($type, $guid);
if ($sync_ts && $sync_ts == $guid_ts) {
// Entry came from the client, so we get a duplicate here
Horde::logMessage('SyncML: CONFLICT for locuri ' . $syncItem->getLocURI()
. ' guid ' . $guid , __FILE__, __LINE__, PEAR_LOG_WARNING);
if ($sync_conflicts != CONFLICT_RESOLVED_WITH_DUPLICATE) {
$state->log("Client-AddReplaceIgnored");
continue;
}
} else {
Horde::logMessage('SyncML: Error in replacing/add client entry:' . $guid->message, __FILE__, __LINE__, PEAR_LOG_ERR);
$state->log("Client-AddFailure");
# Entry exists in database already. Just update the mapping
Horde::logMessage('SyncML: adding mapping for locuri:'
. $syncItem->getLocURI() . ' and guid:' . $guid,
__FILE__, __LINE__, PEAR_LOG_DEBUG);
$state->setUID($type, $syncItem->getLocURI(), $guid, mktime());
$state->log("Client-Map");
continue;
}
}
if ($sync_conflicts > CONFLICT_RESOLVED_WITH_DUPLICATE) {
// We enforce the client not to change anything
if ($sync_conflicts > CONFLICT_CLIENT_CHANGES_IGNORED) {
// delete this item from client
Horde::logMessage('SyncML: Server RO! REMOVE ' . $syncItem->getLocURI()
. ' from client', __FILE__, __LINE__, PEAR_LOG_WARNING);
$state->addConflictItem($type, $syncItem->getLocURI());
} else {
Horde::logMessage('SyncML: Server RO! REJECT all client changes',
__FILE__, __LINE__, PEAR_LOG_WARNING);
$state->log("Client-AddReplaceIgnored");
}
$command->setStatus(RESPONSE_NO_EXECUTED);
continue;
}
// Add entry to the database.
$state->removeUID($type, $syncItem->getLocURI());
Horde::logMessage('SyncML: try to add contentype ' . $contentType .' to '. $hordeType, __FILE__, __LINE__, PEAR_LOG_DEBUG);
$guid = $registry->call($hordeType . '/import',
array($state->convertClient2Server($syncItem->getContent(), $contentType), $contentType));
if (!is_a($guid, 'PEAR_Error') && $guid != false) {
$ts = $state->getSyncTSforAction($guid, 'modify');
if (!$ts) {
$ts = $state->getSyncTSforAction($guid, 'add');
}
$state->setUID($type, $syncItem->getLocURI(), $guid, $ts);
$state->log("Client-AddReplace");
Horde::logMessage('SyncML: r/ added client entry as ' . $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG);
} else {
Horde::logMessage('SyncML: Error in replacing/add client entry:' . $guid->message, __FILE__, __LINE__, PEAR_LOG_ERR);
$state->log("Client-AddFailure");
}
}
return true;
}
function loadData() {
global $registry;
$state = &$_SESSION['SyncML.state'];
$syncType = $this->_targetLocURI;
$hordeType = $state->getHordeType($syncType);
$state->setTargetURI($syncType);
$future = $state->getServerAnchorNext($syncType);
$delta_add = 0;
Horde::logMessage("SyncML: reading added items from database for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$state->setAddedItems($hordeType, $registry->call($hordeType. '/list', array()));
$adds = &$state->getAddedItems($hordeType);
$delta_add = count($registry->call($hordeType. '/listBy',
array('action' => 'add',
'timestamp' => $future,
'type' => $syncType,
'filter' => $this->_filterExpression)));
$state->mergeAddedItems($syncType, $registry->call($hordeType. '/list', array('filter' => $this->_filterExpression)));
$this->_syncDataLoaded = TRUE;
return count($state->getAddedItems($hordeType));
return count($state->getAddedItems($syncType)) - $delta_add;
}
}

View File

@ -1,175 +1,323 @@
<?php
/**
* eGroupWare - SyncML based on Horde 3
*
*
* Using the PEAR Log class (which need to be installed!)
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage horde
* @author Anthony Mills <amills@pyramid6.com>
* @author Karsten Fourmont <fourmont@gmx.de>
* @author Joerg Lehrke <jlehrke@noc.de>
* @copyright (c) The Horde Project (http://www.horde.org/)
* @version $Id$
*/
include_once 'Horde/SyncML/Sync.php';
include_once 'Horde/SyncML/Command/Sync/ContentSyncElement.php';
/**
* $Horde: framework/SyncML/SyncML/Sync/TwoWaySync.php,v 1.12 2004/07/26 09:24:38 jan Exp $
*
* Copyright 2003-2004 Anthony Mills <amills@pyramid6.com>
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Anthony Mills <amills@pyramid6.com>
* @author Karsten Fourmont <fourmont@gmx.de>
*
* @version $Revision$
* @since Horde 3.0
* @package Horde_SyncML
*/
class Horde_SyncML_Sync_TwoWaySync extends Horde_SyncML_Sync {
function endSync($currentCmdID, &$output) {
function endSync($currentCmdID, & $output) {
global $registry;
$state = &$_SESSION['SyncML.state'];
$state = & $_SESSION['SyncML.state'];
$syncType = $this->_targetLocURI;
$hordeType = $state->getHordeType($syncType);
$refts = $state->getServerAnchorLast($syncType);
$currentCmdID = $this->handleSync($currentCmdID,
$hordeType,
$syncType,
$output,
$refts);
$currentCmdID = $this->handleSync($currentCmdID, $hordeType, $syncType, $output, $refts);
return $currentCmdID;
}
function handleSync($currentCmdID, $hordeType, $syncType,&$output, $refts) {
function handleSync($currentCmdID, $hordeType, $syncType, & $output, $refts) {
global $registry;
// array of Items which got modified, but got never send to the client before
$missedAdds = array();
$missedAdds = array ();
// array of Items which the client wanted to add, but must be deleted due to
// user's sync policy
$remoteDeletes = array ();
$history = $GLOBALS['egw']->contenthistory;
$state = &$_SESSION['SyncML.state'];
$counter = 0;
$state = & $_SESSION['SyncML.state'];
$maxMsgSize = $state->getMaxMsgSizeClient();
$deviceInfo = $state->getClientDeviceInfo();
$changes = &$state->getChangedItems($hordeType);
$deletes = &$state->getDeletedItems($hordeType);
$adds = &$state->getAddedItems($hordeType);
if (isset($deviceInfo['maxEntries'])) {
$maxEntries = $deviceInfo['maxEntries'];
if (!$maxMsgSize && !$maxEntries) {
// fallback to default
$maxEntries = MAX_ENTRIES;
}
} else {
$maxEntries = MAX_ENTRIES;
}
Horde::logMessage("SyncML: ".count($changes).' changed items found for '.$hordeType, __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde::logMessage("SyncML: ".count($deletes).' deleted items found for '.$hordeType, __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde::logMessage("SyncML: ".count($adds). ' added items found for '.$hordeType , __FILE__, __LINE__, PEAR_LOG_DEBUG);
$serverAnchorNext = $state->getServerAnchorNext($syncType);
if (isset ($state->curSyncItem)) {
// Finish the pending sync item
$cmd = & $state->curSyncItem;
unset ($state->curSyncItem);
$currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Sync');
// moreData split; save in session state and end current message
if ($cmd->hasMoreData()) {
$state->curSyncItem = & $cmd;
$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
return $currentCmdID;
}
$state->incNumberOfElements();
}
$changes = & $state->getChangedItems($syncType);
$deletes = & $state->getDeletedItems($syncType);
$adds = & $state->getAddedItems($syncType);
$conflicts = & $state->getConflictItems($syncType);
Horde :: logMessage('SyncML: ' . count($changes) . ' changed items found for ' . $syncType, __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde :: logMessage('SyncML: ' . count($deletes) . ' deleted items found for ' . $syncType, __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde :: logMessage('SyncML: ' . count($conflicts) . ' items to delete on client found for ' . $syncType, __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde :: logMessage('SyncML: ' . count($adds) . ' added items found for ' . $syncType, __FILE__, __LINE__, PEAR_LOG_DEBUG);
// handle changes
if(is_array($changes)) {
while($guid = array_shift($changes)) {
if (is_array($changes)) {
while ($guid = array_shift($changes)) {
$currentSize = $output->getOutputSize();
// return if we have to much data
if (($maxEntries
&& ($state->getNumberOfElements() >= $maxEntries)
&& isset ($contentType['mayFragment'])
&& $contentType['mayFragment'])
|| ($maxMsgSize
&& (($currentSize +MIN_MSG_LEFT * 2) > $maxMsgSize))) {
// put the item back in the queue
$changes[] = $guid;
$state->maxNumberOfElements();
$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
return $currentCmdID;
}
$guid_ts = $state->getSyncTSforAction($guid, 'modify');
$sync_ts = $state->getChangeTS($syncType, $guid);
Horde::logMessage("SyncML: timestamp modify guid_ts: $guid_ts sync_ts: $sync_ts", __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde :: logMessage("SyncML: timestamp modify $guid guid_ts: $guid_ts sync_ts: $sync_ts", __FILE__, __LINE__, PEAR_LOG_DEBUG);
if ($sync_ts && $sync_ts == $guid_ts) {
// Change was done by us upon request of client.
// Don't mirror that back to the client.
Horde::logMessage("SyncML: change: $guid ignored, came from client", __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde :: logMessage("SyncML: change: $guid ignored, came from client", __FILE__, __LINE__, PEAR_LOG_DEBUG);
continue;
}
if ($guid_ts > $serverAnchorNext) {
// Change was made after we started this sync.
// Don't sent this now to the client.
Horde :: logMessage("SyncML: change $guid is in our future: $serverAnchorNext", __FILE__, __LINE__, PEAR_LOG_DEBUG);
continue;
}
Horde::logMessage("SyncML: change $guid hs_ts:$guid_ts dt_ts:" . $state->getChangeTS($syncType, $guid), __FILE__, __LINE__, PEAR_LOG_DEBUG);
$locid = $state->getLocID($syncType, $guid);
if (!$locid) {
// somehow we missed to add, lets store the uid, so we add this entry later
$missedAdds[] = $guid;
Horde::logMessage("SyncML: unable to create change for $guid: locid not found in map", __FILE__, __LINE__, PEAR_LOG_WARNING);
Horde :: logMessage("SyncML: unable to create change for $guid: locid not found in map", __FILE__, __LINE__, PEAR_LOG_WARNING);
continue;
}
// Create a replace request for client.
$contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI, $this->_targetLocURI);
$c = $registry->call($hordeType. '/export',
array('guid' => $guid, 'contentType' => $contentType));
if (!is_a($c, 'PEAR_Error')) {
$c = $registry->call($hordeType . '/export', array (
'guid' => $guid,
'contentType' => $contentType
));
if (is_a($c, 'PEAR_Error')) {
// Item in history but not in database. Strange, but can happen.
Horde::logMessage("SyncML: change: $guid export content: $c", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$cmd = &new Horde_SyncML_Command_Sync_ContentSyncElement();
# LK $cmd->setContent($state->convertServer2Client($c, $contentType));
$cmd->setContent($c);
$cmd->setSourceURI($guid);
$cmd->setTargetURI($locid);
$cmd->setContentType($contentType['ContentType']);
if (isset($contentType['ContentFormat']))
{
$cmd->setContentFormat($contentType['ContentFormat']);
Horde :: logMessage("SyncML: change: export of guid $guid failed:\n" . print_r($c, true), __FILE__, __LINE__, PEAR_LOG_WARNING);
continue;
}
$size = strlen($c);
// return if we have to much data
if ($maxMsgSize && !$deviceInfo['supportLargeObjs']) {
if (($size +MIN_MSG_LEFT * 2) > $maxMsgSize) {
Horde :: logMessage("SyncML: change: export of guid $guid failed due to size $size", __FILE__, __LINE__, PEAR_LOG_ERROR);
$state->log('Server-ExportFailed');
continue;
}
$currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Replace');
$state->log('Server-Replace');
// return if we have to much data
if (++$counter >= MAX_ENTRIES
&& isset($contentType['mayFragment'])
&& $contentType['mayFragment'])
{
if (($currentSize + $size +MIN_MSG_LEFT * 2) > $maxMsgSize) {
// put the item back in the queue
$changes[] = $guid;
$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
return $currentCmdID;
}
}
Horde :: logMessage("SyncML: change: export guid $guid, content:\n$c", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$cmd = new Horde_SyncML_Command_Sync_ContentSyncElement();
# LK $cmd->setContent($state->convertServer2Client($c, $contentType));
$cmd->setContent($c);
$cmd->setLocURI($locid);
$cmd->setContentType($contentType['ContentType']);
if (isset ($contentType['ContentFormat'])) {
$cmd->setContentFormat($contentType['ContentFormat']);
}
$currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Replace');
$state->log('Server-Replace');
// moreData split; save in session state and end current message
if ($cmd->hasMoreData()) {
$state->curSyncItem = & $cmd;
$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
return $currentCmdID;
}
$state->incNumberOfElements();
}
}
Horde::logMessage("SyncML: handling sync (changes done) ".$currentCmdID, __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde :: logMessage("SyncML: handling sync (changes done) " . $currentCmdID, __FILE__, __LINE__, PEAR_LOG_DEBUG);
// handle deletes
if(is_array($deletes)) {
while($guid = array_shift($deletes)) {
if (is_array($deletes)) {
while ($guid = array_shift($deletes)) {
$currentSize = $output->getOutputSize();
// return if we have to much data
if (($maxEntries && ($state->getNumberOfElements() >= $maxEntries)
&& isset ($contentType['mayFragment'])
&& $contentType['mayFragment'])
|| ($maxMsgSize
&& (($currentSize +MIN_MSG_LEFT * 2) > $maxMsgSize))) {
// put the item back in the queue
$deletes[] = $guid;
$state->maxNumberOfElements();
$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
return $currentCmdID;
}
$guid_ts = $state->getSyncTSforAction($guid, 'delete');
$sync_ts = $state->getChangeTS($syncType, $guid);
Horde::logMessage("SyncML: timestamp delete guid_ts: $guid_ts sync_ts: $sync_ts", __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde :: logMessage("SyncML: timestamp delete guid_ts: $guid_ts sync_ts: $sync_ts",
__FILE__, __LINE__, PEAR_LOG_DEBUG);
if ($sync_ts && $sync_ts == $guid_ts) {
// Change was done by us upon request of client.
// Don't mirror that back to the client.
Horde::logMessage("SyncML: delete $guid ignored, came from client", __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde :: logMessage("SyncML: delete $guid ignored, came from client",
__FILE__, __LINE__, PEAR_LOG_DEBUG);
if ($sync_ts < $serverAnchorNext
&& ($locid = $state->getLocID($syncType, $guid))) {
// Now we can remove the past
$state->removeUID($syncType, $locid);
}
continue;
}
if ($guid_ts > $serverAnchorNext) {
// Change was made after we started this sync.
// Don't sent this now to the client.
Horde :: logMessage("SyncML: delete $guid is in our future: $serverAnchorNext", __FILE__, __LINE__, PEAR_LOG_DEBUG);
continue;
}
$locid = $state->getLocID($syncType, $guid);
if (!$locid) {
Horde::logMessage("SyncML: unable to create delete for $guid: locid not found in map", __FILE__, __LINE__, PEAR_LOG_WARNING);
Horde :: logMessage("SyncML: unable to delete $guid: locid not found in map", __FILE__, __LINE__, PEAR_LOG_INFO);
$state->log("Server-DeleteFailure");
continue;
}
Horde::logMessage("SyncML: delete: $guid", __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde :: logMessage("SyncML: delete: $guid", __FILE__, __LINE__, PEAR_LOG_DEBUG);
// Create a Delete request for client.
$cmd = &new Horde_SyncML_Command_Sync_ContentSyncElement();
$cmd->setTargetURI($locid);
$cmd->setSourceURI($guid);
$cmd = new Horde_SyncML_Command_Sync_ContentSyncElement();
$cmd->setLocURI($locid);
$currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Delete');
$state->log('Server-Delete');
$state->removeUID($syncType, $locid);
$contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI, $this->_targetLocURI);
// return if we have to much data
if(++$counter >= MAX_ENTRIES
&& isset($contentType['mayFragment'])
&& $contentType['mayFragment'])
{
// moreData split; save in session state and end current message
if ($cmd->hasMoreData()) {
$state->curSyncItem = & $cmd;
$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
return $currentCmdID;
}
$state->incNumberOfElements();
}
}
// handle remote deletes due to conflicts
if (count($conflicts) > 0) {
while ($locid = array_shift($conflicts)) {
$currentSize = $output->getOutputSize();
// return if we have to much data
if (($maxEntries && ($state->getNumberOfElements() >= $maxEntries)
&& isset ($contentType['mayFragment'])
&& $contentType['mayFragment'])
|| ($maxMsgSize
&& (($currentSize +MIN_MSG_LEFT * 2) > $maxMsgSize))) {
// put the item back in the queue
$conflicts[] = $locid;
$state->maxNumberOfElements();
$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
return $currentCmdID;
}
Horde :: logMessage("SyncML: delete client locid: $locid", __FILE__, __LINE__, PEAR_LOG_DEBUG);
// Create a Delete request for client.
$cmd = new Horde_SyncML_Command_Sync_ContentSyncElement();
$cmd->setLocURI($locid);
$currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Delete');
$state->log('Server-DeletedConflicts');
$state->removeUID($syncType, $locid);
// moreData split; save in session state and end current message
if ($cmd->hasMoreData()) {
$state->curSyncItem = & $cmd;
$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
return $currentCmdID;
}
$state->incNumberOfElements();
}
}
#Horde::logMessage("SyncML: handling sync ".$currentCmdID, __FILE__, __LINE__, PEAR_LOG_DEBUG);
// handle missing adds.
if(count($missedAdds) > 0) {
Horde::logMessage("SyncML: add missed changes as adds ".count($adds).' / '.$missedAdds[0], __FILE__, __LINE__, PEAR_LOG_DEBUG);
$state->setAddedItems($hordeType, array_merge($adds, $missedAdds));
$adds = &$state->getAddedItems($hordeType);
Horde::logMessage("SyncML: merged adds counter ".count($adds).' / '.$adds[0], __FILE__, __LINE__, PEAR_LOG_DEBUG);
if (count($missedAdds) > 0) {
Horde :: logMessage("SyncML: add missed changes as adds " . count($adds) . ' / ' . $missedAdds[0], __FILE__, __LINE__, PEAR_LOG_DEBUG);
$adds = array_merge($adds, $missedAdds);
Horde :: logMessage("SyncML: merged adds counter " . count($adds) . ' / ' . $adds[0], __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
if(is_array($adds)) {
while($guid = array_shift($adds)) {
$guid_ts = $state->getSyncTSforAction($guid, 'add');
if (is_array($adds)) {
while ($guid = array_shift($adds)) {
$currentSize = $output->getOutputSize();
// return if we have to much data
if (($maxEntries && ($state->getNumberOfElements() >= $maxEntries)
&& isset ($contentType['mayFragment'])
&& $contentType['mayFragment'])
|| ($maxMsgSize
&& (($currentSize +MIN_MSG_LEFT * 2) > $maxMsgSize))) {
// put the item back in the queue
$adds[] = $guid;
$state->maxNumberOfElements();
$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
return $currentCmdID;
}
// first we try the modification timestamp then the creation ts
if (!($guid_ts = $state->getSyncTSforAction($guid, 'modify'))) {
$guid_ts = $state->getSyncTSforAction($guid, 'add');
}
$sync_ts = $state->getChangeTS($syncType, $guid);
Horde::logMessage("SyncML: timestamp add $guid guid_ts: $guid_ts sync_ts: $sync_ts", __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde :: logMessage("SyncML: timestamp add $guid guid_ts: $guid_ts sync_ts: $sync_ts", __FILE__, __LINE__, PEAR_LOG_DEBUG);
if ($sync_ts && $sync_ts == $guid_ts) {
// Change was done by us upon request of client.
// Don't mirror that back to the client.
Horde::logMessage("SyncML: add: $guid ignored, came from client", __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde :: logMessage("SyncML: add: $guid ignored, came from client", __FILE__, __LINE__, PEAR_LOG_DEBUG);
continue;
}
if ($guid_ts > $serverAnchorNext && !in_array($guid, $conflicts)) {
// Change was made after we started this sync.
// Don't sent this now to the client.
Horde :: logMessage("SyncML: add $guid is in our future: $serverAnchorNext", __FILE__, __LINE__, PEAR_LOG_DEBUG);
continue;
}
@ -179,47 +327,65 @@ class Horde_SyncML_Sync_TwoWaySync extends Horde_SyncML_Sync {
// For slow sync (ts=0): do not add data for which we
// have a locid again. This is a heuristic to avoid
// duplication of entries.
Horde::logMessage("SyncML: skipping add of guid $guid as there already is a locid $locid", __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde :: logMessage("SyncML: skipping add of guid $guid as there already is a locid $locid", __FILE__, __LINE__, PEAR_LOG_DEBUG);
continue;
}
Horde::logMessage("SyncML: add: $guid", __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde :: logMessage("SyncML: add: $guid", __FILE__, __LINE__, PEAR_LOG_DEBUG);
// Create an Add request for client.
$contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI, $this->_targetLocURI);
$cmd = &new Horde_SyncML_Command_Sync_ContentSyncElement();
$c = $registry->call($hordeType . '/export',
array(
'guid' => $guid ,
'contentType' => $contentType ,
)
);
$c = $registry->call($hordeType . '/export', array (
'guid' => $guid,
'contentType' => $contentType,
if (!is_a($c, 'PEAR_Error')) {
));
if (is_a($c, 'PEAR_Error')) {
// Item in history but not in database. Strange, but can happen.
$cmd->setContent($c);
$cmd->setContentType($contentType['ContentType']);
if (isset($contentType['ContentFormat']))
{
$cmd->setContentFormat($contentType['ContentFormat']);
}
$cmd->setSourceURI($guid);
$currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Add');
$state->log('Server-Add');
Horde :: logMessage("SyncML: add: export of guid $guid failed:\n" . print_r($c, true), __FILE__, __LINE__, PEAR_LOG_WARNING);
continue;
}
// return if we have to much data
if(++$counter >= MAX_ENTRIES
&& isset($contentType['mayFragment'])
&& $contentType['mayFragment'])
{
$size = strlen($c);
// return if we have to much data
if ($maxMsgSize && !$deviceInfo['supportLargeObjs']) {
if (($size +MIN_MSG_LEFT * 2) > $maxMsgSize) {
Horde :: logMessage("SyncML: add: export of guid $guid failed due to size $size", __FILE__, __LINE__, PEAR_LOG_ERROR);
$state->log("Server-ExportFailed");
continue;
}
if (($currentSize + $size +MIN_MSG_LEFT * 2) > $maxMsgSize) {
// put the item back in the queue
$adds[] = $guid;
$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
return $currentCmdID;
}
}
Horde :: logMessage("SyncML: add guid $guid to client\n$c", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$cmd = new Horde_SyncML_Command_Sync_ContentSyncElement();
$cmd->setContent($c);
$cmd->setContentType($contentType['ContentType']);
if (isset ($contentType['ContentFormat'])) {
$cmd->setContentFormat($contentType['ContentFormat']);
}
$cmd->setGUID($guid);
$currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Add');
$state->log('Server-Add');
// moreData split; put the guid back in the list and return
if ($cmd->hasMoreData()) {
$state->curSyncItem = & $cmd;
$state->setSyncStatus(SERVER_SYNC_DATA_PENDING);
return $currentCmdID;
}
$state->incNumberOfElements();
}
}
#Horde::logMessage("SyncML: handling sync ".$currentCmdID, __FILE__, __LINE__, PEAR_LOG_DEBUG);
Horde::logMessage("SyncML: All items handled for sync $syncType", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$state->removeExpiredUID($syncType, time());
$state->clearSync($syncType);
return $currentCmdID;
@ -228,24 +394,53 @@ class Horde_SyncML_Sync_TwoWaySync extends Horde_SyncML_Sync {
function loadData() {
global $registry;
$state = &$_SESSION['SyncML.state'];
$state = & $_SESSION['SyncML.state'];
$syncType = $this->_targetLocURI;
$hordeType = $state->getHordeType($syncType);
$state->setTargetURI($syncType);
$refts = $state->getServerAnchorLast($syncType);
$future = $state->getServerAnchorNext($syncType);
$delta_mod = 0;
$delta_add = 0;
Horde::logMessage("SyncML: reading changed items from database for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$state->setChangedItems($hordeType, $registry->call($hordeType. '/listBy', array('action' => 'modify', 'timestamp' => $refts)));
Horde :: logMessage("SyncML: reading changed items from database for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$delta_mod = count($registry->call($hordeType . '/listBy', array (
'action' => 'modify',
'timestamp' => $future,
'type' => $syncType,
'filter' => $this->_filterExpression
)));
$state->mergeChangedItems($syncType, $registry->call($hordeType . '/listBy', array (
'action' => 'modify',
'timestamp' => $refts,
'type' => $syncType,
'filter' => $this->_filterExpression
)));
Horde::logMessage("SyncML: reading deleted items from database for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$state->setDeletedItems($hordeType, $registry->call($hordeType. '/listBy', array('action' => 'delete', 'timestamp' => $refts)));
Horde :: logMessage("SyncML: reading deleted items from database for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$state->setDeletedItems($syncType, $registry->call($hordeType . '/listBy', array (
'action' => 'delete',
'timestamp' => $refts,
'type' => $syncType,
'filter' => $this->_filterExpression
)));
Horde::logMessage("SyncML: reading added items from database for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$state->setAddedItems($hordeType, $registry->call($hordeType. '/listBy', array('action' => 'add', 'timestamp' => $refts)));
Horde :: logMessage("SyncML: reading added items from database for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG);
$delta_add = count($registry->call($hordeType . '/listBy', array (
'action' => 'add',
'timestamp' => $future,
'type' => $syncType,
'filter' => $this->_filterExpression
)));
$state->mergeAddedItems($syncType, $registry->call($hordeType . '/listBy', array (
'action' => 'add',
'timestamp' => $refts,
'type' => $syncType,
'filter' => $this->_filterExpression
)));
$this->_syncDataLoaded = TRUE;
return count($state->getChangedItems($hordeType)) +
count($state->getDeletedItems($hordeType)) +
count($state->getAddedItems($hordeType));
return count($state->getChangedItems($syncType)) - $delta_mod + count($state->getDeletedItems($syncType)) + count($state->getAddedItems($syncType)) - $delta_add +count($state->getConflictItems($syncType));
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,15 +2,14 @@
/**
* Class representing vAlarms.
*
* $Horde: framework/iCalendar/iCalendar/valarm.php,v 1.8 2004/08/13 19:11:35 karsten Exp $
* $Horde: framework/iCalendar/iCalendar/valarm.php,v 1.8.10.8 2008/07/03 08:42:58 jan Exp $
*
* Copyright 2003-2004 Mike Cochrane <mike@graftonhall.co.nz>
* Copyright 2003-2008 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Mike Cochrane <mike@graftonhall.co.nz>
* @version $Revision$
* @since Horde 3.0
* @package Horde_iCalendar
*/
@ -21,11 +20,6 @@ class Horde_iCalendar_valarm extends Horde_iCalendar {
return 'vAlarm';
}
function parsevCalendar($data)
{
parent::parsevCalendar($data, 'VALARM');
}
function exportvCalendar()
{
return parent::_exportvData('VALARM');

View File

@ -2,7 +2,6 @@
require_once EGW_API_INC.'/horde/Horde/iCalendar.php';
// The following were shamelessly yoinked from Contact_Vcard_Build
// Part numbers for N components.
define('VCARD_N_FAMILY', 0);
@ -27,45 +26,47 @@ define('VCARD_GEO_LON', 1);
/**
* Class representing vCard entries.
*
* $Horde: framework/iCalendar/iCalendar/vcard.php,v 1.2 2004/08/18 03:16:24 chuck Exp $
* $Horde: framework/iCalendar/iCalendar/vcard.php,v 1.3.10.16 2008/09/22 04:16:30 chuck Exp $
*
* Copyright 2003-2004 Karsten Fourmont (karsten@horde.org)
* Copyright 2003-2008 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Karsten Fourmont <karsten@horde.org>
* @version $Revision$
* @package Horde_iCalendar
*/
class Horde_iCalendar_vcard extends Horde_iCalendar {
function Horde_iCalendar_vcard($version = '2.1')
{
return parent::Horde_iCalendar($version);
}
function getType()
{
return 'vcard';
}
function parsevCalendar($data)
{
return parent::parsevCalendar($data, 'vcard');
}
/**
* Unlike vevent and vtodo, a vcard is normally not enclosed in an
* iCalendar container. (BEGIN..END)
*/
function exportvCalendar()
{
#$requiredAttributes['BODY'] = '';
$requiredAttributes['VERSION'] = '2.1';
$requiredAttributes['VERSION'] = $this->_version;
$requiredAttributes['N'] = ';;;;;;';
if ($this->_version == '3.0') {
$requiredAttributes['FN'] = '';
}
foreach ($requiredAttributes as $name => $default_value) {
if (is_a($this->getattribute($name), 'PEAR_Error')) {
if (is_a($this->getAttribute($name), 'PEAR_Error')) {
$this->setAttribute($name, $default_value);
}
}
return $this->_exportvData('VCARD') . $this->_newline;
return $this->_exportvData('VCARD');
}
/**
@ -76,8 +77,7 @@ class Horde_iCalendar_vcard extends Horde_iCalendar {
* to
* "Professor Dagobert T Duck Sen"
*
* @return string Full name of vcard "N" tag
* or null if no N tag.
* @return string Full name of vcard "N" tag or null if no N tag.
*/
function printableName()
{
@ -86,6 +86,8 @@ class Horde_iCalendar_vcard extends Horde_iCalendar {
return null;
}
$name_arr = array();
if (!empty($name_parts[VCARD_N_PREFIX])) {
$name_arr[] = $name_parts[VCARD_N_PREFIX];
}
@ -114,16 +116,22 @@ class Horde_iCalendar_vcard extends Horde_iCalendar {
*/
function getBareEmail($address)
{
// Empty values are still empty.
if (!$address) {
return $address;
}
require_once 'Mail/RFC822.php';
require_once 'Horde/MIME.php';
static $rfc822;
if (is_null($rfc822)) {
$rfc822 = &new Mail_RFC822();
$rfc822 = new Mail_RFC822();
}
$rfc822->validateMailbox($address);
if (!$rfc822->validateMailbox($address)) {
return $address;
}
return MIME::rfc822WriteAddress($address->mailbox, $address->host);
}

View File

@ -2,15 +2,14 @@
/**
* Class representing vEvents.
*
* $Horde: framework/iCalendar/iCalendar/vevent.php,v 1.31 2004/08/18 03:16:24 chuck Exp $
* $Horde: framework/iCalendar/iCalendar/vevent.php,v 1.31.10.15 2008/07/03 08:42:58 jan Exp $
*
* Copyright 2003-2004 Mike Cochrane <mike@graftonhall.co.nz>
* Copyright 2003-2008 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Mike Cochrane <mike@graftonhall.co.nz>
* @version $Revision$
* @since Horde 3.0
* @package Horde_iCalendar
*/
@ -21,32 +20,30 @@ class Horde_iCalendar_vevent extends Horde_iCalendar {
return 'vEvent';
}
function parsevCalendar($data)
{
parent::parsevCalendar($data, 'VEVENT');
}
function exportvCalendar()
{
// Default values.
$requiredAttributes = array();
$requiredAttributes['DTSTAMP'] = time();
#$requiredAttributes['ORGANIZER'] = 'Unknown Organizer';
$requiredAttributes['UID'] = $this->_exportDateTime(time()) . '@' . $_SERVER['SERVER_NAME'];
/* This is handled by the upper layers.
$requiredAttributes['UID'] = $this->_exportDateTime(time())
. substr(str_pad(base_convert(microtime(), 10, 36), 16, uniqid(mt_rand()), STR_PAD_LEFT), -16)
. '@' . (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost');
*/
$method = !empty($this->_container) ?
$this->_container->getAttribute('METHOD') : 'PUBLISH';
switch ($method) {
case 'PUBLISH':
$requiredAttributes['DTSTART'] = time();
$requiredAttributes['SUMMARY'] = '';
$requiredAttributes['DTSTART'] = time();
$requiredAttributes['SUMMARY'] = '';
break;
case 'REQUEST':
$requiredAttributes['ATTENDEE'] = '';
$requiredAttributes['DTSTART'] = time();
$requiredAttributes['SUMMARY'] = '';
$requiredAttributes['DTSTART'] = time();
$requiredAttributes['SUMMARY'] = '';
break;
case 'REPLY':
@ -54,9 +51,9 @@ class Horde_iCalendar_vevent extends Horde_iCalendar {
break;
case 'ADD':
$requiredAttributes['DTSTART'] = time();
$requiredAttributes['DTSTART'] = time();
$requiredAttributes['SEQUENCE'] = 1;
$requiredAttributes['SUMMARY'] = '';
$requiredAttributes['SUMMARY'] = '';
break;
case 'CANCEL':
@ -88,7 +85,8 @@ class Horde_iCalendar_vevent extends Horde_iCalendar {
function updateAttendee($email, $status, $fullname = '')
{
foreach ($this->_attributes as $key => $attribute) {
if ($attribute['name'] == 'ATTENDEE' && $attribute['value'] == 'MAILTO:' . $email) {
if ($attribute['name'] == 'ATTENDEE' &&
$attribute['value'] == 'mailto:' . $email) {
$this->_attributes[$key]['params']['PARTSTAT'] = $status;
if (!empty($fullname)) {
$this->_attributes[$key]['params']['CN'] = $fullname;
@ -101,7 +99,7 @@ class Horde_iCalendar_vevent extends Horde_iCalendar {
if (!empty($fullname)) {
$params['CN'] = $fullname;
}
$this->setAttribute('ATTENDEE', 'MAILTO:' . $email, $params);
$this->setAttribute('ATTENDEE', 'mailto:' . $email, $params);
}
/**
@ -113,7 +111,7 @@ class Horde_iCalendar_vevent extends Horde_iCalendar {
{
$organizer = $this->getAttribute('ORGANIZER', true);
if (is_a($organizer, 'PEAR_Error')) {
return null;
return _("An unknown person");
}
if (isset($organizer[0]['CN'])) {
@ -128,7 +126,7 @@ class Horde_iCalendar_vevent extends Horde_iCalendar {
/**
* Update this event with details from another event.
*
* @param object Horde_iCalendar_vEvent $vevent The vEvent with latest details.
* @param Horde_iCalendar_vEvent $vevent The vEvent with latest details.
*/
function updateFromvEvent($vevent)
{
@ -137,7 +135,9 @@ class Horde_iCalendar_vevent extends Horde_iCalendar {
$currentValue = $this->getAttribute($newAttribute['name']);
if (is_a($currentValue, 'PEAR_error')) {
// Already exists so just add it.
$this->setAttribute($newAttribute['name'], $newAttribute['value'], $newAttribute['params']);
$this->setAttribute($newAttribute['name'],
$newAttribute['value'],
$newAttribute['params']);
} else {
// Already exists so locate and modify.
$found = false;
@ -178,19 +178,21 @@ class Horde_iCalendar_vevent extends Horde_iCalendar {
* Update just the attendess of event with details from another
* event.
*
* @param object Horde_iCalendar_vEvent $vevent The vEvent with latest details
* @param Horde_iCalendar_vEvent $vevent The vEvent with latest details
*/
function updateAttendeesFromvEvent($vevent)
{
$newAttributes = $vevent->getAllAttributes();
foreach ($newAttributes as $newAttribute) {
if (!$newAttribute['name'] == 'ATTENDEE') {
if ($newAttribute['name'] != 'ATTENDEE') {
continue;
}
$currentValue = $this->getAttribute($newAttribute['name']);
if (is_a($currentValue, 'PEAR_error')) {
// Already exists so just add it.
$this->setAttribute($newAttribute['name'], $newAttribute['value'], $newAttribute['params']);
$this->setAttribute($newAttribute['name'],
$newAttribute['value'],
$newAttribute['params']);
} else {
// Already exists so locate and modify.
$found = false;

View File

@ -1,52 +1,78 @@
<?php
/**
* Class representing vFreebusys.
* Class representing vFreebusy components.
*
* $Horde: framework/iCalendar/iCalendar/vfreebusy.php,v 1.16 2004/08/18 03:16:24 chuck Exp $
* $Horde: framework/iCalendar/iCalendar/vfreebusy.php,v 1.16.10.17 2008/09/17 08:46:57 jan Exp $
*
* Copyright 2003-2004 Mike Cochrane <mike@graftonhall.co.nz>
* Copyright 2003-2008 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @todo Don't use timestamps
*
* @author Mike Cochrane <mike@graftonhall.co.nz>
* @version $Revision$
* @since Horde 3.0
* @package Horde_iCalendar
*/
class Horde_iCalendar_vfreebusy extends Horde_iCalendar {
var $_busyPeriods = array();
var $_extraParams = array();
/**
* Returns the type of this calendar component.
*
* @return string The type of this component.
*/
function getType()
{
return 'vFreebusy';
}
function parsevCalendar($data)
/**
* Parses a string containing vFreebusy data.
*
* @param string $data The data to parse.
*/
function parsevCalendar($data, $type = null, $charset = null)
{
parent::parsevCalendar($data, 'VFREEBUSY');
parent::parsevCalendar($data, 'VFREEBUSY', $charset);
// Do something with all the busy periods.
foreach ($this->_attributes as $key => $attribute) {
if ($attribute['name'] == 'FREEBUSY') {
foreach ($attribute['value'] as $value) {
if (array_key_exists('duration', $attribute['value'])) {
$this->addBusyPeriod('BUSY', $value['start'], null, $value['duration']);
} else {
$this->addBusyPeriod('BUSY', $value['start'], $value['end']);
}
}
unset($this->_attributes[$key]);
if ($attribute['name'] != 'FREEBUSY') {
continue;
}
foreach ($attribute['values'] as $value) {
$params = isset($attribute['params'])
? $attribute['params']
: array();
if (isset($value['duration'])) {
$this->addBusyPeriod('BUSY', $value['start'], null,
$value['duration'], $params);
} else {
$this->addBusyPeriod('BUSY', $value['start'],
$value['end'], null, $params);
}
}
unset($this->_attributes[$key]);
}
}
/**
* Returns the component exported as string.
*
* @return string The exported vFreeBusy information according to the
* iCalender format specification.
*/
function exportvCalendar()
{
foreach ($this->_busyPeriods as $start => $end) {
$periods = array(array('start' => $start, 'end' => $end));
$this->setAttribute('FREEBUSY', $periods);
$this->setAttribute('FREEBUSY', $periods,
isset($this->_extraParams[$start])
? $this->_extraParams[$start] : array());
}
$res = parent::_exportvData('VFREEBUSY');
@ -61,7 +87,9 @@ class Horde_iCalendar_vfreebusy extends Horde_iCalendar {
}
/**
* Get a display name for this object.
* Returns a display name for this object.
*
* @return string A clear text name for displaying this object.
*/
function getName()
{
@ -71,12 +99,12 @@ class Horde_iCalendar_vfreebusy extends Horde_iCalendar {
if (is_a($method, 'PEAR_Error') || $method == 'PUBLISH') {
$attr = 'ORGANIZER';
} else if ($method == 'REPLY') {
} elseif ($method == 'REPLY') {
$attr = 'ATTENDEE';
}
$name = $this->getAttribute($attr, true);
if (array_key_exists('CN', $name[0])) {
if (!is_a($name, 'PEAR_Error') && isset($name[0]['CN'])) {
return $name[0]['CN'];
}
@ -90,7 +118,9 @@ class Horde_iCalendar_vfreebusy extends Horde_iCalendar {
}
/**
* Get the email address for this object.
* Returns the email address for this object.
*
* @return string The email address of this object's owner.
*/
function getEmail()
{
@ -100,7 +130,7 @@ class Horde_iCalendar_vfreebusy extends Horde_iCalendar {
if (is_a($method, 'PEAR_Error') || $method == 'PUBLISH') {
$attr = 'ORGANIZER';
} else if ($method == 'REPLY') {
} elseif ($method == 'REPLY') {
$attr = 'ATTENDEE';
}
@ -113,13 +143,34 @@ class Horde_iCalendar_vfreebusy extends Horde_iCalendar {
}
}
/**
* Returns the busy periods.
*
* @return array All busy periods.
*/
function getBusyPeriods()
{
return $this->_busyPeriods;
}
/**
* Return all the free periods of time in a given period.
* Returns any additional freebusy parameters.
*
* @return array Additional parameters of the freebusy periods.
*/
function getExtraParams()
{
return $this->_extraParams;
}
/**
* Returns all the free periods of time in a given period.
*
* @param integer $startStamp The start timestamp.
* @param integer $endStamp The end timestamp.
*
* @return array A hash with free time periods, the start times as the
* keys and the end times as the values.
*/
function getFreePeriods($startStamp, $endStamp)
{
@ -131,21 +182,21 @@ class Horde_iCalendar_vfreebusy extends Horde_iCalendar {
return $periods;
}
// Locate the first time in the requested period we have data
// for.
// Locate the first time in the requested period we have data for.
$nextstart = max($startStamp, $this->getStart());
// Check each busy period and add free periods in between.
foreach ($this->_busyPeriods as $start => $end) {
if ($start <= $endStamp && $end >= $nextstart) {
$periods[$nextstart] = min($start, $endStamp);
if ($nextstart <= $start) {
$periods[$nextstart] = min($start, $endStamp);
}
$nextstart = min($end, $endStamp);
}
}
// If we didn't read the end of the requested period but still
// have data then mark as free to the end of the period or
// available data.
// If we didn't read the end of the requested period but still have
// data then mark as free to the end of the period or available data.
if ($nextstart < $endStamp && $nextstart < $this->getEnd()) {
$periods[$nextstart] = min($this->getEnd(), $endStamp);
}
@ -154,16 +205,29 @@ class Horde_iCalendar_vfreebusy extends Horde_iCalendar {
}
/**
* Add a busy period to the info.
* Adds a busy period to the info.
*
* This function may throw away data in case you add a period with a start
* date that already exists. The longer of the two periods will be chosen
* (and all information associated with the shorter one will be removed).
*
* @param string $type The type of the period. Either 'FREE' or
* 'BUSY'; only 'BUSY' supported at the moment.
* @param integer $start The start timestamp of the period.
* @param integer $end The end timestamp of the period.
* @param integer $duration The duration of the period. If specified, the
* $end parameter will be ignored.
* @param array $extra Additional parameters for this busy period.
*/
function addBusyPeriod($type, $start, $end = null, $duration = null)
function addBusyPeriod($type, $start, $end = null, $duration = null,
$extra = array())
{
if ($type == "FREE") {
if ($type == 'FREE') {
// Make sure this period is not marked as busy.
return false;
}
// Calculate the end time is duration was specified.
// Calculate the end time if duration was specified.
$tempEnd = is_null($duration) ? $end : $start + $duration;
// Make sure the period length is always positive.
@ -171,26 +235,35 @@ class Horde_iCalendar_vfreebusy extends Horde_iCalendar {
$start = min($start, $tempEnd);
if (isset($this->_busyPeriods[$start])) {
// Already a period starting at this time. Extend to the
// length of the longest of the two.
$this->_busyPeriods[$start] = max($end, $this->_busyPeriods[$start]);
// Already a period starting at this time. Change the current
// period only if the new one is longer. This might be a problem
// if the callee assumes that there is no simplification going
// on. But since the periods are stored using the start time of
// the busy periods we have to throw away data here.
if ($end > $this->_busyPeriods[$start]) {
$this->_busyPeriods[$start] = $end;
$this->_extraParams[$start] = $extra;
}
} else {
// Add a new busy period.
$this->_busyPeriods[$start] = $end;
$this->_extraParams[$start] = $extra;
}
return true;
}
/**
* Get the timestamp of the start of the time period this free
* busy information covers.
* Returns the timestamp of the start of the time period this free busy
* information covers.
*
* @return integer A timestamp.
*/
function getStart()
{
if (!is_a($this->getAttribute('DTSTART'), 'PEAR_Error')) {
return $this->getAttribute('DTSTART');
} else if (count($this->_busyPeriods)) {
} elseif (count($this->_busyPeriods)) {
return min(array_keys($this->_busyPeriods));
} else {
return false;
@ -198,14 +271,16 @@ class Horde_iCalendar_vfreebusy extends Horde_iCalendar {
}
/**
* Get the timestamp of the end of the time period this free busy
* Returns the timestamp of the end of the time period this free busy
* information covers.
*
* @return integer A timestamp.
*/
function getEnd()
{
if (!is_a($this->getAttribute('DTEND'), 'PEAR_Error')) {
return $this->getAttribute('DTEND');
} else if (count($this->_busyPeriods)) {
} elseif (count($this->_busyPeriods)) {
return max(array_values($this->_busyPeriods));
} else {
return false;
@ -213,7 +288,16 @@ class Horde_iCalendar_vfreebusy extends Horde_iCalendar {
}
/**
* Merge the busy periods of another VFreebusy into this one.
* Merges the busy periods of another Horde_iCalendar_vfreebusy object
* into this one.
*
* This might lead to simplification no matter what you specify for the
* "simplify" flag since periods with the same start date will lead to the
* shorter period being removed (see addBusyPeriod).
*
* @param Horde_iCalendar_vfreebusy $freebusy A freebusy object.
* @param boolean $simplify If true, simplify() will
* called after the merge.
*/
function merge($freebusy, $simplify = true)
{
@ -221,70 +305,155 @@ class Horde_iCalendar_vfreebusy extends Horde_iCalendar {
return false;
}
$extra = $freebusy->getExtraParams();
foreach ($freebusy->getBusyPeriods() as $start => $end) {
$this->addBusyPeriod('BUSY', $start, $end);
// This might simplify the busy periods without taking the
// "simplify" flag into account.
$this->addBusyPeriod('BUSY', $start, $end, null,
isset($extra[$start])
? $extra[$start] : array());
}
$thisattr = $this->getAttribute('DTSTART');
$thatattr = $freebusy->getAttribute('DTSTART');
if (is_a($thisattr, 'PEAR_Error') && !is_a($thatattr, 'PEAR_Error')) {
$this->setAttribute('DTSTART', $thatattr);
$this->setAttribute('DTSTART', $thatattr, array(), false);
} elseif (!is_a($thatattr, 'PEAR_Error')) {
if ($thatattr > $thisattr) {
$this->setAttribute('DTSTART', $thatattr);
if ($thatattr < $thisattr) {
$this->setAttribute('DTSTART', $thatattr, array(), false);
}
}
$thisattr = $this->getAttribute('DTEND');
$thatattr = $freebusy->getAttribute('DTEND');
if (is_a($thisattr, 'PEAR_Error') && !is_a($thatattr, 'PEAR_Error')) {
$this->setAttribute('DTEND', $thatattr);
$this->setAttribute('DTEND', $thatattr, array(), false);
} elseif (!is_a($thatattr, 'PEAR_Error')) {
if ($thatattr < $thisattr) {
$this->setAttribute('DTEND', $thatattr);
if ($thatattr > $thisattr) {
$this->setAttribute('DTEND', $thatattr, array(), false);
}
}
if ($simplify) {
$this->simplify();
}
return true;
}
/**
* Remove all overlaps and simplify the busy periods array as much
* as possible.
* Removes all overlaps and simplifies the busy periods array as much as
* possible.
*/
function simplify()
{
$clean = false;
$busy = array($this->_busyPeriods, $this->_extraParams);
while (!$clean) {
$result = $this->_simplify($busy[0], $busy[1]);
$clean = $result === $busy;
$busy = $result;
}
ksort($result[1], SORT_NUMERIC);
$this->_extraParams = $result[1];
ksort($result[0], SORT_NUMERIC);
$this->_busyPeriods = $result[0];
}
function _simplify($busyPeriods, $extraParams = array())
{
$checked = array();
$checkedExtra = array();
$checkedEmpty = true;
foreach ($this->_busyPeriods as $start => $end) {
foreach ($busyPeriods as $start => $end) {
if ($checkedEmpty) {
$checked[$start] = $end;
$checkedExtra[$start] = isset($extraParams[$start])
? $extraParams[$start] : array();
$checkedEmpty = false;
} else {
$added = false;
foreach ($checked as $testStart => $testEnd) {
if ($start == $testStart) {
$checked[$testStart] = max($testEnd, $end);
$added = true;
} else if ($end <= $testEnd && $end >= $testStart) {
// Replace old period if the new period lies around the
// old period.
if ($start <= $testStart && $end >= $testEnd) {
// Remove old period entry.
unset($checked[$testStart]);
$checked[min($testStart, $start)] = max($testEnd, $end);
unset($checkedExtra[$testStart]);
// Add replacing entry.
$checked[$start] = $end;
$checkedExtra[$start] = isset($extraParams[$start])
? $extraParams[$start] : array();
$added = true;
} elseif ($start >= $testStart && $end <= $testEnd) {
// The new period lies fully within the old
// period. Just forget about it.
$added = true;
} elseif (($end <= $testEnd && $end >= $testStart) ||
($start >= $testStart && $start <= $testEnd)) {
// Now we are in trouble: Overlapping time periods. If
// we allow for additional parameters we cannot simply
// choose one of the two parameter sets. It's better
// to leave two separated time periods.
$extra = isset($extraParams[$start])
? $extraParams[$start] : array();
$testExtra = isset($checkedExtra[$testStart])
? $checkedExtra[$testStart] : array();
// Remove old period entry.
unset($checked[$testStart]);
unset($checkedExtra[$testStart]);
// We have two periods overlapping. Are their
// additional parameters the same or different?
$newStart = min($start, $testStart);
$newEnd = max($end, $testEnd);
if ($extra === $testExtra) {
// Both periods have the same information. So we
// can just merge.
$checked[$newStart] = $newEnd;
$checkedExtra[$newStart] = $extra;
} else {
// Extra parameters are different. Create one
// period at the beginning with the params of the
// first period and create a trailing period with
// the params of the second period. The break
// point will be the end of the first period.
$break = min($end, $testEnd);
$checked[$newStart] = $break;
$checkedExtra[$newStart] =
isset($extraParams[$newStart])
? $extraParams[$newStart] : array();
$checked[$break] = $newEnd;
$highStart = max($start, $testStart);
$checkedExtra[$break] =
isset($extraParams[$highStart])
? $extraParams[$highStart] : array();
// Ensure we also have the extra data in the
// extraParams.
$extraParams[$break] =
isset($extraParams[$highStart])
? $extraParams[$highStart] : array();
}
$added = true;
}
if ($added) {
break;
}
}
if (!$added) {
$checked[$start] = $end;
$checkedExtra[$start] = isset($extraParams[$start])
? $extraParams[$start] : array();
}
}
}
ksort($checked, SORT_NUMERIC);
$this->_busyPeriods = $checked;
return array($checked, $checkedExtra);
}
}

View File

@ -2,15 +2,14 @@
/**
* Class representing vJournals.
*
* $Horde: framework/iCalendar/iCalendar/vjournal.php,v 1.8 2004/08/13 19:11:35 karsten Exp $
* $Horde: framework/iCalendar/iCalendar/vjournal.php,v 1.8.10.8 2008/07/03 08:42:58 jan Exp $
*
* Copyright 2003-2004 Mike Cochrane <mike@graftonhall.co.nz>
* Copyright 2003-2008 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Mike Cochrane <mike@graftonhall.co.nz>
* @version $Revision$
* @since Horde 3.0
* @package Horde_iCalendar
*/
@ -21,11 +20,6 @@ class Horde_iCalendar_vjournal extends Horde_iCalendar {
return 'vJournal';
}
function parsevCalendar($data)
{
parent::parsevCalendar($data, 'VJOURNAL');
}
function exportvCalendar()
{
return parent::_exportvData('VJOURNAL');

View File

@ -5,29 +5,29 @@ require_once EGW_API_INC.'/horde/Horde/iCalendar.php';
/**
* Class representing vNotes.
*
* $Horde: framework/iCalendar/iCalendar/vnote.php,v 1.2 2004/08/13 19:11:35 karsten Exp $
* $Horde: framework/iCalendar/iCalendar/vnote.php,v 1.3.10.9 2008/07/03 08:42:58 jan Exp $
*
* Copyright 2003-2004 Mike Cochrane <mike@graftonhall.co.nz>
* Copyright 2003-2008 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Mike Cochrane <mike@graftonhall.co.nz>
* @author Karsten Fourmont <fourmont@gmx.de>
* @version $Revision$
* @package Horde_iCalendar
*/
class Horde_iCalendar_vnote extends Horde_iCalendar {
function Horde_iCalendar_vnote($version = '1.1')
{
return parent::Horde_iCalendar($version);
}
function getType()
{
return 'vNote';
}
function parsevCalendar($data)
{
return parent::parsevCalendar($data, 'VNOTE');
}
/**
* Unlike vevent and vtodo, a vnote is normally not enclosed in an
* iCalendar container. (BEGIN..END)
@ -43,7 +43,7 @@ class Horde_iCalendar_vnote extends Horde_iCalendar {
}
}
return $this->_exportvData('VNOTE') . $this->_newline;
return $this->_exportvData('VNOTE');
}
}

View File

@ -2,15 +2,14 @@
/**
* Class representing vTimezones.
*
* $Horde: framework/iCalendar/iCalendar/vtimezone.php,v 1.8 2004/08/13 19:11:35 karsten Exp $
* $Horde: framework/iCalendar/iCalendar/vtimezone.php,v 1.8.10.9 2008/07/03 08:42:58 jan Exp $
*
* Copyright 2003-2004 Mike Cochrane <mike@graftonhall.co.nz>
* Copyright 2003-2008 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Mike Cochrane <mike@graftonhall.co.nz>
* @version $Revision$
* @since Horde 3.0
* @package Horde_iCalendar
*/
@ -21,16 +20,178 @@ class Horde_iCalendar_vtimezone extends Horde_iCalendar {
return 'vTimeZone';
}
function parsevCalendar($data)
{
parent::parsevCalendar($data, 'VTIMEZONE');
}
function exportvCalendar()
{
return parent::_exportvData('VTIMEZONE');
}
/**
* Parse child components of the vTimezone component. Returns an
* array with the exact time of the time change as well as the
* 'from' and 'to' offsets around the change. Time is arbitrarily
* based on UTC for comparison.
*/
function parseChild(&$child, $year)
{
// Make sure 'time' key is first for sort().
$result['time'] = 0;
$rrule_interval = 0; // 0 undefined, 1 yearly, 12 monthly
$t = $child->getAttribute('TZOFFSETFROM');
if (is_a($t, 'PEAR_Error')) {
return false;
}
$result['from'] = ($t['hour'] * 60 * 60 + $t['minute'] * 60) * ($t['ahead'] ? 1 : -1);
$t = $child->getAttribute('TZOFFSETTO');
if (is_a($t, 'PEAR_Error')) {
return false;
}
$result['to'] = ($t['hour'] * 60 * 60 + $t['minute'] * 60) * ($t['ahead'] ? 1 : -1);
$switch_time = $child->getAttribute('DTSTART');
if (is_a($switch_time, 'PEAR_Error')) {
return false;
}
$rrules = $child->getAttribute('RRULE');
if (is_a($rrules, 'PEAR_Error')) {
if (!is_int($switch_time)) {
return false;
}
// Convert this timestamp from local time to UTC for
// comparison (All dates are compared as if they are UTC).
$t = getdate($switch_time);
$result['time'] = @gmmktime($t['hours'], $t['minutes'], $t['seconds'],
$t['mon'], $t['mday'], $t['year']);
return $result;
}
$switch_year = date("Y", $switch_time);
if ( $switch_year > $year ) {
return false;
}
$rrules = explode(';', $rrules);
foreach ($rrules as $rrule) {
$t = explode('=', $rrule);
switch ($t[0]) {
case 'FREQ':
switch($t[1]) {
case 'YEARLY':
if ($rrule_interval == 12) {
return false;
}
$rrule_interval = 1;
break;
case 'MONTHLY':
if ($rrule_interval == 1) {
return false;
}
$rrule_interval = 12;
break;
default:
return false;
}
break;
case 'INTERVAL':
if ($rrule_interval && $t[1] != $rrule_interval) {
return false;
}
$rrule_interval = intval($t[1]);
if ($rrule_interval != 1 && $rrule_interval != 12) {
return false;
}
break;
case 'COUNT':
if ($switch_year + intval($t[1]) < intval($year)) {
return false;
}
break;
case 'BYMONTH':
$month = intval($t[1]);
break;
case 'BYDAY':
$len = strspn($t[1], '1234567890-+');
if ($len == 0) {
return false;
}
$weekday = substr($t[1], $len);
$weekdays = array(
'SU' => 0,
'MO' => 1,
'TU' => 2,
'WE' => 3,
'TH' => 4,
'FR' => 5,
'SA' => 6
);
$weekday = $weekdays[$weekday];
$which = intval(substr($t[1], 0, $len));
break;
case 'UNTIL':
if (intval($year) > intval(substr($t[1], 0, 4))) {
return false;
}
break;
}
}
if ($rrule_interval == 12) {
$month = date("n", $switch_time);
}
if (empty($month) || !isset($weekday)) {
return false;
}
if (is_int($switch_time)) {
// Was stored as localtime.
$switch_time = strftime('%H:%M:%S', $switch_time);
$switch_time = explode(':', $switch_time);
} else {
$switch_time = explode('T', $switch_time);
if (count($switch_time) != 2) {
return false;
}
$switch_time[0] = substr($switch_time[1], 0, 2);
$switch_time[2] = substr($switch_time[1], 4, 2);
$switch_time[1] = substr($switch_time[1], 2, 2);
}
// Get the timestamp for the first day of $month.
$when = gmmktime($switch_time[0], $switch_time[1], $switch_time[2],
$month, 1, $year);
// Get the day of the week for the first day of $month.
$first_of_month_weekday = intval(gmstrftime('%w', $when));
// Go to the first $weekday before first day of $month.
if ($weekday >= $first_of_month_weekday) {
$weekday -= 7;
}
$when -= ($first_of_month_weekday - $weekday) * 60 * 60 * 24;
// If going backwards go to the first $weekday after last day
// of $month.
if ($which < 0) {
do {
$when += 60*60*24*7;
} while (intval(gmstrftime('%m', $when)) == $month);
}
// Calculate $weekday number $which.
$when += $which * 60 * 60 * 24 * 7;
$result['time'] = $when;
return $result;
}
}
/**

View File

@ -2,15 +2,14 @@
/**
* Class representing vTodos.
*
* $Horde: framework/iCalendar/iCalendar/vtodo.php,v 1.13 2004/08/13 19:11:35 karsten Exp $
* $Horde: framework/iCalendar/iCalendar/vtodo.php,v 1.13.10.8 2008/07/03 08:42:58 jan Exp $
*
* Copyright 2003-2004 Mike Cochrane <mike@graftonhall.co.nz>
* Copyright 2003-2008 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Mike Cochrane <mike@graftonhall.co.nz>
* @version $Revision$
* @since Horde 3.0
* @package Horde_iCalendar
*/
@ -21,11 +20,6 @@ class Horde_iCalendar_vtodo extends Horde_iCalendar {
return 'vTodo';
}
function parsevCalendar($data)
{
parent::parsevCalendar($data, 'VTODO');
}
function exportvCalendar()
{
return parent::_exportvData('VTODO');

View File

@ -1,8 +1,8 @@
<?php
/**
* Constants are from Binary XML Content Format Specification Version
* 1.3, 25 July 2001 found at http://www.wapforum.org
* Constants are from Binary XML Content Format Specification Version 1.3, 25
* July 2001 found at http://www.wapforum.org
*/
/**
@ -49,8 +49,16 @@ define('DPI_DTD_WML_1_3', '-//WAPFORUM//DTD WML 1.3//EN');
define('DPI_DTD_PROV_1_0', '-//WAPFORUM//DTD PROV 1.0//EN');
define('DPI_DTD_WTA_WML_1_2', '-//WAPFORUM//DTD WTA-WML 1.2//EN');
define('DPI_DTD_CHANNEL_1_2', '-//WAPFORUM//DTD CHANNEL 1.2//EN');
define('DPI_DTD_SYNCML_1_0', '-//SYNCML//DTD SyncML 1.0//EN');
define('DPI_DTD_DEVINF_1_0', '-//SYNCML//DTD DevInf 1.0//EN');
define('DPI_DTD_METINF_1_0', '-//SYNCML//DTD MetInf 1.0//EN');
define('DPI_DTD_SYNCML_1_1', '-//SYNCML//DTD SyncML 1.1//EN');
define('DPI_DTD_DEVINF_1_1', '-//SYNCML//DTD DevInf 1.1//EN');
define('DPI_DTD_METINF_1_1', '-//SYNCML//DTD MetInf 1.1//EN');
define('DPI_DTD_SYNCML_1_2', '-//SYNCML//DTD SyncML 1.2//EN');
define('DPI_DTD_DEVINF_1_2', '-//SYNCML//DTD DevInf 1.2//EN');
define('DPI_DTD_METINF_1_2', '-//SYNCML//DTD MetInf 1.2//EN');
/**
* Only default character encodings from J2SE are currently supported.
@ -63,13 +71,14 @@ define('CHARSET_UTF_16LE', 'UTF-16LE');
define('CHARSET_UTF_16', 'UTF-16');
/**
* Copyright 2003-2006 Anthony Mills <amills@pyramid6.com>
* $Horde: framework/XML_WBXML/WBXML.php,v 1.13.12.11 2008/01/02 11:31:02 jan Exp $
*
* See the enclosed file COPYING for license information (LGPL). If you
* Copyright 2003-2008 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* $Horde: framework/XML_WBXML/WBXML.php,v 1.18 2006/01/01 21:10:25 jan Exp $
*
* @author Anthony Mills <amills@pyramid6.com>
* @package XML_WBXML
*/
class XML_WBXML {
@ -173,8 +182,17 @@ class XML_WBXML {
// Not all SyncML clients know this, so we
// should use the string table.
// 0xFD1 => DPI_DTD_SYNCML_1_1,
4051 => DPI_DTD_SYNCML_1_1,
4052 => DPI_DTD_DEVINF_1_1,
// These codes are taken from libwbxml wbxml_tables.h:
4049 => DPI_DTD_SYNCML_1_0, // 0x0fd1
4050 => DPI_DTD_DEVINF_1_0, // 0x0fd2
4051 => DPI_DTD_SYNCML_1_1, // 0x0fd3
4052 => DPI_DTD_DEVINF_1_1, // 0x0fd4
4609 => DPI_DTD_SYNCML_1_2, // 0x1201
//@todo: verify this:
4611 => DPI_DTD_DEVINF_1_2 // 0x1203
// taken from libxml but might be wrong:
// 4610 => DPI_DTD_DEVINF_1_2, // 0x1202
// 4611 => DPI_DTD_METINF_1_2 // 0x1203
);
return isset($DPIString[$i]) ? $DPIString[$i] : null;
}
@ -197,8 +215,18 @@ class XML_WBXML {
DPI_DTD_WTA_WML_1_2 => 12,
DPI_DTD_CHANNEL_1_2 => 13,
// Not all SyncML clients know this, so we
// Not all SyncML clients know this, so maybe we
// should use the string table.
// These codes are taken from libwbxml wbxml_tables.h:
DPI_DTD_SYNCML_1_0 => 4049,
DPI_DTD_DEVINF_1_0 => 4050,
DPI_DTD_SYNCML_1_1 => 4051,
DPI_DTD_DEVINF_1_1 => 4052,
DPI_DTD_SYNCML_1_2 => 4609, // 0x1201
// DPI_DTD_DEVINF_1_2 => 4610, // 0x1202
// DPI_DTD_METINF_1_2 => 4611 // 0x1203
//@todo: verify this
DPI_DTD_DEVINF_1_2 => 4611 // 0x1203
// DPI_DTD_SYNCML_1_1 => 0xFD1,
// DPI_DTD_DEVINF_1_1 => 0xFD2,
);

View File

@ -1,15 +1,16 @@
<?php
/**
* $Horde: framework/XML_WBXML/WBXML/ContentHandler.php,v 1.15 2006/01/01 21:10:25 jan Exp $
*
* Copyright 2003-2006 Anthony Mills <amills@pyramid6.com>
*
* See the enclosed file COPYING for license information (LGPL). If you did
* not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* From Binary XML Content Format Specification Version 1.3, 25 July 2001
* found at http://www.wapforum.org
*
* $Horde: framework/XML_WBXML/WBXML/ContentHandler.php,v 1.9.10.11 2008/08/26 15:41:13 jan Exp $
*
* Copyright 2003-2008 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Anthony Mills <amills@pyramid6.com>
* @package XML_WBXML
*/
class XML_WBXML_ContentHandler {
@ -32,12 +33,16 @@ class XML_WBXML_ContentHandler {
function XML_WBXML_ContentHandler()
{
$this->_currentUri = &new XML_WBXML_LifoQueue();
$this->_currentUri = new XML_WBXML_LifoQueue();
}
/**
*/
function raiseError($error)
{
include_once 'PEAR.php';
if (!class_exists('PEAR')) {
require 'PEAR.php';
}
return PEAR::raiseError($error);
}
@ -71,7 +76,7 @@ class XML_WBXML_ContentHandler {
return strlen($this->_output);
}
function startElement($uri, $element, $attrs)
function startElement($uri, $element, $attrs = array())
{
$this->_output .= '<' . $element;
@ -104,12 +109,7 @@ class XML_WBXML_ContentHandler {
function opaque($o)
{
// I can check the first chanracter and see if it is WBXML.
if (ord($o[0]) < 10) {
// Should decode this, I really need a call back function.
} else {
$this->_output .= $o;
}
$this->_output .= $o;
}
function setOpaqueHandler($opaqueHandler)
@ -122,6 +122,15 @@ class XML_WBXML_ContentHandler {
unset($this->_opaqueHandler);
}
function createSubHandler()
{
$name = get_class($this); // clone current class
$sh = new $name();
$sh->setCharset($this->getCharsetStr());
$sh->setVersion($this->getVersion());
return $sh;
}
}
class XML_WBXML_LifoQueue {

View File

@ -1,15 +1,16 @@
<?php
/**
* $Horde: framework/XML_WBXML/WBXML/DTD.php,v 1.8 2006/01/01 21:10:25 jan Exp $
*
* From Binary XML Content Format Specification Version 1.3, 25 July 2001
* found at http://www.wapforum.org
*
* Copyright 2003-2006 Anthony Mills <amills@pyramid6.com>
* $Horde: framework/XML_WBXML/WBXML/DTD.php,v 1.6.12.8 2008/01/02 11:31:02 jan Exp $
*
* See the enclosed file COPYING for license information (LGPL). If you
* Copyright 2003-2008 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Anthony Mills <amills@pyramid6.com>
* @package XML_WBXML
*/
class XML_WBXML_DTD {
@ -88,6 +89,11 @@ class XML_WBXML_DTD {
function toCodePageURI($uri)
{
$uri = strtolower($uri);
if (!isset($this->strCodePagesURI[$uri])) {
//Horde::logMessage("WBXML unable to find codepage for $uri!", __FILE__, __LINE__, PEAR_LOG_DEBUG);
//die("unable to find codepage for $uri!\n");
}
$ret = isset($this->strCodePagesURI[$uri]) ? $this->strCodePagesURI[$uri] : false;
return $ret;

View File

@ -3,16 +3,17 @@
include_once 'XML/WBXML/DTD.php';
/**
* $Horde: framework/XML_WBXML/WBXML/DTD/SyncML.php,v 1.11 2006/01/01 21:10:26 jan Exp $
*
* From Binary XML Content Format Specification Version 1.3, 25 July 2001
* found at http://www.wapforum.org
*
* Copyright 2003-2006 Anthony Mills <amills@pyramid6.com>
* $Horde: framework/XML_WBXML/WBXML/DTD/SyncML.php,v 1.6.12.8 2008/01/02 11:31:03 jan Exp $
*
* See the enclosed file COPYING for license information (LGPL). If you
* Copyright 2003-2008 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Anthony Mills <amills@pyramid6.com>
* @package XML_WBXML
*/
class XML_WBXML_DTD_SyncML extends XML_WBXML_DTD {
@ -72,17 +73,29 @@ class XML_WBXML_DTD_SyncML extends XML_WBXML_DTD {
$this->setTag(0x30, "Reserved for future use"); // 0x00
$this->setTag(0x31, "VerDTD"); // 0x00
$this->setTag(0x32, "VerProto"); // 0x00
$this->setTag(0x33, "NumberOfChanged"); // 0x00
$this->setTag(0x33, "NumberOfChanges"); // 0x00
$this->setTag(0x34, "MoreData"); // 0x00
$this->setTag(0x35, "Field"); // 0x00
$this->setTag(0x36, "Filter"); // 0x00
$this->setTag(0x37, "Record"); // 0x00
$this->setTag(0x38, "FilterType"); // 0x00
$this->setTag(0x39, "SourceParent"); // 0x00
$this->setTag(0x3a, "TargetParent"); // 0x00
$this->setTag(0x3b, "Move"); // 0x00
$this->setTag(0x3c, "Correlator"); // 0x00
if ($this->version == 0) {
$this->setCodePage(0, '-//SYNCML//DTD SyncML 1.0//EN', 'syncml:syncml1.0');
$this->setCodePage(1, '-//SYNCML//DTD MetInf 1.0//EN', 'syncml:metinf');
$this->setURI('syncml:syncml1.0');
} else {
$this->setCodePage(0, '-//SYNCML//DTD SyncML 1.1//EN', 'syncml:syncml1.1');
$this->setCodePage(1, '-//SYNCML//DTD MetInf 1.1//EN', 'syncml:metinf1.1');
if ($this->version == 1) {
$this->setCodePage(0, DPI_DTD_SYNCML_1_1, 'syncml:syncml1.1');
$this->setCodePage(1, DPI_DTD_METINF_1_1, 'syncml:metinf1.1');
$this->setURI('syncml:syncml1.1');
} elseif ($this->version == 2) {
$this->setCodePage(0, DPI_DTD_SYNCML_1_2, 'syncml:syncml1.2');
$this->setCodePage(1, DPI_DTD_METINF_1_2, 'syncml:metinf1.2');
$this->setURI('syncml:syncml1.2');
} else {
$this->setCodePage(0, DPI_DTD_SYNCML_1_0, 'syncml:syncml1.0');
$this->setCodePage(1, DPI_DTD_METINF_1_0, 'syncml:metinf1.0');
$this->setURI('syncml:syncml1.0');
}
}

View File

@ -3,16 +3,17 @@
include_once 'XML/WBXML/DTD.php';
/**
* $Horde: framework/XML_WBXML/WBXML/DTD/SyncMLDevInf.php,v 1.11 2006/01/01 21:10:26 jan Exp $
*
* Copyright 2003-2006 Anthony Mills <amills@pyramid6.com>
*
* From Binary XML Content Format Specification Version 1.3, 25 July 2001
* found at http://www.wapforum.org
*
* See the enclosed file COPYING for license information (LGPL). If you
* $Horde: framework/XML_WBXML/WBXML/DTD/SyncMLDevInf.php,v 1.4.12.8 2008/01/02 11:31:03 jan Exp $
*
* Copyright 2003-2008 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Anthony Mills <amills@pyramid6.com>
* @package XML_WBXML
*/
class XML_WBXML_DTD_SyncMLDevInf extends XML_WBXML_DTD {
@ -26,6 +27,8 @@ class XML_WBXML_DTD_SyncMLDevInf extends XML_WBXML_DTD {
* | sed -e 's#^.*\"\([^\"]*\)\", *\(0x..\), \(0x..\) },.*$# \$this->setTag\(\3, \"\1\"\); // \2#g'
*/
#Horde::logMessage("XML_WBXML_DTD_SyncMLDevInf version=" . $this->version, __FILE__, __LINE__, PEAR_LOG_DEBUG);
$this->setTag(0x05, "CTCap"); // 0x00
$this->setTag(0x06, "CTType"); // 0x00
$this->setTag(0x07, "DataStore"); // 0x00
@ -64,13 +67,25 @@ class XML_WBXML_DTD_SyncMLDevInf extends XML_WBXML_DTD {
$this->setTag(0x28, "UTC"); // 0x00
$this->setTag(0x29, "SupportNumberOfChanges"); // 0x00
$this->setTag(0x2a, "SupportLargeObjs"); // 0x00
$this->setTag(0x2b, "Property"); // 0x00
$this->setTag(0x2c, "PropParam"); // 0x00
$this->setTag(0x2d, "MaxOccur"); // 0x00
$this->setTag(0x2e, "NoTruncate"); // 0x00
$this->setTag(0x30, "Filter-Rx"); // 0x00
$this->setTag(0x31, "FilterCap"); // 0x00
$this->setTag(0x32, "FilterKeyword"); // 0x00
$this->setTag(0x33, "FieldLevel"); // 0x00
$this->setTag(0x34, "SupportHierarchicalSync"); // 0x00
if ($this->version == 0) {
$this->setCodePage(0, '-//SYNCML//DTD DevInf 1.0//EN', 'syncml:devinf');
$this->setURI('syncml:devinf');
} else {
$this->setCodePage(0, '-//SYNCML//DTD DevInf 1.1//EN', 'syncml:devinf1.1');
if ($this->version == 1) {
$this->setCodePage(0, DPI_DTD_DEVINF_1_1, 'syncml:devinf1.1');
$this->setURI('syncml:devinf1.1');
} elseif ($this->version == 2) {
$this->setCodePage(0, DPI_DTD_DEVINF_1_2, 'syncml:devinf1.2');
$this->setURI('syncml:devinf1.2');
} else {
$this->setCodePage(0, DPI_DTD_DEVINF_1_0, 'syncml:devinf1.0');
$this->setURI('syncml:devinf1.0');
}
}

View File

@ -3,16 +3,17 @@
include_once 'XML/WBXML/DTD.php';
/**
* $Horde: framework/XML_WBXML/WBXML/DTD/SyncMLMetInf.php,v 1.9 2006/01/01 21:10:26 jan Exp $
*
* Copyright 2003-2006 Anthony Mills <amills@pyramid6.com>
*
* From Binary XML Content Format Specification Version 1.3, 25 July 2001
* found at http://www.wapforum.org
*
* See the enclosed file COPYING for license information (LGPL). If you
* $Horde: framework/XML_WBXML/WBXML/DTD/SyncMLMetInf.php,v 1.4.12.8 2008/01/02 11:31:03 jan Exp $
*
* Copyright 2003-2008 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Anthony Mills <amills@pyramid6.com>
* @package XML_WBXML
*/
class XML_WBXML_DTD_SyncMLMetInf extends XML_WBXML_DTD {
@ -43,17 +44,22 @@ class XML_WBXML_DTD_SyncMLMetInf extends XML_WBXML_DTD {
$this->setTag(0x12, "Size"); // 0x01
$this->setTag(0x13, "Type"); // 0x01
$this->setTag(0x14, "Version"); // 0x01
$this->setTag(0x15, "MaxObjSize"); // 0x01
$this->setTag(0x16, "FieldLevel"); // 0x01
if ($this->version == 0) {
#$this->setCodePage(0, '-//SYNCML//DTD SyncML 1.0//EN', 'syncml:SYNCML1.0');
$this->setCodePage(0, '-//SYNCML//DTD SyncML 1.0//EN', 'syncml:syncml1.0');
$this->setCodePage(1, '-//SYNCML//DTD MetInf 1.0//EN', 'syncml:metinf');
$this->setURI('syncml:metinf');
} else {
$this->setCodePage(0, '-//SYNCML//DTD SyncML 1.1//EN', 'syncml:syncml1.1');
$this->setCodePage(1, '-//SYNCML//DTD MetInf 1.1//EN', 'syncml:metinf1.1');
if ($this->version == 1) {
$this->setCodePage(0, DPI_DTD_SYNCML_1_1, 'syncml:syncml1.1');
$this->setCodePage(1, DPI_DTD_METINF_1_1, 'syncml:metinf1.1');
$this->setURI('syncml:metinf1.1');
//$this->setURI('syncml:metinf'); // for some funny reason, libwbxml produces no :metinf1.1 here
} elseif ($this->version == 2) {
$this->setCodePage(0, DPI_DTD_SYNCML_1_2, 'syncml:syncml1.2');
$this->setCodePage(1, DPI_DTD_METINF_1_2, 'syncml:metinf1.2');
$this->setURI('syncml:metinf1.2');
} else {
$this->setCodePage(0, DPI_DTD_SYNCML_1_0, 'syncml:syncml1.0');
$this->setCodePage(1, DPI_DTD_METINF_1_0, 'syncml:metinf1.0');
$this->setURI('syncml:metinf1.0');
}
}

View File

@ -5,50 +5,94 @@ include_once 'XML/WBXML/DTD/SyncMLMetInf.php';
include_once 'XML/WBXML/DTD/SyncMLDevInf.php';
/**
* $Horde: framework/XML_WBXML/WBXML/DTDManager.php,v 1.7 2006/01/01 21:10:25 jan Exp $
* From Binary XML Content Format Specification Version 1.3, 25 July 2001
* found at http://www.wapforum.org
*
* Copyright 2003-2006 Anthony Mills <amills@pyramid6.com>
* $Horde: framework/XML_WBXML/WBXML/DTDManager.php,v 1.3.12.14 2008/01/02 11:31:02 jan Exp $
*
* See the enclosed file COPYING for license information (LGPL). If you
* Copyright 2003-2008 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* From Binary XML Content Format Specification Version 1.3, 25 July
* 2001 found at http://www.wapforum.org
*
* @author Anthony Mills <amills@pyramid6.com>
* @package XML_WBXML
*/
class XML_WBXML_DTDManager {
/**
* @var array
*/
var $_strDTD = array();
/**
* @var array
*/
var $_strDTDURI = array();
/**
*/
function XML_WBXML_DTDManager()
{
$this->registerDTD('-//SYNCML//DTD SyncML 1.0//EN', 'syncml:syncml1.0', new XML_WBXML_DTD_SyncML(0));
$this->registerDTD('-//SYNCML//DTD SyncML 1.1//EN', 'syncml:syncml1.1', new XML_WBXML_DTD_SyncML(1));
$this->registerDTD(DPI_DTD_SYNCML_1_0, 'syncml:syncml1.0', new XML_WBXML_DTD_SyncML(0));
$this->registerDTD(DPI_DTD_SYNCML_1_1, 'syncml:syncml1.1', new XML_WBXML_DTD_SyncML(1));
$this->registerDTD(DPI_DTD_SYNCML_1_2, 'syncml:syncml1.2', new XML_WBXML_DTD_SyncML(2));
$this->registerDTD('-//SYNCML//DTD MetInf 1.0//EN', 'syncml:metinf', new XML_WBXML_DTD_SyncMLMetInf(0));
$this->registerDTD('-//SYNCML//DTD MetInf 1.1//EN', 'syncml:metinf1.1', new XML_WBXML_DTD_SyncMLMetInf(1));
$this->registerDTD(DPI_DTD_METINF_1_0, 'syncml:metinf1.0', new XML_WBXML_DTD_SyncMLMetInf(0));
$this->registerDTD(DPI_DTD_METINF_1_1, 'syncml:metinf1.1', new XML_WBXML_DTD_SyncMLMetInf(1));
$this->registerDTD(DPI_DTD_METINF_1_2, 'syncml:metinf1.2', new XML_WBXML_DTD_SyncMLMetInf(2));
$this->registerDTD('-//SYNCML//DTD DevInf 1.0//EN', 'syncml:devinf', new XML_WBXML_DTD_SyncMLDevInf(0));
$this->registerDTD('-//SYNCML//DTD DevInf 1.1//EN', 'syncml:devinf1.1', new XML_WBXML_DTD_SyncMLDevInf(1));
$this->registerDTD(DPI_DTD_DEVINF_1_0, 'syncml:devinf1.0', new XML_WBXML_DTD_SyncMLDevInf(0));
$this->registerDTD(DPI_DTD_DEVINF_1_1, 'syncml:devinf1.1', new XML_WBXML_DTD_SyncMLDevInf(1));
$this->registerDTD(DPI_DTD_DEVINF_1_2, 'syncml:devinf1.2', new XML_WBXML_DTD_SyncMLDevInf(2));
}
function getInstance($publicIdentifier)
/**
*/
function &getInstance($publicIdentifier)
{
return isset($this->_strDTD[$publicIdentifier]) ? $this->_strDTD[$publicIdentifier] : null;
$publicIdentifier = strtolower($publicIdentifier);
if (isset($this->_strDTD[$publicIdentifier])) {
$dtd = &$this->_strDTD[$publicIdentifier];
} else {
$dtd = null;
}
return $dtd;
}
function getInstanceURI($uri)
/**
*/
function &getInstanceURI($uri)
{
$uri = strtolower($uri);
return isset($this->_strDTDURI[$uri]) ? $this->_strDTDURI[$uri] : null;
// some manual hacks:
if ($uri == 'syncml:syncml') {
$uri = 'syncml:syncml1.0';
}
if ($uri == 'syncml:metinf') {
$uri = 'syncml:metinf1.0';
}
if ($uri == 'syncml:devinf') {
$uri = 'syncml:devinf1.0';
}
if (isset($this->_strDTDURI[$uri])) {
$dtd = &$this->_strDTDURI[$uri];
} else {
$dtd = null;
}
return $dtd;
}
/**
*/
function registerDTD($publicIdentifier, $uri, &$dtd)
{
$dtd->setDPI($publicIdentifier);
$publicIdentifier = strtolower($publicIdentifier);
$this->_strDTD[$publicIdentifier] = $dtd;
$this->_strDTDURI[strtolower($uri)] = $dtd;
}

View File

@ -5,16 +5,17 @@ include_once 'XML/WBXML/DTDManager.php';
include_once 'XML/WBXML/ContentHandler.php';
/**
* $Horde: framework/XML_WBXML/WBXML/Decoder.php,v 1.36 2006/01/01 21:10:25 jan Exp $
* From Binary XML Content Format Specification Version 1.3, 25 July 2001
* found at http://www.wapforum.org
*
* Copyright 2003-2006 Anthony Mills <amills@pyramid6.com>
* $Horde: framework/XML_WBXML/WBXML/Decoder.php,v 1.22.10.11 2008/01/02 11:31:02 jan Exp $
*
* See the enclosed file COPYING for license information (LGPL). If you
* Copyright 2003-2008 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* From Binary XML Content Format Specification Version 1.3, 25 July
* 2001 found at http://www.wapforum.org
*
* @author Anthony Mills <amills@pyramid6.com>
* @package XML_WBXML
*/
class XML_WBXML_Decoder extends XML_WBXML_ContentHandler {
@ -75,7 +76,7 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler {
*/
function XML_WBXML_Decoder()
{
$this->_dtdManager = &new XML_WBXML_DTDManager();
$this->_dtdManager = new XML_WBXML_DTDManager();
}
/**
@ -84,7 +85,8 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler {
*
* @param XML_WBXML_ContentHandler $ch The contentHandler
*/
function setContentHandler(&$ch) {
function setContentHandler(&$ch)
{
$this->_ch = &$ch;
}
/**
@ -96,7 +98,7 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler {
{
$value = $input{$this->_strpos++};
$value = ord($value);
return $value;
}
@ -112,7 +114,7 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler {
*/
function decodeToString($wbxml)
{
$this->_ch = &new XML_WBXML_ContentHandler();
$this->_ch = new XML_WBXML_ContentHandler();
$r = $this->decode($wbxml);
if (is_a($r, 'PEAR_Error')) {
@ -133,12 +135,8 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler {
*/
function decode($wbxml)
{
// fix for Nokia Series 60 which seem to send empty data block sometimes
if(strlen($wbxml) == 0) {
return true;
}
$this->_error = false; // reset state
$this->_strpos = 0;
if (empty($this->_ch)) {
@ -149,6 +147,7 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler {
// version = u_int8
// currently 1, 2 or 3
$this->_wbxmlVersion = $this->getVersionNumber($wbxml);
#Horde::logMessage("WBXML[" . $this->_strpos . "] version " . $this->_wbxmlVersion, __FILE__, __LINE__, PEAR_LOG_DEBUG);
// Get Document Public Idetifier from Section 5.5
// publicid = mb_u_int32 | (zero index)
@ -156,9 +155,11 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler {
// Containing the value zero (0)
// The actual DPI is determined after the String Table is read.
$dpiStruct = $this->getDocumentPublicIdentifier($wbxml);
// Get Charset from 5.6
// charset = mb_u_int32
$this->_charset = $this->getCharset($wbxml);
#Horde::logMessage("WBXML[" . $this->_strpos . "] charset " . $this->_charset, __FILE__, __LINE__, PEAR_LOG_DEBUG);
// Get String Table from 5.7
// strb1 = length *byte
@ -166,22 +167,22 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler {
// Get Document Public Idetifier from Section 5.5.
$this->_dpi = $this->getDocumentPublicIdentifierImpl($dpiStruct['dpiType'],
$dpiStruct['dpiNumber'],
$this->_stringTable);
$dpiStruct['dpiNumber']);
#$this->_stringTable);
// Now the real fun begins.
// From Sections 5.2 and 5.8
// Default content handler.
$this->_dtdManager = &new XML_WBXML_DTDManager();
$this->_dtdManager = new XML_WBXML_DTDManager();
// Get the starting DTD.
$this->_tagDTD = $this->_dtdManager->getInstance($this->_dpi);
if (!$this->_tagDTD) {
return $this->raiseError('No DTD found for '
. $this->_dpi . '/'
return $this->raiseError('No DTD found for '
. $this->_dpi . '/'
. $dpiStruct['dpiNumber']);
}
@ -204,6 +205,7 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler {
function getDocumentPublicIdentifier($input)
{
$i = XML_WBXML::MBUInt32ToInt($input, $this->_strpos);
if ($i == 0) {
return array('dpiType' => 2,
'dpiNumber' => $this->getByte($input));
@ -218,6 +220,7 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler {
if ($dpiType == 1) {
return XML_WBXML::getDPIString($dpiNumber);
} else {
#Horde::logMessage("WBXML string table $dpiNumber:\n" . print_r($this->_stringTable, true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
return $this->getStringTableEntry($dpiNumber);
}
}
@ -235,7 +238,7 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler {
}
/**
* Retrieves the string table.
* Retrieves the string table.
* The string table consists of an mb_u_int32 length
* and then length bytes forming the table.
* References to the string table refer to the
@ -254,10 +257,10 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler {
{
if ($index >= strlen($this->_stringTable)) {
$this->_error =
$this->_ch->raiseError('Invalid offset ' . $index
. ' value encountered around position '
. $this->_strpos
. '. Broken wbxml?');
$this->raiseError('Invalid offset ' . $index
. ' value encountered around position '
. $this->_strpos
. '. Broken wbxml?');
return '';
}
@ -270,17 +273,17 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler {
if (ord($ch) == 0) {
return ''; // don't return '#'
}
while (ord($ch) != 0) {
$str[$i++] = $ch;
if ($index >= strlen($this->_stringTable)) {
break;
break;
}
$ch = $this->_stringTable[$index++];
}
// print "string table entry: $str\n";
return $str;
}
function _decode($input)
@ -288,7 +291,7 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler {
$token = $this->getByte($input);
$str = '';
#print "position: " . $this->_strpos . " token: " . $token . " str10: " . substr($input, $this->_strpos, 10) . "\n"; // @todo: remove debug output
// print "position: " . $this->_strpos . " token: " . $token . " str10: " . substr($input, $this->_strpos, 10) . "\n"; // @todo: remove debug output
switch ($token) {
case XML_WBXML_GLOBAL_TOKEN_STR_I:
@ -370,34 +373,36 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler {
case XML_WBXML_GLOBAL_TOKEN_OPAQUE:
// Section 5.8.4.6
$size = XML_WBXML::MBUInt32ToInt($input, $this->_strpos);
// print "opaque of size $size\n"; // @todo remove debug
$b = $this->_substr($input, $this->_strpos, $size);
#$b = mb_substr($input, $this->_strpos, $size, 'ISO-8859-1');
$this->_strpos += $size;
if ($size > 0) {
#Horde::logMessage("WBXML opaque document size=$size, next=" . ord($input{$this->_strpos}), __FILE__, __LINE__, PEAR_LOG_DEBUG);
$b = $this->_substr($input, $this->_strpos, $size);
// print "opaque of size $size: ($b)\n"; // @todo remove debug
$this->_strpos += $size;
// opaque data inside a <data> element may or may not be
// a nested wbxml document (for example devinf data).
// We find out by checking the first byte of the data: if it's
// 1, 2 or 3 we expect it to be the version number of a wbxml
// document and thus start a new wbxml decoder instance on it.
// opaque data inside a <data> element may or may not be
// a nested wbxml document (for example devinf data).
// We find out by checking the first byte of the data: if it's
// 1, 2 or 3 we expect it to be the version number of a wbxml
// document and thus start a new wbxml decoder instance on it.
if ($this->_isData && ord($b) <= 10) {
$decoder = &new XML_WBXML_Decoder(true);
$decoder->setContentHandler($this->_ch);
$s = $decoder->decode($b);
// /* // @todo: FIXME currently we can't decode Nokia
// DevInf data. So ignore error for the time beeing.
if (is_a($s, 'PEAR_Error')) {
$this->_error = $s;
return;
if ($this->_isData && ord($b) < 10) {
#Horde::logMessage("WBXML opaque document size=$size, \$b[0]=" . ord($b), __FILE__, __LINE__, PEAR_LOG_DEBUG);
$decoder = new XML_WBXML_Decoder(true);
$decoder->setContentHandler($this->_ch);
$s = $decoder->decode($b);
// /* // @todo: FIXME currently we can't decode Nokia
// DevInf data. So ignore error for the time beeing.
if (is_a($s, 'PEAR_Error')) {
$this->_error = $s;
return;
}
// */
// $this->_ch->characters($s);
} else {
/* normal opaque behaviour: just copy the raw data: */
// print "opaque handled as string=$b\n"; // @todo remove debug
$this->_ch->characters($b);
}
// */
// $this->_ch->characters($s);
} else {
/* normal opaque behaviour: just copy the raw data: */
$this->_ch->characters( $b);
}
// old approach to deal with opaque data inside ContentHandler:
// FIXME Opaque is used by SYNCML. Opaque data that depends on the context
// if (contentHandler instanceof OpaqueContentHandler) {
@ -650,7 +655,7 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler {
*/
function termstr($input)
{
$str = '#'; // must start with nonempty string to allow array access
$str = '#'; // must start with nonempty string to allow array access
$i = 0;
$ch = $input[$this->_strpos++];
if (ord($ch) == 0) {
@ -679,6 +684,5 @@ class XML_WBXML_Decoder extends XML_WBXML_ContentHandler {
}
return $ret;
}
}

View File

@ -6,16 +6,17 @@ include_once 'XML/WBXML/DTDManager.php';
include_once 'Horde/String.php';
/**
* $Horde: framework/XML_WBXML/WBXML/Encoder.php,v 1.39 2006/01/01 21:10:25 jan Exp $
* From Binary XML Content Format Specification Version 1.3, 25 July 2001
* found at http://www.wapforum.org
*
* Copyright 2003-2006 Anthony Mills <amills@pyramid6.com>
* $Horde: framework/XML_WBXML/WBXML/Encoder.php,v 1.25.10.17 2008/08/26 15:41:21 jan Exp $
*
* See the enclosed file COPYING for license information (LGPL). If you
* Copyright 2003-2008 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* From Binary XML Content Format Specification Version 1.3, 25 July
* 2001 found at http://www.wapforum.org
*
* @author Anthony Mills <amills@pyramid6.com>
* @package XML_WBXML
*/
class XML_WBXML_Encoder extends XML_WBXML_ContentHandler {
@ -38,7 +39,7 @@ class XML_WBXML_Encoder extends XML_WBXML_ContentHandler {
var $_subParser = null;
var $_subParserStack = 0;
/**
* The XML parser.
*
@ -58,8 +59,8 @@ class XML_WBXML_Encoder extends XML_WBXML_ContentHandler {
*/
function XML_WBXML_Encoder()
{
$this->_dtdManager = &new XML_WBXML_DTDManager();
$this->_stringTable = &new XML_WBXML_HashTable();
$this->_dtdManager = new XML_WBXML_DTDManager();
$this->_stringTable = new XML_WBXML_HashTable();
}
/**
@ -97,7 +98,10 @@ class XML_WBXML_Encoder extends XML_WBXML_ContentHandler {
function writeHeader($uri)
{
$this->_dtd = &$this->_dtdManager->getInstanceURI($uri);
if (!$this->_dtd) {
// TODO: proper error handling
die('Unable to find dtd for ' . $uri);
}
$dpiString = $this->_dtd->getDPI();
// Set Version Number from Section 5.4
@ -132,7 +136,11 @@ class XML_WBXML_Encoder extends XML_WBXML_ContentHandler {
function writeDocumentPublicIdentifier($dpiString, &$strings)
{
$i = XML_WBXML::getDPIInt($dpiString);
$i = 0;
// The OMA test suite doesn't like DPI as integer code.
// So don't try lookup and always send full DPI string.
// $i = XML_WBXML::getDPIInt($dpiString);
if ($i == 0) {
$strings[0] = $dpiString;
@ -191,7 +199,7 @@ class XML_WBXML_Encoder extends XML_WBXML_ContentHandler {
function _getBytes($string, $cs)
{
$string = String::convertCharset($string, $cs, 'utf-8');
$string = String::convertCharset($string, $cs, 'utf-8');
$nbytes = strlen($string);
$bytes = array();
@ -210,8 +218,10 @@ class XML_WBXML_Encoder extends XML_WBXML_ContentHandler {
return array($uri, $name);
}
function startElement($uri, $name, $attributes)
function startElement($uri, $name, $attributes = array())
{
#Horde::logMessage("WBXML Encoder $uri, " . ($this->_hasWrittenHeader ? 'true' : 'false'), __FILE__, __LINE__, PEAR_LOG_DEBUG);
if ($this->_subParser == null) {
if (!$this->_hasWrittenHeader) {
$this->writeHeader($uri);
@ -222,11 +232,11 @@ class XML_WBXML_Encoder extends XML_WBXML_ContentHandler {
if ($this->_subParser == null) {
$this->writeTag($name, $attributes, true, $this->_charset);
} else {
$this->_subParser->startElement($uri,$name, $attributes);
$this->_subParser->startElement($uri, $name, $attributes);
}
} else {
$this->_subParserStack++;
$this->_subParser->startElement($uri,$name,$attributes);
$this->_subParser->startElement($uri, $name, $attributes);
}
}
@ -237,12 +247,12 @@ class XML_WBXML_Encoder extends XML_WBXML_ContentHandler {
$this->startElement($uri, $name, $attributes);
}
function opaque($bytes)
function opaque($o)
{
if ($this->_subParser == null) {
$this->_output .= chr(XML_WBXML_GLOBAL_TOKEN_OPAQUE);
XML_WBXML::intToMBUInt32($this->_output, count($bytes));
$this->_output .= $bytes;
XML_WBXML::intToMBUInt32($this->_output, strlen($o));
$this->_output .= $o;
}
}
@ -274,7 +284,6 @@ class XML_WBXML_Encoder extends XML_WBXML_ContentHandler {
function writeTag($name, $attrs, $hasContent, $cs)
{
if ($attrs != null && !count($attrs)) {
$attrs = null;
}
@ -309,7 +318,7 @@ class XML_WBXML_Encoder extends XML_WBXML_ContentHandler {
}
}
if ($attrs != null && is_array($attrs) && count($attrs) > 0 ) {
if ($attrs != null && is_array($attrs) && count($attrs) > 0) {
$this->writeAttributes($attrs, $cs);
}
}
@ -376,11 +385,16 @@ class XML_WBXML_Encoder extends XML_WBXML_ContentHandler {
function changecodepage($uri)
{
// @todo: this is a hack!
// what's the reason for this hack???? Lars
if ($uri != 'syncml:devinf' && $uri != 'syncml:metinf' && $uri != 'syncml:syncml1.0' && !preg_match('/1\.1$/', $uri)) {
if ($this->_dtd->getVersion() == 2 && !preg_match('/1\.2$/', $uri)) {
$uri .= '1.2';
}
if ($this->_dtd->getVersion() == 1 && !preg_match('/1\.1$/', $uri)) {
$uri .= '1.1';
}
if ($this->_dtd->getVersion() == 0 && !preg_match('/1\.0$/', $uri)) {
$uri .= '1.0';
}
$cp = $this->_dtd->toCodePageURI($uri);
if (strlen($cp)) {
$this->_dtd = &$this->_dtdManager->getInstanceURI($uri);
@ -390,7 +404,7 @@ class XML_WBXML_Encoder extends XML_WBXML_ContentHandler {
$this->_currentURI = $uri;
} else {
$this->_subParser = &new XML_WBXML_Encoder(true);
$this->_subParser = new XML_WBXML_Encoder(true);
$this->_subParserStack = 1;
}
}

View File

@ -21,10 +21,10 @@ $conf['auth']['driver'] = 'auto';
$conf['log']['priority'] = PEAR_LOG_DEBUG;
$conf['log']['ident'] = 'EGWSYNC';
$conf['log']['params'] = array();
$conf['log']['name'] = '/tmp/egroupware_syncml.log';
$conf['log']['name'] = '/tmp/egroupware_syncml-1.6.log';
$conf['log']['params']['append'] = true;
$conf['log']['type'] = 'error_log';
$conf['log']['enabled'] = true;
$conf['log']['enabled'] = false;
$conf['log_accesskeys'] = false;
$conf['prefs']['driver'] = 'none';
$conf['datatree']['params']['driverconfig'] = 'horde';

View File

@ -48,7 +48,7 @@ $conf = &$GLOBALS['conf'];
/* Set up the menu. */
#require_once 'Horde/Menu.php';
#$menu = &new Menu();
#$menu = new Menu();
// Compress output
#Horde::compressOutput();

View File

@ -21,8 +21,16 @@ ini_set('magic_quotes_runtime', 0);
* include_path, you must add an ini_set() call here to add their location to
* the include_path. */
// ini_set('include_path', dirname(__FILE__) . PATH_SEPARATOR . ini_get('include_path'));
set_include_path(dirname(__FILE__). '/../../horde/' . PATH_SEPARATOR . dirname(__FILE__). '/../../../../egw-pear/' . PATH_SEPARATOR . get_include_path());
//set_include_path(dirname(__FILE__). '/../../horde/' . PATH_SEPARATOR . dirname(__FILE__). '/../../../../egw-pear/' . PATH_SEPARATOR . get_include_path());
@define('EGW_BASE', dirname(dirname(__FILE__) . '/../../../../rpc.php'));
// Check for a prior definition of HORDE_BASE (perhaps by an
// auto_prepend_file definition for site customization).
if (!defined('HORDE_BASE')) {
@define('HORDE_BASE', EGW_BASE . '/phpgwapi/inc/horde/');
}
set_include_path(HORDE_BASE . PATH_SEPARATOR . EGW_BASE . '/egw-pear/' . PATH_SEPARATOR . get_include_path());
/* PEAR base class. */
include_once 'PEAR.php';
@ -30,14 +38,17 @@ include_once 'PEAR.php';
include_once 'Horde.php';
include_once 'Horde/Registry.php';
#include_once 'Horde/DataTree.php';
#include_once 'Horde/String.php';
include_once 'Horde/String.php';
include_once 'Horde/Date.php';
include_once 'Horde/NLS.php';
#include_once 'Horde/Notification.php';
#include_once 'Horde/Auth.php';
#include_once 'Horde/Browser.php';
#include_once 'Horde/Perms.php';
include_once 'Horde/iCalendar.php';
//include_once 'Horde/Notification.php';
//include_once 'Horde/Auth.php';
//include_once 'Horde/Browser.php';
//include_once 'Horde/Perms.php';
#/* Browser detection object. */
#if (class_exists('Browser')) {
# $browser = &Browser::singleton();
#}
/* Browser detection object. *
if (class_exists('Browser')) {
$browser = &Browser::singleton();
}
*/

View File

@ -96,7 +96,7 @@
{
$data['type'] = $data['type'] ? $data['type'] : '';
$data['cat_id'] = $data['cat_id'] ? $data['cat_id'] : '';
return $this->cats->exists($data['type'],$data['cat_name'],$data['type'] == 'subs' ? 0 : $data['cat_id'],$data['type'] != 'subs' ? 0 : $data['cat_id']);
return $this->cats->exists($data['type'],$data['cat_name'],$data['type'] == 'subs' ? 0 : $data['cat_id'],$data['type'] != 'subs' ? 0 : $data['cat_id'], $data['cat_access']);
}
function formatted_list($format,$type,$cat_parent,$global_cats)
@ -126,18 +126,20 @@
{
$exists = $this->exists(array
(
'type' => 'appandmains',
'cat_name' => $values['name'],
'cat_id' => $values['id']
'type' => 'appandmains',
'cat_name' => $values['name'],
'cat_id' => $values['id'],
'cat_access' => $values['access']
));
}
else
{
$exists = $this->exists(array
(
'type' => 'appandsubs',
'cat_name' => $values['name'],
'cat_id' => $values['id']
'type' => 'appandsubs',
'cat_name' => $values['name'],
'cat_id' => $values['id'],
'cat_access' => $values['access']
));
}

View File

@ -180,7 +180,7 @@
// ------------------------------ end nextmatch ------------------------------------------
//------------------- list header variable template-declarations -------------------------
//------------------- list header variable template-declarations -------------------------
$GLOBALS['egw']->template->set_var('sort_name',$this->nextmatchs->show_sort_order($this->sort,'cat_name',$this->order,'/index.php',lang('Name'),$link_data));
$GLOBALS['egw']->template->set_var('sort_description',$this->nextmatchs->show_sort_order($this->sort,'cat_description',$this->order,'/index.php',lang('Description'),$link_data));
@ -215,6 +215,8 @@
}
$GLOBALS['egw']->template->set_var('color',$gray < 128 ? 'style="color: white;"' : '');
$accountId = $GLOBALS['egw_info']['user']['account_id'];
if ($cat['app_name'] == 'phpgw')
{
$appendix = '&lt;' . lang('Global') . '&gt;';
@ -223,6 +225,10 @@
{
$appendix = '&lt;' . lang('Global') . '&nbsp;' . $GLOBALS['egw_info']['apps'][$cats_app]['title'] . '&gt;';
}
elseif ($cat['owner'] != $accountId)
{
$appendix = '&lt;' . $GLOBALS['egw']->accounts->id2name($cat['owner'], 'account_fullname') . '&gt;';
}
else
{
$appendix = '';