class CalInvite::Providers::BaseProvider
Base class for calendar providers that implements common functionality and defines the interface that all providers must implement.
@abstract Subclass and override {#generate} to implement a calendar provider
Constants
- IMPORTANCE_VALUES
- NO_RSVP_METHODS
- PARTSTAT_VALUES
- VISIBILITY_VALUES
Attributes
Public Class Methods
# File lib/cal_invite/providers/base_provider.rb 17 def initialize(event, method: :publish) 18 @event = event 19 @method = method 20 end
Initialize a new calendar provider
@param event [CalInvite::Event] The event to generate a calendar URL for @param method [Symbol] The iCalendar METHOD (:publish or :request). Only meaningful to the ics-family providers; URL-based providers ignore it.
Public Instance Methods
Source
# File lib/cal_invite/providers/base_provider.rb 28 def generate 29 raise NotImplementedError, "#{self.class} must implement #generate" 30 end
Generate a calendar URL or content for the event. This method must be implemented by all provider subclasses.
@abstract @return [String] The generated calendar URL or content @raise [NotImplementedError] if the provider class doesnât implement this method
Protected Instance Methods
Source
# File lib/cal_invite/providers/base_provider.rb 72 def add_optional_params(params) 73 params[:description] = url_encode(format_description_with_url) if format_description || format_url 74 params[:location] = url_encode(format_location) if format_location 75 76 if event.show_attendees && event.attendees&.any? 77 params[:attendees] = attendee_emails.join(',') 78 end 79 80 params 81 end
Source
# File lib/cal_invite/providers/base_provider.rb 100 def attendee_email(attendee) 101 attendee.is_a?(Hash) ? attendee_hash_value(attendee, :email) : attendee.to_s 102 end
@param attendee [String, Hash] An attendee as given in Event#attendees @return [String] The attendeeâs email address
Source
# File lib/cal_invite/providers/base_provider.rb 94 def attendee_emails 95 attendees_list.map { |attendee| attendee_email(attendee) } 96 end
Plain email addresses for all attendees, regardless of whether they were given as strings or { email:, name:, partstat: } hashes. @return [Array<String>]
# File lib/cal_invite/providers/base_provider.rb 111 def attendee_hash_value(attendee, key) 112 return nil unless attendee.is_a?(Hash) 113 return attendee[key] if attendee.key?(key) 114 115 attendee[key.to_s] 116 end
Reads key from an attendee hash, trying both symbol and string keys. Unlike attendee[:key] || attendee["key"], this doesnât misread an explicit false value (e.g. rsvp: false) as ânot setâ.
@param attendee [Hash] An attendee hash @param key [Symbol] The key to read @return [Object, nil]
Source
# File lib/cal_invite/providers/base_provider.rb 137 def attendee_line(attendee) 138 email = attendee_email(attendee) 139 name = attendee_hash_value(attendee, :name) 140 partstat_key = attendee_hash_value(attendee, :partstat) 141 rsvp_override = attendee_hash_value(attendee, :rsvp) 142 143 cn = name ? ";CN=#{quoted_param(name)}" : "" 144 partstat = PARTSTAT_VALUES[partstat_key&.to_sym] || "NEEDS-ACTION" 145 # REPLY/COUNTER/DECLINECOUNTER all flow attendee -> organizer; RSVP=TRUE 146 # ("please respond") only makes sense on an organizer -> attendee REQUEST. 147 rsvp_default = !NO_RSVP_METHODS.include?(method) 148 rsvp = (rsvp_override.nil? ? rsvp_default : rsvp_override) ? ";RSVP=TRUE" : "" 149 150 "ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=#{partstat}#{rsvp}#{cn}:mailto:#{email}" 151 end
Formats a full ATTENDEE property line for iCalendar output.
@param attendee [String, Hash] An email string, or a hash like { email:, name:, partstat:, rsvp: } for a display name, specific RSVP status, and/or an explicit RSVP override. rsvp: is rarely needed â e.g. a âregistration confirmedâ invite where the recipient is already partstat: :accepted and nothing is actually being requested, so rsvp: false suppresses RSVP=TRUE even under method: :request. @return [String] The formatted ATTENDEE line
Source
# File lib/cal_invite/providers/base_provider.rb 86 def attendees_list 87 return [] unless event.show_attendees && event.attendees&.any? 88 event.attendees 89 end
Get the list of attendees if showing attendees is enabled @return [Array<String, Hash>] The list of attendees (email strings or { email:, name:, partstat: } hashes) or empty array if disabled/none present
Source
# File lib/cal_invite/providers/base_provider.rb 290 def busystatus_line 291 "X-MICROSOFT-CDO-BUSYSTATUS:#{event.busy == false ? "FREE" : "BUSY"}" 292 end
Outlookâs non-standard X-MICROSOFT-CDO-BUSYSTATUS, mirroring Event#busy. Included alongside the standard TRANSP property since some Outlook versions honor this one more reliably. @return [String] e.g. âX-MICROSOFT-CDO-BUSYSTATUS:BUSYâ
Source
# File lib/cal_invite/providers/base_provider.rb 233 def class_line 234 value = (event.visibility || :public).to_s.upcase 235 unless VISIBILITY_VALUES.include?(value) 236 raise ArgumentError, "visibility must be one of :public, :private, :confidential (got #{event.visibility.inspect})" 237 end 238 239 "CLASS:#{value}" 240 end
The CLASS property, from Event#visibility (default :public). @return [String] e.g. âCLASS:PUBLICâ @raise [ArgumentError] if visibility isnât :public, :private, or :confidential
Source
# File lib/cal_invite/providers/base_provider.rb 300 def disallow_counter_line 301 return nil if event.allow_counter != false 302 303 "X-MICROSOFT-DISALLOW-COUNTER:TRUE" 304 end
Outlookâs non-standard X-MICROSOFT-DISALLOW-COUNTER, from Event#allow_counter (default true). Set Event#allow_counter = false to hide Outlookâs âPropose New Timeâ action â the practical lever for âprevent attendee-initiated reschedulesâ on Outlook specifically; other clients donât expose an equivalent control and ignore this property. @return [String, nil] âX-MICROSOFT-DISALLOW-COUNTER:TRUEâ, or nil if counters are allowed
Source
# File lib/cal_invite/providers/base_provider.rb 44 def format_description 45 parts = [] 46 parts << event.description if event.description 47 parts << "Notes: #{event.notes}" if event.notes 48 parts.join("\n\n") 49 end
Format the event description @return [String, nil] The formatted description or nil if no content
# File lib/cal_invite/providers/base_provider.rb 65 def format_description_with_url 66 parts = [] 67 parts << format_description if format_description 68 parts << "Virtual Meeting URL: #{format_url}" if format_url 69 parts.join("\n\n") 70 end
Format description including URL if present @return [String, nil] The formatted description with URL
Source
# File lib/cal_invite/providers/base_provider.rb 53 def format_location 54 event.location 55 end
Get just the physical location @return [String, nil] The location or nil if not present
Source
# File lib/cal_invite/providers/base_provider.rb 59 def format_url 60 event.url 61 end
Get the URL for virtual meetings @return [String, nil] The URL or nil if not present
Source
# File lib/cal_invite/providers/base_provider.rb 202 def geo_line 203 return nil unless event.geo 204 205 lat, lng = event.geo.is_a?(Hash) ? [event.geo[:lat] || event.geo["lat"], event.geo[:lng] || event.geo["lng"]] : event.geo 206 return nil unless lat && lng 207 208 "GEO:#{Float(lat)};#{Float(lng)}" 209 rescue TypeError, ArgumentError 210 raise ArgumentError, "geo lat/lng must be numeric (got #{event.geo.inspect})" 211 end
Format the GEO property from Event#geo. @return [String, nil] The formatted GEO line, or nil if no geo is set @raise [ArgumentError] if lat/lng arenât numeric
Source
# File lib/cal_invite/providers/base_provider.rb 278 def importance_lines 279 values = IMPORTANCE_VALUES[event.importance&.to_sym] 280 return [] unless values 281 282 priority, importance = values 283 ["PRIORITY:#{priority}", "X-MICROSOFT-CDO-IMPORTANCE:#{importance}"] 284 end
Standard RFC 5545 PRIORITY plus Outlookâs non-standard X-MICROSOFT-CDO-IMPORTANCE, from Event#importance (:low/:normal/:high). Safe to always include: RFC 5545 requires unrecognized X- properties be ignored by compliant parsers, so this has no effect outside Outlook. @return [Array<String>] 0, 1, or 2 lines
Source
# File lib/cal_invite/providers/base_provider.rb 189 def local_wall_time(time) 190 CalInvite::IcalTimezone.local_time(event.timezone, time) || time 191 end
Converts a time to local wall-clock time for the eventâs timezone, for use in a DTSTART;TZID=.../DTEND;TZID=... property. Falls back to the time as given (unconverted) when the timezone isnât a TZInfo-recognized identifier (e.g. âUTCâ or a raw offset string), matching each providerâs prior behavior for those cases.
@param time [Time] The time to convert (interpreted as UTC) @return [Time] The local wall-clock time
Source
# File lib/cal_invite/providers/base_provider.rb 172 def method_value 173 method == :decline_counter ? "DECLINECOUNTER" : method.to_s.upcase 174 end
The value for the calendar-level METHOD property. RFC 5545 spells this method âDECLINECOUNTERâ (one word) despite :decline_counter reading more naturally as a Ruby symbol. @return [String] e.g. âREQUESTâ, âDECLINECOUNTERâ
Source
# File lib/cal_invite/providers/base_provider.rb 156 def organizer_line 157 organizer = event.organizer 158 return nil unless organizer 159 160 email = attendee_email(organizer) 161 return nil if email.nil? || email.empty? 162 163 name = attendee_hash_value(organizer, :name) 164 cn = name ? ";CN=#{quoted_param(name)}" : "" 165 "ORGANIZER#{cn}:mailto:#{email}" 166 end
Format the ORGANIZER property for iCalendar output. Like attendees, the organizer may be a plain email string or an { email:, name: } hash. @return [String, nil] The formatted ORGANIZER line, or nil if no organizer is set
Source
# File lib/cal_invite/providers/base_provider.rb 247 def quoted_param(value) 248 escaped = value.to_s.gsub(/\^|"|\r\n|[\r\n]/) { |match| { '^' => '^^', '"' => "^'" }.fetch(match, '^n') } 249 %("#{escaped}") 250 end
Quotes a parameter value (e.g. CN) and escapes it per RFC 6868, so a display name canât close the quoted string and inject parameters, or break the line.
@param value [#to_s] The raw parameter value @return [String] The quoted, escaped value
Source
# File lib/cal_invite/providers/base_provider.rb 254 def rrule_line 255 return nil unless event.rrule 256 257 value = event.rrule.to_s 258 value.start_with?("RRULE:") ? value : "RRULE:#{value}" 259 end
The RRULE property, from Event#rrule, if set. @return [String, nil] e.g. âRRULE:FREQ=WEEKLY;COUNT=5â, or nil if no rrule is set
Source
# File lib/cal_invite/providers/base_provider.rb 216 def sequence_line 217 "SEQUENCE:#{Integer(event.sequence)}" 218 rescue TypeError, ArgumentError 219 raise ArgumentError, "sequence must be an integer (got #{event.sequence.inspect})" 220 end
The SEQUENCE property, from Event#sequence. @return [String] e.g. âSEQUENCE:0â @raise [ArgumentError] if sequence isnât an integer
Source
# File lib/cal_invite/providers/base_provider.rb 178 def status_line 179 method == :cancel ? "STATUS:CANCELLED" : "STATUS:CONFIRMED" 180 end
The STATUS property, driven by the iCalendar METHOD in use. @return [String] âSTATUS:CANCELLEDâ for :cancel, âSTATUS:CONFIRMEDâ otherwise
Source
# File lib/cal_invite/providers/base_provider.rb 224 def transp_line 225 event.busy == false ? "TRANSP:TRANSPARENT" : "TRANSP:OPAQUE" 226 end
The TRANSP property, from Event#busy (default true). @return [String] âTRANSP:OPAQUEâ (busy) or âTRANSP:TRANSPARENTâ (free)
Source
# File lib/cal_invite/providers/base_provider.rb 38 def url_encode(str) 39 URI.encode_www_form_component(str.to_s) 40 end
URL encode a string for use in calendar URLs
@param str [#to_s] The string to encode @return [String] The URL encoded string
Source
# File lib/cal_invite/providers/base_provider.rb 263 def valarm_lines 264 return [] unless event.reminders&.any? 265 266 event.reminders.flat_map do |minutes| 267 ["BEGIN:VALARM", "TRIGGER:-PT#{minutes.to_i}M", "ACTION:DISPLAY", "DESCRIPTION:Reminder", "END:VALARM"] 268 end 269 end
Builds VALARM sub-components from Event#reminders (minutes-before-start values). @return [Array<String>] iCalendar lines, one VALARM block per reminder, or [] if none
Source
# File lib/cal_invite/providers/base_provider.rb 195 def vtimezone_lines 196 CalInvite::IcalTimezone.vtimezone_lines(event.timezone) 197 end
Builds the VTIMEZONE component lines for the eventâs timezone, if applicable. @return [Array<String>, nil] iCalendar lines, or nil for UTC/unrecognized timezones