197 lines
7.8 KiB
Ruby
197 lines
7.8 KiB
Ruby
#
|
|
# Copyright (C) 2011 Instructure, Inc.
|
|
#
|
|
# 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 Conversation < ActiveRecord::Base
|
|
has_many :conversation_participants
|
|
has_many :subscribed_conversation_participants,
|
|
:conditions => "subscribed",
|
|
:class_name => 'ConversationParticipant'
|
|
has_many :conversation_messages, :order => "created_at DESC, id DESC"
|
|
|
|
# see also User#messageable_users
|
|
has_many :participants,
|
|
:through => :conversation_participants,
|
|
:source => :user,
|
|
:select => User::MESSAGEABLE_USER_COLUMN_SQL + ", NULL AS common_courses, NULL AS common_groups",
|
|
:order => 'last_authored_at IS NULL, last_authored_at DESC, LOWER(COALESCE(short_name, name))'
|
|
has_many :subscribed_participants,
|
|
:through => :subscribed_conversation_participants,
|
|
:source => :user,
|
|
:select => User::MESSAGEABLE_USER_COLUMN_SQL + ", NULL AS common_courses, NULL AS common_groups",
|
|
:order => 'last_authored_at IS NULL, last_authored_at DESC, LOWER(COALESCE(short_name, name))'
|
|
has_many :attachments, :through => :conversation_messages
|
|
|
|
attr_accessible
|
|
|
|
def private?
|
|
private_hash.present?
|
|
end
|
|
|
|
def self.initiate(user_ids, private)
|
|
private_hash = private ? Digest::SHA1.hexdigest(user_ids.sort.join(',')) : nil
|
|
transaction do
|
|
unless private_hash && conversation = find_by_private_hash(private_hash)
|
|
conversation = new
|
|
conversation.private_hash = private_hash
|
|
conversation.has_attachments = false
|
|
conversation.has_media_objects = false
|
|
conversation.save!
|
|
user_ids.each do |user_id|
|
|
participant = conversation.conversation_participants.build
|
|
participant.user_id = user_id
|
|
participant.workflow_state = 'read'
|
|
participant.save!
|
|
end
|
|
end
|
|
conversation
|
|
end
|
|
end
|
|
|
|
def add_participants(current_user, user_ids)
|
|
user_ids.map!(&:to_i)
|
|
raise "can't add participants to a private conversation" if private?
|
|
transaction do
|
|
lock!
|
|
user_ids -= conversation_participants.map(&:user_id)
|
|
next if user_ids.empty?
|
|
|
|
last_message_at = conversation_messages.human.first.created_at
|
|
raise "can't add participants if there are no messages" unless last_message_at
|
|
num_messages = conversation_messages.human.size
|
|
|
|
User.update_all('unread_conversations_count = unread_conversations_count + 1', :id => user_ids)
|
|
user_ids.each do |user_id|
|
|
participant = conversation_participants.build
|
|
participant.user_id = user_id
|
|
participant.workflow_state = 'unread'
|
|
participant.last_message_at = last_message_at
|
|
participant.message_count = num_messages
|
|
participant.save!
|
|
end
|
|
|
|
# give them all messages
|
|
connection.execute(<<-SQL)
|
|
INSERT INTO conversation_message_participants(conversation_message_id, conversation_participant_id)
|
|
SELECT conversation_messages.id, conversation_participants.id
|
|
FROM conversation_messages, conversation_participants
|
|
WHERE conversation_messages.conversation_id = #{self.id}
|
|
AND conversation_messages.conversation_id = conversation_participants.conversation_id
|
|
AND conversation_participants.user_id IN (#{user_ids.join(', ')})
|
|
SQL
|
|
|
|
# announce their arrival
|
|
add_event_message(current_user, :event_type => :users_added, :user_ids => user_ids)
|
|
end
|
|
end
|
|
|
|
def add_event_message(current_user, event_data={})
|
|
add_message(current_user, event_data.to_yaml, :generated => true)
|
|
end
|
|
|
|
def add_message(current_user, body, options = {})
|
|
options = {:generated => false,
|
|
:forwarded_message_ids => [],
|
|
:update_for_sender => true}.update(options)
|
|
transaction do
|
|
lock!
|
|
|
|
message = conversation_messages.build
|
|
message.author_id = current_user.id
|
|
message.body = body
|
|
message.generated = options[:generated]
|
|
message.context = options[:context]
|
|
if options[:forwarded_message_ids].present?
|
|
messages = ConversationMessage.find_all_by_id(options[:forwarded_message_ids].map(&:to_i))
|
|
conversation_ids = messages.map(&:conversation_id).uniq
|
|
raise "can only forward one conversation at a time" if conversation_ids.size != 1
|
|
raise "user doesn't have permission to forward these messages" unless current_user.conversations.find_by_conversation_id(conversation_ids.first)
|
|
# TODO: optimize me
|
|
message.forwarded_message_ids = messages.map(&:id).join(',')
|
|
end
|
|
message.save!
|
|
|
|
yield message if block_given?
|
|
|
|
connection.execute(<<-SQL)
|
|
INSERT INTO conversation_message_participants(conversation_message_id, conversation_participant_id)
|
|
SELECT #{message.id}, id FROM conversation_participants WHERE conversation_id = #{id}
|
|
SQL
|
|
|
|
unless options[:generated]
|
|
connection.execute("UPDATE conversation_participants SET message_count = message_count + 1 WHERE conversation_id = #{id}")
|
|
|
|
# make sure this jumps to the top of the inbox and is marked as unread for anyone who's subscribed
|
|
cp_conditions = self.class.send :sanitize_sql_array, [
|
|
"cp.conversation_id = ? AND cp.workflow_state <> 'unread' AND (cp.last_message_at IS NULL OR cp.subscribed) AND cp.user_id <> ?",
|
|
self.id,
|
|
current_user.id
|
|
]
|
|
if connection.adapter_name =~ /mysql/i
|
|
connection.execute <<-SQL
|
|
UPDATE users, conversation_participants cp
|
|
SET unread_conversations_count = unread_conversations_count + 1
|
|
WHERE users.id = cp.user_id AND #{cp_conditions}
|
|
SQL
|
|
else
|
|
User.update_all 'unread_conversations_count = unread_conversations_count + 1',
|
|
"id IN (SELECT user_id FROM conversation_participants cp WHERE #{cp_conditions})"
|
|
end
|
|
conversation_participants.update_all(
|
|
{:last_message_at => Time.now.utc, :workflow_state => 'unread'},
|
|
["(last_message_at IS NULL OR subscribed) AND user_id <> ?", current_user.id]
|
|
)
|
|
|
|
# for the sender, update the timestamps (marking it as read is always a ui concern)
|
|
if (sender_conversation = conversation_participants.find_by_user_id(current_user.id)) && (options[:update_for_sender] || sender_conversation.last_message_at)
|
|
sender_conversation.last_message_at = Time.now.utc
|
|
sender_conversation.last_authored_at = Time.now.utc
|
|
sender_conversation.save
|
|
end
|
|
|
|
updated = false
|
|
if message.attachments.present?
|
|
self.has_attachments = true
|
|
conversation_participants.update_all({:has_attachments => true}, "NOT has_attachments")
|
|
updated = true
|
|
end
|
|
if message.media_comment_id.present?
|
|
self.has_media_objects = true
|
|
conversation_participants.update_all({:has_media_objects => true}, "NOT has_media_objects")
|
|
updated = true
|
|
end
|
|
self.save if updated
|
|
end
|
|
|
|
message
|
|
end
|
|
end
|
|
|
|
def reply_from(opts)
|
|
user = opts.delete(:user)
|
|
message = opts.delete(:text).to_s.strip
|
|
user = nil unless user && self.participants.find_by_id(user.id)
|
|
if !user
|
|
raise "Only message participants may reply to messages"
|
|
elsif message.blank?
|
|
raise "Message body cannot be blank"
|
|
else
|
|
add_message(user, message, opts)
|
|
end
|
|
end
|
|
end
|