##// END OF EJS Templates
fix typos of source comments at lib/redmine/plugin.rb...
Toshi MARUYAMA -
r12794:242b440073ce
parent child
Show More
@@ -1,481 +1,481
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 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, :directory
67 def_field :name, :description, :url, :author, :author_url, :version, :settings, :directory
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
74
75 # Set a default name if it was not provided during registration
75 # Set a default name if it was not provided during registration
76 p.name(id.to_s.humanize) if p.name.nil?
76 p.name(id.to_s.humanize) if p.name.nil?
77 # Set a default directory if it was not provided during registration
77 # Set a default directory if it was not provided during registration
78 p.directory(File.join(self.directory, id.to_s)) if p.directory.nil?
78 p.directory(File.join(self.directory, id.to_s)) if p.directory.nil?
79
79
80 # Adds plugin locales if any
80 # Adds plugin locales if any
81 # YAML translation files should be found under <plugin>/config/locales/
81 # YAML translation files should be found under <plugin>/config/locales/
82 Rails.application.config.i18n.load_path += Dir.glob(File.join(p.directory, 'config', 'locales', '*.yml'))
82 Rails.application.config.i18n.load_path += Dir.glob(File.join(p.directory, 'config', 'locales', '*.yml'))
83
83
84 # Prepends the app/views directory of the plugin to the view path
84 # Prepends the app/views directory of the plugin to the view path
85 view_path = File.join(p.directory, 'app', 'views')
85 view_path = File.join(p.directory, 'app', 'views')
86 if File.directory?(view_path)
86 if File.directory?(view_path)
87 ActionController::Base.prepend_view_path(view_path)
87 ActionController::Base.prepend_view_path(view_path)
88 ActionMailer::Base.prepend_view_path(view_path)
88 ActionMailer::Base.prepend_view_path(view_path)
89 end
89 end
90
90
91 # Adds the app/{controllers,helpers,models} directories of the plugin to the autoload path
91 # Adds the app/{controllers,helpers,models} directories of the plugin to the autoload path
92 Dir.glob File.expand_path(File.join(p.directory, 'app', '{controllers,helpers,models}')) do |dir|
92 Dir.glob File.expand_path(File.join(p.directory, 'app', '{controllers,helpers,models}')) do |dir|
93 ActiveSupport::Dependencies.autoload_paths += [dir]
93 ActiveSupport::Dependencies.autoload_paths += [dir]
94 end
94 end
95
95
96 registered_plugins[id] = p
96 registered_plugins[id] = p
97 end
97 end
98
98
99 # Returns an array of all registered plugins
99 # Returns an array of all registered plugins
100 def self.all
100 def self.all
101 registered_plugins.values.sort
101 registered_plugins.values.sort
102 end
102 end
103
103
104 # Finds a plugin by its id
104 # Finds a plugin by its id
105 # Returns a PluginNotFound exception if the plugin doesn't exist
105 # Returns a PluginNotFound exception if the plugin doesn't exist
106 def self.find(id)
106 def self.find(id)
107 registered_plugins[id.to_sym] || raise(PluginNotFound)
107 registered_plugins[id.to_sym] || raise(PluginNotFound)
108 end
108 end
109
109
110 # Clears the registered plugins hash
110 # Clears the registered plugins hash
111 # It doesn't unload installed plugins
111 # It doesn't unload installed plugins
112 def self.clear
112 def self.clear
113 @registered_plugins = {}
113 @registered_plugins = {}
114 end
114 end
115
115
116 # Removes a plugin from the registered plugins
116 # Removes a plugin from the registered plugins
117 # It doesn't unload the plugin
117 # It doesn't unload the plugin
118 def self.unregister(id)
118 def self.unregister(id)
119 @registered_plugins.delete(id)
119 @registered_plugins.delete(id)
120 end
120 end
121
121
122 # Checks if a plugin is installed
122 # Checks if a plugin is installed
123 #
123 #
124 # @param [String] id name of the plugin
124 # @param [String] id name of the plugin
125 def self.installed?(id)
125 def self.installed?(id)
126 registered_plugins[id.to_sym].present?
126 registered_plugins[id.to_sym].present?
127 end
127 end
128
128
129 def self.load
129 def self.load
130 Dir.glob(File.join(self.directory, '*')).sort.each do |directory|
130 Dir.glob(File.join(self.directory, '*')).sort.each do |directory|
131 if File.directory?(directory)
131 if File.directory?(directory)
132 lib = File.join(directory, "lib")
132 lib = File.join(directory, "lib")
133 if File.directory?(lib)
133 if File.directory?(lib)
134 $:.unshift lib
134 $:.unshift lib
135 ActiveSupport::Dependencies.autoload_paths += [lib]
135 ActiveSupport::Dependencies.autoload_paths += [lib]
136 end
136 end
137 initializer = File.join(directory, "init.rb")
137 initializer = File.join(directory, "init.rb")
138 if File.file?(initializer)
138 if File.file?(initializer)
139 require initializer
139 require initializer
140 end
140 end
141 end
141 end
142 end
142 end
143 end
143 end
144
144
145 def initialize(id)
145 def initialize(id)
146 @id = id.to_sym
146 @id = id.to_sym
147 end
147 end
148
148
149 def public_directory
149 def public_directory
150 File.join(self.class.public_directory, id.to_s)
150 File.join(self.class.public_directory, id.to_s)
151 end
151 end
152
152
153 def to_param
153 def to_param
154 id
154 id
155 end
155 end
156
156
157 def assets_directory
157 def assets_directory
158 File.join(directory, 'assets')
158 File.join(directory, 'assets')
159 end
159 end
160
160
161 def <=>(plugin)
161 def <=>(plugin)
162 self.id.to_s <=> plugin.id.to_s
162 self.id.to_s <=> plugin.id.to_s
163 end
163 end
164
164
165 # Sets a requirement on Redmine version
165 # Sets a requirement on Redmine version
166 # Raises a PluginRequirementError exception if the requirement is not met
166 # Raises a PluginRequirementError exception if the requirement is not met
167 #
167 #
168 # Examples
168 # Examples
169 # # Requires Redmine 0.7.3 or higher
169 # # Requires Redmine 0.7.3 or higher
170 # requires_redmine :version_or_higher => '0.7.3'
170 # requires_redmine :version_or_higher => '0.7.3'
171 # requires_redmine '0.7.3'
171 # requires_redmine '0.7.3'
172 #
172 #
173 # # Requires Redmine 0.7.x or higher
173 # # Requires Redmine 0.7.x or higher
174 # requires_redmine '0.7'
174 # requires_redmine '0.7'
175 #
175 #
176 # # Requires a specific Redmine version
176 # # Requires a specific Redmine version
177 # requires_redmine :version => '0.7.3' # 0.7.3 only
177 # requires_redmine :version => '0.7.3' # 0.7.3 only
178 # requires_redmine :version => '0.7' # 0.7.x
178 # requires_redmine :version => '0.7' # 0.7.x
179 # requires_redmine :version => ['0.7.3', '0.8.0'] # 0.7.3 or 0.8.0
179 # requires_redmine :version => ['0.7.3', '0.8.0'] # 0.7.3 or 0.8.0
180 #
180 #
181 # # Requires a Redmine version within a range
181 # # Requires a Redmine version within a range
182 # requires_redmine :version => '0.7.3'..'0.9.1' # >= 0.7.3 and <= 0.9.1
182 # requires_redmine :version => '0.7.3'..'0.9.1' # >= 0.7.3 and <= 0.9.1
183 # requires_redmine :version => '0.7'..'0.9' # >= 0.7.x and <= 0.9.x
183 # requires_redmine :version => '0.7'..'0.9' # >= 0.7.x and <= 0.9.x
184 def requires_redmine(arg)
184 def requires_redmine(arg)
185 arg = { :version_or_higher => arg } unless arg.is_a?(Hash)
185 arg = { :version_or_higher => arg } unless arg.is_a?(Hash)
186 arg.assert_valid_keys(:version, :version_or_higher)
186 arg.assert_valid_keys(:version, :version_or_higher)
187
187
188 current = Redmine::VERSION.to_a
188 current = Redmine::VERSION.to_a
189 arg.each do |k, req|
189 arg.each do |k, req|
190 case k
190 case k
191 when :version_or_higher
191 when :version_or_higher
192 raise ArgumentError.new(":version_or_higher accepts a version string only") unless req.is_a?(String)
192 raise ArgumentError.new(":version_or_higher accepts a version string only") unless req.is_a?(String)
193 unless compare_versions(req, current) <= 0
193 unless compare_versions(req, current) <= 0
194 raise PluginRequirementError.new("#{id} plugin requires Redmine #{req} or higher but current is #{current.join('.')}")
194 raise PluginRequirementError.new("#{id} plugin requires Redmine #{req} or higher but current is #{current.join('.')}")
195 end
195 end
196 when :version
196 when :version
197 req = [req] if req.is_a?(String)
197 req = [req] if req.is_a?(String)
198 if req.is_a?(Array)
198 if req.is_a?(Array)
199 unless req.detect {|ver| compare_versions(ver, current) == 0}
199 unless req.detect {|ver| compare_versions(ver, current) == 0}
200 raise PluginRequirementError.new("#{id} plugin requires one the following Redmine versions: #{req.join(', ')} but current is #{current.join('.')}")
200 raise PluginRequirementError.new("#{id} plugin requires one the following Redmine versions: #{req.join(', ')} but current is #{current.join('.')}")
201 end
201 end
202 elsif req.is_a?(Range)
202 elsif req.is_a?(Range)
203 unless compare_versions(req.first, current) <= 0 && compare_versions(req.last, current) >= 0
203 unless compare_versions(req.first, current) <= 0 && compare_versions(req.last, current) >= 0
204 raise PluginRequirementError.new("#{id} plugin requires a Redmine version between #{req.first} and #{req.last} but current is #{current.join('.')}")
204 raise PluginRequirementError.new("#{id} plugin requires a Redmine version between #{req.first} and #{req.last} but current is #{current.join('.')}")
205 end
205 end
206 else
206 else
207 raise ArgumentError.new(":version option accepts a version string, an array or a range of versions")
207 raise ArgumentError.new(":version option accepts a version string, an array or a range of versions")
208 end
208 end
209 end
209 end
210 end
210 end
211 true
211 true
212 end
212 end
213
213
214 def compare_versions(requirement, current)
214 def compare_versions(requirement, current)
215 requirement = requirement.split('.').collect(&:to_i)
215 requirement = requirement.split('.').collect(&:to_i)
216 requirement <=> current.slice(0, requirement.size)
216 requirement <=> current.slice(0, requirement.size)
217 end
217 end
218 private :compare_versions
218 private :compare_versions
219
219
220 # Sets a requirement on a Redmine plugin version
220 # Sets a requirement on a Redmine plugin version
221 # Raises a PluginRequirementError exception if the requirement is not met
221 # Raises a PluginRequirementError exception if the requirement is not met
222 #
222 #
223 # Examples
223 # Examples
224 # # Requires a plugin named :foo version 0.7.3 or higher
224 # # Requires a plugin named :foo version 0.7.3 or higher
225 # requires_redmine_plugin :foo, :version_or_higher => '0.7.3'
225 # requires_redmine_plugin :foo, :version_or_higher => '0.7.3'
226 # requires_redmine_plugin :foo, '0.7.3'
226 # requires_redmine_plugin :foo, '0.7.3'
227 #
227 #
228 # # Requires a specific version of a Redmine plugin
228 # # Requires a specific version of a Redmine plugin
229 # requires_redmine_plugin :foo, :version => '0.7.3' # 0.7.3 only
229 # requires_redmine_plugin :foo, :version => '0.7.3' # 0.7.3 only
230 # requires_redmine_plugin :foo, :version => ['0.7.3', '0.8.0'] # 0.7.3 or 0.8.0
230 # requires_redmine_plugin :foo, :version => ['0.7.3', '0.8.0'] # 0.7.3 or 0.8.0
231 def requires_redmine_plugin(plugin_name, arg)
231 def requires_redmine_plugin(plugin_name, arg)
232 arg = { :version_or_higher => arg } unless arg.is_a?(Hash)
232 arg = { :version_or_higher => arg } unless arg.is_a?(Hash)
233 arg.assert_valid_keys(:version, :version_or_higher)
233 arg.assert_valid_keys(:version, :version_or_higher)
234
234
235 plugin = Plugin.find(plugin_name)
235 plugin = Plugin.find(plugin_name)
236 current = plugin.version.split('.').collect(&:to_i)
236 current = plugin.version.split('.').collect(&:to_i)
237
237
238 arg.each do |k, v|
238 arg.each do |k, v|
239 v = [] << v unless v.is_a?(Array)
239 v = [] << v unless v.is_a?(Array)
240 versions = v.collect {|s| s.split('.').collect(&:to_i)}
240 versions = v.collect {|s| s.split('.').collect(&:to_i)}
241 case k
241 case k
242 when :version_or_higher
242 when :version_or_higher
243 raise ArgumentError.new("wrong number of versions (#{versions.size} for 1)") unless versions.size == 1
243 raise ArgumentError.new("wrong number of versions (#{versions.size} for 1)") unless versions.size == 1
244 unless (current <=> versions.first) >= 0
244 unless (current <=> versions.first) >= 0
245 raise PluginRequirementError.new("#{id} plugin requires the #{plugin_name} plugin #{v} or higher but current is #{current.join('.')}")
245 raise PluginRequirementError.new("#{id} plugin requires the #{plugin_name} plugin #{v} or higher but current is #{current.join('.')}")
246 end
246 end
247 when :version
247 when :version
248 unless versions.include?(current.slice(0,3))
248 unless versions.include?(current.slice(0,3))
249 raise PluginRequirementError.new("#{id} plugin requires one the following versions of #{plugin_name}: #{v.join(', ')} but current is #{current.join('.')}")
249 raise PluginRequirementError.new("#{id} plugin requires one the following versions of #{plugin_name}: #{v.join(', ')} but current is #{current.join('.')}")
250 end
250 end
251 end
251 end
252 end
252 end
253 true
253 true
254 end
254 end
255
255
256 # Adds an item to the given +menu+.
256 # Adds an item to the given +menu+.
257 # The +id+ parameter (equals to the project id) is automatically added to the url.
257 # The +id+ parameter (equals to the project id) is automatically added to the url.
258 # menu :project_menu, :plugin_example, { :controller => 'example', :action => 'say_hello' }, :caption => 'Sample'
258 # menu :project_menu, :plugin_example, { :controller => 'example', :action => 'say_hello' }, :caption => 'Sample'
259 #
259 #
260 # +name+ parameter can be: :top_menu, :account_menu, :application_menu or :project_menu
260 # +name+ parameter can be: :top_menu, :account_menu, :application_menu or :project_menu
261 #
261 #
262 def menu(menu, item, url, options={})
262 def menu(menu, item, url, options={})
263 Redmine::MenuManager.map(menu).push(item, url, options)
263 Redmine::MenuManager.map(menu).push(item, url, options)
264 end
264 end
265 alias :add_menu_item :menu
265 alias :add_menu_item :menu
266
266
267 # Removes +item+ from the given +menu+.
267 # Removes +item+ from the given +menu+.
268 def delete_menu_item(menu, item)
268 def delete_menu_item(menu, item)
269 Redmine::MenuManager.map(menu).delete(item)
269 Redmine::MenuManager.map(menu).delete(item)
270 end
270 end
271
271
272 # Defines a permission called +name+ for the given +actions+.
272 # Defines a permission called +name+ for the given +actions+.
273 #
273 #
274 # The +actions+ argument is a hash with controllers as keys and actions as values (a single value or an array):
274 # The +actions+ argument is a hash with controllers as keys and actions as values (a single value or an array):
275 # permission :destroy_contacts, { :contacts => :destroy }
275 # permission :destroy_contacts, { :contacts => :destroy }
276 # permission :view_contacts, { :contacts => [:index, :show] }
276 # permission :view_contacts, { :contacts => [:index, :show] }
277 #
277 #
278 # The +options+ argument is a hash that accept the following keys:
278 # The +options+ argument is a hash that accept the following keys:
279 # * :public => the permission is public if set to true (implicitly given to any user)
279 # * :public => the permission is public if set to true (implicitly given to any user)
280 # * :require => can be set to one of the following values to restrict users the permission can be given to: :loggedin, :member
280 # * :require => can be set to one of the following values to restrict users the permission can be given to: :loggedin, :member
281 # * :read => set it to true so that the permission is still granted on closed projects
281 # * :read => set it to true so that the permission is still granted on closed projects
282 #
282 #
283 # Examples
283 # Examples
284 # # A permission that is implicitly given to any user
284 # # A permission that is implicitly given to any user
285 # # This permission won't appear on the Roles & Permissions setup screen
285 # # This permission won't appear on the Roles & Permissions setup screen
286 # permission :say_hello, { :example => :say_hello }, :public => true, :read => true
286 # permission :say_hello, { :example => :say_hello }, :public => true, :read => true
287 #
287 #
288 # # A permission that can be given to any user
288 # # A permission that can be given to any user
289 # permission :say_hello, { :example => :say_hello }
289 # permission :say_hello, { :example => :say_hello }
290 #
290 #
291 # # A permission that can be given to registered users only
291 # # A permission that can be given to registered users only
292 # permission :say_hello, { :example => :say_hello }, :require => :loggedin
292 # permission :say_hello, { :example => :say_hello }, :require => :loggedin
293 #
293 #
294 # # A permission that can be given to project members only
294 # # A permission that can be given to project members only
295 # permission :say_hello, { :example => :say_hello }, :require => :member
295 # permission :say_hello, { :example => :say_hello }, :require => :member
296 def permission(name, actions, options = {})
296 def permission(name, actions, options = {})
297 if @project_module
297 if @project_module
298 Redmine::AccessControl.map {|map| map.project_module(@project_module) {|map|map.permission(name, actions, options)}}
298 Redmine::AccessControl.map {|map| map.project_module(@project_module) {|map|map.permission(name, actions, options)}}
299 else
299 else
300 Redmine::AccessControl.map {|map| map.permission(name, actions, options)}
300 Redmine::AccessControl.map {|map| map.permission(name, actions, options)}
301 end
301 end
302 end
302 end
303
303
304 # Defines a project module, that can be enabled/disabled for each project.
304 # Defines a project module, that can be enabled/disabled for each project.
305 # Permissions defined inside +block+ will be bind to the module.
305 # Permissions defined inside +block+ will be bind to the module.
306 #
306 #
307 # project_module :things do
307 # project_module :things do
308 # permission :view_contacts, { :contacts => [:list, :show] }, :public => true
308 # permission :view_contacts, { :contacts => [:list, :show] }, :public => true
309 # permission :destroy_contacts, { :contacts => :destroy }
309 # permission :destroy_contacts, { :contacts => :destroy }
310 # end
310 # end
311 def project_module(name, &block)
311 def project_module(name, &block)
312 @project_module = name
312 @project_module = name
313 self.instance_eval(&block)
313 self.instance_eval(&block)
314 @project_module = nil
314 @project_module = nil
315 end
315 end
316
316
317 # Registers an activity provider.
317 # Registers an activity provider.
318 #
318 #
319 # Options:
319 # Options:
320 # * <tt>:class_name</tt> - one or more model(s) that provide these events (inferred from event_type by default)
320 # * <tt>:class_name</tt> - one or more model(s) that provide these events (inferred from event_type by default)
321 # * <tt>:default</tt> - setting this option to false will make the events not displayed by default
321 # * <tt>:default</tt> - setting this option to false will make the events not displayed by default
322 #
322 #
323 # A model can provide several activity event types.
323 # A model can provide several activity event types.
324 #
324 #
325 # Examples:
325 # Examples:
326 # register :news
326 # register :news
327 # register :scrums, :class_name => 'Meeting'
327 # register :scrums, :class_name => 'Meeting'
328 # register :issues, :class_name => ['Issue', 'Journal']
328 # register :issues, :class_name => ['Issue', 'Journal']
329 #
329 #
330 # Retrieving events:
330 # Retrieving events:
331 # Associated model(s) must implement the find_events class method.
331 # Associated model(s) must implement the find_events class method.
332 # ActiveRecord models can use acts_as_activity_provider as a way to implement this class method.
332 # ActiveRecord models can use acts_as_activity_provider as a way to implement this class method.
333 #
333 #
334 # The following call should return all the scrum events visible by current user that occured in the 5 last days:
334 # The following call should return all the scrum events visible by current user that occurred in the 5 last days:
335 # Meeting.find_events('scrums', User.current, 5.days.ago, Date.today)
335 # Meeting.find_events('scrums', User.current, 5.days.ago, Date.today)
336 # Meeting.find_events('scrums', User.current, 5.days.ago, Date.today, :project => foo) # events for project foo only
336 # Meeting.find_events('scrums', User.current, 5.days.ago, Date.today, :project => foo) # events for project foo only
337 #
337 #
338 # Note that :view_scrums permission is required to view these events in the activity view.
338 # Note that :view_scrums permission is required to view these events in the activity view.
339 def activity_provider(*args)
339 def activity_provider(*args)
340 Redmine::Activity.register(*args)
340 Redmine::Activity.register(*args)
341 end
341 end
342
342
343 # Registers a wiki formatter.
343 # Registers a wiki formatter.
344 #
344 #
345 # Parameters:
345 # Parameters:
346 # * +name+ - human-readable name
346 # * +name+ - human-readable name
347 # * +formatter+ - formatter class, which should have an instance method +to_html+
347 # * +formatter+ - formatter class, which should have an instance method +to_html+
348 # * +helper+ - helper module, which will be included by wiki pages
348 # * +helper+ - helper module, which will be included by wiki pages
349 def wiki_format_provider(name, formatter, helper)
349 def wiki_format_provider(name, formatter, helper)
350 Redmine::WikiFormatting.register(name, formatter, helper)
350 Redmine::WikiFormatting.register(name, formatter, helper)
351 end
351 end
352
352
353 # Returns +true+ if the plugin can be configured.
353 # Returns +true+ if the plugin can be configured.
354 def configurable?
354 def configurable?
355 settings && settings.is_a?(Hash) && !settings[:partial].blank?
355 settings && settings.is_a?(Hash) && !settings[:partial].blank?
356 end
356 end
357
357
358 def mirror_assets
358 def mirror_assets
359 source = assets_directory
359 source = assets_directory
360 destination = public_directory
360 destination = public_directory
361 return unless File.directory?(source)
361 return unless File.directory?(source)
362
362
363 source_files = Dir[source + "/**/*"]
363 source_files = Dir[source + "/**/*"]
364 source_dirs = source_files.select { |d| File.directory?(d) }
364 source_dirs = source_files.select { |d| File.directory?(d) }
365 source_files -= source_dirs
365 source_files -= source_dirs
366
366
367 unless source_files.empty?
367 unless source_files.empty?
368 base_target_dir = File.join(destination, File.dirname(source_files.first).gsub(source, ''))
368 base_target_dir = File.join(destination, File.dirname(source_files.first).gsub(source, ''))
369 begin
369 begin
370 FileUtils.mkdir_p(base_target_dir)
370 FileUtils.mkdir_p(base_target_dir)
371 rescue Exception => e
371 rescue Exception => e
372 raise "Could not create directory #{base_target_dir}: " + e.message
372 raise "Could not create directory #{base_target_dir}: " + e.message
373 end
373 end
374 end
374 end
375
375
376 source_dirs.each do |dir|
376 source_dirs.each do |dir|
377 # strip down these paths so we have simple, relative paths we can
377 # strip down these paths so we have simple, relative paths we can
378 # add to the destination
378 # add to the destination
379 target_dir = File.join(destination, dir.gsub(source, ''))
379 target_dir = File.join(destination, dir.gsub(source, ''))
380 begin
380 begin
381 FileUtils.mkdir_p(target_dir)
381 FileUtils.mkdir_p(target_dir)
382 rescue Exception => e
382 rescue Exception => e
383 raise "Could not create directory #{target_dir}: " + e.message
383 raise "Could not create directory #{target_dir}: " + e.message
384 end
384 end
385 end
385 end
386
386
387 source_files.each do |file|
387 source_files.each do |file|
388 begin
388 begin
389 target = File.join(destination, file.gsub(source, ''))
389 target = File.join(destination, file.gsub(source, ''))
390 unless File.exist?(target) && FileUtils.identical?(file, target)
390 unless File.exist?(target) && FileUtils.identical?(file, target)
391 FileUtils.cp(file, target)
391 FileUtils.cp(file, target)
392 end
392 end
393 rescue Exception => e
393 rescue Exception => e
394 raise "Could not copy #{file} to #{target}: " + e.message
394 raise "Could not copy #{file} to #{target}: " + e.message
395 end
395 end
396 end
396 end
397 end
397 end
398
398
399 # Mirrors assets from one or all plugins to public/plugin_assets
399 # Mirrors assets from one or all plugins to public/plugin_assets
400 def self.mirror_assets(name=nil)
400 def self.mirror_assets(name=nil)
401 if name.present?
401 if name.present?
402 find(name).mirror_assets
402 find(name).mirror_assets
403 else
403 else
404 all.each do |plugin|
404 all.each do |plugin|
405 plugin.mirror_assets
405 plugin.mirror_assets
406 end
406 end
407 end
407 end
408 end
408 end
409
409
410 # The directory containing this plugin's migrations (<tt>plugin/db/migrate</tt>)
410 # The directory containing this plugin's migrations (<tt>plugin/db/migrate</tt>)
411 def migration_directory
411 def migration_directory
412 File.join(Rails.root, 'plugins', id.to_s, 'db', 'migrate')
412 File.join(Rails.root, 'plugins', id.to_s, 'db', 'migrate')
413 end
413 end
414
414
415 # Returns the version number of the latest migration for this plugin. Returns
415 # Returns the version number of the latest migration for this plugin. Returns
416 # nil if this plugin has no migrations.
416 # nil if this plugin has no migrations.
417 def latest_migration
417 def latest_migration
418 migrations.last
418 migrations.last
419 end
419 end
420
420
421 # Returns the version numbers of all migrations for this plugin.
421 # Returns the version numbers of all migrations for this plugin.
422 def migrations
422 def migrations
423 migrations = Dir[migration_directory+"/*.rb"]
423 migrations = Dir[migration_directory+"/*.rb"]
424 migrations.map { |p| File.basename(p).match(/0*(\d+)\_/)[1].to_i }.sort
424 migrations.map { |p| File.basename(p).match(/0*(\d+)\_/)[1].to_i }.sort
425 end
425 end
426
426
427 # Migrate this plugin to the given version
427 # Migrate this plugin to the given version
428 def migrate(version = nil)
428 def migrate(version = nil)
429 puts "Migrating #{id} (#{name})..."
429 puts "Migrating #{id} (#{name})..."
430 Redmine::Plugin::Migrator.migrate_plugin(self, version)
430 Redmine::Plugin::Migrator.migrate_plugin(self, version)
431 end
431 end
432
432
433 # Migrates all plugins or a single plugin to a given version
433 # Migrates all plugins or a single plugin to a given version
434 # Exemples:
434 # Exemples:
435 # Plugin.migrate
435 # Plugin.migrate
436 # Plugin.migrate('sample_plugin')
436 # Plugin.migrate('sample_plugin')
437 # Plugin.migrate('sample_plugin', 1)
437 # Plugin.migrate('sample_plugin', 1)
438 #
438 #
439 def self.migrate(name=nil, version=nil)
439 def self.migrate(name=nil, version=nil)
440 if name.present?
440 if name.present?
441 find(name).migrate(version)
441 find(name).migrate(version)
442 else
442 else
443 all.each do |plugin|
443 all.each do |plugin|
444 plugin.migrate
444 plugin.migrate
445 end
445 end
446 end
446 end
447 end
447 end
448
448
449 class Migrator < ActiveRecord::Migrator
449 class Migrator < ActiveRecord::Migrator
450 # We need to be able to set the 'current' plugin being migrated.
450 # We need to be able to set the 'current' plugin being migrated.
451 cattr_accessor :current_plugin
451 cattr_accessor :current_plugin
452
452
453 class << self
453 class << self
454 # Runs the migrations from a plugin, up (or down) to the version given
454 # Runs the migrations from a plugin, up (or down) to the version given
455 def migrate_plugin(plugin, version)
455 def migrate_plugin(plugin, version)
456 self.current_plugin = plugin
456 self.current_plugin = plugin
457 return if current_version(plugin) == version
457 return if current_version(plugin) == version
458 migrate(plugin.migration_directory, version)
458 migrate(plugin.migration_directory, version)
459 end
459 end
460
460
461 def current_version(plugin=current_plugin)
461 def current_version(plugin=current_plugin)
462 # Delete migrations that don't match .. to_i will work because the number comes first
462 # Delete migrations that don't match .. to_i will work because the number comes first
463 ::ActiveRecord::Base.connection.select_values(
463 ::ActiveRecord::Base.connection.select_values(
464 "SELECT version FROM #{schema_migrations_table_name}"
464 "SELECT version FROM #{schema_migrations_table_name}"
465 ).delete_if{ |v| v.match(/-#{plugin.id}/) == nil }.map(&:to_i).max || 0
465 ).delete_if{ |v| v.match(/-#{plugin.id}/) == nil }.map(&:to_i).max || 0
466 end
466 end
467 end
467 end
468
468
469 def migrated
469 def migrated
470 sm_table = self.class.schema_migrations_table_name
470 sm_table = self.class.schema_migrations_table_name
471 ::ActiveRecord::Base.connection.select_values(
471 ::ActiveRecord::Base.connection.select_values(
472 "SELECT version FROM #{sm_table}"
472 "SELECT version FROM #{sm_table}"
473 ).delete_if{ |v| v.match(/-#{current_plugin.id}/) == nil }.map(&:to_i).sort
473 ).delete_if{ |v| v.match(/-#{current_plugin.id}/) == nil }.map(&:to_i).sort
474 end
474 end
475
475
476 def record_version_state_after_migrating(version)
476 def record_version_state_after_migrating(version)
477 super(version.to_s + "-" + current_plugin.id.to_s)
477 super(version.to_s + "-" + current_plugin.id.to_s)
478 end
478 end
479 end
479 end
480 end
480 end
481 end
481 end
General Comments 0
You need to be logged in to leave comments. Login now