diff --git a/app/controllers/context_menus_controller.rb b/app/controllers/context_menus_controller.rb index 36041fec6..f371b2e04 100644 --- a/app/controllers/context_menus_controller.rb +++ b/app/controllers/context_menus_controller.rb @@ -65,6 +65,7 @@ class ContextMenusController < ApplicationController end end + @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&) render :layout => false end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index d35ada049..ece3f7844 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -92,6 +92,48 @@ module IssuesHelper s.html_safe end + class IssueFieldsRows + include ActionView::Helpers::TagHelper + + def initialize + @left = [] + @right = [] + end + + def left(*args) + args.any? ? @left << cells(*args) : @left + end + + def right(*args) + args.any? ? @right << cells(*args) : @right + end + + def size + @left.size > @right.size ? @left.size : @right.size + end + + def to_html + html = ''.html_safe + blank = content_tag('th', '') + content_tag('td', '') + size.times do |i| + left = @left[i] || blank + right = @right[i] || blank + html << content_tag('tr', left + right) + end + html + end + + def cells(label, text, options={}) + content_tag('th', "#{label}:", options) + content_tag('td', text, options) + end + end + + def issue_fields_rows + r = IssueFieldsRows.new + yield r + r.to_html + end + def render_custom_fields_rows(issue) return if issue.custom_field_values.empty? ordered_values = [] diff --git a/app/helpers/versions_helper.rb b/app/helpers/versions_helper.rb index f1fd7c8b5..e6f886734 100644 --- a/app/helpers/versions_helper.rb +++ b/app/helpers/versions_helper.rb @@ -19,10 +19,10 @@ module VersionsHelper - STATUS_BY_CRITERIAS = %w(category tracker status priority author assigned_to) + STATUS_BY_CRITERIAS = %w(tracker status priority author assigned_to category) def render_issue_status_by(version, criteria) - criteria = 'category' unless STATUS_BY_CRITERIAS.include?(criteria) + criteria = 'tracker' unless STATUS_BY_CRITERIAS.include?(criteria) h = Hash.new {|k,v| k[v] = [0, 0]} begin diff --git a/app/models/issue.rb b/app/models/issue.rb index 96c3f8526..5fc0f1d61 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -336,6 +336,12 @@ class Issue < ActiveRecord::Base :if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) && user.allowed_to?(:manage_subtasks, issue.project)} + def safe_attribute_names(*args) + names = super(*args) + names -= disabled_core_fields + names + end + # Safely sets attributes # Should be called from controllers instead of #attributes= # attr_accessible is too rough because we still want things like @@ -343,21 +349,22 @@ class Issue < ActiveRecord::Base def safe_attributes=(attrs, user=User.current) return unless attrs.is_a?(Hash) - # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed - attrs = delete_unsafe_attributes(attrs, user) - return if attrs.empty? + attrs = attrs.dup # Project and Tracker must be set before since new_statuses_allowed_to depends on it. - if p = attrs.delete('project_id') + if (p = attrs.delete('project_id')) && safe_attribute?('project_id') if allowed_target_projects(user).collect(&:id).include?(p.to_i) self.project_id = p end end - if t = attrs.delete('tracker_id') + if (t = attrs.delete('tracker_id')) && safe_attribute?('tracker_id') self.tracker_id = t end + attrs = delete_unsafe_attributes(attrs, user) + return if attrs.empty? + if attrs['status_id'] unless new_statuses_allowed_to(user).collect(&:id).include?(attrs['status_id'].to_i) attrs.delete('status_id') @@ -376,6 +383,10 @@ class Issue < ActiveRecord::Base assign_attributes attrs, :without_protection => true end + def disabled_core_fields + tracker ? tracker.disabled_core_fields : [] + end + def done_ratio if Issue.use_status_for_done_ratio? && status && status.default_done_ratio status.default_done_ratio diff --git a/app/models/query.rb b/app/models/query.rb index de5fa4c16..3f4173a1b 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -213,11 +213,13 @@ class Query < ActiveRecord::Base is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project) end + def trackers + @trackers ||= project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers + end + def available_filters return @available_filters if @available_filters - trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers - @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } }, "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } }, "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } }, @@ -300,6 +302,11 @@ class Query < ActiveRecord::Base end add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true})) end + + Tracker.disabled_core_fields(trackers).each {|field| + @available_filters.delete field + } + @available_filters end @@ -380,6 +387,12 @@ class Query < ActiveRecord::Base :caption => :label_spent_time ) end + + disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')} + @available_columns.reject! {|column| + disabled_fields.include?(column.name.to_s) + } + @available_columns end diff --git a/app/models/tracker.rb b/app/models/tracker.rb index ce34f782c..109e0f423 100644 --- a/app/models/tracker.rb +++ b/app/models/tracker.rb @@ -16,6 +16,10 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class Tracker < ActiveRecord::Base + + # Other fields should be appended, not inserted! + CORE_FIELDS = %w(assigned_to_id category_id fixed_version_id parent_issue_id start_date due_date estimated_hours done_ratio) + before_destroy :check_integrity has_many :issues has_many :workflows, :dependent => :delete_all do @@ -28,6 +32,8 @@ class Tracker < ActiveRecord::Base has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => "#{table_name_prefix}custom_fields_trackers#{table_name_suffix}", :association_foreign_key => 'custom_field_id' acts_as_list + attr_protected :field_bits + validates_presence_of :name validates_uniqueness_of :name validates_length_of :name, :maximum => 30 @@ -58,6 +64,39 @@ class Tracker < ActiveRecord::Base @issue_statuses = IssueStatus.find_all_by_id(ids).sort end + def disabled_core_fields + i = -1 + @disabled_core_fields ||= CORE_FIELDS.select { i += 1; (fields_bits || 0) & (2 ** i) != 0} + end + + def core_fields + CORE_FIELDS - disabled_core_fields + end + + def core_fields=(fields) + raise ArgumentError.new("Tracker.core_fields takes an array") unless fields.is_a?(Array) + + bits = 0 + CORE_FIELDS.each_with_index do |field, i| + unless fields.include?(field) + bits |= 2 ** i + end + end + self.fields_bits = bits + @disabled_core_fields = nil + core_fields + end + + # Returns the fields that are disabled for all the given trackers + def self.disabled_core_fields(trackers) + trackers.uniq.map(&:disabled_core_fields).reduce(:&) + end + + # Returns the fields that are enabled for one tracker at least + def self.core_fields(trackers) + trackers.uniq.map(&:core_fields).reduce(:|) + end + private def check_integrity raise Exception.new("Can't delete tracker") if Issue.where(:tracker_id => self.id).any? diff --git a/app/views/context_menus/issues.html.erb b/app/views/context_menus/issues.html.erb index e2b87944c..75b0a7652 100644 --- a/app/views/context_menus/issues.html.erb +++ b/app/views/context_menus/issues.html.erb @@ -33,6 +33,7 @@ <% end %> + <% if @safe_attributes.include?('priority_id') -%>
  • <%= l(:field_priority) %>
  • + <% end %> <% #TODO: allow editing versions when multiple projects %> - <% unless @project.nil? || @project.shared_versions.open.empty? -%> + <% if @safe_attributes.include?('fixed_version_id') && @project && @project.shared_versions.open.any? -%>
  • <%= l(:field_fixed_version) %>
  • <% end %> - <% if @assignables.present? -%> + + <% if @safe_attributes.include?('assigned_to_id') && @assignables.present? -%>
  • <%= l(:field_assigned_to) %>
  • <% end %> - <% unless @project.nil? || @project.issue_categories.empty? -%> + + <% if @safe_attributes.include?('category_id') && @project && @project.issue_categories.any? -%>
  • <%= l(:field_category) %>