Index: app/models/message.rb =================================================================== --- app/models/message.rb (revision 5352) +++ app/models/message.rb (working copy) @@ -36,14 +36,19 @@ :author_key => :author_id acts_as_watchable - attr_protected :locked, :sticky + attr_protected :locked, :sticky, :is_private validates_presence_of :board, :subject, :content validates_length_of :subject, :maximum => 255 after_create :add_author_as_watcher def visible?(user=User.current) - !user.nil? && user.allowed_to?(:view_messages, project) + # Check private conditions first + if is_private? + !user.nil? && user.allowed_to?(:view_private_messages, project) + else + !user.nil? && user.allowed_to?(:view_messages, project) + end end def validate_on_create @@ -78,10 +83,19 @@ sticky == 1 end + def is_private=(arg) + write_attribute :is_private , (arg == true || arg.to_s == '1' ? 1 : 0) + end + + def is_private? + is_private == 1 + end + def project board.project end + # @TODO probably should check private message perms here? def editable_by?(usr) usr && usr.logged? && (usr.allowed_to?(:edit_messages, project) || (self.author == usr && usr.allowed_to?(:edit_own_messages, project))) end Index: app/models/board.rb =================================================================== --- app/models/board.rb (revision 5352) +++ app/models/board.rb (working copy) @@ -19,22 +19,53 @@ belongs_to :project has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_on DESC" has_many :messages, :dependent => :destroy, :order => "#{Message.table_name}.created_on DESC" - belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id + + # Should messages be restricted by privacy settings? + conditions = "#{Message.table_name}.is_private IN (0, " + (User.current.allowed_to?(:view_private_messages, @project) ? "1" : "0") + ")" + has_one :last_message, + :class_name => 'Message', + :conditions => "#{Message.table_name}.parent_id IS NULL AND #{conditions}", + :order => "#{Message.table_name}.created_on DESC" + acts_as_list :scope => :project_id acts_as_watchable - + validates_presence_of :name, :description validates_length_of :name, :maximum => 30 validates_length_of :description, :maximum => 255 def visible?(user=User.current) + # We do not check for private messages here because view_private_messages + # should be a sub-permission of view_messages !user.nil? && user.allowed_to?(:view_messages, project) end def to_s name end - + + # Ensures the message count reflects the topics the user has rights to see + def permissioned_topics_count! + if User.current.allowed_to?(:view_private_messages, project) + conditions = "#{Message.table_name}.is_private IN (0, 1)" + else + conditions = "#{Message.table_name}.is_private = 0"; + end + permissioned_topics = messages.find :all, :conditions => "board_id=#{id} AND #{Message.table_name}.parent_id IS NULL AND #{conditions}" + return permissioned_topics.count + end + + # Ensures the message count reflects the messages the user has rights to see + def permissioned_messages_count! + if User.current.allowed_to?(:view_private_messages, project) + conditions = "#{Message.table_name}.is_private IN (0, 1)" + else + conditions = "#{Message.table_name}.is_private = 0"; + end + permissioned_messages = messages.find :all, :conditions => "board_id=#{id} AND #{conditions}" + return permissioned_messages.count + end + def reset_counters! self.class.reset_counters!(id) end @@ -47,4 +78,4 @@ " last_message_id = (SELECT MAX(id) FROM #{Message.table_name} WHERE board_id=#{board_id})", ["id = ?", board_id]) end -end +end \ No newline at end of file Index: app/controllers/messages_controller.rb =================================================================== --- app/controllers/messages_controller.rb (revision 5352) +++ app/controllers/messages_controller.rb (working copy) @@ -33,13 +33,17 @@ # Show a topic and its replies def show + return render_403 if !@message.visible? + page = params[:page] # Find the page of the requested reply if params[:r] && page.nil? offset = @topic.children.count(:conditions => ["#{Message.table_name}.id < ?", params[:r].to_i]) page = 1 + offset / REPLIES_PER_PAGE end - + + # We are not concerned about privacy here because the reply of a private + # message will be private as well @reply_count = @topic.children.count @reply_pages = Paginator.new self, @reply_count, REPLIES_PER_PAGE, page @replies = @topic.children.find(:all, :include => [:author, :attachments, {:board => :project}], @@ -59,6 +63,7 @@ if params[:message] && User.current.allowed_to?(:edit_messages, @project) @message.locked = params[:message]['locked'] @message.sticky = params[:message]['sticky'] + @message.is_private = params[:message]['is_private'] end if request.post? && @message.save call_hook(:controller_messages_new_after_save, { :params => params, :message => @message}) @@ -73,6 +78,8 @@ @reply = Message.new(params[:reply]) @reply.author = User.current @reply.board = @board + # Inherit privacy settings from original post + @reply.is_private = @topic.is_private @topic.children << @reply if !@reply.new_record? call_hook(:controller_messages_reply_after_save, { :params => params, :message => @reply}) @@ -88,6 +95,7 @@ if params[:message] @message.locked = params[:message]['locked'] @message.sticky = params[:message]['sticky'] + @message.is_private = params[:message]['is_private'] end if request.post? && @message.update_attributes(params[:message]) attachments = Attachment.attach_files(@message, params[:attachments]) Index: app/controllers/boards_controller.rb =================================================================== --- app/controllers/boards_controller.rb (revision 5352) +++ app/controllers/boards_controller.rb (working copy) @@ -37,6 +37,14 @@ end def show + + # Can the user see private messages? + if User.current.allowed_to?(:view_private_messages, @board.project) + conditions = "#{Message.table_name}.is_private IN (0, 1)" + else + conditions = "#{Message.table_name}.is_private = 0"; + end + respond_to do |format| format.html { sort_init 'updated_on', 'desc' @@ -49,7 +57,8 @@ @topics = @board.topics.find :all, :order => ["#{Message.table_name}.sticky DESC", sort_clause].compact.join(', '), :include => [:author, {:last_reply => :author}], :limit => @topic_pages.items_per_page, - :offset => @topic_pages.current.offset + :offset => @topic_pages.current.offset, + :conditions => [conditions] @message = Message.new render :action => 'show', :layout => !request.xhr? } Index: app/views/messages/show.rhtml =================================================================== --- app/views/messages/show.rhtml (revision 5352) +++ app/views/messages/show.rhtml (working copy) @@ -8,7 +8,9 @@ <%= link_to(l(:button_delete), {:action => 'destroy', :id => @topic}, :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') if @message.destroyable_by?(User.current) %> -

