comparison app/helpers/application_helper.rb @ 1115:433d4f72a19b redmine-2.2

Update to Redmine SVN revision 11137 on 2.2-stable branch
author Chris Cannam
date Mon, 07 Jan 2013 12:01:42 +0000
parents 5f33065ddc4b
children bb32da3bea34 3e4c3460b6ca
comparison
equal deleted inserted replaced
929:5f33065ddc4b 1115:433d4f72a19b
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|
297 end 315 end
298 316
299 # Returns a string for users/groups option tags 317 # Returns a string for users/groups option tags
300 def principals_options_for_select(collection, selected=nil) 318 def principals_options_for_select(collection, selected=nil)
301 s = '' 319 s = ''
320 if collection.include?(User.current)
321 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
322 end
302 groups = '' 323 groups = ''
303 collection.sort.each do |element| 324 collection.sort.each do |element|
304 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) 325 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
305 (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>)
306 end 327 end
307 unless groups.empty? 328 unless groups.empty?
308 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>) 329 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
309 end 330 end
310 s 331 s.html_safe
332 end
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
311 end 341 end
312 342
313 # Truncates and returns the string as a single line 343 # Truncates and returns the string as a single line
314 def truncate_single_line(string, *args) 344 def truncate_single_line(string, *args)
315 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ') 345 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
323 else 353 else
324 string 354 string
325 end 355 end
326 end 356 end
327 357
358 def anchor(text)
359 text.to_s.gsub(' ', '_')
360 end
361
328 def html_hours(text) 362 def html_hours(text)
329 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
330 end 364 end
331 365
332 def authoring(created, author, options={}) 366 def authoring(created, author, options={})
334 end 368 end
335 369
336 def time_tag(time) 370 def time_tag(time)
337 text = distance_of_time_in_words(Time.now, time) 371 text = distance_of_time_in_words(Time.now, time)
338 if @project 372 if @project
339 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :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))
340 else 374 else
341 content_tag('acronym', text, :title => format_time(time)) 375 content_tag('acronym', text, :title => format_time(time))
342 end 376 end
343 end 377 end
344 378
379 def syntax_highlight_lines(name, content)
380 lines = []
381 syntax_highlight(name, content).each_line { |line| lines << line }
382 lines
383 end
384
345 def syntax_highlight(name, content) 385 def syntax_highlight(name, content)
346 Redmine::SyntaxHighlighting.highlight_by_filename(content, name) 386 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
347 end 387 end
348 388
349 def to_path_param(path) 389 def to_path_param(path)
350 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?} 390 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
391 str.blank? ? nil : str
351 end 392 end
352 393
353 def pagination_links_full(paginator, count=nil, options={}) 394 def pagination_links_full(paginator, count=nil, options={})
354 page_param = options.delete(:page_param) || :page 395 page_param = options.delete(:page_param) || :page
355 per_page_links = options.delete(:per_page_links) 396 per_page_links = options.delete(:per_page_links)
374 url_param.merge(page_param => paginator.current.next)) 415 url_param.merge(page_param => paginator.current.next))
375 end 416 end
376 417
377 unless count.nil? 418 unless count.nil?
378 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})" 419 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
379 if per_page_links != false && links = per_page_links(paginator.items_per_page) 420 if per_page_links != false && links = per_page_links(paginator.items_per_page, count)
380 html << " | #{links}" 421 html << " | #{links}"
381 end 422 end
382 end 423 end
383 424
384 html.html_safe 425 html.html_safe
385 end 426 end
386 427
387 def per_page_links(selected=nil) 428 def per_page_links(selected=nil, item_count=nil)
388 links = Setting.per_page_options_array.collect do |n| 429 values = Setting.per_page_options_array
430 if item_count && values.any?
431 if item_count > values.first
432 max = values.detect {|value| value >= item_count} || item_count
433 else
434 max = item_count
435 end
436 values = values.select {|value| value <= max || value == selected}
437 end
438 if values.empty? || (values.size == 1 && values.first == selected)
439 return nil
440 end
441 links = values.collect do |n|
389 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))
390 end 443 end
391 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil 444 l(:label_display_per_page, links.join(', '))
392 end 445 end
393 446
394 def reorder_links(name, url, method = :post) 447 def reorder_links(name, url, method = :post)
395 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), 448 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
396 url.merge({"#{name}[move_to]" => 'highest'}), 449 url.merge({"#{name}[move_to]" => 'highest'}),
455 css = [] 508 css = []
456 if theme = Redmine::Themes.theme(Setting.ui_theme) 509 if theme = Redmine::Themes.theme(Setting.ui_theme)
457 css << 'theme-' + theme.name 510 css << 'theme-' + theme.name
458 end 511 end
459 512
460 css << 'controller-' + params[:controller] 513 css << 'controller-' + controller_name
461 css << 'action-' + params[:action] 514 css << 'action-' + action_name
462 css.join(' ') 515 css.join(' ')
463 end 516 end
464 517
465 def accesskey(s) 518 def accesskey(s)
466 Redmine::AccessKeys.key_for s 519 Redmine::AccessKeys.key_for s
485 end 538 end
486 return '' if text.blank? 539 return '' if text.blank?
487 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)
488 only_path = options.delete(:only_path) == false ? false : true 541 only_path = options.delete(:only_path) == false ? false : true
489 542
543 text = text.dup
544 macros = catch_macros(text)
490 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)
491 546
492 @parsed_headings = [] 547 @parsed_headings = []
493 @heading_anchors = {} 548 @heading_anchors = {}
494 @current_section = 0 if options[:edit_section_links] 549 @current_section = 0 if options[:edit_section_links]
495 550
496 parse_sections(text, project, obj, attr, only_path, options) 551 parse_sections(text, project, obj, attr, only_path, options)
497 text = parse_non_pre_blocks(text) do |text| 552 text = parse_non_pre_blocks(text, obj, macros) do |text|
498 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros].each do |method_name| 553 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
499 send method_name, text, project, obj, attr, only_path, options 554 send method_name, text, project, obj, attr, only_path, options
500 end 555 end
501 end 556 end
502 parse_headings(text, project, obj, attr, only_path, options) 557 parse_headings(text, project, obj, attr, only_path, options)
503 558
504 if @parsed_headings.any? 559 if @parsed_headings.any?
505 replace_toc(text, @parsed_headings) 560 replace_toc(text, @parsed_headings)
506 end 561 end
507 562
508 text 563 text.html_safe
509 end 564 end
510 565
511 def parse_non_pre_blocks(text) 566 def parse_non_pre_blocks(text, obj, macros)
512 s = StringScanner.new(text) 567 s = StringScanner.new(text)
513 tags = [] 568 tags = []
514 parsed = '' 569 parsed = ''
515 while !s.eos? 570 while !s.eos?
516 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im) 571 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
517 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]
518 if tags.empty? 573 if tags.empty?
519 yield text 574 yield text
575 inject_macros(text, obj, macros) if macros.any?
576 else
577 inject_macros(text, obj, macros, false) if macros.any?
520 end 578 end
521 parsed << text 579 parsed << text
522 if tag 580 if tag
523 if closing 581 if closing
524 if tags.last == tag.downcase 582 if tags.last == tag.downcase
532 end 590 end
533 # Close any non closing tags 591 # Close any non closing tags
534 while tag = tags.pop 592 while tag = tags.pop
535 parsed << "</#{tag}>" 593 parsed << "</#{tag}>"
536 end 594 end
537 parsed.html_safe 595 parsed
538 end 596 end
539 597
540 def parse_inline_attachments(text, project, obj, attr, only_path, options) 598 def parse_inline_attachments(text, project, obj, attr, only_path, options)
541 # 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
542 if options[:attachments] || (obj && obj.respond_to?(:attachments)) 600 if options[:attachments] || (obj && obj.respond_to?(:attachments))
549 :action => 'download', :id => found 607 :action => 'download', :id => found
550 desc = found.description.to_s.gsub('"', '') 608 desc = found.description.to_s.gsub('"', '')
551 if !desc.blank? && alttext.blank? 609 if !desc.blank? && alttext.blank?
552 alt = " title=\"#{desc}\" alt=\"#{desc}\"" 610 alt = " title=\"#{desc}\" alt=\"#{desc}\""
553 end 611 end
554 "src=\"#{image_url}\"#{alt}".html_safe 612 "src=\"#{image_url}\"#{alt}"
555 else 613 else
556 m.html_safe 614 m
557 end 615 end
558 end 616 end
559 end 617 end
560 end 618 end
561 619
595 case options[:wiki_links] 653 case options[:wiki_links]
596 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '') 654 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
597 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
598 else 656 else
599 wiki_page_id = page.present? ? Wiki.titleize(page) : nil 657 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
600 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor) 658 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
659 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
660 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
601 end 661 end
602 end 662 end
603 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')))
604 else 664 else
605 # project or wiki doesn't exist 665 # project or wiki doesn't exist
606 all.html_safe 666 all
607 end 667 end
608 else 668 else
609 all.html_safe 669 all
610 end 670 end
611 end 671 end
612 end 672 end
613 673
614 # Redmine links 674 # Redmine links
642 # identifier:r52 702 # identifier:r52
643 # identifier:document:"Some document" 703 # identifier:document:"Some document"
644 # identifier:version:1.0.0 704 # identifier:version:1.0.0
645 # identifier:source:some/file 705 # identifier:source:some/file
646 def parse_redmine_links(text, project, obj, attr, only_path, options) 706 def parse_redmine_links(text, project, obj, attr, only_path, options)
647 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| 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|
648 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10 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
649 link = nil 709 link = nil
650 if project_identifier 710 if project_identifier
651 project = Project.visible.find_by_identifier(project_identifier) 711 project = Project.visible.find_by_identifier(project_identifier)
652 end 712 end
653 if esc.nil? 713 if esc.nil?
654 if prefix.nil? && sep == 'r' 714 if prefix.nil? && sep == 'r'
655 # project.changesets.visible raises an SQL error because of a double join on repositories 715 if project
656 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier)) 716 repository = nil
657 link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision}, 717 if repo_identifier
658 :class => 'changeset', 718 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
659 :title => truncate_single_line(changeset.comments, :length => 100)) 719 else
720 repository = project.repository
721 end
722 # project.changesets.visible raises an SQL error because of a double join on repositories
723 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
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},
725 :class => 'changeset',
726 :title => truncate_single_line(changeset.comments, :length => 100))
727 end
660 end 728 end
661 elsif sep == '#' 729 elsif sep == '#'
662 oid = identifier.to_i 730 oid = identifier.to_i
663 case prefix 731 case prefix
664 when nil 732 when nil
665 if issue = Issue.visible.find_by_id(oid, :include => :status) 733 if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status)
666 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid}, 734 anchor = comment_id ? "note-#{comment_id}" : nil
735 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
667 :class => issue.css_classes, 736 :class => issue.css_classes,
668 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") 737 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
669 end 738 end
670 when 'document' 739 when 'document'
671 if document = Document.visible.find_by_id(oid) 740 if document = Document.visible.find_by_id(oid)
718 when 'news' 787 when 'news'
719 if project && news = project.news.visible.find_by_title(name) 788 if project && news = project.news.visible.find_by_title(name)
720 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},
721 :class => 'news' 790 :class => 'news'
722 end 791 end
723 when 'commit' 792 when 'commit', 'source', 'export'
724 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"])) 793 if project
725 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier}, 794 repository = nil
726 :class => 'changeset', 795 if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
727 :title => truncate_single_line(h(changeset.comments), :length => 100) 796 repo_prefix, repo_identifier, name = $1, $2, $3
728 end 797 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
729 when 'source', 'export' 798 else
730 if project && project.repository && User.current.allowed_to?(:browse_repository, project) 799 repository = project.repository
731 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$} 800 end
732 path, rev, anchor = $1, $3, $5 801 if prefix == 'commit'
733 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, 802 if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"]))
734 :path => to_path_param(path), 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},
735 :rev => rev, 804 :class => 'changeset',
736 :anchor => anchor, 805 :title => truncate_single_line(h(changeset.comments), :length => 100)
737 :format => (prefix == 'export' ? 'raw' : nil)}, 806 end
738 :class => (prefix == 'export' ? 'source download' : 'source') 807 else
808 if repository && User.current.allowed_to?(:browse_repository, project)
809 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
810 path, rev, anchor = $1, $3, $5
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,
812 :path => to_path_param(path),
813 :rev => rev,
814 :anchor => anchor},
815 :class => (prefix == 'export' ? 'source download' : 'source')
816 end
817 end
818 repo_prefix = nil
739 end 819 end
740 when 'attachment' 820 when 'attachment'
741 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) 821 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
742 if attachments && attachment = attachments.detect {|a| a.filename == name } 822 if attachments && attachment = attachments.detect {|a| a.filename == name }
743 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},
748 link = link_to_project(p, {:only_path => only_path}, :class => 'project') 828 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
749 end 829 end
750 end 830 end
751 end 831 end
752 end 832 end
753 (leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")).html_safe 833 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
754 end 834 end
755 end 835 end
756 836
757 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE) 837 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
758 838
759 def parse_sections(text, project, obj, attr, only_path, options) 839 def parse_sections(text, project, obj, attr, only_path, options)
760 return unless options[:edit_section_links] 840 return unless options[:edit_section_links]
761 text.gsub!(HEADING_RE) do 841 text.gsub!(HEADING_RE) do
842 heading = $1
762 @current_section += 1 843 @current_section += 1
763 if @current_section > 1 844 if @current_section > 1
764 content_tag('div', 845 content_tag('div',
765 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)),
766 :class => 'contextual', 847 :class => 'contextual',
767 :title => l(:button_edit_section)) + $1 848 :title => l(:button_edit_section)) + heading.html_safe
768 else 849 else
769 $1 850 heading
770 end 851 end
771 end 852 end
772 end 853 end
773 854
774 # Headings and TOC 855 # Headings and TOC
790 @parsed_headings << [level, anchor, item] 871 @parsed_headings << [level, anchor, item]
791 "<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}>"
792 end 873 end
793 end 874 end
794 875
795 MACROS_RE = / 876 MACROS_RE = /(
796 (!)? # escaping 877 (!)? # escaping
797 ( 878 (
798 \{\{ # opening tag 879 \{\{ # opening tag
799 ([\w]+) # macro name 880 ([\w]+) # macro name
800 (\(([^\}]*)\))? # optional arguments 881 (\(([^\n\r]*?)\))? # optional arguments
882 ([\n\r].*?[\n\r])? # optional block of text
801 \}\} # closing tag 883 \}\} # closing tag
802 ) 884 )
803 /x unless const_defined?(:MACROS_RE) 885 )/mx unless const_defined?(:MACROS_RE)
804 886
805 # Macros substitution 887 MACRO_SUB_RE = /(
806 def parse_macros(text, project, obj, attr, only_path, options) 888 \{\{
889 macro\((\d+)\)
890 \}\}
891 )/x unless const_defined?(:MACRO_SUB_RE)
892
893 # Extracts macros from text
894 def catch_macros(text)
895 macros = {}
807 text.gsub!(MACROS_RE) do 896 text.gsub!(MACROS_RE) do
808 esc, all, macro = $1, $2, $3.downcase 897 all, macro = $1, $4.downcase
809 args = ($5 || '').split(',').each(&:strip) 898 if macro_exists?(macro) || all =~ MACRO_SUB_RE
810 if esc.nil? 899 index = macros.size
811 begin 900 macros[index] = all
812 exec_macro(macro, obj, args) 901 "{{macro(#{index})}}"
813 rescue => e
814 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
815 end || all
816 else 902 else
817 all 903 all
904 end
905 end
906 macros
907 end
908
909 # Executes and replaces macros in text
910 def inject_macros(text, obj, macros, execute=true)
911 text.gsub!(MACRO_SUB_RE) do
912 all, index = $1, $2.to_i
913 orig = macros.delete(index)
914 if execute && orig && orig =~ MACROS_RE
915 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
916 if esc.nil?
917 h(exec_macro(macro, obj, args, block) || all)
918 else
919 h(all)
920 end
921 elsif orig
922 h(orig)
923 else
924 h(all)
818 end 925 end
819 end 926 end
820 end 927 end
821 928
822 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE) 929 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
823 930
824 # Renders the TOC with given headings 931 # Renders the TOC with given headings
825 def replace_toc(text, headings) 932 def replace_toc(text, headings)
826 text.gsub!(TOC_RE) do 933 text.gsub!(TOC_RE) do
934 # Keep only the 4 first levels
935 headings = headings.select{|level, anchor, item| level <= 4}
827 if headings.empty? 936 if headings.empty?
828 '' 937 ''
829 else 938 else
830 div_class = 'toc' 939 div_class = 'toc'
831 div_class << ' right' if $1 == '>' 940 div_class << ' right' if $1 == '>'
860 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br 969 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
861 html_safe 970 html_safe
862 end 971 end
863 972
864 def lang_options_for_select(blank=true) 973 def lang_options_for_select(blank=true)
865 (blank ? [["(auto)", ""]] : []) + 974 (blank ? [["(auto)", ""]] : []) + languages_options
866 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
867 end 975 end
868 976
869 def label_tag_for(name, option_tags = nil, options = {}) 977 def label_tag_for(name, option_tags = nil, options = {})
870 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"): "")
871 content_tag("label", label_text) 979 content_tag("label", label_text)
872 end 980 end
873 981
874 def labelled_tabular_form_for(*args, &proc)
875 args << {} unless args.last.is_a?(Hash)
876 options = args.last
877 options[:html] ||= {}
878 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
879 options.merge!({:builder => TabularFormBuilder})
880 form_for(*args, &proc)
881 end
882
883 def labelled_form_for(*args, &proc) 982 def labelled_form_for(*args, &proc)
884 args << {} unless args.last.is_a?(Hash) 983 args << {} unless args.last.is_a?(Hash)
885 options = args.last 984 options = args.last
886 options.merge!({:builder => TabularFormBuilder}) 985 if args.first.is_a?(Symbol)
986 options.merge!(:as => args.shift)
987 end
988 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
887 form_for(*args, &proc) 989 form_for(*args, &proc)
888 end 990 end
889 991
992 def labelled_fields_for(*args, &proc)
993 args << {} unless args.last.is_a?(Hash)
994 options = args.last
995 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
996 fields_for(*args, &proc)
997 end
998
999 def labelled_remote_form_for(*args, &proc)
1000 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
1001 args << {} unless args.last.is_a?(Hash)
1002 options = args.last
1003 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
1004 form_for(*args, &proc)
1005 end
1006
1007 def error_messages_for(*objects)
1008 html = ""
1009 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
1010 errors = objects.map {|o| o.errors.full_messages}.flatten
1011 if errors.any?
1012 html << "<div id='errorExplanation'><ul>\n"
1013 errors.each do |error|
1014 html << "<li>#{h error}</li>\n"
1015 end
1016 html << "</ul></div>\n"
1017 end
1018 html.html_safe
1019 end
1020
1021 def delete_link(url, options={})
1022 options = {
1023 :method => :delete,
1024 :data => {:confirm => l(:text_are_you_sure)},
1025 :class => 'icon icon-del'
1026 }.merge(options)
1027
1028 link_to l(:button_delete), url, options
1029 end
1030
1031 def preview_link(url, form, target='preview', options={})
1032 content_tag 'a', l(:label_preview), {
1033 :href => "#",
1034 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1035 :accesskey => accesskey(:preview)
1036 }.merge(options)
1037 end
1038
1039 def link_to_function(name, function, html_options={})
1040 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1041 end
1042
1043 # Helper to render JSON in views
1044 def raw_json(arg)
1045 arg.to_json.to_s.gsub('/', '\/').html_safe
1046 end
1047
1048 def back_url
1049 url = params[:back_url]
1050 if url.nil? && referer = request.env['HTTP_REFERER']
1051 url = CGI.unescape(referer.to_s)
1052 end
1053 url
1054 end
1055
890 def back_url_hidden_field_tag 1056 def back_url_hidden_field_tag
891 back_url = params[:back_url] || request.env['HTTP_REFERER'] 1057 url = back_url
892 back_url = CGI.unescape(back_url.to_s) 1058 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
893 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
894 end 1059 end
895 1060
896 def check_all_links(form_name) 1061 def check_all_links(form_name)
897 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") + 1062 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
898 " | ".html_safe + 1063 " | ".html_safe +
932 stylesheet_link_tag('context_menu_rtl') 1097 stylesheet_link_tag('context_menu_rtl')
933 end 1098 end
934 end 1099 end
935 @context_menu_included = true 1100 @context_menu_included = true
936 end 1101 end
937 javascript_tag "new ContextMenu('#{ url_for(url) }')" 1102 javascript_tag "contextMenuInit('#{ url_for(url) }')"
938 end
939
940 def context_menu_link(name, url, options={})
941 options[:class] ||= ''
942 if options.delete(:selected)
943 options[:class] << ' icon-checked disabled'
944 options[:disabled] = true
945 end
946 if options.delete(:disabled)
947 options.delete(:method)
948 options.delete(:confirm)
949 options.delete(:onclick)
950 options[:class] << ' disabled'
951 url = '#'
952 end
953 link_to h(name), url, options
954 end 1103 end
955 1104
956 def calendar_for(field_id) 1105 def calendar_for(field_id)
957 include_calendar_headers_tags 1106 include_calendar_headers_tags
958 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) + 1107 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
959 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
960 end 1108 end
961 1109
962 def include_calendar_headers_tags 1110 def include_calendar_headers_tags
963 unless @calendar_headers_tags_included 1111 unless @calendar_headers_tags_included
964 @calendar_headers_tags_included = true 1112 @calendar_headers_tags_included = true
965 content_for :header_tags do 1113 content_for :header_tags do
966 start_of_week = case Setting.start_of_week.to_i 1114 start_of_week = Setting.start_of_week
967 when 1 1115 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
968 'Calendar._FD = 1;' # Monday 1116 # Redmine uses 1..7 (monday..sunday) in settings and locales
969 when 7 1117 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
970 'Calendar._FD = 0;' # Sunday 1118 start_of_week = start_of_week.to_i % 7
971 when 6 1119
972 'Calendar._FD = 6;' # Saturday 1120 tags = javascript_tag(
1121 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1122 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1123 path_to_image('/images/calendar.png') +
1124 "', showButtonPanel: true};")
1125 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1126 unless jquery_locale == 'en'
1127 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
1128 end
1129 tags
1130 end
1131 end
1132 end
1133
1134 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1135 # Examples:
1136 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1137 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1138 #
1139 def stylesheet_link_tag(*sources)
1140 options = sources.last.is_a?(Hash) ? sources.pop : {}
1141 plugin = options.delete(:plugin)
1142 sources = sources.map do |source|
1143 if plugin
1144 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1145 elsif current_theme && current_theme.stylesheets.include?(source)
1146 current_theme.stylesheet_path(source)
1147 else
1148 source
1149 end
1150 end
1151 super sources, options
1152 end
1153
1154 # Overrides Rails' image_tag with themes and plugins support.
1155 # Examples:
1156 # image_tag('image.png') # => picks image.png from the current theme or defaults
1157 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1158 #
1159 def image_tag(source, options={})
1160 if plugin = options.delete(:plugin)
1161 source = "/plugin_assets/#{plugin}/images/#{source}"
1162 elsif current_theme && current_theme.images.include?(source)
1163 source = current_theme.image_path(source)
1164 end
1165 super source, options
1166 end
1167
1168 # Overrides Rails' javascript_include_tag with plugins support
1169 # Examples:
1170 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1171 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1172 #
1173 def javascript_include_tag(*sources)
1174 options = sources.last.is_a?(Hash) ? sources.pop : {}
1175 if plugin = options.delete(:plugin)
1176 sources = sources.map do |source|
1177 if plugin
1178 "/plugin_assets/#{plugin}/javascripts/#{source}"
973 else 1179 else
974 '' # use language 1180 source
975 end 1181 end
976 1182 end
977 javascript_include_tag('calendar/calendar') + 1183 end
978 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") + 1184 super sources, options
979 javascript_tag(start_of_week) +
980 javascript_include_tag('calendar/calendar-setup') +
981 stylesheet_link_tag('calendar')
982 end
983 end
984 end 1185 end
985 1186
986 def content_for(name, content = nil, &block) 1187 def content_for(name, content = nil, &block)
987 @has_content ||= {} 1188 @has_content ||= {}
988 @has_content[name] = true 1189 @has_content[name] = true
991 1192
992 def has_content?(name) 1193 def has_content?(name)
993 (@has_content && @has_content[name]) || false 1194 (@has_content && @has_content[name]) || false
994 end 1195 end
995 1196
1197 def sidebar_content?
1198 has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1199 end
1200
1201 def view_layouts_base_sidebar_hook_response
1202 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1203 end
1204
996 def email_delivery_enabled? 1205 def email_delivery_enabled?
997 !!ActionMailer::Base.perform_deliveries 1206 !!ActionMailer::Base.perform_deliveries
998 end 1207 end
999 1208
1000 # 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
1001 # +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>')
1002 def avatar(user, options = { }) 1211 def avatar(user, options = { })
1003 if Setting.gravatar_enabled? 1212 if Setting.gravatar_enabled?
1004 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default}) 1213 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1005 email = nil 1214 email = nil
1006 if user.respond_to?(:mail) 1215 if user.respond_to?(:mail)
1007 email = user.mail 1216 email = user.mail
1008 elsif user.to_s =~ %r{<(.+?)>} 1217 elsif user.to_s =~ %r{<(.+?)>}
1009 email = $1 1218 email = $1
1013 '' 1222 ''
1014 end 1223 end
1015 end 1224 end
1016 1225
1017 def sanitize_anchor_name(anchor) 1226 def sanitize_anchor_name(anchor)
1018 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') 1227 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
1228 anchor.gsub(%r{[^\p{Word}\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1229 else
1230 # TODO: remove when ruby1.8 is no longer supported
1231 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1232 end
1019 end 1233 end
1020 1234
1021 # 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
1022 def javascript_heads 1236 def javascript_heads
1023 tags = javascript_include_tag(:defaults) 1237 tags = javascript_include_tag('jquery-1.7.2-ui-1.8.21-ujs-2.0.3', 'application')
1024 unless User.current.pref.warn_on_leaving_unsaved == '0' 1238 unless User.current.pref.warn_on_leaving_unsaved == '0'
1025 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new 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)}'); });")
1026 end 1240 end
1027 tags 1241 tags
1028 end 1242 end
1029 1243
1030 def favicon 1244 def favicon