annotate app/helpers/.svn/text-base/application_helper.rb.svn-base @ 45:65d9e2cabaa3 luisf

Added tipoftheday to the config/settings in order to correct previous issues. Tip of the day is now working correctly. Added the heading strings to the locales files.
author luisf
date Tue, 23 Nov 2010 11:50:01 +0000
parents 94944d00e43c
children af80e5618e9b 8661b858af72
rev   line source
chris@37 1 # Redmine - project management software
chris@37 2 # Copyright (C) 2006-2010 Jean-Philippe Lang
Chris@0 3 #
Chris@0 4 # This program is free software; you can redistribute it and/or
Chris@0 5 # modify it under the terms of the GNU General Public License
Chris@0 6 # as published by the Free Software Foundation; either version 2
Chris@0 7 # of the License, or (at your option) any later version.
Chris@0 8 #
Chris@0 9 # This program is distributed in the hope that it will be useful,
Chris@0 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
Chris@0 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Chris@0 12 # GNU General Public License for more details.
Chris@0 13 #
Chris@0 14 # You should have received a copy of the GNU General Public License
Chris@0 15 # along with this program; if not, write to the Free Software
Chris@0 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Chris@0 17
Chris@0 18 require 'forwardable'
Chris@0 19 require 'cgi'
Chris@0 20
Chris@0 21 module ApplicationHelper
Chris@0 22 include Redmine::WikiFormatting::Macros::Definitions
Chris@0 23 include Redmine::I18n
Chris@0 24 include GravatarHelper::PublicMethods
Chris@0 25
Chris@0 26 extend Forwardable
Chris@0 27 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
Chris@0 28
Chris@0 29 # Return true if user is authorized for controller/action, otherwise false
Chris@0 30 def authorize_for(controller, action)
Chris@0 31 User.current.allowed_to?({:controller => controller, :action => action}, @project)
Chris@0 32 end
Chris@0 33
Chris@0 34 # Display a link if user is authorized
chris@22 35 #
chris@22 36 # @param [String] name Anchor text (passed to link_to)
chris@37 37 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
chris@22 38 # @param [optional, Hash] html_options Options passed to link_to
chris@22 39 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
Chris@0 40 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
chris@37 41 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
Chris@0 42 end
Chris@0 43
Chris@0 44 # Display a link to remote if user is authorized
Chris@0 45 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
Chris@0 46 url = options[:url] || {}
Chris@0 47 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
Chris@0 48 end
Chris@0 49
Chris@0 50 # Displays a link to user's account page if active
Chris@0 51 def link_to_user(user, options={})
Chris@0 52 if user.is_a?(User)
Chris@0 53 name = h(user.name(options[:format]))
Chris@0 54 if user.active?
Chris@0 55 link_to name, :controller => 'users', :action => 'show', :id => user
Chris@0 56 else
Chris@0 57 name
Chris@0 58 end
Chris@0 59 else
Chris@0 60 h(user.to_s)
Chris@0 61 end
Chris@0 62 end
Chris@0 63
Chris@0 64 # Displays a link to +issue+ with its subject.
Chris@0 65 # Examples:
Chris@0 66 #
Chris@0 67 # link_to_issue(issue) # => Defect #6: This is the subject
Chris@0 68 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
Chris@0 69 # link_to_issue(issue, :subject => false) # => Defect #6
Chris@0 70 # link_to_issue(issue, :project => true) # => Foo - Defect #6
Chris@0 71 #
Chris@0 72 def link_to_issue(issue, options={})
Chris@0 73 title = nil
Chris@0 74 subject = nil
Chris@0 75 if options[:subject] == false
Chris@0 76 title = truncate(issue.subject, :length => 60)
Chris@0 77 else
Chris@0 78 subject = issue.subject
Chris@0 79 if options[:truncate]
Chris@0 80 subject = truncate(subject, :length => options[:truncate])
Chris@0 81 end
Chris@0 82 end
Chris@0 83 s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
Chris@0 84 :class => issue.css_classes,
Chris@0 85 :title => title
Chris@0 86 s << ": #{h subject}" if subject
Chris@0 87 s = "#{h issue.project} - " + s if options[:project]
Chris@0 88 s
Chris@0 89 end
Chris@0 90
Chris@0 91 # Generates a link to an attachment.
Chris@0 92 # Options:
Chris@0 93 # * :text - Link text (default to attachment filename)
Chris@0 94 # * :download - Force download (default: false)
Chris@0 95 def link_to_attachment(attachment, options={})
Chris@0 96 text = options.delete(:text) || attachment.filename
Chris@0 97 action = options.delete(:download) ? 'download' : 'show'
Chris@0 98
Chris@0 99 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
Chris@0 100 end
Chris@0 101
Chris@0 102 # Generates a link to a SCM revision
Chris@0 103 # Options:
Chris@0 104 # * :text - Link text (default to the formatted revision)
Chris@0 105 def link_to_revision(revision, project, options={})
Chris@0 106 text = options.delete(:text) || format_revision(revision)
Chris@0 107
Chris@0 108 link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision))
Chris@0 109 end
Chris@0 110
Chris@14 111 # Generates a link to a project if active
Chris@14 112 # Examples:
Chris@14 113 #
Chris@14 114 # link_to_project(project) # => link to the specified project overview
Chris@14 115 # link_to_project(project, :action=>'settings') # => link to project settings
Chris@14 116 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
Chris@14 117 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
Chris@14 118 #
Chris@14 119 def link_to_project(project, options={}, html_options = nil)
Chris@14 120 if project.active?
Chris@14 121 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
Chris@14 122 link_to(h(project), url, html_options)
Chris@14 123 else
Chris@14 124 h(project)
Chris@14 125 end
Chris@14 126 end
Chris@14 127
Chris@0 128 def toggle_link(name, id, options={})
Chris@0 129 onclick = "Element.toggle('#{id}'); "
Chris@0 130 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
Chris@0 131 onclick << "return false;"
Chris@0 132 link_to(name, "#", :onclick => onclick)
Chris@0 133 end
Chris@0 134
Chris@0 135 def image_to_function(name, function, html_options = {})
Chris@0 136 html_options.symbolize_keys!
Chris@0 137 tag(:input, html_options.merge({
Chris@0 138 :type => "image", :src => image_path(name),
Chris@0 139 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
Chris@0 140 }))
Chris@0 141 end
Chris@0 142
Chris@0 143 def prompt_to_remote(name, text, param, url, html_options = {})
Chris@0 144 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
Chris@0 145 link_to name, {}, html_options
Chris@0 146 end
Chris@0 147
Chris@0 148 def format_activity_title(text)
Chris@0 149 h(truncate_single_line(text, :length => 100))
Chris@0 150 end
Chris@0 151
Chris@0 152 def format_activity_day(date)
Chris@0 153 date == Date.today ? l(:label_today).titleize : format_date(date)
Chris@0 154 end
Chris@0 155
Chris@0 156 def format_activity_description(text)
Chris@0 157 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
Chris@0 158 end
Chris@0 159
Chris@0 160 def format_version_name(version)
Chris@0 161 if version.project == @project
Chris@0 162 h(version)
Chris@0 163 else
Chris@0 164 h("#{version.project} - #{version}")
Chris@0 165 end
Chris@0 166 end
Chris@0 167
Chris@0 168 def due_date_distance_in_words(date)
Chris@0 169 if date
Chris@0 170 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
Chris@0 171 end
Chris@0 172 end
Chris@0 173
Chris@0 174 def render_page_hierarchy(pages, node=nil)
Chris@0 175 content = ''
Chris@0 176 if pages[node]
Chris@0 177 content << "<ul class=\"pages-hierarchy\">\n"
Chris@0 178 pages[node].each do |page|
Chris@0 179 content << "<li>"
chris@37 180 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
Chris@0 181 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
Chris@0 182 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
Chris@0 183 content << "</li>\n"
Chris@0 184 end
Chris@0 185 content << "</ul>\n"
Chris@0 186 end
Chris@0 187 content
Chris@0 188 end
Chris@0 189
Chris@0 190 # Renders flash messages
Chris@0 191 def render_flash_messages
Chris@0 192 s = ''
Chris@0 193 flash.each do |k,v|
Chris@0 194 s << content_tag('div', v, :class => "flash #{k}")
Chris@0 195 end
Chris@0 196 s
Chris@0 197 end
Chris@0 198
Chris@0 199 # Renders tabs and their content
Chris@0 200 def render_tabs(tabs)
Chris@0 201 if tabs.any?
Chris@0 202 render :partial => 'common/tabs', :locals => {:tabs => tabs}
Chris@0 203 else
Chris@0 204 content_tag 'p', l(:label_no_data), :class => "nodata"
Chris@0 205 end
Chris@0 206 end
Chris@0 207
Chris@0 208 # Renders the project quick-jump box
Chris@0 209 def render_project_jump_box
Chris@0 210 # Retrieve them now to avoid a COUNT query
Chris@0 211 projects = User.current.projects.all
Chris@0 212 if projects.any?
Chris@0 213 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
Chris@0 214 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
Chris@0 215 '<option value="" disabled="disabled">---</option>'
Chris@0 216 s << project_tree_options_for_select(projects, :selected => @project) do |p|
Chris@0 217 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
Chris@0 218 end
Chris@0 219 s << '</select>'
Chris@0 220 s
Chris@0 221 end
Chris@0 222 end
Chris@0 223
Chris@0 224 def project_tree_options_for_select(projects, options = {})
Chris@0 225 s = ''
Chris@0 226 project_tree(projects) do |project, level|
Chris@0 227 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
Chris@0 228 tag_options = {:value => project.id}
Chris@0 229 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
Chris@0 230 tag_options[:selected] = 'selected'
Chris@0 231 else
Chris@0 232 tag_options[:selected] = nil
Chris@0 233 end
Chris@0 234 tag_options.merge!(yield(project)) if block_given?
Chris@0 235 s << content_tag('option', name_prefix + h(project), tag_options)
Chris@0 236 end
Chris@0 237 s
Chris@0 238 end
Chris@0 239
Chris@0 240 # Yields the given block for each project with its level in the tree
chris@37 241 #
chris@37 242 # Wrapper for Project#project_tree
Chris@0 243 def project_tree(projects, &block)
chris@37 244 Project.project_tree(projects, &block)
Chris@0 245 end
Chris@0 246
Chris@0 247 def project_nested_ul(projects, &block)
Chris@0 248 s = ''
Chris@0 249 if projects.any?
Chris@0 250 ancestors = []
Chris@0 251 projects.sort_by(&:lft).each do |project|
Chris@0 252 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
Chris@0 253 s << "<ul>\n"
Chris@0 254 else
Chris@0 255 ancestors.pop
Chris@0 256 s << "</li>"
Chris@0 257 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
Chris@0 258 ancestors.pop
Chris@0 259 s << "</ul></li>\n"
Chris@0 260 end
Chris@0 261 end
Chris@0 262 s << "<li>"
Chris@0 263 s << yield(project).to_s
Chris@0 264 ancestors << project
Chris@0 265 end
Chris@0 266 s << ("</li></ul>\n" * ancestors.size)
Chris@0 267 end
Chris@0 268 s
Chris@0 269 end
Chris@0 270
Chris@0 271 def principals_check_box_tags(name, principals)
Chris@0 272 s = ''
Chris@0 273 principals.sort.each do |principal|
Chris@0 274 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
Chris@0 275 end
Chris@0 276 s
Chris@0 277 end
Chris@0 278
Chris@0 279 # Truncates and returns the string as a single line
Chris@0 280 def truncate_single_line(string, *args)
Chris@0 281 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
Chris@0 282 end
Chris@0 283
Chris@0 284 # Truncates at line break after 250 characters or options[:length]
Chris@0 285 def truncate_lines(string, options={})
Chris@0 286 length = options[:length] || 250
Chris@0 287 if string.to_s =~ /\A(.{#{length}}.*?)$/m
Chris@0 288 "#{$1}..."
Chris@0 289 else
Chris@0 290 string
Chris@0 291 end
Chris@0 292 end
Chris@0 293
Chris@0 294 def html_hours(text)
Chris@0 295 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
Chris@0 296 end
Chris@0 297
Chris@0 298 def authoring(created, author, options={})
Chris@0 299 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
Chris@0 300 end
Chris@0 301
Chris@0 302 def time_tag(time)
Chris@0 303 text = distance_of_time_in_words(Time.now, time)
Chris@0 304 if @project
chris@22 305 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
Chris@0 306 else
Chris@0 307 content_tag('acronym', text, :title => format_time(time))
Chris@0 308 end
Chris@0 309 end
Chris@0 310
Chris@0 311 def syntax_highlight(name, content)
Chris@0 312 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
Chris@0 313 end
Chris@0 314
Chris@0 315 def to_path_param(path)
Chris@0 316 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
Chris@0 317 end
Chris@0 318
Chris@0 319 def pagination_links_full(paginator, count=nil, options={})
Chris@0 320 page_param = options.delete(:page_param) || :page
Chris@0 321 per_page_links = options.delete(:per_page_links)
Chris@0 322 url_param = params.dup
Chris@0 323 # don't reuse query params if filters are present
Chris@0 324 url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
Chris@0 325
Chris@0 326 html = ''
Chris@0 327 if paginator.current.previous
Chris@0 328 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
Chris@0 329 end
Chris@0 330
Chris@0 331 html << (pagination_links_each(paginator, options) do |n|
Chris@0 332 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
Chris@0 333 end || '')
Chris@0 334
Chris@0 335 if paginator.current.next
Chris@0 336 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
Chris@0 337 end
Chris@0 338
Chris@0 339 unless count.nil?
Chris@0 340 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
Chris@0 341 if per_page_links != false && links = per_page_links(paginator.items_per_page)
Chris@0 342 html << " | #{links}"
Chris@0 343 end
Chris@0 344 end
Chris@0 345
Chris@0 346 html
Chris@0 347 end
Chris@0 348
Chris@0 349 def per_page_links(selected=nil)
Chris@0 350 url_param = params.dup
Chris@0 351 url_param.clear if url_param.has_key?(:set_filter)
Chris@0 352
Chris@0 353 links = Setting.per_page_options_array.collect do |n|
Chris@0 354 n == selected ? n : link_to_remote(n, {:update => "content",
Chris@0 355 :url => params.dup.merge(:per_page => n),
Chris@0 356 :method => :get},
Chris@0 357 {:href => url_for(url_param.merge(:per_page => n))})
Chris@0 358 end
Chris@0 359 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
Chris@0 360 end
Chris@0 361
Chris@0 362 def reorder_links(name, url)
Chris@0 363 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
Chris@0 364 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
Chris@0 365 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
Chris@0 366 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
Chris@0 367 end
Chris@0 368
Chris@0 369 def breadcrumb(*args)
Chris@0 370 elements = args.flatten
Chris@0 371 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
Chris@0 372 end
Chris@0 373
Chris@0 374 def other_formats_links(&block)
Chris@0 375 concat('<p class="other-formats">' + l(:label_export_to))
Chris@0 376 yield Redmine::Views::OtherFormatsBuilder.new(self)
Chris@0 377 concat('</p>')
Chris@0 378 end
Chris@0 379
Chris@0 380 def page_header_title
Chris@0 381 if @project.nil? || @project.new_record?
Chris@0 382 h(Setting.app_title)
Chris@0 383 else
Chris@0 384 b = []
Chris@0 385 ancestors = (@project.root? ? [] : @project.ancestors.visible)
Chris@0 386 if ancestors.any?
Chris@0 387 root = ancestors.shift
Chris@14 388 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
Chris@0 389 if ancestors.size > 2
Chris@0 390 b << '&#8230;'
Chris@0 391 ancestors = ancestors[-2, 2]
Chris@0 392 end
Chris@14 393 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
Chris@0 394 end
Chris@0 395 b << h(@project)
Chris@0 396 b.join(' &#187; ')
Chris@0 397 end
Chris@0 398 end
Chris@0 399
Chris@0 400 def html_title(*args)
Chris@0 401 if args.empty?
Chris@0 402 title = []
Chris@0 403 title << @project.name if @project
Chris@0 404 title += @html_title if @html_title
Chris@0 405 title << Setting.app_title
Chris@0 406 title.select {|t| !t.blank? }.join(' - ')
Chris@0 407 else
Chris@0 408 @html_title ||= []
Chris@0 409 @html_title += args
Chris@0 410 end
Chris@0 411 end
Chris@0 412
Chris@14 413 # Returns the theme, controller name, and action as css classes for the
Chris@14 414 # HTML body.
Chris@14 415 def body_css_classes
Chris@14 416 css = []
Chris@14 417 if theme = Redmine::Themes.theme(Setting.ui_theme)
Chris@14 418 css << 'theme-' + theme.name
Chris@14 419 end
Chris@14 420
Chris@14 421 css << 'controller-' + params[:controller]
Chris@14 422 css << 'action-' + params[:action]
Chris@14 423 css.join(' ')
Chris@14 424 end
Chris@14 425
Chris@0 426 def accesskey(s)
Chris@0 427 Redmine::AccessKeys.key_for s
Chris@0 428 end
Chris@0 429
Chris@0 430 # Formats text according to system settings.
Chris@0 431 # 2 ways to call this method:
Chris@0 432 # * with a String: textilizable(text, options)
Chris@0 433 # * with an object and one of its attribute: textilizable(issue, :description, options)
Chris@0 434 def textilizable(*args)
Chris@0 435 options = args.last.is_a?(Hash) ? args.pop : {}
Chris@0 436 case args.size
Chris@0 437 when 1
Chris@0 438 obj = options[:object]
Chris@0 439 text = args.shift
Chris@0 440 when 2
Chris@0 441 obj = args.shift
Chris@0 442 attr = args.shift
Chris@0 443 text = obj.send(attr).to_s
Chris@0 444 else
Chris@0 445 raise ArgumentError, 'invalid arguments to textilizable'
Chris@0 446 end
Chris@0 447 return '' if text.blank?
Chris@0 448 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
Chris@0 449 only_path = options.delete(:only_path) == false ? false : true
Chris@0 450
Chris@0 451 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
Chris@0 452
Chris@0 453 parse_non_pre_blocks(text) do |text|
chris@37 454 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings].each do |method_name|
Chris@0 455 send method_name, text, project, obj, attr, only_path, options
Chris@0 456 end
Chris@0 457 end
Chris@0 458 end
Chris@0 459
Chris@0 460 def parse_non_pre_blocks(text)
Chris@0 461 s = StringScanner.new(text)
Chris@0 462 tags = []
Chris@0 463 parsed = ''
Chris@0 464 while !s.eos?
Chris@0 465 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
Chris@0 466 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
Chris@0 467 if tags.empty?
Chris@0 468 yield text
Chris@0 469 end
Chris@0 470 parsed << text
Chris@0 471 if tag
Chris@0 472 if closing
Chris@0 473 if tags.last == tag.downcase
Chris@0 474 tags.pop
Chris@0 475 end
Chris@0 476 else
Chris@0 477 tags << tag.downcase
Chris@0 478 end
Chris@0 479 parsed << full_tag
Chris@0 480 end
Chris@0 481 end
Chris@0 482 # Close any non closing tags
Chris@0 483 while tag = tags.pop
Chris@0 484 parsed << "</#{tag}>"
Chris@0 485 end
Chris@0 486 parsed
Chris@0 487 end
Chris@0 488
Chris@0 489 def parse_inline_attachments(text, project, obj, attr, only_path, options)
Chris@0 490 # when using an image link, try to use an attachment, if possible
Chris@0 491 if options[:attachments] || (obj && obj.respond_to?(:attachments))
Chris@0 492 attachments = nil
Chris@0 493 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
Chris@0 494 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
Chris@0 495 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
Chris@0 496 # search for the picture in attachments
Chris@0 497 if found = attachments.detect { |att| att.filename.downcase == filename }
Chris@0 498 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
Chris@0 499 desc = found.description.to_s.gsub('"', '')
Chris@0 500 if !desc.blank? && alttext.blank?
Chris@0 501 alt = " title=\"#{desc}\" alt=\"#{desc}\""
Chris@0 502 end
Chris@0 503 "src=\"#{image_url}\"#{alt}"
Chris@0 504 else
Chris@0 505 m
Chris@0 506 end
Chris@0 507 end
Chris@0 508 end
Chris@0 509 end
Chris@0 510
Chris@0 511 # Wiki links
Chris@0 512 #
Chris@0 513 # Examples:
Chris@0 514 # [[mypage]]
Chris@0 515 # [[mypage|mytext]]
Chris@0 516 # wiki links can refer other project wikis, using project name or identifier:
Chris@0 517 # [[project:]] -> wiki starting page
Chris@0 518 # [[project:|mytext]]
Chris@0 519 # [[project:mypage]]
Chris@0 520 # [[project:mypage|mytext]]
Chris@0 521 def parse_wiki_links(text, project, obj, attr, only_path, options)
Chris@0 522 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
Chris@0 523 link_project = project
Chris@0 524 esc, all, page, title = $1, $2, $3, $5
Chris@0 525 if esc.nil?
Chris@0 526 if page =~ /^([^\:]+)\:(.*)$/
chris@37 527 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
Chris@0 528 page = $2
Chris@0 529 title ||= $1 if page.blank?
Chris@0 530 end
Chris@0 531
Chris@0 532 if link_project && link_project.wiki
Chris@0 533 # extract anchor
Chris@0 534 anchor = nil
Chris@0 535 if page =~ /^(.+?)\#(.+)$/
Chris@0 536 page, anchor = $1, $2
Chris@0 537 end
Chris@0 538 # check if page exists
Chris@0 539 wiki_page = link_project.wiki.find_page(page)
Chris@0 540 url = case options[:wiki_links]
Chris@0 541 when :local; "#{title}.html"
Chris@0 542 when :anchor; "##{title}" # used for single-file wiki export
Chris@0 543 else
chris@37 544 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
chris@37 545 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
Chris@0 546 end
Chris@0 547 link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
Chris@0 548 else
Chris@0 549 # project or wiki doesn't exist
Chris@0 550 all
Chris@0 551 end
Chris@0 552 else
Chris@0 553 all
Chris@0 554 end
Chris@0 555 end
Chris@0 556 end
Chris@0 557
Chris@0 558 # Redmine links
Chris@0 559 #
Chris@0 560 # Examples:
Chris@0 561 # Issues:
Chris@0 562 # #52 -> Link to issue #52
Chris@0 563 # Changesets:
Chris@0 564 # r52 -> Link to revision 52
Chris@0 565 # commit:a85130f -> Link to scmid starting with a85130f
Chris@0 566 # Documents:
Chris@0 567 # document#17 -> Link to document with id 17
Chris@0 568 # document:Greetings -> Link to the document with title "Greetings"
Chris@0 569 # document:"Some document" -> Link to the document with title "Some document"
Chris@0 570 # Versions:
Chris@0 571 # version#3 -> Link to version with id 3
Chris@0 572 # version:1.0.0 -> Link to version named "1.0.0"
Chris@0 573 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
Chris@0 574 # Attachments:
Chris@0 575 # attachment:file.zip -> Link to the attachment of the current object named file.zip
Chris@0 576 # Source files:
Chris@0 577 # source:some/file -> Link to the file located at /some/file in the project's repository
Chris@0 578 # source:some/file@52 -> Link to the file's revision 52
Chris@0 579 # source:some/file#L120 -> Link to line 120 of the file
Chris@0 580 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
Chris@0 581 # export:some/file -> Force the download of the file
Chris@0 582 # Forum messages:
Chris@0 583 # message#1218 -> Link to message with id 1218
Chris@0 584 def parse_redmine_links(text, project, obj, attr, only_path, options)
Chris@0 585 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
Chris@0 586 leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8
Chris@0 587 link = nil
Chris@0 588 if esc.nil?
Chris@0 589 if prefix.nil? && sep == 'r'
Chris@0 590 if project && (changeset = project.changesets.find_by_revision(identifier))
Chris@0 591 link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
Chris@0 592 :class => 'changeset',
Chris@0 593 :title => truncate_single_line(changeset.comments, :length => 100))
Chris@0 594 end
Chris@0 595 elsif sep == '#'
Chris@0 596 oid = identifier.to_i
Chris@0 597 case prefix
Chris@0 598 when nil
Chris@0 599 if issue = Issue.visible.find_by_id(oid, :include => :status)
Chris@0 600 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
Chris@0 601 :class => issue.css_classes,
Chris@0 602 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
Chris@0 603 end
Chris@0 604 when 'document'
Chris@0 605 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
Chris@0 606 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
Chris@0 607 :class => 'document'
Chris@0 608 end
Chris@0 609 when 'version'
Chris@0 610 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
Chris@0 611 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
Chris@0 612 :class => 'version'
Chris@0 613 end
Chris@0 614 when 'message'
Chris@0 615 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
Chris@0 616 link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
Chris@0 617 :controller => 'messages',
Chris@0 618 :action => 'show',
Chris@0 619 :board_id => message.board,
Chris@0 620 :id => message.root,
Chris@0 621 :anchor => (message.parent ? "message-#{message.id}" : nil)},
Chris@0 622 :class => 'message'
Chris@0 623 end
Chris@0 624 when 'project'
Chris@0 625 if p = Project.visible.find_by_id(oid)
Chris@14 626 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
Chris@0 627 end
Chris@0 628 end
Chris@0 629 elsif sep == ':'
Chris@0 630 # removes the double quotes if any
Chris@0 631 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
Chris@0 632 case prefix
Chris@0 633 when 'document'
Chris@0 634 if project && document = project.documents.find_by_title(name)
Chris@0 635 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
Chris@0 636 :class => 'document'
Chris@0 637 end
Chris@0 638 when 'version'
Chris@0 639 if project && version = project.versions.find_by_name(name)
Chris@0 640 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
Chris@0 641 :class => 'version'
Chris@0 642 end
Chris@0 643 when 'commit'
Chris@0 644 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
Chris@0 645 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
Chris@0 646 :class => 'changeset',
Chris@0 647 :title => truncate_single_line(changeset.comments, :length => 100)
Chris@0 648 end
Chris@0 649 when 'source', 'export'
Chris@0 650 if project && project.repository
Chris@0 651 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
Chris@0 652 path, rev, anchor = $1, $3, $5
Chris@0 653 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
Chris@0 654 :path => to_path_param(path),
Chris@0 655 :rev => rev,
Chris@0 656 :anchor => anchor,
Chris@0 657 :format => (prefix == 'export' ? 'raw' : nil)},
Chris@0 658 :class => (prefix == 'export' ? 'source download' : 'source')
Chris@0 659 end
Chris@0 660 when 'attachment'
Chris@0 661 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
Chris@0 662 if attachments && attachment = attachments.detect {|a| a.filename == name }
Chris@0 663 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
Chris@0 664 :class => 'attachment'
Chris@0 665 end
Chris@0 666 when 'project'
Chris@0 667 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
Chris@14 668 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
Chris@0 669 end
Chris@0 670 end
Chris@0 671 end
Chris@0 672 end
Chris@0 673 leading + (link || "#{prefix}#{sep}#{identifier}")
Chris@0 674 end
Chris@0 675 end
chris@37 676
chris@37 677 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
chris@37 678 HEADING_RE = /<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>/i unless const_defined?(:HEADING_RE)
chris@37 679
chris@37 680 # Headings and TOC
chris@37 681 # Adds ids and links to headings and renders the TOC if needed unless options[:headings] is set to false
chris@37 682 def parse_headings(text, project, obj, attr, only_path, options)
chris@37 683 headings = []
chris@37 684 text.gsub!(HEADING_RE) do
chris@37 685 level, attrs, content = $1.to_i, $2, $3
chris@37 686 item = strip_tags(content).strip
chris@37 687 anchor = item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
chris@37 688 headings << [level, anchor, item]
chris@37 689 "<h#{level} #{attrs} id=\"#{anchor}\">#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
chris@37 690 end unless options[:headings] == false
chris@37 691
chris@37 692 text.gsub!(TOC_RE) do
chris@37 693 if headings.empty?
chris@37 694 ''
chris@37 695 else
chris@37 696 div_class = 'toc'
chris@37 697 div_class << ' right' if $1 == '>'
chris@37 698 div_class << ' left' if $1 == '<'
chris@37 699 out = "<ul class=\"#{div_class}\"><li>"
chris@37 700 root = headings.map(&:first).min
chris@37 701 current = root
chris@37 702 started = false
chris@37 703 headings.each do |level, anchor, item|
chris@37 704 if level > current
chris@37 705 out << '<ul><li>' * (level - current)
chris@37 706 elsif level < current
chris@37 707 out << "</li></ul>\n" * (current - level) + "</li><li>"
chris@37 708 elsif started
chris@37 709 out << '</li><li>'
chris@37 710 end
chris@37 711 out << "<a href=\"##{anchor}\">#{item}</a>"
chris@37 712 current = level
chris@37 713 started = true
chris@37 714 end
chris@37 715 out << '</li></ul>' * (current - root)
chris@37 716 out << '</li></ul>'
chris@37 717 end
chris@37 718 end
chris@37 719 end
Chris@0 720
Chris@0 721 # Same as Rails' simple_format helper without using paragraphs
Chris@0 722 def simple_format_without_paragraph(text)
Chris@0 723 text.to_s.
Chris@0 724 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
Chris@0 725 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
Chris@0 726 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
Chris@0 727 end
Chris@0 728
Chris@0 729 def lang_options_for_select(blank=true)
Chris@0 730 (blank ? [["(auto)", ""]] : []) +
Chris@0 731 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
Chris@0 732 end
Chris@0 733
Chris@0 734 def label_tag_for(name, option_tags = nil, options = {})
Chris@0 735 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
Chris@0 736 content_tag("label", label_text)
Chris@0 737 end
Chris@0 738
Chris@0 739 def labelled_tabular_form_for(name, object, options, &proc)
Chris@0 740 options[:html] ||= {}
Chris@0 741 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
Chris@0 742 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
Chris@0 743 end
Chris@0 744
Chris@0 745 def back_url_hidden_field_tag
Chris@0 746 back_url = params[:back_url] || request.env['HTTP_REFERER']
Chris@0 747 back_url = CGI.unescape(back_url.to_s)
Chris@0 748 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
Chris@0 749 end
Chris@0 750
Chris@0 751 def check_all_links(form_name)
Chris@0 752 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
Chris@0 753 " | " +
Chris@0 754 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
Chris@0 755 end
Chris@0 756
Chris@0 757 def progress_bar(pcts, options={})
Chris@0 758 pcts = [pcts, pcts] unless pcts.is_a?(Array)
Chris@0 759 pcts = pcts.collect(&:round)
Chris@0 760 pcts[1] = pcts[1] - pcts[0]
Chris@0 761 pcts << (100 - pcts[1] - pcts[0])
Chris@0 762 width = options[:width] || '100px;'
Chris@0 763 legend = options[:legend] || ''
Chris@0 764 content_tag('table',
Chris@0 765 content_tag('tr',
Chris@0 766 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
Chris@0 767 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
Chris@0 768 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
Chris@0 769 ), :class => 'progress', :style => "width: #{width};") +
Chris@0 770 content_tag('p', legend, :class => 'pourcent')
Chris@0 771 end
Chris@0 772
Chris@0 773 def checked_image(checked=true)
Chris@0 774 if checked
Chris@0 775 image_tag 'toggle_check.png'
Chris@0 776 end
Chris@0 777 end
Chris@0 778
Chris@0 779 def context_menu(url)
Chris@0 780 unless @context_menu_included
Chris@0 781 content_for :header_tags do
Chris@0 782 javascript_include_tag('context_menu') +
Chris@0 783 stylesheet_link_tag('context_menu')
Chris@0 784 end
Chris@14 785 if l(:direction) == 'rtl'
Chris@14 786 content_for :header_tags do
Chris@14 787 stylesheet_link_tag('context_menu_rtl')
Chris@14 788 end
Chris@14 789 end
Chris@0 790 @context_menu_included = true
Chris@0 791 end
Chris@0 792 javascript_tag "new ContextMenu('#{ url_for(url) }')"
Chris@0 793 end
Chris@0 794
Chris@0 795 def context_menu_link(name, url, options={})
Chris@0 796 options[:class] ||= ''
Chris@0 797 if options.delete(:selected)
Chris@0 798 options[:class] << ' icon-checked disabled'
Chris@0 799 options[:disabled] = true
Chris@0 800 end
Chris@0 801 if options.delete(:disabled)
Chris@0 802 options.delete(:method)
Chris@0 803 options.delete(:confirm)
Chris@0 804 options.delete(:onclick)
Chris@0 805 options[:class] << ' disabled'
Chris@0 806 url = '#'
Chris@0 807 end
Chris@0 808 link_to name, url, options
Chris@0 809 end
Chris@0 810
Chris@0 811 def calendar_for(field_id)
Chris@0 812 include_calendar_headers_tags
Chris@0 813 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
Chris@0 814 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
Chris@0 815 end
Chris@0 816
Chris@0 817 def include_calendar_headers_tags
Chris@0 818 unless @calendar_headers_tags_included
Chris@0 819 @calendar_headers_tags_included = true
Chris@0 820 content_for :header_tags do
Chris@0 821 start_of_week = case Setting.start_of_week.to_i
Chris@0 822 when 1
Chris@0 823 'Calendar._FD = 1;' # Monday
Chris@0 824 when 7
Chris@0 825 'Calendar._FD = 0;' # Sunday
Chris@0 826 else
Chris@0 827 '' # use language
Chris@0 828 end
Chris@0 829
Chris@0 830 javascript_include_tag('calendar/calendar') +
Chris@0 831 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
Chris@0 832 javascript_tag(start_of_week) +
Chris@0 833 javascript_include_tag('calendar/calendar-setup') +
Chris@0 834 stylesheet_link_tag('calendar')
Chris@0 835 end
Chris@0 836 end
Chris@0 837 end
Chris@0 838
Chris@0 839 def content_for(name, content = nil, &block)
Chris@0 840 @has_content ||= {}
Chris@0 841 @has_content[name] = true
Chris@0 842 super(name, content, &block)
Chris@0 843 end
Chris@0 844
Chris@0 845 def has_content?(name)
Chris@0 846 (@has_content && @has_content[name]) || false
Chris@0 847 end
Chris@0 848
Chris@0 849 # Returns the avatar image tag for the given +user+ if avatars are enabled
Chris@0 850 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
Chris@0 851 def avatar(user, options = { })
Chris@0 852 if Setting.gravatar_enabled?
chris@22 853 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
Chris@0 854 email = nil
Chris@0 855 if user.respond_to?(:mail)
Chris@0 856 email = user.mail
Chris@0 857 elsif user.to_s =~ %r{<(.+?)>}
Chris@0 858 email = $1
Chris@0 859 end
Chris@0 860 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
chris@22 861 else
chris@22 862 ''
Chris@0 863 end
Chris@0 864 end
Chris@0 865
Chris@14 866 def favicon
Chris@14 867 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
Chris@14 868 end
Chris@14 869
Chris@0 870 private
Chris@0 871
Chris@0 872 def wiki_helper
Chris@0 873 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
Chris@0 874 extend helper
Chris@0 875 return self
Chris@0 876 end
Chris@0 877
Chris@0 878 def link_to_remote_content_update(text, url_params)
Chris@0 879 link_to_remote(text,
Chris@0 880 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
Chris@0 881 {:href => url_for(:params => url_params)}
Chris@0 882 )
Chris@0 883 end
Chris@0 884
Chris@0 885 end