2011-02-01 09:57:29 +08:00
#
2014-01-08 04:19:11 +08:00
# Copyright (C) 2011 - 2014 Instructure, Inc.
2011-02-01 09:57:29 +08:00
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require 'date'
class CalendarEvent < ActiveRecord :: Base
include CopyAuthorizedLinks
2012-03-29 04:29:39 +08:00
include TextHelper
2014-02-05 04:53:04 +08:00
include HtmlTextHelper
2012-01-04 04:30:49 +08:00
attr_accessible :title , :description , :start_at , :end_at , :location_name ,
2012-04-13 08:27:52 +08:00
:location_address , :time_zone_edited , :cancel_reason ,
:participants_per_appointment , :child_event_data ,
2013-02-16 02:43:45 +08:00
:remove_child_events , :all_day
2013-11-07 23:43:53 +08:00
attr_accessor :cancel_reason , :imported
2014-01-29 05:29:09 +08:00
sanitize_field :description , CanvasSanitize :: SANITIZE
2012-05-18 06:58:23 +08:00
copy_authorized_links ( :description ) { [ self . effective_context , nil ] }
2012-01-04 04:30:49 +08:00
2011-02-01 09:57:29 +08:00
include Workflow
2012-01-04 04:30:49 +08:00
2011-07-14 00:24:17 +08:00
2011-02-01 09:57:29 +08:00
belongs_to :context , :polymorphic = > true
belongs_to :user
2012-01-04 04:30:49 +08:00
belongs_to :parent_event , :class_name = > 'CalendarEvent' , :foreign_key = > :parent_calendar_event_id
has_many :child_events , :class_name = > 'CalendarEvent' , :foreign_key = > :parent_calendar_event_id , :conditions = > " calendar_events.workflow_state <> 'deleted' "
2013-08-08 06:19:48 +08:00
validates_presence_of :context , :workflow_state
2012-01-04 04:30:49 +08:00
validates_associated :context , :if = > lambda { | record | record . validate_context }
2011-02-01 09:57:29 +08:00
validates_length_of :description , :maximum = > maximum_long_text_length , :allow_nil = > true , :allow_blank = > true
2012-06-27 05:07:35 +08:00
validates_length_of :title , :maximum = > maximum_string_length , :allow_nil = > true , :allow_blank = > true
2011-02-01 09:57:29 +08:00
before_save :default_values
after_save :touch_context
2012-04-13 08:27:52 +08:00
after_save :replace_child_events
2012-04-18 06:38:45 +08:00
after_save :sync_parent_event
2012-04-13 08:27:52 +08:00
after_update :sync_child_events
# when creating/updating a calendar_event, you can give it a list of child
# events. these will update/replace any existing child events. the format is:
# [{:start_at => start_at, :end_at => end_at, :context_code => context_code},
# {:start_at => start_at, :end_at => end_at, :context_code => context_code},
# ...]
# the context for each child event must have this event's context as its
# parent_event_context, and there can only be one event per context.
# remove_child_events can be set to remove all existing events (since rails
# form handling mechanism doesn't let you specify an empty array)
attr_accessor :child_event_data , :remove_child_events , :child_event_contexts
validates_each :child_event_data do | record , attr , events |
next unless events || Canvas :: Plugin . value_to_boolean ( record . remove_child_events )
events || = [ ]
events = events . values if events . is_a? ( Hash )
next record . errors . add ( attr , t ( 'errors.no_updating_user' , " Can't update child events unless an updating_user is set " ) ) if events . present? && ! record . updating_user
context_codes = events . map { | e | e [ :context_code ] }
next record . errors . add ( attr , t ( 'errors.duplicate_child_event_contexts' , " Duplicate child event contexts " ) ) if context_codes != context_codes . uniq
contexts = find_all_by_asset_string ( context_codes ) . group_by ( & :asset_string )
context_codes . each do | code |
context = contexts [ code ] && contexts [ code ] [ 0 ]
next if context && context . grants_right? ( record . updating_user , :manage_calendar ) && context . try ( :parent_event_context ) == record . context
break record . errors . add ( attr , t ( 'errors.invalid_child_event_context' , " Invalid child event context " ) )
end
record . child_event_contexts = contexts
record . child_event_data = events
end
def replace_child_events
return unless @child_event_data
current_events = child_events . group_by { | e | e [ :context_code ] }
@child_event_data . each do | data |
if event = current_events . delete ( data [ :context_code ] ) and event = event [ 0 ]
2012-04-27 05:15:31 +08:00
event . updating_user = @updating_user
2012-04-13 08:27:52 +08:00
event . update_attributes ( :start_at = > data [ :start_at ] , :end_at = > data [ :end_at ] )
else
context = @child_event_contexts [ data [ :context_code ] ] [ 0 ]
event = child_events . build ( :start_at = > data [ :start_at ] , :end_at = > data [ :end_at ] )
2012-04-27 05:15:31 +08:00
event . updating_user = @updating_user
2012-04-13 08:27:52 +08:00
event . context = context
2012-04-18 06:38:45 +08:00
event . skip_sync_parent_event = true
2012-04-13 08:27:52 +08:00
event . save
end
end
2013-01-17 04:15:32 +08:00
2012-04-13 08:27:52 +08:00
current_events . values . flatten . each ( & :destroy )
2012-04-18 06:38:45 +08:00
cache_child_event_ranges!
2012-04-13 08:27:52 +08:00
@child_event_data = nil
end
2012-01-04 04:30:49 +08:00
2012-04-18 06:38:45 +08:00
def hidden?
2013-04-01 17:49:44 +08:00
! appointment_group? && child_events . size > 0
2012-04-18 06:38:45 +08:00
end
def effective_context
effective_context_code && ActiveRecord :: Base . find_by_asset_string ( effective_context_code ) || context
end
2013-03-21 03:38:19 +08:00
scope :order_by_start_at , order ( :start_at )
2013-01-31 02:15:26 +08:00
2013-03-21 03:38:19 +08:00
scope :active , where ( " calendar_events.workflow_state<>'deleted' " )
2014-02-20 23:29:48 +08:00
scope :are_locked , where ( :workflow_state = > 'locked' )
scope :are_unlocked , where ( " calendar_events.workflow_state NOT IN ('deleted', 'locked') " )
2012-01-04 04:30:49 +08:00
# controllers/apis/etc. should generally use for_user_and_context_codes instead
2013-03-21 03:38:19 +08:00
scope :for_context_codes , lambda { | codes | where ( :context_code = > codes ) }
2012-01-04 04:30:49 +08:00
# appointments and appointment_participants have the appointment_group and
# the user as the context, respectively. we are actually interested in
# grouping them under the effective context (i.e. appointment_group.context).
# it's the responsibility of the caller to ensure the user has rights to the
# specified codes (e.g. using User#appointment_context_codes)
2013-03-21 03:38:19 +08:00
scope :for_user_and_context_codes , lambda { | user , * args |
2012-04-13 08:27:52 +08:00
codes = args . shift
2012-04-18 06:38:45 +08:00
section_codes = args . shift || user . section_context_codes ( codes )
2012-04-13 08:27:52 +08:00
effectively_courses_codes = [ user . asset_string ] + section_codes
2012-01-04 04:30:49 +08:00
# the all_codes check is redundant, but makes the query more efficient
2012-04-13 08:27:52 +08:00
all_codes = codes | effectively_courses_codes
2012-01-04 04:30:49 +08:00
group_codes = codes . grep ( / \ Aappointment_group_ \ d+ \ z / )
codes -= group_codes
2012-04-19 07:47:03 +08:00
codes_conditions = codes . map { | code |
wildcard ( quoted_table_name + '.effective_context_code' , code , :delimiter = > ',' )
} . join ( " OR " )
codes_conditions = self . connection . quote ( false ) if codes_conditions . blank?
2013-03-21 03:38:19 +08:00
where ( <<-SQL, all_codes, codes, group_codes, effectively_courses_codes)
2012-01-04 04:30:49 +08:00
calendar_events . context_code IN ( ?)
AND (
( - - explicit contexts ( e . g . course_123 )
calendar_events . context_code IN ( ?)
AND calendar_events . effective_context_code IS NULL
)
OR ( - - appointments ( manageable or reservable )
calendar_events . context_code IN ( ?)
)
2012-04-13 08:27:52 +08:00
OR ( - - own appointment_participants , or section events in the course
calendar_events . context_code IN ( ?)
2012-04-19 07:47:03 +08:00
AND ( #{codes_conditions})
2012-01-04 04:30:49 +08:00
)
)
SQL
}
2013-03-21 03:38:19 +08:00
scope :undated , where ( :start_at = > nil , :end_at = > nil )
2012-01-04 04:30:49 +08:00
2013-03-21 03:38:19 +08:00
scope :between , lambda { | start , ending | where ( :start_at = > start .. ending ) }
scope :current , lambda { where ( " calendar_events.end_at>=? " , Time . zone . now . midnight ) }
scope :updated_after , lambda { | * args |
2011-02-01 09:57:29 +08:00
if args . first
2013-03-21 03:38:19 +08:00
where ( " calendar_events.updated_at IS NULL OR calendar_events.updated_at>? " , args . first )
else
scoped
2011-02-01 09:57:29 +08:00
end
}
2012-01-04 04:30:49 +08:00
2013-03-21 03:38:19 +08:00
scope :events_without_child_events , where ( " NOT EXISTS (SELECT 1 FROM calendar_events children WHERE children.parent_calendar_event_id = calendar_events.id AND children.workflow_state<>'deleted') " )
scope :events_with_child_events , where ( " EXISTS (SELECT 1 FROM calendar_events children WHERE children.parent_calendar_event_id = calendar_events.id AND children.workflow_state<>'deleted') " )
2012-09-01 02:01:08 +08:00
2012-01-04 04:30:49 +08:00
def validate_context!
@validate_context = true
2012-01-21 05:35:14 +08:00
context . validation_event_override = self
2012-01-04 04:30:49 +08:00
end
attr_reader :validate_context
2011-02-01 09:57:29 +08:00
def default_values
self . context_code = " #{ self . context_type . underscore } _ #{ self . context_id } "
self . title || = ( self . context_type . to_s + " Event " ) rescue " Event "
2013-02-16 02:43:45 +08:00
populate_missing_dates
2013-11-07 23:43:53 +08:00
populate_all_day_flag unless self . imported
2013-02-16 02:43:45 +08:00
if parent_event
populate_with_parent_event
elsif context . is_a? ( AppointmentGroup )
populate_appointment_group_defaults
end
end
protected :default_values
def populate_appointment_group_defaults
self . effective_context_code = context . appointment_group_contexts . map ( & :context_code ) . join ( " , " )
if new_record?
AppointmentGroup :: EVENT_ATTRIBUTES . each { | attr | send ( " #{ attr } = " , attr == :description ? context . description_html : context . send ( attr ) ) }
if locked?
self . start_at = start_at_was if ! new_record? && start_at_changed?
self . end_at = end_at_was if ! new_record? && end_at_changed?
end
else
# we only allow changing the description
( AppointmentGroup :: EVENT_ATTRIBUTES - [ :description ] ) . each { | attr | send ( " #{ attr } = " , send ( " #{ attr } _was " ) ) if send ( " #{ attr } _changed? " ) }
end
end
protected :populate_appointment_group_defaults
def populate_with_parent_event
self . effective_context_code = if appointment_group # appointment participant
appointment_group . appointment_group_contexts . map ( & :context_code ) . join ( ',' ) if appointment_group . participant_type == 'User'
else # e.g. section-level event
parent_event . context_code
end
( locked? ? LOCKED_ATTRIBUTES : CASCADED_ATTRIBUTES ) . each { | attr | send ( " #{ attr } = " , parent_event . send ( attr ) ) }
end
protected :populate_with_parent_event
# Populate the start and end dates if they are not set, or if they are invalid
def populate_missing_dates
2011-02-01 09:57:29 +08:00
self . end_at || = self . start_at
self . start_at || = self . end_at
2013-02-16 02:43:45 +08:00
if self . start_at && self . end_at && self . end_at < self . start_at
2011-02-01 09:57:29 +08:00
self . end_at = self . start_at
end
2013-02-16 02:43:45 +08:00
end
protected :populate_missing_dates
def populate_all_day_flag
# If the all day flag has been changed to all day, set the times to 00:00
if self . all_day_changed? && self . all_day?
self . start_at = self . end_at = zoned_start_at . beginning_of_day rescue nil
elsif self . start_at_changed? || self . end_at_changed?
2011-02-01 09:57:29 +08:00
if self . start_at && self . start_at == self . end_at && zoned_start_at . strftime ( " %H:%M " ) == '00:00'
self . all_day = true
else
self . all_day = false
end
end
2013-02-16 02:43:45 +08:00
if self . all_day && ( ! self . all_day_date || self . start_at_changed? || self . all_day_date_changed? )
self . start_at = self . end_at = zoned_start_at . beginning_of_day rescue nil
self . all_day_date = ( zoned_start_at . to_date rescue nil )
2012-01-04 04:30:49 +08:00
end
2011-02-01 09:57:29 +08:00
end
2013-02-16 02:43:45 +08:00
protected :populate_all_day_flag
# Localized start_at
def zoned_start_at
self . start_at && ActiveSupport :: TimeWithZone . new ( self . start_at . utc ,
( ( ActiveSupport :: TimeZone . new ( self . time_zone_edited ) rescue nil ) || Time . zone ) )
end
2012-01-04 04:30:49 +08:00
2012-04-13 08:27:52 +08:00
CASCADED_ATTRIBUTES = [
2012-01-04 04:30:49 +08:00
:title ,
:description ,
:location_name ,
:location_address
]
2012-04-13 08:27:52 +08:00
LOCKED_ATTRIBUTES = CASCADED_ATTRIBUTES + [
:start_at ,
:end_at
]
2012-01-04 04:30:49 +08:00
2012-04-13 08:27:52 +08:00
def sync_child_events
locked_changes = LOCKED_ATTRIBUTES . select { | attr | send ( " #{ attr } _changed? " ) }
cascaded_changes = CASCADED_ATTRIBUTES . select { | attr | send ( " #{ attr } _changed? " ) }
2014-02-20 23:29:48 +08:00
child_events . are_locked . update_all Hash [ locked_changes . map { | attr | [ attr , send ( attr ) ] } ] if locked_changes . present?
child_events . are_unlocked . update_all Hash [ cascaded_changes . map { | attr | [ attr , send ( attr ) ] } ] if cascaded_changes . present?
2012-01-04 04:30:49 +08:00
end
2012-04-18 06:38:45 +08:00
attr_writer :skip_sync_parent_event
def sync_parent_event
return unless parent_event
return if appointment_group
return unless start_at_changed? || end_at_changed? || workflow_state_changed?
return if @skip_sync_parent_event
2013-01-17 04:15:32 +08:00
parent_event . cache_child_event_ranges! unless workflow_state == 'deleted'
2012-04-18 06:38:45 +08:00
end
def cache_child_event_ranges!
events = child_events ( true )
2013-01-17 04:15:32 +08:00
if events . present?
2013-03-19 03:07:47 +08:00
CalendarEvent . where ( :id = > self ) .
update_all ( :start_at = > events . map ( & :start_at ) . min ,
:end_at = > events . map ( & :end_at ) . max )
2013-01-17 04:15:32 +08:00
reload
end
2012-04-18 06:38:45 +08:00
end
2011-02-01 09:57:29 +08:00
workflow do
state :active
2012-01-04 04:30:49 +08:00
state :locked do # locked events may only be deleted, they cannot be edited directly
event :unlock , :transitions_to = > :active
end
2011-02-01 09:57:29 +08:00
state :deleted
end
2012-01-04 04:30:49 +08:00
2011-02-01 09:57:29 +08:00
alias_method :destroy! , :destroy
2012-01-04 04:31:58 +08:00
def destroy ( update_context_or_parent = true )
2012-01-04 04:30:49 +08:00
transaction do
self . workflow_state = 'deleted'
self . deleted_at = Time . now . utc
save!
2014-04-17 01:37:00 +08:00
child_events . find_each do | e |
2012-01-04 04:31:58 +08:00
e . cancel_reason = cancel_reason
e . updating_user = updating_user
e . destroy ( false )
end
return true unless update_context_or_parent
if appointment_group
context . touch if context_type == 'AppointmentGroup' # ensures end_at/start_at get updated
# when deleting an appointment or appointment_participant, make sure we reset the cache
appointment_group . clear_cached_available_slots!
end
if parent_event && parent_event . locked? && parent_event . child_events . size == 0
2012-01-04 04:30:49 +08:00
parent_event . workflow_state = 'active'
parent_event . save!
end
true
end
2011-02-01 09:57:29 +08:00
end
2012-01-04 04:30:49 +08:00
2011-02-01 09:57:29 +08:00
def time_zone_edited
CGI :: unescapeHTML ( read_attribute ( :time_zone_edited ) || " " )
end
2012-01-04 04:30:49 +08:00
2011-02-01 09:57:29 +08:00
has_a_broadcast_policy
2012-01-04 04:30:49 +08:00
set_broadcast_policy do
dispatch :new_event_created
to { participants - [ @updating_user ] }
whenever {
2012-04-24 07:46:19 +08:00
! appointment_group && context . available? && just_created && ! hidden?
2012-01-04 04:30:49 +08:00
}
dispatch :event_date_changed
to { participants - [ @updating_user ] }
whenever {
! appointment_group &&
context . available? && (
changed_in_state ( :active , :fields = > :start_at ) ||
changed_in_state ( :active , :fields = > :end_at )
2012-04-24 07:46:19 +08:00
) && ! hidden?
2012-01-04 04:30:49 +08:00
}
dispatch :appointment_reserved_by_user
2012-01-13 07:57:58 +08:00
to { appointment_group . instructors }
2012-01-04 04:30:49 +08:00
whenever {
appointment_group && parent_event &&
just_created &&
context == appointment_group . participant_for ( user )
}
2012-03-31 07:03:39 +08:00
data { { :updating_user = > @updating_user } }
2012-01-04 04:30:49 +08:00
dispatch :appointment_canceled_by_user
2012-01-13 07:57:58 +08:00
to { appointment_group . instructors }
2012-01-04 04:30:49 +08:00
whenever {
appointment_group && parent_event &&
deleted? &&
workflow_state_changed? &&
@updating_user &&
context == appointment_group . participant_for ( @updating_user )
}
2012-03-31 07:03:39 +08:00
data { {
:updating_user = > @updating_user ,
:cancel_reason = > @cancel_reason
} }
2012-01-04 04:30:49 +08:00
dispatch :appointment_reserved_for_user
to { participants - [ @updating_user ] }
whenever {
appointment_group && parent_event &&
just_created
2011-02-01 09:57:29 +08:00
}
2012-03-31 07:03:39 +08:00
data { { :updating_user = > @updating_user } }
2012-01-04 04:30:49 +08:00
dispatch :appointment_deleted_for_user
to { participants - [ @updating_user ] }
whenever {
appointment_group && parent_event &&
deleted? &&
workflow_state_changed?
2011-02-01 09:57:29 +08:00
}
2012-03-31 07:03:39 +08:00
data { {
:updating_user = > @updating_user ,
:cancel_reason = > @cancel_reason
} }
2011-02-01 09:57:29 +08:00
end
2012-01-04 04:30:49 +08:00
2011-02-01 09:57:29 +08:00
def participants
2012-01-04 04:30:49 +08:00
# TODO: User#participants should probably be fixed to return [self],
# then we can simplify this again
context_type == 'User' ? [ context ] : context . participants
end
attr_reader :updating_user
def updating_user = ( user )
self . user || = user
@updating_user = user
content_being_saved_by ( user )
end
def user
read_attribute ( :user ) || ( context_type == 'User' ? context : nil )
end
2013-04-01 17:49:44 +08:00
def appointment_group?
context_type == 'AppointmentGroup' || parent_event . try ( :context_type ) == 'AppointmentGroup'
end
2012-01-04 04:30:49 +08:00
def appointment_group
if parent_event . try ( :context ) . is_a? ( AppointmentGroup )
parent_event . context
elsif context_type == 'AppointmentGroup'
context
end
end
class ReservationError < StandardError ; end
2012-02-14 08:05:31 +08:00
def reserve_for ( participant , user , options = { } )
2012-01-04 04:30:49 +08:00
raise ReservationError , " not an appointment " unless context_type == 'AppointmentGroup'
raise ReservationError , " ineligible participant " unless context . eligible_participant? ( participant )
transaction do
lock! # in case two people two participants try to grab the same slot
participant . lock! # in case two people try to make a reservation for the same participant
2012-02-14 08:05:31 +08:00
if options [ :cancel_existing ]
2013-03-19 03:07:47 +08:00
context . reservations_for ( participant ) . lock . each do | reservation |
2012-02-14 08:05:31 +08:00
reservation . updating_user = user
reservation . destroy
end
end
raise ReservationError , " participant has met per-participant limit " if context . max_appointments_per_participant && context . reservations_for ( participant ) . size > = context . max_appointments_per_participant
2012-03-25 12:55:56 +08:00
raise ReservationError , " all slots filled " if participants_per_appointment && child_events . size > = participants_per_appointment
2012-01-04 04:30:49 +08:00
raise ReservationError , " participant has already reserved this appointment " if child_events_for ( participant ) . present?
event = child_events . build
event . updating_user = user
event . context = participant
event . workflow_state = :locked
event . save!
if active?
self . workflow_state = 'locked'
save!
end
context . clear_cached_available_slots!
event
end
end
def child_events_for ( participant )
2014-04-17 01:37:00 +08:00
if child_events . loaded?
child_events . select { | e | e . has_asset? ( participant ) }
else
child_events . where ( context_type : participant . class . name , context_id : participant )
end
2011-02-01 09:57:29 +08:00
end
2012-01-04 04:30:49 +08:00
2012-03-25 12:55:56 +08:00
def participants_per_appointment
if override_participants_per_appointment?
read_attribute ( :participants_per_appointment )
else
context . is_a? ( AppointmentGroup ) ? context . participants_per_appointment : nil
end
end
def participants_per_appointment = ( limit )
# if the given limit is the same as the context's limit, we should not override
if limit == context . participants_per_appointment && override_participants_per_appointment?
self . override_participants_per_appointment = false
write_attribute ( :participants_per_appointment , nil )
else
write_attribute ( :participants_per_appointment , limit )
self . override_participants_per_appointment = true
end
limit
end
2011-02-01 09:57:29 +08:00
def update_matching_days = ( update )
@update_matching_days = update == '1' || update == true || update == 'true'
end
2012-01-04 04:30:49 +08:00
2011-02-01 09:57:29 +08:00
def all_day
read_attribute ( :all_day ) || ( self . new_record? && self . start_at && self . start_at . strftime ( " %H:%M " ) == '00:00' )
end
def to_atom ( opts = { } )
extend ApplicationHelper
Atom :: Entry . new do | entry |
2011-06-16 21:24:15 +08:00
entry . title = t ( :feed_item_title , " Calendar Event: %{event_title} " , :event_title = > self . title ) unless opts [ :include_context ]
entry . title = t ( :feed_item_title_with_context , " Calendar Event, %{course_or_account_name}: %{event_title} " , :course_or_account_name = > self . context . name , :event_title = > self . title ) if opts [ :include_context ]
2012-04-05 03:06:48 +08:00
entry . authors << Atom :: Person . new ( :name = > self . context . name )
2011-02-01 09:57:29 +08:00
entry . updated = self . updated_at . utc
entry . published = self . created_at . utc
2012-01-04 04:30:49 +08:00
entry . links << Atom :: Link . new ( :rel = > 'alternate' ,
2011-02-01 09:57:29 +08:00
:href = > " http:// #{ HostUrl . context_host ( self . context ) } / #{ context_url_prefix } /calendar?month= #{ self . start_at . strftime ( " %m " ) rescue " " } &year= #{ self . start_at . strftime ( " %Y " ) rescue " " } # calendar_event_ #{ self . id } " )
entry . id = " tag: #{ HostUrl . default_host } , #{ self . created_at . strftime ( " %Y-%m-%d " ) } :/calendar_events/ #{ self . feed_code } _ #{ self . start_at . strftime ( " %Y-%m-%d-%H-%M " ) rescue " none " } _ #{ self . end_at . strftime ( " %Y-%m-%d-%H-%M " ) rescue " none " } "
entry . content = Atom :: Content :: Html . new ( " #{ datetime_string ( self . start_at , self . end_at ) } <br/> #{ self . description } " )
end
end
2012-01-04 04:30:49 +08:00
2011-02-01 09:57:29 +08:00
def to_ics ( in_own_calendar = true )
2012-06-28 02:47:40 +08:00
return CalendarEvent :: IcalEvent . new ( self ) . to_ics ( in_own_calendar )
2011-02-01 09:57:29 +08:00
end
2012-06-28 02:47:40 +08:00
2011-02-01 09:57:29 +08:00
def self . process_migration ( data , migration )
events = data [ 'calendar_events' ] ? data [ 'calendar_events' ] : [ ]
events . each do | event |
2013-05-30 06:41:50 +08:00
if migration . import_object? ( " calendar_events " , event [ 'migration_id' ] ) || migration . import_object? ( " events " , event [ 'migration_id' ] )
2011-06-18 00:58:18 +08:00
begin
import_from_migration ( event , migration . context )
rescue
2013-04-24 01:12:19 +08:00
migration . add_import_warning ( t ( '#migration.calendar_event_type' , " Calendar Event " ) , event [ :title ] , $! )
2011-06-18 00:58:18 +08:00
end
2011-02-01 09:57:29 +08:00
end
end
end
2012-01-04 04:30:49 +08:00
2011-02-01 09:57:29 +08:00
def self . import_from_migration ( hash , context , item = nil )
hash = hash . with_indifferent_access
return nil if hash [ :migration_id ] && hash [ :events_to_import ] && ! hash [ :events_to_import ] [ hash [ :migration_id ] ]
item || = find_by_context_type_and_context_id_and_id ( context . class . to_s , context . id , hash [ :id ] )
item || = find_by_context_type_and_context_id_and_migration_id ( context . class . to_s , context . id , hash [ :migration_id ] ) if hash [ :migration_id ]
item || = context . calendar_events . new
2014-04-15 04:58:50 +08:00
Importers :: CalendarEvent . import_from_migration ( hash , context , item )
2011-02-01 09:57:29 +08:00
end
2012-01-04 04:30:49 +08:00
2011-02-01 09:57:29 +08:00
def self . max_visible_calendars
10
end
set_policy do
given { | user , session | self . cached_context_grants_right? ( user , session , :read ) } #students.include?(user) }
2011-07-14 00:24:17 +08:00
can :read
2012-01-04 04:30:49 +08:00
2013-04-01 17:49:44 +08:00
given { | user , session | ! appointment_group? ^ cached_context_grants_right? ( user , session , :read_appointment_participants ) }
2012-01-04 04:30:49 +08:00
can :read_child_events
2013-04-01 17:49:44 +08:00
given { | user , session | parent_event && appointment_group? && parent_event . grants_right? ( user , session , :manage ) }
2012-01-04 04:30:49 +08:00
can :read and can :delete
2013-04-01 17:49:44 +08:00
given { | user , session | appointment_group? && cached_context_grants_right? ( user , session , :manage ) }
2012-01-04 04:30:49 +08:00
can :manage
given { | user , session |
2013-04-01 17:49:44 +08:00
appointment_group? && (
2012-01-04 04:30:49 +08:00
grants_right? ( user , session , :manage ) ||
cached_context_grants_right? ( user , nil , :reserve ) && context . participant_for ( user ) . present?
)
}
can :reserve
2011-02-01 09:57:29 +08:00
given { | user , session | self . cached_context_grants_right? ( user , session , :manage_calendar ) } #admins.include?(user) }
2011-07-14 00:24:17 +08:00
can :read and can :create
2012-01-04 04:30:49 +08:00
2012-01-21 05:35:14 +08:00
given { | user , session | ( ! locked? || context . is_a? ( AppointmentGroup ) ) && ! deleted? && self . cached_context_grants_right? ( user , session , :manage_calendar ) } #admins.include?(user) }
2012-01-04 04:30:49 +08:00
can :update and can :update_content
given { | user , session | ! deleted? && self . cached_context_grants_right? ( user , session , :manage_calendar ) }
can :delete
2011-02-01 09:57:29 +08:00
end
2012-06-28 02:47:40 +08:00
class IcalEvent
include Api
2013-10-29 04:58:47 +08:00
if CANVAS_RAILS2
include ActionController :: UrlWriter
else
include Rails . application . routes . url_helpers
end
2014-02-05 04:53:04 +08:00
include HtmlTextHelper
2012-06-28 02:47:40 +08:00
def initialize ( event )
@event = event
end
def location
end
def to_ics ( in_own_calendar )
cal = Icalendar :: Calendar . new
# to appease Outlook
cal . custom_property ( " METHOD " , " PUBLISH " )
event = Icalendar :: Event . new
event . klass = " PUBLIC "
start_at = @event . is_a? ( CalendarEvent ) ? @event . start_at : @event . due_at
end_at = @event . is_a? ( CalendarEvent ) ? @event . end_at : @event . due_at
if start_at
event . start = start_at . utc_datetime
event . start . icalendar_tzid = 'UTC'
end
if end_at
event . end = end_at . utc_datetime
event . end . icalendar_tzid = 'UTC'
end
if @event . all_day
event . start = Date . new ( @event . all_day_date . year , @event . all_day_date . month , @event . all_day_date . day )
event . start . ical_params = { " VALUE " = > [ " DATE " ] }
event . end = event . start
event . end . ical_params = { " VALUE " = > [ " DATE " ] }
end
2013-03-19 06:00:05 +08:00
2012-06-28 02:47:40 +08:00
event . summary = @event . title
2014-02-05 04:53:04 +08:00
2013-05-11 07:08:32 +08:00
if @event . is_a? ( CalendarEvent ) && @event . description
2012-06-28 02:47:40 +08:00
html = api_user_content ( @event . description , @event . context )
event . description html_to_text ( html )
event . x_alt_desc ( html , { 'FMTTYPE' = > 'text/html' } )
end
if @event . is_a? ( CalendarEvent )
loc_string = " "
loc_string << @event . location_name + " , " if @event . location_name . present?
loc_string << @event . location_address if @event . location_address . present?
else
loc_string = @event . location
end
event . location = loc_string
event . dtstamp = @event . updated_at . utc_datetime if @event . updated_at
event . dtstamp . icalendar_tzid = 'UTC' if event . dtstamp
tag_name = @event . class . name . underscore
# This will change when there are other things that have calendars...
# can't call calendar_url or calendar_url_for here, have to do it manually
event . url " http:// #{ HostUrl . context_host ( @event . context ) } /calendar?include_contexts= #{ @event . context . asset_string } &month= #{ start_at . try ( :strftime , " %m " ) } &year= #{ start_at . try ( :strftime , " %Y " ) } # #{ tag_name } _ #{ @event . id . to_s } "
event . uid " event- #{ tag_name . gsub ( '_' , '-' ) } - #{ @event . id . to_s } "
event . sequence 0
2012-11-28 03:30:15 +08:00
if @event . respond_to? ( :applied_overrides )
@event . applied_overrides . try ( :each ) do | override |
next unless override . due_at_overridden
tag_name = override . class . name . underscore
event . uid = " event- #{ tag_name . gsub ( '_' , '-' ) } - #{ override . id } "
event . summary = " #{ @event . title } ( #{ override . title } ) "
#TODO: event.url
end
end
2013-03-19 06:00:05 +08:00
# make an effort to find an associated course without diving too deep down the rabbit hole
associated_course = nil
if @event . is_a? ( CalendarEvent )
if @event . effective_context . is_a? ( Course )
associated_course = @event . effective_context
elsif @event . effective_context . respond_to? ( :context ) && @event . effective_context . context . is_a? ( Course )
associated_course = @event . effective_context . context
end
elsif @event . respond_to? ( :context ) && @event . context_type == " Course "
associated_course = @event . context
end
event . summary += " [ #{ associated_course . course_code } ] " if associated_course
2014-02-05 04:53:04 +08:00
2012-11-28 03:30:15 +08:00
event = nil unless start_at
2012-06-28 02:47:40 +08:00
return event unless in_own_calendar
cal . add_event ( event ) if event
return cal . to_ical
end
end
2011-02-01 09:57:29 +08:00
end