<%= avatar(@topic.author, :size => "24") %><%=h @topic.subject %>

+

<%= avatar(@topic.author, :size => "24") %><%=h @topic.subject %> + <% if @message.is_private? %><%= l(:text_message_is_private) %><% end %> +

<%= authoring @topic.created_on, @topic.author %>

Index: app/views/messages/_form.rhtml =================================================================== --- app/views/messages/_form.rhtml (revision 5352) +++ app/views/messages/_form.rhtml (working copy) @@ -9,6 +9,7 @@ <% if !replying && User.current.allowed_to?(:edit_messages, @project) %> + <% end %>

Index: app/views/boards/show.rhtml =================================================================== --- app/views/boards/show.rhtml (revision 5352) +++ app/views/boards/show.rhtml (working copy) @@ -42,7 +42,9 @@ <% @topics.each do |topic| %> - <%= link_to h(topic.subject), { :controller => 'messages', :action => 'show', :board_id => @board, :id => topic } %> + <%= link_to h(topic.subject), { :controller => 'messages', :action => 'show', :board_id => @board, :id => topic } %> + <% if topic.is_private? %><%= l(:label_board_message_is_private) %><% end %> + <%= topic.author %> <%= format_time(topic.created_on) %> <%= topic.replies_count %> Index: app/views/boards/index.rhtml =================================================================== --- app/views/boards/index.rhtml (revision 5352) +++ app/views/boards/index.rhtml (working copy) @@ -14,14 +14,14 @@ <%= link_to h(board.name), {:action => 'show', :id => board}, :class => "board" %>
<%=h board.description %> - <%= board.topics_count %> - <%= board.messages_count %> + <%= board.permissioned_topics_count! %> + <%= board.permissioned_messages_count! %> <% if board.last_message %> <%= authoring board.last_message.created_on, board.last_message.author %>
- <%= link_to_message board.last_message %> - <% end %> + <%= link_to_message board.last_message %> + <% end %>
Index: db/migrate/20110405_add_message_private.rb =================================================================== --- db/migrate/20110405_add_message_private.rb (revision 0) +++ db/migrate/20110405_add_message_private.rb (revision 0) @@ -0,0 +1,9 @@ +class AddMessagePrivate < ActiveRecord::Migration + def self.up + add_column :messages, :is_private, :integer, :default => 0 + end + + def self.down + remove_column :messages, :is_private + end +end Index: config/locales/en.yml =================================================================== --- config/locales/en.yml (revision 5352) +++ config/locales/en.yml (working copy) @@ -793,7 +793,9 @@ label_project_copy_notifications: Send email notifications during the project copy label_principal_search: "Search for user or group:" label_user_search: "Search for user:" - + label_message_private: "Private post" + label_board_message_is_private: "(Private post)" + button_login: Login button_submit: Submit button_save: Save @@ -908,6 +910,7 @@ text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?" text_zoom_in: Zoom in text_zoom_out: Zoom out + text_message_is_private: "(Private message)" default_role_manager: Manager default_role_developer: Developer @@ -935,4 +938,3 @@ enumeration_doc_categories: Document categories enumeration_activities: Activities (time tracking) enumeration_system_activity: System Activity - Index: lib/redmine.rb =================================================================== --- lib/redmine.rb (revision 5352) +++ lib/redmine.rb (working copy) @@ -129,6 +129,7 @@ map.project_module :boards 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 :view_private_messages, {:boards => [:index, :show], :messages => [:show]}, :public => false map.permission :add_messages, {:messages => [:new, :reply, :quote]} map.permission :edit_messages, {:messages => :edit}, :require => :member map.permission :edit_own_messages, {:messages => :edit}, :require => :loggedin