Compare commits
17 Commits
master
...
plugin-hooks
| Author | SHA1 | Date | |
|---|---|---|---|
| 99ea01ec93 | |||
| e87140a719 | |||
| ca8fb4026e | |||
| e14b86453e | |||
| cb485c92ef | |||
| e7309d8c57 | |||
| d6808130dc | |||
| c5242b4386 | |||
| 6615002df2 | |||
| 08058e6a02 | |||
| 8995245a0c | |||
| 04434cd6ef | |||
| fe22ef95a8 | |||
| 355143ca18 | |||
| 5e1bcc6b24 | |||
| 00659ab8c5 | |||
| 404e6164cb |
@@ -223,7 +223,6 @@ class IssuesController < ApplicationController
|
|||||||
assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
|
assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
|
||||||
category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
|
category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
|
||||||
fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.versions.find_by_id(params[:fixed_version_id])
|
fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.versions.find_by_id(params[:fixed_version_id])
|
||||||
|
|
||||||
unsaved_issue_ids = []
|
unsaved_issue_ids = []
|
||||||
@issues.each do |issue|
|
@issues.each do |issue|
|
||||||
journal = issue.init_journal(User.current, params[:notes])
|
journal = issue.init_journal(User.current, params[:notes])
|
||||||
@@ -234,6 +233,9 @@ class IssuesController < ApplicationController
|
|||||||
issue.start_date = params[:start_date] unless params[:start_date].blank?
|
issue.start_date = params[:start_date] unless params[:start_date].blank?
|
||||||
issue.due_date = params[:due_date] unless params[:due_date].blank?
|
issue.due_date = params[:due_date] unless params[:due_date].blank?
|
||||||
issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
|
issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
|
||||||
|
|
||||||
|
Redmine::Plugin::Hook::Manager.call_hook(:issue_bulk_edit_save, {:params => params, :issue => issue })
|
||||||
|
|
||||||
# Don't save any change to the issue if the user is not authorized to apply the requested status
|
# Don't save any change to the issue if the user is not authorized to apply the requested status
|
||||||
if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
|
if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
|
||||||
# Send notification for each issue (if changed)
|
# Send notification for each issue (if changed)
|
||||||
|
|||||||
@@ -86,7 +86,9 @@ module IssuesHelper
|
|||||||
when 'attachment'
|
when 'attachment'
|
||||||
label = l(:label_attachment)
|
label = l(:label_attachment)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Redmine::Plugin::Hook::Manager.call_hook(:issues_helper_show_details, {:detail => detail, :label => label, :value => value, :old_value => old_value })
|
||||||
|
|
||||||
label ||= detail.prop_key
|
label ||= detail.prop_key
|
||||||
value ||= detail.value
|
value ||= detail.value
|
||||||
old_value ||= detail.old_value
|
old_value ||= detail.old_value
|
||||||
|
|||||||
@@ -48,4 +48,6 @@
|
|||||||
<p><label><%=l(:label_attachment_plural)%></label><%= render :partial => 'attachments/form' %></p>
|
<p><label><%=l(:label_attachment_plural)%></label><%= render :partial => 'attachments/form' %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<%= Redmine::Plugin::Hook::Manager.call_hook(:issue_edit, {:project => @project, :issue => @issue, :form => f }) %>
|
||||||
|
|
||||||
<%= wikitoolbar_for 'issue_description' %>
|
<%= wikitoolbar_for 'issue_description' %>
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
<label><%= l(:field_done_ratio) %>:
|
<label><%= l(:field_done_ratio) %>:
|
||||||
<%= select_tag 'done_ratio', options_for_select([[l(:label_no_change_option), '']] + (0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></label>
|
<%= select_tag 'done_ratio', options_for_select([[l(:label_no_change_option), '']] + (0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></label>
|
||||||
</p>
|
</p>
|
||||||
|
<%= Redmine::Plugin::Hook::Manager.call_hook(:issue_bulk_edit, {:project => @project, :issue => @issues }) %>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset><legend><%= l(:field_notes) %></legend>
|
<fieldset><legend><%= l(:field_notes) %></legend>
|
||||||
|
|||||||
@@ -53,6 +53,8 @@
|
|||||||
<%end
|
<%end
|
||||||
end %>
|
end %>
|
||||||
</tr>
|
</tr>
|
||||||
|
<%= Redmine::Plugin::Hook::Manager.call_hook(:issue_show, {:project => @project, :issue => @issue}) %>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<th><%= l(:label_user) %></th>
|
<th><%= l(:label_user) %></th>
|
||||||
<th><%= l(:label_role) %></th>
|
<th><%= l(:label_role) %></th>
|
||||||
|
<%= Redmine::Plugin::Hook::Manager.call_hook(:project_member_list_header, {:project => @project }) %>
|
||||||
<th style="width:15%"></th>
|
<th style="width:15%"></th>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -24,6 +25,8 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
|
<%= Redmine::Plugin::Hook::Manager.call_hook(:project_member_list_column_three, {:project => @project, :member => member }) %>
|
||||||
|
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<%= link_to_remote l(:button_delete), { :url => {:controller => 'members', :action => 'destroy', :id => member},
|
<%= link_to_remote l(:button_delete), { :url => {:controller => 'members', :action => 'destroy', :id => member},
|
||||||
:method => :post
|
:method => :post
|
||||||
|
|||||||
@@ -117,6 +117,18 @@ module Redmine #:nodoc:
|
|||||||
@project_module = nil
|
@project_module = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Registers a +method+ to be called when Redmine runs a hook called
|
||||||
|
# +hook_name+
|
||||||
|
#
|
||||||
|
# # Run puts whenever the issue_show hook is called
|
||||||
|
# add_hook :issue_show, Proc.new { puts 'Hello' }
|
||||||
|
#
|
||||||
|
# # Call the class method +my_method+ passing in all the context
|
||||||
|
# add_hook :issue_show, Proc.new {|context| MyPlugin.my_method(context)}
|
||||||
|
def add_hook(hook_name, method)
|
||||||
|
Redmine::Plugin::Hook::Manager.add_listener(hook_name, method)
|
||||||
|
end
|
||||||
|
|
||||||
# Registers an activity provider.
|
# Registers an activity provider.
|
||||||
#
|
#
|
||||||
# Options:
|
# Options:
|
||||||
@@ -147,5 +159,92 @@ module Redmine #:nodoc:
|
|||||||
def configurable?
|
def configurable?
|
||||||
settings && settings.is_a?(Hash) && !settings[:partial].blank?
|
settings && settings.is_a?(Hash) && !settings[:partial].blank?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Hook is used to allow plugins to hook into Redmine at specific sections
|
||||||
|
# to change it's behavior. See +Redmine::Plugin.add_hook+ for details.
|
||||||
|
class Hook
|
||||||
|
class Manager
|
||||||
|
# Hooks and the procs added
|
||||||
|
@@hooks = {
|
||||||
|
:issue_show => [],
|
||||||
|
:issue_edit => [],
|
||||||
|
:issue_bulk_edit => [],
|
||||||
|
:issue_bulk_edit_save => [],
|
||||||
|
:issue_update => [],
|
||||||
|
:project_member_list_header => [],
|
||||||
|
:project_member_list_column_three => [],
|
||||||
|
:issues_helper_show_details => []
|
||||||
|
}
|
||||||
|
|
||||||
|
cattr_reader :hooks
|
||||||
|
|
||||||
|
class << self
|
||||||
|
|
||||||
|
def valid_hook?(hook_name)
|
||||||
|
return @@hooks.has_key?(hook_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Add +method+ to +hook_name+
|
||||||
|
def add_listener(hook_name, method)
|
||||||
|
if valid_hook?(hook_name)
|
||||||
|
@@hooks[hook_name.to_sym] << method
|
||||||
|
puts "Listener added for #{hook_name.to_s}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Removes all listeners
|
||||||
|
def clear_listeners()
|
||||||
|
@@hooks.each do |hook, registrations|
|
||||||
|
@@hooks[hook] = []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Run all the hooks for +hook_name+ passing in +context+
|
||||||
|
def call_hook(hook_name, context = { })
|
||||||
|
response = ''
|
||||||
|
@@hooks[hook_name.to_sym].each do |method|
|
||||||
|
method_response = method.call(context)
|
||||||
|
response += method_response unless method_response.nil?
|
||||||
|
end
|
||||||
|
response
|
||||||
|
end
|
||||||
|
|
||||||
|
# Are hooks registered for +hook_name+
|
||||||
|
def hook_registered?(hook_name)
|
||||||
|
return @@hooks[hook_name.to_sym].size > 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Base class for Redmin Plugin hooks.
|
||||||
|
class Base
|
||||||
|
|
||||||
|
# Class level access to Rails' helper methods.
|
||||||
|
def self.help
|
||||||
|
Helper.instance
|
||||||
|
end
|
||||||
|
|
||||||
|
# Includes several Helper methods to be used in the class
|
||||||
|
class Helper # :nodoc:
|
||||||
|
include Singleton
|
||||||
|
include ERB::Util
|
||||||
|
include ActionView::Helpers::TagHelper
|
||||||
|
include ActionView::Helpers::FormHelper
|
||||||
|
include ActionView::Helpers::FormTagHelper
|
||||||
|
include ActionView::Helpers::FormOptionsHelper
|
||||||
|
include ActionView::Helpers::JavaScriptHelper
|
||||||
|
include ActionView::Helpers::PrototypeHelper
|
||||||
|
include ActionView::Helpers::NumberHelper
|
||||||
|
include ActionView::Helpers::UrlHelper
|
||||||
|
|
||||||
|
include ActionController::UrlWriter
|
||||||
|
|
||||||
|
def protect_against_forgery? # :nodoc:
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
require File.dirname(__FILE__) + '/../../../test_helper'
|
||||||
|
|
||||||
|
class Redmine::Plugin::Hook::ManagerTest < Test::Unit::TestCase
|
||||||
|
def setup
|
||||||
|
@manager = Redmine::Plugin::Hook::Manager
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
@manager.clear_listeners
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_sanity
|
||||||
|
assert true
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_hook_format
|
||||||
|
assert_kind_of Hash, @manager::hooks
|
||||||
|
@manager::hooks.each do |hook, registrations|
|
||||||
|
assert_kind_of Symbol, hook
|
||||||
|
assert_kind_of Array, registrations
|
||||||
|
assert_equal 0, registrations.length
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_valid_hook
|
||||||
|
assert @manager::valid_hook?(:issue_show)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_invalid_hook
|
||||||
|
assert_equal false, @manager::valid_hook?(:an_invalid_hook_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_clear_listeners
|
||||||
|
assert_equal 0, @manager::hooks[:issue_show].length
|
||||||
|
@manager.add_listener(:issue_show, Proc.new { } )
|
||||||
|
@manager.add_listener(:issue_show, Proc.new { } )
|
||||||
|
@manager.add_listener(:issue_show, Proc.new { } )
|
||||||
|
@manager.add_listener(:issue_show, Proc.new { } )
|
||||||
|
assert_equal 4, @manager::hooks[:issue_show].length
|
||||||
|
|
||||||
|
@manager.clear_listeners
|
||||||
|
assert_equal 0, @manager::hooks[:issue_show].length
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_add_listener
|
||||||
|
assert_equal 0, @manager::hooks[:issue_show].length
|
||||||
|
@manager.add_listener(:issue_show, Proc.new { } )
|
||||||
|
assert_equal 1, @manager::hooks[:issue_show].length
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_add_invalid_listener
|
||||||
|
hooks = @manager::hooks
|
||||||
|
@manager.add_listener(:invalid, Proc.new { } )
|
||||||
|
assert_equal hooks, @manager::hooks
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_call_hook_with_response
|
||||||
|
function = Proc.new { return 'response' }
|
||||||
|
|
||||||
|
@manager.add_listener(:issue_show, function)
|
||||||
|
|
||||||
|
assert_equal 'response', @manager.call_hook(:issue_show)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_call_multiple_hooks_with_response
|
||||||
|
function1 = Proc.new { return 'First Call.' }
|
||||||
|
function2 = Proc.new { return 'Second Call.' }
|
||||||
|
function3 = Proc.new { return 'Third Call.' }
|
||||||
|
|
||||||
|
@manager.add_listener(:issue_show, function1)
|
||||||
|
@manager.add_listener(:issue_show, function2)
|
||||||
|
@manager.add_listener(:issue_show, function3)
|
||||||
|
|
||||||
|
assert_equal 'First Call.Second Call.Third Call.', @manager.call_hook(:issue_show)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_call_hook_without_response
|
||||||
|
function = Proc.new { }
|
||||||
|
|
||||||
|
@manager.add_listener(:issue_show, function)
|
||||||
|
|
||||||
|
assert_equal '', @manager.call_hook(:issue_show)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_call_multiple_hooks_without_responses
|
||||||
|
function1 = Proc.new { }
|
||||||
|
function2 = Proc.new { }
|
||||||
|
function3 = Proc.new { }
|
||||||
|
|
||||||
|
@manager.add_listener(:issue_show, function1)
|
||||||
|
@manager.add_listener(:issue_show, function2)
|
||||||
|
@manager.add_listener(:issue_show, function3)
|
||||||
|
|
||||||
|
assert_equal '', @manager.call_hook(:issue_show)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_hook_registered_yes
|
||||||
|
@manager.add_listener(:issue_show, Proc.new { })
|
||||||
|
assert @manager.hook_registered?(:issue_show)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_hook_registered_no
|
||||||
|
assert_equal false, @manager.hook_registered?(:issue_show)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Redmine::Plugin::Hook::BaseTest < Test::Unit::TestCase
|
||||||
|
def test_sanity
|
||||||
|
assert true
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_help_should_be_a_singleton
|
||||||
|
assert Redmine::Plugin::Hook::Base::Helper.include?(Singleton)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_helper_should_include_actionview_helpers
|
||||||
|
[ActionView::Helpers::TagHelper,
|
||||||
|
ActionView::Helpers::FormHelper,
|
||||||
|
ActionView::Helpers::FormTagHelper,
|
||||||
|
ActionView::Helpers::FormOptionsHelper,
|
||||||
|
ActionView::Helpers::JavaScriptHelper,
|
||||||
|
ActionView::Helpers::PrototypeHelper,
|
||||||
|
ActionView::Helpers::NumberHelper,
|
||||||
|
ActionView::Helpers::UrlHelper].each do |helper|
|
||||||
|
assert Redmine::Plugin::Hook::Base::Helper.include?(helper), "#{helper} wasn't included."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
require File.dirname(__FILE__) + '/../../../test_helper'
|
||||||
|
|
||||||
|
class Redmine::PluginTest < Test::Unit::TestCase
|
||||||
|
def test_sanity
|
||||||
|
assert true
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_add_hook
|
||||||
|
assert_equal false, Redmine::Plugin::Hook::Manager.hook_registered?(:issue_show)
|
||||||
|
Redmine::Plugin.add_hook(:issue_show, Proc.new { })
|
||||||
|
assert Redmine::Plugin::Hook::Manager.hook_registered?(:issue_show)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_add_hook_invalid
|
||||||
|
assert_equal false, Redmine::Plugin::Hook::Manager.hook_registered?(:invalid)
|
||||||
|
Redmine::Plugin.add_hook(:invalid, Proc.new { })
|
||||||
|
assert Redmine::Plugin::Hook::Manager.hook_registered?(:invalid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
Reference in New Issue
Block a user