Redmine 4.1.1
This commit is contained in:
parent
33e7b881a5
commit
3d976f1b3b
1593 changed files with 36180 additions and 19489 deletions
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
|
@ -54,8 +56,7 @@ class Issue < ActiveRecord::Base
|
|||
|
||||
DONE_RATIO_OPTIONS = %w(issue_field issue_status)
|
||||
|
||||
attr_accessor :deleted_attachment_ids
|
||||
attr_reader :current_journal
|
||||
attr_writer :deleted_attachment_ids
|
||||
delegate :notes, :notes=, :private_notes, :private_notes=, :to => :current_journal, :allow_nil => true
|
||||
|
||||
validates_presence_of :subject, :project, :tracker
|
||||
|
@ -69,7 +70,6 @@ class Issue < ActiveRecord::Base
|
|||
validates :start_date, :date => true
|
||||
validates :due_date, :date => true
|
||||
validate :validate_issue, :validate_required_fields, :validate_permissions
|
||||
attr_protected :id
|
||||
|
||||
scope :visible, lambda {|*args|
|
||||
joins(:project).
|
||||
|
@ -77,7 +77,7 @@ class Issue < ActiveRecord::Base
|
|||
}
|
||||
|
||||
scope :open, lambda {|*args|
|
||||
is_closed = args.size > 0 ? !args.first : false
|
||||
is_closed = !args.empty? ? !args.first : false
|
||||
joins(:status).
|
||||
where(:issue_statuses => {:is_closed => is_closed})
|
||||
}
|
||||
|
@ -108,34 +108,35 @@ class Issue < ActiveRecord::Base
|
|||
before_validation :default_assign, on: :create
|
||||
before_validation :clear_disabled_fields
|
||||
before_save :close_duplicates, :update_done_ratio_from_issue_status,
|
||||
:force_updated_on_change, :update_closed_on, :set_assigned_to_was
|
||||
after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?}
|
||||
:force_updated_on_change, :update_closed_on
|
||||
after_save {|issue| issue.send :after_project_change if !issue.saved_change_to_id? && issue.saved_change_to_project_id?}
|
||||
after_save :reschedule_following_issues, :update_nested_set_attributes,
|
||||
:update_parent_attributes, :delete_selected_attachments, :create_journal
|
||||
# Should be after_create but would be called before previous after_save callbacks
|
||||
after_save :after_create_from_copy
|
||||
after_destroy :update_parent_attributes
|
||||
after_create :send_notification
|
||||
after_create_commit :send_notification
|
||||
|
||||
# Returns a SQL conditions string used to find all issues visible by the specified user
|
||||
def self.visible_condition(user, options={})
|
||||
Project.allowed_to_condition(user, :view_issues, options) do |role, user|
|
||||
sql = if user.id && user.logged?
|
||||
case role.issues_visibility
|
||||
when 'all'
|
||||
'1=1'
|
||||
when 'default'
|
||||
user_ids = [user.id] + user.groups.map(&:id).compact
|
||||
"(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
|
||||
when 'own'
|
||||
user_ids = [user.id] + user.groups.map(&:id).compact
|
||||
"(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
|
||||
sql =
|
||||
if user.id && user.logged?
|
||||
case role.issues_visibility
|
||||
when 'all'
|
||||
'1=1'
|
||||
when 'default'
|
||||
user_ids = [user.id] + user.groups.pluck(:id).compact
|
||||
"(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
|
||||
when 'own'
|
||||
user_ids = [user.id] + user.groups.pluck(:id).compact
|
||||
"(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
|
||||
else
|
||||
'1=0'
|
||||
end
|
||||
else
|
||||
'1=0'
|
||||
"(#{table_name}.is_private = #{connection.quoted_false})"
|
||||
end
|
||||
else
|
||||
"(#{table_name}.is_private = #{connection.quoted_false})"
|
||||
end
|
||||
unless role.permissions_all_trackers?(:view_issues)
|
||||
tracker_ids = role.permissions_tracker_ids(:view_issues)
|
||||
if tracker_ids.any?
|
||||
|
@ -151,20 +152,21 @@ class Issue < ActiveRecord::Base
|
|||
# Returns true if usr or current user is allowed to view the issue
|
||||
def visible?(usr=nil)
|
||||
(usr || User.current).allowed_to?(:view_issues, self.project) do |role, user|
|
||||
visible = if user.logged?
|
||||
case role.issues_visibility
|
||||
when 'all'
|
||||
true
|
||||
when 'default'
|
||||
!self.is_private? || (self.author == user || user.is_or_belongs_to?(assigned_to))
|
||||
when 'own'
|
||||
self.author == user || user.is_or_belongs_to?(assigned_to)
|
||||
visible =
|
||||
if user.logged?
|
||||
case role.issues_visibility
|
||||
when 'all'
|
||||
true
|
||||
when 'default'
|
||||
!self.is_private? || (self.author == user || user.is_or_belongs_to?(assigned_to))
|
||||
when 'own'
|
||||
self.author == user || user.is_or_belongs_to?(assigned_to)
|
||||
else
|
||||
false
|
||||
end
|
||||
else
|
||||
false
|
||||
!self.is_private?
|
||||
end
|
||||
else
|
||||
!self.is_private?
|
||||
end
|
||||
unless role.permissions_all_trackers?(:view_issues)
|
||||
visible &&= role.permissions_tracker_ids?(:view_issues, tracker_id)
|
||||
end
|
||||
|
@ -179,7 +181,9 @@ class Issue < ActiveRecord::Base
|
|||
|
||||
# Returns true if user or current user is allowed to edit the issue
|
||||
def attributes_editable?(user=User.current)
|
||||
user_tracker_permission?(user, :edit_issues)
|
||||
user_tracker_permission?(user, :edit_issues) || (
|
||||
user_tracker_permission?(user, :edit_own_issues) && author == user
|
||||
)
|
||||
end
|
||||
|
||||
# Overrides Redmine::Acts::Attachable::InstanceMethods#attachments_editable?
|
||||
|
@ -206,7 +210,7 @@ class Issue < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def create_or_update
|
||||
def create_or_update(*args)
|
||||
super
|
||||
ensure
|
||||
@status_was = nil
|
||||
|
@ -260,6 +264,11 @@ class Issue < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
# Overrides Redmine::Acts::Customizable::InstanceMethods#set_custom_field_default?
|
||||
def set_custom_field_default?(custom_value)
|
||||
new_record? || project_id_changed?|| tracker_id_changed?
|
||||
end
|
||||
|
||||
# Copies attributes from another issue, arg can be an id or an Issue
|
||||
def copy_from(arg, options={})
|
||||
issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
|
||||
|
@ -445,7 +454,8 @@ class Issue < ActiveRecord::Base
|
|||
write_attribute :estimated_hours, (h.is_a?(String) ? (h.to_hours || h) : h)
|
||||
end
|
||||
|
||||
safe_attributes 'project_id',
|
||||
safe_attributes(
|
||||
'project_id',
|
||||
'tracker_id',
|
||||
'status_id',
|
||||
'category_id',
|
||||
|
@ -462,29 +472,31 @@ class Issue < ActiveRecord::Base
|
|||
'custom_fields',
|
||||
'lock_version',
|
||||
'notes',
|
||||
:if => lambda {|issue, user| issue.new_record? || issue.attributes_editable?(user) }
|
||||
|
||||
safe_attributes 'notes',
|
||||
:if => lambda {|issue, user| issue.notes_addable?(user)}
|
||||
|
||||
safe_attributes 'private_notes',
|
||||
:if => lambda {|issue, user| !issue.new_record? && user.allowed_to?(:set_notes_private, issue.project)}
|
||||
|
||||
safe_attributes 'watcher_user_ids',
|
||||
:if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)}
|
||||
|
||||
safe_attributes 'is_private',
|
||||
:if => lambda {|issue, user| issue.new_record? || issue.attributes_editable?(user)})
|
||||
safe_attributes(
|
||||
'notes',
|
||||
:if => lambda {|issue, user| issue.notes_addable?(user)})
|
||||
safe_attributes(
|
||||
'private_notes',
|
||||
:if => lambda {|issue, user| !issue.new_record? && user.allowed_to?(:set_notes_private, issue.project)})
|
||||
safe_attributes(
|
||||
'watcher_user_ids',
|
||||
:if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)})
|
||||
safe_attributes(
|
||||
'is_private',
|
||||
:if => lambda {|issue, user|
|
||||
user.allowed_to?(:set_issues_private, issue.project) ||
|
||||
(issue.author_id == user.id && user.allowed_to?(:set_own_issues_private, issue.project))
|
||||
}
|
||||
|
||||
safe_attributes 'parent_issue_id',
|
||||
:if => lambda {|issue, user| (issue.new_record? || issue.attributes_editable?(user)) &&
|
||||
user.allowed_to?(:manage_subtasks, issue.project)}
|
||||
|
||||
safe_attributes 'deleted_attachment_ids',
|
||||
:if => lambda {|issue, user| issue.attachments_deletable?(user)}
|
||||
})
|
||||
safe_attributes(
|
||||
'parent_issue_id',
|
||||
:if => lambda {|issue, user|
|
||||
(issue.new_record? || issue.attributes_editable?(user)) &&
|
||||
user.allowed_to?(:manage_subtasks, issue.project)
|
||||
})
|
||||
safe_attributes(
|
||||
'deleted_attachment_ids',
|
||||
:if => lambda {|issue, user| issue.attachments_deletable?(user)})
|
||||
|
||||
def safe_attribute_names(user=nil)
|
||||
names = super
|
||||
|
@ -511,6 +523,10 @@ class Issue < ActiveRecord::Base
|
|||
# attr_accessible is too rough because we still want things like
|
||||
# Issue.new(:project => foo) to work
|
||||
def safe_attributes=(attrs, user=User.current)
|
||||
if attrs.respond_to?(:to_unsafe_hash)
|
||||
attrs = attrs.to_unsafe_hash
|
||||
end
|
||||
|
||||
@attributes_set_by = user
|
||||
return unless attrs.is_a?(Hash)
|
||||
|
||||
|
@ -518,7 +534,7 @@ class Issue < ActiveRecord::Base
|
|||
|
||||
# Project and Tracker must be set before since new_statuses_allowed_to depends on it.
|
||||
if (p = attrs.delete('project_id')) && safe_attribute?('project_id')
|
||||
if p.is_a?(String) && !p.match(/^\d*$/)
|
||||
if p.is_a?(String) && !/^\d*$/.match?(p)
|
||||
p_id = Project.find_by_identifier(p).try(:id)
|
||||
else
|
||||
p_id = p.to_i
|
||||
|
@ -527,7 +543,7 @@ class Issue < ActiveRecord::Base
|
|||
self.project_id = p_id
|
||||
end
|
||||
|
||||
if project_id_changed? && attrs['category_id'].to_s == category_id_was.to_s
|
||||
if project_id_changed? && attrs['category_id'].present? && attrs['category_id'].to_s == category_id_was.to_s
|
||||
# Discard submitted category on previous project
|
||||
attrs.delete('category_id')
|
||||
end
|
||||
|
@ -563,8 +579,6 @@ class Issue < ActiveRecord::Base
|
|||
if (u = attrs.delete('assigned_to_id')) && safe_attribute?('assigned_to_id')
|
||||
self.assigned_to_id = u
|
||||
end
|
||||
|
||||
|
||||
attrs = delete_unsafe_attributes(attrs, user)
|
||||
return if attrs.empty?
|
||||
|
||||
|
@ -585,8 +599,7 @@ class Issue < ActiveRecord::Base
|
|||
attrs['custom_fields'].select! {|c| editable_custom_field_ids.include?(c['id'].to_s)}
|
||||
end
|
||||
|
||||
# mass-assignment security bypass
|
||||
assign_attributes attrs, :without_protection => true
|
||||
assign_attributes attrs
|
||||
end
|
||||
|
||||
def disabled_core_fields
|
||||
|
@ -760,7 +773,7 @@ class Issue < ActiveRecord::Base
|
|||
user = new_record? ? author : current_journal.try(:user)
|
||||
|
||||
required_attribute_names(user).each do |attribute|
|
||||
if attribute =~ /^\d+$/
|
||||
if /^\d+$/.match?(attribute)
|
||||
attribute = attribute.to_i
|
||||
v = custom_field_values.detect {|v| v.custom_field_id == attribute }
|
||||
if v && Array(v.value).detect(&:present?).nil?
|
||||
|
@ -1006,32 +1019,26 @@ class Issue < ActiveRecord::Base
|
|||
statuses
|
||||
end
|
||||
|
||||
# Returns the previous assignee (user or group) if changed
|
||||
def assigned_to_was
|
||||
# assigned_to_id_was is reset before after_save callbacks
|
||||
user_id = @previous_assigned_to_id || assigned_to_id_was
|
||||
if user_id && user_id != assigned_to_id
|
||||
@assigned_to_was ||= Principal.find_by_id(user_id)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the original tracker
|
||||
def tracker_was
|
||||
Tracker.find_by_id(tracker_id_was)
|
||||
Tracker.find_by_id(tracker_id_in_database)
|
||||
end
|
||||
|
||||
# Returns the previous assignee whenever we're before the save
|
||||
# or in after_* callbacks
|
||||
def previous_assignee
|
||||
if previous_assigned_to_id = assigned_to_id_change_to_be_saved.nil? ? assigned_to_id_before_last_save : assigned_to_id_in_database
|
||||
Principal.find_by_id(previous_assigned_to_id)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the users that should be notified
|
||||
def notified_users
|
||||
notified = []
|
||||
# Author and assignee are always notified unless they have been
|
||||
# locked or don't want to be notified
|
||||
notified << author if author
|
||||
if assigned_to
|
||||
notified += (assigned_to.is_a?(Group) ? assigned_to.users : [assigned_to])
|
||||
end
|
||||
if assigned_to_was
|
||||
notified += (assigned_to_was.is_a?(Group) ? assigned_to_was.users : [assigned_to_was])
|
||||
end
|
||||
notified = [author, assigned_to, previous_assignee].compact.uniq
|
||||
notified = notified.map {|n| n.is_a?(Group) ? n.users : n}.flatten
|
||||
notified.uniq!
|
||||
notified = notified.select {|u| u.active? && u.notify_about?(self)}
|
||||
|
||||
notified += project.notified_users
|
||||
|
@ -1046,21 +1053,6 @@ class Issue < ActiveRecord::Base
|
|||
notified_users.collect(&:mail)
|
||||
end
|
||||
|
||||
def each_notification(users, &block)
|
||||
if users.any?
|
||||
if custom_field_values.detect {|value| !value.custom_field.visible?}
|
||||
users_by_custom_field_visibility = users.group_by do |user|
|
||||
visible_custom_field_values(user).map(&:custom_field_id).sort
|
||||
end
|
||||
users_by_custom_field_visibility.values.each do |users|
|
||||
yield(users)
|
||||
end
|
||||
else
|
||||
yield(users)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def notify?
|
||||
@notify != false
|
||||
end
|
||||
|
@ -1076,11 +1068,12 @@ class Issue < ActiveRecord::Base
|
|||
|
||||
# Returns the total number of hours spent on this issue and its descendants
|
||||
def total_spent_hours
|
||||
@total_spent_hours ||= if leaf?
|
||||
spent_hours
|
||||
else
|
||||
self_and_descendants.joins(:time_entries).sum("#{TimeEntry.table_name}.hours").to_f || 0.0
|
||||
end
|
||||
@total_spent_hours ||=
|
||||
if leaf?
|
||||
spent_hours
|
||||
else
|
||||
self_and_descendants.joins(:time_entries).sum("#{TimeEntry.table_name}.hours").to_f || 0.0
|
||||
end
|
||||
end
|
||||
|
||||
def total_estimated_hours
|
||||
|
@ -1364,7 +1357,7 @@ class Issue < ActiveRecord::Base
|
|||
|
||||
# Returns a string of css classes that apply to the issue
|
||||
def css_classes(user=User.current)
|
||||
s = "issue tracker-#{tracker_id} status-#{status_id} #{priority.try(:css_classes)}"
|
||||
s = +"issue tracker-#{tracker_id} status-#{status_id} #{priority.try(:css_classes)}"
|
||||
s << ' closed' if closed?
|
||||
s << ' overdue' if overdue?
|
||||
s << ' child' if child?
|
||||
|
@ -1387,7 +1380,7 @@ class Issue < ActiveRecord::Base
|
|||
# Unassigns issues from versions that are no longer shared
|
||||
# after +project+ was moved
|
||||
def self.update_versions_from_hierarchy_change(project)
|
||||
moved_project_ids = project.self_and_descendants.reload.collect(&:id)
|
||||
moved_project_ids = project.self_and_descendants.reload.pluck(:id)
|
||||
# Update issues of the moved projects and issues assigned to a version of a moved project
|
||||
Issue.update_versions(
|
||||
["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)",
|
||||
|
@ -1463,28 +1456,28 @@ class Issue < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def self.by_tracker(project)
|
||||
count_and_group_by(:project => project, :association => :tracker)
|
||||
def self.by_tracker(project, with_subprojects=false)
|
||||
count_and_group_by(:project => project, :association => :tracker, :with_subprojects => with_subprojects)
|
||||
end
|
||||
|
||||
def self.by_version(project)
|
||||
count_and_group_by(:project => project, :association => :fixed_version)
|
||||
def self.by_version(project, with_subprojects=false)
|
||||
count_and_group_by(:project => project, :association => :fixed_version, :with_subprojects => with_subprojects)
|
||||
end
|
||||
|
||||
def self.by_priority(project)
|
||||
count_and_group_by(:project => project, :association => :priority)
|
||||
def self.by_priority(project, with_subprojects=false)
|
||||
count_and_group_by(:project => project, :association => :priority, :with_subprojects => with_subprojects)
|
||||
end
|
||||
|
||||
def self.by_category(project)
|
||||
count_and_group_by(:project => project, :association => :category)
|
||||
def self.by_category(project, with_subprojects=false)
|
||||
count_and_group_by(:project => project, :association => :category, :with_subprojects => with_subprojects)
|
||||
end
|
||||
|
||||
def self.by_assigned_to(project)
|
||||
count_and_group_by(:project => project, :association => :assigned_to)
|
||||
def self.by_assigned_to(project, with_subprojects=false)
|
||||
count_and_group_by(:project => project, :association => :assigned_to, :with_subprojects => with_subprojects)
|
||||
end
|
||||
|
||||
def self.by_author(project)
|
||||
count_and_group_by(:project => project, :association => :author)
|
||||
def self.by_author(project, with_subprojects=false)
|
||||
count_and_group_by(:project => project, :association => :author, :with_subprojects => with_subprojects)
|
||||
end
|
||||
|
||||
def self.by_subproject(project)
|
||||
|
@ -1521,8 +1514,15 @@ class Issue < ActiveRecord::Base
|
|||
end
|
||||
|
||||
# Returns a scope of projects that user can assign the issue to
|
||||
def allowed_target_projects(user=User.current)
|
||||
current_project = new_record? ? nil : project
|
||||
def allowed_target_projects(user=User.current, context=nil)
|
||||
if new_record? && context.is_a?(Project) && !copy?
|
||||
current_project = context.self_and_descendants
|
||||
elsif new_record?
|
||||
current_project = nil
|
||||
else
|
||||
current_project = project
|
||||
end
|
||||
|
||||
self.class.allowed_target_projects(user, current_project)
|
||||
end
|
||||
|
||||
|
@ -1530,8 +1530,10 @@ class Issue < ActiveRecord::Base
|
|||
# If current_project is given, it will be included in the scope
|
||||
def self.allowed_target_projects(user=User.current, current_project=nil)
|
||||
condition = Project.allowed_to_condition(user, :add_issues)
|
||||
if current_project
|
||||
if current_project.is_a?(Project)
|
||||
condition = ["(#{condition}) OR #{Project.table_name}.id = ?", current_project.id]
|
||||
elsif current_project
|
||||
condition = ["(#{condition}) AND #{Project.table_name}.id IN (?)", current_project.map(&:id)]
|
||||
end
|
||||
Project.where(condition).having_trackers
|
||||
end
|
||||
|
@ -1589,7 +1591,7 @@ class Issue < ActiveRecord::Base
|
|||
|
||||
# Move subtasks that were in the same project
|
||||
children.each do |child|
|
||||
next unless child.project_id == project_id_was
|
||||
next unless child.project_id == project_id_before_last_save
|
||||
# Change project and keep project
|
||||
child.send :project=, project, true
|
||||
unless child.save
|
||||
|
@ -1648,7 +1650,7 @@ class Issue < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def update_nested_set_attributes
|
||||
if parent_id_changed?
|
||||
if saved_change_to_parent_id?
|
||||
update_nested_set_attributes_on_parent_change
|
||||
end
|
||||
remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue)
|
||||
|
@ -1656,7 +1658,7 @@ class Issue < ActiveRecord::Base
|
|||
|
||||
# Updates the nested set for when an existing issue is moved
|
||||
def update_nested_set_attributes_on_parent_change
|
||||
former_parent_id = parent_id_was
|
||||
former_parent_id = parent_id_before_last_save
|
||||
# delete invalid relations of all descendants
|
||||
self_and_descendants.each do |issue|
|
||||
issue.relations.each do |relation|
|
||||
|
@ -1713,7 +1715,7 @@ class Issue < ActiveRecord::Base
|
|||
estimated * ratio
|
||||
}.sum
|
||||
progress = done / (average * children.count)
|
||||
p.done_ratio = progress.round
|
||||
p.done_ratio = progress.floor
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1723,21 +1725,24 @@ class Issue < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
# Update issues so their versions are not pointing to a
|
||||
# fixed_version that is not shared with the issue's project
|
||||
def self.update_versions(conditions=nil)
|
||||
# Only need to update issues with a fixed_version from
|
||||
# a different project and that is not systemwide shared
|
||||
Issue.joins(:project, :fixed_version).
|
||||
where("#{Issue.table_name}.fixed_version_id IS NOT NULL" +
|
||||
" AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" +
|
||||
" AND #{Version.table_name}.sharing <> 'system'").
|
||||
where(conditions).each do |issue|
|
||||
next if issue.project.nil? || issue.fixed_version.nil?
|
||||
unless issue.project.shared_versions.include?(issue.fixed_version)
|
||||
issue.init_journal(User.current)
|
||||
issue.fixed_version = nil
|
||||
issue.save
|
||||
# Singleton class method is public
|
||||
class << self
|
||||
# Update issues so their versions are not pointing to a
|
||||
# fixed_version that is not shared with the issue's project
|
||||
def update_versions(conditions=nil)
|
||||
# Only need to update issues with a fixed_version from
|
||||
# a different project and that is not systemwide shared
|
||||
Issue.joins(:project, :fixed_version).
|
||||
where("#{Issue.table_name}.fixed_version_id IS NOT NULL" +
|
||||
" AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" +
|
||||
" AND #{Version.table_name}.sharing <> 'system'").
|
||||
where(conditions).each do |issue|
|
||||
next if issue.project.nil? || issue.fixed_version.nil?
|
||||
unless issue.project.shared_versions.include?(issue.fixed_version)
|
||||
issue.init_journal(User.current)
|
||||
issue.fixed_version = nil
|
||||
issue.save
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1793,7 +1798,7 @@ class Issue < ActiveRecord::Base
|
|||
|
||||
# Updates start/due dates of following issues
|
||||
def reschedule_following_issues
|
||||
if start_date_changed? || due_date_changed?
|
||||
if saved_change_to_start_date? || saved_change_to_due_date?
|
||||
relations_from.each do |relation|
|
||||
relation.set_issue_to_dates(@current_journal)
|
||||
end
|
||||
|
@ -1802,7 +1807,7 @@ class Issue < ActiveRecord::Base
|
|||
|
||||
# Closes duplicates if the issue is being closed
|
||||
def close_duplicates
|
||||
if closing?
|
||||
if Setting.close_duplicate_issues? && closing?
|
||||
duplicates.each do |duplicate|
|
||||
# Reload is needed in case the duplicate was updated by a previous duplicate
|
||||
duplicate.reload
|
||||
|
@ -1852,18 +1857,6 @@ class Issue < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
# Stores the previous assignee so we can still have access
|
||||
# to it during after_save callbacks (assigned_to_id_was is reset)
|
||||
def set_assigned_to_was
|
||||
@previous_assigned_to_id = assigned_to_id_was
|
||||
end
|
||||
|
||||
# Clears the previous assignee at the end of after_save callbacks
|
||||
def clear_assigned_to_was
|
||||
@assigned_to_was = nil
|
||||
@previous_assigned_to_id = nil
|
||||
end
|
||||
|
||||
def clear_disabled_fields
|
||||
if tracker
|
||||
tracker.disabled_core_fields.each do |attribute|
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue