Project

General

Profile

Patch #28996 ยป update_redmine_plugin.patch

Sho HASHIMOTO, 2018-06-08 16:51

View differences:

lib/redmine/plugin.rb
16 16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 17

  
18 18
module Redmine
19

  
19
  # Exception on specified plugin was not found.
20
  #
21
  # @since 0.8.0
20 22
  class PluginNotFound < StandardError; end
23

  
24
  # Exception on required incompatible plugin with redmine.
25
  #
26
  # @since 0.8.0
21 27
  class PluginRequirementError < StandardError; end
22 28

  
23 29
  # Base class for Redmine plugins.
......
37 43
  # It must be a hash with the following keys:
38 44
  # * <tt>:default</tt>: default value for the plugin settings
39 45
  # * <tt>:partial</tt>: path of the configuration partial view, relative to the plugin <tt>app/views</tt> directory
40
  # Example:
46
  # @example
41 47
  #   settings :default => {'foo'=>'bar'}, :partial => 'settings/settings'
42 48
  # In this example, the settings partial will be found here in the plugin directory: <tt>app/views/settings/_settings.rhtml</tt>.
43 49
  #
44 50
  # When rendered, the plugin settings value is available as the local variable +settings+
51
  #
52
  # @since 0.6.0
53
  # @see http://www.redmine.org/projects/redmine/wiki/Plugin_Tutorial
45 54
  class Plugin
55
    # @attr [String] self.directory redmine plugin directory.
56
    # @since 2.0.0
46 57
    cattr_accessor :directory
47 58
    self.directory = File.join(Rails.root, 'plugins')
48 59

  
60
    # @attr [String] self.public_directory Redmine pulugin's public directory.
61
    # @since 1.4.0
49 62
    cattr_accessor :public_directory
50 63
    self.public_directory = File.join(Rails.root, 'public', 'plugin_assets')
51 64

  
......
53 66
    @used_partials = {}
54 67

  
55 68
    class << self
69
      # @since 0.6.0
56 70
      attr_reader :registered_plugins
57 71
      private :new
58 72

  
73
      # @since 0.6.0
59 74
      def def_field(*names)
60 75
        class_eval do
61 76
          names.each do |name|
......
67 82
      end
68 83
    end
69 84
    def_field :name, :description, :url, :author, :author_url, :version, :settings, :directory
85
    # @since 0.8.0
70 86
    attr_reader :id
71 87

  
72 88
    # Plugin constructor
89
    # @param [String, Symbol] id plugin id.
90
    # @yield Evaluates block with no block parameter. In block, self is new Redmine::Plugin object with specified id. Redmine::Plugin instance methods can call without receiver to new plugin object.
91
    # @example
92
    #   Redmine::Plugin.register :example do
93
    #     name 'Example plugin'
94
    #     author 'John Smith'
95
    #     description 'This is an example plugin for Redmine'
96
    #     version '0.0.1'
97
    #     requires_redmine version_or_higher: '3.0.0'
98
    #   end
99
    #
100
    # @since 0.6.0
101
    # @see Redmine::Plugin#requires_redmine
73 102
    def self.register(id, &block)
74 103
      p = new(id)
75 104
      p.instance_eval(&block)
......
114 143
    end
115 144

  
116 145
    # Returns an array of all registered plugins
146
    #
147
    # @return [[Redmine::Plugin]] an array of plugins
148
    # @since 0.8.0
117 149
    def self.all
118 150
      registered_plugins.values.sort
119 151
    end
120 152

  
121 153
    # Finds a plugin by its id
122 154
    # Returns a PluginNotFound exception if the plugin doesn't exist
155
    #
156
    # @param [String, Symbol] id name of the plugin
157
    # @return [Redmine::Plugin] Redmine plugin
158
    # @since 0.8.0
123 159
    def self.find(id)
124 160
      registered_plugins[id.to_sym] || raise(PluginNotFound)
125 161
    end
126 162

  
127 163
    # Clears the registered plugins hash
128 164
    # It doesn't unload installed plugins
165
    #
166
    # @since 0.8.0
129 167
    def self.clear
130 168
      @registered_plugins = {}
131 169
    end
132 170

  
133 171
    # Removes a plugin from the registered plugins
134 172
    # It doesn't unload the plugin
173
    #
174
    # @param [String, Symbol] id name of the plugin
175
    # @since 2.6.0
135 176
    def self.unregister(id)
136 177
      @registered_plugins.delete(id)
137 178
    end
138 179

  
139 180
    # Checks if a plugin is installed
140 181
    #
141
    # @param [String] id name of the plugin
182
    # @param [String, Symbol] id name of the plugin
183
    # @since 1.0.3
142 184
    def self.installed?(id)
143 185
      registered_plugins[id.to_sym].present?
144 186
    end
145 187

  
188
    # @since 2.0.0
146 189
    def self.load
147 190
      Dir.glob(File.join(self.directory, '*')).sort.each do |directory|
