Patch #44660
openMore view hooks and registries for plugins
Description
More view hooks and registries for plugins¶
Redmine's view hooks let a plugin add to a page without touching core. Where a hook exists, this works well. But the hooks are uneven: some pages have hooks in every part, others have none, and for tabs, action menus, journal details and list rows there is no hook at all, only helpers and views to override.
Where a hook is missing, a plugin has to replace a whole view or partial (and only one plugin can do that), prepend to a core helper (and several plugins then chain over the same helper), or find core's markup in the browser and move its element there with JavaScript. All of these break when core changes, and every new Redmine version means checking them again. In a set of 28 plugins we maintain, three core views are replaced, IssuesHelper#show_detail and ProjectsHelper#project_settings_tabs are each overridden by six plugins, and about 80 places in JavaScript depend on core's markup.
Proposal¶
Only additions: nothing existing changes, and a plugin that does not use them sees no difference. The attached patch (against trunk) covers seven items. Items 2 and 3 give the most for the smallest change, if you prefer to take them first.
- Hooks for the regions of every page.
- In the layout:
view_layouts_base_header_topand_header_bottom,view_layouts_base_sidebar_top(the sidebar shows when only this hook has content),view_layouts_base_content_top, andview_layouts_base_footer_topand_footer_bottom. These sit next to the existingview_layouts_base_sidebarandview_layouts_base_content. - In the title row:
view_layouts_base_title_after, from thetitlehelper. - In the contextual area of 16 more pages:
view_<controller>_<action>_contextual, the pattern core already uses for versions, repositories and My page. It comes after core's own links and before the "..." actions menu. - Inside each of the 8 "..." actions menus, after core's items:
view_<controller>_<action>_actions_menu(view_issues_index_actions_menu,view_issues_show_actions_menu,view_projects_show_actions_menu,view_timelog_index_actions_menu,view_users_index_actions_menu,view_versions_index_actions_menu,view_wiki_show_actions_menuandview_repositories_navigation_actions_menu). A menu with only hook content is shown too. - On the issue page:
view_issues_show_top,view_issues_show_details_endandview_issues_show_bottom.
- In the layout:
- A registry for tabs on project settings, administration settings, the user and group edit pages, and an issue's history (
Redmine::Tabs). A plugin registers a tab instead of overriding the helper that builds the list. It gives the tab's partial, label, permission and module (project settings), an:ifcondition, and its place (:after/:before). Project settings keep core's filtering by permission and module; a tab without a permission needs:edit_project, as the project tab does. - A registry for journal details (
Redmine::JournalDetails). A plugin registers a renderer for its ownproperty(optionally perprop_key).show_detailasks the registry first, so the plugin's line is rendered as HTML, as text and in mail; everything else is unchanged. - Action menus as menus.
issue_actions(the "..." menu of an issue),issue_context_menu,project_actions,user_actionsandversion_actionsareRedmine::MenuManagermenus that plugins push items to, as they already can for the top, application, project and admin menus. For these menus, the:ifand:captionProcs and a Proc url receive the object (the issue, the selected issues, the project, the user or the version), and a Hash url gets the object as its:param. - Form hooks at the top, the bottom and after each section:
- issues: after the subject, after the description, after the attributes, and before and after the custom fields
- projects, versions, groups and trackers: top and bottom
- users: top, after the authentication, after the mail notifications, and bottom
- List hooks in the issue, project and user lists:
view_<list>_list_row_css_classesadds classes to a row; they are escaped.view_<list>_list_row_afteradds rows after a row, with the colspan given.
- Hooks as a contract.
doc/HOOKSlists every hook with its context keys, the file it is called in and the version it appeared in.test/unit/lib/redmine/hook_list_test.rbfails when a hook is called but not listed, or listed but no longer called.test/integration/lib/redmine/view_hooks_test.rbchecks that each new hook is called on its page, that the contextual hook comes before the actions menu and the menu hook inside it, and that hook output leaves each page's HTML as valid as it was (Nokogiri HTML5, same errors with and without the hooks). A hook is removed only after a deprecation notice. The HTML check found one place that broke: the user edit page put the avatar at the last space of the title's HTML, which withview_layouts_base_title_afteris inside the hook's output; it now looks for the last space before</h2>.
Where the registries live¶
In core, like the registries core already has (menus, permissions, field formats, activity providers, search, wiki macros): a plugin declares in init.rb, the registry is filled when plugins load and lives in memory only.
Redmine::Plugin.register :a_plugin do
project_settings_tab :a_tab, :label => :label_a_tab,
:partial => 'a_plugin/settings', :permission => :manage_a_plugin, :after => :members
issue_history_tab :a_tab, :remote => true, :onclick => "getRemoteTab('a_tab', ...)"
journal_detail :property => 'a_plugin', :renderer => APlugin::JournalDetail
menu :issue_actions, :a_action, {:controller => 'a_plugin', :action => 'run'},
:caption => :label_a_action, :icon => 'add', :if => Proc.new {|issue| issue.closed?}
end
A renderer responds to call(detail, view, no_html, options) and returns the line.
Tested with the existing test suite and the new tests against trunk at the commit named in the patch.
Green marks new hook placements:

![]()
Files
No data to display