##// END OF EJS Templates
Reverts r14510 (#20508) and r14511 (#20507), tests broken....
Jean-Philippe Lang -
r14138:a19f6fb5003c
parent child
Show More
@@ -1,175 +1,175
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 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 @@listener_classes = []
20 @@listener_classes = []
21 @@listeners = nil
21 @@listeners = nil
22 @@hook_listeners = {}
22 @@hook_listeners = {}
23
23
24 class << self
24 class << self
25 # Adds a listener class.
25 # Adds a listener class.
26 # Automatically called when a class inherits from Redmine::Hook::Listener.
26 # Automatically called when a class inherits from Redmine::Hook::Listener.
27 def add_listener(klass)
27 def add_listener(klass)
28 raise "Hooks must include Singleton module." unless klass.included_modules.include?(Singleton)
28 raise "Hooks must include Singleton module." unless klass.included_modules.include?(Singleton)
29 @@listener_classes << klass
29 @@listener_classes << klass
30 clear_listeners_instances
30 clear_listeners_instances
31 end
31 end
32
32
33 # Returns all the listener instances.
33 # Returns all the listener instances.
34 def listeners
34 def listeners
35 @@listeners ||= @@listener_classes.collect {|listener| listener.instance}
35 @@listeners ||= @@listener_classes.collect {|listener| listener.instance}
36 end
36 end
37
37
38 # Returns the listener instances for the given hook.
38 # Returns the listener instances for the given hook.
39 def hook_listeners(hook)
39 def hook_listeners(hook)
40 @@hook_listeners[hook] ||= listeners.select {|listener| listener.respond_to?(hook)}
40 @@hook_listeners[hook] ||= listeners.select {|listener| listener.respond_to?(hook)}
41 end
41 end
42
42
43 # Clears all the listeners.
43 # Clears all the listeners.
44 def clear_listeners
44 def clear_listeners
45 @@listener_classes = []
45 @@listener_classes = []
46 clear_listeners_instances
46 clear_listeners_instances
47 end
47 end
48
48
49 # Clears all the listeners instances.
49 # Clears all the listeners instances.
50 def clear_listeners_instances
50 def clear_listeners_instances
51 @@listeners = nil
51 @@listeners = nil
52 @@hook_listeners = {}
52 @@hook_listeners = {}
53 end
53 end
54
54
55 # Calls a hook.
55 # Calls a hook.
56 # Returns the listeners response.
56 # Returns the listeners response.
57 def call_hook(hook, context={})
57 def call_hook(hook, context={})
58 [].tap do |response|
58 [].tap do |response|
59 hls = hook_listeners(hook)
59 hls = hook_listeners(hook)
60 if hls.any?
60 if hls.any?
61 hls.each {|listener| response << listener.send(hook, context)}
61 hls.each {|listener| response << listener.send(hook, context)}
62 end
62 end
63 end
63 end
64 end
64 end
65 end
65 end
66
66
67 # Base class for hook listeners.
67 # Base class for hook listeners.
68 class Listener
68 class Listener
69 include Singleton
69 include Singleton
70 include Redmine::I18n
70 include Redmine::I18n
71
71
72 # Registers the listener
72 # Registers the listener
73 def self.inherited(child)
73 def self.inherited(child)
74 Redmine::Hook.add_listener(child)
74 Redmine::Hook.add_listener(child)
75 super
75 super
76 end
76 end
77
77
78 end
78 end
79
79
80 # Listener class used for views hooks.
80 # Listener class used for views hooks.
81 # Listeners that inherit this class will include various helpers by default.
81 # Listeners that inherit this class will include various helpers by default.
82 class ViewListener < Listener
82 class ViewListener < Listener
83 include ERB::Util
83 include ERB::Util
84 include ActionView::Helpers::TagHelper
84 include ActionView::Helpers::TagHelper
85 include ActionView::Helpers::FormHelper
85 include ActionView::Helpers::FormHelper
86 include ActionView::Helpers::FormTagHelper
86 include ActionView::Helpers::FormTagHelper
87 include ActionView::Helpers::FormOptionsHelper
87 include ActionView::Helpers::FormOptionsHelper
88 include ActionView::Helpers::JavaScriptHelper
88 include ActionView::Helpers::JavaScriptHelper
89 include ActionView::Helpers::NumberHelper
89 include ActionView::Helpers::NumberHelper
90 include ActionView::Helpers::UrlHelper
90 include ActionView::Helpers::UrlHelper
91 include ActionView::Helpers::AssetTagHelper
91 include ActionView::Helpers::AssetTagHelper
92 include ActionView::Helpers::TextHelper
92 include ActionView::Helpers::TextHelper
93 include Rails.application.routes.url_helpers
93 include Rails.application.routes.url_helpers
94 include ApplicationHelper
94 include ApplicationHelper
95
95
96 # Default to creating links using only the path. Subclasses can
96 # Default to creating links using only the path. Subclasses can
97 # change this default as needed
97 # change this default as needed
98 def self.default_url_options
98 def self.default_url_options
99 {:only_path => true, :script_name => Redmine::Utils.relative_url_root}
99 {:only_path => true, :script_name => Redmine::Utils.relative_url_root}
100 end
100 end
101
101
102 # Helper method to directly render using the context,
102 # Helper method to directly render using the context,
103 # render_options must be valid #render options.
103 # render_options must be valid #render options.
104 #
104 #
105 # class MyHook < Redmine::Hook::ViewListener
105 # class MyHook < Redmine::Hook::ViewListener
106 # render_on :view_issues_show_details_bottom, :partial => "show_more_data"
106 # render_on :view_issues_show_details_bottom, :partial => "show_more_data"
107 # end
107 # end
108 #
108 #
109 # class MultipleHook < Redmine::Hook::ViewListener
109 # class MultipleHook < Redmine::Hook::ViewListener
110 # render_on :view_issues_show_details_bottom,
110 # render_on :view_issues_show_details_bottom,
111 # {:partial => "show_more_data"},
111 # {:partial => "show_more_data"},
112 # {:partial => "show_even_more_data"}
112 # {:partial => "show_even_more_data"}
113 # end
113 # end
114 #
114 #
115 def self.render_on(hook, *render_options)
115 def self.render_on(hook, *render_options)
116 define_method hook do |context|
116 define_method hook do |context|
117 render_options.map do |options|
117 render_options.map do |options|
118 if context[:hook_caller].respond_to?(:render)
118 if context[:hook_caller].respond_to?(:render)
119 context[:hook_caller].send(:render, {:locals => context}.merge(options))
119 context[:hook_caller].send(:render, {:locals => context}.merge(options))
120 elsif context[:controller].is_a?(ActionController::Base)
120 elsif context[:controller].is_a?(ActionController::Base)
121 context[:controller].send(:render_to_string, {:locals => context}.merge(options))
121 context[:controller].send(:render_to_string, {:locals => context}.merge(options))
122 else
122 else
123 raise "Cannot render #{self.name} hook from #{context[:hook_caller].class.name}"
123 raise "Cannot render #{self.name} hook from #{context[:hook_caller].class.name}"
124 end
124 end
125 end
125 end
126 end
126 end
127 end
127 end
128
128
129 def controller
129 def controller
130 nil
130 nil
131 end
131 end
132
132
133 def config
133 def config
134 ActionController::Base.config
134 ActionController::Base.config
135 end
135 end
136 end
136 end
137
137
138 # Helper module included in ApplicationHelper and ActionController so that
138 # Helper module included in ApplicationHelper and ActionController so that
139 # hooks can be called in views like this:
139 # hooks can be called in views like this:
140 #
140 #
141 # <%= call_hook(:some_hook) %>
141 # <%= call_hook(:some_hook) %>
142 # <%= call_hook(:another_hook, :foo => 'bar') %>
142 # <%= call_hook(:another_hook, :foo => 'bar') %>
143 #
143 #
144 # Or in controllers like:
144 # Or in controllers like:
145 # call_hook(:some_hook)
145 # call_hook(:some_hook)
146 # call_hook(:another_hook, :foo => 'bar')
146 # call_hook(:another_hook, :foo => 'bar')
147 #
147 #
148 # Hooks added to views will be concatenated into a string. Hooks added to
148 # Hooks added to views will be concatenated into a string. Hooks added to
149 # controllers will return an array of results.
149 # controllers will return an array of results.
150 #
150 #
151 # Several objects are automatically added to the call context:
151 # Several objects are automatically added to the call context:
152 #
152 #
153 # * project => current project
153 # * project => current project
154 # * request => Request instance
154 # * request => Request instance
155 # * controller => current Controller instance
155 # * controller => current Controller instance
156 # * hook_caller => object that called the hook
156 # * hook_caller => object that called the hook
157 #
157 #
158 module Helper
158 module Helper
159 def call_hook(hook, context={})
159 def call_hook(hook, context={})
160 if is_a?(ActionController::Base)
160 if is_a?(ActionController::Base)
161 default_context = {:controller => self, :project => @project, :request => request, :hook_caller => self}
161 default_context = {:controller => self, :project => @project, :request => request, :hook_caller => self}
162 Redmine::Hook.call_hook(hook, default_context.merge(context))
162 Redmine::Hook.call_hook(hook, default_context.merge(context))
163 else
163 else
164 default_context = { :project => @project, :hook_caller => self }
164 default_context = { :project => @project, :hook_caller => self }
165 default_context[:controller] = controller if respond_to?(:controller)
165 default_context[:controller] = controller if respond_to?(:controller)
166 default_context[:request] = request if respond_to?(:request)
166 default_context[:request] = request if respond_to?(:request)
167 Redmine::Hook.call_hook(hook, default_context.merge(context)).join(' ').html_safe
167 Redmine::Hook.call_hook(hook, default_context.merge(context)).join(' ').html_safe
168 end
168 end
169 end
169 end
170 end
170 end
171 end
171 end
172 end
172 end
173
173
174 ActionView::Base.send(:include, Redmine::Hook::Helper)
174 ApplicationHelper.send(:include, Redmine::Hook::Helper)
175 ActionController::Base.send(:include, Redmine::Hook::Helper)
175 ActionController::Base.send(:include, Redmine::Hook::Helper)
@@ -1,144 +1,143
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 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 Themes
19 module Themes
20
20
21 # Return an array of installed themes
21 # Return an array of installed themes
22 def self.themes
22 def self.themes
23 @@installed_themes ||= scan_themes
23 @@installed_themes ||= scan_themes
24 end
24 end
25
25
26 # Rescan themes directory
26 # Rescan themes directory
27 def self.rescan
27 def self.rescan
28 @@installed_themes = scan_themes
28 @@installed_themes = scan_themes
29 end
29 end
30
30
31 # Return theme for given id, or nil if it's not found
31 # Return theme for given id, or nil if it's not found
32 def self.theme(id, options={})
32 def self.theme(id, options={})
33 return nil if id.blank?
33 return nil if id.blank?
34
34
35 found = themes.find {|t| t.id == id}
35 found = themes.find {|t| t.id == id}
36 if found.nil? && options[:rescan] != false
36 if found.nil? && options[:rescan] != false
37 rescan
37 rescan
38 found = theme(id, :rescan => false)
38 found = theme(id, :rescan => false)
39 end
39 end
40 found
40 found
41 end
41 end
42
42
43 # Class used to represent a theme
43 # Class used to represent a theme
44 class Theme
44 class Theme
45 attr_reader :path, :name, :dir
45 attr_reader :path, :name, :dir
46
46
47 def initialize(path)
47 def initialize(path)
48 @path = path
48 @path = path
49 @dir = File.basename(path)
49 @dir = File.basename(path)
50 @name = @dir.humanize
50 @name = @dir.humanize
51 @stylesheets = nil
51 @stylesheets = nil
52 @javascripts = nil
52 @javascripts = nil
53 end
53 end
54
54
55 # Directory name used as the theme id
55 # Directory name used as the theme id
56 def id; dir end
56 def id; dir end
57
57
58 def ==(theme)
58 def ==(theme)
59 theme.is_a?(Theme) && theme.dir == dir
59 theme.is_a?(Theme) && theme.dir == dir
60 end
60 end
61
61
62 def <=>(theme)
62 def <=>(theme)
63 name <=> theme.name
63 name <=> theme.name
64 end
64 end
65
65
66 def stylesheets
66 def stylesheets
67 @stylesheets ||= assets("stylesheets", "css")
67 @stylesheets ||= assets("stylesheets", "css")
68 end
68 end
69
69
70 def images
70 def images
71 @images ||= assets("images")
71 @images ||= assets("images")
72 end
72 end
73
73
74 def javascripts
74 def javascripts
75 @javascripts ||= assets("javascripts", "js")
75 @javascripts ||= assets("javascripts", "js")
76 end
76 end
77
77
78 def favicons
78 def favicons
79 @favicons ||= assets("favicon")
79 @favicons ||= assets("favicon")
80 end
80 end
81
81
82 def favicon
82 def favicon
83 favicons.first
83 favicons.first
84 end
84 end
85
85
86 def favicon?
86 def favicon?
87 favicon.present?
87 favicon.present?
88 end
88 end
89
89
90 def stylesheet_path(source)
90 def stylesheet_path(source)
91 "/themes/#{dir}/stylesheets/#{source}"
91 "/themes/#{dir}/stylesheets/#{source}"
92 end
92 end
93
93
94 def image_path(source)
94 def image_path(source)
95 "/themes/#{dir}/images/#{source}"
95 "/themes/#{dir}/images/#{source}"
96 end
96 end
97
97
98 def javascript_path(source)
98 def javascript_path(source)
99 "/themes/#{dir}/javascripts/#{source}"
99 "/themes/#{dir}/javascripts/#{source}"
100 end
100 end
101
101
102 def favicon_path
102 def favicon_path
103 "/themes/#{dir}/favicon/#{favicon}"
103 "/themes/#{dir}/favicon/#{favicon}"
104 end
104 end
105
105
106 private
106 private
107
107
108 def assets(dir, ext=nil)
108 def assets(dir, ext=nil)
109 if ext
109 if ext
110 Dir.glob("#{path}/#{dir}/*.#{ext}").collect {|f| File.basename(f).gsub(/\.#{ext}$/, '')}
110 Dir.glob("#{path}/#{dir}/*.#{ext}").collect {|f| File.basename(f).gsub(/\.#{ext}$/, '')}
111 else
111 else
112 Dir.glob("#{path}/#{dir}/*").collect {|f| File.basename(f)}
112 Dir.glob("#{path}/#{dir}/*").collect {|f| File.basename(f)}
113 end
113 end
114 end
114 end
115 end
115 end
116
116
117 private
117 private
118
118
119 def self.scan_themes
119 def self.scan_themes
120 dirs = Dir.glob("#{Rails.public_path}/themes/*").select do |f|
120 dirs = Dir.glob("#{Rails.public_path}/themes/*").select do |f|
121 # A theme should at least override application.css
121 # A theme should at least override application.css
122 File.directory?(f) && File.exist?("#{f}/stylesheets/application.css")
122 File.directory?(f) && File.exist?("#{f}/stylesheets/application.css")
123 end
123 end
124 dirs.collect {|dir| Theme.new(dir)}.sort
124 dirs.collect {|dir| Theme.new(dir)}.sort
125 end
125 end
126 end
126 end
127 end
127 end
128
128
129 module ThemesHelper
129 module ApplicationHelper
130 def current_theme
130 def current_theme
131 unless instance_variable_defined?(:@current_theme)
131 unless instance_variable_defined?(:@current_theme)
132 @current_theme = Redmine::Themes.theme(Setting.ui_theme)
132 @current_theme = Redmine::Themes.theme(Setting.ui_theme)
133 end
133 end
134 @current_theme
134 @current_theme
135 end
135 end
136
136
137 # Returns the header tags for the current theme
137 # Returns the header tags for the current theme
138 def heads_for_theme
138 def heads_for_theme
139 if current_theme && current_theme.javascripts.include?('theme')
139 if current_theme && current_theme.javascripts.include?('theme')
140 javascript_include_tag current_theme.javascript_path('theme')
140 javascript_include_tag current_theme.javascript_path('theme')
141 end
141 end
142 end
142 end
143 end
143 end
144 ActionView::Base.send(:include, ThemesHelper)
General Comments 0
You need to be logged in to leave comments. Login now