Chris@1464: # Redmine - project management software Chris@1464: # Copyright (C) 2006-2013 Jean-Philippe Lang Chris@1464: # Chris@1464: # This program is free software; you can redistribute it and/or Chris@1464: # modify it under the terms of the GNU General Public License Chris@1464: # as published by the Free Software Foundation; either version 2 Chris@1464: # of the License, or (at your option) any later version. Chris@1464: # Chris@1464: # This program is distributed in the hope that it will be useful, Chris@1464: # but WITHOUT ANY WARRANTY; without even the implied warranty of Chris@1464: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Chris@1464: # GNU General Public License for more details. Chris@1464: # Chris@1464: # You should have received a copy of the GNU General Public License Chris@1464: # along with this program; if not, write to the Free Software Chris@1464: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Chris@1464: Chris@1464: module Redmine Chris@1464: module Hook Chris@1464: @@listener_classes = [] Chris@1464: @@listeners = nil Chris@1464: @@hook_listeners = {} Chris@1464: Chris@1464: class << self Chris@1464: # Adds a listener class. Chris@1464: # Automatically called when a class inherits from Redmine::Hook::Listener. Chris@1464: def add_listener(klass) Chris@1464: raise "Hooks must include Singleton module." unless klass.included_modules.include?(Singleton) Chris@1464: @@listener_classes << klass Chris@1464: clear_listeners_instances Chris@1464: end Chris@1464: Chris@1464: # Returns all the listerners instances. Chris@1464: def listeners Chris@1464: @@listeners ||= @@listener_classes.collect {|listener| listener.instance} Chris@1464: end Chris@1464: Chris@1464: # Returns the listeners instances for the given hook. Chris@1464: def hook_listeners(hook) Chris@1464: @@hook_listeners[hook] ||= listeners.select {|listener| listener.respond_to?(hook)} Chris@1464: end Chris@1464: Chris@1464: # Clears all the listeners. Chris@1464: def clear_listeners Chris@1464: @@listener_classes = [] Chris@1464: clear_listeners_instances Chris@1464: end Chris@1464: Chris@1464: # Clears all the listeners instances. Chris@1464: def clear_listeners_instances Chris@1464: @@listeners = nil Chris@1464: @@hook_listeners = {} Chris@1464: end Chris@1464: Chris@1464: # Calls a hook. Chris@1464: # Returns the listeners response. Chris@1464: def call_hook(hook, context={}) Chris@1464: [].tap do |response| Chris@1464: hls = hook_listeners(hook) Chris@1464: if hls.any? Chris@1464: hls.each {|listener| response << listener.send(hook, context)} Chris@1464: end Chris@1464: end Chris@1464: end Chris@1464: end Chris@1464: Chris@1464: # Base class for hook listeners. Chris@1464: class Listener Chris@1464: include Singleton Chris@1464: include Redmine::I18n Chris@1464: Chris@1464: # Registers the listener Chris@1464: def self.inherited(child) Chris@1464: Redmine::Hook.add_listener(child) Chris@1464: super Chris@1464: end Chris@1464: Chris@1464: end Chris@1464: Chris@1464: # Listener class used for views hooks. Chris@1464: # Listeners that inherit this class will include various helpers by default. Chris@1464: class ViewListener < Listener Chris@1464: include ERB::Util Chris@1464: include ActionView::Helpers::TagHelper Chris@1464: include ActionView::Helpers::FormHelper Chris@1464: include ActionView::Helpers::FormTagHelper Chris@1464: include ActionView::Helpers::FormOptionsHelper Chris@1464: include ActionView::Helpers::JavaScriptHelper Chris@1464: include ActionView::Helpers::NumberHelper Chris@1464: include ActionView::Helpers::UrlHelper Chris@1464: include ActionView::Helpers::AssetTagHelper Chris@1464: include ActionView::Helpers::TextHelper Chris@1464: include Rails.application.routes.url_helpers Chris@1464: include ApplicationHelper Chris@1464: Chris@1464: # Default to creating links using only the path. Subclasses can Chris@1464: # change this default as needed Chris@1464: def self.default_url_options Chris@1464: {:only_path => true } Chris@1464: end Chris@1464: Chris@1464: # Helper method to directly render a partial using the context: Chris@1464: # Chris@1464: # class MyHook < Redmine::Hook::ViewListener Chris@1464: # render_on :view_issues_show_details_bottom, :partial => "show_more_data" Chris@1464: # end Chris@1464: # Chris@1464: def self.render_on(hook, options={}) Chris@1464: define_method hook do |context| Chris@1464: if context[:hook_caller].respond_to?(:render) Chris@1464: context[:hook_caller].send(:render, {:locals => context}.merge(options)) Chris@1464: elsif context[:controller].is_a?(ActionController::Base) Chris@1464: context[:controller].send(:render_to_string, {:locals => context}.merge(options)) Chris@1464: else Chris@1464: raise "Cannot render #{self.name} hook from #{context[:hook_caller].class.name}" Chris@1464: end Chris@1464: end Chris@1464: end Chris@1464: Chris@1464: def controller Chris@1464: nil Chris@1464: end Chris@1464: Chris@1464: def config Chris@1464: ActionController::Base.config Chris@1464: end Chris@1464: end Chris@1464: Chris@1464: # Helper module included in ApplicationHelper and ActionController so that Chris@1464: # hooks can be called in views like this: Chris@1464: # Chris@1464: # <%= call_hook(:some_hook) %> Chris@1464: # <%= call_hook(:another_hook, :foo => 'bar') %> Chris@1464: # Chris@1464: # Or in controllers like: Chris@1464: # call_hook(:some_hook) Chris@1464: # call_hook(:another_hook, :foo => 'bar') Chris@1464: # Chris@1464: # Hooks added to views will be concatenated into a string. Hooks added to Chris@1464: # controllers will return an array of results. Chris@1464: # Chris@1464: # Several objects are automatically added to the call context: Chris@1464: # Chris@1464: # * project => current project Chris@1464: # * request => Request instance Chris@1464: # * controller => current Controller instance Chris@1464: # * hook_caller => object that called the hook Chris@1464: # Chris@1464: module Helper Chris@1464: def call_hook(hook, context={}) Chris@1464: if is_a?(ActionController::Base) Chris@1464: default_context = {:controller => self, :project => @project, :request => request, :hook_caller => self} Chris@1464: Redmine::Hook.call_hook(hook, default_context.merge(context)) Chris@1464: else Chris@1464: default_context = { :project => @project, :hook_caller => self } Chris@1464: default_context[:controller] = controller if respond_to?(:controller) Chris@1464: default_context[:request] = request if respond_to?(:request) Chris@1464: Redmine::Hook.call_hook(hook, default_context.merge(context)).join(' ').html_safe Chris@1464: end Chris@1464: end Chris@1464: end Chris@1464: end Chris@1464: end Chris@1464: Chris@1464: ApplicationHelper.send(:include, Redmine::Hook::Helper) Chris@1464: ActionController::Base.send(:include, Redmine::Hook::Helper)