osem-fcy/app/helpers/conference_helper.rb
Asish Kumar c5e84221c2 Make iCalendar feed routes return calendar data
Both iCalendar routes were unusable:

* `/conferences/.../schedule.ics` raised
  `NoMethodError: undefined method 'icalendar_proposals'` because the
  `ConferenceHelper#icalendar_proposals` helper isn't auto-included in
  controllers; the action crashed before rendering.

* `/calendar.ics` silently redirected to `/`. The controller goes
  through `load_and_authorize_resource`, which authorises `:calendar`
  on `Conference` and is rejected for anonymous visitors because no
  ability grants it — CanCan's `AccessDenied` rescue redirects to
  root.

The helper is invoked through the controller `helpers` proxy so it
works in both contexts, and the calendar action is opted out of
resource loading and explicitly marked as not requiring authorization
since it serves a public feed across conferences. The helper is also
hardened so that proposals missing a room, difficulty level, track,
event type or scheduled time produce a valid event instead of
crashing the whole feed.
2026-05-14 11:02:49 +05:30

69 lines
2.3 KiB
Ruby

# frozen_string_literal: true
module ConferenceHelper
# Return true if only call_for_papers or call_for_tracks or call_for_booths is open
def one_call_open(*calls)
calls.one? { |call| call.try(:open?) }
end
# Return true if exactly two of those calls are open: call_for_papers , call_for_tracks , call_for_booths
def two_calls_open(*calls)
calls.count{ |call| call.try(:open?) } == 2
end
# URL for sponsorship emails
def sponsorship_mailto(conference)
[
'mailto:',
conference.contact.sponsor_email,
'?subject=',
url_encode(conference.short_title),
'%20Sponsorship'
].join
end
# adds events to icalendar for proposals in a conference
def icalendar_proposals(calendar, proposals, conference)
proposals.each do |proposal|
calendar.event { |e| populate_icalendar_event(e, proposal, conference) }
end
calendar
end
private
def populate_icalendar_event(event, proposal, conference)
length = proposal.event_type&.length
event.dtstart = proposal.time
event.dtend = proposal.time + (length * 60) if proposal.time && length
event.duration = "PT#{length}M" if length
event.created = proposal.created_at
event.last_modified = proposal.updated_at
event.summary = proposal.title
event.description = proposal.abstract
event.uid = proposal.guid
event.url = conference_program_proposal_url(conference.short_title, proposal.id)
venue = conference.venue
if venue
event.geo = venue.latitude, venue.longitude if venue.latitude && venue.longitude
event.location = icalendar_event_location(proposal, venue)
end
event.categories = icalendar_event_categories(proposal, conference)
end
def icalendar_event_location(proposal, venue)
location = ''
location += "#{proposal.room.name} - " if proposal.room&.name
location += " - #{venue.street}, " if venue.street
location += "#{venue.postalcode} #{venue.city}, " if venue.postalcode && venue.city
location += "#{venue.country_name}, " if venue.country_name
location
end
def icalendar_event_categories(proposal, conference)
categories = [conference.title]
categories << "Difficulty: #{proposal.difficulty_level.title}" if proposal.difficulty_level
categories << "Track: #{proposal.track.name}" if proposal.track
categories
end
end