2008-05-17 20:03:33 +02:00
< ? php
/**
2011-04-11 11:29:39 +02:00
* EGroupware : GroupDAV access : infolog handler
2008-05-17 20:03:33 +02:00
*
* @ link http :// www . egroupware . org
* @ license http :// opensource . org / licenses / gpl - license . php GPL - GNU General Public License
* @ package infolog
* @ subpackage groupdav
* @ author Ralf Becker < RalfBecker - AT - outdoor - training . de >
2013-09-23 12:21:31 +02:00
* @ copyright ( c ) 2007 - 13 by Ralf Becker < RalfBecker - AT - outdoor - training . de >
2008-05-17 20:03:33 +02:00
* @ version $Id $
*/
2011-02-23 11:59:12 +01:00
require_once EGW_SERVER_ROOT . '/phpgwapi/inc/horde/lib/core.php' ;
2008-05-17 20:03:33 +02:00
/**
2011-04-11 11:29:39 +02:00
* EGroupware : GroupDAV access : infolog handler
2012-02-21 21:04:45 +01:00
*
* Permanent error_log () calls should use $this -> groupdav -> log ( $str ) instead , to be send to PHP error_log ()
* and our request - log ( prefixed with " ### " after request and response , like exceptions ) .
2008-05-17 20:03:33 +02:00
*/
class infolog_groupdav extends groupdav_handler
{
/**
* bo class of the application
*
2008-10-07 14:50:14 +02:00
* @ var infolog_bo
2008-05-17 20:03:33 +02:00
*/
var $bo ;
2009-10-16 10:36:28 +02:00
2011-02-23 11:59:12 +01:00
/**
* vCalendar Instance for parsing
*
* @ var array
*/
var $vCalendar ;
2010-03-15 10:55:16 +01:00
var $filter_prop2infolog = array (
'SUMMARY' => 'info_subject' ,
'UID' => 'info_uid' ,
'DTSTART' => 'info_startdate' ,
'DUE' => 'info_enddate' ,
'DESCRIPTION' => 'info_des' ,
'STATUS' => 'info_status' ,
'PRIORITY' => 'info_priority' ,
'LOCATION' => 'info_location' ,
'COMPLETED' => 'info_datecompleted' ,
2011-10-06 12:29:01 +02:00
'CREATED' => 'info_created' ,
2010-03-15 10:55:16 +01:00
);
2011-04-11 11:29:39 +02:00
/**
* Are we using info_id , info_uid or caldav_name for the path / url
*
* Get 's set in constructor to ' caldav_name ' and groupdav_handler::$path_extension = ' ' !
*/
static $path_attr = 'info_id' ;
2008-05-17 20:03:33 +02:00
/**
* Constructor
*
* @ param string $app 'calendar' , 'addressbook' or 'infolog'
2011-09-18 12:56:56 +02:00
* @ param groupdav $groupdav calling class
2008-05-17 20:03:33 +02:00
*/
2011-09-18 12:56:56 +02:00
function __construct ( $app , groupdav $groupdav )
2008-05-17 20:03:33 +02:00
{
2011-09-18 12:56:56 +02:00
parent :: __construct ( $app , $groupdav );
2009-10-16 10:36:28 +02:00
2009-06-08 18:21:14 +02:00
$this -> bo = new infolog_bo ();
2011-02-23 11:59:12 +01:00
$this -> vCalendar = new Horde_iCalendar ;
2008-05-17 20:03:33 +02:00
2011-04-11 11:29:39 +02:00
// since 1.9.002 we allow clients to specify the URL when creating a new event, as specified by CalDAV
if ( version_compare ( $GLOBALS [ 'egw_info' ][ 'apps' ][ 'calendar' ][ 'version' ], '1.9.002' , '>=' ))
{
self :: $path_attr = 'caldav_name' ;
groupdav_handler :: $path_extension = '' ;
}
}
2008-11-18 07:11:12 +01:00
/**
* Create the path for an event
*
* @ param array | int $info
* @ return string
*/
2011-09-21 22:08:21 +02:00
function get_path ( $info )
2008-11-18 07:11:12 +01:00
{
2011-04-11 11:29:39 +02:00
if ( is_numeric ( $info ) && self :: $path_attr == 'info_id' )
2008-11-18 07:11:12 +01:00
{
$name = $info ;
}
else
{
if ( ! is_array ( $info )) $info = $this -> bo -> read ( $info );
2011-04-11 11:29:39 +02:00
$name = $info [ self :: $path_attr ];
2008-11-18 07:11:12 +01:00
}
2011-04-11 11:29:39 +02:00
return $name . groupdav_handler :: $path_extension ;
2008-11-18 07:11:12 +01:00
}
2008-05-17 20:03:33 +02:00
/**
2011-09-28 14:35:53 +02:00
* Get filter - array for infolog_bo :: search used by getctag and propfind
2008-05-17 20:03:33 +02:00
*
* @ param string $path
* @ param int $user account_id
2011-09-28 14:35:53 +02:00
* @ return array
2008-05-17 20:03:33 +02:00
*/
2011-09-28 14:35:53 +02:00
private function get_infolog_filter ( $path , $user )
2008-05-17 20:03:33 +02:00
{
2012-01-26 02:49:56 +01:00
if ( ! ( $infolog_types = $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'groupdav' ][ 'infolog-types' ]))
2011-09-28 14:35:53 +02:00
{
$infolog_types = 'task' ;
}
2010-03-11 08:30:46 +01:00
if ( $path == '/infolog/' )
{
2010-03-15 10:55:16 +01:00
$task_filter = 'own' ;
2010-03-11 08:30:46 +01:00
}
else
{
2012-01-26 02:49:56 +01:00
if ( $user == $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ])
2010-03-15 10:55:16 +01:00
{
2012-01-26 02:49:56 +01:00
$task_filter = 'own' ;
2010-03-15 10:55:16 +01:00
}
else
{
2012-01-26 02:49:56 +01:00
$task_filter = 'user' . $user ;
2010-03-15 10:55:16 +01:00
}
2010-03-11 08:30:46 +01:00
}
2012-01-26 02:49:56 +01:00
$ret = array (
2010-03-16 21:44:55 +01:00
'filter' => $task_filter ,
2011-09-28 14:35:53 +02:00
'info_type' => explode ( ',' , $infolog_types ),
2010-03-15 10:55:16 +01:00
);
2012-01-29 23:34:43 +01:00
//error_log(__METHOD__."('$path', $user) returning ".array2string($ret));
2012-01-26 02:49:56 +01:00
return $ret ;
2011-09-28 14:35:53 +02:00
}
/**
* Handle propfind in the infolog folder
*
* @ param string $path
2012-06-27 22:08:56 +02:00
* @ param array & $options
2011-09-28 14:35:53 +02:00
* @ param array & $files
* @ param int $user account_id
* @ return mixed boolean true on success , false on failure or string with http status ( eg . '404 Not Found' )
*/
2012-06-27 22:08:56 +02:00
function propfind ( $path , & $options , & $files , $user , $id = '' )
2011-09-28 14:35:53 +02:00
{
// todo add a filter to limit how far back entries from the past get synced
$filter = $this -> get_infolog_filter ( $path , $user );
2010-03-07 00:06:43 +01:00
2010-03-15 10:55:16 +01:00
// process REPORT filters or multiget href's
2012-09-24 12:26:29 +02:00
if (( $id || $options [ 'root' ][ 'name' ] != 'propfind' ) && ! $this -> _report_filters ( $options , $filter , $id , $nresults ))
2010-03-15 10:55:16 +01:00
{
2011-09-30 14:52:40 +02:00
// return empty collection, as iCal under iOS 5 had problems with returning "404 Not found" status
// when trying to request not supported components, eg. VTODO on a calendar collection
return true ;
2010-03-15 10:55:16 +01:00
}
2011-10-06 12:29:01 +02:00
// enable time-range filter for tests via propfind / autoindex
//$filter[] = $sql = $this->_time_range_filter(array('end' => '20001231T000000Z'));
2011-10-04 14:16:03 +02:00
if ( $id ) $path = dirname ( $path ) . '/' ; // caldav_name get's added anyway in the callback
2010-03-15 10:55:16 +01:00
if ( $this -> debug > 1 )
{
error_log ( __METHOD__ . " ( $path ,,, $user , $id ) filter= " .
array2string ( $filter ));
2010-03-07 00:06:43 +01:00
}
// check if we have to return the full calendar data or just the etag's
2010-03-15 10:55:16 +01:00
if ( ! ( $filter [ 'calendar_data' ] = $options [ 'props' ] == 'all' &&
$options [ 'root' ][ 'ns' ] == groupdav :: CALDAV ) && is_array ( $options [ 'props' ]))
2010-03-07 00:06:43 +01:00
{
foreach ( $options [ 'props' ] as $prop )
{
if ( $prop [ 'name' ] == 'calendar-data' )
{
2010-03-15 10:55:16 +01:00
$filter [ 'calendar_data' ] = true ;
2010-03-07 00:06:43 +01:00
break ;
}
}
}
2012-09-24 12:26:29 +02:00
// rfc 6578 sync-collection report: filter for sync-token is already set in _report_filters
if ( $options [ 'root' ][ 'name' ] == 'sync-collection' )
{
2012-09-26 16:30:47 +02:00
// callback to query sync-token, after propfind_callbacks / iterator is run and
// stored max. modification-time in $this->sync_collection_token
$files [ 'sync-token' ] = array ( $this , 'get_sync_collection_token' );
$files [ 'sync-token-params' ] = array ( $path , $user );
$this -> sync_collection_token = null ;
2012-09-24 12:26:29 +02:00
}
if ( isset ( $nresults ))
{
$files [ 'files' ] = $this -> propfind_callback ( $path , $filter , array ( 0 , ( int ) $nresults ));
}
else
{
// return iterator, calling ourself to return result in chunks
$files [ 'files' ] = new groupdav_propfind_iterator ( $this , $path , $filter , $files [ 'files' ]);
}
2010-03-15 10:55:16 +01:00
return true ;
}
2010-03-07 00:06:43 +01:00
2010-03-15 10:55:16 +01:00
/**
* Callback for profind interator
*
* @ param string $path
* @ param array $filter
* @ param array | boolean $start = false false = return all or array ( start , num )
* @ return array with " files " array with values for keys path and props
*/
function & propfind_callback ( $path , array $filter , $start = false )
{
if ( $this -> debug ) $starttime = microtime ( true );
2010-03-07 00:06:43 +01:00
2010-03-15 10:55:16 +01:00
if (( $calendar_data = $filter [ 'calendar_data' ]))
{
$handler = self :: _get_handler ();
}
unset ( $filter [ 'calendar_data' ]);
$task_filter = $filter [ 'filter' ];
unset ( $filter [ 'filter' ]);
2008-08-04 21:08:09 +02:00
2012-09-24 12:26:29 +02:00
$order = 'info_datemodified' ;
$sort = 'DESC' ;
if ( preg_match ( '/^([a-z0-9_]+)( DESC| ASC)?$/i' , $filter [ 'order' ], $matches ))
{
$order = $matches [ 1 ];
if ( $matches [ 2 ]) $sort = $matches [ 2 ];
unset ( $filter [ 'order' ]);
}
2010-03-15 10:55:16 +01:00
$query = array (
2012-09-24 12:26:29 +02:00
'order' => $order ,
'sort' => $sort ,
2010-03-15 10:55:16 +01:00
'filter' => $task_filter ,
'date_format' => 'server' ,
2008-08-04 21:08:09 +02:00
'col_filter' => $filter ,
2012-01-31 19:00:57 +01:00
'custom_fields' => true , // otherwise custom fields get NOT loaded!
2010-03-15 10:55:16 +01:00
);
2010-03-16 21:44:55 +01:00
if ( ! $calendar_data )
2010-03-16 08:01:36 +01:00
{
2012-09-24 12:26:29 +02:00
$query [ 'cols' ] = array ( 'info_id' , 'info_datemodified' , 'info_uid' , 'caldav_name' , 'info_subject' , 'info_status' );
2010-03-16 08:01:36 +01:00
}
2010-03-15 10:55:16 +01:00
if ( is_array ( $start ))
{
$query [ 'start' ] = $offset = $start [ 0 ];
$query [ 'num_rows' ] = $start [ 1 ];
}
else
{
$offset = 0 ;
}
2013-10-01 13:38:34 +02:00
$requested_multiget_ids = ( array ) $filter [ self :: $path_attr ];
2013-09-25 12:27:41 +02:00
2010-03-15 10:55:16 +01:00
$files = array ();
// ToDo: add parameter to only return id & etag
$tasks =& $this -> bo -> search ( $query );
if ( $tasks && $offset == $query [ 'start' ])
2008-05-17 20:03:33 +02:00
{
2010-03-16 21:44:55 +01:00
foreach ( $tasks as $task )
2008-05-17 20:03:33 +02:00
{
2013-09-25 12:27:41 +02:00
// remove task from requested multiget ids, to be able to report not found urls
if ( $requested_multiget_ids && ( $k = array_search ( $task [ self :: $path_attr ], $requested_multiget_ids )) !== false )
{
unset ( $requested_multiget_ids [ $k ]);
}
2012-09-24 12:26:29 +02:00
// sync-collection report: deleted entry need to be reported without properties
2012-09-26 16:30:47 +02:00
if ( $task [ 'info_status' ] == 'deleted' )
2012-09-24 12:26:29 +02:00
{
$files [] = array ( 'path' => $path . urldecode ( $this -> get_path ( $task )));
continue ;
}
2010-03-07 00:06:43 +01:00
$props = array (
2011-09-21 22:08:21 +02:00
'getcontenttype' => $this -> agent != 'kde' ? 'text/calendar; charset=utf-8; component=VTODO' : 'text/calendar' , // Konqueror (3.5) dont understand it otherwise
'getlastmodified' => $task [ 'info_datemodified' ],
2012-01-31 01:37:01 +01:00
'displayname' => $task [ 'info_subject' ],
2010-03-07 00:06:43 +01:00
);
if ( $calendar_data )
{
2011-10-03 14:53:28 +02:00
$content = $handler -> exportVTODO ( $task , '2.0' , null ); // no METHOD:PUBLISH for CalDAV
2011-09-21 22:08:21 +02:00
$props [ 'getcontentlength' ] = bytes ( $content );
2010-03-07 00:06:43 +01:00
$props [] = HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'calendar-data' , $content );
}
2011-09-21 22:08:21 +02:00
$files [] = $this -> add_resource ( $path , $task , $props );
2008-05-17 20:03:33 +02:00
}
}
2013-09-25 12:27:41 +02:00
// report not found multiget urls
if ( $requested_multiget_ids )
{
foreach ( $requested_multiget_ids as $id )
{
$files [] = array ( 'path' => $path . $id . self :: $path_extension );
}
}
2012-09-26 16:30:47 +02:00
// sync-collection report --> return modified of last contact as sync-token
$sync_collection_report = strpos ( $task_filter , '+deleted' ) !== false ;
if ( $sync_collection_report )
2012-09-24 12:26:29 +02:00
{
2012-09-26 16:30:47 +02:00
$this -> sync_collection_token = $info [ 'date_modified' ];
// hack to support limit with sync-collection report: tasks are returned in modified ASC order (oldest first)
// if limit is smaller then full result, return modified-1 as sync-token, so client requests next chunk incl. modified
// (which might contain further entries with identical modification time)
if ( $start [ 0 ] == 0 && $start [ 1 ] != groupdav_propfind_iterator :: CHUNK_SIZE && $query [ 'total' ] > $start [ 1 ])
{
-- $this -> sync_collection_token ;
}
2012-09-24 12:26:29 +02:00
}
2010-03-15 10:55:16 +01:00
if ( $this -> debug ) error_log ( __METHOD__ . " ( $path ) took " . ( microtime ( true ) - $starttime ) . ' to return ' . count ( $files ) . ' items' );
return $files ;
}
/**
* Process the filters from the CalDAV REPORT request
*
* @ param array $options
* @ param array & $cal_filters
* @ param string $id
2012-09-24 12:26:29 +02:00
* @ param int & $nresult on return limit for number or results or unchanged / null
* @ return boolean true if filter could be processed
2010-03-15 10:55:16 +01:00
*/
2013-04-04 17:12:33 +02:00
function _report_filters ( $options , & $cal_filters , $id , & $nresults )
2010-03-15 10:55:16 +01:00
{
if ( $options [ 'filters' ])
{
2011-10-07 11:05:11 +02:00
$cal_filters_in = $cal_filters ; // remember filter, to be able to reset standard open-filter, if client sets own filters
2010-03-15 10:55:16 +01:00
foreach ( $options [ 'filters' ] as $filter )
{
switch ( $filter [ 'name' ])
{
case 'comp-filter' :
if ( $this -> debug > 1 ) error_log ( __METHOD__ . " ( $options[path] ,...) comp-filter=' { $filter [ 'attrs' ][ 'name' ] } ' " );
switch ( $filter [ 'attrs' ][ 'name' ])
{
case 'VTODO' :
2010-06-09 21:44:58 +02:00
case 'VCALENDAR' :
2010-03-15 10:55:16 +01:00
break ;
default :
return false ;
}
break ;
case 'prop-filter' :
if ( $this -> debug > 1 ) error_log ( __METHOD__ . " ( $options[path] ,...) prop-filter=' { $filter [ 'attrs' ][ 'name' ] } ' " );
$prop_filter = $filter [ 'attrs' ][ 'name' ];
break ;
case 'text-match' :
if ( $this -> debug > 1 ) error_log ( __METHOD__ . " ( $options[path] ,...) text-match: $prop_filter =' { $filter [ 'data' ] } ' " );
if ( ! isset ( $this -> filter_prop2infolog [ strtoupper ( $prop_filter )]))
{
if ( $this -> debug ) error_log ( __METHOD__ . " ( $options[path] , " . array2string ( $options ) . " ,...) unknown property ' $prop_filter ' --> ignored " );
}
else
{
$cal_filters [ $this -> filter_prop2infolog [ strtoupper ( $prop_filter )]] = $filter [ 'data' ];
}
unset ( $prop_filter );
break ;
case 'param-filter' :
if ( $this -> debug ) error_log ( __METHOD__ . " ( $options[path] ,...) param-filter=' { $filter [ 'attrs' ][ 'name' ] } ' not (yet) implemented! " );
break ;
case 'time-range' :
2011-10-07 11:05:11 +02:00
$cal_filters [] = $this -> _time_range_filter ( $filter [ 'attrs' ]);
2010-03-15 10:55:16 +01:00
break ;
default :
if ( $this -> debug ) error_log ( __METHOD__ . " ( $options[path] , " . array2string ( $options ) . " ,...) unknown filter --> ignored " );
break ;
}
}
2012-09-24 12:26:29 +02:00
}
// parse limit from $options['other']
/* Example limit
< B : limit >
< B : nresults > 10 </ B : nresults >
</ B : limit >
*/
foreach (( array ) $options [ 'other' ] as $option )
{
switch ( $option [ 'name' ])
2011-10-07 11:05:11 +02:00
{
2012-09-24 12:26:29 +02:00
case 'nresults' :
$nresults = ( int ) $option [ 'data' ];
//error_log(__METHOD__."(...) options[other]=".array2string($options['other'])." --> nresults=$nresults");
break ;
case 'limit' :
break ;
case 'href' :
break ; // from addressbook-multiget, handled below
// rfc 6578 sync-report
case 'sync-token' :
if ( ! empty ( $option [ 'data' ]))
{
$parts = explode ( '/' , $option [ 'data' ]);
$sync_token = array_pop ( $parts );
2013-12-12 02:54:16 +01:00
$cal_filters [] = 'info_datemodified>' . ( int ) $sync_token ;
$cal_filters [ 'filter' ] .= '+deleted' ; // to return deleted entries too
$cal_filters [ 'order' ] = 'info_datemodified ASC' ; // return oldest modifications first
2012-09-24 12:26:29 +02:00
}
break ;
case 'sync-level' :
if ( $option [ 'data' ] != '1' )
{
$this -> groupdav -> log ( __METHOD__ . " (...) only sync-level { $option [ 'data' ] } requested, but only 1 supported! options[other]= " . array2string ( $options [ 'other' ]));
}
break ;
default :
$this -> groupdav -> log ( __METHOD__ . " (...) unknown xml tag ' { $option [ 'name' ] } ': options[other]= " . array2string ( $options [ 'other' ]));
break ;
}
2010-03-15 10:55:16 +01:00
}
// multiget or propfind on a given id
//error_log(__FILE__ . __METHOD__ . "multiget of propfind:");
if ( $options [ 'root' ][ 'name' ] == 'calendar-multiget' || $id )
{
$ids = array ();
if ( $id )
{
2011-04-11 11:29:39 +02:00
$cal_filters [ self :: $path_attr ] = groupdav_handler :: $path_extension ?
basename ( $id , groupdav_handler :: $path_extension ) : $id ;
2010-03-15 10:55:16 +01:00
}
else // fetch all given url's
{
foreach ( $options [ 'other' ] as $option )
{
if ( $option [ 'name' ] == 'href' )
{
$parts = explode ( '/' , $option [ 'data' ]);
2014-02-20 16:11:27 +01:00
if (( $id = basename ( urldecode ( array_pop ( $parts )))))
2011-04-11 11:29:39 +02:00
{
$cal_filters [ self :: $path_attr ][] = groupdav_handler :: $path_extension ?
basename ( $id , groupdav_handler :: $path_extension ) : $id ;
}
2010-03-15 10:55:16 +01:00
}
}
}
if ( $this -> debug > 1 ) error_log ( __METHOD__ . " ( $options[path] ,..., $id ) calendar-multiget: ids= " . implode ( ',' , $ids ));
}
2008-05-17 20:03:33 +02:00
return true ;
}
2011-10-06 12:29:01 +02:00
/**
* Create SQL filter from time - range filter attributes
*
* CalDAV time - range for VTODO checks DTSTART , DTEND , DUE , CREATED and allways includes tasks if none given
* @ see http :// tools . ietf . org / html / rfc4791 #section-9.9
*
* @ param array $attrs values for keys 'start' and / or 'end' , at least one is required by CalDAV rfc !
* @ return string with sql
*/
private function _time_range_filter ( array $attrs )
{
$to_or = $to_and = array ();
if ( ! empty ( $attrs [ 'start' ]))
{
$start = ( int ) $this -> vCalendar -> _parseDateTime ( $attrs [ 'start' ]);
}
if ( ! empty ( $attrs [ 'end' ]))
{
$end = ( int ) $this -> vCalendar -> _parseDateTime ( $attrs [ 'end' ]);
}
elseif ( empty ( $attrs [ 'start' ]))
{
2012-02-21 21:04:45 +01:00
$this -> groupdav -> log ( __METHOD__ . '(' . array2string ( $attrs ) . ') minimum one of start or end is required!' );
2011-10-06 12:29:01 +02:00
return '1' ; // to not give sql error, but simply not filter out anything
}
// we dont need to care for DURATION line in rfc4791#section-9.9, as we always put that in DUE/info_enddate
// we have start- and/or enddate
if ( isset ( $start ))
{
$to_and [] = " ( $start < info_enddate OR $start <= info_startdate) " ;
}
if ( isset ( $end ))
{
$to_and [] = " (info_startdate < $end OR info_enddate <= $end ) " ;
}
$to_or [] = '(' . implode ( ' AND ' , $to_and ) . ')' ;
/* either start or enddate is already included in the above , because of OR !
// only a startdate, no enddate
$to_or [] = " NOT info_enddate > 0 " . ( $start ? " AND $start <= info_startdate " : '' ) .
( $end ? " AND info_startdate < $end " : '' );
// only an enddate, no startdate
$to_or [] = " NOT info_startdate > 0 " . ( $start ? " AND $start < info_enddate " : '' ) .
( $end ? " AND info_enddate <= $end " : '' ); */
// no startdate AND no enddate (2. half of rfc4791#section-9.9) --> use created and due dates instead
$to_or [] = 'NOT info_startdate > 0 AND NOT info_enddate > 0 AND (' .
// we have a completed date
" info_datecompleted > 0 " . ( isset ( $start ) ? " AND ( $start <= info_datecompleted OR $start <= info_created) " : '' ) .
( isset ( $end ) ? " AND (info_datecompleted <= $end OR info_created <= $end ) " : '' ) . ' OR ' .
// we have no completed date, but always a created date
" NOT info_datecompleted > 0 " . ( isset ( $end ) ? " AND info_created < $end " : '' ) .
')' ;
$sql = '(' . implode ( ' OR ' , $to_or ) . ')' ;
if ( $this -> debug > 1 ) error_log ( __FILE__ . __METHOD__ . '(' . array2string ( $attrs ) . " ) time-range= { $filter [ 'attrs' ][ 'start' ] } - { $filter [ 'attrs' ][ 'end' ] } --> $sql " );
return $sql ;
}
2010-03-15 10:55:16 +01:00
2008-05-17 20:03:33 +02:00
/**
* Handle get request for a task / infolog entry
*
* @ param array & $options
* @ param int $id
2011-03-05 11:21:32 +01:00
* @ param int $user = null account_id
2008-05-17 20:03:33 +02:00
* @ return mixed boolean true on success , false on failure or string with http status ( eg . '404 Not Found' )
*/
2011-03-05 11:21:32 +01:00
function get ( & $options , $id , $user = null )
2008-05-17 20:03:33 +02:00
{
if ( ! is_array ( $task = $this -> _common_get_put_delete ( 'GET' , $options , $id )))
{
return $task ;
}
2008-11-03 10:36:20 +01:00
$handler = $this -> _get_handler ();
2011-10-03 14:53:28 +02:00
$options [ 'data' ] = $handler -> exportVTODO ( $task , '2.0' , null ); // no METHOD:PUBLISH for CalDAV
2008-05-17 20:03:33 +02:00
$options [ 'mimetype' ] = 'text/calendar; charset=utf-8' ;
header ( 'Content-Encoding: identity' );
2011-10-05 10:15:24 +02:00
header ( 'ETag: "' . $this -> get_etag ( $task ) . '"' );
2008-05-17 20:03:33 +02:00
return true ;
}
/**
* Handle put request for a task / infolog entry
*
* @ param array & $options
* @ param int $id
* @ param int $user = null account_id of owner , default null
2010-10-26 11:21:52 +02:00
* @ param string $prefix = null user prefix from path ( eg . / ralf from / ralf / addressbook )
2008-05-17 20:03:33 +02:00
* @ return mixed boolean true on success , false on failure or string with http status ( eg . '404 Not Found' )
*/
2010-10-26 11:21:52 +02:00
function put ( & $options , $id , $user = null , $prefix = null )
2008-05-17 20:03:33 +02:00
{
2010-03-07 00:06:43 +01:00
if ( $this -> debug ) error_log ( __METHOD__ . " ( $id , $user ) " . print_r ( $options , true ));
$oldTask = $this -> _common_get_put_delete ( 'PUT' , $options , $id );
if ( ! is_null ( $oldTask ) && ! is_array ( $oldTask ))
2008-05-17 20:03:33 +02:00
{
2010-03-07 00:06:43 +01:00
return $oldTask ;
2008-05-17 20:03:33 +02:00
}
2010-03-07 00:06:43 +01:00
2008-11-03 10:36:20 +01:00
$handler = $this -> _get_handler ();
2010-03-07 00:06:43 +01:00
$vTodo = htmlspecialchars_decode ( $options [ 'content' ]);
if ( is_array ( $oldTask ))
{
$taskId = $oldTask [ 'info_id' ];
$retval = true ;
}
2011-04-11 11:29:39 +02:00
else // new entry
2010-03-07 00:06:43 +01:00
{
2011-04-11 11:29:39 +02:00
$taskId = 0 ;
$retval = '201 Created' ;
2010-03-07 00:06:43 +01:00
}
2012-07-20 15:06:47 +02:00
if ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'groupdav' ][ 'infolog-cat-action' ] &&
$GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'groupdav' ][ 'infolog-cat-action' ] !== 'none' )
{
$callback_data = array ( array ( $this , 'cat_action' ), $oldTask );
}
if ( ! ( $infoId = $handler -> importVTODO ( $vTodo , $taskId , false , $user , null , $id , $callback_data )))
2008-05-17 20:03:33 +02:00
{
2009-10-16 10:36:28 +02:00
if ( $this -> debug ) error_log ( __METHOD__ . " (, $id ) import_vtodo( $options[content] ) returned false " );
2008-07-08 07:34:10 +02:00
return '403 Forbidden' ;
2008-05-17 20:03:33 +02:00
}
2010-03-07 00:06:43 +01:00
2010-03-15 10:55:16 +01:00
if ( $infoId != $taskId )
{
$retval = '201 Created' ;
}
2010-03-07 00:06:43 +01:00
2012-02-04 21:24:01 +01:00
// send evtl. necessary respose headers: Location, etag, ...
2012-03-22 13:15:18 +01:00
// but only for new entries, as X-INFOLOG-STATUS get's not updated on client, if we confirm with an etag
if ( $retval !== true && ( ! $path_attr_is_name ||
// POST with add-member query parameter
$_SERVER [ 'REQUEST_METHOD' ] == 'POST' && isset ( $_GET [ 'add-member' ])))
{
$this -> put_response_headers ( $infoId , $options [ 'path' ], $retval , self :: $path_attr == 'caldav_name' );
}
2011-04-11 11:29:39 +02:00
return $retval ;
2008-05-17 20:03:33 +02:00
}
2013-09-23 12:21:31 +02:00
/**
* Update etag , ctag and sync - token to reflect changed attachments
*
* @ param array | string | int $entry array with entry data from read , or id
*/
public function update_tags ( $entry )
{
if ( ! is_array ( $entry )) $entry = $this -> read ( $entry );
$this -> bo -> write ( $entry , true );
}
2012-07-20 15:06:47 +02:00
/**
* Callback for infolog_ical :: importVTODO to implement infolog - cat - action
*
* @ param array $task
* @ param array $oldTask = null
* @ return array modified task data
*/
public function cat_action ( array $task , $oldTask = null )
{
$action = $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'groupdav' ][ 'infolog-cat-action' ];
//error_log(__METHOD__.'('.array2string($task).', '.array2string($oldTask).") action=$action");
if ( $task [ 'info_cat' ] && ( $new_cat = categories :: id2name ( $task [ 'info_cat' ])) &&
strpos ( $new_cat , '@' ) !== false )
{
$new_user = $GLOBALS [ 'egw' ] -> accounts -> name2id ( $new_cat , 'account_email' );
}
$old_responsible = $task [ 'info_responsible' ];
// no action taken, if cat is not email of user
if ( $new_user )
{
// make sure category is global, as otherwise it will not be transmitted to other users
if ( ! categories :: is_global ( $task [ 'info_cat' ]))
{
$cat_obj = new categories ( categories :: GLOBAL_ACCOUNT , 'infolog' );
$cat = categories :: read ( $task [ 'info_cat' ]);
$cat [ 'owner' ] = categories :: GLOBAL_ACCOUNT ;
$cat [ 'access' ] = 'public' ;
$cat_obj -> edit ( $cat );
}
// if replace, remove user of old category from responsible
if ( $action == 'replace' && $oldTask && $oldTask [ 'info_cat' ] &&
( $old_cat = categories :: id2name ( $oldTask [ 'info_cat' ])) && strpos ( $old_cat , '@' ) !== false &&
( $old_user = $GLOBALS [ 'egw' ] -> accounts -> name2id ( $old_cat , 'account_email' )) &&
( $key = array_search ( $old_user , ( array ) $task [ 'info_responsible' ])) !== false )
{
unset ( $task [ 'info_responsible' ][ $key ]);
}
switch ( $action )
{
case 'set' :
$task [ 'info_responsible' ] = array ();
// fall through
case 'set-user' :
foreach ( $task [ 'info_responsible' ] as $key => $account_id )
{
if ( $GLOBALS [ 'egw' ] -> accounts -> get_type ( $account_id ) == 'u' )
{
unset ( $task [ 'info_responsible' ][ $key ]);
}
}
// fall-through
case 'add' :
case 'replace' :
if ( ! in_array ( $new_user , ( array ) $task [ 'info_responsible' ]))
{
$task [ 'info_responsible' ][] = $new_user ;
}
break ;
}
}
error_log ( __METHOD__ . " () action= $action , new_cat= $new_cat --> new_user= $new_user , old_cat= $old_cat --> old_user= $old_user : responsible: " . array2string ( $old_responsible ) . ' --> ' . array2string ( $task [ 'info_responsible' ]));
return $task ;
}
2008-05-17 20:03:33 +02:00
/**
* Handle delete request for a task / infolog entry
*
* @ param array & $options
* @ param int $id
* @ return mixed boolean true on success , false on failure or string with http status ( eg . '404 Not Found' )
*/
function delete ( & $options , $id )
{
if ( ! is_array ( $task = $this -> _common_get_put_delete ( 'DELETE' , $options , $id )))
{
return $task ;
}
2011-04-11 11:29:39 +02:00
return $this -> bo -> delete ( $task [ 'info_id' ]);
2008-05-17 20:03:33 +02:00
}
/**
* Read an entry
*
2011-10-04 16:18:35 +02:00
* We have to make sure to not return or even consider in read deleted infologs , as the might have
* the same UID and / or caldav_name as not deleted ones and would block access to valid entries
*
2011-04-11 11:29:39 +02:00
* @ param string | id $id
* @ return array | boolean array with entry , false if no read rights , null if $id does not exist
2008-05-17 20:03:33 +02:00
*/
function read ( $id )
{
2011-10-04 16:18:35 +02:00
return $this -> bo -> read ( array ( self :: $path_attr => $id , " info_status!='deleted' " ), false , 'server' );
2008-05-17 20:03:33 +02:00
}
2013-09-23 12:21:31 +02:00
/**
* Get id from entry - array returned by read ()
*
* Reimplemented because id uses key 'info_id'
*
* @ param int | string | array $entry
* @ return int | string
*/
function get_id ( $entry )
{
return is_array ( $entry ) ? $entry [ 'info_id' ] : $entry ;
}
2008-05-17 20:03:33 +02:00
/**
* Check if user has the neccessary rights on a task / infolog entry
*
* @ param int $acl EGW_ACL_READ , EGW_ACL_EDIT or EGW_ACL_DELETE
2011-04-11 11:29:39 +02:00
* @ param array | int $task task - array or id
2008-05-17 20:03:33 +02:00
* @ return boolean null if entry does not exist , false if no access , true if access permitted
*/
function check_access ( $acl , $task )
{
2010-03-15 10:55:16 +01:00
if ( is_null ( $task )) return true ;
2011-04-11 11:29:39 +02:00
2012-01-26 02:49:56 +01:00
$access = $this -> bo -> check_access ( $task , $acl );
if ( ! $access && $acl == EGW_ACL_EDIT && $this -> bo -> is_responsible ( $task ))
{
$access = true ; // access limited to $this->bo->responsible_edit fields (handled in infolog_bo::write())
}
if ( $this -> debug > 1 ) error_log ( __METHOD__ . " ( $acl , " . array2string ( $task ) . ') returning ' . array2string ( $access ));
return $access ;
2008-05-17 20:03:33 +02:00
}
2010-04-13 17:31:59 +02:00
/**
* Query ctag for infolog
*
* @ return string
*/
public function getctag ( $path , $user )
{
2011-09-28 14:35:53 +02:00
return $this -> bo -> getctag ( $this -> get_infolog_filter ( $path , $user ));
2010-04-13 17:31:59 +02:00
}
2008-05-17 20:03:33 +02:00
/**
* Get the etag for an infolog entry
*
2012-09-24 12:26:29 +02:00
* etag currently uses the modifcation time ( info_datemodified ), 1.9 . 002 adds etag column , but it ' s not yet used !
2011-04-11 11:29:39 +02:00
*
* @ param array | int $info array with infolog entry or info_id
* @ return string | boolean string with etag or false
2008-05-17 20:03:33 +02:00
*/
function get_etag ( $info )
{
if ( ! is_array ( $info ))
{
2010-02-04 13:08:03 +01:00
$info = $this -> bo -> read ( $info , true , 'server' );
2008-05-17 20:03:33 +02:00
}
if ( ! is_array ( $info ) || ! isset ( $info [ 'info_id' ]) || ! isset ( $info [ 'info_datemodified' ]))
{
return false ;
}
2011-10-08 13:34:55 +02:00
return $info [ 'info_id' ] . ':' . $info [ 'info_datemodified' ];
2010-03-07 00:06:43 +01:00
}
/**
* Add extra properties for calendar collections
*
* @ param array $props = array () regular props by the groupdav handler
* @ param string $displayname
* @ param string $base_uri = null base url of handler
2011-10-20 15:35:01 +02:00
* @ param int $user = null account_id of owner of collection
2010-03-07 00:06:43 +01:00
* @ return array
*/
2011-10-20 15:35:01 +02:00
public function extra_properties ( array $props = array (), $displayname , $base_uri = null , $user = null )
2010-03-07 00:06:43 +01:00
{
// calendar description
2011-10-20 15:35:01 +02:00
$displayname = translation :: convert ( lang ( 'Tasks of' ), translation :: charset (), 'utf-8' ) . ' ' . $displayname ;
2011-10-20 16:01:16 +02:00
$props [ 'calendar-description' ] = HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'calendar-description' , $displayname );
2010-03-07 00:06:43 +01:00
// supported components, currently only VEVENT
2011-10-20 16:01:16 +02:00
$props [ 'supported-calendar-component-set' ] = HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'supported-calendar-component-set' , array (
2010-04-13 17:31:59 +02:00
HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'comp' , array ( 'name' => 'VCALENDAR' )),
2010-03-07 00:06:43 +01:00
HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'comp' , array ( 'name' => 'VTODO' )),
));
2011-10-20 15:35:01 +02:00
// supported reports
2012-09-24 12:26:29 +02:00
$props [ 'supported-report-set' ] = array (
'calendar-query' => HTTP_WebDAV_Server :: mkprop ( 'supported-report' , array (
HTTP_WebDAV_Server :: mkprop ( 'report' , array (
HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'calendar-query' , '' ))))),
'calendar-multiget' => HTTP_WebDAV_Server :: mkprop ( 'supported-report' , array (
2011-10-20 16:01:16 +02:00
HTTP_WebDAV_Server :: mkprop ( 'report' , array (
2012-09-26 16:30:47 +02:00
HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'calendar-multiget' , '' ))))),
2012-09-24 12:26:29 +02:00
);
2014-01-07 12:10:51 +01:00
// only advertice rfc 6578 sync-collection report, if "delete-prevention" is switched on (deleted entries get marked deleted but not actualy deleted
$config = config :: read ( 'infolog' );
if ( $config [ 'history' ])
{
$props [ 'supported-report-set' ][ 'sync-collection' ] = HTTP_WebDAV_Server :: mkprop ( 'supported-report' , array (
HTTP_WebDAV_Server :: mkprop ( 'report' , array (
HTTP_WebDAV_Server :: mkprop ( 'sync-collection' , '' )))));
}
2011-10-20 15:35:01 +02:00
// get timezone of calendar
if ( $this -> groupdav -> prop_requested ( 'calendar-timezone' ))
{
$props [ 'calendar-timezone' ] = HTTP_WebDAV_Server :: mkprop ( groupdav :: CALDAV , 'calendar-timezone' ,
2011-10-20 16:01:16 +02:00
calendar_timezones :: user_timezone ( $user ));
2011-10-20 15:35:01 +02:00
}
2010-03-07 00:06:43 +01:00
return $props ;
2008-05-17 20:03:33 +02:00
}
2008-11-03 10:36:20 +01:00
/**
* Get the handler and set the supported fields
*
* @ return infolog_ical
*/
private function _get_handler ()
{
2009-06-08 18:21:14 +02:00
$handler = new infolog_ical ();
2012-03-08 17:23:43 +01:00
$handler -> tzid = false ; // as we read server-time timestamps (!= null=user-time), exports UTC times
2008-11-03 10:36:20 +01:00
$handler -> setSupportedFields ( 'GroupDAV' , $this -> agent );
return $handler ;
}
2012-02-04 02:24:34 +01:00
/**
* Return appliction specific settings
*
2012-02-14 18:38:45 +01:00
* @ param array $hook_data
* @ return array of array with settings
2012-02-04 02:24:34 +01:00
*/
2012-02-14 18:38:45 +01:00
static function get_settings ( $hook_data )
2012-02-04 02:24:34 +01:00
{
2012-02-14 18:38:45 +01:00
if ( ! isset ( $hook_data [ 'setup' ]))
{
translation :: add_app ( 'infolog' );
$infolog = new infolog_bo ();
$types = $infolog -> enums [ 'type' ];
}
if ( ! isset ( $types ))
2012-02-04 02:24:34 +01:00
{
$types = array (
'task' => 'Tasks' ,
);
}
$settings = array ();
$settings [ 'infolog-types' ] = array (
'type' => 'multiselect' ,
'label' => 'InfoLog types to sync' ,
'name' => 'infolog-types' ,
'help' => 'Which InfoLog types should be synced with the device, default only tasks.' ,
'values' => $types ,
'default' => 'task' ,
'xmlrpc' => True ,
'admin' => False ,
);
2012-07-20 15:06:47 +02:00
$settings [ 'infolog-cat-action' ] = array (
'type' => 'select' ,
'label' => 'Action when category is an EMail address' ,
'name' => 'infolog-cat-action' ,
'help' => 'Allows to modify responsible users from devices not supporting them, by setting EMail address of a user as category.' ,
'values' => array (
'none' => lang ( 'none' ),
'add' => lang ( 'add user to responsibles' ),
'replace' => lang ( 'add user to responsibles, removing evtl. previous category user' ),
'set' => lang ( 'set user as only responsible, removing all existing responsibles' ),
'set-user' => lang ( 'set user as only responsible user, but keeping groups' ),
),
'default' => 'none' ,
'xmlrpc' => True ,
'admin' => False ,
);
2012-02-04 02:24:34 +01:00
return $settings ;
}
}