Actualiza a Redmine 3.4.13
This commit is contained in:
parent
807ff3308d
commit
ecddcaf1d3
224 changed files with 2222 additions and 1000 deletions
|
@ -156,7 +156,7 @@ class Attachment < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def title
|
||||
title = filename.to_s
|
||||
title = filename.dup
|
||||
if description.present?
|
||||
title << " (#{description})"
|
||||
end
|
||||
|
|
|
@ -217,6 +217,7 @@ class Import < ActiveRecord::Base
|
|||
|
||||
csv_options = {:headers => false}
|
||||
csv_options[:encoding] = settings['encoding'].to_s.presence || 'UTF-8'
|
||||
csv_options[:encoding] = 'bom|UTF-8' if csv_options[:encoding] == 'UTF-8'
|
||||
separator = settings['separator'].to_s
|
||||
csv_options[:col_sep] = separator if separator.size == 1
|
||||
wrapper = settings['wrapper'].to_s
|
||||
|
|
|
@ -275,7 +275,8 @@ class Issue < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
unless options[:watchers] == false
|
||||
self.watcher_user_ids = issue.watcher_user_ids.dup
|
||||
self.watcher_user_ids =
|
||||
issue.watcher_users.select{|u| u.status == User::STATUS_ACTIVE}.map(&:id)
|
||||
end
|
||||
@copied_from = issue
|
||||
@copy_options = options
|
||||
|
@ -1086,7 +1087,7 @@ class Issue < ActiveRecord::Base
|
|||
if leaf?
|
||||
estimated_hours
|
||||
else
|
||||
@total_estimated_hours ||= self_and_descendants.sum(:estimated_hours)
|
||||
@total_estimated_hours ||= self_and_descendants.visible.sum(:estimated_hours)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1300,7 +1301,7 @@ class Issue < ActiveRecord::Base
|
|||
|
||||
# Reschedules the issue on the given date or the next working day and saves the record.
|
||||
# If the issue is a parent task, this is done by rescheduling its subtasks.
|
||||
def reschedule_on!(date)
|
||||
def reschedule_on!(date, journal=nil)
|
||||
return if date.nil?
|
||||
if leaf? || !dates_derived?
|
||||
if start_date.nil? || start_date != date
|
||||
|
@ -1308,6 +1309,9 @@ class Issue < ActiveRecord::Base
|
|||
# Issue can not be moved earlier than its soonest start date
|
||||
date = [soonest_start(true), date].compact.max
|
||||
end
|
||||
if journal
|
||||
init_journal(journal.user)
|
||||
end
|
||||
reschedule_on(date)
|
||||
begin
|
||||
save
|
||||
|
@ -1631,6 +1635,8 @@ class Issue < ActiveRecord::Base
|
|||
copy.author = author
|
||||
copy.project = project
|
||||
copy.parent_issue_id = copied_issue_ids[child.parent_id]
|
||||
copy.fixed_version_id = nil unless child.fixed_version.present? && child.fixed_version.status == 'open'
|
||||
copy.assigned_to = nil unless child.assigned_to_id.present? && child.assigned_to.status == User::STATUS_ACTIVE
|
||||
unless copy.save
|
||||
logger.error "Could not copy subtask ##{child.id} while copying ##{@copied_from.id} to ##{id} due to validation errors: #{copy.errors.full_messages.join(', ')}" if logger
|
||||
next
|
||||
|
@ -1789,7 +1795,7 @@ class Issue < ActiveRecord::Base
|
|||
def reschedule_following_issues
|
||||
if start_date_changed? || due_date_changed?
|
||||
relations_from.each do |relation|
|
||||
relation.set_issue_to_dates
|
||||
relation.set_issue_to_dates(@current_journal)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -122,7 +122,10 @@ class IssueImport < Import
|
|||
end
|
||||
end
|
||||
if issue.project && version_name = row_value(row, 'fixed_version')
|
||||
if version = issue.project.versions.named(version_name).first
|
||||
version =
|
||||
issue.project.versions.named(version_name).first ||
|
||||
issue.project.shared_versions.named(version_name).first
|
||||
if version
|
||||
attributes['fixed_version_id'] = version.id
|
||||
elsif create_versions?
|
||||
version = issue.project.versions.build
|
||||
|
|
|
@ -37,8 +37,8 @@ class IssueQuery < Query
|
|||
QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
|
||||
QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours", :totalable => true),
|
||||
QueryColumn.new(:total_estimated_hours,
|
||||
:sortable => "COALESCE((SELECT SUM(estimated_hours) FROM #{Issue.table_name} subtasks" +
|
||||
" WHERE subtasks.root_id = #{Issue.table_name}.root_id AND subtasks.lft >= #{Issue.table_name}.lft AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)",
|
||||
:sortable => -> { "COALESCE((SELECT SUM(estimated_hours) FROM #{Issue.table_name} subtasks" +
|
||||
" WHERE #{Issue.visible_condition(User.current).gsub(/\bissues\b/, 'subtasks')} AND subtasks.root_id = #{Issue.table_name}.root_id AND subtasks.lft >= #{Issue.table_name}.lft AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)" },
|
||||
:default_order => 'desc'),
|
||||
QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
|
||||
QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
|
||||
|
@ -221,6 +221,7 @@ class IssueQuery < Query
|
|||
end
|
||||
|
||||
disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')}
|
||||
disabled_fields << "total_estimated_hours" if disabled_fields.include?("estimated_hours")
|
||||
@available_columns.reject! {|column|
|
||||
disabled_fields.include?(column.name.to_s)
|
||||
}
|
||||
|
@ -373,7 +374,7 @@ class IssueQuery < Query
|
|||
neg = (operator == '!' ? 'NOT' : '')
|
||||
subquery = "SELECT 1 FROM #{Journal.table_name} sj" +
|
||||
" WHERE sj.journalized_type='Issue' AND sj.journalized_id=#{Issue.table_name}.id AND (#{sql_for_field field, '=', value, 'sj', 'user_id'})" +
|
||||
" AND sj.id = (SELECT MAX(#{Journal.table_name}.id) FROM #{Journal.table_name}" +
|
||||
" AND sj.id IN (SELECT MAX(#{Journal.table_name}.id) FROM #{Journal.table_name}" +
|
||||
" WHERE #{Journal.table_name}.journalized_type='Issue' AND #{Journal.table_name}.journalized_id=#{Issue.table_name}.id" +
|
||||
" AND (#{Journal.visible_notes_condition(User.current, :skip_pre_condition => true)}))"
|
||||
|
||||
|
@ -382,8 +383,26 @@ class IssueQuery < Query
|
|||
|
||||
def sql_for_watcher_id_field(field, operator, value)
|
||||
db_table = Watcher.table_name
|
||||
"#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " +
|
||||
sql_for_field(field, '=', value, db_table, 'user_id') + ')'
|
||||
|
||||
me, others = value.partition { |id| ['0', User.current.id.to_s].include?(id) }
|
||||
sql = if others.any?
|
||||
"SELECT #{Issue.table_name}.id FROM #{Issue.table_name} " +
|
||||
"INNER JOIN #{db_table} ON #{Issue.table_name}.id = #{db_table}.watchable_id AND #{db_table}.watchable_type = 'Issue' " +
|
||||
"LEFT OUTER JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Issue.table_name}.project_id " +
|
||||
"WHERE (" +
|
||||
sql_for_field(field, '=', me, db_table, 'user_id') +
|
||||
') OR (' +
|
||||
Project.allowed_to_condition(User.current, :view_issue_watchers) +
|
||||
' AND ' +
|
||||
sql_for_field(field, '=', others, db_table, 'user_id') +
|
||||
')'
|
||||
else
|
||||
"SELECT #{db_table}.watchable_id FROM #{db_table} " +
|
||||
"WHERE #{db_table}.watchable_type='Issue' AND " +
|
||||
sql_for_field(field, '=', me, db_table, 'user_id')
|
||||
end
|
||||
|
||||
"#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (#{sql})"
|
||||
end
|
||||
|
||||
def sql_for_member_of_group_field(field, operator, value)
|
||||
|
|
|
@ -176,10 +176,10 @@ class IssueRelation < ActiveRecord::Base
|
|||
set_issue_to_dates
|
||||
end
|
||||
|
||||
def set_issue_to_dates
|
||||
def set_issue_to_dates(journal=nil)
|
||||
soonest_start = self.successor_soonest_start
|
||||
if soonest_start && issue_to
|
||||
issue_to.reschedule_on!(soonest_start)
|
||||
issue_to.reschedule_on!(soonest_start, journal)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ class MailHandler < ActionMailer::Base
|
|||
def self.safe_receive(*args)
|
||||
receive(*args)
|
||||
rescue Exception => e
|
||||
logger.error "MailHandler: an unexpected error occurred when receiving email: #{e.message}" if logger
|
||||
Rails.logger.error "MailHandler: an unexpected error occurred when receiving email: #{e.message}"
|
||||
return false
|
||||
end
|
||||
|
||||
|
@ -65,7 +65,7 @@ class MailHandler < ActionMailer::Base
|
|||
%w(project status tracker category priority assigned_to fixed_version).each do |option|
|
||||
options[:issue][option.to_sym] = env[option] if env[option]
|
||||
end
|
||||
%w(allow_override unknown_user no_permission_check no_account_notice default_group project_from_subaddress).each do |option|
|
||||
%w(allow_override unknown_user no_permission_check no_account_notice no_notification default_group project_from_subaddress).each do |option|
|
||||
options[option.to_sym] = env[option] if env[option]
|
||||
end
|
||||
if env['private']
|
||||
|
@ -250,8 +250,8 @@ class MailHandler < ActionMailer::Base
|
|||
|
||||
# add To and Cc as watchers before saving so the watchers can reply to Redmine
|
||||
add_watchers(issue)
|
||||
add_attachments(issue)
|
||||
issue.save!
|
||||
add_attachments(issue)
|
||||
if logger
|
||||
logger.info "MailHandler: issue ##{issue.id} updated by #{user}"
|
||||
end
|
||||
|
@ -286,7 +286,7 @@ class MailHandler < ActionMailer::Base
|
|||
reply
|
||||
else
|
||||
if logger
|
||||
logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic"
|
||||
logger.info "MailHandler: ignoring reply from [#{email.from.first}] to a locked topic"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -311,7 +311,7 @@ class Mailer < ActionMailer::Base
|
|||
end
|
||||
|
||||
# Notifies user that his password was updated
|
||||
def self.password_updated(user)
|
||||
def self.password_updated(user, options={})
|
||||
# Don't send a notification to the dummy email address when changing the password
|
||||
# of the default admin account which is required after the first login
|
||||
# TODO: maybe not the best way to handle this
|
||||
|
@ -320,6 +320,8 @@ class Mailer < ActionMailer::Base
|
|||
security_notification(user,
|
||||
message: :mail_body_password_updated,
|
||||
title: :button_change_password,
|
||||
remote_ip: options[:remote_ip],
|
||||
originator: user,
|
||||
url: {controller: 'my', action: 'password'}
|
||||
).deliver
|
||||
end
|
||||
|
@ -333,7 +335,6 @@ class Mailer < ActionMailer::Base
|
|||
end
|
||||
|
||||
def security_notification(recipients, options={})
|
||||
redmine_headers 'Sender' => User.current.login
|
||||
@user = Array(recipients).detect{|r| r.is_a? User }
|
||||
set_language_if_valid(@user.try :language)
|
||||
@message = l(options[:message],
|
||||
|
@ -341,7 +342,11 @@ class Mailer < ActionMailer::Base
|
|||
value: options[:value]
|
||||
)
|
||||
@title = options[:title] && l(options[:title])
|
||||
@originator = options[:originator] || User.current
|
||||
@remote_ip = options[:remote_ip] || @originator.remote_ip
|
||||
@url = options[:url] && (options[:url].is_a?(Hash) ? url_for(options[:url]) : options[:url])
|
||||
redmine_headers 'Sender' => @originator.login
|
||||
redmine_headers 'Url' => @url
|
||||
mail :to => recipients,
|
||||
:subject => "[#{Setting.app_title}] #{l(:mail_subject_security_notification)}"
|
||||
end
|
||||
|
|
|
@ -319,9 +319,10 @@ class Query < ActiveRecord::Base
|
|||
" INNER JOIN #{table_name_prefix}queries_roles#{table_name_suffix} qr on qr.query_id = q.id" +
|
||||
" INNER JOIN #{MemberRole.table_name} mr ON mr.role_id = qr.role_id" +
|
||||
" INNER JOIN #{Member.table_name} m ON m.id = mr.member_id AND m.user_id = ?" +
|
||||
" INNER JOIN #{Project.table_name} p ON p.id = m.project_id AND p.status <> ?" +
|
||||
" WHERE q.project_id IS NULL OR q.project_id = m.project_id))" +
|
||||
" OR #{table_name}.user_id = ?",
|
||||
VISIBILITY_PUBLIC, VISIBILITY_ROLES, user.id, user.id)
|
||||
VISIBILITY_PUBLIC, VISIBILITY_ROLES, user.id, Project::STATUS_ARCHIVED, user.id)
|
||||
elsif user.logged?
|
||||
scope.where("#{table_name}.visibility = ? OR #{table_name}.user_id = ?", VISIBILITY_PUBLIC, user.id)
|
||||
else
|
||||
|
@ -340,7 +341,7 @@ class Query < ActiveRecord::Base
|
|||
if project
|
||||
(user.roles_for_project(project) & roles).any?
|
||||
else
|
||||
Member.where(:user_id => user.id).joins(:roles).where(:member_roles => {:role_id => roles.map(&:id)}).any?
|
||||
user.memberships.joins(:member_roles).where(:member_roles => {:role_id => roles.map(&:id)}).any?
|
||||
end
|
||||
else
|
||||
user == self.user
|
||||
|
@ -398,6 +399,8 @@ class Query < ActiveRecord::Base
|
|||
params[:v][field] = options[:values]
|
||||
end
|
||||
params[:c] = column_names
|
||||
params[:group_by] = group_by.to_s if group_by.present?
|
||||
params[:t] = totalable_names.map(&:to_s) if totalable_names.any?
|
||||
params[:sort] = sort_criteria.to_param
|
||||
params[:set_filter] = 1
|
||||
params
|
||||
|
|
|
@ -45,7 +45,7 @@ class Version < ActiveRecord::Base
|
|||
scope :like, lambda {|arg|
|
||||
if arg.present?
|
||||
pattern = "%#{arg.to_s.strip}%"
|
||||
where("LOWER(#{Version.table_name}.name) LIKE :p", :p => pattern)
|
||||
where([Redmine::Database.like("#{Version.table_name}.name", '?'), pattern])
|
||||
end
|
||||
}
|
||||
scope :open, lambda { where(:status => 'open') }
|
||||
|
@ -268,7 +268,7 @@ class Version < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def deletable?
|
||||
fixed_issues.empty? && !referenced_by_a_custom_field?
|
||||
fixed_issues.empty? && !referenced_by_a_custom_field? && attachments.empty?
|
||||
end
|
||||
|
||||
def default_project_version
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue