2010-11-26 10:45:38 +01:00
< ? php
/**
2010-12-02 10:03:56 +01:00
* EGroupware : ActiveSync access : Calendar plugin
2010-11-26 10:45:38 +01:00
*
* @ link http :// www . egroupware . org
* @ package calendar
* @ subpackage activesync
* @ author Ralf Becker < rb @ stylite . de >
* @ author Klaus Leithoff < kl @ stylite . de >
* @ author Philip Herbert < philip @ knauber . de >
* @ license http :// opensource . org / licenses / gpl - license . php GPL - GNU General Public License
2010-12-02 10:03:56 +01:00
* @ version $Id $
2010-11-26 10:45:38 +01:00
*/
/**
* Calendar activesync plugin
*
2010-12-19 01:31:07 +01:00
* Plugin to make EGroupware calendar data available via Active Sync
2010-12-21 03:45:40 +01:00
*
* Handling of ( virtual ) exceptions of recurring events :
* ----------------------------------------------------
* Virtual exceptions are exceptions caused by recurcences with just different participant status
* compared to regular series ( master ) . Real exceptions usually have different dates and / or other data .
* EGroupware calendar data model does NOT store virtual exceptions as exceptions ,
* as participant status is stored per recurrence date and not just per event !
* GetMessageList reports virtual exceptions with an id like cal_id : recur_date , which
* is understood bei StatMessage , GetMessage and ChangeMessage ( implementation is currently missing ! ) .
* Real exceptions have there own calendar id , under which they are reported by GetMessageList .
*
* @ todo alarms / reminders ( currently they are not reported to and not changed by the device )
* We probably want to report only alarms of the current user ( which should ring on the device )
* and save alarms set on the device only for the current user , if not yet there ( preserving all other alarms ) .
* How to deal with multiple alarms allowed in EGroupware : report earliest one to the device
* ( and hope it resyncs before next one is due , thought we do NOT report that as change currently ! ) .
2010-11-26 10:45:38 +01:00
*/
2010-12-19 04:54:09 +01:00
class calendar_activesync implements activesync_plugin_write
2010-11-26 10:45:38 +01:00
{
/**
* var BackendEGW
*/
private $backend ;
/**
* Instance of calendar_bo
*
2010-11-26 12:13:00 +01:00
* @ var calendar_boupdate
2010-11-26 10:45:38 +01:00
*/
private $calendar ;
2011-02-24 23:55:05 +01:00
/**
* Instance of addressbook_bo
*
* @ var addressbook_bo
*/
private $addressbook ;
2010-11-26 10:45:38 +01:00
/**
* Constructor
*
* @ param BackendEGW $backend
*/
public function __construct ( BackendEGW $backend )
{
$this -> backend = $backend ;
}
/**
* This function is analogous to GetMessageList .
*
* @ ToDo implement preference , include own private calendar
*/
public function GetFolderList ()
{
2010-11-26 12:13:00 +01:00
if ( ! isset ( $this -> calendar )) $this -> calendar = new calendar_boupdate ();
foreach ( $this -> calendar -> list_cals () as $label => $entry )
{
2011-02-24 23:55:05 +01:00
// uncomment next line to get only own calendar
2011-03-07 22:00:17 +01:00
if ( $entry [ 'grantor' ] != $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ]) continue ;
2010-11-26 12:13:00 +01:00
$folderlist [] = $f = array (
'id' => $this -> backend -> createID ( 'calendar' , $entry [ 'grantor' ]),
2010-12-03 18:55:04 +01:00
'mod' => $GLOBALS [ 'egw' ] -> accounts -> id2name ( $entry [ 'grantor' ], 'account_fullname' ),
2010-11-26 10:45:38 +01:00
'parent' => '0' ,
2010-11-26 12:13:00 +01:00
);
};
2010-11-26 22:33:34 +01:00
//error_log(__METHOD__."() returning ".array2string($folderlist));
2011-02-24 23:55:05 +01:00
debugLog ( __METHOD__ . " () returning " . array2string ( $folderlist ));
2010-11-26 10:45:38 +01:00
return $folderlist ;
}
2010-11-26 12:13:00 +01:00
2010-11-26 10:45:38 +01:00
/**
* Get Information about a folder
*
* @ param string $id
* @ return SyncFolder | boolean false on error
*/
public function GetFolder ( $id )
{
2010-11-26 12:13:00 +01:00
$this -> backend -> splitID ( $id , $type , $owner );
2010-11-26 10:45:38 +01:00
$folderObj = new SyncFolder ();
$folderObj -> serverid = $id ;
$folderObj -> parentid = '0' ;
2010-11-26 22:33:34 +01:00
$folderObj -> displayname = $GLOBALS [ 'egw' ] -> accounts -> id2name ( $owner , 'account_fullname' );
2010-11-26 12:13:00 +01:00
if ( $owner == $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ])
{
2010-11-26 22:33:34 +01:00
$folderObj -> type = SYNC_FOLDER_TYPE_APPOINTMENT ;
2010-11-26 10:45:38 +01:00
}
else
{
2010-11-26 22:33:34 +01:00
$folderObj -> type = SYNC_FOLDER_TYPE_USER_APPOINTMENT ;
2010-11-26 10:45:38 +01:00
}
2010-11-26 22:33:34 +01:00
//error_log(__METHOD__."('$id') folderObj=".array2string($folderObj));
2011-02-24 23:55:05 +01:00
debugLog ( __METHOD__ . " (' $id ') folderObj= " . array2string ( $folderObj ));
2010-11-26 10:45:38 +01:00
return $folderObj ;
}
/**
* Return folder stats . This means you must return an associative array with the
* following properties :
*
* " id " => The server ID that will be used to identify the folder . It must be unique , and not too long
* How long exactly is not known , but try keeping it under 20 chars or so . It must be a string .
* " parent " => The server ID of the parent of the folder . Same restrictions as 'id' apply .
* " mod " => This is the modification signature . It is any arbitrary string which is constant as long as
* the folder has not changed . In practice this means that 'mod' can be equal to the folder name
* as this is the only thing that ever changes in folders . ( the type is normally constant )
*
* @ return array with values for keys 'id' , 'mod' and 'parent'
*/
public function StatFolder ( $id )
{
$folder = $this -> GetFolder ( $id );
2010-11-26 12:13:00 +01:00
$this -> backend -> splitID ( $id , $type , $owner );
2010-11-26 10:45:38 +01:00
$stat = array (
2010-12-17 04:44:43 +01:00
'id' => $id ,
'mod' => $GLOBALS [ 'egw' ] -> accounts -> id2name ( $owner , 'account_fullname' ),
2010-11-26 10:45:38 +01:00
'parent' => '0' ,
);
2011-02-24 23:55:05 +01:00
//error_log(__METHOD__."('$id') folderObj=".array2string($stat));
debugLog ( __METHOD__ . " (' $id ') folderObj= " . array2string ( $stat ));
2010-11-26 10:45:38 +01:00
return $stat ;
}
2010-11-26 12:13:00 +01:00
2010-11-26 22:33:34 +01:00
/**
* Should return a list ( array ) of messages , each entry being an associative array
2010-12-17 04:44:43 +01:00
* with the same entries as StatMessage () . This function should return stable information ; ie
* if nothing has changed , the items in the array must be exactly the same . The order of
* the items within the array is not important though .
*
* The cutoffdate is a date in the past , representing the date since which items should be shown .
* This cutoffdate is determined by the user 's setting of getting ' Last 3 days ' of e - mail , etc . If
* you ignore the cutoffdate , the user will not be able to select their own cutoffdate , but all
* will work OK apart from that .
*
* @ param string $id folder id
* @ param int $cutoffdate = null
* @ return array
2010-11-26 12:13:00 +01:00
*/
function GetMessageList ( $id , $cutoffdate = NULL )
2010-11-26 10:45:38 +01:00
{
2010-11-26 22:33:34 +01:00
if ( ! isset ( $this -> calendar )) $this -> calendar = new calendar_boupdate ();
debugLog ( __METHOD__ . " (' $id ', $cutoffdate ) " );
$this -> backend -> splitID ( $id , $type , $user );
if ( ! $cutoffdate ) $cutoffdate = $this -> bo -> now - 100 * 24 * 3600 ; // default three month back -30 breaks all sync recurrences
2010-11-26 12:13:00 +01:00
2010-11-26 22:33:34 +01:00
$filter = array (
'users' => $user ,
'start' => $cutoffdate , // default one month back -30 breaks all sync recurrences
'enum_recuring' => false ,
'daywise' => false ,
'date_format' => 'server' ,
'filter' => 'default' , // not rejected
2010-12-21 03:45:40 +01:00
// @todo return only etag relevant information (seems not to work ...)
//'cols' => array('egw_cal.cal_id', 'cal_start', 'recur_type', 'cal_modified', 'cal_uid', 'cal_etag'),
2010-11-26 22:33:34 +01:00
);
2010-11-26 12:13:00 +01:00
2010-11-26 22:33:34 +01:00
$messagelist = array ();
2010-12-21 03:45:40 +01:00
foreach ( $this -> calendar -> search ( $filter ) as $event )
2010-11-26 22:33:34 +01:00
{
$messagelist [] = $this -> StatMessage ( $id , $event );
2010-12-21 03:45:40 +01:00
// add virtual exceptions for recuring events too
// (we need to read event, as get_recurrence_exceptions need all infos!)
/* if ( $event [ 'recur_type' ] != calendar_rrule :: NONE ) // && ($event = $this->calendar->read($event['id'],0,true,'server')))
{
foreach ( $this -> calendar -> so -> get_recurrence_exceptions ( $event ,
egw_time :: $server_timezone -> getName (), $cutoffdate , 0 , 'all' ) as $recur_date )
{
$messagelist [] = $this -> StatMessage ( $id , $event [ 'id' ] . ':' . $recur_date );
}
} */
2010-11-26 22:33:34 +01:00
}
return $messagelist ;
2010-11-26 10:45:38 +01:00
}
2010-11-26 12:13:00 +01:00
2010-12-20 04:29:37 +01:00
/**
* Conversation to AS status
*
* @ var array
*/
2010-12-19 04:54:09 +01:00
static $status2as = array (
'U' => 0 , // unknown
'T' => 2 , // tentative
'A' => 3 , // accepted
'R' => 4 , // decline
// 5 = not responded
);
2010-12-20 04:29:37 +01:00
/**
* Conversation to AS " roles " , not really the same thing
*
* @ var array
*/
2010-12-19 04:54:09 +01:00
static $role2as = array (
'REQ-PARTICIPANT' => 1 , // required
'CHAIR' => 1 , // required
'OPT-PARTICIPANT' => 2 , // optional
'NON-PARTICIPANT' => 2 ,
// 3 = ressource
);
2010-12-20 04:29:37 +01:00
/**
* Conversation to AS recurrence types
*
* @ var array
*/
static $recur_type2as = array (
calendar_rrule :: DAILY => 0 ,
calendar_rrule :: WEEKLY => 1 ,
calendar_rrule :: MONTHLY_MDAY => 2 , // monthly
calendar_rrule :: MONTHLY_WDAY => 3 , // monthly on nth day
calendar_rrule :: YEARLY => 5 ,
// 6 = yearly on nth day (same as 5 on non-leapyears or before March on leapyears)
);
2010-12-19 04:54:09 +01:00
/**
* Changes or adds a message on the server
*
2010-12-20 04:29:37 +01:00
* Timestamps from z - push are in servertime and need to get converted to user - time , as bocalendar_update :: save ()
* expects user - time !
*
2010-12-19 04:54:09 +01:00
* @ param string $folderid
* @ param int $id for change | empty for create new
* @ param SyncAppointment $message object to SyncObject to create
*
* @ return array $stat whatever would be returned from StatMessage
*
* This function is called when a message has been changed on the PDA . You should parse the new
* message here and save the changes to disk . The return value must be whatever would be returned
* from StatMessage () after the message has been saved . This means that both the 'flags' and the 'mod'
* properties of the StatMessage () item may change via ChangeMessage () .
* Note that this function will never be called on E - mail items as you can ' t change e - mail items , you
* can only set them as 'read' .
*/
public function ChangeMessage ( $folderid , $id , $message )
{
if ( ! isset ( $this -> calendar )) $this -> calendar = new calendar_boupdate ();
$event = array ();
$this -> backend -> splitID ( $folderid , $type , $account );
debugLog ( __METHOD__ . " (' $folderid ', $id , " . array2string ( $message ) . " ) type=' $type ', account= $account " );
2010-12-21 03:45:40 +01:00
list ( $id , $recur_date ) = explode ( ':' , $id );
if ( $type != 'calendar' || $id && ! ( $event = $this -> calendar -> read ( $id , $recur_date , false , 'server' )))
2010-12-19 04:54:09 +01:00
{
debugLog ( __METHOD__ . " (' $folderid ', $id ,...) Folder wrong or event does not existing " );
return false ;
}
2010-12-21 03:45:40 +01:00
if ( $recur_date ) // virtual exception
{
// @todo check if virtual exception needs to be saved as real exception, or only stati need to be changed
2011-03-07 16:03:56 +01:00
debugLog ( __METHOD__ . " (' $folderid ', $id : $recur_date , " . array2string ( $message ) . " ) handling of virtual exception not yet implemented! " );
2010-12-21 03:45:40 +01:00
error_log ( __METHOD__ . " (' $folderid ', $id : $recur_date , " . array2string ( $message ) . " ) handling of virtual exception not yet implemented! " );
}
2010-12-19 04:54:09 +01:00
if ( ! $this -> calendar -> check_perms ( $id ? EGW_ACL_EDIT : EGW_ACL_ADD , $event ? $event : 0 , $account ))
{
2010-12-20 04:29:37 +01:00
// @todo: write in users calendar and make account only a participant
2010-12-19 04:54:09 +01:00
debugLog ( __METHOD__ . " (' $folderid ', $id ,...) no rights to add/edit event! " );
2010-12-21 03:45:40 +01:00
error_log ( __METHOD__ . " (' $folderid ', $id , " . array2string ( $message ) . " ) no rights to add/edit event! " );
2010-12-19 04:54:09 +01:00
return false ;
}
// timestamps (created & modified are updated automatically)
foreach ( array (
'start' => 'starttime' ,
'end' => 'endtime' ,
) as $key => $attr )
{
$event [ $key ] = egw_time :: server2user ( $message -> $attr );
}
if ( ! $id ) $event [ 'owner' ] = $account ; // we do NOT allow to change the owner of existing events
// copying strings
foreach ( array (
'title' => 'subject' ,
'uid' => 'uid' ,
'location' => 'location' ,
) as $key => $attr )
{
if ( isset ( $message -> $attr )) $event [ $key ] = $message -> $attr ;
}
2011-03-07 22:00:17 +01:00
$event [ 'description' ] = $this -> backend -> messagenote2note ( $message -> body , $message -> rtf , $message -> airsyncbasebody );
2010-12-20 04:29:37 +01:00
$event [ 'public' ] = ( int )( $message -> sensitivity < 1 ); // 0=normal, 1=personal, 2=private, 3=confidential
if (( $event [ 'whole_day' ] = $message -> alldayevent ))
{
$event [ 'end' ] -- ; // otherwise our whole-day event code in save makes it one more day!
}
2010-12-19 04:54:09 +01:00
$participants = array ();
foreach (( array ) $message -> attendees as $attendee )
{
2010-12-21 03:45:40 +01:00
if ( $attendee -> type == 3 ) continue ; // we can not identify resources and re-add them anyway later
2011-02-24 23:55:05 +01:00
if ( preg_match ( '/^noreply-(.*)-uid@egroupware.org$/' , $attendee -> email , $matches ))
{
$uid = $matches [ 1 ];
}
elseif ( ! ( $uid = $GLOBALS [ 'egw' ] -> accounts -> name2id ( $attendee -> email , 'account_email' )))
2010-12-19 04:54:09 +01:00
{
2010-12-21 03:45:40 +01:00
$search = array (
'email' => $attendee -> email ,
'email_home' => $attendee -> email ,
//'n_fn' => $attendee->name, // not sure if we want matches without email
);
// search addressbook for participant
2011-02-24 23:55:05 +01:00
if ( ! isset ( $this -> addressbook )) $this -> addressbook = new addressbook_bo ();
2010-12-21 03:45:40 +01:00
if (( list ( $data ) = $this -> addressbook -> search ( $search ,
array ( 'id' , 'egw_addressbook.account_id as account_id' , 'n_fn' ),
'egw_addressbook.account_id IS NOT NULL DESC, n_fn IS NOT NULL DESC' ,
'' , '' , false , 'OR' )))
{
$uid = $data [ 'account_id' ] ? ( int ) $data [ 'account_id' ] : 'c' . $data [ 'id' ];
}
else // store just the email
{
$uid = 'e' . $attendee -> name . ' <' . $attendee -> email . '>' ;
}
2010-12-19 04:54:09 +01:00
}
if ( ! ( $status = array_search ( $attendee -> status , self :: $status2as ))) $status = 'U' ;
if ( $attendee -> email == $message -> organizeremail )
{
$role = 'CHAIR' ;
$chair_set = true ;
}
elseif ( ! ( $role = array_search ( $attendee -> type , self :: $role2as )))
{
$role = 'REQ-PARTICIPANT' ;
}
$quantitiy = 1 ;
// if old role gives same type, use old role, as we have a lot more roles then AS
if ( $id && isset ( $event [ 'participants' ][ $uid ]))
{
calendar_so :: split_status ( $status , $quantity , $old_role );
if (( int ) self :: $role2as [ $old_role ] == $attendee -> type )
{
$role = $old_role ;
}
}
$participants [ $uid ] = calendar_so :: combine_status ( $status , $quantitiy , $role );
}
// if organizer is not already participant, add him as chair
if (( $uid = $GLOBALS [ 'egw' ] -> accounts -> name2id ( $message -> organizeremail , 'account_email' )) && ! isset ( $participants [ $uid ]))
{
$participants [ $uid ] = calendar_so :: combine_status ( $uid == $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ] ?
'A' : 'U' , 1 , 'CHAIR' );
$chair_set = true ;
}
2010-12-21 03:45:40 +01:00
// preserve all resource types not account, contact or email (eg. resources) for existing events
2011-02-24 23:55:05 +01:00
// $account is also preserved, as AS does not add him as participant!
2010-12-21 03:45:40 +01:00
foreach (( array ) $event [ 'participant_types' ] as $type => $parts )
{
2011-02-24 23:55:05 +01:00
if ( in_array ( $type , array ( 'c' , 'e' ))) continue ; // they are correctly representable in AS
2010-12-21 03:45:40 +01:00
foreach ( $parts as $id => $status )
{
2011-02-24 23:55:05 +01:00
// accounts are represented correctly, but the event owner which is no participant in AS
if ( $type == 'u' && $id != $account ) continue ;
2010-12-21 03:45:40 +01:00
$uid = calendar_so :: combine_user ( $type , $id );
if ( ! isset ( $participants [ $uid ]))
{
$participants [ $uid ] = $status ;
}
}
}
2011-02-24 23:55:05 +01:00
// add calendar owner as participant, as otherwise event will NOT be in his calendar, in which it was posted
if ( ! $id || ! $participants || ! isset ( $participants [ $account ]))
{
$participants [ $account ] = calendar_so :: combine_status ( $account == $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ] ?
'A' : 'U' , 1 , ! $chair_set ? 'CHAIR' : 'REQ-PARTICIPANT' );
}
2010-12-19 04:54:09 +01:00
$event [ 'participants' ] = $participants ;
2010-12-20 04:29:37 +01:00
if ( isset ( $message -> categories ))
2010-12-19 04:54:09 +01:00
{
2010-12-20 04:29:37 +01:00
$event [ 'categories' ] = $this -> calendar -> find_or_add_categories ( $message -> categories , $id );
2010-12-19 04:54:09 +01:00
}
// check if event is recurring and import recur information (incl. timezone)
if ( $message -> recurrence )
{
if ( $message -> timezone && ! $id ) // dont care for timezone, if no new and recurring event
{
2010-12-20 04:29:37 +01:00
$event [ 'tzid' ] = self :: as2tz ( self :: _getTZFromSyncBlob ( base64_decode ( $message -> timezone )));
}
$event [ 'recur_type' ] = $message -> recurrence -> type == 6 ? calendar_rrule :: YEARLY :
array_search ( $message -> recurrence -> type , self :: $recur_type2as );
$event [ 'recur_interval' ] = $message -> recurrence -> interval ;
switch ( $event [ 'recur_type' ])
{
case calendar_rrule :: MONTHLY_WDAY :
// $message->recurrence->weekofmonth is not explicitly stored in egw, just taken from start date
// fall throught
case calendar_rrule :: WEEKLY :
$event [ 'recur_data' ] = $message -> recurrence -> dayofweek ; // 1=Su, 2=Mo, 4=Tu, .., 64=Sa
break ;
case calendar_rrule :: MONTHLY_MDAY :
// $message->recurrence->dayofmonth is not explicitly stored in egw, just taken from start date
break ;
case calendar_rrule :: YEARLY :
// $message->recurrence->(dayofmonth|monthofyear) is not explicitly stored in egw, just taken from start date
break ;
}
if ( $message -> recurrence -> until )
{
2010-12-21 03:45:40 +01:00
$event [ 'recur_enddate' ] = egw_time :: server2user ( $message -> recurrence -> until );
2010-12-20 04:29:37 +01:00
}
$event [ 'recur_exceptions' ] = array ();
if ( $message -> exceptions )
{
foreach ( $message -> exceptions as $exception )
{
2010-12-21 03:45:40 +01:00
$event [ 'recur_exceptions' ][] = egw_time :: server2user ( $exception -> starttime ); // exceptions seems to be full SyncAppointments, with only starttime required
2010-12-20 04:29:37 +01:00
}
}
if ( $message -> recurrence -> occurrences > 0 )
{
// calculate enddate from occurences count, as we only support enddate
$count = $message -> recurrence -> occurrences ;
2010-12-21 03:45:40 +01:00
foreach ( calendar_rrule :: event2rrule ( $event , true ) as $time ) // true = timestamps are user time here, because of save!
2010-12-20 04:29:37 +01:00
{
if ( -- $count <= 0 ) break ;
}
$event [ 'recur_enddate' ] = $rtime -> format ( 'ts' );
2010-12-19 04:54:09 +01:00
}
}
2010-12-20 04:29:37 +01:00
// @todo: body or description
2011-02-24 23:55:05 +01:00
if ( ! ( $id = $this -> calendar -> update ( $event , true ))) // true = ignore conflicts
2010-12-19 04:54:09 +01:00
{
debugLog ( __METHOD__ . " (' $folderid ', $id ,...) error saving event= " . array2string ( $event ) . " ! " );
return false ;
}
debugLog ( __METHOD__ . " (' $folderid ', $id ,...) SUCESS saving event= " . array2string ( $event ) . " , id= $id " );
2010-12-21 03:45:40 +01:00
error_log ( __METHOD__ . " (' $folderid ', $id , " . array2string ( $message ) . " ) SUCESS saving event= " . array2string ( $event ) . " , id= $id " );
2010-12-19 04:54:09 +01:00
return $this -> StatMessage ( $folderid , $id );
}
/**
* Creates or modifies a folder
*
* @ param $id of the parent folder
* @ param $oldid => if empty -> new folder created , else folder is to be renamed
* @ param $displayname => new folder name ( to be created , or to be renamed to )
* @ param type => folder type , ignored in IMAP
*
* @ return stat | boolean false on error
*/
public function ChangeFolder ( $id , $oldid , $displayname , $type )
{
2010-12-20 04:29:37 +01:00
debugLog ( __METHOD__ . " (' $id ', ' $oldid ', ' $displayname ', $type ) NOT supported! " );
2010-12-19 04:54:09 +01:00
return false ;
}
/**
* Deletes ( really delete ) a Folder
*
* @ param $parentid of the folder to delete
* @ param $id of the folder to delete
*
* @ return
* @ TODO check what is to be returned
*/
public function DeleteFolder ( $parentid , $id )
{
2010-12-20 04:29:37 +01:00
debugLog ( __METHOD__ . " (' $parentid ', ' $id ') NOT supported! " );
2010-12-19 04:54:09 +01:00
return false ;
}
/**
* Moves a message from one folder to another
*
* @ param $folderid of the current folder
* @ param $id of the message
* @ param $newfolderid
*
* @ return $newid as a string | boolean false on error
*
* After this call , StatMessage () and GetMessageList () should show the items
* to have a new parent . This means that it will disappear from GetMessageList () will not return the item
* at all on the source folder , and the destination folder will show the new message
*/
public function MoveMessage ( $folderid , $id , $newfolderid )
{
2010-12-20 04:29:37 +01:00
debugLog ( __METHOD__ . " (' $folderid ', $id , ' $newfolderid ') NOT supported! " );
2010-12-19 04:54:09 +01:00
return false ;
}
/**
* Delete ( really delete ) a message in a folder
*
* @ param $folderid
* @ param $id
*
2010-12-19 05:07:01 +01:00
* @ return boolean true on success , false on error , diffbackend does NOT use the returnvalue
2010-12-19 04:54:09 +01:00
*
* @ DESC After this call has succeeded , a call to
* GetMessageList () should no longer list the message . If it does , the message will be re - sent to the PDA
* as it will be seen as a 'new' item . This means that if you don ' t implement this function , you will
* be able to delete messages on the PDA , but as soon as you sync , you ' ll get the item back
*/
public function DeleteMessage ( $folderid , $id )
{
2010-12-19 05:07:01 +01:00
if ( ! isset ( $this -> caledar )) $this -> calendar = new calendar_boupdate ();
$ret = $this -> calendar -> delete ( $id );
debugLog ( __METHOD__ . " (' $folderid ', $id ) delete( $id ) returned " . array2string ( $ret ));
return $ret ;
2010-12-19 04:54:09 +01:00
}
2011-03-07 16:03:56 +01:00
/**
* This should change the 'read' flag of a message on disk . The $flags
* parameter can only be '1' ( read ) or '0' ( unread ) . After a call to
* SetReadFlag (), GetMessageList () should return the message with the
* new 'flags' but should not modify the 'mod' parameter . If you do
* change 'mod' , simply setting the message to 'read' on the PDA will trigger
* a full resync of the item from the server
*/
function SetReadFlag ( $folderid , $id , $flags )
{
return false ;
}
2010-11-26 10:45:38 +01:00
/**
* Get specified item from specified folder .
2010-11-26 22:33:34 +01:00
*
2010-12-19 01:31:07 +01:00
* Timezone wise we supply zpush with timestamps in servertime ( ! ), which it " converts " in streamer :: formatDate ( $ts )
* via gmstrftime ( " %Y%m%dT%H%M%SZ " , $ts ) to UTC times .
* Timezones are only used to get correct recurring events !
*
2010-11-26 10:45:38 +01:00
* @ param string $folderid
2010-11-26 12:13:00 +01:00
* @ param string $id
2010-11-26 10:45:38 +01:00
* @ param int $truncsize
* @ param int $bodypreference
* @ param bool $mimesupport
2010-12-19 04:54:09 +01:00
* @ return SyncAppointment | boolean false on error
2010-12-19 01:31:07 +01:00
*/
2010-11-26 10:45:38 +01:00
public function GetMessage ( $folderid , $id , $truncsize , $bodypreference = false , $mimesupport = 0 )
{
2010-11-26 22:33:34 +01:00
if ( ! isset ( $this -> calendar )) $this -> calendar = new calendar_boupdate ();
debugLog ( __METHOD__ . " (' $folderid ', $id , truncsize= $truncsize , bodyprefence= $bodypreference , mimesupport= $mimesupport ) " );
$this -> backend -> splitID ( $folderid , $type , $account );
2010-12-21 03:45:40 +01:00
list ( $id , $recur_date ) = explode ( ':' , $id );
if ( $type != 'calendar' || ! ( $event = $this -> calendar -> read ( $id , $recur_date , false , 'server' , $account )))
2010-11-26 22:33:34 +01:00
{
2010-12-19 01:31:07 +01:00
error_log ( __METHOD__ . " (' $folderid ', $id , ...) read( $id ,null,false,'server', $account ) returned false " );
2010-11-26 22:33:34 +01:00
return false ;
}
2010-11-26 10:45:38 +01:00
$message = new SyncAppointment ();
2010-12-17 04:44:43 +01:00
2010-12-19 01:31:07 +01:00
// set timezone
try {
$as_tz = self :: tz2as ( $event [ 'tzid' ]);
$message -> timezone = base64_encode ( self :: _getSyncBlobFromTZ ( $as_tz ));
}
catch ( Exception $e ) {
// ignore exception, simply set no timezone, as it is optional
}
2010-12-20 04:29:37 +01:00
// copying timestamps (they are already read in servertime, so non tz conversation)
2010-11-26 22:33:34 +01:00
foreach ( array (
'start' => 'starttime' ,
'end' => 'endtime' ,
'created' => 'dtstamp' ,
'modified' => 'dtstamp' ,
) as $key => $attr )
{
2010-12-02 17:04:53 +01:00
if ( ! empty ( $event [ $key ])) $message -> $attr = $event [ $key ];
2010-11-26 22:33:34 +01:00
}
// copying strings
foreach ( array (
'title' => 'subject' ,
'uid' => 'uid' ,
'location' => 'location' ,
) as $key => $attr )
{
if ( ! empty ( $event [ $key ])) $message -> $attr = $event [ $key ];
}
2011-03-07 22:00:17 +01:00
// appoint description
if ( $bodypreference == false )
{
$message -> body = $event [ 'description' ];
$message -> bodysize = strlen ( $message -> body );
$message -> bodytruncated = 0 ;
}
else
{
debugLog ( " airsyncbasebody! " );
$message -> airsyncbasebody = new SyncAirSyncBaseBody ();
$message -> airsyncbasenativebodytype = 1 ;
$this -> backend -> note2messagenote ( $event [ 'description' ], $bodypreference , & $message -> airsyncbasebody );
}
2010-11-26 22:33:34 +01:00
$message -> organizername = $GLOBALS [ 'egw' ] -> accounts -> id2name ( $event [ 'owner' ], 'account_fullname' );
$message -> organizeremail = $GLOBALS [ 'egw' ] -> accounts -> id2name ( $event [ 'owner' ], 'account_email' );
$message -> sensitivity = $event [ 'public' ] ? 0 : 2 ; // 0=normal, 1=personal, 2=private, 3=confidential
2010-12-21 03:45:40 +01:00
$message -> alldayevent = ( int ) calendar_bo :: isWholeDay ( $event );
2010-11-26 22:33:34 +01:00
$message -> attendees = array ();
foreach ( $event [ 'participants' ] as $uid => $status )
{
2011-02-24 23:55:05 +01:00
// AS does NOT want calendar owner as participant
if ( $uid == $account ) continue ;
2010-11-26 22:33:34 +01:00
calendar_so :: split_status ( $status , $quantity , $role );
$attendee = new SyncAttendee ();
2010-12-19 04:54:09 +01:00
$attendee -> status = ( int ) self :: $status2as [ $status ];
$attendee -> type = ( int ) self :: $role2as [ $role ];
2010-11-26 22:33:34 +01:00
if ( is_numeric ( $uid ))
{
$attendee -> name = $GLOBALS [ 'egw' ] -> accounts -> id2name ( $uid , 'account_fullname' );
$attendee -> email = $GLOBALS [ 'egw' ] -> accounts -> id2name ( $uid , 'account_email' );
2010-12-17 04:44:43 +01:00
}
else
{
list ( $info ) = $this -> calendar -> resources [ $uid [ 0 ]][ 'info' ] ?
ExecMethod ( $this -> resources [ $uid [ 0 ]][ 'info' ], substr ( $uid , 1 )) : array ( false );
2010-12-19 01:31:07 +01:00
if ( ! $info ) continue ;
if ( ! $info [ 'email' ] && $info [ 'responsible' ])
2010-12-17 04:44:43 +01:00
{
2010-12-19 01:31:07 +01:00
$info [ 'email' ] = $GLOBALS [ 'egw' ] -> accounts -> id2name ( $info [ 'responsible' ], 'account_email' );
2010-12-17 04:44:43 +01:00
}
2010-12-19 01:31:07 +01:00
$attendee -> name = empty ( $info [ 'cn' ]) ? $info [ 'name' ] : $info [ 'cn' ];
$attendee -> email = $info [ 'email' ];
if ( $uid [ 0 ] == 'r' ) $attendee -> type = 3 ; // 3 = resource
2010-11-26 22:33:34 +01:00
}
2011-02-24 23:55:05 +01:00
// email must NOT be empy, but MAY be an arbitrary text
if ( empty ( $attendee -> email )) $attendee -> email = 'noreply-' . $uid . '-uid@egroupware.org' ;
2010-11-26 22:33:34 +01:00
$message -> attendees [] = $attendee ;
}
$message -> categories = array ();
2011-03-07 16:03:56 +01:00
foreach ( $event [ 'category' ] ? explode ( ',' , $event [ 'category' ]) : array () as $cat_id )
2010-11-26 22:33:34 +01:00
{
$message -> categories [] = categories :: id2name ( $cat_id );
}
2010-12-17 04:44:43 +01:00
2010-12-21 03:45:40 +01:00
// recurring information, only if not a single recurrence eg. virtual exception (!$recur_date)
if ( $event [ 'recur_type' ] != calendar_rrule :: NONE && ! $recur_date )
2010-12-17 04:44:43 +01:00
{
$message -> recurrence = $recurrence = new SyncRecurrence ();
2010-12-20 04:29:37 +01:00
$rrule = calendar_rrule :: event2rrule ( $event , false ); // false = timestamps in $event are servertime
$recurrence -> type = ( int ) self :: $recur_type2as [ $rrule -> type ];
2010-12-17 04:44:43 +01:00
$recurrence -> interval = $rrule -> interval ;
switch ( $rrule -> type )
{
case calendar_rrule :: MONTHLY_WDAY :
$recurrence -> weekofmonth = $rrule -> monthly_byday_num >= 1 ?
$rrule -> monthly_byday_num : 5 ; // 1..5=last week of month, not -1
// fall throught
case calendar_rrule :: WEEKLY :
$recurrence -> dayofweek = $rrule -> weekdays ; // 1=Su, 2=Mo, 4=Tu, .., 64=Sa
break ;
case calendar_rrule :: MONTHLY_MDAY :
$recurrence -> dayofmonth = $rrule -> monthly_bymonthday >= 1 ? // 1..31
$rrule -> monthly_bymonthday : 31 ; // not -1 for last day of month!
break ;
case calendar_rrule :: YEARLY :
$recurrence -> dayofmonth = ( int ) $rrule -> time -> format ( 'd' ); // 1..31
$recurrence -> monthofyear = ( int ) $rrule -> time -> format ( 'm' ); // 1..12
break ;
}
2010-12-20 04:29:37 +01:00
if ( $rrule -> enddate ) $recurrence -> until = $rrule -> enddate -> format ( 'server' );
2010-12-17 04:44:43 +01:00
if ( $rrule -> exceptions )
{
$message -> exceptions = array ();
2011-02-24 23:55:05 +01:00
foreach ( $rrule -> exceptions_objs as $exception_time )
2010-12-17 04:44:43 +01:00
{
$exception = new SyncAppointment (); // exceptions seems to be full SyncAppointments, with only starttime required
2010-12-20 04:29:37 +01:00
$exception -> starttime = $exception_time -> format ( 'server' );
2010-12-17 04:44:43 +01:00
$message -> exceptions [] = $exception ;
}
}
2010-12-21 03:45:40 +01:00
// add virtual exceptions here too (get_recurrence_exceptions should be able to return real-exceptions too!)
foreach ( $this -> calendar -> so -> get_recurrence_exceptions ( $event ,
egw_time :: $server_timezone -> getName (), $cutoffdate , 0 , 'all' ) as $virtual_exception_time )
{
$exception = new SyncAppointment (); // exceptions seems to be full SyncAppointments, with only starttime required
$exception -> starttime = $virtual_exception_time ;
$message -> exceptions [] = $exception ;
}
2010-12-17 04:44:43 +01:00
}
2010-11-26 22:33:34 +01:00
//$message->busystatus;
//$message->reminder;
//$message->meetingstatus;
//$message->deleted;
/*
if ( isset ( $protocolversion ) && $protocolversion < 12.0 ) {
2010-11-26 10:45:38 +01:00
$message -> body ;
$message -> bodytruncated ;
$message -> rtf ;
}
if ( isset ( $protocolversion ) && $protocolversion >= 12.0 ) {
2010-11-26 12:13:00 +01:00
2010-11-26 10:45:38 +01:00
$message -> airsyncbasebody ; // SYNC SyncAirSyncBaseBody
2010-11-26 12:13:00 +01:00
}
2010-11-26 10:45:38 +01:00
*/
2010-12-20 04:29:37 +01:00
// @todo: body or description
2010-11-26 10:45:38 +01:00
return $message ;
}
2010-11-26 12:13:00 +01:00
2010-11-26 22:33:34 +01:00
/**
* StatMessage should return message stats , analogous to the folder stats ( StatFolder ) . Entries are :
2010-12-17 04:44:43 +01:00
* 'id' => Server unique identifier for the message . Again , try to keep this short ( under 20 chars )
* 'flags' => simply '0' for unread , '1' for read
* 'mod' => modification signature . As soon as this signature changes , the item is assumed to be completely
* changed , and will be sent to the PDA as a whole . Normally you can use something like the modification
* time for this field , which will change as soon as the contents have changed .
*
* @ param string $folderid
2010-12-21 03:45:40 +01:00
* @ param int | array $id event id or array or cal_id : recur_date for virtual exception
2010-12-17 04:44:43 +01:00
* @ return array
*/
2010-11-26 22:33:34 +01:00
public function StatMessage ( $folderid , $id )
{
if ( ! isset ( $this -> calendar )) $this -> calendar = new calendar_boupdate ();
2010-12-21 03:45:40 +01:00
if ( ! ( $etag = $this -> calendar -> get_etag ( $id , false ))) // false: do NOT include exceptions into master etag
2010-11-26 22:33:34 +01:00
{
$stat = false ;
2010-12-20 04:29:37 +01:00
// error_log why access is denied (should never happen for everything returned by calendar_bo::search)
2010-11-28 12:01:44 +01:00
$backup = $this -> calendar -> debug ;
$this -> calendar -> debug = 2 ;
2010-12-21 03:45:40 +01:00
list ( $id ) = explode ( ':' , $id );
2010-12-19 01:31:07 +01:00
$this -> calendar -> check_perms ( EGW_ACL_FREEBUSY , $id , 0 , 'server' );
2010-11-28 12:01:44 +01:00
$this -> calendar -> debug = $backup ;
2010-11-26 22:33:34 +01:00
}
else
{
$stat = array (
'mod' => $etag ,
'id' => is_array ( $id ) ? $id [ 'id' ] : $id ,
'flags' => 1 ,
);
}
2010-12-20 04:29:37 +01:00
debugLog ( __METHOD__ . " (' $folderid ', " . array2string ( is_array ( $id ) ? $id [ 'id' ] : $id ) . " ) returning " . array2string ( $stat ));
2010-11-26 22:33:34 +01:00
return $stat ;
}
/**
2010-12-02 23:27:32 +01:00
* Return a changes array
*
2010-12-17 04:44:43 +01:00
* if changes occurr default diff engine computes the actual changes
2010-12-02 23:27:32 +01:00
*
* @ param string $folderid
* @ param string & $syncstate on call old syncstate , on return new syncstate
* @ return array | boolean false if $folderid not found , array () if no changes or array ( array ( " type " => " fakeChange " ))
2010-11-26 22:33:34 +01:00
*/
function AlterPingChanges ( $folderid , & $syncstate )
{
2010-12-02 23:27:32 +01:00
$this -> backend -> splitID ( $folderid , $type , $owner );
2011-02-24 23:55:05 +01:00
debugLog ( __METHOD__ . " (' $folderid ',' $syncstate ') type=' $type ', owner= $owner " );
2010-12-02 23:27:32 +01:00
if ( $type != 'calendar' ) return false ;
2010-12-17 04:44:43 +01:00
if ( ! isset ( $this -> calendar )) $this -> calendar = new calendar_boupdate ();
2010-12-02 23:27:32 +01:00
$ctag = $this -> calendar -> get_ctag ( $owner );
$changes = array (); // no change
$syncstate_was = $syncstate ;
if ( $ctag !== $syncstate )
{
$syncstate = $ctag ;
$changes = array ( array ( 'type' => 'fakeChange' ));
}
//error_log(__METHOD__."('$folderid','$syncstate_was') syncstate='$syncstate' returning ".array2string($changes));
2011-02-24 23:55:05 +01:00
debugLog ( __METHOD__ . " (' $folderid ',' $syncstate_was ') syncstate=' $syncstate ' returning " . array2string ( $changes ));
2010-12-02 23:27:32 +01:00
return $changes ;
2010-11-26 10:45:38 +01:00
}
2010-12-17 04:44:43 +01:00
/**
* Return AS timezone data from given timezone and time
*
* AS spezifies the timezone by the date it changes to dst and back and the offsets .
* Unfortunately this data is not available from PHP ' s DateTime ( Zone ) class .
* Just given the exact time of the next transition , which is available via DateTimeZone :: getTransistions (),
* will fail for recurring events longer then a year , as the transition date / time changes !
*
2010-12-19 01:31:07 +01:00
* We use now the RRule given in the iCal timezone defintion available via calendar_timezones :: tz2id ( $tz , 'component' ) .
2010-12-17 04:44:43 +01:00
*
* Not every timezone uses DST , in which case only bias matters and dstbias = 0
* ( probably all other values should be 0 , as MapiMapping :: _getGMTTZ () in backend / ics . php does it ) .
*
2010-12-20 04:29:37 +01:00
* For southern hermisphere DST in southern winter ( eg . January ), Active Sync implementation of iPhone
* uses a negative dstbias ( eg . - 60 ) and an accordingly moved start - and end - time .
* For Pacific / Auckland TZ iPhone AS implementation uses - 720 =- 12 h instead of 720 =+ 12 h .
* Both are corrected now in our Active Sync timezone generation , as we can not find
* matching timezones for incomming timezone data . iPhone seems not to care on receiving about the above .
*
2010-12-19 01:31:07 +01:00
* @ param string | DateTimeZone $tz timezone , timezone name ( eg . " Europe/Berlin " ) or ical with VTIMEZONE
2010-12-17 04:44:43 +01:00
* @ param int | string | DateTime $ts = null time for which active sync timezone data is requested , default current time
* @ return array with values for keys :
* - " bias " : timezone offset from UTC in minutes for NO DST
* - " dstendmonth " , " dstendday " , " dstendweek " , " dstendhour " , " dstendminute " , " dstendsecond " , " dstendmillis "
* - " stdbias " : seems not to be used
* - " dststartmonth " , " dststartday " , " dststartweek " , " dststarthour " , " dststartminute " , " dststartsecond " , " dststartmillis "
2010-12-19 01:31:07 +01:00
* - " dstbias " : offset in minutes for no DST --> DST , usually 60 or 0 for no DST
2010-12-17 04:44:43 +01:00
*
* @ link http :// download . microsoft . com / download / 5 / D / D / 5 DD33FDF - 91 F5 - 496 D - 9884 - 0 A0B0EE698BB /% 5 BMS - ASDTYPE % 5 D . pdf
2010-12-19 01:31:07 +01:00
* @ throws egw_exception_assertion_failed if no vtimezone data found for given timezone
2010-12-17 04:44:43 +01:00
*/
2010-12-19 01:31:07 +01:00
static public function tz2as ( $tz , $ts = null )
2010-12-17 04:44:43 +01:00
{
/*
BEGIN : VTIMEZONE
TZID : Europe / Berlin
X - LIC - LOCATION : Europe / Berlin
BEGIN : DAYLIGHT
TZOFFSETFROM :+ 0100
--> bias : 60 min
TZOFFSETTO :+ 0200
--> dstbias : + 0200 - + 0100 = + 0100 = 60 min
TZNAME : CEST
DTSTART : 19700329 T020000
RRULE : FREQ = YEARLY ; BYDAY =- 1 SU ; BYMONTH = 3
2010-12-19 01:31:07 +01:00
--> dststart : month : 3 , day : SU ( 0 ? ? ? ), week : - 1 | 5 , hour : 2 , minute , second , millis : 0
2010-12-17 04:44:43 +01:00
END : DAYLIGHT
BEGIN : STANDARD
TZOFFSETFROM :+ 0200
TZOFFSETTO :+ 0100
TZNAME : CET
DTSTART : 19701025 T030000
RRULE : FREQ = YEARLY ; BYDAY =- 1 SU ; BYMONTH = 10
--> dstend : month : 10 , day : SU ( 0 ? ? ? ), week : - 1 | 5 , hour : 3 , minute , second , millis : 0
END : STANDARD
END : VTIMEZONE
*/
$data = array (
'bias' => 0 ,
'stdbias' => 0 ,
'dstbias' => 0 ,
2010-12-20 04:29:37 +01:00
'dststartyear' => 0 , 'dststartmonth' => 0 , 'dststartday' => 0 , 'dststartweek' => 0 ,
2010-12-17 04:44:43 +01:00
'dststarthour' => 0 , 'dststartminute' => 0 , 'dststartsecond' => 0 , 'dststartmillis' => 0 ,
2010-12-20 04:29:37 +01:00
'dstendyear' => 0 , 'dstendmonth' => 0 , 'dstendday' => 0 , 'dstendweek' => 0 ,
2010-12-17 04:44:43 +01:00
'dstendhour' => 0 , 'dstendminute' => 0 , 'dstendsecond' => 0 , 'dstendmillis' => 0 ,
);
2010-12-19 01:31:07 +01:00
$name = $component = is_a ( $tz , 'DateTimeZone' ) ? $tz -> getName () : $tz ;
if ( strpos ( $component , 'VTIMEZONE' ) === false ) $component = calendar_timezones :: tz2id ( $name , 'component' );
// parse ical timezone defintion
$ical = self :: ical2array ( $ical = $component );
$standard = $ical [ 'VTIMEZONE' ][ 'STANDARD' ];
$daylight = $ical [ 'VTIMEZONE' ][ 'DAYLIGHT' ];
2010-12-17 04:44:43 +01:00
2010-12-19 01:31:07 +01:00
if ( ! isset ( $standard ))
2010-12-17 04:44:43 +01:00
{
throw new egw_exception_assertion_failed ( " NO standard component for ' $name ' in ' $component '! " );
}
2010-12-19 01:31:07 +01:00
// get bias and dstbias from standard component, which is present in all tz's
// (dstbias is relative to bias and almost always 60 or 0)
$data [ 'bias' ] = 60 * substr ( $standard [ 'TZOFFSETTO' ], 0 , - 2 ) + substr ( $standard [ 'TZOFFSETTO' ], - 2 );
$data [ 'dstbias' ] = 60 * substr ( $standard [ 'TZOFFSETFROM' ], 0 , - 2 ) + substr ( $standard [ 'TZOFFSETFROM' ], - 2 ) - $data [ 'bias' ];
2010-12-17 04:44:43 +01:00
2010-12-20 04:29:37 +01:00
// at least Active Sync implementation on iPhone uses -720=-12h for Pacific/Auckland, not 720=+12h
if ( $data [ 'bias' ] == 720 )
{
$data [ 'bias' ] = - 720 ;
}
2010-12-19 01:31:07 +01:00
// check if we have an additional DAYLIGHT component and both have a RRULE component --> tz uses daylight saving
if ( isset ( $standard [ 'RRULE' ]) && isset ( $daylight ) && isset ( $daylight [ 'RRULE' ]))
2010-12-17 04:44:43 +01:00
{
2010-12-19 01:31:07 +01:00
foreach ( array ( 'dststart' => $daylight , 'dstend' => $standard ) as $prefix => $comp )
2010-12-17 04:44:43 +01:00
{
2010-12-19 01:31:07 +01:00
if ( preg_match ( '/FREQ=YEARLY;BYDAY=(.*);BYMONTH=(\d+)/' , $comp [ 'RRULE' ], $matches ))
2010-12-17 04:44:43 +01:00
{
$data [ $prefix . 'month' ] = ( int ) $matches [ 2 ];
$data [ $prefix . 'week' ] = ( int ) $matches [ 1 ];
2010-12-19 01:31:07 +01:00
if ( $data [ $prefix . 'week' ] < 0 ) $data [ $prefix . 'week' ] = 5 ; // -1 for last week might be 5 for as as in recuring events definition
2010-12-17 04:44:43 +01:00
static $day2int = array ( 'SU' => 0 , 'MO' => 1 , 'TU' => 2 , 'WE' => 3 , 'TH' => 4 , 'FR' => 5 , 'SA' => 6 );
$data [ $prefix . 'day' ] = ( int ) $day2int [ substr ( $matches [ 1 ], - 2 )];
}
2010-12-19 01:31:07 +01:00
if ( preg_match ( '/^\d{8}T(\d{6})$/' , $comp [ 'DTSTART' ], $matches ))
2010-12-17 04:44:43 +01:00
{
$data [ $prefix . 'hour' ] = ( int ) substr ( $matches [ 1 ], 0 , 2 );
$data [ $prefix . 'minute' ] = ( int ) substr ( $matches [ 1 ], 2 , 2 );
$data [ $prefix . 'second' ] = ( int ) substr ( $matches [ 1 ], 4 , 2 );
}
}
2010-12-20 04:29:37 +01:00
// for southern hermisphere, were DST is in January, Active Sync (at least iPhone implementation)
// sends a negative dstbias and a accordingly moved start- and endtime
if ( $data [ 'dststartmonth' ] > $data [ 'dstendmonth' ])
{
$data [ 'dststarthour' ] += $data [ 'dstbias' ] / 60 ;
$data [ 'dststartminute' ] += $data [ 'dstbias' ] % 60 ;
$data [ 'dstendhour' ] -= $data [ 'dstbias' ] / 60 ;
$data [ 'dstendminute' ] -= $data [ 'dstbias' ] % 60 ;
$data [ 'dstbias' ] = - $data [ 'dstbias' ];
}
2010-12-17 04:44:43 +01:00
}
2010-12-19 01:31:07 +01:00
//error_log(__METHOD__."('$name') returning ".array2string($data));
2010-12-17 04:44:43 +01:00
return $data ;
}
2010-12-19 01:31:07 +01:00
/**
* Simple iCal parser :
*
* BEGIN : VTIMEZONE
* TZID : Europe / Berlin
* X - LIC - LOCATION : Europe / Berlin
* BEGIN : DAYLIGHT
* TZOFFSETFROM :+ 0100
* TZOFFSETTO :+ 0200
* TZNAME : CEST
* DTSTART : 19700329 T020000
* RRULE : FREQ = YEARLY ; BYDAY =- 1 SU ; BYMONTH = 3
* END : DAYLIGHT
* BEGIN : STANDARD
* TZOFFSETFROM :+ 0200
* TZOFFSETTO :+ 0100
* TZNAME : CET
* DTSTART : 19701025 T030000
* RRULE : FREQ = YEARLY ; BYDAY =- 1 SU ; BYMONTH = 10
* END : STANDARD
* END : VTIMEZONE
*
* Array
* (
2010-12-19 04:54:09 +01:00
* [ VTIMEZONE ] => Array
* (
* [ TZID ] => Europe / Berlin
* [ X - LIC - LOCATION ] => Europe / Berlin
* [ DAYLIGHT ] => Array
* (
* [ TZOFFSETFROM ] => + 0100
* [ TZOFFSETTO ] => + 0200
* [ TZNAME ] => CEST
* [ DTSTART ] => 19700329 T020000
* [ RRULE ] => FREQ = YEARLY ; BYDAY =- 1 SU ; BYMONTH = 3
* )
* [ STANDARD ] => Array
* (
* [ TZOFFSETFROM ] => + 0200
* [ TZOFFSETTO ] => + 0100
* [ TZNAME ] => CET
* [ DTSTART ] => 19701025 T030000
* [ RRULE ] => FREQ = YEARLY ; BYDAY =- 1 SU ; BYMONTH = 10
* )
* )
2010-12-19 01:31:07 +01:00
* )
*
* @ param string | array $ical lines of ical file
* @ param string $component = null
* @ return array with parsed ical components
*/
static public function ical2array ( & $ical , $component = null )
{
$arr = array ();
if ( ! is_array ( $ical )) $ical = preg_split ( " /[ \r \n ]+/m " , $ical );
while (( $line = array_shift ( $ical )))
{
list ( $name , $value ) = explode ( ':' , $line , 2 );
if ( $name == 'BEGIN' )
{
$arr [ $value ] = self :: ical2array ( $ical , $value );
}
elseif ( $name == 'END' )
{
break ;
}
else
{
$arr [ $name ] = $value ;
}
}
return $arr ;
}
2010-12-17 04:44:43 +01:00
/**
* Get timezone from AS timezone data
*
2010-12-19 01:31:07 +01:00
* Here we can only loop through all available timezones ( starting with the users timezone ) and
2010-12-17 04:44:43 +01:00
* try to find a timezone matching the change data and offsets specified in $data .
* This conversation is not unique , as multiple timezones can match the given data or none !
*
* @ param array $data
2010-12-20 04:29:37 +01:00
* @ return string timezone name , eg . " Europe/Berlin " or 'UTC' if NO matching timezone found
2010-12-17 04:44:43 +01:00
*/
2010-12-19 01:31:07 +01:00
public static function as2tz ( array $data )
2010-12-17 04:44:43 +01:00
{
2010-12-19 01:31:07 +01:00
static $cache ; // some caching withing the request
2010-12-20 04:29:37 +01:00
unset ( $data [ 'name' ]); // not used, but can stall the match
2010-12-19 01:31:07 +01:00
$key = serialize ( $data );
for ( $n = 0 ; ! isset ( $cache [ $key ]); ++ $n )
{
if ( ! $n ) // check users timezone first
{
$tz = egw_time :: $user_timezone -> getName ();
}
elseif ( ! ( $tz = calendar_timezones :: id2tz ( $n ))) // no further timezones to check
{
2010-12-20 04:29:37 +01:00
$cache [ $key ] = 'UTC' ;
error_log ( __METHOD__ . '(' . array2string ( $data ) . ') NO matching timezone found --> using UTC now!' );
2010-12-19 01:31:07 +01:00
break ;
}
if ( self :: tz2as ( $tz ) == $data )
{
$cache [ $key ] = $tz ;
break ;
}
}
return $cache [ $key ];
2010-12-17 04:44:43 +01:00
}
/**
* Unpack timezone info from Sync
*
* copied from backend / ics . php
*/
2010-12-20 04:29:37 +01:00
static public function _getTZFromSyncBlob ( $data )
2010-12-19 01:31:07 +01:00
{
2010-12-17 04:44:43 +01:00
$tz = unpack ( " lbias/a64name/vdstendyear/vdstendmonth/vdstendday/vdstendweek/vdstendhour/vdstendminute/vdstendsecond/vdstendmillis/ " .
" lstdbias/a64name/vdststartyear/vdststartmonth/vdststartday/vdststartweek/vdststarthour/vdststartminute/vdststartsecond/vdststartmillis/ " .
" ldstbias " , $data );
return $tz ;
}
/**
* Pack timezone info for Sync
*
* copied from backend / ics . php
*/
2010-12-20 04:29:37 +01:00
static public function _getSyncBlobFromTZ ( $tz )
2010-12-19 01:31:07 +01:00
{
2010-12-17 04:44:43 +01:00
$packed = pack ( " la64vvvvvvvv " . " la64vvvvvvvv " . " l " ,
$tz [ " bias " ], " " , 0 , $tz [ " dstendmonth " ], $tz [ " dstendday " ], $tz [ " dstendweek " ], $tz [ " dstendhour " ], $tz [ " dstendminute " ], $tz [ " dstendsecond " ], $tz [ " dstendmillis " ],
$tz [ " stdbias " ], " " , 0 , $tz [ " dststartmonth " ], $tz [ " dststartday " ], $tz [ " dststartweek " ], $tz [ " dststarthour " ], $tz [ " dststartminute " ], $tz [ " dststartsecond " ], $tz [ " dststartmillis " ],
$tz [ " dstbias " ]);
return $packed ;
}
2010-11-26 10:45:38 +01:00
}
2010-12-19 01:31:07 +01:00
/**
* Testcode for active sync timezone stuff
*
2010-12-20 04:29:37 +01:00
* You need to comment implements activesync_plugin_write
*/
if ( isset ( $_SERVER [ 'SCRIPT_FILENAME' ]) && $_SERVER [ 'SCRIPT_FILENAME' ] == __FILE__ ) // some tests
{
$GLOBALS [ 'egw_info' ] = array (
'flags' => array (
'currentapp' => 'login'
)
);
require_once ( '../../header.inc.php' );
ini_set ( 'display_errors' , 1 );
error_reporting ( E_ALL & ~ E_NOTICE );
// get as timezone data for agive timezone
$tz = 'Pacific/Auckland' ; //'Europe/Zurich';//'America/New_York';//'Australia/Darwin';//'Europe/Berlin';
$ical = calendar_timezones :: tz2id ( $tz , 'component' );
echo " <pre> " . print_r ( $ical , true ) . " </pre> \n " ;
$ical_arr = calendar_activesync :: ical2array ( $ical_tz = $ical );
echo " <pre> " . print_r ( $ical_arr , true ) . " </pre> \n " ;
$as_tz = calendar_activesync :: tz2as ( $tz );
echo " <pre> " . print_r ( $as_tz , true ) . " </pre> \n " ;
// this is what iPhone sends as TZ for New Zealand (eg. Pacific/Auckland)
$sync_blob = 'MP3//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAABAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkAAAAFAAMAAAAAAAAAxP///w==' ;
$as_tz = calendar_activesync :: _getTZFromSyncBlob ( base64_decode ( $sync_blob ));
echo " <pre> " . print_r ( $as_tz , true ) . " </pre> \n " ;
// find matching timezone from as data
// this returns the FIRST match, which is in case of Pacific/Auckland eg. Antarctica/McMurdo ;-)
echo array2string ( calendar_activesync :: as2tz ( $as_tz ));
2011-03-07 16:03:56 +01:00
}