Index: app/helpers/issues_helper.rb =================================================================== --- app/helpers/issues_helper.rb (revision 12448) +++ app/helpers/issues_helper.rb (working copy) @@ -160,7 +160,7 @@ end def render_custom_fields_rows(issue) - values = issue.visible_custom_field_values + values = issue.viewable_custom_field_values return if values.empty? ordered_values = [] half = (values.size / 2.0).ceil @@ -260,6 +260,7 @@ strings = [] values_by_field = {} details.each do |detail| + unless detail.journal.issue.hidden_attribute?(detail.prop_key, options[:user]) if detail.property == 'cf' field = detail.custom_field if field && field.multiple? @@ -274,16 +275,19 @@ end end strings << show_detail(detail, no_html, options) + end end values_by_field.each do |field, changes| detail = JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s) - detail.instance_variable_set "@custom_field", field - if changes[:added].any? - detail.value = changes[:added] - strings << show_detail(detail, no_html, options) - elsif changes[:deleted].any? - detail.old_value = changes[:deleted] - strings << show_detail(detail, no_html, options) + unless ( detail.journal != nil && detail.journal.issue.hidden_attribute?(detail.prop_key, options[:user])) + detail.instance_variable_set "@custom_field", field + if changes[:added].any? + detail.value = changes[:added] + strings << show_detail(detail, no_html, options) + elsif changes[:deleted].any? + detail.old_value = changes[:deleted] + strings << show_detail(detail, no_html, options) + end end end strings Index: app/helpers/workflows_helper.rb =================================================================== --- app/helpers/workflows_helper.rb (revision 12448) +++ app/helpers/workflows_helper.rb (working copy) @@ -25,6 +25,7 @@ def field_permission_tag(permissions, status, field, role) name = field.is_a?(CustomField) ? field.id.to_s : field options = [["", ""], [l(:label_readonly), "readonly"]] + options << [l(:label_hidden), "hidden"] unless field_required?(field) options << [l(:label_required), "required"] unless field_required?(field) html_options = {} selected = permissions[status.id][name] Index: app/models/project.rb =================================================================== --- app/models/project.rb (revision 12448) +++ app/models/project.rb (working copy) @@ -152,6 +152,36 @@ user.allowed_to?(:view_project, self) end + # Returns list of attributes that are hidden on all statuses of all trackers for +user+ or the current user. + def completely_hidden_attribute_names(user=nil) + user_real = user || User.current + roles = user_real.admin ? Role.all : user_real.roles_for_project(self) + return {} if roles.empty? + + result = {} + workflow_permissions = WorkflowPermission.where(:tracker_id => trackers.map(&:id), :old_status_id => IssueStatus.all.map(&:id), :role_id => roles.map(&:id), :rule => 'hidden').all + + if workflow_permissions.any? + workflow_rules = workflow_permissions.inject({}) do |h, wp| + h[wp.field_name] ||= [] + h[wp.field_name] << wp.rule + h + end + workflow_rules.each do |attr, rules| + next if rules.size < (roles.size * trackers.size * IssueStatus.all.size) + uniq_rules = rules.uniq + if uniq_rules.size == 1 + result[attr] = uniq_rules.first + else + result[attr] = 'required' + end + end + end + + result.keys + end + + # Returns a SQL conditions string used to find all projects visible by the specified user. # # Examples: Index: app/models/workflow_permission.rb =================================================================== --- app/models/workflow_permission.rb (revision 12448) +++ app/models/workflow_permission.rb (working copy) @@ -16,13 +16,13 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class WorkflowPermission < WorkflowRule - validates_inclusion_of :rule, :in => %w(readonly required) + validates_inclusion_of :rule, :in => %w(readonly required hidden) validate :validate_field_name # Replaces the workflow permissions for the given tracker and role # # Example: - # WorkflowPermission.replace_permissions role, tracker, {'due_date' => {'1' => 'readonly', '2' => 'required'}} + # WorkflowPermission.replace_permissions role, tracker, {'due_date' => {'1' => 'readonly', '2' => 'required', '3' => 'hidden'}} def self.replace_permissions(tracker, role, permissions) destroy_all(:tracker_id => tracker.id, :role_id => role.id) Index: app/models/issue.rb =================================================================== --- app/models/issue.rb (revision 12448) +++ app/models/issue.rb (working copy) @@ -483,18 +483,30 @@ end end + # Returns the custom_field_values that can be viewed by the given user, also always excludes Fix Info, RNs and TAT, as it is printed separately below description. + def viewable_custom_field_values(user=nil) + custom_field_values.reject do |value| + hidden_attribute_names(user).include?(value.custom_field_id.to_s) + end + end + # Returns the names of attributes that are read-only for user or the current user # For users with multiple roles, the read-only fields are the intersection of # read-only fields of each role - # The result is an array of strings where sustom fields are represented with their ids + # The result is an array of strings where custom fields are represented with their ids # # Examples: # issue.read_only_attribute_names # => ['due_date', '2'] # issue.read_only_attribute_names(user) # => [] def read_only_attribute_names(user=nil) - workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'readonly'}.keys + workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'readonly' && rule != 'hidden' }.keys end + # Same as above, return the names of attributes that are hidden for user or the current user + def hidden_attribute_names(user=nil) + workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'hidden'}.keys + end + # Returns the names of required attributes for user or the current user # For users with multiple roles, the required fields are the intersection of # required fields of each role @@ -512,6 +524,12 @@ required_attribute_names(user).include?(name.to_s) end + # Returns true if the attribute should be hidden for user + def hidden_attribute?(name, user=nil) + hidden_attribute_names(user).include?(name.to_s) + end + + # Returns a hash of the workflow rule by attribute for the given user # # Examples: Index: app/views/mailer/_issue.text.erb =================================================================== --- app/views/mailer/_issue.text.erb (revision 12448) +++ app/views/mailer/_issue.text.erb (working copy) @@ -1,7 +1,27 @@ <%= "#{issue.tracker.name} ##{issue.id}: #{issue.subject}" %> <%= issue_url %> -<%= render_email_issue_attributes(issue, users.first) %> +* <%=l(:field_author)%>: <%= issue.author %> +<% unless issue.hidden_attribute?('status', user) %> +* <%=l(:field_status)%>: <%= issue.status %> +<% end %> +<% unless issue.hidden_attribute?('priority', user) %> +* <%=l(:field_priority)%>: <%= issue.priority %> +<% end %> +<% unless issue.disabled_core_fields.include?('assigned_to_id') || issue.hidden_attribute?('assigned_to_id', user) %> +* <%=l(:field_assigned_to)%>: <%= issue.assigned_to %> +<% end %> +<% unless issue.disabled_core_fields.include?('category_id') || issue.hidden_attribute?('category_id', user) %> +* <%=l(:field_category)%>: <%= issue.category %> +<% end %> +<% unless issue.disabled_core_fields.include?('fixed_version_id') || issue.hidden_attribute?('fixed_version_id', user) %> +* <%=l(:field_fixed_version)%>: <%= issue.fixed_version %> +<% end %> +<% issue.custom_field_values.each do |c| %> +<% unless issue.hidden_attribute?(c.custom_field.id, user) %> +* <%= c.custom_field.name %>: <%= show_value(c) %> +<% end %> +<% end -%> ---------------------------------------- <%= issue.description %> Index: app/views/mailer/_issue.html.erb =================================================================== --- app/views/mailer/_issue.html.erb (revision 12448) +++ app/views/mailer/_issue.html.erb (working copy) @@ -1,6 +1,28 @@

