Module: ApplicationHelper

Extended by:
Forwardable
Includes:
Gravatarify::Helper, Redmine::I18n
Included in:
IssuesHelper, JournalsHelper, Redmine::Hook::ViewListener, TimelogHelper
Defined in:
lib/redmine/themes.rb,
app/helpers/application_helper.rb

Constant Summary

%r{
  <a
  (?:
    (\shref=
      (?:                         # the href and link
        (?:'(\/[^>]+?)')|
        (?:"(\/[^>]+?)")
      )
    )|
    [^>]
  )*
  >
  [^<]*?<\/a>                     # content and closing link tag.
}x
HEADING_RE =
/<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>/i
TOC_RE =
/<p>\{%\s*toc(_right|_left)?\s*%\}<\/p>/i

Instance Method Summary (collapse)

Methods included from Redmine::I18n

#current_language, #day_name, #find_language, #format_date, #format_time, included, #l, #l_hours, #l_or_humanize, #ll, #month_name, #set_language_if_valid, #valid_languages

Instance Method Details

- (Object) accesskey(s)



445
446
447
# File 'app/helpers/application_helper.rb', line 445

def accesskey(s)
  Redmine::AccessKeys.key_for s
end

- (Object) api_meta(options)

Returns options or nil if nometa param or X-ChiliProject-Nometa header was set in the request



1018
1019
1020
1021
1022
1023
1024
1025
1026
# File 'app/helpers/application_helper.rb', line 1018

def api_meta(options)
  if params[:nometa].present? || request.headers['X-ChiliProject-Nometa']
    # compatibility mode for activeresource clients that raise
    # an error when unserializing an array with attributes
    nil
  else
    options
  end
end

- (Object) authoring(created, author, options = {})



320
321
322
# File 'app/helpers/application_helper.rb', line 320

def authoring(created, author, options={})
  l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
end

- (Object) authorize_for(controller, action)

Return true if user is authorized for controller/action, otherwise false



26
27
28
# File 'app/helpers/application_helper.rb', line 26

def authorize_for(controller, action)
  User.current.allowed_to?({:controller => controller, :action => action}, @project)
end

- (Object) avatar(user, options = { })

Returns the avatar image tag for the given user if avatars are enabled user can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')



963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
# File 'app/helpers/application_helper.rb', line 963

def avatar(user, options = { })
  if Setting.gravatar_enabled?
    if user.is_a?(Group)
      size = options[:size] || 50
      size = "#{size}x#{size}" # image_tag uses WxH
      options[:class] ||= 'gravatar'
      return image_tag("group.png", options.merge(:size => size))
    end
    options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
    email = nil
    if user.respond_to?(:mail)
      email = user.mail
    elsif user.to_s =~ %r{<(.+?)>}
      email = $1
    end
    return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
  else
    ''
  end
end

- (Object) back_url_hidden_field_tag



852
853
854
855
856
# File 'app/helpers/application_helper.rb', line 852

def back_url_hidden_field_tag
  back_url = params[:back_url] || request.env['HTTP_REFERER']
  back_url = CGI.unescape(back_url.to_s)
  hidden_field_tag('back_url', CGI.escape(back_url), :id => nil) unless back_url.blank?
end

- (Object) body_css_classes

Returns the theme, controller name, and action as css classes for the HTML body.



433
434
435
436
437
438
439
440
441
442
443
# File 'app/helpers/application_helper.rb', line 433

def body_css_classes
  css = []
  if theme = Redmine::Themes.theme(Setting.ui_theme)
    css << 'theme-' + theme.name
  end

  css << 'project-' + @project.id.to_s if @project.present?
  css << 'controller-' + params[:controller] if params[:controller]
  css << 'action-' + params[:action] if params[:action]
  css.join(' ')
end


385
386
387
388
# File 'app/helpers/application_helper.rb', line 385

