Mercurial > hg > soundsoftware-site
comparison app/helpers/application_helper.rb @ 909:cbb26bc654de redmine-1.3
Update to Redmine 1.3-stable branch (Redmine SVN rev 8964)
author | Chris Cannam |
---|---|
date | Fri, 24 Feb 2012 19:09:32 +0000 |
parents | cbce1fd3b1b7 |
children | 5e80956cc792 5f33065ddc4b |
comparison
equal
deleted
inserted
replaced
908:c6c2cbd0afee | 909:cbb26bc654de |
---|---|
1 # encoding: utf-8 | |
2 # | |
1 # Redmine - project management software | 3 # Redmine - project management software |
2 # Copyright (C) 2006-2011 Jean-Philippe Lang | 4 # Copyright (C) 2006-2011 Jean-Philippe Lang |
3 # | 5 # |
4 # This program is free software; you can redistribute it and/or | 6 # This program is free software; you can redistribute it and/or |
5 # modify it under the terms of the GNU General Public License | 7 # modify it under the terms of the GNU General Public License |
78 subject = issue.subject | 80 subject = issue.subject |
79 if options[:truncate] | 81 if options[:truncate] |
80 subject = truncate(subject, :length => options[:truncate]) | 82 subject = truncate(subject, :length => options[:truncate]) |
81 end | 83 end |
82 end | 84 end |
83 s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, | 85 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, |
84 :class => issue.css_classes, | 86 :class => issue.css_classes, |
85 :title => title | 87 :title => title |
86 s << ": #{h subject}" if subject | 88 s << ": #{h subject}" if subject |
87 s = "#{h issue.project} - " + s if options[:project] | 89 s = "#{h issue.project} - " + s if options[:project] |
88 s | 90 s |
93 # * :text - Link text (default to attachment filename) | 95 # * :text - Link text (default to attachment filename) |
94 # * :download - Force download (default: false) | 96 # * :download - Force download (default: false) |
95 def link_to_attachment(attachment, options={}) | 97 def link_to_attachment(attachment, options={}) |
96 text = options.delete(:text) || attachment.filename | 98 text = options.delete(:text) || attachment.filename |
97 action = options.delete(:download) ? 'download' : 'show' | 99 action = options.delete(:download) ? 'download' : 'show' |
98 | 100 link_to(h(text), |
99 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options) | 101 {:controller => 'attachments', :action => action, |
102 :id => attachment, :filename => attachment.filename }, | |
103 options) | |
100 end | 104 end |
101 | 105 |
102 # Generates a link to a SCM revision | 106 # Generates a link to a SCM revision |
103 # Options: | 107 # Options: |
104 # * :text - Link text (default to the formatted revision) | 108 # * :text - Link text (default to the formatted revision) |
105 def link_to_revision(revision, project, options={}) | 109 def link_to_revision(revision, project, options={}) |
106 text = options.delete(:text) || format_revision(revision) | 110 text = options.delete(:text) || format_revision(revision) |
107 rev = revision.respond_to?(:identifier) ? revision.identifier : revision | 111 rev = revision.respond_to?(:identifier) ? revision.identifier : revision |
108 | 112 |
109 link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev}, | 113 link_to(h(text), {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev}, |
110 :title => l(:label_revision_id, format_revision(revision))) | 114 :title => l(:label_revision_id, format_revision(revision))) |
111 end | 115 end |
112 | 116 |
113 # Generates a link to a message | 117 # Generates a link to a message |
114 def link_to_message(message, options={}, html_options = nil) | 118 def link_to_message(message, options={}, html_options = nil) |
198 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id] | 202 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id] |
199 content << "</li>\n" | 203 content << "</li>\n" |
200 end | 204 end |
201 content << "</ul>\n" | 205 content << "</ul>\n" |
202 end | 206 end |
203 content | 207 content.html_safe |
204 end | 208 end |
205 | 209 |
206 # Renders flash messages | 210 # Renders flash messages |
207 def render_flash_messages | 211 def render_flash_messages |
208 s = '' | 212 s = '' |
209 flash.each do |k,v| | 213 flash.each do |k,v| |
210 s << content_tag('div', v, :class => "flash #{k}") | 214 s << content_tag('div', v, :class => "flash #{k}") |
211 end | 215 end |
212 s | 216 s.html_safe |
213 end | 217 end |
214 | 218 |
215 # Renders tabs and their content | 219 # Renders tabs and their content |
216 def render_tabs(tabs) | 220 def render_tabs(tabs) |
217 if tabs.any? | 221 if tabs.any? |
231 '<option value="" disabled="disabled">---</option>' | 235 '<option value="" disabled="disabled">---</option>' |
232 s << project_tree_options_for_select(projects, :selected => @project) do |p| | 236 s << project_tree_options_for_select(projects, :selected => @project) do |p| |
233 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) } | 237 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) } |
234 end | 238 end |
235 s << '</select>' | 239 s << '</select>' |
236 s | 240 s.html_safe |
237 end | 241 end |
238 end | 242 end |
239 | 243 |
240 def project_tree_options_for_select(projects, options = {}) | 244 def project_tree_options_for_select(projects, options = {}) |
241 s = '' | 245 s = '' |
248 tag_options[:selected] = nil | 252 tag_options[:selected] = nil |
249 end | 253 end |
250 tag_options.merge!(yield(project)) if block_given? | 254 tag_options.merge!(yield(project)) if block_given? |
251 s << content_tag('option', name_prefix + h(project), tag_options) | 255 s << content_tag('option', name_prefix + h(project), tag_options) |
252 end | 256 end |
253 s | 257 s.html_safe |
254 end | 258 end |
255 | 259 |
256 # Yields the given block for each project with its level in the tree | 260 # Yields the given block for each project with its level in the tree |
257 # | 261 # |
258 # Wrapper for Project#project_tree | 262 # Wrapper for Project#project_tree |
279 s << yield(project).to_s | 283 s << yield(project).to_s |
280 ancestors << project | 284 ancestors << project |
281 end | 285 end |
282 s << ("</li></ul>\n" * ancestors.size) | 286 s << ("</li></ul>\n" * ancestors.size) |
283 end | 287 end |
284 s | 288 s.html_safe |
285 end | 289 end |
286 | 290 |
287 def principals_check_box_tags(name, principals) | 291 def principals_check_box_tags(name, principals) |
288 s = '' | 292 s = '' |
289 principals.sort.each do |principal| | 293 principals.sort.each do |principal| |
290 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n" | 294 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n" |
295 end | |
296 s.html_safe | |
297 end | |
298 | |
299 # Returns a string for users/groups option tags | |
300 def principals_options_for_select(collection, selected=nil) | |
301 s = '' | |
302 groups = '' | |
303 collection.sort.each do |element| | |
304 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>) | |
306 end | |
307 unless groups.empty? | |
308 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>) | |
291 end | 309 end |
292 s | 310 s |
293 end | 311 end |
294 | 312 |
295 # Truncates and returns the string as a single line | 313 # Truncates and returns the string as a single line |
306 string | 324 string |
307 end | 325 end |
308 end | 326 end |
309 | 327 |
310 def html_hours(text) | 328 def html_hours(text) |
311 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>') | 329 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe |
312 end | 330 end |
313 | 331 |
314 def authoring(created, author, options={}) | 332 def authoring(created, author, options={}) |
315 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)) | 333 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe |
316 end | 334 end |
317 | 335 |
318 def time_tag(time) | 336 def time_tag(time) |
319 text = distance_of_time_in_words(Time.now, time) | 337 text = distance_of_time_in_words(Time.now, time) |
320 if @project | 338 if @project |
337 per_page_links = options.delete(:per_page_links) | 355 per_page_links = options.delete(:per_page_links) |
338 url_param = params.dup | 356 url_param = params.dup |
339 | 357 |
340 html = '' | 358 html = '' |
341 if paginator.current.previous | 359 if paginator.current.previous |
342 html << link_to_content_update('« ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' ' | 360 # \xc2\xab(utf-8) = « |
361 html << link_to_content_update( | |
362 "\xc2\xab " + l(:label_previous), | |
363 url_param.merge(page_param => paginator.current.previous)) + ' ' | |
343 end | 364 end |
344 | 365 |
345 html << (pagination_links_each(paginator, options) do |n| | 366 html << (pagination_links_each(paginator, options) do |n| |
346 link_to_content_update(n.to_s, url_param.merge(page_param => n)) | 367 link_to_content_update(n.to_s, url_param.merge(page_param => n)) |
347 end || '') | 368 end || '') |
348 | 369 |
349 if paginator.current.next | 370 if paginator.current.next |
350 html << ' ' + link_to_content_update((l(:label_next) + ' »'), url_param.merge(page_param => paginator.current.next)) | 371 # \xc2\xbb(utf-8) = » |
372 html << ' ' + link_to_content_update( | |
373 (l(:label_next) + " \xc2\xbb"), | |
374 url_param.merge(page_param => paginator.current.next)) | |
351 end | 375 end |
352 | 376 |
353 unless count.nil? | 377 unless count.nil? |
354 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})" | 378 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})" |
355 if per_page_links != false && links = per_page_links(paginator.items_per_page) | 379 if per_page_links != false && links = per_page_links(paginator.items_per_page) |
356 html << " | #{links}" | 380 html << " | #{links}" |
357 end | 381 end |
358 end | 382 end |
359 | 383 |
360 html | 384 html.html_safe |
361 end | 385 end |
362 | 386 |
363 def per_page_links(selected=nil) | 387 def per_page_links(selected=nil) |
364 links = Setting.per_page_options_array.collect do |n| | 388 links = Setting.per_page_options_array.collect do |n| |
365 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n)) | 389 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n)) |
366 end | 390 end |
367 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil | 391 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil |
368 end | 392 end |
369 | 393 |
370 def reorder_links(name, url) | 394 def reorder_links(name, url, method = :post) |
371 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) + | 395 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), |
372 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) + | 396 url.merge({"#{name}[move_to]" => 'highest'}), |
373 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) + | 397 :method => method, :title => l(:label_sort_highest)) + |
374 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest)) | 398 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), |
399 url.merge({"#{name}[move_to]" => 'higher'}), | |
400 :method => method, :title => l(:label_sort_higher)) + | |
401 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), | |
402 url.merge({"#{name}[move_to]" => 'lower'}), | |
403 :method => method, :title => l(:label_sort_lower)) + | |
404 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), | |
405 url.merge({"#{name}[move_to]" => 'lowest'}), | |
406 :method => method, :title => l(:label_sort_lowest)) | |
375 end | 407 end |
376 | 408 |
377 def breadcrumb(*args) | 409 def breadcrumb(*args) |
378 elements = args.flatten | 410 elements = args.flatten |
379 elements.any? ? content_tag('p', args.join(' » ') + ' » ', :class => 'breadcrumb') : nil | 411 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil |
380 end | 412 end |
381 | 413 |
382 def other_formats_links(&block) | 414 def other_formats_links(&block) |
383 concat('<p class="other-formats">' + l(:label_export_to)) | 415 concat('<p class="other-formats">'.html_safe + l(:label_export_to)) |
384 yield Redmine::Views::OtherFormatsBuilder.new(self) | 416 yield Redmine::Views::OtherFormatsBuilder.new(self) |
385 concat('</p>') | 417 concat('</p>'.html_safe) |
386 end | 418 end |
387 | 419 |
388 def page_header_title | 420 def page_header_title |
389 if @project.nil? || @project.new_record? | 421 if @project.nil? || @project.new_record? |
390 h(Setting.app_title) | 422 h(Setting.app_title) |
393 ancestors = (@project.root? ? [] : @project.ancestors.visible.all) | 425 ancestors = (@project.root? ? [] : @project.ancestors.visible.all) |
394 if ancestors.any? | 426 if ancestors.any? |
395 root = ancestors.shift | 427 root = ancestors.shift |
396 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root') | 428 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root') |
397 if ancestors.size > 2 | 429 if ancestors.size > 2 |
398 b << '…' | 430 b << "\xe2\x80\xa6" |
399 ancestors = ancestors[-2, 2] | 431 ancestors = ancestors[-2, 2] |
400 end | 432 end |
401 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') } | 433 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') } |
402 end | 434 end |
403 b << h(@project) | 435 b << h(@project) |
404 b.join(' » ') | 436 b.join(" \xc2\xbb ").html_safe |
405 end | 437 end |
406 end | 438 end |
407 | 439 |
408 def html_title(*args) | 440 def html_title(*args) |
409 if args.empty? | 441 if args.empty? |
410 title = [] | 442 title = @html_title || [] |
411 title << @project.name if @project | 443 title << @project.name if @project |
412 title += @html_title if @html_title | 444 title << Setting.app_title unless Setting.app_title == title.last |
413 title << Setting.app_title | |
414 title.select {|t| !t.blank? }.join(' - ') | 445 title.select {|t| !t.blank? }.join(' - ') |
415 else | 446 else |
416 @html_title ||= [] | 447 @html_title ||= [] |
417 @html_title += args | 448 @html_title += args |
418 end | 449 end |
454 end | 485 end |
455 return '' if text.blank? | 486 return '' if text.blank? |
456 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) | 487 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) |
457 only_path = options.delete(:only_path) == false ? false : true | 488 only_path = options.delete(:only_path) == false ? false : true |
458 | 489 |
459 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) } | 490 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) |
460 | 491 |
461 @parsed_headings = [] | 492 @parsed_headings = [] |
493 @current_section = 0 if options[:edit_section_links] | |
462 text = parse_non_pre_blocks(text) do |text| | 494 text = parse_non_pre_blocks(text) do |text| |
463 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings].each do |method_name| | 495 [:parse_sections, :parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros, :parse_headings].each do |method_name| |
464 send method_name, text, project, obj, attr, only_path, options | 496 send method_name, text, project, obj, attr, only_path, options |
465 end | 497 end |
466 end | 498 end |
467 | 499 |
468 if @parsed_headings.any? | 500 if @parsed_headings.any? |
496 end | 528 end |
497 # Close any non closing tags | 529 # Close any non closing tags |
498 while tag = tags.pop | 530 while tag = tags.pop |
499 parsed << "</#{tag}>" | 531 parsed << "</#{tag}>" |
500 end | 532 end |
501 parsed | 533 parsed.html_safe |
502 end | 534 end |
503 | 535 |
504 def parse_inline_attachments(text, project, obj, attr, only_path, options) | 536 def parse_inline_attachments(text, project, obj, attr, only_path, options) |
505 # when using an image link, try to use an attachment, if possible | 537 # when using an image link, try to use an attachment, if possible |
506 if options[:attachments] || (obj && obj.respond_to?(:attachments)) | 538 if options[:attachments] || (obj && obj.respond_to?(:attachments)) |
507 attachments = nil | 539 attachments = options[:attachments] || obj.attachments |
508 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m| | 540 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m| |
509 filename, ext, alt, alttext = $1.downcase, $2, $3, $4 | 541 filename, ext, alt, alttext = $1.downcase, $2, $3, $4 |
510 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse | |
511 # search for the picture in attachments | 542 # search for the picture in attachments |
512 if found = attachments.detect { |att| att.filename.downcase == filename } | 543 if found = Attachment.latest_attach(attachments, filename) |
513 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found | 544 image_url = url_for :only_path => only_path, :controller => 'attachments', |
545 :action => 'download', :id => found | |
514 desc = found.description.to_s.gsub('"', '') | 546 desc = found.description.to_s.gsub('"', '') |
515 if !desc.blank? && alttext.blank? | 547 if !desc.blank? && alttext.blank? |
516 alt = " title=\"#{desc}\" alt=\"#{desc}\"" | 548 alt = " title=\"#{desc}\" alt=\"#{desc}\"" |
517 end | 549 end |
518 "src=\"#{image_url}\"#{alt}" | 550 "src=\"#{image_url}\"#{alt}".html_safe |
519 else | 551 else |
520 m | 552 m.html_safe |
521 end | 553 end |
522 end | 554 end |
523 end | 555 end |
524 end | 556 end |
525 | 557 |
548 # extract anchor | 580 # extract anchor |
549 anchor = nil | 581 anchor = nil |
550 if page =~ /^(.+?)\#(.+)$/ | 582 if page =~ /^(.+?)\#(.+)$/ |
551 page, anchor = $1, $2 | 583 page, anchor = $1, $2 |
552 end | 584 end |
585 anchor = sanitize_anchor_name(anchor) if anchor.present? | |
553 # check if page exists | 586 # check if page exists |
554 wiki_page = link_project.wiki.find_page(page) | 587 wiki_page = link_project.wiki.find_page(page) |
555 url = case options[:wiki_links] | 588 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page |
556 when :local; "#{title}.html" | 589 "##{anchor}" |
557 when :anchor; "##{title}" # used for single-file wiki export | 590 else |
591 case options[:wiki_links] | |
592 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '') | |
593 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export | |
558 else | 594 else |
559 wiki_page_id = page.present? ? Wiki.titleize(page) : nil | 595 wiki_page_id = page.present? ? Wiki.titleize(page) : nil |
560 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor) | 596 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor) |
561 end | 597 end |
562 link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new'))) | 598 end |
599 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new'))) | |
563 else | 600 else |
564 # project or wiki doesn't exist | 601 # project or wiki doesn't exist |
565 all | 602 all.html_safe |
566 end | 603 end |
567 else | 604 else |
568 all | 605 all.html_safe |
569 end | 606 end |
570 end | 607 end |
571 end | 608 end |
572 | 609 |
573 # Redmine links | 610 # Redmine links |
601 # identifier:r52 | 638 # identifier:r52 |
602 # identifier:document:"Some document" | 639 # identifier:document:"Some document" |
603 # identifier:version:1.0.0 | 640 # identifier:version:1.0.0 |
604 # identifier:source:some/file | 641 # identifier:source:some/file |
605 def parse_redmine_links(text, project, obj, attr, only_path, options) | 642 def parse_redmine_links(text, project, obj, attr, only_path, options) |
606 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m| | 643 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| |
607 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10 | 644 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10 |
608 link = nil | 645 link = nil |
609 if project_identifier | 646 if project_identifier |
610 project = Project.visible.find_by_identifier(project_identifier) | 647 project = Project.visible.find_by_identifier(project_identifier) |
611 end | 648 end |
612 if esc.nil? | 649 if esc.nil? |
613 if prefix.nil? && sep == 'r' | 650 if prefix.nil? && sep == 'r' |
614 # project.changesets.visible raises an SQL error because of a double join on repositories | 651 # project.changesets.visible raises an SQL error because of a double join on repositories |
615 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier)) | 652 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier)) |
616 link = link_to("#{project_prefix}r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision}, | 653 link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision}, |
617 :class => 'changeset', | 654 :class => 'changeset', |
618 :title => truncate_single_line(changeset.comments, :length => 100)) | 655 :title => truncate_single_line(changeset.comments, :length => 100)) |
619 end | 656 end |
620 elsif sep == '#' | 657 elsif sep == '#' |
621 oid = identifier.to_i | 658 oid = identifier.to_i |
638 end | 675 end |
639 when 'message' | 676 when 'message' |
640 if message = Message.visible.find_by_id(oid, :include => :parent) | 677 if message = Message.visible.find_by_id(oid, :include => :parent) |
641 link = link_to_message(message, {:only_path => only_path}, :class => 'message') | 678 link = link_to_message(message, {:only_path => only_path}, :class => 'message') |
642 end | 679 end |
680 when 'forum' | |
681 if board = Board.visible.find_by_id(oid) | |
682 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project}, | |
683 :class => 'board' | |
684 end | |
685 when 'news' | |
686 if news = News.visible.find_by_id(oid) | |
687 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, | |
688 :class => 'news' | |
689 end | |
643 when 'project' | 690 when 'project' |
644 if p = Project.visible.find_by_id(oid) | 691 if p = Project.visible.find_by_id(oid) |
645 link = link_to_project(p, {:only_path => only_path}, :class => 'project') | 692 link = link_to_project(p, {:only_path => only_path}, :class => 'project') |
646 end | 693 end |
647 end | 694 end |
657 when 'version' | 704 when 'version' |
658 if project && version = project.versions.visible.find_by_name(name) | 705 if project && version = project.versions.visible.find_by_name(name) |
659 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, | 706 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, |
660 :class => 'version' | 707 :class => 'version' |
661 end | 708 end |
709 when 'forum' | |
710 if project && board = project.boards.visible.find_by_name(name) | |
711 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project}, | |
712 :class => 'board' | |
713 end | |
714 when 'news' | |
715 if project && news = project.news.visible.find_by_title(name) | |
716 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, | |
717 :class => 'news' | |
718 end | |
662 when 'commit' | 719 when 'commit' |
663 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"])) | 720 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"])) |
664 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier}, | 721 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier}, |
665 :class => 'changeset', | 722 :class => 'changeset', |
666 :title => truncate_single_line(changeset.comments, :length => 100) | 723 :title => truncate_single_line(h(changeset.comments), :length => 100) |
667 end | 724 end |
668 when 'source', 'export' | 725 when 'source', 'export' |
669 if project && project.repository && User.current.allowed_to?(:browse_repository, project) | 726 if project && project.repository && User.current.allowed_to?(:browse_repository, project) |
670 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$} | 727 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$} |
671 path, rev, anchor = $1, $3, $5 | 728 path, rev, anchor = $1, $3, $5 |
687 link = link_to_project(p, {:only_path => only_path}, :class => 'project') | 744 link = link_to_project(p, {:only_path => only_path}, :class => 'project') |
688 end | 745 end |
689 end | 746 end |
690 end | 747 end |
691 end | 748 end |
692 leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}") | 749 (leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")).html_safe |
693 end | 750 end |
694 end | 751 end |
695 | 752 |
696 HEADING_RE = /<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>/i unless const_defined?(:HEADING_RE) | 753 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE) |
754 | |
755 def parse_sections(text, project, obj, attr, only_path, options) | |
756 return unless options[:edit_section_links] | |
757 text.gsub!(HEADING_RE) do | |
758 @current_section += 1 | |
759 if @current_section > 1 | |
760 content_tag('div', | |
761 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)), | |
762 :class => 'contextual', | |
763 :title => l(:button_edit_section)) + $1 | |
764 else | |
765 $1 | |
766 end | |
767 end | |
768 end | |
697 | 769 |
698 # Headings and TOC | 770 # Headings and TOC |
699 # Adds ids and links to headings unless options[:headings] is set to false | 771 # Adds ids and links to headings unless options[:headings] is set to false |
700 def parse_headings(text, project, obj, attr, only_path, options) | 772 def parse_headings(text, project, obj, attr, only_path, options) |
701 return if options[:headings] == false | 773 return if options[:headings] == false |
702 | 774 |
703 text.gsub!(HEADING_RE) do | 775 text.gsub!(HEADING_RE) do |
704 level, attrs, content = $1.to_i, $2, $3 | 776 level, attrs, content = $2.to_i, $3, $4 |
705 item = strip_tags(content).strip | 777 item = strip_tags(content).strip |
706 anchor = item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') | 778 anchor = sanitize_anchor_name(item) |
779 # used for single-file wiki export | |
780 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) | |
707 @parsed_headings << [level, anchor, item] | 781 @parsed_headings << [level, anchor, item] |
708 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">¶</a></h#{level}>" | 782 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">¶</a></h#{level}>" |
783 end | |
784 end | |
785 | |
786 MACROS_RE = / | |
787 (!)? # escaping | |
788 ( | |
789 \{\{ # opening tag | |
790 ([\w]+) # macro name | |
791 (\(([^\}]*)\))? # optional arguments | |
792 \}\} # closing tag | |
793 ) | |
794 /x unless const_defined?(:MACROS_RE) | |
795 | |
796 # Macros substitution | |
797 def parse_macros(text, project, obj, attr, only_path, options) | |
798 text.gsub!(MACROS_RE) do | |
799 esc, all, macro = $1, $2, $3.downcase | |
800 args = ($5 || '').split(',').each(&:strip) | |
801 if esc.nil? | |
802 begin | |
803 exec_macro(macro, obj, args) | |
804 rescue => e | |
805 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>" | |
806 end || all | |
807 else | |
808 all | |
809 end | |
709 end | 810 end |
710 end | 811 end |
711 | 812 |
712 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE) | 813 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE) |
713 | 814 |
745 # Same as Rails' simple_format helper without using paragraphs | 846 # Same as Rails' simple_format helper without using paragraphs |
746 def simple_format_without_paragraph(text) | 847 def simple_format_without_paragraph(text) |
747 text.to_s. | 848 text.to_s. |
748 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n | 849 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n |
749 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br | 850 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br |
750 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br | 851 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br |
852 html_safe | |
751 end | 853 end |
752 | 854 |
753 def lang_options_for_select(blank=true) | 855 def lang_options_for_select(blank=true) |
754 (blank ? [["(auto)", ""]] : []) + | 856 (blank ? [["(auto)", ""]] : []) + |
755 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last } | 857 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last } |
758 def label_tag_for(name, option_tags = nil, options = {}) | 860 def label_tag_for(name, option_tags = nil, options = {}) |
759 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") | 861 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") |
760 content_tag("label", label_text) | 862 content_tag("label", label_text) |
761 end | 863 end |
762 | 864 |
763 def labelled_tabular_form_for(name, object, options, &proc) | 865 def labelled_tabular_form_for(*args, &proc) |
866 args << {} unless args.last.is_a?(Hash) | |
867 options = args.last | |
764 options[:html] ||= {} | 868 options[:html] ||= {} |
765 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class) | 869 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class) |
766 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc) | 870 options.merge!({:builder => TabularFormBuilder}) |
871 form_for(*args, &proc) | |
872 end | |
873 | |
874 def labelled_form_for(*args, &proc) | |
875 args << {} unless args.last.is_a?(Hash) | |
876 options = args.last | |
877 options.merge!({:builder => TabularFormBuilder}) | |
878 form_for(*args, &proc) | |
767 end | 879 end |
768 | 880 |
769 def back_url_hidden_field_tag | 881 def back_url_hidden_field_tag |
770 back_url = params[:back_url] || request.env['HTTP_REFERER'] | 882 back_url = params[:back_url] || request.env['HTTP_REFERER'] |
771 back_url = CGI.unescape(back_url.to_s) | 883 back_url = CGI.unescape(back_url.to_s) |
772 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank? | 884 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank? |
773 end | 885 end |
774 | 886 |
775 def check_all_links(form_name) | 887 def check_all_links(form_name) |
776 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") + | 888 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") + |
777 " | " + | 889 " | ".html_safe + |
778 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)") | 890 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)") |
779 end | 891 end |
780 | 892 |
781 def progress_bar(pcts, options={}) | 893 def progress_bar(pcts, options={}) |
782 pcts = [pcts, pcts] unless pcts.is_a?(Array) | 894 pcts = [pcts, pcts] unless pcts.is_a?(Array) |
785 pcts << (100 - pcts[1] - pcts[0]) | 897 pcts << (100 - pcts[1] - pcts[0]) |
786 width = options[:width] || '100px;' | 898 width = options[:width] || '100px;' |
787 legend = options[:legend] || '' | 899 legend = options[:legend] || '' |
788 content_tag('table', | 900 content_tag('table', |
789 content_tag('tr', | 901 content_tag('tr', |
790 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') + | 902 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) + |
791 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') + | 903 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) + |
792 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '') | 904 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe) |
793 ), :class => 'progress', :style => "width: #{width};") + | 905 ), :class => 'progress', :style => "width: #{width};").html_safe + |
794 content_tag('p', legend, :class => 'pourcent') | 906 content_tag('p', legend, :class => 'pourcent').html_safe |
795 end | 907 end |
796 | 908 |
797 def checked_image(checked=true) | 909 def checked_image(checked=true) |
798 if checked | 910 if checked |
799 image_tag 'toggle_check.png' | 911 image_tag 'toggle_check.png' |
827 options.delete(:confirm) | 939 options.delete(:confirm) |
828 options.delete(:onclick) | 940 options.delete(:onclick) |
829 options[:class] << ' disabled' | 941 options[:class] << ' disabled' |
830 url = '#' | 942 url = '#' |
831 end | 943 end |
832 link_to name, url, options | 944 link_to h(name), url, options |
833 end | 945 end |
834 | 946 |
835 def calendar_for(field_id) | 947 def calendar_for(field_id) |
836 include_calendar_headers_tags | 948 include_calendar_headers_tags |
837 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) + | 949 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) + |
870 | 982 |
871 def has_content?(name) | 983 def has_content?(name) |
872 (@has_content && @has_content[name]) || false | 984 (@has_content && @has_content[name]) || false |
873 end | 985 end |
874 | 986 |
987 def email_delivery_enabled? | |
988 !!ActionMailer::Base.perform_deliveries | |
989 end | |
990 | |
875 # Returns the avatar image tag for the given +user+ if avatars are enabled | 991 # Returns the avatar image tag for the given +user+ if avatars are enabled |
876 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>') | 992 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>') |
877 def avatar(user, options = { }) | 993 def avatar(user, options = { }) |
878 if Setting.gravatar_enabled? | 994 if Setting.gravatar_enabled? |
879 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default}) | 995 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default}) |
887 else | 1003 else |
888 '' | 1004 '' |
889 end | 1005 end |
890 end | 1006 end |
891 | 1007 |
1008 def sanitize_anchor_name(anchor) | |
1009 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') | |
1010 end | |
1011 | |
892 # Returns the javascript tags that are included in the html layout head | 1012 # Returns the javascript tags that are included in the html layout head |
893 def javascript_heads | 1013 def javascript_heads |
894 tags = javascript_include_tag(:defaults) | 1014 tags = javascript_include_tag(:defaults) |
895 unless User.current.pref.warn_on_leaving_unsaved == '0' | 1015 unless User.current.pref.warn_on_leaving_unsaved == '0' |
896 tags << "\n" + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });") | 1016 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });") |
897 end | 1017 end |
898 tags | 1018 tags |
899 end | 1019 end |
900 | 1020 |
901 def favicon | 1021 def favicon |
902 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />" | 1022 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe |
903 end | 1023 end |
904 | 1024 |
905 def robot_exclusion_tag | 1025 def robot_exclusion_tag |
906 '<meta name="robots" content="noindex,follow,noarchive" />' | 1026 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe |
907 end | 1027 end |
908 | 1028 |
909 # Returns true if arg is expected in the API response | 1029 # Returns true if arg is expected in the API response |
910 def include_in_api_response?(arg) | 1030 def include_in_api_response?(arg) |
911 unless @included_in_api_response | 1031 unless @included_in_api_response |