diff --git a/README.md b/README.md index 81262a3..3bdce31 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,14 @@ Corresponding issue on redmine.org: [#12005](http://www.redmine.org/issues/12005 - Configure hidden fields in workflows, thus per status and roles. -- Hidden fields don't show up for respective users on issues (views, table, forms, history), available columns & filters, exported .csv and .pdf files. +- Hidden fields don't show up for respective users on issues (views, table, forms, history), exported .csv and .pdf files. + +- Completely hidden fields (fields that are configured to not be visible anywhere for the user) are removed from available columns & filters. ###Compatibility -The plugin was developed and tested with Redmine version 2.5.2. +The plugin was developed and tested with Redmine version 3.0.1. ###Installation diff --git a/app/views/issues/_history.html.erb b/app/views/issues/_history.html.erb index c73ad62..5724b2c 100644 --- a/app/views/issues/_history.html.erb +++ b/app/views/issues/_history.html.erb @@ -1,27 +1,24 @@ <% reply_links = authorize_for('issues', 'edit') -%> <% for journal in journals %> + <% if details_to_strings(journal.details).any? || journal.notes.blank? == false %> +
+
+

#<%= journal.indice %> + <%= avatar(journal.user, :size => "24") %> + <%= authoring journal.created_on, journal.user, :label => :label_updated_time_by %> + <%= content_tag('span', l(:field_is_private), :class => 'private') if journal.private_notes? %>

-<% if details_to_strings(journal.details).any? || journal.notes.blank? == false %> - -
-
-

<%= link_to "##{journal.indice}", {:anchor => "note-#{journal.indice}"}, :class => "journal-link" %> - <%= avatar(journal.user, :size => "24") %> - <%= authoring journal.created_on, journal.user, :label => :label_updated_time_by %>

- - <% if journal.details.any? %> -
    - <% details_to_strings(journal.visible_details).each do |string| %> -
  • <%= string %>
  • - <% end %> -
- <% end %> - <%= render_notes(issue, journal, :reply_links => reply_links) unless journal.notes.blank? %> -
-
- <%= call_hook(:view_issues_history_journal_bottom, { :journal => journal }) %> + <% if journal.details.any? %> + + <% end %> + <%= render_notes(issue, journal, :reply_links => reply_links) unless journal.notes.blank? %> +
+
+ <%= call_hook(:view_issues_history_journal_bottom, { :journal => journal }) %> + <% end %> <% end %> - -<% end %> - -<% heads_for_wiki_formatter if User.current.allowed_to?(:edit_issue_notes, issue.project) || User.current.allowed_to?(:edit_own_issue_notes, issue.project) %> +<% heads_for_wiki_formatter if User.current.allowed_to?(:edit_issue_notes, issue.project) || User.current.allowed_to?(:edit_own_issue_notes, issue.project) %> \ No newline at end of file diff --git a/app/views/issues/show.html.erb b/app/views/issues/show.html.erb index b657253..eae8aa6 100644 --- a/app/views/issues/show.html.erb +++ b/app/views/issues/show.html.erb @@ -8,14 +8,16 @@ <%= link_to_if @prev_issue_id, "\xc2\xab #{l(:label_previous)}", (@prev_issue_id ? issue_path(@prev_issue_id) : nil), - :title => "##{@prev_issue_id}" %> | + :title => "##{@prev_issue_id}", + :accesskey => accesskey(:previous) %> | <% if @issue_position && @issue_count %> <%= l(:label_item_position, :position => @issue_position, :count => @issue_count) %> | <% end %> <%= link_to_if @next_issue_id, "#{l(:label_next)} \xc2\xbb", (@next_issue_id ? issue_path(@next_issue_id) : nil), - :title => "##{@next_issue_id}" %> + :title => "##{@next_issue_id}", + :accesskey => accesskey(:next) %> <% end %> @@ -32,39 +34,88 @@

-<%= issue_fields_rows do |rows| - - #adding unless stmts to hide 'hidden' fields - - rows.left l(:field_status), h(@issue.status.name), :class => 'status' unless @issue.hidden_attribute?('status') - rows.left l(:field_priority), h(@issue.priority.name), :class => 'priority' unless @issue.hidden_attribute?('priority_id') - +<%= issue_fields_rows do |rows| + i = 0 + unless @issue.hidden_attribute?('status') + rows.left l(:field_status), h(@issue.status.name), :class => 'status' + i += 1 + end + + unless @issue.hidden_attribute?('priority_id') + if i % 2 == 0 + rows.left l(:field_priority), h(@issue.priority.name), :class => 'priority' + else + rows.right l(:field_priority), h(@issue.priority.name), :class => 'priority' + end + i += 1 + end 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' + if i % 2 == 0 + 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' + else + rows.right 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 + i += 1 end + 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' + if i % 2 == 0 + rows.left l(:field_category), h(@issue.category ? @issue.category.name : "-"), :class => 'category' + else + rows.right l(:field_category), h(@issue.category ? @issue.category.name : "-"), :class => 'category' + end + i += 1 end 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' + if i % 2 == 0 + rows.left l(:field_fixed_version), (@issue.fixed_version ? link_to_version(@issue.fixed_version) : "-"), :class => 'fixed-version' + else + rows.right l(:field_fixed_version), (@issue.fixed_version ? link_to_version(@issue.fixed_version) : "-"), :class => 'fixed-version' + end + i += 1 end 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' + if i % 2 == 0 + rows.left l(:field_start_date), format_date(@issue.start_date), :class => 'start-date' + else + rows.right l(:field_start_date), format_date(@issue.start_date), :class => 'start-date' + end + i += 1 end 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' + if i % 2 == 0 + rows.left l(:field_due_date), format_date(@issue.due_date), :class => 'due-date' + else + rows.right l(:field_due_date), format_date(@issue.due_date), :class => 'due-date' + end + i += 1 end 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' + if i % 2 == 0 + rows.left l(:field_done_ratio), progress_bar(@issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%"), :class => 'progress' + else + rows.right l(:field_done_ratio), progress_bar(@issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%"), :class => 'progress' + end + i += 1 end + 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' + if i % 2 == 0 + rows.left l(:field_estimated_hours), l_hours(@issue.estimated_hours), :class => 'estimated-hours' + else + rows.right l(:field_estimated_hours), l_hours(@issue.estimated_hours), :class => 'estimated-hours' + end + i += 1 end end if User.current.allowed_to?(:view_time_entries, @project) - rows.right l(:label_spent_time), (@issue.total_spent_hours > 0 ? link_to(l_hours(@issue.total_spent_hours), project_issue_time_entries_path(@project, @issue)) : "-"), :class => 'spent-time' + if i % 2 == 0 + rows.left l(:label_spent_time), (@issue.total_spent_hours > 0 ? link_to(l_hours(@issue.total_spent_hours), issue_time_entries_path(@issue)) : "-"), :class => 'spent-time' + else + rows.right l(:label_spent_time), (@issue.total_spent_hours > 0 ? link_to(l_hours(@issue.total_spent_hours), issue_time_entries_path(@issue)) : "-"), :class => 'spent-time' + end end end %> <%= render_custom_fields_rows(@issue) %> diff --git a/app/views/queries/_form.html.erb b/app/views/queries/_form.html.erb new file mode 100644 index 0000000..d96063b --- /dev/null +++ b/app/views/queries/_form.html.erb @@ -0,0 +1,87 @@ +<%= error_messages_for 'query' %> + +
+
+<%= hidden_field_tag 'gantt', '1' if params[:gantt] %> + +

+<%= text_field 'query', 'name', :size => 80 %>

+ +<% if User.current.admin? || User.current.allowed_to?(:manage_public_queries, @project) %> +

+ + + <% Role.givable.sorted.each do |role| %> + + <% end %> + + <%= hidden_field_tag 'query[role_ids][]', '' %> +

+<% end %> + +

+<%= check_box_tag 'query_is_for_all', 1, @query.project.nil?, + :disabled => (!@query.new_record? && (@query.project.nil? || (@query.is_public? && !User.current.admin?))) %>

+ +<% unless params[:gantt] %> +
<%= l(:label_options) %> +

+<%= check_box_tag 'default_columns', 1, @query.has_default_columns?, :id => 'query_default_columns', + :onclick => 'if (this.checked) {$("#columns").hide();} else {$("#columns").show();}' %>

+ +

+<%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %>

+ +

+<%= available_block_columns_tags(@query) %>

+
+<% else %> +
<%= l(:label_options) %> +

+ + +

+
+<% end %> +
+ +
<%= l(:label_filter_plural) %> +<%= render :partial => 'queries/filters', :locals => {:query => query}%> +
+ +<% unless params[:gantt] %> +
<%= l(:label_sort) %> +<% 3.times do |i| %> +<%= i+1 %>: +<%= label_tag "query_sort_criteria_attribute_" + i.to_s, + l(:description_query_sort_criteria_attribute), :class => "hidden-for-sighted" %> +<%= select_tag("query[sort_criteria][#{i}][]", + options_for_select([[]] + query.available_and_visible_columns.select(&:sortable?).collect {|column| [column.caption, column.name.to_s]}, @query.sort_criteria_key(i)), + :id => "query_sort_criteria_attribute_" + i.to_s)%> +<%= label_tag "query_sort_criteria_direction_" + i.to_s, + l(:description_query_sort_criteria_direction), :class => "hidden-for-sighted" %> +<%= select_tag("query[sort_criteria][#{i}][]", + options_for_select([[], [l(:label_ascending), 'asc'], [l(:label_descending), 'desc']], @query.sort_criteria_order(i)), + :id => "query_sort_criteria_direction_" + i.to_s) %> +
+<% end %> +
+<% end %> + +<% unless params[:gantt] %> +<%= content_tag 'fieldset', :id => 'columns', :style => (query.has_default_columns? ? 'display:none;' : nil) do %> +<%= l(:field_column_names) %> +<%= render_query_columns_selection(query) %> +<% end %> +<% end %> + +
+ +<%= javascript_tag do %> +$(document).ready(function(){ + $("input[name='query[visibility]']").change(function(){ + var checked = $('#query_visibility_1').is(':checked'); + $("input[name='query[role_ids][]'][type=checkbox]").attr('disabled', !checked); + }).trigger('change'); +}); +<% end %> diff --git a/init.rb b/init.rb index f690c21..b685a22 100644 --- a/init.rb +++ b/init.rb @@ -1,5 +1,4 @@ require 'redmine' -require 'pdf' ActionDispatch::Callbacks.to_prepare do require_dependency 'issue' @@ -15,19 +14,22 @@ require_dependency 'query' Query.send(:include, RedmineWorkflowHiddenFields::QueryPatch) QueryColumn.send(:include, RedmineWorkflowHiddenFields::QueryColumnPatch) + QueryCustomFieldColumn.send(:include, RedmineWorkflowHiddenFields::QueryCustomFieldColumnPatch) require_dependency 'workflow_permission' WorkflowPermission.send(:include, RedmineWorkflowHiddenFields::WorkflowPermissionPatch) require_dependency 'workflows_helper' WorkflowsHelper.send(:include, RedmineWorkflowHiddenFields::WorkflowsHelperPatch) + require_dependency 'redmine/export/pdf/issues_pdf_helper' + Redmine::Export::PDF::IssuesPdfHelper.send(:include, RedmineWorkflowHiddenFields::IssuesPdfHelperPatch) end Redmine::Plugin.register :redmine_workflow_hidden_fields do - requires_redmine :version_or_higher => '2.5.2' + requires_redmine :version_or_higher => '3.0.1' name 'Redmine Workflow Hidden Fields plugin' - author 'Alexander Wais, et al.' + author 'Alexander Wais, David Robinson, et al.' description "Provides a 'hidden' issue field permission for workflows" - version '0.1.2' + version '0.2.0' url 'https://github.com/alexwais/redmine_workflow_hidden_fields' author_url 'http://www.redmine.org/issues/12005' end diff --git a/lib/pdf.rb b/lib/pdf.rb deleted file mode 100644 index 41b8fcf..0000000 --- a/lib/pdf.rb +++ /dev/null @@ -1,807 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2014 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -require 'tcpdf' -require 'fpdf/chinese' -require 'fpdf/japanese' -require 'fpdf/korean' - -if RUBY_VERSION < '1.9' - require 'iconv' -end - -module Redmine - module Export - module PDF - include ActionView::Helpers::TextHelper - include ActionView::Helpers::NumberHelper - include IssuesHelper - - class ITCPDF < TCPDF - include Redmine::I18n - attr_accessor :footer_date - - def initialize(lang, orientation='P') - @@k_path_cache = Rails.root.join('tmp', 'pdf') - FileUtils.mkdir_p @@k_path_cache unless File::exist?(@@k_path_cache) - set_language_if_valid lang - pdf_encoding = l(:general_pdf_encoding).upcase - super(orientation, 'mm', 'A4', (pdf_encoding == 'UTF-8'), pdf_encoding) - case current_language.to_s.downcase - when 'vi' - @font_for_content = 'DejaVuSans' - @font_for_footer = 'DejaVuSans' - else - case pdf_encoding - when 'UTF-8' - @font_for_content = 'FreeSans' - @font_for_footer = 'FreeSans' - when 'CP949' - extend(PDF_Korean) - AddUHCFont() - @font_for_content = 'UHC' - @font_for_footer = 'UHC' - when 'CP932', 'SJIS', 'SHIFT_JIS' - extend(PDF_Japanese) - AddSJISFont() - @font_for_content = 'SJIS' - @font_for_footer = 'SJIS' - when 'GB18030' - extend(PDF_Chinese) - AddGBFont() - @font_for_content = 'GB' - @font_for_footer = 'GB' - when 'BIG5' - extend(PDF_Chinese) - AddBig5Font() - @font_for_content = 'Big5' - @font_for_footer = 'Big5' - else - @font_for_content = 'Arial' - @font_for_footer = 'Helvetica' - end - end - SetCreator(Redmine::Info.app_name) - SetFont(@font_for_content) - @outlines = [] - @outlineRoot = nil - end - - def SetFontStyle(style, size) - SetFont(@font_for_content, style, size) - end - - def SetTitle(txt) - txt = begin - utf16txt = to_utf16(txt) - hextxt = "" - rescue - txt - end || '' - super(txt) - end - - def textstring(s) - # Format a text string - if s =~ /^\{\{([<>]?)toc\}\}<\/p>/i, '') - html - end - - # Encodes an UTF-8 string to UTF-16BE - def to_utf16(str) - if str.respond_to?(:encode) - str.encode('UTF-16BE') - else - Iconv.conv('UTF-16BE', 'UTF-8', str) - end - end - - def RDMCell(w ,h=0, txt='', border=0, ln=0, align='', fill=0, link='') - Cell(w, h, fix_text_encoding(txt), border, ln, align, fill, link) - end - - def RDMMultiCell(w, h=0, txt='', border=0, align='', fill=0, ln=1) - MultiCell(w, h, fix_text_encoding(txt), border, align, fill, ln) - end - - def RDMwriteHTMLCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0) - @attachments = attachments - writeHTMLCell(w, h, x, y, - fix_text_encoding(formatted_text(txt)), - border, ln, fill) - end - - def getImageFilename(attrname) - # attrname: general_pdf_encoding string file/uri name - atta = RDMPdfEncoding.attach(@attachments, attrname, l(:general_pdf_encoding)) - if atta - return atta.diskfile - else - return nil - end - end - - def Footer - SetFont(@font_for_footer, 'I', 8) - SetY(-15) - SetX(15) - RDMCell(0, 5, @footer_date, 0, 0, 'L') - SetY(-15) - SetX(-30) - RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C') - end - - def Bookmark(txt, level=0, y=0) - if (y == -1) - y = GetY() - end - @outlines << {:t => txt, :l => level, :p => PageNo(), :y => (@h - y)*@k} - end - - def bookmark_title(txt) - txt = begin - utf16txt = to_utf16(txt) - hextxt = "" - rescue - txt - end || '' - end - - def putbookmarks - nb=@outlines.size - return if (nb==0) - lru=[] - level=0 - @outlines.each_with_index do |o, i| - if(o[:l]>0) - parent=lru[o[:l]-1] - #Set parent and last pointers - @outlines[i][:parent]=parent - @outlines[parent][:last]=i - if (o[:l]>level) - #Level increasing: set first pointer - @outlines[parent][:first]=i - end - else - @outlines[i][:parent]=nb - end - if (o[:l]<=level && i>0) - #Set prev and next pointers - prev=lru[o[:l]] - @outlines[prev][:next]=i - @outlines[i][:prev]=prev - end - lru[o[:l]]=i - level=o[:l] - end - #Outline items - n=self.n+1 - @outlines.each_with_index do |o, i| - newobj() - out('<>') - out('endobj') - end - #Outline root - newobj() - @outlineRoot=self.n - out("<>"); - out('endobj'); - end - - def putresources() - super - putbookmarks() - end - - def putcatalog() - super - if(@outlines.size > 0) - out("/Outlines #{@outlineRoot} 0 R"); - out('/PageMode /UseOutlines'); - end - end - end - - # fetch row values - def fetch_row_values(issue, query, level) - query.inline_columns.collect do |column| - s = if column.is_a?(QueryCustomFieldColumn) - cv = issue.visible_custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id} - show_value(cv, false) - else - value = issue.send(column.name) - if column.name == :subject - value = " " * level + value - end - if value.is_a?(Date) - format_date(value) - elsif value.is_a?(Time) - format_time(value) - else - value - end - end - s.to_s - end - end - - # calculate columns width - def calc_col_width(issues, query, table_width, pdf) - # calculate statistics - # by captions - pdf.SetFontStyle('B',8) - col_padding = pdf.GetStringWidth('OO') - col_width_min = query.inline_columns.map {|v| pdf.GetStringWidth(v.caption) + col_padding} - col_width_max = Array.new(col_width_min) - col_width_avg = Array.new(col_width_min) - word_width_max = query.inline_columns.map {|c| - n = 10 - c.caption.split.each {|w| - x = pdf.GetStringWidth(w) + col_padding - n = x if n < x - } - n - } - - # by properties of issues - pdf.SetFontStyle('',8) - col_padding = pdf.GetStringWidth('OO') - k = 1 - issue_list(issues) {|issue, level| - k += 1 - values = fetch_row_values(issue, query, level) - values.each_with_index {|v,i| - n = pdf.GetStringWidth(v) + col_padding - col_width_max[i] = n if col_width_max[i] < n - col_width_min[i] = n if col_width_min[i] > n - col_width_avg[i] += n - v.split.each {|w| - x = pdf.GetStringWidth(w) + col_padding - word_width_max[i] = x if word_width_max[i] < x - } - } - } - col_width_avg.map! {|x| x / k} - - # calculate columns width - ratio = table_width / col_width_avg.inject(0, :+) - col_width = col_width_avg.map {|w| w * ratio} - - # correct max word width if too many columns - ratio = table_width / word_width_max.inject(0, :+) - word_width_max.map! {|v| v * ratio} if ratio < 1 - - # correct and lock width of some columns - done = 1 - col_fix = [] - col_width.each_with_index do |w,i| - if w > col_width_max[i] - col_width[i] = col_width_max[i] - col_fix[i] = 1 - done = 0 - elsif w < word_width_max[i] - col_width[i] = word_width_max[i] - col_fix[i] = 1 - done = 0 - else - col_fix[i] = 0 - end - end - - # iterate while need to correct and lock coluns width - while done == 0 - # calculate free & locked columns width - done = 1 - fix_col_width = 0 - free_col_width = 0 - col_width.each_with_index do |w,i| - if col_fix[i] == 1 - fix_col_width += w - else - free_col_width += w - end - end - - # calculate column normalizing ratio - if free_col_width == 0 - ratio = table_width / col_width.inject(0, :+) - else - ratio = (table_width - fix_col_width) / free_col_width - end - - # correct columns width - col_width.each_with_index do |w,i| - if col_fix[i] == 0 - col_width[i] = w * ratio - - # check if column width less then max word width - if col_width[i] < word_width_max[i] - col_width[i] = word_width_max[i] - col_fix[i] = 1 - done = 0 - elsif col_width[i] > col_width_max[i] - col_width[i] = col_width_max[i] - col_fix[i] = 1 - done = 0 - end - end - end - end - col_width - end - - def render_table_header(pdf, query, col_width, row_height, table_width) - # headers - pdf.SetFontStyle('B',8) - pdf.SetFillColor(230, 230, 230) - - # render it background to find the max height used - base_x = pdf.GetX - base_y = pdf.GetY - max_height = issues_to_pdf_write_cells(pdf, query.inline_columns, col_width, row_height, true) - pdf.Rect(base_x, base_y, table_width, max_height, 'FD'); - pdf.SetXY(base_x, base_y); - - # write the cells on page - issues_to_pdf_write_cells(pdf, query.inline_columns, col_width, row_height, true) - issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, 0, col_width) - pdf.SetY(base_y + max_height); - - # rows - pdf.SetFontStyle('',8) - pdf.SetFillColor(255, 255, 255) - end - - # Returns a PDF string of a list of issues - def issues_to_pdf(issues, project, query) - pdf = ITCPDF.new(current_language, "L") - title = query.new_record? ? l(:label_issue_plural) : query.name - title = "#{project} - #{title}" if project - pdf.SetTitle(title) - pdf.alias_nb_pages - pdf.footer_date = format_date(Date.today) - pdf.SetAutoPageBreak(false) - pdf.AddPage("L") - - # Landscape A4 = 210 x 297 mm - page_height = 210 - page_width = 297 - left_margin = 10 - right_margin = 10 - bottom_margin = 20 - row_height = 4 - - # column widths - table_width = page_width - right_margin - left_margin - col_width = [] - unless query.inline_columns.empty? - col_width = calc_col_width(issues, query, table_width, pdf) - table_width = col_width.inject(0, :+) - end - - # use full width if the description is displayed - if table_width > 0 && query.has_column?(:description) - col_width = col_width.map {|w| w * (page_width - right_margin - left_margin) / table_width} - table_width = col_width.inject(0, :+) - end - - # title - pdf.SetFontStyle('B',11) - pdf.RDMCell(190,10, title) - pdf.Ln - render_table_header(pdf, query, col_width, row_height, table_width) - previous_group = false - issue_list(issues) do |issue, level| - if query.grouped? && - (group = query.group_by_column.value(issue)) != previous_group - pdf.SetFontStyle('B',10) - group_label = group.blank? ? 'None' : group.to_s.dup - group_label << " (#{query.issue_count_by_group[group]})" - pdf.Bookmark group_label, 0, -1 - pdf.RDMCell(table_width, row_height * 2, group_label, 1, 1, 'L') - pdf.SetFontStyle('',8) - previous_group = group - end - - # fetch row values - col_values = fetch_row_values(issue, query, level) - - # render it off-page to find the max height used - base_x = pdf.GetX - base_y = pdf.GetY - pdf.SetY(2 * page_height) - max_height = issues_to_pdf_write_cells(pdf, col_values, col_width, row_height) - pdf.SetXY(base_x, base_y) - - # make new page if it doesn't fit on the current one - space_left = page_height - base_y - bottom_margin - if max_height > space_left - pdf.AddPage("L") - render_table_header(pdf, query, col_width, row_height, table_width) - base_x = pdf.GetX - base_y = pdf.GetY - end - - # write the cells on page - issues_to_pdf_write_cells(pdf, col_values, col_width, row_height) - issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, 0, col_width) - pdf.SetY(base_y + max_height); - - if query.has_column?(:description) && issue.description? - pdf.SetX(10) - pdf.SetAutoPageBreak(true, 20) - pdf.RDMwriteHTMLCell(0, 5, 10, 0, issue.description.to_s, issue.attachments, "LRBT") - pdf.SetAutoPageBreak(false) - end - end - - if issues.size == Setting.issues_export_limit.to_i - pdf.SetFontStyle('B',10) - pdf.RDMCell(0, row_height, '...') - end - pdf.Output - end - - # Renders MultiCells and returns the maximum height used - def issues_to_pdf_write_cells(pdf, col_values, col_widths, row_height, head=false) - base_y = pdf.GetY - max_height = row_height - col_values.each_with_index do |column, i| - col_x = pdf.GetX - if head == true - pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1) - else - pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1) - end - max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height - pdf.SetXY(col_x + col_widths[i], base_y); - end - return max_height - end - - # Draw lines to close the row (MultiCell border drawing in not uniform) - # - # parameter "col_id_width" is not used. it is kept for compatibility. - def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y, - col_id_width, col_widths) - col_x = top_x - pdf.Line(col_x, top_y, col_x, lower_y) # id right border - col_widths.each do |width| - col_x += width - pdf.Line(col_x, top_y, col_x, lower_y) # columns right border - end - pdf.Line(top_x, top_y, top_x, lower_y) # left border - pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border - end - - # Returns a PDF string of a single issue - def issue_to_pdf(issue, assoc={}) - pdf = ITCPDF.new(current_language) - pdf.SetTitle("#{issue.project} - #{issue.tracker} ##{issue.id}") - pdf.alias_nb_pages - pdf.footer_date = format_date(Date.today) - pdf.AddPage - pdf.SetFontStyle('B',11) - buf = "#{issue.project} - #{issue.tracker} ##{issue.id}" - pdf.RDMMultiCell(190, 5, buf) - pdf.SetFontStyle('',8) - base_x = pdf.GetX - i = 1 - issue.ancestors.visible.each do |ancestor| - pdf.SetX(base_x + i) - buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}" - pdf.RDMMultiCell(190 - i, 5, buf) - i += 1 if i < 35 - end - pdf.SetFontStyle('B',11) - pdf.RDMMultiCell(190 - i, 5, issue.subject.to_s) - pdf.SetFontStyle('',8) - pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}") - pdf.Ln - - left = [] - left << [l(:field_status), issue.status] unless issue.hidden_attribute?('status_id') - 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') 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 - while left.size < rows - left << nil - end - while right.size < rows - right << nil - end - - half = (issue.visible_custom_field_values.size / 2.0).ceil - issue.visible_custom_field_values.each_with_index do |custom_value, i| - (i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value, false)] - end - - rows = left.size > right.size ? left.size : right.size - rows.times do |i| - item = left[i] - pdf.SetFontStyle('B',9) - pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L") - pdf.SetFontStyle('',9) - pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R") - - item = right[i] - pdf.SetFontStyle('B',9) - pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L") - pdf.SetFontStyle('',9) - pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R") - pdf.Ln - end - - pdf.SetFontStyle('B',9) - pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1) - pdf.SetFontStyle('',9) - - # Set resize image scale - pdf.SetImageScale(1.6) - pdf.RDMwriteHTMLCell(35+155, 5, 0, 0, - issue.description.to_s, issue.attachments, "LRB") - - unless issue.leaf? - # for CJK - truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 ) - pdf.SetFontStyle('B',9) - pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR") - pdf.Ln - issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level| - buf = "#{child.tracker} # #{child.id}: #{child.subject}". - truncate(truncate_length) - level = 10 if level >= 10 - pdf.SetFontStyle('',8) - pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, "L") - pdf.SetFontStyle('B',8) - pdf.RDMCell(20,5, child.status.to_s, "R") - pdf.Ln - end - end - - relations = issue.relations.select { |r| r.other_issue(issue).visible? } - unless relations.empty? - # for CJK - truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 ) - pdf.SetFontStyle('B',9) - pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR") - pdf.Ln - relations.each do |relation| - buf = "" - buf += "#{l(relation.label_for(issue))} " - if relation.delay && relation.delay != 0 - buf += "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)}) " - end - if Setting.cross_project_issue_relations? - buf += "#{relation.other_issue(issue).project} - " - end - buf += "#{relation.other_issue(issue).tracker}" + - " # #{relation.other_issue(issue).id}: #{relation.other_issue(issue).subject}" - buf = buf.truncate(truncate_length) - pdf.SetFontStyle('', 8) - pdf.RDMCell(35+155-60, 5, buf, "L") - pdf.SetFontStyle('B',8) - pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "") - pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "") - pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), "R") - pdf.Ln - end - end - pdf.RDMCell(190,5, "", "T") - pdf.Ln - - if issue.changesets.any? && - User.current.allowed_to?(:view_changesets, issue.project) - pdf.SetFontStyle('B',9) - pdf.RDMCell(190,5, l(:label_associated_revisions), "B") - pdf.Ln - for changeset in issue.changesets - pdf.SetFontStyle('B',8) - csstr = "#{l(:label_revision)} #{changeset.format_identifier} - " - csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s - pdf.RDMCell(190, 5, csstr) - pdf.Ln - unless changeset.comments.blank? - pdf.SetFontStyle('',8) - pdf.RDMwriteHTMLCell(190,5,0,0, - changeset.comments.to_s, issue.attachments, "") - end - pdf.Ln - end - end - - if assoc[:journals].present? - pdf.SetFontStyle('B',9) - pdf.RDMCell(190,5, l(:label_history), "B") - pdf.Ln - assoc[:journals].each do |journal| - pdf.SetFontStyle('B',8) - title = "##{journal.indice} - #{format_time(journal.created_on)} - #{journal.user}" - title << " (#{l(:field_private_notes)})" if journal.private_notes? - pdf.RDMCell(190,5, title) - pdf.Ln - pdf.SetFontStyle('I',8) - details_to_strings(journal.visible_details, true).each do |string| - pdf.RDMMultiCell(190,5, "- " + string) - end - if journal.notes? - pdf.Ln unless journal.details.empty? - pdf.SetFontStyle('',8) - pdf.RDMwriteHTMLCell(190,5,0,0, - journal.notes.to_s, issue.attachments, "") - end - pdf.Ln - end - end - - if issue.attachments.any? - pdf.SetFontStyle('B',9) - pdf.RDMCell(190,5, l(:label_attachment_plural), "B") - pdf.Ln - for attachment in issue.attachments - pdf.SetFontStyle('',8) - pdf.RDMCell(80,5, attachment.filename) - pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R") - pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R") - pdf.RDMCell(65,5, attachment.author.name,0,0,"R") - pdf.Ln - end - end - pdf.Output - end - - # Returns a PDF string of a set of wiki pages - def wiki_pages_to_pdf(pages, project) - pdf = ITCPDF.new(current_language) - pdf.SetTitle(project.name) - pdf.alias_nb_pages - pdf.footer_date = format_date(Date.today) - pdf.AddPage - pdf.SetFontStyle('B',11) - pdf.RDMMultiCell(190,5, project.name) - pdf.Ln - # Set resize image scale - pdf.SetImageScale(1.6) - pdf.SetFontStyle('',9) - write_page_hierarchy(pdf, pages.group_by(&:parent_id)) - pdf.Output - end - - # Returns a PDF string of a single wiki page - def wiki_page_to_pdf(page, project) - pdf = ITCPDF.new(current_language) - pdf.SetTitle("#{project} - #{page.title}") - pdf.alias_nb_pages - pdf.footer_date = format_date(Date.today) - pdf.AddPage - pdf.SetFontStyle('B',11) - pdf.RDMMultiCell(190,5, - "#{project} - #{page.title} - # #{page.content.version}") - pdf.Ln - # Set resize image scale - pdf.SetImageScale(1.6) - pdf.SetFontStyle('',9) - write_wiki_page(pdf, page) - pdf.Output - end - - def write_page_hierarchy(pdf, pages, node=nil, level=0) - if pages[node] - pages[node].each do |page| - if @new_page - pdf.AddPage - else - @new_page = true - end - pdf.Bookmark page.title, level - write_wiki_page(pdf, page) - write_page_hierarchy(pdf, pages, page.id, level + 1) if pages[page.id] - end - end - end - - def write_wiki_page(pdf, page) - pdf.RDMwriteHTMLCell(190,5,0,0, - page.content.text.to_s, page.attachments, 0) - if page.attachments.any? - pdf.Ln - pdf.SetFontStyle('B',9) - pdf.RDMCell(190,5, l(:label_attachment_plural), "B") - pdf.Ln - for attachment in page.attachments - pdf.SetFontStyle('',8) - pdf.RDMCell(80,5, attachment.filename) - pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R") - pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R") - pdf.RDMCell(65,5, attachment.author.name,0,0,"R") - pdf.Ln - end - end - end - - class RDMPdfEncoding - def self.rdm_from_utf8(txt, encoding) - txt ||= '' - txt = Redmine::CodesetUtil.from_utf8(txt, encoding) - if txt.respond_to?(:force_encoding) - txt.force_encoding('ASCII-8BIT') - end - txt - end - - def self.attach(attachments, filename, encoding) - filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding) - atta = nil - if filename_utf8 =~ /^[^\/"]+\.(gif|jpg|jpe|jpeg|png)$/i - atta = Attachment.latest_attach(attachments, filename_utf8) - end - if atta && atta.readable? && atta.visible? - return atta - else - return nil - end - end - end - end - end -end diff --git a/lib/redmine_workflow_hidden_fields/issue_patch.rb b/lib/redmine_workflow_hidden_fields/issue_patch.rb index 0de2869..3d3824c 100644 --- a/lib/redmine_workflow_hidden_fields/issue_patch.rb +++ b/lib/redmine_workflow_hidden_fields/issue_patch.rb @@ -1,72 +1,76 @@ module RedmineWorkflowHiddenFields - module IssuePatch - def self.included(base) - base.send(:include, InstanceMethods) - base.class_eval do - unloadable - alias_method_chain :safe_attribute_names, :hidden - end - end + module IssuePatch + def self.included(base) + base.send(:include, InstanceMethods) + base.class_eval do + unloadable + alias_method_chain :safe_attribute_names, :hidden + alias_method_chain :visible_custom_field_values, :hidden + alias_method_chain :read_only_attribute_names, :hidden + alias_method_chain :each_notification, :hidden + end + end - module InstanceMethods + module InstanceMethods - def visible_custom_field_values(user=nil) - user_real = user || User.current - fields = custom_field_values.select do |value| - value.custom_field.visible_by?(project, user_real) - end - fields = fields & viewable_custom_field_values(user_real) - fields - end + def visible_custom_field_values_with_hidden(user=nil) + user_real = user || User.current + fields = custom_field_values.select do |value| + value.custom_field.visible_by?(project, user_real) + end + fields = fields & viewable_custom_field_values(user_real) + fields + end - # Returns the custom_field_values that can be viewed by the given user - # For now: just exclude Fix Info and RNs, as it is printed seperately 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 custom_field_values that can be viewed by the given user + # For now: just exclude Fix Info and RNs, as it is printed seperately 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 - def read_only_attribute_names(user=nil) - workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'readonly' and rule != 'hidden'}.keys - end + def read_only_attribute_names_with_hidden(user=nil) + workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'readonly' and rule !='hidden'}.keys + end - # Same as above, but for hidden fields - def hidden_attribute_names(user=nil) - workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'hidden'}.keys - end + # Same as above, but for hidden fields + def hidden_attribute_names(user=nil) + workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'hidden'}.keys + 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 true if the attribute should be hidden for user + def hidden_attribute?(name, user=nil) + hidden_attribute_names(user).include?(name.to_s) + end - def each_notification(users, &block) - if users.any? - variations = users.collect { - |user| (hidden_attribute_names(user) + (custom_field_values - visible_custom_field_values(user))).uniq - }.uniq - recipient_groups = Array.new(variations.count) { Array.new } - users.each do |user| - recipient_groups[variations.index(hidden_attribute_names(user))] << user - end - recipient_groups.each do |group| - yield(group) - end - end - end + def each_notification_with_hidden(users, &block) + if users.any? + variations = users.collect { + |user| (hidden_attribute_names(user) + (custom_field_values - visible_custom_field_values(user))).uniq + }.uniq + recipient_groups = Array.new(variations.count) { Array.new } + users.each do |user| +# recipient_groups[variations.index(hidden_attribute_names(user))] << user + recipient_groups[variations.index(hidden_attribute_names(user) + (custom_field_values - visible_custom_field_values(user)))] << user + end + recipient_groups.each do |group| + yield(group) + end + end + end - def safe_attribute_names_with_hidden(user=nil) - names = safe_attribute_names_without_hidden - names -= hidden_attribute_names(user) - names - end + def safe_attribute_names_with_hidden(user=nil) + names = safe_attribute_names_without_hidden(user) + names -= hidden_attribute_names(user) + names + end - end - end + end + end end diff --git a/lib/redmine_workflow_hidden_fields/issue_query_patch.rb b/lib/redmine_workflow_hidden_fields/issue_query_patch.rb index 8ec117e..54e2102 100644 --- a/lib/redmine_workflow_hidden_fields/issue_query_patch.rb +++ b/lib/redmine_workflow_hidden_fields/issue_query_patch.rb @@ -1,54 +1,45 @@ module RedmineWorkflowHiddenFields - module IssueQueryPatch - def self.included(base) - base.send(:include, InstanceMethods) - base.class_eval do - unloadable - #alias_method_chain :initialize_available_filters, :hidden - alias_method_chain :available_columns, :hidden - alias_method_chain :available_filters, :hidden - end - end - - module InstanceMethods - - def available_columns_with_hidden - - @available_columns = available_columns_without_hidden - - if project == nil - hidden_fields = [] - all_projects.each { |prj| - if prj.visible? and User.current.roles_for_project(prj).count > 0 - hidden_fields = hidden_fields == [] ? prj.completely_hidden_attribute_names : hidden_fields & prj.completely_hidden_attribute_names - end - } - else - hidden_fields = project.completely_hidden_attribute_names - end - hidden_fields.map! {|field| field.sub(/_id$/, '')} - - @available_columns.reject! {|column| - hidden_fields.include?(column.name.to_s) - } - - @available_columns - end - - def available_filters_with_hidden - @available_filters = available_filters_without_hidden - hidden_fields.each {|field| - delete_available_filter field - if field == "assigned_to_id" then - delete_available_filter "assigned_to_role" - end - if field == "assigned_to_id" then - delete_available_filter "member_of_group" - end - } - @available_filters - end - - end - end + module IssueQueryPatch + def self.included(base) + base.send(:include, InstanceMethods) + base.class_eval do + unloadable + #alias_method_chain :initialize_available_filters, :hidden + alias_method_chain :available_columns, :hidden + alias_method_chain :available_filters, :hidden + end + end + + module InstanceMethods + + def available_columns_with_hidden + unless @available_columns + @available_columns = available_columns_without_hidden + fields = completely_hidden_fields.map {|field| field.sub(/_id$/, '')} + + @available_columns.reject! {|column| + fields.include?(column.name.to_s) + } + end + @available_columns + end + + + def available_filters_with_hidden + unless @available_filters + @available_filters = available_filters_without_hidden + completely_hidden_fields.each {|field| + delete_available_filter field + if field == "assigned_to_id" then + delete_available_filter "assigned_to_role" + end + if field == "assigned_to_id" then + delete_available_filter "member_of_group" + end + } + end + @available_filters + end + end + end end diff --git a/lib/redmine_workflow_hidden_fields/issues_helper_patch.rb b/lib/redmine_workflow_hidden_fields/issues_helper_patch.rb index 223331c..e53c512 100644 --- a/lib/redmine_workflow_hidden_fields/issues_helper_patch.rb +++ b/lib/redmine_workflow_hidden_fields/issues_helper_patch.rb @@ -1,67 +1,73 @@ module RedmineWorkflowHiddenFields - module IssuesHelperPatch - def self.included(base) - base.send(:include, InstanceMethods) - base.class_eval do - unloadable - alias_method_chain :email_issue_attributes, :hidden - alias_method_chain :details_to_strings, :hidden - end - end + module IssuesHelperPatch + def self.included(base) + base.send(:include, InstanceMethods) + base.class_eval do + unloadable + alias_method_chain :email_issue_attributes, :hidden + alias_method_chain :details_to_strings, :hidden + end + end - module InstanceMethods + module InstanceMethods - def email_issue_attributes_with_hidden(issue, user) - items = [] - %w(author status priority assigned_to category fixed_version).each do |attribute| - unless issue.disabled_core_fields.include?(attribute+"_id") or issue.hidden_attribute_names(user).include?(attribute+"_id") - items << "#{l("field_#{attribute}")}: #{issue.send attribute}" - end - end - issue.visible_custom_field_values(user).each do |value| - items << "#{value.custom_field.name}: #{show_value(value, false)}" - end - items - end + def email_issue_attributes_with_hidden(issue, user) + items = [] + %w(author status priority assigned_to category fixed_version).each do |attribute| + unless issue.disabled_core_fields.include?(attribute+"_id") or issue.hidden_attribute_names(user).include?(attribute+"_id") + items << "#{l("field_#{attribute}")}: #{issue.send attribute}" + end + end + issue.visible_custom_field_values(user).each do |value| + items << "#{value.custom_field.name}: #{show_value(value, false)}" + end + items + end - def details_to_strings_with_hidden(details, no_html=false, options={}) - options[:only_path] = (options[:only_path] == false ? false : true) - strings = [] - values_by_field = {} - details.each do |detail| - unless (detail.property == 'cf' || detail.property == 'attr') && detail.journal.issue.hidden_attribute?(detail.prop_key, options[:user]) - if detail.property == 'cf' - field = detail.custom_field - if field && field.multiple? - values_by_field[field] ||= {:added => [], :deleted => []} - if detail.old_value - values_by_field[field][:deleted] << detail.old_value - end - if detail.value - values_by_field[field][:added] << detail.value - end - next - end - end - end - strings << show_detail(detail, no_html, options) - end - values_by_field.each do |field, changes| - detail = JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s) - unless 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 - end - - end - end + def details_to_strings_with_hidden(details, no_html=false, options={}) + options[:only_path] = (options[:only_path] == false ? false : true) + strings = [] + values_by_field = {} + details.each do |detail| + unless (detail.property == 'cf' || detail.property == 'attr') && detail.journal.issue.hidden_attribute?(detail.prop_key, options[:user]) + if detail.property == 'cf' + field = detail.custom_field + if field && field.multiple? + values_by_field[field] ||= {:added => [], :deleted => []} + if detail.old_value + values_by_field[field][:deleted] << detail.old_value + end + if detail.value + values_by_field[field][:added] << detail.value + end + next + end + end + strings << show_detail(detail, no_html, options) + end + end + if values_by_field.present? + multiple_values_detail = Struct.new(:property, :prop_key, :custom_field, :old_value, :value) + values_by_field.each do |field, changes| + unless details.first.journal.issue.hidden_attribute?(field.id.to_s, options[:user]) + if changes[:added].any? + detail = multiple_values_detail.new('cf', field.id.to_s, field) + detail.value = changes[:added] + strings << show_detail(detail, no_html, options) + end + if changes[:deleted].any? + detail = multiple_values_detail.new('cf', field.id.to_s, field) + detail.old_value = changes[:deleted] + strings << show_detail(detail, no_html, options) + end + end + end + end + strings + end + end + end end + + + \ No newline at end of file diff --git a/lib/redmine_workflow_hidden_fields/issues_pdf_helper_patch.rb b/lib/redmine_workflow_hidden_fields/issues_pdf_helper_patch.rb new file mode 100644 index 0000000..febb29e --- /dev/null +++ b/lib/redmine_workflow_hidden_fields/issues_pdf_helper_patch.rb @@ -0,0 +1,272 @@ +module RedmineWorkflowHiddenFields + module IssuesPdfHelperPatch + def self.included(base) + base.send(:include, InstanceMethods) + base.class_eval do + unloadable + alias_method_chain :issue_to_pdf, :hidden + alias_method_chain :fetch_row_values, :hidden + end + end + + module InstanceMethods + # Returns a PDF string of a single issue + def issue_to_pdf_with_hidden(issue, assoc={}) + pdf = Redmine::Export::PDF::ITCPDF.new(current_language) + pdf.set_title("#{issue.project} - #{issue.tracker} ##{issue.id}") + pdf.alias_nb_pages + pdf.footer_date = format_date(Date.today) + pdf.add_page + pdf.SetFontStyle('B',11) + buf = "#{issue.project} - #{issue.tracker} ##{issue.id}" + pdf.RDMMultiCell(190, 5, buf) + pdf.SetFontStyle('',8) + base_x = pdf.get_x + i = 1 + issue.ancestors.visible.each do |ancestor| + pdf.set_x(base_x + i) + buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}" + pdf.RDMMultiCell(190 - i, 5, buf) + i += 1 if i < 35 + end + pdf.SetFontStyle('B',11) + pdf.RDMMultiCell(190 - i, 5, issue.subject.to_s) + pdf.SetFontStyle('',8) + pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}") + pdf.ln + +#Modifications start around here + stdcols = [] + stdcols << [l(:field_status), issue.status] unless issue.hidden_attribute?('status') + stdcols << [l(:field_priority), issue.priority] unless issue.hidden_attribute?('priority_id') + stdcols << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id') or issue.hidden_attribute?('assigned_to_id') + stdcols << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id') or issue.hidden_attribute?('category_id') + stdcols << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id') or issue.hidden_attribute?('fixed_version_id') + + + stdcols << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date') or issue.hidden_attribute?('start_date') + stdcols << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date') or issue.hidden_attribute?('due_date') + stdcols << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio') or issue.hidden_attribute?('done_ratio') + stdcols << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours') or issue.hidden_attribute?('estimated_hours') + stdcols << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project) + + left = [] + right = [] + stdcols.each_with_index do |value, i| + if i % 2 == 0 + left << value + else + right << value + end + end +#Modifications end here + + rows = left.size > right.size ? left.size : right.size + while left.size < rows + left << nil + end + while right.size < rows + right << nil + end + + half = (issue.visible_custom_field_values.size / 2.0).ceil + issue.visible_custom_field_values.each_with_index do |custom_value, i| + (i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value, false)] + end + + if pdf.get_rtl + border_first_top = 'RT' + border_last_top = 'LT' + border_first = 'R' + border_last = 'L' + else + border_first_top = 'LT' + border_last_top = 'RT' + border_first = 'L' + border_last = 'R' + end + + rows = left.size > right.size ? left.size : right.size + rows.times do |i| + heights = [] + pdf.SetFontStyle('B',9) + item = left[i] + heights << pdf.get_string_height(35, item ? "#{item.first}:" : "") + item = right[i] + heights << pdf.get_string_height(35, item ? "#{item.first}:" : "") + pdf.SetFontStyle('',9) + item = left[i] + heights << pdf.get_string_height(60, item ? item.last.to_s : "") + item = right[i] + heights << pdf.get_string_height(60, item ? item.last.to_s : "") + height = heights.max + + item = left[i] + pdf.SetFontStyle('B',9) + pdf.RDMMultiCell(35, height, item ? "#{item.first}:" : "", (i == 0 ? border_first_top : border_first), '', 0, 0) + pdf.SetFontStyle('',9) + pdf.RDMMultiCell(60, height, item ? item.last.to_s : "", (i == 0 ? border_last_top : border_last), '', 0, 0) + + item = right[i] + pdf.SetFontStyle('B',9) + pdf.RDMMultiCell(35, height, item ? "#{item.first}:" : "", (i == 0 ? border_first_top : border_first), '', 0, 0) + pdf.SetFontStyle('',9) + pdf.RDMMultiCell(60, height, item ? item.last.to_s : "", (i == 0 ? border_last_top : border_last), '', 0, 2) + + pdf.set_x(base_x) + end + + pdf.SetFontStyle('B',9) + pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1) + pdf.SetFontStyle('',9) + + # Set resize image scale + pdf.set_image_scale(1.6) + text = textilizable(issue, :description, + :only_path => false, + :edit_section_links => false, + :headings => false, + :inline_attachments => false + ) + pdf.RDMwriteFormattedCell(35+155, 5, '', '', text, issue.attachments, "LRB") + + unless issue.leaf? + truncate_length = (!is_cjk? ? 90 : 65) + pdf.SetFontStyle('B',9) + pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR") + pdf.ln + issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level| + buf = "#{child.tracker} # #{child.id}: #{child.subject}". + truncate(truncate_length) + level = 10 if level >= 10 + pdf.SetFontStyle('',8) + pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, border_first) + pdf.SetFontStyle('B',8) + pdf.RDMCell(20,5, child.status.to_s, border_last) + pdf.ln + end + end + + relations = issue.relations.select { |r| r.other_issue(issue).visible? } + unless relations.empty? + truncate_length = (!is_cjk? ? 80 : 60) + pdf.SetFontStyle('B',9) + pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR") + pdf.ln + relations.each do |relation| + buf = relation.to_s(issue) {|other| + text = "" + if Setting.cross_project_issue_relations? + text += "#{relation.other_issue(issue).project} - " + end + text += "#{other.tracker} ##{other.id}: #{other.subject}" + text + } + buf = buf.truncate(truncate_length) + pdf.SetFontStyle('', 8) + pdf.RDMCell(35+155-60, 5, buf, border_first) + pdf.SetFontStyle('B',8) + pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "") + pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "") + pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), border_last) + pdf.ln + end + end + pdf.RDMCell(190,5, "", "T") + pdf.ln + + if issue.changesets.any? && + User.current.allowed_to?(:view_changesets, issue.project) + pdf.SetFontStyle('B',9) + pdf.RDMCell(190,5, l(:label_associated_revisions), "B") + pdf.ln + for changeset in issue.changesets + pdf.SetFontStyle('B',8) + csstr = "#{l(:label_revision)} #{changeset.format_identifier} - " + csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s + pdf.RDMCell(190, 5, csstr) + pdf.ln + unless changeset.comments.blank? + pdf.SetFontStyle('',8) + pdf.RDMwriteHTMLCell(190,5,'','', + changeset.comments.to_s, issue.attachments, "") + end + pdf.ln + end + end + + if assoc[:journals].present? + pdf.SetFontStyle('B',9) + pdf.RDMCell(190,5, l(:label_history), "B") + pdf.ln + assoc[:journals].each do |journal| + pdf.SetFontStyle('B',8) + title = "##{journal.indice} - #{format_time(journal.created_on)} - #{journal.user}" + title << " (#{l(:field_private_notes)})" if journal.private_notes? + pdf.RDMCell(190,5, title) + pdf.ln + pdf.SetFontStyle('I',8) + details_to_strings(journal.visible_details, true).each do |string| + pdf.RDMMultiCell(190,5, "- " + string) + end + if journal.notes? + pdf.ln unless journal.details.empty? + pdf.SetFontStyle('',8) + text = textilizable(journal, :notes, + :only_path => false, + :edit_section_links => false, + :headings => false, + :inline_attachments => false + ) + pdf.RDMwriteFormattedCell(190,5,'','', text, issue.attachments, "") + end + pdf.ln + end + end + + if issue.attachments.any? + pdf.SetFontStyle('B',9) + pdf.RDMCell(190,5, l(:label_attachment_plural), "B") + pdf.ln + for attachment in issue.attachments + pdf.SetFontStyle('',8) + pdf.RDMCell(80,5, attachment.filename) + pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R") + pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R") + pdf.RDMCell(65,5, attachment.author.name,0,0,"R") + pdf.ln + end + end + pdf.output + end + + + # fetch row values + def fetch_row_values_with_hidden(issue, query, level) + query.inline_columns.collect do |column| + if issue.hidden_attribute?(column.name.to_s + '_id') + "" + else + s = if column.is_a?(QueryCustomFieldColumn) + cv = issue.visible_custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id} + show_value(cv, false) + else + value = issue.send(column.name) + if column.name == :subject + value = " " * level + value + end + if value.is_a?(Date) + format_date(value) + elsif value.is_a?(Time) + format_time(value) + else + value + end + end + s.to_s + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/redmine_workflow_hidden_fields/journal_patch.rb b/lib/redmine_workflow_hidden_fields/journal_patch.rb index 19e6e8f..935da41 100644 --- a/lib/redmine_workflow_hidden_fields/journal_patch.rb +++ b/lib/redmine_workflow_hidden_fields/journal_patch.rb @@ -1,30 +1,25 @@ module RedmineWorkflowHiddenFields - module JournalPatch - def self.included(base) - base.send(:include, InstanceMethods) - base.class_eval do - unloadable - alias_method_chain :visible_details, :hidden - end - end + module JournalPatch + def self.included(base) + base.send(:include, InstanceMethods) + base.class_eval do + unloadable + alias_method_chain :visible_details, :hidden + end + end - module InstanceMethods - - # Returns journal details that are visible to user - def visible_details_with_hidden(user=User.current) - details.select do |detail| - if detail.property == 'attr' - !issue.hidden_attribute?(detail.prop_key, user) - elsif detail.property == 'cf' - detail.custom_field && detail.custom_field.visible_by?(project, user) && !issue.hidden_attribute?(detail.prop_key, user) - elsif detail.property == 'relation' - Issue.find_by_id(detail.value || detail.old_value).try(:visible?, user) - else - true - end - end - end - - end - end -end + module InstanceMethods + # Returns journal details that are visible to user + def visible_details_with_hidden(user=User.current) + dets = visible_details_without_hidden(user) + dets.select do |detail| + if detail.property == 'attr' or detail.property == 'cf' + !issue.hidden_attribute?(detail.prop_key, user) + else + true + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/redmine_workflow_hidden_fields/project_patch.rb b/lib/redmine_workflow_hidden_fields/project_patch.rb index 51bff17..0638e63 100644 --- a/lib/redmine_workflow_hidden_fields/project_patch.rb +++ b/lib/redmine_workflow_hidden_fields/project_patch.rb @@ -1,41 +1,47 @@ module RedmineWorkflowHiddenFields - module ProjectPatch - def self.included(base) - base.send(:include, InstanceMethods) - base.class_eval do - unloadable - end - end + module ProjectPatch + def self.included(base) + base.send(:include, InstanceMethods) + base.class_eval do + unloadable + end + end - module InstanceMethods + module InstanceMethods - # 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 + # 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=User.current) + roles = user.admin ? Role.all : user.roles_for_project(self) + + result = [] + unless roles.empty? + 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 |hash, permission| + hash[permission.field_name] ||= [] + hash[permission.field_name] << permission.rule + hash + end + workflow_rules.each do |attr, rules| + result << attr if rules.size >= (roles.size * trackers.size * IssueStatus.all.size) + end + end + else + result = Tracker.core_fields(trackers) + result << self.all_issue_custom_fields.map {|field| field.id.to_s} + end + + + result += Tracker.disabled_core_fields(trackers) + + result += IssueCustomField. + sorted. + where("is_for_all = ? AND id NOT IN (SELECT DISTINCT cfp.custom_field_id" + + " FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} cfp" + + " WHERE cfp.project_id = ?)", false, id).map {|field| field.id.to_s} + result + end - 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 - - end - end + end + end end diff --git a/lib/redmine_workflow_hidden_fields/query_column_patch.rb b/lib/redmine_workflow_hidden_fields/query_column_patch.rb index f7d4fcf..e3c4b01 100644 --- a/lib/redmine_workflow_hidden_fields/query_column_patch.rb +++ b/lib/redmine_workflow_hidden_fields/query_column_patch.rb @@ -1,29 +1,31 @@ module RedmineWorkflowHiddenFields - module QueryColumnPatch - def self.included(base) - base.send(:include, InstanceMethods) - base.class_eval do - unloadable - alias_method_chain :value, :hidden - end - end + module QueryColumnPatch + def self.included(base) + base.send(:include, InstanceMethods) + base.class_eval do + unloadable + alias_method_chain :value, :hidden + alias_method_chain :value_object, :hidden + end + end - module InstanceMethods - - def value_with_hidden(object) - object.send name - if object.respond_to?(:hidden_attribute_names) - hidden_fields = object.hidden_attribute_names.map {|field| field.sub(/_id$/, '')} - if hidden_fields.include?(name.to_s) - "" - else - object.send name - end - else - object.send name - end - end - - end - end + module InstanceMethods + def value_with_hidden(object) + if object.respond_to?(:hidden_attribute_names) + hidden_fields = object.hidden_attribute_names.map {|field| field.sub(/_id$/, '')} + if hidden_fields.include?(name.to_s) + "" + else + object.send name + end + else + object.send name + end + end + + def value_object_with_hidden(object) + value_with_hidden(object) + end + end + end end diff --git a/lib/redmine_workflow_hidden_fields/query_custom_field_column_patch.rb b/lib/redmine_workflow_hidden_fields/query_custom_field_column_patch.rb new file mode 100644 index 0000000..31b1b70 --- /dev/null +++ b/lib/redmine_workflow_hidden_fields/query_custom_field_column_patch.rb @@ -0,0 +1,31 @@ +module RedmineWorkflowHiddenFields + module QueryCustomFieldColumnPatch + def self.included(base) + base.send(:include, InstanceMethods) + base.class_eval do + unloadable + alias_method_chain :value_object, :hidden + end + end + + module InstanceMethods + def visible?(object) + if object.respond_to?(:hidden_attribute_names) + hidden_fields = object.hidden_attribute_names.map {|field| field.sub(/_id$/, '')} + !hidden_fields.include?(custom_field.id.to_s) + else + true + end + end + + def value_object_with_hidden(object) + if custom_field.visible_by?(object.project, User.current) and visible?(object) + cv = object.custom_values.select {|v| v.custom_field_id == @cf.id} + cv.size > 1 ? cv.sort {|a,b| a.value.to_s <=> b.value.to_s} : cv.first + else + nil + end + end + end + end +end diff --git a/lib/redmine_workflow_hidden_fields/query_patch.rb b/lib/redmine_workflow_hidden_fields/query_patch.rb index 5b08c63..225cd98 100644 --- a/lib/redmine_workflow_hidden_fields/query_patch.rb +++ b/lib/redmine_workflow_hidden_fields/query_patch.rb @@ -1,59 +1,92 @@ module RedmineWorkflowHiddenFields - module QueryPatch - def self.included(base) - base.send(:include, InstanceMethods) - base.class_eval do - unloadable - alias_method_chain :available_filters, :hidden - alias_method_chain :add_custom_field_filter, :hidden - end - end - - - module InstanceMethods - - def available_filters_with_hidden - unless @available_filters - initialize_available_filters - @available_filters.each do |field, options| - options[:name] ||= l(options[:label] || "field_#{field}".gsub(/_id$/, '')) - end - end - hidden_fields.each {|field| - @available_filters.delete field - } - @available_filters - end - - def hidden_fields - return @hidden_fields if @hidden_fields - if project != nil - @hidden_fields = project.completely_hidden_attribute_names - else - @hidden_fields = [] - usr = User.current; - first = true - all_projects.each { |prj| - if prj.visible? and usr.roles_for_project(prj).count > 0 - if first - @hidden_fields = prj.completely_hidden_attribute_names(usr) - else - @hidden_fields &= prj.completely_hidden_attribute_names(usr) - end - return @hidden_fields if @hidden_fields.empty? - end - first = false - } - end - return @hidden_fields - end - - def add_custom_field_filter_with_hidden(field, assoc=nil) - unless hidden_fields.include?(field.id.to_s) - add_custom_field_filter_without_hidden(field, assoc) - end - end - - end - end + module QueryPatch + def self.included(base) + base.send(:include, InstanceMethods) + base.class_eval do + unloadable + alias_method_chain :available_filters, :hidden + alias_method_chain :add_custom_field_filter, :hidden + alias_method_chain :columns, :hidden + alias_method_chain :available_inline_columns, :hidden + alias_method_chain :available_block_columns, :hidden + alias_method_chain :groupable_columns, :hidden + alias_method_chain :sortable_columns, :hidden + end + end + + + module InstanceMethods + + def available_and_visible_columns + available_columns.reject{|col| col.respond_to?(:custom_field) ? completely_hidden_fields.include?(col.custom_field.id.to_s) : completely_hidden_fields.include?(col.name)} + end + + # Returns an array of columns that can be used to group the results + def groupable_columns_with_hidden + available_and_visible_columns.select {|c| c.groupable} + end + + # Returns a Hash of columns and the key for sorting + def sortable_columns_with_hidden + available_and_visible_columns.inject({}) {|h, column| + h[column.name.to_s] = column.sortable + h + } + end + + def available_inline_columns_with_hidden + available_and_visible_columns.select(&:inline?) + end + + def available_block_columns_with_hidden + available_and_visible_columns.reject(&:inline?) + end + + + def columns_with_hidden + columns_without_hidden.reject{|col| col.respond_to?(:custom_field) ? completely_hidden_fields.include?(col.custom_field.id.to_s) : completely_hidden_fields.include?(col.name)} + end + + def available_filters_with_hidden + unless @available_filters + available_filters_without_hidden + completely_hidden_fields.each {|field| + @available_filters.delete field + } + end + @available_filters + end + + def completely_hidden_fields + unless @completely_hidden_fields + if project != nil + @completely_hidden_fields = project.completely_hidden_attribute_names + else + @completely_hidden_fields = [] + usr = User.current; + first = true + all_projects.each { |prj| + if usr.roles_for_project(prj).count > 0 + if first + @completely_hidden_fields = prj.completely_hidden_attribute_names(usr) + else + @completely_hidden_fields &= prj.completely_hidden_attribute_names(usr) + end + return @completely_hidden_fields if @completely_hidden_fields.empty? + first = false + end + } + end + end + return @completely_hidden_fields + end + + def add_custom_field_filter_with_hidden(field, assoc=nil) + unless completely_hidden_fields.include?(field.id.to_s) + add_custom_field_filter_without_hidden(field, assoc) + end + end + + end + end end diff --git a/lib/redmine_workflow_hidden_fields/workflow_permission_patch.rb b/lib/redmine_workflow_hidden_fields/workflow_permission_patch.rb index 433a963..b6cdf12 100644 --- a/lib/redmine_workflow_hidden_fields/workflow_permission_patch.rb +++ b/lib/redmine_workflow_hidden_fields/workflow_permission_patch.rb @@ -1,9 +1,7 @@ module RedmineWorkflowHiddenFields module WorkflowPermissionPatch def self.included(base) - base.send(:include, InstanceMethods) base.class_eval do - # The filters are part of validators are raw, they can be skipped with the following way. rule_inclusion_validation = base._validators[:rule].find{ |validator| validator.is_a? ActiveModel::Validations::InclusionValidator } base._validators[:rule].delete(rule_inclusion_validation) @@ -13,9 +11,5 @@ def self.included(base) validates_inclusion_of :rule, :in => %w(readonly required hidden) end end - - module InstanceMethods - - end end end diff --git a/lib/redmine_workflow_hidden_fields/workflows_helper_patch.rb b/lib/redmine_workflow_hidden_fields/workflows_helper_patch.rb index b8c1f67..90d7bc2 100644 --- a/lib/redmine_workflow_hidden_fields/workflows_helper_patch.rb +++ b/lib/redmine_workflow_hidden_fields/workflows_helper_patch.rb @@ -1,34 +1,45 @@ module RedmineWorkflowHiddenFields - module WorkflowsHelperPatch - def self.included(base) - base.send(:include, InstanceMethods) - base.class_eval do - unloadable - alias_method_chain :field_permission_tag, :hidden - end - end - - module InstanceMethods - - def field_permission_tag_with_hidden(permissions, status, field, role) - name = field.is_a?(CustomField) ? field.id.to_s : field - options = [["", ""], [l(:label_readonly), "readonly"]] - options << [l(:label_hidden), "hidden"] - options << [l(:label_required), "required"] unless field_required?(field) - html_options = {} - selected = permissions[status.id][name] - - hidden = field.is_a?(CustomField) && !field.visible? && !role.custom_fields.to_a.include?(field) - if hidden - options[0][0] = l(:label_hidden) - selected = '' - html_options[:disabled] = true - end - - select_tag("permissions[#{name}][#{status.id}]", options_for_select(options, selected), html_options) - end - - end - end + module WorkflowsHelperPatch + def self.included(base) + base.send(:include, InstanceMethods) + base.class_eval do + unloadable + alias_method_chain :field_permission_tag, :hidden + end + end + + module InstanceMethods + + def field_permission_tag_with_hidden(permissions, status, field, roles) + name = field.is_a?(CustomField) ? field.id.to_s : field + options = [["", ""], [l(:label_readonly), "readonly"]] + options << [l(:label_hidden), "hidden"] + options << [l(:label_required), "required"] unless field_required?(field) + html_options = {} + + if perm = permissions[status.id][name] + if perm.uniq.size > 1 || perm.size < @roles.size * @trackers.size + options << [l(:label_no_change_option), "no_change"] + selected = 'no_change' + else + selected = perm.first + end + end + + hidden = field.is_a?(CustomField) && + !field.visible? && + !roles.detect {|role| role.custom_fields.to_a.include?(field)} + + if hidden + options[0][0] = l(:label_hidden) + selected = '' + html_options[:disabled] = true + end + + select_tag("permissions[#{status.id}][#{name}]", options_for_select(options, selected), html_options) + end + + end + end end \ No newline at end of file