##// END OF EJS Templates
Added request and controller objects to the hooks by default....
Eric Davis -
r2368:5b7a5c39a7da
parent child
Show More
@@ -1,111 +1,155
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 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 module Redmine
18 module Redmine
19 module Hook
19 module Hook
20 include ActionController::UrlWriter
21
20 @@listener_classes = []
22 @@listener_classes = []
21 @@listeners = nil
23 @@listeners = nil
22 @@hook_listeners = {}
24 @@hook_listeners = {}
23
25
24 class << self
26 class << self
25 # Adds a listener class.
27 # Adds a listener class.
26 # Automatically called when a class inherits from Redmine::Hook::Listener.
28 # Automatically called when a class inherits from Redmine::Hook::Listener.
27 def add_listener(klass)
29 def add_listener(klass)
28 raise "Hooks must include Singleton module." unless klass.included_modules.include?(Singleton)
30 raise "Hooks must include Singleton module." unless klass.included_modules.include?(Singleton)
29 @@listener_classes << klass
31 @@listener_classes << klass
30 clear_listeners_instances
32 clear_listeners_instances
31 end
33 end
32
34
33 # Returns all the listerners instances.
35 # Returns all the listerners instances.
34 def listeners
36 def listeners
35 @@listeners ||= @@listener_classes.collect {|listener| listener.instance}
37 @@listeners ||= @@listener_classes.collect {|listener| listener.instance}
36 end
38 end
37
39
38 # Returns the listeners instances for the given hook.
40 # Returns the listeners instances for the given hook.
39 def hook_listeners(hook)
41 def hook_listeners(hook)
40 @@hook_listeners[hook] ||= listeners.select {|listener| listener.respond_to?(hook)}
42 @@hook_listeners[hook] ||= listeners.select {|listener| listener.respond_to?(hook)}
41 end
43 end
42
44
43 # Clears all the listeners.
45 # Clears all the listeners.
44 def clear_listeners
46 def clear_listeners
45 @@listener_classes = []
47 @@listener_classes = []
46 clear_listeners_instances
48 clear_listeners_instances
47 end
49 end
48
50
49 # Clears all the listeners instances.
51 # Clears all the listeners instances.
50 def clear_listeners_instances
52 def clear_listeners_instances
51 @@listeners = nil
53 @@listeners = nil
52 @@hook_listeners = {}
54 @@hook_listeners = {}
53 end
55 end
54
56
55 # Calls a hook.
57 # Calls a hook.
56 # Returns the listeners response.
58 # Returns the listeners response.
57 def call_hook(hook, context={})
59 def call_hook(hook, context={})
58 response = ''
60 returning [] do |response|
59 hook_listeners(hook).each do |listener|
61 hls = hook_listeners(hook)
60 response << listener.send(hook, context).to_s
62 if hls.any?
63 request = context[:request]
64 if request
65 default_url_options[:host] ||= request.env["SERVER_NAME"]
66 # Only set port if it's requested and isn't port 80. Otherwise a url
67 # like: +http://example.com:/url+ may be generated
68 if request.env["SERVER_PORT"] && request.env["SERVER_PORT"] != 80
69 default_url_options[:port] ||= request.env["SERVER_PORT"]
70 end
71 default_url_options[:protocol] ||= request.protocol
72 end
73 hls.each {|listener| response << listener.send(hook, context)}
74 end
61 end
75 end
62 response
63 end
76 end
64 end
77 end
65
78
66 # Base class for hook listeners.
79 # Base class for hook listeners.
67 class Listener
80 class Listener
68 include Singleton
81 include Singleton
69 include GLoc
82 include GLoc
70
83
71 # Registers the listener
84 # Registers the listener
72 def self.inherited(child)
85 def self.inherited(child)
73 Redmine::Hook.add_listener(child)
86 Redmine::Hook.add_listener(child)
74 super
87 super
75 end
88 end
76 end
89 end
77
90
78 # Listener class used for views hooks.
91 # Listener class used for views hooks.
79 # Listeners that inherit this class will include various helpers by default.
92 # Listeners that inherit this class will include various helpers by default.
80 class ViewListener < Listener
93 class ViewListener < Listener
81 include ERB::Util
94 include ERB::Util
82 include ActionView::Helpers::TagHelper
95 include ActionView::Helpers::TagHelper
83 include ActionView::Helpers::FormHelper
96 include ActionView::Helpers::FormHelper
84 include ActionView::Helpers::FormTagHelper
97 include ActionView::Helpers::FormTagHelper
85 include ActionView::Helpers::FormOptionsHelper
98 include ActionView::Helpers::FormOptionsHelper
86 include ActionView::Helpers::JavaScriptHelper
99 include ActionView::Helpers::JavaScriptHelper
87 include ActionView::Helpers::PrototypeHelper
100 include ActionView::Helpers::PrototypeHelper
88 include ActionView::Helpers::NumberHelper
101 include ActionView::Helpers::NumberHelper
89 include ActionView::Helpers::UrlHelper
102 include ActionView::Helpers::UrlHelper
90 include ActionView::Helpers::AssetTagHelper
103 include ActionView::Helpers::AssetTagHelper
91 include ActionView::Helpers::TextHelper
104 include ActionView::Helpers::TextHelper
92 include ActionController::UrlWriter
105 include ActionController::UrlWriter
93 include ApplicationHelper
106 include ApplicationHelper
107
108 # Helper method to directly render a partial using the context:
109 #
110 # class MyHook < Redmine::Hook::ViewListener
111 # render_on :view_issues_show_details_bottom, :partial => "show_more_data"
112 # end
113 #
114 def self.render_on(hook, options={})
115 define_method hook do |context|
116 context[:controller].send(:render_to_string, {:locals => context}.merge(options))
117 end
118 end
94 end
119 end
95
120
96 # Helper module included in ApplicationHelper so that hooks can be called
121 # Helper module included in ApplicationHelper and ActionControllerso that
97 # in views like this:
122 # hooks can be called in views like this:
123 #
98 # <%= call_hook(:some_hook) %>
124 # <%= call_hook(:some_hook) %>
99 # <%= call_hook(:another_hook, :foo => 'bar' %>
125 # <%= call_hook(:another_hook, :foo => 'bar' %>
100 #
126 #
101 # Current project is automatically added to the call context.
127 # Or in controllers like:
128 # call_hook(:some_hook)
129 # call_hook(:another_hook, :foo => 'bar'
130 #
131 # Hooks added to views will be concatenated into a string. Hooks added to
132 # controllers will return an array of results.
133 #
134 # Several objects are automatically added to the call context:
135 #
136 # * project => current project
137 # * request => Request instance
138 # * controller => current Controller instance
139 #
102 module Helper
140 module Helper
103 def call_hook(hook, context={})
141 def call_hook(hook, context={})
104 Redmine::Hook.call_hook(hook, {:project => @project}.merge(context))
142 if is_a?(ActionController::Base)
143 ctx = {:controller => self, :project => @project, :request => request}
144 Redmine::Hook.call_hook(hook, ctx.merge(context))
145 else
146 ctx = {:controller => controller, :project => @project, :request => request}
147 Redmine::Hook.call_hook(hook, ctx.merge(context)).join(' ')
148 end
105 end
149 end
106 end
150 end
107 end
151 end
108 end
152 end
109
153
110 ApplicationHelper.send(:include, Redmine::Hook::Helper)
154 ApplicationHelper.send(:include, Redmine::Hook::Helper)
111 ActionController::Base.send(:include, Redmine::Hook::Helper)
155 ActionController::Base.send(:include, Redmine::Hook::Helper)
@@ -1,83 +1,158
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 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.dirname(__FILE__) + '/../../../test_helper'
18 require File.dirname(__FILE__) + '/../../../test_helper'
19
19
20 class Redmine::Hook::ManagerTest < Test::Unit::TestCase
20 class Redmine::Hook::ManagerTest < Test::Unit::TestCase
21
21
22 # Some hooks that are manually registered in these tests
22 # Some hooks that are manually registered in these tests
23 class TestHook < Redmine::Hook::Listener; end
23 class TestHook < Redmine::Hook::ViewListener; end
24
24
25 class TestHook1 < TestHook
25 class TestHook1 < TestHook
26 def view_layouts_base_html_head(context)
26 def view_layouts_base_html_head(context)
27 'Test hook 1 listener.'
27 'Test hook 1 listener.'
28 end
28 end
29 end
29 end
30
30
31 class TestHook2 < TestHook
31 class TestHook2 < TestHook
32 def view_layouts_base_html_head(context)
32 def view_layouts_base_html_head(context)
33 'Test hook 2 listener.'
33 'Test hook 2 listener.'
34 end
34 end
35 end
35 end
36
36
37 class TestHook3 < TestHook
37 class TestHook3 < TestHook
38 def view_layouts_base_html_head(context)
38 def view_layouts_base_html_head(context)
39 "Context keys: #{context.keys.collect(&:to_s).sort.join(', ')}."
39 "Context keys: #{context.keys.collect(&:to_s).sort.join(', ')}."
40 end
40 end
41 end
41 end
42
43 class TestLinkToHook < TestHook
44 def view_layouts_base_html_head(context)
45 link_to('Issues', :controller => 'issues')
46 end
47 end
48
42 Redmine::Hook.clear_listeners
49 Redmine::Hook.clear_listeners
43
50
44 def setup
51 def setup
45 @hook_module = Redmine::Hook
52 @hook_module = Redmine::Hook
46 end
53 end
47
54
48 def teardown
55 def teardown
49 @hook_module.clear_listeners
56 @hook_module.clear_listeners
57 @hook_module.default_url_options = { }
50 end
58 end
51
59
52 def test_clear_listeners
60 def test_clear_listeners
53 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
61 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
54 @hook_module.add_listener(TestHook1)
62 @hook_module.add_listener(TestHook1)
55 @hook_module.add_listener(TestHook2)
63 @hook_module.add_listener(TestHook2)
56 assert_equal 2, @hook_module.hook_listeners(:view_layouts_base_html_head).size
64 assert_equal 2, @hook_module.hook_listeners(:view_layouts_base_html_head).size
57
65
58 @hook_module.clear_listeners
66 @hook_module.clear_listeners
59 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
67 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
60 end
68 end
61
69
62 def test_add_listener
70 def test_add_listener
63 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
71 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
64 @hook_module.add_listener(TestHook1)
72 @hook_module.add_listener(TestHook1)
65 assert_equal 1, @hook_module.hook_listeners(:view_layouts_base_html_head).size
73 assert_equal 1, @hook_module.hook_listeners(:view_layouts_base_html_head).size
66 end
74 end
67
75
68 def test_call_hook
76 def test_call_hook
69 @hook_module.add_listener(TestHook1)
77 @hook_module.add_listener(TestHook1)
70 assert_equal 'Test hook 1 listener.', @hook_module.call_hook(:view_layouts_base_html_head)
78 assert_equal ['Test hook 1 listener.'], @hook_module.call_hook(:view_layouts_base_html_head)
71 end
79 end
72
80
73 def test_call_hook_with_context
81 def test_call_hook_with_context
74 @hook_module.add_listener(TestHook3)
82 @hook_module.add_listener(TestHook3)
75 assert_equal 'Context keys: bar, foo.', @hook_module.call_hook(:view_layouts_base_html_head, :foo => 1, :bar => 'a')
83 assert_equal ['Context keys: bar, foo.'], @hook_module.call_hook(:view_layouts_base_html_head, :foo => 1, :bar => 'a')
76 end
84 end
77
85
78 def test_call_hook_with_multiple_listeners
86 def test_call_hook_with_multiple_listeners
79 @hook_module.add_listener(TestHook1)
87 @hook_module.add_listener(TestHook1)
80 @hook_module.add_listener(TestHook2)
88 @hook_module.add_listener(TestHook2)
81 assert_equal 'Test hook 1 listener.Test hook 2 listener.', @hook_module.call_hook(:view_layouts_base_html_head)
89 assert_equal ['Test hook 1 listener.', 'Test hook 2 listener.'], @hook_module.call_hook(:view_layouts_base_html_head)
90 end
91
92 # Context: Redmine::Hook::call_hook
93 def test_call_hook_default_url_options_set
94 request = ActionController::TestRequest.new
95 request.env = { "SERVER_NAME" => 'example.com'}
96 @hook_module.add_listener(TestLinkToHook)
97
98 assert_equal ['<a href="http://example.com/issues">Issues</a>'],
99 @hook_module.call_hook(:view_layouts_base_html_head, :request => request)
100 end
101
102 def test_call_hook_default_url_options_set_with_no_standard_request_port
103 request = ActionController::TestRequest.new
104 request.env = { "SERVER_NAME" => 'example.com', "SERVER_PORT" => 3000}
105 @hook_module.add_listener(TestLinkToHook)
106
107 assert_equal ['<a href="http://example.com:3000/issues">Issues</a>'],
108 @hook_module.call_hook(:view_layouts_base_html_head, :request => request)
109 end
110
111 def test_call_hook_default_url_options_set_with_ssl
112 request = ActionController::TestRequest.new
113 request.env = { "SERVER_NAME" => 'example.com', "HTTPS" => 'on'}
114 @hook_module.add_listener(TestLinkToHook)
115
116 assert_equal ['<a href="https://example.com/issues">Issues</a>'],
117 @hook_module.call_hook(:view_layouts_base_html_head, :request => request)
118 end
119
120 def test_call_hook_default_url_options_set_with_forwarded_ssl
121 request = ActionController::TestRequest.new
122 request.env = { "SERVER_NAME" => 'example.com', "HTTP_X_FORWARDED_PROTO" => "https"}
123 @hook_module.add_listener(TestLinkToHook)
124
125 assert_equal ['<a href="https://example.com/issues">Issues</a>'],
126 @hook_module.call_hook(:view_layouts_base_html_head, :request => request)
127 end
128
129 # Context: Redmine::Hook::Helper.call_hook
130 def test_call_hook_with_project_added_to_context
131 # TODO: Implement test
132 end
133
134 def test_call_hook_from_controller_with_controller_added_to_context
135 # TODO: Implement test
136 end
137
138 def test_call_hook_from_controller_with_request_added_to_context
139 # TODO: Implement test
140 end
141
142 def test_call_hook_from_view_with_project_added_to_context
143 # TODO: Implement test
144 end
145
146 def test_call_hook_from_view_with_controller_added_to_context
147 # TODO: Implement test
148 end
149
150 def test_call_hook_from_view_with_request_added_to_context
151 # TODO: Implement test
152 end
153
154 def test_call_hook_from_view_should_join_responses_with_a_space
155 # TODO: Implement test
82 end
156 end
83 end
157 end
158
General Comments 0
You need to be logged in to leave comments. Login now