2011-02-01 09:57:29 +08:00
#
2013-02-14 01:13:44 +08:00
# Copyright (C) 2011-2013 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/>.
#
class Message < ActiveRecord :: Base
2013-02-14 01:13:44 +08:00
# Included modules
2013-10-29 04:58:47 +08:00
if CANVAS_RAILS2
include ActionController :: UrlWriter
else
include Rails . application . routes . url_helpers
end
2014-01-28 05:07:09 +08:00
include PolymorphicTypeOverride
override_polymorphic_types context_type : { 'QuizSubmission' = > 'Quizzes::QuizSubmission' ,
'QuizRegradeRun' = > 'Quizzes::QuizRegradeRun' } ,
asset_context_type : { 'QuizSubmission' = > 'Quizzes::QuizSubmission' ,
'QuizRegradeRun' = > 'Quizzes::QuizRegradeRun' }
2013-02-21 03:10:53 +08:00
include ERB :: Util
2011-02-01 09:57:29 +08:00
include SendToStream
2011-06-30 07:04:59 +08:00
include TextHelper
2014-02-05 04:53:04 +08:00
include HtmlTextHelper
2013-02-14 01:13:44 +08:00
include Workflow
2011-02-01 09:57:29 +08:00
2013-02-21 03:10:53 +08:00
extend TextHelper
2013-02-14 01:13:44 +08:00
# Associations
belongs_to :asset_context , :polymorphic = > true
2011-02-01 09:57:29 +08:00
belongs_to :communication_channel
2013-02-14 01:13:44 +08:00
belongs_to :context , :polymorphic = > true
belongs_to :notification
2011-02-01 09:57:29 +08:00
belongs_to :user
2013-02-14 01:13:44 +08:00
has_many :attachments , :as = > :context
2011-02-01 09:57:29 +08:00
2013-02-14 01:13:44 +08:00
attr_accessible :to , :from , :subject , :body , :delay_for , :context , :path_type ,
:from_name , :sent_at , :notification , :user , :communication_channel ,
2013-03-13 23:11:14 +08:00
:notification_name , :asset_context , :data , :root_account_id
2011-05-14 00:49:23 +08:00
2013-02-14 01:13:44 +08:00
attr_writer :delayed_messages
2014-02-12 09:29:31 +08:00
attr_accessor :output_buffer
2013-02-14 01:13:44 +08:00
2014-04-11 03:03:43 +08:00
EXPORTABLE_ATTRIBUTES = [
:id , :to , :from , :cc , :bcc , :subject , :body , :delay_for , :dispatch_at , :sent_at , :workflow_state , :transmission_errors , :is_bounced , :notification_id ,
:communication_channel_id , :context_id , :context_type , :asset_context_id , :asset_context_type , :user_id , :created_at , :updated_at , :notification_name , :url , :path_type ,
:from_name , :asset_context_code , :notification_category , :to_email , :html_body , :root_account_id
]
EXPORTABLE_ASSOCIATIONS = [ :asset_context , :communication_channel , :context , :notification , :user , :attachments ]
2013-02-14 01:13:44 +08:00
# Callbacks
after_save :stage_message
2011-02-01 09:57:29 +08:00
before_save :infer_defaults
before_save :move_dashboard_messages
2013-02-14 01:13:44 +08:00
before_save :move_messages_for_deleted_users
2011-02-01 09:57:29 +08:00
before_save :set_asset_context_code
2012-12-07 03:19:08 +08:00
2013-02-14 01:13:44 +08:00
# Validations
validates_length_of :body , :maximum = > maximum_text_length , :allow_nil = > true , :allow_blank = > true
validates_length_of :transmission_errors , :maximum = > maximum_text_length , :allow_nil = > true , :allow_blank = > true
2012-12-07 03:19:08 +08:00
2013-02-14 01:13:44 +08:00
# Stream policy
2011-02-01 09:57:29 +08:00
on_create_send_to_streams do
2013-02-14 01:13:44 +08:00
if to == 'dashboard' && Notification . types_to_show_in_feed . include? ( notification_name )
user_id
2011-02-01 09:57:29 +08:00
else
[ ]
end
end
2012-12-07 03:19:08 +08:00
2013-02-14 01:13:44 +08:00
# State machine
2011-02-01 09:57:29 +08:00
workflow do
state :created do
event :stage , :transitions_to = > :staged do
self . dispatch_at = Time . now . utc + self . delay_for
2011-02-23 05:28:50 +08:00
if self . to != 'dashboard' && ! @stage_without_dispatch
2011-02-01 09:57:29 +08:00
MessageDispatcher . dispatch ( self )
end
end
event :cancel , :transitions_to = > :cancelled
event :close , :transitions_to = > :closed # needed for dashboard messages
end
state :staged do
event :dispatch , :transitions_to = > :sending
event :cancel , :transitions_to = > :cancelled
event :close , :transitions_to = > :closed # needed for dashboard messages
end
state :sending do
event :complete_dispatch , :transitions_to = > :sent do
self . sent_at || = Time . now
end
event :cancel , :transitions_to = > :cancelled
event :close , :transitions_to = > :closed
event :errored_dispatch , :transitions_to = > :staged do
# A little delay so we don't churn so much when the server is down.
self . dispatch_at = Time . now . utc + 5 . minutes
end
end
state :sent do
event :close , :transitions_to = > :closed
event :bounce , :transitions_to = > :bounced do
# Permenant reminder that this bounced.
self . communication_channel . bounce_count += 1
self . communication_channel . save!
self . is_bounced = true
end
event :recycle , :transitions_to = > :staged
end
2012-12-07 03:19:08 +08:00
2011-02-01 09:57:29 +08:00
state :bounced do
event :close , :transitions_to = > :closed
end
2012-12-07 03:19:08 +08:00
2011-02-01 09:57:29 +08:00
state :dashboard do
event :close , :transitions_to = > :closed
event :cancel , :transitions_to = > :closed
end
state :cancelled
state :closed do
event :send_message , :transitions_to = > :closed do
self . sent_at || = Time . now
end
end
2013-02-14 01:13:44 +08:00
end
# Named scopes
2013-03-21 03:38:19 +08:00
scope :for_asset_context_codes , lambda { | context_codes | where ( :asset_context_code = > context_codes ) }
2013-02-14 01:13:44 +08:00
2013-03-21 03:38:19 +08:00
scope :for , lambda { | context | where ( :context_type = > context . class . base_ar_class . to_s , :context_id = > context ) }
2013-02-14 01:13:44 +08:00
2013-03-21 03:38:19 +08:00
scope :after , lambda { | date | where ( " messages.created_at>? " , date ) }
2013-02-14 01:13:44 +08:00
2013-03-21 03:38:19 +08:00
scope :to_dispatch , lambda {
where ( " messages.workflow_state='staged' AND messages.dispatch_at<=? AND 'messages.to'<>'dashboard' " , Time . now . utc )
2013-02-14 01:13:44 +08:00
}
2013-03-21 03:38:19 +08:00
scope :to_email , where ( :path_type = > [ 'email' , 'sms' ] )
2013-02-14 01:13:44 +08:00
2013-03-21 03:38:19 +08:00
scope :to_facebook , where ( :path_type = > 'facebook' , :workflow_state = > 'sent' ) . order ( " sent_at DESC " ) . limit ( 25 )
2013-02-14 01:13:44 +08:00
2013-03-21 03:38:19 +08:00
scope :not_to_email , where ( " messages.path_type NOT IN ('email', 'sms') " )
2013-02-14 01:13:44 +08:00
2013-03-21 03:38:19 +08:00
scope :by_name , lambda { | notification_name | where ( :notification_name = > notification_name ) }
2013-02-14 01:13:44 +08:00
2013-03-21 03:38:19 +08:00
scope :before , lambda { | date | where ( " messages.created_at<? " , date ) }
2013-02-14 01:13:44 +08:00
2013-03-21 03:38:19 +08:00
scope :for_user , lambda { | user | where ( :user_id = > user ) }
2013-02-14 01:13:44 +08:00
2013-02-28 10:11:57 +08:00
# messages that can be moved to the 'cancelled' state. dashboard messages
# can be closed by calling 'cancel', but aren't included
2013-03-21 03:38:19 +08:00
scope :cancellable , where ( :workflow_state = > [ 'created' , 'staged' , 'sending' ] )
2013-02-14 01:13:44 +08:00
# For finding a very particular message:
# Message.for(context).by_name(name).directed_to(to).for_user(user), or
# messages.for(context).by_name(name).directed_to(to).for_user(user)
# Where user can be a User or id, name needs to be the Notification name.
2013-03-21 03:38:19 +08:00
scope :staged , lambda { where ( " messages.workflow_state='staged' AND messages.dispatch_at>? " , Time . now . utc ) }
2013-02-14 01:13:44 +08:00
2013-03-21 03:38:19 +08:00
scope :in_state , lambda { | state | where ( :workflow_state = > Array ( state ) . map ( & :to_s ) ) }
2013-02-14 01:13:44 +08:00
# Public: Helper to generate a URI for the given subject. Overrides Rails'
# built-in ActionController::PolymorphicRoutes#polymorphic_url method because
# it forces option defaults for protocol and host.
#
# Differs from the built-in method in that it doesn't accept a hash as a
# subject; only ActiveRecord objects and arrays.
#
# subject - An ActiveRecord object, or an array of ActiveRecord objects.
# options - A hash of URI options (default: {}):
# :protocol - HTTP protocol string. Either 'http' or 'https'.
# :host - A host string (e.g. 'canvas.instructure.com').
#
# Returns a URL string.
def polymorphic_url_with_context_host ( subject , options = { } )
# Force options
options [ :protocol ] = HostUrl . protocol
options [ :host ] = if subject . is_a? ( Array )
HostUrl . context_host ( subject . first )
else
HostUrl . context_host ( subject )
end
2011-02-01 09:57:29 +08:00
2013-02-14 01:13:44 +08:00
polymorphic_url_without_context_host ( subject , options )
end
alias_method_chain :polymorphic_url , :context_host
2013-04-20 06:00:15 +08:00
# the hostname for user-specific links (e.g. setting notification prefs).
# may be different from the asset/context host
def primary_host
primary_context = user . pseudonym . try ( :account )
primary_context || = context . respond_to? ( :context ) ? context . context : context
HostUrl . context_host primary_context
end
2013-02-14 01:13:44 +08:00
# Internal: Store any transmission errors in the database to help with later
# debugging.
#
# val - An error string.
#
# Returns nothing.
def transmission_errors = ( val )
write_attribute ( :transmission_errors , val [ 0 , self . class . maximum_text_length ] )
2011-02-01 09:57:29 +08:00
end
2011-02-23 05:28:50 +08:00
2013-02-14 01:13:44 +08:00
# Public: Custom getter that delegates and caches notification category to
2014-01-28 05:07:09 +08:00
# associated notification
2013-02-14 01:13:44 +08:00
#
# Returns a notification category string.
def notification_category
@cat || = notification . try ( :category )
end
# Public: Return associated notification's display category.
#
# Returns notification display category string.
def notification_display_category
notification . try ( :display_category )
end
# Public: Skip message dispatch during stage transition. Used when batch
2011-02-23 05:28:50 +08:00
# dispatching.
2013-02-14 01:13:44 +08:00
#
# Returns nothing.
2011-02-23 05:28:50 +08:00
def stage_without_dispatch!
@stage_without_dispatch = true
end
2012-12-07 03:19:08 +08:00
2013-02-14 01:13:44 +08:00
# Public: Stage the message during the dispatch process. Messages travel
# from created -> staged -> sending -> sent.
#
# Returns nothing.
2011-02-01 09:57:29 +08:00
def stage_message
2013-02-14 01:13:44 +08:00
stage if state == :created
if dashboard?
2013-03-19 03:07:47 +08:00
messages = Message . in_state ( :dashboard ) . where (
2013-02-14 01:13:44 +08:00
:notification_id = > notification_id ,
:context_id = > context_id ,
:context_type = > context_type ,
:user_id = > user_id
2013-03-19 03:07:47 +08:00
)
2013-02-14 01:13:44 +08:00
( messages - [ self ] ) . each ( & :close )
2011-02-01 09:57:29 +08:00
end
end
2012-12-07 03:19:08 +08:00
2013-02-14 01:13:44 +08:00
# Public: Store content in a message_content_... instance variable.
#
# name - The symbol name of the content.
# block - ?
#
# Returns an empty string.
2011-02-01 09:57:29 +08:00
def define_content ( name , & block )
2014-01-13 23:37:39 +08:00
old_output_buffer , @output_buffer = [ @output_buffer , @output_buffer . dup . clear ]
2013-02-14 01:13:44 +08:00
2011-02-01 09:57:29 +08:00
yield
2012-12-07 03:19:08 +08:00
2013-02-14 01:13:44 +08:00
instance_variable_set ( :" @message_content_ #{ name } " ,
@output_buffer . to_s . strip )
@output_buffer = old_output_buffer . sub ( / \ n \ z / , '' )
2014-01-13 23:37:39 +08:00
if old_output_buffer . is_a? ( ActiveSupport :: SafeBuffer ) && old_output_buffer . html_safe?
2014-02-12 09:29:31 +08:00
@output_buffer = old_output_buffer . class . new ( @output_buffer )
2014-01-13 23:37:39 +08:00
end
2013-02-14 01:13:44 +08:00
''
end
2012-12-07 03:19:08 +08:00
2013-02-14 01:13:44 +08:00
# Public: Get a message_content_... instance variable.
#
# name - The name of the message content variable as a symbol.
#
# Returns value of instance variable (should be a string?).
2011-02-01 09:57:29 +08:00
def content ( name )
2013-02-14 01:13:44 +08:00
instance_variable_get ( :" @message_content_ #{ name } " )
2011-02-01 09:57:29 +08:00
end
2012-12-07 03:19:08 +08:00
2013-02-14 01:13:44 +08:00
# Public: Custom getter for @message_content_link.
#
# Returns string content from @message_content_link.
2011-02-01 09:57:29 +08:00
def main_link
content ( :link )
end
2012-12-06 03:29:24 +08:00
# Public: Load a message template from app/messages. Also sets @i18n_scope.
#
# filename - The string path to the template (e.g. "/var/web/canvas/app/messages/template.email.erb")
#
# Returns a template string or false if it can't be found.
def get_template ( filename )
path = Canvas :: MessageHelper . find_message_path ( filename )
if ! ( File . exist? ( path ) rescue false )
2013-02-14 01:13:44 +08:00
filename = self . notification . name . downcase . gsub ( / \ s / , '_' ) + " .email.erb "
2012-12-06 03:29:24 +08:00
path = Canvas :: MessageHelper . find_message_path ( filename )
end
@i18n_scope = " messages. " + filename . sub ( / \ .erb \ z / , '' )
if ( File . exist? ( path ) rescue false )
File . read ( path )
else
false
end
end
2012-12-07 03:19:08 +08:00
2013-02-21 03:10:53 +08:00
# Public: Get the template name based on the path type.
#
# path_type - The path to send the message across, e.g, 'email'.
#
# Returns file name for erb template
def template_filename ( path_type = nil )
self . notification . name . parameterize . underscore + " . " + path_type + " .erb "
end
2013-04-20 04:30:44 +08:00
# Public: Apply an HTML email template to this message.
2013-02-21 13:16:24 +08:00
#
# _binding - The binding to attach to the template.
#
2013-04-20 04:30:44 +08:00
# Returns an HTML template (or nil).
def apply_html_template ( _binding )
2013-07-12 00:36:36 +08:00
orig_i18n_scope = @i18n_scope
@i18n_scope = " #{ @i18n_scope } .html "
2013-04-20 04:30:44 +08:00
return nil unless template = load_html_template
2013-02-21 13:16:24 +08:00
2013-03-26 02:13:42 +08:00
# Add the attribute 'inner_html' with the value of inner_html into the _binding
2014-02-12 09:29:31 +08:00
@output_buffer = nil
2013-04-20 04:30:44 +08:00
inner_html = RailsXss :: Erubis . new ( template , :bufvar = > '@output_buffer' ) . result ( _binding )
2013-03-26 02:13:42 +08:00
setter = eval " inner_html = nil; lambda { |v| inner_html = v } " , _binding
setter . call ( inner_html )
2013-02-21 13:16:24 +08:00
layout_path = Canvas :: MessageHelper . find_message_path ( '_layout.email.html.erb' )
2014-04-05 00:56:38 +08:00
@output_buffer = nil
2013-04-20 04:30:44 +08:00
RailsXss :: Erubis . new ( File . read ( layout_path ) ) . result ( _binding )
2013-07-12 00:36:36 +08:00
ensure
@i18n_scope = orig_i18n_scope
2013-04-20 04:30:44 +08:00
end
def load_html_template
html_file = template_filename ( 'email.html' )
html_path = Canvas :: MessageHelper . find_message_path ( html_file )
File . read ( html_path ) if File . exist? ( html_path )
2013-02-21 13:16:24 +08:00
end
2013-02-21 03:10:53 +08:00
# Public: Assign the body, subject and url to the message.
#
# message_body_template - Raw template body
# path_type - Path to send the message across, e.g, 'email'.
# _binding - Message binding
#
# Returns message body
def populate_body ( message_body_template , path_type , _binding )
# Build the body content based on the path type
if path_type == 'facebook'
# this will ensure we escape anything that's not already safe
2014-02-12 09:29:31 +08:00
@output_buffer = nil
2013-02-21 13:16:24 +08:00
self . body = RailsXss :: Erubis . new ( message_body_template ) . result ( _binding )
2013-02-21 03:10:53 +08:00
else
2013-02-21 13:16:24 +08:00
self . body = Erubis :: Eruby . new ( message_body_template ,
:bufvar = > '@output_buffer' ) . result ( _binding )
2013-04-20 04:30:44 +08:00
self . html_body = apply_html_template ( _binding ) if path_type == 'email'
2013-02-21 03:10:53 +08:00
end
# Append a footer to the body if the path type is email
if path_type == 'email'
raw_footer_message = File . read ( Canvas :: MessageHelper . find_message_path ( '_email_footer.email.erb' ) )
2013-04-20 06:00:15 +08:00
footer_message = Erubis :: Eruby . new ( raw_footer_message , :bufvar = > " @output_buffer " ) . result ( _binding )
2013-02-21 13:16:24 +08:00
if footer_message . present?
self . body = <<-END.strip_heredoc
#{self.body}
________________________________________
#{footer_message}
END
end
2013-02-21 03:10:53 +08:00
end
self . body
end
2013-02-14 01:13:44 +08:00
# Public: Prepare a message for delivery by setting body, subject, etc.
#
# path_type - The path to send the message across, e.g, 'email'.
#
# Returns nothing.
2011-02-01 09:57:29 +08:00
def parse! ( path_type = nil )
raise StandardError , " Cannot parse without a context " unless self . context
2013-02-21 03:10:53 +08:00
# Get the users timezone but maintain the original timezone in order to set it back at the end
original_time_zone = Time . zone . name || " UTC "
2013-02-21 13:16:24 +08:00
user_time_zone = self . user . try ( :time_zone ) || original_time_zone
Time . zone = user_time_zone
2013-02-21 03:10:53 +08:00
# Ensure we have a path_type
path_type = 'dashboard' if to == 'summary'
unless path_type
path_type = communication_channel . try ( :path_type ) || 'email'
end
2013-02-21 13:16:24 +08:00
2013-02-21 03:10:53 +08:00
# Determine the message template file to be used in the message
filename = template_filename ( path_type )
message_body_template = get_template ( filename )
2013-02-21 13:16:24 +08:00
context , asset , user , delayed_messages , asset_context , data = [ self . context ,
2014-01-13 22:23:27 +08:00
self . context , self . user , @delayed_messages , self . asset_context , @data ]
2013-02-21 03:10:53 +08:00
if message_body_template . present? && path_type . present?
populate_body ( message_body_template , path_type , binding )
# Set the subject and url
2011-05-07 02:44:34 +08:00
self . subject = @message_content_subject || t ( '#message.default_subject' , 'Canvas Alert' )
2013-02-21 13:16:24 +08:00
self . url = @message_content_link || nil
2011-02-01 09:57:29 +08:00
else
2013-02-21 03:10:53 +08:00
# Message doesn't exist so we flag the message as an error
2013-02-21 13:16:24 +08:00
main_link = Erubis :: Eruby . new ( self . notification . main_link || " " ) . result ( binding )
2013-02-21 03:10:53 +08:00
self . subject = Erubis :: Eruby . new ( subject ) . result ( binding )
2013-02-21 13:16:24 +08:00
self . body = Erubis :: Eruby . new ( body ) . result ( binding )
2012-12-06 03:29:24 +08:00
self . transmission_errors = " couldn't find #{ Canvas :: MessageHelper . find_message_path ( filename ) } "
2011-02-01 09:57:29 +08:00
end
2013-02-21 03:10:53 +08:00
2011-02-01 09:57:29 +08:00
self . body
2011-05-07 02:44:34 +08:00
ensure
2013-02-21 03:10:53 +08:00
# Set the timezone back to what it originally was
Time . zone = original_time_zone if original_time_zone . present?
2011-05-07 02:44:34 +08:00
@i18n_scope = nil
2011-02-01 09:57:29 +08:00
end
2011-04-07 02:46:06 +08:00
2013-02-14 01:13:44 +08:00
# Public: Deliver this message.
#
# Returns nothing.
2011-02-01 09:57:29 +08:00
def deliver
2013-02-14 01:13:44 +08:00
# don't dispatch canceled or already-sent messages.
return nil unless dispatch
2012-02-24 01:13:22 +08:00
2013-02-14 01:13:44 +08:00
unless path_type . present?
logger . warn " Could not find a path type for #{ inspect } "
return nil
2011-02-01 09:57:29 +08:00
end
2013-02-14 01:13:44 +08:00
delivery_method = " deliver_via_ #{ path_type } " . to_sym
if not delivery_method or not respond_to? ( delivery_method )
logger . warn ( " Could not set delivery_method from #{ path_type } " )
return nil
2011-02-01 09:57:29 +08:00
end
2012-02-24 01:13:22 +08:00
2013-02-14 01:13:44 +08:00
send ( delivery_method )
2011-02-01 09:57:29 +08:00
end
2012-02-24 01:13:22 +08:00
2013-02-14 01:13:44 +08:00
# Public: Fetch the dashboard messages for the given messages.
#
# messages - An array of message objects.
#
# Returns an array of dashboard messages.
2011-02-01 09:57:29 +08:00
def self . dashboard_messages ( messages )
2013-02-14 01:13:44 +08:00
message_types = messages . inject ( { } ) do | types , message |
type = message . notification . category rescue 'Other'
if type . present?
types [ type ] || = [ ]
types [ type ] << message
2011-02-01 09:57:29 +08:00
end
2013-02-14 01:13:44 +08:00
hash
2011-02-01 09:57:29 +08:00
end
2013-02-14 01:13:44 +08:00
# not sure what this is even doing?
2014-03-18 04:54:26 +08:00
message_types . to_a . sort_by { | m | m [ 0 ] == 'Other' ? CanvasSort :: Last : m [ 0 ] }
2011-02-01 09:57:29 +08:00
end
2013-02-14 01:13:44 +08:00
# Public: Format and return the body for this message.
#
# Returns a body string.
2011-02-01 09:57:29 +08:00
def formatted_body
2013-02-14 01:13:44 +08:00
# NOTE: I'm pretty sure this is only used for Facebook messages; confirm
# that and maybe rename the method/do something different with it?
case path_type
when 'facebook'
( body || '' ) .
gsub ( / \ n / , " <br /> \n " ) .
gsub ( / ( \ s \ s+) / ) { | str | str . gsub ( / \ s / , ' ' ) }
when 'email'
formatted_body = format_message ( body ) . first
formatted_body
2011-02-01 09:57:29 +08:00
else
body
end
end
2012-12-07 03:19:08 +08:00
2013-02-14 01:13:44 +08:00
# Public: Get the root account of this message's context.
#
# Returns an account.
2012-10-25 06:35:57 +08:00
def context_root_account
unbounded_loop_paranoia_counter = 10
2013-02-14 01:13:44 +08:00
current_context = context
until current_context . respond_to? ( :root_account ) do
2013-03-13 23:11:14 +08:00
return nil if unbounded_loop_paranoia_counter < = 0 || current_context . nil?
return nil unless current_context . respond_to? ( :context )
2013-02-14 01:13:44 +08:00
current_context = current_context . context
2012-10-25 06:35:57 +08:00
unbounded_loop_paranoia_counter -= 1
end
2013-02-14 01:13:44 +08:00
current_context . root_account
2012-10-25 06:35:57 +08:00
end
2013-02-14 01:13:44 +08:00
# Internal: Set default values before save.
#
# Returns true.
2011-02-01 09:57:29 +08:00
def infer_defaults
2013-02-14 01:13:44 +08:00
if notification
self . notification_name || = notification . name
self . notification_category || = notification_category
end
self . path_type || = communication_channel . try ( :path_type )
self . path_type = 'summary' if to == 'dashboard'
self . path_type = 'email' if context_type == 'ErrorReport'
self . to_email = true if %w[ email sms ] . include? ( path_type )
2012-10-25 06:35:57 +08:00
2013-03-13 23:11:14 +08:00
root_account = context_root_account
self . root_account_id || = root_account . try ( :id )
self . from_name = root_account . settings [ :outgoing_email_default_name ] rescue nil
2013-02-14 01:13:44 +08:00
self . from_name = HostUrl . outgoing_email_default_name if from_name . blank?
self . from_name = asset_context . name if ( asset_context &&
! asset_context . is_a? ( Account ) && asset_context . name &&
notification . dashboard? rescue false )
self . from_name = from_name if respond_to? ( :from_name )
2012-10-25 06:35:57 +08:00
2011-02-01 09:57:29 +08:00
true
end
2011-05-07 02:44:34 +08:00
2013-02-14 01:13:44 +08:00
# Public: Convenience method for translation calls.
#
# key - The translation key.
# default - The English default of the key.
# options - An options hash passed to translate (default: {}).
#
# Returns a translated string.
2011-05-07 02:44:34 +08:00
def translate ( key , default , options = { } )
2013-02-14 01:13:44 +08:00
# Add scope if it's present in the model and missing from the key.
if @i18n_scope && key !~ / \ A # /
key = " # #{ @i18n_scope } . #{ key } "
end
2011-05-07 02:44:34 +08:00
super ( key , default , options )
end
alias :t :translate
2013-02-14 01:13:44 +08:00
# Public: Store data on the message for use at delivery-time.
#
# values_hash - A hash of values to store in the model's data attribute.
#
# Returns nothing.
2012-03-30 13:20:15 +08:00
def data = ( values_hash )
@data = OpenStruct . new ( values_hash )
end
2013-02-14 01:13:44 +08:00
# Public: Before save, close this message if it has no user or a deleted
# user and isn't for an ErrorReport.
#
# Returns nothing.
def move_messages_for_deleted_users
if context_type != 'ErrorReport' && ( ! user || user . deleted? )
self . workflow_state = 'closed'
end
end
2012-12-07 03:19:08 +08:00
2013-02-14 01:13:44 +08:00
# Public: Before save, prepare dashboard messages for display on dashboard.
#
# Returns nothing.
def move_dashboard_messages
if to == 'dashboard' && ! cancelled? && ! closed?
self . workflow_state = 'dashboard'
end
end
# Public: Before save, set the proper asset_context_code for the model.
#
# Returns an asset_context_code string or nil.
def set_asset_context_code
self . asset_context_code = " #{ context_type . underscore } _ #{ context_id } "
rescue
nil
end
2013-03-13 23:11:14 +08:00
# Public: Return the message as JSON filtered to selected fields and
# flattened appropriately.
#
# Returns json hash.
def as_json ( options = { } )
super ( :only = > [ :id , :created_at , :sent_at , :workflow_state , :from , :to , :reply_to , :subject , :body , :html_body ] ) [ 'message' ]
end
2013-02-14 01:13:44 +08:00
protected
# Internal: Deliver the message through email.
#
# Returns nothing.
# Raises Net::SMTPServerBusy if the message cannot be sent.
# Raises Timeout::Error if the remote server times out.
2012-12-07 03:19:08 +08:00
def deliver_via_email
res = nil
2013-02-14 01:13:44 +08:00
logger . info " Delivering mail: #{ self . inspect } "
2012-12-07 03:19:08 +08:00
begin
2014-01-17 06:08:04 +08:00
res = Mailer . create_message ( self ) . deliver
2012-12-07 03:19:08 +08:00
rescue Net :: SMTPServerBusy = > e
@exception = e
logger . error " Exception: #{ e . class } : #{ e . message } \n \t #{ e . backtrace . join ( " \n \t " ) } "
2013-02-14 01:13:44 +08:00
cancel if e . message . try ( :match , / Bad recipient / )
2013-02-26 08:04:06 +08:00
rescue StandardError , Timeout :: Error = > e
2012-12-07 03:19:08 +08:00
@exception = e
logger . error " Exception: #{ e . class } : #{ e . message } \n \t #{ e . backtrace . join ( " \n \t " ) } "
2011-02-01 09:57:29 +08:00
end
2013-02-14 01:13:44 +08:00
2012-12-07 03:19:08 +08:00
if res
2011-02-10 07:50:14 +08:00
complete_dispatch
2012-12-07 03:19:08 +08:00
elsif @exception
2013-01-06 00:22:55 +08:00
raise_error = @exception . to_s !~ / ^450 /
log_error = raise_error && ! @exception . is_a? ( Timeout :: Error )
if log_error
2012-12-07 03:19:08 +08:00
ErrorReport . log_exception ( :default , @exception , {
2013-02-14 01:13:44 +08:00
:message = > 'Message delivery failed' ,
:to = > to ,
:object = > inspect . to_s } )
2012-12-07 03:19:08 +08:00
end
2013-02-14 01:13:44 +08:00
2012-12-07 03:19:08 +08:00
self . errored_dispatch
2013-01-06 00:22:55 +08:00
if raise_error
raise @exception
else
return false
end
2011-02-10 07:50:14 +08:00
end
2013-02-14 01:13:44 +08:00
2012-12-07 03:19:08 +08:00
true
end
2013-02-14 01:13:44 +08:00
# Internal: Deliver the message through Twitter.
#
2013-04-23 02:48:29 +08:00
# The template should define the content for :link and not place into the body of the template itself
#
2013-02-14 01:13:44 +08:00
# Returns nothing.
2012-12-07 03:19:08 +08:00
def deliver_via_twitter
2014-04-10 00:34:33 +08:00
twitter_service = user . user_services . find_by_service ( 'twitter' )
2014-04-15 01:30:13 +08:00
host = HostUrl . short_host ( self . asset_context )
msg_id = AssetSignature . generate ( self )
Twitter :: Messenger . new ( self , twitter_service , host , msg_id ) . deliver
2012-12-07 03:19:08 +08:00
complete_dispatch
end
2013-02-14 01:13:44 +08:00
# Internal: Deliver the message through Facebook.
#
# Returns nothing.
2012-12-07 03:19:08 +08:00
def deliver_via_facebook
facebook_user_id = self . to . to_i . to_s
service = self . user . user_services . for_service ( 'facebook' ) . find_by_service_user_id ( facebook_user_id )
2014-04-19 01:41:45 +08:00
Facebook :: Connection . dashboard_increment_count ( service . service_user_id , service . token , I18n . t ( :new_facebook_message , 'You have a new message from Canvas' ) ) if service && service . token
2012-12-07 03:19:08 +08:00
complete_dispatch
end
2013-02-14 01:13:44 +08:00
# Internal: Send the message through SMS. Right now this just calls
# deliver_via_email because we're using email SMS gateways.
#
# Returns nothing.
2012-12-07 03:19:08 +08:00
def deliver_via_sms
deliver_via_email
end
2011-02-01 09:57:29 +08:00
end