diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index de51676..76bb5d3 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -19,6 +19,7 @@ class IssuesController < ApplicationController menu_item :new_issue, :only => :new before_filter :find_issue, :only => [:show, :edit, :reply] + before_filter :check_issue_perms, :only => [:show, :edit] before_filter :find_issues, :only => [:bulk_edit, :move, :destroy] before_filter :find_project, :only => [:new, :update_form, :preview] before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu] @@ -63,6 +64,7 @@ class IssuesController < ApplicationController :conditions => @query.statement, :limit => limit, :offset => @issue_pages.current.offset + @issues.map!{ |issue| issue_without_hidden_fields(issue) } # don't show forbidden fields respond_to do |format| format.html { if @query.grouped? @@ -113,6 +115,8 @@ class IssuesController < ApplicationController @changesets.reverse! if User.current.wants_comments_in_reverse_order? @allowed_statuses = @issue.new_statuses_allowed_to(User.current) @edit_allowed = User.current.allowed_to?(:edit_issues, @project) + @issue = issue_without_hidden_fields(@issue) + @journals = journals_without_hidden_entries(@journals) @priorities = IssuePriority.all @time_entry = TimeEntry.new respond_to do |format| @@ -355,6 +359,9 @@ class IssuesController < ApplicationController events += Version.find(:all, :include => :project, :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to]) + + events += @project.related_versions.select { |v| ((v.project.id != @project.id) && (@gantt.date_from..@gantt.date_to).include?(v.effective_date)) } + @gantt.events = events end @@ -387,7 +394,9 @@ class IssuesController < ApplicationController ) events += Version.find(:all, :include => :project, :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt]) - + + events += @project.related_versions.select { |v| ((v.project.id != @project.id) && (@calendar.startdt..@calendar.enddt).include?(v.effective_date)) } + @calendar.events = events end @@ -471,6 +480,55 @@ private render_404 end + def issue_fixed_version_allowed?(issue) + ((not issue.fixed_version) || + (User.current.allowed_to?(:view_issues, + issue.fixed_version.project))) + end + + def check_issue_perms + @forbidden_fixed_version = (not issue_fixed_version_allowed?(@issue)) + end + + ## Filter journals to remove non allowed entries. We want here + ## to handle the _specific case_ of a user 'Joe' browsing 'Project + ## B' issues. 'Project B' is a subproject of 'Project A' and + ## inherits its versions. Joe can't see 'Project A, but issue + ## fixed_versions has been set at some point to a version belonging + ## to 'Project A', by an other member of 'Project B'. Joe should not + ## see this sensitive information. + def journals_without_hidden_entries(journals) + p = @project + forbidden_parents = [] + while p = p.parent do + forbidden_parents.push p.id unless User.current.allowed_to?(:view_issues, p) + end + + unless (journals.empty? || forbidden_parents.empty?) + journals = journals.dup + journals.delete_if do |j| + j.details.map{ |d| (d.property == 'attr' && + d.prop_key == 'fixed_version_id' && + ( + (d.old_value && forbidden_parents.include?(Version.find(d.old_value.to_i).project_id)) || + (d.value && forbidden_parents.include?(Version.find(d.value.to_i).project_id)) + ) + ) }.include?(true) + end + end + journals + end + + ## Filter issue to remove non allowed fields. For more information, + ## see documentation of journals_without_hidden_entries + def issue_without_hidden_fields(issue) + unless issue_fixed_version_allowed?(issue) + issue = issue.dup + issue.fixed_version = nil + end + issue + end + # Retrieve query from session or build a new query def retrieve_query if !params[:query_id].blank? diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index f3280cc..fafa3b9 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -221,7 +221,7 @@ class ProjectsController < ApplicationController def add_file if request.post? - container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id])) + container = (params[:version_id].blank? ? @project : @project.related_versions.find { |v| v.id.to_s == params[:version_id] }) attachments = attach_files(container, params[:attachments]) if !attachments.empty? && Setting.notified_events.include?('file_added') Mailer.deliver_attachments_added(attachments) @@ -229,7 +229,7 @@ class ProjectsController < ApplicationController redirect_to :controller => 'projects', :action => 'list_files', :id => @project return end - @versions = @project.versions.sort + @versions = @project.related_versions.sort end def list_files @@ -240,7 +240,7 @@ class ProjectsController < ApplicationController 'downloads' => "#{Attachment.table_name}.downloads" @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)] - @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse + @containers += Version.find(:all, :include => :attachments, :order => sort_clause, :conditions => ['versions.id IN (?)', @project.related_versions.map {|v| v.id}]).sort.reverse render :layout => !request.xhr? end @@ -248,14 +248,13 @@ class ProjectsController < ApplicationController def changelog @trackers = @project.trackers.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position') retrieve_selected_tracker_ids(@trackers) - @versions = @project.versions.sort + @versions = @project.related_versions.sort end def roadmap @trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true]) retrieve_selected_tracker_ids(@trackers) - @versions = @project.versions.sort - @versions = @versions.select {|v| !v.completed? } unless params[:completed] + @versions = retrieve_related_versions end def activity @@ -316,6 +315,12 @@ private render_404 end + def retrieve_related_versions + related_versions=@project.related_versions + # should this test be moved to projects_helper.rb : version_visible_issues ? + params[:completed] ? related_versions : related_versions.select {|v| !v.completed? } + end + def retrieve_selected_tracker_ids(selectable_trackers) if ids = params[:tracker_ids] @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s } diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index d192a19..a2ce58c 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -31,7 +31,7 @@ class ReportsController < ApplicationController render :template => "reports/issue_report_details" when "version" @field = "fixed_version_id" - @rows = @project.versions.sort + @rows = @project.related_versions.sort @data = issues_by_version @report_title = l(:field_version) render :template => "reports/issue_report_details" @@ -67,7 +67,7 @@ class ReportsController < ApplicationController render :template => "reports/issue_report_details" else @trackers = @project.trackers - @versions = @project.versions.sort + @versions = @project.related_versions.sort @priorities = IssuePriority.all @categories = @project.issue_categories @assignees = @project.members.collect { |m| m.user } diff --git a/app/controllers/versions_controller.rb b/app/controllers/versions_controller.rb index c269432..127e6ef 100644 --- a/app/controllers/versions_controller.rb +++ b/app/controllers/versions_controller.rb @@ -19,7 +19,11 @@ class VersionsController < ApplicationController menu_item :roadmap before_filter :find_project, :authorize + helper :projects + include ProjectsHelper + def show + @issues = version_visible_issues(@version,@project,@project.trackers.collect { |x| x.id.to_s }) end def edit diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 912450c..bfb2293 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -33,6 +33,43 @@ module ProjectsHelper ] tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)} end + + ## return an array of the issues attached to version that a user should see + ## As a convenience, allow limitation by trackers with Array of tracker_ids + def version_visible_issues(version,project,tracker_ids=[],user=User.current) + # Returns void when user can not see issues or if tracker_ids is void + return [] if ((not user.allowed_to?(:view_issues, + version.project)) or + tracker_ids.empty?) + # user can see project issues, carry on, retrieve them ... + issues = version. + fixed_issues.find(:all, + :include => [:status, :tracker], + :conditions => ["tracker_id in (#{tracker_ids.join(',')})"], + :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id") + # Only keep issues from self, parent or children, + related_projects = project.self_and_descendants.map { |t_item| t_item.id } + + # do we need parents at all? + p = project + while p = p.parent do + related_projects.push p.id + end + related_projects = related_projects.select{|x| user.allowed_to?(:view_issues, Project.find(x))} + + # now only keep related issues. (issues from brother projects are discarded) + issues.select {|x| related_projects.include?(x.project.id) } + end + + def grouped_versions_for_select(versions_list) + return [] if versions_list == nil + + grouped_structure = [] + versions_list.each do |prj, ver| + grouped_structure.push [prj.name, ver.collect { |v| [v.name, v.id] }] + end + grouped_structure + end def parent_project_select_tag(project) options = '' + project_tree_options_for_select(project.possible_parents, :selected => project.parent) diff --git a/app/models/project.rb b/app/models/project.rb index 8799e3b..1ebf97a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -274,6 +274,35 @@ class Project < ActiveRecord::Base members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user.mail} end + ## Return versions attached to self, and to parents if relevant. + ## Sorting is done in the SQL request to take advantage of cache + def related_versions(user=User.current) + p = self + parent_ids = [self.id] + while p = p.parent do + parent_ids.push p.id if user.allowed_to?(:view_issues, p) + end + + Version.find(:all, + :conditions => {:project_id => parent_ids}, + :order => "effective_date ASC, name ASC ") + end + + def grouped_versions(version_list=nil, user=User.current) + version_list ||= related_versions(user) + + p, i = self, 0 + parent_nesting = {} + parent_nesting[p.id] = i + + while p = p.parent do + i += 1 + parent_nesting[p.id] = i + end + + version_list.group_by(&:project).sort_by { |item| parent_nesting[item.first.id] } + end + # Returns an array of all custom fields enabled for project issues # (explictly associated custom fields and custom fields enabled for all projects) def all_issue_custom_fields diff --git a/app/models/query.rb b/app/models/query.rb index 7ce95f0..174a22d 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -89,6 +89,7 @@ class Query < ActiveRecord::Base @@operators_by_filter_type = { :list => [ "=", "!" ], :list_status => [ "o", "=", "!", "c", "*" ], :list_optional => [ "=", "!", "!*", "*" ], + :list_grouped => [ "=", "!", "!*", "*" ], :list_subprojects => [ "*", "!*", "=" ], :date => [ "t+", "t+", "t", "w", ">t-", " [ ">t-", " {:operator => "o", :values => [""]} } @@ -181,8 +184,10 @@ class Query < ActiveRecord::Base unless @project.issue_categories.empty? @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } } end - unless @project.versions.empty? - @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } } + unless @project.related_versions.empty? + @available_filters["fixed_version_id"] = { :type => :list_grouped, + :order => 7, + :values => grouped_versions_for_select(@project.grouped_versions) } end unless @project.descendants.active.empty? @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.descendants.visible.collect{|s| [s.name, s.id.to_s] } } diff --git a/app/views/issues/_form.rhtml b/app/views/issues/_form.rhtml index fac2d6a..c150b9c 100644 --- a/app/views/issues/_form.rhtml +++ b/app/views/issues/_form.rhtml @@ -32,9 +32,11 @@ {:controller => 'projects', :action => 'add_issue_category', :id => @project}, :class => 'small', :tabindex => 199) if authorize_for('projects', 'add_issue_category') %>

