]> code.citadel.org Git - citadel.git/blob - webcit/event.c
QP-Encode questionmarks.
[citadel.git] / webcit / event.c
1 /*
2  * $Id$
3  *
4  * Editing calendar events.
5  *
6  * Copyright (c) 1996-2010 by the citadel.org team
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22
23 #include "webcit.h"
24 #include "webserver.h"
25 #include "calendar.h"
26
27 /*
28  * Display an event by itself (for editing)
29  * supplied_vevent      the event to edit
30  * msgnum               reference on the citserver
31  */
32 void display_edit_individual_event(icalcomponent *supplied_vevent, long msgnum, char *from,
33         int unread, calview *calv)
34 {
35         icalcomponent *vevent;
36         icalproperty *p;
37         icalvalue *v;
38         struct icaltimetype t_start, t_end;
39         time_t now;
40         struct tm tm_now;
41         int created_new_vevent = 0;
42         icalproperty *organizer = NULL;
43         char organizer_string[SIZ];
44         icalproperty *attendee = NULL;
45         char attendee_string[SIZ];
46         char buf[SIZ];
47         int organizer_is_me = 0;
48         int i, j = 0;
49 #ifdef DEBUG_UID_CALENDAR
50         int sequence = 0;
51 #endif
52         char weekday_labels[7][32];
53         char month_labels[12][32];
54         long weekstart = 0;
55         icalproperty *rrule = NULL;
56         struct icalrecurrencetype recur;
57         char weekday_is_selected[7];
58         int which_rrmonthtype_is_preselected = 0;
59
60         int rrmday;
61         int rrmweekday;
62
63         icaltimetype day1;
64         int weekbase;
65         int rrmweek;
66         int rrymweek;
67         int rrymweekday;
68         int rrymonth;
69         int which_rrend_is_preselected;
70         int which_rryeartype_is_preselected;
71
72         const char *ch;
73         char *tabnames[3];
74         const char *frequency_units[8];
75         const char *ordinals[6];
76
77         frequency_units[0] = _("seconds");
78         frequency_units[1] = _("minutes");
79         frequency_units[2] = _("hours");
80         frequency_units[3] = _("days");
81         frequency_units[4] = _("weeks");
82         frequency_units[5] = _("months");
83         frequency_units[6] = _("years");
84         frequency_units[7] = _("never");
85
86
87         ordinals[0] = "0";
88         ordinals[1] = _("first");
89         ordinals[2] = _("second");
90         ordinals[3] = _("third");
91         ordinals[4] = _("fourth");
92         ordinals[5] = _("fifth");
93
94         
95         tabnames[0] = _("Event");
96         tabnames[1] = _("Attendees");
97         tabnames[2] = _("Recurrence");
98
99         get_pref_long("weekstart", &weekstart, 17);
100         if (weekstart > 6) weekstart = 0;
101
102         lprintf(9, "display_edit_individual_event(%ld) calview=%s year=%s month=%s day=%s\n",
103                 msgnum, bstr("calview"), bstr("year"), bstr("month"), bstr("day")
104         );
105
106         /* populate the weekday names - begin */
107         now = time(NULL);
108         localtime_r(&now, &tm_now);
109         while (tm_now.tm_wday != 0) {
110                 now -= 86400L;
111                 localtime_r(&now, &tm_now);
112         }
113         for (i=0; i<7; ++i) {
114                 localtime_r(&now, &tm_now);
115                 wc_strftime(weekday_labels[i], 32, "%A", &tm_now);
116                 now += 86400L;
117         }
118         /* populate the weekday names - end */
119
120         /* populate the month names - begin */
121         now = 259200L;  /* 1970-jan-04 is the first Sunday ever */
122         localtime_r(&now, &tm_now);
123         for (i=0; i<12; ++i) {
124                 localtime_r(&now, &tm_now);
125                 wc_strftime(month_labels[i], 32, "%B", &tm_now);
126                 now += 2678400L;
127         }
128         /* populate the month names - end */
129
130         now = time(NULL);
131         strcpy(organizer_string, "");
132         strcpy(attendee_string, "");
133
134         if (supplied_vevent != NULL) {
135                 vevent = supplied_vevent;
136
137                 /* Convert all timestamps to UTC to make them easier to process. */
138                 ical_dezonify(vevent);
139
140                 /*
141                  * If we're looking at a fully encapsulated VCALENDAR
142                  * rather than a VEVENT component, attempt to use the first
143                  * relevant VEVENT subcomponent.  If there is none, the
144                  * NULL returned by icalcomponent_get_first_component() will
145                  * tell the next iteration of this function to create a
146                  * new one.
147                  */
148                 if (icalcomponent_isa(vevent) == ICAL_VCALENDAR_COMPONENT) {
149                         display_edit_individual_event(
150                                 icalcomponent_get_first_component(
151                                         vevent, ICAL_VEVENT_COMPONENT), 
152                                 msgnum, from, unread, NULL
153                         );
154                         return;
155                 }
156         }
157         else {
158                 vevent = icalcomponent_new(ICAL_VEVENT_COMPONENT);
159                 created_new_vevent = 1;
160         }
161
162         /* Learn the sequence */
163         p = icalcomponent_get_first_property(vevent, ICAL_SEQUENCE_PROPERTY);
164 #ifdef DEBUG_UID_CALENDAR
165         if (p != NULL) {
166                 sequence = icalproperty_get_sequence(p);
167         }
168 #endif
169
170         /* Begin output */
171         output_headers(1, 1, 2, 0, 0, 0);
172         wc_printf("<div id=\"banner\">\n");
173         wc_printf("<h1>");
174         wc_printf(_("Add or edit an event"));
175         wc_printf("</h1>");
176         wc_printf("</div>\n");
177
178         wc_printf("<div id=\"content\" class=\"service\">\n");
179
180         wc_printf("<div class=\"fix_scrollbar_bug\">");
181
182 #ifdef DEBUG_UID_CALENDAR
183         /************************************************************
184          * Uncomment this to see the UID in calendar events for debugging
185         *************************************************************/
186         wc_printf("UID == ");
187         p = icalcomponent_get_first_property(vevent, ICAL_UID_PROPERTY);
188         if (p != NULL) {
189                 escputs((char *)icalproperty_get_comment(p));
190         }
191         wc_printf("<br />\n");
192         wc_printf("SEQUENCE == %d<br />\n", sequence);
193 #endif
194
195         wc_printf("<form name=\"EventForm\" method=\"POST\" action=\"save_event\">\n");
196         wc_printf("<input type=\"hidden\" name=\"nonce\" value=\"%d\">\n", WC->nonce);
197
198         wc_printf("<INPUT TYPE=\"hidden\" NAME=\"msgnum\" VALUE=\"%ld\">\n",
199                 msgnum);
200         wc_printf("<INPUT TYPE=\"hidden\" NAME=\"calview\" VALUE=\"%s\">\n",
201                 bstr("calview"));
202         wc_printf("<INPUT TYPE=\"hidden\" NAME=\"year\" VALUE=\"%s\">\n",
203                 bstr("year"));
204         wc_printf("<INPUT TYPE=\"hidden\" NAME=\"month\" VALUE=\"%s\">\n",
205                 bstr("month"));
206         wc_printf("<INPUT TYPE=\"hidden\" NAME=\"day\" VALUE=\"%s\">\n",
207                 bstr("day"));
208
209
210         tabbed_dialog(3, tabnames);
211         begin_tab(0, 3);
212
213         /* Put it in a borderless table so it lines up nicely */
214         wc_printf("<TABLE border=0 width=100%%>\n");
215
216         wc_printf("<TR><TD><B>");
217         wc_printf(_("Summary"));
218         wc_printf("</B></TD><TD>\n"
219                 "<INPUT TYPE=\"text\" NAME=\"summary\" "
220                 "MAXLENGTH=\"64\" SIZE=\"64\" VALUE=\"");
221         p = icalcomponent_get_first_property(vevent, ICAL_SUMMARY_PROPERTY);
222         if (p != NULL) {
223                 escputs((char *)icalproperty_get_comment(p));
224         }
225         wc_printf("\"></TD></TR>\n");
226
227         wc_printf("<TR><TD><B>");
228         wc_printf(_("Location"));
229         wc_printf("</B></TD><TD>\n"
230                 "<INPUT TYPE=\"text\" NAME=\"location\" "
231                 "MAXLENGTH=\"64\" SIZE=\"64\" VALUE=\"");
232         p = icalcomponent_get_first_property(vevent, ICAL_LOCATION_PROPERTY);
233         if (p != NULL) {
234                 escputs((char *)icalproperty_get_comment(p));
235         }
236         wc_printf("\"></TD></TR>\n");
237
238         wc_printf("<TR><TD><B>");
239         wc_printf(_("Start"));
240         wc_printf("</B></TD><TD>\n");
241         p = icalcomponent_get_first_property(vevent, ICAL_DTSTART_PROPERTY);
242         if (p != NULL) {
243                 t_start = icalproperty_get_dtstart(p);
244                 if (t_start.is_date) {
245                         t_start.hour = 0;
246                         t_start.minute = 0;
247                         t_start.second = 0;
248                 }
249         }
250         else {
251                 localtime_r(&now, &tm_now);
252                 if (havebstr("year")) {
253                         tm_now.tm_year = ibstr("year") - 1900;
254                         tm_now.tm_mon = ibstr("month") - 1;
255                         tm_now.tm_mday = ibstr("day");
256                 }
257                 if (havebstr("hour")) {
258                         tm_now.tm_hour = ibstr("hour");
259                         tm_now.tm_min = ibstr("minute");
260                         tm_now.tm_sec = 0;
261                 }
262                 else {
263                         tm_now.tm_hour = 0;
264                         tm_now.tm_min = 0;
265                         tm_now.tm_sec = 0;
266                 }
267
268                 t_start = icaltime_from_timet_with_zone(
269                         mktime(&tm_now),
270                         ((yesbstr("alldayevent")) ? 1 : 0),
271                         icaltimezone_get_utc_timezone()
272                 );
273                 t_start.is_utc = 1;
274
275         }
276         display_icaltimetype_as_webform(&t_start, "dtstart", 0);
277
278         wc_printf("<INPUT TYPE=\"checkbox\" id=\"alldayevent\" NAME=\"alldayevent\" "
279                 "VALUE=\"yes\" onclick=\"eventEditAllDay();\""
280                 " %s >%s",
281                 (t_start.is_date ? "CHECKED=\"CHECKED\"" : "" ),
282                 _("All day event")
283         );
284
285         wc_printf("</TD></TR>\n");
286
287         wc_printf("<TR><TD><B>");
288         wc_printf(_("End"));
289         wc_printf("</B></TD><TD id=\"dtendcell\">\n");
290         p = icalcomponent_get_first_property(vevent,
291                                                 ICAL_DTEND_PROPERTY);
292         if (p != NULL) {
293                 t_end = icalproperty_get_dtend(p);
294                 
295                 /*
296                  * If this is an all-day-event, the end time is set to real end
297                  * day + 1, so we have to adjust accordingly. 
298                  */
299                 if (t_start.is_date) {
300                         icaltime_adjust(&t_end, -1, 0, 0, 0);
301                 }
302         }
303         else {
304                 if (created_new_vevent == 1) {
305                         /* set default duration */
306                         if (t_start.is_date) {
307                                 /*
308                                  * If this is an all-day-event, set the end time to be identical to
309                                  * the start time (the hour/minute/second will be set to midnight).
310                                  */
311                                 t_end = t_start;
312                         }
313                         else {
314                                 /*
315                                  * If this is not an all-day event and there is no
316                                  * end time specified, make the default one hour
317                                  * from the start time.
318                                  */
319                                 t_end = t_start;
320                                 t_end.hour += 1;
321                                 t_end.second = 0;
322                                 t_end = icaltime_normalize(t_end);
323                                 /* t_end = icaltime_from_timet(now, 0); */
324                         }
325                 }
326                 else {
327                         /*
328                          * If an existing event has no end date/time this is
329                          * supposed to mean end = start.
330                          */
331                         t_end = t_start;
332                 }
333         }
334         display_icaltimetype_as_webform(&t_end, "dtend", 0);
335         wc_printf("</TD></TR>\n");
336
337         wc_printf("<TR><TD><B>");
338         wc_printf(_("Notes"));
339         wc_printf("</B></TD><TD>\n"
340                 "<TEXTAREA NAME=\"description\" wrap=soft "
341                 "ROWS=5 COLS=72 WIDTH=72>\n"
342         );
343         p = icalcomponent_get_first_property(vevent, ICAL_DESCRIPTION_PROPERTY);
344         if (p != NULL) {
345                 escputs((char *)icalproperty_get_comment(p));
346         }
347         wc_printf("</TEXTAREA></TD></TR>");
348
349         /*
350          * For a new event, the user creating the event should be the
351          * organizer.  Set this field accordingly.
352          */
353         if (icalcomponent_get_first_property(vevent, ICAL_ORGANIZER_PROPERTY)
354            == NULL) {
355                 sprintf(organizer_string, "MAILTO:%s", ChrPtr(WC->cs_inet_email));
356                 icalcomponent_add_property(vevent,
357                         icalproperty_new_organizer(organizer_string)
358                 );
359         }
360
361         /*
362          * Determine who is the organizer of this event.
363          * We need to determine "me" or "not me."
364          */
365         organizer = icalcomponent_get_first_property(vevent, ICAL_ORGANIZER_PROPERTY);
366         if (organizer != NULL) {
367                 strcpy(organizer_string, icalproperty_get_organizer(organizer));
368                 if (!strncasecmp(organizer_string, "MAILTO:", 7)) {
369                         strcpy(organizer_string, &organizer_string[7]);
370                         striplt(organizer_string);
371                         serv_printf("ISME %s", organizer_string);
372                         serv_getln(buf, sizeof buf);
373                         if (buf[0] == '2') {
374                                 organizer_is_me = 1;
375                         }
376                 }
377         }
378
379         wc_printf("<TR><TD><B>");
380         wc_printf(_("Organizer"));
381         wc_printf("</B></TD><TD>");
382         escputs(organizer_string);
383         if (organizer_is_me) {
384                 wc_printf(" <FONT SIZE=-1><I>");
385                 wc_printf(_("(you are the organizer)"));
386                 wc_printf("</I></FONT>\n");
387         }
388
389         /*
390          * Transmit the organizer as a hidden field.   We don't want the user
391          * to be able to change it, but we do want it fed back to the server,
392          * especially if this is a new event and there is no organizer already
393          * in the calendar object.
394          */
395         wc_printf("<INPUT TYPE=\"hidden\" NAME=\"organizer\" VALUE=\"");
396         escputs(organizer_string);
397         wc_printf("\">");
398
399         wc_printf("</TD></TR>\n");
400
401         /* Transparency */
402         wc_printf("<TR><TD><B>");
403         wc_printf(_("Show time as:"));
404         wc_printf("</B></TD><TD>");
405
406         p = icalcomponent_get_first_property(vevent, ICAL_TRANSP_PROPERTY);
407         if (p == NULL) {
408                 /* No transparency found.  Default to opaque (busy). */
409                 p = icalproperty_new_transp(ICAL_TRANSP_OPAQUE);
410                 if (p != NULL) {
411                         icalcomponent_add_property(vevent, p);
412                 }
413         }
414         if (p != NULL) {
415                 v = icalproperty_get_value(p);
416         }
417         else {
418                 v = NULL;
419         }
420
421         wc_printf("<INPUT TYPE=\"radio\" NAME=\"transp\" VALUE=\"transparent\"");
422         if ((v != NULL) && (icalvalue_get_transp(v) == ICAL_TRANSP_TRANSPARENT)) {
423                 wc_printf(" CHECKED");
424         }
425         wc_printf(">");
426         wc_printf(_("Free"));
427         wc_printf("&nbsp;&nbsp;");
428
429         wc_printf("<INPUT TYPE=\"radio\" NAME=\"transp\" VALUE=\"opaque\"");
430         if ((v != NULL) && (icalvalue_get_transp(v) == ICAL_TRANSP_OPAQUE)) {
431                 wc_printf(" CHECKED");
432         }
433         wc_printf(">");
434         wc_printf(_("Busy"));
435
436         wc_printf("</TD></TR>\n");
437
438
439         /* Done with properties. */
440         wc_printf("</TABLE>\n");
441
442         end_tab(0, 3);
443
444         /* Attendees tab (need to move things here) */
445         begin_tab(1, 3);
446         wc_printf("<TABLE border=0 width=100%%>\n");    /* same table style as the event tab */
447         wc_printf("<TR><TD><B>");
448         wc_printf(_("Attendees"));
449         wc_printf("</B><br />"
450                 "<font size=-2>");
451         wc_printf(_("(One per line)"));
452         wc_printf("</font>\n");
453
454         /* Pop open an address book -- begin */
455         wc_printf(
456                 "&nbsp;<a href=\"javascript:PopOpenAddressBook('attendees_box|%s');\" "
457                 "title=\"%s\">"
458                 "<img align=middle border=0 width=24 height=24 src=\"static/viewcontacts_24x.gif\">"
459                 "</a>",
460                 _("Attendees"),
461                 _("Contacts")
462         );
463         /* Pop open an address book -- end */
464
465         wc_printf("</TD><TD>"
466                 "<TEXTAREA %s NAME=\"attendees\" id=\"attendees_box\" wrap=soft "
467                 "onchange=\"EnableOrDisableCheckButton();\" "
468                 "onKeyPress=\"EnableOrDisableCheckButton();\" "
469                 "ROWS=10 COLS=72 WIDTH=72>\n",
470                 (organizer_is_me ? "" : "DISABLED ")
471         );
472         i = 0;
473         for (attendee = icalcomponent_get_first_property(vevent, ICAL_ATTENDEE_PROPERTY);
474             attendee != NULL;
475             attendee = icalcomponent_get_next_property(vevent, ICAL_ATTENDEE_PROPERTY)) {
476
477                 ch = icalproperty_get_attendee(attendee);
478                 if ((ch != NULL) && !strncasecmp(ch, "MAILTO:", 7)) {
479
480                         /* screen name or email address */
481                         safestrncpy(attendee_string, ch + 7, sizeof(attendee_string));
482                         striplt(attendee_string);
483                         if (i++) wc_printf("\n");
484                         escputs(attendee_string);
485                         wc_printf(" ");
486
487                         /* participant status */
488                         partstat_as_string(buf, attendee);
489                         escputs(buf);
490                 }
491         }
492         wc_printf("</TEXTAREA></TD></TR>\n");
493         wc_printf("</TABLE>\n");
494         end_tab(1, 3);
495
496         /* Recurrence tab */
497         begin_tab(2, 3);
498
499         rrule = icalcomponent_get_first_property(vevent, ICAL_RRULE_PROPERTY);
500         if (rrule) {
501                 recur = icalproperty_get_rrule(rrule);
502         }
503         else {
504                 /* blank recurrence with some sensible defaults */
505                 memset(&recur, 0, sizeof(struct icalrecurrencetype));
506                 recur.count = 3;
507                 recur.until = icaltime_null_time();
508                 recur.interval = 1;
509                 recur.freq = ICAL_WEEKLY_RECURRENCE;
510         }
511
512         wc_printf("<INPUT TYPE=\"checkbox\" id=\"is_recur\" NAME=\"is_recur\" "
513                 "VALUE=\"yes\" "
514                 "onclick=\"RecurrenceShowHide();\""
515                 " %s >%s",
516                 (rrule ? "CHECKED=\"CHECKED\"" : "" ),
517                 _("This is a recurring event")
518         );
519
520         wc_printf("<div id=\"rrule_div\">\n");          /* begin 'rrule_div' div */
521
522         wc_printf("<table border=0 cellspacing=\"10\" width=100%%>\n");
523
524         wc_printf("<tr><td><b>");
525         wc_printf(_("Recurrence rule"));
526         wc_printf("</b></td><td>");
527
528         if ((recur.freq < 0) || (recur.freq > 6)) recur.freq = 4;
529         wc_printf("%s ", _("Repeats every"));
530
531         wc_printf("<input type=\"text\" name=\"interval\" maxlength=\"3\" size=\"3\" ");
532         wc_printf("value=\"%d\">&nbsp;", recur.interval);
533
534         wc_printf("<select name=\"freq\" id=\"freq_selector\" size=\"1\" "
535                 "onChange=\"RecurrenceShowHide();\">\n");
536         for (i=0; i<(sizeof frequency_units / sizeof(char *)); ++i) {
537                 wc_printf("<option %s%svalue=\"%d\">%s</option>\n",
538                         ((i == recur.freq) ? "selected " : ""),
539                         (((i == recur.freq) || ((i>=3)&&(i<=6))) ? "" : "disabled "),
540                         i,
541                         frequency_units[i]
542                 );
543         }
544         wc_printf("</select>\n");
545
546         wc_printf("<div id=\"weekday_selector\">");     /* begin 'weekday_selector' div */
547         wc_printf("%s<br>", _("on these weekdays:"));
548
549         memset(weekday_is_selected, 0, 7);
550
551         for (i=0; i<ICAL_BY_DAY_SIZE; ++i) {
552                 if (recur.by_day[i] == ICAL_RECURRENCE_ARRAY_MAX) {
553                         i = ICAL_RECURRENCE_ARRAY_MAX;                  /* all done */
554                 }
555                 else {
556                         for (j=0; j<7; ++j) {
557                                 if (icalrecurrencetype_day_day_of_week(recur.by_day[i]) == j+1) {
558                                         weekday_is_selected[j] = 1;
559                                 }
560                         }
561                 }
562         }
563
564         for (j=0; j<7; ++j) {
565                 i = ((j + (int)weekstart) % 7);
566                 wc_printf("<input type=\"checkbox\" name=\"weekday%d\" value=\"yes\"", i);
567                 if (weekday_is_selected[i]) wc_printf(" checked");
568                 wc_printf(">%s\n", weekday_labels[i]);
569         }
570         wc_printf("</div>\n");                          /* end 'weekday_selector' div */
571
572
573
574
575
576         wc_printf("<div id=\"monthday_selector\">");    /* begin 'monthday_selector' div */
577
578         wc_printf("<input type=\"radio\" name=\"rrmonthtype\" id=\"rrmonthtype_mday\" "
579                 "value=\"rrmonthtype_mday\" "
580                 "%s onChange=\"RecurrenceShowHide();\">",
581                 ((which_rrmonthtype_is_preselected == 0) ? "checked" : "")
582         );
583
584         rrmday = t_start.day;
585         rrmweekday = icaltime_day_of_week(t_start) - 1;
586
587         /* Figure out what week of the month we're in */
588         day1 = t_start;
589         day1.day = 1;
590         weekbase = icaltime_week_number(day1);
591         rrmweek = icaltime_week_number(t_start) - weekbase + 1;
592
593         /* Are we going by day of the month or week/day? */
594
595         if (recur.by_month_day[0] != ICAL_RECURRENCE_ARRAY_MAX) {
596                 which_rrmonthtype_is_preselected = 0;
597                 rrmday = recur.by_month_day[0];
598         }
599         else if (recur.by_day[0] != ICAL_RECURRENCE_ARRAY_MAX) {
600                 which_rrmonthtype_is_preselected = 1;
601                 rrmweek = icalrecurrencetype_day_position(recur.by_day[0]);
602                 rrmweekday = icalrecurrencetype_day_day_of_week(recur.by_day[0]) - 1;
603         }
604
605         wc_printf(_("on day %s%d%s of the month"), "<span id=\"rrmday\">", rrmday, "</span>");
606         wc_printf("<br />\n");
607
608         wc_printf("<input type=\"radio\" name=\"rrmonthtype\" id=\"rrmonthtype_wday\" "
609                 "value=\"rrmonthtype_wday\" "
610                 "%s onChange=\"RecurrenceShowHide();\">",
611                 ((which_rrmonthtype_is_preselected == 1) ? "checked" : "")
612         );
613
614         wc_printf(_("on the "));
615         wc_printf("<select name=\"rrmweek\" id=\"rrmweek\" size=\"1\" "
616                 "onChange=\"RecurrenceShowHide();\">\n");
617         for (i=1; i<=5; ++i) {
618                 wc_printf("<option %svalue=\"%d\">%s</option>\n",
619                         ((i==rrmweek) ? "selected " : ""),
620                         i,
621                         ordinals[i]
622                 );
623         }
624         wc_printf("</select> \n");
625
626         wc_printf("<select name=\"rrmweekday\" id=\"rrmweekday\" size=\"1\" "
627                 "onChange=\"RecurrenceShowHide();\">\n");
628         for (j=0; j<7; ++j) {
629                 i = ((j + (int)weekstart) % 7);
630                 wc_printf("<option %svalue=\"%d\">%s</option>\n",
631                         ((i==rrmweekday) ? "selected " : ""),
632                         i,
633                         weekday_labels[i]
634                 );
635         }
636         wc_printf("</select>");
637
638         wc_printf(" %s<br />\n", _("of the month"));
639
640         wc_printf("</div>\n");                          /* end 'monthday_selector' div */
641
642
643         rrymweek = rrmweek;
644         rrymweekday = rrmweekday;
645         rrymonth = t_start.month;
646         which_rryeartype_is_preselected = 0;
647
648         if (
649                 (recur.by_day[0] != ICAL_RECURRENCE_ARRAY_MAX) 
650                 && (recur.by_day[0] != 0) 
651                 && (recur.by_month[0] != ICAL_RECURRENCE_ARRAY_MAX)
652                 && (recur.by_month[0] != 0)
653         ) {
654                 which_rryeartype_is_preselected = 1;
655                 rrymweek = icalrecurrencetype_day_position(recur.by_day[0]);
656                 rrymweekday = icalrecurrencetype_day_day_of_week(recur.by_day[0]) - 1;
657                 rrymonth = recur.by_month[0];
658         }
659
660         wc_printf("<div id=\"yearday_selector\">");     /* begin 'yearday_selector' div */
661
662         wc_printf("<input type=\"radio\" name=\"rryeartype\" id=\"rryeartype_ymday\" "
663                 "value=\"rryeartype_ymday\" "
664                 "%s onChange=\"RecurrenceShowHide();\">",
665                 ((which_rryeartype_is_preselected == 0) ? "checked" : "")
666         );
667         wc_printf(_("every "));
668         wc_printf("<span id=\"ymday\">%s</span><br />", _("year on this date"));
669
670         wc_printf("<input type=\"radio\" name=\"rryeartype\" id=\"rryeartype_ywday\" "
671                 "value=\"rryeartype_ywday\" "
672                 "%s onChange=\"RecurrenceShowHide();\">",
673                 ((which_rryeartype_is_preselected == 1) ? "checked" : "")
674         );
675
676         wc_printf(_("on the "));
677         wc_printf("<select name=\"rrymweek\" id=\"rrymweek\" size=\"1\" "
678                 "onChange=\"RecurrenceShowHide();\">\n");
679         for (i=1; i<=5; ++i) {
680                 wc_printf("<option %svalue=\"%d\">%s</option>\n",
681                         ((i==rrymweek) ? "selected " : ""),
682                         i,
683                         ordinals[i]
684                 );
685         }
686         wc_printf("</select> \n");
687
688         wc_printf("<select name=\"rrymweekday\" id=\"rrymweekday\" size=\"1\" "
689                 "onChange=\"RecurrenceShowHide();\">\n");
690         for (j=0; j<7; ++j) {
691                 i = ((j + (int)weekstart) % 7);
692                 wc_printf("<option %svalue=\"%d\">%s</option>\n",
693                         ((i==rrymweekday) ? "selected " : ""),
694                         i,
695                         weekday_labels[i]
696                 );
697         }
698         wc_printf("</select>");
699
700         wc_printf(" %s ", _("of"));
701
702         wc_printf("<select name=\"rrymonth\" id=\"rrymonth\" size=\"1\" "
703                 "onChange=\"RecurrenceShowHide();\">\n");
704         for (i=1; i<=12; ++i) {
705                 wc_printf("<option %svalue=\"%d\">%s</option>\n",
706                         ((i==rrymonth) ? "selected " : ""),
707                         i,
708                         month_labels[i-1]
709                 );
710         }
711         wc_printf("</select>");
712         wc_printf("<br />\n");
713
714         wc_printf("</div>\n");                          /* end 'yearday_selector' div */
715
716         wc_printf("</td></tr>\n");
717
718
719         which_rrend_is_preselected = 0;
720         if (!icaltime_is_null_time(recur.until)) which_rrend_is_preselected = 2;
721         if (recur.count > 0) which_rrend_is_preselected = 1;
722
723         wc_printf("<tr><td><b>");
724         wc_printf(_("Recurrence range"));
725         wc_printf("</b></td><td>\n");
726
727         wc_printf("<input type=\"radio\" name=\"rrend\" id=\"rrend_none\" "
728                 "value=\"rrend_none\" "
729                 "%s onChange=\"RecurrenceShowHide();\">",
730                 ((which_rrend_is_preselected == 0) ? "checked" : "")
731         );
732         wc_printf("%s<br />\n", _("No ending date"));
733
734         wc_printf("<input type=\"radio\" name=\"rrend\" id=\"rrend_count\" "
735                 "value=\"rrend_count\" "
736                 "%s onChange=\"RecurrenceShowHide();\">",
737                 ((which_rrend_is_preselected == 1) ? "checked" : "")
738         );
739         wc_printf(_("Repeat this event"));
740         wc_printf(" <input type=\"text\" name=\"rrcount\" id=\"rrcount\" maxlength=\"3\" size=\"3\" ");
741         wc_printf("value=\"%d\"> ", recur.count);
742         wc_printf(_("times"));
743         wc_printf("<br />\n");
744
745         wc_printf("<input type=\"radio\" name=\"rrend\" id=\"rrend_until\" "
746                 "value=\"rrend_until\" "
747                 "%s onChange=\"RecurrenceShowHide();\">",
748                 ((which_rrend_is_preselected == 2) ? "checked" : "")
749         );
750         wc_printf(_("Repeat this event until "));
751
752         if (icaltime_is_null_time(recur.until)) {
753                 recur.until = icaltime_add(t_start, icaldurationtype_from_int(604800));
754         }
755         display_icaltimetype_as_webform(&recur.until, "rruntil", 1);
756         wc_printf("<br />\n");
757
758         wc_printf("</td></tr>\n");
759
760         wc_printf("</table>\n");
761         wc_printf("</div>\n");                          /* end 'rrule' div */
762
763         end_tab(2, 3);
764
765         /* submit buttons (common area beneath the tabs) */
766         begin_tab(3, 3);
767         wc_printf("<CENTER>"
768                 "<INPUT TYPE=\"submit\" NAME=\"save_button\" VALUE=\"%s\">"
769                 "&nbsp;&nbsp;"
770                 "<INPUT TYPE=\"submit\" NAME=\"delete_button\" VALUE=\"%s\">\n"
771                 "&nbsp;&nbsp;"
772                 "<INPUT TYPE=\"submit\" id=\"check_button\" NAME=\"check_button\" VALUE=\"%s\">\n"
773                 "&nbsp;&nbsp;"
774                 "<INPUT TYPE=\"submit\" NAME=\"cancel_button\" VALUE=\"%s\">\n"
775                 "</CENTER>\n",
776                 _("Save"),
777                 _("Delete"),
778                 _("Check attendee availability"),
779                 _("Cancel")
780         );
781         wc_printf("</FORM>\n");
782         end_tab(3, 3);
783
784         wc_printf("</div>\n");                  /* end 'fix_scrollbar_bug' div */
785
786         StrBufAppendPrintf(WC->trailing_javascript,
787                 "eventEditAllDay();             \n"
788                 "RecurrenceShowHide();          \n"
789                 "EnableOrDisableCheckButton();  \n"
790         );
791         address_book_popup();
792         wDumpContent(1);
793
794         if (created_new_vevent) {
795                 icalcomponent_free(vevent);
796         }
797 }
798
799 /*
800  * Save an edited event
801  *
802  * supplied_vevent:     the event to save
803  * msgnum:              the index on the citserver
804  */
805 void save_individual_event(icalcomponent *supplied_vevent, long msgnum, char *from,
806                         int unread, calview *calv) {
807         char buf[SIZ];
808         icalproperty *prop;
809         icalcomponent *vevent, *encaps;
810         int created_new_vevent = 0;
811         int all_day_event = 0;
812         struct icaltimetype event_start, t;
813         icalproperty *attendee = NULL;
814         char attendee_string[SIZ];
815         int i, j;
816         int foundit;
817         char form_attendees[SIZ];
818         char organizer_string[SIZ];
819         int sequence = 0;
820         enum icalproperty_transp formtransp = ICAL_TRANSP_NONE;
821         const char *ch;
822
823         if (supplied_vevent != NULL) {
824                 vevent = supplied_vevent;
825
826                 /* Convert all timestamps to UTC to make them easier to process. */
827                 ical_dezonify(vevent);
828
829                 /*
830                  * If we're looking at a fully encapsulated VCALENDAR
831                  * rather than a VEVENT component, attempt to use the first
832                  * relevant VEVENT subcomponent.  If there is none, the
833                  * NULL returned by icalcomponent_get_first_component() will
834                  * tell the next iteration of this function to create a
835                  * new one.
836                  */
837                 if (icalcomponent_isa(vevent) == ICAL_VCALENDAR_COMPONENT) {
838                         save_individual_event(
839                                 icalcomponent_get_first_component(
840                                         vevent, ICAL_VEVENT_COMPONENT), 
841                                 msgnum, from, unread, NULL
842                         );
843                         return;
844                 }
845         }
846         else {
847                 vevent = icalcomponent_new(ICAL_VEVENT_COMPONENT);
848                 created_new_vevent = 1;
849         }
850
851         if ( (havebstr("save_button"))
852            || (havebstr("check_button")) ) {
853
854                 /* Replace values in the component with ones from the form */
855
856                 while (prop = icalcomponent_get_first_property(vevent,
857                       ICAL_SUMMARY_PROPERTY), prop != NULL) {
858                         icalcomponent_remove_property(vevent, prop);
859                         icalproperty_free(prop);
860                 }
861
862                 /* Add NOW() to the calendar object... */
863                 icalcomponent_set_dtstamp(vevent, 
864                                           icaltime_from_timet(
865                                                   time(NULL), 0));
866
867                 if (havebstr("summary")) {
868                         icalcomponent_add_property(vevent,
869                                         icalproperty_new_summary(bstr("summary")));
870                 } else {
871                         icalcomponent_add_property(vevent,
872                                         icalproperty_new_summary(_("Untitled Event")));
873                 }
874         
875                 while (prop = icalcomponent_get_first_property(vevent,
876                                         ICAL_LOCATION_PROPERTY), prop != NULL) {
877                         icalcomponent_remove_property(vevent, prop);
878                         icalproperty_free(prop);
879                 }
880                 if (havebstr("location")) {
881                         icalcomponent_add_property(vevent,
882                                         icalproperty_new_location(bstr("location")));
883                 }
884                 while (prop = icalcomponent_get_first_property(vevent,
885                                   ICAL_DESCRIPTION_PROPERTY), prop != NULL) {
886                         icalcomponent_remove_property(vevent, prop);
887                         icalproperty_free(prop);
888                 }
889                 if (havebstr("description")) {
890                         icalcomponent_add_property(vevent,
891                                 icalproperty_new_description(bstr("description")));
892                 }
893
894                 while (prop = icalcomponent_get_first_property(vevent,
895                       ICAL_DTSTART_PROPERTY), prop != NULL) {
896                         icalcomponent_remove_property(vevent, prop);
897                         icalproperty_free(prop);
898                 }
899
900                 if (yesbstr("alldayevent")) {
901                         all_day_event = 1;
902                 }
903                 else {
904                         all_day_event = 0;
905                 }
906
907                 if (all_day_event) {
908                         icaltime_from_webform_dateonly(&event_start, "dtstart");
909                 }
910                 else {
911                         icaltime_from_webform(&event_start, "dtstart");
912                 }
913
914                 prop = icalproperty_new_dtstart(event_start);
915
916                 if (all_day_event) {
917                         /* Force it to serialize as a date-only rather than date/time */
918                         icalproperty_set_value(prop, icalvalue_new_date(event_start));
919                 }
920
921                 if (prop) icalcomponent_add_property(vevent, prop);
922                 else icalproperty_free(prop);
923
924                 while (prop = icalcomponent_get_first_property(vevent,
925                       ICAL_DTEND_PROPERTY), prop != NULL) {
926                         icalcomponent_remove_property(vevent, prop);
927                         icalproperty_free(prop);
928                 }
929                 while (prop = icalcomponent_get_first_property(vevent,
930                       ICAL_DURATION_PROPERTY), prop != NULL) {
931                         icalcomponent_remove_property(vevent, prop);
932                         icalproperty_free(prop);
933                 }
934
935                 if (all_day_event) {
936                         icaltime_from_webform_dateonly(&t, "dtend");    
937
938                         /* with this field supposed to be non-inclusive we have to add one day */
939                         icaltime_adjust(&t, 1, 0, 0, 0);
940                 }
941                 else {
942                         icaltime_from_webform(&t, "dtend");     
943                 }
944
945                 icalcomponent_add_property(vevent,
946                         icalproperty_new_dtend(icaltime_normalize(t)
947                         )
948                 );
949
950                 /* recurrence rules -- begin */
951
952                 /* remove any existing rule */
953                 while (prop = icalcomponent_get_first_property(vevent, ICAL_RRULE_PROPERTY), prop != NULL) {
954                         icalcomponent_remove_property(vevent, prop);
955                         icalproperty_free(prop);
956                 }
957
958                 if (yesbstr("is_recur")) {
959                         struct icalrecurrencetype recur;
960                         icalrecurrencetype_clear(&recur);
961
962                         recur.interval = atoi(bstr("interval"));
963                         recur.freq = atoi(bstr("freq"));
964
965                         switch(recur.freq) {
966
967                                 /* These can't happen; they're disabled. */
968                                 case ICAL_SECONDLY_RECURRENCE:
969                                         break;
970                                 case ICAL_MINUTELY_RECURRENCE:
971                                         break;
972                                 case ICAL_HOURLY_RECURRENCE:
973                                         break;
974
975                                 /* Daily is valid but there are no further inputs. */
976                                 case ICAL_DAILY_RECURRENCE:
977                                         break;
978
979                                 /* These are the real options. */
980
981                                 case ICAL_WEEKLY_RECURRENCE:
982                                         j=0;
983                                         for (i=0; i<7; ++i) {
984                                                 snprintf(buf, sizeof buf, "weekday%d", i);
985                                                 if (YESBSTR(buf)) recur.by_day[j++] =
986                                                         icalrecurrencetype_day_day_of_week(i+1);
987                                         }
988                                         recur.by_day[j] = ICAL_RECURRENCE_ARRAY_MAX;
989                                         break;
990
991                                 case ICAL_MONTHLY_RECURRENCE:
992                                         if (!strcasecmp(bstr("rrmonthtype"), "rrmonthtype_mday")) {
993                                                 recur.by_month_day[0] = event_start.day;
994                                                 recur.by_month_day[1] = ICAL_RECURRENCE_ARRAY_MAX;
995                                         }
996                                         else if (!strcasecmp(bstr("rrmonthtype"), "rrmonthtype_wday")) {
997                                                 recur.by_day[0] = (atoi(bstr("rrmweek")) * 8)
998                                                                 + atoi(bstr("rrmweekday")) + 1;
999                                                 recur.by_day[1] = ICAL_RECURRENCE_ARRAY_MAX;
1000                                         }
1001                                         break;
1002
1003                                 case ICAL_YEARLY_RECURRENCE:
1004                                         if (!strcasecmp(bstr("rryeartype"), "rryeartype_ymday")) {
1005                                                 /* no further action is needed here */
1006                                         }
1007                                         else if (!strcasecmp(bstr("rryeartype"), "rryeartype_ywday")) {
1008                                                 recur.by_month[0] = atoi(bstr("rrymonth"));
1009                                                 recur.by_month[1] = ICAL_RECURRENCE_ARRAY_MAX;
1010                                                 recur.by_day[0] = (atoi(bstr("rrymweek")) * 8)
1011                                                                 + atoi(bstr("rrymweekday")) + 1;
1012                                                 recur.by_day[1] = ICAL_RECURRENCE_ARRAY_MAX;
1013                                         }
1014                                         break;
1015
1016                                 /* This one can't happen either. */
1017                                 case ICAL_NO_RECURRENCE:
1018                                         break;
1019                         }
1020
1021                         if (!strcasecmp(bstr("rrend"), "rrend_count")) {
1022                                 recur.count = atoi(bstr("rrcount"));
1023                         }
1024                         else if (!strcasecmp(bstr("rrend"), "rrend_until")) {
1025                                 icaltime_from_webform_dateonly(&recur.until, "rruntil");
1026                         }
1027
1028                         icalcomponent_add_property(vevent, icalproperty_new_rrule(recur));
1029                 }
1030
1031                 /* recurrence rules -- end */
1032
1033                 /* See if transparency is indicated */
1034                 if (havebstr("transp")) {
1035                         if (!strcasecmp(bstr("transp"), "opaque")) {
1036                                 formtransp = ICAL_TRANSP_OPAQUE;
1037                         }
1038                         else if (!strcasecmp(bstr("transp"), "transparent")) {
1039                                 formtransp = ICAL_TRANSP_TRANSPARENT;
1040                         }
1041
1042                         while (prop = icalcomponent_get_first_property(vevent, ICAL_TRANSP_PROPERTY),
1043                               (prop != NULL)) {
1044                                 icalcomponent_remove_property(vevent, prop);
1045                                 icalproperty_free(prop);
1046                         }
1047
1048                         icalcomponent_add_property(vevent, icalproperty_new_transp(formtransp));
1049                 }
1050
1051                 /* Give this event a UID if it doesn't have one. */
1052                 if (icalcomponent_get_first_property(vevent,
1053                    ICAL_UID_PROPERTY) == NULL) {
1054                         generate_uuid(buf);
1055                         icalcomponent_add_property(vevent, icalproperty_new_uid(buf));
1056                 }
1057
1058                 /* Increment the sequence ID */
1059                 while (prop = icalcomponent_get_first_property(vevent,
1060                       ICAL_SEQUENCE_PROPERTY), (prop != NULL) ) {
1061                         i = icalproperty_get_sequence(prop);
1062                         if (i > sequence) sequence = i;
1063                         icalcomponent_remove_property(vevent, prop);
1064                         icalproperty_free(prop);
1065                 }
1066                 ++sequence;
1067                 icalcomponent_add_property(vevent,
1068                         icalproperty_new_sequence(sequence)
1069                 );
1070                 
1071                 /*
1072                  * Set the organizer, only if one does not already exist *and*
1073                  * the form is supplying one
1074                  */
1075                 strcpy(buf, bstr("organizer"));
1076                 if ( (icalcomponent_get_first_property(vevent,
1077                    ICAL_ORGANIZER_PROPERTY) == NULL) 
1078                    && (!IsEmptyStr(buf)) ) {
1079
1080                         /* set new organizer */
1081                         sprintf(organizer_string, "MAILTO:%s", buf);
1082                         icalcomponent_add_property(vevent,
1083                                 icalproperty_new_organizer(organizer_string)
1084                         );
1085
1086                 }
1087
1088                 /*
1089                  * Add any new attendees listed in the web form
1090                  */
1091
1092                 /* First, strip out the parenthesized partstats.  */
1093                 strcpy(form_attendees, bstr("attendees"));
1094                 while ( stripout(form_attendees, '(', ')') != 0);
1095
1096                 /* Next, change any commas to newlines, because we want newline-separated attendees. */
1097                 j = strlen(form_attendees);
1098                 for (i=0; i<j; ++i) {
1099                         if (form_attendees[i] == ',') {
1100                                 form_attendees[i] = '\n';
1101                                 while (isspace(form_attendees[i+1])) {
1102                                         strcpy(&form_attendees[i+1], &form_attendees[i+2]);
1103                                 }
1104                         }
1105                 }
1106
1107                 /* Now iterate! */
1108                 for (i=0; i<num_tokens(form_attendees, '\n'); ++i) {
1109                         extract_token(buf, form_attendees, i, '\n', sizeof buf);
1110                         striplt(buf);
1111                         if (!IsEmptyStr(buf)) {
1112                                 sprintf(attendee_string, "MAILTO:%s", buf);
1113                                 foundit = 0;
1114
1115                                 for (attendee = icalcomponent_get_first_property(vevent, ICAL_ATTENDEE_PROPERTY); attendee != NULL; attendee = icalcomponent_get_next_property(vevent, ICAL_ATTENDEE_PROPERTY)) {
1116                                         ch = icalproperty_get_attendee(attendee);
1117                                         if ((ch != NULL) && !strcasecmp(attendee_string, ch))
1118                                                 ++foundit;
1119                                 }
1120
1121
1122                                 if (foundit == 0) {
1123                                         icalcomponent_add_property(vevent,
1124                                                                    icalproperty_new_attendee(attendee_string)
1125                                         );
1126                                 }
1127                         }
1128                 }
1129
1130                 /*
1131                  * Remove any attendees *not* listed in the web form
1132                  */
1133 STARTOVER:      for (attendee = icalcomponent_get_first_property(vevent, ICAL_ATTENDEE_PROPERTY); attendee != NULL; attendee = icalcomponent_get_next_property(vevent, ICAL_ATTENDEE_PROPERTY)) {
1134                         ch = icalproperty_get_attendee(attendee);
1135                         if ((ch != NULL) && !strncasecmp(ch, "MAILTO:", 7)) {
1136                                 safestrncpy(attendee_string, ch + 7, sizeof(attendee_string));
1137                                 striplt(attendee_string);
1138                                 foundit = 0;
1139                                 for (i=0; i<num_tokens(form_attendees, '\n'); ++i) {
1140                                         extract_token(buf, form_attendees, i, '\n', sizeof buf);
1141                                         striplt(buf);
1142                                         if (!strcasecmp(buf, attendee_string)) ++foundit;
1143                                 }
1144                                 if (foundit == 0) {
1145                                         icalcomponent_remove_property(vevent, attendee);
1146                                         icalproperty_free(attendee);
1147                                         goto STARTOVER;
1148                                 }
1149                         }
1150                 }
1151
1152                 /*
1153                  * Encapsulate event into full VCALENDAR component.  Clone it first,
1154                  * for two reasons: one, it's easier to just free the whole thing
1155                  * when we're done instead of unbundling, but more importantly, we
1156                  * can't encapsulate something that may already be encapsulated
1157                  * somewhere else.
1158                  */
1159                 encaps = ical_encapsulate_subcomponent(icalcomponent_new_clone(vevent));
1160
1161                 /* Set the method to PUBLISH */
1162                 icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
1163
1164                 /* If the user clicked 'Save' then save it to the server. */
1165                 if ( (encaps != NULL) && (havebstr("save_button")) ) {
1166                         serv_puts("ENT0 1|||4|||1|");
1167                         serv_getln(buf, sizeof buf);
1168                         if (buf[0] == '8') {
1169                                 serv_puts("Content-type: text/calendar");
1170                                 serv_puts("");
1171                                 serv_puts(icalcomponent_as_ical_string(encaps));
1172                                 serv_puts("000");
1173                         }
1174                         if ( (buf[0] == '8') || (buf[0] == '4') ) {
1175                                 while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
1176                                 }
1177                         }
1178                         if (buf[0] == '2') {
1179                                 strcpy(WC->ImportantMessage, &buf[4]);
1180                         }
1181                         icalmemory_free_ring ();
1182                         icalcomponent_free(encaps);
1183                         encaps = NULL;
1184                 }
1185
1186                 /* Or, check attendee availability if the user asked for that. */
1187                 if ( (encaps != NULL) && (havebstr("check_button")) ) {
1188
1189                         /* Call this function, which does the real work */
1190                         check_attendee_availability(encaps);
1191
1192                         /* This displays the form again, with our annotations */
1193                         display_edit_individual_event(encaps, msgnum, from, unread, NULL);
1194
1195                         icalcomponent_free(encaps);
1196                         encaps = NULL;
1197                 }
1198                 if (encaps != NULL) {
1199                         icalcomponent_free(encaps);
1200                         encaps = NULL;
1201                 }
1202
1203         }
1204
1205         /*
1206          * If the user clicked 'Delete' then delete it.
1207          */
1208         if ( (havebstr("delete_button")) && (msgnum > 0L) ) {
1209                 serv_printf("DELE %ld", lbstr("msgnum"));
1210                 serv_getln(buf, sizeof buf);
1211         }
1212
1213         if (created_new_vevent) {
1214                 icalcomponent_free(vevent);
1215         }
1216
1217         /* If this was a save or delete, go back to the calendar or summary view. */
1218         if (!havebstr("check_button")) {
1219                 if (!strcasecmp(bstr("calview"), "summary")) {
1220                         summary();
1221                 }
1222                 else {
1223                         readloop(readfwd, eUseDefault);
1224                 }
1225         }
1226 }