Revision 55670b15
Added by Assos Assos over 10 years ago
drupal7/sites/all/modules/date_ical/libraries/ParserVcalendar.inc | ||
---|---|---|
16 | 16 |
protected $timezones = array(); |
17 | 17 |
protected $xtimezone; |
18 | 18 |
|
19 |
/** |
|
20 |
* The parsed data for the component that's currently being processed. |
|
21 |
* |
|
22 |
* ParserVcalendar parses one component at a time. This array is stored as a |
|
23 |
* property so that each handler can tell what work the previous handlers |
|
24 |
* have already completed on the current component. |
|
25 |
* |
|
26 |
* @var array |
|
27 |
*/ |
|
28 |
protected $parsed_data = array(); |
|
29 |
|
|
19 | 30 |
/** |
20 | 31 |
* Variables used for batch processing. |
21 | 32 |
*/ |
... | ... | |
28 | 39 |
public function __construct($calendar, $source, $fetcher_result, $config) { |
29 | 40 |
$this->calendar = $calendar; |
30 | 41 |
$this->source = $source; |
42 |
$this->mapping_sources = feeds_importer($source->id)->parser->getMappingSources(); |
|
31 | 43 |
$this->fetcherResult = $fetcher_result; |
32 | 44 |
$this->config = $config; |
33 | 45 |
} |
34 |
|
|
46 |
|
|
35 | 47 |
/** |
36 | 48 |
* Parses the vcalendar object into an array of event data arrays. |
37 | 49 |
* |
... | ... | |
42 | 54 |
* Specifies how many components to parse on this run. |
43 | 55 |
* |
44 | 56 |
* @return array |
45 |
* An array of parsed event data keyed by the same strings as the array |
|
46 |
* returned by DateiCalFeedsParser::getiCalMappingSources(). |
|
57 |
* An array of parsed event data keyed by our mapping source property keys. |
|
47 | 58 |
*/ |
48 | 59 |
public function parse($offset, $limit) { |
49 | 60 |
// Sometimes, the feed will set a timezone for every event in the calendar |
... | ... | |
69 | 80 |
$this->timezones[] = $component; |
70 | 81 |
} |
71 | 82 |
|
83 |
// This content array is used by date_ical_import_component_alter() and |
|
84 |
// date_ical_import_parsed_data_alter(). |
|
85 |
$context2 = array( |
|
86 |
'calendar' => $this->calendar, |
|
87 |
'source' => $this->source, |
|
88 |
'fetcher_result' => $this->fetcherResult, |
|
89 |
); |
|
90 |
|
|
72 | 91 |
// Collect each component, so we can batch them properly in the next loop. |
73 | 92 |
$raw_components = array(); |
74 | 93 |
$types = array('VEVENT', 'VTODO', 'VJOURNAL', 'VFREEBUSY', 'VALARM'); |
... | ... | |
76 | 95 |
while ($vcalendar_component = $this->calendar->getComponent($type)) { |
77 | 96 |
// Allow modules to alter the vcalendar component before we parse it |
78 | 97 |
// into a Feeds-compatible data array. |
79 |
$context = array( |
|
80 |
'calendar' => $this->calendar, |
|
81 |
'source' => $this->source, |
|
82 |
'fetcher_result' => $this->fetcherResult, |
|
83 |
); |
|
84 |
drupal_alter('date_ical_import_component', $vcalendar_component, $context); |
|
98 |
drupal_alter('date_ical_import_component', $vcalendar_component, $context2); |
|
85 | 99 |
$raw_components[] = $vcalendar_component; |
86 | 100 |
} |
87 | 101 |
} |
... | ... | |
92 | 106 |
// Parse each raw component in the current batch into a Feeds-compatible |
93 | 107 |
// event data array. |
94 | 108 |
$events = array(); |
95 |
$sources = DateiCalFeedsParser::getiCalMappingSources(); |
|
96 | 109 |
$batch = array_slice($raw_components, $offset, $limit, TRUE); |
97 | 110 |
foreach ($batch as $ndx => $raw_component) { |
98 |
$parsed_component = array(); |
|
99 |
foreach ($sources as $property_key => $data) { |
|
100 |
$handler = $data['date_ical_parse_handler']; |
|
101 |
$parsed_component[$property_key] = $this->$handler($property_key, $raw_component); |
|
111 |
$this->parsed_data = array(); |
|
112 |
foreach ($this->mapping_sources as $property_key => $data) { |
|
113 |
$handler = NULL; |
|
114 |
if (isset($data['date_ical_parse_handler'])) { |
|
115 |
$handler = $data['date_ical_parse_handler']; |
|
116 |
} |
|
117 |
else { |
|
118 |
// This is not one of our sources, so if we don't recognize and |
|
119 |
// support it, we'll have to pass a warning to the user. |
|
120 |
if ($property_key == 'geofield') { |
|
121 |
$handler = 'parseGeofield'; |
|
122 |
} |
|
123 |
else { |
|
124 |
// We can safely ignore certain sources. |
|
125 |
$known_unknowns = array( |
|
126 |
// "Black Source 1" is from Feeds Tamper. |
|
127 |
'Blank source 1', |
|
128 |
); |
|
129 |
if (!in_array($property_key, $known_unknowns)) { |
|
130 |
// Only warn the user if this mapping source is in use. |
|
131 |
foreach ($this->source->importer->processor->config['mappings'] as $mapping) { |
|
132 |
if ($mapping['source'] == $property_key) { |
|
133 |
drupal_set_message(t('Date iCal does not recognize the "@name" Mapping Source, and must skip it.', array('@name' => $data['name'])), 'warning', FALSE); |
|
134 |
break; |
|
135 |
} |
|
136 |
} |
|
137 |
} |
|
138 |
} |
|
139 |
} |
|
140 |
if ($handler) { |
|
141 |
$this->parsed_data[$property_key] = $this->$handler($property_key, $raw_component); |
|
142 |
} |
|
143 |
|
|
144 |
if ($property_key == 'geofield' && !empty($this->parsed_data['geofield'])) { |
|
145 |
// To make our data readable by geofield_feeds_combined_source(), we |
|
146 |
// need to put it into the format output by Simplepie 1.3. |
|
147 |
$this->parsed_data['location_latitude'] = array($this->parsed_data['geofield']['lat']); |
|
148 |
$this->parsed_data['location_longitude'] = array($this->parsed_data['geofield']['lon']); |
|
149 |
} |
|
150 |
} |
|
151 |
|
|
152 |
// Allow modules to alter the final parsed data before we send it to the |
|
153 |
// Feeds processor. |
|
154 |
drupal_alter('date_ical_import_post_parse', $this->parsed_data, $context2); |
|
155 |
|
|
156 |
// Skip this event if it's earlier than the user's specified skip time. |
|
157 |
if (!$this->_skip_current_event()) { |
|
158 |
$events[] = $this->parsed_data; |
|
102 | 159 |
} |
103 |
$events[] = $parsed_component; |
|
104 | 160 |
// The indices of the original $raw_components array are preserved in |
105 | 161 |
// $batch, so using the $ndx value here lets us communicate our progress |
106 | 162 |
// through the full collection of components. |
... | ... | |
125 | 181 |
} |
126 | 182 |
|
127 | 183 |
/** |
128 |
* Parses text fields.
|
|
184 |
* Handler that parses text fields.
|
|
129 | 185 |
* |
130 | 186 |
* @return string |
131 | 187 |
* The parsed text property. |
... | ... | |
144 | 200 |
$text = str_replace(array('\n', '\N'), "\n", $text); |
145 | 201 |
return $text; |
146 | 202 |
} |
147 |
|
|
203 |
|
|
148 | 204 |
/** |
149 |
* Parses field parameters. |
|
205 |
* Handler that parses GEO fields. |
|
206 |
* |
|
207 |
* @return array |
|
208 |
* The latitude and longitude values, keyed by 'lat' and 'lon'. |
|
209 |
*/ |
|
210 |
public function parseGeofield($property_key, $vcalendar_component) { |
|
211 |
$geo = array(); |
|
212 |
if (!empty($vcalendar_component->geo['value'])) { |
|
213 |
$geo['lat'] = $vcalendar_component->geo['value']['latitude']; |
|
214 |
$geo['lon'] = $vcalendar_component->geo['value']['longitude']; |
|
215 |
} |
|
216 |
return $geo; |
|
217 |
} |
|
218 |
|
|
219 |
/** |
|
220 |
* Handler that parses field parameters. |
|
150 | 221 |
* |
151 | 222 |
* @return string |
152 | 223 |
* The parsed field parameter. |
... | ... | |
158 | 229 |
// If the component doesn't have this property, return NULL. |
159 | 230 |
return NULL; |
160 | 231 |
} |
161 |
return isset($property['params'][$attr]) ? $property['params'][$attr] : ''; |
|
232 |
$param = isset($property['params'][$attr]) ? $property['params'][$attr] : ''; |
|
233 |
return $param; |
|
162 | 234 |
} |
163 |
|
|
235 |
|
|
164 | 236 |
/** |
165 |
* Parses DATE-TIME and DATE fields.
|
|
237 |
* Handler that parses DATE-TIME and DATE fields.
|
|
166 | 238 |
* |
167 | 239 |
* @return FeedsDateTime |
168 | 240 |
* The parsed datetime object. |
... | ... | |
199 | 271 |
// If this is an all-day event with no end or duration, treat this |
200 | 272 |
// as a single-day event by emulating DTEND as 1 day after DTSTART. |
201 | 273 |
$property = $dtstart; |
202 |
$property['value']['day'] = $dtstart['value']['day'] + 1;
|
|
274 |
$property['value'] = iCalUtilityFunctions::_duration2date($property['value'], array('day' => 1));
|
|
203 | 275 |
} |
204 | 276 |
else { |
205 | 277 |
// This event has no end date. |
... | ... | |
239 | 311 |
$property['value'] = $prev_day; |
240 | 312 |
} |
241 | 313 |
} |
242 |
|
|
314 |
|
|
243 | 315 |
// FeedsDateTime->setTimezone() ignores timezone changes made to dates |
244 | 316 |
// with no time element, which means we can't compensate for the Date |
245 | 317 |
// module's automatic timezone conversion when it writes to the DB. To |
... | ... | |
290 | 362 |
$datetimezone = new DateTimeZone(date_default_timezone_get()); |
291 | 363 |
} |
292 | 364 |
} |
293 |
|
|
294 |
return new FeedsDateTime($date_string, $datetimezone); |
|
365 |
|
|
366 |
$datetime = new FeedsDateTime($date_string, $datetimezone); |
|
367 |
return $datetime; |
|
295 | 368 |
} |
296 |
|
|
369 |
|
|
297 | 370 |
/** |
298 |
* Parses multi-value fields, like the CATEGORIES component.
|
|
371 |
* Handler that parses multi-value fields, like the CATEGORIES component.
|
|
299 | 372 |
* |
300 | 373 |
* @return array |
301 | 374 |
* An array of strings contaning the individual values. |
... | ... | |
316 | 389 |
} |
317 | 390 |
return $property; |
318 | 391 |
} |
319 |
|
|
392 |
|
|
320 | 393 |
/** |
321 |
* Format RRULEs, which specify when and how often the event is repeated.
|
|
394 |
* Handler that parses RRULE, RDATE, EXRULE, and EXDATE together.
|
|
322 | 395 |
* |
323 | 396 |
* @return string |
324 |
* An RRULE string, with EXDATE and RDATE values separated by \n. |
|
325 |
* This is to make the RRULE compatible with date_repeat_split_rrule(). |
|
397 |
* The RRULE, RDATE, EXRULE, and EXDATE values concatinated with |. |
|
326 | 398 |
*/ |
327 | 399 |
public function parseRepeatProperty($property_key, $vcalendar_component) { |
328 | 400 |
if ($vcalendar_component->getProperty($property_key) === FALSE) { |
329 | 401 |
return NULL; |
330 | 402 |
} |
331 |
|
|
403 |
|
|
404 |
$uid = $vcalendar_component->getProperty('UID'); |
|
405 |
$count = $this->config['indefinite_count']; |
|
332 | 406 |
// Due to a few bugs and limitations with Date Repeat, we need to massage |
333 | 407 |
// the RRULE a bit. |
334 | 408 |
if (count($vcalendar_component->rrule) > 1) { |
335 |
drupal_set_message(t('The event with UID %uid has multiple RRULEs, but the Date Repeat module only supports one. Only the first RRULE in the event will be used.<br> |
|
336 |
If your events need to have a complex repeat pattern, using RDATEs should help.', |
|
337 |
array('%uid' => $vcalendar_component->getProperty('UID'))), 'warning' |
|
338 |
); |
|
409 |
$msg = 'The event with UID %uid has multiple RRULEs, but the Date Repeat module only supports one. Only the first RRULE in the event will be used. |
|
410 |
If your events need to have a complex repeat pattern, using RDATEs should help.'; |
|
411 |
watchdog('date_ical', $msg, array('%uid' => $uid), 'warning'); |
|
412 |
drupal_set_message('At least one of the events in this iCal feed has multiple RRULEs, but the Date Repeat module only supports one. |
|
413 |
Only the first RRULE in an event will be used.', 'warning', FALSE); |
|
414 |
|
|
339 | 415 |
// Date Repeat will get extremely confused if it's sent multiple RRULE |
340 | 416 |
// values, so we need to manually pare it down to only the first one. |
341 | 417 |
$vcalendar_component->rrule = array($vcalendar_component->rrule[0]); |
... | ... | |
345 | 421 |
if (!isset($rrule_data['value']['INTERVAL'])) { |
346 | 422 |
$rrule_data['value']['INTERVAL'] = '1'; |
347 | 423 |
} |
348 |
|
|
424 |
|
|
349 | 425 |
if (!isset($rrule_data['value']['COUNT']) && !isset($rrule_data['value']['UNTIL'])) { |
350 |
drupal_set_message(t("The event with UID %uid has an indefinitely repeating RRULE, which the Date Repeat module doesn't support.<br> |
|
426 |
$msg = "The event with UID %uid has an indefinitely repeating RRULE, which the Date Repeat module doesn't support. |
|
427 |
As a workaround, Date iCal set the repeat count to @count. This value can be customized in the iCal parser settings."; |
|
428 |
watchdog('date_ical', $msg, array('%uid' => $uid, '@count' => $count), WATCHDOG_WARNING); |
|
429 |
drupal_set_message( |
|
430 |
t("At least one of the events in this iCal feed has an indefinitely repeating RRULE, which the Date Repeat module doesn't support.<br> |
|
351 | 431 |
As a workaround, Date iCal set the repeat count to @count. This value can be customized in the iCal parser settings.", |
352 |
array('%uid' => $vcalendar_component->getProperty('UID'), '@count' => $this->config['indefinite_count'])), 'warning' |
|
432 |
array('@count' => $count)), |
|
433 |
'warning', |
|
434 |
FALSE |
|
353 | 435 |
); |
354 | 436 |
$rrule_data['value']['COUNT'] = $this->config['indefinite_count']; |
355 | 437 |
} |
356 | 438 |
} |
357 |
|
|
439 |
|
|
358 | 440 |
$rrule = trim($vcalendar_component->createRrule()); |
359 | 441 |
$rdate = trim($vcalendar_component->createRdate()); |
360 | 442 |
$exrule = trim($vcalendar_component->createExrule()); |
361 | 443 |
$exdate = trim($vcalendar_component->createExdate()); |
362 | 444 |
return "$rrule|$rdate|$exrule|$exdate"; |
363 | 445 |
} |
364 |
|
|
446 |
|
|
365 | 447 |
/** |
366 | 448 |
* Internal helper function for creating DateTimeZone objects. |
367 | 449 |
*/ |
... | ... | |
377 | 459 |
$windows_to_olson_map = array(); |
378 | 460 |
foreach ($zones_assoc['supplemental']['windowsZones']['mapTimezones'] as $mapTimezone) { |
379 | 461 |
if ($mapTimezone['mapZone']['_other'] == $tzid) { |
380 |
// $mapTimezone['mapZone']['_type'] is space-separated TZIDs.
|
|
462 |
// Parse out the space-separated TZIDs from $mapTimezone['mapZone']['_type'].
|
|
381 | 463 |
$tzids = preg_split('/\s/', $mapTimezone['mapZone']['_type']); |
382 | 464 |
try { |
383 | 465 |
// They all have the same UTC offset, so for our purposes we can |
... | ... | |
405 | 487 |
} |
406 | 488 |
return $datetimezone; |
407 | 489 |
} |
490 |
|
|
491 |
/** |
|
492 |
* Internal helper function for skipping old events. |
|
493 |
*/ |
|
494 |
protected function _skip_current_event() { |
|
495 |
// Must use !isset() here, because 0 and NULL mean different things. |
|
496 |
if (!isset($this->config['skip_days'])) { |
|
497 |
return FALSE; |
|
498 |
} |
|
499 |
$compare_date = isset($this->parsed_data['DTEND']) ? $this->parsed_data['DTEND'] : $this->parsed_data['DTSTART']; |
|
500 |
$skip_date = new FeedsDateTime("today -{$this->config['skip_days']} days", $compare_date->getTimezone()); |
|
501 |
$skip = ($skip_date > $compare_date); |
|
502 |
return $skip; |
|
503 |
} |
|
408 | 504 |
} |
Also available in: Unified diff
Weekly update of contrib modules