def breadcrumb(*args)
  elements = args.flatten
  elements.any? ? ('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
end

- (Object) calendar_for(field_id)



916
917
918
# File 'app/helpers/application_helper.rb', line 916

def calendar_for(field_id)
  javascript_tag("jQuery('##{field_id}').datepicker(datepickerSettings)")
end


858
859
860
861
862
# File 'app/helpers/application_helper.rb', line 858

def check_all_links(form_name)
  link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
  " | " +
  link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
end

- (Object) checked_image(checked = true)



878
879
880
881
882
# File 'app/helpers/application_helper.rb', line 878

def checked_image(checked=true)
  if checked
    image_tag 'toggle_check.png'
  end
end

- (Object) content_for(name, content = nil, &block)



938
939
940
941
942
# File 'app/helpers/application_helper.rb', line 938

def content_for(name, content = nil, &block)
  @has_content ||= {}
  @has_content[name] = true
  super(name, content, &block)
end

- (Object) context_menu(url)



884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
# File 'app/helpers/application_helper.rb', line 884

def context_menu(url)
  unless @context_menu_included
    content_for :header_tags do
      javascript_include_tag('context_menu.jquery') +
        stylesheet_link_tag('context_menu')
    end
    if l(:direction) == 'rtl'
      content_for :header_tags do
        stylesheet_link_tag('context_menu_rtl')
      end
    end
    @context_menu_included = true
  end
  javascript_tag "jQuery(document).ContextMenu('#{ url_for(url) }')"
end


900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
# File 'app/helpers/application_helper.rb', line 900

def context_menu_link(name, url, options={})
  options[:class] ||= ''
  if options.delete(:selected)
    options[:class] << ' icon-checked disabled'
    options[:disabled] = true
  end
  if options.delete(:disabled)
    options.delete(:method)
    options.delete(:confirm)
    options.delete(:onclick)
    options[:class] << ' disabled'
    url = '#'
  end
  link_to h(name), url, options
end

- (Object) current_theme



110
111
112
113
114
115
# File 'lib/redmine/themes.rb', line 110

def current_theme
  unless instance_variable_defined?(:@current_theme)
    @current_theme = Redmine::Themes.theme(Setting.ui_theme)
  end
  @current_theme
end

- (Object) due_date_distance_in_words(date)



185
186
187
188
189
# File 'app/helpers/application_helper.rb', line 185

def due_date_distance_in_words(date)
  if date
    l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
  end
end

- (Object) expand_current_menu

Expands the current menu item using JavaScript based on the params



1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
# File 'app/helpers/application_helper.rb', line 1029

def expand_current_menu
  current_menu_class =
    case
    when params[:controller] == "timelog"
      "reports"
    when params[:controller] == 'projects' && params[:action] == 'changelog'
      "reports"
    when params[:controller] == 'issues' && ['calendar','gantt'].include?(params[:action])
      "reports"
    when params[:controller] == 'projects' && params[:action] == 'roadmap'
      'roadmap'
    when params[:controller] == 'versions' && params[:action] == 'show'
      'roadmap'
    when params[:controller] == 'projects' && params[:action] == 'settings'
      'settings'
    when params[:controller] == 'contracts' || params[:controller] == 'deliverables'
      'contracts'
    else
      params[:controller]
    end


  javascript_tag("jQuery.menu_expand({ menuItem: '.#{current_menu_class}' });")
end

- (Object) favicon



994
995
996
# File 'app/helpers/application_helper.rb', line 994

def favicon
  "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
end

- (Object) format_activity_day(date)



169
170
171
# File 'app/helpers/application_helper.rb', line 169

def format_activity_day(date)
  date == Date.today ? l(:label_today).titleize : format_date(date)
end

- (Object) format_activity_description(text)



173
174
175
# File 'app/helpers/application_helper.rb', line 173

def format_activity_description(text)
  h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
end

- (Object) format_activity_title(text)



165
166
167
# File 'app/helpers/application_helper.rb', line 165

def format_activity_title(text)
  h(truncate_single_line(text, :length => 100))
end

- (Object) format_version_name(version)



177
178
179
180
181
182
183
# File 'app/helpers/application_helper.rb', line 177

def format_version_name(version)
  if version.project == @project
  	h(version)
  else
    h("#{version.project} - #{version}")
  end
end

- (Object) gravatar(email, options = {})

Returns the gravatar image tag for the given email email is a string with an email address



950
951
952
953
954
955
956
957
958
959
# File 'app/helpers/application_helper.rb', line 950

def gravatar(email, options={})
  gravatarify_options = {}
  gravatarify_options[:secure] = options.delete :ssl
  [:default, :size, :rating, :filetype].each {|key| gravatarify_options[key] = options.delete key}
  # Default size is 50x50 px
  gravatarify_options[:size] ||= 50
  options[:class] ||= 'gravatar'
  gravatarify_options[:html] = options
  gravatar_tag email, gravatarify_options
end

- (Boolean) has_content?(name)



944
945
946
# File 'app/helpers/application_helper.rb', line 944

def has_content?(name)
  (@has_content && @has_content[name]) || false
end

- (Object) heads_for_theme

Returns the header tags for the current theme



130
131
132
133
134
# File 'lib/redmine/themes.rb', line 130

def heads_for_theme
  if current_theme && current_theme.javascripts.include?('theme')
    javascript_include_tag current_theme.javascript_path('theme')
  end
end

- (Object) help_menu_item



1064
1065
1066
# File 'app/helpers/application_helper.rb', line 1064

def help_menu_item
  split_top_menu_into_main_or_more_menus[:help]
end

- (Object) html_hours(text)



316
317
318
# File 'app/helpers/application_helper.rb', line 316

def html_hours(text)
  text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
end

- (Object) html_title(*args)



418
419
420
421
422
423
424
425
426
427
428
429
# File 'app/helpers/application_helper.rb', line 418

def html_title(*args)
  if args.empty?
    title = []
    title << h(@project.name) if @project
    title += @html_title if @html_title
    title << h(Setting.app_title)
    title.select {|t| !t.blank? }.join(' - ')
  else
    @html_title ||= []
    @html_title += args
  end
end

- (Object) image_to_function(name, function, html_options = {})



152
153
154
155
156
157
158
# File 'app/helpers/application_helper.rb', line 152

def image_to_function(name, function, html_options = {})
  html_options.symbolize_keys!
  tag(:input, html_options.merge({
      :type => "image", :src => image_path(name),
      :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
      }))
end

- (Boolean) include_in_api_response?(arg)

Returns true if arg is expected in the API response



1007
1008
1009
1010
1011
1012
1013
1014
# File 'app/helpers/application_helper.rb', line 1007

def include_in_api_response?(arg)
  unless @included_in_api_response
    param = params[:include]
    @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
    @included_in_api_response.collect!(&:strip)
  end
  @included_in_api_response.include?(arg.to_s)
end

- (Object) javascript_heads

Returns the javascript tags that are included in the html layout head



985
986
987
988
989
990
991
992
# File 'app/helpers/application_helper.rb', line 985

def javascript_heads
  tags = javascript_include_tag(:defaults)
  unless User.current.pref.warn_on_leaving_unsaved == '0'
    tags << "\n" + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
  end
  tags << jquery_datepicker_settings
  tags
end

- (Object) jquery_datepicker_settings



920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
# File 'app/helpers/application_helper.rb', line 920

def jquery_datepicker_settings
  start_of_week = Setting.start_of_week.to_s
  start_of_week_string = start_of_week.present? ? "firstDay: '#{start_of_week}', " : ''
  script = javascript_tag("var datepickerSettings = {" +
                 start_of_week_string +
                 "showOn: 'both', " +
                 "buttonImage: '" + path_to_image('/images/calendar.png') + "', " +
                 "buttonImageOnly: true, " +
                 "showButtonPanel: true, " +
                 "dateFormat: 'yy-mm-dd' " +
                 "}")
  unless current_language == :en
    jquery_locale = l("jquery.ui", :default => current_language.to_s)
    script << javascript_include_tag("libs/ui/i18n/jquery.ui.datepicker-#{jquery_locale}.js")
  end
  script
end

- (Object) label_tag_for(name, option_tags = nil, options = {})



841
842
843
844
# File 'app/helpers/application_helper.rb', line 841

def label_tag_for(name, option_tags = nil, options = {})
  label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.("span", " *", :class => "required"): "")
  ("label", label_text)
