* Added 'overflow:hidden' to the stylesheet for 'mimepart' divs. This prevents the...
[citadel.git] / webcit / calendar.c
1 /*
2  * $Id$
3  *
4  * Functions which handle calendar objects and their processing/display.
5  */
6
7 #include "webcit.h"
8 #include "webserver.h"
9
10
11 /*
12  * Process a calendar object.  At this point it's already been deserialized by cal_process_attachment()
13  *
14  * cal:                 the calendar object
15  * recursion_level:     Number of times we've recursed into this function
16  * msgnum:              Message number on the Citadel server
17  * cal_partnum:         MIME part number within that message containing the calendar object
18  */
19 void cal_process_object(StrBuf *Target,
20                         icalcomponent *cal,
21                         int recursion_level,
22                         long msgnum,
23                         const char *cal_partnum) 
24 {
25         icalcomponent *c;
26         icalproperty *method = NULL;
27         icalproperty_method the_method = ICAL_METHOD_NONE;
28         icalproperty *p;
29         struct icaltimetype t;
30         time_t tt;
31         char buf[256];
32         char conflict_name[256];
33         char conflict_message[256];
34         int is_update = 0;
35         char divname[32];
36         static int divcount = 0;
37
38         sprintf(divname, "rsvp%04x", ++divcount);
39
40         /* Convert timezones to something easy to display.
41          * It's safe to do this in memory because we're only changing it on the
42          * display side -- when we tell the server to do something with the object,
43          * the server will be working with its original copy in the database.
44          */
45         if ((cal) && (recursion_level == 0)) {
46                 ical_dezonify(cal);
47         }
48
49         /* Leading HTML for the display of this object */
50         if (recursion_level == 0) {
51                 StrBufAppendPrintf(Target, "<div class=\"mimepart\">\n");
52         }
53
54         /* Look for a method */
55         method = icalcomponent_get_first_property(cal, ICAL_METHOD_PROPERTY);
56
57         /* See what we need to do with this */
58         if (method != NULL) {
59                 the_method = icalproperty_get_method(method);
60                 char *title;
61
62                 StrBufAppendPrintf(Target, "<div id=\"%s_title\">", divname);
63                 StrBufAppendPrintf(Target, "<img src=\"static/calarea_48x.gif\">");
64                 StrBufAppendPrintf(Target, "<span>");
65                 switch(the_method) {
66                 case ICAL_METHOD_REQUEST:
67                         title = _("Meeting invitation");
68                         break;
69                 case ICAL_METHOD_REPLY:
70                         title = _("Attendee's reply to your invitation");
71                         break;
72                 case ICAL_METHOD_PUBLISH:
73                         title = _("Published event");
74                         break;
75                 default:
76                         title = _("This is an unknown type of calendar item.");
77                         break;
78                 }
79                 StrBufAppendPrintf(Target, "</span>");
80
81                 StrBufAppendPrintf(Target, "&nbsp;&nbsp;%s",title);
82                 StrBufAppendPrintf(Target, "</div>");
83         }
84
85         StrBufAppendPrintf(Target, "<dl>");
86         p = icalcomponent_get_first_property(cal, ICAL_SUMMARY_PROPERTY);
87         if (p != NULL) {
88                 StrBufAppendPrintf(Target, "<dt>");
89                 StrBufAppendPrintf(Target, _("Summary:"));
90                 StrBufAppendPrintf(Target, "</dt><dd>");
91                 StrEscAppend(Target, NULL, (char *)icalproperty_get_comment(p), 0, 0);
92                 StrBufAppendPrintf(Target, "</dd>\n");
93         }
94
95         p = icalcomponent_get_first_property(cal, ICAL_LOCATION_PROPERTY);
96         if (p != NULL) {
97                 StrBufAppendPrintf(Target, "<dt>");
98                 StrBufAppendPrintf(Target, _("Location:"));
99                 StrBufAppendPrintf(Target, "</dt><dd>");
100                 StrEscAppend(Target, NULL, (char *)icalproperty_get_comment(p), 0, 0);
101                 StrBufAppendPrintf(Target, "</dd>\n");
102         }
103
104         /*
105          * Only show start/end times if we're actually looking at the VEVENT
106          * component.  Otherwise it shows bogus dates for things like timezone.
107          */
108         if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
109
110                 p = icalcomponent_get_first_property(cal, ICAL_DTSTART_PROPERTY);
111                 if (p != NULL) {
112                         t = icalproperty_get_dtstart(p);
113
114                         if (t.is_date) {
115                                 struct tm d_tm;
116                                 char d_str[32];
117                                 memset(&d_tm, 0, sizeof d_tm);
118                                 d_tm.tm_year = t.year - 1900;
119                                 d_tm.tm_mon = t.month - 1;
120                                 d_tm.tm_mday = t.day;
121                                 wc_strftime(d_str, sizeof d_str, "%x", &d_tm);
122                                 StrBufAppendPrintf(Target, "<dt>");
123                                 StrBufAppendPrintf(Target, _("Date:"));
124                                 StrBufAppendPrintf(Target, "</dt><dd>%s</dd>", d_str);
125                         }
126                         else {
127                                 tt = icaltime_as_timet(t);
128                                 webcit_fmt_date(buf, tt, 0);
129                                 StrBufAppendPrintf(Target, "<dt>");
130                                 StrBufAppendPrintf(Target, _("Starting date/time:"));
131                                 StrBufAppendPrintf(Target, "</dt><dd>%s</dd>", buf);
132                         }
133                 }
134         
135                 p = icalcomponent_get_first_property(cal, ICAL_DTEND_PROPERTY);
136                 if (p != NULL) {
137                         t = icalproperty_get_dtend(p);
138                         tt = icaltime_as_timet(t);
139                         webcit_fmt_date(buf, tt, 0);
140                         StrBufAppendPrintf(Target, "<dt>");
141                         StrBufAppendPrintf(Target, _("Ending date/time:"));
142                         StrBufAppendPrintf(Target, "</dt><dd>%s</dd>", buf);
143                 }
144
145         }
146
147         p = icalcomponent_get_first_property(cal, ICAL_DESCRIPTION_PROPERTY);
148         if (p != NULL) {
149                 StrBufAppendPrintf(Target, "<dt>");
150                 StrBufAppendPrintf(Target, _("Description:"));
151                 StrBufAppendPrintf(Target, "</dt><dd>");
152                 StrEscAppend(Target, NULL, (char *)icalproperty_get_comment(p), 0, 0);
153                 StrBufAppendPrintf(Target, "</dd>\n");
154         }
155
156         /* If the component has attendees, iterate through them. */
157         for (p = icalcomponent_get_first_property(cal, ICAL_ATTENDEE_PROPERTY); 
158              (p != NULL); 
159              p = icalcomponent_get_next_property(cal, ICAL_ATTENDEE_PROPERTY)) {
160                 StrBufAppendPrintf(Target, "<dt>");
161                 StrBufAppendPrintf(Target, _("Attendee:"));
162                 StrBufAppendPrintf(Target, "</dt><dd>");
163                 safestrncpy(buf, icalproperty_get_attendee(p), sizeof buf);
164                 if (!strncasecmp(buf, "MAILTO:", 7)) {
165
166                         /** screen name or email address */
167                         strcpy(buf, &buf[7]);
168                         striplt(buf);
169                         StrEscAppend(Target, NULL, buf, 0, 0);
170                         StrBufAppendPrintf(Target, " ");
171
172                         /** participant status */
173                         partstat_as_string(buf, p);
174                         StrEscAppend(Target, NULL, buf, 0, 0);
175                 }
176                 StrBufAppendPrintf(Target, "</dd>\n");
177         }
178
179         /* If the component has subcomponents, recurse through them. */
180         for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
181              (c != 0);
182              c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
183                 /* Recursively process subcomponent */
184                 cal_process_object(Target, c, recursion_level+1, msgnum, cal_partnum);
185         }
186
187         /* If this is a REQUEST, display conflicts and buttons */
188         if (the_method == ICAL_METHOD_REQUEST) {
189
190                 /* Check for conflicts */
191                 lprintf(9, "Checking server calendar for conflicts...\n");
192                 serv_printf("ICAL conflicts|%ld|%s|", msgnum, cal_partnum);
193                 serv_getln(buf, sizeof buf);
194                 if (buf[0] == '1') {
195                         while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
196                                 extract_token(conflict_name, buf, 3, '|', sizeof conflict_name);
197                                 is_update = extract_int(buf, 4);
198
199                                 if (is_update) {
200                                         snprintf(conflict_message, sizeof conflict_message,
201                                                  _("This is an update of '%s' which is already in your calendar."), conflict_name);
202                                 }
203                                 else {
204                                         snprintf(conflict_message, sizeof conflict_message,
205                                                  _("This event would conflict with '%s' which is already in your calendar."), conflict_name);
206                                 }
207
208                                 StrBufAppendPrintf(Target, "<dt>%s",
209                                         (is_update ?
210                                          _("Update:") :
211                                          _("CONFLICT:")
212                                                 )
213                                         );
214                                 StrBufAppendPrintf(Target, "</dt><dd>");
215                                 StrEscAppend(Target, NULL, conflict_message, 0, 0);
216                                 StrBufAppendPrintf(Target, "</dd>\n");
217                         }
218                 }
219                 lprintf(9, "...done.\n");
220
221                 StrBufAppendPrintf(Target, "</dl>");
222
223                 /* Display the Accept/Decline buttons */
224                 StrBufAppendPrintf(Target, "<p id=\"%s_question\">"
225                         "%s "
226                         "&nbsp;&nbsp;&nbsp;<span class=\"button_link\"> "
227                         "<a href=\"javascript:RespondToInvitation('%s_question','%s_title','%ld','%s','Accept');\">%s</a>"
228                         "</span>&nbsp;&nbsp;&nbsp;<span class=\"button_link\">"
229                         "<a href=\"javascript:RespondToInvitation('%s_question','%s_title','%ld','%s','Tentative');\">%s</a>"
230                         "</span>&nbsp;&nbsp;&nbsp;<span class=\"button_link\">"
231                         "<a href=\"javascript:RespondToInvitation('%s_question','%s_title','%ld','%s','Decline');\">%s</a>"
232                         "</span></p>\n",
233                         divname,
234                         _("How would you like to respond to this invitation?"),
235                         divname, divname, msgnum, cal_partnum, _("Accept"),
236                         divname, divname, msgnum, cal_partnum, _("Tentative"),
237                         divname, divname, msgnum, cal_partnum, _("Decline")
238                         );
239
240         }
241
242         /* If this is a REPLY, display update button */
243         if (the_method == ICAL_METHOD_REPLY) {
244
245                 /* Display the update buttons */
246                 StrBufAppendPrintf(Target, "<p id=\"%s_question\" >"
247                         "%s "
248                         "&nbsp;&nbsp;&nbsp;<span class=\"button_link\"> "
249                         "<a href=\"javascript:HandleRSVP('%s_question','%s_title','%ld','%s','Update');\">%s</a>"
250                         "</span>&nbsp;&nbsp;&nbsp;<span class=\"button_link\">"
251                         "<a href=\"javascript:HandleRSVP('%s_question','%s_title','%ld','%s','Ignore');\">%s</a>"
252                         "</span></p>\n",
253                         divname,
254                         _("Click <i>Update</i> to accept this reply and update your calendar."),
255                         divname, divname, msgnum, cal_partnum, _("Update"),
256                         divname, divname, msgnum, cal_partnum, _("Ignore")
257                         );
258         
259         }
260         
261         /* Trailing HTML for the display of this object */
262         if (recursion_level == 0) {
263                 StrBufAppendPrintf(Target, "<p>&nbsp;</p></div>\n");
264         }
265 }
266
267
268 /*
269  * Deserialize a calendar object in a message so it can be displayed.
270  *
271  */
272 void cal_process_attachment(wc_mime_attachment *Mime) 
273 {
274         icalcomponent *cal;
275         
276         cal = icalcomponent_new_from_string(ChrPtr(Mime->Data));
277         FlushStrBuf(Mime->Data);
278         if (cal == NULL) {
279                 StrBufAppendPrintf(Mime->Data, _("There was an error parsing this calendar item."));
280                 StrBufAppendPrintf(Mime->Data, "<br />\n");
281                 return;
282         }
283
284         cal_process_object(Mime->Data, cal, 0, Mime->msgnum, ChrPtr(Mime->PartNum));
285
286         /* Free the memory we obtained from libical's constructor */
287         icalcomponent_free(cal);
288 }
289
290
291
292
293 /**
294  * \brief accept/decline meeting
295  * Respond to a meeting request
296  */
297 void respond_to_request(void) 
298 {
299         char buf[1024];
300
301         begin_ajax_response();
302
303         serv_printf("ICAL respond|%s|%s|%s|",
304                 bstr("msgnum"),
305                 bstr("cal_partnum"),
306                 bstr("sc")
307         );
308         serv_getln(buf, sizeof buf);
309
310         if (buf[0] == '2') {
311                 wprintf("<img src=\"static/calarea_48x.gif\"><span>");
312                 if (!strcasecmp(bstr("sc"), "accept")) {
313                         wprintf(_("You have accepted this meeting invitation.  "
314                                 "It has been entered into your calendar.")
315                         );
316                 } else if (!strcasecmp(bstr("sc"), "tentative")) {
317                         wprintf(_("You have tentatively accepted this meeting invitation.  "
318                                 "It has been 'pencilled in' to your calendar.")
319                         );
320                 } else if (!strcasecmp(bstr("sc"), "decline")) {
321                         wprintf(_("You have declined this meeting invitation.  "
322                                   "It has <b>not</b> been entered into your calendar.")
323                                 );
324                 }
325                 wprintf(" ");
326                 wprintf(_("A reply has been sent to the meeting organizer."));
327                 wprintf("</span>");
328         } else {
329                 wprintf("<img align=\"center\" src=\"static/error.gif\"><span>");
330                 wprintf("%s\n", &buf[4]);
331                 wprintf("</span>");
332         }
333
334         end_ajax_response();
335 }
336
337
338
339 /**
340  * \brief Handle an incoming RSVP
341  */
342 void handle_rsvp(void) 
343 {
344         char buf[1024];
345
346         begin_ajax_response();
347
348         serv_printf("ICAL handle_rsvp|%s|%s|%s|",
349                 bstr("msgnum"),
350                 bstr("cal_partnum"),
351                 bstr("sc")
352         );
353         serv_getln(buf, sizeof buf);
354
355         if (buf[0] == '2') {
356                 wprintf("<img src=\"static/calarea_48x.gif\"><span>");
357                 if (!strcasecmp(bstr("sc"), "update")) {
358                         wprintf(_("Your calendar has been updated to reflect this RSVP."));
359                 } else if (!strcasecmp(bstr("sc"), "ignore")) {
360                         wprintf(_("You have chosen to ignore this RSVP. "
361                                   "Your calendar has <b>not</b> been updated.")
362                                 );
363                 }
364                 wprintf("</span>");
365         } else {
366                 wprintf("<img src=\"static/error.gif\"><span> %s\n", &buf[4]);
367                 wprintf("</span>");
368         }
369
370         end_ajax_response();
371 }
372
373
374
375
376 /*
377  * free memory allocated using libical
378  */
379 void delete_cal(void *vCal)
380 {
381         disp_cal *Cal = (disp_cal*) vCal;
382         icalcomponent_free(Cal->cal);
383         free(Cal->from);
384         free(Cal);
385 }
386
387 /*
388  * This is the meat-and-bones of the first part of our two-phase calendar display.
389  * As we encounter calendar items in messages being read from the server, we break out
390  * any iCalendar objects and store them in a hash table.  Later on, the second phase will
391  * use this hash table to render the calendar for display.
392  */
393 void display_individual_cal(icalcomponent *cal, long msgnum, char *from, int unread, struct calview *calv)
394 {
395         icalproperty *ps = NULL;
396         struct icaltimetype dtstart, dtend;
397         struct icaldurationtype dur;
398         wcsession *WCC = WC;
399         disp_cal *Cal;
400         size_t len;
401         time_t final_recurrence = 0;
402         icalcomponent *cptr = NULL;
403
404         /* recur variables */
405         icalproperty *rrule = NULL;
406         struct icalrecurrencetype recur;
407         icalrecur_iterator *ritr = NULL;
408         struct icaltimetype next;
409         int num_recur = 0;
410
411         dtstart = icaltime_null_time();
412         dtend = icaltime_null_time();
413         
414         if (WCC->disp_cal_items == NULL)
415                 WCC->disp_cal_items = NewHash(0, Flathash);
416
417         /* Note: anything we do here, we also have to do below for the recurrences. */
418         Cal = (disp_cal*) malloc(sizeof(disp_cal));
419         memset(Cal, 0, sizeof(disp_cal));
420         Cal->cal = icalcomponent_new_clone(cal);
421
422         /* Dezonify and decapsulate at the very last moment */
423         /* lprintf(9, "INITIAL: %s\n", icaltime_as_ical_string(icalproperty_get_dtstart(
424                 icalcomponent_get_first_property(icalcomponent_get_first_component(
425                 Cal->cal, ICAL_VEVENT_COMPONENT), ICAL_DTSTART_PROPERTY)))
426         ); */
427         ical_dezonify(Cal->cal);
428         if (icalcomponent_isa(Cal->cal) != ICAL_VEVENT_COMPONENT) {
429                 cptr = icalcomponent_get_first_component(Cal->cal, ICAL_VEVENT_COMPONENT);
430                 if (cptr) {
431                         cptr = icalcomponent_new_clone(cptr);
432                         icalcomponent_free(Cal->cal);
433                         Cal->cal = cptr;
434                 }
435         }
436
437         Cal->unread = unread;
438         len = strlen(from);
439         Cal->from = (char*)malloc(len+ 1);
440         memcpy(Cal->from, from, len + 1);
441         Cal->cal_msgnum = msgnum;
442
443         /* Precalculate the starting date and time of this event, and store it in our top-level
444          * structure.  Later, when we are rendering the calendar, we can just peek at these values
445          * without having to break apart every calendar item.
446          */
447         ps = icalcomponent_get_first_property(Cal->cal, ICAL_DTSTART_PROPERTY);
448         if (ps != NULL) {
449                 dtstart = icalproperty_get_dtstart(ps);
450                 Cal->event_start = icaltime_as_timet(dtstart);
451         }
452
453         /* Do the same for the ending date and time.  It makes the day view much easier to render. */
454         ps = icalcomponent_get_first_property(Cal->cal, ICAL_DTEND_PROPERTY);
455         if (ps != NULL) {
456                 dtend = icalproperty_get_dtend(ps);
457                 Cal->event_end = icaltime_as_timet(dtend);
458         }
459
460         /* Store it in the hash list. */
461         Put(WCC->disp_cal_items, 
462             (char*) &Cal->event_start,
463             sizeof(Cal->event_start), 
464             Cal, 
465             delete_cal);
466
467         /****************************** handle recurring events ******************************/
468
469         if (icaltime_is_null_time(dtstart)) return;     /* Can't recur without a start time */
470
471         if (!icaltime_is_null_time(dtend)) {            /* Need duration for recurrences */
472                 dur = icaltime_subtract(dtend, dtstart);
473         }
474
475         /*
476          * Just let libical iterate the recurrence, and keep looping back to the top of this function,
477          * adding new hash entries that all point back to the same msgnum, until either the iteration
478          * stops or some outer bound is reached.  The display code will automatically do the Right Thing.
479          */
480         cptr = cal;
481         if (icalcomponent_isa(cptr) != ICAL_VEVENT_COMPONENT) {
482                 cptr = icalcomponent_get_first_component(cptr, ICAL_VEVENT_COMPONENT);
483         }
484         if (!cptr) return;
485         ps = icalcomponent_get_first_property(cptr, ICAL_DTSTART_PROPERTY);
486         if (ps == NULL) return;
487         dtstart = icalproperty_get_dtstart(ps);
488         rrule = icalcomponent_get_first_property(cptr, ICAL_RRULE_PROPERTY);
489         if (!rrule) return;
490         recur = icalproperty_get_rrule(rrule);
491         ritr = icalrecur_iterator_new(recur, dtstart);
492         if (!ritr) return;
493
494         int stop_rr = 0;
495         while (next = icalrecur_iterator_next(ritr), ((!icaltime_is_null_time(next))&&(!stop_rr)) ) {
496                 ++num_recur;
497                 if (num_recur > 1) {            /* Skip the first one.  We already did it at the root. */
498                         /* lprintf(9, "REPEATS: %s\n", icaltime_as_ical_string(next)); */
499
500                         /* Note: anything we do here, we also have to do above for the root event. */
501                         Cal = (disp_cal*) malloc(sizeof(disp_cal));
502                         memset(Cal, 0, sizeof(disp_cal));
503                         Cal->cal = icalcomponent_new_clone(cal);
504                         Cal->unread = unread;
505                         len = strlen(from);
506                         Cal->from = (char*)malloc(len+ 1);
507                         memcpy(Cal->from, from, len + 1);
508                         Cal->cal_msgnum = msgnum;
509
510                         icalcomponent *cptr;
511                         if (icalcomponent_isa(Cal->cal) == ICAL_VEVENT_COMPONENT) {
512                                 cptr = Cal->cal;
513                         }
514                         else {
515                                 cptr = icalcomponent_get_first_component(Cal->cal, ICAL_VEVENT_COMPONENT);
516                         }
517                         if (cptr) {
518                                 ps = icalcomponent_get_first_property(cptr, ICAL_DTSTART_PROPERTY);
519                                 if (ps != NULL) {
520                                         icalcomponent_remove_property(cptr, ps);
521                                         ps = icalproperty_new_dtstart(next);
522                                         icalcomponent_add_property(cptr, ps);
523         
524                                         Cal->event_start = icaltime_as_timet(next);
525                                         final_recurrence = Cal->event_start;
526                                 }
527
528                                 ps = icalcomponent_get_first_property(cptr, ICAL_DTEND_PROPERTY);
529                                 if (ps != NULL) {
530                                         icalcomponent_remove_property(cptr, ps);
531         
532                                         /* Make a new dtend */
533                                         ps = icalproperty_new_dtend(icaltime_add(next, dur));
534                 
535                                         /* and stick it somewhere */
536                                         icalcomponent_add_property(cptr, ps);
537                                 }
538
539                         }
540
541                         /* Dezonify and decapsulate at the very last moment */
542                         ical_dezonify(Cal->cal);
543                         if (icalcomponent_isa(Cal->cal) != ICAL_VEVENT_COMPONENT) {
544                                 cptr = icalcomponent_get_first_component(Cal->cal, ICAL_VEVENT_COMPONENT);
545                                 if (cptr) {
546                                         cptr = icalcomponent_new_clone(cptr);
547                                         icalcomponent_free(Cal->cal);
548                                         Cal->cal = cptr;
549                                 }
550                         }
551
552                         if ( (Cal->event_start > calv->lower_bound)
553                            && (Cal->event_start < calv->upper_bound) ) {
554                                 Put(WCC->disp_cal_items, 
555                                         (char*) &Cal->event_start,
556                                         sizeof(Cal->event_start), 
557                                         Cal, 
558                                         delete_cal
559                                 );
560                         }
561                         else {
562                                 delete_cal(Cal);
563                         }
564
565                         /* If an upper bound is set, stop when we go out of scope */
566                         if (final_recurrence > calv->upper_bound) stop_rr = 1;
567                 }
568         }
569         icalrecur_iterator_free(ritr);
570         /* lprintf(9, "Performed %d recurrences; final one is %s", num_recur, ctime(&final_recurrence)); */
571
572 }
573
574
575
576 /*
577  * Display a task by itself (for editing)
578  */
579 void display_edit_individual_task(icalcomponent *supplied_vtodo, long msgnum, char *from,
580                         int unread, struct calview *calv)
581 {
582         icalcomponent *vtodo;
583         icalproperty *p;
584         struct icaltimetype IcalTime;
585         time_t now;
586         int created_new_vtodo = 0;
587         icalproperty_status todoStatus;
588
589         now = time(NULL);
590
591         if (supplied_vtodo != NULL) {
592                 vtodo = supplied_vtodo;
593
594                 /**
595                  * If we're looking at a fully encapsulated VCALENDAR
596                  * rather than a VTODO component, attempt to use the first
597                  * relevant VTODO subcomponent.  If there is none, the
598                  * NULL returned by icalcomponent_get_first_component() will
599                  * tell the next iteration of this function to create a
600                  * new one.
601                  */
602                 if (icalcomponent_isa(vtodo) == ICAL_VCALENDAR_COMPONENT) {
603                         display_edit_individual_task(
604                                 icalcomponent_get_first_component(
605                                         vtodo, ICAL_VTODO_COMPONENT
606                                         ), 
607                                 msgnum, from, unread, calv
608                                 );
609                         return;
610                 }
611         }
612         else {
613                 vtodo = icalcomponent_new(ICAL_VTODO_COMPONENT);
614                 created_new_vtodo = 1;
615         }
616         
617         // TODO: Can we take all this and move it into a template?      
618         output_headers(1, 1, 1, 0, 0, 0);
619         wprintf("<!-- start task edit form -->");
620         p = icalcomponent_get_first_property(vtodo, ICAL_SUMMARY_PROPERTY);
621         // Get summary early for title
622         wprintf("<div class=\"box\">\n");
623         wprintf("<div class=\"boxlabel\">");
624         wprintf(_("Edit task"));
625         wprintf("- ");
626         if (p != NULL) {
627                 escputs((char *)icalproperty_get_comment(p));
628         }
629         wprintf("</div>");
630         
631         wprintf("<div class=\"boxcontent\">\n");
632         wprintf("<FORM METHOD=\"POST\" action=\"save_task\">\n");
633         wprintf("<div style=\"display: none;\">\n       ");
634         wprintf("<input type=\"hidden\" name=\"nonce\" value=\"%d\">\n", WC->nonce);
635         wprintf("<INPUT TYPE=\"hidden\" NAME=\"msgnum\" VALUE=\"%ld\">\n",
636                 msgnum);
637         wprintf("</div>");
638         wprintf("<table class=\"calendar_background\"><tr><td>");
639         wprintf("<TABLE STYLE=\"border: none;\">\n");
640
641         wprintf("<TR><TD>");
642         wprintf(_("Summary:"));
643         wprintf("</TD><TD>"
644                 "<INPUT TYPE=\"text\" NAME=\"summary\" "
645                 "MAXLENGTH=\"64\" SIZE=\"64\" VALUE=\"");
646         p = icalcomponent_get_first_property(vtodo, ICAL_SUMMARY_PROPERTY);
647         if (p != NULL) {
648                 escputs((char *)icalproperty_get_comment(p));
649         }
650         wprintf("\"></TD></TR>\n");
651
652         wprintf("<TR><TD>");
653         wprintf(_("Start date:"));
654         wprintf("</TD><TD>");
655         p = icalcomponent_get_first_property(vtodo, ICAL_DTSTART_PROPERTY);
656         wprintf("<INPUT TYPE=\"CHECKBOX\" NAME=\"nodtstart\" ID=\"nodtstart\" VALUE=\"NODTSTART\" ");
657         if (p == NULL) {
658                 wprintf("CHECKED=\"CHECKED\"");
659         }
660         wprintf(">");
661         wprintf(_("No date"));
662         
663         wprintf(" ");
664         wprintf(_("or"));
665         wprintf(" ");
666         if (p != NULL) {
667                 IcalTime = icalproperty_get_dtstart(p);
668         }
669         else
670                 IcalTime = icaltime_current_time_with_zone(get_default_icaltimezone());
671         display_icaltimetype_as_webform(&IcalTime, "dtstart", 0);
672         wprintf("</TD></TR>\n");
673
674         wprintf("<TR><TD>");
675         wprintf(_("Due date:"));
676         wprintf("</TD><TD>");
677         p = icalcomponent_get_first_property(vtodo, ICAL_DUE_PROPERTY);
678         wprintf("<INPUT TYPE=\"CHECKBOX\" NAME=\"nodue\" ID=\"nodue\" VALUE=\"NODUE\"");
679         if (p == NULL) {
680                 wprintf("CHECKED=\"CHECKED\"");
681         }
682         wprintf(">");
683         wprintf(_("No date"));
684         wprintf(" ");
685         wprintf(_("or"));
686         wprintf(" ");
687         if (p != NULL) {
688                 IcalTime = icalproperty_get_due(p);
689         }
690         else
691                 IcalTime = icaltime_current_time_with_zone(get_default_icaltimezone());
692         display_icaltimetype_as_webform(&IcalTime, "due", 0);
693                 
694         wprintf("</TD></TR>\n");
695         todoStatus = icalcomponent_get_status(vtodo);
696         wprintf("<TR><TD>\n");
697         wprintf(_("Completed:"));
698         wprintf("</TD><TD>");
699         wprintf("<INPUT TYPE=\"CHECKBOX\" NAME=\"status\" VALUE=\"COMPLETED\"");
700         if (todoStatus == ICAL_STATUS_COMPLETED) {
701                 wprintf(" CHECKED=\"CHECKED\"");
702         } 
703         wprintf(" >");
704         wprintf("</TD></TR>");
705         // start category field
706         p = icalcomponent_get_first_property(vtodo, ICAL_CATEGORIES_PROPERTY);
707         wprintf("<TR><TD>");
708         wprintf(_("Category:"));
709         wprintf("</TD><TD>");
710         wprintf("<INPUT TYPE=\"text\" NAME=\"category\" MAXLENGTH=\"32\" SIZE=\"32\" VALUE=\"");
711         if (p != NULL) {
712                 escputs((char *)icalproperty_get_categories(p));
713         }
714         wprintf("\">");
715         wprintf("</TD></TR>\n   ");
716         // end category field
717         wprintf("<TR><TD>");
718         wprintf(_("Description:"));
719         wprintf("</TD><TD>");
720         wprintf("<TEXTAREA NAME=\"description\" "
721                 "ROWS=\"10\" COLS=\"80\">\n"
722                 );
723         p = icalcomponent_get_first_property(vtodo, ICAL_DESCRIPTION_PROPERTY);
724         if (p != NULL) {
725                 escputs((char *)icalproperty_get_comment(p));
726         }
727         wprintf("</TEXTAREA></TD></TR></TABLE>\n");
728
729         wprintf("<SPAN STYLE=\"text-align: center;\">"
730                 "<INPUT TYPE=\"submit\" NAME=\"save_button\" VALUE=\"%s\">"
731                 "&nbsp;&nbsp;"
732                 "<INPUT TYPE=\"submit\" NAME=\"delete_button\" VALUE=\"%s\">\n"
733                 "&nbsp;&nbsp;"
734                 "<INPUT TYPE=\"submit\" NAME=\"cancel_button\" VALUE=\"%s\">\n"
735                 "</SPAN>\n",
736                 _("Save"),
737                 _("Delete"),
738                 _("Cancel")
739                 );
740         wprintf("</td></tr></table>");
741         wprintf("</FORM>\n");
742         wprintf("</div></div></div>\n");
743         wprintf("<!-- end task edit form -->");
744         wDumpContent(1);
745
746         if (created_new_vtodo) {
747                 icalcomponent_free(vtodo);
748         }
749 }
750
751 /*
752  * \brief Save an edited task
753  * \param supplied_vtodo the task to save
754  * \param msgnum number of the mesage in our db
755  */
756 void save_individual_task(icalcomponent *supplied_vtodo, long msgnum, char* from, int unread,
757                                 struct calview *calv)
758 {
759         char buf[SIZ];
760         int delete_existing = 0;
761         icalproperty *prop;
762         icalcomponent *vtodo, *encaps;
763         int created_new_vtodo = 0;
764         int i;
765         int sequence = 0;
766         struct icaltimetype t;
767
768         if (supplied_vtodo != NULL) {
769                 vtodo = supplied_vtodo;
770                 /**
771                  * If we're looking at a fully encapsulated VCALENDAR
772                  * rather than a VTODO component, attempt to use the first
773                  * relevant VTODO subcomponent.  If there is none, the
774                  * NULL returned by icalcomponent_get_first_component() will
775                  * tell the next iteration of this function to create a
776                  * new one.
777                  */
778                 if (icalcomponent_isa(vtodo) == ICAL_VCALENDAR_COMPONENT) {
779                         save_individual_task(
780                                 icalcomponent_get_first_component(
781                                         vtodo, ICAL_VTODO_COMPONENT), 
782                                 msgnum, from, unread, calv
783                                 );
784                         return;
785                 }
786         }
787         else {
788                 vtodo = icalcomponent_new(ICAL_VTODO_COMPONENT);
789                 created_new_vtodo = 1;
790         }
791
792         if (havebstr("save_button")) {
793
794                 /** Replace values in the component with ones from the form */
795
796                 while (prop = icalcomponent_get_first_property(vtodo,
797                                                                ICAL_SUMMARY_PROPERTY), prop != NULL) {
798                         icalcomponent_remove_property(vtodo, prop);
799                         icalproperty_free(prop);
800                 }
801                 if (havebstr("summary")) {
802
803                         icalcomponent_add_property(vtodo,
804                                                    icalproperty_new_summary(bstr("summary")));
805                 } else {
806                         icalcomponent_add_property(vtodo,
807                                                    icalproperty_new_summary("Untitled Task"));
808                 }
809         
810                 while (prop = icalcomponent_get_first_property(vtodo,
811                                                                ICAL_DESCRIPTION_PROPERTY), prop != NULL) {
812                         icalcomponent_remove_property(vtodo, prop);
813                         icalproperty_free(prop);
814                 }
815                 if (havebstr("description")) {
816                         icalcomponent_add_property(vtodo,
817                                                    icalproperty_new_description(bstr("description")));
818                 }
819         
820                 while (prop = icalcomponent_get_first_property(vtodo,
821                                                                ICAL_DTSTART_PROPERTY), prop != NULL) {
822                         icalcomponent_remove_property(vtodo, prop);
823                         icalproperty_free(prop);
824                 }
825                 if (IsEmptyStr(bstr("nodtstart"))) {
826                         icaltime_from_webform(&t, "dtstart");
827                         icalcomponent_add_property(vtodo,
828                                                    icalproperty_new_dtstart(t)
829                                 );
830                 }
831                 while(prop = icalcomponent_get_first_property(vtodo,
832                                                               ICAL_STATUS_PROPERTY), prop != NULL) {
833                         icalcomponent_remove_property(vtodo,prop);
834                         icalproperty_free(prop);
835                 }
836                 if (havebstr("status")) {
837                         icalproperty_status taskStatus = icalproperty_string_to_status(
838                                 bstr("status"));
839                         icalcomponent_set_status(vtodo, taskStatus);
840                 }
841                 while (prop = icalcomponent_get_first_property(vtodo,
842                                                                ICAL_CATEGORIES_PROPERTY), prop != NULL) {
843                         icalcomponent_remove_property(vtodo,prop);
844                         icalproperty_free(prop);
845                 }
846                 if (!IsEmptyStr(bstr("category"))) {
847                         prop = icalproperty_new_categories(bstr("category"));
848                         icalcomponent_add_property(vtodo,prop);
849                 }
850                 while (prop = icalcomponent_get_first_property(vtodo,
851                                                                ICAL_DUE_PROPERTY), prop != NULL) {
852                         icalcomponent_remove_property(vtodo, prop);
853                         icalproperty_free(prop);
854                 }
855                 if (IsEmptyStr(bstr("nodue"))) {
856                         icaltime_from_webform(&t, "due");
857                         icalcomponent_add_property(vtodo,
858                                                    icalproperty_new_due(t)
859                                 );
860                 }
861                 /** Give this task a UID if it doesn't have one. */
862                 lprintf(9, "Give this task a UID if it doesn't have one.\n");
863                 if (icalcomponent_get_first_property(vtodo,
864                                                      ICAL_UID_PROPERTY) == NULL) {
865                         generate_uuid(buf);
866                         icalcomponent_add_property(vtodo,
867                                                    icalproperty_new_uid(buf)
868                                 );
869                 }
870
871                 /** Increment the sequence ID */
872                 lprintf(9, "Increment the sequence ID\n");
873                 while (prop = icalcomponent_get_first_property(vtodo,
874                                                                ICAL_SEQUENCE_PROPERTY), (prop != NULL) ) {
875                         i = icalproperty_get_sequence(prop);
876                         lprintf(9, "Sequence was %d\n", i);
877                         if (i > sequence) sequence = i;
878                         icalcomponent_remove_property(vtodo, prop);
879                         icalproperty_free(prop);
880                 }
881                 ++sequence;
882                 lprintf(9, "New sequence is %d.  Adding...\n", sequence);
883                 icalcomponent_add_property(vtodo,
884                                            icalproperty_new_sequence(sequence)
885                         );
886
887                 /**
888                  * Encapsulate event into full VCALENDAR component.  Clone it first,
889                  * for two reasons: one, it's easier to just free the whole thing
890                  * when we're done instead of unbundling, but more importantly, we
891                  * can't encapsulate something that may already be encapsulated
892                  * somewhere else.
893                  */
894                 lprintf(9, "Encapsulating into a full VCALENDAR component\n");
895                 encaps = ical_encapsulate_subcomponent(icalcomponent_new_clone(vtodo));
896
897                 /* Serialize it and save it to the message base */
898                 serv_puts("ENT0 1|||4");
899                 serv_getln(buf, sizeof buf);
900                 if (buf[0] == '4') {
901                         serv_puts("Content-type: text/calendar");
902                         serv_puts("");
903                         serv_puts(icalcomponent_as_ical_string(encaps));
904                         serv_puts("000");
905
906                         /**
907                          * Probably not necessary; the server will see the UID
908                          * of the object and delete the old one anyway, but
909                          * just in case...
910                          */
911                         delete_existing = 1;
912                 }
913                 icalcomponent_free(encaps);
914         }
915
916         /**
917          * If the user clicked 'Delete' then explicitly delete the message.
918          */
919         if (havebstr("delete_button")) {
920                 delete_existing = 1;
921         }
922
923         if ( (delete_existing) && (msgnum > 0L) ) {
924                 serv_printf("DELE %ld", lbstr("msgnum"));
925                 serv_getln(buf, sizeof buf);
926         }
927
928         if (created_new_vtodo) {
929                 icalcomponent_free(vtodo);
930         }
931
932         /** Go back to the task list */
933         readloop(readfwd);
934 }
935
936
937
938 /*
939  * Code common to all icalendar display handlers.  Given a message number and a MIME
940  * type, we load the message and hunt for that MIME type.  If found, we load
941  * the relevant part, deserialize it into a libical component, filter it for
942  * the requested object type, and feed it to the specified handler.
943  */
944 void load_ical_object(long msgnum, int unread,
945                            icalcomponent_kind which_kind,
946                            void (*callback)(icalcomponent *, long, char*, int, struct calview *),
947                            struct calview *calv
948         ) 
949 {
950         char buf[1024];
951         char from[128] = "";
952         char mime_partnum[256];
953         char mime_filename[256];
954         char mime_content_type[256];
955         char mime_disposition[256];
956         int mime_length;
957         char relevant_partnum[256];
958         char *relevant_source = NULL;
959         icalcomponent *cal, *c;
960
961         relevant_partnum[0] = '\0';
962         sprintf(buf, "MSG4 %ld", msgnum);       /* we need the mime headers */
963         serv_puts(buf);
964         serv_getln(buf, sizeof buf);
965         if (buf[0] != '1') return;
966
967         while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
968                 if (!strncasecmp(buf, "part=", 5)) {
969                         extract_token(mime_filename, &buf[5], 1, '|', sizeof mime_filename);
970                         extract_token(mime_partnum, &buf[5], 2, '|', sizeof mime_partnum);
971                         extract_token(mime_disposition, &buf[5], 3, '|', sizeof mime_disposition);
972                         extract_token(mime_content_type, &buf[5], 4, '|', sizeof mime_content_type);
973                         mime_length = extract_int(&buf[5], 5);
974
975                         if (  (!strcasecmp(mime_content_type, "text/calendar"))
976                               || (!strcasecmp(mime_content_type, "application/ics"))
977                               || (!strcasecmp(mime_content_type, "text/vtodo"))
978                                 ) {
979                                 strcpy(relevant_partnum, mime_partnum);
980                         }
981                 }
982                 else if (!strncasecmp(buf, "from=", 4)) {
983                         extract_token(from, buf, 1, '=', sizeof(from));
984                 }
985         }
986
987         if (!IsEmptyStr(relevant_partnum)) {
988                 relevant_source = load_mimepart(msgnum, relevant_partnum);
989                 if (relevant_source != NULL) {
990
991                         cal = icalcomponent_new_from_string(relevant_source);
992                         if (cal != NULL) {
993
994                                 /* A which_kind of (-1) means just load the whole thing */
995                                 if (which_kind == (-1)) {
996                                         callback(cal, msgnum, from, unread, calv);
997                                 }
998
999                                 /* Otherwise recurse and hunt */
1000                                 else {
1001
1002                                         /* Simple components of desired type */
1003                                         if (icalcomponent_isa(cal) == which_kind) {
1004                                                 callback(cal, msgnum, from, unread, calv);
1005                                         }
1006         
1007                                         /* Subcomponents of desired type */
1008                                         for (c = icalcomponent_get_first_component(cal, which_kind);
1009                                         (c != 0);
1010                                         c = icalcomponent_get_next_component(cal, which_kind)) {
1011                                                 callback(c, msgnum, from, unread, calv);
1012                                         }
1013
1014                                 }
1015
1016                                 icalcomponent_free(cal);
1017                         }
1018                         free(relevant_source);
1019                 }
1020         }
1021         icalmemory_free_ring();
1022 }
1023
1024 /*
1025  * Display a calendar item
1026  */
1027 void load_calendar_item(message_summary *Msg, int unread, struct calview *c) {
1028         /*load_ical_object(Msg->msgnum, unread, ICAL_VEVENT_COMPONENT, display_individual_cal, c);*/
1029         load_ical_object(Msg->msgnum, unread, (-1), display_individual_cal, c);
1030 }
1031
1032 /*
1033  * Display task view
1034  */
1035 void display_task(message_summary *Msg, int unread) {
1036         load_ical_object(Msg->msgnum, unread, ICAL_VTODO_COMPONENT, display_individual_cal, NULL);
1037 }
1038
1039 /*
1040  * Display the editor component for a task
1041  */
1042 void display_edit_task(void) {
1043         long msgnum = 0L;
1044                         
1045         /* Force change the room if we have to */
1046         if (havebstr("taskrm")) {
1047                 gotoroom((char *)bstr("taskrm"));
1048         }
1049
1050         msgnum = lbstr("msgnum");
1051         if (msgnum > 0L) {
1052                 /* existing task */
1053                 load_ical_object(msgnum, 0,
1054                                       ICAL_VTODO_COMPONENT,
1055                                       display_edit_individual_task,
1056                                       NULL
1057                 );
1058         }
1059         else {
1060                 /* new task */
1061                 display_edit_individual_task(NULL, 0L, "", 0, NULL);
1062         }
1063 }
1064
1065 /*
1066  * save an edited task
1067  */
1068 void save_task(void) {
1069         long msgnum = 0L;
1070         msgnum = lbstr("msgnum");
1071         if (msgnum > 0L) {
1072                 load_ical_object(msgnum, 0, ICAL_VTODO_COMPONENT, save_individual_task, NULL);
1073         }
1074         else {
1075                 save_individual_task(NULL, 0L, "", 0, NULL);
1076         }
1077 }
1078
1079 /*
1080  * display the editor component for an event
1081  */
1082 void display_edit_event(void) {
1083         long msgnum = 0L;
1084
1085         msgnum = lbstr("msgnum");
1086         if (msgnum > 0L) {
1087                 /* existing event */
1088                 load_ical_object(msgnum, 0, ICAL_VEVENT_COMPONENT, display_edit_individual_event, NULL);
1089         }
1090         else {
1091                 /* new event */
1092                 display_edit_individual_event(NULL, 0L, "", 0, NULL);
1093         }
1094 }
1095
1096 /*
1097  * save an edited event
1098  */
1099 void save_event(void) {
1100         long msgnum = 0L;
1101
1102         msgnum = lbstr("msgnum");
1103
1104         if (msgnum > 0L) {
1105                 /* load_ical_object(msgnum, 0, ICAL_VEVENT_COMPONENT, save_individual_event, NULL); */
1106                 load_ical_object(msgnum, 0, (-1), save_individual_event, NULL);
1107         }
1108         else {
1109                 save_individual_event(NULL, 0L, "", 0, NULL);
1110         }
1111 }
1112
1113
1114
1115
1116
1117 /*
1118  * Anonymous request of freebusy data for a user
1119  */
1120 void do_freebusy(const char *req) {
1121         char who[SIZ];
1122         char buf[SIZ];
1123         int len;
1124         long lines;
1125
1126         extract_token(who, req, 1, ' ', sizeof who);
1127         if (!strncasecmp(who, "/freebusy/", 10)) {
1128                 strcpy(who, &who[10]);
1129         }
1130         unescape_input(who);
1131
1132         len = strlen(who);
1133         if ( (!strcasecmp(&who[len-4], ".vcf"))
1134              || (!strcasecmp(&who[len-4], ".ifb"))
1135              || (!strcasecmp(&who[len-4], ".vfb")) ) {
1136                 who[len-4] = 0;
1137         }
1138
1139         lprintf(9, "freebusy requested for <%s>\n", who);
1140         serv_printf("ICAL freebusy|%s", who);
1141         serv_getln(buf, sizeof buf);
1142
1143         if (buf[0] != '1') {
1144                 hprintf("HTTP/1.1 404 %s\n", &buf[4]);
1145                 output_headers(0, 0, 0, 0, 0, 0);
1146                 hprintf("Content-Type: text/plain\r\n");
1147                 wprintf("%s\n", &buf[4]);
1148                 end_burst();
1149                 return;
1150         }
1151
1152         read_server_text(WC->WBuf, &lines);
1153         http_transmit_thing("text/calendar", 0);
1154 }
1155
1156
1157
1158
1159
1160 void 
1161 InitModule_CALENDAR
1162 (void)
1163 {
1164         WebcitAddUrlHandler(HKEY("display_edit_task"), display_edit_task, 0);
1165         WebcitAddUrlHandler(HKEY("save_task"), save_task, 0);
1166         WebcitAddUrlHandler(HKEY("display_edit_event"), display_edit_event, 0);
1167         WebcitAddUrlHandler(HKEY("save_event"), save_event, 0);
1168         WebcitAddUrlHandler(HKEY("respond_to_request"), respond_to_request, 0);
1169         WebcitAddUrlHandler(HKEY("handle_rsvp"), handle_rsvp, 0);
1170 }