<%= link_to(h("#{issue.tracker.name} ##{issue.id}: #{issue.subject}"), issue_url) %>

-<%= render_email_issue_attributes(issue, users.first, true) %> + <%= textilizable(issue, :description, :only_path => false) %> Index: app/views/issues/show.html.erb =================================================================== --- app/views/issues/show.html.erb (revision 12448) +++ app/views/issues/show.html.erb (working copy) @@ -33,29 +33,32 @@ <%= issue_fields_rows do |rows| + unless @issue.hidden_attribute?('status') rows.left l(:field_status), h(@issue.status.name), :class => 'status' + end + unless @issue.hidden_attribute?('priority_id') rows.left l(:field_priority), h(@issue.priority.name), :class => 'priority' - unless @issue.disabled_core_fields.include?('assigned_to_id') + unless @issue.disabled_core_fields.include?('assigned_to_id') || @issue.hidden_attribute?('assigned_to_id') rows.left l(:field_assigned_to), avatar(@issue.assigned_to, :size => "14").to_s.html_safe + (@issue.assigned_to ? link_to_user(@issue.assigned_to) : "-"), :class => 'assigned-to' end - unless @issue.disabled_core_fields.include?('category_id') + unless @issue.disabled_core_fields.include?('category_id') || @issue.hidden_attribute?('category_id') rows.left l(:field_category), h(@issue.category ? @issue.category.name : "-"), :class => 'category' end - unless @issue.disabled_core_fields.include?('fixed_version_id') + unless @issue.disabled_core_fields.include?('fixed_version_id') || @issue.hidden_attribute?('fixed_version_id') rows.left l(:field_fixed_version), (@issue.fixed_version ? link_to_version(@issue.fixed_version) : "-"), :class => 'fixed-version' end - unless @issue.disabled_core_fields.include?('start_date') + unless @issue.disabled_core_fields.include?('start_date') || @issue.hidden_attribute?('start_date') rows.right l(:field_start_date), format_date(@issue.start_date), :class => 'start-date' end - unless @issue.disabled_core_fields.include?('due_date') + unless @issue.disabled_core_fields.include?('due_date') || @issue.hidden_attribute?('due_date') rows.right l(:field_due_date), format_date(@issue.due_date), :class => 'due-date' end - unless @issue.disabled_core_fields.include?('done_ratio') + unless @issue.disabled_core_fields.include?('done_ratio') || @issue.hidden_attribute?('done_ratio') rows.right l(:field_done_ratio), progress_bar(@issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%"), :class => 'progress' end - unless @issue.disabled_core_fields.include?('estimated_hours') + unless @issue.disabled_core_fields.include?('estimated_hours') || @issue.hidden_attribute?('estimated_hours') unless @issue.estimated_hours.nil? rows.right l(:field_estimated_hours), l_hours(@issue.estimated_hours), :class => 'estimated-hours' end Index: config/locales/en.yml =================================================================== --- config/locales/en.yml (revision 12448) +++ config/locales/en.yml (working copy) @@ -888,7 +888,7 @@ label_fields_permissions: Fields permissions label_readonly: Read-only label_required: Required - label_hidden: Hidden + label_hidden: " Hidden " label_attribute_of_project: "Project's %{name}" label_attribute_of_issue: "Issue's %{name}" label_attribute_of_author: "Author's %{name}" Index: lib/redmine/export/pdf.rb =================================================================== --- lib/redmine/export/pdf.rb (revision 12448) +++ lib/redmine/export/pdf.rb (working copy) @@ -550,17 +550,17 @@ pdf.Ln left = [] - left << [l(:field_status), issue.status] - left << [l(:field_priority), issue.priority] - left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id') - left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id') - left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id') + left << [l(:field_status), issue.status] unless issue.hidden_attribute?('status') + left << [l(:field_priority), issue.priority] unless issue.hidden_attribute?('priority_id') + left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id') or issue.hidden_attribute?('assigned_to_id') + left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id') or issue.hidden_attribute?('category_id') + left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id') or issue.hidden_attribute?('fixed_version_id') right = [] - right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date') - right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date') - right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio') - right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours') + right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date') or issue.hidden_attribute?('start_date') + right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date') or issue.hidden_attribute?('due_date') + right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio') or issue.hidden_attribute?('done_ratio') + right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours') or issue.hidden_attribute?('estimated_hours') right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project) rows = left.size > right.size ? left.size : right.size