148 191
        if File.directory?(directory)
......
159 202
      end
160 203
    end
161 204

  
205
    # @since 0.8.0
162 206
    def initialize(id)
163 207
      @id = id.to_sym
164 208
    end
165 209

  
210
    # @since 2.0.0
166 211
    def public_directory
167 212
      File.join(self.class.public_directory, id.to_s)
168 213
    end
169 214

  
215
    # @since 2.3.0
170 216
    def to_param
171 217
      id
172 218
    end
173 219

  
220
    # plugin assets directory
221
    #
222
    # @since 2.0.0
174 223
    def assets_directory
175 224
      File.join(directory, 'assets')
176 225
    end
177 226

  
227
    # @since 0.8.0
178 228
    def <=>(plugin)
179 229
      self.id.to_s <=> plugin.id.to_s
180 230
    end
......
182 232
    # Sets a requirement on Redmine version
183 233
    # Raises a PluginRequirementError exception if the requirement is not met
184 234
    #
185
    # Examples
235
    # @example
186 236
    #   # Requires Redmine 0.7.3 or higher
187 237
    #   requires_redmine :version_or_higher => '0.7.3'
188 238
    #   requires_redmine '0.7.3'
......
198 248
    #   # Requires a Redmine version within a range
199 249
    #   requires_redmine :version => '0.7.3'..'0.9.1'     # >= 0.7.3 and <= 0.9.1
200 250
    #   requires_redmine :version => '0.7'..'0.9'         # >= 0.7.x and <= 0.9.x
251
    #
252
    # @since 0.8.0
201 253
    def requires_redmine(arg)
202 254
      arg = { :version_or_higher => arg } unless arg.is_a?(Hash)
203 255
      arg.assert_valid_keys(:version, :version_or_higher)
......
228 280
      true
229 281
    end
230 282

  
283
    # @since 2.2.0
231 284
    def compare_versions(requirement, current)
232 285
      requirement = requirement.split('.').collect(&:to_i)
233 286
      requirement <=> current.slice(0, requirement.size)
......
237 290
    # Sets a requirement on a Redmine plugin version
238 291
    # Raises a PluginRequirementError exception if the requirement is not met
239 292
    #
240
    # Examples
293
    # @example
241 294
    #   # Requires a plugin named :foo version 0.7.3 or higher
242 295
    #   requires_redmine_plugin :foo, :version_or_higher => '0.7.3'
243 296
    #   requires_redmine_plugin :foo, '0.7.3'
......
245 298
    #   # Requires a specific version of a Redmine plugin
246 299
    #   requires_redmine_plugin :foo, :version => '0.7.3'              # 0.7.3 only
247 300
    #   requires_redmine_plugin :foo, :version => ['0.7.3', '0.8.0']   # 0.7.3 or 0.8.0
301
    #
302
    # @since 0.9.0
248 303
    def requires_redmine_plugin(plugin_name, arg)
249 304
      arg = { :version_or_higher => arg } unless arg.is_a?(Hash)
250 305
      arg.assert_valid_keys(:version, :version_or_higher)
......
276 331
    #
277 332
    # +name+ parameter can be: :top_menu, :account_menu, :application_menu or :project_menu
278 333
    #
334
    # @since 0.6.0
279 335
    def menu(menu, item, url, options={})
280 336
      Redmine::MenuManager.map(menu).push(item, url, options)
281 337
    end
338
    # @since 0.8.0
282 339
    alias :add_menu_item :menu
283 340

  
284 341
    # Removes +item+ from the given +menu+.
342
    #
343
    # @since 0.8.0
285 344
    def delete_menu_item(menu, item)
286 345
      Redmine::MenuManager.map(menu).delete(item)
287 346
    end
......
297 356
    # * :require => can be set to one of the following values to restrict users the permission can be given to: :loggedin, :member
298 357
    # * :read => set it to true so that the permission is still granted on closed projects
299 358
    #
300
    # Examples
359
    # @example
301 360
    #   # A permission that is implicitly given to any user
302 361
    #   # This permission won't appear on the Roles & Permissions setup screen
303 362
    #   permission :say_hello, { :example => :say_hello }, :public => true, :read => true
......
310 369
    #
311 370
    #   # A permission that can be given to project members only
312 371
    #   permission :say_hello, { :example => :say_hello }, :require => :member
372
    #
373
    # @since 0.6.0
313 374
    def permission(name, actions, options = {})
314 375
      if @project_module
315 376
        Redmine::AccessControl.map {|map| map.project_module(@project_module) {|map|map.permission(name, actions, options)}}
......
325 386
    #     permission :view_contacts, { :contacts => [:list, :show] }, :public => true
326 387
    #     permission :destroy_contacts, { :contacts => :destroy }
327 388
    #   end
389
    #
390
    # @since 0.6.0
328 391
    def project_module(name, &block)
329 392
      @project_module = name
