1
# encoding: utf-8
2
#--
3
#   Copyright (C) 2012 Gitorious AS
4
#   Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
5
#   Copyright (C) 2008 Johan Sørensen <johan@johansorensen.com>
6
#   Copyright (C) 2008 David A. Cuadrado <krawek@gmail.com>
7
#   Copyright (C) 2008 Tor Arne Vestbø <tavestbo@trolltech.com>
8
#
9
#   This program is free software: you can redistribute it and/or modify
10
#   it under the terms of the GNU Affero General Public License as published by
11
#   the Free Software Foundation, either version 3 of the License, or
12
#   (at your option) any later version.
13
#
14
#   This program is distributed in the hope that it will be useful,
15
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
16
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
#   GNU Affero General Public License for more details.
18
#
19
#   You should have received a copy of the GNU Affero General Public License
20
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
#++
22
23
class Comment < ActiveRecord::Base
24
  include Gitorious::Search
25
  include Gitorious::Authorization
26
27
  belongs_to :user
28
  belongs_to :target, :polymorphic => true
29
  belongs_to :project
30
  has_many   :events, :as => :target, :dependent => :destroy
31
  after_create :notify_target_if_supported
32
  after_create :update_state_in_target
33
  serialize :state_change, Array
34
35
  is_indexed do |s|
36
    s.index :body
37
    s.index "user#login", :as => :commented_by
38
  end
39
40
  attr_protected :user_id
41
42
  validates_presence_of :user_id, :target, :project_id
43
  validates_presence_of :body, :if =>  Proc.new {|mr| mr.body_required?}
44
45
  named_scope :with_shas, proc{|*shas|
46
    {:conditions => { :sha1 => shas.flatten }, :include => :user}
47
  }
48
49
  NOTIFICATION_TARGETS = [ MergeRequest, MergeRequestVersion ]
50
51
  def deliver_notification_to(another_user)
52
    message_body = "#{user.title} commented:\n\n#{body}"
53
    if [MergeRequest, MergeRequestVersion].include?(target.class)
54
      if state_change
55
        message_body << "\n\nThe status of your merge request"
56
        message_body << " is now #{state_changed_to}"
57
      end
58
      subject_class_name = "merge request"
59
    else
60
      subject_class_name = target.class.human_name.downcase
61
    end
62
    message = Message.new({
63
      :sender => self.user,
64
      :recipient => another_user,
65
      :subject => "#{user.title} commented on your #{subject_class_name}",
66
      :body => message_body,
67
      :notifiable => self.target,
68
    })
69
    message.save
70
  end
71
72
  def state=(new_state)
73
    return if new_state.blank?
74
    result = []
75
    if applies_to_merge_request?
76
      return if target.status_tag.to_s == new_state
77
      result << (target.status_tag.nil? ? nil : target.status_tag.name)
78
    end
79
    result << new_state
80
    self.state_change = result
81
  end
82
83
  def state_changed_to
84
    state_change.to_a.last
85
  end
86
87
  def state_changed_from
88
    state_change.to_a.size > 1 ? state_change.first : nil
89
  end
90
91
  def body_required?
92
    if applies_to_merge_request?
93
      return state_change.blank?
94
    else
95
      return true
96
    end
97
  end
98
99
  # +lines_str+ is a representation of the first and last line-number
100
  # tuple (as generated by Diff::Unified::Generator) and the lines the
101
  # comment span, in the follow format:
102
  # first_tuple:last_tuple+line_span
103
  def lines=(lines_str)
104
    start, rest = lines_str.split(":")
105
    raise "invalid lines format" if rest.blank?
106
    last, amount = rest.split("+")
107
    if start.blank? || last.blank? || amount.blank?
108
      raise "invalid lines format"
109
    end
110
    self.first_line_number = start
111
    self.last_line_number = last
112
    self.number_of_lines = amount
113
  end
114
115
  def lines
116
    "#{self.first_line_number}:#{self.last_line_number}+#{self.number_of_lines}"
117
  end
118
119
  def sha_range
120
    first, last = sha1.split("-")
121
    first..(last||first)
122
  end
123
124
  def applies_to_line_numbers?
125
    return !first_line_number.blank?
126
  end
127
128
  def applies_to_merge_request?
129
    MergeRequest === target
130
  end
131
132
  def creator?(a_user)
133
    a_user == user
134
  end
135
136
  def recently_created?
137
    created_at > 10.minutes.ago
138
  end
139
140
  protected
141
  def notify_target_if_supported
142
    if target && NOTIFICATION_TARGETS.include?(target.class)
143
      if self.target === MergeRequestVersion
144
        target_user = target.merge_request.user
145
      else
146
        target_user = target.user
147
      end
148
      return if target_user == user
149
      deliver_notification_to(target_user)
150
    end
151
  end
152
153
  def update_state_in_target
154
    if applies_to_merge_request? and state_change
155
      target.with_user(user) do
156
        if can_resolve_merge_request?(user, target)
157
          target.status_tag=(state_changed_to)
158
          target.create_status_change_event(body)
159
        end
160
      end
161
    end
162
  end
163
end