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