330 393
      self.instance_eval(&block)
......
339 402
    #
340 403
    # A model can provide several activity event types.
341 404
    #
342
    # Examples:
405
    # @example
343 406
    #   register :news
344 407
    #   register :scrums, :class_name => 'Meeting'
345 408
    #   register :issues, :class_name => ['Issue', 'Journal']
......
353 416
    #   Meeting.find_events('scrums', User.current, 5.days.ago, Date.today, :project => foo) # events for project foo only
354 417
    #
355 418
    # Note that :view_scrums permission is required to view these events in the activity view.
419
    #
420
    # @since 0.8.0
356 421
    def activity_provider(*args)
357 422
      Redmine::Activity.register(*args)
358 423
    end
......
367 432
    # * +options+ - a Hash of options (optional)
368 433
    #   * :label - label for the formatter displayed in application settings
369 434
    #
370
    # Examples:
435
    # @example
371 436
    #   wiki_format_provider(:custom_formatter, CustomFormatter, :label => "My custom formatter") 
372 437
    #
438
    # @since 0.8.0
373 439
    def wiki_format_provider(name, *args)
374 440
      Redmine::WikiFormatting.register(name, *args)
375 441
    end
376 442

  
377 443
    # Returns +true+ if the plugin can be configured.
444
    #
445
    # @since 0.6.0
378 446
    def configurable?
379 447
      settings && settings.is_a?(Hash) && !settings[:partial].blank?
380 448
    end
381 449

  
450
    # @since 2.0.0
382 451
    def mirror_assets
383 452
      source = assets_directory
384 453
      destination = public_directory
......
421 490
    end
422 491

  
423 492
    # Mirrors assets from one or all plugins to public/plugin_assets
493
    #
494
    # @since 2.0.0
424 495
    def self.mirror_assets(name=nil)
425 496
      if name.present?
426 497
        find(name).mirror_assets
......
432 503
    end
433 504

  
434 505
    # The directory containing this plugin's migrations (<tt>plugin/db/migrate</tt>)
506
    #
507
    # @since 2.0.0
435 508
    def migration_directory
436 509
      File.join(directory, 'db', 'migrate')
437 510
    end
438 511

  
439 512
    # Returns the version number of the latest migration for this plugin. Returns
440 513
    # nil if this plugin has no migrations.
514
    #
515
    # @since 2.0.0
441 516
    def latest_migration
442 517
      migrations.last
443 518
    end
444 519

  
445 520
    # Returns the version numbers of all migrations for this plugin.
521
    #
522
    # @since 2.0.0
446 523
    def migrations
447 524
      migrations = Dir[migration_directory+"/*.rb"]
448 525
      migrations.map { |p| File.basename(p).match(/0*(\d+)\_/)[1].to_i }.sort
449 526
    end
450 527

  
451 528
    # Migrate this plugin to the given version
529
    #
530
    # @since 2.0.0
452 531
    def migrate(version = nil)
453 532
      puts "Migrating #{id} (#{name})..."
454 533
      Redmine::Plugin::Migrator.migrate_plugin(self, version)
......
460 539
    #   Plugin.migrate('sample_plugin')
461 540
    #   Plugin.migrate('sample_plugin', 1)
462 541
    #
542
    # @since 2.0.0
463 543
    def self.migrate(name=nil, version=nil)
464 544
      if name.present?
465 545
        find(name).migrate(version)
......
470 550
      end
471 551
    end
472 552

  
553
    # @since 2.0.0
473 554
    class Migrator < ActiveRecord::Migrator
474 555
      # We need to be able to set the 'current' plugin being migrated.
556
      #
557
      # @since 2.0.0
475 558
      cattr_accessor :current_plugin
476 559

  
477 560
      class << self
478 561
        # Runs the migrations from a plugin, up (or down) to the version given
562
        #
563
        # @since 2.0.0
479 564
        def migrate_plugin(plugin, version)
480 565
          self.current_plugin = plugin
481 566
          return if current_version(plugin) == version
482 567
          migrate(plugin.migration_directory, version)
483 568
        end
484 569

  
570
        # @since 2.0.0
485 571
        def current_version(plugin=current_plugin)
486 572
          # Delete migrations that don't match .. to_i will work because the number comes first
487 573
          sm_table = ::ActiveRecord::SchemaMigration.table_name
......
491 577
        end
492 578
      end
493 579

  
580
      # @since 2.0.0
494 581
      def migrated
495 582
        sm_table = ::ActiveRecord::SchemaMigration.table_name
496 583
        ::ActiveRecord::Base.connection.select_values(
......
498 585
        ).delete_if{ |v| v.match(/-#{current_plugin.id}$/) == nil }.map(&:to_i).sort
499 586
      end
500 587

  
588
      # @since 2.0.0
501 589
      def record_version_state_after_migrating(version)
502 590
        super(version.to_s + "-" + current_plugin.id.to_s)
503 591
      end
    (1-1/1)