1<?php
2/*
3 * XXX
4 */
5
6class CalenDoku_CalendarComponent {
7    var $text;
8    var $type;
9    var $invalid;
10    var $iscalendoku = false;
11    var $icalfields = array();
12    var $properties = array();
13    var $renderfields = array();
14
15    function __construct($plugin, $text) {
16        if (!preg_match('/^BEGIN:VEVENT/', $text))
17            return;
18        $text = preg_replace('/^BEGIN:VEVENT/', '', $text);
19        if (!preg_match('/END:VEVENT$/', $text))
20            return;
21        $text = preg_replace('/END:VEVENT$/', '', $text);
22
23        $this->iscalendoku = true;
24        $this->plugin = $plugin;
25        $this->explodeText($text);
26        $this->invalid = false;
27        $this->fillRendered();
28    }
29
30    function explodeText($text) {
31        $textlines = preg_split('/\n/', $text);
32        $curfield = '';
33
34        foreach ($textlines as $line) {
35            if (preg_match('/^ /', $line)) {
36                $line = preg_replace('/^ /', '', $line);
37                if (!strcmp($curfield, ''))
38                    continue;
39                $this->icalfields[$curfield] = $this->icalfields[$curfield]."\n".$line;
40            } else {
41                $pregretval = preg_split('/:/', $line, 2);
42                $curfield = $pregretval[0];
43                $value = $pregretval[1];
44                $properties = preg_split('/;/', $curfield);
45                $curfield = $properties[0];
46                if (count($properties) > 1)
47                    for ($i = 1; $i < count($properties); $i++) {
48                        $propretval = preg_split('/=/', $properties[$i]);
49                        $this->properties[$curfield][$propretval[0]] = $propretval[1];
50                    }
51
52                if (strcmp($this->icalfields[$curfield], ''))
53                    continue;
54                $this->icalfields[$curfield] = $value;
55            }
56        }
57    }
58
59    function parseDateTime($datestr, $tzstring) {
60        if ($this->dttype == 0) {
61            $datetime = preg_split('/T/', $datestr, 2);
62            if (count($datetime) > 1) {
63                $this->dttype = 2;
64            } else {
65                $this->dttype = 1;
66            }
67        }
68        $datetime = preg_split('/T/', $datestr, 2);
69        $year = substr($datetime[0], 0, 4);
70        $month = substr($datetime[0], 4, 2);
71        $day = substr($datetime[0], 6, 2);
72        if ($this->dttype == 2) {
73            $hour = substr($datetime[1], 0, 2);
74            $minute = substr($datetime[1], 2, 2);
75            $second = substr($datetime[1], 4, 2);
76        } else {
77            $hour = 0;
78            $minute = 0;
79            $second = 0;
80        }
81
82        $date = new DateTime();
83        $date->setDate(intval($year), intval($month), intval($day));
84        $date->setTime(intval($hour), intval($minute), intval($second));
85
86        if (intval($date->format('U')) < 86400) {
87            $this->invalid = true;
88            return new DateTime();
89        }
90
91
92        /*
93         * Wow, this is annoying!
94         * PHP doesn't have proper support for date/time objects. The timezone
95         * settings are totally confused (you cannot ask a DateTimeZone object
96         * for its offset, but only a DateTime object) and important
97         * functionality is missing (subtracting time and getting a new DateTime
98         * object). Thus, this strange tango here...
99         *
100         * A DateTime object will always have its time set to UTC. If you set a
101         * timezone for the DateTime object, then it will be treated to have the
102         * offset of this timezone. So if you have a time given in a certain
103         * timezone and want to translate it to another one, you have to convert
104         * it to seconds since epoch, generate another object which has the
105         * timezone set, get the offset from it, subtract that, then generate a
106         * new object with these seconds set.
107         *
108         * For time with just any timezone set, we have to translate it back to
109         * UTC by setting the timezone to UTC, taking seconds, subtracting the
110         * offset from the local timezone, then convert it back.
111         */
112        if (strlen($this->plugin->getConf('defaulttimezone')) > 0) {
113            $mytz = new DateTimeZone($this->plugin->getConf('defaulttimezone'));
114        } else {
115            $mytz = new DateTimeZone(date_default_timezone_get());
116        }
117        $utctz = new DateTimeZone('UTC');
118        if ($this->dttype > 1) {
119            if ($this->plugin->getConf('showlocaltime')) {
120                $zpos = strpos($datestr, 'Z');
121                if ($zpos !== false) {
122                    $date->setTimeZone($mytz);
123                } else if (strlen($tzstring) == 0) {
124                    $date->setTimeZone($utctz);
125                    $datesecs = intval($date->format('U'));
126                    $date->setTimeZone($mytz);
127                    $datesecs = strval($datesecs - intval($date->getOffset()));
128                    $date = DateTime::createFromFormat('U', $datesecs);
129                    $date->setTimeZone($mytz);
130                } else {
131                    $date->setTimeZone($mytz);
132                    $datesecs = strval($date->format('U')) + intval($date->getOffset());
133                    $date = DateTime::createFromFormat('U', $datesecs);
134                    $date->setTimeZone($mytz);
135                }
136            } else if (strlen($tzstring) == 0) {
137                $date->setTimeZone($utctz);
138                $datesecs = intval($date->format('U'));
139                $date->setTimeZone($mytz);
140                $datesecs = strval($datesecs - intval($date->getOffset()));
141                $date = DateTime::createFromFormat('U', $datesecs);
142                $date->setTimeZone($mytz);
143            } else {
144                $date->setTimeZone($mytz);
145                $datesecs = strval($date->format('U')) + intval($date->getOffset());
146                $date = DateTime::createFromFormat('U', $datesecs);
147                $date->setTimeZone($mytz);
148            }
149        } else {
150            $date->setTimeZone($mytz);
151            $datesecs = strval($date->format('U')) + intval($date->getOffset());
152            $date = DateTime::createFromFormat('U', $datesecs);
153            $date->setTimeZone($mytz);
154        }
155
156        return $date;
157    }
158
159    function renderDateTime($date) {
160        if ($this->dttype > 1) {
161            return $date->format($this->plugin->getConf('datetimeformat'));
162        }
163        return $date->format($this->plugin->getConf('dateformat'));
164    }
165
166    function checkStartEndType() {
167        if (isset($this->properties['DTSTART']['VALUE'])) {
168            if (!strcmp($this->properties['DTSTART']['VALUE'], 'DATE')) {
169                $this->dttype = 1;
170            } else if (!strcmp($this->properties['DTSTART']['VALUE'], 'DATETIME')) {
171                $this->dttype = 2;
172            }
173        } else if (isset($this->properties['DTEND']['VALUE'])) {
174            if (!strcmp($this->properties['DTEND']['VALUE'], 'DATE')) {
175                $this->dttype = 1;
176            } else if (!strcmp($this->properties['DTSTART']['VALUE'], 'DATETIME')) {
177                $this->dttype = 2;
178            }
179        } else {
180            $this->dttype = 0;
181        }
182    }
183
184    function renderDTSTART() {
185        if (isset($this->icalfields['DTSTART'])) {
186            $this->dtstart = $this->parseDateTime($this->icalfields['DTSTART'], $this->properties['DTSTART']['TZID']);
187            $this->dtstartint = intval($this->dtstart->format('U'));
188            $this->renderfields['DTSTART'] = $this->renderDateTime($this->dtstart);
189        } else {
190            $this->dtstartint = 0;
191            $this->renderfields['DTSTART'] = '';
192        }
193    }
194
195    function renderDTEND() {
196        if (isset($this->icalfields['DTEND'])) {
197            $this->dtend = $this->parseDateTime($this->icalfields['DTEND'], $this->properties['DTEND']['TZID']);
198            $this->dtendint = intval($this->dtend->format('U'));
199            $this->renderfields['DTEND'] = $this->renderDateTime($this->dtend);
200        } else {
201            $this->dtendint = 0;
202            $this->renderfields['DTEND'] = '';
203        }
204    }
205
206    function renderSTRING($field) {
207        $this->renderfields[$field] = $this->icalfields[$field];
208    }
209
210    // XXX
211    function renderMULTISTRING($field, $desc) {
212        if (isset($this->icalfields[$field]))
213            $this->renderfields[$field] = $this->icalfields[$field];
214        else
215            $this->renderfields[$field] = '';
216    }
217
218    // XXX
219    /*
220     * geo        = "GEO" geoparam ":" geovalue CRLF
221     * geoparam   = *(";" other-param)
222     * geovalue   = float ";" float
223     * ;Latitude and Longitude components
224     */
225    function renderGEO() {
226        $geostr = preg_split('/;/', $this->icalfields['GEO']);
227        $this->renderfields['LATITUDE'] = floatval($geostr[0]);
228        $this->renderfields['LONGITUDE'] = floatval($geostr[1]);
229    }
230
231    function renderINT() {
232        $this->renderfields[$field] = $this->icalfields[$field];
233    }
234
235    function renderCALADDRESS($field) {
236        $this->renderfields[$field] = $this->icalfields[$field];
237    }
238
239    function fillRendered() {
240        /* DTSTART/DTEND value */
241        $this->checkStartEndType();
242        $this->renderDTSTART();
243        $this->renderDTEND();
244
245        foreach($this->icalfields as $field => $value) {
246            /* DTSTART */
247            if (!strcmp($field, 'DTSTART'))
248                continue;
249            /* DTEND */
250            else if (!strcmp($field, 'DTEND'))
251                continue;
252            /* CATEGORIES */
253            else if (!strcmp($field, 'CATEGORIES'))
254                $this->renderMULTISTRING('CATEGORIES', 'CATEGORY');
255            /* GEO */
256            else if (!strcmp($field, 'GEO'))
257                $this->renderGEO();
258            else if (!strcmp($field, 'PRIORITY'))
259                $this->renderINT('PRIORITY');
260            else if (!strcmp($field, 'ATTENDEE'))
261                $this->renderCALADDRESS('ATTENDEE');
262            else if (!strcmp($field, 'ORGANIZER'))
263                $this->renderCALADDRESS('ORGANIZER');
264            /* SUMMARY
265             * DESCRIPTION
266             * LOCATION
267             * COMMENT
268             * CONTACT
269             * ORGANIZER
270             * UID
271             * CLASS
272             * STATUS
273             * RESOURCES
274             * RELATED-TO
275             * URL. We render it as a string and leave it up to the user to define
276             * a template which makes it a link.
277             */
278            else
279                $this->renderSTRING($field);
280            // XXX: GEO
281        }
282    }
283
284    function isWithinTime($startdate, $enddate) {
285        if (!strcmp(($enddate->format('U')), '0'))
286            $enddate = $this->dtstart;
287
288        if ($this->dtstartint > intval($enddate->format('U'))
289            || intval($startdate->format('U')) > $this->dtendint)
290            return false;
291        return true;
292    }
293}
294
295class CalenDoku_CalendarObject {
296    function __construct($plugin, $page) {
297        $text = preg_replace('/^[\s\n]*BEGIN:VCALENDAR\n(.*)\nEND:VCALENDAR[\n\s]*$/s', '\1', $page, -1, $count);
298        if ($count < 1)
299            return;
300
301        $this->iscalendoku = true;
302        $this->plugin = $plugin;
303        $this->separateComponents($text);
304    }
305
306    function separateComponents($text) {
307        /* Get properties of the calendar. */
308        // XXX
309
310        /* Get the calendar components. */
311        $this->components = array();
312        if (preg_match_all('/BEGIN:VEVENT\n.*?END:VEVENT/s', $text, $vevents)) {
313            foreach ($vevents as $vevent) {
314                $component = new CalenDoku_CalendarComponent($this->plugin, $vevent[0]);
315                array_push($this->components, $component);
316            }
317        }
318        /* XXX: VTODO, VJOURNAL, VFREEBUSY, VTIMEZONE, VALARM. */
319    }
320}
321
322function reHandleText($text, $pos, $handler) {
323    $instructions = p_get_instructions($text);
324    foreach ($instructions as $instruction)
325        $handler->_addCall($instruction[0],
326            $instruction[1] ? $instruction[1] : array(),
327            $pos);
328}
329
330/**
331 * Guess the time format. We try several date and time formats which are common
332 * and take the first one that fits.
333 *
334 * If we have less than eight numbers, we assume it is a Unix timestamp (with
335 * eight numbers, we might hit YYYYMMDD). This means, we won't be able to serve
336 * dates before Sat Mar  3 10:46:40 CET 1973. We allow no dates with two-digit
337 * years. En exception is there for a '0', which is diretly parsed to epoch.
338 *
339 * Then, we try dates this order:
340 *   1. European date               DD.MM.YYYY
341 *   2. European date w/o year      DD.MM.
342 *   3. Technical date              YYYY-MM-DD
343 *   4. Technical date w/o day      YYYY-MM
344 *   5. Terror date                 YYYYMMDD
345 *   6. Year only                   YYYY
346 * Since American dates are ambiguous, they are not checked.
347 *
348 * Now check for the time:
349 *   1. 24h time w/o seconds        hh:mm
350 *   2. 24h time                    hh:mm:ss
351 *   3. Terror time                 hhmmss
352 *
353 * Returns an DateTime object filled with the date from the argument.
354 */
355function guessDateTime($datestr) {
356    $rexu = '(0|[0-9]{9,})';
357    $rexy = '([0-9]{4})';
358    $rexm = '(0[1-9]|1[12])';
359    $rexd = '([0-2][0-9]|3[01])';
360    $rexH = '([0-1][0-9]|2[0-3])';
361    $rexM = '([0-5][0-9])';
362    $rexS = '([0-5][0-9]|60)';
363    $rexsep = '(?=[T\s]*)';
364    $rexend = '.*?';
365
366    /* %s */
367    if (preg_match('/^'.$rexu.$rexsep.'$/', $datestr)) {
368        $date = DateTime::createFromFormat('U', $datestr);
369        return $date;
370    }
371
372    $year = date('Y');
373    $month = date('m');
374    $day = date('d');
375    /* DD.MM.YYYY */
376    if (preg_match('/^'.$rexd.'\.'.$rexm.'\.'.$rexy.$rexsep.'/', $datestr, $match)) {
377        $datestr = preg_replace('/^'.$rexd.'\.'.$rexm.'\.'.$rexy.$rexsep.'/', '', $datestr);
378        $day = $match[1];
379        $month = $match[2];
380        $year = $match[3];
381    /* DD.MM. */
382    } else if (preg_match('/^'.$rexd.'\.'.$rexm.$rexsep.'/', $datestr, $match)) {
383        $datestr = preg_replace('/^'.$rexd.'\.'.$rexm.$rexsep.'/', '', $datestr);
384        $year = date('Y');
385        $month = $match[2];
386        $day = $match[1];
387    /* YYYY-MM-DD */
388    } else if (preg_match('/^'.$rexy.'-'.$rexm.'-'.$rexd.$rexsep.'/', $datestr, $match)) {
389        $datestr = preg_replace('/^'.$rexy.'-'.$rexm.'-'.$rexd.$rexsep.'/', '', $datestr);
390        $year = $match[1];
391        $month = $match[2];
392        $day = $match[3];
393    /* YYYY-MM */
394    } else if (preg_match('/^'.$rexy.'-'.$rexm.$rexsep.'/', $datestr, $match)) {
395        $datestr = preg_replace('/^'.$rexy.'-'.$rexm.$rexsep.'/', '', $datestr);
396        $year = $match[1];
397        $month = $match[2];
398        $day = '1';
399    /* YYYYMMDD */
400    } else if (preg_match('/^'.$rexy.$rexm.$rexd.$rexsep.'/', $datestr, $match)) {
401        $datestr = preg_replace('/^'.$rexy.$rexm.$rexd.$rexsep.'/', '', $datestr);
402        $year = $match[1];
403        $month = $match[2];
404        $day = $match[3];
405    } else if (preg_match('/^'.$rexy.$rexsep.'/', $datestr, $match)) {
406        $datestr = preg_replace('/^'.$rexy.$rexsep.'/', '', $datestr);
407        $year = $match[1];
408        $month = '1';
409        $day = '1';
410    }
411
412    $hour = '0';
413    $mins = '0';
414    $secs = '0';
415    /* hh:mm */
416    if (preg_match('/^'.$rexH.':'.$rexM.$rexend.'$/', $datestr, $match)) {
417        $hour = $match[1];
418        $mins = $match[2];
419        $secs = '0';
420    /* hh:mm:ss */
421    } else if (preg_match('/^'.$rexH.':'.$rexM.':'.$rexS.$rexend.'$/', $datestr, $match)) {
422        $hour = $match[1];
423        $mins = $match[2];
424        $secs = $match[3];
425    /* hhmmss */
426    } else if (preg_match('/^'.$rexH.$rexM.$rexS.$rexend.'$/', $datestr, $match)) {
427        $hour = $match[1];
428        $mins = $match[2];
429        $secs = $match[3];
430    }
431
432    $date = new DateTime();
433    $date->setDate(intval($year), intval($month), intval($day));
434    $date->setTime(intval($hour), intval($mins), intval($secs));
435
436    return $date;
437}
438//Setup VIM: ex: et ts=4 enc=utf-8 :
439