##// END OF EJS Templates
Adds the ability to move threads between project forums (#2452). 'Edit message' permission is required....
Jean-Philippe Lang -
r2560:ca166b30e1b2
parent child
Show More
@@ -1,81 +1,82
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class BoardsController < ApplicationController
18 class BoardsController < ApplicationController
19 before_filter :find_project, :authorize
19 before_filter :find_project, :authorize
20
20
21 helper :messages
21 helper :messages
22 include MessagesHelper
22 include MessagesHelper
23 helper :sort
23 helper :sort
24 include SortHelper
24 include SortHelper
25 helper :watchers
25 helper :watchers
26 include WatchersHelper
26 include WatchersHelper
27
27
28 def index
28 def index
29 @boards = @project.boards
29 @boards = @project.boards
30 # show the board if there is only one
30 # show the board if there is only one
31 if @boards.size == 1
31 if @boards.size == 1
32 @board = @boards.first
32 @board = @boards.first
33 show
33 show
34 end
34 end
35 end
35 end
36
36
37 def show
37 def show
38 sort_init 'updated_on', 'desc'
38 sort_init 'updated_on', 'desc'
39 sort_update 'created_on' => "#{Message.table_name}.created_on",
39 sort_update 'created_on' => "#{Message.table_name}.created_on",
40 'replies' => "#{Message.table_name}.replies_count",
40 'replies' => "#{Message.table_name}.replies_count",
41 'updated_on' => "#{Message.table_name}.updated_on"
41 'updated_on' => "#{Message.table_name}.updated_on"
42
42
43 @topic_count = @board.topics.count
43 @topic_count = @board.topics.count
44 @topic_pages = Paginator.new self, @topic_count, per_page_option, params['page']
44 @topic_pages = Paginator.new self, @topic_count, per_page_option, params['page']
45 @topics = @board.topics.find :all, :order => ["#{Message.table_name}.sticky DESC", sort_clause].compact.join(', '),
45 @topics = @board.topics.find :all, :order => ["#{Message.table_name}.sticky DESC", sort_clause].compact.join(', '),
46 :include => [:author, {:last_reply => :author}],
46 :include => [:author, {:last_reply => :author}],
47 :limit => @topic_pages.items_per_page,
47 :limit => @topic_pages.items_per_page,
48 :offset => @topic_pages.current.offset
48 :offset => @topic_pages.current.offset
49 @message = Message.new
49 render :action => 'show', :layout => !request.xhr?
50 render :action => 'show', :layout => !request.xhr?
50 end
51 end
51
52
52 verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :index }
53 verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :index }
53
54
54 def new
55 def new
55 @board = Board.new(params[:board])
56 @board = Board.new(params[:board])
56 @board.project = @project
57 @board.project = @project
57 if request.post? && @board.save
58 if request.post? && @board.save
58 flash[:notice] = l(:notice_successful_create)
59 flash[:notice] = l(:notice_successful_create)
59 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
60 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
60 end
61 end
61 end
62 end
62
63
63 def edit
64 def edit
64 if request.post? && @board.update_attributes(params[:board])
65 if request.post? && @board.update_attributes(params[:board])
65 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
66 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
66 end
67 end
67 end
68 end
68
69
69 def destroy
70 def destroy
70 @board.destroy
71 @board.destroy
71 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
72 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
72 end
73 end
73
74
74 private
75 private
75 def find_project
76 def find_project
76 @project = Project.find(params[:project_id])
77 @project = Project.find(params[:project_id])
77 @board = @project.boards.find(params[:id]) if params[:id]
78 @board = @project.boards.find(params[:id]) if params[:id]
78 rescue ActiveRecord::RecordNotFound
79 rescue ActiveRecord::RecordNotFound
79 render_404
80 render_404
80 end
81 end
81 end
82 end
@@ -1,125 +1,126
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class MessagesController < ApplicationController
18 class MessagesController < ApplicationController
19 menu_item :boards
19 menu_item :boards
20 before_filter :find_board, :only => [:new, :preview]
20 before_filter :find_board, :only => [:new, :preview]
21 before_filter :find_message, :except => [:new, :preview]
21 before_filter :find_message, :except => [:new, :preview]
22 before_filter :authorize, :except => [:preview, :edit, :destroy]
22 before_filter :authorize, :except => [:preview, :edit, :destroy]
23
23
24 verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show }
24 verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show }
25 verify :xhr => true, :only => :quote
25 verify :xhr => true, :only => :quote
26
26
27 helper :watchers
27 helper :watchers
28 helper :attachments
28 helper :attachments
29 include AttachmentsHelper
29 include AttachmentsHelper
30
30
31 # Show a topic and its replies
31 # Show a topic and its replies
32 def show
32 def show
33 @replies = @topic.children.find(:all, :include => [:author, :attachments, {:board => :project}])
33 @replies = @topic.children.find(:all, :include => [:author, :attachments, {:board => :project}])
34 @replies.reverse! if User.current.wants_comments_in_reverse_order?
34 @replies.reverse! if User.current.wants_comments_in_reverse_order?
35 @reply = Message.new(:subject => "RE: #{@message.subject}")
35 @reply = Message.new(:subject => "RE: #{@message.subject}")
36 render :action => "show", :layout => false if request.xhr?
36 render :action => "show", :layout => false if request.xhr?
37 end
37 end
38
38
39 # Create a new topic
39 # Create a new topic
40 def new
40 def new
41 @message = Message.new(params[:message])
41 @message = Message.new(params[:message])
42 @message.author = User.current
42 @message.author = User.current
43 @message.board = @board
43 @message.board = @board
44 if params[:message] && User.current.allowed_to?(:edit_messages, @project)
44 if params[:message] && User.current.allowed_to?(:edit_messages, @project)
45 @message.locked = params[:message]['locked']
45 @message.locked = params[:message]['locked']
46 @message.sticky = params[:message]['sticky']
46 @message.sticky = params[:message]['sticky']
47 end
47 end
48 if request.post? && @message.save
48 if request.post? && @message.save
49 attach_files(@message, params[:attachments])
49 attach_files(@message, params[:attachments])
50 redirect_to :action => 'show', :id => @message
50 redirect_to :action => 'show', :id => @message
51 end
51 end
52 end
52 end
53
53
54 # Reply to a topic
54 # Reply to a topic
55 def reply
55 def reply
56 @reply = Message.new(params[:reply])
56 @reply = Message.new(params[:reply])
57 @reply.author = User.current
57 @reply.author = User.current
58 @reply.board = @board
58 @reply.board = @board
59 @topic.children << @reply
59 @topic.children << @reply
60 if !@reply.new_record?
60 if !@reply.new_record?
61 attach_files(@reply, params[:attachments])
61 attach_files(@reply, params[:attachments])
62 end
62 end
63 redirect_to :action => 'show', :id => @topic
63 redirect_to :action => 'show', :id => @topic
64 end
64 end
65
65
66 # Edit a message
66 # Edit a message
67 def edit
67 def edit
68 render_403 and return false unless @message.editable_by?(User.current)
68 render_403 and return false unless @message.editable_by?(User.current)
69 if params[:message]
69 if params[:message]
70 @message.locked = params[:message]['locked']
70 @message.locked = params[:message]['locked']
71 @message.sticky = params[:message]['sticky']
71 @message.sticky = params[:message]['sticky']
72 end
72 end
73 if request.post? && @message.update_attributes(params[:message])
73 if request.post? && @message.update_attributes(params[:message])
74 attach_files(@message, params[:attachments])
74 attach_files(@message, params[:attachments])
75 flash[:notice] = l(:notice_successful_update)
75 flash[:notice] = l(:notice_successful_update)
76 redirect_to :action => 'show', :id => @topic
76 @message.reload
77 redirect_to :action => 'show', :board_id => @message.board, :id => @message.root
77 end
78 end
78 end
79 end
79
80
80 # Delete a messages
81 # Delete a messages
81 def destroy
82 def destroy
82 render_403 and return false unless @message.destroyable_by?(User.current)
83 render_403 and return false unless @message.destroyable_by?(User.current)
83 @message.destroy
84 @message.destroy
84 redirect_to @message.parent.nil? ?
85 redirect_to @message.parent.nil? ?
85 { :controller => 'boards', :action => 'show', :project_id => @project, :id => @board } :
86 { :controller => 'boards', :action => 'show', :project_id => @project, :id => @board } :
86 { :action => 'show', :id => @message.parent }
87 { :action => 'show', :id => @message.parent }
87 end
88 end
88
89
89 def quote
90 def quote
90 user = @message.author
91 user = @message.author
91 text = @message.content
92 text = @message.content
92 content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
93 content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
93 content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
94 content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
94 render(:update) { |page|
95 render(:update) { |page|
95 page.<< "$('message_content').value = \"#{content}\";"
96 page.<< "$('message_content').value = \"#{content}\";"
96 page.show 'reply'
97 page.show 'reply'
97 page << "Form.Element.focus('message_content');"
98 page << "Form.Element.focus('message_content');"
98 page << "Element.scrollTo('reply');"
99 page << "Element.scrollTo('reply');"
99 page << "$('message_content').scrollTop = $('message_content').scrollHeight - $('message_content').clientHeight;"
100 page << "$('message_content').scrollTop = $('message_content').scrollHeight - $('message_content').clientHeight;"
100 }
101 }
101 end
102 end
102
103
103 def preview
104 def preview
104 message = @board.messages.find_by_id(params[:id])
105 message = @board.messages.find_by_id(params[:id])
105 @attachements = message.attachments if message
106 @attachements = message.attachments if message
106 @text = (params[:message] || params[:reply])[:content]
107 @text = (params[:message] || params[:reply])[:content]
107 render :partial => 'common/preview'
108 render :partial => 'common/preview'
108 end
109 end
109
110
110 private
111 private
111 def find_message
112 def find_message
112 find_board
113 find_board
113 @message = @board.messages.find(params[:id], :include => :parent)
114 @message = @board.messages.find(params[:id], :include => :parent)
114 @topic = @message.root
115 @topic = @message.root
115 rescue ActiveRecord::RecordNotFound
116 rescue ActiveRecord::RecordNotFound
116 render_404
117 render_404
117 end
118 end
118
119
119 def find_board
120 def find_board
120 @board = Board.find(params[:board_id], :include => :project)
121 @board = Board.find(params[:board_id], :include => :project)
121 @project = @board.project
122 @project = @board.project
122 rescue ActiveRecord::RecordNotFound
123 rescue ActiveRecord::RecordNotFound
123 render_404
124 render_404
124 end
125 end
125 end
126 end
@@ -1,29 +1,42
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Board < ActiveRecord::Base
18 class Board < ActiveRecord::Base
19 belongs_to :project
19 belongs_to :project
20 has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_on DESC"
20 has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_on DESC"
21 has_many :messages, :dependent => :delete_all, :order => "#{Message.table_name}.created_on DESC"
21 has_many :messages, :dependent => :delete_all, :order => "#{Message.table_name}.created_on DESC"
22 belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id
22 belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id
23 acts_as_list :scope => :project_id
23 acts_as_list :scope => :project_id
24 acts_as_watchable
24 acts_as_watchable
25
25
26 validates_presence_of :name, :description
26 validates_presence_of :name, :description
27 validates_length_of :name, :maximum => 30
27 validates_length_of :name, :maximum => 30
28 validates_length_of :description, :maximum => 255
28 validates_length_of :description, :maximum => 255
29
30 def reset_counters!
31 self.class.reset_counters!(id)
32 end
33
34 # Updates topics_count, messages_count and last_message_id attributes for +board_id+
35 def self.reset_counters!(board_id)
36 board_id = board_id.to_i
37 update_all("topics_count = (SELECT COUNT(*) FROM #{Message.table_name} WHERE board_id=#{board_id} AND parent_id IS NULL)," +
38 " messages_count = (SELECT COUNT(*) FROM #{Message.table_name} WHERE board_id=#{board_id})," +
39 " last_message_id = (SELECT MAX(id) FROM #{Message.table_name} WHERE board_id=#{board_id})",
40 ["id = ?", board_id])
41 end
29 end
42 end
@@ -1,89 +1,90
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Message < ActiveRecord::Base
18 class Message < ActiveRecord::Base
19 belongs_to :board
19 belongs_to :board
20 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
20 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
21 acts_as_tree :counter_cache => :replies_count, :order => "#{Message.table_name}.created_on ASC"
21 acts_as_tree :counter_cache => :replies_count, :order => "#{Message.table_name}.created_on ASC"
22 acts_as_attachable
22 acts_as_attachable
23 belongs_to :last_reply, :class_name => 'Message', :foreign_key => 'last_reply_id'
23 belongs_to :last_reply, :class_name => 'Message', :foreign_key => 'last_reply_id'
24
24
25 acts_as_searchable :columns => ['subject', 'content'],
25 acts_as_searchable :columns => ['subject', 'content'],
26 :include => {:board => :project},
26 :include => {:board => :project},
27 :project_key => 'project_id',
27 :project_key => 'project_id',
28 :date_column => "#{table_name}.created_on"
28 :date_column => "#{table_name}.created_on"
29 acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"},
29 acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"},
30 :description => :content,
30 :description => :content,
31 :type => Proc.new {|o| o.parent_id.nil? ? 'message' : 'reply'},
31 :type => Proc.new {|o| o.parent_id.nil? ? 'message' : 'reply'},
32 :url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id}.merge(o.parent_id.nil? ? {:id => o.id} :
32 :url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id}.merge(o.parent_id.nil? ? {:id => o.id} :
33 {:id => o.parent_id, :anchor => "message-#{o.id}"})}
33 {:id => o.parent_id, :anchor => "message-#{o.id}"})}
34
34
35 acts_as_activity_provider :find_options => {:include => [{:board => :project}, :author]},
35 acts_as_activity_provider :find_options => {:include => [{:board => :project}, :author]},
36 :author_key => :author_id
36 :author_key => :author_id
37 acts_as_watchable
37 acts_as_watchable
38
38
39 attr_protected :locked, :sticky
39 attr_protected :locked, :sticky
40 validates_presence_of :subject, :content
40 validates_presence_of :board, :subject, :content
41 validates_length_of :subject, :maximum => 255
41 validates_length_of :subject, :maximum => 255
42
42
43 after_create :add_author_as_watcher
43 after_create :add_author_as_watcher
44
44
45 def validate_on_create
45 def validate_on_create
46 # Can not reply to a locked topic
46 # Can not reply to a locked topic
47 errors.add_to_base 'Topic is locked' if root.locked? && self != root
47 errors.add_to_base 'Topic is locked' if root.locked? && self != root
48 end
48 end
49
49
50 def after_create
50 def after_create
51 board.update_attribute(:last_message_id, self.id)
52 board.increment! :messages_count
53 if parent
51 if parent
54 parent.reload.update_attribute(:last_reply_id, self.id)
52 parent.reload.update_attribute(:last_reply_id, self.id)
55 else
53 end
56 board.increment! :topics_count
54 board.reset_counters!
55 end
56
57 def after_update
58 if board_id_changed?
59 Message.update_all("board_id = #{board_id}", ["id = ? OR parent_id = ?", root.id, root.id])
60 Board.reset_counters!(board_id_was)
61 Board.reset_counters!(board_id)
57 end
62 end
58 end
63 end
59
64
60 def after_destroy
65 def after_destroy
61 # The following line is required so that the previous counter
66 board.reset_counters!
62 # updates (due to children removal) are not overwritten
63 board.reload
64 board.decrement! :messages_count
65 board.decrement! :topics_count unless parent
66 end
67 end
67
68
68 def sticky?
69 def sticky?
69 sticky == 1
70 sticky == 1
70 end
71 end
71
72
72 def project
73 def project
73 board.project
74 board.project
74 end
75 end
75
76
76 def editable_by?(usr)
77 def editable_by?(usr)
77 usr && usr.logged? && (usr.allowed_to?(:edit_messages, project) || (self.author == usr && usr.allowed_to?(:edit_own_messages, project)))
78 usr && usr.logged? && (usr.allowed_to?(:edit_messages, project) || (self.author == usr && usr.allowed_to?(:edit_own_messages, project)))
78 end
79 end
79
80
80 def destroyable_by?(usr)
81 def destroyable_by?(usr)
81 usr && usr.logged? && (usr.allowed_to?(:delete_messages, project) || (self.author == usr && usr.allowed_to?(:delete_own_messages, project)))
82 usr && usr.logged? && (usr.allowed_to?(:delete_messages, project) || (self.author == usr && usr.allowed_to?(:delete_own_messages, project)))
82 end
83 end
83
84
84 private
85 private
85
86
86 def add_author_as_watcher
87 def add_author_as_watcher
87 Watcher.create(:watchable => self.root, :user => author)
88 Watcher.create(:watchable => self.root, :user => author)
88 end
89 end
89 end
90 end
@@ -1,21 +1,26
1 <%= error_messages_for 'message' %>
1 <%= error_messages_for 'message' %>
2 <% replying ||= false %>
2 <% replying ||= false %>
3
3
4 <div class="box">
4 <div class="box">
5 <!--[form:message]-->
5 <!--[form:message]-->
6 <p><label><%= l(:field_subject) %></label><br />
6 <p><label><%= l(:field_subject) %></label><br />
7 <%= f.text_field :subject, :size => 120 %>
7 <%= f.text_field :subject, :size => 120 %>
8
8
9 <% if !replying && User.current.allowed_to?(:edit_messages, @project) %>
9 <% if !replying && User.current.allowed_to?(:edit_messages, @project) %>
10 <label><%= f.check_box :sticky %> Sticky</label>
10 <label><%= f.check_box :sticky %> Sticky</label>
11 <label><%= f.check_box :locked %> Locked</label>
11 <label><%= f.check_box :locked %> Locked</label>
12 <% end %>
12 <% end %>
13 </p>
13 </p>
14
14
15 <% if !replying && !@message.new_record? && User.current.allowed_to?(:edit_messages, @project) %>
16 <p><label><%= l(:label_board) %></label><br />
17 <%= f.select :board_id, @project.boards.collect {|b| [b.name, b.id]} %></p>
18 <% end %>
19
15 <p><%= f.text_area :content, :cols => 80, :rows => 15, :class => 'wiki-edit', :id => 'message_content' %></p>
20 <p><%= f.text_area :content, :cols => 80, :rows => 15, :class => 'wiki-edit', :id => 'message_content' %></p>
16 <%= wikitoolbar_for 'message_content' %>
21 <%= wikitoolbar_for 'message_content' %>
17 <!--[eoform:message]-->
22 <!--[eoform:message]-->
18
23
19 <p><%= l(:label_attachment_plural) %><br />
24 <p><%= l(:label_attachment_plural) %><br />
20 <%= render :partial => 'attachments/form' %></p>
25 <%= render :partial => 'attachments/form' %></p>
21 </div>
26 </div>
@@ -1,19 +1,19
1 ---
1 ---
2 boards_001:
2 boards_001:
3 name: Help
3 name: Help
4 project_id: 1
4 project_id: 1
5 topics_count: 2
5 topics_count: 2
6 id: 1
6 id: 1
7 description: Help board
7 description: Help board
8 position: 1
8 position: 1
9 last_message_id: 5
9 last_message_id: 6
10 messages_count: 5
10 messages_count: 6
11 boards_002:
11 boards_002:
12 name: Discussion
12 name: Discussion
13 project_id: 1
13 project_id: 1
14 topics_count: 0
14 topics_count: 0
15 id: 2
15 id: 2
16 description: Discussion board
16 description: Discussion board
17 position: 2
17 position: 2
18 last_message_id:
18 last_message_id:
19 messages_count: 0
19 messages_count: 0
@@ -1,97 +1,131
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
1 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
2
19
3 class MessageTest < Test::Unit::TestCase
20 class MessageTest < Test::Unit::TestCase
4 fixtures :projects, :roles, :members, :boards, :messages, :users, :watchers
21 fixtures :projects, :roles, :members, :boards, :messages, :users, :watchers
5
22
6 def setup
23 def setup
7 @board = Board.find(1)
24 @board = Board.find(1)
8 @user = User.find(1)
25 @user = User.find(1)
9 end
26 end
10
27
11 def test_create
28 def test_create
12 topics_count = @board.topics_count
29 topics_count = @board.topics_count
13 messages_count = @board.messages_count
30 messages_count = @board.messages_count
14
31
15 message = Message.new(:board => @board, :subject => 'Test message', :content => 'Test message content', :author => @user)
32 message = Message.new(:board => @board, :subject => 'Test message', :content => 'Test message content', :author => @user)
16 assert message.save
33 assert message.save
17 @board.reload
34 @board.reload
18 # topics count incremented
35 # topics count incremented
19 assert_equal topics_count+1, @board[:topics_count]
36 assert_equal topics_count+1, @board[:topics_count]
20 # messages count incremented
37 # messages count incremented
21 assert_equal messages_count+1, @board[:messages_count]
38 assert_equal messages_count+1, @board[:messages_count]
22 assert_equal message, @board.last_message
39 assert_equal message, @board.last_message
23 # author should be watching the message
40 # author should be watching the message
24 assert message.watched_by?(@user)
41 assert message.watched_by?(@user)
25 end
42 end
26
43
27 def test_reply
44 def test_reply
28 topics_count = @board.topics_count
45 topics_count = @board.topics_count
29 messages_count = @board.messages_count
46 messages_count = @board.messages_count
30 @message = Message.find(1)
47 @message = Message.find(1)
31 replies_count = @message.replies_count
48 replies_count = @message.replies_count
32
49
33 reply_author = User.find(2)
50 reply_author = User.find(2)
34 reply = Message.new(:board => @board, :subject => 'Test reply', :content => 'Test reply content', :parent => @message, :author => reply_author)
51 reply = Message.new(:board => @board, :subject => 'Test reply', :content => 'Test reply content', :parent => @message, :author => reply_author)
35 assert reply.save
52 assert reply.save
36 @board.reload
53 @board.reload
37 # same topics count
54 # same topics count
38 assert_equal topics_count, @board[:topics_count]
55 assert_equal topics_count, @board[:topics_count]
39 # messages count incremented
56 # messages count incremented
40 assert_equal messages_count+1, @board[:messages_count]
57 assert_equal messages_count+1, @board[:messages_count]
41 assert_equal reply, @board.last_message
58 assert_equal reply, @board.last_message
42 @message.reload
59 @message.reload
43 # replies count incremented
60 # replies count incremented
44 assert_equal replies_count+1, @message[:replies_count]
61 assert_equal replies_count+1, @message[:replies_count]
45 assert_equal reply, @message.last_reply
62 assert_equal reply, @message.last_reply
46 # author should be watching the message
63 # author should be watching the message
47 assert @message.watched_by?(reply_author)
64 assert @message.watched_by?(reply_author)
48 end
65 end
49
66
67 def test_moving_message_should_update_counters
68 @message = Message.find(1)
69 assert_no_difference 'Message.count' do
70 # Previous board
71 assert_difference 'Board.find(1).topics_count', -1 do
72 assert_difference 'Board.find(1).messages_count', -(1 + @message.replies_count) do
73 # New board
74 assert_difference 'Board.find(2).topics_count' do
75 assert_difference 'Board.find(2).messages_count', (1 + @message.replies_count) do
76 @message.update_attributes(:board_id => 2)
77 end
78 end
79 end
80 end
81 end
82 end
83
50 def test_destroy_topic
84 def test_destroy_topic
51 message = Message.find(1)
85 message = Message.find(1)
52 board = message.board
86 board = message.board
53 topics_count, messages_count = board.topics_count, board.messages_count
87 topics_count, messages_count = board.topics_count, board.messages_count
54
88
55 assert_difference('Watcher.count', -1) do
89 assert_difference('Watcher.count', -1) do
56 assert message.destroy
90 assert message.destroy
57 end
91 end
58 board.reload
92 board.reload
59
93
60 # Replies deleted
94 # Replies deleted
61 assert Message.find_all_by_parent_id(1).empty?
95 assert Message.find_all_by_parent_id(1).empty?
62 # Checks counters
96 # Checks counters
63 assert_equal topics_count - 1, board.topics_count
97 assert_equal topics_count - 1, board.topics_count
64 assert_equal messages_count - 3, board.messages_count
98 assert_equal messages_count - 3, board.messages_count
65 # Watchers removed
99 # Watchers removed
66 end
100 end
67
101
68 def test_destroy_reply
102 def test_destroy_reply
69 message = Message.find(5)
103 message = Message.find(5)
70 board = message.board
104 board = message.board
71 topics_count, messages_count = board.topics_count, board.messages_count
105 topics_count, messages_count = board.topics_count, board.messages_count
72 assert message.destroy
106 assert message.destroy
73 board.reload
107 board.reload
74
108
75 # Checks counters
109 # Checks counters
76 assert_equal topics_count, board.topics_count
110 assert_equal topics_count, board.topics_count
77 assert_equal messages_count - 1, board.messages_count
111 assert_equal messages_count - 1, board.messages_count
78 end
112 end
79
113
80 def test_editable_by
114 def test_editable_by
81 message = Message.find(6)
115 message = Message.find(6)
82 author = message.author
116 author = message.author
83 assert message.editable_by?(author)
117 assert message.editable_by?(author)
84
118
85 author.role_for_project(message.project).remove_permission!(:edit_own_messages)
119 author.role_for_project(message.project).remove_permission!(:edit_own_messages)
86 assert !message.reload.editable_by?(author.reload)
120 assert !message.reload.editable_by?(author.reload)
87 end
121 end
88
122
89 def test_destroyable_by
123 def test_destroyable_by
90 message = Message.find(6)
124 message = Message.find(6)
91 author = message.author
125 author = message.author
92 assert message.destroyable_by?(author)
126 assert message.destroyable_by?(author)
93
127
94 author.role_for_project(message.project).remove_permission!(:delete_own_messages)
128 author.role_for_project(message.project).remove_permission!(:delete_own_messages)
95 assert !message.reload.destroyable_by?(author.reload)
129 assert !message.reload.destroyable_by?(author.reload)
96 end
130 end
97 end
131 end
General Comments 0
You need to be logged in to leave comments. Login now