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
|
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 Twitter
|
|
|
|
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
|
|
|
|
|
|
|
|
# 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
|
|
|
|
# associated notification
|
|
|
|
#
|
|
|
|
# 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?
|
|
|
|
@output_buffer = ActiveSupport::SafeBuffer.new(@output_buffer)
|
|
|
|
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
|
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')
|
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
|
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?
|
2013-10-02 01:03:38 +08:00
|
|
|
message_types.to_a.sort_by { |m| m[0] == 'Other' ? SortLast : 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
|
|
|
|
TwitterMessenger.new(self).deliver
|
|
|
|
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)
|
|
|
|
Facebook.dashboard_increment_count(service) if service && service.token
|
|
|
|
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
|