项目

通用

个人资料

操作

Redmine 插件钩子

Redmine 支持钩子的概念。这是一个 API,允许外部代码以干净的方式扩展 Redmine 的核心功能。钩子允许插件作者注册回调函数,当 Redmine 代码到达代码中的特定点时,依次执行这些函数。

有一份有效钩子的列表。但找到它们最好的方法是查看代码,找到您想扩展的位置,并在附近的钩子调用附近搜索。

扩展或替换 Redmine 代码的其他方法包括

基础知识

如上所述,当调用钩子时,它会执行先前注册的回调函数。这些函数必须接受一个精确的参数:一个提供某些上下文的哈希。此上下文哈希将始终包含在回调函数中执行某些有用操作所需的数据。对于控制器或视图钩子(如下所述),它至少包含以下信息
  • :controller => 当前控制器实例的引用
  • :project => 当前项目(如果由控制器设置),
  • :request => 当前 请求对象,包含有关当前 Web 请求的许多信息

此外,哈希还将包含与相应钩子相关的某些数据。这些数据直接传递给在 Redmine 代码中找到的 call_hook 调用。

模型钩子不会包含默认数据,因为它不适用。这些钩子仅包含 call_hook 调用中传递的数据。

钩子类型

基本上,目前有三种类型的钩子
  • 视图钩子
  • 控制器钩子
  • 模型钩子

虽然这两种类型使用完全相同的 API,但它们有不同的基本用途。

视图钩子

视图钩子在渲染视图的 HTML 代码时执行。这允许插件作者将一些自定义 HTML 代码插入视图的一些合理位置。存在一个简写来渲染单个部分。以下是一个示例。

控制器钩子

控制器钩子的数量少于视图钩子。通常,使用额外的过滤器或扩展模型类就足够了,因为控制器动作应该(且大多数情况下)非常短,不会做太多。然而,也有一些更长的动作使用钩子。为了正确使用这些钩子,必须理解上下文字典中的对象只是被引用。因此,如果你就地更改一个对象,更改将在实际控制器(以及稍后视图)中可用。考虑以下简化示例:

假设以下函数已注册到do_something钩子。下面将说明如何实现这一点。

def do_something(context={ })
  context[:issue].subject = "Nothing to fix" 
end

现在考虑一个具有以下代码的控制器动作:

issue = Issue.find(1)
# issue.subject is "Fix me" 
call_hook(:do_something, :issue => issue)
# issue.subject is now "Nothing to fix" 

如你所见,钩子函数可以就地更改issue对象。然而,无法完全替换对象,因为这会破坏对象引用。

模型钩子

在Redmine中,模型钩子非常少。大多数模型代码的扩展可以通过添加新方法或通过创造性地应用alias_method_chain模式来封装现有方法来实现。钩子可以使用与控制器钩子相同的方式使用。

将函数注册到钩子

视图钩子

以下示例将一个函数钩入到钩子view_issues_form_details_bottom中。这可以用于在问题编辑表单中添加一些额外的字段。

  1. 在你的插件(假设名为my_plugin)中,在lib/my_plugin/hooks.rb中创建以下类。你可以在同一个类中注册多个钩子。
    module MyPlugin
      class Hooks < Redmine::Hook::ViewListener
        # This just renders the partial in
        # app/views/hooks/my_plugin/_view_issues_form_details_bottom.rhtml
        # The contents of the context hash is made available as local variables to the partial.
        #
        # Additional context fields
        #   :issue  => the issue this is edited
        #   :f      => the form object to create additional fields
        render_on :view_issues_form_details_bottom,
                  :partial => 'hooks/my_plugin/view_issues_form_details_bottom'
      end
    end
    
    以下类与上述类完全相同,但使用方法而不是较短的render_on辅助方法。方法名决定了它注册到哪个回调。
    module MyPlugin
      class Hooks < Redmine::Hook::ViewListener
        def view_issues_form_details_bottom(context={ })
          # the controller parameter is part of the current params object
          # This will render the partial into a string and return it.
          context[:controller].send(:render_to_string, {
            :partial => "hooks/my_plugin/view_issues_form_details_bottom",
            :locals => context
          })
    
          # Instead of the above statement, you could return any string generated
          # by your code. That string will be included into the view
        end
      end
    end
    
  2. 在你的init.rb中,确保包含包含钩子的文件。它应该看起来像这样:
    require 'redmine'
    
    # This is the important line.
    # It requires the file in lib/my_plugin/hooks.rb
    require_dependency 'my_plugin/hooks'
    
    Redmine::Plugin.register :my_plugin do
      [...]
    end
    

控制器和模型钩子

你可以像视图钩子一样将方法注册到控制器和模型钩子。始终记得在你的init.rb中包含钩子类。下面是一个示例:

module MyPlugin
  class Hooks < Redmine::Hook::ViewListener
    def controller_issues_bulk_edit_before_save(context={ })
      # set my_attribute on the issue to a default value if not set explictly
      context[:issue].my_attribute ||= "default" 
    end
  end
end

附加示例

一些额外的实际示例可以在以下位置找到:

视图钩子

辅助钩子

控制器钩子

TODO

  • 如何向现有控制器添加过滤器?
  • 如何使用alias_method_chain覆盖方法?
    • 实例方法
    • 类方法
    • 初始化
    • 模块

Joshua Smith更新,超过10年前· 14次修订