end

- (Object) labelled_tabular_form_for(name, object, options, &proc)



846
847
848
849
850
# File 'app/helpers/application_helper.rb', line 846

def labelled_tabular_form_for(name, object, options, &proc)
  options[:html] ||= {}
  options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
  form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
end

- (Object) lang_options_for_select(blank = true)



836
837
838
839
# File 'app/helpers/application_helper.rb', line 836

def lang_options_for_select(blank=true)
  (blank ? [["(auto)", ""]] : []) +
    valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
end

Generates a link to an attachment. Options:

  • :text - Link text (default to attachment filename)

  • :download - Force download (default: false)



96
97
98
99
100
101
# File 'app/helpers/application_helper.rb', line 96

def link_to_attachment(attachment, options={})
  text = options.delete(:text) || attachment.filename
  action = options.delete(:download) ? 'download' : 'show'

  link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
end

Display a link if user is authorized



36
37
38
# File 'app/helpers/application_helper.rb', line 36

def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
  link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
end

Displays a link to issue with its subject. Examples:

link_to_issue(issue)                        # => Defect #6: This is the subject
link_to_issue(issue, :truncate => 6)        # => Defect #6: This i...
link_to_issue(issue, :subject => false)     # => Defect #6
link_to_issue(issue, :project => true)      # => Foo - Defect #6


73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'app/helpers/application_helper.rb', line 73

def link_to_issue(issue, options={})
  title = nil
  subject = nil
  if options[:subject] == false
    title = truncate(issue.subject, :length => 60)
  else
    subject = issue.subject
    if options[:truncate]
      subject = truncate(subject, :length => options[:truncate])
    end
  end
  s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
                                               :class => issue.css_classes,
                                               :title => title
  s << ": #{h subject}" if subject
  s = "#{h issue.project} - " + s if options[:project]
  s
end

Generates a link to a message



115
116
117
118
119
120
121
122
123
124
125
126
# File 'app/helpers/application_helper.rb', line 115

def link_to_message(message, options={}, html_options = nil)
  link_to(
    h(truncate(message.subject, :length => 60)),
    { :controller => 'messages', :action => 'show',
      :board_id => message.board_id,
      :id => message.root,
      :r => (message.parent_id && message.id),
      :anchor => (message.parent_id ? "message-#{message.id}" : nil)
    }.merge(options),
    html_options
  )
end

Generates a link to a project if active Examples:

link_to_project(project)                          # => link to the specified project overview
link_to_project(project, :action=>'settings')     # => link to project settings
link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
link_to_project(project, {}, :class => "project") # => html options with default url (project overview)


