@@ -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 |
e |
|
53 | end | |
56 |
|
|
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: |
|
9 | last_message_id: 6 | |
10 |
messages_count: |
|
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