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