comparison app/helpers/application_helper.rb @ 1339:c03a6c3c4db9 luisf

Merge
author luisf <luis.figueira@eecs.qmul.ac.uk>
date Thu, 20 Jun 2013 14:34:42 +0100
parents 0a574315af3e
children 4f746d8966dd 51364c0cd58f
comparison
equal deleted inserted replaced
1079:413d1d9c3efa 1339:c03a6c3c4db9
1 # encoding: utf-8 1 # encoding: utf-8
2 # 2 #
3 # Redmine - project management software 3 # Redmine - project management software
4 # Copyright (C) 2006-2011 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.
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 # Display a link to remote if user is authorized
47 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
48 url = options[:url] || {}
49 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
50 end
51
52 # Displays a link to user's account page if active 46 # Displays a link to user's account page if active
53 def link_to_user(user, options={}) 47 def link_to_user(user, options={})
54 if user.is_a?(User) 48 if user.is_a?(User)
55 name = h(user.name(options[:format])) 49 name = h(user.name(options[:format]))
56 if user.active? 50 if user.active? || (User.current.admin? && user.logged?)
57 link_to(name, :controller => 'users', :action => 'show', :id => user) 51 link_to name, user_path(user), :class => user.css_classes
58 else 52 else
59 name 53 name
60 end 54 end
61 else 55 else
62 h(user.to_s) 56 h(user.to_s)
68 # 62 #
69 # link_to_issue(issue) # => Defect #6: This is the subject 63 # link_to_issue(issue) # => Defect #6: This is the subject
70 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i... 64 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
71 # link_to_issue(issue, :subject => false) # => Defect #6 65 # link_to_issue(issue, :subject => false) # => Defect #6
72 # 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
73 # 68 #
74 def link_to_issue(issue, options={}) 69 def link_to_issue(issue, options={})
75 title = nil 70 title = nil
76 subject = nil 71 subject = nil
72 text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}"
77 if options[:subject] == false 73 if options[:subject] == false
78 title = truncate(issue.subject, :length => 60) 74 title = truncate(issue.subject, :length => 60)
79 else 75 else
80 subject = issue.subject 76 subject = issue.subject
81 if options[:truncate] 77 if options[:truncate]
82 subject = truncate(subject, :length => options[:truncate]) 78 subject = truncate(subject, :length => options[:truncate])
83 end 79 end
84 end 80 end
85 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, 81 s = link_to text, issue_path(issue), :class => issue.css_classes, :title => title
86 :class => issue.css_classes, 82 s << h(": #{subject}") if subject
87 :title => title 83 s = h("#{issue.project} - ") + s if options[:project]
88 s << ": #{h subject}" if subject
89 s = "#{h issue.project} - " + s if options[:project]
90 s 84 s
91 end 85 end
92 86
93 # Generates a link to an attachment. 87 # Generates a link to an attachment.
94 # Options: 88 # Options:
95 # * :text - Link text (default to attachment filename) 89 # * :text - Link text (default to attachment filename)
96 # * :download - Force download (default: false) 90 # * :download - Force download (default: false)
97 def link_to_attachment(attachment, options={}) 91 def link_to_attachment(attachment, options={})
98 text = options.delete(:text) || attachment.filename 92 text = options.delete(:text) || attachment.filename
99 action = options.delete(:download) ? 'download' : 'show' 93 action = options.delete(:download) ? 'download' : 'show'
94 opt_only_path = {}
95 opt_only_path[:only_path] = (options[:only_path] == false ? false : true)
96 options.delete(:only_path)
100 link_to(h(text), 97 link_to(h(text),
101 {:controller => 'attachments', :action => action, 98 {:controller => 'attachments', :action => action,
102 :id => attachment, :filename => attachment.filename }, 99 :id => attachment, :filename => attachment.filename}.merge(opt_only_path),
103 options) 100 options)
104 end 101 end
105 102
106 # Generates a link to a SCM revision 103 # Generates a link to a SCM revision
107 # Options: 104 # Options:
108 # * :text - Link text (default to the formatted revision) 105 # * :text - Link text (default to the formatted revision)
109 def link_to_revision(revision, project, options={}) 106 def link_to_revision(revision, repository, options={})
107 if repository.is_a?(Project)
108 repository = repository.repository
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 112 link_to(
113 link_to(h(text), {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev}, 113 h(text),
114 :title => l(:label_revision_id, format_revision(revision))) 114 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
115 :title => l(:label_revision_id, format_revision(revision))
116 )
115 end 117 end
116 118
117 # Generates a link to a message 119 # Generates a link to a message
118 def link_to_message(message, options={}, html_options = nil) 120 def link_to_message(message, options={}, html_options = nil)
119 link_to( 121 link_to(
120 h(truncate(message.subject, :length => 60)), 122 h(truncate(message.subject, :length => 60)),
121 { :controller => 'messages', :action => 'show', 123 { :controller => 'messages', :action => 'show',
122 :board_id => message.board_id, 124 :board_id => message.board_id,
123 :id => message.root, 125 :id => (message.parent_id || message.id),
124 :r => (message.parent_id && message.id), 126 :r => (message.parent_id && message.id),
125 :anchor => (message.parent_id ? "message-#{message.id}" : nil) 127 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
126 }.merge(options), 128 }.merge(options),
127 html_options 129 html_options
128 ) 130 )
135 # link_to_project(project, :action=>'settings') # => link to project settings 137 # link_to_project(project, :action=>'settings') # => link to project settings
136 # 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
137 # 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)
138 # 140 #
139 def link_to_project(project, options={}, html_options = nil) 141 def link_to_project(project, options={}, html_options = nil)
140 if project.active? 142 if project.archived?
143 h(project)
144 else
141 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options) 145 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
142 link_to(h(project), url, html_options) 146 link_to(h(project), url, html_options)
143 else 147 end
144 h(project) 148 end
145 end 149
150 def wiki_page_path(page, options={})
151 url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options))
152 end
153
154 def thumbnail_tag(attachment)
155 link_to image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment)),
156 {:controller => 'attachments', :action => 'show', :id => attachment, :filename => attachment.filename},
157 :title => attachment.filename
146 end 158 end
147 159
148 def toggle_link(name, id, options={}) 160 def toggle_link(name, id, options={})
149 onclick = "Element.toggle('#{id}'); " 161 onclick = "$('##{id}').toggle(); "
150 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ") 162 onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ")
151 onclick << "return false;" 163 onclick << "return false;"
152 link_to(name, "#", :onclick => onclick) 164 link_to(name, "#", :onclick => onclick)
153 end 165 end
154 166
155 def image_to_function(name, function, html_options = {}) 167 def image_to_function(name, function, html_options = {})
158 :type => "image", :src => image_path(name), 170 :type => "image", :src => image_path(name),
159 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" 171 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
160 })) 172 }))
161 end 173 end
162 174
163 def prompt_to_remote(name, text, param, url, html_options = {})
164 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
165 link_to name, {}, html_options
166 end
167
168 def format_activity_title(text) 175 def format_activity_title(text)
169 h(truncate_single_line(text, :length => 100)) 176 h(truncate_single_line(text, :length => 100))
170 end 177 end
171 178
172 def format_activity_day(date) 179 def format_activity_day(date)
173 date == Date.today ? l(:label_today).titleize : format_date(date) 180 date == User.current.today ? l(:label_today).titleize : format_date(date)
174 end 181 end
175 182
176 def format_activity_description(text) 183 def format_activity_description(text)
177 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />") 184 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
185 ).gsub(/[\r\n]+/, "<br />").html_safe
178 end 186 end
179 187
180 def format_version_name(version) 188 def format_version_name(version)
181 if version.project == @project 189 if version.project == @project
182 h(version) 190 h(version)
189 if date 197 if date
190 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))
191 end 199 end
192 end 200 end
193 201
194 def render_page_hierarchy(pages, node=nil, options={}) 202 # Renders a tree of projects as a nested set of unordered lists
195 content = '' 203 # The given collection may be a subset of the whole project tree
196 if pages[node] 204 # (eg. some intermediate nodes are private and can not be seen)
197 content << "<ul class=\"pages-hierarchy\">\n" 205 def render_project_nested_lists(projects)
198 pages[node].each do |page|
199 content << "<li>"
200 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
201 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
202 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
203 content << "</li>\n"
204 end
205 content << "</ul>\n"
206 end
207 content.html_safe
208 end
209
210 # Renders flash messages
211 def render_flash_messages
212 s = ''
213 flash.each do |k,v|
214 s << content_tag('div', v, :class => "flash #{k}")
215 end
216 s.html_safe
217 end
218
219 # Renders tabs and their content
220 def render_tabs(tabs)
221 if tabs.any?
222 render :partial => 'common/tabs', :locals => {:tabs => tabs}
223 else
224 content_tag 'p', l(:label_no_data), :class => "nodata"
225 end
226 end
227
228 # Renders the project quick-jump box
229 def render_project_jump_box
230 return unless User.current.logged?
231 projects = User.current.memberships.collect(&:project).compact.uniq
232 if projects.any?
233 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
234 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
235 '<option value="" disabled="disabled">---</option>'
236 s << project_tree_options_for_select(projects, :selected => @project) do |p|
237 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
238 end
239 s << '</select>'
240 s.html_safe
241 end
242 end
243
244 def project_tree_options_for_select(projects, options = {})
245 s = ''
246 project_tree(projects) do |project, level|
247 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
248 tag_options = {:value => project.id}
249 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
250 tag_options[:selected] = 'selected'
251 else
252 tag_options[:selected] = nil
253 end
254 tag_options.merge!(yield(project)) if block_given?
255 s << content_tag('option', name_prefix + h(project), tag_options)
256 end
257 s.html_safe
258 end
259
260 # Yields the given block for each project with its level in the tree
261 #
262 # Wrapper for Project#project_tree
263 def project_tree(projects, &block)
264 Project.project_tree(projects, &block)
265 end
266
267 def project_nested_ul(projects, &block)
268 s = '' 206 s = ''
269 if projects.any? 207 if projects.any?
270 ancestors = [] 208 ancestors = []
209 original_project = @project
271 projects.sort_by(&:lft).each do |project| 210 projects.sort_by(&:lft).each do |project|
211 # set the project environment to please macros.
212 @project = project
272 if (ancestors.empty? || project.is_descendant_of?(ancestors.last)) 213 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
273 s << "<ul>\n" 214 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
274 else 215 else
275 ancestors.pop 216 ancestors.pop
276 s << "</li>" 217 s << "</li>"
277 while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) 218 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
278 ancestors.pop 219 ancestors.pop
279 s << "</ul></li>\n" 220 s << "</ul></li>\n"
280 end 221 end
281 end 222 end
282 s << "<li>" 223 classes = (ancestors.empty? ? 'root' : 'child')
283 s << yield(project).to_s 224 s << "<li class='#{classes}'><div class='#{classes}'>"
225 s << h(block_given? ? yield(project) : project.name)
226 s << "</div>\n"
284 ancestors << project 227 ancestors << project
285 end 228 end
286 s << ("</li></ul>\n" * ancestors.size) 229 s << ("</li></ul>\n" * ancestors.size)
230 @project = original_project
287 end 231 end
288 s.html_safe 232 s.html_safe
233 end
234
235 def render_page_hierarchy(pages, node=nil, options={})
236 content = ''
237 if pages[node]
238 content << "<ul class=\"pages-hierarchy\">\n"
239 pages[node].each do |page|
240 content << "<li>"
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))
243 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
244 content << "</li>\n"
245 end
246 content << "</ul>\n"
247 end
248 content.html_safe
249 end
250
251 # Renders flash messages
252 def render_flash_messages
253 s = ''
254 flash.each do |k,v|
255 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
256 end
257 s.html_safe
258 end
259
260 # Renders tabs and their content
261 def render_tabs(tabs)
262 if tabs.any?
263 render :partial => 'common/tabs', :locals => {:tabs => tabs}
264 else
265 content_tag 'p', l(:label_no_data), :class => "nodata"
266 end
267 end
268
269 # Renders the project quick-jump box
270 def render_project_jump_box
271 return unless User.current.logged?
272 projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq
273 if projects.any?
274 options =
275 ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
276 '<option value="" disabled="disabled">---</option>').html_safe
277
278 options << project_tree_options_for_select(projects, :selected => @project) do |p|
279 { :value => project_path(:id => p, :jump => current_menu_item) }
280 end
281
282 select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
283 end
284 end
285
286 def project_tree_options_for_select(projects, options = {})
287 s = ''
288 project_tree(projects) do |project, level|
289 name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
290 tag_options = {:value => project.id}
291 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
292 tag_options[:selected] = 'selected'
293 else
294 tag_options[:selected] = nil
295 end
296 tag_options.merge!(yield(project)) if block_given?
297 s << content_tag('option', name_prefix + h(project), tag_options)
298 end
299 s.html_safe
300 end
301
302 # Yields the given block for each project with its level in the tree
303 #
304 # Wrapper for Project#project_tree
305 def project_tree(projects, &block)
306 Project.project_tree(projects, &block)
289 end 307 end
290 308
291 def principals_check_box_tags(name, principals) 309 def principals_check_box_tags(name, principals)
292 s = '' 310 s = ''
293 principals.sort.each do |principal| 311 principals.sort.each do |principal|
294 312
295 if principal.type == "User": 313 if principal.type == "User"
296 s << "<label>#{ check_box_tag name, principal.id, false } #{link_to_user principal}</label>\n" 314 s << "<label>#{ check_box_tag name, principal.id, false } #{link_to_user principal}</label>\n"
297 else 315 else
298 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal} (Group)</label>\n" 316 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal} (Group)</label>\n"
299 end 317 end
300 318
301 end 319 end
302 s.html_safe 320 s.html_safe
303 end 321 end
304 322
305 # Returns a string for users/groups option tags 323 # Returns a string for users/groups option tags
306 def principals_options_for_select(collection, selected=nil) 324 def principals_options_for_select(collection, selected=nil)
307 s = '' 325 s = ''
326 if collection.include?(User.current)
327 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
328 end
308 groups = '' 329 groups = ''
309 collection.sort.each do |element| 330 collection.sort.each do |element|
310 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) 331 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
311 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>) 332 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
312 end 333 end
313 unless groups.empty? 334 unless groups.empty?
314 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>) 335 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
315 end 336 end
316 s 337 s.html_safe
338 end
339
340 # Options for the new membership projects combo-box
341 def options_for_membership_project_select(principal, projects)
342 options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
343 options << project_tree_options_for_select(projects) do |p|
344 {:disabled => principal.projects.include?(p)}
345 end
346 options
317 end 347 end
318 348
319 # Truncates and returns the string as a single line 349 # Truncates and returns the string as a single line
320 def truncate_single_line(string, *args) 350 def truncate_single_line(string, *args)
321 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ') 351 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
329 else 359 else
330 string 360 string
331 end 361 end
332 end 362 end
333 363
364 def anchor(text)
365 text.to_s.gsub(' ', '_')
366 end
367
334 def html_hours(text) 368 def html_hours(text)
335 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe 369 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
336 end 370 end
337 371
338 def authoring(created, author, options={}) 372 def authoring(created, author, options={})
340 end 374 end
341 375
342 def time_tag(time) 376 def time_tag(time)
343 text = distance_of_time_in_words(Time.now, time) 377 text = distance_of_time_in_words(Time.now, time)
344 if @project 378 if @project
345 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time)) 379 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
346 else 380 else
347 content_tag('acronym', text, :title => format_time(time)) 381 content_tag('acronym', text, :title => format_time(time))
348 end 382 end
349 end 383 end
350 384
385 def syntax_highlight_lines(name, content)
386 lines = []
387 syntax_highlight(name, content).each_line { |line| lines << line }
388 lines
389 end
390
351 def syntax_highlight(name, content) 391 def syntax_highlight(name, content)
352 Redmine::SyntaxHighlighting.highlight_by_filename(content, name) 392 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
353 end 393 end
354 394
355 def to_path_param(path) 395 def to_path_param(path)
356 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?} 396 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
397 str.blank? ? nil : str
357 end 398 end
358 399
359 def pagination_links_full(paginator, count=nil, options={}) 400 def pagination_links_full(paginator, count=nil, options={})
360 page_param = options.delete(:page_param) || :page 401 page_param = options.delete(:page_param) || :page
361 per_page_links = options.delete(:per_page_links) 402 per_page_links = options.delete(:per_page_links)
380 url_param.merge(page_param => paginator.current.next)) 421 url_param.merge(page_param => paginator.current.next))
381 end 422 end
382 423
383 unless count.nil? 424 unless count.nil?
384 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})" 425 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
385 if per_page_links != false && links = per_page_links(paginator.items_per_page) 426 if per_page_links != false && links = per_page_links(paginator.items_per_page, count)
386 html << " | #{links}" 427 html << " | #{links}"
387 end 428 end
388 end 429 end
389 430
390 html.html_safe 431 html.html_safe
391 end 432 end
392 433
393 def per_page_links(selected=nil) 434 def per_page_links(selected=nil, item_count=nil)
394 links = Setting.per_page_options_array.collect do |n| 435 values = Setting.per_page_options_array
436 if item_count && values.any?
437 if item_count > values.first
438 max = values.detect {|value| value >= item_count} || item_count
439 else
440 max = item_count
441 end
442 values = values.select {|value| value <= max || value == selected}
443 end
444 if values.empty? || (values.size == 1 && values.first == selected)
445 return nil
446 end
447 links = values.collect do |n|
395 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n)) 448 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
396 end 449 end
397 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil 450 l(:label_display_per_page, links.join(', '))
398 end 451 end
399 452
400 def reorder_links(name, url, method = :post) 453 def reorder_links(name, url, method = :post)
401 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), 454 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
402 url.merge({"#{name}[move_to]" => 'highest'}), 455 url.merge({"#{name}[move_to]" => 'highest'}),
433 ancestors = (@project.root? ? [] : @project.ancestors.visible.all) 486 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
434 if ancestors.any? 487 if ancestors.any?
435 root = ancestors.shift 488 root = ancestors.shift
436 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root') 489 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
437 if ancestors.size > 2 490 if ancestors.size > 2
438 b << '&#8230;' 491 b << '&#8230;'
439 ancestors = ancestors[-2, 2] 492 ancestors = ancestors[-2, 2]
440 end 493 end
441 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') } 494 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
442 b = b.join(' &#187; ') 495 b = b.join(' &#187; ').html_safe
443 b << (' &#187;') 496 b << (' &#187;'.html_safe)
444 end 497 end
445 498
446 pname << h(@project) 499 pname << h(@project)
447 500
448 a = [pname, b] 501 a = [pname, b]
468 css = [] 521 css = []
469 if theme = Redmine::Themes.theme(Setting.ui_theme) 522 if theme = Redmine::Themes.theme(Setting.ui_theme)
470 css << 'theme-' + theme.name 523 css << 'theme-' + theme.name
471 end 524 end
472 525
473 css << 'controller-' + params[:controller] 526 css << 'controller-' + controller_name
474 css << 'action-' + params[:action] 527 css << 'action-' + action_name
475 css.join(' ') 528 css.join(' ')
476 end 529 end
477 530
478 def accesskey(s) 531 def accesskey(s)
479 Redmine::AccessKeys.key_for s 532 Redmine::AccessKeys.key_for s
498 end 551 end
499 return '' if text.blank? 552 return '' if text.blank?
500 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) 553 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
501 only_path = options.delete(:only_path) == false ? false : true 554 only_path = options.delete(:only_path) == false ? false : true
502 555
556 text = text.dup
557 macros = catch_macros(text)
503 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) 558 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
504 559
505 @parsed_headings = [] 560 @parsed_headings = []
506 @heading_anchors = {} 561 @heading_anchors = {}
507 @current_section = 0 if options[:edit_section_links] 562 @current_section = 0 if options[:edit_section_links]
508 563
509 parse_sections(text, project, obj, attr, only_path, options) 564 parse_sections(text, project, obj, attr, only_path, options)
510 text = parse_non_pre_blocks(text) do |text| 565 text = parse_non_pre_blocks(text, obj, macros) do |text|
511 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros].each do |method_name| 566 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
512 send method_name, text, project, obj, attr, only_path, options 567 send method_name, text, project, obj, attr, only_path, options
513 end 568 end
514 end 569 end
515 parse_headings(text, project, obj, attr, only_path, options) 570 parse_headings(text, project, obj, attr, only_path, options)
516 571
517 if @parsed_headings.any? 572 if @parsed_headings.any?
518 replace_toc(text, @parsed_headings) 573 replace_toc(text, @parsed_headings)
519 end 574 end
520 575
521 text 576 text.html_safe
522 end 577 end
523 578
524 def parse_non_pre_blocks(text) 579 def parse_non_pre_blocks(text, obj, macros)
525 s = StringScanner.new(text) 580 s = StringScanner.new(text)
526 tags = [] 581 tags = []
527 parsed = '' 582 parsed = ''
528 while !s.eos? 583 while !s.eos?
529 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im) 584 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
530 text, full_tag, closing, tag = s[1], s[2], s[3], s[4] 585 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
531 if tags.empty? 586 if tags.empty?
532 yield text 587 yield text
588 inject_macros(text, obj, macros) if macros.any?
589 else
590 inject_macros(text, obj, macros, false) if macros.any?
533 end 591 end
534 parsed << text 592 parsed << text
535 if tag 593 if tag
536 if closing 594 if closing
537 if tags.last == tag.downcase 595 if tags.last == tag.downcase
545 end 603 end
546 # Close any non closing tags 604 # Close any non closing tags
547 while tag = tags.pop 605 while tag = tags.pop
548 parsed << "</#{tag}>" 606 parsed << "</#{tag}>"
549 end 607 end
550 parsed.html_safe 608 parsed
551 end 609 end
552 610
553 def parse_inline_attachments(text, project, obj, attr, only_path, options) 611 def parse_inline_attachments(text, project, obj, attr, only_path, options)
554 # when using an image link, try to use an attachment, if possible 612 # when using an image link, try to use an attachment, if possible
555 if options[:attachments] || (obj && obj.respond_to?(:attachments)) 613 attachments = options[:attachments] || []
556 attachments = options[:attachments] || obj.attachments 614 attachments += obj.attachments if obj.respond_to?(:attachments)
615 if attachments.present?
557 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m| 616 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
558 filename, ext, alt, alttext = $1.downcase, $2, $3, $4 617 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
559 # search for the picture in attachments 618 # search for the picture in attachments
560 if found = Attachment.latest_attach(attachments, filename) 619 if found = Attachment.latest_attach(attachments, filename)
561 image_url = url_for :only_path => only_path, :controller => 'attachments', 620 image_url = url_for :only_path => only_path, :controller => 'attachments',
562 :action => 'download', :id => found 621 :action => 'download', :id => found
563 desc = found.description.to_s.gsub('"', '') 622 desc = found.description.to_s.gsub('"', '')
564 if !desc.blank? && alttext.blank? 623 if !desc.blank? && alttext.blank?
565 alt = " title=\"#{desc}\" alt=\"#{desc}\"" 624 alt = " title=\"#{desc}\" alt=\"#{desc}\""
566 end 625 end
567 "src=\"#{image_url}\"#{alt}".html_safe 626 "src=\"#{image_url}\"#{alt}"
568 else 627 else
569 m.html_safe 628 m
570 end 629 end
571 end 630 end
572 end 631 end
573 end 632 end
574 633
608 case options[:wiki_links] 667 case options[:wiki_links]
609 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '') 668 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
610 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export 669 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
611 else 670 else
612 wiki_page_id = page.present? ? Wiki.titleize(page) : nil 671 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
613 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor) 672 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
673 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
674 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
614 end 675 end
615 end 676 end
616 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new'))) 677 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
617 else 678 else
618 # project or wiki doesn't exist 679 # project or wiki doesn't exist
619 all.html_safe 680 all
620 end 681 end
621 else 682 else
622 all.html_safe 683 all
623 end 684 end
624 end 685 end
625 end 686 end
626 687
627 # Redmine links 688 # Redmine links
654 # Links can refer other objects from other projects, using project identifier: 715 # Links can refer other objects from other projects, using project identifier:
655 # identifier:r52 716 # identifier:r52
656 # identifier:document:"Some document" 717 # identifier:document:"Some document"
657 # identifier:version:1.0.0 718 # identifier:version:1.0.0
658 # identifier:source:some/file 719 # identifier:source:some/file
659 def parse_redmine_links(text, project, obj, attr, only_path, options) 720 def parse_redmine_links(text, default_project, obj, attr, only_path, options)
660 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|forum|news|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m| 721 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|
661 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10 722 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
662 link = nil 723 link = nil
724 project = default_project
663 if project_identifier 725 if project_identifier
664 project = Project.visible.find_by_identifier(project_identifier) 726 project = Project.visible.find_by_identifier(project_identifier)
665 end 727 end
666 if esc.nil? 728 if esc.nil?
667 if prefix.nil? && sep == 'r' 729 if prefix.nil? && sep == 'r'
668 # project.changesets.visible raises an SQL error because of a double join on repositories 730 if project
669 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier)) 731 repository = nil
670 link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision}, 732 if repo_identifier
671 :class => 'changeset', 733 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
672 :title => truncate_single_line(changeset.comments, :length => 100)) 734 else
735 repository = project.repository
736 end
737 # project.changesets.visible raises an SQL error because of a double join on repositories
738 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
739 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},
740 :class => 'changeset',
741 :title => truncate_single_line(changeset.comments, :length => 100))
742 end
673 end 743 end
674 elsif sep == '#' 744 elsif sep == '#'
675 oid = identifier.to_i 745 oid = identifier.to_i
676 case prefix 746 case prefix
677 when nil 747 when nil
678 if issue = Issue.visible.find_by_id(oid, :include => :status) 748 if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status)
679 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid}, 749 anchor = comment_id ? "note-#{comment_id}" : nil
750 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
680 :class => issue.css_classes, 751 :class => issue.css_classes,
681 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") 752 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
682 end 753 end
683 when 'document' 754 when 'document'
684 if document = Document.visible.find_by_id(oid) 755 if document = Document.visible.find_by_id(oid)
731 when 'news' 802 when 'news'
732 if project && news = project.news.visible.find_by_title(name) 803 if project && news = project.news.visible.find_by_title(name)
733 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, 804 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
734 :class => 'news' 805 :class => 'news'
735 end 806 end
736 when 'commit' 807 when 'commit', 'source', 'export'
737 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"])) 808 if project
738 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier}, 809 repository = nil
739 :class => 'changeset', 810 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
740 :title => truncate_single_line(h(changeset.comments), :length => 100) 811 repo_prefix, repo_identifier, name = $1, $2, $3
741 end 812 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
742 when 'source', 'export' 813 else
743 if project && project.repository && User.current.allowed_to?(:browse_repository, project) 814 repository = project.repository
744 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$} 815 end
745 path, rev, anchor = $1, $3, $5 816 if prefix == 'commit'
746 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, 817 if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"]))
747 :path => to_path_param(path), 818 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},
748 :rev => rev, 819 :class => 'changeset',
749 :anchor => anchor, 820 :title => truncate_single_line(h(changeset.comments), :length => 100)
750 :format => (prefix == 'export' ? 'raw' : nil)}, 821 end
751 :class => (prefix == 'export' ? 'source download' : 'source') 822 else
823 if repository && User.current.allowed_to?(:browse_repository, project)
824 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
825 path, rev, anchor = $1, $3, $5
826 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param,
827 :path => to_path_param(path),
828 :rev => rev,
829 :anchor => anchor},
830 :class => (prefix == 'export' ? 'source download' : 'source')
831 end
832 end
833 repo_prefix = nil
752 end 834 end
753 when 'attachment' 835 when 'attachment'
754 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) 836 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
755 if attachments && attachment = attachments.detect {|a| a.filename == name } 837 if attachments && attachment = Attachment.latest_attach(attachments, name)
756 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment}, 838 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
757 :class => 'attachment' 839 :class => 'attachment'
758 end 840 end
759 when 'project' 841 when 'project'
760 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}]) 842 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
761 link = link_to_project(p, {:only_path => only_path}, :class => 'project') 843 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
762 end 844 end
763 end 845 end
764 end 846 end
765 end 847 end
766 (leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")).html_safe 848 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
767 end 849 end
768 end 850 end
769 851
770 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE) 852 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
771 853
772 def parse_sections(text, project, obj, attr, only_path, options) 854 def parse_sections(text, project, obj, attr, only_path, options)
773 return unless options[:edit_section_links] 855 return unless options[:edit_section_links]
774 text.gsub!(HEADING_RE) do 856 text.gsub!(HEADING_RE) do
857 heading = $1
775 @current_section += 1 858 @current_section += 1
776 if @current_section > 1 859 if @current_section > 1
777 content_tag('div', 860 content_tag('div',
778 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)), 861 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
779 :class => 'contextual', 862 :class => 'contextual',
780 :title => l(:button_edit_section)) + $1 863 :title => l(:button_edit_section)) + heading.html_safe
781 else 864 else
782 $1 865 heading
783 end 866 end
784 end 867 end
785 end 868 end
786 869
787 # Headings and TOC 870 # Headings and TOC
803 @parsed_headings << [level, anchor, item] 886 @parsed_headings << [level, anchor, item]
804 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>" 887 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
805 end 888 end
806 end 889 end
807 890
808 MACROS_RE = / 891 MACROS_RE = /(
809 (!)? # escaping 892 (!)? # escaping
810 ( 893 (
811 \{\{ # opening tag 894 \{\{ # opening tag
812 ([\w]+) # macro name 895 ([\w]+) # macro name
813 (\(([^\}]*)\))? # optional arguments 896 (\(([^\n\r]*?)\))? # optional arguments
897 ([\n\r].*?[\n\r])? # optional block of text
814 \}\} # closing tag 898 \}\} # closing tag
815 ) 899 )
816 /x unless const_defined?(:MACROS_RE) 900 )/mx unless const_defined?(:MACROS_RE)
817 901
818 # Macros substitution 902 MACRO_SUB_RE = /(
819 def parse_macros(text, project, obj, attr, only_path, options) 903 \{\{
904 macro\((\d+)\)
905 \}\}
906 )/x unless const_defined?(:MACRO_SUB_RE)
907
908 # Extracts macros from text
909 def catch_macros(text)
910 macros = {}
820 text.gsub!(MACROS_RE) do 911 text.gsub!(MACROS_RE) do
821 esc, all, macro = $1, $2, $3.downcase 912 all, macro = $1, $4.downcase
822 args = ($5 || '').split(',').each(&:strip) 913 if macro_exists?(macro) || all =~ MACRO_SUB_RE
823 if esc.nil? 914 index = macros.size
824 begin 915 macros[index] = all
825 exec_macro(macro, obj, args) 916 "{{macro(#{index})}}"
826 rescue => e
827 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
828 end || all
829 else 917 else
830 all 918 all
919 end
920 end
921 macros
922 end
923
924 # Executes and replaces macros in text
925 def inject_macros(text, obj, macros, execute=true)
926 text.gsub!(MACRO_SUB_RE) do
927 all, index = $1, $2.to_i
928 orig = macros.delete(index)
929 if execute && orig && orig =~ MACROS_RE
930 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
931 if esc.nil?
932 h(exec_macro(macro, obj, args, block) || all)
933 else
934 h(all)
935 end
936 elsif orig
937 h(orig)
938 else
939 h(all)
831 end 940 end
832 end 941 end
833 end 942 end
834 943
835 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE) 944 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
836 945
837 # Renders the TOC with given headings 946 # Renders the TOC with given headings
838 def replace_toc(text, headings) 947 def replace_toc(text, headings)
839 text.gsub!(TOC_RE) do 948 text.gsub!(TOC_RE) do
949 # Keep only the 4 first levels
950 headings = headings.select{|level, anchor, item| level <= 4}
840 if headings.empty? 951 if headings.empty?
841 '' 952 ''
842 else 953 else
843 div_class = 'toc' 954 div_class = 'toc'
844 div_class << ' right' if $1 == '>' 955 div_class << ' right' if $1 == '>'
873 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br 984 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
874 html_safe 985 html_safe
875 end 986 end
876 987
877 def lang_options_for_select(blank=true) 988 def lang_options_for_select(blank=true)
878 (blank ? [["(auto)", ""]] : []) + 989 (blank ? [["(auto)", ""]] : []) + languages_options
879 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
880 end 990 end
881 991
882 def label_tag_for(name, option_tags = nil, options = {}) 992 def label_tag_for(name, option_tags = nil, options = {})
883 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") 993 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
884 content_tag("label", label_text) 994 content_tag("label", label_text)
885 end 995 end
886 996
887 def labelled_tabular_form_for(*args, &proc)
888 args << {} unless args.last.is_a?(Hash)
889 options = args.last
890 options[:html] ||= {}
891 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
892 options.merge!({:builder => TabularFormBuilder})
893 form_for(*args, &proc)
894 end
895
896 def labelled_form_for(*args, &proc) 997 def labelled_form_for(*args, &proc)
897 args << {} unless args.last.is_a?(Hash) 998 args << {} unless args.last.is_a?(Hash)
898 options = args.last 999 options = args.last
899 options.merge!({:builder => TabularFormBuilder}) 1000 if args.first.is_a?(Symbol)
1001 options.merge!(:as => args.shift)
1002 end
1003 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
900 form_for(*args, &proc) 1004 form_for(*args, &proc)
901 end 1005 end
902 1006
1007 def labelled_fields_for(*args, &proc)
1008 args << {} unless args.last.is_a?(Hash)
1009 options = args.last
1010 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1011 fields_for(*args, &proc)
1012 end
1013
1014 def labelled_remote_form_for(*args, &proc)
1015 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
1016 args << {} unless args.last.is_a?(Hash)
1017 options = args.last
1018 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
1019 form_for(*args, &proc)
1020 end
1021
1022 def error_messages_for(*objects)
1023 html = ""
1024 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
1025 errors = objects.map {|o| o.errors.full_messages}.flatten
1026 if errors.any?
1027 html << "<div id='errorExplanation'><ul>\n"
1028 errors.each do |error|
1029 html << "<li>#{h error}</li>\n"
1030 end
1031 html << "</ul></div>\n"
1032 end
1033 html.html_safe
1034 end
1035
1036 def delete_link(url, options={})
1037 options = {
1038 :method => :delete,
1039 :data => {:confirm => l(:text_are_you_sure)},
1040 :class => 'icon icon-del'
1041 }.merge(options)
1042
1043 link_to l(:button_delete), url, options
1044 end
1045
1046 def preview_link(url, form, target='preview', options={})
1047 content_tag 'a', l(:label_preview), {
1048 :href => "#",
1049 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1050 :accesskey => accesskey(:preview)
1051 }.merge(options)
1052 end
1053
1054 def link_to_function(name, function, html_options={})
1055 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1056 end
1057
1058 # Helper to render JSON in views
1059 def raw_json(arg)
1060 arg.to_json.to_s.gsub('/', '\/').html_safe
1061 end
1062
1063 def back_url
1064 url = params[:back_url]
1065 if url.nil? && referer = request.env['HTTP_REFERER']
1066 url = CGI.unescape(referer.to_s)
1067 end
1068 url
1069 end
1070
903 def back_url_hidden_field_tag 1071 def back_url_hidden_field_tag
904 back_url = params[:back_url] || request.env['HTTP_REFERER'] 1072 url = back_url
905 back_url = CGI.unescape(back_url.to_s) 1073 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
906 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
907 end 1074 end
908 1075
909 def check_all_links(form_name) 1076 def check_all_links(form_name)
910 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") + 1077 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
911 " | ".html_safe + 1078 " | ".html_safe +
945 stylesheet_link_tag('context_menu_rtl') 1112 stylesheet_link_tag('context_menu_rtl')
946 end 1113 end
947 end 1114 end
948 @context_menu_included = true 1115 @context_menu_included = true
949 end 1116 end
950 javascript_tag "new ContextMenu('#{ url_for(url) }')" 1117 javascript_tag "contextMenuInit('#{ url_for(url) }')"
951 end
952
953 def context_menu_link(name, url, options={})
954 options[:class] ||= ''
955 if options.delete(:selected)
956 options[:class] << ' icon-checked disabled'
957 options[:disabled] = true
958 end
959 if options.delete(:disabled)
960 options.delete(:method)
961 options.delete(:confirm)
962 options.delete(:onclick)
963 options[:class] << ' disabled'
964 url = '#'
965 end
966 link_to h(name), url, options
967 end 1118 end
968 1119
969 def calendar_for(field_id) 1120 def calendar_for(field_id)
970 include_calendar_headers_tags 1121 include_calendar_headers_tags
971 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) + 1122 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
972 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
973 end 1123 end
974 1124
975 def include_calendar_headers_tags 1125 def include_calendar_headers_tags
976 unless @calendar_headers_tags_included 1126 unless @calendar_headers_tags_included
977 @calendar_headers_tags_included = true 1127 @calendar_headers_tags_included = true
978 content_for :header_tags do 1128 content_for :header_tags do
979 start_of_week = case Setting.start_of_week.to_i 1129 start_of_week = Setting.start_of_week
980 when 1 1130 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
981 'Calendar._FD = 1;' # Monday 1131 # Redmine uses 1..7 (monday..sunday) in settings and locales
982 when 7 1132 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
983 'Calendar._FD = 0;' # Sunday 1133 start_of_week = start_of_week.to_i % 7
984 when 6 1134
985 'Calendar._FD = 6;' # Saturday 1135 tags = javascript_tag(
1136 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1137 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1138 path_to_image('/images/calendar.png') +
1139 "', showButtonPanel: true};")
1140 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1141 unless jquery_locale == 'en'
1142 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
1143 end
1144 tags
1145 end
1146 end
1147 end
1148
1149 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1150 # Examples:
1151 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1152 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1153 #
1154 def stylesheet_link_tag(*sources)
1155 options = sources.last.is_a?(Hash) ? sources.pop : {}
1156 plugin = options.delete(:plugin)
1157 sources = sources.map do |source|
1158 if plugin
1159 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1160 elsif current_theme && current_theme.stylesheets.include?(source)
1161 current_theme.stylesheet_path(source)
1162 else
1163 source
1164 end
1165 end
1166 super sources, options
1167 end
1168
1169 # Overrides Rails' image_tag with themes and plugins support.
1170 # Examples:
1171 # image_tag('image.png') # => picks image.png from the current theme or defaults
1172 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1173 #
1174 def image_tag(source, options={})
1175 if plugin = options.delete(:plugin)
1176 source = "/plugin_assets/#{plugin}/images/#{source}"
1177 elsif current_theme && current_theme.images.include?(source)
1178 source = current_theme.image_path(source)
1179 end
1180 super source, options
1181 end
1182
1183 # Overrides Rails' javascript_include_tag with plugins support
1184 # Examples:
1185 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1186 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1187 #
1188 def javascript_include_tag(*sources)
1189 options = sources.last.is_a?(Hash) ? sources.pop : {}
1190 if plugin = options.delete(:plugin)
1191 sources = sources.map do |source|
1192 if plugin
1193 "/plugin_assets/#{plugin}/javascripts/#{source}"
986 else 1194 else
987 '' # use language 1195 source
988 end 1196 end
989 1197 end
990 javascript_include_tag('calendar/calendar') + 1198 end
991 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") + 1199 super sources, options
992 javascript_tag(start_of_week) +
993 javascript_include_tag('calendar/calendar-setup') +
994 stylesheet_link_tag('calendar')
995 end
996 end
997 end 1200 end
998 1201
999 def content_for(name, content = nil, &block) 1202 def content_for(name, content = nil, &block)
1000 @has_content ||= {} 1203 @has_content ||= {}
1001 @has_content[name] = true 1204 @has_content[name] = true
1004 1207
1005 def has_content?(name) 1208 def has_content?(name)
1006 (@has_content && @has_content[name]) || false 1209 (@has_content && @has_content[name]) || false
1007 end 1210 end
1008 1211
1212 def sidebar_content?
1213 has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1214 end
1215
1216 def view_layouts_base_sidebar_hook_response
1217 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1218 end
1219
1009 def email_delivery_enabled? 1220 def email_delivery_enabled?
1010 !!ActionMailer::Base.perform_deliveries 1221 !!ActionMailer::Base.perform_deliveries
1011 end 1222 end
1012 1223
1013 # Returns the avatar image tag for the given +user+ if avatars are enabled 1224 # Returns the avatar image tag for the given +user+ if avatars are enabled
1014 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>') 1225 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1015 def avatar(user, options = { }) 1226 def avatar(user, options = { })
1016 if Setting.gravatar_enabled? 1227 if Setting.gravatar_enabled?
1017 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default}) 1228 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1018 email = nil 1229 email = nil
1019 if user.respond_to?(:mail) 1230 if user.respond_to?(:mail)
1020 email = user.mail 1231 email = user.mail
1021 elsif user.to_s =~ %r{<(.+?)>} 1232 elsif user.to_s =~ %r{<(.+?)>}
1022 email = $1 1233 email = $1
1026 '' 1237 ''
1027 end 1238 end
1028 end 1239 end
1029 1240
1030 def sanitize_anchor_name(anchor) 1241 def sanitize_anchor_name(anchor)
1031 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') 1242 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
1243 anchor.gsub(%r{[^\p{Word}\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1244 else
1245 # TODO: remove when ruby1.8 is no longer supported
1246 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1247 end
1032 end 1248 end
1033 1249
1034 # Returns the javascript tags that are included in the html layout head 1250 # Returns the javascript tags that are included in the html layout head
1035 def javascript_heads 1251 def javascript_heads
1036 tags = javascript_include_tag(:defaults) 1252 tags = javascript_include_tag('jquery-1.7.2-ui-1.8.21-ujs-2.0.3', 'application')
1037 unless User.current.pref.warn_on_leaving_unsaved == '0' 1253 unless User.current.pref.warn_on_leaving_unsaved == '0'
1038 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });") 1254 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1039 end 1255 end
1040 tags 1256 tags
1041 end 1257 end
1042 1258
1043 def favicon 1259 def favicon