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