##// 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 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2008 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 include ActionController::UrlWriter
21
20 22 @@listener_classes = []
21 23 @@listeners = nil
22 24 @@hook_listeners = {}
23 25
24 26 class << self
25 27 # Adds a listener class.
26 28 # Automatically called when a class inherits from Redmine::Hook::Listener.
27 29 def add_listener(klass)
28 30 raise "Hooks must include Singleton module." unless klass.included_modules.include?(Singleton)
29 31 @@listener_classes << klass
30 32 clear_listeners_instances
31 33 end
32 34
33 35 # Returns all the listerners instances.
34 36 def listeners
35 37 @@listeners ||= @@listener_classes.collect {|listener| listener.instance}
36 38 end
37 39
38 40 # Returns the listeners instances for the given hook.
39 41 def hook_listeners(hook)
40 42 @@hook_listeners[hook] ||= listeners.select {|listener| listener.respond_to?(hook)}
41 43 end
42 44
43 45 # Clears all the listeners.
44 46 def clear_listeners
45 47 @@listener_classes = []
46 48 clear_listeners_instances
47 49 end
48 50
49 51 # Clears all the listeners instances.
50 52 def clear_listeners_instances
51 53 @@listeners = nil
52 54 @@hook_listeners = {}
53 55 end
54 56
55 57 # Calls a hook.
56 58 # Returns the listeners response.
57 59 def call_hook(hook, context={})
58 response = ''
59 hook_listeners(hook).each do |listener|
60 response << listener.send(hook, context).to_s
60 returning [] do |response|
61 hls = hook_listeners(hook)
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 75 end
62 response
63 76 end
64 77 end
65 78
66 79 # Base class for hook listeners.
67 80 class Listener
68 81 include Singleton
69 82 include GLoc
70 83
71 84 # Registers the listener
72 85 def self.inherited(child)
73 86 Redmine::Hook.add_listener(child)
74 87 super
75 88 end
76 89 end
77 90
78 91 # Listener class used for views hooks.
79 92 # Listeners that inherit this class will include various helpers by default.
80 93 class ViewListener < Listener
81 94 include ERB::Util
82 95 include ActionView::Helpers::TagHelper
83 96 include ActionView::Helpers::FormHelper
84 97 include ActionView::Helpers::FormTagHelper
85 98 include ActionView::Helpers::FormOptionsHelper
86 99 include ActionView::Helpers::JavaScriptHelper
87 100 include ActionView::Helpers::PrototypeHelper
88 101 include ActionView::Helpers::NumberHelper
89 102 include ActionView::Helpers::UrlHelper
90 103 include ActionView::Helpers::AssetTagHelper
91 104 include ActionView::Helpers::TextHelper
92 105 include ActionController::UrlWriter
93 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 119 end
95 120
96 # Helper module included in ApplicationHelper so that hooks can be called
97 # in views like this:
121 # Helper module included in ApplicationHelper and ActionControllerso that
122 # hooks can be called in views like this:
123 #
98 124 # <%= call_hook(:some_hook) %>
99 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 140 module Helper
103 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 149 end
106 150 end
107 151 end
108 152 end
109 153
110 154 ApplicationHelper.send(:include, Redmine::Hook::Helper)
111 155 ActionController::Base.send(:include, Redmine::Hook::Helper)
@@ -1,83 +1,158
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2008 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.dirname(__FILE__) + '/../../../test_helper'
19 19
20 20 class Redmine::Hook::ManagerTest < Test::Unit::TestCase
21 21
22 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 25 class TestHook1 < TestHook
26 26 def view_layouts_base_html_head(context)
27 27 'Test hook 1 listener.'
28 28 end
29 29 end
30 30
31 31 class TestHook2 < TestHook
32 32 def view_layouts_base_html_head(context)
33 33 'Test hook 2 listener.'
34 34 end
35 35 end
36 36
37 37 class TestHook3 < TestHook
38 38 def view_layouts_base_html_head(context)
39 39 "Context keys: #{context.keys.collect(&:to_s).sort.join(', ')}."
40 40 end
41 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 49 Redmine::Hook.clear_listeners
43 50
44 51 def setup
45 52 @hook_module = Redmine::Hook
46 53 end
47 54
48 55 def teardown
49 56 @hook_module.clear_listeners
57 @hook_module.default_url_options = { }
50 58 end
51 59
52 60 def test_clear_listeners
53 61 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
54 62 @hook_module.add_listener(TestHook1)
55 63 @hook_module.add_listener(TestHook2)
56 64 assert_equal 2, @hook_module.hook_listeners(:view_layouts_base_html_head).size
57 65
58 66 @hook_module.clear_listeners
59 67 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
60 68 end
61 69
62 70 def test_add_listener
63 71 assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size
64 72 @hook_module.add_listener(TestHook1)
65 73 assert_equal 1, @hook_module.hook_listeners(:view_layouts_base_html_head).size
66 74 end
67 75
68 76 def test_call_hook
69 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 79 end
72 80
73 81 def test_call_hook_with_context
74 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 84 end
77 85
78 86 def test_call_hook_with_multiple_listeners
79 87 @hook_module.add_listener(TestHook1)
80 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 156 end
83 157 end
158
General Comments 0
You need to be logged in to leave comments. Login now