##// END OF EJS Templates
Moved duplicate #options_for_membership_project_select to ApplicationHelper....
Jean-Philippe Lang -
r10674:65d3bffad7fd
parent child
Show More
@@ -1,1276 +1,1285
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2012 Jean-Philippe Lang
4 # Copyright (C) 2006-2012 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require 'forwardable'
20 require 'forwardable'
21 require 'cgi'
21 require 'cgi'
22
22
23 module ApplicationHelper
23 module ApplicationHelper
24 include Redmine::WikiFormatting::Macros::Definitions
24 include Redmine::WikiFormatting::Macros::Definitions
25 include Redmine::I18n
25 include Redmine::I18n
26 include GravatarHelper::PublicMethods
26 include GravatarHelper::PublicMethods
27
27
28 extend Forwardable
28 extend Forwardable
29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
30
30
31 # Return true if user is authorized for controller/action, otherwise false
31 # Return true if user is authorized for controller/action, otherwise false
32 def authorize_for(controller, action)
32 def authorize_for(controller, action)
33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
34 end
34 end
35
35
36 # Display a link if user is authorized
36 # Display a link if user is authorized
37 #
37 #
38 # @param [String] name Anchor text (passed to link_to)
38 # @param [String] name Anchor text (passed to link_to)
39 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
39 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
40 # @param [optional, Hash] html_options Options passed to link_to
40 # @param [optional, Hash] html_options Options passed to link_to
41 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
41 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
42 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
42 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
43 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
43 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
44 end
44 end
45
45
46 # Displays a link to user's account page if active
46 # Displays a link to user's account page if active
47 def link_to_user(user, options={})
47 def link_to_user(user, options={})
48 if user.is_a?(User)
48 if user.is_a?(User)
49 name = h(user.name(options[:format]))
49 name = h(user.name(options[:format]))
50 if user.active? || (User.current.admin? && user.logged?)
50 if user.active? || (User.current.admin? && user.logged?)
51 link_to name, user_path(user), :class => user.css_classes
51 link_to name, user_path(user), :class => user.css_classes
52 else
52 else
53 name
53 name
54 end
54 end
55 else
55 else
56 h(user.to_s)
56 h(user.to_s)
57 end
57 end
58 end
58 end
59
59
60 # Displays a link to +issue+ with its subject.
60 # Displays a link to +issue+ with its subject.
61 # Examples:
61 # Examples:
62 #
62 #
63 # link_to_issue(issue) # => Defect #6: This is the subject
63 # link_to_issue(issue) # => Defect #6: This is the subject
64 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
64 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
65 # link_to_issue(issue, :subject => false) # => Defect #6
65 # link_to_issue(issue, :subject => false) # => Defect #6
66 # link_to_issue(issue, :project => true) # => Foo - Defect #6
66 # link_to_issue(issue, :project => true) # => Foo - Defect #6
67 # link_to_issue(issue, :subject => false, :tracker => false) # => #6
67 # link_to_issue(issue, :subject => false, :tracker => false) # => #6
68 #
68 #
69 def link_to_issue(issue, options={})
69 def link_to_issue(issue, options={})
70 title = nil
70 title = nil
71 subject = nil
71 subject = nil
72 text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}"
72 text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}"
73 if options[:subject] == false
73 if options[:subject] == false
74 title = truncate(issue.subject, :length => 60)
74 title = truncate(issue.subject, :length => 60)
75 else
75 else
76 subject = issue.subject
76 subject = issue.subject
77 if options[:truncate]
77 if options[:truncate]
78 subject = truncate(subject, :length => options[:truncate])
78 subject = truncate(subject, :length => options[:truncate])
79 end
79 end
80 end
80 end
81 s = link_to text, issue_path(issue), :class => issue.css_classes, :title => title
81 s = link_to text, issue_path(issue), :class => issue.css_classes, :title => title
82 s << h(": #{subject}") if subject
82 s << h(": #{subject}") if subject
83 s = h("#{issue.project} - ") + s if options[:project]
83 s = h("#{issue.project} - ") + s if options[:project]
84 s
84 s
85 end
85 end
86
86
87 # Generates a link to an attachment.
87 # Generates a link to an attachment.
88 # Options:
88 # Options:
89 # * :text - Link text (default to attachment filename)
89 # * :text - Link text (default to attachment filename)
90 # * :download - Force download (default: false)
90 # * :download - Force download (default: false)
91 def link_to_attachment(attachment, options={})
91 def link_to_attachment(attachment, options={})
92 text = options.delete(:text) || attachment.filename
92 text = options.delete(:text) || attachment.filename
93 action = options.delete(:download) ? 'download' : 'show'
93 action = options.delete(:download) ? 'download' : 'show'
94 opt_only_path = {}
94 opt_only_path = {}
95 opt_only_path[:only_path] = (options[:only_path] == false ? false : true)
95 opt_only_path[:only_path] = (options[:only_path] == false ? false : true)
96 options.delete(:only_path)
96 options.delete(:only_path)
97 link_to(h(text),
97 link_to(h(text),
98 {:controller => 'attachments', :action => action,
98 {:controller => 'attachments', :action => action,
99 :id => attachment, :filename => attachment.filename}.merge(opt_only_path),
99 :id => attachment, :filename => attachment.filename}.merge(opt_only_path),
100 options)
100 options)
101 end
101 end
102
102
103 # Generates a link to a SCM revision
103 # Generates a link to a SCM revision
104 # Options:
104 # Options:
105 # * :text - Link text (default to the formatted revision)
105 # * :text - Link text (default to the formatted revision)
106 def link_to_revision(revision, repository, options={})
106 def link_to_revision(revision, repository, options={})
107 if repository.is_a?(Project)
107 if repository.is_a?(Project)
108 repository = repository.repository
108 repository = repository.repository
109 end
109 end
110 text = options.delete(:text) || format_revision(revision)
110 text = options.delete(:text) || format_revision(revision)
111 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
111 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
112 link_to(
112 link_to(
113 h(text),
113 h(text),
114 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
114 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
115 :title => l(:label_revision_id, format_revision(revision))
115 :title => l(:label_revision_id, format_revision(revision))
116 )
116 )
117 end
117 end
118
118
119 # Generates a link to a message
119 # Generates a link to a message
120 def link_to_message(message, options={}, html_options = nil)
120 def link_to_message(message, options={}, html_options = nil)
121 link_to(
121 link_to(
122 h(truncate(message.subject, :length => 60)),
122 h(truncate(message.subject, :length => 60)),
123 { :controller => 'messages', :action => 'show',
123 { :controller => 'messages', :action => 'show',
124 :board_id => message.board_id,
124 :board_id => message.board_id,
125 :id => (message.parent_id || message.id),
125 :id => (message.parent_id || message.id),
126 :r => (message.parent_id && message.id),
126 :r => (message.parent_id && message.id),
127 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
127 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
128 }.merge(options),
128 }.merge(options),
129 html_options
129 html_options
130 )
130 )
131 end
131 end
132
132
133 # Generates a link to a project if active
133 # Generates a link to a project if active
134 # Examples:
134 # Examples:
135 #
135 #
136 # link_to_project(project) # => link to the specified project overview
136 # link_to_project(project) # => link to the specified project overview
137 # link_to_project(project, :action=>'settings') # => link to project settings
137 # link_to_project(project, :action=>'settings') # => link to project settings
138 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
138 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
139 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
139 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
140 #
140 #
141 def link_to_project(project, options={}, html_options = nil)
141 def link_to_project(project, options={}, html_options = nil)
142 if project.archived?
142 if project.archived?
143 h(project)
143 h(project)
144 else
144 else
145 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
145 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
146 link_to(h(project), url, html_options)
146 link_to(h(project), url, html_options)
147 end
147 end
148 end
148 end
149
149
150 def wiki_page_path(page, options={})
150 def wiki_page_path(page, options={})
151 url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options))
151 url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options))
152 end
152 end
153
153
154 def thumbnail_tag(attachment)
154 def thumbnail_tag(attachment)
155 link_to image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment)),
155 link_to image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment)),
156 {:controller => 'attachments', :action => 'show', :id => attachment, :filename => attachment.filename},
156 {:controller => 'attachments', :action => 'show', :id => attachment, :filename => attachment.filename},
157 :title => attachment.filename
157 :title => attachment.filename
158 end
158 end
159
159
160 def toggle_link(name, id, options={})
160 def toggle_link(name, id, options={})
161 onclick = "$('##{id}').toggle(); "
161 onclick = "$('##{id}').toggle(); "
162 onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ")
162 onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ")
163 onclick << "return false;"
163 onclick << "return false;"
164 link_to(name, "#", :onclick => onclick)
164 link_to(name, "#", :onclick => onclick)
165 end
165 end
166
166
167 def image_to_function(name, function, html_options = {})
167 def image_to_function(name, function, html_options = {})
168 html_options.symbolize_keys!
168 html_options.symbolize_keys!
169 tag(:input, html_options.merge({
169 tag(:input, html_options.merge({
170 :type => "image", :src => image_path(name),
170 :type => "image", :src => image_path(name),
171 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
171 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
172 }))
172 }))
173 end
173 end
174
174
175 def format_activity_title(text)
175 def format_activity_title(text)
176 h(truncate_single_line(text, :length => 100))
176 h(truncate_single_line(text, :length => 100))
177 end
177 end
178
178
179 def format_activity_day(date)
179 def format_activity_day(date)
180 date == User.current.today ? l(:label_today).titleize : format_date(date)
180 date == User.current.today ? l(:label_today).titleize : format_date(date)
181 end
181 end
182
182
183 def format_activity_description(text)
183 def format_activity_description(text)
184 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
184 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
185 ).gsub(/[\r\n]+/, "<br />").html_safe
185 ).gsub(/[\r\n]+/, "<br />").html_safe
186 end
186 end
187
187
188 def format_version_name(version)
188 def format_version_name(version)
189 if version.project == @project
189 if version.project == @project
190 h(version)
190 h(version)
191 else
191 else
192 h("#{version.project} - #{version}")
192 h("#{version.project} - #{version}")
193 end
193 end
194 end
194 end
195
195
196 def due_date_distance_in_words(date)
196 def due_date_distance_in_words(date)
197 if date
197 if date
198 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
198 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
199 end
199 end
200 end
200 end
201
201
202 # Renders a tree of projects as a nested set of unordered lists
202 # Renders a tree of projects as a nested set of unordered lists
203 # The given collection may be a subset of the whole project tree
203 # The given collection may be a subset of the whole project tree
204 # (eg. some intermediate nodes are private and can not be seen)
204 # (eg. some intermediate nodes are private and can not be seen)
205 def render_project_nested_lists(projects)
205 def render_project_nested_lists(projects)
206 s = ''
206 s = ''
207 if projects.any?
207 if projects.any?
208 ancestors = []
208 ancestors = []
209 original_project = @project
209 original_project = @project
210 projects.sort_by(&:lft).each do |project|
210 projects.sort_by(&:lft).each do |project|
211 # set the project environment to please macros.
211 # set the project environment to please macros.
212 @project = project
212 @project = project
213 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
213 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
214 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
214 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
215 else
215 else
216 ancestors.pop
216 ancestors.pop
217 s << "</li>"
217 s << "</li>"
218 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
218 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
219 ancestors.pop
219 ancestors.pop
220 s << "</ul></li>\n"
220 s << "</ul></li>\n"
221 end
221 end
222 end
222 end
223 classes = (ancestors.empty? ? 'root' : 'child')
223 classes = (ancestors.empty? ? 'root' : 'child')
224 s << "<li class='#{classes}'><div class='#{classes}'>"
224 s << "<li class='#{classes}'><div class='#{classes}'>"
225 s << h(block_given? ? yield(project) : project.name)
225 s << h(block_given? ? yield(project) : project.name)
226 s << "</div>\n"
226 s << "</div>\n"
227 ancestors << project
227 ancestors << project
228 end
228 end
229 s << ("</li></ul>\n" * ancestors.size)
229 s << ("</li></ul>\n" * ancestors.size)
230 @project = original_project
230 @project = original_project
231 end
231 end
232 s.html_safe
232 s.html_safe
233 end
233 end
234
234
235 def render_page_hierarchy(pages, node=nil, options={})
235 def render_page_hierarchy(pages, node=nil, options={})
236 content = ''
236 content = ''
237 if pages[node]
237 if pages[node]
238 content << "<ul class=\"pages-hierarchy\">\n"
238 content << "<ul class=\"pages-hierarchy\">\n"
239 pages[node].each do |page|
239 pages[node].each do |page|
240 content << "<li>"
240 content << "<li>"
241 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil},
241 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil},
242 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
242 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
243 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
243 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
244 content << "</li>\n"
244 content << "</li>\n"
245 end
245 end
246 content << "</ul>\n"
246 content << "</ul>\n"
247 end
247 end
248 content.html_safe
248 content.html_safe
249 end
249 end
250
250
251 # Renders flash messages
251 # Renders flash messages
252 def render_flash_messages
252 def render_flash_messages
253 s = ''
253 s = ''
254 flash.each do |k,v|
254 flash.each do |k,v|
255 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
255 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
256 end
256 end
257 s.html_safe
257 s.html_safe
258 end
258 end
259
259
260 # Renders tabs and their content
260 # Renders tabs and their content
261 def render_tabs(tabs)
261 def render_tabs(tabs)
262 if tabs.any?
262 if tabs.any?
263 render :partial => 'common/tabs', :locals => {:tabs => tabs}
263 render :partial => 'common/tabs', :locals => {:tabs => tabs}
264 else
264 else
265 content_tag 'p', l(:label_no_data), :class => "nodata"
265 content_tag 'p', l(:label_no_data), :class => "nodata"
266 end
266 end
267 end
267 end
268
268
269 # Renders the project quick-jump box
269 # Renders the project quick-jump box
270 def render_project_jump_box
270 def render_project_jump_box
271 return unless User.current.logged?
271 return unless User.current.logged?
272 projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq
272 projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq
273 if projects.any?
273 if projects.any?
274 options =
274 options =
275 ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
275 ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
276 '<option value="" disabled="disabled">---</option>').html_safe
276 '<option value="" disabled="disabled">---</option>').html_safe
277
277
278 options << project_tree_options_for_select(projects, :selected => @project) do |p|
278 options << project_tree_options_for_select(projects, :selected => @project) do |p|
279 { :value => project_path(:id => p, :jump => current_menu_item) }
279 { :value => project_path(:id => p, :jump => current_menu_item) }
280 end
280 end
281
281
282 select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
282 select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
283 end
283 end
284 end
284 end
285
285
286 def project_tree_options_for_select(projects, options = {})
286 def project_tree_options_for_select(projects, options = {})
287 s = ''
287 s = ''
288 project_tree(projects) do |project, level|
288 project_tree(projects) do |project, level|
289 name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
289 name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
290 tag_options = {:value => project.id}
290 tag_options = {:value => project.id}
291 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
291 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
292 tag_options[:selected] = 'selected'
292 tag_options[:selected] = 'selected'
293 else
293 else
294 tag_options[:selected] = nil
294 tag_options[:selected] = nil
295 end
295 end
296 tag_options.merge!(yield(project)) if block_given?
296 tag_options.merge!(yield(project)) if block_given?
297 s << content_tag('option', name_prefix + h(project), tag_options)
297 s << content_tag('option', name_prefix + h(project), tag_options)
298 end
298 end
299 s.html_safe
299 s.html_safe
300 end
300 end
301
301
302 # Yields the given block for each project with its level in the tree
302 # Yields the given block for each project with its level in the tree
303 #
303 #
304 # Wrapper for Project#project_tree
304 # Wrapper for Project#project_tree
305 def project_tree(projects, &block)
305 def project_tree(projects, &block)
306 Project.project_tree(projects, &block)
306 Project.project_tree(projects, &block)
307 end
307 end
308
308
309 def principals_check_box_tags(name, principals)
309 def principals_check_box_tags(name, principals)
310 s = ''
310 s = ''
311 principals.sort.each do |principal|
311 principals.sort.each do |principal|
312 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
312 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
313 end
313 end
314 s.html_safe
314 s.html_safe
315 end
315 end
316
316
317 # Returns a string for users/groups option tags
317 # Returns a string for users/groups option tags
318 def principals_options_for_select(collection, selected=nil)
318 def principals_options_for_select(collection, selected=nil)
319 s = ''
319 s = ''
320 if collection.include?(User.current)
320 if collection.include?(User.current)
321 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
321 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
322 end
322 end
323 groups = ''
323 groups = ''
324 collection.sort.each do |element|
324 collection.sort.each do |element|
325 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
325 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
326 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
326 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
327 end
327 end
328 unless groups.empty?
328 unless groups.empty?
329 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
329 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
330 end
330 end
331 s.html_safe
331 s.html_safe
332 end
332 end
333
333
334 # Options for the new membership projects combo-box
335 def options_for_membership_project_select(principal, projects)
336 options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
337 options << project_tree_options_for_select(projects) do |p|
338 {:disabled => principal.projects.include?(p)}
339 end
340 options
341 end
342
334 # Truncates and returns the string as a single line
343 # Truncates and returns the string as a single line
335 def truncate_single_line(string, *args)
344 def truncate_single_line(string, *args)
336 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
345 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
337 end
346 end
338
347
339 # Truncates at line break after 250 characters or options[:length]
348 # Truncates at line break after 250 characters or options[:length]
340 def truncate_lines(string, options={})
349 def truncate_lines(string, options={})
341 length = options[:length] || 250
350 length = options[:length] || 250
342 if string.to_s =~ /\A(.{#{length}}.*?)$/m
351 if string.to_s =~ /\A(.{#{length}}.*?)$/m
343 "#{$1}..."
352 "#{$1}..."
344 else
353 else
345 string
354 string
346 end
355 end
347 end
356 end
348
357
349 def anchor(text)
358 def anchor(text)
350 text.to_s.gsub(' ', '_')
359 text.to_s.gsub(' ', '_')
351 end
360 end
352
361
353 def html_hours(text)
362 def html_hours(text)
354 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
363 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
355 end
364 end
356
365
357 def authoring(created, author, options={})
366 def authoring(created, author, options={})
358 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
367 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
359 end
368 end
360
369
361 def time_tag(time)
370 def time_tag(time)
362 text = distance_of_time_in_words(Time.now, time)
371 text = distance_of_time_in_words(Time.now, time)
363 if @project
372 if @project
364 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
373 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
365 else
374 else
366 content_tag('acronym', text, :title => format_time(time))
375 content_tag('acronym', text, :title => format_time(time))
367 end
376 end
368 end
377 end
369
378
370 def syntax_highlight_lines(name, content)
379 def syntax_highlight_lines(name, content)
371 lines = []
380 lines = []
372 syntax_highlight(name, content).each_line { |line| lines << line }
381 syntax_highlight(name, content).each_line { |line| lines << line }
373 lines
382 lines
374 end
383 end
375
384
376 def syntax_highlight(name, content)
385 def syntax_highlight(name, content)
377 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
386 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
378 end
387 end
379
388
380 def to_path_param(path)
389 def to_path_param(path)
381 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
390 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
382 str.blank? ? nil : str
391 str.blank? ? nil : str
383 end
392 end
384
393
385 def pagination_links_full(paginator, count=nil, options={})
394 def pagination_links_full(paginator, count=nil, options={})
386 page_param = options.delete(:page_param) || :page
395 page_param = options.delete(:page_param) || :page
387 per_page_links = options.delete(:per_page_links)
396 per_page_links = options.delete(:per_page_links)
388 url_param = params.dup
397 url_param = params.dup
389
398
390 html = ''
399 html = ''
391 if paginator.current.previous
400 if paginator.current.previous
392 # \xc2\xab(utf-8) = &#171;
401 # \xc2\xab(utf-8) = &#171;
393 html << link_to_content_update(
402 html << link_to_content_update(
394 "\xc2\xab " + l(:label_previous),
403 "\xc2\xab " + l(:label_previous),
395 url_param.merge(page_param => paginator.current.previous)) + ' '
404 url_param.merge(page_param => paginator.current.previous)) + ' '
396 end
405 end
397
406
398 html << (pagination_links_each(paginator, options) do |n|
407 html << (pagination_links_each(paginator, options) do |n|
399 link_to_content_update(n.to_s, url_param.merge(page_param => n))
408 link_to_content_update(n.to_s, url_param.merge(page_param => n))
400 end || '')
409 end || '')
401
410
402 if paginator.current.next
411 if paginator.current.next
403 # \xc2\xbb(utf-8) = &#187;
412 # \xc2\xbb(utf-8) = &#187;
404 html << ' ' + link_to_content_update(
413 html << ' ' + link_to_content_update(
405 (l(:label_next) + " \xc2\xbb"),
414 (l(:label_next) + " \xc2\xbb"),
406 url_param.merge(page_param => paginator.current.next))
415 url_param.merge(page_param => paginator.current.next))
407 end
416 end
408
417
409 unless count.nil?
418 unless count.nil?
410 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
419 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
411 if per_page_links != false && links = per_page_links(paginator.items_per_page, count)
420 if per_page_links != false && links = per_page_links(paginator.items_per_page, count)
412 html << " | #{links}"
421 html << " | #{links}"
413 end
422 end
414 end
423 end
415
424
416 html.html_safe
425 html.html_safe
417 end
426 end
418
427
419 def per_page_links(selected=nil, item_count=nil)
428 def per_page_links(selected=nil, item_count=nil)
420 values = Setting.per_page_options_array
429 values = Setting.per_page_options_array
421 if item_count && values.any?
430 if item_count && values.any?
422 if item_count > values.first
431 if item_count > values.first
423 max = values.detect {|value| value >= item_count} || item_count
432 max = values.detect {|value| value >= item_count} || item_count
424 else
433 else
425 max = item_count
434 max = item_count
426 end
435 end
427 values = values.select {|value| value <= max || value == selected}
436 values = values.select {|value| value <= max || value == selected}
428 end
437 end
429 if values.empty? || (values.size == 1 && values.first == selected)
438 if values.empty? || (values.size == 1 && values.first == selected)
430 return nil
439 return nil
431 end
440 end
432 links = values.collect do |n|
441 links = values.collect do |n|
433 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
442 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
434 end
443 end
435 l(:label_display_per_page, links.join(', '))
444 l(:label_display_per_page, links.join(', '))
436 end
445 end
437
446
438 def reorder_links(name, url, method = :post)
447 def reorder_links(name, url, method = :post)
439 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
448 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
440 url.merge({"#{name}[move_to]" => 'highest'}),
449 url.merge({"#{name}[move_to]" => 'highest'}),
441 :method => method, :title => l(:label_sort_highest)) +
450 :method => method, :title => l(:label_sort_highest)) +
442 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
451 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
443 url.merge({"#{name}[move_to]" => 'higher'}),
452 url.merge({"#{name}[move_to]" => 'higher'}),
444 :method => method, :title => l(:label_sort_higher)) +
453 :method => method, :title => l(:label_sort_higher)) +
445 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
454 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
446 url.merge({"#{name}[move_to]" => 'lower'}),
455 url.merge({"#{name}[move_to]" => 'lower'}),
447 :method => method, :title => l(:label_sort_lower)) +
456 :method => method, :title => l(:label_sort_lower)) +
448 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
457 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
449 url.merge({"#{name}[move_to]" => 'lowest'}),
458 url.merge({"#{name}[move_to]" => 'lowest'}),
450 :method => method, :title => l(:label_sort_lowest))
459 :method => method, :title => l(:label_sort_lowest))
451 end
460 end
452
461
453 def breadcrumb(*args)
462 def breadcrumb(*args)
454 elements = args.flatten
463 elements = args.flatten
455 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
464 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
456 end
465 end
457
466
458 def other_formats_links(&block)
467 def other_formats_links(&block)
459 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
468 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
460 yield Redmine::Views::OtherFormatsBuilder.new(self)
469 yield Redmine::Views::OtherFormatsBuilder.new(self)
461 concat('</p>'.html_safe)
470 concat('</p>'.html_safe)
462 end
471 end
463
472
464 def page_header_title
473 def page_header_title
465 if @project.nil? || @project.new_record?
474 if @project.nil? || @project.new_record?
466 h(Setting.app_title)
475 h(Setting.app_title)
467 else
476 else
468 b = []
477 b = []
469 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
478 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
470 if ancestors.any?
479 if ancestors.any?
471 root = ancestors.shift
480 root = ancestors.shift
472 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
481 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
473 if ancestors.size > 2
482 if ancestors.size > 2
474 b << "\xe2\x80\xa6"
483 b << "\xe2\x80\xa6"
475 ancestors = ancestors[-2, 2]
484 ancestors = ancestors[-2, 2]
476 end
485 end
477 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
486 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
478 end
487 end
479 b << h(@project)
488 b << h(@project)
480 b.join(" \xc2\xbb ").html_safe
489 b.join(" \xc2\xbb ").html_safe
481 end
490 end
482 end
491 end
483
492
484 def html_title(*args)
493 def html_title(*args)
485 if args.empty?
494 if args.empty?
486 title = @html_title || []
495 title = @html_title || []
487 title << @project.name if @project
496 title << @project.name if @project
488 title << Setting.app_title unless Setting.app_title == title.last
497 title << Setting.app_title unless Setting.app_title == title.last
489 title.select {|t| !t.blank? }.join(' - ')
498 title.select {|t| !t.blank? }.join(' - ')
490 else
499 else
491 @html_title ||= []
500 @html_title ||= []
492 @html_title += args
501 @html_title += args
493 end
502 end
494 end
503 end
495
504
496 # Returns the theme, controller name, and action as css classes for the
505 # Returns the theme, controller name, and action as css classes for the
497 # HTML body.
506 # HTML body.
498 def body_css_classes
507 def body_css_classes
499 css = []
508 css = []
500 if theme = Redmine::Themes.theme(Setting.ui_theme)
509 if theme = Redmine::Themes.theme(Setting.ui_theme)
501 css << 'theme-' + theme.name
510 css << 'theme-' + theme.name
502 end
511 end
503
512
504 css << 'controller-' + controller_name
513 css << 'controller-' + controller_name
505 css << 'action-' + action_name
514 css << 'action-' + action_name
506 css.join(' ')
515 css.join(' ')
507 end
516 end
508
517
509 def accesskey(s)
518 def accesskey(s)
510 Redmine::AccessKeys.key_for s
519 Redmine::AccessKeys.key_for s
511 end
520 end
512
521
513 # Formats text according to system settings.
522 # Formats text according to system settings.
514 # 2 ways to call this method:
523 # 2 ways to call this method:
515 # * with a String: textilizable(text, options)
524 # * with a String: textilizable(text, options)
516 # * with an object and one of its attribute: textilizable(issue, :description, options)
525 # * with an object and one of its attribute: textilizable(issue, :description, options)
517 def textilizable(*args)
526 def textilizable(*args)
518 options = args.last.is_a?(Hash) ? args.pop : {}
527 options = args.last.is_a?(Hash) ? args.pop : {}
519 case args.size
528 case args.size
520 when 1
529 when 1
521 obj = options[:object]
530 obj = options[:object]
522 text = args.shift
531 text = args.shift
523 when 2
532 when 2
524 obj = args.shift
533 obj = args.shift
525 attr = args.shift
534 attr = args.shift
526 text = obj.send(attr).to_s
535 text = obj.send(attr).to_s
527 else
536 else
528 raise ArgumentError, 'invalid arguments to textilizable'
537 raise ArgumentError, 'invalid arguments to textilizable'
529 end
538 end
530 return '' if text.blank?
539 return '' if text.blank?
531 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
540 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
532 only_path = options.delete(:only_path) == false ? false : true
541 only_path = options.delete(:only_path) == false ? false : true
533
542
534 text = text.dup
543 text = text.dup
535 macros = catch_macros(text)
544 macros = catch_macros(text)
536 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
545 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
537
546
538 @parsed_headings = []
547 @parsed_headings = []
539 @heading_anchors = {}
548 @heading_anchors = {}
540 @current_section = 0 if options[:edit_section_links]
549 @current_section = 0 if options[:edit_section_links]
541
550
542 parse_sections(text, project, obj, attr, only_path, options)
551 parse_sections(text, project, obj, attr, only_path, options)
543 text = parse_non_pre_blocks(text, obj, macros) do |text|
552 text = parse_non_pre_blocks(text, obj, macros) do |text|
544 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
553 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
545 send method_name, text, project, obj, attr, only_path, options
554 send method_name, text, project, obj, attr, only_path, options
546 end
555 end
547 end
556 end
548 parse_headings(text, project, obj, attr, only_path, options)
557 parse_headings(text, project, obj, attr, only_path, options)
549
558
550 if @parsed_headings.any?
559 if @parsed_headings.any?
551 replace_toc(text, @parsed_headings)
560 replace_toc(text, @parsed_headings)
552 end
561 end
553
562
554 text.html_safe
563 text.html_safe
555 end
564 end
556
565
557 def parse_non_pre_blocks(text, obj, macros)
566 def parse_non_pre_blocks(text, obj, macros)
558 s = StringScanner.new(text)
567 s = StringScanner.new(text)
559 tags = []
568 tags = []
560 parsed = ''
569 parsed = ''
561 while !s.eos?
570 while !s.eos?
562 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
571 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
563 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
572 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
564 if tags.empty?
573 if tags.empty?
565 yield text
574 yield text
566 inject_macros(text, obj, macros) if macros.any?
575 inject_macros(text, obj, macros) if macros.any?
567 else
576 else
568 inject_macros(text, obj, macros, false) if macros.any?
577 inject_macros(text, obj, macros, false) if macros.any?
569 end
578 end
570 parsed << text
579 parsed << text
571 if tag
580 if tag
572 if closing
581 if closing
573 if tags.last == tag.downcase
582 if tags.last == tag.downcase
574 tags.pop
583 tags.pop
575 end
584 end
576 else
585 else
577 tags << tag.downcase
586 tags << tag.downcase
578 end
587 end
579 parsed << full_tag
588 parsed << full_tag
580 end
589 end
581 end
590 end
582 # Close any non closing tags
591 # Close any non closing tags
583 while tag = tags.pop
592 while tag = tags.pop
584 parsed << "</#{tag}>"
593 parsed << "</#{tag}>"
585 end
594 end
586 parsed
595 parsed
587 end
596 end
588
597
589 def parse_inline_attachments(text, project, obj, attr, only_path, options)
598 def parse_inline_attachments(text, project, obj, attr, only_path, options)
590 # when using an image link, try to use an attachment, if possible
599 # when using an image link, try to use an attachment, if possible
591 if options[:attachments] || (obj && obj.respond_to?(:attachments))
600 if options[:attachments] || (obj && obj.respond_to?(:attachments))
592 attachments = options[:attachments] || obj.attachments
601 attachments = options[:attachments] || obj.attachments
593 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
602 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
594 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
603 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
595 # search for the picture in attachments
604 # search for the picture in attachments
596 if found = Attachment.latest_attach(attachments, filename)
605 if found = Attachment.latest_attach(attachments, filename)
597 image_url = url_for :only_path => only_path, :controller => 'attachments',
606 image_url = url_for :only_path => only_path, :controller => 'attachments',
598 :action => 'download', :id => found
607 :action => 'download', :id => found
599 desc = found.description.to_s.gsub('"', '')
608 desc = found.description.to_s.gsub('"', '')
600 if !desc.blank? && alttext.blank?
609 if !desc.blank? && alttext.blank?
601 alt = " title=\"#{desc}\" alt=\"#{desc}\""
610 alt = " title=\"#{desc}\" alt=\"#{desc}\""
602 end
611 end
603 "src=\"#{image_url}\"#{alt}"
612 "src=\"#{image_url}\"#{alt}"
604 else
613 else
605 m
614 m
606 end
615 end
607 end
616 end
608 end
617 end
609 end
618 end
610
619
611 # Wiki links
620 # Wiki links
612 #
621 #
613 # Examples:
622 # Examples:
614 # [[mypage]]
623 # [[mypage]]
615 # [[mypage|mytext]]
624 # [[mypage|mytext]]
616 # wiki links can refer other project wikis, using project name or identifier:
625 # wiki links can refer other project wikis, using project name or identifier:
617 # [[project:]] -> wiki starting page
626 # [[project:]] -> wiki starting page
618 # [[project:|mytext]]
627 # [[project:|mytext]]
619 # [[project:mypage]]
628 # [[project:mypage]]
620 # [[project:mypage|mytext]]
629 # [[project:mypage|mytext]]
621 def parse_wiki_links(text, project, obj, attr, only_path, options)
630 def parse_wiki_links(text, project, obj, attr, only_path, options)
622 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
631 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
623 link_project = project
632 link_project = project
624 esc, all, page, title = $1, $2, $3, $5
633 esc, all, page, title = $1, $2, $3, $5
625 if esc.nil?
634 if esc.nil?
626 if page =~ /^([^\:]+)\:(.*)$/
635 if page =~ /^([^\:]+)\:(.*)$/
627 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
636 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
628 page = $2
637 page = $2
629 title ||= $1 if page.blank?
638 title ||= $1 if page.blank?
630 end
639 end
631
640
632 if link_project && link_project.wiki
641 if link_project && link_project.wiki
633 # extract anchor
642 # extract anchor
634 anchor = nil
643 anchor = nil
635 if page =~ /^(.+?)\#(.+)$/
644 if page =~ /^(.+?)\#(.+)$/
636 page, anchor = $1, $2
645 page, anchor = $1, $2
637 end
646 end
638 anchor = sanitize_anchor_name(anchor) if anchor.present?
647 anchor = sanitize_anchor_name(anchor) if anchor.present?
639 # check if page exists
648 # check if page exists
640 wiki_page = link_project.wiki.find_page(page)
649 wiki_page = link_project.wiki.find_page(page)
641 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
650 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
642 "##{anchor}"
651 "##{anchor}"
643 else
652 else
644 case options[:wiki_links]
653 case options[:wiki_links]
645 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
654 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
646 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
655 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
647 else
656 else
648 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
657 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
649 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
658 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
650 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
659 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
651 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
660 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
652 end
661 end
653 end
662 end
654 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
663 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
655 else
664 else
656 # project or wiki doesn't exist
665 # project or wiki doesn't exist
657 all
666 all
658 end
667 end
659 else
668 else
660 all
669 all
661 end
670 end
662 end
671 end
663 end
672 end
664
673
665 # Redmine links
674 # Redmine links
666 #
675 #
667 # Examples:
676 # Examples:
668 # Issues:
677 # Issues:
669 # #52 -> Link to issue #52
678 # #52 -> Link to issue #52
670 # Changesets:
679 # Changesets:
671 # r52 -> Link to revision 52
680 # r52 -> Link to revision 52
672 # commit:a85130f -> Link to scmid starting with a85130f
681 # commit:a85130f -> Link to scmid starting with a85130f
673 # Documents:
682 # Documents:
674 # document#17 -> Link to document with id 17
683 # document#17 -> Link to document with id 17
675 # document:Greetings -> Link to the document with title "Greetings"
684 # document:Greetings -> Link to the document with title "Greetings"
676 # document:"Some document" -> Link to the document with title "Some document"
685 # document:"Some document" -> Link to the document with title "Some document"
677 # Versions:
686 # Versions:
678 # version#3 -> Link to version with id 3
687 # version#3 -> Link to version with id 3
679 # version:1.0.0 -> Link to version named "1.0.0"
688 # version:1.0.0 -> Link to version named "1.0.0"
680 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
689 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
681 # Attachments:
690 # Attachments:
682 # attachment:file.zip -> Link to the attachment of the current object named file.zip
691 # attachment:file.zip -> Link to the attachment of the current object named file.zip
683 # Source files:
692 # Source files:
684 # source:some/file -> Link to the file located at /some/file in the project's repository
693 # source:some/file -> Link to the file located at /some/file in the project's repository
685 # source:some/file@52 -> Link to the file's revision 52
694 # source:some/file@52 -> Link to the file's revision 52
686 # source:some/file#L120 -> Link to line 120 of the file
695 # source:some/file#L120 -> Link to line 120 of the file
687 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
696 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
688 # export:some/file -> Force the download of the file
697 # export:some/file -> Force the download of the file
689 # Forum messages:
698 # Forum messages:
690 # message#1218 -> Link to message with id 1218
699 # message#1218 -> Link to message with id 1218
691 #
700 #
692 # Links can refer other objects from other projects, using project identifier:
701 # Links can refer other objects from other projects, using project identifier:
693 # identifier:r52
702 # identifier:r52
694 # identifier:document:"Some document"
703 # identifier:document:"Some document"
695 # identifier:version:1.0.0
704 # identifier:version:1.0.0
696 # identifier:source:some/file
705 # identifier:source:some/file
697 def parse_redmine_links(text, project, obj, attr, only_path, options)
706 def parse_redmine_links(text, project, obj, attr, only_path, options)
698 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
707 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
699 leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
708 leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
700 link = nil
709 link = nil
701 if project_identifier
710 if project_identifier
702 project = Project.visible.find_by_identifier(project_identifier)
711 project = Project.visible.find_by_identifier(project_identifier)
703 end
712 end
704 if esc.nil?
713 if esc.nil?
705 if prefix.nil? && sep == 'r'
714 if prefix.nil? && sep == 'r'
706 if project
715 if project
707 repository = nil
716 repository = nil
708 if repo_identifier
717 if repo_identifier
709 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
718 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
710 else
719 else
711 repository = project.repository
720 repository = project.repository
712 end
721 end
713 # project.changesets.visible raises an SQL error because of a double join on repositories
722 # project.changesets.visible raises an SQL error because of a double join on repositories
714 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
723 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
715 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision},
724 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision},
716 :class => 'changeset',
725 :class => 'changeset',
717 :title => truncate_single_line(changeset.comments, :length => 100))
726 :title => truncate_single_line(changeset.comments, :length => 100))
718 end
727 end
719 end
728 end
720 elsif sep == '#'
729 elsif sep == '#'
721 oid = identifier.to_i
730 oid = identifier.to_i
722 case prefix
731 case prefix
723 when nil
732 when nil
724 if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status)
733 if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status)
725 anchor = comment_id ? "note-#{comment_id}" : nil
734 anchor = comment_id ? "note-#{comment_id}" : nil
726 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
735 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
727 :class => issue.css_classes,
736 :class => issue.css_classes,
728 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
737 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
729 end
738 end
730 when 'document'
739 when 'document'
731 if document = Document.visible.find_by_id(oid)
740 if document = Document.visible.find_by_id(oid)
732 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
741 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
733 :class => 'document'
742 :class => 'document'
734 end
743 end
735 when 'version'
744 when 'version'
736 if version = Version.visible.find_by_id(oid)
745 if version = Version.visible.find_by_id(oid)
737 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
746 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
738 :class => 'version'
747 :class => 'version'
739 end
748 end
740 when 'message'
749 when 'message'
741 if message = Message.visible.find_by_id(oid, :include => :parent)
750 if message = Message.visible.find_by_id(oid, :include => :parent)
742 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
751 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
743 end
752 end
744 when 'forum'
753 when 'forum'
745 if board = Board.visible.find_by_id(oid)
754 if board = Board.visible.find_by_id(oid)
746 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
755 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
747 :class => 'board'
756 :class => 'board'
748 end
757 end
749 when 'news'
758 when 'news'
750 if news = News.visible.find_by_id(oid)
759 if news = News.visible.find_by_id(oid)
751 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
760 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
752 :class => 'news'
761 :class => 'news'
753 end
762 end
754 when 'project'
763 when 'project'
755 if p = Project.visible.find_by_id(oid)
764 if p = Project.visible.find_by_id(oid)
756 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
765 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
757 end
766 end
758 end
767 end
759 elsif sep == ':'
768 elsif sep == ':'
760 # removes the double quotes if any
769 # removes the double quotes if any
761 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
770 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
762 case prefix
771 case prefix
763 when 'document'
772 when 'document'
764 if project && document = project.documents.visible.find_by_title(name)
773 if project && document = project.documents.visible.find_by_title(name)
765 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
774 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
766 :class => 'document'
775 :class => 'document'
767 end
776 end
768 when 'version'
777 when 'version'
769 if project && version = project.versions.visible.find_by_name(name)
778 if project && version = project.versions.visible.find_by_name(name)
770 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
779 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
771 :class => 'version'
780 :class => 'version'
772 end
781 end
773 when 'forum'
782 when 'forum'
774 if project && board = project.boards.visible.find_by_name(name)
783 if project && board = project.boards.visible.find_by_name(name)
775 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
784 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
776 :class => 'board'
785 :class => 'board'
777 end
786 end
778 when 'news'
787 when 'news'
779 if project && news = project.news.visible.find_by_title(name)
788 if project && news = project.news.visible.find_by_title(name)
780 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
789 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
781 :class => 'news'
790 :class => 'news'
782 end
791 end
783 when 'commit', 'source', 'export'
792 when 'commit', 'source', 'export'
784 if project
793 if project
785 repository = nil
794 repository = nil
786 if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
795 if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
787 repo_prefix, repo_identifier, name = $1, $2, $3
796 repo_prefix, repo_identifier, name = $1, $2, $3
788 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
797 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
789 else
798 else
790 repository = project.repository
799 repository = project.repository
791 end
800 end
792 if prefix == 'commit'
801 if prefix == 'commit'
793 if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"]))
802 if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"]))
794 link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
803 link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
795 :class => 'changeset',
804 :class => 'changeset',
796 :title => truncate_single_line(h(changeset.comments), :length => 100)
805 :title => truncate_single_line(h(changeset.comments), :length => 100)
797 end
806 end
798 else
807 else
799 if repository && User.current.allowed_to?(:browse_repository, project)
808 if repository && User.current.allowed_to?(:browse_repository, project)
800 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
809 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
801 path, rev, anchor = $1, $3, $5
810 path, rev, anchor = $1, $3, $5
802 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param,
811 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param,
803 :path => to_path_param(path),
812 :path => to_path_param(path),
804 :rev => rev,
813 :rev => rev,
805 :anchor => anchor},
814 :anchor => anchor},
806 :class => (prefix == 'export' ? 'source download' : 'source')
815 :class => (prefix == 'export' ? 'source download' : 'source')
807 end
816 end
808 end
817 end
809 repo_prefix = nil
818 repo_prefix = nil
810 end
819 end
811 when 'attachment'
820 when 'attachment'
812 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
821 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
813 if attachments && attachment = attachments.detect {|a| a.filename == name }
822 if attachments && attachment = attachments.detect {|a| a.filename == name }
814 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
823 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
815 :class => 'attachment'
824 :class => 'attachment'
816 end
825 end
817 when 'project'
826 when 'project'
818 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
827 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
819 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
828 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
820 end
829 end
821 end
830 end
822 end
831 end
823 end
832 end
824 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
833 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
825 end
834 end
826 end
835 end
827
836
828 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
837 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
829
838
830 def parse_sections(text, project, obj, attr, only_path, options)
839 def parse_sections(text, project, obj, attr, only_path, options)
831 return unless options[:edit_section_links]
840 return unless options[:edit_section_links]
832 text.gsub!(HEADING_RE) do
841 text.gsub!(HEADING_RE) do
833 heading = $1
842 heading = $1
834 @current_section += 1
843 @current_section += 1
835 if @current_section > 1
844 if @current_section > 1
836 content_tag('div',
845 content_tag('div',
837 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
846 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
838 :class => 'contextual',
847 :class => 'contextual',
839 :title => l(:button_edit_section)) + heading.html_safe
848 :title => l(:button_edit_section)) + heading.html_safe
840 else
849 else
841 heading
850 heading
842 end
851 end
843 end
852 end
844 end
853 end
845
854
846 # Headings and TOC
855 # Headings and TOC
847 # Adds ids and links to headings unless options[:headings] is set to false
856 # Adds ids and links to headings unless options[:headings] is set to false
848 def parse_headings(text, project, obj, attr, only_path, options)
857 def parse_headings(text, project, obj, attr, only_path, options)
849 return if options[:headings] == false
858 return if options[:headings] == false
850
859
851 text.gsub!(HEADING_RE) do
860 text.gsub!(HEADING_RE) do
852 level, attrs, content = $2.to_i, $3, $4
861 level, attrs, content = $2.to_i, $3, $4
853 item = strip_tags(content).strip
862 item = strip_tags(content).strip
854 anchor = sanitize_anchor_name(item)
863 anchor = sanitize_anchor_name(item)
855 # used for single-file wiki export
864 # used for single-file wiki export
856 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
865 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
857 @heading_anchors[anchor] ||= 0
866 @heading_anchors[anchor] ||= 0
858 idx = (@heading_anchors[anchor] += 1)
867 idx = (@heading_anchors[anchor] += 1)
859 if idx > 1
868 if idx > 1
860 anchor = "#{anchor}-#{idx}"
869 anchor = "#{anchor}-#{idx}"
861 end
870 end
862 @parsed_headings << [level, anchor, item]
871 @parsed_headings << [level, anchor, item]
863 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
872 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
864 end
873 end
865 end
874 end
866
875
867 MACROS_RE = /(
876 MACROS_RE = /(
868 (!)? # escaping
877 (!)? # escaping
869 (
878 (
870 \{\{ # opening tag
879 \{\{ # opening tag
871 ([\w]+) # macro name
880 ([\w]+) # macro name
872 (\(([^\n\r]*?)\))? # optional arguments
881 (\(([^\n\r]*?)\))? # optional arguments
873 ([\n\r].*?[\n\r])? # optional block of text
882 ([\n\r].*?[\n\r])? # optional block of text
874 \}\} # closing tag
883 \}\} # closing tag
875 )
884 )
876 )/mx unless const_defined?(:MACROS_RE)
885 )/mx unless const_defined?(:MACROS_RE)
877
886
878 MACRO_SUB_RE = /(
887 MACRO_SUB_RE = /(
879 \{\{
888 \{\{
880 macro\((\d+)\)
889 macro\((\d+)\)
881 \}\}
890 \}\}
882 )/x unless const_defined?(:MACRO_SUB_RE)
891 )/x unless const_defined?(:MACRO_SUB_RE)
883
892
884 # Extracts macros from text
893 # Extracts macros from text
885 def catch_macros(text)
894 def catch_macros(text)
886 macros = {}
895 macros = {}
887 text.gsub!(MACROS_RE) do
896 text.gsub!(MACROS_RE) do
888 all, macro = $1, $4.downcase
897 all, macro = $1, $4.downcase
889 if macro_exists?(macro) || all =~ MACRO_SUB_RE
898 if macro_exists?(macro) || all =~ MACRO_SUB_RE
890 index = macros.size
899 index = macros.size
891 macros[index] = all
900 macros[index] = all
892 "{{macro(#{index})}}"
901 "{{macro(#{index})}}"
893 else
902 else
894 all
903 all
895 end
904 end
896 end
905 end
897 macros
906 macros
898 end
907 end
899
908
900 # Executes and replaces macros in text
909 # Executes and replaces macros in text
901 def inject_macros(text, obj, macros, execute=true)
910 def inject_macros(text, obj, macros, execute=true)
902 text.gsub!(MACRO_SUB_RE) do
911 text.gsub!(MACRO_SUB_RE) do
903 all, index = $1, $2.to_i
912 all, index = $1, $2.to_i
904 orig = macros.delete(index)
913 orig = macros.delete(index)
905 if execute && orig && orig =~ MACROS_RE
914 if execute && orig && orig =~ MACROS_RE
906 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
915 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
907 if esc.nil?
916 if esc.nil?
908 h(exec_macro(macro, obj, args, block) || all)
917 h(exec_macro(macro, obj, args, block) || all)
909 else
918 else
910 h(all)
919 h(all)
911 end
920 end
912 elsif orig
921 elsif orig
913 h(orig)
922 h(orig)
914 else
923 else
915 h(all)
924 h(all)
916 end
925 end
917 end
926 end
918 end
927 end
919
928
920 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
929 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
921
930
922 # Renders the TOC with given headings
931 # Renders the TOC with given headings
923 def replace_toc(text, headings)
932 def replace_toc(text, headings)
924 text.gsub!(TOC_RE) do
933 text.gsub!(TOC_RE) do
925 # Keep only the 4 first levels
934 # Keep only the 4 first levels
926 headings = headings.select{|level, anchor, item| level <= 4}
935 headings = headings.select{|level, anchor, item| level <= 4}
927 if headings.empty?
936 if headings.empty?
928 ''
937 ''
929 else
938 else
930 div_class = 'toc'
939 div_class = 'toc'
931 div_class << ' right' if $1 == '>'
940 div_class << ' right' if $1 == '>'
932 div_class << ' left' if $1 == '<'
941 div_class << ' left' if $1 == '<'
933 out = "<ul class=\"#{div_class}\"><li>"
942 out = "<ul class=\"#{div_class}\"><li>"
934 root = headings.map(&:first).min
943 root = headings.map(&:first).min
935 current = root
944 current = root
936 started = false
945 started = false
937 headings.each do |level, anchor, item|
946 headings.each do |level, anchor, item|
938 if level > current
947 if level > current
939 out << '<ul><li>' * (level - current)
948 out << '<ul><li>' * (level - current)
940 elsif level < current
949 elsif level < current
941 out << "</li></ul>\n" * (current - level) + "</li><li>"
950 out << "</li></ul>\n" * (current - level) + "</li><li>"
942 elsif started
951 elsif started
943 out << '</li><li>'
952 out << '</li><li>'
944 end
953 end
945 out << "<a href=\"##{anchor}\">#{item}</a>"
954 out << "<a href=\"##{anchor}\">#{item}</a>"
946 current = level
955 current = level
947 started = true
956 started = true
948 end
957 end
949 out << '</li></ul>' * (current - root)
958 out << '</li></ul>' * (current - root)
950 out << '</li></ul>'
959 out << '</li></ul>'
951 end
960 end
952 end
961 end
953 end
962 end
954
963
955 # Same as Rails' simple_format helper without using paragraphs
964 # Same as Rails' simple_format helper without using paragraphs
956 def simple_format_without_paragraph(text)
965 def simple_format_without_paragraph(text)
957 text.to_s.
966 text.to_s.
958 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
967 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
959 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
968 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
960 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
969 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
961 html_safe
970 html_safe
962 end
971 end
963
972
964 def lang_options_for_select(blank=true)
973 def lang_options_for_select(blank=true)
965 (blank ? [["(auto)", ""]] : []) + languages_options
974 (blank ? [["(auto)", ""]] : []) + languages_options
966 end
975 end
967
976
968 def label_tag_for(name, option_tags = nil, options = {})
977 def label_tag_for(name, option_tags = nil, options = {})
969 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
978 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
970 content_tag("label", label_text)
979 content_tag("label", label_text)
971 end
980 end
972
981
973 def labelled_form_for(*args, &proc)
982 def labelled_form_for(*args, &proc)
974 args << {} unless args.last.is_a?(Hash)
983 args << {} unless args.last.is_a?(Hash)
975 options = args.last
984 options = args.last
976 if args.first.is_a?(Symbol)
985 if args.first.is_a?(Symbol)
977 options.merge!(:as => args.shift)
986 options.merge!(:as => args.shift)
978 end
987 end
979 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
988 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
980 form_for(*args, &proc)
989 form_for(*args, &proc)
981 end
990 end
982
991
983 def labelled_fields_for(*args, &proc)
992 def labelled_fields_for(*args, &proc)
984 args << {} unless args.last.is_a?(Hash)
993 args << {} unless args.last.is_a?(Hash)
985 options = args.last
994 options = args.last
986 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
995 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
987 fields_for(*args, &proc)
996 fields_for(*args, &proc)
988 end
997 end
989
998
990 def labelled_remote_form_for(*args, &proc)
999 def labelled_remote_form_for(*args, &proc)
991 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
1000 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
992 args << {} unless args.last.is_a?(Hash)
1001 args << {} unless args.last.is_a?(Hash)
993 options = args.last
1002 options = args.last
994 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
1003 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
995 form_for(*args, &proc)
1004 form_for(*args, &proc)
996 end
1005 end
997
1006
998 def error_messages_for(*objects)
1007 def error_messages_for(*objects)
999 html = ""
1008 html = ""
1000 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
1009 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
1001 errors = objects.map {|o| o.errors.full_messages}.flatten
1010 errors = objects.map {|o| o.errors.full_messages}.flatten
1002 if errors.any?
1011 if errors.any?
1003 html << "<div id='errorExplanation'><ul>\n"
1012 html << "<div id='errorExplanation'><ul>\n"
1004 errors.each do |error|
1013 errors.each do |error|
1005 html << "<li>#{h error}</li>\n"
1014 html << "<li>#{h error}</li>\n"
1006 end
1015 end
1007 html << "</ul></div>\n"
1016 html << "</ul></div>\n"
1008 end
1017 end
1009 html.html_safe
1018 html.html_safe
1010 end
1019 end
1011
1020
1012 def delete_link(url, options={})
1021 def delete_link(url, options={})
1013 options = {
1022 options = {
1014 :method => :delete,
1023 :method => :delete,
1015 :data => {:confirm => l(:text_are_you_sure)},
1024 :data => {:confirm => l(:text_are_you_sure)},
1016 :class => 'icon icon-del'
1025 :class => 'icon icon-del'
1017 }.merge(options)
1026 }.merge(options)
1018
1027
1019 link_to l(:button_delete), url, options
1028 link_to l(:button_delete), url, options
1020 end
1029 end
1021
1030
1022 def preview_link(url, form, target='preview', options={})
1031 def preview_link(url, form, target='preview', options={})
1023 content_tag 'a', l(:label_preview), {
1032 content_tag 'a', l(:label_preview), {
1024 :href => "#",
1033 :href => "#",
1025 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1034 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1026 :accesskey => accesskey(:preview)
1035 :accesskey => accesskey(:preview)
1027 }.merge(options)
1036 }.merge(options)
1028 end
1037 end
1029
1038
1030 def link_to_function(name, function, html_options={})
1039 def link_to_function(name, function, html_options={})
1031 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1040 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1032 end
1041 end
1033
1042
1034 # Helper to render JSON in views
1043 # Helper to render JSON in views
1035 def raw_json(arg)
1044 def raw_json(arg)
1036 arg.to_json.to_s.gsub('/', '\/').html_safe
1045 arg.to_json.to_s.gsub('/', '\/').html_safe
1037 end
1046 end
1038
1047
1039 def back_url
1048 def back_url
1040 url = params[:back_url]
1049 url = params[:back_url]
1041 if url.nil? && referer = request.env['HTTP_REFERER']
1050 if url.nil? && referer = request.env['HTTP_REFERER']
1042 url = CGI.unescape(referer.to_s)
1051 url = CGI.unescape(referer.to_s)
1043 end
1052 end
1044 url
1053 url
1045 end
1054 end
1046
1055
1047 def back_url_hidden_field_tag
1056 def back_url_hidden_field_tag
1048 url = back_url
1057 url = back_url
1049 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1058 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1050 end
1059 end
1051
1060
1052 def check_all_links(form_name)
1061 def check_all_links(form_name)
1053 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1062 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1054 " | ".html_safe +
1063 " | ".html_safe +
1055 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1064 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1056 end
1065 end
1057
1066
1058 def progress_bar(pcts, options={})
1067 def progress_bar(pcts, options={})
1059 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1068 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1060 pcts = pcts.collect(&:round)
1069 pcts = pcts.collect(&:round)
1061 pcts[1] = pcts[1] - pcts[0]
1070 pcts[1] = pcts[1] - pcts[0]
1062 pcts << (100 - pcts[1] - pcts[0])
1071 pcts << (100 - pcts[1] - pcts[0])
1063 width = options[:width] || '100px;'
1072 width = options[:width] || '100px;'
1064 legend = options[:legend] || ''
1073 legend = options[:legend] || ''
1065 content_tag('table',
1074 content_tag('table',
1066 content_tag('tr',
1075 content_tag('tr',
1067 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1076 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1068 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1077 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1069 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1078 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1070 ), :class => 'progress', :style => "width: #{width};").html_safe +
1079 ), :class => 'progress', :style => "width: #{width};").html_safe +
1071 content_tag('p', legend, :class => 'pourcent').html_safe
1080 content_tag('p', legend, :class => 'pourcent').html_safe
1072 end
1081 end
1073
1082
1074 def checked_image(checked=true)
1083 def checked_image(checked=true)
1075 if checked
1084 if checked
1076 image_tag 'toggle_check.png'
1085 image_tag 'toggle_check.png'
1077 end
1086 end
1078 end
1087 end
1079
1088
1080 def context_menu(url)
1089 def context_menu(url)
1081 unless @context_menu_included
1090 unless @context_menu_included
1082 content_for :header_tags do
1091 content_for :header_tags do
1083 javascript_include_tag('context_menu') +
1092 javascript_include_tag('context_menu') +
1084 stylesheet_link_tag('context_menu')
1093 stylesheet_link_tag('context_menu')
1085 end
1094 end
1086 if l(:direction) == 'rtl'
1095 if l(:direction) == 'rtl'
1087 content_for :header_tags do
1096 content_for :header_tags do
1088 stylesheet_link_tag('context_menu_rtl')
1097 stylesheet_link_tag('context_menu_rtl')
1089 end
1098 end
1090 end
1099 end
1091 @context_menu_included = true
1100 @context_menu_included = true
1092 end
1101 end
1093 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1102 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1094 end
1103 end
1095
1104
1096 def calendar_for(field_id)
1105 def calendar_for(field_id)
1097 include_calendar_headers_tags
1106 include_calendar_headers_tags
1098 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
1107 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
1099 end
1108 end
1100
1109
1101 def include_calendar_headers_tags
1110 def include_calendar_headers_tags
1102 unless @calendar_headers_tags_included
1111 unless @calendar_headers_tags_included
1103 @calendar_headers_tags_included = true
1112 @calendar_headers_tags_included = true
1104 content_for :header_tags do
1113 content_for :header_tags do
1105 start_of_week = Setting.start_of_week
1114 start_of_week = Setting.start_of_week
1106 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1115 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1107 # Redmine uses 1..7 (monday..sunday) in settings and locales
1116 # Redmine uses 1..7 (monday..sunday) in settings and locales
1108 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1117 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1109 start_of_week = start_of_week.to_i % 7
1118 start_of_week = start_of_week.to_i % 7
1110
1119
1111 tags = javascript_tag(
1120 tags = javascript_tag(
1112 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1121 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1113 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1122 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1114 path_to_image('/images/calendar.png') +
1123 path_to_image('/images/calendar.png') +
1115 "', showButtonPanel: true};")
1124 "', showButtonPanel: true};")
1116 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1125 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1117 unless jquery_locale == 'en'
1126 unless jquery_locale == 'en'
1118 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
1127 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
1119 end
1128 end
1120 tags
1129 tags
1121 end
1130 end
1122 end
1131 end
1123 end
1132 end
1124
1133
1125 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1134 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1126 # Examples:
1135 # Examples:
1127 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1136 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1128 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1137 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1129 #
1138 #
1130 def stylesheet_link_tag(*sources)
1139 def stylesheet_link_tag(*sources)
1131 options = sources.last.is_a?(Hash) ? sources.pop : {}
1140 options = sources.last.is_a?(Hash) ? sources.pop : {}
1132 plugin = options.delete(:plugin)
1141 plugin = options.delete(:plugin)
1133 sources = sources.map do |source|
1142 sources = sources.map do |source|
1134 if plugin
1143 if plugin
1135 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1144 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1136 elsif current_theme && current_theme.stylesheets.include?(source)
1145 elsif current_theme && current_theme.stylesheets.include?(source)
1137 current_theme.stylesheet_path(source)
1146 current_theme.stylesheet_path(source)
1138 else
1147 else
1139 source
1148 source
1140 end
1149 end
1141 end
1150 end
1142 super sources, options
1151 super sources, options
1143 end
1152 end
1144
1153
1145 # Overrides Rails' image_tag with themes and plugins support.
1154 # Overrides Rails' image_tag with themes and plugins support.
1146 # Examples:
1155 # Examples:
1147 # image_tag('image.png') # => picks image.png from the current theme or defaults
1156 # image_tag('image.png') # => picks image.png from the current theme or defaults
1148 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1157 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1149 #
1158 #
1150 def image_tag(source, options={})
1159 def image_tag(source, options={})
1151 if plugin = options.delete(:plugin)
1160 if plugin = options.delete(:plugin)
1152 source = "/plugin_assets/#{plugin}/images/#{source}"
1161 source = "/plugin_assets/#{plugin}/images/#{source}"
1153 elsif current_theme && current_theme.images.include?(source)
1162 elsif current_theme && current_theme.images.include?(source)
1154 source = current_theme.image_path(source)
1163 source = current_theme.image_path(source)
1155 end
1164 end
1156 super source, options
1165 super source, options
1157 end
1166 end
1158
1167
1159 # Overrides Rails' javascript_include_tag with plugins support
1168 # Overrides Rails' javascript_include_tag with plugins support
1160 # Examples:
1169 # Examples:
1161 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1170 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1162 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1171 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1163 #
1172 #
1164 def javascript_include_tag(*sources)
1173 def javascript_include_tag(*sources)
1165 options = sources.last.is_a?(Hash) ? sources.pop : {}
1174 options = sources.last.is_a?(Hash) ? sources.pop : {}
1166 if plugin = options.delete(:plugin)
1175 if plugin = options.delete(:plugin)
1167 sources = sources.map do |source|
1176 sources = sources.map do |source|
1168 if plugin
1177 if plugin
1169 "/plugin_assets/#{plugin}/javascripts/#{source}"
1178 "/plugin_assets/#{plugin}/javascripts/#{source}"
1170 else
1179 else
1171 source
1180 source
1172 end
1181 end
1173 end
1182 end
1174 end
1183 end
1175 super sources, options
1184 super sources, options
1176 end
1185 end
1177
1186
1178 def content_for(name, content = nil, &block)
1187 def content_for(name, content = nil, &block)
1179 @has_content ||= {}
1188 @has_content ||= {}
1180 @has_content[name] = true
1189 @has_content[name] = true
1181 super(name, content, &block)
1190 super(name, content, &block)
1182 end
1191 end
1183
1192
1184 def has_content?(name)
1193 def has_content?(name)
1185 (@has_content && @has_content[name]) || false
1194 (@has_content && @has_content[name]) || false
1186 end
1195 end
1187
1196
1188 def sidebar_content?
1197 def sidebar_content?
1189 has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1198 has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1190 end
1199 end
1191
1200
1192 def view_layouts_base_sidebar_hook_response
1201 def view_layouts_base_sidebar_hook_response
1193 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1202 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1194 end
1203 end
1195
1204
1196 def email_delivery_enabled?
1205 def email_delivery_enabled?
1197 !!ActionMailer::Base.perform_deliveries
1206 !!ActionMailer::Base.perform_deliveries
1198 end
1207 end
1199
1208
1200 # Returns the avatar image tag for the given +user+ if avatars are enabled
1209 # Returns the avatar image tag for the given +user+ if avatars are enabled
1201 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1210 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1202 def avatar(user, options = { })
1211 def avatar(user, options = { })
1203 if Setting.gravatar_enabled?
1212 if Setting.gravatar_enabled?
1204 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1213 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1205 email = nil
1214 email = nil
1206 if user.respond_to?(:mail)
1215 if user.respond_to?(:mail)
1207 email = user.mail
1216 email = user.mail
1208 elsif user.to_s =~ %r{<(.+?)>}
1217 elsif user.to_s =~ %r{<(.+?)>}
1209 email = $1
1218 email = $1
1210 end
1219 end
1211 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1220 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1212 else
1221 else
1213 ''
1222 ''
1214 end
1223 end
1215 end
1224 end
1216
1225
1217 def sanitize_anchor_name(anchor)
1226 def sanitize_anchor_name(anchor)
1218 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
1227 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
1219 anchor.gsub(%r{[^\p{Word}\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1228 anchor.gsub(%r{[^\p{Word}\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1220 else
1229 else
1221 # TODO: remove when ruby1.8 is no longer supported
1230 # TODO: remove when ruby1.8 is no longer supported
1222 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1231 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1223 end
1232 end
1224 end
1233 end
1225
1234
1226 # Returns the javascript tags that are included in the html layout head
1235 # Returns the javascript tags that are included in the html layout head
1227 def javascript_heads
1236 def javascript_heads
1228 tags = javascript_include_tag('jquery-1.7.2-ui-1.8.21-ujs-2.0.3', 'application')
1237 tags = javascript_include_tag('jquery-1.7.2-ui-1.8.21-ujs-2.0.3', 'application')
1229 unless User.current.pref.warn_on_leaving_unsaved == '0'
1238 unless User.current.pref.warn_on_leaving_unsaved == '0'
1230 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1239 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1231 end
1240 end
1232 tags
1241 tags
1233 end
1242 end
1234
1243
1235 def favicon
1244 def favicon
1236 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1245 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1237 end
1246 end
1238
1247
1239 def robot_exclusion_tag
1248 def robot_exclusion_tag
1240 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1249 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1241 end
1250 end
1242
1251
1243 # Returns true if arg is expected in the API response
1252 # Returns true if arg is expected in the API response
1244 def include_in_api_response?(arg)
1253 def include_in_api_response?(arg)
1245 unless @included_in_api_response
1254 unless @included_in_api_response
1246 param = params[:include]
1255 param = params[:include]
1247 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1256 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1248 @included_in_api_response.collect!(&:strip)
1257 @included_in_api_response.collect!(&:strip)
1249 end
1258 end
1250 @included_in_api_response.include?(arg.to_s)
1259 @included_in_api_response.include?(arg.to_s)
1251 end
1260 end
1252
1261
1253 # Returns options or nil if nometa param or X-Redmine-Nometa header
1262 # Returns options or nil if nometa param or X-Redmine-Nometa header
1254 # was set in the request
1263 # was set in the request
1255 def api_meta(options)
1264 def api_meta(options)
1256 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1265 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1257 # compatibility mode for activeresource clients that raise
1266 # compatibility mode for activeresource clients that raise
1258 # an error when unserializing an array with attributes
1267 # an error when unserializing an array with attributes
1259 nil
1268 nil
1260 else
1269 else
1261 options
1270 options
1262 end
1271 end
1263 end
1272 end
1264
1273
1265 private
1274 private
1266
1275
1267 def wiki_helper
1276 def wiki_helper
1268 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1277 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1269 extend helper
1278 extend helper
1270 return self
1279 return self
1271 end
1280 end
1272
1281
1273 def link_to_content_update(text, url_params = {}, html_options = {})
1282 def link_to_content_update(text, url_params = {}, html_options = {})
1274 link_to(text, url_params, html_options)
1283 link_to(text, url_params, html_options)
1275 end
1284 end
1276 end
1285 end
@@ -1,36 +1,27
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2012 Jean-Philippe Lang
4 # Copyright (C) 2006-2012 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 module GroupsHelper
20 module GroupsHelper
21 # Options for the new membership projects combo-box
22 def options_for_membership_project_select(user, projects)
23 options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
24 options << project_tree_options_for_select(projects) do |p|
25 {:disabled => (user.projects.include?(p))}
26 end
27 options
28 end
29
30 def group_settings_tabs
21 def group_settings_tabs
31 tabs = [{:name => 'general', :partial => 'groups/general', :label => :label_general},
22 tabs = [{:name => 'general', :partial => 'groups/general', :label => :label_general},
32 {:name => 'users', :partial => 'groups/users', :label => :label_user_plural},
23 {:name => 'users', :partial => 'groups/users', :label => :label_user_plural},
33 {:name => 'memberships', :partial => 'groups/memberships', :label => :label_project_plural}
24 {:name => 'memberships', :partial => 'groups/memberships', :label => :label_project_plural}
34 ]
25 ]
35 end
26 end
36 end
27 end
@@ -1,63 +1,54
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2012 Jean-Philippe Lang
4 # Copyright (C) 2006-2012 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 module UsersHelper
20 module UsersHelper
21 def users_status_options_for_select(selected)
21 def users_status_options_for_select(selected)
22 user_count_by_status = User.count(:group => 'status').to_hash
22 user_count_by_status = User.count(:group => 'status').to_hash
23 options_for_select([[l(:label_all), ''],
23 options_for_select([[l(:label_all), ''],
24 ["#{l(:status_active)} (#{user_count_by_status[1].to_i})", '1'],
24 ["#{l(:status_active)} (#{user_count_by_status[1].to_i})", '1'],
25 ["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", '2'],
25 ["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", '2'],
26 ["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", '3']], selected.to_s)
26 ["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", '3']], selected.to_s)
27 end
27 end
28
28
29 # Options for the new membership projects combo-box
30 def options_for_membership_project_select(user, projects)
31 options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
32 options << project_tree_options_for_select(projects) do |p|
33 {:disabled => (user.projects.include?(p))}
34 end
35 options
36 end
37
38 def user_mail_notification_options(user)
29 def user_mail_notification_options(user)
39 user.valid_notification_options.collect {|o| [l(o.last), o.first]}
30 user.valid_notification_options.collect {|o| [l(o.last), o.first]}
40 end
31 end
41
32
42 def change_status_link(user)
33 def change_status_link(user)
43 url = {:controller => 'users', :action => 'update', :id => user, :page => params[:page], :status => params[:status], :tab => nil}
34 url = {:controller => 'users', :action => 'update', :id => user, :page => params[:page], :status => params[:status], :tab => nil}
44
35
45 if user.locked?
36 if user.locked?
46 link_to l(:button_unlock), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock'
37 link_to l(:button_unlock), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock'
47 elsif user.registered?
38 elsif user.registered?
48 link_to l(:button_activate), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock'
39 link_to l(:button_activate), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock'
49 elsif user != User.current
40 elsif user != User.current
50 link_to l(:button_lock), url.merge(:user => {:status => User::STATUS_LOCKED}), :method => :put, :class => 'icon icon-lock'
41 link_to l(:button_lock), url.merge(:user => {:status => User::STATUS_LOCKED}), :method => :put, :class => 'icon icon-lock'
51 end
42 end
52 end
43 end
53
44
54 def user_settings_tabs
45 def user_settings_tabs
55 tabs = [{:name => 'general', :partial => 'users/general', :label => :label_general},
46 tabs = [{:name => 'general', :partial => 'users/general', :label => :label_general},
56 {:name => 'memberships', :partial => 'users/memberships', :label => :label_project_plural}
47 {:name => 'memberships', :partial => 'users/memberships', :label => :label_project_plural}
57 ]
48 ]
58 if Group.all.any?
49 if Group.all.any?
59 tabs.insert 1, {:name => 'groups', :partial => 'users/groups', :label => :label_group_plural}
50 tabs.insert 1, {:name => 'groups', :partial => 'users/groups', :label => :label_group_plural}
60 end
51 end
61 tabs
52 tabs
62 end
53 end
63 end
54 end
General Comments 0
You need to be logged in to leave comments. Login now