##// END OF EJS Templates
remove trailing white-spaces from ApplicationHelper...
Toshi MARUYAMA -
r11845:e13814a59c70
parent child
Show More
@@ -1,1272 +1,1272
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2013 Jean-Philippe Lang
4 # Copyright (C) 2006-2013 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require 'forwardable'
20 require 'forwardable'
21 require 'cgi'
21 require 'cgi'
22
22
23 module ApplicationHelper
23 module ApplicationHelper
24 include Redmine::WikiFormatting::Macros::Definitions
24 include Redmine::WikiFormatting::Macros::Definitions
25 include Redmine::I18n
25 include Redmine::I18n
26 include GravatarHelper::PublicMethods
26 include GravatarHelper::PublicMethods
27 include Redmine::Pagination::Helper
27 include Redmine::Pagination::Helper
28
28
29 extend Forwardable
29 extend Forwardable
30 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
30 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
31
31
32 # Return true if user is authorized for controller/action, otherwise false
32 # Return true if user is authorized for controller/action, otherwise false
33 def authorize_for(controller, action)
33 def authorize_for(controller, action)
34 User.current.allowed_to?({:controller => controller, :action => action}, @project)
34 User.current.allowed_to?({:controller => controller, :action => action}, @project)
35 end
35 end
36
36
37 # Display a link if user is authorized
37 # Display a link if user is authorized
38 #
38 #
39 # @param [String] name Anchor text (passed to link_to)
39 # @param [String] name Anchor text (passed to link_to)
40 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
40 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
41 # @param [optional, Hash] html_options Options passed to link_to
41 # @param [optional, Hash] html_options Options passed to link_to
42 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
42 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
43 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
43 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
44 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
44 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
45 end
45 end
46
46
47 # Displays a link to user's account page if active
47 # Displays a link to user's account page if active
48 def link_to_user(user, options={})
48 def link_to_user(user, options={})
49 if user.is_a?(User)
49 if user.is_a?(User)
50 name = h(user.name(options[:format]))
50 name = h(user.name(options[:format]))
51 if user.active? || (User.current.admin? && user.logged?)
51 if user.active? || (User.current.admin? && user.logged?)
52 link_to name, user_path(user), :class => user.css_classes
52 link_to name, user_path(user), :class => user.css_classes
53 else
53 else
54 name
54 name
55 end
55 end
56 else
56 else
57 h(user.to_s)
57 h(user.to_s)
58 end
58 end
59 end
59 end
60
60
61 # Displays a link to +issue+ with its subject.
61 # Displays a link to +issue+ with its subject.
62 # Examples:
62 # Examples:
63 #
63 #
64 # link_to_issue(issue) # => Defect #6: This is the subject
64 # link_to_issue(issue) # => Defect #6: This is the subject
65 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
65 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
66 # link_to_issue(issue, :subject => false) # => Defect #6
66 # link_to_issue(issue, :subject => false) # => Defect #6
67 # link_to_issue(issue, :project => true) # => Foo - Defect #6
67 # link_to_issue(issue, :project => true) # => Foo - Defect #6
68 # link_to_issue(issue, :subject => false, :tracker => false) # => #6
68 # link_to_issue(issue, :subject => false, :tracker => false) # => #6
69 #
69 #
70 def link_to_issue(issue, options={})
70 def link_to_issue(issue, options={})
71 title = nil
71 title = nil
72 subject = nil
72 subject = nil
73 text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}"
73 text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}"
74 if options[:subject] == false
74 if options[:subject] == false
75 title = truncate(issue.subject, :length => 60)
75 title = truncate(issue.subject, :length => 60)
76 else
76 else
77 subject = issue.subject
77 subject = issue.subject
78 if options[:truncate]
78 if options[:truncate]
79 subject = truncate(subject, :length => options[:truncate])
79 subject = truncate(subject, :length => options[:truncate])
80 end
80 end
81 end
81 end
82 s = link_to text, issue_path(issue), :class => issue.css_classes, :title => title
82 s = link_to text, issue_path(issue), :class => issue.css_classes, :title => title
83 s << h(": #{subject}") if subject
83 s << h(": #{subject}") if subject
84 s = h("#{issue.project} - ") + s if options[:project]
84 s = h("#{issue.project} - ") + s if options[:project]
85 s
85 s
86 end
86 end
87
87
88 # Generates a link to an attachment.
88 # Generates a link to an attachment.
89 # Options:
89 # Options:
90 # * :text - Link text (default to attachment filename)
90 # * :text - Link text (default to attachment filename)
91 # * :download - Force download (default: false)
91 # * :download - Force download (default: false)
92 def link_to_attachment(attachment, options={})
92 def link_to_attachment(attachment, options={})
93 text = options.delete(:text) || attachment.filename
93 text = options.delete(:text) || attachment.filename
94 route_method = options.delete(:download) ? :download_named_attachment_path : :named_attachment_path
94 route_method = options.delete(:download) ? :download_named_attachment_path : :named_attachment_path
95 html_options = options.slice!(:only_path)
95 html_options = options.slice!(:only_path)
96 url = send(route_method, attachment, attachment.filename, options)
96 url = send(route_method, attachment, attachment.filename, options)
97 link_to text, url, html_options
97 link_to text, url, html_options
98 end
98 end
99
99
100 # Generates a link to a SCM revision
100 # Generates a link to a SCM revision
101 # Options:
101 # Options:
102 # * :text - Link text (default to the formatted revision)
102 # * :text - Link text (default to the formatted revision)
103 def link_to_revision(revision, repository, options={})
103 def link_to_revision(revision, repository, options={})
104 if repository.is_a?(Project)
104 if repository.is_a?(Project)
105 repository = repository.repository
105 repository = repository.repository
106 end
106 end
107 text = options.delete(:text) || format_revision(revision)
107 text = options.delete(:text) || format_revision(revision)
108 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
108 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
109 link_to(
109 link_to(
110 h(text),
110 h(text),
111 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
111 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
112 :title => l(:label_revision_id, format_revision(revision))
112 :title => l(:label_revision_id, format_revision(revision))
113 )
113 )
114 end
114 end
115
115
116 # Generates a link to a message
116 # Generates a link to a message
117 def link_to_message(message, options={}, html_options = nil)
117 def link_to_message(message, options={}, html_options = nil)
118 link_to(
118 link_to(
119 truncate(message.subject, :length => 60),
119 truncate(message.subject, :length => 60),
120 board_message_path(message.board_id, message.parent_id || message.id, {
120 board_message_path(message.board_id, message.parent_id || message.id, {
121 :r => (message.parent_id && message.id),
121 :r => (message.parent_id && message.id),
122 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
122 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
123 }.merge(options)),
123 }.merge(options)),
124 html_options
124 html_options
125 )
125 )
126 end
126 end
127
127
128 # Generates a link to a project if active
128 # Generates a link to a project if active
129 # Examples:
129 # Examples:
130 #
130 #
131 # link_to_project(project) # => link to the specified project overview
131 # link_to_project(project) # => link to the specified project overview
132 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
132 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
133 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
133 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
134 #
134 #
135 def link_to_project(project, options={}, html_options = nil)
135 def link_to_project(project, options={}, html_options = nil)
136 if project.archived?
136 if project.archived?
137 h(project.name)
137 h(project.name)
138 elsif options.key?(:action)
138 elsif options.key?(:action)
139 ActiveSupport::Deprecation.warn "#link_to_project with :action option is deprecated and will be removed in Redmine 3.0."
139 ActiveSupport::Deprecation.warn "#link_to_project with :action option is deprecated and will be removed in Redmine 3.0."
140 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
140 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
141 link_to project.name, url, html_options
141 link_to project.name, url, html_options
142 else
142 else
143 link_to project.name, project_path(project, options), html_options
143 link_to project.name, project_path(project, options), html_options
144 end
144 end
145 end
145 end
146
146
147 # Generates a link to a project settings if active
147 # Generates a link to a project settings if active
148 def link_to_project_settings(project, options={}, html_options=nil)
148 def link_to_project_settings(project, options={}, html_options=nil)
149 if project.active?
149 if project.active?
150 link_to project.name, settings_project_path(project, options), html_options
150 link_to project.name, settings_project_path(project, options), html_options
151 elsif project.archived?
151 elsif project.archived?
152 h(project.name)
152 h(project.name)
153 else
153 else
154 link_to project.name, project_path(project, options), html_options
154 link_to project.name, project_path(project, options), html_options
155 end
155 end
156 end
156 end
157
157
158 def wiki_page_path(page, options={})
158 def wiki_page_path(page, options={})
159 url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options))
159 url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options))
160 end
160 end
161
161
162 def thumbnail_tag(attachment)
162 def thumbnail_tag(attachment)
163 link_to image_tag(thumbnail_path(attachment)),
163 link_to image_tag(thumbnail_path(attachment)),
164 named_attachment_path(attachment, attachment.filename),
164 named_attachment_path(attachment, attachment.filename),
165 :title => attachment.filename
165 :title => attachment.filename
166 end
166 end
167
167
168 def toggle_link(name, id, options={})
168 def toggle_link(name, id, options={})
169 onclick = "$('##{id}').toggle(); "
169 onclick = "$('##{id}').toggle(); "
170 onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ")
170 onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ")
171 onclick << "return false;"
171 onclick << "return false;"
172 link_to(name, "#", :onclick => onclick)
172 link_to(name, "#", :onclick => onclick)
173 end
173 end
174
174
175 def image_to_function(name, function, html_options = {})
175 def image_to_function(name, function, html_options = {})
176 html_options.symbolize_keys!
176 html_options.symbolize_keys!
177 tag(:input, html_options.merge({
177 tag(:input, html_options.merge({
178 :type => "image", :src => image_path(name),
178 :type => "image", :src => image_path(name),
179 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
179 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
180 }))
180 }))
181 end
181 end
182
182
183 def format_activity_title(text)
183 def format_activity_title(text)
184 h(truncate_single_line(text, :length => 100))
184 h(truncate_single_line(text, :length => 100))
185 end
185 end
186
186
187 def format_activity_day(date)
187 def format_activity_day(date)
188 date == User.current.today ? l(:label_today).titleize : format_date(date)
188 date == User.current.today ? l(:label_today).titleize : format_date(date)
189 end
189 end
190
190
191 def format_activity_description(text)
191 def format_activity_description(text)
192 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
192 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
193 ).gsub(/[\r\n]+/, "<br />").html_safe
193 ).gsub(/[\r\n]+/, "<br />").html_safe
194 end
194 end
195
195
196 def format_version_name(version)
196 def format_version_name(version)
197 if version.project == @project
197 if version.project == @project
198 h(version)
198 h(version)
199 else
199 else
200 h("#{version.project} - #{version}")
200 h("#{version.project} - #{version}")
201 end
201 end
202 end
202 end
203
203
204 def due_date_distance_in_words(date)
204 def due_date_distance_in_words(date)
205 if date
205 if date
206 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
206 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
207 end
207 end
208 end
208 end
209
209
210 # Renders a tree of projects as a nested set of unordered lists
210 # Renders a tree of projects as a nested set of unordered lists
211 # The given collection may be a subset of the whole project tree
211 # The given collection may be a subset of the whole project tree
212 # (eg. some intermediate nodes are private and can not be seen)
212 # (eg. some intermediate nodes are private and can not be seen)
213 def render_project_nested_lists(projects)
213 def render_project_nested_lists(projects)
214 s = ''
214 s = ''
215 if projects.any?
215 if projects.any?
216 ancestors = []
216 ancestors = []
217 original_project = @project
217 original_project = @project
218 projects.sort_by(&:lft).each do |project|
218 projects.sort_by(&:lft).each do |project|
219 # set the project environment to please macros.
219 # set the project environment to please macros.
220 @project = project
220 @project = project
221 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
221 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
222 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
222 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
223 else
223 else
224 ancestors.pop
224 ancestors.pop
225 s << "</li>"
225 s << "</li>"
226 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
226 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
227 ancestors.pop
227 ancestors.pop
228 s << "</ul></li>\n"
228 s << "</ul></li>\n"
229 end
229 end
230 end
230 end
231 classes = (ancestors.empty? ? 'root' : 'child')
231 classes = (ancestors.empty? ? 'root' : 'child')
232 s << "<li class='#{classes}'><div class='#{classes}'>"
232 s << "<li class='#{classes}'><div class='#{classes}'>"
233 s << h(block_given? ? yield(project) : project.name)
233 s << h(block_given? ? yield(project) : project.name)
234 s << "</div>\n"
234 s << "</div>\n"
235 ancestors << project
235 ancestors << project
236 end
236 end
237 s << ("</li></ul>\n" * ancestors.size)
237 s << ("</li></ul>\n" * ancestors.size)
238 @project = original_project
238 @project = original_project
239 end
239 end
240 s.html_safe
240 s.html_safe
241 end
241 end
242
242
243 def render_page_hierarchy(pages, node=nil, options={})
243 def render_page_hierarchy(pages, node=nil, options={})
244 content = ''
244 content = ''
245 if pages[node]
245 if pages[node]
246 content << "<ul class=\"pages-hierarchy\">\n"
246 content << "<ul class=\"pages-hierarchy\">\n"
247 pages[node].each do |page|
247 pages[node].each do |page|
248 content << "<li>"
248 content << "<li>"
249 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil},
249 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil},
250 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
250 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
251 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
251 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
252 content << "</li>\n"
252 content << "</li>\n"
253 end
253 end
254 content << "</ul>\n"
254 content << "</ul>\n"
255 end
255 end
256 content.html_safe
256 content.html_safe
257 end
257 end
258
258
259 # Renders flash messages
259 # Renders flash messages
260 def render_flash_messages
260 def render_flash_messages
261 s = ''
261 s = ''
262 flash.each do |k,v|
262 flash.each do |k,v|
263 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
263 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
264 end
264 end
265 s.html_safe
265 s.html_safe
266 end
266 end
267
267
268 # Renders tabs and their content
268 # Renders tabs and their content
269 def render_tabs(tabs)
269 def render_tabs(tabs)
270 if tabs.any?
270 if tabs.any?
271 render :partial => 'common/tabs', :locals => {:tabs => tabs}
271 render :partial => 'common/tabs', :locals => {:tabs => tabs}
272 else
272 else
273 content_tag 'p', l(:label_no_data), :class => "nodata"
273 content_tag 'p', l(:label_no_data), :class => "nodata"
274 end
274 end
275 end
275 end
276
276
277 # Renders the project quick-jump box
277 # Renders the project quick-jump box
278 def render_project_jump_box
278 def render_project_jump_box
279 return unless User.current.logged?
279 return unless User.current.logged?
280 projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq
280 projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq
281 if projects.any?
281 if projects.any?
282 options =
282 options =
283 ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
283 ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
284 '<option value="" disabled="disabled">---</option>').html_safe
284 '<option value="" disabled="disabled">---</option>').html_safe
285
285
286 options << project_tree_options_for_select(projects, :selected => @project) do |p|
286 options << project_tree_options_for_select(projects, :selected => @project) do |p|
287 { :value => project_path(:id => p, :jump => current_menu_item) }
287 { :value => project_path(:id => p, :jump => current_menu_item) }
288 end
288 end
289
289
290 select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
290 select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
291 end
291 end
292 end
292 end
293
293
294 def project_tree_options_for_select(projects, options = {})
294 def project_tree_options_for_select(projects, options = {})
295 s = ''
295 s = ''
296 project_tree(projects) do |project, level|
296 project_tree(projects) do |project, level|
297 name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
297 name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
298 tag_options = {:value => project.id}
298 tag_options = {:value => project.id}
299 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
299 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
300 tag_options[:selected] = 'selected'
300 tag_options[:selected] = 'selected'
301 else
301 else
302 tag_options[:selected] = nil
302 tag_options[:selected] = nil
303 end
303 end
304 tag_options.merge!(yield(project)) if block_given?
304 tag_options.merge!(yield(project)) if block_given?
305 s << content_tag('option', name_prefix + h(project), tag_options)
305 s << content_tag('option', name_prefix + h(project), tag_options)
306 end
306 end
307 s.html_safe
307 s.html_safe
308 end
308 end
309
309
310 # Yields the given block for each project with its level in the tree
310 # Yields the given block for each project with its level in the tree
311 #
311 #
312 # Wrapper for Project#project_tree
312 # Wrapper for Project#project_tree
313 def project_tree(projects, &block)
313 def project_tree(projects, &block)
314 Project.project_tree(projects, &block)
314 Project.project_tree(projects, &block)
315 end
315 end
316
316
317 def principals_check_box_tags(name, principals)
317 def principals_check_box_tags(name, principals)
318 s = ''
318 s = ''
319 principals.each do |principal|
319 principals.each do |principal|
320 s << "<label>#{ check_box_tag name, principal.id, false, :id => nil } #{h principal}</label>\n"
320 s << "<label>#{ check_box_tag name, principal.id, false, :id => nil } #{h principal}</label>\n"
321 end
321 end
322 s.html_safe
322 s.html_safe
323 end
323 end
324
324
325 # Returns a string for users/groups option tags
325 # Returns a string for users/groups option tags
326 def principals_options_for_select(collection, selected=nil)
326 def principals_options_for_select(collection, selected=nil)
327 s = ''
327 s = ''
328 if collection.include?(User.current)
328 if collection.include?(User.current)
329 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
329 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
330 end
330 end
331 groups = ''
331 groups = ''
332 collection.sort.each do |element|
332 collection.sort.each do |element|
333 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) || element.id.to_s == selected
333 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) || element.id.to_s == selected
334 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
334 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
335 end
335 end
336 unless groups.empty?
336 unless groups.empty?
337 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
337 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
338 end
338 end
339 s.html_safe
339 s.html_safe
340 end
340 end
341
341
342 # Options for the new membership projects combo-box
342 # Options for the new membership projects combo-box
343 def options_for_membership_project_select(principal, projects)
343 def options_for_membership_project_select(principal, projects)
344 options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
344 options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
345 options << project_tree_options_for_select(projects) do |p|
345 options << project_tree_options_for_select(projects) do |p|
346 {:disabled => principal.projects.to_a.include?(p)}
346 {:disabled => principal.projects.to_a.include?(p)}
347 end
347 end
348 options
348 options
349 end
349 end
350
350
351 def option_tag(name, text, value, selected=nil, options={})
351 def option_tag(name, text, value, selected=nil, options={})
352 content_tag 'option', value, options.merge(:value => value, :selected => (value == selected))
352 content_tag 'option', value, options.merge(:value => value, :selected => (value == selected))
353 end
353 end
354
354
355 # Truncates and returns the string as a single line
355 # Truncates and returns the string as a single line
356 def truncate_single_line(string, *args)
356 def truncate_single_line(string, *args)
357 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
357 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
358 end
358 end
359
359
360 # Truncates at line break after 250 characters or options[:length]
360 # Truncates at line break after 250 characters or options[:length]
361 def truncate_lines(string, options={})
361 def truncate_lines(string, options={})
362 length = options[:length] || 250
362 length = options[:length] || 250
363 if string.to_s =~ /\A(.{#{length}}.*?)$/m
363 if string.to_s =~ /\A(.{#{length}}.*?)$/m
364 "#{$1}..."
364 "#{$1}..."
365 else
365 else
366 string
366 string
367 end
367 end
368 end
368 end
369
369
370 def anchor(text)
370 def anchor(text)
371 text.to_s.gsub(' ', '_')
371 text.to_s.gsub(' ', '_')
372 end
372 end
373
373
374 def html_hours(text)
374 def html_hours(text)
375 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
375 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
376 end
376 end
377
377
378 def authoring(created, author, options={})
378 def authoring(created, author, options={})
379 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
379 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
380 end
380 end
381
381
382 def time_tag(time)
382 def time_tag(time)
383 text = distance_of_time_in_words(Time.now, time)
383 text = distance_of_time_in_words(Time.now, time)
384 if @project
384 if @project
385 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
385 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
386 else
386 else
387 content_tag('acronym', text, :title => format_time(time))
387 content_tag('acronym', text, :title => format_time(time))
388 end
388 end
389 end
389 end
390
390
391 def syntax_highlight_lines(name, content)
391 def syntax_highlight_lines(name, content)
392 lines = []
392 lines = []
393 syntax_highlight(name, content).each_line { |line| lines << line }
393 syntax_highlight(name, content).each_line { |line| lines << line }
394 lines
394 lines
395 end
395 end
396
396
397 def syntax_highlight(name, content)
397 def syntax_highlight(name, content)
398 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
398 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
399 end
399 end
400
400
401 def to_path_param(path)
401 def to_path_param(path)
402 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
402 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
403 str.blank? ? nil : str
403 str.blank? ? nil : str
404 end
404 end
405
405
406 def reorder_links(name, url, method = :post)
406 def reorder_links(name, url, method = :post)
407 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
407 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
408 url.merge({"#{name}[move_to]" => 'highest'}),
408 url.merge({"#{name}[move_to]" => 'highest'}),
409 :method => method, :title => l(:label_sort_highest)) +
409 :method => method, :title => l(:label_sort_highest)) +
410 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
410 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
411 url.merge({"#{name}[move_to]" => 'higher'}),
411 url.merge({"#{name}[move_to]" => 'higher'}),
412 :method => method, :title => l(:label_sort_higher)) +
412 :method => method, :title => l(:label_sort_higher)) +
413 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
413 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
414 url.merge({"#{name}[move_to]" => 'lower'}),
414 url.merge({"#{name}[move_to]" => 'lower'}),
415 :method => method, :title => l(:label_sort_lower)) +
415 :method => method, :title => l(:label_sort_lower)) +
416 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
416 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
417 url.merge({"#{name}[move_to]" => 'lowest'}),
417 url.merge({"#{name}[move_to]" => 'lowest'}),
418 :method => method, :title => l(:label_sort_lowest))
418 :method => method, :title => l(:label_sort_lowest))
419 end
419 end
420
420
421 def breadcrumb(*args)
421 def breadcrumb(*args)
422 elements = args.flatten
422 elements = args.flatten
423 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
423 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
424 end
424 end
425
425
426 def other_formats_links(&block)
426 def other_formats_links(&block)
427 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
427 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
428 yield Redmine::Views::OtherFormatsBuilder.new(self)
428 yield Redmine::Views::OtherFormatsBuilder.new(self)
429 concat('</p>'.html_safe)
429 concat('</p>'.html_safe)
430 end
430 end
431
431
432 def page_header_title
432 def page_header_title
433 if @project.nil? || @project.new_record?
433 if @project.nil? || @project.new_record?
434 h(Setting.app_title)
434 h(Setting.app_title)
435 else
435 else
436 b = []
436 b = []
437 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
437 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
438 if ancestors.any?
438 if ancestors.any?
439 root = ancestors.shift
439 root = ancestors.shift
440 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
440 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
441 if ancestors.size > 2
441 if ancestors.size > 2
442 b << "\xe2\x80\xa6"
442 b << "\xe2\x80\xa6"
443 ancestors = ancestors[-2, 2]
443 ancestors = ancestors[-2, 2]
444 end
444 end
445 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
445 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
446 end
446 end
447 b << h(@project)
447 b << h(@project)
448 b.join(" \xc2\xbb ").html_safe
448 b.join(" \xc2\xbb ").html_safe
449 end
449 end
450 end
450 end
451
451
452 # Returns a h2 tag and sets the html title with the given arguments
452 # Returns a h2 tag and sets the html title with the given arguments
453 def title(*args)
453 def title(*args)
454 strings = args.map do |arg|
454 strings = args.map do |arg|
455 if arg.is_a?(Array) && arg.size >= 2
455 if arg.is_a?(Array) && arg.size >= 2
456 link_to(*arg)
456 link_to(*arg)
457 else
457 else
458 h(arg.to_s)
458 h(arg.to_s)
459 end
459 end
460 end
460 end
461 html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
461 html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
462 content_tag('h2', strings.join(' &#187; ').html_safe)
462 content_tag('h2', strings.join(' &#187; ').html_safe)
463 end
463 end
464
464
465 # Sets the html title
465 # Sets the html title
466 # Returns the html title when called without arguments
466 # Returns the html title when called without arguments
467 # Current project name and app_title and automatically appended
467 # Current project name and app_title and automatically appended
468 # Exemples:
468 # Exemples:
469 # html_title 'Foo', 'Bar'
469 # html_title 'Foo', 'Bar'
470 # html_title # => 'Foo - Bar - My Project - Redmine'
470 # html_title # => 'Foo - Bar - My Project - Redmine'
471 def html_title(*args)
471 def html_title(*args)
472 if args.empty?
472 if args.empty?
473 title = @html_title || []
473 title = @html_title || []
474 title << @project.name if @project
474 title << @project.name if @project
475 title << Setting.app_title unless Setting.app_title == title.last
475 title << Setting.app_title unless Setting.app_title == title.last
476 title.reject(&:blank?).join(' - ')
476 title.reject(&:blank?).join(' - ')
477 else
477 else
478 @html_title ||= []
478 @html_title ||= []
479 @html_title += args
479 @html_title += args
480 end
480 end
481 end
481 end
482
482
483 # Returns the theme, controller name, and action as css classes for the
483 # Returns the theme, controller name, and action as css classes for the
484 # HTML body.
484 # HTML body.
485 def body_css_classes
485 def body_css_classes
486 css = []
486 css = []
487 if theme = Redmine::Themes.theme(Setting.ui_theme)
487 if theme = Redmine::Themes.theme(Setting.ui_theme)
488 css << 'theme-' + theme.name
488 css << 'theme-' + theme.name
489 end
489 end
490
490
491 css << 'controller-' + controller_name
491 css << 'controller-' + controller_name
492 css << 'action-' + action_name
492 css << 'action-' + action_name
493 css.join(' ')
493 css.join(' ')
494 end
494 end
495
495
496 def accesskey(s)
496 def accesskey(s)
497 @used_accesskeys ||= []
497 @used_accesskeys ||= []
498 key = Redmine::AccessKeys.key_for(s)
498 key = Redmine::AccessKeys.key_for(s)
499 return nil if @used_accesskeys.include?(key)
499 return nil if @used_accesskeys.include?(key)
500 @used_accesskeys << key
500 @used_accesskeys << key
501 key
501 key
502 end
502 end
503
503
504 # Formats text according to system settings.
504 # Formats text according to system settings.
505 # 2 ways to call this method:
505 # 2 ways to call this method:
506 # * with a String: textilizable(text, options)
506 # * with a String: textilizable(text, options)
507 # * with an object and one of its attribute: textilizable(issue, :description, options)
507 # * with an object and one of its attribute: textilizable(issue, :description, options)
508 def textilizable(*args)
508 def textilizable(*args)
509 options = args.last.is_a?(Hash) ? args.pop : {}
509 options = args.last.is_a?(Hash) ? args.pop : {}
510 case args.size
510 case args.size
511 when 1
511 when 1
512 obj = options[:object]
512 obj = options[:object]
513 text = args.shift
513 text = args.shift
514 when 2
514 when 2
515 obj = args.shift
515 obj = args.shift
516 attr = args.shift
516 attr = args.shift
517 text = obj.send(attr).to_s
517 text = obj.send(attr).to_s
518 else
518 else
519 raise ArgumentError, 'invalid arguments to textilizable'
519 raise ArgumentError, 'invalid arguments to textilizable'
520 end
520 end
521 return '' if text.blank?
521 return '' if text.blank?
522 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
522 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
523 only_path = options.delete(:only_path) == false ? false : true
523 only_path = options.delete(:only_path) == false ? false : true
524
524
525 text = text.dup
525 text = text.dup
526 macros = catch_macros(text)
526 macros = catch_macros(text)
527 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
527 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
528
528
529 @parsed_headings = []
529 @parsed_headings = []
530 @heading_anchors = {}
530 @heading_anchors = {}
531 @current_section = 0 if options[:edit_section_links]
531 @current_section = 0 if options[:edit_section_links]
532
532
533 parse_sections(text, project, obj, attr, only_path, options)
533 parse_sections(text, project, obj, attr, only_path, options)
534 text = parse_non_pre_blocks(text, obj, macros) do |text|
534 text = parse_non_pre_blocks(text, obj, macros) do |text|
535 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
535 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
536 send method_name, text, project, obj, attr, only_path, options
536 send method_name, text, project, obj, attr, only_path, options
537 end
537 end
538 end
538 end
539 parse_headings(text, project, obj, attr, only_path, options)
539 parse_headings(text, project, obj, attr, only_path, options)
540
540
541 if @parsed_headings.any?
541 if @parsed_headings.any?
542 replace_toc(text, @parsed_headings)
542 replace_toc(text, @parsed_headings)
543 end
543 end
544
544
545 text.html_safe
545 text.html_safe
546 end
546 end
547
547
548 def parse_non_pre_blocks(text, obj, macros)
548 def parse_non_pre_blocks(text, obj, macros)
549 s = StringScanner.new(text)
549 s = StringScanner.new(text)
550 tags = []
550 tags = []
551 parsed = ''
551 parsed = ''
552 while !s.eos?
552 while !s.eos?
553 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
553 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
554 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
554 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
555 if tags.empty?
555 if tags.empty?
556 yield text
556 yield text
557 inject_macros(text, obj, macros) if macros.any?
557 inject_macros(text, obj, macros) if macros.any?
558 else
558 else
559 inject_macros(text, obj, macros, false) if macros.any?
559 inject_macros(text, obj, macros, false) if macros.any?
560 end
560 end
561 parsed << text
561 parsed << text
562 if tag
562 if tag
563 if closing
563 if closing
564 if tags.last == tag.downcase
564 if tags.last == tag.downcase
565 tags.pop
565 tags.pop
566 end
566 end
567 else
567 else
568 tags << tag.downcase
568 tags << tag.downcase
569 end
569 end
570 parsed << full_tag
570 parsed << full_tag
571 end
571 end
572 end
572 end
573 # Close any non closing tags
573 # Close any non closing tags
574 while tag = tags.pop
574 while tag = tags.pop
575 parsed << "</#{tag}>"
575 parsed << "</#{tag}>"
576 end
576 end
577 parsed
577 parsed
578 end
578 end
579
579
580 def parse_inline_attachments(text, project, obj, attr, only_path, options)
580 def parse_inline_attachments(text, project, obj, attr, only_path, options)
581 # when using an image link, try to use an attachment, if possible
581 # when using an image link, try to use an attachment, if possible
582 attachments = options[:attachments] || []
582 attachments = options[:attachments] || []
583 attachments += obj.attachments if obj.respond_to?(:attachments)
583 attachments += obj.attachments if obj.respond_to?(:attachments)
584 if attachments.present?
584 if attachments.present?
585 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
585 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
586 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
586 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
587 # search for the picture in attachments
587 # search for the picture in attachments
588 if found = Attachment.latest_attach(attachments, filename)
588 if found = Attachment.latest_attach(attachments, filename)
589 image_url = download_named_attachment_path(found, found.filename, :only_path => only_path)
589 image_url = download_named_attachment_path(found, found.filename, :only_path => only_path)
590 desc = found.description.to_s.gsub('"', '')
590 desc = found.description.to_s.gsub('"', '')
591 if !desc.blank? && alttext.blank?
591 if !desc.blank? && alttext.blank?
592 alt = " title=\"#{desc}\" alt=\"#{desc}\""
592 alt = " title=\"#{desc}\" alt=\"#{desc}\""
593 end
593 end
594 "src=\"#{image_url}\"#{alt}"
594 "src=\"#{image_url}\"#{alt}"
595 else
595 else
596 m
596 m
597 end
597 end
598 end
598 end
599 end
599 end
600 end
600 end
601
601
602 # Wiki links
602 # Wiki links
603 #
603 #
604 # Examples:
604 # Examples:
605 # [[mypage]]
605 # [[mypage]]
606 # [[mypage|mytext]]
606 # [[mypage|mytext]]
607 # wiki links can refer other project wikis, using project name or identifier:
607 # wiki links can refer other project wikis, using project name or identifier:
608 # [[project:]] -> wiki starting page
608 # [[project:]] -> wiki starting page
609 # [[project:|mytext]]
609 # [[project:|mytext]]
610 # [[project:mypage]]
610 # [[project:mypage]]
611 # [[project:mypage|mytext]]
611 # [[project:mypage|mytext]]
612 def parse_wiki_links(text, project, obj, attr, only_path, options)
612 def parse_wiki_links(text, project, obj, attr, only_path, options)
613 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
613 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
614 link_project = project
614 link_project = project
615 esc, all, page, title = $1, $2, $3, $5
615 esc, all, page, title = $1, $2, $3, $5
616 if esc.nil?
616 if esc.nil?
617 if page =~ /^([^\:]+)\:(.*)$/
617 if page =~ /^([^\:]+)\:(.*)$/
618 identifier, page = $1, $2
618 identifier, page = $1, $2
619 link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
619 link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
620 title ||= identifier if page.blank?
620 title ||= identifier if page.blank?
621 end
621 end
622
622
623 if link_project && link_project.wiki
623 if link_project && link_project.wiki
624 # extract anchor
624 # extract anchor
625 anchor = nil
625 anchor = nil
626 if page =~ /^(.+?)\#(.+)$/
626 if page =~ /^(.+?)\#(.+)$/
627 page, anchor = $1, $2
627 page, anchor = $1, $2
628 end
628 end
629 anchor = sanitize_anchor_name(anchor) if anchor.present?
629 anchor = sanitize_anchor_name(anchor) if anchor.present?
630 # check if page exists
630 # check if page exists
631 wiki_page = link_project.wiki.find_page(page)
631 wiki_page = link_project.wiki.find_page(page)
632 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
632 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
633 "##{anchor}"
633 "##{anchor}"
634 else
634 else
635 case options[:wiki_links]
635 case options[:wiki_links]
636 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
636 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
637 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
637 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
638 else
638 else
639 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
639 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
640 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
640 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
641 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
641 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
642 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
642 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
643 end
643 end
644 end
644 end
645 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
645 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
646 else
646 else
647 # project or wiki doesn't exist
647 # project or wiki doesn't exist
648 all
648 all
649 end
649 end
650 else
650 else
651 all
651 all
652 end
652 end
653 end
653 end
654 end
654 end
655
655
656 # Redmine links
656 # Redmine links
657 #
657 #
658 # Examples:
658 # Examples:
659 # Issues:
659 # Issues:
660 # #52 -> Link to issue #52
660 # #52 -> Link to issue #52
661 # Changesets:
661 # Changesets:
662 # r52 -> Link to revision 52
662 # r52 -> Link to revision 52
663 # commit:a85130f -> Link to scmid starting with a85130f
663 # commit:a85130f -> Link to scmid starting with a85130f
664 # Documents:
664 # Documents:
665 # document#17 -> Link to document with id 17
665 # document#17 -> Link to document with id 17
666 # document:Greetings -> Link to the document with title "Greetings"
666 # document:Greetings -> Link to the document with title "Greetings"
667 # document:"Some document" -> Link to the document with title "Some document"
667 # document:"Some document" -> Link to the document with title "Some document"
668 # Versions:
668 # Versions:
669 # version#3 -> Link to version with id 3
669 # version#3 -> Link to version with id 3
670 # version:1.0.0 -> Link to version named "1.0.0"
670 # version:1.0.0 -> Link to version named "1.0.0"
671 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
671 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
672 # Attachments:
672 # Attachments:
673 # attachment:file.zip -> Link to the attachment of the current object named file.zip
673 # attachment:file.zip -> Link to the attachment of the current object named file.zip
674 # Source files:
674 # Source files:
675 # source:some/file -> Link to the file located at /some/file in the project's repository
675 # source:some/file -> Link to the file located at /some/file in the project's repository
676 # source:some/file@52 -> Link to the file's revision 52
676 # source:some/file@52 -> Link to the file's revision 52
677 # source:some/file#L120 -> Link to line 120 of the file
677 # source:some/file#L120 -> Link to line 120 of the file
678 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
678 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
679 # export:some/file -> Force the download of the file
679 # export:some/file -> Force the download of the file
680 # Forum messages:
680 # Forum messages:
681 # message#1218 -> Link to message with id 1218
681 # message#1218 -> Link to message with id 1218
682 # Projects:
682 # Projects:
683 # project:someproject -> Link to project named "someproject"
683 # project:someproject -> Link to project named "someproject"
684 # project#3 -> Link to project with id 3
684 # project#3 -> Link to project with id 3
685 #
685 #
686 # Links can refer other objects from other projects, using project identifier:
686 # Links can refer other objects from other projects, using project identifier:
687 # identifier:r52
687 # identifier:r52
688 # identifier:document:"Some document"
688 # identifier:document:"Some document"
689 # identifier:version:1.0.0
689 # identifier:version:1.0.0
690 # identifier:source:some/file
690 # identifier:source:some/file
691 def parse_redmine_links(text, default_project, obj, attr, only_path, options)
691 def parse_redmine_links(text, default_project, obj, attr, only_path, options)
692 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
692 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
693 leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
693 leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
694 link = nil
694 link = nil
695 project = default_project
695 project = default_project
696 if project_identifier
696 if project_identifier
697 project = Project.visible.find_by_identifier(project_identifier)
697 project = Project.visible.find_by_identifier(project_identifier)
698 end
698 end
699 if esc.nil?
699 if esc.nil?
700 if prefix.nil? && sep == 'r'
700 if prefix.nil? && sep == 'r'
701 if project
701 if project
702 repository = nil
702 repository = nil
703 if repo_identifier
703 if repo_identifier
704 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
704 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
705 else
705 else
706 repository = project.repository
706 repository = project.repository
707 end
707 end
708 # project.changesets.visible raises an SQL error because of a double join on repositories
708 # project.changesets.visible raises an SQL error because of a double join on repositories
709 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
709 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
710 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision},
710 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision},
711 :class => 'changeset',
711 :class => 'changeset',
712 :title => truncate_single_line(changeset.comments, :length => 100))
712 :title => truncate_single_line(changeset.comments, :length => 100))
713 end
713 end
714 end
714 end
715 elsif sep == '#'
715 elsif sep == '#'
716 oid = identifier.to_i
716 oid = identifier.to_i
717 case prefix
717 case prefix
718 when nil
718 when nil
719 if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status)
719 if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status)
720 anchor = comment_id ? "note-#{comment_id}" : nil
720 anchor = comment_id ? "note-#{comment_id}" : nil
721 link = link_to(h("##{oid}#{comment_suffix}"), {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
721 link = link_to(h("##{oid}#{comment_suffix}"), {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
722 :class => issue.css_classes,
722 :class => issue.css_classes,
723 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
723 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
724 end
724 end
725 when 'document'
725 when 'document'
726 if document = Document.visible.find_by_id(oid)
726 if document = Document.visible.find_by_id(oid)
727 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
727 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
728 :class => 'document'
728 :class => 'document'
729 end
729 end
730 when 'version'
730 when 'version'
731 if version = Version.visible.find_by_id(oid)
731 if version = Version.visible.find_by_id(oid)
732 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
732 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
733 :class => 'version'
733 :class => 'version'
734 end
734 end
735 when 'message'
735 when 'message'
736 if message = Message.visible.find_by_id(oid, :include => :parent)
736 if message = Message.visible.find_by_id(oid, :include => :parent)
737 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
737 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
738 end
738 end
739 when 'forum'
739 when 'forum'
740 if board = Board.visible.find_by_id(oid)
740 if board = Board.visible.find_by_id(oid)
741 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
741 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
742 :class => 'board'
742 :class => 'board'
743 end
743 end
744 when 'news'
744 when 'news'
745 if news = News.visible.find_by_id(oid)
745 if news = News.visible.find_by_id(oid)
746 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
746 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
747 :class => 'news'
747 :class => 'news'
748 end
748 end
749 when 'project'
749 when 'project'
750 if p = Project.visible.find_by_id(oid)
750 if p = Project.visible.find_by_id(oid)
751 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
751 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
752 end
752 end
753 end
753 end
754 elsif sep == ':'
754 elsif sep == ':'
755 # removes the double quotes if any
755 # removes the double quotes if any
756 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
756 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
757 case prefix
757 case prefix
758 when 'document'
758 when 'document'
759 if project && document = project.documents.visible.find_by_title(name)
759 if project && document = project.documents.visible.find_by_title(name)
760 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
760 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
761 :class => 'document'
761 :class => 'document'
762 end
762 end
763 when 'version'
763 when 'version'
764 if project && version = project.versions.visible.find_by_name(name)
764 if project && version = project.versions.visible.find_by_name(name)
765 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
765 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
766 :class => 'version'
766 :class => 'version'
767 end
767 end
768 when 'forum'
768 when 'forum'
769 if project && board = project.boards.visible.find_by_name(name)
769 if project && board = project.boards.visible.find_by_name(name)
770 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
770 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
771 :class => 'board'
771 :class => 'board'
772 end
772 end
773 when 'news'
773 when 'news'
774 if project && news = project.news.visible.find_by_title(name)
774 if project && news = project.news.visible.find_by_title(name)
775 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
775 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
776 :class => 'news'
776 :class => 'news'
777 end
777 end
778 when 'commit', 'source', 'export'
778 when 'commit', 'source', 'export'
779 if project
779 if project
780 repository = nil
780 repository = nil
781 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
781 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
782 repo_prefix, repo_identifier, name = $1, $2, $3
782 repo_prefix, repo_identifier, name = $1, $2, $3
783 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
783 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
784 else
784 else
785 repository = project.repository
785 repository = project.repository
786 end
786 end
787 if prefix == 'commit'
787 if prefix == 'commit'
788 if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
788 if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
789 link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
789 link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
790 :class => 'changeset',
790 :class => 'changeset',
791 :title => truncate_single_line(changeset.comments, :length => 100)
791 :title => truncate_single_line(changeset.comments, :length => 100)
792 end
792 end
793 else
793 else
794 if repository && User.current.allowed_to?(:browse_repository, project)
794 if repository && User.current.allowed_to?(:browse_repository, project)
795 name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
795 name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
796 path, rev, anchor = $1, $3, $5
796 path, rev, anchor = $1, $3, $5
797 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param,
797 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param,
798 :path => to_path_param(path),
798 :path => to_path_param(path),
799 :rev => rev,
799 :rev => rev,
800 :anchor => anchor},
800 :anchor => anchor},
801 :class => (prefix == 'export' ? 'source download' : 'source')
801 :class => (prefix == 'export' ? 'source download' : 'source')
802 end
802 end
803 end
803 end
804 repo_prefix = nil
804 repo_prefix = nil
805 end
805 end
806 when 'attachment'
806 when 'attachment'
807 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
807 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
808 if attachments && attachment = Attachment.latest_attach(attachments, name)
808 if attachments && attachment = Attachment.latest_attach(attachments, name)
809 link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
809 link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
810 end
810 end
811 when 'project'
811 when 'project'
812 if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
812 if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
813 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
813 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
814 end
814 end
815 end
815 end
816 end
816 end
817 end
817 end
818 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
818 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
819 end
819 end
820 end
820 end
821
821
822 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
822 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
823
823
824 def parse_sections(text, project, obj, attr, only_path, options)
824 def parse_sections(text, project, obj, attr, only_path, options)
825 return unless options[:edit_section_links]
825 return unless options[:edit_section_links]
826 text.gsub!(HEADING_RE) do
826 text.gsub!(HEADING_RE) do
827 heading = $1
827 heading = $1
828 @current_section += 1
828 @current_section += 1
829 if @current_section > 1
829 if @current_section > 1
830 content_tag('div',
830 content_tag('div',
831 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
831 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
832 :class => 'contextual',
832 :class => 'contextual',
833 :title => l(:button_edit_section)) + heading.html_safe
833 :title => l(:button_edit_section)) + heading.html_safe
834 else
834 else
835 heading
835 heading
836 end
836 end
837 end
837 end
838 end
838 end
839
839
840 # Headings and TOC
840 # Headings and TOC
841 # Adds ids and links to headings unless options[:headings] is set to false
841 # Adds ids and links to headings unless options[:headings] is set to false
842 def parse_headings(text, project, obj, attr, only_path, options)
842 def parse_headings(text, project, obj, attr, only_path, options)
843 return if options[:headings] == false
843 return if options[:headings] == false
844
844
845 text.gsub!(HEADING_RE) do
845 text.gsub!(HEADING_RE) do
846 level, attrs, content = $2.to_i, $3, $4
846 level, attrs, content = $2.to_i, $3, $4
847 item = strip_tags(content).strip
847 item = strip_tags(content).strip
848 anchor = sanitize_anchor_name(item)
848 anchor = sanitize_anchor_name(item)
849 # used for single-file wiki export
849 # used for single-file wiki export
850 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
850 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
851 @heading_anchors[anchor] ||= 0
851 @heading_anchors[anchor] ||= 0
852 idx = (@heading_anchors[anchor] += 1)
852 idx = (@heading_anchors[anchor] += 1)
853 if idx > 1
853 if idx > 1
854 anchor = "#{anchor}-#{idx}"
854 anchor = "#{anchor}-#{idx}"
855 end
855 end
856 @parsed_headings << [level, anchor, item]
856 @parsed_headings << [level, anchor, item]
857 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
857 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
858 end
858 end
859 end
859 end
860
860
861 MACROS_RE = /(
861 MACROS_RE = /(
862 (!)? # escaping
862 (!)? # escaping
863 (
863 (
864 \{\{ # opening tag
864 \{\{ # opening tag
865 ([\w]+) # macro name
865 ([\w]+) # macro name
866 (\(([^\n\r]*?)\))? # optional arguments
866 (\(([^\n\r]*?)\))? # optional arguments
867 ([\n\r].*?[\n\r])? # optional block of text
867 ([\n\r].*?[\n\r])? # optional block of text
868 \}\} # closing tag
868 \}\} # closing tag
869 )
869 )
870 )/mx unless const_defined?(:MACROS_RE)
870 )/mx unless const_defined?(:MACROS_RE)
871
871
872 MACRO_SUB_RE = /(
872 MACRO_SUB_RE = /(
873 \{\{
873 \{\{
874 macro\((\d+)\)
874 macro\((\d+)\)
875 \}\}
875 \}\}
876 )/x unless const_defined?(:MACRO_SUB_RE)
876 )/x unless const_defined?(:MACRO_SUB_RE)
877
877
878 # Extracts macros from text
878 # Extracts macros from text
879 def catch_macros(text)
879 def catch_macros(text)
880 macros = {}
880 macros = {}
881 text.gsub!(MACROS_RE) do
881 text.gsub!(MACROS_RE) do
882 all, macro = $1, $4.downcase
882 all, macro = $1, $4.downcase
883 if macro_exists?(macro) || all =~ MACRO_SUB_RE
883 if macro_exists?(macro) || all =~ MACRO_SUB_RE
884 index = macros.size
884 index = macros.size
885 macros[index] = all
885 macros[index] = all
886 "{{macro(#{index})}}"
886 "{{macro(#{index})}}"
887 else
887 else
888 all
888 all
889 end
889 end
890 end
890 end
891 macros
891 macros
892 end
892 end
893
893
894 # Executes and replaces macros in text
894 # Executes and replaces macros in text
895 def inject_macros(text, obj, macros, execute=true)
895 def inject_macros(text, obj, macros, execute=true)
896 text.gsub!(MACRO_SUB_RE) do
896 text.gsub!(MACRO_SUB_RE) do
897 all, index = $1, $2.to_i
897 all, index = $1, $2.to_i
898 orig = macros.delete(index)
898 orig = macros.delete(index)
899 if execute && orig && orig =~ MACROS_RE
899 if execute && orig && orig =~ MACROS_RE
900 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
900 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
901 if esc.nil?
901 if esc.nil?
902 h(exec_macro(macro, obj, args, block) || all)
902 h(exec_macro(macro, obj, args, block) || all)
903 else
903 else
904 h(all)
904 h(all)
905 end
905 end
906 elsif orig
906 elsif orig
907 h(orig)
907 h(orig)
908 else
908 else
909 h(all)
909 h(all)
910 end
910 end
911 end
911 end
912 end
912 end
913
913
914 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
914 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
915
915
916 # Renders the TOC with given headings
916 # Renders the TOC with given headings
917 def replace_toc(text, headings)
917 def replace_toc(text, headings)
918 text.gsub!(TOC_RE) do
918 text.gsub!(TOC_RE) do
919 # Keep only the 4 first levels
919 # Keep only the 4 first levels
920 headings = headings.select{|level, anchor, item| level <= 4}
920 headings = headings.select{|level, anchor, item| level <= 4}
921 if headings.empty?
921 if headings.empty?
922 ''
922 ''
923 else
923 else
924 div_class = 'toc'
924 div_class = 'toc'
925 div_class << ' right' if $1 == '>'
925 div_class << ' right' if $1 == '>'
926 div_class << ' left' if $1 == '<'
926 div_class << ' left' if $1 == '<'
927 out = "<ul class=\"#{div_class}\"><li>"
927 out = "<ul class=\"#{div_class}\"><li>"
928 root = headings.map(&:first).min
928 root = headings.map(&:first).min
929 current = root
929 current = root
930 started = false
930 started = false
931 headings.each do |level, anchor, item|
931 headings.each do |level, anchor, item|
932 if level > current
932 if level > current
933 out << '<ul><li>' * (level - current)
933 out << '<ul><li>' * (level - current)
934 elsif level < current
934 elsif level < current
935 out << "</li></ul>\n" * (current - level) + "</li><li>"
935 out << "</li></ul>\n" * (current - level) + "</li><li>"
936 elsif started
936 elsif started
937 out << '</li><li>'
937 out << '</li><li>'
938 end
938 end
939 out << "<a href=\"##{anchor}\">#{item}</a>"
939 out << "<a href=\"##{anchor}\">#{item}</a>"
940 current = level
940 current = level
941 started = true
941 started = true
942 end
942 end
943 out << '</li></ul>' * (current - root)
943 out << '</li></ul>' * (current - root)
944 out << '</li></ul>'
944 out << '</li></ul>'
945 end
945 end
946 end
946 end
947 end
947 end
948
948
949 # Same as Rails' simple_format helper without using paragraphs
949 # Same as Rails' simple_format helper without using paragraphs
950 def simple_format_without_paragraph(text)
950 def simple_format_without_paragraph(text)
951 text.to_s.
951 text.to_s.
952 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
952 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
953 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
953 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
954 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
954 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
955 html_safe
955 html_safe
956 end
956 end
957
957
958 def lang_options_for_select(blank=true)
958 def lang_options_for_select(blank=true)
959 (blank ? [["(auto)", ""]] : []) + languages_options
959 (blank ? [["(auto)", ""]] : []) + languages_options
960 end
960 end
961
961
962 def label_tag_for(name, option_tags = nil, options = {})
962 def label_tag_for(name, option_tags = nil, options = {})
963 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
963 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
964 content_tag("label", label_text)
964 content_tag("label", label_text)
965 end
965 end
966
966
967 def labelled_form_for(*args, &proc)
967 def labelled_form_for(*args, &proc)
968 args << {} unless args.last.is_a?(Hash)
968 args << {} unless args.last.is_a?(Hash)
969 options = args.last
969 options = args.last
970 if args.first.is_a?(Symbol)
970 if args.first.is_a?(Symbol)
971 options.merge!(:as => args.shift)
971 options.merge!(:as => args.shift)
972 end
972 end
973 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
973 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
974 form_for(*args, &proc)
974 form_for(*args, &proc)
975 end
975 end
976
976
977 def labelled_fields_for(*args, &proc)
977 def labelled_fields_for(*args, &proc)
978 args << {} unless args.last.is_a?(Hash)
978 args << {} unless args.last.is_a?(Hash)
979 options = args.last
979 options = args.last
980 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
980 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
981 fields_for(*args, &proc)
981 fields_for(*args, &proc)
982 end
982 end
983
983
984 def labelled_remote_form_for(*args, &proc)
984 def labelled_remote_form_for(*args, &proc)
985 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
985 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
986 args << {} unless args.last.is_a?(Hash)
986 args << {} unless args.last.is_a?(Hash)
987 options = args.last
987 options = args.last
988 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
988 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
989 form_for(*args, &proc)
989 form_for(*args, &proc)
990 end
990 end
991
991
992 def error_messages_for(*objects)
992 def error_messages_for(*objects)
993 html = ""
993 html = ""
994 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
994 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
995 errors = objects.map {|o| o.errors.full_messages}.flatten
995 errors = objects.map {|o| o.errors.full_messages}.flatten
996 if errors.any?
996 if errors.any?
997 html << "<div id='errorExplanation'><ul>\n"
997 html << "<div id='errorExplanation'><ul>\n"
998 errors.each do |error|
998 errors.each do |error|
999 html << "<li>#{h error}</li>\n"
999 html << "<li>#{h error}</li>\n"
1000 end
1000 end
1001 html << "</ul></div>\n"
1001 html << "</ul></div>\n"
1002 end
1002 end
1003 html.html_safe
1003 html.html_safe
1004 end
1004 end
1005
1005
1006 def delete_link(url, options={})
1006 def delete_link(url, options={})
1007 options = {
1007 options = {
1008 :method => :delete,
1008 :method => :delete,
1009 :data => {:confirm => l(:text_are_you_sure)},
1009 :data => {:confirm => l(:text_are_you_sure)},
1010 :class => 'icon icon-del'
1010 :class => 'icon icon-del'
1011 }.merge(options)
1011 }.merge(options)
1012
1012
1013 link_to l(:button_delete), url, options
1013 link_to l(:button_delete), url, options
1014 end
1014 end
1015
1015
1016 def preview_link(url, form, target='preview', options={})
1016 def preview_link(url, form, target='preview', options={})
1017 content_tag 'a', l(:label_preview), {
1017 content_tag 'a', l(:label_preview), {
1018 :href => "#",
1018 :href => "#",
1019 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1019 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1020 :accesskey => accesskey(:preview)
1020 :accesskey => accesskey(:preview)
1021 }.merge(options)
1021 }.merge(options)
1022 end
1022 end
1023
1023
1024 def link_to_function(name, function, html_options={})
1024 def link_to_function(name, function, html_options={})
1025 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1025 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1026 end
1026 end
1027
1027
1028 # Helper to render JSON in views
1028 # Helper to render JSON in views
1029 def raw_json(arg)
1029 def raw_json(arg)
1030 arg.to_json.to_s.gsub('/', '\/').html_safe
1030 arg.to_json.to_s.gsub('/', '\/').html_safe
1031 end
1031 end
1032
1032
1033 def back_url
1033 def back_url
1034 url = params[:back_url]
1034 url = params[:back_url]
1035 if url.nil? && referer = request.env['HTTP_REFERER']
1035 if url.nil? && referer = request.env['HTTP_REFERER']
1036 url = CGI.unescape(referer.to_s)
1036 url = CGI.unescape(referer.to_s)
1037 end
1037 end
1038 url
1038 url
1039 end
1039 end
1040
1040
1041 def back_url_hidden_field_tag
1041 def back_url_hidden_field_tag
1042 url = back_url
1042 url = back_url
1043 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1043 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1044 end
1044 end
1045
1045
1046 def check_all_links(form_name)
1046 def check_all_links(form_name)
1047 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1047 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1048 " | ".html_safe +
1048 " | ".html_safe +
1049 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1049 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1050 end
1050 end
1051
1051
1052 def progress_bar(pcts, options={})
1052 def progress_bar(pcts, options={})
1053 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1053 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1054 pcts = pcts.collect(&:round)
1054 pcts = pcts.collect(&:round)
1055 pcts[1] = pcts[1] - pcts[0]
1055 pcts[1] = pcts[1] - pcts[0]
1056 pcts << (100 - pcts[1] - pcts[0])
1056 pcts << (100 - pcts[1] - pcts[0])
1057 width = options[:width] || '100px;'
1057 width = options[:width] || '100px;'
1058 legend = options[:legend] || ''
1058 legend = options[:legend] || ''
1059 content_tag('table',
1059 content_tag('table',
1060 content_tag('tr',
1060 content_tag('tr',
1061 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1061 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1062 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1062 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1063 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1063 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1064 ), :class => 'progress', :style => "width: #{width};").html_safe +
1064 ), :class => 'progress', :style => "width: #{width};").html_safe +
1065 content_tag('p', legend, :class => 'percent').html_safe
1065 content_tag('p', legend, :class => 'percent').html_safe
1066 end
1066 end
1067
1067
1068 def checked_image(checked=true)
1068 def checked_image(checked=true)
1069 if checked
1069 if checked
1070 image_tag 'toggle_check.png'
1070 image_tag 'toggle_check.png'
1071 end
1071 end
1072 end
1072 end
1073
1073
1074 def context_menu(url)
1074 def context_menu(url)
1075 unless @context_menu_included
1075 unless @context_menu_included
1076 content_for :header_tags do
1076 content_for :header_tags do
1077 javascript_include_tag('context_menu') +
1077 javascript_include_tag('context_menu') +
1078 stylesheet_link_tag('context_menu')
1078 stylesheet_link_tag('context_menu')
1079 end
1079 end
1080 if l(:direction) == 'rtl'
1080 if l(:direction) == 'rtl'
1081 content_for :header_tags do
1081 content_for :header_tags do
1082 stylesheet_link_tag('context_menu_rtl')
1082 stylesheet_link_tag('context_menu_rtl')
1083 end
1083 end
1084 end
1084 end
1085 @context_menu_included = true
1085 @context_menu_included = true
1086 end
1086 end
1087 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1087 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1088 end
1088 end
1089
1089
1090 def calendar_for(field_id)
1090 def calendar_for(field_id)
1091 include_calendar_headers_tags
1091 include_calendar_headers_tags
1092 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
1092 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
1093 end
1093 end
1094
1094
1095 def include_calendar_headers_tags
1095 def include_calendar_headers_tags
1096 unless @calendar_headers_tags_included
1096 unless @calendar_headers_tags_included
1097 tags = javascript_include_tag("datepicker")
1097 tags = javascript_include_tag("datepicker")
1098 @calendar_headers_tags_included = true
1098 @calendar_headers_tags_included = true
1099 content_for :header_tags do
1099 content_for :header_tags do
1100 start_of_week = Setting.start_of_week
1100 start_of_week = Setting.start_of_week
1101 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1101 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1102 # Redmine uses 1..7 (monday..sunday) in settings and locales
1102 # Redmine uses 1..7 (monday..sunday) in settings and locales
1103 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1103 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1104 start_of_week = start_of_week.to_i % 7
1104 start_of_week = start_of_week.to_i % 7
1105 tags << javascript_tag(
1105 tags << javascript_tag(
1106 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1106 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1107 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1107 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1108 path_to_image('/images/calendar.png') +
1108 path_to_image('/images/calendar.png') +
1109 "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " +
1109 "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " +
1110 "selectOtherMonths: true, changeMonth: true, changeYear: true, " +
1110 "selectOtherMonths: true, changeMonth: true, changeYear: true, " +
1111 "beforeShow: beforeShowDatePicker};")
1111 "beforeShow: beforeShowDatePicker};")
1112 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1112 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1113 unless jquery_locale == 'en'
1113 unless jquery_locale == 'en'
1114 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
1114 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
1115 end
1115 end
1116 tags
1116 tags
1117 end
1117 end
1118 end
1118 end
1119 end
1119 end
1120
1120
1121 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1121 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1122 # Examples:
1122 # Examples:
1123 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1123 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1124 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1124 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1125 #
1125 #
1126 def stylesheet_link_tag(*sources)
1126 def stylesheet_link_tag(*sources)
1127 options = sources.last.is_a?(Hash) ? sources.pop : {}
1127 options = sources.last.is_a?(Hash) ? sources.pop : {}
1128 plugin = options.delete(:plugin)
1128 plugin = options.delete(:plugin)
1129 sources = sources.map do |source|
1129 sources = sources.map do |source|
1130 if plugin
1130 if plugin
1131 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1131 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1132 elsif current_theme && current_theme.stylesheets.include?(source)
1132 elsif current_theme && current_theme.stylesheets.include?(source)
1133 current_theme.stylesheet_path(source)
1133 current_theme.stylesheet_path(source)
1134 else
1134 else
1135 source
1135 source
1136 end
1136 end
1137 end
1137 end
1138 super sources, options
1138 super sources, options
1139 end
1139 end
1140
1140
1141 # Overrides Rails' image_tag with themes and plugins support.
1141 # Overrides Rails' image_tag with themes and plugins support.
1142 # Examples:
1142 # Examples:
1143 # image_tag('image.png') # => picks image.png from the current theme or defaults
1143 # image_tag('image.png') # => picks image.png from the current theme or defaults
1144 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1144 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1145 #
1145 #
1146 def image_tag(source, options={})
1146 def image_tag(source, options={})
1147 if plugin = options.delete(:plugin)
1147 if plugin = options.delete(:plugin)
1148 source = "/plugin_assets/#{plugin}/images/#{source}"
1148 source = "/plugin_assets/#{plugin}/images/#{source}"
1149 elsif current_theme && current_theme.images.include?(source)
1149 elsif current_theme && current_theme.images.include?(source)
1150 source = current_theme.image_path(source)
1150 source = current_theme.image_path(source)
1151 end
1151 end
1152 super source, options
1152 super source, options
1153 end
1153 end
1154
1154
1155 # Overrides Rails' javascript_include_tag with plugins support
1155 # Overrides Rails' javascript_include_tag with plugins support
1156 # Examples:
1156 # Examples:
1157 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1157 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1158 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1158 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1159 #
1159 #
1160 def javascript_include_tag(*sources)
1160 def javascript_include_tag(*sources)
1161 options = sources.last.is_a?(Hash) ? sources.pop : {}
1161 options = sources.last.is_a?(Hash) ? sources.pop : {}
1162 if plugin = options.delete(:plugin)
1162 if plugin = options.delete(:plugin)
1163 sources = sources.map do |source|
1163 sources = sources.map do |source|
1164 if plugin
1164 if plugin
1165 "/plugin_assets/#{plugin}/javascripts/#{source}"
1165 "/plugin_assets/#{plugin}/javascripts/#{source}"
1166 else
1166 else
1167 source
1167 source
1168 end
1168 end
1169 end
1169 end
1170 end
1170 end
1171 super sources, options
1171 super sources, options
1172 end
1172 end
1173
1173
1174 def content_for(name, content = nil, &block)
1174 def content_for(name, content = nil, &block)
1175 @has_content ||= {}
1175 @has_content ||= {}
1176 @has_content[name] = true
1176 @has_content[name] = true
1177 super(name, content, &block)
1177 super(name, content, &block)
1178 end
1178 end
1179
1179
1180 def has_content?(name)
1180 def has_content?(name)
1181 (@has_content && @has_content[name]) || false
1181 (@has_content && @has_content[name]) || false
1182 end
1182 end
1183
1183
1184 def sidebar_content?
1184 def sidebar_content?
1185 has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1185 has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1186 end
1186 end
1187
1187
1188 def view_layouts_base_sidebar_hook_response
1188 def view_layouts_base_sidebar_hook_response
1189 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1189 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1190 end
1190 end
1191
1191
1192 def email_delivery_enabled?
1192 def email_delivery_enabled?
1193 !!ActionMailer::Base.perform_deliveries
1193 !!ActionMailer::Base.perform_deliveries
1194 end
1194 end
1195
1195
1196 # Returns the avatar image tag for the given +user+ if avatars are enabled
1196 # Returns the avatar image tag for the given +user+ if avatars are enabled
1197 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1197 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1198 def avatar(user, options = { })
1198 def avatar(user, options = { })
1199 if Setting.gravatar_enabled?
1199 if Setting.gravatar_enabled?
1200 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1200 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1201 email = nil
1201 email = nil
1202 if user.respond_to?(:mail)
1202 if user.respond_to?(:mail)
1203 email = user.mail
1203 email = user.mail
1204 elsif user.to_s =~ %r{<(.+?)>}
1204 elsif user.to_s =~ %r{<(.+?)>}
1205 email = $1
1205 email = $1
1206 end
1206 end
1207 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1207 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1208 else
1208 else
1209 ''
1209 ''
1210 end
1210 end
1211 end
1211 end
1212
1212
1213 def sanitize_anchor_name(anchor)
1213 def sanitize_anchor_name(anchor)
1214 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
1214 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
1215 anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1215 anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1216 else
1216 else
1217 # TODO: remove when ruby1.8 is no longer supported
1217 # TODO: remove when ruby1.8 is no longer supported
1218 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1218 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1219 end
1219 end
1220 end
1220 end
1221
1221
1222 # Returns the javascript tags that are included in the html layout head
1222 # Returns the javascript tags that are included in the html layout head
1223 def javascript_heads
1223 def javascript_heads
1224 tags = javascript_include_tag('jquery-1.8.3-ui-1.9.2-ujs-2.0.3', 'application')
1224 tags = javascript_include_tag('jquery-1.8.3-ui-1.9.2-ujs-2.0.3', 'application')
1225 unless User.current.pref.warn_on_leaving_unsaved == '0'
1225 unless User.current.pref.warn_on_leaving_unsaved == '0'
1226 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1226 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1227 end
1227 end
1228 tags
1228 tags
1229 end
1229 end
1230
1230
1231 def favicon
1231 def favicon
1232 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1232 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1233 end
1233 end
1234
1234
1235 def robot_exclusion_tag
1235 def robot_exclusion_tag
1236 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1236 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1237 end
1237 end
1238
1238
1239 # Returns true if arg is expected in the API response
1239 # Returns true if arg is expected in the API response
1240 def include_in_api_response?(arg)
1240 def include_in_api_response?(arg)
1241 unless @included_in_api_response
1241 unless @included_in_api_response
1242 param = params[:include]
1242 param = params[:include]
1243 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1243 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1244 @included_in_api_response.collect!(&:strip)
1244 @included_in_api_response.collect!(&:strip)
1245 end
1245 end
1246 @included_in_api_response.include?(arg.to_s)
1246 @included_in_api_response.include?(arg.to_s)
1247 end
1247 end
1248
1248
1249 # Returns options or nil if nometa param or X-Redmine-Nometa header
1249 # Returns options or nil if nometa param or X-Redmine-Nometa header
1250 # was set in the request
1250 # was set in the request
1251 def api_meta(options)
1251 def api_meta(options)
1252 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1252 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1253 # compatibility mode for activeresource clients that raise
1253 # compatibility mode for activeresource clients that raise
1254 # an error when unserializing an array with attributes
1254 # an error when unserializing an array with attributes
1255 nil
1255 nil
1256 else
1256 else
1257 options
1257 options
1258 end
1258 end
1259 end
1259 end
1260
1260
1261 private
1261 private
1262
1262
1263 def wiki_helper
1263 def wiki_helper
1264 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1264 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1265 extend helper
1265 extend helper
1266 return self
1266 return self
1267 end
1267 end
1268
1268
1269 def link_to_content_update(text, url_params = {}, html_options = {})
1269 def link_to_content_update(text, url_params = {}, html_options = {})
1270 link_to(text, url_params, html_options)
1270 link_to(text, url_params, html_options)
1271 end
1271 end
1272 end
1272 end
General Comments 0
You need to be logged in to leave comments. Login now