##// END OF EJS Templates
Menus items:...
Jean-Philippe Lang -
r1158:792b7f30e32e
parent child
Show More
@@ -1,25 +1,25
1 # Redmine sample plugin
1 # Redmine sample plugin
2 require 'redmine'
2 require 'redmine'
3
3
4 RAILS_DEFAULT_LOGGER.info 'Starting Example plugin for RedMine'
4 RAILS_DEFAULT_LOGGER.info 'Starting Example plugin for RedMine'
5
5
6 Redmine::Plugin.register :sample_plugin do
6 Redmine::Plugin.register :sample_plugin do
7 name 'Example plugin'
7 name 'Example plugin'
8 author 'Author name'
8 author 'Author name'
9 description 'This is a sample plugin for Redmine'
9 description 'This is a sample plugin for Redmine'
10 version '0.0.1'
10 version '0.0.1'
11 settings :default => {'sample_setting' => 'value', 'foo'=>'bar'}, :partial => 'settings/settings'
11 settings :default => {'sample_setting' => 'value', 'foo'=>'bar'}, :partial => 'settings/settings'
12
12
13 # This plugin adds a project module
13 # This plugin adds a project module
14 # It can be enabled/disabled at project level (Project settings -> Modules)
14 # It can be enabled/disabled at project level (Project settings -> Modules)
15 project_module :example_module do
15 project_module :example_module do
16 # A public action
16 # A public action
17 permission :example_say_hello, {:example => [:say_hello]}, :public => true
17 permission :example_say_hello, {:example => [:say_hello]}, :public => true
18 # This permission has to be explicitly given
18 # This permission has to be explicitly given
19 # It will be listed on the permissions screen
19 # It will be listed on the permissions screen
20 permission :example_say_goodbye, {:example => [:say_goodbye]}
20 permission :example_say_goodbye, {:example => [:say_goodbye]}
21 end
21 end
22
22
23 # A new item is added to the project menu
23 # A new item is added to the project menu
24 menu :project_menu, :label_plugin_example, :controller => 'example', :action => 'say_hello'
24 menu :project_menu, :sample_plugin, { :controller => 'example', :action => 'say_hello' }, :caption => 'Sample'
25 end
25 end
@@ -1,139 +1,144
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 module Redmine
18 module Redmine
19 module MenuManager
19 module MenuManager
20 module MenuController
20 module MenuController
21 def self.included(base)
21 def self.included(base)
22 base.extend(ClassMethods)
22 base.extend(ClassMethods)
23 end
23 end
24
24
25 module ClassMethods
25 module ClassMethods
26 @@menu_items = Hash.new {|hash, key| hash[key] = {:default => key, :actions => {}}}
26 @@menu_items = Hash.new {|hash, key| hash[key] = {:default => key, :actions => {}}}
27 mattr_accessor :menu_items
27 mattr_accessor :menu_items
28
28
29 # Set the menu item name for a controller or specific actions
29 # Set the menu item name for a controller or specific actions
30 # Examples:
30 # Examples:
31 # * menu_item :tickets # => sets the menu name to :tickets for the whole controller
31 # * menu_item :tickets # => sets the menu name to :tickets for the whole controller
32 # * menu_item :tickets, :only => :list # => sets the menu name to :tickets for the 'list' action only
32 # * menu_item :tickets, :only => :list # => sets the menu name to :tickets for the 'list' action only
33 # * menu_item :tickets, :only => [:list, :show] # => sets the menu name to :tickets for 2 actions only
33 # * menu_item :tickets, :only => [:list, :show] # => sets the menu name to :tickets for 2 actions only
34 #
34 #
35 # The default menu item name for a controller is controller_name by default
35 # The default menu item name for a controller is controller_name by default
36 # Eg. the default menu item name for ProjectsController is :projects
36 # Eg. the default menu item name for ProjectsController is :projects
37 def menu_item(id, options = {})
37 def menu_item(id, options = {})
38 if actions = options[:only]
38 if actions = options[:only]
39 actions = [] << actions unless actions.is_a?(Array)
39 actions = [] << actions unless actions.is_a?(Array)
40 actions.each {|a| menu_items[controller_name.to_sym][:actions][a.to_sym] = id}
40 actions.each {|a| menu_items[controller_name.to_sym][:actions][a.to_sym] = id}
41 else
41 else
42 menu_items[controller_name.to_sym][:default] = id
42 menu_items[controller_name.to_sym][:default] = id
43 end
43 end
44 end
44 end
45 end
45 end
46
46
47 def menu_items
47 def menu_items
48 self.class.menu_items
48 self.class.menu_items
49 end
49 end
50
50
51 # Returns the menu item name according to the current action
51 # Returns the menu item name according to the current action
52 def current_menu_item
52 def current_menu_item
53 menu_items[controller_name.to_sym][:actions][action_name.to_sym] ||
53 menu_items[controller_name.to_sym][:actions][action_name.to_sym] ||
54 menu_items[controller_name.to_sym][:default]
54 menu_items[controller_name.to_sym][:default]
55 end
55 end
56 end
56 end
57
57
58 module MenuHelper
58 module MenuHelper
59 # Returns the current menu item name
59 # Returns the current menu item name
60 def current_menu_item
60 def current_menu_item
61 @controller.current_menu_item
61 @controller.current_menu_item
62 end
62 end
63
63
64 # Renders the application main menu
64 # Renders the application main menu
65 def render_main_menu(project)
65 def render_main_menu(project)
66 render_menu((project && !project.new_record?) ? :project_menu : :application_menu, project)
66 render_menu((project && !project.new_record?) ? :project_menu : :application_menu, project)
67 end
67 end
68
68
69 def render_menu(menu, project=nil)
69 def render_menu(menu, project=nil)
70 links = []
70 links = []
71 Redmine::MenuManager.allowed_items(menu, User.current, project).each do |item|
71 Redmine::MenuManager.allowed_items(menu, User.current, project).each do |item|
72 unless item.condition && !item.condition.call(project)
72 unless item.condition && !item.condition.call(project)
73 url = case item.url
73 url = case item.url
74 when Hash
74 when Hash
75 project.nil? ? item.url : {item.param => project}.merge(item.url)
75 project.nil? ? item.url : {item.param => project}.merge(item.url)
76 when Symbol
76 when Symbol
77 send(item.url)
77 send(item.url)
78 else
78 else
79 item.url
79 item.url
80 end
80 end
81 #url = (project && item.url.is_a?(Hash)) ? {item.param => project}.merge(item.url) : (item.url.is_a?(Symbol) ? send(item.url) : item.url)
81 #url = (project && item.url.is_a?(Hash)) ? {item.param => project}.merge(item.url) : (item.url.is_a?(Symbol) ? send(item.url) : item.url)
82 links << content_tag('li',
82 links << content_tag('li',
83 link_to(l(item.caption), url, (current_menu_item == item.name ? item.html_options.merge(:class => 'selected') : item.html_options)))
83 link_to(l(item.caption), url, (current_menu_item == item.name ? item.html_options.merge(:class => 'selected') : item.html_options)))
84 end
84 end
85 end
85 end
86 links.empty? ? nil : content_tag('ul', links.join("\n"))
86 links.empty? ? nil : content_tag('ul', links.join("\n"))
87 end
87 end
88 end
88 end
89
89
90 class << self
90 class << self
91 def map(menu_name)
91 def map(menu_name)
92 mapper = Mapper.new
92 mapper = Mapper.new
93 yield mapper
93 yield mapper
94 @items ||= {}
94 @items ||= {}
95 @items[menu_name.to_sym] ||= []
95 @items[menu_name.to_sym] ||= []
96 @items[menu_name.to_sym] += mapper.items
96 @items[menu_name.to_sym] += mapper.items
97 end
97 end
98
98
99 def items(menu_name)
99 def items(menu_name)
100 @items[menu_name.to_sym] || []
100 @items[menu_name.to_sym] || []
101 end
101 end
102
102
103 def allowed_items(menu_name, user, project)
103 def allowed_items(menu_name, user, project)
104 project ? items(menu_name).select {|item| user && user.allowed_to?(item.url, project)} : items(menu_name)
104 project ? items(menu_name).select {|item| user && user.allowed_to?(item.url, project)} : items(menu_name)
105 end
105 end
106 end
106 end
107
107
108 class Mapper
108 class Mapper
109 # Adds an item at the end of the menu. Available options:
109 # Adds an item at the end of the menu. Available options:
110 # * param: the parameter name that is used for the project id (default is :id)
110 # * param: the parameter name that is used for the project id (default is :id)
111 # * if: a proc that is called before rendering the item, the item is displayed only if it returns true
111 # * if: a proc that is called before rendering the item, the item is displayed only if it returns true
112 # * caption: the localized string key that is used as the item label
112 # * caption: the localized string key that is used as the item label
113 # * html_options: a hash of html options that are passed to link_to
113 # * html_options: a hash of html options that are passed to link_to
114 def push(name, url, options={})
114 def push(name, url, options={})
115 items << MenuItem.new(name, url, options)
115 items << MenuItem.new(name, url, options)
116 end
116 end
117
117
118 def items
118 def items
119 @items ||= []
119 @items ||= []
120 end
120 end
121 end
121 end
122
122
123 class MenuItem
123 class MenuItem
124 include GLoc
124 include GLoc
125 attr_reader :name, :url, :param, :condition, :caption, :html_options
125 attr_reader :name, :url, :param, :condition, :html_options
126
126
127 def initialize(name, url, options)
127 def initialize(name, url, options)
128 raise "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call)
128 raise "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call)
129 raise "Invalid option :html for menu item '#{name}'" if options[:html] && !options[:html].is_a?(Hash)
129 raise "Invalid option :html for menu item '#{name}'" if options[:html] && !options[:html].is_a?(Hash)
130 @name = name
130 @name = name
131 @url = url
131 @url = url
132 @condition = options[:if]
132 @condition = options[:if]
133 @param = options[:param] || :id
133 @param = options[:param] || :id
134 @caption = options[:caption] || (l_has_string?("label_#{name}".to_sym) ? "label_#{name}".to_sym : name.to_s.humanize)
134 @caption_key = options[:caption]
135 @html_options = options[:html] || {}
135 @html_options = options[:html] || {}
136 end
136 end
137
138 def caption
139 # check if localized string exists on first render (after GLoc strings are loaded)
140 @caption ||= (@caption_key || (l_has_string?("label_#{@name}".to_sym) ? "label_#{@name}".to_sym : @name.to_s.humanize))
141 end
137 end
142 end
138 end
143 end
139 end
144 end
@@ -1,124 +1,125
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 module Redmine #:nodoc:
18 module Redmine #:nodoc:
19
19
20 # Base class for Redmine plugins.
20 # Base class for Redmine plugins.
21 # Plugins are registered using the <tt>register</tt> class method that acts as the public constructor.
21 # Plugins are registered using the <tt>register</tt> class method that acts as the public constructor.
22 #
22 #
23 # Redmine::Plugin.register :example do
23 # Redmine::Plugin.register :example do
24 # name 'Example plugin'
24 # name 'Example plugin'
25 # author 'John Smith'
25 # author 'John Smith'
26 # description 'This is an example plugin for Redmine'
26 # description 'This is an example plugin for Redmine'
27 # version '0.0.1'
27 # version '0.0.1'
28 # settings :default => {'foo'=>'bar'}, :partial => 'settings/settings'
28 # settings :default => {'foo'=>'bar'}, :partial => 'settings/settings'
29 # end
29 # end
30 #
30 #
31 # === Plugin attributes
31 # === Plugin attributes
32 #
32 #
33 # +settings+ is an optional attribute that let the plugin be configurable.
33 # +settings+ is an optional attribute that let the plugin be configurable.
34 # It must be a hash with the following keys:
34 # It must be a hash with the following keys:
35 # * <tt>:default</tt>: default value for the plugin settings
35 # * <tt>:default</tt>: default value for the plugin settings
36 # * <tt>:partial</tt>: path of the configuration partial view, relative to the plugin <tt>app/views</tt> directory
36 # * <tt>:partial</tt>: path of the configuration partial view, relative to the plugin <tt>app/views</tt> directory
37 # Example:
37 # Example:
38 # settings :default => {'foo'=>'bar'}, :partial => 'settings/settings'
38 # settings :default => {'foo'=>'bar'}, :partial => 'settings/settings'
39 # In this example, the settings partial will be found here in the plugin directory: <tt>app/views/settings/_settings.rhtml</tt>.
39 # In this example, the settings partial will be found here in the plugin directory: <tt>app/views/settings/_settings.rhtml</tt>.
40 #
40 #
41 # When rendered, the plugin settings value is available as the local variable +settings+
41 # When rendered, the plugin settings value is available as the local variable +settings+
42 class Plugin
42 class Plugin
43 @registered_plugins = {}
43 @registered_plugins = {}
44 class << self
44 class << self
45 attr_reader :registered_plugins
45 attr_reader :registered_plugins
46 private :new
46 private :new
47
47
48 def def_field(*names)
48 def def_field(*names)
49 class_eval do
49 class_eval do
50 names.each do |name|
50 names.each do |name|
51 define_method(name) do |*args|
51 define_method(name) do |*args|
52 args.empty? ? instance_variable_get("@#{name}") : instance_variable_set("@#{name}", *args)
52 args.empty? ? instance_variable_get("@#{name}") : instance_variable_set("@#{name}", *args)
53 end
53 end
54 end
54 end
55 end
55 end
56 end
56 end
57 end
57 end
58 def_field :name, :description, :author, :version, :settings
58 def_field :name, :description, :author, :version, :settings
59
59
60 # Plugin constructor
60 # Plugin constructor
61 def self.register(name, &block)
61 def self.register(name, &block)
62 p = new
62 p = new
63 p.instance_eval(&block)
63 p.instance_eval(&block)
64 Plugin.registered_plugins[name] = p
64 Plugin.registered_plugins[name] = p
65 end
65 end
66
66
67 # Adds an item to the given +menu+.
67 # Adds an item to the given +menu+.
68 # The +id+ parameter (equals to the project id) is automatically added to the url.
68 # The +id+ parameter (equals to the project id) is automatically added to the url.
69 # menu :project_menu, :label_plugin_example, :controller => 'example', :action => 'say_hello'
69 # menu :project_menu, :plugin_example, { :controller => 'example', :action => 'say_hello' }, :caption => 'Sample'
70 #
70 #
71 # Currently, only the project menu can be extended. Thus, the +name+ parameter must be +:project_menu+
71 # +name+ parameter can be: :top_menu, :account_menu, :application_menu or :project_menu
72 def menu(name, label, url)
72 #
73 Redmine::MenuManager.map(name) {|menu| menu.push label, url}
73 def menu(name, item, url, options={})
74 Redmine::MenuManager.map(name) {|menu| menu.push item, url, options}
74 end
75 end
75
76
76 # Defines a permission called +name+ for the given +actions+.
77 # Defines a permission called +name+ for the given +actions+.
77 #
78 #
78 # The +actions+ argument is a hash with controllers as keys and actions as values (a single value or an array):
79 # The +actions+ argument is a hash with controllers as keys and actions as values (a single value or an array):
79 # permission :destroy_contacts, { :contacts => :destroy }
80 # permission :destroy_contacts, { :contacts => :destroy }
80 # permission :view_contacts, { :contacts => [:index, :show] }
81 # permission :view_contacts, { :contacts => [:index, :show] }
81 #
82 #
82 # The +options+ argument can be used to make the permission public (implicitly given to any user)
83 # The +options+ argument can be used to make the permission public (implicitly given to any user)
83 # or to restrict users the permission can be given to.
84 # or to restrict users the permission can be given to.
84 #
85 #
85 # Examples
86 # Examples
86 # # A permission that is implicitly given to any user
87 # # A permission that is implicitly given to any user
87 # # This permission won't appear on the Roles & Permissions setup screen
88 # # This permission won't appear on the Roles & Permissions setup screen
88 # permission :say_hello, { :example => :say_hello }, :public => true
89 # permission :say_hello, { :example => :say_hello }, :public => true
89 #
90 #
90 # # A permission that can be given to any user
91 # # A permission that can be given to any user
91 # permission :say_hello, { :example => :say_hello }
92 # permission :say_hello, { :example => :say_hello }
92 #
93 #
93 # # A permission that can be given to registered users only
94 # # A permission that can be given to registered users only
94 # permission :say_hello, { :example => :say_hello }, :require => loggedin
95 # permission :say_hello, { :example => :say_hello }, :require => loggedin
95 #
96 #
96 # # A permission that can be given to project members only
97 # # A permission that can be given to project members only
97 # permission :say_hello, { :example => :say_hello }, :require => member
98 # permission :say_hello, { :example => :say_hello }, :require => member
98 def permission(name, actions, options = {})
99 def permission(name, actions, options = {})
99 if @project_module
100 if @project_module
100 Redmine::AccessControl.map {|map| map.project_module(@project_module) {|map|map.permission(name, actions, options)}}
101 Redmine::AccessControl.map {|map| map.project_module(@project_module) {|map|map.permission(name, actions, options)}}
101 else
102 else
102 Redmine::AccessControl.map {|map| map.permission(name, actions, options)}
103 Redmine::AccessControl.map {|map| map.permission(name, actions, options)}
103 end
104 end
104 end
105 end
105
106
106 # Defines a project module, that can be enabled/disabled for each project.
107 # Defines a project module, that can be enabled/disabled for each project.
107 # Permissions defined inside +block+ will be bind to the module.
108 # Permissions defined inside +block+ will be bind to the module.
108 #
109 #
109 # project_module :things do
110 # project_module :things do
110 # permission :view_contacts, { :contacts => [:list, :show] }, :public => true
111 # permission :view_contacts, { :contacts => [:list, :show] }, :public => true
111 # permission :destroy_contacts, { :contacts => :destroy }
112 # permission :destroy_contacts, { :contacts => :destroy }
112 # end
113 # end
113 def project_module(name, &block)
114 def project_module(name, &block)
114 @project_module = name
115 @project_module = name
115 self.instance_eval(&block)
116 self.instance_eval(&block)
116 @project_module = nil
117 @project_module = nil
117 end
118 end
118
119
119 # Returns +true+ if the plugin can be configured.
120 # Returns +true+ if the plugin can be configured.
120 def configurable?
121 def configurable?
121 settings && settings.is_a?(Hash) && !settings[:partial].blank?
122 settings && settings.is_a?(Hash) && !settings[:partial].blank?
122 end
123 end
123 end
124 end
124 end
125 end
General Comments 0
You need to be logged in to leave comments. Login now