<% end %> -<%= content_tag('p', f.select(:fixed_version_id, - (@project.versions.sort.collect {|v| [v.name, v.id]}), - { :include_blank => true })) unless @project.versions.empty? %> +<% unless (@project.related_versions.empty? || @forbidden_fixed_version) then %> + <%= content_tag('p', f.select_ex(:fixed_version_id, + grouped_versions_for_select(@project.grouped_versions), + { :include_blank => true})) %> +<% end %>
diff --git a/app/views/issues/_form_update.rhtml b/app/views/issues/_form_update.rhtml index 3f17a03..385a95a 100644 --- a/app/views/issues/_form_update.rhtml +++ b/app/views/issues/_form_update.rhtml @@ -5,8 +5,13 @@

<%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %>

-<%= content_tag('p', f.select(:fixed_version_id, - (@project.versions.sort.collect {|v| [v.name, v.id]}), - { :include_blank => true })) unless @project.versions.empty? %> +<%= unless (@issue.fixed_version && + (not User.current.allowed_to?(:view_issues, + @issue.fixed_version.project)) || + @project.related_versions.empty?) then + content_tag('p', f.select_ex(:fixed_version_id, + grouped_versions_for_select(@project.grouped_versions), + { :include_blank => true })) + end %>
diff --git a/app/views/issues/bulk_edit.rhtml b/app/views/issues/bulk_edit.rhtml index 4ea9ffd..f3b3507 100644 --- a/app/views/issues/bulk_edit.rhtml +++ b/app/views/issues/bulk_edit.rhtml @@ -27,7 +27,7 @@ + grouped_options_for_select(grouped_versions_for_select(@project.grouped_versions))) %>

