diff -ruN redmine-0.8.0-orig/app/controllers/projects_controller.rb redmine-0.8.0/app/controllers/projects_controller.rb --- redmine-0.8.0-orig/app/controllers/projects_controller.rb 2008-12-30 13:09:23.000000000 +0300 +++ redmine-0.8.0/app/controllers/projects_controller.rb 2009-01-07 17:17:01.000000000 +0300 @@ -91,9 +91,17 @@ cond = @project.project_condition(Setting.display_subprojects_issues?) Issue.visible_by(User.current) do + issue_cond = cond; + if not User.current.allowed_to?(:view_private_issues, @project) + issue_cond += " AND #{Issue.table_name}.private = false" + end @open_issues_by_tracker = Issue.count(:group => :tracker, :include => [:project, :status, :tracker], - :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false]) + :conditions => ["(#{issue_cond}) AND #{IssueStatus.table_name}.is_closed=?", false]) + @private_issues_by_tracker = Issue.count(:group => :tracker, + :include => [:project, :status, :tracker], + :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false], + :conditions => ["#{Issue.table_name}.private=?",true]) @total_issues_by_tracker = Issue.count(:group => :tracker, :include => [:project, :status, :tracker], :conditions => cond) @@ -240,7 +248,12 @@ @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty? events = @activity.events(@date_from, @date_to) - + + # The private issues should be removed from events + events.each do |event| + events.delete(event) if event.kind_of?(Issue) && !event.visible?(User.current, Project.find(event.project)) + end + respond_to do |format| format.html { @events_by_day = events.group_by(&:event_date) diff -ruN redmine-0.8.0-orig/app/models/issue.rb redmine-0.8.0/app/models/issue.rb --- redmine-0.8.0-orig/app/models/issue.rb 2008-12-30 13:09:23.000000000 +0300 +++ redmine-0.8.0/app/models/issue.rb 2009-01-02 02:33:40.000000000 +0300 @@ -262,6 +262,10 @@ yield end end + + def visible? (usr, project) + private==false || private==true && usr.allowed_to?(:view_private_issues, project) + end def to_s "#{tracker} ##{id}: #{subject}" diff -ruN redmine-0.8.0-orig/app/views/issues/_form.rhtml redmine-0.8.0/app/views/issues/_form.rhtml --- redmine-0.8.0-orig/app/views/issues/_form.rhtml 2008-12-30 13:09:24.000000000 +0300 +++ redmine-0.8.0/app/views/issues/_form.rhtml 2009-01-02 00:08:51.000000000 +0300 @@ -41,6 +41,10 @@
<%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %>
+<% if User.current.allowed_to?(:add_private_issues, @project) %> +<%=f.check_box :private %>
+<% end%> +<%=l(:label_access_denied)%>
+<% end %> diff -ruN redmine-0.8.0-orig/app/views/projects/show.rhtml redmine-0.8.0/app/views/projects/show.rhtml --- redmine-0.8.0-orig/app/views/projects/show.rhtml 2008-12-30 13:09:24.000000000 +0300 +++ redmine-0.8.0/app/views/projects/show.rhtml 2009-01-02 05:25:04.000000000 +0300 @@ -26,6 +26,7 @@ :set_filter => 1, "tracker_id" => tracker.id %>: <%= @open_issues_by_tracker[tracker] || 0 %> <%= lwr(:label_open_issues, @open_issues_by_tracker[tracker] || 0) %> + (<%= @private_issues_by_tracker[tracker] || 0 %> <%= lwr(:label_private_issues, @private_issues_by_tracker[tracker] || 0)%>) <%= l(:label_on) %> <%= @total_issues_by_tracker[tracker] || 0 %> <% end %> diff -ruN redmine-0.8.0-orig/db/migrate/102_add_issues_private_flag.rb redmine-0.8.0/db/migrate/102_add_issues_private_flag.rb --- redmine-0.8.0-orig/db/migrate/102_add_issues_private_flag.rb 1970-01-01 03:00:00.000000000 +0300 +++ redmine-0.8.0/db/migrate/102_add_issues_private_flag.rb 2009-01-01 12:47:54.000000000 +0300 @@ -0,0 +1,9 @@ +class AddIssuesPrivateFlag < ActiveRecord::Migration + def self.up + add_column :issues, :private, :boolean, :default => false, :null => false + end + + def self.down + remove_column :issues, :private + end +end diff -ruN redmine-0.8.0-orig/lang/en.yml redmine-0.8.0/lang/en.yml --- redmine-0.8.0-orig/lang/en.yml 2008-12-30 13:09:26.000000000 +0300 +++ redmine-0.8.0/lang/en.yml 2009-01-02 05:36:32.000000000 +0300 @@ -184,6 +184,7 @@ field_default_value: Default value field_comments_sorting: Display comments field_parent_title: Parent page +field_private: Private issue setting_app_title: Application title setting_app_subtitle: Application subtitle @@ -231,6 +232,8 @@ permission_manage_versions: Manage versions permission_manage_categories: Manage issue categories permission_add_issues: Add issues +permission_add_private_issues: Add private issues +permission_view_private_issues: View private issues permission_edit_issues: Edit issues permission_manage_issue_relations: Manage issue relations permission_add_issue_notes: Add notes @@ -283,6 +286,7 @@ project_module_repository: Repository project_module_boards: Boards +label_access_denied: Access denied label_user: User label_user_plural: Users label_user_new: New user @@ -395,6 +399,8 @@ label_public_projects: Public projects label_open_issues: open label_open_issues_plural: open +label_private_issues: private +label_private_issues_plural: private label_closed_issues: closed label_closed_issues_plural: closed label_total: Total diff -ruN redmine-0.8.0-orig/lang/ru.yml redmine-0.8.0/lang/ru.yml --- redmine-0.8.0-orig/lang/ru.yml 2008-12-30 13:09:26.000000000 +0300 +++ redmine-0.8.0/lang/ru.yml 2009-01-02 05:35:59.000000000 +0300 @@ -179,6 +179,7 @@ field_port: Порт field_possible_values: Возможные значения field_priority: Приоритет +field_private: Конфиденциальная задача field_project: Проект field_redirect_existing_links: Перенаправить существующие ссылки field_regexp: Регулярное выражение @@ -227,6 +228,7 @@ gui_validation_error_plural: %d ошибок label_activity: Активность +label_access_denied: Доступ запрещен label_add_another_file: Добавить ещё один файл label_added_time_by: Добавил(а) %s %s назад label_added: добавлено @@ -430,6 +432,10 @@ label_open_issues_plural5: открыто label_open_issues_plural: открыто label_open_issues: открыт +label_private_issues_plural2: конфиденциальных +label_private_issues_plural5: конфиденциальных +label_private_issues_plural: конфиденциальных +label_private_issues: конфиденциальная label_optional_description: Описание (опционально) label_options: Опции label_overall_activity: Сводная активность @@ -622,6 +628,8 @@ permission_view_documents: Просмотр документов permission_edit_project: Редактирование проектов permission_add_issue_notes: Добавление примечаний +permission_add_private_issues: Добавление конфиденциальных задач +permission_view_private_issues: Просмотр конфиденциальных задач permission_save_queries: Сохранение запросов permission_view_wiki_pages: Просмотр wiki permission_rename_wiki_pages: Переименование страниц wiki diff -ruN redmine-0.8.0-orig/lib/redmine.rb redmine-0.8.0/lib/redmine.rb --- redmine-0.8.0-orig/lib/redmine.rb 2008-12-30 13:09:28.000000000 +0300 +++ redmine-0.8.0/lib/redmine.rb 2009-01-02 00:47:01.000000000 +0300 @@ -35,6 +35,12 @@ :queries => :index, :reports => :issue_report}, :public => true map.permission :add_issues, {:issues => :new} + map.permission :add_private_issues, {:issues => :new} + map.permission :view_private_issues, {:projects => [:changelog, :roadmap], + :issues => [:index, :changes, :show, :context_menu], + :versions => [:show, :status_by], + :queries => :index, + :reports => :issue_report} map.permission :edit_issues, {:issues => [:edit, :reply, :bulk_edit, :destroy_attachment]} map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]} map.permission :add_issue_notes, {:issues => [:edit, :reply]} diff -ruN redmine-0.8.0-orig/test/fixtures/issues.yml redmine-0.8.0/test/fixtures/issues.yml --- redmine-0.8.0-orig/test/fixtures/issues.yml 2008-12-30 13:09:22.000000000 +0300 +++ redmine-0.8.0/test/fixtures/issues.yml 2009-01-07 20:17:43.000000000 +0300 @@ -108,4 +108,19 @@ start_date: <%= 10.days.ago.to_s(:db) %> due_date: <%= Date.today.to_s(:db) %> lock_version: 0 - \ В конце файла нет новой строки +issues_008: + created_on: <%= 5.days.ago.to_date.to_s(:db) %> + project_id: 1 + updated_on: <%= 2.days.ago.to_date.to_s(:db) %> + priority_id: 5 + subject: Private Issue on project 2 + id: 8 + fixed_version_id: + category_id: + description: Priavte Issue on project 2 + tracker_id: 1 + assigned_to_id: + author_id: 2 + status_id: 1 + private: 1 + diff -ruN redmine-0.8.0-orig/test/fixtures/roles.yml redmine-0.8.0/test/fixtures/roles.yml --- redmine-0.8.0-orig/test/fixtures/roles.yml 2008-12-30 13:09:22.000000000 +0300 +++ redmine-0.8.0/test/fixtures/roles.yml 2009-01-06 22:52:31.000000000 +0300 @@ -45,6 +45,8 @@ - :browse_repository - :manage_repository - :view_changesets + - :add_private_issues + - :view_private_issues position: 1 roles_002: @@ -87,6 +89,7 @@ - :manage_files - :browse_repository - :view_changesets + - :view_private_issues position: 2 roles_003: @@ -173,4 +176,4 @@ - :view_changesets position: 5 - \ В конце файла нет новой строки + diff -ruN redmine-0.8.0-orig/test/functional/issues_controller_test.rb redmine-0.8.0/test/functional/issues_controller_test.rb --- redmine-0.8.0-orig/test/functional/issues_controller_test.rb 2008-12-30 13:09:22.000000000 +0300 +++ redmine-0.8.0/test/functional/issues_controller_test.rb 2009-01-07 23:01:30.000000000 +0300 @@ -252,6 +252,49 @@ :content => /Notes/ } } end + def test_show_private_issue_by_manager + @request.session[:user_id] = 2 + get :show, :id => 8 + assert_response :success + assert_tag :input, :attributes => { :name => 'issue[private]'} + assert_tag :td, :attributes => { :class => 'private-issue'} + end + + def test_show_private_issue_by_admin + @request.session[:user_id] = 1 + get :show, :id => 8 + assert_response :success + assert_tag :input, :attributes => { :name => 'issue[private]'} + assert_tag :td, :attributes => { :class => 'private-issue'} + end + + def test_show_private_issue_by_developer + @request.session[:user_id] = 3 + get :show, :id => 8 + assert_response :success + # Developer can't change issue type + assert_no_tag :input, :attributes => { :name => 'issue[private]'} + # Developer can view private issues + assert_tag :td, :attributes => { :class => 'private-issue'} + end + + def test_show_private_issue_by_non_member + @request.session[:user_id] = 4 + get :show, :id => 8 + assert_response :success + assert_no_tag :input, :attributes => { :name => 'issue[private]'} + assert_no_tag :td, :attributes => { :class => 'private-issue'} + assert_tag :p, :attributes => { :class => 'nodata'} + end + + def test_show_private_issue_by_anonymous + get :show, :id => 8 + assert_response :success + assert_no_tag :input, :attributes => { :name => 'issue[private]'} + assert_no_tag :td, :attributes => { :class => 'private-issue'} + assert_tag :p, :attributes => { :class => 'nodata'} + end + def test_get_new @request.session[:user_id] = 2 get :new, :project_id => 1, :tracker_id => 1 diff -ruN redmine-0.8.0-orig/test/functional/projects_controller_test.rb redmine-0.8.0/test/functional/projects_controller_test.rb --- redmine-0.8.0-orig/test/functional/projects_controller_test.rb 2008-12-30 13:09:22.000000000 +0300 +++ redmine-0.8.0/test/functional/projects_controller_test.rb 2009-01-07 23:05:32.000000000 +0300 @@ -202,7 +202,83 @@ } } end + + #private issue are not visible for Anonymous user in global Activity + def test_private_issue_global_activity_for_anonymous + get :activity + assert_response :success + assert_template 'activity' + assert_not_nil assigns(:events_by_day) + + assert_no_tag :tag => "h3", + :content => /#{5.day.ago.to_date.day}/, + :sibling => { :tag => "dl", + :child => { :tag => "dt", + :attributes => { :class => /issue/ }, + :child => { :tag => "a", + :content => /#{Issue.find(8).subject}/, + } + } + } + end + + def test_private_issue_global_activity_for_manager + @request.session[:user_id] = 2 # manager + get :activity + assert_response :success + assert_template 'activity' + assert_not_nil assigns(:events_by_day) + + assert_tag :tag => "h3", + :content => /#{5.day.ago.to_date.day}/, + :sibling => { :tag => "dl", + :child => { :tag => "dt", + :attributes => { :class => /issue/ }, + :child => { :tag => "a", + :content => /#{Issue.find(8).subject}/, + } + } + } + end + def test_private_issue_global_activity_for_developer + @request.session[:user_id] = 3 # developer + get :activity + assert_response :success + assert_template 'activity' + assert_not_nil assigns(:events_by_day) + + assert_tag :tag => "h3", + :content => /#{5.day.ago.to_date.day}/, + :sibling => { :tag => "dl", + :child => { :tag => "dt", + :attributes => { :class => /issue/ }, + :child => { :tag => "a", + :content => /#{Issue.find(8).subject}/, + } + } + } + end + + def test_private_issue_global_activity_for_non_member + @request.session[:user_id] = 4 # does not have any role in project #1 + get :activity + assert_response :success + assert_template 'activity' + assert_not_nil assigns(:events_by_day) + + assert_no_tag :tag => "h3", + :content => /#{5.day.ago.to_date.day}/, + :sibling => { :tag => "dl", + :child => { :tag => "dt", + :attributes => { :class => /issue/ }, + :child => { :tag => "a", + :content => /#{Issue.find(8).subject}/, + } + } + } + end + def test_user_activity get :activity, :user_id => 2 assert_response :success diff -ruN redmine-0.8.0-orig/test/unit/issue_test.rb redmine-0.8.0/test/unit/issue_test.rb --- redmine-0.8.0-orig/test/unit/issue_test.rb 2008-12-30 13:09:22.000000000 +0300 +++ redmine-0.8.0/test/unit/issue_test.rb 2009-01-07 01:12:05.000000000 +0300 @@ -197,4 +197,21 @@ assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue? assert !Issue.new(:due_date => nil).overdue? end + + def test_visible + issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 2, :status_id => 1, :priority => Enumeration.get_values('IPRI').first, :subject => 'test_private_create', :description => 'IssueTest#test_private_create', :estimated_hours => '5:30', :private => true) + assert issue.save + issue.reload + assert_equal true, issue.private + # Test fixtures contain "add_private_issues" and "view_private_issues" + # permissions for Manager role and only "view_private_issues" for Developer. + # User with id #3 in project with id #1 has Developer role + assert_equal true, issue.visible?(User.find(3), Project.find(1)) + # User with id #2 in project with id #1 has Manager role + assert_equal true, issue.visible?(User.find(2), Project.find(1)) + # User with id #6 has Anonymous role + assert_equal false, issue.visible?(User.find(6), Project.find(1)) + # User with id #4 does not have any role in project #1 + assert_equal false, issue.visible?(User.find(4), Project.find(1)) + end end