136
137
138
139
140
141
142
143
# File 'app/helpers/application_helper.rb', line 136

def link_to_project(project, options={}, html_options = nil)
  if project.active?
    url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
    link_to(h(project), url, html_options)
  else
    h(project)
  end
end

Display a link to remote if user is authorized



41
42
43
44
# File 'app/helpers/application_helper.rb', line 41

def link_to_remote_if_authorized(name, options = {}, html_options = nil)
  url = options[:url] || {}
  link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
end

Generates a link to a SCM revision Options:

  • :text - Link text (default to the formatted revision)



106
107
108
109
110
111
112
# File 'app/helpers/application_helper.rb', line 106

def link_to_revision(revision, project, options={})
  text = options.delete(:text) || format_revision(revision)
  rev = revision.respond_to?(:identifier) ? revision.identifier : revision

  link_to(h(text), {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev},
          :title => l(:label_revision_id, format_revision(revision)))
end

Displays a link to user's account page if active



47
48
49
50
51
52
53
54
55
56
57
58
# File 'app/helpers/application_helper.rb', line 47

def link_to_user(user, options={})
  if user.is_a?(User)
    name = h(user.name(options[:format]))
    if user.active?
      link_to name, :controller => 'users', :action => 'show', :id => user
    else
      name
    end
  else
    h(user.to_s)
  end
end

- (Object) list_users(users, options = {})

Show a sorted linkified (if active) comma-joined list of users



61
62
63
# File 'app/helpers/application_helper.rb', line 61

def list_users(users, options={})
  users.sort.collect{|u| link_to_user(u, options)}.join(", ")
end

- (Object) main_top_menu_items

Menu items for the main top menu



1055
1056
1057
# File 'app/helpers/application_helper.rb', line 1055

def main_top_menu_items
  split_top_menu_into_main_or_more_menus[:main]
end

- (Object) more_top_menu_items

Menu items for the more top menu



1060
1061
1062
# File 'app/helpers/application_helper.rb', line 1060

def more_top_menu_items
  split_top_menu_into_main_or_more_menus[:more]
end


390
391
392
393
394
# File 'app/helpers/application_helper.rb', line 390

def other_formats_links(&block)
  concat('<p class="other-formats">' + l(:label_export_to))
  yield Redmine::Views::OtherFormatsBuilder.new(self)
  concat('</p>')
end

- (Object) page_header_title



396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
# File 'app/helpers/application_helper.rb', line 396

def page_header_title
  if @page_header_title.present?
    h(@page_header_title)
  elsif @project.nil? || @project.new_record?
    h(Setting.app_title)
  else
    b = []
    ancestors = (@project.root? ? [] : @project.ancestors.visible)
    if ancestors.any?
      root = ancestors.shift
      b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
      if ancestors.size > 2
        b << '&#8230;'
        ancestors = ancestors[-2, 2]
      end
      b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
    end
    b << h(@project)
    b.join(' &#187; ')
  end
end


341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
# File 'app/helpers/application_helper.rb', line 341

def pagination_links_full(paginator, count=nil, options={})
  page_param = options.delete(:page_param) || :page
  per_page_links = options.delete(:per_page_links)
  url_param = params.dup
  # don't reuse query params if filters are present
  url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)

  html = ''
  if paginator.current.previous
    html << link_to_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
  end

  html << (pagination_links_each(paginator, options) do |n|
    link_to_content_update(n.to_s, url_param.merge(page_param => n))
  end || '')

  if paginator.current.next
    html << ' ' + link_to_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
  end

  unless count.nil?
    html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
    if per_page_links != false && links = per_page_links(paginator.items_per_page)
     html << " | #{links}"
    end
  end

  html
end

- (Object) parse_headings(text, project, obj, attr, only_path, options)

Headings and TOC Adds ids and links to headings unless options is set to false



778
779
780
781
782
783
784
785
786
787
788
# File 'app/helpers/application_helper.rb', line 778

def parse_headings(text, project, obj, attr, only_path, options)
  return if options[:headings] == false

  text.gsub!(HEADING_RE) do
    level, attrs, content = $1.to_i, $2, $3
    item = strip_tags(content).strip
    anchor = item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
    @parsed_headings << [level, anchor, item]
    "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
  end
end

- (Object) parse_inline_attachments(text, project, obj, attr, only_path, options)



582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
# File 'app/helpers/application_helper.rb', line 582

def parse_inline_attachments(text, project, obj, attr, only_path, options)
  # when using an image link, try to use an attachment, if possible
  if options[:attachments] || (obj && obj.respond_to?(:attachments))
    attachments = nil
    text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
      filename, ext, alt, alttext = $1.downcase, $2, $3, $4
      attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
      # search for the picture in attachments
      if found = attachments.detect { |att| att.filename.downcase == filename }
        image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
        desc = found.description.to_s.gsub('"', '')
        if !desc.blank? && alttext.blank?
          alt = " title=\"#{desc}\" alt=\"#{desc}\""
        end
        "src=\"#{image_url}\"#{alt}"
      else
        m
      end
    end
  end
