Commit 2e71a5d7 authored by jplang's avatar jplang

Merged rails-5.1 branch (#23630).

git-svn-id: https://svn.redmine.org/redmine/trunk@16859 e93f8b46-1217-0410-a6f0-8f06a7374b81
parent dddf54fd
......@@ -4,18 +4,16 @@ if Gem::Version.new(Bundler::VERSION) < Gem::Version.new('1.5.0')
abort "Redmine requires Bundler 1.5.0 or higher (you're using #{Bundler::VERSION}).\nPlease update with 'gem update bundler'."
end
gem "rails", "4.2.8"
gem "jquery-rails", "~> 3.1.4"
gem "rails", "5.1.2"
gem "coderay", "~> 1.1.1"
gem "request_store", "1.0.5"
gem "mime-types", "~> 3.0"
gem "protected_attributes"
gem "actionpack-xml_parser"
gem "roadie-rails", "~> 1.1.1"
gem "roadie-rails"
gem "roadie", "~> 3.2.1"
gem "mimemagic"
gem "nokogiri", (RUBY_VERSION >= "2.1" ? "~> 1.7.2" : "~> 1.6.8")
gem "nokogiri", "~> 1.7.2"
gem "i18n", "~> 0.7.0"
# Request at least rails-html-sanitizer 1.0.3 because of security advisories
......@@ -85,12 +83,9 @@ group :development do
end
group :test do
gem "minitest"
gem "rails-dom-testing"
gem "mocha"
gem "simplecov", "~> 0.14.1", :require => false
# TODO: remove this after upgrading to Rails 5
gem "test_after_commit", "~> 0.4.2"
# For running UI tests
gem "capybara"
gem "selenium-webdriver", "~> 2.53.4"
......
......@@ -109,9 +109,9 @@ class ImportsController < ApplicationController
end
def update_from_params
if params[:import_settings].is_a?(Hash)
if params[:import_settings].present?
@import.settings ||= {}
@import.settings.merge!(params[:import_settings])
@import.settings.merge!(params[:import_settings].to_unsafe_hash)
@import.save!
end
end
......
......@@ -138,7 +138,7 @@ class MyController < ApplicationController
block_settings = params[:settings] || {}
block_settings.each do |block, settings|
@user.pref.update_block_settings(block, settings)
@user.pref.update_block_settings(block, settings.to_unsafe_hash)
end
@user.pref.save
@updated_blocks = block_settings.keys
......
......@@ -20,15 +20,8 @@ class ProjectEnumerationsController < ApplicationController
before_action :authorize
def update
if params[:enumerations]
saved = Project.transaction do
params[:enumerations].each do |id, activity|
@project.update_or_create_time_entry_activity(id, activity)
end
end
if saved
flash[:notice] = l(:notice_successful_update)
end
if @project.update_or_create_time_entry_activities(update_params)
flash[:notice] = l(:notice_successful_update)
end
redirect_to settings_project_path(@project, :tab => 'activities')
......@@ -41,4 +34,12 @@ class ProjectEnumerationsController < ApplicationController
flash[:notice] = l(:notice_successful_update)
redirect_to settings_project_path(@project, :tab => 'activities')
end
private
def update_params
params.
permit(:enumerations => [:parent_id, :active, {:custom_field_values => {}}]).
require(:enumerations)
end
end
......@@ -68,7 +68,7 @@ class SearchController < ApplicationController
fetcher = Redmine::Search::Fetcher.new(
@question, User.current, @scope, projects_to_search,
:all_words => @all_words, :titles_only => @titles_only, :attachments => @search_attachments, :open_issues => @open_issues,
:cache => params[:page].present?, :params => params
:cache => params[:page].present?, :params => params.to_unsafe_hash
)
if fetcher.tokens.present?
......
......@@ -34,7 +34,7 @@ class SettingsController < ApplicationController
def edit
@notifiables = Redmine::Notifiable.all
if request.post?
errors = Setting.set_all_from_params(params[:settings])
errors = Setting.set_all_from_params(params[:settings].to_unsafe_hash)
if errors.blank?
flash[:notice] = l(:notice_successful_update)
redirect_to settings_path(:tab => params[:tab])
......
......@@ -101,7 +101,7 @@ class UsersController < ApplicationController
format.html {
flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user)))
if params[:continue]
attrs = params[:user].slice(:generate_password)
attrs = {:generate_password => @user.generate_password }
redirect_to new_user_path(:user => attrs)
else
redirect_to edit_user_path(@user)
......
......@@ -1440,7 +1440,7 @@ module ApplicationHelper
# Returns the javascript tags that are included in the html layout head
def javascript_heads
tags = javascript_include_tag('jquery-1.11.1-ui-1.11.0-ujs-3.1.4', 'application', 'responsive')
tags = javascript_include_tag('jquery-1.11.1-ui-1.11.0-ujs-5.1.2', 'application', 'responsive')
unless User.current.pref.warn_on_leaving_unsaved == '0'
tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
end
......
......@@ -28,7 +28,6 @@ class Attachment < ActiveRecord::Base
validates_length_of :disk_filename, :maximum => 255
validates_length_of :description, :maximum => 255
validate :validate_max_file_size, :validate_file_extension
attr_protected :id
acts_as_event :title => :filename,
:url => Proc.new {|o| {:controller => 'attachments', :action => 'show', :id => o.id, :filename => o.filename}}
......
......@@ -30,7 +30,6 @@ class AuthSource < ActiveRecord::Base
validates_presence_of :name
validates_uniqueness_of :name
validates_length_of :name, :maximum => 60
attr_protected :id
safe_attributes 'name',
'host',
......
......@@ -28,7 +28,6 @@ class Board < ActiveRecord::Base
validates_length_of :name, :maximum => 30
validates_length_of :description, :maximum => 255
validate :validate_board
attr_protected :id
scope :visible, lambda {|*args|
joins(:project).
......
......@@ -21,7 +21,6 @@ class Change < ActiveRecord::Base
validates_presence_of :changeset_id, :action, :path
before_save :init_path
before_validation :replace_invalid_utf8_of_path
attr_protected :id
def replace_invalid_utf8_of_path
self.path = Redmine::CodesetUtil.replace_invalid_utf8(self.path)
......
......@@ -46,7 +46,6 @@ class Changeset < ActiveRecord::Base
validates_presence_of :repository_id, :revision, :committed_on, :commit_date
validates_uniqueness_of :revision, :scope => :repository_id
validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
attr_protected :id
scope :visible, lambda {|*args|
joins(:repository => :project).
......
......@@ -21,7 +21,6 @@ class Comment < ActiveRecord::Base
belongs_to :author, :class_name => 'User'
validates_presence_of :commented, :author, :comments
attr_protected :id
after_create :send_notification
......
......@@ -35,7 +35,6 @@ class CustomField < ActiveRecord::Base
validates_length_of :regexp, maximum: 255
validates_inclusion_of :field_format, :in => Proc.new { Redmine::FieldFormat.available_formats }
validate :validate_custom_field
attr_protected :id
before_validation :set_searchable
before_save do |field|
......@@ -43,7 +42,7 @@ class CustomField < ActiveRecord::Base
end
after_save :handle_multiplicity_change
after_save do |field|
if field.visible_changed? && field.visible
if field.saved_change_to_visible? && field.visible
field.roles.clear
end
end
......@@ -316,7 +315,7 @@ class CustomField < ActiveRecord::Base
# Removes multiple values for the custom field after setting the multiple attribute to false
# We kepp the value with the highest id for each customized object
def handle_multiplicity_change
if !new_record? && multiple_was && !multiple
if !new_record? && multiple_before_last_save && !multiple
ids = custom_values.
where("EXISTS(SELECT 1 FROM #{CustomValue.table_name} cve WHERE cve.custom_field_id = #{CustomValue.table_name}.custom_field_id" +
" AND cve.customized_type = #{CustomValue.table_name}.customized_type AND cve.customized_id = #{CustomValue.table_name}.customized_id" +
......
......@@ -18,7 +18,6 @@
class CustomValue < ActiveRecord::Base
belongs_to :custom_field
belongs_to :customized, :polymorphic => true
attr_protected :id
after_save :custom_field_after_save_custom_value
......
......@@ -31,7 +31,6 @@ class Document < ActiveRecord::Base
validates_presence_of :project, :title, :category
validates_length_of :title, :maximum => 255
attr_protected :id
after_create :send_notification
......
......@@ -19,7 +19,6 @@ class EmailAddress < ActiveRecord::Base
include Redmine::SafeAttributes
belongs_to :user
attr_protected :id
after_create :deliver_security_notification_create
after_update :destroy_tokens, :deliver_security_notification_update
......@@ -63,17 +62,17 @@ class EmailAddress < ActiveRecord::Base
# send a security notification to user that an email has been changed (notified/not notified)
def deliver_security_notification_update
if address_changed?
recipients = [user, address_was]
if saved_change_to_address?
recipients = [user, address_before_last_save]
options = {
message: :mail_body_security_notification_change_to,
field: :field_mail,
value: address
}
elsif notify_changed?
elsif saved_change_to_notify?
recipients = [user, address]
options = {
message: notify_was ? :mail_body_security_notification_notify_disabled : :mail_body_security_notification_notify_enabled,
message: notify_before_last_save ? :mail_body_security_notification_notify_disabled : :mail_body_security_notification_notify_enabled,
value: address
}
end
......@@ -103,7 +102,7 @@ class EmailAddress < ActiveRecord::Base
# This helps to keep the account secure in case the associated email account
# was compromised.
def destroy_tokens
if address_changed? || destroyed?
if saved_change_to_address? || destroyed?
tokens = ['recovery']
Token.where(:user_id => user_id, :action => tokens).delete_all
end
......
......@@ -21,7 +21,6 @@ class EnabledModule < ActiveRecord::Base
validates_presence_of :name
validates_uniqueness_of :name, :scope => :project_id
attr_protected :id
after_create :module_enabled
......
......@@ -29,8 +29,6 @@ class Enumeration < ActiveRecord::Base
before_destroy :check_integrity
before_save :check_default
attr_protected :type
validates_presence_of :name
validates_uniqueness_of :name, :scope => [:type, :project_id]
validates_length_of :name, :maximum => 30
......@@ -148,7 +146,7 @@ class Enumeration < ActiveRecord::Base
# position as the overridden enumeration
def update_position
super
if position_changed?
if saved_change_to_position?
self.class.where.not(:parent_id => nil).update_all(
"position = coalesce((
select position
......
......@@ -28,7 +28,6 @@ class Group < Principal
validates_presence_of :lastname
validates_uniqueness_of :lastname, :case_sensitive => false
validates_length_of :lastname, :maximum => 255
attr_protected :id
self.valid_statuses = [STATUS_ACTIVE]
......
......@@ -69,7 +69,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).
......@@ -108,16 +107,14 @@ 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
# Keep it at the end of after_save callbacks
after_save :clear_assigned_to_was
# Returns a SQL conditions string used to find all issues visible by the specified user
def self.visible_condition(user, options={})
......@@ -208,7 +205,7 @@ class Issue < ActiveRecord::Base
end
end
def create_or_update
def create_or_update(*args)
super
ensure
@status_was = nil
......@@ -512,6 +509,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)
......@@ -586,8 +587,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
......@@ -1007,32 +1007,27 @@ 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
# This is how ActiveRecord::AttributeMethods::Dirty checks if we're in a after_* callback
if previous_assigned_to_id = mutation_tracker.equal?(mutations_from_database) ? assigned_to_id_in_database : assigned_to_id_before_last_save
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
......@@ -1587,7 +1582,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
......@@ -1644,7 +1639,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)
......@@ -1652,7 +1647,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|
......@@ -1789,7 +1784,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
end
......@@ -1848,18 +1843,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|
......
......@@ -24,7 +24,6 @@ class IssueCategory < ActiveRecord::Base
validates_presence_of :name
validates_uniqueness_of :name, :scope => [:project_id]
validates_length_of :name, :maximum => 60
attr_protected :id
safe_attributes 'name', 'assigned_to_id'
......
......@@ -16,8 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class IssueCustomField < CustomField
has_and_belongs_to_many :projects, :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", :foreign_key => "custom_field_id"
has_and_belongs_to_many :trackers, :join_table => "#{table_name_prefix}custom_fields_trackers#{table_name_suffix}", :foreign_key => "custom_field_id"
has_and_belongs_to_many :projects, :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", :foreign_key => "custom_field_id", :autosave => true
has_and_belongs_to_many :trackers, :join_table => "#{table_name_prefix}custom_fields_trackers#{table_name_suffix}", :foreign_key => "custom_field_id", :autosave => true
has_many :issues, :through => :issue_custom_values
safe_attributes 'project_ids',
......
......@@ -19,7 +19,7 @@ class IssuePriority < Enumeration
has_many :issues, :foreign_key => 'priority_id'
after_destroy {|priority| priority.class.compute_position_names}
after_save {|priority| priority.class.compute_position_names if (priority.position_changed? && priority.position) || priority.active_changed?}
after_save {|priority| priority.class.compute_position_names if (priority.saved_change_to_position? && priority.position) || priority.saved_change_to_active?}
OptionName = :enumeration_issue_priorities
......
......@@ -72,7 +72,6 @@ class IssueRelation < ActiveRecord::Base
validates_uniqueness_of :issue_to_id, :scope => :issue_from_id
validate :validate_issue_relation
attr_protected :issue_from_id, :issue_to_id
before_save :handle_issue_order
after_create :call_issues_relation_added_callback
after_destroy :call_issues_relation_removed_callback
......@@ -82,6 +81,10 @@ class IssueRelation < ActiveRecord::Base
'issue_to_id'
def safe_attributes=(attrs, user=User.current)
if attrs.respond_to?(:to_unsafe_hash)
attrs = attrs.to_unsafe_hash
end
return unless attrs.is_a?(Hash)
attrs = attrs.deep_dup
......
......@@ -30,7 +30,6 @@ class IssueStatus < ActiveRecord::Base
validates_uniqueness_of :name
validates_length_of :name, :maximum => 30
validates_inclusion_of :default_done_ratio, :in => 0..100, :allow_nil => true
attr_protected :id
scope :sorted, lambda { order(:position) }
scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
......@@ -89,7 +88,7 @@ class IssueStatus < ActiveRecord::Base
# Updates issues closed_on attribute when an existing status is set as closed.
def handle_is_closed_change
if is_closed_changed? && is_closed == true
if saved_change_to_is_closed? && is_closed == true
# First we update issues that have a journal for when the current status was set,
# a subselect is used to update all issues with a single query
subquery = Journal.joins(:details).
......
......@@ -26,7 +26,6 @@ class Journal < ActiveRecord::Base
belongs_to :user
has_many :details, :class_name => "JournalDetail", :dependent => :delete_all, :inverse_of => :journal
attr_accessor :indice
attr_protected :id
acts_as_event :title => Proc.new {|o| status = ((s = o.new_status) ? " (#{s})" : nil); "#{o.issue.tracker} ##{o.issue.id}#{status}: #{o.issue.subject}" },
:description => :notes,
......
......@@ -17,7 +17,6 @@
class JournalDetail < ActiveRecord::Base
belongs_to :journal
attr_protected :id
def custom_field
if property == 'cf'
......
......@@ -25,7 +25,6 @@ class Member < ActiveRecord::Base
validates_presence_of :principal, :project
validates_uniqueness_of :user_id, :scope => :project_id
validate :validate_role
attr_protected :id
before_destroy :set_issue_category_nil, :remove_from_project_default_assigned_to
......
......@@ -26,7 +26,6 @@ class MemberRole < ActiveRecord::Base
validates_presence_of :role
validate :validate_role_member
attr_protected :id