Feature #12005 » workflow_hidden_field_DR_v0.05.patch
| app/helpers/issues_helper.rb (working copy) | ||
|---|---|---|
| 146 | 146 |
end |
| 147 | 147 | |
| 148 | 148 |
def render_custom_fields_rows(issue) |
| 149 |
return if issue.custom_field_values.empty? |
|
| 149 |
local_viewablecf=issue.viewable_custom_field_values |
|
| 150 |
return if local_viewablecf.empty? |
|
| 150 | 151 |
ordered_values = [] |
| 151 |
half = (issue.custom_field_values.size / 2.0).ceil
|
|
| 152 |
half = (local_viewablecf.size / 2.0).ceil
|
|
| 152 | 153 |
half.times do |i| |
| 153 |
ordered_values << issue.custom_field_values[i]
|
|
| 154 |
ordered_values << issue.custom_field_values[i + half]
|
|
| 154 |
ordered_values << local_viewablecf[i]
|
|
| 155 |
ordered_values << local_viewablecf[i + half]
|
|
| 155 | 156 |
end |
| 156 | 157 |
s = "<tr>\n" |
| 157 | 158 |
n = 0 |
| ... | ... | |
| 221 | 222 |
strings = [] |
| 222 | 223 |
values_by_field = {}
|
| 223 | 224 |
details.each do |detail| |
| 224 |
if detail.property == 'cf' |
|
| 225 |
field_id = detail.prop_key |
|
| 226 |
field = CustomField.find_by_id(field_id) |
|
| 227 |
if field && field.multiple? |
|
| 228 |
values_by_field[field_id] ||= {:added => [], :deleted => []}
|
|
| 229 |
if detail.old_value |
|
| 230 |
values_by_field[field_id][:deleted] << detail.old_value |
|
| 231 |
end |
|
| 232 |
if detail.value |
|
| 233 |
values_by_field[field_id][:added] << detail.value |
|
| 234 |
end |
|
| 235 |
next |
|
| 236 |
end |
|
| 225 |
unless detail.journal.issue.hidden_attribute?(detail.prop_key, options[:user]) |
|
| 226 |
if detail.property == 'cf' |
|
| 227 |
field_id = detail.prop_key |
|
| 228 |
field = CustomField.find_by_id(field_id) |
|
| 229 |
if field && field.multiple? |
|
| 230 |
values_by_field[field_id] ||= {:added => [], :deleted => []}
|
|
| 231 |
if detail.old_value |
|
| 232 |
values_by_field[field_id][:deleted] << detail.old_value |
|
| 233 |
end |
|
| 234 |
if detail.value |
|
| 235 |
values_by_field[field_id][:added] << detail.value |
|
| 236 |
end |
|
| 237 |
next |
|
| 238 |
end |
|
| 239 |
end |
|
| 240 |
strings << show_detail(detail, no_html, options) |
|
| 237 | 241 |
end |
| 238 |
strings << show_detail(detail, no_html, options) |
|
| 239 | 242 |
end |
| 240 | 243 |
values_by_field.each do |field_id, changes| |
| 241 |
detail = JournalDetail.new(:property => 'cf', :prop_key => field_id) |
|
| 242 |
if changes[:added].any? |
|
| 243 |
detail.value = changes[:added] |
|
| 244 |
strings << show_detail(detail, no_html, options) |
|
| 245 |
elsif changes[:deleted].any? |
|
| 246 |
detail.old_value = changes[:deleted] |
|
| 247 |
strings << show_detail(detail, no_html, options) |
|
| 248 |
end |
|
| 244 |
unless detail.journal.issue.hidden_attribute?(detail.prop_key, options[:user]) |
|
| 245 |
detail = JournalDetail.new(:property => 'cf', :prop_key => field_id) |
|
| 246 |
if changes[:added].any? |
|
| 247 |
detail.value = changes[:added] |
|
| 248 |
strings << show_detail(detail, no_html, options) |
|
| 249 |
elsif changes[:deleted].any? |
|
| 250 |
detail.old_value = changes[:deleted] |
|
| 251 |
strings << show_detail(detail, no_html, options) |
|
| 252 |
end |
|
| 253 |
end |
|
| 249 | 254 |
end |
| 250 | 255 |
strings |
| 251 | 256 |
end |
| ... | ... | |
| 381 | 386 |
# csv lines |
| 382 | 387 |
issues.each do |issue| |
| 383 | 388 |
col_values = columns.collect do |column| |
| 384 |
s = if column.is_a?(QueryCustomFieldColumn) |
|
| 385 |
cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id}
|
|
| 386 |
show_value(cv)
|
|
| 389 |
hidden_fields = issue.hidden_attribute_names.map {|field| field.sub(/_id$/, '')}
|
|
| 390 |
if hidden_fields.include?(column.is_a?(QueryCustomFieldColumn) ? column.custom_field.id.to_s : column.name.to_s) |
|
| 391 |
""
|
|
| 387 | 392 |
else |
| 388 |
value = column.value(issue) |
|
| 389 |
if value.is_a?(Date) |
|
| 390 |
format_date(value) |
|
| 391 |
elsif value.is_a?(Time) |
|
| 392 |
format_time(value) |
|
| 393 |
elsif value.is_a?(Float) |
|
| 394 |
("%.2f" % value).gsub('.', decimal_separator)
|
|
| 393 |
s = if column.is_a?(QueryCustomFieldColumn) |
|
| 394 |
cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id}
|
|
| 395 |
show_value(cv) |
|
| 395 | 396 |
else |
| 396 |
value |
|
| 397 |
value = column.value(issue) |
|
| 398 |
if value.is_a?(Date) |
|
| 399 |
format_date(value) |
|
| 400 |
elsif value.is_a?(Time) |
|
| 401 |
format_time(value) |
|
| 402 |
elsif value.is_a?(Float) |
|
| 403 |
("%.2f" % value).gsub('.', decimal_separator)
|
|
| 404 |
else |
|
| 405 |
value |
|
| 406 |
end |
|
| 397 | 407 |
end |
| 408 |
s.to_s |
|
| 398 | 409 |
end |
| 399 |
s.to_s |
|
| 400 | 410 |
end |
| 401 | 411 |
csv << [ issue.id.to_s ] + col_values.collect {|c| Redmine::CodesetUtil.from_utf8(c.to_s, encoding) } +
|
| 402 | 412 |
(options[:description] ? [Redmine::CodesetUtil.from_utf8(issue.description, encoding)] : []) |
| app/helpers/workflows_helper.rb (working copy) | ||
|---|---|---|
| 25 | 25 |
def field_permission_tag(permissions, status, field) |
| 26 | 26 |
name = field.is_a?(CustomField) ? field.id.to_s : field |
| 27 | 27 |
options = [["", ""], [l(:label_readonly), "readonly"]] |
| 28 |
options << [l(:label_hidden), "hidden"] |
|
| 28 | 29 |
options << [l(:label_required), "required"] unless field_required?(field) |
| 29 | 30 | |
| 30 | 31 |
select_tag("permissions[#{name}][#{status.id}]", options_for_select(options, permissions[status.id][name]))
|
| app/models/issue.rb (working copy) | ||
|---|---|---|
| 446 | 446 |
end |
| 447 | 447 |
end |
| 448 | 448 | |
| 449 |
# Returns the custom_field_values that can be viewed by the given user |
|
| 450 |
# For now: just exclude Fix Info and RNs, as it is printed seperately below description. |
|
| 451 |
def viewable_custom_field_values(user=nil) |
|
| 452 |
custom_field_values.reject do |value| |
|
| 453 |
hidden_attribute_names(user).include?(value.custom_field_id.to_s) |
|
| 454 |
end |
|
| 455 |
end |
|
| 456 | ||
| 449 | 457 |
# Returns the names of attributes that are read-only for user or the current user |
| 450 | 458 |
# For users with multiple roles, the read-only fields are the intersection of |
| 451 | 459 |
# read-only fields of each role |
| ... | ... | |
| 455 | 463 |
# issue.read_only_attribute_names # => ['due_date', '2'] |
| 456 | 464 |
# issue.read_only_attribute_names(user) # => [] |
| 457 | 465 |
def read_only_attribute_names(user=nil) |
| 458 |
workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'readonly'}.keys
|
|
| 466 |
workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'readonly' and rule != 'hidden'}.keys
|
|
| 459 | 467 |
end |
| 460 | 468 | |
| 469 |
# Same as above, but for hidden fields |
|
| 470 |
def hidden_attribute_names(user=nil) |
|
| 471 |
workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'hidden'}.keys
|
|
| 472 |
end |
|
| 473 | ||
| 461 | 474 |
# Returns the names of required attributes for user or the current user |
| 462 | 475 |
# For users with multiple roles, the required fields are the intersection of |
| 463 | 476 |
# required fields of each role |
| ... | ... | |
| 475 | 488 |
required_attribute_names(user).include?(name.to_s) |
| 476 | 489 |
end |
| 477 | 490 | |
| 491 |
# Returns true if the attribute should be hidden for user |
|
| 492 |
def hidden_attribute?(name, user=nil) |
|
| 493 |
hidden_attribute_names(user).include?(name.to_s) |
|
| 494 |
end |
|
| 495 | ||
| 496 | ||
| 478 | 497 |
# Returns a hash of the workflow rule by attribute for the given user |
| 479 | 498 |
# |
| 480 | 499 |
# Examples: |
| app/models/issue_observer.rb (working copy) | ||
|---|---|---|
| 16 | 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 17 | 17 | |
| 18 | 18 |
class IssueObserver < ActiveRecord::Observer |
| 19 |
def after_create(issue) |
|
| 20 |
Mailer.issue_add(issue).deliver if Setting.notified_events.include?('issue_added')
|
|
| 19 |
def after_create(issue) |
|
| 20 |
if Setting.notified_events.include?('issue_added')
|
|
| 21 |
recipients = issue.notified_users + issue.watcher_recipient_users |
|
| 22 | ||
| 23 |
if recipients.any? |
|
| 24 |
variations = recipients.collect { |user| journal.issue.hidden_attribute_names(user) }.uniq
|
|
| 25 |
recipient_groups = Array.new(variations.count) { Array.new }
|
|
| 26 |
recipients.each do |user| |
|
| 27 |
recipient_groups[variations.index(journal.issue.hidden_attribute_names(user))] << user |
|
| 28 |
end |
|
| 29 | ||
| 30 |
recipient_groups.each do |group| |
|
| 31 |
Mailer.issue_add(issue, group).deliver |
|
| 32 |
end |
|
| 33 |
end |
|
| 34 |
end |
|
| 21 | 35 |
end |
| 22 | 36 |
end |
| app/models/journal_observer.rb (working copy) | ||
|---|---|---|
| 23 | 23 |
(Setting.notified_events.include?('issue_status_updated') && journal.new_status.present?) ||
|
| 24 | 24 |
(Setting.notified_events.include?('issue_priority_updated') && journal.new_value_for('priority_id').present?)
|
| 25 | 25 |
) |
| 26 |
Mailer.issue_edit(journal).deliver |
|
| 26 | ||
| 27 |
recipients = journal.journalized.notified_users |
|
| 28 |
recipients += journal.journalized.watcher_recipient_users |
|
| 29 |
if journal.private_notes? |
|
| 30 |
recipients = recipients.select {|user| user.allowed_to?(:view_private_notes, journal.journalized.project)}
|
|
| 31 |
end |
|
| 32 |
|
|
| 33 |
if recipients.any? |
|
| 34 |
variations = recipients.collect { |user| journal.issue.hidden_attribute_names(user) }.uniq
|
|
| 35 |
recipient_groups = Array.new(variations.count) { Array.new }
|
|
| 36 |
recipients.each do |user| |
|
| 37 |
recipient_groups[variations.index(journal.issue.hidden_attribute_names(user))] << user |
|
| 38 |
end |
|
| 39 | ||
| 40 | ||
| 41 |
recipient_groups.each do |group| |
|
| 42 |
Mailer.issue_edit(journal, group).deliver |
|
| 43 |
end |
|
| 44 |
end |
|
| 27 | 45 |
end |
| 28 | 46 |
end |
| 29 | 47 |
end |
| app/models/mailer.rb (working copy) | ||
|---|---|---|
| 32 | 32 |
# Example: |
| 33 | 33 |
# issue_add(issue) => Mail::Message object |
| 34 | 34 |
# Mailer.issue_add(issue).deliver => sends an email to issue recipients |
| 35 |
def issue_add(issue) |
|
| 35 |
def issue_add(issue, ausers)
|
|
| 36 | 36 |
redmine_headers 'Project' => issue.project.identifier, |
| 37 | 37 |
'Issue-Id' => issue.id, |
| 38 | 38 |
'Issue-Author' => issue.author.login |
| ... | ... | |
| 41 | 41 |
@author = issue.author |
| 42 | 42 |
@issue = issue |
| 43 | 43 |
@issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue) |
| 44 |
recipients = issue.recipients |
|
| 45 |
cc = issue.watcher_recipients - recipients |
|
| 44 | ||
| 45 |
@auser = ausers[0] |
|
| 46 |
recipients = ausers.map(&:mail) |
|
| 47 |
cc = issue.watcher_recipient_users.map(&:mail).select {|rcpt| recipients.include?(rcpt)}
|
|
| 46 | 48 |
mail :to => recipients, |
| 47 | 49 |
:cc => cc, |
| 48 | 50 |
:subject => "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
|
| 49 | 51 |
end |
| 50 | 52 | |
| 53 | ||
| 54 | ||
| 51 | 55 |
# Builds a Mail::Message object used to email recipients of the edited issue. |
| 52 | 56 |
# |
| 53 | 57 |
# Example: |
| 54 |
# issue_edit(journal) => Mail::Message object |
|
| 55 |
# Mailer.issue_edit(journal).deliver => sends an email to issue recipients
|
|
| 56 |
def issue_edit(journal) |
|
| 58 |
# issue_edit(journal, users) => Mail::Message object
|
|
| 59 |
# Mailer.issue_edit(journal, users).deliver => sends an email to +users+
|
|
| 60 |
def issue_edit(journal, ausers)
|
|
| 57 | 61 |
issue = journal.journalized.reload |
| 58 | 62 |
redmine_headers 'Project' => issue.project.identifier, |
| 59 | 63 |
'Issue-Id' => issue.id, |
| ... | ... | |
| 62 | 66 |
message_id journal |
| 63 | 67 |
references issue |
| 64 | 68 |
@author = journal.user |
| 65 |
recipients = journal.recipients |
|
| 66 |
# Watchers in cc |
|
| 67 |
cc = issue.watcher_recipients - recipients |
|
| 69 |
@auser = ausers[0] |
|
| 70 |
recipients = ausers.map(&:mail) |
|
| 71 |
cc = issue.watcher_recipient_users.map(&:mail).select {|rcpt| recipients.include?(rcpt)}
|
|
| 72 |
recipients.reject! {|rcpt| cc.include?(rcpt)}
|
|
| 73 | ||
| 68 | 74 |
s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
|
| 69 | 75 |
s << "(#{issue.status.name}) " if journal.new_value_for('status_id')
|
| 70 | 76 |
s << issue.subject |
| app/models/project.rb (working copy) | ||
|---|---|---|
| 146 | 146 |
user.allowed_to?(:view_project, self) |
| 147 | 147 |
end |
| 148 | 148 | |
| 149 |
# Returns list of attributes that are hidden on all statuses of all trackers for +user+ or the current user. |
|
| 150 |
def completely_hidden_attribute_names(user=nil) |
|
| 151 |
user_real = user || User.current |
|
| 152 |
roles = user_real.admin ? Role.all : user_real.roles_for_project(self) |
|
| 153 |
return {} if roles.empty?
|
|
| 154 | ||
| 155 |
result = {}
|
|
| 156 |
workflow_permissions = WorkflowPermission.where(:tracker_id => trackers.map(&:id), :old_status_id => IssueStatus.all.map(&:id), :role_id => roles.map(&:id), :rule => 'hidden').all |
|
| 157 | ||
| 158 |
if workflow_permissions.any? |
|
| 159 |
workflow_rules = workflow_permissions.inject({}) do |h, wp|
|
|
| 160 |
h[wp.field_name] ||= [] |
|
| 161 |
h[wp.field_name] << wp.rule |
|
| 162 |
h |
|
| 163 |
end |
|
| 164 |
workflow_rules.each do |attr, rules| |
|
| 165 |
next if rules.size < (roles.size * trackers.size * IssueStatus.all.size) |
|
| 166 |
uniq_rules = rules.uniq |
|
| 167 |
if uniq_rules.size == 1 |
|
| 168 |
result[attr] = uniq_rules.first |
|
| 169 |
else |
|
| 170 |
result[attr] = 'required' |
|
| 171 |
end |
|
| 172 |
end |
|
| 173 |
end |
|
| 174 | ||
| 175 |
result.keys |
|
| 176 |
end |
|
| 177 | ||
| 178 | ||
| 149 | 179 |
# Returns a SQL conditions string used to find all projects visible by the specified user. |
| 150 | 180 |
# |
| 151 | 181 |
# Examples: |
| app/models/query.rb (working copy) | ||
|---|---|---|
| 44 | 44 |
end |
| 45 | 45 | |
| 46 | 46 |
def value(issue) |
| 47 |
issue.send name |
|
| 47 |
hidden_fields = issue.hidden_attribute_names.map {|field| field.sub(/_id$/, '')}
|
|
| 48 |
if hidden_fields.include?(name.to_s) |
|
| 49 |
"" |
|
| 50 |
else |
|
| 51 |
issue.send name |
|
| 52 |
end |
|
| 48 | 53 |
end |
| 49 | 54 | |
| 50 | 55 |
def css_classes |
| ... | ... | |
| 360 | 365 |
Tracker.disabled_core_fields(trackers).each {|field|
|
| 361 | 366 |
@available_filters.delete field |
| 362 | 367 |
} |
| 368 | ||
| 369 |
if project != nil |
|
| 370 |
project.completely_hidden_attribute_names.each {|field|
|
|
| 371 |
@available_filters.delete field |
|
| 372 |
} |
|
| 373 |
end |
|
| 374 | ||
| 363 | 375 |
@available_filters.each do |field, options| |
| 364 | 376 |
options[:name] ||= l(options[:label] || "field_#{field}".gsub(/_id$/, ''))
|
| 365 | 377 |
end |
| ... | ... | |
| 451 | 463 |
def available_columns |
| 452 | 464 |
return @available_columns if @available_columns |
| 453 | 465 |
@available_columns = ::Query.available_columns.dup |
| 466 | ||
| 467 |
hidden_fields = project == nil ? [] : project.completely_hidden_attribute_names.map {|field| field.sub(/_id$/, '')}
|
|
| 468 | ||
| 454 | 469 |
@available_columns += (project ? |
| 455 | 470 |
project.all_issue_custom_fields : |
| 456 | 471 |
IssueCustomField.find(:all) |
| 457 |
).collect {|cf| QueryCustomFieldColumn.new(cf) }
|
|
| 472 |
).collect {|cf| QueryCustomFieldColumn.new(cf) }.reject{|column| hidden_fields.include?(column.custom_field.id.to_s) }
|
|
| 458 | 473 | |
| 459 | 474 |
if User.current.allowed_to?(:view_time_entries, project, :global => true) |
| 460 | 475 |
index = nil |
| ... | ... | |
| 477 | 492 |
@available_columns.reject! {|column|
|
| 478 | 493 |
disabled_fields.include?(column.name.to_s) |
| 479 | 494 |
} |
| 495 |
|
|
| 496 |
@available_columns.reject! {|column|
|
|
| 497 |
hidden_fields.include?(column.name.to_s) |
|
| 498 |
} |
|
| 480 | 499 | |
| 481 | 500 |
@available_columns |
| 482 | 501 |
end |
| ... | ... | |
| 966 | 985 |
@available_filters ||= {}
|
| 967 | 986 | |
| 968 | 987 |
custom_fields.select(&:is_filter?).each do |field| |
| 969 |
case field.field_format |
|
| 970 |
when "text" |
|
| 971 |
options = { :type => :text, :order => 20 }
|
|
| 972 |
when "list" |
|
| 973 |
options = { :type => :list_optional, :values => field.possible_values, :order => 20}
|
|
| 974 |
when "date" |
|
| 975 |
options = { :type => :date, :order => 20 }
|
|
| 976 |
when "bool" |
|
| 977 |
options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
|
|
| 978 |
when "int" |
|
| 979 |
options = { :type => :integer, :order => 20 }
|
|
| 980 |
when "float" |
|
| 981 |
options = { :type => :float, :order => 20 }
|
|
| 982 |
when "user", "version" |
|
| 983 |
next unless project |
|
| 984 |
values = field.possible_values_options(project) |
|
| 985 |
if User.current.logged? && field.field_format == 'user' |
|
| 986 |
values.unshift ["<< #{l(:label_me)} >>", "me"]
|
|
| 988 |
unless project == nil or project.completely_hidden_attribute_names.include?(field.id.to_s) |
|
| 989 |
case field.field_format |
|
| 990 |
when "text" |
|
| 991 |
options = { :type => :text, :order => 20 }
|
|
| 992 |
when "list" |
|
| 993 |
options = { :type => :list_optional, :values => field.possible_values, :order => 20}
|
|
| 994 |
when "date" |
|
| 995 |
options = { :type => :date, :order => 20 }
|
|
| 996 |
when "bool" |
|
| 997 |
options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
|
|
| 998 |
when "int" |
|
| 999 |
options = { :type => :integer, :order => 20 }
|
|
| 1000 |
when "float" |
|
| 1001 |
options = { :type => :float, :order => 20 }
|
|
| 1002 |
when "user", "version" |
|
| 1003 |
next unless project |
|
| 1004 |
values = field.possible_values_options(project) |
|
| 1005 |
if User.current.logged? && field.field_format == 'user' |
|
| 1006 |
values.unshift ["<< #{l(:label_me)} >>", "me"]
|
|
| 1007 |
end |
|
| 1008 |
options = { :type => :list_optional, :values => values, :order => 20}
|
|
| 1009 |
else |
|
| 1010 |
options = { :type => :string, :order => 20 }
|
|
| 987 | 1011 |
end |
| 988 |
options = { :type => :list_optional, :values => values, :order => 20}
|
|
| 989 |
else |
|
| 990 |
options = { :type => :string, :order => 20 }
|
|
| 1012 |
filter_id = "cf_#{field.id}"
|
|
| 1013 |
filter_name = field.name |
|
| 1014 |
if assoc.present? |
|
| 1015 |
filter_id = "#{assoc}.#{filter_id}"
|
|
| 1016 |
filter_name = l("label_attribute_of_#{assoc}", :name => filter_name)
|
|
| 1017 |
end |
|
| 1018 |
@available_filters[filter_id] = options.merge({
|
|
| 1019 |
:name => filter_name, |
|
| 1020 |
:format => field.field_format, |
|
| 1021 |
:field => field |
|
| 1022 |
}) |
|
| 991 | 1023 |
end |
| 992 |
filter_id = "cf_#{field.id}"
|
|
| 993 |
filter_name = field.name |
|
| 994 |
if assoc.present? |
|
| 995 |
filter_id = "#{assoc}.#{filter_id}"
|
|
| 996 |
filter_name = l("label_attribute_of_#{assoc}", :name => filter_name)
|
|
| 997 |
end |
|
| 998 |
@available_filters[filter_id] = options.merge({
|
|
| 999 |
:name => filter_name, |
|
| 1000 |
:format => field.field_format, |
|
| 1001 |
:field => field |
|
| 1002 |
}) |
|
| 1003 | 1024 |
end |
| 1004 | 1025 |
end |
| 1005 | 1026 | |
| app/models/workflow_permission.rb (working copy) | ||
|---|---|---|
| 16 | 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 17 | 17 | |
| 18 | 18 |
class WorkflowPermission < WorkflowRule |
| 19 |
validates_inclusion_of :rule, :in => %w(readonly required) |
|
| 19 |
validates_inclusion_of :rule, :in => %w(readonly required hidden)
|
|
| 20 | 20 |
validate :validate_field_name |
| 21 | 21 | |
| 22 | 22 |
# Replaces the workflow permissions for the given tracker and role |
| 23 | 23 |
# |
| 24 | 24 |
# Example: |
| 25 |
# WorkflowPermission.replace_permissions role, tracker, {'due_date' => {'1' => 'readonly', '2' => 'required'}}
|
|
| 25 |
# WorkflowPermission.replace_permissions role, tracker, {'due_date' => {'1' => 'readonly', '2' => 'required', '3' => 'hidden'}}
|
|
| 26 | 26 |
def self.replace_permissions(tracker, role, permissions) |
| 27 | 27 |
destroy_all(:tracker_id => tracker.id, :role_id => role.id) |
| 28 | 28 | |
| app/views/issues/_form_custom_fields.html.erb (working copy) | ||
|---|---|---|
| 1 | 1 |
<div class="splitcontent"> |
| 2 | 2 |
<div class="splitcontentleft"> |
| 3 | 3 |
<% i = 0 %> |
| 4 |
<% split_on = (@issue.custom_field_values.size / 2.0).ceil - 1 %> |
|
| 4 |
<% split_on = (@issue.editable_custom_field_values.size / 2.0).ceil - 1 %>
|
|
| 5 | 5 |
<% @issue.editable_custom_field_values.each do |value| %> |
| 6 | 6 |
<p><%= custom_field_tag_with_label :issue, value, :required => @issue.required_attribute?(value.custom_field_id) %></p> |
| 7 | 7 |
<% if i == split_on -%> |
| app/views/issues/_history.html.erb (working copy) | ||
|---|---|---|
| 1 | 1 |
<% reply_links = authorize_for('issues', 'edit') -%>
|
| 2 | 2 |
<% for journal in journals %> |
| 3 |
<div id="change-<%= journal.id %>" class="<%= journal.css_classes %>"> |
|
| 4 |
<div id="note-<%= journal.indice %>"> |
|
| 5 |
<h4><%= link_to "##{journal.indice}", {:anchor => "note-#{journal.indice}"}, :class => "journal-link" %>
|
|
| 6 |
<%= avatar(journal.user, :size => "24") %> |
|
| 7 |
<%= authoring journal.created_on, journal.user, :label => :label_updated_time_by %></h4> |
|
| 8 | ||
| 9 |
<% if journal.details.any? %> |
|
| 10 |
<ul class="details"> |
|
| 11 |
<% details_to_strings(journal.details).each do |string| %> |
|
| 12 |
<li><%= string %></li> |
|
| 3 |
<% if details_to_strings(journal.details).any? || journal.notes.blank? == false %> |
|
| 4 |
<div id="change-<%= journal.id %>" class="<%= journal.css_classes %>"> |
|
| 5 |
<div id="note-<%= journal.indice %>"> |
|
| 6 |
<h4><%= link_to "##{journal.indice}", {:anchor => "note-#{journal.indice}"}, :class => "journal-link" %>
|
|
| 7 |
<%= avatar(journal.user, :size => "24") %> |
|
| 8 |
<%= authoring journal.created_on, journal.user, :label => :label_updated_time_by %></h4> |
|
| 9 |
|
|
| 10 |
<% if journal.details.any? %> |
|
| 11 |
<ul class="details"> |
|
| 12 |
<% details_to_strings(journal.details).each do |string| %> |
|
| 13 |
<li><%= string %></li> |
|
| 14 |
<% end %> |
|
| 15 |
</ul> |
|
| 13 | 16 |
<% end %> |
| 14 |
</ul> |
|
| 15 |
<% end %> |
|
| 16 |
<%= render_notes(issue, journal, :reply_links => reply_links) unless journal.notes.blank? %> |
|
| 17 |
<%= render_notes(issue, journal, :reply_links => reply_links) unless journal.notes.blank? %> |
|
| 18 |
</div> |
|
| 17 | 19 |
</div> |
| 18 |
</div>
|
|
| 19 |
<%= call_hook(:view_issues_history_journal_bottom, { :journal => journal }) %>
|
|
| 20 |
<%= call_hook(:view_issues_history_journal_bottom, { :journal => journal }) %>
|
|
| 21 |
<% end %>
|
|
| 20 | 22 |
<% end %> |
| 21 | 23 | |
| 22 | 24 |
<% heads_for_wiki_formatter if User.current.allowed_to?(:edit_issue_notes, issue.project) || User.current.allowed_to?(:edit_own_issue_notes, issue.project) %> |
| app/views/issues/show.html.erb (working copy) | ||
|---|---|---|
| 33 | 33 | |
| 34 | 34 |
<table class="attributes"> |
| 35 | 35 |
<%= issue_fields_rows do |rows| |
| 36 |
rows.left l(:field_status), h(@issue.status.name), :class => 'status' |
|
| 37 |
rows.left l(:field_priority), h(@issue.priority.name), :class => 'priority' |
|
| 36 |
unless @issue.hidden_attribute?('status')
|
|
| 37 |
rows.left l(:field_status), h(@issue.status.name), :class => 'status' |
|
| 38 |
end |
|
| 39 |
unless @issue.hidden_attribute?('priority')
|
|
| 40 |
rows.left l(:field_priority), h(@issue.priority.name), :class => 'priority' |
|
| 41 |
end |
|
| 38 | 42 | |
| 39 |
unless @issue.disabled_core_fields.include?('assigned_to_id')
|
|
| 43 |
unless @issue.disabled_core_fields.include?('assigned_to_id') || @issue.hidden_attribute?('assigned_to_id')
|
|
| 40 | 44 |
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' |
| 41 | 45 |
end |
| 42 |
unless @issue.disabled_core_fields.include?('category_id')
|
|
| 46 |
unless @issue.disabled_core_fields.include?('category_id') || @issue.hidden_attribute?('category_id')
|
|
| 43 | 47 |
rows.left l(:field_category), h(@issue.category ? @issue.category.name : "-"), :class => 'category' |
| 44 | 48 |
end |
| 45 |
unless @issue.disabled_core_fields.include?('fixed_version_id')
|
|
| 49 |
unless @issue.disabled_core_fields.include?('fixed_version_id') || @issue.hidden_attribute?('fixed_version_id')
|
|
| 46 | 50 |
rows.left l(:field_fixed_version), (@issue.fixed_version ? link_to_version(@issue.fixed_version) : "-"), :class => 'fixed-version' |
| 47 | 51 |
end |
| 48 | 52 | |
| 49 |
unless @issue.disabled_core_fields.include?('start_date')
|
|
| 53 |
unless @issue.disabled_core_fields.include?('start_date') || @issue.hidden_attribute?('start_date')
|
|
| 50 | 54 |
rows.right l(:field_start_date), format_date(@issue.start_date), :class => 'start-date' |
| 51 | 55 |
end |
| 52 |
unless @issue.disabled_core_fields.include?('due_date')
|
|
| 56 |
unless @issue.disabled_core_fields.include?('due_date') || @issue.hidden_attribute?('due_date')
|
|
| 53 | 57 |
rows.right l(:field_due_date), format_date(@issue.due_date), :class => 'due-date' |
| 54 | 58 |
end |
| 55 |
unless @issue.disabled_core_fields.include?('done_ratio')
|
|
| 59 |
unless @issue.disabled_core_fields.include?('done_ratio') || @issue.hidden_attribute?('done_ratio')
|
|
| 56 | 60 |
rows.right l(:field_done_ratio), progress_bar(@issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%"), :class => 'progress'
|
| 57 | 61 |
end |
| 58 |
unless @issue.disabled_core_fields.include?('estimated_hours')
|
|
| 62 |
unless @issue.disabled_core_fields.include?('estimated_hours') || @issue.hidden_attribute?('estimated_hours')
|
|
| 59 | 63 |
unless @issue.estimated_hours.nil? |
| 60 | 64 |
rows.right l(:field_estimated_hours), l_hours(@issue.estimated_hours), :class => 'estimated-hours' |
| 61 | 65 |
end |
| app/views/mailer/_issue.html.erb (working copy) | ||
|---|---|---|
| 2 | 2 | |
| 3 | 3 |
<ul> |
| 4 | 4 |
<li><%=l(:field_author)%>: <%=h issue.author %></li> |
| 5 |
<li><%=l(:field_status)%>: <%=h issue.status %></li> |
|
| 6 |
<li><%=l(:field_priority)%>: <%=h issue.priority %></li> |
|
| 7 |
<li><%=l(:field_assigned_to)%>: <%=h issue.assigned_to %></li> |
|
| 8 |
<li><%=l(:field_category)%>: <%=h issue.category %></li> |
|
| 9 |
<li><%=l(:field_fixed_version)%>: <%=h issue.fixed_version %></li> |
|
| 5 |
<% unless issue.hidden_attribute?('status', user) %>
|
|
| 6 |
<li><%=l(:field_status)%>: <%=h issue.status %></li> |
|
| 7 |
<% end %> |
|
| 8 |
<% unless issue.hidden_attribute?('priority', user) %>
|
|
| 9 |
<li><%=l(:field_priority)%>: <%=h issue.priority %></li> |
|
| 10 |
<% end %> |
|
| 11 |
<% unless issue.disabled_core_fields.include?('assigned_to_id') || issue.hidden_attribute?('assigned_to_id', user) %>
|
|
| 12 |
<li><%=l(:field_assigned_to)%>: <%=h issue.assigned_to %></li> |
|
| 13 |
<% end %> |
|
| 14 |
<% unless issue.disabled_core_fields.include?('category_id') || issue.hidden_attribute?('category_id', user) %>
|
|
| 15 |
<li><%=l(:field_category)%>: <%=h issue.category %></li> |
|
| 16 |
<% end %> |
|
| 17 |
<% unless issue.disabled_core_fields.include?('fixed_version_id') || issue.hidden_attribute?('fixed_version_id', user) %>
|
|
| 18 |
<li><%=l(:field_fixed_version)%>: <%=h issue.fixed_version %></li> |
|
| 19 |
<% end %> |
|
| 10 | 20 |
<% issue.custom_field_values.each do |c| %> |
| 11 |
<li><%=h c.custom_field.name %>: <%=h show_value(c) %></li> |
|
| 21 |
<% unless issue.hidden_attribute?(c.custom_field.id, user) %> |
|
| 22 |
<li><%=h c.custom_field.name %>: <%=h show_value(c) %></li> |
|
| 23 |
<% end %> |
|
| 12 | 24 |
<% end %> |
| 13 | 25 |
</ul> |
| 14 | 26 | |
| app/views/mailer/_issue.text.erb (working copy) | ||
|---|---|---|
| 2 | 2 |
<%= issue_url %> |
| 3 | 3 | |
| 4 | 4 |
* <%=l(:field_author)%>: <%= issue.author %> |
| 5 |
<% unless issue.hidden_attribute?('status', user) %>
|
|
| 5 | 6 |
* <%=l(:field_status)%>: <%= issue.status %> |
| 7 |
<% end %> |
|
| 8 |
<% unless issue.hidden_attribute?('priority', user) %>
|
|
| 6 | 9 |
* <%=l(:field_priority)%>: <%= issue.priority %> |
| 10 |
<% end %> |
|
| 11 |
<% unless issue.disabled_core_fields.include?('assigned_to_id') || issue.hidden_attribute?('assigned_to_id', user) %>
|
|
| 7 | 12 |
* <%=l(:field_assigned_to)%>: <%= issue.assigned_to %> |
| 13 |
<% end %> |
|
| 14 |
<% unless issue.disabled_core_fields.include?('category_id') || issue.hidden_attribute?('category_id', user) %>
|
|
| 8 | 15 |
* <%=l(:field_category)%>: <%= issue.category %> |
| 16 |
<% end %> |
|
| 17 |
<% unless issue.disabled_core_fields.include?('fixed_version_id') || issue.hidden_attribute?('fixed_version_id', user) %>
|
|
| 9 | 18 |
* <%=l(:field_fixed_version)%>: <%= issue.fixed_version %> |
| 10 |
<% issue.custom_field_values.each do |c| %>* <%= c.custom_field.name %>: <%= show_value(c) %> |
|
| 19 |
<% end %> |
|
| 20 |
<% issue.custom_field_values.each do |c| %> |
|
| 21 |
<% unless issue.hidden_attribute?(c.custom_field.id, user) %> |
|
| 22 |
* <%= c.custom_field.name %>: <%= show_value(c) %> |
|
| 23 |
<% end %> |
|
| 11 | 24 |
<% end -%> |
| 12 | 25 |
---------------------------------------- |
| 13 | 26 |
<%= issue.description %> |
| app/views/mailer/issue_add.html.erb (working copy) | ||
|---|---|---|
| 1 | 1 |
<%= l(:text_issue_added, :id => "##{@issue.id}", :author => h(@issue.author)) %>
|
| 2 | 2 |
<hr /> |
| 3 |
<%= render :partial => 'issue', :formats => [:html], :locals => { :issue => @issue, :issue_url => @issue_url } %>
|
|
| 3 |
<%= render :partial => 'issue', :formats => [:html], :locals => { :issue => @issue, :issue_url => @issue_url, :user => @auser } %>
|
|
| app/views/mailer/issue_add.text.erb (working copy) | ||
|---|---|---|
| 1 | 1 |
<%= l(:text_issue_added, :id => "##{@issue.id}", :author => @issue.author) %>
|
| 2 | 2 | |
| 3 | 3 |
---------------------------------------- |
| 4 |
<%= render :partial => 'issue', :formats => [:text], :locals => { :issue => @issue, :issue_url => @issue_url } %>
|
|
| 4 |
<%= render :partial => 'issue', :formats => [:text], :locals => { :issue => @issue, :issue_url => @issue_url, :user => @auser } %>
|
|
| app/views/mailer/issue_edit.html.erb (working copy) | ||
|---|---|---|
| 1 | 1 |
<%= l(:text_issue_updated, :id => "##{@issue.id}", :author => h(@journal.user)) %>
|
| 2 | 2 | |
| 3 | 3 |
<ul> |
| 4 |
<% details_to_strings(@journal.details, false, :only_path => false).each do |string| %> |
|
| 4 |
<% details_to_strings(@journal.details, false, :only_path => false, :user => @auser).each do |string| %>
|
|
| 5 | 5 |
<li><%= string %></li> |
| 6 | 6 |
<% end %> |
| 7 | 7 |
</ul> |
| 8 | 8 | |
| 9 | 9 |
<%= textilizable(@journal, :notes, :only_path => false) %> |
| 10 | 10 |
<hr /> |
| 11 |
<%= render :partial => 'issue', :formats => [:html], :locals => { :issue => @issue, :issue_url => @issue_url } %>
|
|
| 11 |
<%= render :partial => 'issue', :formats => [:html], :locals => { :issue => @issue, :issue_url => @issue_url, :user => @auser } %>
|
|
| app/views/mailer/issue_edit.text.erb (working copy) | ||
|---|---|---|
| 1 | 1 |
<%= l(:text_issue_updated, :id => "##{@issue.id}", :author => @journal.user) %>
|
| 2 | 2 | |
| 3 |
<% details_to_strings(@journal.details, true).each do |string| -%> |
|
| 3 |
<% details_to_strings(@journal.details, true, :user => @auser).each do |string| -%>
|
|
| 4 | 4 |
<%= string %> |
| 5 | 5 |
<% end -%> |
| 6 | 6 | |
| ... | ... | |
| 9 | 9 | |
| 10 | 10 |
<% end -%> |
| 11 | 11 |
---------------------------------------- |
| 12 |
<%= render :partial => 'issue', :formats => [:text], :locals => { :issue => @issue, :issue_url => @issue_url } %>
|
|
| 12 |
<%= render :partial => 'issue', :formats => [:text], :locals => { :issue => @issue, :issue_url => @issue_url, :user => @auser } %>
|
|
| config/locales/en.yml (working copy) | ||
|---|---|---|
| 879 | 879 |
label_fields_permissions: Fields permissions |
| 880 | 880 |
label_readonly: Read-only |
| 881 | 881 |
label_required: Required |
| 882 |
label_hidden: " Hidden " |
|
| 882 | 883 |
label_attribute_of_project: "Project's %{name}"
|
| 883 | 884 |
label_attribute_of_author: "Author's %{name}"
|
| 884 | 885 |
label_attribute_of_assigned_to: "Assignee's %{name}"
|
| lib/plugins/acts_as_watchable/lib/acts_as_watchable.rb (working copy) | ||
|---|---|---|
| 78 | 78 |
notified.collect(&:mail).compact |
| 79 | 79 |
end |
| 80 | 80 | |
| 81 |
# Returns an array of watchers |
|
| 82 |
def watcher_recipient_users |
|
| 83 |
notified = watcher_users.active |
|
| 84 |
notified.reject! {|user| user.mail_notification == 'none'}
|
|
| 85 | ||
| 86 |
if respond_to?(:visible?) |
|
| 87 |
notified.reject! {|user| !visible?(user)}
|
|
| 88 |
end |
|
| 89 |
notified |
|
| 90 |
end |
|
| 91 | ||
| 81 | 92 |
module ClassMethods; end |
| 82 | 93 |
end |
| 83 | 94 |
end |
| lib/redmine/export/pdf.rb (working copy) | ||
|---|---|---|
| 520 | 520 |
pdf.Ln |
| 521 | 521 | |
| 522 | 522 |
left = [] |
| 523 |
left << [l(:field_status), issue.status] |
|
| 524 |
left << [l(:field_priority), issue.priority] |
|
| 525 |
left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id')
|
|
| 526 |
left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id')
|
|
| 527 |
left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id')
|
|
| 523 |
left << [l(:field_status), issue.status] unless issue.hidden_attribute?('status')
|
|
| 524 |
left << [l(:field_priority), issue.priority] unless issue.hidden_attribute?('priority')
|
|
| 525 |
left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id') or issue.hidden_attribute?('assigned_to_id')
|
|
| 526 |
left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id') or issue.hidden_attribute?('category_id')
|
|
| 527 |
left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id') or issue.hidden_attribute?('fixed_version_id')
|
|
| 528 | 528 | |
| 529 | 529 |
right = [] |
| 530 |
right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date')
|
|
| 531 |
right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date')
|
|
| 532 |
right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio')
|
|
| 533 |
right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours')
|
|
| 530 |
right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date') or issue.hidden_attribute?('start_date')
|
|
| 531 |
right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date') or issue.hidden_attribute?('due_date')
|
|
| 532 |
right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio') or issue.hidden_attribute?('done_ratio')
|
|
| 533 |
right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours') or issue.hidden_attribute?('estimated_hours')
|
|
| 534 | 534 |
right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project) |
| 535 | 535 | |
| 536 | 536 |
rows = left.size > right.size ? left.size : right.size |
| ... | ... | |
| 541 | 541 |
right << nil |
| 542 | 542 |
end |
| 543 | 543 | |
| 544 |
half = (issue.custom_field_values.size / 2.0).ceil |
|
| 545 |
issue.custom_field_values.each_with_index do |custom_value, i| |
|
| 546 |
(i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value)] |
|
| 544 |
half = (issue.viewable_custom_field_values.size / 2.0).ceil
|
|
| 545 |
issue.viewable_custom_field_values.each_with_index do |custom_value, i|
|
|
| 546 |
(i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value)] unless issue.hidden_attribute?(custom_value.custom_field.name)
|
|
| 547 | 547 |
end |
| 548 | 548 | |
| 549 | 549 |
rows = left.size > right.size ? left.size : right.size |