##// END OF EJS Templates
link_to in Redmine::Hook::ViewListener omits relative url root (#19024)....
Jean-Philippe Lang -
r13578:d347fd4d39ff
parent child
Show More
@@ -1,175 +1,175
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 module Redmine
19 19 module Hook
20 20 @@listener_classes = []
21 21 @@listeners = nil
22 22 @@hook_listeners = {}
23 23
24 24 class << self
25 25 # Adds a listener class.
26 26 # Automatically called when a class inherits from Redmine::Hook::Listener.
27 27 def add_listener(klass)
28 28 raise "Hooks must include Singleton module." unless klass.included_modules.include?(Singleton)
29 29 @@listener_classes << klass
30 30 clear_listeners_instances
31 31 end
32 32
33 33 # Returns all the listener instances.
34 34 def listeners
35 35 @@listeners ||= @@listener_classes.collect {|listener| listener.instance}
36 36 end
37 37
38 38 # Returns the listener instances for the given hook.
39 39 def hook_listeners(hook)
40 40 @@hook_listeners[hook] ||= listeners.select {|listener| listener.respond_to?(hook)}
41 41 end
42 42
43 43 # Clears all the listeners.
44 44 def clear_listeners
45 45 @@listener_classes = []
46 46 clear_listeners_instances
47 47 end
48 48
49 49 # Clears all the listeners instances.
50 50 def clear_listeners_instances
51 51 @@listeners = nil
52 52 @@hook_listeners = {}
53 53 end
54 54
55 55 # Calls a hook.
56 56 # Returns the listeners response.
57 57 def call_hook(hook, context={})
58 58 [].tap do |response|
59 59 hls = hook_listeners(hook)
60 60 if hls.any?
61 61 hls.each {|listener| response << listener.send(hook, context)}
62 62 end
63 63 end
64 64 end
65 65 end
66 66
67 67 # Base class for hook listeners.
68 68 class Listener
69 69 include Singleton
70 70 include Redmine::I18n
71 71
72 72 # Registers the listener
73 73 def self.inherited(child)
74 74 Redmine::Hook.add_listener(child)
75 75 super
76 76 end
77 77
78 78 end
79 79
80 80 # Listener class used for views hooks.
81 81 # Listeners that inherit this class will include various helpers by default.
82 82 class ViewListener < Listener
83 83 include ERB::Util
84 84 include ActionView::Helpers::TagHelper
85 85 include ActionView::Helpers::FormHelper
86 86 include ActionView::Helpers::FormTagHelper
87 87 include ActionView::Helpers::FormOptionsHelper
88 88 include ActionView::Helpers::JavaScriptHelper
89 89 include ActionView::Helpers::NumberHelper
90 90 include ActionView::Helpers::UrlHelper
91 91 include ActionView::Helpers::AssetTagHelper
92 92 include ActionView::Helpers::TextHelper
93 93 include Rails.application.routes.url_helpers
94 94 include ApplicationHelper
95 95
96 96 # Default to creating links using only the path. Subclasses can
97 97 # change this default as needed
98 98 def self.default_url_options
99 {:only_path => true }
99 {:only_path => true, :script_name => Redmine::Utils.relative_url_root}
100 100 end
101 101
102 102 # Helper method to directly render using the context,
103 103 # render_options must be valid #render options.
104 104 #
105 105 # class MyHook < Redmine::Hook::ViewListener
106 106 # render_on :view_issues_show_details_bottom, :partial => "show_more_data"
107 107 # end
108 108 #
109 109 # class MultipleHook < Redmine::Hook::ViewListener
110 110 # render_on :view_issues_show_details_bottom,
111 111 # {:partial => "show_more_data"},
112 112 # {:partial => "show_even_more_data"}
113 113 # end
114 114 #
115 115 def self.render_on(hook, *render_options)
116 116 define_method hook do |context|
117 117 render_options.map do |options|
118 118 if context[:hook_caller].respond_to?(:render)
119 119 context[:hook_caller].send(:render, {:locals => context}.merge(options))
120 120 elsif context[:controller].is_a?(ActionController::Base)
121 121 context[:controller].send(:render_to_string, {:locals => context}.merge(options))
122 122 else
123 123 raise "Cannot render #{self.name} hook from #{context[:hook_caller].class.name}"
124 124 end
125 125 end
126 126 end
127 127 end
128 128
129 129 def controller
130 130 nil
131 131 end
132 132
133 133 def config
134 134 ActionController::Base.config
135 135 end
136 136 end
137 137
138 138 # Helper module included in ApplicationHelper and ActionController so that
139 139 # hooks can be called in views like this:
140 140 #
141 141 # <%= call_hook(:some_hook) %>
142 142 # <%= call_hook(:another_hook, :foo => 'bar') %>
143 143 #
144 144 # Or in controllers like:
145 145 # call_hook(:some_hook)
146 146 # call_hook(:another_hook, :foo => 'bar')
147 147 #
148 148 # Hooks added to views will be concatenated into a string. Hooks added to
149 149 # controllers will return an array of results.
150 150 #
151 151 # Several objects are automatically added to the call context:
152 152 #
153 153 # * project => current project
154 154 # * request => Request instance
155 155 # * controller => current Controller instance
156 156 # * hook_caller => object that called the hook
157 157 #
158 158 module Helper
159 159 def call_hook(hook, context={})
160 160 if is_a?(ActionController::Base)
161 161 default_context = {:controller => self, :project => @project, :request => request, :hook_caller => self}
162 162 Redmine::Hook.call_hook(hook, default_context.merge(context))
163 163 else
164 164 default_context = { :project => @project, :hook_caller => self }
165 165 default_context[:controller] = controller if respond_to?(:controller)
166 166 default_context[:request] = request if respond_to?(:request)
167 167 Redmine::Hook.call_hook(hook, default_context.merge(context)).join(' ').html_safe
168 168 end
169 169 end
170 170 end
171 171 end
172 172 end
173 173
174 174 ApplicationHelper.send(:include, Redmine::Hook::Helper)
175 175 ActionController::Base.send(:include, Redmine::Hook::Helper)
@@ -1,180 +1,189
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../../../test_helper', __FILE__)
19 19
20 20 class Redmine::Hook::ManagerTest < ActionView::TestCase
21 21 fixtures :projects, :users, :members, :member_roles, :roles,
22 22 :groups_users,
23 23 :email_addresses,
24 24 :trackers, :projects_trackers,
25 25 :enabled_modules,
26 26 :versions,
27 27 :issue_statuses, :issue_categories, :issue_relations,
28 28 :enumerations,
29 29 :issues
30 30
31 31 # Some hooks that are manually registered in these tests
32 32 class TestHook < Redmine::Hook::ViewListener; end
33 33
34 34 class TestHook1 < TestHook
35 35 def view_layouts_base_html_head(context)
36 36 'Test hook 1 listener.'
37 37 end
38 38 end
39 39
40 40 class TestHook2 < TestHook
41 41 def view_layouts_base_html_head(context)
42 42 'Test hook 2 listener.'
43 43 end
44 44 end
45 45
46 46 class TestHook3 < TestHook
47 47 def view_layouts_base_html_head(context)
48 48 "Context keys: #{context.keys.collect(&:to_s).sort.join(', ')}."
49 49 end
50 50 end
51 51
52 52 class TestLinkToHook < TestHook
53 53 def view_layouts_base_html_head(context)
54 54 link_to('Issues', :controller => 'issues')
55 55 end
56 56 end
57 57
58 58 class TestHookHelperController < ActionController::Base
59 59 include Redmine::Hook::Helper
60 60 end
61 61
62 62 class TestHookHelperView < ActionView::Base
63 63 include Redmine::Hook::Helper
64 64 end
65 65
66 66 Redmine::Hook.clear_listeners
67 67
68 68 def setup
69 69 @hook_module = Redmine::Hook
70 70 @hook_module.clear_listeners
71 71 end
72 72
73 73 def teardown
74 74 @hook_module.clear_listeners
75 75 end
76 76
77 77 def test_clear_listeners
78 78 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
79 79 @hook_module.add_listener(TestHook1)
80 80 @hook_module.add_listener(TestHook2)
81 81 assert_equal 2, @hook_module.hook_listeners(:view_layouts_base_html_head).size
82 82
83 83 @hook_module.clear_listeners
84 84 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
85 85 end
86 86
87 87 def test_add_listener
88 88 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
89 89 @hook_module.add_listener(TestHook1)
90 90 assert_equal 1, @hook_module.hook_listeners(:view_layouts_base_html_head).size
91 91 end
92 92
93 93 def test_call_hook
94 94 @hook_module.add_listener(TestHook1)
95 95 assert_equal ['Test hook 1 listener.'], hook_helper.call_hook(:view_layouts_base_html_head)
96 96 end
97 97
98 98 def test_call_hook_with_context
99 99 @hook_module.add_listener(TestHook3)
100 100 assert_equal ['Context keys: bar, controller, foo, hook_caller, project, request.'],
101 101 hook_helper.call_hook(:view_layouts_base_html_head, :foo => 1, :bar => 'a')
102 102 end
103 103
104 104 def test_call_hook_with_multiple_listeners
105 105 @hook_module.add_listener(TestHook1)
106 106 @hook_module.add_listener(TestHook2)
107 107 assert_equal ['Test hook 1 listener.', 'Test hook 2 listener.'], hook_helper.call_hook(:view_layouts_base_html_head)
108 108 end
109 109
110 110 # Context: Redmine::Hook::Helper.call_hook default_url
111 111 def test_call_hook_default_url_options
112 112 @hook_module.add_listener(TestLinkToHook)
113 113
114 114 assert_equal ['<a href="/issues">Issues</a>'], hook_helper.call_hook(:view_layouts_base_html_head)
115 115 end
116 116
117 def test_view_hook_should_generate_links_with_relative_url_root
118 Redmine::Utils.relative_url_root = '/foo'
119 @hook_module.add_listener(TestLinkToHook)
120
121 assert_equal ['<a href="/foo/issues">Issues</a>'], hook_helper.call_hook(:view_layouts_base_html_head)
122 ensure
123 Redmine::Utils.relative_url_root = ''
124 end
125
117 126 # Context: Redmine::Hook::Helper.call_hook
118 127 def test_call_hook_with_project_added_to_context
119 128 @hook_module.add_listener(TestHook3)
120 129 assert_match /project/i, hook_helper.call_hook(:view_layouts_base_html_head)[0]
121 130 end
122 131
123 132 def test_call_hook_from_controller_with_controller_added_to_context
124 133 @hook_module.add_listener(TestHook3)
125 134 assert_match /controller/i, hook_helper.call_hook(:view_layouts_base_html_head)[0]
126 135 end
127 136
128 137 def test_call_hook_from_controller_with_request_added_to_context
129 138 @hook_module.add_listener(TestHook3)
130 139 assert_match /request/i, hook_helper.call_hook(:view_layouts_base_html_head)[0]
131 140 end
132 141
133 142 def test_call_hook_from_view_with_project_added_to_context
134 143 @hook_module.add_listener(TestHook3)
135 144 assert_match /project/i, view_hook_helper.call_hook(:view_layouts_base_html_head)
136 145 end
137 146
138 147 def test_call_hook_from_view_with_controller_added_to_context
139 148 @hook_module.add_listener(TestHook3)
140 149 assert_match /controller/i, view_hook_helper.call_hook(:view_layouts_base_html_head)
141 150 end
142 151
143 152 def test_call_hook_from_view_with_request_added_to_context
144 153 @hook_module.add_listener(TestHook3)
145 154 assert_match /request/i, view_hook_helper.call_hook(:view_layouts_base_html_head)
146 155 end
147 156
148 157 def test_call_hook_from_view_should_join_responses_with_a_space
149 158 @hook_module.add_listener(TestHook1)
150 159 @hook_module.add_listener(TestHook2)
151 160 assert_equal 'Test hook 1 listener. Test hook 2 listener.',
152 161 view_hook_helper.call_hook(:view_layouts_base_html_head)
153 162 end
154 163
155 164 def test_call_hook_should_not_change_the_default_url_for_email_notifications
156 165 issue = Issue.find(1)
157 166
158 167 ActionMailer::Base.deliveries.clear
159 168 Mailer.deliver_issue_add(issue)
160 169 mail = ActionMailer::Base.deliveries.last
161 170
162 171 @hook_module.add_listener(TestLinkToHook)
163 172 hook_helper.call_hook(:view_layouts_base_html_head)
164 173
165 174 ActionMailer::Base.deliveries.clear
166 175 Mailer.deliver_issue_add(issue)
167 176 mail2 = ActionMailer::Base.deliveries.last
168 177
169 178 assert_equal mail_body(mail), mail_body(mail2)
170 179 end
171 180
172 181 def hook_helper
173 182 @hook_helper ||= TestHookHelperController.new
174 183 end
175 184
176 185 def view_hook_helper
177 186 @view_hook_helper ||= TestHookHelperView.new(Rails.root.to_s + '/app/views')
178 187 end
179 188 end
180 189
General Comments 0
You need to be logged in to leave comments. Login now