diff --git a/egwical/doc/EgwIcal-BUGS-and-TODOS.txt b/egwical/doc/EgwIcal-BUGS-and-TODOS.txt new file mode 100644 index 0000000000..1cd51ba64d --- /dev/null +++ b/egwical/doc/EgwIcal-BUGS-and-TODOS.txt @@ -0,0 +1,95 @@ +/*! \page pageegwicalbugandtodo BUGS and TODOS + +
+Know BUGS and things todo +--------------------------- +TODO means: should be done/would be nice in a newer release +BUG means: known problem needs to be fixed +FAIl means: known failure to provide (not easily to be done/fixed + +(note: this list is probably not quite up todate) + + +release: VERSION 0.9.0 NEED TO UPDATE THIS!! + +new in V0.7.78 versus v0.7.70: + + bovevents: + ++ whole day events import/export seems to work nice ++ continue on erroneous overwrites (errors only visible in errorlog) ++ no error messages for forbidden overwrite events of non owned events ++ errormessages (when activated) only shows problematic events +++ basic (multiple) VALARM import and export, working! (without action select) ++ allow for vevents with either DTEND or DURATION ++ provided (horde iCalendar) patch to prevent segfaults on some bad iCal inputs + +1) EgwIcal package general (V0.9) +---------------- + + +[ ]1.2 TODO get it nicely into egw cvs +[+/-]1.7 TODO/WISH allow for import of ATTENDEEs not in Egw yet (create new addressbook entry?) + [+](ad hoc solution: add CN and mailto to description) +[+/-]1.4 TODO check and generate source code documentation(phpdoc or doxygen) + +[ ]1.5 TODO test these routines in use for syncml import/export + +1.6 TODO/WISH: nothing done yet for vfreebusy, vjournal, notes, components + only vevents and vtodos are supported. + + +3) egwical.egwical (V0.9.01) +-------------------- + +[+?]3.1 TODO check/implement correct timezone handling (seems ok) +[ ]3.9 TEST with syncml +[ ]3.2 TODO/WISH handle location (GEO), link(?), url, delegation, ()CONTACT? +[ ]3.3 TODO write more documentation (continue mainpage) +[ ]3.4 TODO add accumulting buffer system to egwical + +4) egwical.bocalupdate_vevents (V0.9.02) +------------------------------ + +[+?]4.1 TODO check/implement correct timezone handling (related 3.1) +[ ]4.2a TODO/WISH handle URL, GEO etc. fields. (needs extra fields handling in bocalupdate!) +[ ]4.2b FAIL rrule-COUNT import not implemented in egw? +[+]4.2 BUG!!!! rrule-BYDAY import goes wrong. FIXED (v0.7.76) +[+]4.3 TODO check and improve ORGANIZER,ATTENDEE<-> participants mapping + ATTENDEE s import and export (appears to work oke >= V0.7.71) +[+]4.4 TODO handle import and export of ALARMS +[ ]4.7 TODO (ad hoc) add non egw known attendees to description on import +[ ]4.8 TODO move compatibility code to compat classes. + + +4.9 see 3.2 + + +5) egwical.boinfolog_vtodos (V0.9.02) +---------------------------- +5.1 TODO check/implement correct timezone handling (related 3.1) +5.3 FAIL import/export more than 1 category per task (egw doesnot allow) +5.3 TODO check and improve ORGANIZER,ATTENDEE<-> info_responsible etc mapping +5.4 TODO handle import and export of ALARMS +5.5 TODO rewrite code into supportedFields structure (for next version) +[ ]5.9 TODO rewrite into skeleton structure analog to egwical.bocalupdate_vevents + + + +10.) in used routines from others: + +10.1 BUG Horde_iCalendar(1.2rc6): EXDATE bug + -[+]fixed by patch 'exdate ....' +10.2 BUG Horde_iCalendar(1.2rc6): standard.php Warning + -[+]fixed by patch '...??..' + +10.3 BUG infolog(1.2rc6): datetime is 1 hour wrong for untimed due field in tasks + +11) detected errors/flaws in other programs + +11.1 Korganizer 3.5: recurrence endondate display is interpreted different from egw and +mozilla (and probable also rfc 2445) + + ++*/ \ No newline at end of file diff --git a/egwical/doc/EgwIcal-Timezone-handling.txt b/egwical/doc/EgwIcal-Timezone-handling.txt new file mode 100644 index 0000000000..d75b3c49b3 --- /dev/null +++ b/egwical/doc/EgwIcal-Timezone-handling.txt @@ -0,0 +1,183 @@ +/*! \page pageegwicaltzh EgwIcal Timezone Handling + +Short description on the timezone handling in EgwIcal + +@date 20060216 +@version 0.9.04 +@author JVL +@note Document needs further editing and annotations!! + + + +In EgwIcal the timed data of Egroupware like events and tasks get +converted to elements for an iCalendar. In the Egroupware server we +are dealing with socalled "egw server" time, in applications that a +user of egw runs, the user can set a "locale" and thereby defining a +"local timezone". And finally when events and task get exported to an +iCalendar that is sent to a client or uploaded from it, they may also +have their timesettings defined according to some "timezone". + +How is this all handled in EgwIcal? + + + +@section secegwicalgentzh EgwIcal general Timezone handling + +In general EgwIcal distinguishes between two different types of time +descriptions for an event. An event can either be a: +
+ mon 12 june 2006 at 12:33 GMT+001
Examples are: the
+ opening of the Olympic games.
+
+ DT events are thus absolute (in global world time) anchored in time:
+ everyone watching live TV, anywhere in the world can see it happen
+ at that same moment described by DT.
+
+ Result is that a DT described event will occur at different hours or
+ even days in different timezones. (Some people have to watch the
+ Olympic game openings at night..)
+
+
+ ......
+
+
+@section secusageeicnv Converting Egw elements to iCalendar elements.
+
+To convert a egw component of e.g. "calendar event" into a
+corresponding iCalendar datacomponent of e.g. type "VEVENT"you can use the
+following in your code:
+
+ ......
+
+
+@section secusageimport Importing iCalendar elements into Egw
+applications storage.
+
+To import a iCalendar datacomponentof e.g. type "VEVENT" as a
+corresponding egw component of type "calendar event" into your
+calendar application (its database) you can use the
+following in your code:
+
+ ......
+
+
+@section secusageexport Exporting Egw application elements as iCalendar elements.
+
+To export as a egw datacomponent of e.g. type "calendar event" from your
+calendar application (its database) as a iCalendar datacomponentof e.g. type "VEVENT",
+you can use the following in your code:
+
+ ......
+
+
+@section secusageexpimpbuffer Exporting (mixed) collections of iCalendar elements.
+
+Beside directly exporting single elements or sets of
+iCalendar data generated generated from a egw application like
+calendar, you can also use the "buffer" system of EgwIcal, to collect
+various sets of such dataelements, even from different Egw
+applications (like calendar and infolog), and then later on export
+these all together as one big iCalendar.
+
+To do collection followed by export you can use code like below:
+ ......
+
+
+
+@section secimplementation How is EgwIcal implemented?
+
+To use or extend EgwIcal best, as a developer, it may be good know a bit about
+its implementation.
+EgwIcal is built according to the Workers Union Representatives
+Hierarchy (WURH) pattern. Quite a mouthfull, and probably there are
+somewhere better names for this pattern (see ...). In @ref pageegwicalwurh
+you can read more on this.
+
+Basically it means that there is one super "worker" class
+@ref egwical that uses for specific work like e.g. conversion
+between VEVENTS and calendar tasks, a specific subclass
+like e.g. bocalupdate_vevents that is connected to the
+egwical object via a socalled "representative" member in the registry $reg_rscs
+(attribute).
+
+Currently EgwIcal has the following subclasses:
+
+- bocalupdate_vevents
for manipulating calendar events as
+ VEVENTS.
+
+- boinfolog_vtodos
for manipulating infolog tasks as VTODOS.
+
+Future subclasses might be:
+
+- ???_vjournals
for manipulating .... as VJOURNALS.
+
+- bocal_vfreebusies
for manipulating calendar data as
+ VFREEBUSY elements.
+
+- addressbook_vcard
for manipulating addressdata data as
+ VCARDS.
+
+- ???_vnotes
for manipulating .... as VNOTES.
+
+
+
+@section secdeletioncheatcode Cheatcode for Deleting Calendars, events or todos
+
+Egwical provides a special "hackish" feature specifically meant for
+use with the crippeld ical-over-http (aka webcal) implementation that
+the package IcalSrv provides for Egroupware.
+
+This IcalSrv implementation does not support deletion of egw calendar
+or infolog elements directly (see the IcalSrv package). As a help out
+the "cheat" is implemented that: you can though delete an item by
+simply giving the VEVENT or VTODO 'SUMMARY' field a value
+X-DELETE
or _DELETED_
and then importing it
+through the Egwical system. I you do so the the corresponding egw
+element from calendar or infolog will get deleted.
+
+
+@section secpatches Patches
+
+
+You may need to apply some of the patches found in PATCHES.
+Currently there a two patches to fix a warning and a bug (related to EXDATE
+fields handling) in the horde routines.
+Note these may be already committed in the CVS version.
+
+
+
+@section sectest Extra testing
+
+The package contains two "compatibility" classes (@ref boical and @ref vcalinfolog) with
+which you can replace
+the current infolog/inc/class.vcalinfolog.inc.php
resp.
+calendar/inc/class.boical.inc.php
+files.
+
+When done so, the egw syncml, ical import/export and infolog import/export in egw
+will all use the new code. This way you can discover more bugs and failure in the code :)
+
+But, when not renamed, these programs will use the old code (and thus are not broken or
+improved by the new one..)
+
+
+@section secdocumentation Documentation
+
+There is (somewhere) complete doxygen generated documentation for the
+EgwIcal package. Otherwise you can generate it yourself by using
+doxygen on the source filetree. Maybe phpdocumenter can give you also
+something usefull. And maybe you are just reading it now this very moment...
+
+
+
+@section secbugandtodo BuG and todo list
+
+ @todo notably timezone handling in EgwIcal should be documented and
+possible features to allow import/export and use of iCalendar
+VTIMEZONE components should be provided.
+
+ -see BUGs and TODOs in @ref pageegwicalbugandtodo
+
+
+
+
+have fun
+
+
+JVL
+
+
+*/
\ No newline at end of file
diff --git a/egwical/doc/egwical-doxydoc.tgz b/egwical/doc/egwical-doxydoc.tgz
new file mode 100644
index 0000000000..a3c46fdf8b
Binary files /dev/null and b/egwical/doc/egwical-doxydoc.tgz differ
diff --git a/egwical/doc/egwical-wurh-pattern.txt b/egwical/doc/egwical-wurh-pattern.txt
new file mode 100644
index 0000000000..b33329a01c
--- /dev/null
+++ b/egwical/doc/egwical-wurh-pattern.txt
@@ -0,0 +1,205 @@
+/*!
+\page pageegwicalwurh EgwIcal Workers Union Representatives Hierarchy Pattern
+implementation.
+
+@author JVL
+@date 20060214
+@version v0.9.02
+
+The current Egw Ical package provides routines for exporting iCalendar
+data like vevents, vtodos, vcards etc. from the various corresponding
+Egroupware packages (the "backends"). And vice versa importing these to the datastores
+of these packages. On the other hand there can also be a variety of
+interface components that transport e.g these exported vevents to
+further applications (the "clients"). Think e.g on the "icalsrv"
+service to transport whole sets of vevents in one go over http to a
+client, or the "syncml" service that transports them to syncml
+speaking device, or simple routine that let you save or upload a
+calendar file from within the webgui.
+
+An iCalendar data object can hold a set of these various kinds of
+data-elements as parts of itself. And these data-elements itself can
+also be composed of sub-elements (like e.g. VALARM and VTIMEZONE
+components).
+
+
+So in this situation we have Wholes (like an iCalendar object) with
+Parts (like groups of Vevents and groups of Vtodos) on the one hand.
+And on the other hand we have dedicated subappliations that may handle
+these groups of Part data. To implement this we can make use of a
+smart combination of two implementation primitives "compounds" and
+"classes and subclasses".
+
+In the WURH pattern the combination of these two is very rather
+outspoken and quite entangled and even a bit tricky: each specific
+type of Part is "represented" by specific subclass of the class of
+Whole compound. The data in the parts can now be manipulated in two
+ways:
+
+1) all together as whole, via methods of the compound, or
+2) all in the group of a part, by the methods of the (associated)
+subclass its representative.
+
+@note I forgot the official (GOF) name of the pattern (if there is
+any..)
+
+
+
+\section secwurhexample Example WURH pattern implementation: Meal preparation
+
+As simple example should explain this: Meal preparation URH pattern
+implementation
+
+think of the preparation of meal, that consist of soup and meat.
+To "cook" the meal, the soup has to be boiled and the meat has to be
+baked.
+
+in OO notation: <>-
or .
. means member of,
+<-
means subclass of
+
+Classes:
++MealCooker <- SoupCooker; MealCooker <- MeatCooker; +MealCooker <>- SoupCooker; MealCooker <>- MeatCooker; ++Methods: +
+MealCooker->cook(); SoupCooker->boil(); MeatCooker->bake(); ++and possibly the cook() method may even, for easy of use in the Soup and +Meat classes by implemented (overridden) by the boil() resp. bake() method. + +Now the code to prepare to have $mymealck prepare a meail with $mysoupck and $mymeatck (without +subclass overrriding): +
+$mymealck = New MealCooker; +$mymeatck = New MeatCooker; +$mysoupck = New SoupCooker; + +$meat_in_mealck = $mymealck->addMeatHandler($mymeatck); +$soup_in mealck = $mymealck->addSoupHandler($mysoupck); + +// these are in class MealCooker implemented as +// $this.m = meatcooker_config($mymeatck); resp. $this.s = soupcooker_config($mysoupck); ++ +Now cooking the meal is done by, cooking the soup in its appropiate +way (namely "boiling()" )via its union representative ($soup_in_mealck). Note that it is +called "union" representative because multiple SoupCookers parts may have been +added already to use. And also cooking the meat in its appropiate way +("baking()") likewise. Thus +
+$mymealck->cook([$apiec_of_meat,$avolum_of_soup]) can be done by calling: + + $meat_in_mealck->bake($apiece_of_meat); + $soup_in_mealck->boil($avolume_of_soup); ++and then you can serve boths parts in one go with: +
+$mymealck->serve(); ++ +If we implemented the specific ways of cooking as overriding subclass +methods for Meal->cook() then in simple cases we dont even need to use +the representatives to call the preparation as we implement to main +cook() method to call all itself on all its parts: +
+$mymealck = New MealCooker; +$mymeatck = New MeatCooker; +$mysoupck = New SoupCooker; + + $mymealck->addMeatHandler($mymeatck); + $mymealck->addSoupHandler($mysoupck); ++ +And
$mymealck->cook([$apiec_of_meat,$avolum_of_soup])
can then done by
+just calling:
+
++ $meat_in_mealck->bake($apiece_of_meat); + $soup_in_mealck->boil($avolume_of_soup); + + // which effects in $mymealck.m->cook($apiece_o_fmeat); + // and $mymealck.s ->cook($avolume_of_soup); + // that effects again in $mymealck.m->bake($apiece_o_fmeat); + // and $mymealck.s->boil($avolume_of_soup); + ++ +So this allows for using of manipulation methods of the whole (in case +of simple generic actions reimplemented in the subclasses of the +parts) together with using more specific methods for specific +parts. (e.g. $soup_in_mealck->set_boil_time(10) etc.) + + + +\section securhinei WURH pattern usage in Egwical. + +The Egwical class manages an complete iCalendar component with +VEVENTS, VTODOS etc. as parts. The handling of these part(unions)s is +done by specific subclasses of Egwical, like e.g. infolog_bovtodo and +calendar_bovevents. +The system can be used in two different modes of operation: + +- 1) for conversion of egw tasks, events, etc. to their iCal + counterparts. (the cnv_ methods). + +- 2) for filling the (internal) egwical object with al lot of vtodos and + vevents, either coming from import or from conversion of egw + counterparts. When the compound is filled it can (as a whole) be + exported or imported. + + +add 1) So e.g. exporting a group of events refered to by $event_ids as +VEVENTS goes as follows: +
+$cal = New Bocal; // build calendar +$event_ids = $cal->search("filt_def'); // get group of egw event ids to export. + +$ei = New Egwical; // build Compound iCalendar processor +$bve = $ei->addRsc($cal); // add calendar part and get representive +$vcalstr = $bve->cnv2VEVENTS($event_ids); // export some events + ++ +if we also want to export some VTODOs this goes as follows: + +
+$binf = New Infolog // build infolog app. object +$task_ids = $binf->search("filt_def'); // get group of egw task ids to export. + +$bvt = $ei->addRsc($binf); // add infolog part and get representive +$vcalstr = $bvt->cnv2VTODOS($task_ids); + ++add 2) the same egw elements from 1) are now first collected in +egwical and then as a whole exported. + +
+$cal = New Bocal; // build calendar +$event_ids = $cal->search("filt_def'); // get group of egw event ids to export. + +$ei = New Egwical; // build Compound iCalendar processor +$bve = $ei->addRsc($cal); // add calendar sys part and get representive + $bve->clear(); // empty the list of Vevents in bve + $bve->addEventsOntoVEVENTS($event_ids); // add the converted events to vevents in $ei + ++if we also want to add some VTODOs this goes as follows: +
+$binf = New Infolog // build infolog app. object +$task_ids = $binf->search("filt_def'); // get group of egw task ids to export. + + $bvt = $ei->addRsc($binf); // add infolog sys part and get representive + $bvt->clear(); // empty the list of Vtodos in bvt + $bvt>addTasksOntoVTODOS($task_ids); // add the converted tasks to vtodos in $ei + ++and finally export the whole iCalendar +
+$ei->export() ++ + ---------- + +*/ \ No newline at end of file diff --git a/egwical/inc/class.bocalupdate_vevents.inc.php b/egwical/inc/class.bocalupdate_vevents.inc.php new file mode 100644 index 0000000000..f263e65ee5 --- /dev/null +++ b/egwical/inc/class.bocalupdate_vevents.inc.php @@ -0,0 +1,1075 @@ + (This version. new api rewrite, + * refactoring, and extension). + * @author Lars Kneschke
isset($this->supportedFields['ORGANIZER'])
.
+ * To detect if a certain egw field (eg status
) is supported in the current
+ * data import/export do a
+ * in_array(array_flatten(array_values($this->supportedFields)),'status')
+ * or something like that (not tested, implemented, or needed yet..) Maybe should
+ * implement a method for this..
+ * @note This table should probably better be in class @ref egwical
+ */
+ var $supportedFields;
+
+
+
+
+ /**
+ * @var boolean
+ * Switch that determines if uid matching is tried.
+ *
+ * For a more on uidmatching @see \secimpumatch
+ *
+ * If $uid_matching is true then:
+ * - on import of a vevent the update routines will first try to
+ * find an existing egw event with the same uid value as present
+ * in the UID field of the newly to be imported vevent. If this
+ * succeeds this egw event will get updated with the info from
+ * the vevent. If this fails a new event will be generated and
+ * the uid taken from the vevent will be stored in its uid
+ * field.
+ *
+ * if $uid_matching is false then:
+ * - On import the VEVENT UID field will be checked, if it
+ * appears to be a previously exported uid value then the
+ * encoded egw id of the old egw event is retrieved and used for
+ * update. If it doesnot have a uid value with a valid egw id
+ * encoding, then the its is handled as being a new VEVENT to be
+ * imported, and a new egw id will be generated. The old vevent
+ * uid will though be saved for possible later use, (just as
+ * with uid_matching on).
+ */
+ var $uid_matching = false;
+
+ /**
+ * @var boolean
+ * Switch that determines if events not anymore in egw are allowed to be reimported
+ *
+ * Default this is on
+ */
+ var $reimport_missing_events = true;
+
+
+
+ /**
+ * Export Egw events and add them to a Horde_iCalendar.
+ *
+ * The eGW events in $events are exported to iCalendar VEVENTS and then these are added to
+ * the Horde_iCalendar object &$hIcal.
+ * Note that only supported Fields are exported as VEVENTS to the iCalendar.
+ *
+ * @section secexpeuid Egw uid export switch
+ * If $euid_export is set, then for each exported event, the current value of the event uid
+ * as stored in Egw, will be used to produce a value for the vevent its UID field. When off
+ * a new UID value will generated with the egw event id encoded.
+ *
+ * @param Horde_iCalendar &$hIcal
+ * object to wich the produced VEvents are added.
+ * @param array $events the array with eGW events (or event id's) that will be exported
+ * @param boolean $euid_export switch to enable export of the egw uid fields, when off
+ * default) the vevents uid fields get a value generated with the egw id encoded.
+ * @return boolean|int $ok/$vcnt on error: false / on success: nof vevents exported
+ * @ref $supportedFields determines which fields of VEVENT will be exported
+ */
+ function exportEventsOntoIcal(&$hIcal, $events, $euid_export=false,
+ $reimport_missing_events=false)
+ {
+ $vexpcnt =0; // number of vevents exported
+
+ $veExportFields =& $this->supportedFields;
+
+ if (!is_array($events)) $events = array($events);
+
+ foreach($events as $event) {
+ // event was passed as an event id
+ if (!is_array($event)){
+ $eid = $event;
+ if( !$event = $this->mycal->read($eid,null,false,'server')){
+ // server = timestamp in server-time(!)
+ return false; // no permission to read $cal_id
+ }
+ // event was passed as an array of fields
+ } else {
+ $eid = $event['id'];
+ // now read it again to get all fields (including our alarms)
+ $event = $this->mycal->read($eid);
+ }
+
+ // error_log('>>>>>>>>>>>' .'event to export=' . print_r($event,true));
+
+ // now create a UID value
+ if ($euid_export) {
+ // put egw uid into VEVENT, to allow client to sync with his uids
+ $eventGUID = $event['uid'];
+ } else {
+ $eventGUID = $this->mki_v_guid($eid,'calendar');
+ }
+
+ $vevent = Horde_iCalendar::newComponent('VEVENT',$hIcal);
+ $parameters = $attributes = array();
+ // to important to let supportedFields decide on this
+ $attributes['UID'] = $eventGUID;
+
+ foreach($veExportFields as $veFieldName) {
+
+ switch($veFieldName) {
+ case 'UID':
+ // already set
+ break;
+
+ case 'ATTENDEE':
+ foreach((array)$event['participants'] as $pid => $partstat) {
+ if (!is_numeric($pid)) continue;
+
+ list($propval,$propparams) =
+ $this->mki_vp_4ATTENDEE($pid,$partstat,$event['owner']);
+ // NOTE: we need to add it already: multiple ATTENDEE fields may be occur
+ $this->addAttributeOntoVevent($vevent,'ATTENDEE',$propval,$propparams);
+ }
+ break;
+
+ case 'CLASS':
+ $attributes['CLASS'] = $event['public'] ? 'PUBLIC' : 'PRIVATE';
+ break;
+
+ // according to rfc, ORGANIZER not used for events in the own calendar
+ case 'ORGANIZER':
+ if (!isset($event['participants'][$event['owner']])
+ || count($event['participants']) > 1) {
+ $attributes['ORGANIZER'] = $this->mki_v_CAL_ADDRESS($event['owner']);
+ $parameters['ORGANIZER'] = $this->mki_p_CN($event['owner']);
+ }
+ break;
+
+ // Note; wholeday detection may change the DTEND value later!
+ case 'DTEND':
+ // if(date('H:i:s',$event['end']) == '23:59:59')
+ // $event['end']++;
+ $attributes[$veFieldName] = $event['end'];
+ break;
+
+ case 'RRULE':
+ if ($event['recur_type'] == MCAL_RECUR_NONE)
+ break; // no recuring event
+ $attributes['RRULE'] = $this->mki_v_RECUR($event['recur_type'],
+ $event['recur_data'],
+ $event['recur_interval'],
+ $event['start'],
+ $event['recur_enddate']);
+ break;
+
+ case 'EXDATE':
+ if ($event['recur_exception']) {
+ list( $attributes['EXDATE'], $parameters['EXDATE'])=
+ $this->mki_vp_4EXDATE($event['recur_exception'],false);
+ }
+ break;
+
+ case 'PRIORITY':
+ if (is_numeric($eprio = $event['priority']) && ($eprio >0) )
+ $attributes['PRIORITY'] = $this->mki_v_prio($eprio);
+ break;
+
+ case 'TRANSP':
+ $attributes['TRANSP'] = $event['non_blocking'] ? 'TRANSPARENT' : 'OPAQUE';
+ break;
+
+ case 'CATEGORIES':
+ if ($catids = $event['category']){
+ $catnamescstr = $this->cats_ids2idnamescstr(explode(',',$catids));
+ $attributes['CATEGORIES'] = $catnamescstr;
+ }
+ break;
+
+ // @todo find out about AALARM, DALARM, Is this in the RFC !?
+ case 'AALARM':
+ foreach($event['alarm'] as $alarmID => $alarmData) {
+ $attributes['AALARM'] = $hIcal->_exportDateTime($alarmData['time']);
+ // lets take only the first alarm
+ break;
+ }
+ break;
+
+ case 'DALARM':
+ foreach($event['alarm'] as $alarmID => $alarmData) {
+ $attributes['DALARM'] = $hIcal->_exportDateTime($alarmData['time']);
+ // lets take only the first alarm
+ break;
+ }
+ break;
+
+ case 'VALARM':
+ foreach($event['alarm'] as $alarmID => $alarmData) {
+ $this->mki_c_VALARM($alarmData, $vevent,
+ $event['start'], $veExportFields);
+ }
+ break;
+
+ case 'STATUS': // note: custom field in event
+ if (! $evstat = strtoupper($event['status']))
+ $evstat = 'CONFIRMED'; //default..
+ $attributes['STATUS'] = $evstat;
+ break;
+
+ default:
+ // only use default for level1 VEVENT fields
+ if(strpos($veFieldName, '/') !== false)
+ break;
+ // use first related field only for the simple conversion
+ $efield = $this->ical2egwFields[$veFieldName][0];
+ if ($event[$efield]) { // dont write empty fields
+ $attributes[$veFieldName] = $event[$efield];
+ }
+ break;
+ }
+
+ } //end foreach
+
+ // wholeday detector (DTEND =23:59:59 && DTSTART = 00:00)
+ // if detected the times will be exported in VALUE=DATE format
+ if(((date('H:i:s',$event['end']) == '23:59:59') ||
+ (date('H:i:s',$event['end']) == '00:00:00'))
+ && (date('H:i',$event['start'] == '00:00'))){
+ $attributes['DTSTART'] =
+ $this->hi->_parseDate(date('Ymd',$event['start']));
+ $attributes['DTEND'] =
+ $this->hi->_parseDate(date('Ymd',$event['end']+1));
+ $parameters['DTEND']['VALUE'] = 'DATE';
+ $parameters['DTSTART']['VALUE'] = 'DATE';
+ // error_log('WHOLE DAY DETECTED');
+ }
+
+ // handle created and modified field setting
+ $created = $this->get_TSdbAdd($event['id'],'calendar');
+ if (!$created && !$modified)
+ $created = $event['modified'];
+ if ($created)
+ $attributes['CREATED'] = $created;
+ if (!$modified)
+ $modified = $event['modified'];
+ if ($modified)
+ $attributes['LAST-MODIFIED'] = $modified;
+
+ // add all collected attributes (not yet added) to the vevent
+ foreach($attributes as $aname => $avalue) {
+ $this->addAttributeOntoVevent($vevent,
+ $aname,
+ $avalue,
+ $parameters[$aname]);
+ }
+ $hIcal->addComponent($vevent);
+ $vexpcnt += 1;
+ }
+
+ return $vexpcnt; //return nof vevents exported
+ }
+
+
+
+
+ /**
+ * Import all VEVENTS from a Horde_iCalendar into Egw
+ *
+ * The ical VEVENTS components that are contained in de $hIcal Horde_iCalendar
+ * are converted to eGW events and imported into the eGW calendar.
+ * Depending on the value of $importMode, the conversion will generate either eGW
+ * events with completely new id s (DUPLICATE mode) or generate ids created after
+ * the VEVENT;UID field so that VEVENTS that refer to already existing eGW events
+ * will be used to update these (OVERWRITE mode).
+ *
+ * @section secimpumatch Uidmatching
+ * When $uid_matching is not set, the default situation, the uid field of each vevent
+ * to be imported will examined to check if it has a valid egw id encoded. If so the import
+ * will try to update the egw event indicated by this id with the contents of the vevent.
+ * When this doesnot succeed an appropiate error or skip (if you had not enough write rights)
+ * will be the result. If there can be no valid egw id be decoded, the vevent will be considered
+ * as a new one and an hence a new egw id will automatically be produced.
+ *
+ * When $uid_matching is enabled, the value of the uid field of the vevent will matched against
+ * all the uid fields of existing egw events. If a matching egw event with id is found,
+ * the import
+ * routine will try to update this event. If no success an appropiate error will be generated.
+ * If no match is found, the import proceeds, just as without uidmatching, by generating a
+ * new egw event with a new id. The events uid field will be filled with the vevents uid,
+ * for possible later re-use.
+ *
+ * @note Mostly it is best to disable uidmatching. It prevents that multiple duplicates
+ * of a event will be created in Egw, that may not be accessible anymore via the Ical-Service
+ * interface. Only use it when you really need to reimport an already once imported calendar
+ * because you accidentally deleted parts of it in Egw. Better still would be copy these lost
+ * events into a downloaded version of your original calendar and then update this one without
+ * the uid_matching enabled. (It has namely no effect for new events and the old (i.e.
+ * already downloaded to the client) events will be recognized without uidmatching.
+ *
+ * @param Horde_iCalendar &$hIcal object with ical VEVENT objects
+ * @param string $importMode toggle for duplicate (ICAL_IMODE_DUPLICATE)
+ * or overwrite (ICAL_IMODE_OVERWRITE) import mode
+ * @param int $cal_id strange parameter, at least for -1 create new events
+ * and if 0 then always add user to participants
+ * JVL: THIS NEEDS TO BE CLARIFIED!
+ * @param boolean $reimport_missing_events enable the import of previously exported events
+ * that are now gone in egw (probably deleted by someone else) Default false.
+ * @return boolean| int $false|$evcnt on error: false | on success: nof imported elms
+ * @ref $supportedFields determins the VEVENTS that will be used for import
+ */
+ function importVEventsFromIcal(&$hIcal, $importMode='OVERWRITE', $cal_id=0,
+ $reimport_missing_events=false)
+ {
+ $overwritemode = stristr($importMode,'overwrite') ? true : false;
+ $evokcnt = 0; // nof events imported ok
+ $everrcnt = 0; // nof events imported erroneous
+ $evskipcnt = 0; // nof events imported skipped (user !== owner)
+ $evdelcnt = 0; // nof events deleted ok
+ $evmisskipcnt =0; // nof missing event updates skipped
+
+ $veImportFields =& $this->supportedFields;
+
+// error_log('veImportFields::'. print_r($veImportFields,true));
+
+ $eidOk = false; // returning false, if file contains no components
+ $user_id = $GLOBALS['egw_info']['user']['account_id'];
+
+ foreach($hIcal->getComponents() as $vevent) {
+ // HANDLE ONLY VEVENTS HERE
+ if(!is_a($vevent, 'Horde_iCalendar_vevent'))
+ continue;
+
+// $event = array('participants' => array());
+ $event = array('title' => 'Untitled');
+ $alarms = array();
+ unset($owner_id);
+ $evduration = false;
+ $nonegw_participants = array();
+
+ // handle UID field always first according to uid_matching algorithm
+ $cur_eid = false; // current egw event id
+ $cur_owner_id = false; // current egw event owner id
+ $cur_event = false; // and the whole array of possibly correspond egw event
+ // import action description (just for fun and debug) :
+ // NEW|NEW-NONUID|NEW-FOR-MISSING
+ // DEL-MISSING|DEL-READ|DEL-READ-UID|
+ // UPD-MISSING|UPD-READ|UPD-READ-UID
+ $imp_action = 'NEW-NONUID';
+
+ if($uidval = $vevent->getAttribute('UID')){
+ // ad hoc hack: egw hates slashes in a uid so we replace these anyhow with -
+ $vuid = strtr($uidval,'/','-');
+ $event['uid'] = $vuid;
+
+ if(!$this->uid_matching){
+
+ // UID_MATCHING DISABLED, try to decode cur_eid from uid
+ if ($cur_eid = $this->mke_guid2id($vuid,'calendar')){
+ // yes a request to import a previously exported event!
+ if ($cur_event = $this->mycal->read($cur_eid)){
+ // oke we can read the old event
+ $cur_owner_id = $cur_event['owner'];
+ $imp_action = 'UPD-READ';
+ $event['id'] = $cur_eid;
+ } elseif($reimport_missing_events){
+ // else: a pity couldnot read the corresponding cur_event,
+ // maybe it was deleted in egw already..
+ $imp_action = 'UPD-MISSING';
+ unset($event['id']); // import as a new one
+ } else{
+ // go on with next vevent
+ $evmisskipcnt += 1;
+ continue;
+ }
+ }else{
+ // no decodable egw id there, so per definition no corresponding egw event
+ // so will just import the vevent as a new event
+ $imp_action = 'NEW';
+ }
+
+ //UID_MATCHING ENABLED
+ } elseif($overwritemode && $cal_id <= 0 && !empty($vuid)){
+ // go do uidmatching, search for a egw event with the vuid as uid field
+ if ($cur_event = $this->mycal->read($vuid)) {
+ $cur_eid = $uidmatch_event['id'];
+ $cur_owner_id = $uidmatch_event['owner'];
+ $imp_action = 'UPD-READ-UID';
+ $event['id'] = $cur_eid;
+ }else{
+ // uidmatch failed, insert as new
+ $imp_action = 'NEW';
+ }
+ }
+
+ }
+
+ // lets see what other supported veImportFields we can get from the vevent
+ foreach($vevent->_attributes as $attr) {
+ $attrval = $GLOBALS['egw']->translation->convert($attr['value'],'UTF-8');
+
+
+ // SKIP UNSUPPORTED VEVENT FIELDS
+ if(!in_array($attr['name'],$veImportFields))
+ continue;
+
+// error_log('cnv field:' . $attr['name'] . ' val:' . $attrval);
+
+ switch($attr['name']) {
+ // oke again these strange ALARM properties...
+ case 'AALARM':
+ case 'DALARM':
+ if (preg_match('/.*Z$/',$attrval,$matches)) {
+ $alarmTime = $hIcal->_parseDateTime($attrval);
+ $alarms[$alarmTime] = array('time' => $alarmTime);
+ }
+ break;
+
+ case 'CLASS':
+ $event['public'] = (int)(strtolower($attrval) == 'public');
+ break;
+
+ case 'DESCRIPTION':
+ $event['description'] = $attrval;
+ break;
+
+ case 'DTEND':
+ // will be reviewed after all fields are collected
+ $event['end'] = $attrval;
+ break;
+
+ // note: DURATION and DTEND are mutually exclusive
+ case 'DURATION':
+ // duration after eventstart in secs
+ $evduration = $attrval;
+ break;
+
+ case 'DTSTART':
+ // will be reviewed after all fields are collected
+ $event['start'] = $attrval;
+ break;
+
+ case 'LOCATION':
+ $event['location'] = $attrval;
+ break;
+
+ case 'RRULE':
+ // we may need to find a startdate first so delegate to later
+ // by putting it in event['RECUR']
+ $event['RECUR'] = $attrval;
+ break;
+ case 'EXDATE':
+ if (($exdays = $this->mke_EXDATEpv2udays($attr['params'], $attrval))
+ !== false ){
+ foreach ($exdays as $day){
+ $event['recur_exception'][] = $day;
+ }
+ }
+ break;
+
+ case 'SUMMARY':
+ $event['title'] = $attrval;
+ break;
+
+ case 'TRANSP':
+ $event['non_blocking'] = $attrval == 'TRANSPARENT';
+ break;
+ // JVL: rewrite!
+ case 'PRIORITY':
+ $event['priority'] = $this->mke_prio($attrval);
+ break;
+
+ case 'CATEGORIES':
+ $catnames = explode(',',$attrval);
+ $catidcstr = $this->cats_names2idscstr($catnames,$user_id,'calendar');
+ $event['category'] .= (!empty($event['category']))
+ ? ',' . $catidcstr : $catidcstr;
+ break;
+
+ // when we encounter an new valid cal_address but not yet in egw db
+ // should we import it?
+ case 'ATTENDEE':
+ if ($pid = $this->mke_CAL_ADDRESS2pid($attrval)){
+ if( $epartstat = $this->mke_params2partstat($attr['params'])){
+ $event['participants'][$pid] = $epartstat;
+ } elseif ($pid == $event['owner']){
+ $event['participants'][$pid] = 'A';
+ } else {
+ $event['participants'][$pid] = 'U';
+ }
+ // egw unknown participant, add to nonegw_participants list
+ } else {
+ $nonegw_participants[] =
+ $this->mke_ATTENDEE2cneml($attrval,$attr['params']);
+ }
+ break;
+
+ // make organizer into a accepting participant
+ case 'ORGANIZER': // make him
+ if ($pid = $this->mke_CAL_ADDRESS2pid($attrval))
+ $event['participants'][$pid] = 'A';
+ //$event['owner'] = $pid;
+ break;
+
+ case 'CREATED': // will be written direct to the event
+ if ($event['modified']) break;
+ // fall through
+
+ case 'LAST-MODIFIED': // will be written direct to the event
+ $event['modified'] = $attrval;
+ break;
+
+ case 'STATUS': // note: custom field in event
+ $event['status'] = strtoupper($attrval);
+ break;
+
+ default:
+ error_log('VEVENT field:' .$attr['name'] .':'
+ . $attrval . 'HAS NO CONVERSION YET');
+ }
+ } // end of fields loop
+
+ // now all fields are gathered do some checking and combinations
+
+ // we may have a RECUR value set? Then convert to egw recur def
+ if ($recurval = $event['RECUR']){
+//error_log('recurval=' . $recurval . '=');
+ if(!($recur = $this->mke_RECUR2rar($recurval,$event['start'])) == false){
+ foreach($recur as $rf => $rfval){
+ $event[$rf] = $rfval;
+ }
+ }
+ unset($event['RECUR']);
+ }
+
+ // build endtime from duration if dtend was not set
+ if (!isset($event['end']) && ($evduration !== false)){
+ $event['end'] = $this->mke_DDT2utime($event['start']) + $evduration;
+ }
+
+ // a trick for whole day handling or ...??
+ if(date('H:i:s',$event['end']) == '00:00:00')
+ $event['end']--;
+
+ // check vevent for subcomponents (VALARM only at the moment)
+ // maybe some day do it recursively... (would be better..)
+ foreach($vevent->getComponents() as $valarm) {
+ // SKIP anything but a VALARM
+ if(!is_a($valarm, 'Horde_iCalendar_valarm'))
+ continue;
+ $this->upde_c_VALARM2alarms($alarms,$valarm,$user_id,$veImportFields);
+ }
+
+ // AD HOC solution: add nonegw participants to the description
+ // should be controlable by class member switch
+ if (count($nonegw_participants) > 0)
+ $this->upde_nonegwParticipants2description($event['description'],
+ $nonegw_participants);
+
+ // handle fixed id call (for boical compatibility)
+ // @todo test boical compatibility (esp. with $cal_id>0 case)
+ if($cal_id > 0) {
+ $event['id'] = $cal_id;
+ }
+
+ // SORRY THE PARTICPANTS HANDLING OF EGW IS NOT YET CLEAR TO ME (JVL)
+ // so I do the bold solution to add ourself to participants list if we are not on yet
+ if(!isset($event['participants'][$user_id]))
+ $event['participants'][$user_id] = 'A';
+
+ // error_log('<< ok <<<<' . 'event read for import=' . print_r($event,true));
+
+
+ // -- finally we come to the import into egw ---
+
+ if (($event['title'] == 'X-DELETE') || ($event['title'] == '_DELETED_')){
+
+
+ // -------- DELETION --------------------
+ // error_log('delete event=' . print_r($event,true));
+ $imp_action = 'DEL-' . $imp_action;
+ if(! $cur_eid) {
+ $this->_errorlog_evupd('ERROR: ' . $imp_action,
+ $user_id, $event, false);
+ $everrcnt += 1;
+ continue;
+ } else {
+ // event to delete is found readable
+ if($eidOk = $this->mycal->delete($cur_eid)){
+ // DELETE OK
+ $evdelcnt += 1;
+
+ // ASSUME Alarms are deleted by egw on delete of the event...
+ // otherwise we should use this code:
+ // delete the old alarms
+ //foreach($cur_event['alarm'] as $alarmID => $alarmData) {
+ // $this->delete_alarm($alarmID);
+ //}
+ continue;
+ } elseif ($user_id != $cur_owner_id){
+ // DELETE BAD but it wasnt ours anyway so skip it
+ if ($this->evdebug)
+ $this->_errorlog_evupd('SKIPPED: ' . $imp_action . ' (INSUFFICIENT RIGHTS)',
+ $user_id, $event, $cur_event);
+ $evskipcnt += 1;
+ continue;
+ } else {
+ // DELETE BAD and it was ours
+ $this->_errorlog_evupd('ERROR: ' . $imp_action . '(** INTERNAL ERROR ? **)',
+ $user_id, $event, $cur_event);
+ $everrcnt += 1;
+ continue;
+ }
+
+ }
+
+ // -------- UPDATE --------------------
+ } elseif ($eidOk = $this->mycal->update($event, TRUE)){
+ // UPDATE OKE ,now update alarms
+ $evokcnt += 1; // nof imported ok vevents
+ // handle the found alarms
+ if(in_array('VALARM',$veImportFields)){
+ // delete the old alarms for the event, note: we could also have used $cur_event
+ // but jus to be sure
+ if(!$updatedEvent = $this->mycal->read($eidOk)){
+ error_log('ERROR reading event for Alarm update, will skip update..');
+ continue;
+ }
+
+ // ******** for serious debugging only.. **************
+ // if ($this->evdebug){
+ // $this->_errorlog_evupd('OK: ' . $imp_action,
+ // $user_id, $event, $cur_event);
+ //error_log('event readback dump:' . print_r($updatedEvent,true));
+ // }
+ // ******** eof serious debugging only.. **************
+
+ foreach($updatedEvent['alarm'] as $alarmID => $alarmData) {
+ $this->delete_alarm($alarmID);
+ }
+ // set new alarms
+ foreach($alarms as $alarm) {
+ if(!isset($alarm['offset'])){
+ $alarm['offset'] = $event['start'] - $alarm['time'];
+ } elseif (!isset($alarm['time'])){
+ $alarm['time'] = $event['start'] - $alarm['offset'];
+ }
+ $alarm['owner'] = $user_id;
+// error_log('setting egw alarm as:' . print_r($alarm,true));
+ $this->save_alarm($eidOk, $alarm);
+ }
+ }
+ continue;
+
+ // ---UPDATE BAD --------
+ } elseif ($user_id != $cur_owner_id){
+ // UPDATE BAD, but other ones event, so skip
+ if ($this->evdebug)
+ $this->_errorlog_evupd('SKIPPED: ' . $imp_action . ' (INSUFFICIENT RIGHTS)',
+ $user_id, $event, $cur_event);
+ $evskipcnt += 1;
+ continue;
+ } else {
+ // UPDATE BAD and we own it or it was a new one
+ $this->_errorlog_evupd('ERROR: ' . $imp_action . '(** INTERNAL ERROR ? **)',
+ $user_id, $event, $cur_event);
+ $everrcnt += 1;
+ continue;
+ }
+ error_log('CODING ERROR: SHOULDNOT GET HERE');
+ } // for each
+
+ if (($everrcnt > 0) || $this->evdebug)
+ error_log('** user[' . $user_id . '] vevents imports: ' . $everrcnt . ' BAD,' .
+ $evskipcnt . ' skip-(insufficient rights), ' . $evmisskipcnt .
+ ' skip-(ignore reimport missings), ' .
+ $evokcnt . ' upd-ok, ' . $evdelcnt . ' del-ok');
+ return ($everrcnt > 0) ? false : $evokcnt+ $evdelcnt;
+ }
+
+
+ /**
+ * @private
+ * Log event update problems to http errorlog
+ * @param string $fault description of the fault type
+ * @param ind $user_id the id of the logged in user
+ * @param array $new_event the info converted from the vevent to be imported
+ * @param array|false $cur_event_ids settings of owner, id and uid field of a possibly found
+ * corresponding egw event. When no such event found: false.
+ */
+ function _errorlog_evupd($fault='ERROR', $user_id, &$new_event, $cur_event)
+ {
+ // ex output:
+ // ** bovevents import for user(12 [pietje]): ERROR
+ // current egw event: id=24, owner=34, uid='adaafa'\n
+ // vevent info event: id=24, owner=--, uid='dfafasdf'\n
+
+ $uname =(is_numeric($user_id))
+ ? $user_id . '[' . $GLOBALS['egw']->accounts->id2name($user_id) . ']'
+ : '--';
+ if ($cur_event === false){
+ $cid = $cown = $cuid = '--';
+ }else{
+ $cid = $cur_event['id'];
+ $cown = $cur_event['owner'];
+ $cuid = $cur_event['uid'];
+ }
+ $nid = ($vi = $new_event['id']) ? $vi : '--';
+ $nown = ($vi = $new_event['owner']) ? $vi : '--';
+ $nuid = ($vi = $new_event['uid']) ? $vi : '--';
+
+ error_log('** bovevents import for user (' . $cur_eid .
+ '['. $uname . ']):' . $fault . '\n' .
+ 'current egw event: id=' . $cid . ',owner=' . $cown . ',uid=' . $cuid .'\n' .
+ 'vevent info event: id=' . $nid . ',owner=' . $nown . ',uid=' . $nuid .'\n' );
+// error_log('vevent info event dump:' . print_r($new_event,true) . '\n <<-----------<<\n');
+ }
+
+
+ /**
+ * @private
+ *
+ * Fill member var that holds the iCalendar property to Egw fields mapping.
+ *
+ * Copy keys from this var to the supportedFields member var to allow import/export
+ * of the field refered to by the key.
+ * @todo Maybe someday rethink the ical2egwFields trafo system by rewriting it in paths
+ * starting from iCalendar/Component=>Field or iCalendar/Comp/SubComp etc.
+ * @see $ical2egwFields member var that holds the mapping
+ */
+ function _set_ical2egwFields()
+ {
+ $this->ical2egwFields =
+ array(
+ 'UID' => array('uid'),
+ 'CLASS' => array('public'),
+ 'SUMMARY' => array('title'),
+ 'DESCRIPTION' => array('description'),
+ 'LOCATION' => array('location'),
+ 'DTSTART' => array('start'),
+ 'DTEND' => array('end'),
+ 'DURATION' => array('end-duration'),
+ 'ORGANIZER' => array('owner'),
+ 'ATTENDEE' => array('participants'),
+ 'RRULE' => array('recur_type','recur_interval','recur_data','recur_enddate'),
+ 'EXDATE' => array('recur_exception'),
+ 'PRIORITY' => array('priority'),
+ 'TRANSP' => array('non_blocking'),
+ 'CATEGORIES'=> array('category'),
+ 'URL' => array(''),
+ 'CONTACT' => array(''),
+ 'GEO' => array(''),
+ 'CREATED' => array(''),
+ 'AALARM' => array('alarms'), // NON RFC2445!!
+ 'DALARM' => array('alarms'), // NON RFC2445!!
+ 'VALARM' => array('alarms'),
+ 'VALARM/TRIGGER' => array('alarms/time')
+ );
+ return true;
+ }
+
+
+ /**
+ * Set the list of ical fields that are supported during the next imports and exports.
+ *
+ * The list of iCal fields that should be converted during the following imports and exports
+ * of VEVENTS is set. This is done by providing a productmanufacturer name and
+ * (optionally) a prductname. In a small lookup table the set of currently supported
+ * fields for this is searched and then set thus in the class member @ref $supportedFields.
+ *
+ * @note JVL: I can only see sense in defining supported fields in iCal fields as
+ * these are the fields (terminology) that the devices have in common.
+ * in addressbook this approach is also --correctly-- taken. Why not here?
+ * @param string $_productManufacturer a string indicating the device manufacturer
+ * @param string $_productName a further specification of the current device that is used
+ * for import or export.
+ */
+ function setSupportedFields($_productManufacturer='file', $_productName='')
+ {
+ $defaultFields = array('CLASS','SUMMARY','DESCRIPTION','LOCATION','DTSTART',
+ 'DTEND','RRULE','EXDATE','PRIORITY');
+ // not: 'TRANSP','ATTENDEE','ORGANIZER','CATEGORIES','URL','CONTACT'
+
+ switch(strtolower($_productManufacturer)) {
+ case 'nexthaus corporation':
+ switch(strtolower($_productName)){
+ default:
+ // participants disabled until working correctly
+ // $this->supportedFields = array_merge($defaultFields,array('ATTENDEE'));
+ $this->supportedFields = $defaultFields;
+ break;
+ }
+ break;
+
+ // multisync does not provide anymore information then the manufacturer
+ // we suppose multisync with evolution
+ case 'the multisync project':
+ switch(strtolower($_productName)) {
+ case 'd750i':
+ default:
+ $this->supportedFields = $defaultFields;
+ break;
+ }
+ break;
+ case 'sonyericsson':
+ switch(strtolower($_productName)){
+ default:
+ $this->supportedFields = $defaultFields;
+ break;
+ }
+ break;
+
+ case 'synthesis ag':
+ switch(strtolower($_productName)){
+ default:
+ $this->supportedFields = $defaultFields;
+ break;
+ }
+ break;
+ // used outside of SyncML, eg. by the calendar itself ==> all possible fields
+ case 'file':
+ case 'all':
+ $this->supportedFields =
+ array_merge($defaultFields,
+ array('ATTENDEE','ORGANIZER','TRANSP','CATEGORIES',
+ 'DURATION','VALARM','VALARM/TRIGGER'));
+// error_log('OKE setsupportedFields (all)to:'. print_r($this->supportedFields,true));
+ break;
+
+ // the fallback for SyncML
+ default:
+ error_log("Client not found: $_productManufacturer $_productName");
+ $this->supportedFields = $defaultFields;
+ break;
+ }
+ }
+
+
+ /**
+ *
+ * Exports calendar events as an iCalendar string
+ *
+ * @note -- PART OF calendar.boical API COMPATIBILITY INTERFACE -----------
+ * @param int/array $events (array of) cal_id or array of the events
+ * @param string $method='PUBLISH'
+ * @return string|boolean string with vCal or false on error
+ * (eg. no permission to read the event)
+ *
+ * @see _hiCal class member to hold a temporary Horde_iCalendar object
+ */
+ function &exportVCal($events,$version='1.0',$method='PUBLISH')
+ {
+ $hIcal = &new Horde_iCalendar;
+ $euid_export = false;
+
+ // set some header values of the Horde_iCalendar object
+ $hIcal->setAttribute('PRODID', '-//eGroupWare//NONSGML eGroupWare Calendar '
+ . $GLOBALS['egw_info']['apps']['calendar']['version'].'//'
+ . strtoupper($GLOBALS['egw_info']['user']['preferences']['common']['lang']));
+ $hIcal->setAttribute('VERSION',$version);
+ $hIcal->setAttribute('METHOD',$method);
+
+ // convert the eGW events to VEVENTS and add them to hIcal
+ if(!$this->exportEventsOntoIcal($hIcal, $events,$euid_export))
+ return false;
+
+ // conversion oke, now let Horde stringify it and deliver as result
+ $vcal = $hIcal->exportvCalendar();
+ // JVL: destroy the object by hand or does automagic this in php ?
+ $hIcal = null;
+
+ return $vcal;
+
+ }
+
+
+
+ /**
+ * Convert VEVENT components from an iCalendar string into eGW calendar events
+ * and write these to the eGW calendar as new events or changes of existing events
+ *
+ * @note -- PART OF calendar.boical API COMPATIBILITY INTERFACE -----------
+ * @param string $_vcalData ical data string to be imported
+ * @param int $cal_id id of the eGW event to fill with the VEvent data
+ * when -1 import the VEvent content to new EGW events
+ * (JVL HACK when 0 allow change but no deletion user is added to participants
+ * if needed)
+ * @return boolean $ok false on failure | true on success
+ */
+ function importVCal($_vcalData, $cal_id=-1)
+ {
+
+ $hIcal = &new Horde_iCalendar;
+ // our (patched) horde classes, do NOT unfold folded lines,
+ // which causes a lot trouble in the import, so we do it here
+ $_vcalData = preg_replace("/[\r\n]+ /",'',$_vcalData);
+
+ // let the Horde_iCalendar object parse the Vcal string into its components
+ if(!$hIcal->parsevCalendar($_vcalData)){
+ return FALSE;
+ }
+ $importMode = 'OVERWRITE';
+
+ // now import the found VEVENTS into eGW calendar
+ if(!$this->importVEventsFromIcal($hIcal, $importMode, $cal_id))
+ {
+ //error_log('importVCal(): errors in importVEventsFromIcal');
+ $hIcal = null;
+ return false;
+ }
+
+ $hIcal = null;
+ return true;
+ }
+
+
+ }
+
+
+?>
diff --git a/egwical/inc/class.boical.inc.compat.php b/egwical/inc/class.boical.inc.compat.php
new file mode 100644
index 0000000000..71d1681c3c
--- /dev/null
+++ b/egwical/inc/class.boical.inc.compat.php
@@ -0,0 +1,69 @@
+ei =& CreateObject('egwical.egwical');
+ $this->wkobj =& $this->ei->addRsc($this);
+ // alternatively the fast, shortcut, road for knowingly experts only:
+ //$this->wkobj =& CreateObject('egwical.bocalupdate_vevents');
+ //$this->wkobj->setRsc($this);
+
+ if ($this->wkobj == false){
+ error_log('boical constructor: couldnot add boical resource to egwical: FATAL');
+ return false;
+ }
+ }
+
+
+ // now implement the compatibility methods, that are all moved to egwical!
+
+ function &exportVCal($events,$version='1.0',$method='PUBLISH')
+ {
+ return $this->wkobj->exportVCal($events,$version,$method);
+ }
+
+ function importVCal($_vcalData, $cal_id=-1)
+ {
+ return $this->wkobj->importVCal($_vcalData, $cal_id);
+ }
+
+
+ function setSupportedFields($_productManufacturer='file', $_productName='')
+ {
+ return $this->wkobj->setSupportedFields($_productManufacturer, $_productName);
+ }
+
+ }
+?>
diff --git a/egwical/inc/class.boinfolog_vtodos.inc.php b/egwical/inc/class.boinfolog_vtodos.inc.php
new file mode 100644
index 0000000000..d3ad5a9a98
--- /dev/null
+++ b/egwical/inc/class.boinfolog_vtodos.inc.php
@@ -0,0 +1,561 @@
+ *
+ * -------------------------------------------- *
+ * 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. *
+ **************************************************************************/
+
+ /* JVL Todo V0.7:
+ * - add structure and API terminology from class.bovevents.inc.php:DONE
+ * - rewrite all vevent to vtodo strings: DONE..
+ * - Maybe add a supportFields system as done in calendar.bovevents, to allow for
+ * handling vtodos for various devices
+ * - if done document the supportFields method and show how it can be used
+ * - find out how to do deletion based on imported VTODOS ? Can that be done?
+ * - check the usage and conversions of user time and server times
+ * - add compatibility API for the class infolog.vcalinfolog: DONE but UNTESTED
+ * - add ORGANIZER export: DONE (V0.51) removed (dont know map field)
+ * - add ORGANIZER import: ... maybe map t info_responsible
+ * - add CATEGORIES export: DONE (V0.52)
+ * - add CATEGORIE import: DONE (V0.7.01)
+ * - add "subtask" export: DONE (v0.52)
+ * - add "subtask" import: PARTLY
+ * - rewrite PRIORITY export: DONE (V0.52)
+ * - rewrite PRIORITY import:DONE (V0.7.01)
+ * - repair datecreated: DONT know map field
+ * - repair date modified export: PARTLY done
+ * - repair startdate or enddate without time details: DONE (V0.7.02)
+ */
+
+
+ // require_once EGW_SERVER_ROOT.'/infolog/inc/class.boinfolog.inc.php';
+ require_once EGW_SERVER_ROOT.'/phpgwapi/inc/horde/Horde/iCalendar.php';
+// require_once EGW_SERVER_ROOT.'/icalsrv/inc/class.egwical.inc.php';
+
+ /**
+ *
+ * iCal vtodos import and export via Horde iCalendar classes
+ * @note the routines in this package should be used OO only so that de constructor
+ * can initialize the data common to the import and export routines
+ * @note this package provides compatibilty routines for class infolog.vcalinfolog
+ * this can e.g. be used by making infolog.vcalinfolog a simple extension of
+ * infolog.bovtodos
+ *
+ * @todo move the compatibility functions for vcalinfolog completely to the compat class.
+ * There is no need to have them here anymore.
+ * @todo rewrite bovtodos to use a ical2egw and supportedFields system
+ * @todo IMPORTANT rewrite bovtodos to handle uid_matching analogous to bovevents
+ *
+ * @package egwical
+ * @author Jan van Lieshout mki_
+ * This is for methods that MaKe a Ical thing like a component, field, fieldvalue or
+ * fieldparameter. Thus these are subdived in:mki_c
to make ical Components like VEVENTS or VALARMS mki_v
to make ical field Values like e.g. a ATTENDEE field valuemki_vp
to make both ical field Values and Parameters mke_
+ * This is for methods that MaKe a Egw things like a field of egw event or task.updi_
+ * This is for methods that UPDate an Ical component or field. Note that the Ical component
+ * to be updated will be passed by reference to these routinesupde_
+ * This is for methods that UPDate an Egw entity like an event or task or.... Note again
+ * that the Egw entity will be passed by reference to these routines.bocalupdate_vevents
to convert between egw calendar events and VEVENTS
+ * and allow import and export of these. When added as a resource to an Egwical object,
+ * the representative member for this subclass is set in the attribute $this->buverpr
+ *
+ * - boinfolog_vtodos
to convert between egw infolog tasks events and VTODOS
+ * and allow import and export of these. When added as a resource to an Egwical object,
+ * the representative member for this subclass is set in the attribute $this->ivtrpr
+ *
+ * @note At this moment (V 0.9.01) there is still a little unpleasant piece of extra
+ * coding needed to get
+ * it working: when you develop a new workerssubclass you must enter it manually in
+ * the egwical constructor,
+ * so that it can later on automatically be found and matched to the appropiat egw resource.
+ * (I couldnot find an easy way to search for existing subclasses so therefore it is needed)
+ *
+ * @note in future this package may become a full wrapper around the Horde_iCalendar
+ * libraries. This way we can keep the relevant egw and ical objects alive between
+ * calls so that code could be made faster and less memory consuming..
+ *
+ * @since 0.9.0 api is incompatible with lower versions
+ * @author Jan van Lieshout + * $reg_rscworkers = array($rsclass_name => + * array('workerclass' =>$workerclass_name, + * 'workerobj' => $worker_obj, + * 'vcsup' =>array($velmc1,$velmc2,..)), + * ..) + *+ * example of an entry: + *
+ * $reg_rscworkers['boinfolog'] = array('workerclass' =>'boinfolog_vtodos', + * 'workerobj' => null, + * 'icalsup' =>array('VTODO')) + *+ */ + var $reg_rscworkers = array(); + + + /** Registry for Resources + * + * @var array + * + * The resources registry is an array that holds an entry for + * types of icalendar component class ('VEVENT, VTODO etc.) + * with: + *- 1) an appropiate egw resource object for storing and + * retrieving the info for these components (thus a calendar- or + * infolog- or .. etc. object) and + *- 2) an (instantiated) worker object to handle transport from + * and to the resource. + * The entries of this registry get filled when the + * resources are added to the egwical object by calls to the + * addRsc() method. + * @note multiple entries for the same icalendar component are allowed. + * This can occur when multiple resources can provide e.g. VEVENT data. + * + * Structure of the registry: + *
+ * reg_rscs = array($icalcompclass_name => array($rsc_obj, $worker_obj),..) + *+ * example of an entry: + *
+ * reg_rscs['VTODO'] = array(null, null) + *+ */ + var $reg_rscs = array(); + + + + /** + * Constructor, init the auxiliary object @ref $hi and @ref $TASKMAGIC + * and instantiate the @ref $reg_rscworkers workers registry and the + * @ref $reg_rscs resources + * registry. + * @note At this moment (V 0.9.01): when you develop a new workerssubclass you must + * manually add its characteristics via a class call to its provides_work() function + * from within the constructor code. + * (It is needed because I couldnot find an easy way to search for existing subclasses + * yet) + * + */ + function egwical() + { + // actually this would only be needed by the abstract superclass? + $this->hi = &new Horde_iCalendar; + + $this->TASKMAGIC = $GLOBALS['egw_info']['server']['install_id'] + ? $GLOBALS['egw_info']['server']['install_id'] + : 'local'; + // update following list when a new worker subclasses becomes available + // (i.e. copy the info from its $iprovide_work var) + $this->reg_rscworkers = + array_merge( + $this->reg_rscworkers, + bocalupdate_vevents::provides_work(), + boinfolog_vtodos::provides_work() + ); + // error_log('**now reg_workers=' . print_r($this->reg_rscworkers,true)); + $this->reg_rscs = array(); + } + + + /** + * Add a egw resource. + * + * The egw resource (calendar, infolog, ..) in $egw_rsc is added + * to egwical. This will be used for storing and retrieving + * types of iCalendar components (VEVENT, VTODO,..) for which it has corresponding + * egw data elements (like egw events or egw tasks. + * + * Following the WURH pattern, adding the egw resource $rsc will result in + * the creation of a dedicated representative "worker" subclass object to handle the + * transport to and from the resource. The appropiate worker class is looked up in the + * $reg_workers registry. When the worker is created, the resource and its worker will + * be entered as a resource worker pair in the egwical $reg_rscs registry. + * This representative worker object will also be + * returned by reference to the caller. Using this reference the caller can later on do + * specific settings for the worker, (like setSupportedFields()). + * + * @param object $egw_rsc egw resource (like calendar or infolog,..) object that will be + * used by the egwical representative worker class to transport converted + * ical components to and from. + * @return egwical|false $rpr_worker the representative worker class for handling the + * added egw resource. On error false is returned. + */ + function addRsc($egw_rsc) + { + //step 1: detect the egw_rsc class + if (!$rsc_classname = get_class($egw_rsc)) + // bad value for $egw_rsc + return false; + + //step 1a check that the egwrsc is not already registered + foreach($this->reg_rscs as $vc => $rscwkpair){ + if($rscwkpair[0] === $egw_rcs){ + error_log('Warning: egwical.addRsc(): trying add resource multiple times,' . + ' for class: ' . $rsc_classname . ' : ignored'); + return $rscwkpair[1]; + } + } + + //step 1: detect the egw_rsc class + if (!$rsc_classname = get_class($egw_rsc)) + // bad value for $egw_rsc + return false; + + // step 2a: look in reg_rscworkers for appropiate worker class + if (!$wkprov = $this->reg_rscworkers[$rsc_classname]){ + error_log('Error: egwical.addRsc(): no workerclass available yet for resourcetype:' . + $class_name . ' sorry'); + return false; + } + + // step 2b create a workerobject of the correct type + // note that we dont reuse an already available workerobj of the right type because + // probably the workerclasses are not reentrantly coded yet... + if(! $wkobj =& CreateObject('egwical.' . $wkprov['workerclass'])){ + // workerclass object creation problem + return false; + } + // step 2c give the workerobj the egw_rsc to handle + $wkobj->setRsc($egw_rsc); + + // step3: use the found worker info to register the egw_rsc and workerobj for + // each of the supported type of ical elements + foreach ($wkprov['icalsup'] as $icomptype ){ + //error_log('adding reg_rscs['. $icomptype . '] entry'); + $this->reg_rscs = array_merge($this->reg_rscs, + array($icomptype => array($egw_rsc, $wkobj))); + } + // step 4: return the workerclass (representative obj) + + return $wkobj; + } + + + + // ------------- second: below only generic conversion stuff -------------- + + + // --- generic conversion auxilliary routines ------------- + // --- note: this could be left out in the abstract baseclass instantiation + // but is not that much, so leave it get duplicated... + + /** + * @private + * @var string + * Magic unique number used for de/encoding our uids. + * + * This string that contains global unique magic number that is + * unique for our current database installed etc. It is used to recognize + * earlier exported VTODO or VEVENT UID fields as referring to their eGW counterparts. + */ + var $TASKMAGIC='dummy'; + + + /** + * @var array $status_ical2egw + * Conversion of the egw used priority values(0..3) to corresponding ical values(0..9). + * @private + */ + var $priority_egw2ical = + array( + 0 => 0, // undefined + 1 => 9, // low + 2 => 5, // normal + 3 => 1, // high + ); + /** + * @var array $status_ical2egw conversation of the priority ical => egw + * Conversion of the icalendar used priority values(0..9) to corresponding egw values (0..3). + * @private + */ + var $priority_ical2egw = + array( + 0 => 0, // undefined + 9 => 1, 8 => 1, 7 => 1, // low + 6 => 2, 5 => 2, 4 => 2, // normal + 3 => 3, 2 => 3, 1 => 3, // high + ); + + + /** + * @var array $partstatus_egw2ical + * Conversion of the egw used participant status values to the corresponding icalendar + * attendee status terminology. + * @private + */ + var $partstatus_egw2ical = + array( + 'U' => 'NEEDS-ACTION', + 'A' => 'ACCEPTED', + 'R' => 'DECLINED', + 'T' => 'TENTATIVE', + ); + /** + * @var array + * Conversion of the icalendar used attendee status values to the corresponding icalendar + * participants status terminology. + * @private + */ + var $partstatus_ical2egw = + array( + 'NEEDS-ACTION' => 'U', + 'ACCEPTED' => 'A', + 'DECLINED' => 'R', + 'TENTATIVE' => 'T', + ); + + + /** + * @var array $recur_egw2ical + * Conversion of egw recur-type to ical FREQ values for RRULE fields + * @private + */ + var $recur_egw2ical = + array( + MCAL_RECUR_DAILY => 'DAILY', + MCAL_RECUR_WEEKLY => 'WEEKLY', + MCAL_RECUR_MONTHLY_MDAY => 'MONTHLY', + MCAL_RECUR_MONTHLY_WDAY => 'MONTHLY', + MCAL_RECUR_YEARLY => 'YEARLY', + ); + // BYMONHTDAY={1..31}, BYDAY={1..5}{MO..SO} + + /** + * @var array + * recur_days translates MCAL recur-days to verbose labels + * (copied from class.bocal.inc.php file + * @private + */ + var $recur_days = + array( + MCAL_M_MONDAY => 'Monday', + MCAL_M_TUESDAY => 'Tuesday', + MCAL_M_WEDNESDAY => 'Wednesday', + MCAL_M_THURSDAY => 'Thursday', + MCAL_M_FRIDAY => 'Friday', + MCAL_M_SATURDAY => 'Saturday', + MCAL_M_SUNDAY => 'Sunday', + ); + + /** + * @var array + * Get sequential indexes for the daynames in a week. Used for recurrence count + * calculations. + */ + var $dowseqid = + array('SU' => 1, 'MO' => 2, 'TU' => 3, 'WE' => 4, 'TH' => 5, 'FR' => 6, 'SA' => 7); + + + // --- generic conversion auxilliary routines ------------- + + /** + * Parse a vCalendar string into an Horde_iCalendar object. + * + * To actually parse the string, the Horde_iCalendar in member @ref $hi is used. + * @param string $vcalstr the icalendar input string + * @return boolean|Horde_iCalendar the resulting parsed elements collected in a + * horde ical object. On error: false + */ + function parsevCalendar($vcalstr) + { + // unfoldlines as this was removed from our horde stuff + $vcalstr = preg_replace("/[\r\n]+ /",'',$vcalstr); + + // $this->hi->clear(); + if(!$this->hi->parsevCalendar($vcalstr)){ + error_log('egwical parsevCalendar: ERROR- couldnot parse..'); + return false; + } + + return $this->hi; + } + + + + + /** + * Generate ical UID from egw id. + * + * generate a unique id, with the egw id encoded into it, which can be + * used for later synchronisation. + * @param string|int $egw_id eGW id of the egw entity (event, task,..) + * @param string $app_prefix prefix to use in ecnoding the name + * + * @return string|false on success the global unique id. On error: false. + * + * Uses @ref $TASKMAGIC string that holds our unique ID + */ + function mki_v_guid($egw_id,$app_prefix='egw') + { + if (empty($egw_id)) + return false; + return $app_prefix .'-' . $egw_id. '-' . $this->TASKMAGIC; + } + + + + /** + * Try to decode an egw id from a ical UID + * + * @param string $guid the global Icalendar UID value + * @param string $app_prefix prefix to be found in the encoding + * @return false|int On error: false. + * On success: local egw todo id. + */ + function mke_guid2id($guid,$app_prefix='egw') + { + // error_log('mke_guid2id: trying to recover id from' . $guid); + if (!preg_match('/^' . $app_prefix . '-(\d+)-' . + $this->TASKMAGIC . '$/',$guid,$matches)) + return false; + + // error_log("mke_guid2id: found (" . $matches[1] . ")"); + return $matches[1]; + } + + + + /** + * Get database add date of event or todo + * @private + * @param int $id id of event or todo + * @param string $appname name of the application (='calendar' or 'infolog') + * @return int $createdate of db insert or false on error + */ + function get_TSdbAdd($id,$appname='calendar') + { + if (!(($appname == 'calendar') || ($appname == 'infolog'))) + return false; + if (! $auid = $GLOBALS['egw']->common->generate_uid($appname,$id)) + return false; + + return $GLOBALS['egw']->contenthistory->getTSforAction($auid,'add'); + } + + + /** + * Convert a egw prio into a value for the ical property PRIORITY + * @param int $eprio priority in egw (0..3) + * @return int $iprio conversion of $eprio as value (0..9) for the ical PRIORITY prop + */ + function mki_v_prio($eprio = 0) + { + return $this->priority_egw2ical[$eprio]; + } + + /** + * Convert a ical prio into a value for egw + * @param int $iprio priority in ical (0..9) + * @return int $eprio conversion of $iprio as value (0..3) for egw + */ + function mke_prio($iprio = 0) + { + return $this->priority_ical2egw[$iprio]; + } + + + + /** + * Translate cat-ids to array with id-name pairs + * + * JVLNOTE: boldly copied from class.xmlrpc_server.inc.php because I donot know how + * to instantiate $GLOBALS['server'] (that provides this method) atm. + * @note THIS CODE SHOULD BE SOMEWHERE ELSE: IT HAS NOTHING TO DO WITH ICAL!! + * @param array $cids the list with category ids + * @return string|false $idnamescstr commasep string with names for the category ids or + * on error false + */ + function cats_ids2idnamescstr($cids) + { + if(empty($cids)) + return false; + + if (!is_object($GLOBALS['egw']->categories)) + $GLOBALS['egw']->categories = CreateObject('phpgwapi.categories'); + + $idnames = array(); + foreach($cids as $cid) { + if ($cid) + $idnames[$cid] = stripslashes($GLOBALS['egw']->categories->id2name($cid)); + } + return implode(',',$idnames); + } + + + // ************ JVL CHECK THE CODE BENEATH ***************** + // oke: seems to work for a single categorie (tested form bovtodos calls) + + /** + * Translate catnames back to cat-ids creating/modifying cats on the fly + * + * JVLNOTE boldly copied from class.xmlrpc_server.inc.php because I donot know how + * to instantiate $GLOBALS['server'] (that provides this method) atm. + * + * @note THIS CODE SHOULD BE SOMEWHERE ELSE: IT HAS NOTHING TO DO WITH ICAL!! + * @param array $cnames list with category names + * @return string $cidscstr commasep string with ids generated or found for + * the category names. + */ + function cats_names2idscstr($cnames,$owner_id,$app_name='infolog') + { + if (empty($cnames)) + return false; + + if (!is_object($catsys =& $GLOBALS['egw']->categories)) { + $GLOBALS['egw']->categories =& CreateObject('phpgwapi.categories', + $owner_id,$app_name); + $catsys =& $GLOBALS['egw']->categories; + } + $cids = array(); + foreach($cnames as $name) { + if (!($cid = $catsys->name2id($name))) { + // existing cat-name use the id + // new cat + $cid = $catsys->add(array('name' => $name,'descr' => $name)); + } + $cids[] = (int)$cid; + } + return implode(',',$cids); + } + + + /** + * Convert and egw account id into a iCalendar CAL-ADDRESS type value string + * @param int $aid egw account(person) id + * @return string $cls cal_address format string (mailto:
year
four digit year field
+ * - month
integer month number
+ * - mday
integer day of month number
+ * - hour
integer hour
+ * - minute
integer minutes
+ * - second
integer seconds
+ *
+ * @param int $utime a unixtimestamp assumed in utc timezone
+ * @return array The date in a6date in local timezone format.
+ */
+ function utimetoa6($utime)
+ {
+ $t=getdate($utime);
+ return array('hour' => $t['hours'], 'minute' => $t['minutes'],
+ 'second' => $t['seconds'],'month' => $t['mon'],
+ 'mday' => $t['mday'],'year' => $t['year']);
+ }
+
+
+ /**
+ * Convert a 6 field hash array in the current active timezone to a unix timestamp.
+ *
+ * This is basically the inverseof php getdate() function.
+ *
+ * The a6date array has fields as in the php getdate() function:
+ * - year
four digit year field
+ * - month
integer month number note: mon, not month!!
+ * - mday
integer day of month number
+ * - hour
integer hour
+ * - minute
integer minutes
+ * - second
integer seconds
+ *
+ * @param array $a6 The date in a6date in local timezone format.
+ * @return int a unixtimestamp assumed in utc timezone
+ */
+ function a6toutime ($a6)
+ {
+ return mktime($a6['hour'],$a6['minute'],$a6['second'],
+ $a6['month'],$a6['mday'],$a6['year']);
+ }
+
+
+ /**
+ * Convert the egw person id and its participant status into
+ * an ATTENDEE value and parameterslist
+ *
+ * The resulting value of the ATTENDEE field will be in CAL_ADDRESS type format.
+ * The resulting parameterlist may contain fields of the following:
+ * - ROLE={CHAIR|REQ-PARTICIPANT|OPT-PARTICIPANT|NON-PARTICIPANT}
+ * this parameter is NOT used by eGW atm.
+ * - RSVP={TRUE|FALSE}
+ * resonse is expected, not set in eGW then status will have value U
.
+ * - PARTSTAT={NEEDS-ACTION|ACCEPTED|DECLINED|TENTATIVE|DELEGATED|
+ * COMPLETED|IN-PROGRESS}
everything from delegated is NOT used by eGW atm.
+ * - CUTYPE={INDIVIDUAL|GROUP|RESOURCE|ROOM|UNKNOWN}
only GROUP or INDIVIDUAL
+ * are produced atm.
+ *
+ * @param int $pid egw id of a participant
+ * @param array $partstat egw particpant status of person with $uid
+ * @param int $owner_id id of the owner of the todo or event (needed to set the CHAIR)
+ * @return array ($val,$params) list with value and parameter-array for ATTENDEE property
+ * @note no error handling atm
+ */
+ function mki_vp_4ATTENDEE($pid,$partstat,$owner_id)
+ {
+ $atdval = $this->mki_v_CAL_ADDRESS($pid);
+ // first parameter
+ $atdpars = $this->mki_p_CN($pid);
+ $atdpars['ROLE'] = ($pid == $owner_id) ? 'CHAIR' : 'REQ-PARTICIPANT';
+ $atdpars['RSVP'] = $partstat == 'U' ? 'TRUE' : 'FALSE';
+ $atdpars['CUTYPE'] = $GLOBALS['egw']->accounts->get_type($uid) == 'g'
+ ? 'GROUP' : 'INDIVIDUAL';
+ $atdpars['PARTSTAT'] = $this->partstatus_egw2ical[$partstat];
+
+ return array($atdval,$atdpars);
+ }
+
+
+ /**
+ * Make a value of type RECUR for a ical RRULE property
+ *
+ * A simple example:
+ * ( RRULE) : (FREQ=MONTHLY;COUNT=10;INTERVAL=2)
+ * here the first part between parenthesis is property and the
+ * second is a value of type RECUR
+ *
+ * @param string $recur_type the type of recurrence frequence we have
+ * @param mixed $recur_data Todo describe this parameter...
+ * @param int $recur_interval Todo describe this parameter...
+ * @param utime $recur_enddate the final date that the recurrence ends
+ * @return string ($recurval) a value format as RECUR for the RRULE property
+ * (if a time is set)
+ */
+ function mki_v_RECUR($recur_type,$recur_data,$recur_interval,$recur_start,$recur_enddate)
+ {
+ $recur = array();
+ $recurval ='FREQ=' . $this->recur_egw2ical[$recur_type];
+
+ switch ($recur_type) {
+ case MCAL_RECUR_WEEKLY:
+ $days = array();
+ foreach($this->recur_days as $did => $day) {
+ if ($recur_data & $did)
+ $days[] = strtoupper(substr($day,0,2));
+ }
+ $recur['BYDAY'] = implode(',',$days);
+ break;
+ case MCAL_RECUR_MONTHLY_MDAY: // date of the month: BYMONTDAY={1..31}
+ $recur['BYMONTHDAY'] = (int) date('d',$recur_start);
+ break;
+ case MCAL_RECUR_MONTHLY_WDAY: // weekday of the month: BDAY={1..5}{MO..SO}
+ $recur['BYDAY'] = (1 + (int) ((date('d',$recur_start)-1) / 7))
+ . strtoupper(substr(date('l',$recur_start),0,2));
+ break;
+ }
+
+ if ($recur_interval > 1)
+ $recur['INTERVAL'] = $recur_interval;
+
+ if ($recur_enddate){
+
+// $expdt= $this->hi->_exportDateTime($recur_enddate);
+// error_log('EXPORT UNTIL=' . $recur_enddate . ' expdDT:' . $expdt);
+
+ $recur['UNTIL'] = $this->hi->_exportDateTime($recur_enddate);
+ }
+ foreach($recur as $parnam => $parval){
+ $recurval .= ';' . $parnam . '=' . $parval;
+ }
+ return $recurval;
+ }
+
+ /**
+ * Make a value (commasep string of dates) for the EXDATE property
+ *
+ * In the conversion you can chose between a commastring of DATES or DATE-TIMES
+ * @param array $recur_exceptions list with utime exception dates
+ * @param boolean $dtmode if true generate DATE-TIME dates else DATES
+ * @return array ($exdval, $exdparams) a list with the value and parameters generated
+ */
+ function mki_vp_4EXDATE($recur_exceptions,$dtmode=false)
+ {
+ $days = array();
+ foreach($recur_exceptions as $day) {
+ $days[] = date('Ymd',$day);
+ }
+
+ $exdparams = array();
+ if(!$dtmode)
+ $exdparams['VALUE'] = 'DATE';
+ return array( implode(',',$days), $exdparams);
+ }
+
+ /**
+ * Convert DDT possible DATE|DATE-TIME params and a value commalist
+ * into an array of utime dates.
+ *
+ * Some examples
+ * + * ex1: ...;VALUE=DATE:20060123,20060124 + * ex2: ...:20060118T101500Z,20060119T1000Z + * ex3: ...:VALUE=DATE-TIME:20060118T101500Z,20060119T1000Z + *+ * @note unfortunately horde_icalendar will parse ex1 into an array of + * array(month => .. , mday => .. , year=> ) + * @param array $dvals list of dates + * @return array $udays list with the days from the input list in utime format + */ + function mke_EXDATEpv2udays($params, $dvals) + { + //$exdays = (!is_array($dvals)) ? $dvals : array($dvals); + $exdays = $dvals; + + if (count($exdays) < 1) + return false; +//error_log('EXDAYS params=' . print_r($params,true)); +//error_log('EXDAYS exploded=' . print_r($exdays,true)); + if($params['VALUE'] == 'DATE'){ + // list is in awful horde DATE mode + // convert the date somehow to udays + $udays = array(); + foreach ($exdays as $day){ + $udays[] = $this->mke_DDT2utime($day); + } + } else { + // assume list is in DT mode + $udays = &$exdays; + } + + return $udays; + } + + + + /** + * Convert a RECUR value into the corresponding egw recur fields. + * + * A value of type RECUR (for a ical RRULE property) is parsed + * into the 4 related egw fields. Fields unfilled stay false A + * simple example:
( RRULE) :
+ * (FREQ=MONTHLY;COUNT=10;INTERVAL=2)
here the first
+ * part between parenthesis is property and the second is a
+ * value of type RECUR
+ *
+ * @bug RECUR: MONTHLY;BYMONTHDAY, only ok if startdate is also on this MONTHDAY
+ * egw problem.
+ *
+ * @todo RECUR: COUNT=xx;WEEKLY;BYDAY, may miss the last occurence, if not started
+ * on a BYDAY day: to be fixed! prio=low
+ *
+ * @todo RECUR: YEARLY seems only to support the most basic variant?? To be checked!
+ *
+ * @author JVL (required some thinking..)
+ * @param string $recur RECUR type value of RRULE
+ * @param mixed $rstart start date in UTC format
+ * @return array $rar a assoc array with keys: 'recur_type', 'recur_data', 'recur_interval'
+ * and 'recur_enddate'. On error: false
+ * @note the class var @ref $hi is used as auxiliary Horde_iCalendar object
+ */
+ function mke_RECUR2rar($recur,$rstart)
+ {
+ $ustart = $this->mke_DDT2utime($rstart);
+
+ // a6sd is in Icalsrv usertime
+ $a6sd = $this->utimetoa6($ustart);
+
+// error_log('IMPORT RECURVAL=' . $recur . 'ustart=' .$ustart);
+
+
+ $r_data = 0;
+ $dow =array(); // for weekly count calc
+ $r_type = $r_interval = $r_end = $r_count = false;
+
+ $type = preg_match('/FREQ=([^;: ]+)/i',$recur,$matches)
+ ? $matches[1] : $recur[0];
+ if ($type == false)
+ return false;
+
+ // vCard 2.0 values for all types
+ if (preg_match('/UNTIL=([0-9TZ]+)/',$recur,$matches))
+ $r_end = $this->hi->_parseDateTime($matches[1]);
+
+ if (preg_match('/INTERVAL=([0-9]+)/',$recur,$matches))
+ $r_interval = (int) $matches[1];
+
+ // with count given we must calculate r_end
+ if (preg_match('/COUNT=([0-9]+)/',$recur,$matches)){
+ $r_count = (int) $matches[1];
+ // count calculation auxvars
+ $c_interval = ($r_interval) ? $r_interval : 1; //interval
+ $c_count = ($r_count - 1)*$c_interval;
+ }
+
+ switch($type) {
+
+ case 'W':
+ case 'WEEKLY':
+ $days = array();
+ if(preg_match('/W(\d+) (.*) (.*)/',$recur, $recurMatches)) { // 1.0
+ $r_interval = $recurMatches[1];
+ $c_interval = $r_interval;
+ $days = explode(' ',trim($recurMatches[2]));
+ } elseif (preg_match('/BYDAY=([^;: ]+)/',$recur,$recurMatches)) { // 2.0
+ $days = explode(',',$recurMatches[1]);
+ }
+ if ($days) {
+ foreach($this->recur_days as $mid => $day) {
+ if (in_array(strtoupper(substr($day,0,2)), $days)){ //WAS ERROR IN BOICAL!!
+ $r_data |= $mid;
+ }
+ }
+ $r_type = MCAL_RECUR_WEEKLY;
+ }
+ // --------- r_end calculation from COUNT and BYDAYs ---
+ if ($r_count) {
+ $c_count = ($r_count - 1)*$c_interval;
+ foreach($days as $wdd){
+ $dow[] = $this->dowseqid[$wdd];
+ }
+ sort($dow);
+ $ustart_seqid = $this->dowseqid[strtoupper(substr(date('D',$ustart),0,2))];
+ // find index of start day 0..
+ $sdi = 0; //in case start is not on a byday
+ foreach($dow as $i) {
+ if ($dow[$i] == $ustart_seqid){ // hope start is on byday
+ $sdi = $i; break;
+ } elseif($dow[$i] >= $ustart_seqid){ // else next byday
+ $sdi = $i; break;
+ }
+ }
+ $edi = $sdi + $c_count; // end day index
+ $dur = 0; // duration until end in days
+ $wic = count($dow); // week indexes count
+ $dur = 7 * floor($edi / $wic) + $dow[($edi % $wic)];
+ $dur -= $dow[$sdi];
+ $a6sd['mday'] += intval($dur);
+ $r_end = $this->a6toutime($a6sd);
+ // destroy $a6sd here..
+
+//error_log('count=' . $c_count .'sdi=' . $sdi . ' edi=' . $edi .
+// ' wic=' .$wic . ' dur=' .$dur . ' r_end=' . $r_end);
+ }
+ break;
+
+ case 'D': // 1.0
+ if(!preg_match('/D(\d+) (.*)/',$recur, $recurMatches))
+ break;
+ $c_interval = $r_interval = $recurMatches[1];
+ $r_end = $this->hi->_parseDateTime($recurMatches[2]);
+ // fall-through
+
+ case 'DAILY': // 2.0
+ $r_type = MCAL_RECUR_DAILY;
+ if ($r_count) { // count calc is still experimental!
+ $c_count = ($r_count - 1)*$c_interval;
+ $a6sd['mday'] += $c_count;
+ $r_end = $this->a6toutime($a6sd);
+ }
+ break;
+
+ case 'MONTHLY':
+ $r_type = strstr($recur,'BYDAY') ?
+ MCAL_RECUR_MONTHLY_WDAY : MCAL_RECUR_MONTHLY_MDAY;
+ // break;
+ //fall through
+
+ case 'M':
+ if(preg_match('/MD(\d+) (.*)/',$recur, $recurMatches)) {
+ $r_type = MCAL_RECUR_MONTHLY_MDAY;
+ $c_interval = $r_interval = $recurMatches[1];
+ } elseif(preg_match('/MP(\d+) (.*) (.*) (.*)/',$recur, $recurMatches)) {
+ $r_type = MCAL_RECUR_MONTHLY_WDAY;
+ $c_interval = $r_interval = $recurMatches[1];
+ }
+ //
+ if ($r_count) { // count calc is still experimental!
+ switch ($r_type) {
+ case MCAL_RECUR_MONTHLY_MDAY:
+ // error_log('DOING MOTNHLY MDAY'); Egw doesnot handle this special, see todo
+ $c_count = ($r_count - 1)*$c_interval; // maybe changed because 1.0 found
+ $a6sd['month'] += $c_count;
+ $r_end = $this->a6toutime($a6sd);
+ break;
+
+ case MCAL_RECUR_MONTHLY_WDAY:
+ $c_count = ($r_count - 1)*$c_interval; // maybe changed because 1.0 found
+ // startday
+ $dowsd = date('w',$this->a6toutime($a6sd)); // day of week for sd
+ //end day, first try
+ $a6ed = array_diff($a6sd,array()); $a6ed['month'] += $c_count;
+ //day1 of startmonth
+ $a6smd1 = array_diff($a6sd,array()); $a6smd1['mday'] = 1;
+ $dowsmd1 = date('w',$this->a6toutime($a6smd1)); // day of week for smd1
+
+ //startdate as day of 5week segment, anchored on and afer smd1
+ $do5wsegsd = $dowsmd1 + $a6sd['mday'];
+ if($dowsmd1 > $dowsd)
+ $do5wsegsd -= 7;
+
+ $a6ed['mday'] =1;
+ $dowemd1 = date('w',$this->a6toutime($a6ed)); // endmonthday1 as day of week
+ $a6ed['mday'] = $do5wsegsd - $dowemd1;
+ if($dowemd1 > $dowsd)
+ $a6ed['mday'] += 7;
+
+// error_log('dowsd='. $dowsd . ' dowsmd1='. $dowsmd1 . ' do5wsegsd=' . $do5wsegsd .
+// ' dowemd1='. $dowemd1 . ' edmday=' . $a6ed['mday'] );
+ $r_end = $this->a6toutime($a6ed);
+ break;
+ }
+ }
+ break;
+
+
+ case 'Y': // 1.0
+ if(!preg_match('/YM(\d+) (.*)/',$recur, $recurMatches))
+ break;
+ $c_interval = $r_interval = $recurMatches[1];
+ // fall-through
+
+ case 'YEARLY': // 2.0
+ $r_type = MCAL_RECUR_YEARLY;
+ if ($r_count) { // count calc is still experimental!
+ // is there only this BYMONTHDAY support?
+ $c_count = ($r_count - 1)*$c_interval; // maybe changed because 1.0 found
+ $a6sd['year'] += $c_count;
+ $r_end = $this->a6toutime($a6sd);
+ }
+ break;
+ }
+
+ return array('recur_type' => $r_type,
+ 'recur_data' => $r_data,
+ 'recur_interval' => $r_interval,
+ 'recur_enddate' => $r_end );
+ }
+
+
+ /**
+ * Parse a CAL_ADDRESS and try to find the associated egw person_id
+ * @param string $attrval CAL_ADDRESS type value string
+ * @return int|false $pid associated (by email) egw pid. On error: false.
+ */
+ function mke_CAL_ADDRESS2pid($attrval)
+ {
+ if (preg_match('/MAILTO:([@.a-z0-9_-]+)/i',$attrval,$matches) &&
+ ($pid = $GLOBALS['egw']->accounts->name2id(strtolower($matches[1]),'account_email'))){
+ return $pid;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Parse a CAL_ADDRESS and PARAMS to find the CN name and email
+ * @param string $aval CAL_ADDRESS type value string
+ * @param array $aparams parameters for a ATTENDEE
+ * @return array $cneml assoc array with 'cn' and 'mailto' field
+ */
+ function mke_ATTENDEE2cneml($aval,$aparams)
+ {
+ $cneml = array('cn' => '', 'mailto' => '');
+
+ // if (preg_match('/MAILTO:([@.a-z0-9_-]+)/i',$aval,$matches))
+ // lets be a bit more relaxed here (rfc1378)..
+ // try for "Fnam Lnam " first
+ if (preg_match('/MAILTO:([^<]+)<([@.a-z0-9_-]+)>/i',$aval,$matches)){
+ $cneml['cn'] = $matches[1];
+ $cneml['mailto'] = $matches[2];
+ // try for second
+ }elseif (preg_match('/MAILTO:([@.a-z0-9_-]+)/i',$aval,$matches)){
+ $cneml['mailto'] = $matches[1];
+ }
+ // a CN from the params overrules one from the mailto
+ if(isset($aparams['CN']))
+ $cneml['cn'] = $aparams['CN'];
+
+ return $cneml;
+ }
+
+ /**
+ * Update an egw event description with a list of nonegw participants.
+ *
+ * note: this is a adhoc solution, preferably the nonegw participants
+ * should be added automatically to the addressbook
+ * @param string &$edescription the participants are append to this string as
+ * a string formatted ([cn: name:mailto: eml] [] ... )
+ * @param array &$ne_participants array of the non egw participants as
+ * ('cn' =>, 'mailto' =>) pairs
+ * @return true
+ */
+ function upde_nonegwParticipants2description(&$edescription,&$ne_participants)
+ {
+ $edescription.= "\n - non egw participants:\n(";
+ $neplist = array();
+ foreach ($ne_participants as $nep){
+// $li = '[cn:' . $nep['cn'];
+// $li .= ($nep['mailto']) ? ';mailto:' . $nep['mailto'] .']' : ']';
+ $li = '[' . $nep['cn'];
+ $li .= ($nep['mailto']) ? '<' . $nep['mailto'] .'>]' : ']';
+
+ $neplist[]= $li;
+ }
+ $edescription .= implode("\n",$neplist) . ')';
+
+ return true;
+ }
+
+
+ /**
+ * Search a ical parameterlist for possible setting for a egw participant status.
+ *
+ * Parse the params array to find a PARTSTAT param, convert this to
+ * a egw partstatus (may occur e.g. in ATTENDEE params)
+ * @param array $params params of e.g. an ical ATTENDEE field
+ * @return array|false $epartstatus egw term for particpant status if detected else false
+ */
+ function mke_params2partstat($params)
+ {
+ if (!isset($params['PARTSTAT']))
+ return false;
+
+ return $this->partstatus_ical2egw[strtoupper($params['PARTSTAT'])];
+ }
+
+ /**
+ * Add (append) an new attribute (aka field) to the vevent.
+ *
+ * @param horde_iCalendar_vevent $vevent obj to which the attribute is added
+ * @param string $aname name for the new attribute
+ * @param mixed $avalue value for the new attribute
+ * @param array $aparams parameters for the new attribute
+ * @return true
+ */
+ function addAttributeOntoVevent(&$vevent,$aname,&$avalue,&$aparams)
+ {
+
+ // it appears that translation->convert() can translate an array
+ // (that is: the values!, not the keys though)
+ // so lets apply it to the avalue and aparams, that should be enough!
+// error_log('n:' . $aname . 'v:' . $avalue);
+ $valueData =
+ $GLOBALS['egw']->translation->convert($avalue,
+ $GLOBALS['egw']->translation->charset(),
+ 'UTF-8');
+ $paramData =
+ $GLOBALS['egw']->translation->convert( $aparams,
+ $GLOBALS['egw']->translation->charset(),
+ 'UTF-8');
+// error_log('n:' . $aname . 'v:' . $valueData);
+ $vevent->setAttribute($aname, $valueData, $paramData);
+ $options = array();
+ // JVL:is this really needed?
+ if (is_string($valueData)){
+
+// // JVL: TEMPORARY SWITCHED OFF... TURN ON AGAIN!!!
+// if(!(in_array($aname, array('RRULE')))
+// && preg_match('/([\000-\012\015\016\020-\037\075])/',$valueData)) {
+// $options['ENCODING'] = 'QUOTED-PRINTABLE';
+// }
+
+ if( (preg_match('/([\177-\377])/',$valueData))) {
+ $options['CHARSET'] = 'UTF-8';
+ }
+ }
+ $vevent->setParameter($aname, $options);
+
+ return true;
+ }
+
+
+ /**
+ * Convert egw alarm info to a ical VALARM object.
+ *
+ * Make a VALARM object form data in $alarms and $utstart (in utc)
+ * and with $vevent as container
+ * @param array &$alarm a single egw alarm array to be used
+ * @param horde_object &$vcomp that will be the container for the valarm
+ * mostly vevent or vtodo.
+ * @param array &$veExportFields list with fields that may get imported
+ * @return horde_iCalendar_valarm|false valarm object or, on error, false.
+ */
+ function mki_c_VALARM(&$alarm, &$vcomp, $utstart,&$veExportFields){
+
+// error_log('export comp-alarm-field:' . print_r($alarm,true));
+
+ $valarm = Horde_iCalendar::newComponent('VALARM',$vevent);
+
+ //try first an offset
+ if($durtime = -$alarm['offset']){
+ $valarm->setAttribute('TRIGGER',
+ $durtime,
+ array('VALUE' => 'DURATION',
+ 'RELATED' => 'START'));
+ // no success then try a date-time
+ } elseif($dtime = $alarm['time']){
+ $valarm->setAttribute('TRIGGER',
+ $ddtime,
+ array('VALUE' => 'DATE-TIME'));
+ } else{
+ $valarm = null;
+ return false;
+ }
+ $vcomp->addComponent($valarm);
+
+ return $valarm;
+ }
+
+
+ /**
+ * Update the egw alarms array with info from a VALARM
+ * @param array &$alarms the the egw alarms array to be updated
+ * @param horde_iCalendar_valarm $valarm ref to the valarm component to be updated
+ * @param int $user_id the user that will own the alarms found
+ * @param array &$veImportFields with fields that may get imported
+ * @return true
+ */
+ function upde_c_VALARM2alarms(&$alarms,&$valarm,$user_id,&$veImportFields){
+
+ // lets see what supported veImportFields we can get from the valarm
+ foreach($valarm->_attributes as $vattr) {
+ // $vattrval = $GLOBALS['egw']->translation->convert($vattr['value'],'UTF-8');
+ // handle only supported fields
+ if(!in_array('VALARM/' . $vattr['name'], $veImportFields))
+ continue;
+
+ switch($vattr['name']) {
+ case 'TRIGGER':
+ $vtype = (isset($vattr['params']['VALUE']))
+ ? $vattr['params']['VALUE'] : 'DURATION'; //default type
+ switch ($vtype) {
+ case 'DURATION':
+ $alarms[] = array('offset' => -$vattr['value']);
+ break;
+ case 'DATE-TIME':
+ $alarms[] = array('time' => $vattr['value']);
+ break;
+ default:
+ // we should also do ;RELATED=START|END
+ error_log('VALARM/TRIGGER: unsupported value type:' . $vtype);
+ }
+ break;
+// case 'ACTION':
+// break;
+// case 'DISPLAY':
+// break;
+
+ default:
+ error_log('VALARM field:' .$vattr['name'] .':'
+ . print_r($vattrval,true) . ' HAS NO CONVERSION YET');
+ }
+ }
+// error_log('updated alarms to:' . print_r($alarms,true));
+ return true;
+ }
+
+
+
+
+
+
+ }
+
+?>
\ No newline at end of file
diff --git a/egwical/inc/class.vcalinfolog.inc.compat.php b/egwical/inc/class.vcalinfolog.inc.compat.php
new file mode 100644
index 0000000000..19d81d5694
--- /dev/null
+++ b/egwical/inc/class.vcalinfolog.inc.compat.php
@@ -0,0 +1,72 @@
+ei =& CreateObject('egwical.egwical');
+ // $this->wkobj =& $this->ei->addRsc($this);
+ // or the fast, shortcut, road for knowingly experts only:
+ $this->wkobj =& CreateObject('egwical.boinfolog_vtodos');
+ $this->wkobj->setRsc($this);
+
+ if ($this->wkobj == false){
+ error_log('boical constructor: couldnot add bocal resource to egwical: FATAL');
+ return false;
+ }
+ }
+
+
+ // now implement the compatibility methods, that are all moved to egwical!
+
+ function exportVTODO($_taskID, $_version)
+ {
+ return $this->wkobj->exportVTODO($_taskID, $_version);
+ }
+
+ function importVTODO(&$_vcalData, $_taskID=-1)
+ {
+ return $this->wkobj->importVTODO($_vcalData, $_taskID);
+ }
+
+
+// function setSupportedFields($_productManufacturer='file', $_productName='')
+// {
+// return $this->wkobj->setSupportedFields($_productManufacturer, $_productName);
+// }
+
+ }
+?>