diff --git a/app/views/issues/context_menu.rhtml b/app/views/issues/context_menu.rhtml index ae9a1af..49e51b4 100644 --- a/app/views/issues/context_menu.rhtml +++ b/app/views/issues/context_menu.rhtml @@ -27,14 +27,24 @@ <% end -%> - <% unless @project.nil? || @project.versions.empty? -%> + <% unless @project.nil? || @project.related_versions.empty? -%>

  • <%= l(:field_fixed_version) %>
      - <% @project.versions.sort.each do |v| -%> -
    • <%= context_menu_link v.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'fixed_version_id' => v, :back_to => @back}, :method => :post, - :selected => (@issue && v == @issue.fixed_version), :disabled => !@can[:update] %>
    • - <% end -%> + <% @project.grouped_versions.each do |p, vv| -%> +
    • "> + <% unless @project.id == p.id %> + <%= p.name %> +
        + <% end %> + <% vv.each do |v| %> +
      • <%= context_menu_link v.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'fixed_version_id' => v, :back_to => @back}, :method => :post, :selected => (@issue && v == @issue.fixed_version), :disabled => !@can[:update] %>
      • + <% end %> + <% unless @project.id == p.id %> +
      + <% end %> +
    • + <% end -%>
    • <%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'fixed_version_id' => 'none', :back_to => @back}, :method => :post, :selected => (@issue && @issue.fixed_version.nil?), :disabled => !@can[:update] %>
    diff --git a/app/views/projects/add_file.rhtml b/app/views/projects/add_file.rhtml index ab9c735..9efbd54 100644 --- a/app/views/projects/add_file.rhtml +++ b/app/views/projects/add_file.rhtml @@ -7,7 +7,7 @@ <% if @versions.any? %>

    <%= select_tag "version_id", content_tag('option', '') + - options_from_collection_for_select(@versions, "id", "name") %>

    + grouped_options_for_select(grouped_versions_for_select(@project.grouped_versions)) %>

    <% end %>

    <%= render :partial => 'attachments/form' %>

    diff --git a/app/views/projects/roadmap.rhtml b/app/views/projects/roadmap.rhtml index bcba13a..7f3f583 100644 --- a/app/views/projects/roadmap.rhtml +++ b/app/views/projects/roadmap.rhtml @@ -4,28 +4,36 @@

    <%= l(:label_no_data) %>

    <% else %>
    -<% @versions.each do |version| %> +<% grouped_versions = @project.grouped_versions(@versions) %> +<% grouped_versions.each do |projects, versions_in_project| %> + <% unless @project.id == projects.id %> +
    +

    <%= l(:label_project) %>: <%= link_to h(projects.name), :controller => 'projects', :action => 'show', :id => projects, :jump => 'roadmap' %>

    + <% end %> + <% versions_in_project.each do |version| %> + + <% issues = version_visible_issues(version,@project,@selected_tracker_ids) %> + <%= tag 'a', :name => version.name %>

    <%= link_to h(version.name), :controller => 'versions', :action => 'show', :id => version %>

    <%= render :partial => 'versions/overview', :locals => {:version => version} %> <%= render(:partial => "wiki/content", :locals => {:content => version.wiki_page.content}) if version.wiki_page %> - <% issues = version.fixed_issues.find(:all, - :include => [:status, :tracker], - :conditions => ["tracker_id in (#{@selected_tracker_ids.join(',')})"], - :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id") unless @selected_tracker_ids.empty? - issues ||= [] - %> <% if issues.size > 0 %> <% end %> <%= call_hook :view_projects_roadmap_version_bottom, :version => version %> + <% end %> + + <% unless @project.id == projects.id %> +
    + <% end %> <% end %>
    <% end %> diff --git a/app/views/queries/_filters.rhtml b/app/views/queries/_filters.rhtml index b25cd01..7c44500 100644 --- a/app/views/queries/_filters.rhtml +++ b/app/views/queries/_filters.rhtml @@ -74,9 +74,13 @@ function toggle_multi_select(field) {