1: <?php
2: 3: 4: 5:
6:
7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
20: class Horde_Icalendar
21: {
22: 23: 24: 25: 26:
27: public $type = 'vcalendar';
28:
29: 30: 31: 32: 33:
34: protected $_container = false;
35:
36: 37: 38: 39: 40: 41: 42:
43: protected $_attributes = array();
44:
45: 46: 47: 48: 49:
50: protected $_components = array();
51:
52: 53: 54: 55: 56:
57: protected $_newline = "\r\n";
58:
59: 60: 61: 62: 63: 64:
65: protected $_version;
66:
67: 68: 69: 70: 71: 72: 73:
74: protected $_oldFormat = true;
75:
76: 77: 78: 79: 80:
81: public function __construct($version = '2.0')
82: {
83: $this->setAttribute('VERSION', $version);
84: }
85:
86: 87: 88: 89: 90: 91: 92: 93: 94:
95: static public function newComponent($type, $container)
96: {
97: $type = Horde_String::lower($type);
98: $class = __CLASS__ . '_' . Horde_String::ucfirst($type);
99:
100: if (class_exists($class)) {
101: $component = new $class();
102: if ($container !== false) {
103: $component->_container = $container;
104:
105:
106: $component->setVersion($container->getAttribute('VERSION'));
107: }
108: } else {
109:
110: $component = false;
111: }
112:
113: return $component;
114: }
115:
116: 117: 118: 119: 120: 121: 122: 123:
124: public function setVersion($version)
125: {
126: $this->_oldFormat = $version < 2;
127: $this->_version = $version;
128: }
129:
130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142:
143: public function setAttribute($name, $value, $params = array(),
144: $append = true, $values = false)
145: {
146:
147:
148: if ($name == 'VERSION') {
149: $this->setVersion($value);
150: if ($this->_container !== false) {
151: $this->_container->setVersion($value);
152: }
153: }
154:
155: if (!$values) {
156: $values = array($value);
157: }
158: $found = false;
159:
160: if (!$append) {
161: foreach (array_keys($this->_attributes) as $key) {
162: if ($this->_attributes[$key]['name'] == Horde_String::upper($name)) {
163: $this->_attributes[$key]['params'] = $params;
164: $this->_attributes[$key]['value'] = $value;
165: $this->_attributes[$key]['values'] = $values;
166: $found = true;
167: break;
168: }
169: }
170: }
171:
172: if ($append || !$found) {
173: $this->_attributes[] = array(
174: 'name' => Horde_String::upper($name),
175: 'params' => $params,
176: 'value' => $value,
177: 'values' => $values
178: );
179: }
180: }
181:
182: 183: 184: 185: 186: 187: 188: 189: 190: 191:
192: public function setParameter($name, $params = array())
193: {
194: $keys = array_keys($this->_attributes);
195: foreach ($keys as $key) {
196: if ($this->_attributes[$key]['name'] == $name) {
197: $this->_attributes[$key]['params'] = array_merge($this->_attributes[$key]['params'], $params);
198: return true;
199: }
200: }
201:
202: return false;
203: }
204:
205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216:
217: public function getAttribute($name, $params = false)
218: {
219: if ($name == 'VERSION') {
220: return $this->_version;
221: }
222:
223: $result = array();
224: foreach ($this->_attributes as $attribute) {
225: if ($attribute['name'] == $name) {
226: $result[] = $params
227: ? $attribute['params']
228: : $attribute['value'];
229: }
230: }
231:
232: if (!count($result)) {
233: throw new Horde_Icalendar_Exception('Attribute "' . $name . '" Not Found');
234: } elseif (count($result) == 1 && !$params) {
235: return $result[0];
236: }
237:
238: return $result;
239: }
240:
241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255:
256: public function getAttributeValues($name)
257: {
258: $result = array();
259: foreach ($this->_attributes as $attribute) {
260: if ($attribute['name'] == $name) {
261: $result = array_merge($attribute['values'], $result);
262: }
263: }
264:
265: if (!count($result)) {
266: throw new Horde_Icalendar_Exception('Attribute "' . $name . '" Not Found');
267: }
268:
269: return $result;
270: }
271:
272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282:
283: public function getAttributeDefault($name, $default = '')
284: {
285: try {
286: return $this->getAttribute($name);
287: } catch (Horde_Icalendar_Exception $e) {
288: return $default;
289: }
290: }
291:
292: 293: 294: 295: 296:
297: public function removeAttribute($name)
298: {
299: foreach (array_keys($this->_attributes) as $key) {
300: if ($this->_attributes[$key]['name'] == $name) {
301: unset($this->_attributes[$key]);
302: }
303: }
304: }
305:
306: 307: 308: 309: 310: 311: 312: 313:
314: public function getAllAttributes($tag = false)
315: {
316: if ($tag === false) {
317: return $this->_attributes;
318: }
319:
320: $result = array();
321: foreach ($this->_attributes as $attribute) {
322: if ($attribute['name'] == $tag) {
323: $result[] = $attribute;
324: }
325: }
326:
327: return $result;
328: }
329:
330: 331: 332: 333: 334: 335:
336: public function addComponent($components)
337: {
338: if (!is_array($components)) {
339: $components = array($components);
340: }
341:
342: foreach ($components as $component) {
343: if ($component instanceof Horde_Icalendar) {
344: $component->_container = $this;
345: $this->_components[] = $component;
346: }
347: }
348: }
349:
350: 351: 352: 353: 354:
355: public function getComponents()
356: {
357: return $this->_components;
358: }
359:
360: 361: 362: 363: 364:
365: public function getType()
366: {
367: return $this->type;
368: }
369:
370: 371: 372: 373: 374: 375:
376: public function getComponentClasses()
377: {
378: $r = array();
379:
380: foreach ($this->_components as $c) {
381: $cn = strtolower(get_class($c));
382: if (empty($r[$cn])) {
383: $r[$cn] = 1;
384: } else {
385: ++$r[$cn];
386: }
387: }
388:
389: return $r;
390: }
391:
392: 393: 394: 395: 396:
397: public function getComponentCount()
398: {
399: return count($this->_components);
400: }
401:
402: 403: 404: 405: 406: 407: 408: 409:
410: public function getComponent($idx)
411: {
412: return isset($this->_components[$idx])
413: ? $this->_components[$idx]
414: : false;
415: }
416:
417: 418: 419: 420: 421: 422: 423: 424: 425: 426:
427: public function findComponent($childclass)
428: {
429: $childclass = __CLASS__ . '_' . Horde_String::lower($childclass);
430:
431: foreach (array_keys($this->_components) as $key) {
432: if ($this->_components[$key] instanceof $childclass) {
433: return $this->_components[$key];
434: }
435: }
436:
437: return false;
438: }
439:
440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452:
453: public function findComponentByAttribute($childclass, $attribute,
454: $value = null)
455: {
456: $childclass = __CLASS__ . '_' . Horde_String::lower($childclass);
457:
458: foreach (array_keys($this->_components) as $key) {
459: if ($this->_components[$key] instanceof $childclass) {
460: try {
461: $attr = $this->_components[$key]->getAttribute($attribute);
462: } catch (Horde_Icalendar_Exception $e) {
463: continue;
464: }
465:
466: if (is_null($value) || $value == $attr) {
467: return $this->_components[$key];
468: }
469: }
470: }
471:
472: return false;
473: }
474:
475: 476: 477: 478:
479: public function clear()
480: {
481: $this->_attributes = $this->_components = array();
482: }
483:
484: 485: 486: 487: 488:
489: public function exportvCalendar()
490: {
491:
492:
493: $requiredAttributes['PRODID'] = '-//The Horde Project//Horde iCalendar Library' . (defined('HORDE_VERSION') ? ', Horde ' . constant('HORDE_VERSION') : '') . '//EN';
494: $requiredAttributes['METHOD'] = 'PUBLISH';
495:
496: foreach ($requiredAttributes as $name => $default_value) {
497: try {
498: $this->getAttribute($name);
499: } catch (Horde_Icalendar_Exception $e) {
500: $this->setAttribute($name, $default_value);
501: }
502: }
503:
504: return $this->_exportvData('VCALENDAR');
505: }
506:
507: 508: 509: 510: 511: 512: 513: 514: 515: 516: 517: 518: 519: 520: 521: 522:
523: public function toHash($paramsInKeys = false)
524: {
525: $hash = array();
526:
527: foreach ($this->_attributes as $a) {
528: $k = $a['name'];
529: if ($paramsInKeys && is_array($a['params'])) {
530: foreach ($a['params'] as $p => $v) {
531: $k .= ";$p=$v";
532: }
533: }
534: $hash[$k] = $a['value'];
535: }
536:
537: return $hash;
538: }
539:
540: 541: 542: 543: 544: 545: 546: 547: 548: 549: 550: 551:
552: public function parsevCalendar($text, $base = 'VCALENDAR', $clear = true)
553: {
554: if ($clear) {
555: $this->clear();
556: }
557:
558: $text = Horde_String::trimUtf8Bom($text);
559:
560: if (preg_match('/^BEGIN:' . $base . '(.*)^END:' . $base . '/ism', $text, $matches)) {
561: $container = true;
562: $vCal = $matches[1];
563: } else {
564:
565:
566: $container = false;
567: $vCal = $text;
568: }
569: $vCal = trim($vCal);
570:
571:
572: $matches = $components = null;
573: if (preg_match_all('/^BEGIN:(.*)\s*?(\r\n|\r|\n)(.*)^END:\1\s*?/Uims', $vCal, $components)) {
574: foreach ($components[0] as $key => $data) {
575:
576: $vCal = str_replace($data, '', $vCal);
577: }
578: } elseif (!$container) {
579: return false;
580: }
581:
582:
583:
584:
585:
586: while (preg_match_all('/^([^:]+;\s*(ENCODING=)?QUOTED-PRINTABLE(.*=\r?\n)+(.*[^=])?(\r?\n|$))/mU', $vCal, $matches)) {
587: foreach ($matches[1] as $s) {
588: $r = preg_replace('/=\r?\n/', '', $s);
589: $vCal = str_replace($s, $r, $vCal);
590: }
591: }
592:
593:
594: $vCal = preg_replace('/[\r\n]+[ \t]/', '', $vCal);
595:
596:
597: if (preg_match_all('/^((?:[^":]+|(?:"[^"]*")+)*):([^\r\n]*)\r?$/m', $vCal, $matches)) {
598: foreach ($matches[0] as $attribute) {
599: preg_match('/([^;^:]*)((;(?:[^":]+|(?:"[^"]*")+)*)?):([^\r\n]*)[\r\n]*/', $attribute, $parts);
600: $tag = trim(Horde_String::upper($parts[1]));
601: $value = $parts[4];
602: $params = array();
603:
604:
605: if (!empty($parts[2])) {
606: preg_match_all('/;(([^;=]*)(=("[^"]*"|[^;]*))?)/', $parts[2], $param_parts);
607: foreach ($param_parts[2] as $key => $paramName) {
608: $paramName = Horde_String::upper($paramName);
609: $paramValue = $param_parts[4][$key];
610: if ($paramName == 'TYPE') {
611: $paramValue = preg_split('/(?<!\\\\),/', $paramValue);
612: if (count($paramValue) == 1) {
613: $paramValue = $paramValue[0];
614: }
615: }
616: if (is_string($paramValue)) {
617: if (preg_match('/"([^"]*)"/', $paramValue, $parts)) {
618: $paramValue = $parts[1];
619: }
620: } else {
621: foreach ($paramValue as $k => $tmp) {
622: if (preg_match('/"([^"]*)"/', $tmp, $parts)) {
623: $paramValue[$k] = $parts[1];
624: }
625: }
626: }
627: $params[$paramName] = $paramValue;
628: }
629: }
630:
631:
632: if ((isset($params['ENCODING']) &&
633: Horde_String::upper($params['ENCODING']) == 'QUOTED-PRINTABLE') ||
634: isset($params['QUOTED-PRINTABLE'])) {
635:
636: $value = quoted_printable_decode($value);
637: if (isset($params['CHARSET'])) {
638: $value = Horde_String::convertCharset($value, $params['CHARSET'], 'UTF-8');
639: }
640: } elseif (isset($params['CHARSET'])) {
641: $value = Horde_String::convertCharset($value, $params['CHARSET'], 'UTF-8');
642: }
643:
644:
645: $tzid = isset($params['TZID']) ? trim($params['TZID'], '\"') : false;
646:
647: switch ($tag) {
648:
649: case 'COMPLETED':
650: case 'CREATED':
651: case 'LAST-MODIFIED':
652: case 'X-MOZ-LASTACK':
653: case 'X-MOZ-SNOOZE-TIME':
654: $this->setAttribute($tag, $this->_parseDateTime($value, $tzid), $params);
655: break;
656:
657: case 'BDAY':
658: case 'X-ANNIVERSARY':
659: $this->setAttribute($tag, $this->_parseDate($value), $params);
660: break;
661:
662: case 'DTEND':
663: case 'DTSTART':
664: case 'DTSTAMP':
665: case 'DUE':
666: case 'AALARM':
667: case 'RECURRENCE-ID':
668:
669:
670: $ts = explode(';', $value);
671: if (isset($params['VALUE']) && $params['VALUE'] == 'DATE') {
672: $this->setAttribute($tag, $this->_parseDate($ts[0]), $params);
673: } else {
674: $this->setAttribute($tag, $this->_parseDateTime($ts[0], $tzid), $params);
675: }
676: break;
677:
678: case 'TRIGGER':
679: if (isset($params['VALUE']) &&
680: $params['VALUE'] == 'DATE-TIME') {
681: $this->setAttribute($tag, $this->_parseDateTime($value, $tzid), $params);
682: } else {
683: $this->setAttribute($tag, $this->_parseDuration($value), $params);
684: }
685: break;
686:
687:
688: case 'EXDATE':
689: case 'RDATE':
690: if (!strlen($value)) {
691: break;
692: }
693: $dates = array();
694: $separator = $this->_oldFormat ? ';' : ',';
695: preg_match_all('/' . $separator . '([^' . $separator . ']*)/', $separator . $value, $values);
696:
697: foreach ($values[1] as $value) {
698: $stamp = $this->_parseDateTime($value);
699: $dates[] = array('year' => date('Y', $stamp),
700: 'month' => date('m', $stamp),
701: 'mday' => date('d', $stamp));
702: }
703: $this->setAttribute($tag, isset($dates[0]) ? $dates[0] : null, $params, true, $dates);
704: break;
705:
706:
707: case 'DURATION':
708: $this->setAttribute($tag, $this->_parseDuration($value), $params);
709: break;
710:
711:
712: case 'FREEBUSY':
713: $periods = array();
714: preg_match_all('/,([^,]*)/', ',' . $value, $values);
715: foreach ($values[1] as $value) {
716: $periods[] = $this->_parsePeriod($value);
717: }
718:
719: $this->setAttribute($tag, isset($periods[0]) ? $periods[0] : null, $params, true, $periods);
720: break;
721:
722:
723: case 'TZOFFSETFROM':
724: case 'TZOFFSETTO':
725: $this->setAttribute($tag, $this->_parseUtcOffset($value), $params);
726: break;
727:
728:
729: case 'PERCENT-COMPLETE':
730: case 'PRIORITY':
731: case 'REPEAT':
732: case 'SEQUENCE':
733: $this->setAttribute($tag, intval($value), $params);
734: break;
735:
736:
737: case 'GEO':
738: if ($value) {
739: if ($this->_oldFormat) {
740: $floats = explode(',', $value);
741: $value = array('latitude' => floatval($floats[1]),
742: 'longitude' => floatval($floats[0]));
743: } else {
744: $floats = explode(';', $value);
745: $value = array('latitude' => floatval($floats[0]),
746: 'longitude' => floatval($floats[1]));
747: }
748: }
749: $this->setAttribute($tag, $value, $params);
750: break;
751:
752:
753: case 'EXRULE':
754: case 'RRULE':
755: $this->setAttribute($tag, trim($value), $params);
756: break;
757:
758:
759:
760: case 'ADR':
761: case 'N':
762: case 'ORG':
763: $value = trim($value);
764:
765:
766: $value = str_replace(array('\\n', '\\N', '\\;', '\\:'),
767: array($this->_newline, $this->_newline, ';', ':'),
768: $value);
769:
770:
771: $values = preg_split('/(?<!\\\\);/', $value);
772: $value = str_replace('\\;', ';', $value);
773: $values = str_replace('\\;', ';', $values);
774: $this->setAttribute($tag, trim($value), $params, true, $values);
775: break;
776:
777:
778: default:
779: if ($this->_oldFormat) {
780:
781:
782: $value = trim($value);
783:
784: $values = preg_split('/(?<!\\\\);/', $value);
785: $value = str_replace('\\;', ';', $value);
786: $values = str_replace('\\;', ';', $values);
787: $this->setAttribute($tag, trim($value), $params, true, $values);
788: } else {
789: $value = trim($value);
790:
791:
792:
793: $value = str_replace(array('\\n', '\\N', '\\;', '\\:', '\\\\'),
794: array($this->_newline, $this->_newline, ';', ':', '\\'),
795: $value);
796:
797:
798: $values = preg_split('/(?<!\\\\),/', $value);
799: $value = str_replace('\\,', ',', $value);
800: $values = str_replace('\\,', ',', $values);
801:
802: $this->setAttribute($tag, trim($value), $params, true, $values);
803: }
804: break;
805: }
806: }
807: }
808:
809:
810: if ($components) {
811:
812:
813: foreach ($components[0] as $key => $data) {
814: $type = trim($components[1][$key]);
815: if ($type != 'VTIMEZONE') {
816: continue;
817: }
818: $component = $this->newComponent($type, $this);
819: if ($component === false) {
820: throw new Horde_Icalendar_Exception('Unable to create object for type ' . $type);
821: }
822: $component->parsevCalendar($data, $type);
823:
824: $this->addComponent($component);
825:
826:
827: $vCal = str_replace($data, '', $vCal);
828: }
829:
830:
831: foreach ($components[0] as $key => $data) {
832: $type = trim($components[1][$key]);
833: if ($type == 'VTIMEZONE') {
834: continue;
835: }
836: $component = $this->newComponent($type, $this);
837: if ($component === false) {
838: throw new Horde_Icalendar_Exception('Unable to create object for type ' . $type);
839: }
840: $component->parsevCalendar($data, $type);
841:
842: $this->addComponent($component);
843: }
844: }
845:
846: return true;
847: }
848:
849: 850: 851: 852: 853: 854: 855:
856: protected function _exportvData($base = 'VCALENDAR')
857: {
858: $result = 'BEGIN:' . Horde_String::upper($base) . $this->_newline;
859:
860:
861:
862: if ($base !== 'VEVENT' && $base !== 'VTODO' && $base !== 'VALARM' &&
863: $base !== 'VJOURNAL' && $base !== 'VFREEBUSY' &&
864: $base != 'VTIMEZONE' && $base != 'STANDARD' && $base != 'DAYLIGHT') {
865:
866: $result .= 'VERSION:' . $this->_version . $this->_newline;
867: }
868: foreach ($this->_attributes as $attribute) {
869: $name = $attribute['name'];
870: if ($name == 'VERSION') {
871:
872: continue;
873: }
874:
875: $params_str = '';
876: $params = $attribute['params'];
877: if ($params) {
878: foreach ($params as $param_name => $param_value) {
879:
880: if ($param_name == 'CHARSET' && !$this->_oldFormat) {
881: continue;
882: }
883:
884: if ($this->_oldFormat &&
885: $param_name == 'VALUE' && $param_value == 'DATE') {
886: continue;
887: }
888:
889: if ($param_value === null) {
890: $params_str .= ";$param_name";
891: } else {
892: if (!is_array($param_value)) {
893: $param_value = array($param_value);
894: }
895: foreach ($param_value as &$one_param_value) {
896: $len = strlen($one_param_value);
897: $safe_value = '';
898: $quote = false;
899: for ($i = 0; $i < $len; ++$i) {
900: $ord = ord($one_param_value[$i]);
901:
902: if ($ord == 9 || $ord == 32 || $ord == 33 ||
903: ($ord >= 35 && $ord <= 126) ||
904: $ord >= 128) {
905: $safe_value .= $one_param_value[$i];
906:
907:
908:
909: if ($ord == 44 || $ord == 58 || $ord == 59 ||
910: $ord >= 128) {
911: $quote = true;
912: }
913: }
914: }
915: if ($quote) {
916: $safe_value = '"' . $safe_value . '"';
917: }
918: $one_param_value = $safe_value;
919: }
920: $params_str .= ";$param_name=" . implode(',', $param_value);
921: }
922: }
923: }
924:
925: $value = $attribute['value'];
926: switch ($name) {
927:
928: case 'COMPLETED':
929: case 'CREATED':
930: case 'DCREATED':
931: case 'LAST-MODIFIED':
932: case 'X-MOZ-LASTACK':
933: case 'X-MOZ-SNOOZE-TIME':
934: $value = $this->_exportDateTime($value);
935: break;
936:
937: case 'DTEND':
938: case 'DTSTART':
939: case 'DTSTAMP':
940: case 'DUE':
941: case 'AALARM':
942: case 'RECURRENCE-ID':
943: $floating = $base == 'STANDARD' || $base == 'DAYLIGHT';
944: if (isset($params['VALUE'])) {
945: if ($params['VALUE'] == 'DATE') {
946:
947: if ($this->_oldFormat && $name == 'DTEND') {
948: $d = new Horde_Date($value);
949: $value = new Horde_Date(array(
950: 'year' => $d->year,
951: 'month' => $d->month,
952: 'mday' => $d->mday - 1));
953: $value = $this->_exportDate($value, '235959');
954: } else {
955: $value = $this->_exportDate($value, '000000');
956: }
957: } else {
958: $value = $this->_exportDateTime($value, $floating);
959: }
960: } else {
961: $value = $this->_exportDateTime($value, $floating);
962: }
963: break;
964:
965:
966: case 'EXDATE':
967: case 'RDATE':
968: $floating = $base == 'STANDARD' || $base == 'DAYLIGHT';
969: $dates = array();
970: foreach ($value as $date) {
971: if (isset($params['VALUE'])) {
972: if ($params['VALUE'] == 'DATE') {
973: $dates[] = $this->_exportDate($date, '000000');
974: } elseif ($params['VALUE'] == 'PERIOD') {
975: $dates[] = $this->_exportPeriod($date);
976: } else {
977: $dates[] = $this->_exportDateTime($date, $floating);
978: }
979: } else {
980: $dates[] = $this->_exportDateTime($date, $floating);
981: }
982: }
983: $value = implode($this->_oldFormat ? ';' : ',', $dates);
984: break;
985:
986: case 'TRIGGER':
987: if (isset($params['VALUE'])) {
988: if ($params['VALUE'] == 'DATE-TIME') {
989: $value = $this->_exportDateTime($value);
990: } elseif ($params['VALUE'] == 'DURATION') {
991: $value = $this->_exportDuration($value);
992: }
993: } else {
994: $value = $this->_exportDuration($value);
995: }
996: break;
997:
998:
999: case 'DURATION':
1000: $value = $this->_exportDuration($value);
1001: break;
1002:
1003:
1004: case 'FREEBUSY':
1005: $value_str = '';
1006: foreach ($value as $period) {
1007: $value_str .= empty($value_str) ? '' : ',';
1008: $value_str .= $this->_exportPeriod($period);
1009: }
1010: $value = $value_str;
1011: break;
1012:
1013:
1014: case 'TZOFFSETFROM':
1015: case 'TZOFFSETTO':
1016: $value = $this->_exportUtcOffset($value);
1017: break;
1018:
1019:
1020: case 'PERCENT-COMPLETE':
1021: case 'PRIORITY':
1022: case 'REPEAT':
1023: case 'SEQUENCE':
1024: $value = "$value";
1025: break;
1026:
1027:
1028: case 'GEO':
1029: if ($this->_oldFormat) {
1030: $value = $value['longitude'] . ',' . $value['latitude'];
1031: } else {
1032: $value = $value['latitude'] . ';' . $value['longitude'];
1033: }
1034: break;
1035:
1036:
1037: case 'EXRULE':
1038: case 'RRULE':
1039: break;
1040:
1041: default:
1042: if ($this->_oldFormat) {
1043: if (is_array($attribute['values']) &&
1044: count($attribute['values']) > 1) {
1045: $values = $attribute['values'];
1046: if ($name == 'N' || $name == 'ADR' || $name == 'ORG') {
1047: $glue = ';';
1048: } else {
1049: $glue = ',';
1050: }
1051: $values = str_replace(';', '\\;', $values);
1052: $value = implode($glue, $values);
1053: } else {
1054: 1055:
1056: $value = str_replace(';', '\\;', $value);
1057: }
1058:
1059:
1060:
1061: if (preg_match("/[^\x20-\x7F]/", $value) &&
1062: empty($params['ENCODING'])) {
1063: $params['ENCODING'] = 'QUOTED-PRINTABLE';
1064: $params_str .= ';ENCODING=QUOTED-PRINTABLE';
1065:
1066:
1067: if (empty($params['CHARSET'])) {
1068: $params['CHARSET'] = 'UTF-8';
1069: $params_str .= ';CHARSET=' . $params['CHARSET'];
1070: }
1071: }
1072: } else {
1073: if (is_array($attribute['values']) &&
1074: count($attribute['values'])) {
1075: $values = $attribute['values'];
1076: if ($name == 'N' || $name == 'ADR' || $name == 'ORG') {
1077: $glue = ';';
1078: } else {
1079: $glue = ',';
1080: }
1081:
1082:
1083: $values = str_replace(array('\\', ';', ','),
1084: array('\\\\', '\\;', '\\,'),
1085: $values);
1086: $value = implode($glue, $values);
1087: } else {
1088:
1089:
1090: $value = str_replace(array('\\', ';', ','),
1091: array('\\\\', '\\;', '\\,'),
1092: $value);
1093: }
1094: $value = preg_replace('/\r?\n/', '\n', $value);
1095: }
1096: break;
1097: }
1098:
1099: $value = str_replace("\r", '', $value);
1100: if (!empty($params['ENCODING']) &&
1101: $params['ENCODING'] == 'QUOTED-PRINTABLE' &&
1102: strlen(trim($value))) {
1103: $result .= $name . $params_str . ':'
1104: . preg_replace(array('/(?<!\r)\n/', '/(?<!=)\r\n/'),
1105: array("\r\n", "=0D=0A=\r\n "),
1106: Horde_Mime::quotedPrintableEncode($value))
1107: . $this->_newline;
1108: } else {
1109: $attr_string = $name . $params_str . ':' . $value;
1110: if (!$this->_oldFormat) {
1111: if (isset($params['ENCODING']) && $params['ENCODING'] == 'b') {
1112: $attr_string = chunk_split($attr_string, 75, $this->_newline . ' ');
1113: } else {
1114: $attr_string = Horde_String::wordwrap($attr_string, 75, $this->_newline . ' ', true, true);
1115: }
1116: }
1117: $result .= $attr_string . $this->_newline;
1118: }
1119: }
1120:
1121: foreach ($this->_components as $component) {
1122: $result .= $component->exportvCalendar();
1123: }
1124:
1125: return $result . 'END:' . $base . $this->_newline;
1126: }
1127:
1128: 1129: 1130: 1131: 1132: 1133: 1134:
1135: protected function _parseUtcOffset($text)
1136: {
1137: $offset = array();
1138:
1139: if (preg_match('/(\+|-)([0-9]{2})([0-9]{2})([0-9]{2})?/', $text, $timeParts)) {
1140: $offset['ahead'] = (bool)($timeParts[1] == '+');
1141: $offset['hour'] = intval($timeParts[2]);
1142: $offset['minute'] = intval($timeParts[3]);
1143: if (isset($timeParts[4])) {
1144: $offset['second'] = intval($timeParts[4]);
1145: }
1146: return $offset;
1147: }
1148:
1149: return false;
1150: }
1151:
1152: 1153: 1154: 1155: 1156: 1157: 1158:
1159: function _exportUtcOffset($value)
1160: {
1161: $offset = ($value['ahead'] ? '+' : '-') .
1162: sprintf('%02d%02d', $value['hour'], $value['minute']);
1163:
1164: if (isset($value['second'])) {
1165: $offset .= sprintf('%02d', $value['second']);
1166: }
1167:
1168: return $offset;
1169: }
1170:
1171: 1172: 1173: 1174: 1175: 1176: 1177:
1178: protected function _parsePeriod($text)
1179: {
1180: $periodParts = explode('/', $text);
1181: $start = $this->_parseDateTime($periodParts[0]);
1182:
1183: if ($duration = $this->_parseDuration($periodParts[1])) {
1184: return array('start' => $start, 'duration' => $duration);
1185: } elseif ($end = $this->_parseDateTime($periodParts[1])) {
1186: return array('start' => $start, 'end' => $end);
1187: }
1188: }
1189:
1190: 1191: 1192: 1193: 1194: 1195: 1196:
1197: protected function _exportPeriod($value)
1198: {
1199: $period = $this->_exportDateTime($value['start']) . '/';
1200:
1201: return isset($value['duration'])
1202: ? $period . $this->_exportDuration($value['duration'])
1203: : $period . $this->_exportDateTime($value['end']);
1204: }
1205:
1206: 1207: 1208: 1209: 1210: 1211: 1212: 1213: 1214: 1215:
1216: protected function _parseTZID($date, $time, $tzid)
1217: {
1218: $vtimezone = $this->_container->findComponentByAttribute('vtimezone', 'TZID', $tzid);
1219: if (!$vtimezone) {
1220: return false;
1221: }
1222:
1223: $change_times = array();
1224: foreach ($vtimezone->getComponents() as $o) {
1225: $t = $vtimezone->parseChild($o, $date['year']);
1226: if ($t !== false) {
1227: $change_times[] = $t;
1228: }
1229: }
1230:
1231: if (!$change_times) {
1232: return false;
1233: }
1234:
1235: sort($change_times);
1236:
1237:
1238: $t = @gmmktime($time['hour'], $time['minute'], $time['second'],
1239: $date['month'], $date['mday'], $date['year']);
1240:
1241: if ($t < $change_times[0]['time']) {
1242: return $change_times[0]['from'];
1243: }
1244:
1245: for ($i = 0, $n = count($change_times); $i < $n - 1; $i++) {
1246: if (($t >= $change_times[$i]['time']) &&
1247: ($t < $change_times[$i + 1]['time'])) {
1248: return $change_times[$i]['to'];
1249: }
1250: }
1251:
1252: if ($t >= $change_times[$n - 1]['time']) {
1253: return $change_times[$n - 1]['to'];
1254: }
1255:
1256: return false;
1257: }
1258:
1259: 1260: 1261: 1262: 1263: 1264: 1265: 1266: 1267: 1268: 1269: 1270:
1271: public function _parseDateTime($text, $tzid = false)
1272: {
1273: $dateParts = explode('T', $text);
1274: if (count($dateParts) != 2 && !empty($text)) {
1275:
1276: if (!preg_match('/^(\d{4})-?(\d{2})-?(\d{2})$/', $text, $match)) {
1277:
1278: return $text;
1279: }
1280: $dateParts = array($text, '000000');
1281: }
1282:
1283: if (!($date = $this->_parseDate($dateParts[0])) ||
1284: !($time = $this->_parseTime($dateParts[1]))) {
1285: return $text;
1286: }
1287:
1288:
1289: $tzoffset = ($time['zone'] == 'Local' && $tzid &&
1290: ($this->_container instanceof Horde_Icalendar))
1291: ? $this->_parseTZID($date, $time, $tzid)
1292: : false;
1293: if ($time['zone'] == 'UTC' || $tzoffset !== false) {
1294: $result = @gmmktime($time['hour'], $time['minute'], $time['second'],
1295: $date['month'], $date['mday'], $date['year']);
1296: if ($tzoffset) {
1297: $result -= $tzoffset;
1298: }
1299: } else {
1300:
1301:
1302:
1303: $result = @mktime($time['hour'], $time['minute'], $time['second'],
1304: $date['month'], $date['mday'], $date['year']);
1305: }
1306:
1307: return ($result !== false) ? $result : $text;
1308: }
1309:
1310: 1311: 1312: 1313: 1314: 1315: 1316: 1317: 1318: 1319: 1320: 1321: 1322: 1323: 1324:
1325: public function _exportDateTime($value, $floating = false)
1326: {
1327: $date = new Horde_Date($value);
1328: return $date->toICalendar($floating);
1329: }
1330:
1331: 1332: 1333: 1334: 1335: 1336: 1337:
1338: protected function _parseTime($text)
1339: {
1340: if (!preg_match('/([0-9]{2})([0-9]{2})([0-9]{2})(Z)?/', $text, $timeParts)) {
1341: return false;
1342: }
1343:
1344: return array(
1345: 'hour' => $timeParts[1],
1346: 'minute' => $timeParts[2],
1347: 'second' => $timeParts[3],
1348: 'zone' => isset($timeParts[4]) ? 'UTC' : 'Local'
1349: );
1350: }
1351:
1352: 1353: 1354: 1355: 1356: 1357: 1358:
1359: public function _parseDate($text)
1360: {
1361: $parts = explode('T', $text);
1362: if (count($parts) == 2) {
1363: $text = $parts[0];
1364: }
1365:
1366: if (!preg_match('/^(\d{4})-?(\d{2})-?(\d{2})$/', $text, $match)) {
1367: return false;
1368: }
1369:
1370: return array(
1371: 'year' => $match[1],
1372: 'month' => $match[2],
1373: 'mday' => $match[3]
1374: );
1375: }
1376:
1377: 1378: 1379: 1380: 1381: 1382: 1383: 1384: 1385: 1386:
1387: protected function _exportDate($value, $autoconvert = false)
1388: {
1389: if (is_object($value)) {
1390: $value = array('year' => $value->year, 'month' => $value->month, 'mday' => $value->mday);
1391: }
1392:
1393: return ($autoconvert !== false && $this->_oldFormat)
1394: ? sprintf('%04d%02d%02dT%s', $value['year'], $value['month'], $value['mday'], $autoconvert)
1395: : sprintf('%04d%02d%02d', $value['year'], $value['month'], $value['mday']);
1396: }
1397:
1398: 1399: 1400: 1401: 1402: 1403: 1404:
1405: protected function _parseDuration($text)
1406: {
1407: if (!preg_match('/([+]?|[-])P(([0-9]+W)|([0-9]+D)|)(T(([0-9]+H)|([0-9]+M)|([0-9]+S))+)?/', trim($text), $durvalue)) {
1408: return false;
1409: }
1410:
1411:
1412: $duration = 7 * 86400 * intval($durvalue[3]);
1413:
1414: if (count($durvalue) > 4) {
1415:
1416: $duration += 86400 * intval($durvalue[4]);
1417: }
1418:
1419: if (count($durvalue) > 5) {
1420:
1421: $duration += 3600 * intval($durvalue[7]);
1422:
1423:
1424: if (isset($durvalue[8])) {
1425: $duration += 60 * intval($durvalue[8]);
1426: }
1427:
1428:
1429: if (isset($durvalue[9])) {
1430: $duration += intval($durvalue[9]);
1431: }
1432: }
1433:
1434:
1435: if ($durvalue[1] == "-") {
1436: $duration *= -1;
1437: }
1438:
1439: return $duration;
1440: }
1441:
1442: 1443: 1444: 1445: 1446:
1447: protected function _exportDuration($value)
1448: {
1449: $duration = '';
1450: if ($value < 0) {
1451: $value *= -1;
1452: $duration .= '-';
1453: }
1454: $duration .= 'P';
1455:
1456: $weeks = floor($value / (7 * 86400));
1457: $value = $value % (7 * 86400);
1458: if ($weeks) {
1459: $duration .= $weeks . 'W';
1460: }
1461:
1462: $days = floor($value / (86400));
1463: $value = $value % (86400);
1464: if ($days) {
1465: $duration .= $days . 'D';
1466: }
1467:
1468: if ($value) {
1469: $duration .= 'T';
1470:
1471: $hours = floor($value / 3600);
1472: $value = $value % 3600;
1473: if ($hours) {
1474: $duration .= $hours . 'H';
1475: }
1476:
1477: $mins = floor($value / 60);
1478: $value = $value % 60;
1479: if ($mins) {
1480: $duration .= $mins . 'M';
1481: }
1482:
1483: if ($value) {
1484: $duration .= $value . 'S';
1485: }
1486: }
1487:
1488: return $duration;
1489: }
1490:
1491: }
1492: