1: <?php
2:
3: namespace intouch\ical;
4:
5: 6: 7: 8: 9: 10: 11:
12: class Parser {
13: 14: 15: 16: 17:
18: public static function Parse( $url, iCal $ical ) {
19: $content = self::Fetch( $url );
20: $content = self::UnfoldLines($content);
21: self::_Parse( $content, $ical );
22: }
23:
24: 25: 26: 27: 28:
29: public static function ParseString($content, iCal $ical ) {
30: $content = self::UnfoldLines($content);
31: self::_Parse( $content, $ical );
32: }
33:
34: 35: 36: 37: 38:
39: protected static function Fetch( $resource ) {
40: $is_utf8 = true;
41:
42: if( is_file( $resource ) ) {
43:
44: $content = file_get_contents($resource);
45:
46: if( ! self::_ValidUtf8( $content ) ) {
47:
48: $is_utf8 = false;
49: }
50: } else {
51:
52:
53: $c = curl_init();
54: curl_setopt($c, CURLOPT_URL, $resource);
55: curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
56: if( !ini_get('safe_mode') && !ini_get('open_basedir') ){
57: curl_setopt($c, CURLOPT_FOLLOWLOCATION, true);
58: }
59: $content = curl_exec($c);
60:
61: $ct = curl_getinfo($c, CURLINFO_CONTENT_TYPE);
62: $enc = preg_replace('/^.*charset=([-a-zA-Z0-9]+).*$/', '$1', $ct);
63: if( $ct != '' && strtolower(str_replace('-','', $enc)) != 'utf8' ) {
64:
65: $is_utf8 = false;
66: } elseif( ! self::_ValidUtf8( $content ) ) {
67:
68: $is_utf8 = false;
69: }
70: }
71:
72: if( !$is_utf8 ) {
73: $content = utf8_encode($content);
74: }
75:
76: return $content;
77: }
78:
79: 80: 81: 82: 83:
84: protected static function UnfoldLines($content) {
85: $data = array();
86: $content = explode("\n", $content);
87: for( $i=0; $i < count($content); $i++) {
88: $line = rtrim($content[$i]);
89: while( isset($content[$i+1]) && strlen($content[$i+1]) > 0 && ($content[$i+1]{0} == ' ' || $content[$i+1]{0} == "\t" )) {
90: $line .= rtrim(substr($content[++$i],1));
91: }
92: $data[] = $line;
93: }
94: return $data;
95: }
96:
97: 98: 99: 100: 101: 102:
103: private static function _Parse( $content, iCal $ical ) {
104: $main_sections = array('vevent', 'vjournal', 'vtodo', 'vtimezone', 'vcalendar');
105: $array_idents = array('exdate','rdate');
106: $sections = array();
107: $section = '';
108: $current_data = array();
109:
110: foreach( $content AS $line ) {
111: $line = new Line($line);
112: if( $line->isBegin() ) {
113:
114: $section = strtolower($line->getData());
115: $sections[] = strtolower($line->getData());
116: } elseif( $line->isEnd() ) {
117:
118: $removed = array_pop($sections);
119: $section = end($sections);
120:
121: if( array_search($removed, $main_sections) !== false ) {
122: self::StoreSection( $removed, $current_data[$removed], $ical);
123: $current_data[$removed] = array();
124: }
125: } else {
126:
127: foreach( $main_sections AS $s ) {
128:
129: if( array_search($s, $sections) !== false ) {
130:
131: if( $section == $s ) {
132:
133: if (in_array($line->getIdent(), $array_idents))
134:
135: $current_data[$s][$line->getIdent()][] = $line;
136: else {
137: $current_data[$s][$line->getIdent()] = $line;
138: }
139: } else {
140:
141: $current_data[$s][$section][$line->getIdent()] = $line;
142: }
143: break;
144: }
145: }
146: }
147: }
148: $current_data = array();
149: }
150:
151: 152: 153: 154: 155: 156:
157: protected static function storeSection( $section, $data, iCal $ical ) {
158: $data = Factory::Factory($ical, $section, $data);
159: switch( $section ) {
160: case 'vcalendar':
161: return $ical->setCalendarInfo( $data );
162: case 'vevent':
163: return $ical->addEvent( $data );
164: case 'vjournal':
165: case 'vtodo':
166: return true;
167: case 'vtimezone':
168: return $ical->addTimeZone( $data );
169: }
170: }
171:
172: 173: 174: 175: 176: 177: 178: 179: 180: 181:
182: private static function _ValidUtf8( $data ) {
183: $rx = '[\xC0-\xDF]([^\x80-\xBF]|$)';
184: $rx .= '|[\xE0-\xEF].{0,1}([^\x80-\xBF]|$)';
185: $rx .= '|[\xF0-\xF7].{0,2}([^\x80-\xBF]|$)';
186: $rx .= '|[\xF8-\xFB].{0,3}([^\x80-\xBF]|$)';
187: $rx .= '|[\xFC-\xFD].{0,4}([^\x80-\xBF]|$)';
188: $rx .= '|[\xFE-\xFE].{0,5}([^\x80-\xBF]|$)';
189: $rx .= '|[\x00-\x7F][\x80-\xBF]';
190: $rx .= '|[\xC0-\xDF].[\x80-\xBF]';
191: $rx .= '|[\xE0-\xEF]..[\x80-\xBF]';
192: $rx .= '|[\xF0-\xF7]...[\x80-\xBF]';
193: $rx .= '|[\xF8-\xFB]....[\x80-\xBF]';
194: $rx .= '|[\xFC-\xFD].....[\x80-\xBF]';
195: $rx .= '|[\xFE-\xFE]......[\x80-\xBF]';
196: $rx .= '|^[\x80-\xBF]';
197:
198: return ( ! (bool) preg_match('!'.$rx.'!', $data) );
199: }
200: }
201:
202:
203: