PageRenderTime 71ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/app/helpers/application_helper.rb

https://github.com/martindale/better
Ruby | 1438 lines | 1190 code | 149 blank | 99 comment | 160 complexity | b85e9d7813f4f691e76bb16000bbe6f1 MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. # BetterMeans - Work 2.0
  2. # Copyright (C) 2006-2011 See readme for details and license#
  3. require 'coderay'
  4. require 'coderay/helpers/file_type'
  5. require 'forwardable'
  6. require 'cgi'
  7. module ApplicationHelper
  8. include Redmine::WikiFormatting::Macros::Definitions
  9. include Redmine::I18n
  10. include GravatarHelper::PublicMethods
  11. extend Forwardable
  12. def_delegators :wiki_helper
  13. def help_section(name, popup=false) # spec_me cover_me heckle_me
  14. if popup
  15. return if User.current.anonymous?
  16. help_section = HelpSection.first(:conditions => {:user_id => User.current.id, :name => name})
  17. if help_section.nil?
  18. help_section = HelpSection.create(
  19. :user_id => User.current.id,
  20. :name => name,
  21. :show => true
  22. )
  23. end
  24. render :partial => 'help_sections/show_popup', :locals => {:help_section => help_section} if help_section.show
  25. else
  26. render :partial => 'help_sections/show', :locals => {:name => name}
  27. end
  28. end
  29. # Return true if user is authorized for controller/action, otherwise false
  30. def authorize_for(controller, action) # spec_me cover_me heckle_me
  31. logger.info { "authorize for #{controller} #{action} #{@project.name}" }
  32. User.current.allowed_to?({:controller => controller, :action => action}, @project)
  33. end
  34. # Display a link if user is authorized
  35. def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference) # spec_me cover_me heckle_me
  36. link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
  37. end
  38. # Display a link if user is not logged in
  39. def link_to_if_anon(name, options = {}, html_options = nil, *parameters_for_method_reference) # spec_me cover_me heckle_me
  40. link_to(name, options, html_options, *parameters_for_method_reference) if User.current == User.anonymous
  41. end
  42. # Display a link to remote if user is authorized
  43. def link_to_remote_if_authorized(name, options = {}, html_options = nil) # spec_me cover_me heckle_me
  44. url = options[:url] || {}
  45. link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
  46. end
  47. # Displays a link to user's account page if active
  48. def link_to_user(user, options={}) # spec_me cover_me heckle_me
  49. if user.is_a?(User)
  50. name = h(user.name(options[:format]))
  51. if user.active?
  52. link_to name, :controller => 'users', :action => 'show', :id => user
  53. else
  54. name
  55. end
  56. else
  57. h(user.to_s)
  58. end
  59. end
  60. def link_to_user_or_you(user, options={}) # spec_me cover_me heckle_me
  61. if user == User.current
  62. "You"
  63. else
  64. link_to_user(user,options)
  65. end
  66. end
  67. # Displays a link to project
  68. def link_to_project(project, options={}) # spec_me cover_me heckle_me
  69. if project.is_a?(Project)
  70. name = h(project.name)
  71. link_to name, :controller => 'projects', :action => 'show', :id => project
  72. else
  73. h(project.to_s)
  74. end
  75. end
  76. def link_to_user_from_id(user_id, options={}) # spec_me cover_me heckle_me
  77. link_to_user(User.find(user_id))
  78. end
  79. # Displays a link to +issue+ with its subject.
  80. # Examples:
  81. #
  82. # link_to_issue(issue) # => Defect #6: This is the subject
  83. # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
  84. # link_to_issue(issue, :subject => false) # => Defect #6
  85. # link_to_issue(issue, :project => true) # => Foo - Defect #6
  86. #
  87. def link_to_issue(issue, options={}) # spec_me cover_me heckle_me
  88. title = nil
  89. subject = nil
  90. css_class = nil
  91. if options[:subject] == false
  92. title = truncate(issue.subject, :length => 60)
  93. else
  94. subject = issue.subject
  95. if options[:truncate]
  96. subject = truncate(subject, :length => options[:truncate])
  97. end
  98. end
  99. if options[:css_class]
  100. css_class = options[:css_class]
  101. else
  102. css_class = issue.css_classes
  103. end
  104. css_class = css_class + " fancyframe" #loads fancybox
  105. s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
  106. :class => css_class,
  107. :title => title
  108. s << ": #{h subject}" if subject
  109. s = "#{h issue.project} - " + s if options[:project]
  110. s
  111. end
  112. def link_to_issue_from_id(issue_id, options={}) # spec_me cover_me heckle_me
  113. link_to_issue(Issue.find(issue_id), options)
  114. rescue ActiveRecord::RecordNotFound
  115. css_class = "fancyframe" #loads fancybox
  116. s = link_to "Issue ##{issue_id}", {:controller => "issues", :action => "show", :id => issue_id},
  117. :class => css_class
  118. end
  119. # Generates a link to an attachment.
  120. # Options:
  121. # * :text - Link text (default to attachment filename)
  122. # * :download - Force download (default: false)
  123. def link_to_attachment(attachment, options={}) # spec_me cover_me heckle_me
  124. text = options.delete(:text) || attachment.filename
  125. action = options.delete(:download) ? 'download' : 'show'
  126. link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
  127. end
  128. def current_user # spec_me cover_me heckle_me
  129. User.current
  130. end
  131. def logged_in? # spec_me cover_me heckle_me
  132. User.current.logged?
  133. end
  134. def toggle_link(name, id, options={}) # spec_me cover_me heckle_me
  135. onclick = "$('##{id}').toggle(); "
  136. onclick << "$('##{options[:second_toggle]}').toggle(); " if options[:second_toggle]
  137. onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ")
  138. onclick << "return false;"
  139. link_to(name, "#", options.merge({:onclick => onclick}))
  140. end
  141. def image_to_function(name, function, html_options = {}) # spec_me cover_me heckle_me
  142. html_options.symbolize_keys!
  143. tag(:input, html_options.merge({
  144. :type => "image", :src => image_path(name),
  145. :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
  146. }))
  147. end
  148. def prompt_to_remote(name, text, param, url, html_options = {}) # spec_me cover_me heckle_me
  149. html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
  150. link_to name, {}, html_options
  151. end
  152. #id is the id of the element sending the request
  153. #name is the text on the link
  154. #title of the command prompt
  155. #message bellow title in prompt
  156. #params to be passed with url
  157. #url to submit to after input is collected
  158. #required input or just optional
  159. #html_options for this link
  160. def prompt_input_to_remote(id, name, title, message, param, url, required, html_options = {}) # spec_me cover_me heckle_me
  161. html_options[:onclick] = "comment_prompt_to_remote('#{id}', '#{title}', '#{message}', '#{param}', '#{url_for(url)}', #{required}); return false;"
  162. link_to name, {}, html_options
  163. end
  164. def format_activity_title(text) # spec_me cover_me heckle_me
  165. h(truncate_single_line(text, :length => 100))
  166. end
  167. def format_activity_day(date) # spec_me cover_me heckle_me
  168. date == Date.today ? l(:label_today).titleize : format_date(date)
  169. end
  170. def format_activity_description(text) # spec_me cover_me heckle_me
  171. make_expandable(textilizable(text),300)
  172. end
  173. def make_expandable(newhtml,length=400) # spec_me cover_me heckle_me
  174. return if newhtml.nil?
  175. return newhtml if newhtml.gsub(/<\/?[^>]*>/, "").length < length
  176. id = rand(100000)
  177. h = ""
  178. h << "<div class='hidden' id=#{id.to_s}>"
  179. h << newhtml
  180. h << "</div>"
  181. h << "<div id=truncated_#{id.to_s}>"
  182. h << newhtml.truncate_html(length)
  183. h = h[0..-5]
  184. h << "<a href='' onclick='$(\"#truncated_#{id.to_s}\").remove();$(\"##{id.to_s}\").show();return false;'><strong>...#{l(:label_see_more)}</strong></a>"
  185. h << "<p>"
  186. h << "</div>"
  187. end
  188. def due_date_distance_in_words(date) # spec_me cover_me heckle_me
  189. if date
  190. l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
  191. end
  192. end
  193. def render_page_hierarchy(pages, node=nil) # spec_me cover_me heckle_me
  194. content = ''
  195. if pages[node]
  196. content << "<ul class=\"pages-hierarchy\">\n"
  197. pages[node].each do |page|
  198. content << "<li>"
  199. content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
  200. :title => (page.respond_to?(:updated_at) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_at)) : nil))
  201. content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
  202. content << "</li>\n"
  203. end
  204. content << "</ul>\n"
  205. end
  206. content
  207. end
  208. # Renders flash messages
  209. def render_flash_messages # spec_me cover_me heckle_me
  210. s = ''
  211. flash.each do |k,v|
  212. s << content_tag('div', v, :class => "flash #{k}")
  213. end
  214. s
  215. end
  216. def render_global_messages # spec_me cover_me heckle_me
  217. s = ''
  218. if User.current.logged? && User.current.trial_expired_at && User.current.trial_expired_at < (-1 * Setting::GLOBAL_OVERUSE_THRESHOLD).days.from_now
  219. s << content_tag('div', link_to(l(:text_trial_expired), {:controller => 'my', :action => 'upgrade'}), :class => "flash error")
  220. elsif User.current.logged? && User.current.usage_over_at && User.current.usage_over_at < (-1 * Setting::GLOBAL_OVERUSE_THRESHOLD).days.from_now
  221. s << content_tag('div', link_to(l(:text_usage_over), {:controller => 'my', :action => 'upgrade'}), :class => "flash error")
  222. end
  223. s
  224. end
  225. # Renders tabs and their content
  226. def render_tabs(tabs) # spec_me cover_me heckle_me
  227. if tabs.any?
  228. render :partial => 'common/tabs', :locals => {:tabs => tabs}
  229. else
  230. content_tag 'p', l(:label_no_data), :class => "nodata"
  231. end
  232. end
  233. # Renders the project quick-jump box
  234. def render_project_jump_box # spec_me cover_me heckle_me
  235. # Retrieve them now to avoid a COUNT query
  236. if User.current.pref[:active_only_jumps]
  237. projects = User.current.projects.all
  238. else
  239. project_ids = User.current.projects.collect{|p| p.id}.join(",")
  240. projects = project_ids.any? ? Project.find(:all, :conditions => "(parent_id in (#{project_ids}) OR id in (#{project_ids})) AND (status=#{Project::STATUS_ACTIVE})") : []
  241. end
  242. s = '<select id="jumpbox" onchange="if (this.value != \'\') { window.location = this.value; }">' +
  243. "<option value='/projects' selected=\"yes\">#{l(:label_jump_to_a_project)}</option>" +
  244. '<option value="" disabled="disabled">---</option>'
  245. if projects.any?
  246. s_options = ""
  247. s_options << project_tree_options_for_select(projects, :selected => @project) do |p|
  248. { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
  249. end
  250. s << s_options
  251. s << '<option value="" disabled="disabled">---</option>'
  252. end
  253. s << "<option value='#{url_for({:controller => :projects, :action => :index})}'>#{l(:label_browse_workstreams)}</option>"
  254. s << "<option value='#{url_for({:controller => :projects, :action => :new})}'>#{l(:label_project_new)}</option>"
  255. s << '</select>'
  256. s << '<span id="widthcalc" style="display:none;"></span>'
  257. end
  258. def sub_workstream_project_box(project) # spec_me cover_me heckle_me
  259. return '' if project.nil?
  260. @project_descendants = project.descendants.active
  261. return '' if @project_descendants.length == 0
  262. s = '<select id="project_jumpbox" onchange="if (this.value != \'\') { window.location = this.value; }">' +
  263. "<option value='/projects' selected=\"yes\">#{pluralize(@project_descendants.length,l(:label_subproject)).downcase}</option>" +
  264. '<option value="" disabled="disabled">---</option>'
  265. if @project_descendants.any?
  266. s_options = ""
  267. s_options << project_tree_options_for_select(@project_descendants) do |p|
  268. { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
  269. end
  270. s << s_options
  271. end
  272. if User.current.allowed_to?(:add_subprojects, project)
  273. s << '<option value="" disabled="disabled">---</option>'
  274. s << "<option value='#{url_for({:controller => :projects, :action => :new, :parent_id => project.id})}'>#{l(:label_subproject_new)}</option>"
  275. end
  276. s << '</select>'
  277. end
  278. def project_tree_options_for_select(projects, options = {}) # spec_me cover_me heckle_me
  279. s = ''
  280. project_tree_sorted(projects) do |project, level|
  281. name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
  282. tag_options = {:value => project.id, :selected => ((project == options[:selected] && false) ? 'selected' : nil)}
  283. tag_options.merge!(yield(project)) if block_given?
  284. s << content_tag('option', name_prefix + h(project), tag_options)
  285. end
  286. s
  287. end
  288. # Yields the given block for each project with its level in the tree
  289. def project_tree(projects, &block) # spec_me cover_me heckle_me
  290. ancestors = []
  291. projects.sort_by(&:lft).each do |project|
  292. while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
  293. ancestors.pop
  294. end
  295. yield project, ancestors.size
  296. ancestors << project
  297. end
  298. end
  299. def project_tree_sorted(projects, &block) # spec_me cover_me heckle_me
  300. ancestors = []
  301. sorted = [] #nested array for alphabetical sorting
  302. last_array = sorted
  303. projects.sort_by(&:lft).each do |project|
  304. while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
  305. ancestors.pop
  306. end
  307. if ancestors.size == 0
  308. sorted << [[project.name, ancestors.size,project]]
  309. else
  310. sorted_string = "sorted" + ".last" * ancestors.size
  311. eval(sorted_string) << [[project.name, ancestors.size,project]]
  312. end
  313. # yield project, ancestors.size
  314. ancestors << project
  315. end
  316. sorted = sort2d(sorted)
  317. traverse_sorted(sorted, &block)
  318. sorted
  319. end
  320. def sort2d(ar) # spec_me cover_me heckle_me
  321. ar.sort! {|a,b| a[0][0][0] <=> b[0][0][0]}
  322. if ar[0][0].class.to_s != "String"
  323. ar.each {|sub| sub = sort2d(sub)}
  324. end
  325. end
  326. def traverse_sorted(ar, &block) # spec_me cover_me heckle_me
  327. unless ar[0].class.to_s != "String"
  328. yield ar[2], ar[1]
  329. else
  330. ar.each {|sub| sub = traverse_sorted(sub, &block)}
  331. end
  332. end
  333. def show_detail(detail, no_html=false) # spec_me cover_me heckle_me
  334. case detail.property
  335. when 'attr'
  336. label = l(("field_" + detail.prop_key.to_s.gsub(/\_id$/, "")))
  337. case detail.prop_key
  338. when 'due_date', 'start_date'
  339. value = format_date(detail.value.to_date) if detail.value
  340. old_value = format_date(detail.old_value.to_date) if detail.old_value
  341. when 'project_id'
  342. p = Project.find_by_id(detail.value) and value = p.name if detail.value
  343. p = Project.find_by_id(detail.old_value) and old_value = p.name if detail.old_value
  344. when 'status_id'
  345. if detail.value
  346. s = IssueStatus.find_by_id(detail.value)
  347. value = l("issue.#{s.name.downcase}").capitalize
  348. end
  349. if detail.old_value
  350. s = IssueStatus.find_by_id(detail.old_value)
  351. old_value = l("issue.#{s.name.downcase}").capitalize
  352. end
  353. when 'tracker_id'
  354. t = Tracker.find_by_id(detail.value) and value = t.name if detail.value
  355. t = Tracker.find_by_id(detail.old_value) and old_value = t.name if detail.old_value
  356. when 'assigned_to_id'
  357. u = User.find_by_id(detail.value) and value = u.name if detail.value
  358. u = User.find_by_id(detail.old_value) and old_value = u.name if detail.old_value
  359. when 'estimated_hours'
  360. value = "%0.02f" % detail.value.to_f unless detail.value.blank?
  361. old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank?
  362. end
  363. when 'attachment'
  364. label = l(:label_attachment)
  365. end
  366. label ||= detail.prop_key
  367. value ||= detail.value
  368. old_value ||= detail.old_value
  369. unless no_html
  370. label = content_tag('strong', label)
  371. old_value = content_tag("i", h(old_value)) if detail.old_value
  372. old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?)
  373. if detail.property == 'attachment' && !value.blank? && a = Attachment.find_by_id(detail.prop_key)
  374. # Link to the attachment if it has not been removed
  375. value = link_to_attachment(a)
  376. else
  377. value = content_tag("i", h(value)) if value
  378. end
  379. end
  380. if !detail.value.blank?
  381. case detail.property
  382. when 'attr', 'cf'
  383. if !detail.old_value.blank?
  384. l(:text_journal_changed, :label => label, :old => old_value, :new => value)
  385. else
  386. l(:text_journal_set_to, :label => label, :value => value)
  387. end
  388. when 'attachment'
  389. l(:text_journal_added, :label => label, :value => value)
  390. end
  391. else
  392. l(:text_journal_deleted, :label => label, :old => old_value)
  393. end
  394. end
  395. def format_time_ago(updated_at) # spec_me cover_me heckle_me
  396. "#{distance_of_time_in_words(Time.now,local_time(updated_at))} #{l('general.ago')}"
  397. end
  398. def project_nested_ul(projects, &block) # spec_me cover_me heckle_me
  399. s = ''
  400. if projects.any?
  401. ancestors = []
  402. projects.sort_by(&:lft).each do |project|
  403. if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
  404. s << "<ul>\n"
  405. else
  406. ancestors.pop
  407. s << "</li>"
  408. while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
  409. ancestors.pop
  410. s << "</ul></li>\n"
  411. end
  412. end
  413. s << "<li>"
  414. s << yield(project).to_s
  415. ancestors << project
  416. end
  417. s << ("</li></ul>\n" * ancestors.size)
  418. end
  419. s
  420. end
  421. def users_check_box_tags(name, users) # spec_me cover_me heckle_me
  422. s = ''
  423. users.sort.each do |user|
  424. s << "<label>#{ check_box_tag name, user.id, false } #{h user}</label>\n"
  425. end
  426. s
  427. end
  428. # Truncates and returns the string as a single line
  429. def truncate_single_line(string, *args) # spec_me cover_me heckle_me
  430. truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
  431. end
  432. def html_hours(text) # spec_me cover_me heckle_me
  433. text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
  434. end
  435. def authoring(created, author, options={}) # spec_me cover_me heckle_me
  436. l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
  437. end
  438. def time_tag(time) # spec_me cover_me heckle_me
  439. text = distance_of_time_in_words(Time.now, time)
  440. if @project
  441. link_to(text, {:controller => 'projects', :action => 'activity', :id => @project, :from => time.to_date}, :title => format_time(time))
  442. else
  443. content_tag('acronym', text, :title => format_time(time))
  444. end
  445. end
  446. def since_tag(time) # spec_me cover_me heckle_me
  447. text = distance_of_time_in_words(Time.now, time).gsub(/about/,"")
  448. content_tag('acronym', text, :title => format_time(time))
  449. end
  450. def syntax_highlight(name, content) # spec_me cover_me heckle_me
  451. type = CodeRay::FileType[name]
  452. type ? CodeRay.scan(content, type).html : h(content)
  453. end
  454. def to_path_param(path) # spec_me cover_me heckle_me
  455. path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
  456. end
  457. def pagination_links_full(paginator, count=nil, options={}) # spec_me cover_me heckle_me
  458. page_param = options.delete(:page_param) || :page
  459. url_param = params.dup
  460. # don't reuse query params if filters are present
  461. url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
  462. html = ''
  463. if paginator.current.previous
  464. html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
  465. end
  466. html << (pagination_links_each(paginator, options) do |n|
  467. link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
  468. end || '')
  469. if paginator.current.next
  470. html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
  471. end
  472. unless count.nil?
  473. html << [
  474. " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})",
  475. per_page_links(paginator.items_per_page)
  476. ].compact.join(' | ')
  477. end
  478. html
  479. end
  480. def per_page_links(selected=nil) # spec_me cover_me heckle_me
  481. url_param = params.dup
  482. url_param.clear if url_param.has_key?(:set_filter)
  483. links = Setting.per_page_options_array.collect do |n|
  484. n == selected ? n : link_to_remote(n, {:update => "content",
  485. :url => params.dup.merge(:per_page => n),
  486. :method => :get},
  487. {:href => url_for(url_param.merge(:per_page => n))})
  488. end
  489. links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
  490. end
  491. def reorder_links(name, url) # spec_me cover_me heckle_me
  492. link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
  493. link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
  494. link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
  495. link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
  496. end
  497. def breadcrumb(*args) # spec_me cover_me heckle_me
  498. elements = args.flatten
  499. elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
  500. end
  501. def other_formats_links(&block) # spec_me cover_me heckle_me
  502. concat('<p class="other-formats">' + l(:label_export_to))
  503. yield Redmine::Views::OtherFormatsBuilder.new(self)
  504. concat('</p>')
  505. end
  506. def page_header_title # spec_me cover_me heckle_me
  507. if @project.nil?
  508. link_to(@page_header_name.nil? ? User.current.name : "Bettermeans", {:controller => 'welcome', :action => 'index'}) + (@page_header_name.nil? ? '' : ' &#187; ' + @page_header_name)
  509. elsif @project.new_record? #TODO: would be nice to have the project's parent name here if it's a new record
  510. b = []
  511. b << link_to(l(:label_project_plural), {:controller => 'projects', :action => 'index'}, :class => 'root')
  512. unless @parent.nil?
  513. ancestors = (@parent.root? ? [] : @parent.ancestors.visible)
  514. if ancestors.any?
  515. root = ancestors.shift
  516. b << link_to(h(root), {:controller => 'projects', :action => 'show', :id => root, :jump => current_menu_item }, :class => 'root')
  517. if ancestors.size > 2
  518. b << '&#8230;'
  519. ancestors = ancestors[-2, 2]
  520. end
  521. b += ancestors.collect {|p| link_to(h(p), {:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item}, :class => 'ancestor') }
  522. end
  523. b << link_to(h(@parent), {:controller => 'projects', :action => 'show', :id => @parent, :jump => current_menu_item}, :class => 'ancestor')
  524. b << "New sub workstream"
  525. b = b.join(' &#187; ')
  526. b
  527. else
  528. b << l(:label_project_new)
  529. b = b.join(' &#187; ')
  530. b
  531. end
  532. else
  533. b = []
  534. b << link_to(l(:label_project_plural), {:controller => 'projects', :action => 'index'}, :class => 'root')
  535. ancestors = (@project.root? ? [] : @project.ancestors.visible)
  536. if ancestors.any?
  537. root = ancestors.shift
  538. b << link_to(h(root), {:controller => 'projects', :action => 'show', :id => root, :jump => current_menu_item}, :class => 'root')
  539. if ancestors.size > 2
  540. b << '&#8230;'
  541. ancestors = ancestors[-2, 2]
  542. end
  543. b += ancestors.collect {|p| link_to(h(p), {:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item}, :class => 'ancestor') }
  544. end
  545. b.push link_to(h(@project), {:controller => 'projects', :action => 'show', :id => @project, :jump => current_menu_item}, :class => 'ancestor')
  546. b = b.join(' &#187; ')
  547. end
  548. end
  549. def page_header_name # spec_me cover_me heckle_me
  550. begin
  551. if @project.nil? || @project.new_record?
  552. @page_header_name.nil? ? l(:label_my_home) : @page_header_name
  553. elsif @project.new_record?
  554. l(:label_project_new)
  555. else
  556. html = h(@project.name)
  557. html << privacy(@project)
  558. html << volunteering(@project)
  559. html
  560. end
  561. rescue
  562. "Home"
  563. end
  564. end
  565. def html_title(*args) # spec_me cover_me heckle_me
  566. if args.empty?
  567. title = []
  568. title << @project.name if @project
  569. title += @html_title if @html_title
  570. title << Setting.app_title
  571. title.select {|t| !t.blank? }.join(' - ')
  572. else
  573. @html_title ||= []
  574. @html_title += args
  575. end
  576. end
  577. def accesskey(s) # spec_me cover_me heckle_me
  578. Redmine::AccessKeys.key_for s
  579. end
  580. # Formats text according to system settings.
  581. # 2 ways to call this method:
  582. # * with a String: textilizable(text, options)
  583. # * with an object and one of its attribute: textilizable(issue, :description, options)
  584. def textilizable(*args) # spec_me cover_me heckle_me
  585. options = args.last.is_a?(Hash) ? args.pop : {}
  586. case args.size
  587. when 1
  588. obj = options[:object]
  589. text = args.shift
  590. when 2
  591. obj = args.shift
  592. text = obj.send(args.shift).to_s
  593. else
  594. raise ArgumentError, 'invalid arguments to textilizable'
  595. end
  596. return '' if text.blank?
  597. only_path = options.delete(:only_path) == false ? false : true
  598. # when using an image link, try to use an attachment, if possible
  599. attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
  600. if attachments
  601. attachments = attachments.sort_by(&:created_at).reverse
  602. text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(bmp|gif|jpg|jpeg|png))!/i) do |m|
  603. style = $1
  604. filename = $6.downcase
  605. # search for the picture in attachments
  606. if found = attachments.detect { |att| att.filename.downcase == filename }
  607. image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
  608. desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
  609. alt = desc.blank? ? nil : "(#{desc})"
  610. "!#{style}#{image_url}#{alt}!"
  611. else
  612. m
  613. end
  614. end
  615. end
  616. text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text) { |macro, args| exec_macro(macro, obj, args) }
  617. # different methods for formatting wiki links
  618. case options[:wiki_links]
  619. when :local
  620. # used for local links to html files
  621. format_wiki_link = Proc.new {|project, title, anchor| "#{title}.html" }
  622. when :anchor
  623. # used for single-file wiki export
  624. format_wiki_link = Proc.new {|project, title, anchor| "##{title}" }
  625. else
  626. format_wiki_link = Proc.new {|project, title, anchor| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title, :anchor => anchor) }
  627. end
  628. project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
  629. # Wiki links
  630. #
  631. # Examples:
  632. # [[mypage]]
  633. # [[mypage|mytext]]
  634. # wiki links can refer other project wikis, using project name or identifier:
  635. # [[project:]] -> wiki starting page
  636. # [[project:|mytext]]
  637. # [[project:mypage]]
  638. # [[project:mypage|mytext]]
  639. text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
  640. link_project = project
  641. esc, all, page, title = $1, $2, $3, $5
  642. if esc.nil?
  643. if page =~ /^([^\:]+)\:(.*)$/
  644. link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
  645. page = $2
  646. title ||= $1 if page.blank?
  647. end
  648. if link_project && link_project.wiki
  649. # extract anchor
  650. anchor = nil
  651. if page =~ /^(.+?)\#(.+)$/
  652. page, anchor = $1, $2
  653. end
  654. # check if page exists
  655. wiki_page = link_project.wiki.find_page(page)
  656. link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page), anchor),
  657. :class => ('wiki-page' + (wiki_page ? '' : ' new')))
  658. else
  659. # project or wiki doesn't exist
  660. all
  661. end
  662. else
  663. all
  664. end
  665. end
  666. # Redmine links
  667. #
  668. # Examples:
  669. # Issues:
  670. # #52 -> Link to issue #52
  671. # Documents:
  672. # document#17 -> Link to document with id 17
  673. # document:Greetings -> Link to the document with title "Greetings"
  674. # document:"Some document" -> Link to the document with title "Some document"
  675. # Versions:
  676. # version#3 -> Link to version with id 3
  677. # version:1.0.0 -> Link to version named "1.0.0"
  678. # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
  679. # Attachments:
  680. # attachment:file.zip -> Link to the attachment of the current object named file.zip
  681. # Source files:
  682. # source:some/file -> Link to the file located at /some/file in the project's repository
  683. # source:some/file@52 -> Link to the file's revision 52
  684. # source:some/file#L120 -> Link to line 120 of the file
  685. # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
  686. # export:some/file -> Force the download of the file
  687. # Forum messages:
  688. # message#1218 -> Link to message with id 1218
  689. # User mentions:
  690. # @userlogin -> Link to user with login:userlogin
  691. # text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|<|$)}) do |m|
  692. text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message)?((#|r)(\d+)|(@)([a-zA-Z0-9._@]+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|<|$)}) do |m|
  693. leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
  694. link = nil
  695. if esc.nil?
  696. if sep == '#'
  697. oid = oid.to_i
  698. case prefix
  699. when nil
  700. if issue = Issue.visible.find_by_id(oid, :include => :status)
  701. link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
  702. :class => issue.css_classes,
  703. :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
  704. end
  705. when 'document'
  706. if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
  707. link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
  708. :class => 'document'
  709. end
  710. when 'message'
  711. if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
  712. link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
  713. :controller => 'messages',
  714. :action => 'show',
  715. :board_id => message.board,
  716. :id => message.root,
  717. :anchor => (message.parent ? "message-#{message.id}" : nil)},
  718. :class => 'message'
  719. end
  720. end
  721. elsif sep == '@'
  722. link = link_to("@#{oid}", {:only_path => only_path, :controller => 'users', :action => 'show', :id => 0, :login => oid})
  723. elsif sep == ':'
  724. # removes the double quotes if any
  725. name = oid.gsub(%r{^"(.*)"$}, "\\1")
  726. case prefix
  727. when 'document'
  728. if project && document = project.documents.find_by_title(name)
  729. link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
  730. :class => 'document'
  731. end
  732. when 'attachment'
  733. if attachments && attachment = attachments.detect {|a| a.filename == name }
  734. link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
  735. :class => 'attachment'
  736. end
  737. end
  738. end
  739. end
  740. leading + (link || "#{prefix}#{sep}#{oid}")
  741. end
  742. text
  743. end
  744. # Same as Rails' simple_format helper without using paragraphs
  745. def simple_format_without_paragraph(text) # spec_me cover_me heckle_me
  746. text.to_s.
  747. gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
  748. gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
  749. gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
  750. end
  751. def lang_options_for_select(blank=true) # spec_me cover_me heckle_me
  752. (blank ? [["(auto)", ""]] : []) +
  753. valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
  754. end
  755. def month_hash # spec_me cover_me heckle_me
  756. [
  757. ["01 - January",1],
  758. ["02 - February",2],
  759. ["03 - March",3],
  760. ["04 - April",4],
  761. ["05 - May",5],
  762. ["06 - June",6],
  763. ["07 - July",7],
  764. ["08 - August",8],
  765. ["09 - September",9],
  766. ["10 - October",10],
  767. ["11 - November",11],
  768. ["12 - December",12]
  769. ]
  770. end
  771. def privacy(project) # spec_me cover_me heckle_me
  772. project.is_public ? "" : help_bubble(:help_this_workstream_is_private, {:image =>"icon_privacy.png"})
  773. end
  774. def volunteering(project) # spec_me cover_me heckle_me
  775. project.volunteer ? help_bubble(:help_volunteer, {:image => "icon_volunteer.png"}) : ""
  776. end
  777. def year_hash # spec_me cover_me heckle_me
  778. [0,1,2,3,4,5,6,7,8,9,10].collect{|n| [(Date.today.year + n).to_s, Date.today.year + n]}
  779. end
  780. def unit_for(project) # spec_me cover_me heckle_me
  781. if project.volunteer?
  782. return '♥'
  783. else
  784. return '●'
  785. end
  786. end
  787. def country_hash # spec_me cover_me heckle_me
  788. {
  789. "Afghanistan" => "AF",
  790. "Albania" => "AL",
  791. "Algeria" => "DZ",
  792. "American Samoa" => "AS",
  793. "Andorra" => "AD",
  794. "Angola" => "AO",
  795. "Anguilla" => "AI",
  796. "Antigua and Barbuda" => "AG",
  797. "Argentina" => "AR",
  798. "Armenia" => "AM",
  799. "Aruba" => "AW",
  800. "Australia" => "AU",
  801. "Austria" => "AT",
  802. "Aland Islands" => "AX",
  803. "Azerbaijan" => "AZ",
  804. "Bahamas" => "BS",
  805. "Bahrain" => "BH",
  806. "Bangladesh" => "BD",
  807. "Barbados" => "BB",
  808. "Belarus" => "BY",
  809. "Belgium" => "BE",
  810. "Belize" => "BZ",
  811. "Benin" => "BJ",
  812. "Bermuda" => "BM",
  813. "Bhutan" => "BT",
  814. "Bolivia" => "BO",
  815. "Bosnia and Herzegovina" => "BA",
  816. "Botswana" => "BW",
  817. "Bouvet Island" => "BV",
  818. "Brazil" => "BR",
  819. "Brunei Darussalam" => "BN",
  820. "British Indian Ocean Territory" => "IO",
  821. "Bulgaria" => "BG",
  822. "Burkina Faso" => "BF",
  823. "Burundi" => "BI",
  824. "Cambodia" => "KH",
  825. "Cameroon" => "CM",
  826. "Canada" => "CA",
  827. "Cape Verde" => "CV",
  828. "Cayman Islands" => "KY",
  829. "Central African Republic" => "CF",
  830. "Chad" => "TD",
  831. "Chile" => "CL",
  832. "China" => "CN",
  833. "Christmas Island" => "CX",
  834. "Cocos (Keeling) Islands" => "CC",
  835. "Colombia" => "CO",
  836. "Comoros" => "KM",
  837. "Congo" => "CG",
  838. "Congo, the Democratic Republic of the" => "CD",
  839. "Cook Islands" => "CK",
  840. "Costa Rica" => "CR",
  841. "Cote D'Ivoire" => "CI",
  842. "Croatia" => "HR",
  843. "Cuba" => "CU",
  844. "Cyprus" => "CY",
  845. "Czech Republic" => "CZ",
  846. "Denmark" => "DK",
  847. "Djibouti" => "DJ",
  848. "Dominica" => "DM",
  849. "Dominican Republic" => "DO",
  850. "Ecuador" => "EC",
  851. "Egypt" => "EG",
  852. "El Salvador" => "SV",
  853. "Equatorial Guinea" => "GQ",
  854. "Eritrea" => "ER",
  855. "Estonia" => "EE",
  856. "Ethiopia" => "ET",
  857. "Falkland Islands (Malvinas)" => "FK",
  858. "Faroe Islands" => "FO",
  859. "Fiji" => "FJ",
  860. "Finland" => "FI",
  861. "France" => "FR",
  862. "French Guiana" => "GF",
  863. "French Polynesia" => "PF",
  864. "French Southern Territories" => "TF",
  865. "Gabon" => "GA",
  866. "Gambia" => "GM",
  867. "Georgia" => "GE",
  868. "Germany" => "DE",
  869. "Ghana" => "GH",
  870. "Gibraltar" => "GI",
  871. "Greece" => "GR",
  872. "Greenland" => "GL",
  873. "Grenada" => "GD",
  874. "Guadeloupe" => "GP",
  875. "Guam" => "GU",
  876. "Guatemala" => "GT",
  877. "Guinea" => "GN",
  878. "Guinea-Bissau" => "GW",
  879. "Guyana" => "GY",
  880. "Guernsey" => "GG",
  881. "Haiti" => "HT",
  882. "Holy See (Vatican City State)" => "VA",
  883. "Honduras" => "HN",
  884. "Hong Kong" => "HK",
  885. "Heard Island And Mcdonald Islands" => "HM",
  886. "Hungary" => "HU",
  887. "Iceland" => "IS",
  888. "India" => "IN",
  889. "Indonesia" => "ID",
  890. "Iran, Islamic Republic of" => "IR",
  891. "Iraq" => "IQ",
  892. "Ireland" => "IE",
  893. "Isle Of Man" => "IM",
  894. "Israel" => "IL",
  895. "Italy" => "IT",
  896. "Jamaica" => "JM",
  897. "Japan" => "JP",
  898. "Jersey" => "JE",
  899. "Jordan" => "JO",
  900. "Kazakhstan" => "KZ",
  901. "Kenya" => "KE",
  902. "Kiribati" => "KI",
  903. "Korea, Democratic People's Republic of" => "KP",
  904. "Korea, Republic of" => "KR",
  905. "Kuwait" => "KW",
  906. "Kyrgyzstan" => "KG",
  907. "Lao People's Democratic Republic" => "LA",
  908. "Latvia" => "LV",
  909. "Lebanon" => "LB",
  910. "Lesotho" => "LS",
  911. "Liberia" => "LR",
  912. "Libyan Arab Jamahiriya" => "LY",
  913. "Liechtenstein" => "LI",
  914. "Lithuania" => "LT",
  915. "Luxembourg" => "LU",
  916. "Macao" => "MO",
  917. "Macedonia, the Former Yugoslav Republic of" => "MK",
  918. "Madagascar" => "MG",
  919. "Malawi" => "MW",
  920. "Malaysia" => "MY",
  921. "Maldives" => "MV",
  922. "Mali" => "ML",
  923. "Malta" => "MT",
  924. "Marshall Islands" => "MH",
  925. "Martinique" => "MQ",
  926. "Mauritania" => "MR",
  927. "Mauritius" => "MU",
  928. "Mayotte" => "YT",
  929. "Mexico" => "MX",
  930. "Micronesia, Federated States of" => "FM",
  931. "Moldova, Republic of" => "MD",
  932. "Monaco" => "MC",
  933. "Mongolia" => "MN",
  934. "Montenegro" => "ME",
  935. "Montserrat" => "MS",
  936. "Morocco" => "MA",
  937. "Mozambique" => "MZ",
  938. "Myanmar" => "MM",
  939. "Namibia" => "NA",
  940. "Nauru" => "NR",
  941. "Nepal" => "NP",
  942. "Netherlands" => "NL",
  943. "Netherlands Antilles" => "AN",
  944. "New Caledonia" => "NC",
  945. "New Zealand" => "NZ",
  946. "Nicaragua" => "NI",
  947. "Niger" => "NE",
  948. "Nigeria" => "NG",
  949. "Niue" => "NU",
  950. "Norfolk Island" => "NF",
  951. "Northern Mariana Islands" => "MP",
  952. "Norway" => "NO",
  953. "Oman" => "OM",
  954. "Pakistan" => "PK",
  955. "Palau" => "PW",
  956. "Palestinian Territory, Occupied" => "PS",
  957. "Panama" => "PA",
  958. "Papua New Guinea" => "PG",
  959. "Paraguay" => "PY",
  960. "Peru" => "PE",
  961. "Philippines" => "PH",
  962. "Pitcairn" => "PN",
  963. "Poland" => "PL",
  964. "Portugal" => "PT",
  965. "Puerto Rico" => "PR",
  966. "Qatar" => "QA",
  967. "Reunion" => "RE",
  968. "Romania" => "RO",
  969. "Russian Federation" => "RU",
  970. "Rwanda" => "RW",
  971. "Saint Barthélemy" => "BL",
  972. "Saint Helena" => "SH",
  973. "Saint Kitts and Nevis" => "KN",
  974. "Saint Lucia" => "LC",
  975. "Saint Martin (French part)" => "MF",
  976. "Saint Pierre and Miquelon" => "PM",
  977. "Saint Vincent and the Grenadines" => "VC",
  978. "Samoa" => "WS",
  979. "San Marino" => "SM",
  980. "Sao Tome and Principe" => "ST",
  981. "Saudi Arabia" => "SA",
  982. "Senegal" => "SN",
  983. "Serbia" => "RS",
  984. "Seychelles" => "SC",
  985. "Sierra Leone" => "SL",
  986. "Singapore" => "SG",
  987. "Slovakia" => "SK",
  988. "Slovenia" => "SI",
  989. "Solomon Islands" => "SB",
  990. "Somalia" => "SO",
  991. "South Africa" => "ZA",
  992. "South Georgia and the South Sandwich Islands" => "GS",
  993. "Spain" => "ES",
  994. "Sri Lanka" => "LK",
  995. "Sudan" => "SD",
  996. "Suriname" => "SR",
  997. "Svalbard and Jan Mayen" => "SJ",
  998. "Swaziland" => "SZ",
  999. "Sweden" => "SE",
  1000. "Switzerland" => "CH",
  1001. "Syrian Arab Republic" => "SY",
  1002. "Taiwan, Province of China" => "TW",
  1003. "Tajikistan" => "TJ",
  1004. "Tanzania, United Republic of" => "TZ",
  1005. "Thailand" => "TH",
  1006. "Timor Leste" => "TL",
  1007. "Togo" => "TG",
  1008. "Tokelau" => "TK",
  1009. "Tonga" => "TO",
  1010. "Trinidad and Tobago" => "TT",
  1011. "Tunisia" => "TN",
  1012. "Turkey" => "TR",
  1013. "Turkmenistan" => "TM",
  1014. "Turks and Caicos Islands" => "TC",
  1015. "Tuvalu" => "TV",
  1016. "Uganda" => "UG",
  1017. "Ukraine" => "UA",
  1018. "United Arab Emirates" => "AE",
  1019. "United Kingdom" => "GB",
  1020. "United States" => "US",
  1021. "United States Minor Outlying Islands" => "UM",
  1022. "Uruguay" => "UY",
  1023. "Uzbekistan" => "UZ",
  1024. "Vanuatu" => "VU",
  1025. "Venezuela" => "VE",
  1026. "Viet Nam" => "VN",
  1027. "Virgin Islands, British" => "VG",
  1028. "Virgin Islands, U.S." => "VI",
  1029. "Wallis and Futuna" => "WF",
  1030. "Western Sahara" => "EH",
  1031. "Yemen" => "YE",
  1032. "Zambia" => "ZM",
  1033. "Zimbabwe" => "ZW"
  1034. }
  1035. end
  1036. def label_tag_for(name, option_tags = nil, options = {}) # spec_me cover_me heckle_me
  1037. label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
  1038. content_tag("label", label_text)
  1039. end
  1040. def labelled_tabular_form_for(name, object, options, &proc) # spec_me cover_me heckle_me
  1041. options[:html] ||= {}
  1042. options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
  1043. form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
  1044. end
  1045. def back_url_hidden_field_tag # spec_me cover_me heckle_me
  1046. back_url = params[:back_url] || request.env['HTTP_REFERER']
  1047. back_url = CGI.unescape(back_url.to_s)
  1048. hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
  1049. end
  1050. def check_all_links(form_name) # spec_me cover_me heckle_me
  1051. link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
  1052. " | " +
  1053. link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
  1054. end
  1055. def progress_bar(pcts, options={}) # spec_me cover_me heckle_me
  1056. pcts = [pcts, pcts] unless pcts.is_a?(Array)
  1057. pcts = pcts.collect(&:round)
  1058. pcts[1] = pcts[1] - pcts[0]
  1059. pcts << (100 - pcts[1] - pcts[0])
  1060. width = options[:width] || '100px;'
  1061. legend = options[:legend] || ''
  1062. content_tag('table',
  1063. content_tag('tr',
  1064. (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
  1065. (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
  1066. (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
  1067. ), :class => 'progress', :style => "width: #{width};") +
  1068. content_tag('p', legend, :class => 'pourcent')
  1069. end
  1070. def context_menu_link(name, url, options={}) # spec_me cover_me heckle_me
  1071. options[:class] ||= ''
  1072. if options.delete(:selected)
  1073. options[:class] << ' icon-checked disabled'
  1074. options[:disabled] = true
  1075. end
  1076. if options.delete(:disabled)
  1077. options.delete(:method)
  1078. options.delete(:confirm)
  1079. options.delete(:onclick)
  1080. options[:class] << ' disabled'
  1081. url = '#'
  1082. end
  1083. link_to name, url, options
  1084. end
  1085. def help_link(name, options={}) # spec_me cover_me heckle_me
  1086. options[:show_name] ||= false #When true, we show the text of the help key next to the link
  1087. link_to(options[:show_name] ? l('help_' + name.to_s) : '', {:controller => 'help', :action => 'show', :key => name}, {:id =>'help_button_' + name.to_s, :class => 'lbOn icon icon-help'})
  1088. end
  1089. def help_bubble(name, options={}) # spec_me cover_me heckle_me
  1090. imagename = options[:image] || "question_mark.gif"
  1091. image = image_tag(imagename, :class=> "help_question_mark", :id=>"help_image_#{name}")
  1092. html = link_to(image, {:href => '#'}, {:onclick => "$('#help_image_#{name}').bubbletip('#tip_#{name}', {deltaDirection: 'right', bindShow: 'click'}); return false;"})
  1093. html << content_tag(:span, l(name, options), :class => 'tip hidden', :id=>"tip_#{name}")
  1094. # <img id="help_image_panel_' + name + '" src="/images/question_mark.gif" class="help_question_mark">
  1095. # <div id="help_panel_canceled" style="display:none;">
  1096. # <div class="tip" style="width:300px">
  1097. # <strong>Canceled Ideas</strong><br />
  1098. # If a request hasn't been prioritized by anyone and has been sitting in the queue for more than a month, anyone team member can cancel it.<br /><br />
  1099. # Once a request has been canceled, anyone can re-open it, effectively pushing it back as a new item for reconsideration.
  1100. # </div>
  1101. # </div>
  1102. end
  1103. def calendar_for(field_id) # spec_me cover_me heckle_me
  1104. include_calendar_headers_tags
  1105. image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
  1106. javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
  1107. end
  1108. def include_calendar_headers_tags # spec_me cover_me heckle_me
  1109. unless @calendar_headers_tags_included
  1110. @calendar_headers_tags_included = true
  1111. content_for :header_tags do
  1112. start_of_week = case Setting.start_of_week.to_i
  1113. when 1
  1114. 'Calendar._FD = 1;' # Monday
  1115. when 7
  1116. 'Calendar._FD = 0;' # Sunday
  1117. else
  1118. '' # use language
  1119. end
  1120. javascript_include_tag('calendar/calendar') +
  1121. javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
  1122. javascript_tag(start_of_week) +
  1123. javascript_include_tag('calendar/calendar-setup') +
  1124. stylesheet_link_tag('calendar')
  1125. end
  1126. end
  1127. end
  1128. def content_for(name, content = nil, &block) # spec_me cover_me heckle_me
  1129. @has_content ||= {}
  1130. @has_content[name] = true
  1131. super(name, content, &block)
  1132. end
  1133. def has_content?(name) # spec_me cover_me heckle_me
  1134. (@has_content && @has_content[name]) || false
  1135. end
  1136. # Returns the avatar image tag for the given +user+ if avatars are enabled
  1137. # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
  1138. def avatar(user, options = { }) # spec_me cover_me heckle_me
  1139. options.merge!({:ssl => Setting.protocol == 'https', :default => Setting.gravatar_default})
  1140. email = nil
  1141. if user.respond_to?(:mail)
  1142. email = user.mail
  1143. elsif user.to_s =~ %r{<(.+?)>}
  1144. email = $1
  1145. end
  1146. return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
  1147. end
  1148. def render_journal_details(journal) # spec_me cover_me heckle_me
  1149. return unless journal
  1150. html = ""
  1151. if journal && journal.details && journal.details.count > 0
  1152. html = "<ul>"
  1153. for detail in journal.details
  1154. html << "<li>#{show_detail(detail)}</li>"
  1155. end
  1156. html << "</ul>"
  1157. end
  1158. content = ""
  1159. content << textilizable(journal, :notes)
  1160. content = make_expandable content, 250
  1161. css_classes = "wiki"
  1162. css_classes << " gravatar-margin" if Setting.gravatar_enabled?
  1163. html << content
  1164. end
  1165. def link_to_activity(as) # spec_me cover_me heckle_me
  1166. link_to name_for_activity_stream(as), url_for_activity_stream(as), {:class => class_for_activity_stream(as)}
  1167. end
  1168. def name_for_activity_stream(as) # spec_me cover_me heckle_me
  1169. key =
  1170. if as.tracker_name
  1171. "tracker.#{as.tracker_name.downcase}"
  1172. else
  1173. "label_#{as.object_type.downcase}"
  1174. end
  1175. "#{l('general.a')} #{l(key)}"
  1176. end
  1177. def class_for_activity_stream(as) # spec_me cover_me heckle_me
  1178. (as.object_type.match(/^Issue/)) ? "fancyframe" : "noframe"
  1179. end
  1180. def url_for_activity_stream(as) # spec_me cover_me heckle_me
  1181. case as.object_type.downcase
  1182. when 'message'
  1183. return {:controller => 'messages', :action => 'show', :board_id => 'guess', :id => as.object_id}
  1184. when 'wikipage'
  1185. return {:controller => 'wiki', :action => 'index', :id => as.project_id, :page => as.object_name}
  1186. when 'memberrole'
  1187. return {:controller => 'projects', :action => 'team', :id => as.project_id}
  1188. when 'motion'
  1189. return {:controller => 'motions', :action => 'show', :project_id => as.project_id, :id => as.object_id}
  1190. else
  1191. return {:controller => as.object_type.downcase.pluralize, :action => 'show', :id => as.object_id}
  1192. end
  1193. end
  1194. def title_for_activity_stream(as) # spec_me cover_me heckle_me
  1195. case as.object_type.downcase
  1196. when 'memberrole'
  1197. begin
  1198. "#{as.…

Large files files are truncated, but you can click here to view the full file