##// END OF EJS Templates
Code cleanup: implement Plugin#to_param for generating routes....
Jean-Philippe Lang -
r10766:c31f498ba6a2
parent child
Show More
@@ -1,66 +1,66
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 class SettingsController < ApplicationController
18 class SettingsController < ApplicationController
19 layout 'admin'
19 layout 'admin'
20 menu_item :plugins, :only => :plugin
20 menu_item :plugins, :only => :plugin
21
21
22 before_filter :require_admin
22 before_filter :require_admin
23
23
24 def index
24 def index
25 edit
25 edit
26 render :action => 'edit'
26 render :action => 'edit'
27 end
27 end
28
28
29 def edit
29 def edit
30 @notifiables = Redmine::Notifiable.all
30 @notifiables = Redmine::Notifiable.all
31 if request.post? && params[:settings] && params[:settings].is_a?(Hash)
31 if request.post? && params[:settings] && params[:settings].is_a?(Hash)
32 settings = (params[:settings] || {}).dup.symbolize_keys
32 settings = (params[:settings] || {}).dup.symbolize_keys
33 settings.each do |name, value|
33 settings.each do |name, value|
34 # remove blank values in array settings
34 # remove blank values in array settings
35 value.delete_if {|v| v.blank? } if value.is_a?(Array)
35 value.delete_if {|v| v.blank? } if value.is_a?(Array)
36 Setting[name] = value
36 Setting[name] = value
37 end
37 end
38 flash[:notice] = l(:notice_successful_update)
38 flash[:notice] = l(:notice_successful_update)
39 redirect_to settings_path(:tab => params[:tab])
39 redirect_to settings_path(:tab => params[:tab])
40 else
40 else
41 @options = {}
41 @options = {}
42 user_format = User::USER_FORMATS.collect{|key, value| [key, value[:setting_order]]}.sort{|a, b| a[1] <=> b[1]}
42 user_format = User::USER_FORMATS.collect{|key, value| [key, value[:setting_order]]}.sort{|a, b| a[1] <=> b[1]}
43 @options[:user_format] = user_format.collect{|f| [User.current.name(f[0]), f[0].to_s]}
43 @options[:user_format] = user_format.collect{|f| [User.current.name(f[0]), f[0].to_s]}
44 @deliveries = ActionMailer::Base.perform_deliveries
44 @deliveries = ActionMailer::Base.perform_deliveries
45
45
46 @guessed_host_and_path = request.host_with_port.dup
46 @guessed_host_and_path = request.host_with_port.dup
47 @guessed_host_and_path << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank?
47 @guessed_host_and_path << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank?
48
48
49 Redmine::Themes.rescan
49 Redmine::Themes.rescan
50 end
50 end
51 end
51 end
52
52
53 def plugin
53 def plugin
54 @plugin = Redmine::Plugin.find(params[:id])
54 @plugin = Redmine::Plugin.find(params[:id])
55 if request.post?
55 if request.post?
56 Setting.send "plugin_#{@plugin.id}=", params[:settings]
56 Setting.send "plugin_#{@plugin.id}=", params[:settings]
57 flash[:notice] = l(:notice_successful_update)
57 flash[:notice] = l(:notice_successful_update)
58 redirect_to plugin_settings_path(@plugin.id)
58 redirect_to plugin_settings_path(@plugin)
59 else
59 else
60 @partial = @plugin.settings[:partial]
60 @partial = @plugin.settings[:partial]
61 @settings = Setting.send "plugin_#{@plugin.id}"
61 @settings = Setting.send "plugin_#{@plugin.id}"
62 end
62 end
63 rescue Redmine::PluginNotFound
63 rescue Redmine::PluginNotFound
64 render_404
64 render_404
65 end
65 end
66 end
66 end
@@ -1,19 +1,19
1 <h2><%= l(:label_plugins) %></h2>
1 <h2><%= l(:label_plugins) %></h2>
2
2
3 <% if @plugins.any? %>
3 <% if @plugins.any? %>
4 <table class="list plugins">
4 <table class="list plugins">
5 <% @plugins.each do |plugin| %>
5 <% @plugins.each do |plugin| %>
6 <tr class="<%= cycle('odd', 'even') %>">
6 <tr id="plugin-<%= plugin.id %>" class="<%= cycle('odd', 'even') %>">
7 <td><span class="name"><%=h plugin.name %></span>
7 <td><span class="name"><%=h plugin.name %></span>
8 <%= content_tag('span', h(plugin.description), :class => 'description') unless plugin.description.blank? %>
8 <%= content_tag('span', h(plugin.description), :class => 'description') unless plugin.description.blank? %>
9 <%= content_tag('span', link_to(h(plugin.url), plugin.url), :class => 'url') unless plugin.url.blank? %>
9 <%= content_tag('span', link_to(h(plugin.url), plugin.url), :class => 'url') unless plugin.url.blank? %>
10 </td>
10 </td>
11 <td class="author"><%= plugin.author_url.blank? ? h(plugin.author) : link_to(h(plugin.author), plugin.author_url) %></td>
11 <td class="author"><%= plugin.author_url.blank? ? h(plugin.author) : link_to(h(plugin.author), plugin.author_url) %></td>
12 <td class="version"><%=h plugin.version %></td>
12 <td class="version"><%=h plugin.version %></td>
13 <td class="configure"><%= link_to(l(:button_configure), :controller => 'settings', :action => 'plugin', :id => plugin.id) if plugin.configurable? %></td>
13 <td class="configure"><%= link_to(l(:button_configure), plugin_settings_path(plugin)) if plugin.configurable? %></td>
14 </tr>
14 </tr>
15 <% end %>
15 <% end %>
16 </table>
16 </table>
17 <% else %>
17 <% else %>
18 <p class="nodata"><%= l(:label_no_data) %></p>
18 <p class="nodata"><%= l(:label_no_data) %></p>
19 <% end %>
19 <% end %>
@@ -1,472 +1,476
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 #:nodoc:
18 module Redmine #:nodoc:
19
19
20 class PluginNotFound < StandardError; end
20 class PluginNotFound < StandardError; end
21 class PluginRequirementError < StandardError; end
21 class PluginRequirementError < StandardError; end
22
22
23 # Base class for Redmine plugins.
23 # Base class for Redmine plugins.
24 # Plugins are registered using the <tt>register</tt> class method that acts as the public constructor.
24 # Plugins are registered using the <tt>register</tt> class method that acts as the public constructor.
25 #
25 #
26 # Redmine::Plugin.register :example do
26 # Redmine::Plugin.register :example do
27 # name 'Example plugin'
27 # name 'Example plugin'
28 # author 'John Smith'
28 # author 'John Smith'
29 # description 'This is an example plugin for Redmine'
29 # description 'This is an example plugin for Redmine'
30 # version '0.0.1'
30 # version '0.0.1'
31 # settings :default => {'foo'=>'bar'}, :partial => 'settings/settings'
31 # settings :default => {'foo'=>'bar'}, :partial => 'settings/settings'
32 # end
32 # end
33 #
33 #
34 # === Plugin attributes
34 # === Plugin attributes
35 #
35 #
36 # +settings+ is an optional attribute that let the plugin be configurable.
36 # +settings+ is an optional attribute that let the plugin be configurable.
37 # It must be a hash with the following keys:
37 # It must be a hash with the following keys:
38 # * <tt>:default</tt>: default value for the plugin settings
38 # * <tt>:default</tt>: default value for the plugin settings
39 # * <tt>:partial</tt>: path of the configuration partial view, relative to the plugin <tt>app/views</tt> directory
39 # * <tt>:partial</tt>: path of the configuration partial view, relative to the plugin <tt>app/views</tt> directory
40 # Example:
40 # Example:
41 # settings :default => {'foo'=>'bar'}, :partial => 'settings/settings'
41 # settings :default => {'foo'=>'bar'}, :partial => 'settings/settings'
42 # In this example, the settings partial will be found here in the plugin directory: <tt>app/views/settings/_settings.rhtml</tt>.
42 # In this example, the settings partial will be found here in the plugin directory: <tt>app/views/settings/_settings.rhtml</tt>.
43 #
43 #
44 # When rendered, the plugin settings value is available as the local variable +settings+
44 # When rendered, the plugin settings value is available as the local variable +settings+
45 class Plugin
45 class Plugin
46 cattr_accessor :directory
46 cattr_accessor :directory
47 self.directory = File.join(Rails.root, 'plugins')
47 self.directory = File.join(Rails.root, 'plugins')
48
48
49 cattr_accessor :public_directory
49 cattr_accessor :public_directory
50 self.public_directory = File.join(Rails.root, 'public', 'plugin_assets')
50 self.public_directory = File.join(Rails.root, 'public', 'plugin_assets')
51
51
52 @registered_plugins = {}
52 @registered_plugins = {}
53 class << self
53 class << self
54 attr_reader :registered_plugins
54 attr_reader :registered_plugins
55 private :new
55 private :new
56
56
57 def def_field(*names)
57 def def_field(*names)
58 class_eval do
58 class_eval do
59 names.each do |name|
59 names.each do |name|
60 define_method(name) do |*args|
60 define_method(name) do |*args|
61 args.empty? ? instance_variable_get("@#{name}") : instance_variable_set("@#{name}", *args)
61 args.empty? ? instance_variable_get("@#{name}") : instance_variable_set("@#{name}", *args)
62 end
62 end
63 end
63 end
64 end
64 end
65 end
65 end
66 end
66 end
67 def_field :name, :description, :url, :author, :author_url, :version, :settings
67 def_field :name, :description, :url, :author, :author_url, :version, :settings
68 attr_reader :id
68 attr_reader :id
69
69
70 # Plugin constructor
70 # Plugin constructor
71 def self.register(id, &block)
71 def self.register(id, &block)
72 p = new(id)
72 p = new(id)
73 p.instance_eval(&block)
73 p.instance_eval(&block)
74 # Set a default name if it was not provided during registration
74 # Set a default name if it was not provided during registration
75 p.name(id.to_s.humanize) if p.name.nil?
75 p.name(id.to_s.humanize) if p.name.nil?
76
76
77 # Adds plugin locales if any
77 # Adds plugin locales if any
78 # YAML translation files should be found under <plugin>/config/locales/
78 # YAML translation files should be found under <plugin>/config/locales/
79 ::I18n.load_path += Dir.glob(File.join(p.directory, 'config', 'locales', '*.yml'))
79 ::I18n.load_path += Dir.glob(File.join(p.directory, 'config', 'locales', '*.yml'))
80
80
81 # Prepends the app/views directory of the plugin to the view path
81 # Prepends the app/views directory of the plugin to the view path
82 view_path = File.join(p.directory, 'app', 'views')
82 view_path = File.join(p.directory, 'app', 'views')
83 if File.directory?(view_path)
83 if File.directory?(view_path)
84 ActionController::Base.prepend_view_path(view_path)
84 ActionController::Base.prepend_view_path(view_path)
85 ActionMailer::Base.prepend_view_path(view_path)
85 ActionMailer::Base.prepend_view_path(view_path)
86 end
86 end
87
87
88 # Adds the app/{controllers,helpers,models} directories of the plugin to the autoload path
88 # Adds the app/{controllers,helpers,models} directories of the plugin to the autoload path
89 Dir.glob File.expand_path(File.join(p.directory, 'app', '{controllers,helpers,models}')) do |dir|
89 Dir.glob File.expand_path(File.join(p.directory, 'app', '{controllers,helpers,models}')) do |dir|
90 ActiveSupport::Dependencies.autoload_paths += [dir]
90 ActiveSupport::Dependencies.autoload_paths += [dir]
91 end
91 end
92
92
93 registered_plugins[id] = p
93 registered_plugins[id] = p
94 end
94 end
95
95
96 # Returns an array of all registered plugins
96 # Returns an array of all registered plugins
97 def self.all
97 def self.all
98 registered_plugins.values.sort
98 registered_plugins.values.sort
99 end
99 end
100
100
101 # Finds a plugin by its id
101 # Finds a plugin by its id
102 # Returns a PluginNotFound exception if the plugin doesn't exist
102 # Returns a PluginNotFound exception if the plugin doesn't exist
103 def self.find(id)
103 def self.find(id)
104 registered_plugins[id.to_sym] || raise(PluginNotFound)
104 registered_plugins[id.to_sym] || raise(PluginNotFound)
105 end
105 end
106
106
107 # Clears the registered plugins hash
107 # Clears the registered plugins hash
108 # It doesn't unload installed plugins
108 # It doesn't unload installed plugins
109 def self.clear
109 def self.clear
110 @registered_plugins = {}
110 @registered_plugins = {}
111 end
111 end
112
112
113 # Checks if a plugin is installed
113 # Checks if a plugin is installed
114 #
114 #
115 # @param [String] id name of the plugin
115 # @param [String] id name of the plugin
116 def self.installed?(id)
116 def self.installed?(id)
117 registered_plugins[id.to_sym].present?
117 registered_plugins[id.to_sym].present?
118 end
118 end
119
119
120 def self.load
120 def self.load
121 Dir.glob(File.join(self.directory, '*')).sort.each do |directory|
121 Dir.glob(File.join(self.directory, '*')).sort.each do |directory|
122 if File.directory?(directory)
122 if File.directory?(directory)
123 lib = File.join(directory, "lib")
123 lib = File.join(directory, "lib")
124 if File.directory?(lib)
124 if File.directory?(lib)
125 $:.unshift lib
125 $:.unshift lib
126 ActiveSupport::Dependencies.autoload_paths += [lib]
126 ActiveSupport::Dependencies.autoload_paths += [lib]
127 end
127 end
128 initializer = File.join(directory, "init.rb")
128 initializer = File.join(directory, "init.rb")
129 if File.file?(initializer)
129 if File.file?(initializer)
130 require initializer
130 require initializer
131 end
131 end
132 end
132 end
133 end
133 end
134 end
134 end
135
135
136 def initialize(id)
136 def initialize(id)
137 @id = id.to_sym
137 @id = id.to_sym
138 end
138 end
139
139
140 def directory
140 def directory
141 File.join(self.class.directory, id.to_s)
141 File.join(self.class.directory, id.to_s)
142 end
142 end
143
143
144 def public_directory
144 def public_directory
145 File.join(self.class.public_directory, id.to_s)
145 File.join(self.class.public_directory, id.to_s)
146 end
146 end
147
147
148 def to_param
149 id
150 end
151
148 def assets_directory
152 def assets_directory
149 File.join(directory, 'assets')
153 File.join(directory, 'assets')
150 end
154 end
151
155
152 def <=>(plugin)
156 def <=>(plugin)
153 self.id.to_s <=> plugin.id.to_s
157 self.id.to_s <=> plugin.id.to_s
154 end
158 end
155
159
156 # Sets a requirement on Redmine version
160 # Sets a requirement on Redmine version
157 # Raises a PluginRequirementError exception if the requirement is not met
161 # Raises a PluginRequirementError exception if the requirement is not met
158 #
162 #
159 # Examples
163 # Examples
160 # # Requires Redmine 0.7.3 or higher
164 # # Requires Redmine 0.7.3 or higher
161 # requires_redmine :version_or_higher => '0.7.3'
165 # requires_redmine :version_or_higher => '0.7.3'
162 # requires_redmine '0.7.3'
166 # requires_redmine '0.7.3'
163 #
167 #
164 # # Requires Redmine 0.7.x or higher
168 # # Requires Redmine 0.7.x or higher
165 # requires_redmine '0.7'
169 # requires_redmine '0.7'
166 #
170 #
167 # # Requires a specific Redmine version
171 # # Requires a specific Redmine version
168 # requires_redmine :version => '0.7.3' # 0.7.3 only
172 # requires_redmine :version => '0.7.3' # 0.7.3 only
169 # requires_redmine :version => '0.7' # 0.7.x
173 # requires_redmine :version => '0.7' # 0.7.x
170 # requires_redmine :version => ['0.7.3', '0.8.0'] # 0.7.3 or 0.8.0
174 # requires_redmine :version => ['0.7.3', '0.8.0'] # 0.7.3 or 0.8.0
171 #
175 #
172 # # Requires a Redmine version within a range
176 # # Requires a Redmine version within a range
173 # requires_redmine :version => '0.7.3'..'0.9.1' # >= 0.7.3 and <= 0.9.1
177 # requires_redmine :version => '0.7.3'..'0.9.1' # >= 0.7.3 and <= 0.9.1
174 # requires_redmine :version => '0.7'..'0.9' # >= 0.7.x and <= 0.9.x
178 # requires_redmine :version => '0.7'..'0.9' # >= 0.7.x and <= 0.9.x
175 def requires_redmine(arg)
179 def requires_redmine(arg)
176 arg = { :version_or_higher => arg } unless arg.is_a?(Hash)
180 arg = { :version_or_higher => arg } unless arg.is_a?(Hash)
177 arg.assert_valid_keys(:version, :version_or_higher)
181 arg.assert_valid_keys(:version, :version_or_higher)
178
182
179 current = Redmine::VERSION.to_a
183 current = Redmine::VERSION.to_a
180 arg.each do |k, req|
184 arg.each do |k, req|
181 case k
185 case k
182 when :version_or_higher
186 when :version_or_higher
183 raise ArgumentError.new(":version_or_higher accepts a version string only") unless req.is_a?(String)
187 raise ArgumentError.new(":version_or_higher accepts a version string only") unless req.is_a?(String)
184 unless compare_versions(req, current) <= 0
188 unless compare_versions(req, current) <= 0
185 raise PluginRequirementError.new("#{id} plugin requires Redmine #{req} or higher but current is #{current.join('.')}")
189 raise PluginRequirementError.new("#{id} plugin requires Redmine #{req} or higher but current is #{current.join('.')}")
186 end
190 end
187 when :version
191 when :version
188 req = [req] if req.is_a?(String)
192 req = [req] if req.is_a?(String)
189 if req.is_a?(Array)
193 if req.is_a?(Array)
190 unless req.detect {|ver| compare_versions(ver, current) == 0}
194 unless req.detect {|ver| compare_versions(ver, current) == 0}
191 raise PluginRequirementError.new("#{id} plugin requires one the following Redmine versions: #{req.join(', ')} but current is #{current.join('.')}")
195 raise PluginRequirementError.new("#{id} plugin requires one the following Redmine versions: #{req.join(', ')} but current is #{current.join('.')}")
192 end
196 end
193 elsif req.is_a?(Range)
197 elsif req.is_a?(Range)
194 unless compare_versions(req.first, current) <= 0 && compare_versions(req.last, current) >= 0
198 unless compare_versions(req.first, current) <= 0 && compare_versions(req.last, current) >= 0
195 raise PluginRequirementError.new("#{id} plugin requires a Redmine version between #{req.first} and #{req.last} but current is #{current.join('.')}")
199 raise PluginRequirementError.new("#{id} plugin requires a Redmine version between #{req.first} and #{req.last} but current is #{current.join('.')}")
196 end
200 end
197 else
201 else
198 raise ArgumentError.new(":version option accepts a version string, an array or a range of versions")
202 raise ArgumentError.new(":version option accepts a version string, an array or a range of versions")
199 end
203 end
200 end
204 end
201 end
205 end
202 true
206 true
203 end
207 end
204
208
205 def compare_versions(requirement, current)
209 def compare_versions(requirement, current)
206 requirement = requirement.split('.').collect(&:to_i)
210 requirement = requirement.split('.').collect(&:to_i)
207 requirement <=> current.slice(0, requirement.size)
211 requirement <=> current.slice(0, requirement.size)
208 end
212 end
209 private :compare_versions
213 private :compare_versions
210
214
211 # Sets a requirement on a Redmine plugin version
215 # Sets a requirement on a Redmine plugin version
212 # Raises a PluginRequirementError exception if the requirement is not met
216 # Raises a PluginRequirementError exception if the requirement is not met
213 #
217 #
214 # Examples
218 # Examples
215 # # Requires a plugin named :foo version 0.7.3 or higher
219 # # Requires a plugin named :foo version 0.7.3 or higher
216 # requires_redmine_plugin :foo, :version_or_higher => '0.7.3'
220 # requires_redmine_plugin :foo, :version_or_higher => '0.7.3'
217 # requires_redmine_plugin :foo, '0.7.3'
221 # requires_redmine_plugin :foo, '0.7.3'
218 #
222 #
219 # # Requires a specific version of a Redmine plugin
223 # # Requires a specific version of a Redmine plugin
220 # requires_redmine_plugin :foo, :version => '0.7.3' # 0.7.3 only
224 # requires_redmine_plugin :foo, :version => '0.7.3' # 0.7.3 only
221 # requires_redmine_plugin :foo, :version => ['0.7.3', '0.8.0'] # 0.7.3 or 0.8.0
225 # requires_redmine_plugin :foo, :version => ['0.7.3', '0.8.0'] # 0.7.3 or 0.8.0
222 def requires_redmine_plugin(plugin_name, arg)
226 def requires_redmine_plugin(plugin_name, arg)
223 arg = { :version_or_higher => arg } unless arg.is_a?(Hash)
227 arg = { :version_or_higher => arg } unless arg.is_a?(Hash)
224 arg.assert_valid_keys(:version, :version_or_higher)
228 arg.assert_valid_keys(:version, :version_or_higher)
225
229
226 plugin = Plugin.find(plugin_name)
230 plugin = Plugin.find(plugin_name)
227 current = plugin.version.split('.').collect(&:to_i)
231 current = plugin.version.split('.').collect(&:to_i)
228
232
229 arg.each do |k, v|
233 arg.each do |k, v|
230 v = [] << v unless v.is_a?(Array)
234 v = [] << v unless v.is_a?(Array)
231 versions = v.collect {|s| s.split('.').collect(&:to_i)}
235 versions = v.collect {|s| s.split('.').collect(&:to_i)}
232 case k
236 case k
233 when :version_or_higher
237 when :version_or_higher
234 raise ArgumentError.new("wrong number of versions (#{versions.size} for 1)") unless versions.size == 1
238 raise ArgumentError.new("wrong number of versions (#{versions.size} for 1)") unless versions.size == 1
235 unless (current <=> versions.first) >= 0
239 unless (current <=> versions.first) >= 0
236 raise PluginRequirementError.new("#{id} plugin requires the #{plugin_name} plugin #{v} or higher but current is #{current.join('.')}")
240 raise PluginRequirementError.new("#{id} plugin requires the #{plugin_name} plugin #{v} or higher but current is #{current.join('.')}")
237 end
241 end
238 when :version
242 when :version
239 unless versions.include?(current.slice(0,3))
243 unless versions.include?(current.slice(0,3))
240 raise PluginRequirementError.new("#{id} plugin requires one the following versions of #{plugin_name}: #{v.join(', ')} but current is #{current.join('.')}")
244 raise PluginRequirementError.new("#{id} plugin requires one the following versions of #{plugin_name}: #{v.join(', ')} but current is #{current.join('.')}")
241 end
245 end
242 end
246 end
243 end
247 end
244 true
248 true
245 end
249 end
246
250
247 # Adds an item to the given +menu+.
251 # Adds an item to the given +menu+.
248 # The +id+ parameter (equals to the project id) is automatically added to the url.
252 # The +id+ parameter (equals to the project id) is automatically added to the url.
249 # menu :project_menu, :plugin_example, { :controller => 'example', :action => 'say_hello' }, :caption => 'Sample'
253 # menu :project_menu, :plugin_example, { :controller => 'example', :action => 'say_hello' }, :caption => 'Sample'
250 #
254 #
251 # +name+ parameter can be: :top_menu, :account_menu, :application_menu or :project_menu
255 # +name+ parameter can be: :top_menu, :account_menu, :application_menu or :project_menu
252 #
256 #
253 def menu(menu, item, url, options={})
257 def menu(menu, item, url, options={})
254 Redmine::MenuManager.map(menu).push(item, url, options)
258 Redmine::MenuManager.map(menu).push(item, url, options)
255 end
259 end
256 alias :add_menu_item :menu
260 alias :add_menu_item :menu
257
261
258 # Removes +item+ from the given +menu+.
262 # Removes +item+ from the given +menu+.
259 def delete_menu_item(menu, item)
263 def delete_menu_item(menu, item)
260 Redmine::MenuManager.map(menu).delete(item)
264 Redmine::MenuManager.map(menu).delete(item)
261 end
265 end
262
266
263 # Defines a permission called +name+ for the given +actions+.
267 # Defines a permission called +name+ for the given +actions+.
264 #
268 #
265 # The +actions+ argument is a hash with controllers as keys and actions as values (a single value or an array):
269 # The +actions+ argument is a hash with controllers as keys and actions as values (a single value or an array):
266 # permission :destroy_contacts, { :contacts => :destroy }
270 # permission :destroy_contacts, { :contacts => :destroy }
267 # permission :view_contacts, { :contacts => [:index, :show] }
271 # permission :view_contacts, { :contacts => [:index, :show] }
268 #
272 #
269 # The +options+ argument is a hash that accept the following keys:
273 # The +options+ argument is a hash that accept the following keys:
270 # * :public => the permission is public if set to true (implicitly given to any user)
274 # * :public => the permission is public if set to true (implicitly given to any user)
271 # * :require => can be set to one of the following values to restrict users the permission can be given to: :loggedin, :member
275 # * :require => can be set to one of the following values to restrict users the permission can be given to: :loggedin, :member
272 # * :read => set it to true so that the permission is still granted on closed projects
276 # * :read => set it to true so that the permission is still granted on closed projects
273 #
277 #
274 # Examples
278 # Examples
275 # # A permission that is implicitly given to any user
279 # # A permission that is implicitly given to any user
276 # # This permission won't appear on the Roles & Permissions setup screen
280 # # This permission won't appear on the Roles & Permissions setup screen
277 # permission :say_hello, { :example => :say_hello }, :public => true, :read => true
281 # permission :say_hello, { :example => :say_hello }, :public => true, :read => true
278 #
282 #
279 # # A permission that can be given to any user
283 # # A permission that can be given to any user
280 # permission :say_hello, { :example => :say_hello }
284 # permission :say_hello, { :example => :say_hello }
281 #
285 #
282 # # A permission that can be given to registered users only
286 # # A permission that can be given to registered users only
283 # permission :say_hello, { :example => :say_hello }, :require => :loggedin
287 # permission :say_hello, { :example => :say_hello }, :require => :loggedin
284 #
288 #
285 # # A permission that can be given to project members only
289 # # A permission that can be given to project members only
286 # permission :say_hello, { :example => :say_hello }, :require => :member
290 # permission :say_hello, { :example => :say_hello }, :require => :member
287 def permission(name, actions, options = {})
291 def permission(name, actions, options = {})
288 if @project_module
292 if @project_module
289 Redmine::AccessControl.map {|map| map.project_module(@project_module) {|map|map.permission(name, actions, options)}}
293 Redmine::AccessControl.map {|map| map.project_module(@project_module) {|map|map.permission(name, actions, options)}}
290 else
294 else
291 Redmine::AccessControl.map {|map| map.permission(name, actions, options)}
295 Redmine::AccessControl.map {|map| map.permission(name, actions, options)}
292 end
296 end
293 end
297 end
294
298
295 # Defines a project module, that can be enabled/disabled for each project.
299 # Defines a project module, that can be enabled/disabled for each project.
296 # Permissions defined inside +block+ will be bind to the module.
300 # Permissions defined inside +block+ will be bind to the module.
297 #
301 #
298 # project_module :things do
302 # project_module :things do
299 # permission :view_contacts, { :contacts => [:list, :show] }, :public => true
303 # permission :view_contacts, { :contacts => [:list, :show] }, :public => true
300 # permission :destroy_contacts, { :contacts => :destroy }
304 # permission :destroy_contacts, { :contacts => :destroy }
301 # end
305 # end
302 def project_module(name, &block)
306 def project_module(name, &block)
303 @project_module = name
307 @project_module = name
304 self.instance_eval(&block)
308 self.instance_eval(&block)
305 @project_module = nil
309 @project_module = nil
306 end
310 end
307
311
308 # Registers an activity provider.
312 # Registers an activity provider.
309 #
313 #
310 # Options:
314 # Options:
311 # * <tt>:class_name</tt> - one or more model(s) that provide these events (inferred from event_type by default)
315 # * <tt>:class_name</tt> - one or more model(s) that provide these events (inferred from event_type by default)
312 # * <tt>:default</tt> - setting this option to false will make the events not displayed by default
316 # * <tt>:default</tt> - setting this option to false will make the events not displayed by default
313 #
317 #
314 # A model can provide several activity event types.
318 # A model can provide several activity event types.
315 #
319 #
316 # Examples:
320 # Examples:
317 # register :news
321 # register :news
318 # register :scrums, :class_name => 'Meeting'
322 # register :scrums, :class_name => 'Meeting'
319 # register :issues, :class_name => ['Issue', 'Journal']
323 # register :issues, :class_name => ['Issue', 'Journal']
320 #
324 #
321 # Retrieving events:
325 # Retrieving events:
322 # Associated model(s) must implement the find_events class method.
326 # Associated model(s) must implement the find_events class method.
323 # ActiveRecord models can use acts_as_activity_provider as a way to implement this class method.
327 # ActiveRecord models can use acts_as_activity_provider as a way to implement this class method.
324 #
328 #
325 # The following call should return all the scrum events visible by current user that occured in the 5 last days:
329 # The following call should return all the scrum events visible by current user that occured in the 5 last days:
326 # Meeting.find_events('scrums', User.current, 5.days.ago, Date.today)
330 # Meeting.find_events('scrums', User.current, 5.days.ago, Date.today)
327 # Meeting.find_events('scrums', User.current, 5.days.ago, Date.today, :project => foo) # events for project foo only
331 # Meeting.find_events('scrums', User.current, 5.days.ago, Date.today, :project => foo) # events for project foo only
328 #
332 #
329 # Note that :view_scrums permission is required to view these events in the activity view.
333 # Note that :view_scrums permission is required to view these events in the activity view.
330 def activity_provider(*args)
334 def activity_provider(*args)
331 Redmine::Activity.register(*args)
335 Redmine::Activity.register(*args)
332 end
336 end
333
337
334 # Registers a wiki formatter.
338 # Registers a wiki formatter.
335 #
339 #
336 # Parameters:
340 # Parameters:
337 # * +name+ - human-readable name
341 # * +name+ - human-readable name
338 # * +formatter+ - formatter class, which should have an instance method +to_html+
342 # * +formatter+ - formatter class, which should have an instance method +to_html+
339 # * +helper+ - helper module, which will be included by wiki pages
343 # * +helper+ - helper module, which will be included by wiki pages
340 def wiki_format_provider(name, formatter, helper)
344 def wiki_format_provider(name, formatter, helper)
341 Redmine::WikiFormatting.register(name, formatter, helper)
345 Redmine::WikiFormatting.register(name, formatter, helper)
342 end
346 end
343
347
344 # Returns +true+ if the plugin can be configured.
348 # Returns +true+ if the plugin can be configured.
345 def configurable?
349 def configurable?
346 settings && settings.is_a?(Hash) && !settings[:partial].blank?
350 settings && settings.is_a?(Hash) && !settings[:partial].blank?
347 end
351 end
348
352
349 def mirror_assets
353 def mirror_assets
350 source = assets_directory
354 source = assets_directory
351 destination = public_directory
355 destination = public_directory
352 return unless File.directory?(source)
356 return unless File.directory?(source)
353
357
354 source_files = Dir[source + "/**/*"]
358 source_files = Dir[source + "/**/*"]
355 source_dirs = source_files.select { |d| File.directory?(d) }
359 source_dirs = source_files.select { |d| File.directory?(d) }
356 source_files -= source_dirs
360 source_files -= source_dirs
357
361
358 unless source_files.empty?
362 unless source_files.empty?
359 base_target_dir = File.join(destination, File.dirname(source_files.first).gsub(source, ''))
363 base_target_dir = File.join(destination, File.dirname(source_files.first).gsub(source, ''))
360 begin
364 begin
361 FileUtils.mkdir_p(base_target_dir)
365 FileUtils.mkdir_p(base_target_dir)
362 rescue Exception => e
366 rescue Exception => e
363 raise "Could not create directory #{base_target_dir}: " + e.message
367 raise "Could not create directory #{base_target_dir}: " + e.message
364 end
368 end
365 end
369 end
366
370
367 source_dirs.each do |dir|
371 source_dirs.each do |dir|
368 # strip down these paths so we have simple, relative paths we can
372 # strip down these paths so we have simple, relative paths we can
369 # add to the destination
373 # add to the destination
370 target_dir = File.join(destination, dir.gsub(source, ''))
374 target_dir = File.join(destination, dir.gsub(source, ''))
371 begin
375 begin
372 FileUtils.mkdir_p(target_dir)
376 FileUtils.mkdir_p(target_dir)
373 rescue Exception => e
377 rescue Exception => e
374 raise "Could not create directory #{target_dir}: " + e.message
378 raise "Could not create directory #{target_dir}: " + e.message
375 end
379 end
376 end
380 end
377
381
378 source_files.each do |file|
382 source_files.each do |file|
379 begin
383 begin
380 target = File.join(destination, file.gsub(source, ''))
384 target = File.join(destination, file.gsub(source, ''))
381 unless File.exist?(target) && FileUtils.identical?(file, target)
385 unless File.exist?(target) && FileUtils.identical?(file, target)
382 FileUtils.cp(file, target)
386 FileUtils.cp(file, target)
383 end
387 end
384 rescue Exception => e
388 rescue Exception => e
385 raise "Could not copy #{file} to #{target}: " + e.message
389 raise "Could not copy #{file} to #{target}: " + e.message
386 end
390 end
387 end
391 end
388 end
392 end
389
393
390 # Mirrors assets from one or all plugins to public/plugin_assets
394 # Mirrors assets from one or all plugins to public/plugin_assets
391 def self.mirror_assets(name=nil)
395 def self.mirror_assets(name=nil)
392 if name.present?
396 if name.present?
393 find(name).mirror_assets
397 find(name).mirror_assets
394 else
398 else
395 all.each do |plugin|
399 all.each do |plugin|
396 plugin.mirror_assets
400 plugin.mirror_assets
397 end
401 end
398 end
402 end
399 end
403 end
400
404
401 # The directory containing this plugin's migrations (<tt>plugin/db/migrate</tt>)
405 # The directory containing this plugin's migrations (<tt>plugin/db/migrate</tt>)
402 def migration_directory
406 def migration_directory
403 File.join(Rails.root, 'plugins', id.to_s, 'db', 'migrate')
407 File.join(Rails.root, 'plugins', id.to_s, 'db', 'migrate')
404 end
408 end
405
409
406 # Returns the version number of the latest migration for this plugin. Returns
410 # Returns the version number of the latest migration for this plugin. Returns
407 # nil if this plugin has no migrations.
411 # nil if this plugin has no migrations.
408 def latest_migration
412 def latest_migration
409 migrations.last
413 migrations.last
410 end
414 end
411
415
412 # Returns the version numbers of all migrations for this plugin.
416 # Returns the version numbers of all migrations for this plugin.
413 def migrations
417 def migrations
414 migrations = Dir[migration_directory+"/*.rb"]
418 migrations = Dir[migration_directory+"/*.rb"]
415 migrations.map { |p| File.basename(p).match(/0*(\d+)\_/)[1].to_i }.sort
419 migrations.map { |p| File.basename(p).match(/0*(\d+)\_/)[1].to_i }.sort
416 end
420 end
417
421
418 # Migrate this plugin to the given version
422 # Migrate this plugin to the given version
419 def migrate(version = nil)
423 def migrate(version = nil)
420 puts "Migrating #{id} (#{name})..."
424 puts "Migrating #{id} (#{name})..."
421 Redmine::Plugin::Migrator.migrate_plugin(self, version)
425 Redmine::Plugin::Migrator.migrate_plugin(self, version)
422 end
426 end
423
427
424 # Migrates all plugins or a single plugin to a given version
428 # Migrates all plugins or a single plugin to a given version
425 # Exemples:
429 # Exemples:
426 # Plugin.migrate
430 # Plugin.migrate
427 # Plugin.migrate('sample_plugin')
431 # Plugin.migrate('sample_plugin')
428 # Plugin.migrate('sample_plugin', 1)
432 # Plugin.migrate('sample_plugin', 1)
429 #
433 #
430 def self.migrate(name=nil, version=nil)
434 def self.migrate(name=nil, version=nil)
431 if name.present?
435 if name.present?
432 find(name).migrate(version)
436 find(name).migrate(version)
433 else
437 else
434 all.each do |plugin|
438 all.each do |plugin|
435 plugin.migrate
439 plugin.migrate
436 end
440 end
437 end
441 end
438 end
442 end
439
443
440 class Migrator < ActiveRecord::Migrator
444 class Migrator < ActiveRecord::Migrator
441 # We need to be able to set the 'current' plugin being migrated.
445 # We need to be able to set the 'current' plugin being migrated.
442 cattr_accessor :current_plugin
446 cattr_accessor :current_plugin
443
447
444 class << self
448 class << self
445 # Runs the migrations from a plugin, up (or down) to the version given
449 # Runs the migrations from a plugin, up (or down) to the version given
446 def migrate_plugin(plugin, version)
450 def migrate_plugin(plugin, version)
447 self.current_plugin = plugin
451 self.current_plugin = plugin
448 return if current_version(plugin) == version
452 return if current_version(plugin) == version
449 migrate(plugin.migration_directory, version)
453 migrate(plugin.migration_directory, version)
450 end
454 end
451
455
452 def current_version(plugin=current_plugin)
456 def current_version(plugin=current_plugin)
453 # Delete migrations that don't match .. to_i will work because the number comes first
457 # Delete migrations that don't match .. to_i will work because the number comes first
454 ::ActiveRecord::Base.connection.select_values(
458 ::ActiveRecord::Base.connection.select_values(
455 "SELECT version FROM #{schema_migrations_table_name}"
459 "SELECT version FROM #{schema_migrations_table_name}"
456 ).delete_if{ |v| v.match(/-#{plugin.id}/) == nil }.map(&:to_i).max || 0
460 ).delete_if{ |v| v.match(/-#{plugin.id}/) == nil }.map(&:to_i).max || 0
457 end
461 end
458 end
462 end
459
463
460 def migrated
464 def migrated
461 sm_table = self.class.schema_migrations_table_name
465 sm_table = self.class.schema_migrations_table_name
462 ::ActiveRecord::Base.connection.select_values(
466 ::ActiveRecord::Base.connection.select_values(
463 "SELECT version FROM #{sm_table}"
467 "SELECT version FROM #{sm_table}"
464 ).delete_if{ |v| v.match(/-#{current_plugin.id}/) == nil }.map(&:to_i).sort
468 ).delete_if{ |v| v.match(/-#{current_plugin.id}/) == nil }.map(&:to_i).sort
465 end
469 end
466
470
467 def record_version_state_after_migrating(version)
471 def record_version_state_after_migrating(version)
468 super(version.to_s + "-" + current_plugin.id.to_s)
472 super(version.to_s + "-" + current_plugin.id.to_s)
469 end
473 end
470 end
474 end
471 end
475 end
472 end
476 end
@@ -1,164 +1,167
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 AdminControllerTest < ActionController::TestCase
20 class AdminControllerTest < ActionController::TestCase
21 fixtures :projects, :users, :roles
21 fixtures :projects, :users, :roles
22
22
23 def setup
23 def setup
24 User.current = nil
24 User.current = nil
25 @request.session[:user_id] = 1 # admin
25 @request.session[:user_id] = 1 # admin
26 end
26 end
27
27
28 def test_index
28 def test_index
29 get :index
29 get :index
30 assert_no_tag :tag => 'div',
30 assert_select 'div.nodata', 0
31 :attributes => { :class => /nodata/ }
32 end
31 end
33
32
34 def test_index_with_no_configuration_data
33 def test_index_with_no_configuration_data
35 delete_configuration_data
34 delete_configuration_data
36 get :index
35 get :index
37 assert_tag :tag => 'div',
36 assert_select 'div.nodata'
38 :attributes => { :class => /nodata/ }
39 end
37 end
40
38
41 def test_projects
39 def test_projects
42 get :projects
40 get :projects
43 assert_response :success
41 assert_response :success
44 assert_template 'projects'
42 assert_template 'projects'
45 assert_not_nil assigns(:projects)
43 assert_not_nil assigns(:projects)
46 # active projects only
44 # active projects only
47 assert_nil assigns(:projects).detect {|u| !u.active?}
45 assert_nil assigns(:projects).detect {|u| !u.active?}
48 end
46 end
49
47
50 def test_projects_with_status_filter
48 def test_projects_with_status_filter
51 get :projects, :status => 1
49 get :projects, :status => 1
52 assert_response :success
50 assert_response :success
53 assert_template 'projects'
51 assert_template 'projects'
54 assert_not_nil assigns(:projects)
52 assert_not_nil assigns(:projects)
55 # active projects only
53 # active projects only
56 assert_nil assigns(:projects).detect {|u| !u.active?}
54 assert_nil assigns(:projects).detect {|u| !u.active?}
57 end
55 end
58
56
59 def test_projects_with_name_filter
57 def test_projects_with_name_filter
60 get :projects, :name => 'store', :status => ''
58 get :projects, :name => 'store', :status => ''
61 assert_response :success
59 assert_response :success
62 assert_template 'projects'
60 assert_template 'projects'
63 projects = assigns(:projects)
61 projects = assigns(:projects)
64 assert_not_nil projects
62 assert_not_nil projects
65 assert_equal 1, projects.size
63 assert_equal 1, projects.size
66 assert_equal 'OnlineStore', projects.first.name
64 assert_equal 'OnlineStore', projects.first.name
67 end
65 end
68
66
69 def test_load_default_configuration_data
67 def test_load_default_configuration_data
70 delete_configuration_data
68 delete_configuration_data
71 post :default_configuration, :lang => 'fr'
69 post :default_configuration, :lang => 'fr'
72 assert_response :redirect
70 assert_response :redirect
73 assert_nil flash[:error]
71 assert_nil flash[:error]
74 assert IssueStatus.find_by_name('Nouveau')
72 assert IssueStatus.find_by_name('Nouveau')
75 end
73 end
76
74
77 def test_load_default_configuration_data_should_rescue_error
75 def test_load_default_configuration_data_should_rescue_error
78 delete_configuration_data
76 delete_configuration_data
79 Redmine::DefaultData::Loader.stubs(:load).raises(Exception.new("Something went wrong"))
77 Redmine::DefaultData::Loader.stubs(:load).raises(Exception.new("Something went wrong"))
80 post :default_configuration, :lang => 'fr'
78 post :default_configuration, :lang => 'fr'
81 assert_response :redirect
79 assert_response :redirect
82 assert_not_nil flash[:error]
80 assert_not_nil flash[:error]
83 assert_match /Something went wrong/, flash[:error]
81 assert_match /Something went wrong/, flash[:error]
84 end
82 end
85
83
86 def test_test_email
84 def test_test_email
87 user = User.find(1)
85 user = User.find(1)
88 user.pref[:no_self_notified] = '1'
86 user.pref[:no_self_notified] = '1'
89 user.pref.save!
87 user.pref.save!
90 ActionMailer::Base.deliveries.clear
88 ActionMailer::Base.deliveries.clear
91
89
92 get :test_email
90 get :test_email
93 assert_redirected_to '/settings?tab=notifications'
91 assert_redirected_to '/settings?tab=notifications'
94 mail = ActionMailer::Base.deliveries.last
92 mail = ActionMailer::Base.deliveries.last
95 assert_not_nil mail
93 assert_not_nil mail
96 user = User.find(1)
94 user = User.find(1)
97 assert_equal [user.mail], mail.bcc
95 assert_equal [user.mail], mail.bcc
98 end
96 end
99
97
100 def test_test_email_failure_should_display_the_error
98 def test_test_email_failure_should_display_the_error
101 Mailer.stubs(:test_email).raises(Exception, 'Some error message')
99 Mailer.stubs(:test_email).raises(Exception, 'Some error message')
102 get :test_email
100 get :test_email
103 assert_redirected_to '/settings?tab=notifications'
101 assert_redirected_to '/settings?tab=notifications'
104 assert_match /Some error message/, flash[:error]
102 assert_match /Some error message/, flash[:error]
105 end
103 end
106
104
107 def test_no_plugins
105 def test_no_plugins
108 Redmine::Plugin.clear
106 Redmine::Plugin.clear
109
107
110 get :plugins
108 get :plugins
111 assert_response :success
109 assert_response :success
112 assert_template 'plugins'
110 assert_template 'plugins'
113 end
111 end
114
112
115 def test_plugins
113 def test_plugins
116 # Register a few plugins
114 # Register a few plugins
117 Redmine::Plugin.register :foo do
115 Redmine::Plugin.register :foo do
118 name 'Foo plugin'
116 name 'Foo plugin'
119 author 'John Smith'
117 author 'John Smith'
120 description 'This is a test plugin'
118 description 'This is a test plugin'
121 version '0.0.1'
119 version '0.0.1'
122 settings :default => {'sample_setting' => 'value', 'foo'=>'bar'}, :partial => 'foo/settings'
120 settings :default => {'sample_setting' => 'value', 'foo'=>'bar'}, :partial => 'foo/settings'
123 end
121 end
124 Redmine::Plugin.register :bar do
122 Redmine::Plugin.register :bar do
125 end
123 end
126
124
127 get :plugins
125 get :plugins
128 assert_response :success
126 assert_response :success
129 assert_template 'plugins'
127 assert_template 'plugins'
130
128
131 assert_tag :td, :child => { :tag => 'span', :content => 'Foo plugin' }
129 assert_select 'tr#plugin-foo' do
132 assert_tag :td, :child => { :tag => 'span', :content => 'Bar' }
130 assert_select 'td span.name', :text => 'Foo plugin'
131 assert_select 'td.configure a[href=/settings/plugin/foo]'
132 end
133 assert_select 'tr#plugin-bar' do
134 assert_select 'td span.name', :text => 'Bar'
135 assert_select 'td.configure a', 0
136 end
133 end
137 end
134
138
135 def test_info
139 def test_info
136 get :info
140 get :info
137 assert_response :success
141 assert_response :success
138 assert_template 'info'
142 assert_template 'info'
139 end
143 end
140
144
141 def test_admin_menu_plugin_extension
145 def test_admin_menu_plugin_extension
142 Redmine::MenuManager.map :admin_menu do |menu|
146 Redmine::MenuManager.map :admin_menu do |menu|
143 menu.push :test_admin_menu_plugin_extension, '/foo/bar', :caption => 'Test'
147 menu.push :test_admin_menu_plugin_extension, '/foo/bar', :caption => 'Test'
144 end
148 end
145
149
146 get :index
150 get :index
147 assert_response :success
151 assert_response :success
148 assert_tag :a, :attributes => { :href => '/foo/bar' },
152 assert_select 'div#admin-menu a[href=/foo/bar]', :text => 'Test'
149 :content => 'Test'
150
153
151 Redmine::MenuManager.map :admin_menu do |menu|
154 Redmine::MenuManager.map :admin_menu do |menu|
152 menu.delete :test_admin_menu_plugin_extension
155 menu.delete :test_admin_menu_plugin_extension
153 end
156 end
154 end
157 end
155
158
156 private
159 private
157
160
158 def delete_configuration_data
161 def delete_configuration_data
159 Role.delete_all('builtin = 0')
162 Role.delete_all('builtin = 0')
160 Tracker.delete_all
163 Tracker.delete_all
161 IssueStatus.delete_all
164 IssueStatus.delete_all
162 Enumeration.delete_all
165 Enumeration.delete_all
163 end
166 end
164 end
167 end
General Comments 0
You need to be logged in to leave comments. Login now