PageRenderTime 63ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/app/helpers/application_helper.rb

https://github.com/snapai/bettermeans
Ruby | 1418 lines | 1173 code | 147 blank | 98 comment | 157 complexity | c4a93911cf8ca95805bbebfc9c5cd799 MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0, Apache-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)
  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)
  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)
  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)
  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)
  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={})
  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={})
  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={})
  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={})
  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={})
  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={})
  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={})
  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
  129. User.current
  130. end
  131. def logged_in?
  132. User.current.logged?
  133. end
  134. def toggle_link(name, id, options={})
  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 = {})
  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 = {})
  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 = {})
  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)
  165. h(truncate_single_line(text, :length => 100))
  166. end
  167. def format_activity_day(date)
  168. date == Date.today ? l(:label_today).titleize : format_date(date)
  169. end
  170. def format_activity_description(text)
  171. make_expandable(textilizable(text),300)
  172. end
  173. def make_expandable(newhtml,length=400)
  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>... see more</strong></a>"
  185. h << "<p>"
  186. h << "</div>"
  187. end
  188. def due_date_distance_in_words(date)
  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)
  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
  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
  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)
  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
  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)
  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 = {})
  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)
  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)
  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)
  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)
  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)
  334. case detail.property
  335. when 'attr'
  336. label = l(("field_" + detail.prop_key.to_s.gsub(/\_id$/, "")).to_sym)
  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. s = IssueStatus.find_by_id(detail.value) and value = s.name if detail.value
  346. s = IssueStatus.find_by_id(detail.old_value) and old_value = s.name if detail.old_value
  347. when 'tracker_id'
  348. t = Tracker.find_by_id(detail.value) and value = t.name if detail.value
  349. t = Tracker.find_by_id(detail.old_value) and old_value = t.name if detail.old_value
  350. when 'assigned_to_id'
  351. u = User.find_by_id(detail.value) and value = u.name if detail.value
  352. u = User.find_by_id(detail.old_value) and old_value = u.name if detail.old_value
  353. when 'estimated_hours'
  354. value = "%0.02f" % detail.value.to_f unless detail.value.blank?
  355. old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank?
  356. end
  357. when 'attachment'
  358. label = l(:label_attachment)
  359. end
  360. label ||= detail.prop_key
  361. value ||= detail.value
  362. old_value ||= detail.old_value
  363. unless no_html
  364. label = content_tag('strong', label)
  365. old_value = content_tag("i", h(old_value)) if detail.old_value
  366. old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?)
  367. if detail.property == 'attachment' && !value.blank? && a = Attachment.find_by_id(detail.prop_key)
  368. # Link to the attachment if it has not been removed
  369. value = link_to_attachment(a)
  370. else
  371. value = content_tag("i", h(value)) if value
  372. end
  373. end
  374. if !detail.value.blank?
  375. case detail.property
  376. when 'attr', 'cf'
  377. if !detail.old_value.blank?
  378. l(:text_journal_changed, :label => label, :old => old_value, :new => value)
  379. else
  380. l(:text_journal_set_to, :label => label, :value => value)
  381. end
  382. when 'attachment'
  383. l(:text_journal_added, :label => label, :value => value)
  384. end
  385. else
  386. l(:text_journal_deleted, :label => label, :old => old_value)
  387. end
  388. end
  389. def format_time_ago(updated_at)
  390. "#{distance_of_time_in_words(Time.now,local_time(updated_at))} ago"
  391. end
  392. def project_nested_ul(projects, &block)
  393. s = ''
  394. if projects.any?
  395. ancestors = []
  396. projects.sort_by(&:lft).each do |project|
  397. if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
  398. s << "<ul>\n"
  399. else
  400. ancestors.pop
  401. s << "</li>"
  402. while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
  403. ancestors.pop
  404. s << "</ul></li>\n"
  405. end
  406. end
  407. s << "<li>"
  408. s << yield(project).to_s
  409. ancestors << project
  410. end
  411. s << ("</li></ul>\n" * ancestors.size)
  412. end
  413. s
  414. end
  415. def users_check_box_tags(name, users)
  416. s = ''
  417. users.sort.each do |user|
  418. s << "<label>#{ check_box_tag name, user.id, false } #{h user}</label>\n"
  419. end
  420. s
  421. end
  422. # Truncates and returns the string as a single line
  423. def truncate_single_line(string, *args)
  424. truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
  425. end
  426. def html_hours(text)
  427. text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
  428. end
  429. def authoring(created, author, options={})
  430. l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
  431. end
  432. def time_tag(time)
  433. text = distance_of_time_in_words(Time.now, time)
  434. if @project
  435. link_to(text, {:controller => 'projects', :action => 'activity', :id => @project, :from => time.to_date}, :title => format_time(time))
  436. else
  437. content_tag('acronym', text, :title => format_time(time))
  438. end
  439. end
  440. def since_tag(time)
  441. text = distance_of_time_in_words(Time.now, time).gsub(/about/,"")
  442. content_tag('acronym', text, :title => format_time(time))
  443. end
  444. def syntax_highlight(name, content)
  445. type = CodeRay::FileType[name]
  446. type ? CodeRay.scan(content, type).html : h(content)
  447. end
  448. def to_path_param(path)
  449. path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
  450. end
  451. def pagination_links_full(paginator, count=nil, options={})
  452. page_param = options.delete(:page_param) || :page
  453. url_param = params.dup
  454. # don't reuse query params if filters are present
  455. url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
  456. html = ''
  457. if paginator.current.previous
  458. html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
  459. end
  460. html << (pagination_links_each(paginator, options) do |n|
  461. link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
  462. end || '')
  463. if paginator.current.next
  464. html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
  465. end
  466. unless count.nil?
  467. html << [
  468. " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})",
  469. per_page_links(paginator.items_per_page)
  470. ].compact.join(' | ')
  471. end
  472. html
  473. end
  474. def per_page_links(selected=nil)
  475. url_param = params.dup
  476. url_param.clear if url_param.has_key?(:set_filter)
  477. links = Setting.per_page_options_array.collect do |n|
  478. n == selected ? n : link_to_remote(n, {:update => "content",
  479. :url => params.dup.merge(:per_page => n),
  480. :method => :get},
  481. {:href => url_for(url_param.merge(:per_page => n))})
  482. end
  483. links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
  484. end
  485. def reorder_links(name, url)
  486. link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
  487. link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
  488. link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
  489. link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
  490. end
  491. def breadcrumb(*args)
  492. elements = args.flatten
  493. elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
  494. end
  495. def other_formats_links(&block)
  496. concat('<p class="other-formats">' + l(:label_export_to))
  497. yield Redmine::Views::OtherFormatsBuilder.new(self)
  498. concat('</p>')
  499. end
  500. def page_header_title
  501. if @project.nil?
  502. link_to(@page_header_name.nil? ? User.current.name : "Bettermeans", {:controller => 'welcome', :action => 'index'}) + (@page_header_name.nil? ? '' : ' &#187; ' + @page_header_name)
  503. elsif @project.new_record? #TODO: would be nice to have the project's parent name here if it's a new record
  504. b = []
  505. b << link_to(l(:label_project_plural), {:controller => 'projects', :action => 'index'}, :class => 'root')
  506. unless @parent.nil?
  507. ancestors = (@parent.root? ? [] : @parent.ancestors.visible)
  508. if ancestors.any?
  509. root = ancestors.shift
  510. b << link_to(h(root), {:controller => 'projects', :action => 'show', :id => root, :jump => current_menu_item }, :class => 'root')
  511. if ancestors.size > 2
  512. b << '&#8230;'
  513. ancestors = ancestors[-2, 2]
  514. end
  515. b += ancestors.collect {|p| link_to(h(p), {:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item}, :class => 'ancestor') }
  516. end
  517. b << link_to(h(@parent), {:controller => 'projects', :action => 'show', :id => @parent, :jump => current_menu_item}, :class => 'ancestor')
  518. b << "New sub workstream"
  519. b = b.join(' &#187; ')
  520. b
  521. else
  522. b << l(:label_project_new)
  523. b = b.join(' &#187; ')
  524. b
  525. end
  526. else
  527. b = []
  528. b << link_to(l(:label_project_plural), {:controller => 'projects', :action => 'index'}, :class => 'root')
  529. ancestors = (@project.root? ? [] : @project.ancestors.visible)
  530. if ancestors.any?
  531. root = ancestors.shift
  532. b << link_to(h(root), {:controller => 'projects', :action => 'show', :id => root, :jump => current_menu_item}, :class => 'root')
  533. if ancestors.size > 2
  534. b << '&#8230;'
  535. ancestors = ancestors[-2, 2]
  536. end
  537. b += ancestors.collect {|p| link_to(h(p), {:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item}, :class => 'ancestor') }
  538. end
  539. b.push link_to(h(@project), {:controller => 'projects', :action => 'show', :id => @project, :jump => current_menu_item}, :class => 'ancestor')
  540. b = b.join(' &#187; ')
  541. end
  542. end
  543. def page_header_name
  544. begin
  545. if @project.nil? || @project.new_record?
  546. @page_header_name.nil? ? l(:label_my_home) : @page_header_name
  547. elsif @project.new_record?
  548. l(:label_project_new)
  549. else
  550. html = h(@project.name)
  551. html << privacy(@project)
  552. html << volunteering(@project)
  553. html
  554. end
  555. rescue
  556. "Home"
  557. end
  558. end
  559. def html_title(*args)
  560. if args.empty?
  561. title = []
  562. title << @project.name if @project
  563. title += @html_title if @html_title
  564. title << Setting.app_title
  565. title.select {|t| !t.blank? }.join(' - ')
  566. else
  567. @html_title ||= []
  568. @html_title += args
  569. end
  570. end
  571. def accesskey(s)
  572. Redmine::AccessKeys.key_for s
  573. end
  574. # Formats text according to system settings.
  575. # 2 ways to call this method:
  576. # * with a String: textilizable(text, options)
  577. # * with an object and one of its attribute: textilizable(issue, :description, options)
  578. def textilizable(*args)
  579. options = args.last.is_a?(Hash) ? args.pop : {}
  580. case args.size
  581. when 1
  582. obj = options[:object]
  583. text = args.shift
  584. when 2
  585. obj = args.shift
  586. text = obj.send(args.shift).to_s
  587. else
  588. raise ArgumentError, 'invalid arguments to textilizable'
  589. end
  590. return '' if text.blank?
  591. only_path = options.delete(:only_path) == false ? false : true
  592. # when using an image link, try to use an attachment, if possible
  593. attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
  594. if attachments
  595. attachments = attachments.sort_by(&:created_at).reverse
  596. text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(bmp|gif|jpg|jpeg|png))!/i) do |m|
  597. style = $1
  598. filename = $6.downcase
  599. # search for the picture in attachments
  600. if found = attachments.detect { |att| att.filename.downcase == filename }
  601. image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
  602. desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
  603. alt = desc.blank? ? nil : "(#{desc})"
  604. "!#{style}#{image_url}#{alt}!"
  605. else
  606. m
  607. end
  608. end
  609. end
  610. text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text) { |macro, args| exec_macro(macro, obj, args) }
  611. # different methods for formatting wiki links
  612. case options[:wiki_links]
  613. when :local
  614. # used for local links to html files
  615. format_wiki_link = Proc.new {|project, title, anchor| "#{title}.html" }
  616. when :anchor
  617. # used for single-file wiki export
  618. format_wiki_link = Proc.new {|project, title, anchor| "##{title}" }
  619. else
  620. format_wiki_link = Proc.new {|project, title, anchor| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title, :anchor => anchor) }
  621. end
  622. project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
  623. # Wiki links
  624. #
  625. # Examples:
  626. # [[mypage]]
  627. # [[mypage|mytext]]
  628. # wiki links can refer other project wikis, using project name or identifier:
  629. # [[project:]] -> wiki starting page
  630. # [[project:|mytext]]
  631. # [[project:mypage]]
  632. # [[project:mypage|mytext]]
  633. text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
  634. link_project = project
  635. esc, all, page, title = $1, $2, $3, $5
  636. if esc.nil?
  637. if page =~ /^([^\:]+)\:(.*)$/
  638. link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
  639. page = $2
  640. title ||= $1 if page.blank?
  641. end
  642. if link_project && link_project.wiki
  643. # extract anchor
  644. anchor = nil
  645. if page =~ /^(.+?)\#(.+)$/
  646. page, anchor = $1, $2
  647. end
  648. # check if page exists
  649. wiki_page = link_project.wiki.find_page(page)
  650. link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page), anchor),
  651. :class => ('wiki-page' + (wiki_page ? '' : ' new')))
  652. else
  653. # project or wiki doesn't exist
  654. all
  655. end
  656. else
  657. all
  658. end
  659. end
  660. # Redmine links
  661. #
  662. # Examples:
  663. # Issues:
  664. # #52 -> Link to issue #52
  665. # Documents:
  666. # document#17 -> Link to document with id 17
  667. # document:Greetings -> Link to the document with title "Greetings"
  668. # document:"Some document" -> Link to the document with title "Some document"
  669. # Versions:
  670. # version#3 -> Link to version with id 3
  671. # version:1.0.0 -> Link to version named "1.0.0"
  672. # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
  673. # Attachments:
  674. # attachment:file.zip -> Link to the attachment of the current object named file.zip
  675. # Source files:
  676. # source:some/file -> Link to the file located at /some/file in the project's repository
  677. # source:some/file@52 -> Link to the file's revision 52
  678. # source:some/file#L120 -> Link to line 120 of the file
  679. # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
  680. # export:some/file -> Force the download of the file
  681. # Forum messages:
  682. # message#1218 -> Link to message with id 1218
  683. # User mentions:
  684. # @userlogin -> Link to user with login:userlogin
  685. # text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|<|$)}) do |m|
  686. 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|
  687. leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
  688. link = nil
  689. if esc.nil?
  690. if sep == '#'
  691. oid = oid.to_i
  692. case prefix
  693. when nil
  694. if issue = Issue.visible.find_by_id(oid, :include => :status)
  695. link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
  696. :class => issue.css_classes,
  697. :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
  698. end
  699. when 'document'
  700. if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
  701. link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
  702. :class => 'document'
  703. end
  704. when 'message'
  705. if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
  706. link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
  707. :controller => 'messages',
  708. :action => 'show',
  709. :board_id => message.board,
  710. :id => message.root,
  711. :anchor => (message.parent ? "message-#{message.id}" : nil)},
  712. :class => 'message'
  713. end
  714. end
  715. elsif sep == '@'
  716. link = link_to("@#{oid}", {:only_path => only_path, :controller => 'users', :action => 'show', :id => 0, :login => oid})
  717. elsif sep == ':'
  718. # removes the double quotes if any
  719. name = oid.gsub(%r{^"(.*)"$}, "\\1")
  720. case prefix
  721. when 'document'
  722. if project && document = project.documents.find_by_title(name)
  723. link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
  724. :class => 'document'
  725. end
  726. when 'attachment'
  727. if attachments && attachment = attachments.detect {|a| a.filename == name }
  728. link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
  729. :class => 'attachment'
  730. end
  731. end
  732. end
  733. end
  734. leading + (link || "#{prefix}#{sep}#{oid}")
  735. end
  736. text
  737. end
  738. # Same as Rails' simple_format helper without using paragraphs
  739. def simple_format_without_paragraph(text)
  740. text.to_s.
  741. gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
  742. gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
  743. gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
  744. end
  745. def lang_options_for_select(blank=true)
  746. (blank ? [["(auto)", ""]] : []) +
  747. valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
  748. end
  749. def month_hash
  750. [
  751. ["01 - January",1],
  752. ["02 - February",2],
  753. ["03 - March",3],
  754. ["04 - April",4],
  755. ["05 - May",5],
  756. ["06 - June",6],
  757. ["07 - July",7],
  758. ["08 - August",8],
  759. ["09 - September",9],
  760. ["10 - October",10],
  761. ["11 - November",11],
  762. ["12 - December",12]
  763. ]
  764. end
  765. def privacy(project)
  766. project.is_public ? "" : help_bubble(:help_this_workstream_is_private, {:image =>"icon_privacy.png"})
  767. end
  768. def volunteering(project)
  769. project.volunteer ? help_bubble(:help_volunteer, {:image => "icon_volunteer.png"}) : ""
  770. end
  771. def year_hash
  772. [0,1,2,3,4,5,6,7,8,9,10].collect{|n| [(Date.today.year + n).to_s, Date.today.year + n]}
  773. end
  774. def unit_for(project)
  775. if project.volunteer?
  776. return '♥'
  777. else
  778. return '●'
  779. end
  780. end
  781. def country_hash
  782. {
  783. "Afghanistan" => "AF",
  784. "Albania" => "AL",
  785. "Algeria" => "DZ",
  786. "American Samoa" => "AS",
  787. "Andorra" => "AD",
  788. "Angola" => "AO",
  789. "Anguilla" => "AI",
  790. "Antigua and Barbuda" => "AG",
  791. "Argentina" => "AR",
  792. "Armenia" => "AM",
  793. "Aruba" => "AW",
  794. "Australia" => "AU",
  795. "Austria" => "AT",
  796. "Aland Islands" => "AX",
  797. "Azerbaijan" => "AZ",
  798. "Bahamas" => "BS",
  799. "Bahrain" => "BH",
  800. "Bangladesh" => "BD",
  801. "Barbados" => "BB",
  802. "Belarus" => "BY",
  803. "Belgium" => "BE",
  804. "Belize" => "BZ",
  805. "Benin" => "BJ",
  806. "Bermuda" => "BM",
  807. "Bhutan" => "BT",
  808. "Bolivia" => "BO",
  809. "Bosnia and Herzegovina" => "BA",
  810. "Botswana" => "BW",
  811. "Bouvet Island" => "BV",
  812. "Brazil" => "BR",
  813. "Brunei Darussalam" => "BN",
  814. "British Indian Ocean Territory" => "IO",
  815. "Bulgaria" => "BG",
  816. "Burkina Faso" => "BF",
  817. "Burundi" => "BI",
  818. "Cambodia" => "KH",
  819. "Cameroon" => "CM",
  820. "Canada" => "CA",
  821. "Cape Verde" => "CV",
  822. "Cayman Islands" => "KY",
  823. "Central African Republic" => "CF",
  824. "Chad" => "TD",
  825. "Chile" => "CL",
  826. "China" => "CN",
  827. "Christmas Island" => "CX",
  828. "Cocos (Keeling) Islands" => "CC",
  829. "Colombia" => "CO",
  830. "Comoros" => "KM",
  831. "Congo" => "CG",
  832. "Congo, the Democratic Republic of the" => "CD",
  833. "Cook Islands" => "CK",
  834. "Costa Rica" => "CR",
  835. "Cote D'Ivoire" => "CI",
  836. "Croatia" => "HR",
  837. "Cuba" => "CU",
  838. "Cyprus" => "CY",
  839. "Czech Republic" => "CZ",
  840. "Denmark" => "DK",
  841. "Djibouti" => "DJ",
  842. "Dominica" => "DM",
  843. "Dominican Republic" => "DO",
  844. "Ecuador" => "EC",
  845. "Egypt" => "EG",
  846. "El Salvador" => "SV",
  847. "Equatorial Guinea" => "GQ",
  848. "Eritrea" => "ER",
  849. "Estonia" => "EE",
  850. "Ethiopia" => "ET",
  851. "Falkland Islands (Malvinas)" => "FK",
  852. "Faroe Islands" => "FO",
  853. "Fiji" => "FJ",
  854. "Finland" => "FI",
  855. "France" => "FR",
  856. "French Guiana" => "GF",
  857. "French Polynesia" => "PF",
  858. "French Southern Territories" => "TF",
  859. "Gabon" => "GA",
  860. "Gambia" => "GM",
  861. "Georgia" => "GE",
  862. "Germany" => "DE",
  863. "Ghana" => "GH",
  864. "Gibraltar" => "GI",
  865. "Greece" => "GR",
  866. "Greenland" => "GL",
  867. "Grenada" => "GD",
  868. "Guadeloupe" => "GP",
  869. "Guam" => "GU",
  870. "Guatemala" => "GT",
  871. "Guinea" => "GN",
  872. "Guinea-Bissau" => "GW",
  873. "Guyana" => "GY",
  874. "Guernsey" => "GG",
  875. "Haiti" => "HT",
  876. "Holy See (Vatican City State)" => "VA",
  877. "Honduras" => "HN",
  878. "Hong Kong" => "HK",
  879. "Heard Island And Mcdonald Islands" => "HM",
  880. "Hungary" => "HU",
  881. "Iceland" => "IS",
  882. "India" => "IN",
  883. "Indonesia" => "ID",
  884. "Iran, Islamic Republic of" => "IR",
  885. "Iraq" => "IQ",
  886. "Ireland" => "IE",
  887. "Isle Of Man" => "IM",
  888. "Israel" => "IL",
  889. "Italy" => "IT",
  890. "Jamaica" => "JM",
  891. "Japan" => "JP",
  892. "Jersey" => "JE",
  893. "Jordan" => "JO",
  894. "Kazakhstan" => "KZ",
  895. "Kenya" => "KE",
  896. "Kiribati" => "KI",
  897. "Korea, Democratic People's Republic of" => "KP",
  898. "Korea, Republic of" => "KR",
  899. "Kuwait" => "KW",
  900. "Kyrgyzstan" => "KG",
  901. "Lao People's Democratic Republic" => "LA",
  902. "Latvia" => "LV",
  903. "Lebanon" => "LB",
  904. "Lesotho" => "LS",
  905. "Liberia" => "LR",
  906. "Libyan Arab Jamahiriya" => "LY",
  907. "Liechtenstein" => "LI",
  908. "Lithuania" => "LT",
  909. "Luxembourg" => "LU",
  910. "Macao" => "MO",
  911. "Macedonia, the Former Yugoslav Republic of" => "MK",
  912. "Madagascar" => "MG",
  913. "Malawi" => "MW",
  914. "Malaysia" => "MY",
  915. "Maldives" => "MV",
  916. "Mali" => "ML",
  917. "Malta" => "MT",
  918. "Marshall Islands" => "MH",
  919. "Martinique" => "MQ",
  920. "Mauritania" => "MR",
  921. "Mauritius" => "MU",
  922. "Mayotte" => "YT",
  923. "Mexico" => "MX",
  924. "Micronesia, Federated States of" => "FM",
  925. "Moldova, Republic of" => "MD",
  926. "Monaco" => "MC",
  927. "Mongolia" => "MN",
  928. "Montenegro" => "ME",
  929. "Montserrat" => "MS",
  930. "Morocco" => "MA",
  931. "Mozambique" => "MZ",
  932. "Myanmar" => "MM",
  933. "Namibia" => "NA",
  934. "Nauru" => "NR",
  935. "Nepal" => "NP",
  936. "Netherlands" => "NL",
  937. "Netherlands Antilles" => "AN",
  938. "New Caledonia" => "NC",
  939. "New Zealand" => "NZ",
  940. "Nicaragua" => "NI",
  941. "Niger" => "NE",
  942. "Nigeria" => "NG",
  943. "Niue" => "NU",
  944. "Norfolk Island" => "NF",
  945. "Northern Mariana Islands" => "MP",
  946. "Norway" => "NO",
  947. "Oman" => "OM",
  948. "Pakistan" => "PK",
  949. "Palau" => "PW",
  950. "Palestinian Territory, Occupied" => "PS",
  951. "Panama" => "PA",
  952. "Papua New Guinea" => "PG",
  953. "Paraguay" => "PY",
  954. "Peru" => "PE",
  955. "Philippines" => "PH",
  956. "Pitcairn" => "PN",
  957. "Poland" => "PL",
  958. "Portugal" => "PT",
  959. "Puerto Rico" => "PR",
  960. "Qatar" => "QA",
  961. "Reunion" => "RE",
  962. "Romania" => "RO",
  963. "Russian Federation" => "RU",
  964. "Rwanda" => "RW",
  965. "Saint Barthélemy" => "BL",
  966. "Saint Helena" => "SH",
  967. "Saint Kitts and Nevis" => "KN",
  968. "Saint Lucia" => "LC",
  969. "Saint Martin (French part)" => "MF",
  970. "Saint Pierre and Miquelon" => "PM",
  971. "Saint Vincent and the Grenadines" => "VC",
  972. "Samoa" => "WS",
  973. "San Marino" => "SM",
  974. "Sao Tome and Principe" => "ST",
  975. "Saudi Arabia" => "SA",
  976. "Senegal" => "SN",
  977. "Serbia" => "RS",
  978. "Seychelles" => "SC",
  979. "Sierra Leone" => "SL",
  980. "Singapore" => "SG",
  981. "Slovakia" => "SK",
  982. "Slovenia" => "SI",
  983. "Solomon Islands" => "SB",
  984. "Somalia" => "SO",
  985. "South Africa" => "ZA",
  986. "South Georgia and the South Sandwich Islands" => "GS",
  987. "Spain" => "ES",
  988. "Sri Lanka" => "LK",
  989. "Sudan" => "SD",
  990. "Suriname" => "SR",
  991. "Svalbard and Jan Mayen" => "SJ",
  992. "Swaziland" => "SZ",
  993. "Sweden" => "SE",
  994. "Switzerland" => "CH",
  995. "Syrian Arab Republic" => "SY",
  996. "Taiwan, Province of China" => "TW",
  997. "Tajikistan" => "TJ",
  998. "Tanzania, United Republic of" => "TZ",
  999. "Thailand" => "TH",
  1000. "Timor Leste" => "TL",
  1001. "Togo" => "TG",
  1002. "Tokelau" => "TK",
  1003. "Tonga" => "TO",
  1004. "Trinidad and Tobago" => "TT",
  1005. "Tunisia" => "TN",
  1006. "Turkey" => "TR",
  1007. "Turkmenistan" => "TM",
  1008. "Turks and Caicos Islands" => "TC",
  1009. "Tuvalu" => "TV",
  1010. "Uganda" => "UG",
  1011. "Ukraine" => "UA",
  1012. "United Arab Emirates" => "AE",
  1013. "United Kingdom" => "GB",
  1014. "United States" => "US",
  1015. "United States Minor Outlying Islands" => "UM",
  1016. "Uruguay" => "UY",
  1017. "Uzbekistan" => "UZ",
  1018. "Vanuatu" => "VU",
  1019. "Venezuela" => "VE",
  1020. "Viet Nam" => "VN",
  1021. "Virgin Islands, British" => "VG",
  1022. "Virgin Islands, U.S." => "VI",
  1023. "Wallis and Futuna" => "WF",
  1024. "Western Sahara" => "EH",
  1025. "Yemen" => "YE",
  1026. "Zambia" => "ZM",
  1027. "Zimbabwe" => "ZW"
  1028. }
  1029. end
  1030. def label_tag_for(name, option_tags = nil, options = {})
  1031. label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
  1032. content_tag("label", label_text)
  1033. end
  1034. def labelled_tabular_form_for(name, object, options, &proc)
  1035. options[:html] ||= {}
  1036. options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
  1037. form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
  1038. end
  1039. def back_url_hidden_field_tag
  1040. back_url = params[:back_url] || request.env['HTTP_REFERER']
  1041. back_url = CGI.unescape(back_url.to_s)
  1042. hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
  1043. end
  1044. def check_all_links(form_name)
  1045. link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
  1046. " | " +
  1047. link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
  1048. end
  1049. def progress_bar(pcts, options={})
  1050. pcts = [pcts, pcts] unless pcts.is_a?(Array)
  1051. pcts = pcts.collect(&:round)
  1052. pcts[1] = pcts[1] - pcts[0]
  1053. pcts << (100 - pcts[1] - pcts[0])
  1054. width = options[:width] || '100px;'
  1055. legend = options[:legend] || ''
  1056. content_tag('table',
  1057. content_tag('tr',
  1058. (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
  1059. (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
  1060. (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
  1061. ), :class => 'progress', :style => "width: #{width};") +
  1062. content_tag('p', legend, :class => 'pourcent')
  1063. end
  1064. def context_menu_link(name, url, options={})
  1065. options[:class] ||= ''
  1066. if options.delete(:selected)
  1067. options[:class] << ' icon-checked disabled'
  1068. options[:disabled] = true
  1069. end
  1070. if options.delete(:disabled)
  1071. options.delete(:method)
  1072. options.delete(:confirm)
  1073. options.delete(:onclick)
  1074. options[:class] << ' disabled'
  1075. url = '#'
  1076. end
  1077. link_to name, url, options
  1078. end
  1079. def help_link(name, options={})
  1080. options[:show_name] ||= false #When true, we show the text of the help key next to the link
  1081. 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'})
  1082. end
  1083. def help_bubble(name, options={})
  1084. imagename = options[:image] || "question_mark.gif"
  1085. image = image_tag(imagename, :class=> "help_question_mark", :id=>"help_image_#{name}")
  1086. html = link_to(image, {:href => '#'}, {:onclick => "$('#help_image_#{name}').bubbletip('#tip_#{name}', {deltaDirection: 'right', bindShow: 'click'}); return false;"})
  1087. html << content_tag(:span, l(name, options), :class => 'tip hidden', :id=>"tip_#{name}")
  1088. # <img id="help_image_panel_' + name + '" src="/images/question_mark.gif" class="help_question_mark">
  1089. # <div id="help_panel_canceled" style="display:none;">
  1090. # <div class="tip" style="width:300px">
  1091. # <strong>Canceled Ideas</strong><br>
  1092. # 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>
  1093. # Once a request has been canceled, anyone can re-open it, effectively pushing it back as a new item for reconsideration.
  1094. # </div>
  1095. # </div>
  1096. end
  1097. def calendar_for(field_id)
  1098. include_calendar_headers_tags
  1099. image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
  1100. javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
  1101. end
  1102. def include_calendar_headers_tags
  1103. unless @calendar_headers_tags_included
  1104. @calendar_headers_tags_included = true
  1105. content_for :header_tags do
  1106. start_of_week = case Setting.start_of_week.to_i
  1107. when 1
  1108. 'Calendar._FD = 1;' # Monday
  1109. when 7
  1110. 'Calendar._FD = 0;' # Sunday
  1111. else
  1112. '' # use language
  1113. end
  1114. javascript_include_tag('calendar/calendar') +
  1115. javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
  1116. javascript_tag(start_of_week) +
  1117. javascript_include_tag('calendar/calendar-setup') +
  1118. stylesheet_link_tag('calendar')
  1119. end
  1120. end
  1121. end
  1122. def content_for(name, content = nil, &block)
  1123. @has_content ||= {}
  1124. @has_content[name] = true
  1125. super(name, content, &block)
  1126. end
  1127. def has_content?(name)
  1128. (@has_content && @has_content[name]) || false
  1129. end
  1130. # Returns the avatar image tag for the given +user+ if avatars are enabled
  1131. # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
  1132. def avatar(user, options = { })
  1133. options.merge!({:ssl => Setting.protocol == 'https', :default => Setting.gravatar_default})
  1134. email = nil
  1135. if user.respond_to?(:mail)
  1136. email = user.mail
  1137. elsif user.to_s =~ %r{<(.+?)>}
  1138. email = $1
  1139. end
  1140. return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
  1141. end
  1142. def render_journal_details(journal)
  1143. return unless journal
  1144. html = ""
  1145. if journal && journal.details && journal.details.count > 0
  1146. html = "<ul>"
  1147. for detail in journal.details
  1148. html << "<li>#{show_detail(detail)}</li>"
  1149. end
  1150. html << "</ul>"
  1151. end
  1152. content = ""
  1153. content << textilizable(journal, :notes)
  1154. content = make_expandable content, 250
  1155. css_classes = "wiki"
  1156. css_classes << " gravatar-margin" if Setting.gravatar_enabled?
  1157. html << content
  1158. end
  1159. def link_to_activity(as)
  1160. link_to name_for_activity_stream(as), url_for_activity_stream(as), {:class => class_for_activity_stream(as)}
  1161. end
  1162. def name_for_activity_stream(as)
  1163. (as.tracker_name) ? "a #{as.tracker_name.downcase}" : "a " + l("label_#{as.object_type.downcase}")
  1164. end
  1165. def class_for_activity_stream(as)
  1166. (as.object_type.match(/^Issue/)) ? "fancyframe" : "noframe"
  1167. end
  1168. def url_for_activity_stream(as)
  1169. case as.object_type.downcase
  1170. when 'message'
  1171. return {:controller => 'messages', :action => 'show', :board_id => 'guess', :id => as.object_id}
  1172. when 'wikipage'
  1173. return {:controller => 'wiki', :action => 'index', :id => as.project_id, :page => as.object_name}
  1174. when 'memberrole'
  1175. return {:controller => 'projects', :action => 'team', :id => as.project_id}
  1176. when 'motion'
  1177. return {:controller => 'motions', :action => 'show', :project_id => as.project_id, :id => as.object_id}
  1178. else
  1179. return {:controller => as.object_type.downcase.pluralize, :action => 'show', :id => as.object_id}
  1180. end
  1181. end
  1182. def title_for_activity_stream(as)
  1183. case as.object_type.downcase
  1184. when 'memberrole'
  1185. begin
  1186. "#{as.indirect_object_phrase || as.object.user.name} is now #{as.object_name}"
  1187. rescue
  1188. "New member role"
  1189. end
  1190. else
  1191. format_activity_title(as.object_name)
  1192. end
  1193. end
  1194. def action_times(count)
  1195. count = count.to_i
  1196. return nil if count < 2
  1197. return " twice" if count == 2
  1198. return " #{count.to_s} times" if count > 2
  1199. end
  1200. def avatar_from_id(user_id, options = { })
  1201. avatar(User.find(user_id), options)
  1202. end
  1203. def button(text, cssclass)
  1204. return "<div class='action_button_no_float action_button_#{cssclass}' onclick=\"$('.action_button_no_float').hide();\" ><span>#{text}</span></div>"
  1205. end
  1206. def tally_table(motion)
  1207. content = "<table id='motion_votes_totals' class='gt-table'>"
  1208. content << "<thead><tr>"
  1209. content << "<th>&nbsp;</th><th>#{l :label_binding}</th><th>#{l :label_non_binding}</th>"
  1210. content << "</tr></thead>"
  1211. content << "<tr>"
  1212. content << "<th>#{l(:label_agree)}</th><td>#{motion.agree}</td><td>#{motion.agree_nonbind}</td>"
  1213. content << "</tr>"
  1214. content << "<tr>"
  1215. content << "<th>#{l(:label_disagree)}</th><td>#{motion.disagree}</td><td>#{motion.disagree_nonbind}</td>"
  1216. content << "</tr>"
  1217. content << "<tr>"
  1218. content << "<th>#{l(:label_total)}</th><td>#{motion.agree_total}</td><td>#{motion.agree_total_nonbind}</td>"
  1219. content << "</tr>"
  1220. content << "</table>"
  1221. end
  1222. def tame_bias(number)
  1223. if number.nil?
  1224. return ""
  1225. else
  1226. number = number.round
  1227. number > 0 ? "Self:&nbsp&nbsp; +#{number}" : number == 0 ? "Self:&nbsp&nbsp; No Bias" : "Self:&nbsp&nbsp; #{number}"
  1228. end
  1229. end
  1230. def tame_scale(number)
  1231. if number.nil?
  1232. ""
  1233. else
  1234. number = number.round
  1235. number == 0 ? "Other: No Bias" : "Other: &plusmn;#{number}"
  1236. end
  1237. end
  1238. #depending on credit's status, provides link to activate/deactivate a credit. Project id is the current project being viewed
  1239. def credit_activation_link(credit, project_id, include_sub_workstreams)
  1240. return '' if !credit.settled_on.nil?
  1241. return link_to_remote(l(:button_deactivate),
  1242. { :url => {:controller => 'credits', :action => 'disable', :id => credit.id, :project_id => project_id, :with_subprojects => include_sub_workstreams} },
  1243. :class => 'icon icon-deactivate') if credit.enabled
  1244. return link_to_remote(l(:button_activate),
  1245. { :url => {:controller => 'credits', :action => 'enable', :id => credit.id, :project_id => project_id, :with_subprojects => include_sub_workstreams} },
  1246. :class => 'icon i…

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