end

- (Object) parse_non_pre_blocks(text)



518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
# File 'app/helpers/application_helper.rb', line 518

def parse_non_pre_blocks(text)
  s = StringScanner.new(text)
  tags = []
  parsed = ''
  while !s.eos?
    s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
    text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
    if tags.empty?
      yield text
    end
    parsed << text
    if tag
      if closing
        if tags.last == tag.downcase
          tags.pop
        end
      else
        tags << tag.downcase
      end
      parsed << full_tag
    end
  end
  # Close any non closing tags
  while tag = tags.pop
    parsed << "</#{tag}>"
  end
  parsed
end

Redmine links

Examples:

Issues:
  #52 -> Link to issue #52
Changesets:
  r52 -> Link to revision 52
  commit:a85130f -> Link to scmid starting with a85130f
Documents:
  document#17 -> Link to document with id 17
  document:Greetings -> Link to the document with title "Greetings"
  document:"Some document" -> Link to the document with title "Some document"
Versions:
  version#3 -> Link to version with id 3
  version:1.0.0 -> Link to version named "1.0.0"
  version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
Attachments:
  attachment:file.zip -> Link to the attachment of the current object named file.zip
Source files:
  source:some/file -> Link to the file located at /some/file in the project's repository
  source:some/file@52 -> Link to the file's revision 52
  source:some/file#L120 -> Link to line 120 of the file
  source:some/file@52#L120 -> Link to line 120 of the file's revision 52
  export:some/file -> Force the download of the file
Forum messages:
  message#1218 -> Link to message with id 1218

Links can refer other objects from other projects, using project identifier:
  identifier:r52
  identifier:document:"Some document"
  identifier:version:1.0.0
  identifier:source:some/file


683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
# File 'app/helpers/application_helper.rb', line 683

def parse_redmine_links(text, project, obj, attr, only_path, options)
  text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
    leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
    link = nil
    if project_identifier
      project = Project.visible.find_by_identifier(project_identifier)
    end
    if esc.nil?
      if prefix.nil? && sep == 'r'
        # project.changesets.visible raises an SQL error because of a double join on repositories
        if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
          link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
                                    :class => 'changeset',
                                    :title => truncate_single_line(changeset.comments, :length => 100))
        end
      elsif sep == '#'
        oid = identifier.to_i
        case prefix
        when nil
          if issue = Issue.visible.find_by_id(oid, :include => :status)
            link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
                                      :class => issue.css_classes,
                                      :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
          end
        when 'document'
          if document = Document.visible.find_by_id(oid)
            link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
                                              :class => 'document'
          end
        when 'version'
          if version = Version.visible.find_by_id(oid)
            link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
                                            :class => 'version'
          end
        when 'message'
          if message = Message.visible.find_by_id(oid, :include => :parent)
            link = link_to_message(message, {:only_path => only_path}, :class => 'message')
          end
        when 'project'
          if p = Project.visible.find_by_id(oid)
            link = link_to_project(p, {:only_path => only_path}, :class => 'project')
          end
        end
      elsif sep == ':'
        # removes the double quotes if any
        name = identifier.gsub(%r{^"(.*)"$}, "\\1")
        case prefix
        when 'document'
          if project && document = project.documents.visible.find_by_title(name)
            link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
                                              :class => 'document'
          end
        when 'version'
          if project && version = project.versions.visible.find_by_name(name)
            link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
                                            :class => 'version'
          end
        when 'commit'
          if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
            link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
                                         :class => 'changeset',
                                         :title => truncate_single_line(h(changeset.comments), :length => 100)
          end
        when 'source', 'export'
          if project && project.repository && User.current.allowed_to?(:browse_repository, project)
            name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
            path, rev, anchor = $1, $3, $5
            link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
                                                    :path => to_path_param(path),
                                                    :rev => rev,
                                                    :anchor => anchor,
                                                    :format => (prefix == 'export' ? 'raw' : nil)},
                                                   :class => (prefix == 'export' ? 'source download' : 'source')
          end
        when 'attachment'
          attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
          if attachments && attachment = attachments.detect {|a| a.filename == name }
            link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
                                                   :class => 'attachment'
          end
        when 'project'
          if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
            link = link_to_project(p, {:only_path => only_path}, :class => 'project')
          end
        end
      end
    end
    leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")
  end
end

- (Object) parse_relative_urls(text, project, obj, attr, only_path, options)



562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
# File 'app/helpers/application_helper.rb', line 562

