tilia-vobject 4.0.0.pre.alpha2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rubocop.yml +32 -0
- data/.simplecov +4 -0
- data/.travis.yml +3 -0
- data/CHANGELOG.sabre.md +626 -0
- data/CONTRIBUTING.md +25 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +68 -0
- data/LICENSE +27 -0
- data/LICENSE.sabre +27 -0
- data/README.md +63 -0
- data/Rakefile +17 -0
- data/bin/vobject +7 -0
- data/lib/tilia/v_object/birthday_calendar_generator.rb +142 -0
- data/lib/tilia/v_object/cli.rb +582 -0
- data/lib/tilia/v_object/component/available.rb +107 -0
- data/lib/tilia/v_object/component/v_alarm.rb +114 -0
- data/lib/tilia/v_object/component/v_availability.rb +128 -0
- data/lib/tilia/v_object/component/v_calendar.rb +468 -0
- data/lib/tilia/v_object/component/v_card.rb +457 -0
- data/lib/tilia/v_object/component/v_event.rb +127 -0
- data/lib/tilia/v_object/component/v_free_busy.rb +81 -0
- data/lib/tilia/v_object/component/v_journal.rb +75 -0
- data/lib/tilia/v_object/component/v_time_zone.rb +51 -0
- data/lib/tilia/v_object/component/v_todo.rb +147 -0
- data/lib/tilia/v_object/component.rb +591 -0
- data/lib/tilia/v_object/date_time_parser.rb +486 -0
- data/lib/tilia/v_object/document.rb +218 -0
- data/lib/tilia/v_object/element_list.rb +18 -0
- data/lib/tilia/v_object/eof_exception.rb +8 -0
- data/lib/tilia/v_object/free_busy_data.rb +149 -0
- data/lib/tilia/v_object/free_busy_generator.rb +465 -0
- data/lib/tilia/v_object/i_tip/broker.rb +909 -0
- data/lib/tilia/v_object/i_tip/i_tip_exception.rb +9 -0
- data/lib/tilia/v_object/i_tip/message.rb +109 -0
- data/lib/tilia/v_object/i_tip/same_organizer_for_all_components_exception.rb +13 -0
- data/lib/tilia/v_object/i_tip.rb +10 -0
- data/lib/tilia/v_object/node.rb +192 -0
- data/lib/tilia/v_object/parameter.rb +327 -0
- data/lib/tilia/v_object/parse_exception.rb +7 -0
- data/lib/tilia/v_object/parser/json.rb +149 -0
- data/lib/tilia/v_object/parser/mime_dir.rb +543 -0
- data/lib/tilia/v_object/parser/parser.rb +61 -0
- data/lib/tilia/v_object/parser/xml/element/key_value.rb +60 -0
- data/lib/tilia/v_object/parser/xml/element.rb +11 -0
- data/lib/tilia/v_object/parser/xml.rb +322 -0
- data/lib/tilia/v_object/parser.rb +10 -0
- data/lib/tilia/v_object/property/binary.rb +96 -0
- data/lib/tilia/v_object/property/boolean.rb +57 -0
- data/lib/tilia/v_object/property/flat_text.rb +52 -0
- data/lib/tilia/v_object/property/float_value.rb +107 -0
- data/lib/tilia/v_object/property/i_calendar/cal_address.rb +49 -0
- data/lib/tilia/v_object/property/i_calendar/date.rb +15 -0
- data/lib/tilia/v_object/property/i_calendar/date_time.rb +330 -0
- data/lib/tilia/v_object/property/i_calendar/duration.rb +65 -0
- data/lib/tilia/v_object/property/i_calendar/period.rb +124 -0
- data/lib/tilia/v_object/property/i_calendar/recur.rb +173 -0
- data/lib/tilia/v_object/property/i_calendar.rb +14 -0
- data/lib/tilia/v_object/property/integer_value.rb +60 -0
- data/lib/tilia/v_object/property/text.rb +352 -0
- data/lib/tilia/v_object/property/time.rb +85 -0
- data/lib/tilia/v_object/property/unknown.rb +30 -0
- data/lib/tilia/v_object/property/uri.rb +78 -0
- data/lib/tilia/v_object/property/utc_offset.rb +56 -0
- data/lib/tilia/v_object/property/v_card/date.rb +31 -0
- data/lib/tilia/v_object/property/v_card/date_and_or_time.rb +343 -0
- data/lib/tilia/v_object/property/v_card/date_time.rb +22 -0
- data/lib/tilia/v_object/property/v_card/language_tag.rb +41 -0
- data/lib/tilia/v_object/property/v_card/time_stamp.rb +74 -0
- data/lib/tilia/v_object/property/v_card.rb +13 -0
- data/lib/tilia/v_object/property.rb +532 -0
- data/lib/tilia/v_object/reader.rb +73 -0
- data/lib/tilia/v_object/recur/event_iterator.rb +417 -0
- data/lib/tilia/v_object/recur/no_instances_exception.rb +11 -0
- data/lib/tilia/v_object/recur/r_date_iterator.rb +138 -0
- data/lib/tilia/v_object/recur/r_rule_iterator.rb +717 -0
- data/lib/tilia/v_object/recur.rb +10 -0
- data/lib/tilia/v_object/settings.rb +32 -0
- data/lib/tilia/v_object/splitter/i_calendar.rb +95 -0
- data/lib/tilia/v_object/splitter/splitter_interface.rb +31 -0
- data/lib/tilia/v_object/splitter/v_card.rb +56 -0
- data/lib/tilia/v_object/splitter.rb +9 -0
- data/lib/tilia/v_object/string_util.rb +58 -0
- data/lib/tilia/v_object/time_zone_data/exchange_zones.rb +96 -0
- data/lib/tilia/v_object/time_zone_data/lotus_zones.rb +104 -0
- data/lib/tilia/v_object/time_zone_data/php_zones.rb +49 -0
- data/lib/tilia/v_object/time_zone_data/windows_zones.rb +121 -0
- data/lib/tilia/v_object/time_zone_data.rb +10 -0
- data/lib/tilia/v_object/time_zone_util.rb +213 -0
- data/lib/tilia/v_object/uuid_util.rb +51 -0
- data/lib/tilia/v_object/v_card_converter.rb +354 -0
- data/lib/tilia/v_object/version.rb +9 -0
- data/lib/tilia/v_object/writer.rb +56 -0
- data/lib/tilia/v_object.rb +45 -0
- data/lib/tilia/vobject.rb +1 -0
- data/resources/schema/xcal.rng +1192 -0
- data/resources/schema/xcard.rng +388 -0
- data/test/test_helper.rb +56 -0
- data/test/v_object/attach_issue_test.rb +19 -0
- data/test/v_object/birthday_calendar_generator_test.rb +463 -0
- data/test/v_object/cli_mock.rb +19 -0
- data/test/v_object/cli_test.rb +460 -0
- data/test/v_object/component/available_test.rb +59 -0
- data/test/v_object/component/v_alarm_test.rb +160 -0
- data/test/v_object/component/v_availability_test.rb +388 -0
- data/test/v_object/component/v_calendar_test.rb +646 -0
- data/test/v_object/component/v_card_test.rb +258 -0
- data/test/v_object/component/v_event_test.rb +85 -0
- data/test/v_object/component/v_free_busy_test.rb +59 -0
- data/test/v_object/component/v_journal_test.rb +85 -0
- data/test/v_object/component/v_time_zone_test.rb +47 -0
- data/test/v_object/component/v_todo_test.rb +172 -0
- data/test/v_object/component_test.rb +419 -0
- data/test/v_object/date_time_parser_test.rb +526 -0
- data/test/v_object/document_test.rb +71 -0
- data/test/v_object/element_list_test.rb +27 -0
- data/test/v_object/em_client_test.rb +53 -0
- data/test/v_object/empty_parameter_test.rb +65 -0
- data/test/v_object/empty_value_issue_test.rb +25 -0
- data/test/v_object/fake_component.rb +21 -0
- data/test/v_object/free_busy_data_test.rb +285 -0
- data/test/v_object/free_busy_generator_test.rb +637 -0
- data/test/v_object/google_colon_escaping_test.rb +27 -0
- data/test/v_object/i_calendar/attach_parse_test.rb +24 -0
- data/test/v_object/i_tip/broker_attendee_reply_test.rb +1042 -0
- data/test/v_object/i_tip/broker_delete_event_test.rb +175 -0
- data/test/v_object/i_tip/broker_new_event_test.rb +440 -0
- data/test/v_object/i_tip/broker_process_message_test.rb +153 -0
- data/test/v_object/i_tip/broker_process_reply_test.rb +402 -0
- data/test/v_object/i_tip/broker_tester.rb +71 -0
- data/test/v_object/i_tip/broker_update_event_test.rb +763 -0
- data/test/v_object/i_tip/evolution_test.rb +2644 -0
- data/test/v_object/i_tip/message_test.rb +25 -0
- data/test/v_object/issue153.vcf +352 -0
- data/test/v_object/issue153_test.rb +12 -0
- data/test/v_object/issue26_test.rb +25 -0
- data/test/v_object/issue36_work_around_test.rb +37 -0
- data/test/v_object/issue40_test.rb +26 -0
- data/test/v_object/issue64.vcf +351 -0
- data/test/v_object/issue64_test.rb +17 -0
- data/test/v_object/issue96_test.rb +22 -0
- data/test/v_object/issue_undefined_index_test.rb +24 -0
- data/test/v_object/j_cal_test.rb +150 -0
- data/test/v_object/j_card_test.rb +192 -0
- data/test/v_object/line_folding_issue_test.rb +19 -0
- data/test/v_object/mock_document.rb +6 -0
- data/test/v_object/parameter_test.rb +109 -0
- data/test/v_object/parser/json_test.rb +370 -0
- data/test/v_object/parser/mime_dir_test.rb +14 -0
- data/test/v_object/parser/quoted_printable_test.rb +78 -0
- data/test/v_object/parser/xml_test.rb +2563 -0
- data/test/v_object/property/binary_test.rb +12 -0
- data/test/v_object/property/boolean_test.rb +18 -0
- data/test/v_object/property/compound_test.rb +43 -0
- data/test/v_object/property/float_test.rb +20 -0
- data/test/v_object/property/i_calendar/cal_address_test.rb +26 -0
- data/test/v_object/property/i_calendar/date_time_test.rb +303 -0
- data/test/v_object/property/i_calendar/duration_test.rb +14 -0
- data/test/v_object/property/i_calendar/recur_test.rb +39 -0
- data/test/v_object/property/text_test.rb +81 -0
- data/test/v_object/property/v_card/date_and_or_time_test.rb +205 -0
- data/test/v_object/property/v_card/language_tag_test.rb +35 -0
- data/test/v_object/property_test.rb +338 -0
- data/test/v_object/reader_test.rb +403 -0
- data/test/v_object/recur/event_iterator/by_month_in_daily_test.rb +52 -0
- data/test/v_object/recur/event_iterator/by_set_pos_hang_test.rb +55 -0
- data/test/v_object/recur/event_iterator/expand_floating_times_test.rb +109 -0
- data/test/v_object/recur/event_iterator/fifth_tuesday_problem_test.rb +45 -0
- data/test/v_object/recur/event_iterator/incorrect_expand_test.rb +53 -0
- data/test/v_object/recur/event_iterator/infinite_loop_problem_test.rb +75 -0
- data/test/v_object/recur/event_iterator/issue48_test.rb +43 -0
- data/test/v_object/recur/event_iterator/issue50_test.rb +123 -0
- data/test/v_object/recur/event_iterator/main_test.rb +1222 -0
- data/test/v_object/recur/event_iterator/missing_overridden_test.rb +55 -0
- data/test/v_object/recur/event_iterator/no_instances_test.rb +32 -0
- data/test/v_object/recur/event_iterator/override_first_event_test.rb +106 -0
- data/test/v_object/recur/r_date_iterator_test.rb +44 -0
- data/test/v_object/recur/r_rule_iterator_test.rb +608 -0
- data/test/v_object/recurrence_iterator/UntilRespectsTimezoneTest.ics +39 -0
- data/test/v_object/slash_r_test.rb +15 -0
- data/test/v_object/splitter/i_calendar_test.rb +299 -0
- data/test/v_object/splitter/v_card_test.rb +173 -0
- data/test/v_object/string_util_test.rb +37 -0
- data/test/v_object/test_case.rb +42 -0
- data/test/v_object/time_zone_util_test.rb +271 -0
- data/test/v_object/uuid_util_test.rb +18 -0
- data/test/v_object/v_card21_test.rb +43 -0
- data/test/v_object/v_card_converter_test.rb +419 -0
- data/test/v_object/version_test.rb +15 -0
- data/test/v_object/writer_test.rb +33 -0
- data/tilia-vobject.gemspec +17 -0
- metadata +308 -0
@@ -0,0 +1,468 @@
|
|
1
|
+
module Tilia
|
2
|
+
module VObject
|
3
|
+
class Component
|
4
|
+
# The VCalendar component.
|
5
|
+
#
|
6
|
+
# This component adds functionality to a component, specific for a VCALENDAR.
|
7
|
+
class VCalendar < Document
|
8
|
+
# The default name for this component.
|
9
|
+
#
|
10
|
+
# This should be 'VCALENDAR' or 'VCARD'.
|
11
|
+
#
|
12
|
+
# @var string
|
13
|
+
@default_name = 'VCALENDAR'
|
14
|
+
|
15
|
+
# This is a list of components, and which classes they should map to.
|
16
|
+
#
|
17
|
+
# @var array
|
18
|
+
@component_map = {
|
19
|
+
'VALARM' => Component::VAlarm,
|
20
|
+
'VEVENT' => Component::VEvent,
|
21
|
+
'VFREEBUSY' => Component::VFreeBusy,
|
22
|
+
'VAVAILABILITY' => Component::VAvailability,
|
23
|
+
'AVAILABLE' => Component::Available,
|
24
|
+
'VJOURNAL' => Component::VJournal,
|
25
|
+
'VTIMEZONE' => Component::VTimeZone,
|
26
|
+
'VTODO' => Component::VTodo
|
27
|
+
}
|
28
|
+
|
29
|
+
# List of value-types, and which classes they map to.
|
30
|
+
#
|
31
|
+
# @var array
|
32
|
+
@value_map = {
|
33
|
+
'BINARY' => Property::Binary,
|
34
|
+
'BOOLEAN' => Property::Boolean,
|
35
|
+
'CAL-ADDRESS' => Property::ICalendar::CalAddress,
|
36
|
+
'DATE' => Property::ICalendar::Date,
|
37
|
+
'DATE-TIME' => Property::ICalendar::DateTime,
|
38
|
+
'DURATION' => Property::ICalendar::Duration,
|
39
|
+
'FLOAT' => Property::FloatValue,
|
40
|
+
'INTEGER' => Property::IntegerValue,
|
41
|
+
'PERIOD' => Property::ICalendar::Period,
|
42
|
+
'RECUR' => Property::ICalendar::Recur,
|
43
|
+
'TEXT' => Property::Text,
|
44
|
+
'TIME' => Property::Time,
|
45
|
+
'UNKNOWN' => Property::Unknown, # jCard / jCal-only.
|
46
|
+
'URI' => Property::Uri,
|
47
|
+
'UTC-OFFSET' => Property::UtcOffset
|
48
|
+
}
|
49
|
+
|
50
|
+
# List of properties, and which classes they map to.
|
51
|
+
#
|
52
|
+
# @var array
|
53
|
+
@property_map = {
|
54
|
+
# Calendar properties
|
55
|
+
'CALSCALE' => Property::FlatText,
|
56
|
+
'METHOD' => Property::FlatText,
|
57
|
+
'PRODID' => Property::FlatText,
|
58
|
+
'VERSION' => Property::FlatText,
|
59
|
+
|
60
|
+
# Component properties
|
61
|
+
'ATTACH' => Property::Uri,
|
62
|
+
'CATEGORIES' => Property::Text,
|
63
|
+
'CLASS' => Property::FlatText,
|
64
|
+
'COMMENT' => Property::FlatText,
|
65
|
+
'DESCRIPTION' => Property::FlatText,
|
66
|
+
'GEO' => Property::FloatValue,
|
67
|
+
'LOCATION' => Property::FlatText,
|
68
|
+
'PERCENT-COMPLETE' => Property::IntegerValue,
|
69
|
+
'PRIORITY' => Property::IntegerValue,
|
70
|
+
'RESOURCES' => Property::Text,
|
71
|
+
'STATUS' => Property::FlatText,
|
72
|
+
'SUMMARY' => Property::FlatText,
|
73
|
+
|
74
|
+
# Date and Time Component Properties
|
75
|
+
'COMPLETED' => Property::ICalendar::DateTime,
|
76
|
+
'DTEND' => Property::ICalendar::DateTime,
|
77
|
+
'DUE' => Property::ICalendar::DateTime,
|
78
|
+
'DTSTART' => Property::ICalendar::DateTime,
|
79
|
+
'DURATION' => Property::ICalendar::Duration,
|
80
|
+
'FREEBUSY' => Property::ICalendar::Period,
|
81
|
+
'TRANSP' => Property::FlatText,
|
82
|
+
|
83
|
+
# Time Zone Component Properties
|
84
|
+
'TZID' => Property::FlatText,
|
85
|
+
'TZNAME' => Property::FlatText,
|
86
|
+
'TZOFFSETFROM' => Property::UtcOffset,
|
87
|
+
'TZOFFSETTO' => Property::UtcOffset,
|
88
|
+
'TZURL' => Property::Uri,
|
89
|
+
|
90
|
+
# Relationship Component Properties
|
91
|
+
'ATTENDEE' => Property::ICalendar::CalAddress,
|
92
|
+
'CONTACT' => Property::FlatText,
|
93
|
+
'ORGANIZER' => Property::ICalendar::CalAddress,
|
94
|
+
'RECURRENCE-ID' => Property::ICalendar::DateTime,
|
95
|
+
'RELATED-TO' => Property::FlatText,
|
96
|
+
'URL' => Property::Uri,
|
97
|
+
'UID' => Property::FlatText,
|
98
|
+
|
99
|
+
# Recurrence Component Properties
|
100
|
+
'EXDATE' => Property::ICalendar::DateTime,
|
101
|
+
'RDATE' => Property::ICalendar::DateTime,
|
102
|
+
'RRULE' => Property::ICalendar::Recur,
|
103
|
+
'EXRULE' => Property::ICalendar::Recur, # Deprecated since rfc5545
|
104
|
+
|
105
|
+
# Alarm Component Properties
|
106
|
+
'ACTION' => Property::FlatText,
|
107
|
+
'REPEAT' => Property::IntegerValue,
|
108
|
+
'TRIGGER' => Property::ICalendar::Duration,
|
109
|
+
|
110
|
+
# Change Management Component Properties
|
111
|
+
'CREATED' => Property::ICalendar::DateTime,
|
112
|
+
'DTSTAMP' => Property::ICalendar::DateTime,
|
113
|
+
'LAST-MODIFIED' => Property::ICalendar::DateTime,
|
114
|
+
'SEQUENCE' => Property::IntegerValue,
|
115
|
+
|
116
|
+
# Request Status
|
117
|
+
'REQUEST-STATUS' => Property::Text,
|
118
|
+
|
119
|
+
# Additions from draft-daboo-valarm-extensions-04
|
120
|
+
'ALARM-AGENT' => Property::Text,
|
121
|
+
'ACKNOWLEDGED' => Property::ICalendar::DateTime,
|
122
|
+
'PROXIMITY' => Property::Text,
|
123
|
+
'DEFAULT-ALARM' => Property::Boolean,
|
124
|
+
|
125
|
+
# Additions from draft-daboo-calendar-availability-05
|
126
|
+
'BUSYTYPE' => Property::Text
|
127
|
+
}
|
128
|
+
|
129
|
+
# Returns the current document type.
|
130
|
+
#
|
131
|
+
# @return int
|
132
|
+
def document_type
|
133
|
+
self.class::ICALENDAR20
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns a list of all 'base components'. For instance, if an Event has
|
137
|
+
# a recurrence rule, and one instance is overridden, the overridden event
|
138
|
+
# will have the same UID, but will be excluded from this list.
|
139
|
+
#
|
140
|
+
# VTIMEZONE components will always be excluded.
|
141
|
+
#
|
142
|
+
# @param string component_name filter by component name
|
143
|
+
#
|
144
|
+
# @return VObject\Component[]
|
145
|
+
def base_components(component_name = nil)
|
146
|
+
is_base_component = lambda do |component|
|
147
|
+
return false unless component.is_a?(Component)
|
148
|
+
return false if component.name == 'VTIMEZONE'
|
149
|
+
return false if component.key?('RECURRENCE-ID')
|
150
|
+
true
|
151
|
+
end
|
152
|
+
|
153
|
+
if component_name
|
154
|
+
# Early exit
|
155
|
+
return select(component_name).select is_base_component
|
156
|
+
end
|
157
|
+
|
158
|
+
components = []
|
159
|
+
children.each do |child_group|
|
160
|
+
do_skip = false
|
161
|
+
child_group.each do |child|
|
162
|
+
unless child.is_a?(Component)
|
163
|
+
# If one child is not a component, they all are so we skip
|
164
|
+
# the entire group.
|
165
|
+
do_skip = true
|
166
|
+
break
|
167
|
+
end
|
168
|
+
components << child if is_base_component.call(child)
|
169
|
+
end
|
170
|
+
next if do_skip
|
171
|
+
end
|
172
|
+
|
173
|
+
components
|
174
|
+
end
|
175
|
+
|
176
|
+
# Returns the first component that is not a VTIMEZONE, and does not have
|
177
|
+
# an RECURRENCE-ID.
|
178
|
+
#
|
179
|
+
# If there is no such component, null will be returned.
|
180
|
+
#
|
181
|
+
# @param string component_name filter by component name
|
182
|
+
#
|
183
|
+
# @return VObject\Component|null
|
184
|
+
def base_component(component_name = nil)
|
185
|
+
is_base_component = lambda do |component|
|
186
|
+
return false unless component.is_a?(Component)
|
187
|
+
return false if component.name == 'VTIMEZONE'
|
188
|
+
return false if component.key?('RECURRENCE-ID')
|
189
|
+
true
|
190
|
+
end
|
191
|
+
|
192
|
+
if component_name
|
193
|
+
select(component_name).each do |child|
|
194
|
+
return child if is_base_component.call(child)
|
195
|
+
end
|
196
|
+
return nil
|
197
|
+
end
|
198
|
+
|
199
|
+
children.each do |child_group|
|
200
|
+
child_group.each do |child|
|
201
|
+
return child if is_base_component.call(child)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
nil
|
206
|
+
end
|
207
|
+
|
208
|
+
# If this calendar object, has events with recurrence rules, this method
|
209
|
+
# can be used to expand the event into multiple sub-events.
|
210
|
+
#
|
211
|
+
# Each event will be stripped from it's recurrence information, and only
|
212
|
+
# the instances of the event in the specified timerange will be left
|
213
|
+
# alone.
|
214
|
+
#
|
215
|
+
# In addition, this method will cause timezone information to be stripped,
|
216
|
+
# and normalized to UTC.
|
217
|
+
#
|
218
|
+
# This method will alter the VCalendar. This cannot be reversed.
|
219
|
+
#
|
220
|
+
# This functionality is specifically used by the CalDAV standard. It is
|
221
|
+
# possible for clients to request expand events, if they are rather simple
|
222
|
+
# clients and do not have the possibility to calculate recurrences.
|
223
|
+
#
|
224
|
+
# @param DateTimeInterface start
|
225
|
+
# @param DateTimeInterface end
|
226
|
+
# @param DateTimeZone time_zone reference timezone for floating dates and
|
227
|
+
# times.
|
228
|
+
#
|
229
|
+
# @return void
|
230
|
+
def expand(start, ending, time_zone = nil)
|
231
|
+
new_events = []
|
232
|
+
|
233
|
+
time_zone = ActiveSupport::TimeZone.new('UTC') unless time_zone
|
234
|
+
|
235
|
+
# An array of events. Events are indexed by UID. Each item in this
|
236
|
+
# array is a list of one or more events that match the UID.
|
237
|
+
recurring_events = {}
|
238
|
+
|
239
|
+
select('VEVENT').each do |vevent|
|
240
|
+
uid = vevent['UID'].to_s
|
241
|
+
fail 'Event did not have a UID!' if uid.blank?
|
242
|
+
|
243
|
+
if vevent.key?('RECURRENCE-ID') || vevent.key?('RRULE')
|
244
|
+
if recurring_events.key?(uid)
|
245
|
+
recurring_events[uid] << vevent
|
246
|
+
else
|
247
|
+
recurring_events[uid] = [vevent]
|
248
|
+
end
|
249
|
+
next
|
250
|
+
end
|
251
|
+
|
252
|
+
unless vevent.key?('RRULE')
|
253
|
+
new_events << vevent if vevent.in_time_range?(start, ending)
|
254
|
+
next
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
recurring_events.each do |_uid, events|
|
259
|
+
begin
|
260
|
+
it = Recur::EventIterator.new(events, time_zone)
|
261
|
+
rescue Recur::NoInstancesException
|
262
|
+
# This event is recurring, but it doesn't have a single
|
263
|
+
# instance. We are skipping this event from the output
|
264
|
+
# entirely.
|
265
|
+
next
|
266
|
+
end
|
267
|
+
|
268
|
+
it.fast_forward(start)
|
269
|
+
|
270
|
+
while it.valid && it.dt_start < ending
|
271
|
+
new_events << it.event_object if it.dt_end > start
|
272
|
+
it.next
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Wiping out all old VEVENT objects
|
277
|
+
delete('VEVENT')
|
278
|
+
|
279
|
+
# Setting all properties to UTC time.
|
280
|
+
new_events.each do |new_event|
|
281
|
+
new_event.children.each do |child|
|
282
|
+
next unless child.is_a?(Property::ICalendar::DateTime) && child.time?
|
283
|
+
dt = child.date_times(time_zone)
|
284
|
+
# We only need to update the first timezone, because
|
285
|
+
# setDateTimes will match all other timezones to the
|
286
|
+
# first.
|
287
|
+
dt[0] = dt[0].in_time_zone(ActiveSupport::TimeZone.new('UTC'))
|
288
|
+
child.date_times = dt
|
289
|
+
end
|
290
|
+
|
291
|
+
add(new_event)
|
292
|
+
end
|
293
|
+
|
294
|
+
# Removing all VTIMEZONE components
|
295
|
+
delete('VTIMEZONE')
|
296
|
+
end
|
297
|
+
|
298
|
+
protected
|
299
|
+
|
300
|
+
# This method should return a list of default property values.
|
301
|
+
#
|
302
|
+
# @return array
|
303
|
+
def defaults
|
304
|
+
{
|
305
|
+
'VERSION' => '2.0',
|
306
|
+
'PRODID' => "-//Tilia//Tilia VObject #{Version::VERSION}//EN",
|
307
|
+
'CALSCALE' => 'GREGORIAN'
|
308
|
+
}
|
309
|
+
end
|
310
|
+
|
311
|
+
public
|
312
|
+
|
313
|
+
# A simple list of validation rules.
|
314
|
+
#
|
315
|
+
# This is simply a list of properties, and how many times they either
|
316
|
+
# must or must not appear.
|
317
|
+
#
|
318
|
+
# Possible values per property:
|
319
|
+
# * 0 - Must not appear.
|
320
|
+
# * 1 - Must appear exactly once.
|
321
|
+
# * + - Must appear at least once.
|
322
|
+
# * * - Can appear any number of times.
|
323
|
+
# * ? - May appear, but not more than once.
|
324
|
+
#
|
325
|
+
# @var array
|
326
|
+
def validation_rules
|
327
|
+
{
|
328
|
+
'PRODID' => 1,
|
329
|
+
'VERSION' => 1,
|
330
|
+
|
331
|
+
'CALSCALE' => '?',
|
332
|
+
'METHOD' => '?'
|
333
|
+
}
|
334
|
+
end
|
335
|
+
|
336
|
+
# Validates the node for correctness.
|
337
|
+
#
|
338
|
+
# The following options are supported:
|
339
|
+
# Node::REPAIR - May attempt to automatically repair the problem.
|
340
|
+
# Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes.
|
341
|
+
# Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes.
|
342
|
+
#
|
343
|
+
# This method returns an array with detected problems.
|
344
|
+
# Every element has the following properties:
|
345
|
+
#
|
346
|
+
# * level - problem level.
|
347
|
+
# * message - A human-readable string describing the issue.
|
348
|
+
# * node - A reference to the problematic node.
|
349
|
+
#
|
350
|
+
# The level means:
|
351
|
+
# 1 - The issue was repaired (only happens if REPAIR was turned on).
|
352
|
+
# 2 - A warning.
|
353
|
+
# 3 - An error.
|
354
|
+
#
|
355
|
+
# @param int options
|
356
|
+
#
|
357
|
+
# @return array
|
358
|
+
def validate(options = 0)
|
359
|
+
warnings = super(options)
|
360
|
+
|
361
|
+
ver = self['VERSION']
|
362
|
+
if ver
|
363
|
+
unless ver.to_s == '2.0'
|
364
|
+
warnings << {
|
365
|
+
'level' => 3,
|
366
|
+
'message' => 'Only iCalendar version 2.0 as defined in rfc5545 is supported.',
|
367
|
+
'node' => self
|
368
|
+
}
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
uid_list = {}
|
373
|
+
components_found = 0
|
374
|
+
component_types = []
|
375
|
+
|
376
|
+
children.each do |child|
|
377
|
+
next unless child.is_a?(Component)
|
378
|
+
components_found += 1
|
379
|
+
|
380
|
+
next unless ['VEVENT', 'VTODO', 'VJOURNAL'].include?(child.name)
|
381
|
+
|
382
|
+
component_types << child.name
|
383
|
+
|
384
|
+
uid = child['UID'].to_s
|
385
|
+
is_master = child.key?('RECURRENCE-ID') ? 0 : 1
|
386
|
+
|
387
|
+
if uid_list.key?(uid)
|
388
|
+
uid_list[uid]['count'] += 1
|
389
|
+
if is_master == 1 && uid_list[uid]['hasMaster'] > 0
|
390
|
+
warnings << {
|
391
|
+
'level' => 3,
|
392
|
+
'message' => "More than one master object was found for the object with UID #{uid}",
|
393
|
+
'node' => self
|
394
|
+
}
|
395
|
+
end
|
396
|
+
uid_list[uid]['hasMaster'] += is_master
|
397
|
+
else
|
398
|
+
uid_list[uid] = {
|
399
|
+
'count' => 1,
|
400
|
+
'hasMaster' => is_master
|
401
|
+
}
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
if components_found == 0
|
406
|
+
warnings << {
|
407
|
+
'level' => 3,
|
408
|
+
'message' => 'An iCalendar object must have at least 1 component.',
|
409
|
+
'node' => self
|
410
|
+
}
|
411
|
+
end
|
412
|
+
|
413
|
+
if options & self.class::PROFILE_CALDAV > 0
|
414
|
+
if uid_list.size > 1
|
415
|
+
warnings << {
|
416
|
+
'level' => 3,
|
417
|
+
'message' => 'A calendar object on a CalDAV server may only have components with the same UID.',
|
418
|
+
'node' => self
|
419
|
+
}
|
420
|
+
end
|
421
|
+
|
422
|
+
if component_types.size == 0
|
423
|
+
warnings << {
|
424
|
+
'level' => 3,
|
425
|
+
'message' => 'A calendar object on a CalDAV server must have at least 1 component (VTODO, VEVENT, VJOURNAL).',
|
426
|
+
'node' => self
|
427
|
+
}
|
428
|
+
end
|
429
|
+
|
430
|
+
if component_types.uniq.size > 1
|
431
|
+
warnings << {
|
432
|
+
'level' => 3,
|
433
|
+
'message' => 'A calendar object on a CalDAV server may only have 1 type of component (VEVENT, VTODO or VJOURNAL).',
|
434
|
+
'node' => self
|
435
|
+
}
|
436
|
+
end
|
437
|
+
|
438
|
+
if key?('METHOD')
|
439
|
+
warnings <<
|
440
|
+
{
|
441
|
+
'level' => 3,
|
442
|
+
'message' => 'A calendar object on a CalDAV server MUST NOT have a METHOD property.',
|
443
|
+
'node' => self
|
444
|
+
}
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
warnings
|
449
|
+
end
|
450
|
+
|
451
|
+
# Returns all components with a specific UID value.
|
452
|
+
#
|
453
|
+
# @return array
|
454
|
+
def by_uid(uid)
|
455
|
+
components.select do |item|
|
456
|
+
item_uid = item.select('UID')
|
457
|
+
if item_uid.empty?
|
458
|
+
false
|
459
|
+
else
|
460
|
+
item_uid = item_uid.first.value
|
461
|
+
uid == item_uid
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
end
|