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