Index: app/helpers/issues_helper.rb
===================================================================
--- app/helpers/issues_helper.rb	(revision 1694)
+++ app/helpers/issues_helper.rb	(working copy)
@@ -86,6 +86,8 @@
     when 'attachment'
       label = l(:label_attachment)
     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
     value ||= detail.value
Index: app/controllers/issues_controller.rb
===================================================================
--- app/controllers/issues_controller.rb	(revision 1694)
+++ app/controllers/issues_controller.rb	(working copy)
@@ -234,6 +234,9 @@
         issue.start_date = params[:start_date] unless params[:start_date].blank?
         issue.due_date = params[:due_date] unless params[:due_date].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
         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)
Index: app/views/projects/settings/_members.rhtml
===================================================================
--- app/views/projects/settings/_members.rhtml	(revision 1694)
+++ app/views/projects/settings/_members.rhtml	(working copy)
@@ -9,6 +9,7 @@
 	<thead>
 	  <th><%= l(:label_user) %></th>
 	  <th><%= l(:label_role) %></th>
+          <%= Redmine::Plugin::Hook::Manager.call_hook(:project_member_list_header, {:project => @project }) %>
 	  <th style="width:15%"></th>
 	</thead>
 	<tbody>
@@ -24,6 +25,8 @@
       <% end %>
     <% end %>
     </td>
+    <%= Redmine::Plugin::Hook::Manager.call_hook(:project_member_list_column_three, {:project => @project, :member => member }) %>
+
     <td align="center">
       <%= link_to_remote l(:button_delete), { :url => {:controller => 'members', :action => 'destroy', :id => member},                                              
                                               :method => :post
Index: app/views/issues/bulk_edit.rhtml
===================================================================
--- app/views/issues/bulk_edit.rhtml	(revision 1694)
+++ app/views/issues/bulk_edit.rhtml	(working copy)
@@ -38,6 +38,7 @@
 <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>
 </p>
+<%= Redmine::Plugin::Hook::Manager.call_hook(:issue_bulk_edit, {:project => @project, :issue => @issues }) %>
 </fieldset>
 
 <fieldset><legend><%= l(:field_notes) %></legend>
Index: app/views/issues/_form.rhtml
===================================================================
--- app/views/issues/_form.rhtml	(revision 1694)
+++ app/views/issues/_form.rhtml	(working copy)
@@ -48,4 +48,6 @@
 <p><label><%=l(:label_attachment_plural)%></label><%= render :partial => 'attachments/form' %></p>
 <% end %>
 
+<%= Redmine::Plugin::Hook::Manager.call_hook(:issue_edit, {:project => @project, :issue => @issue, :form => f }) %>
+
 <%= wikitoolbar_for 'issue_description' %>
Index: app/views/issues/show.rhtml
===================================================================
--- app/views/issues/show.rhtml	(revision 1694)
+++ app/views/issues/show.rhtml	(working copy)
@@ -53,6 +53,8 @@
  <%end
 end %>
 </tr>
+<%= Redmine::Plugin::Hook::Manager.call_hook(:issue_show, {:project => @project, :issue => @issue}) %>
+       
 </table>
 <hr />
 
Index: lib/redmine/plugin.rb
===================================================================
--- lib/redmine/plugin.rb	(revision 1694)
+++ lib/redmine/plugin.rb	(working copy)
@@ -116,10 +116,108 @@
       self.instance_eval(&block)
       @project_module = nil
     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
 
     # Returns +true+ if the plugin can be configured.
     def configurable?
       settings && settings.is_a?(Hash) && !settings[:partial].blank?
     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
+          
+          # Run all the hooks for +hook_name+ passing in +context+
+          def call_hook(hook_name, context = { })
+            response = ''
+
+            if hook_registered?(hook_name)
+              @@hooks[hook_name.to_sym].each do |method|
+                response += method.call(context)
+              end
+            end
+
+            return 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
