##// END OF EJS Templates
Support of default Active Record (I18n) transliteration paths (#22383)....
Jean-Philippe Lang -
r14948:223141a20b27
parent child
Show More
@@ -1,180 +1,194
1 require 'active_record'
1 require 'active_record'
2
2
3 module ActiveRecord
3 module ActiveRecord
4 class Base
4 class Base
5 include Redmine::I18n
5 include Redmine::I18n
6 # Translate attribute names for validation errors display
6 # Translate attribute names for validation errors display
7 def self.human_attribute_name(attr, *args)
7 def self.human_attribute_name(attr, options = {})
8 attr = attr.to_s.sub(/_id$/, '').sub(/^.+\./, '')
8 prepared_attr = attr.to_s.sub(/_id$/, '').sub(/^.+\./, '')
9 l("field_#{name.underscore.gsub('/', '_')}_#{attr}", :default => ["field_#{attr}".to_sym, attr])
9
10 redmine_default =
11 [
12 :"field_#{name.underscore.gsub('/', '_')}_#{prepared_attr}",
13 :"field_#{prepared_attr}"
14 ]
15
16 if options[:default].present?
17 options[:default] = [options[:default]] unless options[:default].is_a? Array
18 options[:default].unshift redmine_default
19 else
20 options[:default] = redmine_default
21 end
22
23 super
10 end
24 end
11 end
25 end
12
26
13 # Undefines private Kernel#open method to allow using `open` scopes in models.
27 # Undefines private Kernel#open method to allow using `open` scopes in models.
14 # See Defect #11545 (http://www.redmine.org/issues/11545) for details.
28 # See Defect #11545 (http://www.redmine.org/issues/11545) for details.
15 class Base
29 class Base
16 class << self
30 class << self
17 undef open
31 undef open
18 end
32 end
19 end
33 end
20 class Relation ; undef open ; end
34 class Relation ; undef open ; end
21 end
35 end
22
36
23 module ActionView
37 module ActionView
24 module Helpers
38 module Helpers
25 module DateHelper
39 module DateHelper
26 # distance_of_time_in_words breaks when difference is greater than 30 years
40 # distance_of_time_in_words breaks when difference is greater than 30 years
27 def distance_of_date_in_words(from_date, to_date = 0, options = {})
41 def distance_of_date_in_words(from_date, to_date = 0, options = {})
28 from_date = from_date.to_date if from_date.respond_to?(:to_date)
42 from_date = from_date.to_date if from_date.respond_to?(:to_date)
29 to_date = to_date.to_date if to_date.respond_to?(:to_date)
43 to_date = to_date.to_date if to_date.respond_to?(:to_date)
30 distance_in_days = (to_date - from_date).abs
44 distance_in_days = (to_date - from_date).abs
31
45
32 I18n.with_options :locale => options[:locale], :scope => :'datetime.distance_in_words' do |locale|
46 I18n.with_options :locale => options[:locale], :scope => :'datetime.distance_in_words' do |locale|
33 case distance_in_days
47 case distance_in_days
34 when 0..60 then locale.t :x_days, :count => distance_in_days.round
48 when 0..60 then locale.t :x_days, :count => distance_in_days.round
35 when 61..720 then locale.t :about_x_months, :count => (distance_in_days / 30).round
49 when 61..720 then locale.t :about_x_months, :count => (distance_in_days / 30).round
36 else locale.t :over_x_years, :count => (distance_in_days / 365).floor
50 else locale.t :over_x_years, :count => (distance_in_days / 365).floor
37 end
51 end
38 end
52 end
39 end
53 end
40 end
54 end
41 end
55 end
42
56
43 class Resolver
57 class Resolver
44 def find_all(name, prefix=nil, partial=false, details={}, key=nil, locals=[])
58 def find_all(name, prefix=nil, partial=false, details={}, key=nil, locals=[])
45 cached(key, [name, prefix, partial], details, locals) do
59 cached(key, [name, prefix, partial], details, locals) do
46 if (details[:formats] & [:xml, :json]).any?
60 if (details[:formats] & [:xml, :json]).any?
47 details = details.dup
61 details = details.dup
48 details[:formats] = details[:formats].dup + [:api]
62 details[:formats] = details[:formats].dup + [:api]
49 end
63 end
50 find_templates(name, prefix, partial, details)
64 find_templates(name, prefix, partial, details)
51 end
65 end
52 end
66 end
53 end
67 end
54 end
68 end
55
69
56 ActionView::Base.field_error_proc = Proc.new{ |html_tag, instance| html_tag || ''.html_safe }
70 ActionView::Base.field_error_proc = Proc.new{ |html_tag, instance| html_tag || ''.html_safe }
57
71
58 # HTML5: <option value=""></option> is invalid, use <option value="">&nbsp;</option> instead
72 # HTML5: <option value=""></option> is invalid, use <option value="">&nbsp;</option> instead
59 module ActionView
73 module ActionView
60 module Helpers
74 module Helpers
61 module Tags
75 module Tags
62 class Base
76 class Base
63 private
77 private
64 def add_options_with_non_empty_blank_option(option_tags, options, value = nil)
78 def add_options_with_non_empty_blank_option(option_tags, options, value = nil)
65 if options[:include_blank] == true
79 if options[:include_blank] == true
66 options = options.dup
80 options = options.dup
67 options[:include_blank] = '&nbsp;'.html_safe
81 options[:include_blank] = '&nbsp;'.html_safe
68 end
82 end
69 add_options_without_non_empty_blank_option(option_tags, options, value)
83 add_options_without_non_empty_blank_option(option_tags, options, value)
70 end
84 end
71 alias_method_chain :add_options, :non_empty_blank_option
85 alias_method_chain :add_options, :non_empty_blank_option
72 end
86 end
73 end
87 end
74
88
75 module FormTagHelper
89 module FormTagHelper
76 def select_tag_with_non_empty_blank_option(name, option_tags = nil, options = {})
90 def select_tag_with_non_empty_blank_option(name, option_tags = nil, options = {})
77 if options.delete(:include_blank)
91 if options.delete(:include_blank)
78 options[:prompt] = '&nbsp;'.html_safe
92 options[:prompt] = '&nbsp;'.html_safe
79 end
93 end
80 select_tag_without_non_empty_blank_option(name, option_tags, options)
94 select_tag_without_non_empty_blank_option(name, option_tags, options)
81 end
95 end
82 alias_method_chain :select_tag, :non_empty_blank_option
96 alias_method_chain :select_tag, :non_empty_blank_option
83 end
97 end
84
98
85 module FormOptionsHelper
99 module FormOptionsHelper
86 def options_for_select_with_non_empty_blank_option(container, selected = nil)
100 def options_for_select_with_non_empty_blank_option(container, selected = nil)
87 if container.is_a?(Array)
101 if container.is_a?(Array)
88 container = container.map {|element| element.blank? ? ["&nbsp;".html_safe, ""] : element}
102 container = container.map {|element| element.blank? ? ["&nbsp;".html_safe, ""] : element}
89 end
103 end
90 options_for_select_without_non_empty_blank_option(container, selected)
104 options_for_select_without_non_empty_blank_option(container, selected)
91 end
105 end
92 alias_method_chain :options_for_select, :non_empty_blank_option
106 alias_method_chain :options_for_select, :non_empty_blank_option
93 end
107 end
94 end
108 end
95 end
109 end
96
110
97 require 'mail'
111 require 'mail'
98
112
99 module DeliveryMethods
113 module DeliveryMethods
100 class AsyncSMTP < ::Mail::SMTP
114 class AsyncSMTP < ::Mail::SMTP
101 def deliver!(*args)
115 def deliver!(*args)
102 Thread.start do
116 Thread.start do
103 super *args
117 super *args
104 end
118 end
105 end
119 end
106 end
120 end
107
121
108 class AsyncSendmail < ::Mail::Sendmail
122 class AsyncSendmail < ::Mail::Sendmail
109 def deliver!(*args)
123 def deliver!(*args)
110 Thread.start do
124 Thread.start do
111 super *args
125 super *args
112 end
126 end
113 end
127 end
114 end
128 end
115
129
116 class TmpFile
130 class TmpFile
117 def initialize(*args); end
131 def initialize(*args); end
118
132
119 def deliver!(mail)
133 def deliver!(mail)
120 dest_dir = File.join(Rails.root, 'tmp', 'emails')
134 dest_dir = File.join(Rails.root, 'tmp', 'emails')
121 Dir.mkdir(dest_dir) unless File.directory?(dest_dir)
135 Dir.mkdir(dest_dir) unless File.directory?(dest_dir)
122 File.open(File.join(dest_dir, mail.message_id.gsub(/[<>]/, '') + '.eml'), 'wb') {|f| f.write(mail.encoded) }
136 File.open(File.join(dest_dir, mail.message_id.gsub(/[<>]/, '') + '.eml'), 'wb') {|f| f.write(mail.encoded) }
123 end
137 end
124 end
138 end
125 end
139 end
126
140
127 ActionMailer::Base.add_delivery_method :async_smtp, DeliveryMethods::AsyncSMTP
141 ActionMailer::Base.add_delivery_method :async_smtp, DeliveryMethods::AsyncSMTP
128 ActionMailer::Base.add_delivery_method :async_sendmail, DeliveryMethods::AsyncSendmail
142 ActionMailer::Base.add_delivery_method :async_sendmail, DeliveryMethods::AsyncSendmail
129 ActionMailer::Base.add_delivery_method :tmp_file, DeliveryMethods::TmpFile
143 ActionMailer::Base.add_delivery_method :tmp_file, DeliveryMethods::TmpFile
130
144
131 # Changes how sent emails are logged
145 # Changes how sent emails are logged
132 # Rails doesn't log cc and bcc which is misleading when using bcc only (#12090)
146 # Rails doesn't log cc and bcc which is misleading when using bcc only (#12090)
133 module ActionMailer
147 module ActionMailer
134 class LogSubscriber < ActiveSupport::LogSubscriber
148 class LogSubscriber < ActiveSupport::LogSubscriber
135 def deliver(event)
149 def deliver(event)
136 recipients = [:to, :cc, :bcc].inject("") do |s, header|
150 recipients = [:to, :cc, :bcc].inject("") do |s, header|
137 r = Array.wrap(event.payload[header])
151 r = Array.wrap(event.payload[header])
138 if r.any?
152 if r.any?
139 s << "\n #{header}: #{r.join(', ')}"
153 s << "\n #{header}: #{r.join(', ')}"
140 end
154 end
141 s
155 s
142 end
156 end
143 info("\nSent email \"#{event.payload[:subject]}\" (%1.fms)#{recipients}" % event.duration)
157 info("\nSent email \"#{event.payload[:subject]}\" (%1.fms)#{recipients}" % event.duration)
144 debug(event.payload[:mail])
158 debug(event.payload[:mail])
145 end
159 end
146 end
160 end
147 end
161 end
148
162
149 # #deliver is deprecated in Rails 4.2
163 # #deliver is deprecated in Rails 4.2
150 # Prevents massive deprecation warnings
164 # Prevents massive deprecation warnings
151 module ActionMailer
165 module ActionMailer
152 class MessageDelivery < Delegator
166 class MessageDelivery < Delegator
153 def deliver
167 def deliver
154 deliver_now
168 deliver_now
155 end
169 end
156 end
170 end
157 end
171 end
158
172
159 module ActionController
173 module ActionController
160 module MimeResponds
174 module MimeResponds
161 class Collector
175 class Collector
162 def api(&block)
176 def api(&block)
163 any(:xml, :json, &block)
177 any(:xml, :json, &block)
164 end
178 end
165 end
179 end
166 end
180 end
167 end
181 end
168
182
169 module ActionController
183 module ActionController
170 class Base
184 class Base
171 # Displays an explicit message instead of a NoMethodError exception
185 # Displays an explicit message instead of a NoMethodError exception
172 # when trying to start Redmine with an old session_store.rb
186 # when trying to start Redmine with an old session_store.rb
173 # TODO: remove it in a later version
187 # TODO: remove it in a later version
174 def self.session=(*args)
188 def self.session=(*args)
175 $stderr.puts "Please remove config/initializers/session_store.rb and run `rake generate_secret_token`.\n" +
189 $stderr.puts "Please remove config/initializers/session_store.rb and run `rake generate_secret_token`.\n" +
176 "Setting the session secret with ActionController.session= is no longer supported."
190 "Setting the session secret with ActionController.session= is no longer supported."
177 exit 1
191 exit 1
178 end
192 end
179 end
193 end
180 end
194 end
@@ -1,80 +1,80
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class Redmine::ApiTest::IssueRelationsTest < Redmine::ApiTest::Base
20 class Redmine::ApiTest::IssueRelationsTest < Redmine::ApiTest::Base
21 fixtures :projects, :trackers, :issue_statuses, :issues,
21 fixtures :projects, :trackers, :issue_statuses, :issues,
22 :enumerations, :users, :issue_categories,
22 :enumerations, :users, :issue_categories,
23 :projects_trackers,
23 :projects_trackers,
24 :roles,
24 :roles,
25 :member_roles,
25 :member_roles,
26 :members,
26 :members,
27 :enabled_modules,
27 :enabled_modules,
28 :issue_relations
28 :issue_relations
29
29
30 test "GET /issues/:issue_id/relations.xml should return issue relations" do
30 test "GET /issues/:issue_id/relations.xml should return issue relations" do
31 get '/issues/9/relations.xml', {}, credentials('jsmith')
31 get '/issues/9/relations.xml', {}, credentials('jsmith')
32
32
33 assert_response :success
33 assert_response :success
34 assert_equal 'application/xml', @response.content_type
34 assert_equal 'application/xml', @response.content_type
35
35
36 assert_select 'relations[type=array] relation id', :text => '1'
36 assert_select 'relations[type=array] relation id', :text => '1'
37 end
37 end
38
38
39 test "POST /issues/:issue_id/relations.xml should create the relation" do
39 test "POST /issues/:issue_id/relations.xml should create the relation" do
40 assert_difference('IssueRelation.count') do
40 assert_difference('IssueRelation.count') do
41 post '/issues/2/relations.xml', {:relation => {:issue_to_id => 7, :relation_type => 'relates'}}, credentials('jsmith')
41 post '/issues/2/relations.xml', {:relation => {:issue_to_id => 7, :relation_type => 'relates'}}, credentials('jsmith')
42 end
42 end
43
43
44 relation = IssueRelation.order('id DESC').first
44 relation = IssueRelation.order('id DESC').first
45 assert_equal 2, relation.issue_from_id
45 assert_equal 2, relation.issue_from_id
46 assert_equal 7, relation.issue_to_id
46 assert_equal 7, relation.issue_to_id
47 assert_equal 'relates', relation.relation_type
47 assert_equal 'relates', relation.relation_type
48
48
49 assert_response :created
49 assert_response :created
50 assert_equal 'application/xml', @response.content_type
50 assert_equal 'application/xml', @response.content_type
51 assert_select 'relation id', :text => relation.id.to_s
51 assert_select 'relation id', :text => relation.id.to_s
52 end
52 end
53
53
54 test "POST /issues/:issue_id/relations.xml with failure should return errors" do
54 test "POST /issues/:issue_id/relations.xml with failure should return errors" do
55 assert_no_difference('IssueRelation.count') do
55 assert_no_difference('IssueRelation.count') do
56 post '/issues/2/relations.xml', {:relation => {:issue_to_id => 7, :relation_type => 'foo'}}, credentials('jsmith')
56 post '/issues/2/relations.xml', {:relation => {:issue_to_id => 7, :relation_type => 'foo'}}, credentials('jsmith')
57 end
57 end
58
58
59 assert_response :unprocessable_entity
59 assert_response :unprocessable_entity
60 assert_select 'errors error', :text => /relation_type is not included in the list/
60 assert_select 'errors error', :text => /Relation type is not included in the list/
61 end
61 end
62
62
63 test "GET /relations/:id.xml should return the relation" do
63 test "GET /relations/:id.xml should return the relation" do
64 get '/relations/2.xml', {}, credentials('jsmith')
64 get '/relations/2.xml', {}, credentials('jsmith')
65
65
66 assert_response :success
66 assert_response :success
67 assert_equal 'application/xml', @response.content_type
67 assert_equal 'application/xml', @response.content_type
68 assert_select 'relation id', :text => '2'
68 assert_select 'relation id', :text => '2'
69 end
69 end
70
70
71 test "DELETE /relations/:id.xml should delete the relation" do
71 test "DELETE /relations/:id.xml should delete the relation" do
72 assert_difference('IssueRelation.count', -1) do
72 assert_difference('IssueRelation.count', -1) do
73 delete '/relations/2.xml', {}, credentials('jsmith')
73 delete '/relations/2.xml', {}, credentials('jsmith')
74 end
74 end
75
75
76 assert_response :ok
76 assert_response :ok
77 assert_equal '', @response.body
77 assert_equal '', @response.body
78 assert_nil IssueRelation.find_by_id(2)
78 assert_nil IssueRelation.find_by_id(2)
79 end
79 end
80 end
80 end
General Comments 0
You need to be logged in to leave comments. Login now