def parse_relative_urls(text, project, obj, attr, only_path, options)
  return if only_path
  text.gsub!(RELATIVE_LINK_RE) do |m|
    href, relative_url = $1, $2 || $3
    next m unless href.present?
    if defined?(request) && request.present?
      # we have a request!
      protocol, host_with_port = request.protocol, request.host_with_port
    elsif @controller
      # use the same methods as url_for in the Mailer
      url_opts = @controller.class.default_url_options
      next m unless url_opts && url_opts[:protocol] && url_opts[:host]
      protocol, host_with_port = "#{url_opts[:protocol]}://", url_opts[:host]
    else
      next m
    end
    m.sub href, " href=\"#{protocol}#{host_with_port}#{relative_url}\""
  end
end

Wiki links

Examples:

[[mypage]]
[[mypage|mytext]]

wiki links can refer other project wikis, using project name or identifier:

[[project:]] -> wiki starting page
[[project:|mytext]]
[[project:mypage]]
[[project:mypage|mytext]]


614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
# File 'app/helpers/application_helper.rb', line 614

def parse_wiki_links(text, project, obj, attr, only_path, options)
  text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
    link_project = project
    esc, all, page, title = $1, $2, $3, $5
    if esc.nil?
      if page =~ /^([^\:]+)\:(.*)$/
        link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
        page = $2
        title ||= $1 if page.blank?
      end

      if link_project && link_project.wiki
        # extract anchor
        anchor = nil
        if page =~ /^(.+?)\#(.+)$/
          page, anchor = $1, $2
        end
        # check if page exists
        wiki_page = link_project.wiki.find_page(page)
        url = case options[:wiki_links]
          when :local; "#{title}.html"
          when :anchor; "##{title}"   # used for single-file wiki export
          else
            wiki_page_id = page.present? ? Wiki.titleize(page) : nil
            url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
          end
        link_to(h(title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
      else
        # project or wiki doesn't exist
        all
      end
    else
      all
    end
  end
end

- (Object) path_to_stylesheet(source)



125
126
127
# File 'lib/redmine/themes.rb', line 125

def path_to_stylesheet(source)
  stylesheet_path source
end


371
372
373
374
375
376
# File 'app/helpers/application_helper.rb', line 371

def per_page_links(selected=nil)
  links = Setting.per_page_options_array.collect do |n|
    n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
  end
  links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
end

- (Object) principals_check_box_tags(name, principals)



285
286
287
288
289
290
291
# File 'app/helpers/application_helper.rb', line 285

def principals_check_box_tags(name, principals)
  s = ''
  principals.sort.each do |principal|
    s << "<label style='display:block;'>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
  end
  s
end

- (Object) progress_bar(pcts, options = {})



864
865
866
867
868
869
870
871
872
873
874
875
876
# File 'app/helpers/application_helper.rb', line 864

def progress_bar(pcts, options={})
  pcts = [pcts, pcts] unless pcts.is_a?(Array)
  pcts = pcts.collect(&:round)
  pcts[1] = pcts[1] - pcts[0]
  pcts << (100 - pcts[1] - pcts[0])
  width = options[:width] || '100px;'
  legend = options[:legend] || ''
  ('div',
    ('div', '', :style => "width: #{pcts[0]}%;", :class => 'closed ui-progressbar-value ui-widget-header ui-corner-left') +
    ('div', '', :style => "width: #{pcts[1]}%;", :class => 'done ui-progressbar-value ui-widget-header'),
    :class => 'progress ui-progressbar ui-widget ui-widget-content ui-corner-all', :style => "width: #{width};") +
    ('p', legend, :class => 'pourcent')
end

- (Object) project_nested_ul(projects, &block)



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'app/helpers/application_helper.rb', line 261

def project_nested_ul(projects, &block)
  s = ''
  if projects.any?
    ancestors = []
    projects.sort_by(&:lft).each do |project|
      if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
        s << "<ul>\n"
      else
        ancestors.pop
        s << "</li>"
        while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
          ancestors.pop
          s << "</ul></li>\n"
        end
      end
      s << "<li>"
      s << yield(project).to_s
      ancestors << project
    end
    s << ("</li></ul>\n" * ancestors.size)
  end
  s
end

- (Object) project_tree(projects, &block)

Yields the given block for each project with its level in the tree

Wrapper for Project#project_tree



257
258
259
# File 'app/helpers/application_helper.rb', line 257

def project_tree(projects, &block)
  Project.project_tree(projects, &block)
end

- (Object) project_tree_options_for_select(projects, options = {})



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'app/helpers/application_helper.rb', line 238

def project_tree_options_for_select(projects, options = {})
  s = ''
  project_tree(projects) do |project, level|
    name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
    tag_options = {:value => project.id}
    if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
      tag_options[:selected] = 'selected'
    else
      tag_options[:selected] = nil
    end
    tag_options.merge!(yield(project)) if block_given?
    s << ('option', name_prefix + h(project), tag_options)
  end
  s
end

- (Object) projects_check_box_tags(name, projects)



293
294
295
296
297
298
299
# File 'app/helpers/application_helper.rb', line 293

def projects_check_box_tags(name, projects)
  s = ''
  projects.each do |project|
    s << "<label>#{ check_box_tag name, project.id, false } #{h project}</label>\n"
  end
  s
end

- (Object) prompt_to_remote(name, text, param, url, html_options = {})



160
161
162
163
# File 'app/helpers/application_helper.rb', line 160

def prompt_to_remote(name, text, param, url, html_options = {})
  html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
  link_to name, {}, html_options
end

- (Object) render_flash_messages

Renders flash messages



208
209
210
211
212
213
214
# File 'app/helpers/application_helper.rb', line 208

def render_flash_messages
  s = ''
  flash.each do |k,v|
    s << ('div', v, :class => "flash #{k}")
  end
  s
end

- (Object) render_page_hierarchy(pages, node = nil, options = {})



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'app/helpers/application_helper.rb', line 191

def render_page_hierarchy(pages, node=nil, options={})
  content = ''
  if pages[node]
    content << "<ul class=\"pages-hierarchy\">\n"
    pages[node].each do |page|
      content << "<li>"
      content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
                         :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
      content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
      content << "</li>\n"
    end
    content << "</ul>\n"
  end
  content
end

- (Object) render_project_jump_box(projects = [], html_options = {})

Renders the project quick-jump box



226
227
228
229
230
231
232
233
234
235
236
# File 'app/helpers/application_helper.rb', line 226

def render_project_jump_box(projects = [], html_options = {})
  projects ||= User.current.memberships.collect(&:project).compact.uniq
  if projects.any?
      # option_tags = content_tag :option, l(:label_jump_to_a_project), :value => ""
      option_tags = ( :option, "", :value => "" )
      option_tags << project_tree_options_for_select(projects, :selected => @project) do |p|
        { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
      end
    select_tag "", option_tags, html_options.merge({ :onchange => "if (this.value != \'\') { window.location = this.value; }" })
  end
end

- (Object) render_tabs(tabs)

Renders tabs and their content



217
218
219
220
221
222
223
# File 'app/helpers/application_helper.rb', line 217

def render_tabs(tabs)
  if tabs.any?
    render :partial => 'common/tabs', :locals => {:tabs => tabs}
  else
     'p', l(:label_no_data), :class => "nodata"
  end
end


378
379
380
381
382
383
# File 'app/helpers/application_helper.rb', line 378

def reorder_links(name, url)
  link_to(image_tag('2uparrow.png',   :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
  link_to(image_tag('1uparrow.png',   :alt => l(:label_sort_higher)),  url.merge({"#{name}[move_to]" => 'higher'}),  :method => :post, :title => l(:label_sort_higher)) +
  link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),   url.merge({"#{name}[move_to]" => 'lower'}),   :method => :post, :title => l(:label_sort_lower)) +
  link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),  url.merge({"#{name}[move_to]" => 'lowest'}),  :method => :post, :title => l(:label_sort_lowest))
end

- (Object) replace_toc(text, headings)

Renders the TOC with given headings



793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
# File 'app/helpers/application_helper.rb', line 793

def replace_toc(text, headings)
  text.gsub!(TOC_RE) do
    if headings.empty?
      ''
    else
      toc_class = 'toc'
      toc_class << ' right' if $1 == '_right'
      toc_class << ' left' if $1 == '_left'

      out = "<fieldset class=\"header_collapsible collapsible #{toc_class}\">"
      out << "<legend onclick=\"toggleFieldset(this);\"><span>#{l(:label_toc)}</span></legend>"
      out << "<div>"
      out << "<ul class=\"toc\"><li>"
      root = headings.map(&:first).min
      current = root
      started = false
      headings.each do |level, anchor, item|
        if level > current
          out << '<ul><li>' * (level - current)
        elsif level < current
          out << "</li></ul>\n" * (current - level) + "</li><li>"
        elsif started
          out << '</li><li>'
        end
        out << "<a href=\"##{anchor}\">#{item}</a>"
        current = level
        started = true
      end
      out << '</li></ul>' * (current - root)
      out << '</li></ul>'
      out << '</div></fieldset>'
    end
  end
end

- (Object) robot_exclusion_tag(content = "NOINDEX,FOLLOW,NOARCHIVE")

Add a HTML meta tag to control robots (web spiders)



1002
1003
1004
# File 'app/helpers/application_helper.rb', line 1002

def robot_exclusion_tag(content="NOINDEX,FOLLOW,NOARCHIVE")
  "<meta name='ROBOTS' content='#{h(content)}' />"
end

- (Object) simple_format_without_paragraph(text)

Same as Rails' simple_format helper without using paragraphs



829
830
831
832
833
834
# File 'app/helpers/application_helper.rb', line 829

def simple_format_without_paragraph(text)
  text.to_s.
    gsub(/\r\n?/, "\n").                    # \r\n and \r -> \n
    gsub(/\n\n+/, "<br /><br />").          # 2+ newline  -> 2 br
    gsub(/([^\n]\n)(?=[^\n])/, '\1<br />')  # 1 newline   -> br
end

- (Object) split_top_menu_into_main_or_more_menus

Split the :top_menu into separate :main and :more items



1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
# File 'app/helpers/application_helper.rb', line 1069

def split_top_menu_into_main_or_more_menus
  unless @top_menu_split
    items_for_main_level = []
    items_for_more_level = []
    help_menu = nil
    menu_items_for(:top_menu) do |item|
      if item.name == :home || item.name == :my_page
        items_for_main_level << item
      elsif item.name == :help
        help_menu = item
      elsif item.name == :projects
        # Remove, present in layout
      else
        items_for_more_level << item
      end
    end
    @top_menu_split = {
      :main => items_for_main_level,
      :more => items_for_more_level,
      :help => help_menu
    }
  end
  @top_menu_split
end

- (Object) stylesheet_path(source)



117
118
119
120
121
122
123
# File 'lib/redmine/themes.rb', line 117

def stylesheet_path(source)
  if current_theme && current_theme.stylesheets.include?(source)
    super current_theme.stylesheet_path(source)
  else
    super
  end
end

- (Object) syntax_highlight(name, content)



333
334
335
# File 'app/helpers/application_helper.rb', line 333

def syntax_highlight(name, content)
  Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
end

- (Object) textilizable(*args)

Formats text according to system settings. 2 ways to call this method:

  • with a String: textilizable(text, options)

  • with an object and one of its attribute: textilizable(issue, :description, options)



453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
# File 'app/helpers/application_helper.rb', line 453

def textilizable(*args)
  options = args.last.is_a?(Hash) ? args.pop : {}
  case args.size
  when 1
    obj = options[:object]
    input_text = args.shift
  when 2
    obj = args.shift
    attr = args.shift
    input_text = obj.send(attr).to_s
  else
    raise ArgumentError, 'invalid arguments to textilizable'
  end
  return '' if input_text.blank?
  project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
  only_path = options.delete(:only_path) == false ? false : true

  begin
    text = ChiliProject::Liquid::Legacy.run_macros(input_text)
    liquid_template = ChiliProject::Liquid::Template.parse(text)
    liquid_variables = get_view_instance_variables_for_liquid
    liquid_variables.merge!({'current_user' => User.current})
    liquid_variables.merge!({'toc' => '{{toc}}'}) # Pass toc through to replace later
    liquid_variables.merge!(ChiliProject::Liquid::Variables.all)

    # Pass :view in a register so this view (with helpers) can be used inside of a tag
    text = liquid_template.render(liquid_variables, :registers => {:view => self, :object => obj, :attribute => attr})

    # Add Liquid errors to the log
    if Rails.logger && Rails.logger.debug?
      msg = ""
      liquid_template.errors.each do |exception|
        msg << "[Liquid Error] #{exception.message}\n:\n#{exception.backtrace.join("\n")}"
        msg << "\n\n"
      end
      Rails.logger.debug msg
    end
  rescue Liquid::SyntaxError => exception
    msg = "[Liquid Syntax Error] #{exception.message}"
    if Rails.logger && Rails.logger.debug?
      log_msg = "#{msg}\n"
      log_msg << exception.backtrace.collect{ |str| "    #{str}" }.join("\n")
      log_msg << "\n\n"
      Rails.logger.debug log_msg
    end

    # Skip Liquid if there is a syntax error
    text = (:div, msg, :class => "flash error")
    text << h(input_text)
  end

  @parsed_headings = []
  text = parse_non_pre_blocks(text) do |text|
    [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings, :parse_relative_urls].each do |method_name|
      send method_name, text, project, obj, attr, only_path, options
    end
  end

  if @parsed_headings.any?
    replace_toc(text, @parsed_headings)
  end

  text
end

- (Object) time_tag(time)



324
325
326
327
328
329
330
331
# File 'app/helpers/application_helper.rb', line 324

def time_tag(time)
  text = distance_of_time_in_words(Time.now, time)
  if @project
    link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
  else
    ('acronym', text, :title => format_time(time))
  end
end

- (Object) to_path_param(path)



337
338
339
# File 'app/helpers/application_helper.rb', line 337

def to_path_param(path)
  path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
end


145
146
147
148
149
150
# File 'app/helpers/application_helper.rb', line 145

def toggle_link(name, id, options={})
  onclick = "Element.toggle('#{id}'); "
  onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
  onclick << "return false;"
  link_to(name, "#", :onclick => onclick)
end

- (Object) truncate_lines(string, options = {})

Truncates at line break after 250 characters or options



307
308
309
310
311
312
313
314
# File 'app/helpers/application_helper.rb', line 307

def truncate_lines(string, options={})
  length = options[:length] || 250
  if string.to_s =~ /\A(.{#{length}}.*?)$/m
    "#{$1}..."
  else
    string
  end
end

- (Object) truncate_single_line(string, *args)

Truncates and returns the string as a single line



302
303
304
# File 'app/helpers/application_helper.rb', line 302

def truncate_single_line(string, *args)
  truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
end