##// END OF EJS Templates
Change plugins directory through the configuration.yml file (#24007)....
Jean-Philippe Lang -
r15581:7a9a22f3dd62
parent child
Show More
@@ -1,221 +1,230
1 1 # = Redmine configuration file
2 2 #
3 3 # Each environment has it's own configuration options. If you are only
4 4 # running in production, only the production block needs to be configured.
5 5 # Environment specific configuration options override the default ones.
6 6 #
7 7 # Note that this file needs to be a valid YAML file.
8 8 # DO NOT USE TABS! Use 2 spaces instead of tabs for identation.
9 9
10 10 # default configuration options for all environments
11 11 default:
12 12 # Outgoing emails configuration
13 13 # See the examples below and the Rails guide for more configuration options:
14 14 # http://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration
15 15 email_delivery:
16 16
17 17 # ==== Simple SMTP server at localhost
18 18 #
19 19 # email_delivery:
20 20 # delivery_method: :smtp
21 21 # smtp_settings:
22 22 # address: "localhost"
23 23 # port: 25
24 24 #
25 25 # ==== SMTP server at example.com using LOGIN authentication and checking HELO for foo.com
26 26 #
27 27 # email_delivery:
28 28 # delivery_method: :smtp
29 29 # smtp_settings:
30 30 # address: "example.com"
31 31 # port: 25
32 32 # authentication: :login
33 33 # domain: 'foo.com'
34 34 # user_name: 'myaccount'
35 35 # password: 'password'
36 36 #
37 37 # ==== SMTP server at example.com using PLAIN authentication
38 38 #
39 39 # email_delivery:
40 40 # delivery_method: :smtp
41 41 # smtp_settings:
42 42 # address: "example.com"
43 43 # port: 25
44 44 # authentication: :plain
45 45 # domain: 'example.com'
46 46 # user_name: 'myaccount'
47 47 # password: 'password'
48 48 #
49 49 # ==== SMTP server at using TLS (GMail)
50 50 # This might require some additional configuration. See the guides at:
51 51 # http://www.redmine.org/projects/redmine/wiki/EmailConfiguration
52 52 #
53 53 # email_delivery:
54 54 # delivery_method: :smtp
55 55 # smtp_settings:
56 56 # enable_starttls_auto: true
57 57 # address: "smtp.gmail.com"
58 58 # port: 587
59 59 # domain: "smtp.gmail.com" # 'your.domain.com' for GoogleApps
60 60 # authentication: :plain
61 61 # user_name: "your_email@gmail.com"
62 62 # password: "your_password"
63 63 #
64 64 # ==== Sendmail command
65 65 #
66 66 # email_delivery:
67 67 # delivery_method: :sendmail
68 68
69 69 # Absolute path to the directory where attachments are stored.
70 70 # The default is the 'files' directory in your Redmine instance.
71 71 # Your Redmine instance needs to have write permission on this
72 72 # directory.
73 73 # Examples:
74 74 # attachments_storage_path: /var/redmine/files
75 75 # attachments_storage_path: D:/redmine/files
76 76 attachments_storage_path:
77 77
78 # Absolute path to the directory where plugins are stored.
79 # The default is the 'plugins' directory in your Redmine instance.
80 # Your Redmine instance needs to have read permission on this
81 # directory.
82 # Examples:
83 # plugins_path: /var/redmine/plugins
84 # plugins_path: D:/redmine/plugins
85 plugins_path:
86
78 87 # Configuration of the autologin cookie.
79 88 # autologin_cookie_name: the name of the cookie (default: autologin)
80 89 # autologin_cookie_path: the cookie path (default: /)
81 90 # autologin_cookie_secure: true sets the cookie secure flag (default: false)
82 91 autologin_cookie_name:
83 92 autologin_cookie_path:
84 93 autologin_cookie_secure:
85 94
86 95 # Configuration of SCM executable command.
87 96 #
88 97 # Absolute path (e.g. /usr/local/bin/hg) or command name (e.g. hg.exe, bzr.exe)
89 98 # On Windows + CRuby, *.cmd, *.bat (e.g. hg.cmd, bzr.bat) does not work.
90 99 #
91 100 # On Windows + JRuby 1.6.2, path which contains spaces does not work.
92 101 # For example, "C:\Program Files\TortoiseHg\hg.exe".
93 102 # If you want to this feature, you need to install to the path which does not contains spaces.
94 103 # For example, "C:\TortoiseHg\hg.exe".
95 104 #
96 105 # Examples:
97 106 # scm_subversion_command: svn # (default: svn)
98 107 # scm_mercurial_command: C:\Program Files\TortoiseHg\hg.exe # (default: hg)
99 108 # scm_git_command: /usr/local/bin/git # (default: git)
100 109 # scm_cvs_command: cvs # (default: cvs)
101 110 # scm_bazaar_command: bzr.exe # (default: bzr)
102 111 # scm_darcs_command: darcs-1.0.9-i386-linux # (default: darcs)
103 112 #
104 113 scm_subversion_command:
105 114 scm_mercurial_command:
106 115 scm_git_command:
107 116 scm_cvs_command:
108 117 scm_bazaar_command:
109 118 scm_darcs_command:
110 119
111 120 # SCM paths validation.
112 121 #
113 122 # You can configure a regular expression for each SCM that will be used to
114 123 # validate the path of new repositories (eg. path entered by users with the
115 124 # "Manage repositories" permission and path returned by reposman.rb).
116 125 # The regexp will be wrapped with \A \z, so it must match the whole path.
117 126 # And the regexp is case sensitive.
118 127 #
119 128 # You can match the project identifier by using %project% in the regexp.
120 129 #
121 130 # You can also set a custom hint message for each SCM that will be displayed
122 131 # on the repository form instead of the default one.
123 132 #
124 133 # Examples:
125 134 # scm_subversion_path_regexp: file:///svnpath/[a-z0-9_]+
126 135 # scm_subversion_path_info: SVN URL (eg. file:///svnpath/foo)
127 136 #
128 137 # scm_git_path_regexp: /gitpath/%project%(\.[a-z0-9_])?/
129 138 #
130 139 scm_subversion_path_regexp:
131 140 scm_mercurial_path_regexp:
132 141 scm_git_path_regexp:
133 142 scm_cvs_path_regexp:
134 143 scm_bazaar_path_regexp:
135 144 scm_darcs_path_regexp:
136 145 scm_filesystem_path_regexp:
137 146
138 147 # Absolute path to the SCM commands errors (stderr) log file.
139 148 # The default is to log in the 'log' directory of your Redmine instance.
140 149 # Example:
141 150 # scm_stderr_log_file: /var/log/redmine_scm_stderr.log
142 151 scm_stderr_log_file:
143 152
144 153 # Key used to encrypt sensitive data in the database (SCM and LDAP passwords).
145 154 # If you don't want to enable data encryption, just leave it blank.
146 155 # WARNING: losing/changing this key will make encrypted data unreadable.
147 156 #
148 157 # If you want to encrypt existing passwords in your database:
149 158 # * set the cipher key here in your configuration file
150 159 # * encrypt data using 'rake db:encrypt RAILS_ENV=production'
151 160 #
152 161 # If you have encrypted data and want to change this key, you have to:
153 162 # * decrypt data using 'rake db:decrypt RAILS_ENV=production' first
154 163 # * change the cipher key here in your configuration file
155 164 # * encrypt data using 'rake db:encrypt RAILS_ENV=production'
156 165 database_cipher_key:
157 166
158 167 # Set this to false to disable plugins' assets mirroring on startup.
159 168 # You can use `rake redmine:plugins:assets` to manually mirror assets
160 169 # to public/plugin_assets when you install/upgrade a Redmine plugin.
161 170 #
162 171 #mirror_plugins_assets_on_startup: false
163 172
164 173 # Your secret key for verifying cookie session data integrity. If you
165 174 # change this key, all old sessions will become invalid! Make sure the
166 175 # secret is at least 30 characters and all random, no regular words or
167 176 # you'll be exposed to dictionary attacks.
168 177 #
169 178 # If you have a load-balancing Redmine cluster, you have to use the
170 179 # same secret token on each machine.
171 180 #secret_token: 'change it to a long random string'
172 181
173 182 # Requires users to re-enter their password for sensitive actions (editing
174 183 # of account data, project memberships, application settings, user, group,
175 184 # role, auth source management and project deletion). Disabled by default.
176 185 # Timeout is set in minutes.
177 186 #
178 187 #sudo_mode: true
179 188 #sudo_mode_timeout: 15
180 189
181 190 # Absolute path (e.g. /usr/bin/convert, c:/im/convert.exe) to
182 191 # the ImageMagick's `convert` binary. Used to generate attachment thumbnails.
183 192 #imagemagick_convert_command:
184 193
185 194 # Configuration of RMagick font.
186 195 #
187 196 # Redmine uses RMagick in order to export gantt png.
188 197 # You don't need this setting if you don't install RMagick.
189 198 #
190 199 # In CJK (Chinese, Japanese and Korean),
191 200 # in order to show CJK characters correctly,
192 201 # you need to set this configuration.
193 202 #
194 203 # Because there is no standard font across platforms in CJK,
195 204 # you need to set a font installed in your server.
196 205 #
197 206 # This setting is not necessary in non CJK.
198 207 #
199 208 # Examples for Japanese:
200 209 # Windows:
201 210 # rmagick_font_path: C:\windows\fonts\msgothic.ttc
202 211 # Linux:
203 212 # rmagick_font_path: /usr/share/fonts/ipa-mincho/ipam.ttf
204 213 #
205 214 rmagick_font_path:
206 215
207 216 # Maximum number of simultaneous AJAX uploads
208 217 #max_concurrent_ajax_uploads: 2
209 218
210 219 # Configure OpenIdAuthentication.store
211 220 #
212 221 # allowed values: :memory, :file, :memcache
213 222 #openid_authentication_store: :memory
214 223
215 224 # specific configuration options for production environment
216 225 # that overrides the default ones
217 226 production:
218 227
219 228 # specific configuration options for development environment
220 229 # that overrides the default ones
221 230 development:
@@ -1,37 +1,42
1 1 class RedminePluginGenerator < Rails::Generators::NamedBase
2 2 source_root File.expand_path("../templates", __FILE__)
3 3
4 4 attr_reader :plugin_path, :plugin_name, :plugin_pretty_name
5 5
6 6 def initialize(*args)
7 7 super
8 8 @plugin_name = file_name.underscore
9 9 @plugin_pretty_name = plugin_name.titleize
10 @plugin_path = "plugins/#{plugin_name}"
10 if Redmine::Configuration['plugins_path'].nil?
11 @plugin_path = File.join(Rails.root, 'plugins', plugin_name)
12 else
13 @plugin_path = File.join(Redmine::Configuration['plugins_path'], plugin_name)
14 end
15 puts @plugin_path
11 16 end
12 17
13 18 def copy_templates
14 19 empty_directory "#{plugin_path}/app"
15 20 empty_directory "#{plugin_path}/app/controllers"
16 21 empty_directory "#{plugin_path}/app/helpers"
17 22 empty_directory "#{plugin_path}/app/models"
18 23 empty_directory "#{plugin_path}/app/views"
19 24 empty_directory "#{plugin_path}/db/migrate"
20 25 empty_directory "#{plugin_path}/lib/tasks"
21 26 empty_directory "#{plugin_path}/assets/images"
22 27 empty_directory "#{plugin_path}/assets/javascripts"
23 28 empty_directory "#{plugin_path}/assets/stylesheets"
24 29 empty_directory "#{plugin_path}/config/locales"
25 30 empty_directory "#{plugin_path}/test"
26 31 empty_directory "#{plugin_path}/test/fixtures"
27 32 empty_directory "#{plugin_path}/test/unit"
28 33 empty_directory "#{plugin_path}/test/functional"
29 34 empty_directory "#{plugin_path}/test/integration"
30 35
31 36 template 'README.rdoc', "#{plugin_path}/README.rdoc"
32 37 template 'init.rb.erb', "#{plugin_path}/init.rb"
33 38 template 'routes.rb', "#{plugin_path}/config/routes.rb"
34 39 template 'en_rails_i18n.yml', "#{plugin_path}/config/locales/en.yml"
35 40 template 'test_helper.rb.erb', "#{plugin_path}/test/test_helper.rb"
36 41 end
37 42 end
@@ -1,27 +1,31
1 1 class RedminePluginControllerGenerator < Rails::Generators::NamedBase
2 2 source_root File.expand_path("../templates", __FILE__)
3 3 argument :controller, :type => :string
4 4 argument :actions, :type => :array, :default => [], :banner => "ACTION ACTION ..."
5 5
6 6 attr_reader :plugin_path, :plugin_name, :plugin_pretty_name
7 7
8 8 def initialize(*args)
9 9 super
10 10 @plugin_name = file_name.underscore
11 11 @plugin_pretty_name = plugin_name.titleize
12 @plugin_path = "plugins/#{plugin_name}"
12 if Redmine::Configuration['plugins_path'].nil?
13 @plugin_path = File.join(Rails.root, 'plugins', plugin_name)
14 else
15 @plugin_path = File.join(Redmine::Configuration['plugins_path'], plugin_name)
16 end
13 17 @controller_class = controller.camelize
14 18 end
15 19
16 20 def copy_templates
17 21 template 'controller.rb.erb', "#{plugin_path}/app/controllers/#{controller}_controller.rb"
18 22 template 'helper.rb.erb', "#{plugin_path}/app/helpers/#{controller}_helper.rb"
19 23 template 'functional_test.rb.erb', "#{plugin_path}/test/functional/#{controller}_controller_test.rb"
20 24 # View template for each action.
21 25 actions.each do |action|
22 26 path = "#{plugin_path}/app/views/#{controller}/#{action}.html.erb"
23 27 @action_name = action
24 28 template 'view.html.erb', path
25 29 end
26 30 end
27 31 end
@@ -1,41 +1,45
1 1 class RedminePluginModelGenerator < Rails::Generators::NamedBase
2 2
3 3 source_root File.expand_path("../templates", __FILE__)
4 4 argument :model, :type => :string
5 5 argument :attributes, :type => :array, :default => [], :banner => "field[:type][:index] field[:type][:index]"
6 6 class_option :migration, :type => :boolean
7 7 class_option :timestamps, :type => :boolean
8 8 class_option :parent, :type => :string, :desc => "The parent class for the generated model"
9 9 class_option :indexes, :type => :boolean, :default => true, :desc => "Add indexes for references and belongs_to columns"
10 10
11 11 attr_reader :plugin_path, :plugin_name, :plugin_pretty_name
12 12
13 13 def initialize(*args)
14 14 super
15 15 @plugin_name = file_name.underscore
16 16 @plugin_pretty_name = plugin_name.titleize
17 @plugin_path = "plugins/#{plugin_name}"
17 if Redmine::Configuration['plugins_path'].nil?
18 @plugin_path = File.join(Rails.root, 'plugins', plugin_name)
19 else
20 @plugin_path = File.join(Redmine::Configuration['plugins_path'], plugin_name)
21 end
18 22 @model_class = model.camelize
19 23 @table_name = @model_class.tableize
20 24 @migration_filename = "create_#{@table_name}"
21 25 @migration_class_name = @migration_filename.camelize
22 26 end
23 27
24 28 def copy_templates
25 29 template 'model.rb.erb', "#{plugin_path}/app/models/#{model.underscore}.rb"
26 30 template 'unit_test.rb.erb', "#{plugin_path}/test/unit/#{model.underscore}_test.rb"
27 31
28 32 migration_filename = "%03i_#{@migration_filename}.rb" % (migration_number + 1)
29 33 template "migration.rb", "#{plugin_path}/db/migrate/#{migration_filename}"
30 34 end
31 35
32 36 def attributes_with_index
33 37 attributes.select { |a| a.has_index? || (a.reference? && options[:indexes]) }
34 38 end
35 39
36 40 def migration_number
37 41 current = Dir.glob("#{plugin_path}/db/migrate/*.rb").map do |file|
38 42 File.basename(file).split("_").first.to_i
39 43 end.max.to_i
40 44 end
41 45 end
@@ -1,504 +1,504
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 module Redmine #:nodoc:
19 19
20 20 class PluginNotFound < StandardError; end
21 21 class PluginRequirementError < StandardError; end
22 22
23 23 # Base class for Redmine plugins.
24 24 # Plugins are registered using the <tt>register</tt> class method that acts as the public constructor.
25 25 #
26 26 # Redmine::Plugin.register :example do
27 27 # name 'Example plugin'
28 28 # author 'John Smith'
29 29 # description 'This is an example plugin for Redmine'
30 30 # version '0.0.1'
31 31 # settings :default => {'foo'=>'bar'}, :partial => 'settings/settings'
32 32 # end
33 33 #
34 34 # === Plugin attributes
35 35 #
36 36 # +settings+ is an optional attribute that let the plugin be configurable.
37 37 # It must be a hash with the following keys:
38 38 # * <tt>:default</tt>: default value for the plugin settings
39 39 # * <tt>:partial</tt>: path of the configuration partial view, relative to the plugin <tt>app/views</tt> directory
40 40 # Example:
41 41 # settings :default => {'foo'=>'bar'}, :partial => 'settings/settings'
42 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 44 # When rendered, the plugin settings value is available as the local variable +settings+
45 45 class Plugin
46 46 cattr_accessor :directory
47 self.directory = File.join(Rails.root, 'plugins')
47 self.directory = Redmine::Configuration['plugins_path'] || File.join(Rails.root, 'plugins')
48 48
49 49 cattr_accessor :public_directory
50 50 self.public_directory = File.join(Rails.root, 'public', 'plugin_assets')
51 51
52 52 @registered_plugins = {}
53 53 @used_partials = {}
54 54
55 55 class << self
56 56 attr_reader :registered_plugins
57 57 private :new
58 58
59 59 def def_field(*names)
60 60 class_eval do
61 61 names.each do |name|
62 62 define_method(name) do |*args|
63 63 args.empty? ? instance_variable_get("@#{name}") : instance_variable_set("@#{name}", *args)
64 64 end
65 65 end
66 66 end
67 67 end
68 68 end
69 69 def_field :name, :description, :url, :author, :author_url, :version, :settings, :directory
70 70 attr_reader :id
71 71
72 72 # Plugin constructor
73 73 def self.register(id, &block)
74 74 p = new(id)
75 75 p.instance_eval(&block)
76 76
77 77 # Set a default name if it was not provided during registration
78 78 p.name(id.to_s.humanize) if p.name.nil?
79 79 # Set a default directory if it was not provided during registration
80 80 p.directory(File.join(self.directory, id.to_s)) if p.directory.nil?
81 81
82 82 # Adds plugin locales if any
83 83 # YAML translation files should be found under <plugin>/config/locales/
84 84 Rails.application.config.i18n.load_path += Dir.glob(File.join(p.directory, 'config', 'locales', '*.yml'))
85 85
86 86 # Prepends the app/views directory of the plugin to the view path
87 87 view_path = File.join(p.directory, 'app', 'views')
88 88 if File.directory?(view_path)
89 89 ActionController::Base.prepend_view_path(view_path)
90 90 ActionMailer::Base.prepend_view_path(view_path)
91 91 end
92 92
93 93 # Adds the app/{controllers,helpers,models} directories of the plugin to the autoload path
94 94 Dir.glob File.expand_path(File.join(p.directory, 'app', '{controllers,helpers,models}')) do |dir|
95 95 ActiveSupport::Dependencies.autoload_paths += [dir]
96 96 end
97 97
98 98 # Defines plugin setting if present
99 99 if p.settings
100 100 Setting.define_plugin_setting p
101 101 end
102 102
103 103 # Warn for potential settings[:partial] collisions
104 104 if p.configurable?
105 105 partial = p.settings[:partial]
106 106 if @used_partials[partial]
107 107 Rails.logger.warn "WARNING: settings partial '#{partial}' is declared in '#{p.id}' plugin but it is already used by plugin '#{@used_partials[partial]}'. Only one settings view will be used. You may want to contact those plugins authors to fix this."
108 108 end
109 109 @used_partials[partial] = p.id
110 110 end
111 111
112 112 registered_plugins[id] = p
113 113 end
114 114
115 115 # Returns an array of all registered plugins
116 116 def self.all
117 117 registered_plugins.values.sort
118 118 end
119 119
120 120 # Finds a plugin by its id
121 121 # Returns a PluginNotFound exception if the plugin doesn't exist
122 122 def self.find(id)
123 123 registered_plugins[id.to_sym] || raise(PluginNotFound)
124 124 end
125 125
126 126 # Clears the registered plugins hash
127 127 # It doesn't unload installed plugins
128 128 def self.clear
129 129 @registered_plugins = {}
130 130 end
131 131
132 132 # Removes a plugin from the registered plugins
133 133 # It doesn't unload the plugin
134 134 def self.unregister(id)
135 135 @registered_plugins.delete(id)
136 136 end
137 137
138 138 # Checks if a plugin is installed
139 139 #
140 140 # @param [String] id name of the plugin
141 141 def self.installed?(id)
142 142 registered_plugins[id.to_sym].present?
143 143 end
144 144
145 145 def self.load
146 146 Dir.glob(File.join(self.directory, '*')).sort.each do |directory|
147 147 if File.directory?(directory)
148 148 lib = File.join(directory, "lib")
149 149 if File.directory?(lib)
150 150 $:.unshift lib
151 151 ActiveSupport::Dependencies.autoload_paths += [lib]
152 152 end
153 153 initializer = File.join(directory, "init.rb")
154 154 if File.file?(initializer)
155 155 require initializer
156 156 end
157 157 end
158 158 end
159 159 end
160 160
161 161 def initialize(id)
162 162 @id = id.to_sym
163 163 end
164 164
165 165 def public_directory
166 166 File.join(self.class.public_directory, id.to_s)
167 167 end
168 168
169 169 def to_param
170 170 id
171 171 end
172 172
173 173 def assets_directory
174 174 File.join(directory, 'assets')
175 175 end
176 176
177 177 def <=>(plugin)
178 178 self.id.to_s <=> plugin.id.to_s
179 179 end
180 180
181 181 # Sets a requirement on Redmine version
182 182 # Raises a PluginRequirementError exception if the requirement is not met
183 183 #
184 184 # Examples
185 185 # # Requires Redmine 0.7.3 or higher
186 186 # requires_redmine :version_or_higher => '0.7.3'
187 187 # requires_redmine '0.7.3'
188 188 #
189 189 # # Requires Redmine 0.7.x or higher
190 190 # requires_redmine '0.7'
191 191 #
192 192 # # Requires a specific Redmine version
193 193 # requires_redmine :version => '0.7.3' # 0.7.3 only
194 194 # requires_redmine :version => '0.7' # 0.7.x
195 195 # requires_redmine :version => ['0.7.3', '0.8.0'] # 0.7.3 or 0.8.0
196 196 #
197 197 # # Requires a Redmine version within a range
198 198 # requires_redmine :version => '0.7.3'..'0.9.1' # >= 0.7.3 and <= 0.9.1
199 199 # requires_redmine :version => '0.7'..'0.9' # >= 0.7.x and <= 0.9.x
200 200 def requires_redmine(arg)
201 201 arg = { :version_or_higher => arg } unless arg.is_a?(Hash)
202 202 arg.assert_valid_keys(:version, :version_or_higher)
203 203
204 204 current = Redmine::VERSION.to_a
205 205 arg.each do |k, req|
206 206 case k
207 207 when :version_or_higher
208 208 raise ArgumentError.new(":version_or_higher accepts a version string only") unless req.is_a?(String)
209 209 unless compare_versions(req, current) <= 0
210 210 raise PluginRequirementError.new("#{id} plugin requires Redmine #{req} or higher but current is #{current.join('.')}")
211 211 end
212 212 when :version
213 213 req = [req] if req.is_a?(String)
214 214 if req.is_a?(Array)
215 215 unless req.detect {|ver| compare_versions(ver, current) == 0}
216 216 raise PluginRequirementError.new("#{id} plugin requires one the following Redmine versions: #{req.join(', ')} but current is #{current.join('.')}")
217 217 end
218 218 elsif req.is_a?(Range)
219 219 unless compare_versions(req.first, current) <= 0 && compare_versions(req.last, current) >= 0
220 220 raise PluginRequirementError.new("#{id} plugin requires a Redmine version between #{req.first} and #{req.last} but current is #{current.join('.')}")
221 221 end
222 222 else
223 223 raise ArgumentError.new(":version option accepts a version string, an array or a range of versions")
224 224 end
225 225 end
226 226 end
227 227 true
228 228 end
229 229
230 230 def compare_versions(requirement, current)
231 231 requirement = requirement.split('.').collect(&:to_i)
232 232 requirement <=> current.slice(0, requirement.size)
233 233 end
234 234 private :compare_versions
235 235
236 236 # Sets a requirement on a Redmine plugin version
237 237 # Raises a PluginRequirementError exception if the requirement is not met
238 238 #
239 239 # Examples
240 240 # # Requires a plugin named :foo version 0.7.3 or higher
241 241 # requires_redmine_plugin :foo, :version_or_higher => '0.7.3'
242 242 # requires_redmine_plugin :foo, '0.7.3'
243 243 #
244 244 # # Requires a specific version of a Redmine plugin
245 245 # requires_redmine_plugin :foo, :version => '0.7.3' # 0.7.3 only
246 246 # requires_redmine_plugin :foo, :version => ['0.7.3', '0.8.0'] # 0.7.3 or 0.8.0
247 247 def requires_redmine_plugin(plugin_name, arg)
248 248 arg = { :version_or_higher => arg } unless arg.is_a?(Hash)
249 249 arg.assert_valid_keys(:version, :version_or_higher)
250 250
251 251 plugin = Plugin.find(plugin_name)
252 252 current = plugin.version.split('.').collect(&:to_i)
253 253
254 254 arg.each do |k, v|
255 255 v = [] << v unless v.is_a?(Array)
256 256 versions = v.collect {|s| s.split('.').collect(&:to_i)}
257 257 case k
258 258 when :version_or_higher
259 259 raise ArgumentError.new("wrong number of versions (#{versions.size} for 1)") unless versions.size == 1
260 260 unless (current <=> versions.first) >= 0
261 261 raise PluginRequirementError.new("#{id} plugin requires the #{plugin_name} plugin #{v} or higher but current is #{current.join('.')}")
262 262 end
263 263 when :version
264 264 unless versions.include?(current.slice(0,3))
265 265 raise PluginRequirementError.new("#{id} plugin requires one the following versions of #{plugin_name}: #{v.join(', ')} but current is #{current.join('.')}")
266 266 end
267 267 end
268 268 end
269 269 true
270 270 end
271 271
272 272 # Adds an item to the given +menu+.
273 273 # The +id+ parameter (equals to the project id) is automatically added to the url.
274 274 # menu :project_menu, :plugin_example, { :controller => 'example', :action => 'say_hello' }, :caption => 'Sample'
275 275 #
276 276 # +name+ parameter can be: :top_menu, :account_menu, :application_menu or :project_menu
277 277 #
278 278 def menu(menu, item, url, options={})
279 279 Redmine::MenuManager.map(menu).push(item, url, options)
280 280 end
281 281 alias :add_menu_item :menu
282 282
283 283 # Removes +item+ from the given +menu+.
284 284 def delete_menu_item(menu, item)
285 285 Redmine::MenuManager.map(menu).delete(item)
286 286 end
287 287
288 288 # Defines a permission called +name+ for the given +actions+.
289 289 #
290 290 # The +actions+ argument is a hash with controllers as keys and actions as values (a single value or an array):
291 291 # permission :destroy_contacts, { :contacts => :destroy }
292 292 # permission :view_contacts, { :contacts => [:index, :show] }
293 293 #
294 294 # The +options+ argument is a hash that accept the following keys:
295 295 # * :public => the permission is public if set to true (implicitly given to any user)
296 296 # * :require => can be set to one of the following values to restrict users the permission can be given to: :loggedin, :member
297 297 # * :read => set it to true so that the permission is still granted on closed projects
298 298 #
299 299 # Examples
300 300 # # A permission that is implicitly given to any user
301 301 # # This permission won't appear on the Roles & Permissions setup screen
302 302 # permission :say_hello, { :example => :say_hello }, :public => true, :read => true
303 303 #
304 304 # # A permission that can be given to any user
305 305 # permission :say_hello, { :example => :say_hello }
306 306 #
307 307 # # A permission that can be given to registered users only
308 308 # permission :say_hello, { :example => :say_hello }, :require => :loggedin
309 309 #
310 310 # # A permission that can be given to project members only
311 311 # permission :say_hello, { :example => :say_hello }, :require => :member
312 312 def permission(name, actions, options = {})
313 313 if @project_module
314 314 Redmine::AccessControl.map {|map| map.project_module(@project_module) {|map|map.permission(name, actions, options)}}
315 315 else
316 316 Redmine::AccessControl.map {|map| map.permission(name, actions, options)}
317 317 end
318 318 end
319 319
320 320 # Defines a project module, that can be enabled/disabled for each project.
321 321 # Permissions defined inside +block+ will be bind to the module.
322 322 #
323 323 # project_module :things do
324 324 # permission :view_contacts, { :contacts => [:list, :show] }, :public => true
325 325 # permission :destroy_contacts, { :contacts => :destroy }
326 326 # end
327 327 def project_module(name, &block)
328 328 @project_module = name
329 329 self.instance_eval(&block)
330 330 @project_module = nil
331 331 end
332 332
333 333 # Registers an activity provider.
334 334 #
335 335 # Options:
336 336 # * <tt>:class_name</tt> - one or more model(s) that provide these events (inferred from event_type by default)
337 337 # * <tt>:default</tt> - setting this option to false will make the events not displayed by default
338 338 #
339 339 # A model can provide several activity event types.
340 340 #
341 341 # Examples:
342 342 # register :news
343 343 # register :scrums, :class_name => 'Meeting'
344 344 # register :issues, :class_name => ['Issue', 'Journal']
345 345 #
346 346 # Retrieving events:
347 347 # Associated model(s) must implement the find_events class method.
348 348 # ActiveRecord models can use acts_as_activity_provider as a way to implement this class method.
349 349 #
350 350 # The following call should return all the scrum events visible by current user that occurred in the 5 last days:
351 351 # Meeting.find_events('scrums', User.current, 5.days.ago, Date.today)
352 352 # Meeting.find_events('scrums', User.current, 5.days.ago, Date.today, :project => foo) # events for project foo only
353 353 #
354 354 # Note that :view_scrums permission is required to view these events in the activity view.
355 355 def activity_provider(*args)
356 356 Redmine::Activity.register(*args)
357 357 end
358 358
359 359 # Registers a wiki formatter.
360 360 #
361 361 # Parameters:
362 362 # * +name+ - formatter name
363 363 # * +formatter+ - formatter class, which should have an instance method +to_html+
364 364 # * +helper+ - helper module, which will be included by wiki pages (optional)
365 365 # * +html_parser+ class reponsible for converting HTML to wiki text (optional)
366 366 # * +options+ - a Hash of options (optional)
367 367 # * :label - label for the formatter displayed in application settings
368 368 #
369 369 # Examples:
370 370 # wiki_format_provider(:custom_formatter, CustomFormatter, :label => "My custom formatter")
371 371 #
372 372 def wiki_format_provider(name, *args)
373 373 Redmine::WikiFormatting.register(name, *args)
374 374 end
375 375
376 376 # Returns +true+ if the plugin can be configured.
377 377 def configurable?
378 378 settings && settings.is_a?(Hash) && !settings[:partial].blank?
379 379 end
380 380
381 381 def mirror_assets
382 382 source = assets_directory
383 383 destination = public_directory
384 384 return unless File.directory?(source)
385 385
386 386 source_files = Dir[source + "/**/*"]
387 387 source_dirs = source_files.select { |d| File.directory?(d) }
388 388 source_files -= source_dirs
389 389
390 390 unless source_files.empty?
391 391 base_target_dir = File.join(destination, File.dirname(source_files.first).gsub(source, ''))
392 392 begin
393 393 FileUtils.mkdir_p(base_target_dir)
394 394 rescue Exception => e
395 395 raise "Could not create directory #{base_target_dir}: " + e.message
396 396 end
397 397 end
398 398
399 399 source_dirs.each do |dir|
400 400 # strip down these paths so we have simple, relative paths we can
401 401 # add to the destination
402 402 target_dir = File.join(destination, dir.gsub(source, ''))
403 403 begin
404 404 FileUtils.mkdir_p(target_dir)
405 405 rescue Exception => e
406 406 raise "Could not create directory #{target_dir}: " + e.message
407 407 end
408 408 end
409 409
410 410 source_files.each do |file|
411 411 begin
412 412 target = File.join(destination, file.gsub(source, ''))
413 413 unless File.exist?(target) && FileUtils.identical?(file, target)
414 414 FileUtils.cp(file, target)
415 415 end
416 416 rescue Exception => e
417 417 raise "Could not copy #{file} to #{target}: " + e.message
418 418 end
419 419 end
420 420 end
421 421
422 422 # Mirrors assets from one or all plugins to public/plugin_assets
423 423 def self.mirror_assets(name=nil)
424 424 if name.present?
425 425 find(name).mirror_assets
426 426 else
427 427 all.each do |plugin|
428 428 plugin.mirror_assets
429 429 end
430 430 end
431 431 end
432 432
433 433 # The directory containing this plugin's migrations (<tt>plugin/db/migrate</tt>)
434 434 def migration_directory
435 435 File.join(directory, 'db', 'migrate')
436 436 end
437 437
438 438 # Returns the version number of the latest migration for this plugin. Returns
439 439 # nil if this plugin has no migrations.
440 440 def latest_migration
441 441 migrations.last
442 442 end
443 443
444 444 # Returns the version numbers of all migrations for this plugin.
445 445 def migrations
446 446 migrations = Dir[migration_directory+"/*.rb"]
447 447 migrations.map { |p| File.basename(p).match(/0*(\d+)\_/)[1].to_i }.sort
448 448 end
449 449
450 450 # Migrate this plugin to the given version
451 451 def migrate(version = nil)
452 452 puts "Migrating #{id} (#{name})..."
453 453 Redmine::Plugin::Migrator.migrate_plugin(self, version)
454 454 end
455 455
456 456 # Migrates all plugins or a single plugin to a given version
457 457 # Exemples:
458 458 # Plugin.migrate
459 459 # Plugin.migrate('sample_plugin')
460 460 # Plugin.migrate('sample_plugin', 1)
461 461 #
462 462 def self.migrate(name=nil, version=nil)
463 463 if name.present?
464 464 find(name).migrate(version)
465 465 else
466 466 all.each do |plugin|
467 467 plugin.migrate
468 468 end
469 469 end
470 470 end
471 471
472 472 class Migrator < ActiveRecord::Migrator
473 473 # We need to be able to set the 'current' plugin being migrated.
474 474 cattr_accessor :current_plugin
475 475
476 476 class << self
477 477 # Runs the migrations from a plugin, up (or down) to the version given
478 478 def migrate_plugin(plugin, version)
479 479 self.current_plugin = plugin
480 480 return if current_version(plugin) == version
481 481 migrate(plugin.migration_directory, version)
482 482 end
483 483
484 484 def current_version(plugin=current_plugin)
485 485 # Delete migrations that don't match .. to_i will work because the number comes first
486 486 ::ActiveRecord::Base.connection.select_values(
487 487 "SELECT version FROM #{schema_migrations_table_name}"
488 488 ).delete_if{ |v| v.match(/-#{plugin.id}$/) == nil }.map(&:to_i).max || 0
489 489 end
490 490 end
491 491
492 492 def migrated
493 493 sm_table = self.class.schema_migrations_table_name
494 494 ::ActiveRecord::Base.connection.select_values(
495 495 "SELECT version FROM #{sm_table}"
496 496 ).delete_if{ |v| v.match(/-#{current_plugin.id}$/) == nil }.map(&:to_i).sort
497 497 end
498 498
499 499 def record_version_state_after_migrating(version)
500 500 super(version.to_s + "-" + current_plugin.id.to_s)
501 501 end
502 502 end
503 503 end
504 504 end
General Comments 0
You need to be logged in to leave comments. Login now