1 |
be880f98
|
Florent Torregrosa
|
<?php
|
2 |
|
|
/**
|
3 |
|
|
* @file
|
4 |
|
|
* Defines a class that parses iCalcreator vcalendar objects into
|
5 |
|
|
* Feeds-compatible data arrays.
|
6 |
|
|
*/
|
7 |
|
|
|
8 |
|
|
class ParserVcalendar {
|
9 |
|
|
/**
|
10 |
|
|
* Variables used for parsing.
|
11 |
|
|
*/
|
12 |
|
|
protected $calendar;
|
13 |
|
|
protected $source;
|
14 |
|
|
protected $fetcherResult;
|
15 |
|
|
protected $config;
|
16 |
|
|
protected $timezones = array();
|
17 |
|
|
protected $xtimezone;
|
18 |
|
|
|
19 |
55670b15
|
Assos Assos
|
/**
|
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 |
|
|
|
30 |
be880f98
|
Florent Torregrosa
|
/**
|
31 |
|
|
* Variables used for batch processing.
|
32 |
|
|
*/
|
33 |
|
|
protected $totalComponents = 0;
|
34 |
|
|
protected $lastComponentParsed = 0;
|
35 |
|
|
|
36 |
ca0757b9
|
Assos Assos
|
/**
|
37 |
|
|
* This is the list of iCal properties which are allowed to have more than
|
38 |
|
|
* one entry in a single VEVENT. If we ever support parsing more than just
|
39 |
|
|
* the first one, this listing will be useful.
|
40 |
|
|
*/
|
41 |
|
|
protected $multi_entry_properties = array(
|
42 |
|
|
'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT', 'CONTACT', 'DESCRIPTION',
|
43 |
|
|
'EXDATE', 'EXRULE', 'FREEBUSY', 'RDATE', 'RELATED-TO', 'RESOURCES',
|
44 |
|
|
'RRULE', 'REQUEST-STATUS', 'TZNAME', 'X-PROP'
|
45 |
|
|
);
|
46 |
|
|
|
47 |
be880f98
|
Florent Torregrosa
|
/**
|
48 |
|
|
* Constructor.
|
49 |
|
|
*/
|
50 |
|
|
public function __construct($calendar, $source, $fetcher_result, $config) {
|
51 |
|
|
$this->calendar = $calendar;
|
52 |
|
|
$this->source = $source;
|
53 |
55670b15
|
Assos Assos
|
$this->mapping_sources = feeds_importer($source->id)->parser->getMappingSources();
|
54 |
be880f98
|
Florent Torregrosa
|
$this->fetcherResult = $fetcher_result;
|
55 |
|
|
$this->config = $config;
|
56 |
|
|
}
|
57 |
55670b15
|
Assos Assos
|
|
58 |
be880f98
|
Florent Torregrosa
|
/**
|
59 |
|
|
* Parses the vcalendar object into an array of event data arrays.
|
60 |
|
|
*
|
61 |
|
|
* @param int $offset
|
62 |
|
|
* Specifies which section of the feed to start parsing at.
|
63 |
|
|
*
|
64 |
|
|
* @param int $limit
|
65 |
|
|
* Specifies how many components to parse on this run.
|
66 |
|
|
*
|
67 |
|
|
* @return array
|
68 |
55670b15
|
Assos Assos
|
* An array of parsed event data keyed by our mapping source property keys.
|
69 |
be880f98
|
Florent Torregrosa
|
*/
|
70 |
|
|
public function parse($offset, $limit) {
|
71 |
|
|
// Sometimes, the feed will set a timezone for every event in the calendar
|
72 |
|
|
// using the non-standard X-WR-TIMEZONE property. Date iCal uses this
|
73 |
|
|
// timezone only if the date property is not in UTC and has no TZID.
|
74 |
|
|
$xtimezone = $this->calendar->getProperty('X-WR-TIMEZONE');
|
75 |
|
|
if (!empty($xtimezone[1])) {
|
76 |
|
|
// Allow modules to alter the timezone string before it gets converted
|
77 |
|
|
// into a DateTimeZone.
|
78 |
|
|
$context = array(
|
79 |
|
|
'property_key' => NULL,
|
80 |
|
|
'calendar_component' => NULL,
|
81 |
|
|
'calendar' => $this->calendar,
|
82 |
|
|
'feeeds_source' => $this->source,
|
83 |
|
|
'feeds_fetcher_result' => $this->fetcherResult,
|
84 |
|
|
);
|
85 |
|
|
drupal_alter('date_ical_import_timezone', $xtimezone[1], $context);
|
86 |
|
|
$this->xtimezone = $this->_tzid_to_datetimezone($xtimezone[1]);
|
87 |
|
|
}
|
88 |
|
|
|
89 |
|
|
// Collect the timezones into an array, for easier access.
|
90 |
|
|
while ($component = $this->calendar->getComponent('VTIMEZONE')) {
|
91 |
|
|
$this->timezones[] = $component;
|
92 |
|
|
}
|
93 |
|
|
|
94 |
c7b88c87
|
Assos Assos
|
// This context array is used by date_ical_import_component_alter() and
|
95 |
55670b15
|
Assos Assos
|
// date_ical_import_parsed_data_alter().
|
96 |
|
|
$context2 = array(
|
97 |
|
|
'calendar' => $this->calendar,
|
98 |
|
|
'source' => $this->source,
|
99 |
|
|
'fetcher_result' => $this->fetcherResult,
|
100 |
|
|
);
|
101 |
|
|
|
102 |
be880f98
|
Florent Torregrosa
|
// Collect each component, so we can batch them properly in the next loop.
|
103 |
|
|
$raw_components = array();
|
104 |
|
|
$types = array('VEVENT', 'VTODO', 'VJOURNAL', 'VFREEBUSY', 'VALARM');
|
105 |
|
|
foreach ($types as $type) {
|
106 |
|
|
while ($vcalendar_component = $this->calendar->getComponent($type)) {
|
107 |
|
|
// Allow modules to alter the vcalendar component before we parse it
|
108 |
|
|
// into a Feeds-compatible data array.
|
109 |
55670b15
|
Assos Assos
|
drupal_alter('date_ical_import_component', $vcalendar_component, $context2);
|
110 |
be880f98
|
Florent Torregrosa
|
$raw_components[] = $vcalendar_component;
|
111 |
|
|
}
|
112 |
|
|
}
|
113 |
|
|
|
114 |
|
|
// Store this for use by DateiCalFeedsParser's batch processing code.
|
115 |
|
|
$this->totalComponents = count($raw_components);
|
116 |
|
|
|
117 |
|
|
// Parse each raw component in the current batch into a Feeds-compatible
|
118 |
|
|
// event data array.
|
119 |
|
|
$events = array();
|
120 |
|
|
$batch = array_slice($raw_components, $offset, $limit, TRUE);
|
121 |
|
|
foreach ($batch as $ndx => $raw_component) {
|
122 |
55670b15
|
Assos Assos
|
$this->parsed_data = array();
|
123 |
|
|
foreach ($this->mapping_sources as $property_key => $data) {
|
124 |
|
|
$handler = NULL;
|
125 |
|
|
if (isset($data['date_ical_parse_handler'])) {
|
126 |
|
|
$handler = $data['date_ical_parse_handler'];
|
127 |
|
|
}
|
128 |
|
|
else {
|
129 |
|
|
// This is not one of our sources, so if we don't recognize and
|
130 |
|
|
// support it, we'll have to pass a warning to the user.
|
131 |
|
|
if ($property_key == 'geofield') {
|
132 |
|
|
$handler = 'parseGeofield';
|
133 |
|
|
}
|
134 |
|
|
else {
|
135 |
|
|
// We can safely ignore certain sources.
|
136 |
|
|
$known_unknowns = array(
|
137 |
ca0757b9
|
Assos Assos
|
'Blank source 1', // "Black Source 1" is from Feeds Tamper.
|
138 |
|
|
'parent:nid', // Defined in FeedsParser
|
139 |
|
|
'parent:uid', // Defined in FeedsParser
|
140 |
55670b15
|
Assos Assos
|
);
|
141 |
|
|
if (!in_array($property_key, $known_unknowns)) {
|
142 |
|
|
// Only warn the user if this mapping source is in use.
|
143 |
|
|
foreach ($this->source->importer->processor->config['mappings'] as $mapping) {
|
144 |
|
|
if ($mapping['source'] == $property_key) {
|
145 |
|
|
drupal_set_message(t('Date iCal does not recognize the "@name" Mapping Source, and must skip it.', array('@name' => $data['name'])), 'warning', FALSE);
|
146 |
|
|
break;
|
147 |
|
|
}
|
148 |
|
|
}
|
149 |
|
|
}
|
150 |
|
|
}
|
151 |
|
|
}
|
152 |
|
|
if ($handler) {
|
153 |
|
|
$this->parsed_data[$property_key] = $this->$handler($property_key, $raw_component);
|
154 |
|
|
}
|
155 |
|
|
|
156 |
|
|
if ($property_key == 'geofield' && !empty($this->parsed_data['geofield'])) {
|
157 |
|
|
// To make our data readable by geofield_feeds_combined_source(), we
|
158 |
|
|
// need to put it into the format output by Simplepie 1.3.
|
159 |
|
|
$this->parsed_data['location_latitude'] = array($this->parsed_data['geofield']['lat']);
|
160 |
|
|
$this->parsed_data['location_longitude'] = array($this->parsed_data['geofield']['lon']);
|
161 |
|
|
}
|
162 |
|
|
}
|
163 |
|
|
|
164 |
|
|
// Allow modules to alter the final parsed data before we send it to the
|
165 |
|
|
// Feeds processor.
|
166 |
|
|
drupal_alter('date_ical_import_post_parse', $this->parsed_data, $context2);
|
167 |
|
|
|
168 |
|
|
// Skip this event if it's earlier than the user's specified skip time.
|
169 |
|
|
if (!$this->_skip_current_event()) {
|
170 |
|
|
$events[] = $this->parsed_data;
|
171 |
be880f98
|
Florent Torregrosa
|
}
|
172 |
|
|
// The indices of the original $raw_components array are preserved in
|
173 |
|
|
// $batch, so using the $ndx value here lets us communicate our progress
|
174 |
|
|
// through the full collection of components.
|
175 |
|
|
$this->lastComponentParsed = $ndx;
|
176 |
|
|
}
|
177 |
|
|
|
178 |
|
|
return $events;
|
179 |
|
|
}
|
180 |
|
|
|
181 |
|
|
/**
|
182 |
|
|
* Getter for the protected totalComponents property.
|
183 |
|
|
*/
|
184 |
|
|
public function getTotalComponents() {
|
185 |
|
|
return $this->totalComponents;
|
186 |
|
|
}
|
187 |
|
|
|
188 |
|
|
/**
|
189 |
|
|
* Getter for the protected lastComponentParsed property.
|
190 |
|
|
*/
|
191 |
|
|
public function getLastComponentParsed() {
|
192 |
|
|
return $this->lastComponentParsed;
|
193 |
|
|
}
|
194 |
|
|
|
195 |
ca0757b9
|
Assos Assos
|
/**
|
196 |
|
|
* Handler that parses GEO fields.
|
197 |
|
|
*
|
198 |
|
|
* @return array
|
199 |
|
|
* The latitude and longitude values, keyed by 'lat' and 'lon'.
|
200 |
|
|
*/
|
201 |
|
|
public function parseGeofield($property_key, $vcalendar_component) {
|
202 |
|
|
$geo = array();
|
203 |
|
|
if (!empty($vcalendar_component->geo['value'])) {
|
204 |
|
|
$geo['lat'] = $vcalendar_component->geo['value']['latitude'];
|
205 |
|
|
$geo['lon'] = $vcalendar_component->geo['value']['longitude'];
|
206 |
|
|
}
|
207 |
|
|
return $geo;
|
208 |
|
|
}
|
209 |
|
|
|
210 |
be880f98
|
Florent Torregrosa
|
/**
|
211 |
55670b15
|
Assos Assos
|
* Handler that parses text fields.
|
212 |
be880f98
|
Florent Torregrosa
|
*
|
213 |
|
|
* @return string
|
214 |
|
|
* The parsed text property.
|
215 |
|
|
*/
|
216 |
|
|
public function parseTextProperty($property_key, $vcalendar_component) {
|
217 |
|
|
$text = $vcalendar_component->getProperty($property_key);
|
218 |
ca0757b9
|
Assos Assos
|
// In case someone writes a hook that adds a source for a multi-entry
|
219 |
|
|
// property and a parameter of that same property, we need to force
|
220 |
|
|
// iCalcreator to assume it has not accessed that property, yet.
|
221 |
|
|
// TODO: This is really just a hack. If/when multi-entry properties
|
222 |
|
|
// become supported, this will need to be redesigned.
|
223 |
|
|
if (in_array($property_key, $this->multi_entry_properties)) {
|
224 |
|
|
unset($vcalendar_component->propix[$property_key]);
|
225 |
|
|
}
|
226 |
|
|
|
227 |
be880f98
|
Florent Torregrosa
|
if ($text === FALSE) {
|
228 |
c7b88c87
|
Assos Assos
|
if ($property_key != 'SUMMARY') {
|
229 |
|
|
return NULL;
|
230 |
|
|
}
|
231 |
|
|
else {
|
232 |
be880f98
|
Florent Torregrosa
|
$uid = $vcalendar_component->getProperty('UID');
|
233 |
c7b88c87
|
Assos Assos
|
if ($vcalendar_component->objName == 'vfreebusy') {
|
234 |
|
|
// FREEBUSY elements can't have SUMMARY, but they can have COMMENT.
|
235 |
|
|
// So if the feed has been configured to ask for SUMMARY, use COMMENT
|
236 |
|
|
// instead. If COMMENT is also missing, we can't import.
|
237 |
|
|
$text = $vcalendar_component->getProperty('COMMENT');
|
238 |
|
|
if ($text === FALSE) {
|
239 |
|
|
throw new DateIcalParseException(t('The VFREEBUSY component with UID %uid is invalid because it has no COMMENT.
|
240 |
|
|
Nodes require a title, and since VFREEBUSY components can\'t have SUMMARY, Date iCal pulls that title from the COMMENT.',
|
241 |
|
|
array('%uid' => $uid))
|
242 |
|
|
);
|
243 |
|
|
}
|
244 |
|
|
}
|
245 |
|
|
else {
|
246 |
|
|
// Non-VFREEBUSY components must have a SUMMARY.
|
247 |
|
|
throw new DateIcalParseException(t('The component with UID %uid is invalid because it has no SUMMARY (nodes require a title).',
|
248 |
|
|
array('%uid' => $uid))
|
249 |
|
|
);
|
250 |
|
|
}
|
251 |
be880f98
|
Florent Torregrosa
|
}
|
252 |
|
|
}
|
253 |
|
|
// Convert literal \n and \N into newline characters.
|
254 |
|
|
$text = str_replace(array('\n', '\N'), "\n", $text);
|
255 |
|
|
return $text;
|
256 |
|
|
}
|
257 |
55670b15
|
Assos Assos
|
|
258 |
|
|
/**
|
259 |
|
|
* Handler that parses field parameters.
|
260 |
be880f98
|
Florent Torregrosa
|
*
|
261 |
|
|
* @return string
|
262 |
|
|
* The parsed field parameter.
|
263 |
|
|
*/
|
264 |
|
|
public function parsePropertyParameter($property_key, $vcalendar_component) {
|
265 |
|
|
list($key, $attr) = explode(':', $property_key);
|
266 |
|
|
$property = $vcalendar_component->getProperty($key, FALSE, TRUE);
|
267 |
ca0757b9
|
Assos Assos
|
// See parseTextProperty() for why this is here.
|
268 |
|
|
if (in_array($property_key, $this->multi_entry_properties)) {
|
269 |
|
|
unset($vcalendar_component->propix[$property_key]);
|
270 |
|
|
}
|
271 |
|
|
|
272 |
be880f98
|
Florent Torregrosa
|
if ($property === FALSE) {
|
273 |
|
|
// If the component doesn't have this property, return NULL.
|
274 |
|
|
return NULL;
|
275 |
|
|
}
|
276 |
55670b15
|
Assos Assos
|
$param = isset($property['params'][$attr]) ? $property['params'][$attr] : '';
|
277 |
|
|
return $param;
|
278 |
be880f98
|
Florent Torregrosa
|
}
|
279 |
55670b15
|
Assos Assos
|
|
280 |
be880f98
|
Florent Torregrosa
|
/**
|
281 |
55670b15
|
Assos Assos
|
* Handler that parses DATE-TIME and DATE fields.
|
282 |
be880f98
|
Florent Torregrosa
|
*
|
283 |
|
|
* @return FeedsDateTime
|
284 |
|
|
* The parsed datetime object.
|
285 |
|
|
*/
|
286 |
|
|
public function parseDateTimeProperty($property_key, $vcalendar_component) {
|
287 |
|
|
$property = $vcalendar_component->getProperty($property_key, FALSE, TRUE);
|
288 |
|
|
// Gather all the other date properties, so we can work with them later.
|
289 |
|
|
$duration = $vcalendar_component->getProperty('DURATION', FALSE, TRUE);
|
290 |
|
|
$dtstart = $vcalendar_component->getProperty('DTSTART', FALSE, TRUE);
|
291 |
|
|
$uid = $vcalendar_component->getProperty('UID');
|
292 |
|
|
|
293 |
|
|
// DATE-type properties are treated as All Day events which can span over
|
294 |
|
|
// multiple days.
|
295 |
|
|
// The Date module's All Day event handling was never finalized
|
296 |
|
|
// (http://drupal.org/node/874322), which requires us to do some some
|
297 |
|
|
// special coddling later.
|
298 |
|
|
$is_all_day = (isset($property['params']['VALUE']) && $property['params']['VALUE'] == 'DATE');
|
299 |
|
|
|
300 |
|
|
// Cover various conditions in which either DTSTART or DTEND are not set.
|
301 |
|
|
if ($property === FALSE) {
|
302 |
|
|
// When DTEND isn't defined, we may need to emulate it.
|
303 |
|
|
if ($property_key == 'DTEND') {
|
304 |
|
|
// Unset DTENDs need to emulate the DATE type from DTSTART.
|
305 |
|
|
$is_all_day = (isset($dtstart['params']['VALUE']) && $dtstart['params']['VALUE'] == 'DATE');
|
306 |
|
|
|
307 |
|
|
if ($duration !== FALSE) {
|
308 |
|
|
// If a DURATION is defined, emulate DTEND as DTSTART + DURATION.
|
309 |
|
|
$property = array(
|
310 |
|
|
'value' => iCalUtilityFunctions::_duration2date($dtstart['value'], $duration['value']),
|
311 |
|
|
'params' => $dtstart['params'],
|
312 |
|
|
);
|
313 |
|
|
}
|
314 |
|
|
elseif ($is_all_day) {
|
315 |
|
|
// If this is an all-day event with no end or duration, treat this
|
316 |
|
|
// as a single-day event by emulating DTEND as 1 day after DTSTART.
|
317 |
|
|
$property = $dtstart;
|
318 |
55670b15
|
Assos Assos
|
$property['value'] = iCalUtilityFunctions::_duration2date($property['value'], array('day' => 1));
|
319 |
be880f98
|
Florent Torregrosa
|
}
|
320 |
|
|
else {
|
321 |
|
|
// This event has no end date.
|
322 |
|
|
return NULL;
|
323 |
|
|
}
|
324 |
|
|
}
|
325 |
|
|
elseif ($property_key == 'DTSTART') {
|
326 |
|
|
// DTSTART can only be legally unset in non-VEVENT components.
|
327 |
|
|
if ($vcalendar_component->objName == 'vevent') {
|
328 |
|
|
throw new DateIcalParseException(t('Feed import failed! The VEVENT with UID %uid is invalid: it has no DTSTART.', array('%uid' => $uid)));
|
329 |
|
|
}
|
330 |
|
|
else {
|
331 |
|
|
return NULL;
|
332 |
|
|
}
|
333 |
|
|
}
|
334 |
|
|
}
|
335 |
|
|
|
336 |
|
|
// When iCalcreator parses a UTC date (one that ends with Z) from an iCal
|
337 |
|
|
// feed, it stores that 'Z' into the $property['value']['tz'] value.
|
338 |
|
|
if (isset($property['value']['tz'])) {
|
339 |
|
|
$property['params']['TZID'] = 'UTC';
|
340 |
|
|
}
|
341 |
|
|
|
342 |
|
|
if ($is_all_day) {
|
343 |
|
|
if ($property_key == 'DTEND') {
|
344 |
|
|
if ($dtstart === FALSE) {
|
345 |
|
|
// This will almost certainly never happen, but the error message
|
346 |
|
|
// would be incomprehensible without this check.
|
347 |
|
|
throw new DateIcalParseException(t('Feed import failed! The event with UID %uid is invalid: it has a DTEND but no DTSTART!', array('%uid' => $uid)));
|
348 |
|
|
}
|
349 |
|
|
|
350 |
|
|
if (module_exists('date_all_day')) {
|
351 |
|
|
// If the Date All Day module is installed, we need to rewind the
|
352 |
|
|
// DTEND by one day, because of the problem with FeedsDateTime
|
353 |
|
|
// mentioned below.
|
354 |
|
|
$prev_day = iCalUtilityFunctions::_duration2date($property['value'], array('day' => -1));
|
355 |
|
|
$property['value'] = $prev_day;
|
356 |
|
|
}
|
357 |
|
|
}
|
358 |
55670b15
|
Assos Assos
|
|
359 |
be880f98
|
Florent Torregrosa
|
// FeedsDateTime->setTimezone() ignores timezone changes made to dates
|
360 |
|
|
// with no time element, which means we can't compensate for the Date
|
361 |
|
|
// module's automatic timezone conversion when it writes to the DB. To
|
362 |
|
|
// get around that, we must add 00:00:00 explicitly, even though this
|
363 |
|
|
// causes other problems (see above and below).
|
364 |
|
|
$date_string = sprintf('%d-%d-%d 00:00:00', $property['value']['year'], $property['value']['month'], $property['value']['day']);
|
365 |
|
|
// Use the server's timezone rather than letting it default to UTC.
|
366 |
|
|
// This will help ensure that the date value doesn't get messed up when
|
367 |
|
|
// Date converts its timezone as the value is read from the database.
|
368 |
|
|
// This is *essential* for All Day events, because Date stores them as
|
369 |
|
|
// '2013-10-03 00:00:00' in the database, rather than doing the sensible
|
370 |
|
|
// thing and storing them as '2013-10-03'.
|
371 |
|
|
// NOTE TO MAINTAINERS:
|
372 |
|
|
// This will not work properly if the site is configured to allow users
|
373 |
|
|
// to set their own timezone. Unfortunately, there isn't anything that
|
374 |
|
|
// Date iCal can do about that, as far as I can tell.
|
375 |
|
|
$datetimezone = new DateTimeZone(date_default_timezone_get());
|
376 |
|
|
}
|
377 |
|
|
else {
|
378 |
|
|
// This is a DATE-TIME property.
|
379 |
|
|
$date_string = iCalUtilityFunctions::_format_date_time($property['value']);
|
380 |
|
|
|
381 |
|
|
// Allow modules to alter the timezone string. This also allows for
|
382 |
|
|
// setting a TZID when one was not originally set for this property.
|
383 |
|
|
$tzid = isset($property['params']['TZID']) ? $property['params']['TZID'] : NULL;
|
384 |
|
|
$context = array(
|
385 |
|
|
'property_key' => $property_key,
|
386 |
|
|
'calendar_component' => $vcalendar_component,
|
387 |
|
|
'calendar' => $this->calendar,
|
388 |
|
|
'feeeds_source' => $this->source,
|
389 |
|
|
'feeds_fetcher_result' => $this->fetcherResult,
|
390 |
|
|
);
|
391 |
|
|
drupal_alter('date_ical_import_timezone', $tzid, $context);
|
392 |
|
|
|
393 |
|
|
if (isset($tzid)) {
|
394 |
|
|
$datetimezone = $this->_tzid_to_datetimezone($tzid);
|
395 |
|
|
}
|
396 |
|
|
elseif (isset($this->xtimezone)) {
|
397 |
|
|
// No timezone was set on the parsed date property, so if a timezone
|
398 |
|
|
// was detected for the entire iCal feed, use it.
|
399 |
|
|
$datetimezone = $this->xtimezone;
|
400 |
|
|
}
|
401 |
|
|
else {
|
402 |
|
|
$msg = t("No timezone was detected for one or more of the events in this feed, forcing Date iCal to use this server's timezone as a fallback.<br>
|
403 |
|
|
To make timezone-less events use a different timezone, implement hook_date_ical_import_timezone_alter() in a custom module.");
|
404 |
|
|
drupal_set_message($msg, 'status', FALSE);
|
405 |
|
|
$this->source->log('parse', $msg, array(), WATCHDOG_NOTICE);
|
406 |
|
|
$datetimezone = new DateTimeZone(date_default_timezone_get());
|
407 |
|
|
}
|
408 |
|
|
}
|
409 |
55670b15
|
Assos Assos
|
|
410 |
|
|
$datetime = new FeedsDateTime($date_string, $datetimezone);
|
411 |
|
|
return $datetime;
|
412 |
be880f98
|
Florent Torregrosa
|
}
|
413 |
55670b15
|
Assos Assos
|
|
414 |
be880f98
|
Florent Torregrosa
|
/**
|
415 |
55670b15
|
Assos Assos
|
* Handler that parses multi-value fields, like the CATEGORIES component.
|
416 |
be880f98
|
Florent Torregrosa
|
*
|
417 |
|
|
* @return array
|
418 |
|
|
* An array of strings contaning the individual values.
|
419 |
|
|
*/
|
420 |
|
|
public function parseMultivalueProperty($property_key, $vcalendar_component) {
|
421 |
|
|
// Since we're not telling it to give us the params data, $property will
|
422 |
|
|
// be either FALSE, a string, or an array of strings.
|
423 |
|
|
$property = $vcalendar_component->getProperty($property_key);
|
424 |
|
|
if (empty($property)) {
|
425 |
|
|
// If this multi-value property is being mapped to a Taxonomy field,
|
426 |
|
|
// Feeds will interpret anything besides empty array as an array of
|
427 |
|
|
// empty values (e.g. array('')). This will create a term for that
|
428 |
|
|
// empty value, rather than leaving the field blank.
|
429 |
|
|
return array();
|
430 |
|
|
}
|
431 |
|
|
if (!is_array($property)) {
|
432 |
|
|
$property = array($property);
|
433 |
|
|
}
|
434 |
|
|
return $property;
|
435 |
|
|
}
|
436 |
55670b15
|
Assos Assos
|
|
437 |
be880f98
|
Florent Torregrosa
|
/**
|
438 |
55670b15
|
Assos Assos
|
* Handler that parses RRULE, RDATE, EXRULE, and EXDATE together.
|
439 |
be880f98
|
Florent Torregrosa
|
*
|
440 |
|
|
* @return string
|
441 |
55670b15
|
Assos Assos
|
* The RRULE, RDATE, EXRULE, and EXDATE values concatinated with |.
|
442 |
be880f98
|
Florent Torregrosa
|
*/
|
443 |
|
|
public function parseRepeatProperty($property_key, $vcalendar_component) {
|
444 |
|
|
if ($vcalendar_component->getProperty($property_key) === FALSE) {
|
445 |
|
|
return NULL;
|
446 |
|
|
}
|
447 |
55670b15
|
Assos Assos
|
|
448 |
|
|
$uid = $vcalendar_component->getProperty('UID');
|
449 |
|
|
$count = $this->config['indefinite_count'];
|
450 |
be880f98
|
Florent Torregrosa
|
// Due to a few bugs and limitations with Date Repeat, we need to massage
|
451 |
|
|
// the RRULE a bit.
|
452 |
|
|
if (count($vcalendar_component->rrule) > 1) {
|
453 |
55670b15
|
Assos Assos
|
$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.
|
454 |
|
|
If your events need to have a complex repeat pattern, using RDATEs should help.';
|
455 |
|
|
watchdog('date_ical', $msg, array('%uid' => $uid), 'warning');
|
456 |
|
|
drupal_set_message('At least one of the events in this iCal feed has multiple RRULEs, but the Date Repeat module only supports one.
|
457 |
|
|
Only the first RRULE in an event will be used.', 'warning', FALSE);
|
458 |
|
|
|
459 |
be880f98
|
Florent Torregrosa
|
// Date Repeat will get extremely confused if it's sent multiple RRULE
|
460 |
|
|
// values, so we need to manually pare it down to only the first one.
|
461 |
|
|
$vcalendar_component->rrule = array($vcalendar_component->rrule[0]);
|
462 |
|
|
}
|
463 |
|
|
foreach ($vcalendar_component->rrule as &$rrule_data) {
|
464 |
|
|
// RRULEs must have an INTERVAL, or Date Repeat will throw errors.
|
465 |
|
|
if (!isset($rrule_data['value']['INTERVAL'])) {
|
466 |
|
|
$rrule_data['value']['INTERVAL'] = '1';
|
467 |
|
|
}
|
468 |
55670b15
|
Assos Assos
|
|
469 |
ca0757b9
|
Assos Assos
|
if ((!isset($rrule_data['value']['COUNT']) && !isset($rrule_data['value']['UNTIL']))) {
|
470 |
55670b15
|
Assos Assos
|
$msg = "The event with UID %uid has an indefinitely repeating RRULE, which the Date Repeat module doesn't support.
|
471 |
|
|
As a workaround, Date iCal set the repeat count to @count. This value can be customized in the iCal parser settings.";
|
472 |
|
|
watchdog('date_ical', $msg, array('%uid' => $uid, '@count' => $count), WATCHDOG_WARNING);
|
473 |
ca0757b9
|
Assos Assos
|
if ($this->config['indefinite_message_display']) {
|
474 |
|
|
drupal_set_message(
|
475 |
|
|
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>
|
476 |
|
|
As a workaround, Date iCal set the repeat count to @count. This value can be customized in the iCal parser settings.",
|
477 |
|
|
array('@count' => $count)),
|
478 |
|
|
'warning',
|
479 |
|
|
FALSE
|
480 |
|
|
);
|
481 |
|
|
}
|
482 |
be880f98
|
Florent Torregrosa
|
$rrule_data['value']['COUNT'] = $this->config['indefinite_count'];
|
483 |
|
|
}
|
484 |
|
|
}
|
485 |
55670b15
|
Assos Assos
|
|
486 |
be880f98
|
Florent Torregrosa
|
$rrule = trim($vcalendar_component->createRrule());
|
487 |
|
|
$rdate = trim($vcalendar_component->createRdate());
|
488 |
|
|
$exrule = trim($vcalendar_component->createExrule());
|
489 |
|
|
$exdate = trim($vcalendar_component->createExdate());
|
490 |
|
|
return "$rrule|$rdate|$exrule|$exdate";
|
491 |
|
|
}
|
492 |
55670b15
|
Assos Assos
|
|
493 |
be880f98
|
Florent Torregrosa
|
/**
|
494 |
|
|
* Internal helper function for creating DateTimeZone objects.
|
495 |
|
|
*/
|
496 |
|
|
protected function _tzid_to_datetimezone($tzid) {
|
497 |
|
|
try {
|
498 |
|
|
$datetimezone = new DateTimeZone($tzid);
|
499 |
|
|
}
|
500 |
|
|
catch (Exception $e) {
|
501 |
|
|
// In case this is a Windows TZID, read the mapping file to try and
|
502 |
|
|
// convert it to a real TZID.
|
503 |
|
|
$zones = file_get_contents(drupal_get_path('module', 'date_ical') . '/libraries/windowsZones.json');
|
504 |
|
|
$zones_assoc = json_decode($zones, TRUE);
|
505 |
|
|
$windows_to_olson_map = array();
|
506 |
|
|
foreach ($zones_assoc['supplemental']['windowsZones']['mapTimezones'] as $mapTimezone) {
|
507 |
|
|
if ($mapTimezone['mapZone']['_other'] == $tzid) {
|
508 |
55670b15
|
Assos Assos
|
// Parse out the space-separated TZIDs from $mapTimezone['mapZone']['_type'].
|
509 |
be880f98
|
Florent Torregrosa
|
$tzids = preg_split('/\s/', $mapTimezone['mapZone']['_type']);
|
510 |
|
|
try {
|
511 |
|
|
// They all have the same UTC offset, so for our purposes we can
|
512 |
|
|
// just take the first one.
|
513 |
|
|
return new DateTimeZone($tzids[0]);
|
514 |
|
|
}
|
515 |
|
|
catch (Exception $e) {
|
516 |
|
|
// If this one also fails, we're out of luck, so just fall through
|
517 |
|
|
// to the regular error report code.
|
518 |
|
|
break;
|
519 |
|
|
}
|
520 |
|
|
}
|
521 |
|
|
}
|
522 |
|
|
|
523 |
|
|
$tz_wiki = l(t('here'), 'http://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List');
|
524 |
|
|
$help = l(t('README'), 'admin/help/date_ical', array('absolute' => TRUE));
|
525 |
|
|
$msg = t(
|
526 |
|
|
'"@tz" is not a valid timezone (see the TZ column !here), so Date iCal had to fall back to UTC (which is probably wrong!).<br>
|
527 |
|
|
Please read the Date iCal !readme for instructions on how to fix this.',
|
528 |
|
|
array('@tz' => $tzid, '!here' => $tz_wiki, '!readme' => $help)
|
529 |
|
|
);
|
530 |
|
|
$this->source->log('parse', $msg, array(), WATCHDOG_WARNING);
|
531 |
|
|
drupal_set_message($msg, 'warning', FALSE);
|
532 |
|
|
$datetimezone = new DateTimeZone('UTC');
|
533 |
|
|
}
|
534 |
|
|
return $datetimezone;
|
535 |
|
|
}
|
536 |
55670b15
|
Assos Assos
|
|
537 |
|
|
/**
|
538 |
|
|
* Internal helper function for skipping old events.
|
539 |
|
|
*/
|
540 |
|
|
protected function _skip_current_event() {
|
541 |
|
|
// Must use !isset() here, because 0 and NULL mean different things.
|
542 |
|
|
if (!isset($this->config['skip_days'])) {
|
543 |
|
|
return FALSE;
|
544 |
|
|
}
|
545 |
|
|
$compare_date = isset($this->parsed_data['DTEND']) ? $this->parsed_data['DTEND'] : $this->parsed_data['DTSTART'];
|
546 |
|
|
$skip_date = new FeedsDateTime("today -{$this->config['skip_days']} days", $compare_date->getTimezone());
|
547 |
|
|
$skip = ($skip_date > $compare_date);
|
548 |
|
|
return $skip;
|
549 |
|
|
}
|
550 |
be880f98
|
Florent Torregrosa
|
} |