diff --git a/app/controllers/boards_controller.rb b/app/controllers/boards_controller.rb
index 3a8b021..2007923 100644
--- a/app/controllers/boards_controller.rb
+++ b/app/controllers/boards_controller.rb
@@ -41,7 +41,7 @@ class BoardsController < ApplicationController
@topic_count = @board.topics.count
@topic_pages = Paginator.new self, @topic_count, 25, params['page']
- @topics = @board.topics.find :all, :order => sort_clause,
+ @topics = @board.topics.find :all, :order => "#{Message.table_name}.sticky DESC, #{sort_clause}",
:include => [:author, {:last_reply => :author}],
:limit => @topic_pages.items_per_page,
:offset => @topic_pages.current.offset
diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb
index 9352c4a..46c9ada 100644
--- a/app/controllers/messages_controller.rb
+++ b/app/controllers/messages_controller.rb
@@ -17,22 +17,30 @@
class MessagesController < ApplicationController
layout 'base'
- before_filter :find_project, :authorize
+ before_filter :find_board, :only => :new
+ before_filter :find_message, :except => :new
+ before_filter :authorize
verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show }
helper :attachments
include AttachmentsHelper
+ # Show a topic and its replies
def show
@reply = Message.new(:subject => "RE: #{@message.subject}")
render :action => "show", :layout => false if request.xhr?
end
+ # Create a new topic
def new
@message = Message.new(params[:message])
@message.author = User.current
- @message.board = @board
+ @message.board = @board
+ if params[:message] && User.current.allowed_to?(:edit_messages, @project)
+ @message.locked = params[:message]['locked']
+ @message.sticky = params[:message]['sticky']
+ end
if request.post? && @message.save
params[:attachments].each { |file|
Attachment.create(:container => @message, :file => file, :author => User.current) if file.size > 0
@@ -41,24 +49,55 @@ class MessagesController < ApplicationController
end
end
+ # Reply to a topic
def reply
@reply = Message.new(params[:reply])
@reply.author = User.current
@reply.board = @board
- @message.children << @reply
+ @topic.children << @reply
if !@reply.new_record?
params[:attachments].each { |file|
Attachment.create(:container => @reply, :file => file, :author => User.current) if file.size > 0
} if params[:attachments] and params[:attachments].is_a? Array
end
- redirect_to :action => 'show', :id => @message
+ redirect_to :action => 'show', :id => @topic
+ end
+
+ # Edit a message
+ def edit
+ if params[:message] && User.current.allowed_to?(:edit_messages, @project)
+ @message.locked = params[:message]['locked']
+ @message.sticky = params[:message]['sticky']
+ end
+ if request.post? && @message.update_attributes(params[:message])
+ params[:attachments].each { |file|
+ Attachment.create(:container => @message, :file => file, :author => User.current) if file.size > 0
+ } if params[:attachments] and params[:attachments].is_a? Array
+ flash[:notice] = l(:notice_successful_update)
+ redirect_to :action => 'show', :id => @topic
+ end
+ end
+
+ # Delete a messages
+ def destroy
+ @message.destroy
+ redirect_to @message.parent.nil? ?
+ { :controller => 'boards', :action => 'show', :project_id => @project, :id => @board } :
+ { :action => 'show', :id => @message.parent }
end
private
- def find_project
+ def find_message
+ find_board
+ @message = @board.messages.find(params[:id], :include => :parent)
+ @topic = @message.root
+ rescue ActiveRecord::RecordNotFound
+ render_404
+ end
+
+ def find_board
@board = Board.find(params[:board_id], :include => :project)
@project = @board.project
- @message = @board.topics.find(params[:id]) if params[:id]
rescue ActiveRecord::RecordNotFound
render_404
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 9c8e9c6..f4746c6 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -34,7 +34,7 @@ module ApplicationHelper
# Display a link to user's account page
def link_to_user(user)
- link_to user.name, :controller => 'account', :action => 'show', :id => user
+ user ? link_to(user, :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
end
def link_to_issue(issue)
@@ -92,7 +92,7 @@ module ApplicationHelper
def authoring(created, author)
time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created))
- l(:label_added_time_by, author.name, time_tag)
+ l(:label_added_time_by, author || 'Anonymous', time_tag)
end
def day_name(day)
diff --git a/app/models/message.rb b/app/models/message.rb
index 909c06a..038665c 100644
--- a/app/models/message.rb
+++ b/app/models/message.rb
@@ -30,9 +30,15 @@ class Message < ActiveRecord::Base
:description => :content,
:url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id, :id => o.id}}
+ attr_protected :locked, :sticky
validates_presence_of :subject, :content
validates_length_of :subject, :maximum => 255
+ def validate_on_create
+ # Can not reply to a locked topic
+ errors.add_to_base 'Topic is locked' if root.locked?
+ end
+
def after_create
board.update_attribute(:last_message_id, self.id)
board.increment! :messages_count
@@ -43,6 +49,18 @@ class Message < ActiveRecord::Base
end
end
+ def after_destroy
+ # The following line is required so that the previous counter
+ # updates (due to children removal) are not overwritten
+ board.reload
+ board.decrement! :messages_count
+ board.decrement! :topics_count unless parent
+ end
+
+ def sticky?
+ sticky == 1
+ end
+
def project
board.project
end
diff --git a/app/views/boards/index.rhtml b/app/views/boards/index.rhtml
index 3291d01..cd4e85e 100644
--- a/app/views/boards/index.rhtml
+++ b/app/views/boards/index.rhtml
@@ -19,7 +19,7 @@
<% if board.last_message %>
- <%= board.last_message.author.name %>, <%= format_time(board.last_message.created_on) %>
+ <%= authoring board.last_message.created_on, board.last_message.author %>
<%= link_to_message board.last_message %>
<% end %>
diff --git a/app/views/boards/show.rhtml b/app/views/boards/show.rhtml
index 0af89fd..8bcf960 100644
--- a/app/views/boards/show.rhtml
+++ b/app/views/boards/show.rhtml
@@ -18,7 +18,7 @@
<%=h @board.name %>
<% if @topics.any? %>
-
+
<%= l(:field_subject) %> |
<%= l(:field_author) %> |
@@ -28,18 +28,16 @@
<% @topics.each do |topic| %>
-
- <%= link_to h(topic.subject), :controller => 'messages', :action => 'show', :board_id => @board, :id => topic %> |
- <%= link_to_user topic.author %> |
- <%= format_time(topic.created_on) %> |
- <%= topic.replies_count %> |
-
-
+
+ <%= link_to h(topic.subject), { :controller => 'messages', :action => 'show', :board_id => @board, :id => topic }, :class => 'icon' %> |
+ <%= topic.author %> |
+ <%= format_time(topic.created_on) %> |
+ <%= topic.replies_count %> |
+
<% if topic.last_reply %>
- <%= topic.last_reply.author.name %>, <%= format_time(topic.last_reply.created_on) %>
+ <%= authoring topic.last_reply.created_on, topic.last_reply.author %>
<%= link_to_message topic.last_reply %>
<% end %>
-
|
<% end %>
diff --git a/app/views/messages/_form.rhtml b/app/views/messages/_form.rhtml
index 25d88cd..c2f7fb5 100644
--- a/app/views/messages/_form.rhtml
+++ b/app/views/messages/_form.rhtml
@@ -3,7 +3,13 @@
-<%= f.text_field :subject, :required => true, :size => 120 %>
+<%= f.text_field :subject, :required => true, :size => 120 %>
+
+<% if User.current.allowed_to?(:edit_messages, @project) %>
+
+
+<% end %>
+
<%= f.text_area :content, :required => true, :cols => 80, :rows => 15, :class => 'wiki-edit', :id => 'message_content' %>
<%= wikitoolbar_for 'message_content' %>
diff --git a/app/views/messages/edit.rhtml b/app/views/messages/edit.rhtml
new file mode 100644
index 0000000..808b6ea
--- /dev/null
+++ b/app/views/messages/edit.rhtml
@@ -0,0 +1,6 @@
+ <%= link_to h(@board.name), :controller => 'boards', :action => 'show', :project_id => @project, :id => @board %> » <%=h @message.subject %>
+
+<% form_for :message, @message, :url => {:action => 'edit'}, :html => {:multipart => true} do |f| %>
+ <%= render :partial => 'form', :locals => {:f => f} %>
+ <%= submit_tag l(:button_save) %>
+<% end %>
diff --git a/app/views/messages/show.rhtml b/app/views/messages/show.rhtml
index e39c09d..bb7e2b7 100644
--- a/app/views/messages/show.rhtml
+++ b/app/views/messages/show.rhtml
@@ -1,28 +1,37 @@
- <%= link_to h(@board.name), :controller => 'boards', :action => 'show', :project_id => @project, :id => @board %> » <%=h @message.subject %>
+
+ <%= link_to_if_authorized l(:button_edit), {:action => 'edit', :id => @topic}, :class => 'icon icon-edit' %>
+ <%= link_to_if_authorized l(:button_delete), {:action => 'destroy', :id => @topic}, :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon icon-del' %>
+
+
+ <%= link_to h(@board.name), :controller => 'boards', :action => 'show', :project_id => @project, :id => @board %> » <%=h @topic.subject %>
- <%= authoring @message.created_on, @message.author %>
+ <%= authoring @topic.created_on, @topic.author %>
-<%= textilizable(@message.content, :attachments => @message.attachments) %>
+<%= textilizable(@topic.content, :attachments => @topic.attachments) %>
-<%= link_to_attachments @message.attachments, :no_author => true %>
+<%= link_to_attachments @topic.attachments, :no_author => true %>
-
-<% @message.children.each do |message| %>
+<% @topic.children.each do |message| %>
">
+
+ <%= link_to_if_authorized l(:button_edit), {:action => 'edit', :id => message}, :class => 'icon icon-edit' %>
+ <%= link_to_if_authorized l(:button_delete), {:action => 'destroy', :id => message}, :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon icon-del' %>
+
+
<%=h message.subject %> - <%= authoring message.created_on, message.author %>
<%= textilizable message.content %>
<%= link_to_attachments message.attachments, :no_author => true %>
+
<% end %>
-
-<% if authorize_for('messages', 'reply') %>
+<% if !@topic.locked? && authorize_for('messages', 'reply') %>
<%= toggle_link l(:button_reply), "reply", :focus => 'message_content' %>
-<% form_for :reply, @reply, :url => {:action => 'reply', :id => @message}, :html => {:multipart => true} do |f| %>
+<% form_for :reply, @reply, :url => {:action => 'reply', :id => @topic}, :html => {:multipart => true} do |f| %>
<%= render :partial => 'form', :locals => {:f => f} %>
<%= submit_tag l(:button_submit) %>
<% end %>
diff --git a/db/migrate/082_add_messages_locked.rb b/db/migrate/082_add_messages_locked.rb
new file mode 100644
index 0000000..20a1725
--- /dev/null
+++ b/db/migrate/082_add_messages_locked.rb
@@ -0,0 +1,9 @@
+class AddMessagesLocked < ActiveRecord::Migration
+ def self.up
+ add_column :messages, :locked, :boolean, :default => false
+ end
+
+ def self.down
+ remove_column :messages, :locked
+ end
+end
diff --git a/db/migrate/083_add_messages_sticky.rb b/db/migrate/083_add_messages_sticky.rb
new file mode 100644
index 0000000..8fd5d2c
--- /dev/null
+++ b/db/migrate/083_add_messages_sticky.rb
@@ -0,0 +1,9 @@
+class AddMessagesSticky < ActiveRecord::Migration
+ def self.up
+ add_column :messages, :sticky, :integer, :default => 0
+ end
+
+ def self.down
+ remove_column :messages, :sticky
+ end
+end
diff --git a/lib/redmine.rb b/lib/redmine.rb
index 8a79b26..ffc1cc2 100644
--- a/lib/redmine.rb
+++ b/lib/redmine.rb
@@ -84,6 +84,8 @@ Redmine::AccessControl.map do |map|
map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
map.permission :add_messages, {:messages => [:new, :reply]}
+ map.permission :edit_messages, {:messages => :edit}, :require => :member
+ map.permission :delete_messages, {:messages => :destroy}, :require => :member
end
end
diff --git a/public/images/sticky.png b/public/images/sticky.png
new file mode 100644
index 0000000000000000000000000000000000000000..d32ee63a471527025cec4f060ef6372ae9ffd82c
GIT binary patch
literal 461
zc%17D@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbL!UWsc&iE~kEVo7Fxo Qok%>ZHei@K9FfdmzG`2D{wK6hO&^9nMFz7zqdLF2gy~NYkmHj%m1dky1o95H2
zfI^%F9+AZi4BSE>%y{W;-5;PJOS+@4BLl<6e(pbstU!KsfKQ0)e+Gv4@1MVazxVz7
zt?%C-`~Lm=^XF^7e_#9lJrFH@|9;N%=S%g~%k$?wYqu8v|3Byd|0&_skMx;XeD{swk
zmkBIQ3(gq&o>Z8_**D|Nlt-F;%2lobk2mx`OmNFOe&L-DhsW!^ug!TLY_~lxyA9(fA?qw4<#eF<({&WTBi$CoX+<-1)@O1TaS?83{1OWXM
B+a~}3
literal 0
Hc$@ 1
+ assert_response :success
+ assert_template 'index'
+ assert_not_nil assigns(:boards)
+ assert_not_nil assigns(:project)
+ end
+
+ def test_show
+ get :show, :project_id => 1, :id => 1
+ assert_response :success
+ assert_template 'show'
+ assert_not_nil assigns(:board)
+ assert_not_nil assigns(:project)
+ assert_not_nil assigns(:topics)
+ end
+end
diff --git a/test/functional/messages_controller_test.rb b/test/functional/messages_controller_test.rb
new file mode 100644
index 0000000..25fc136
--- /dev/null
+++ b/test/functional/messages_controller_test.rb
@@ -0,0 +1,49 @@
+# redMine - project management software
+# Copyright (C) 2006-2007 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+require File.dirname(__FILE__) + '/../test_helper'
+require 'messages_controller'
+
+# Re-raise errors caught by the controller.
+class MessagesController; def rescue_action(e) raise e end; end
+
+class MessagesControllerTest < Test::Unit::TestCase
+ fixtures :projects, :users, :members, :roles, :boards, :messages, :enabled_modules
+
+ def setup
+ @controller = MessagesController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ User.current = nil
+ end
+
+ def test_show
+ get :show, :board_id => 1, :id => 1
+ assert_response :success
+ assert_template 'show'
+ assert_not_nil assigns(:board)
+ assert_not_nil assigns(:project)
+ assert_not_nil assigns(:topic)
+ end
+
+ def test_reply
+ @request.session[:user_id] = 2
+ post :reply, :board_id => 1, :id => 1, :reply => { :content => 'This is a test reply', :subject => 'Test reply' }
+ assert_redirected_to 'messages/show'
+ assert Message.find_by_subject('Test reply')
+ end
+end
diff --git a/test/unit/message_test.rb b/test/unit/message_test.rb
index 6d8458b..82ed3fe 100644
--- a/test/unit/message_test.rb
+++ b/test/unit/message_test.rb
@@ -41,4 +41,30 @@ class MessageTest < Test::Unit::TestCase
assert_equal replies_count+1, @message[:replies_count]
assert_equal reply, @message.last_reply
end
+
+ def test_destroy_topic
+ message = Message.find(1)
+ board = message.board
+ topics_count, messages_count = board.topics_count, board.messages_count
+ assert message.destroy
+ board.reload
+
+ # Replies deleted
+ assert Message.find_all_by_parent_id(1).empty?
+ # Checks counters
+ assert_equal topics_count - 1, board.topics_count
+ assert_equal messages_count - 3, board.messages_count
+ end
+
+ def test_destroy_reply
+ message = Message.find(5)
+ board = message.board
+ topics_count, messages_count = board.topics_count, board.messages_count
+ assert message.destroy
+ board.reload
+
+ # Checks counters
+ assert_equal topics_count, board.topics_count
+ assert_equal messages_count - 1, board.messages_count
+ end
end
| |