##// END OF EJS Templates
Fixed escaping issues in #textilizable with Rails 3.1....
Jean-Philippe Lang -
r8865:30282f20daec
parent child
Show More
@@ -1,1106 +1,1107
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
4 # Copyright (C) 2006-2011 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
27
28 extend Forwardable
28 extend Forwardable
29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
30
30
31 # Return true if user is authorized for controller/action, otherwise false
31 # Return true if user is authorized for controller/action, otherwise false
32 def authorize_for(controller, action)
32 def authorize_for(controller, action)
33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
34 end
34 end
35
35
36 # Display a link if user is authorized
36 # Display a link if user is authorized
37 #
37 #
38 # @param [String] name Anchor text (passed to link_to)
38 # @param [String] name Anchor text (passed to link_to)
39 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
39 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
40 # @param [optional, Hash] html_options Options passed to link_to
40 # @param [optional, Hash] html_options Options passed to link_to
41 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
41 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
42 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
42 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
43 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
43 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
44 end
44 end
45
45
46 # Display a link to remote if user is authorized
46 # Display a link to remote if user is authorized
47 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
47 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
48 url = options[:url] || {}
48 url = options[:url] || {}
49 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
49 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
50 end
50 end
51
51
52 # Displays a link to user's account page if active
52 # Displays a link to user's account page if active
53 def link_to_user(user, options={})
53 def link_to_user(user, options={})
54 if user.is_a?(User)
54 if user.is_a?(User)
55 name = h(user.name(options[:format]))
55 name = h(user.name(options[:format]))
56 if user.active?
56 if user.active?
57 link_to name, :controller => 'users', :action => 'show', :id => user
57 link_to name, :controller => 'users', :action => 'show', :id => user
58 else
58 else
59 name
59 name
60 end
60 end
61 else
61 else
62 h(user.to_s)
62 h(user.to_s)
63 end
63 end
64 end
64 end
65
65
66 # Displays a link to +issue+ with its subject.
66 # Displays a link to +issue+ with its subject.
67 # Examples:
67 # Examples:
68 #
68 #
69 # link_to_issue(issue) # => Defect #6: This is the subject
69 # link_to_issue(issue) # => Defect #6: This is the subject
70 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
70 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
71 # link_to_issue(issue, :subject => false) # => Defect #6
71 # link_to_issue(issue, :subject => false) # => Defect #6
72 # link_to_issue(issue, :project => true) # => Foo - Defect #6
72 # link_to_issue(issue, :project => true) # => Foo - Defect #6
73 #
73 #
74 def link_to_issue(issue, options={})
74 def link_to_issue(issue, options={})
75 title = nil
75 title = nil
76 subject = nil
76 subject = nil
77 if options[:subject] == false
77 if options[:subject] == false
78 title = truncate(issue.subject, :length => 60)
78 title = truncate(issue.subject, :length => 60)
79 else
79 else
80 subject = issue.subject
80 subject = issue.subject
81 if options[:truncate]
81 if options[:truncate]
82 subject = truncate(subject, :length => options[:truncate])
82 subject = truncate(subject, :length => options[:truncate])
83 end
83 end
84 end
84 end
85 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
85 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
86 :class => issue.css_classes,
86 :class => issue.css_classes,
87 :title => title
87 :title => title
88 s << h(": #{subject}") if subject
88 s << h(": #{subject}") if subject
89 s = h("#{issue.project} - ") + s if options[:project]
89 s = h("#{issue.project} - ") + s if options[:project]
90 s
90 s
91 end
91 end
92
92
93 # Generates a link to an attachment.
93 # Generates a link to an attachment.
94 # Options:
94 # Options:
95 # * :text - Link text (default to attachment filename)
95 # * :text - Link text (default to attachment filename)
96 # * :download - Force download (default: false)
96 # * :download - Force download (default: false)
97 def link_to_attachment(attachment, options={})
97 def link_to_attachment(attachment, options={})
98 text = options.delete(:text) || attachment.filename
98 text = options.delete(:text) || attachment.filename
99 action = options.delete(:download) ? 'download' : 'show'
99 action = options.delete(:download) ? 'download' : 'show'
100 link_to(h(text),
100 link_to(h(text),
101 {:controller => 'attachments', :action => action,
101 {:controller => 'attachments', :action => action,
102 :id => attachment, :filename => attachment.filename },
102 :id => attachment, :filename => attachment.filename },
103 options)
103 options)
104 end
104 end
105
105
106 # Generates a link to a SCM revision
106 # Generates a link to a SCM revision
107 # Options:
107 # Options:
108 # * :text - Link text (default to the formatted revision)
108 # * :text - Link text (default to the formatted revision)
109 def link_to_revision(revision, repository, options={})
109 def link_to_revision(revision, repository, options={})
110 if repository.is_a?(Project)
110 if repository.is_a?(Project)
111 repository = repository.repository
111 repository = repository.repository
112 end
112 end
113 text = options.delete(:text) || format_revision(revision)
113 text = options.delete(:text) || format_revision(revision)
114 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
114 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
115 link_to(
115 link_to(
116 h(text),
116 h(text),
117 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
117 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
118 :title => l(:label_revision_id, format_revision(revision))
118 :title => l(:label_revision_id, format_revision(revision))
119 )
119 )
120 end
120 end
121
121
122 # Generates a link to a message
122 # Generates a link to a message
123 def link_to_message(message, options={}, html_options = nil)
123 def link_to_message(message, options={}, html_options = nil)
124 link_to(
124 link_to(
125 h(truncate(message.subject, :length => 60)),
125 h(truncate(message.subject, :length => 60)),
126 { :controller => 'messages', :action => 'show',
126 { :controller => 'messages', :action => 'show',
127 :board_id => message.board_id,
127 :board_id => message.board_id,
128 :id => message.root,
128 :id => message.root,
129 :r => (message.parent_id && message.id),
129 :r => (message.parent_id && message.id),
130 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
130 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
131 }.merge(options),
131 }.merge(options),
132 html_options
132 html_options
133 )
133 )
134 end
134 end
135
135
136 # Generates a link to a project if active
136 # Generates a link to a project if active
137 # Examples:
137 # Examples:
138 #
138 #
139 # link_to_project(project) # => link to the specified project overview
139 # link_to_project(project) # => link to the specified project overview
140 # link_to_project(project, :action=>'settings') # => link to project settings
140 # link_to_project(project, :action=>'settings') # => link to project settings
141 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
141 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
142 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
142 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
143 #
143 #
144 def link_to_project(project, options={}, html_options = nil)
144 def link_to_project(project, options={}, html_options = nil)
145 if project.active?
145 if project.active?
146 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
146 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
147 link_to(h(project), url, html_options)
147 link_to(h(project), url, html_options)
148 else
148 else
149 h(project)
149 h(project)
150 end
150 end
151 end
151 end
152
152
153 def toggle_link(name, id, options={})
153 def toggle_link(name, id, options={})
154 onclick = "Element.toggle('#{id}'); "
154 onclick = "Element.toggle('#{id}'); "
155 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
155 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
156 onclick << "return false;"
156 onclick << "return false;"
157 link_to(name, "#", :onclick => onclick)
157 link_to(name, "#", :onclick => onclick)
158 end
158 end
159
159
160 def image_to_function(name, function, html_options = {})
160 def image_to_function(name, function, html_options = {})
161 html_options.symbolize_keys!
161 html_options.symbolize_keys!
162 tag(:input, html_options.merge({
162 tag(:input, html_options.merge({
163 :type => "image", :src => image_path(name),
163 :type => "image", :src => image_path(name),
164 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
164 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
165 }))
165 }))
166 end
166 end
167
167
168 def prompt_to_remote(name, text, param, url, html_options = {})
168 def prompt_to_remote(name, text, param, url, html_options = {})
169 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
169 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
170 link_to name, {}, html_options
170 link_to name, {}, html_options
171 end
171 end
172
172
173 def format_activity_title(text)
173 def format_activity_title(text)
174 h(truncate_single_line(text, :length => 100))
174 h(truncate_single_line(text, :length => 100))
175 end
175 end
176
176
177 def format_activity_day(date)
177 def format_activity_day(date)
178 date == Date.today ? l(:label_today).titleize : format_date(date)
178 date == Date.today ? l(:label_today).titleize : format_date(date)
179 end
179 end
180
180
181 def format_activity_description(text)
181 def format_activity_description(text)
182 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
182 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
183 ).gsub(/[\r\n]+/, "<br />").html_safe
183 ).gsub(/[\r\n]+/, "<br />").html_safe
184 end
184 end
185
185
186 def format_version_name(version)
186 def format_version_name(version)
187 if version.project == @project
187 if version.project == @project
188 h(version)
188 h(version)
189 else
189 else
190 h("#{version.project} - #{version}")
190 h("#{version.project} - #{version}")
191 end
191 end
192 end
192 end
193
193
194 def due_date_distance_in_words(date)
194 def due_date_distance_in_words(date)
195 if date
195 if date
196 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
196 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
197 end
197 end
198 end
198 end
199
199
200 def render_page_hierarchy(pages, node=nil, options={})
200 def render_page_hierarchy(pages, node=nil, options={})
201 content = ''
201 content = ''
202 if pages[node]
202 if pages[node]
203 content << "<ul class=\"pages-hierarchy\">\n"
203 content << "<ul class=\"pages-hierarchy\">\n"
204 pages[node].each do |page|
204 pages[node].each do |page|
205 content << "<li>"
205 content << "<li>"
206 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
206 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
207 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
207 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
208 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
208 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
209 content << "</li>\n"
209 content << "</li>\n"
210 end
210 end
211 content << "</ul>\n"
211 content << "</ul>\n"
212 end
212 end
213 content.html_safe
213 content.html_safe
214 end
214 end
215
215
216 # Renders flash messages
216 # Renders flash messages
217 def render_flash_messages
217 def render_flash_messages
218 s = ''
218 s = ''
219 flash.each do |k,v|
219 flash.each do |k,v|
220 s << (content_tag('div', v.html_safe, :class => "flash #{k}"))
220 s << (content_tag('div', v.html_safe, :class => "flash #{k}"))
221 end
221 end
222 s.html_safe
222 s.html_safe
223 end
223 end
224
224
225 # Renders tabs and their content
225 # Renders tabs and their content
226 def render_tabs(tabs)
226 def render_tabs(tabs)
227 if tabs.any?
227 if tabs.any?
228 render :partial => 'common/tabs', :locals => {:tabs => tabs}
228 render :partial => 'common/tabs', :locals => {:tabs => tabs}
229 else
229 else
230 content_tag 'p', l(:label_no_data), :class => "nodata"
230 content_tag 'p', l(:label_no_data), :class => "nodata"
231 end
231 end
232 end
232 end
233
233
234 # Renders the project quick-jump box
234 # Renders the project quick-jump box
235 def render_project_jump_box
235 def render_project_jump_box
236 return unless User.current.logged?
236 return unless User.current.logged?
237 projects = User.current.memberships.collect(&:project).compact.uniq
237 projects = User.current.memberships.collect(&:project).compact.uniq
238 if projects.any?
238 if projects.any?
239 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
239 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
240 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
240 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
241 '<option value="" disabled="disabled">---</option>'
241 '<option value="" disabled="disabled">---</option>'
242 s << project_tree_options_for_select(projects, :selected => @project) do |p|
242 s << project_tree_options_for_select(projects, :selected => @project) do |p|
243 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
243 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
244 end
244 end
245 s << '</select>'
245 s << '</select>'
246 s.html_safe
246 s.html_safe
247 end
247 end
248 end
248 end
249
249
250 def project_tree_options_for_select(projects, options = {})
250 def project_tree_options_for_select(projects, options = {})
251 s = ''
251 s = ''
252 project_tree(projects) do |project, level|
252 project_tree(projects) do |project, level|
253 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
253 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
254 tag_options = {:value => project.id}
254 tag_options = {:value => project.id}
255 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
255 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
256 tag_options[:selected] = 'selected'
256 tag_options[:selected] = 'selected'
257 else
257 else
258 tag_options[:selected] = nil
258 tag_options[:selected] = nil
259 end
259 end
260 tag_options.merge!(yield(project)) if block_given?
260 tag_options.merge!(yield(project)) if block_given?
261 s << content_tag('option', name_prefix + h(project), tag_options)
261 s << content_tag('option', name_prefix + h(project), tag_options)
262 end
262 end
263 s.html_safe
263 s.html_safe
264 end
264 end
265
265
266 # Yields the given block for each project with its level in the tree
266 # Yields the given block for each project with its level in the tree
267 #
267 #
268 # Wrapper for Project#project_tree
268 # Wrapper for Project#project_tree
269 def project_tree(projects, &block)
269 def project_tree(projects, &block)
270 Project.project_tree(projects, &block)
270 Project.project_tree(projects, &block)
271 end
271 end
272
272
273 def project_nested_ul(projects, &block)
273 def project_nested_ul(projects, &block)
274 s = ''
274 s = ''
275 if projects.any?
275 if projects.any?
276 ancestors = []
276 ancestors = []
277 projects.sort_by(&:lft).each do |project|
277 projects.sort_by(&:lft).each do |project|
278 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
278 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
279 s << "<ul>\n"
279 s << "<ul>\n"
280 else
280 else
281 ancestors.pop
281 ancestors.pop
282 s << "</li>"
282 s << "</li>"
283 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
283 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
284 ancestors.pop
284 ancestors.pop
285 s << "</ul></li>\n"
285 s << "</ul></li>\n"
286 end
286 end
287 end
287 end
288 s << "<li>"
288 s << "<li>"
289 s << yield(project).to_s
289 s << yield(project).to_s
290 ancestors << project
290 ancestors << project
291 end
291 end
292 s << ("</li></ul>\n" * ancestors.size)
292 s << ("</li></ul>\n" * ancestors.size)
293 end
293 end
294 s.html_safe
294 s.html_safe
295 end
295 end
296
296
297 def principals_check_box_tags(name, principals)
297 def principals_check_box_tags(name, principals)
298 s = ''
298 s = ''
299 principals.sort.each do |principal|
299 principals.sort.each do |principal|
300 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
300 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
301 end
301 end
302 s.html_safe
302 s.html_safe
303 end
303 end
304
304
305 # Returns a string for users/groups option tags
305 # Returns a string for users/groups option tags
306 def principals_options_for_select(collection, selected=nil)
306 def principals_options_for_select(collection, selected=nil)
307 s = ''
307 s = ''
308 if collection.include?(User.current)
308 if collection.include?(User.current)
309 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
309 s << content_tag('option', "<< #{l(:label_me)} >>".html_safe, :value => User.current.id)
310 end
310 end
311 groups = ''
311 groups = ''
312 collection.sort.each do |element|
312 collection.sort.each do |element|
313 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
313 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
314 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
314 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
315 end
315 end
316 unless groups.empty?
316 unless groups.empty?
317 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
317 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
318 end
318 end
319 s
319 s
320 end
320 end
321
321
322 # Truncates and returns the string as a single line
322 # Truncates and returns the string as a single line
323 def truncate_single_line(string, *args)
323 def truncate_single_line(string, *args)
324 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
324 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
325 end
325 end
326
326
327 # Truncates at line break after 250 characters or options[:length]
327 # Truncates at line break after 250 characters or options[:length]
328 def truncate_lines(string, options={})
328 def truncate_lines(string, options={})
329 length = options[:length] || 250
329 length = options[:length] || 250
330 if string.to_s =~ /\A(.{#{length}}.*?)$/m
330 if string.to_s =~ /\A(.{#{length}}.*?)$/m
331 "#{$1}..."
331 "#{$1}..."
332 else
332 else
333 string
333 string
334 end
334 end
335 end
335 end
336
336
337 def anchor(text)
337 def anchor(text)
338 text.to_s.gsub(' ', '_')
338 text.to_s.gsub(' ', '_')
339 end
339 end
340
340
341 def html_hours(text)
341 def html_hours(text)
342 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
342 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
343 end
343 end
344
344
345 def authoring(created, author, options={})
345 def authoring(created, author, options={})
346 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
346 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
347 end
347 end
348
348
349 def time_tag(time)
349 def time_tag(time)
350 text = distance_of_time_in_words(Time.now, time)
350 text = distance_of_time_in_words(Time.now, time)
351 if @project
351 if @project
352 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
352 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
353 else
353 else
354 content_tag('acronym', text, :title => format_time(time))
354 content_tag('acronym', text, :title => format_time(time))
355 end
355 end
356 end
356 end
357
357
358 def syntax_highlight(name, content)
358 def syntax_highlight(name, content)
359 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
359 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
360 end
360 end
361
361
362 def to_path_param(path)
362 def to_path_param(path)
363 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
363 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
364 end
364 end
365
365
366 def pagination_links_full(paginator, count=nil, options={})
366 def pagination_links_full(paginator, count=nil, options={})
367 page_param = options.delete(:page_param) || :page
367 page_param = options.delete(:page_param) || :page
368 per_page_links = options.delete(:per_page_links)
368 per_page_links = options.delete(:per_page_links)
369 url_param = params.dup
369 url_param = params.dup
370
370
371 html = ''
371 html = ''
372 if paginator.current.previous
372 if paginator.current.previous
373 # \xc2\xab(utf-8) = &#171;
373 # \xc2\xab(utf-8) = &#171;
374 html << link_to_content_update(
374 html << link_to_content_update(
375 "\xc2\xab " + l(:label_previous),
375 "\xc2\xab " + l(:label_previous),
376 url_param.merge(page_param => paginator.current.previous)) + ' '
376 url_param.merge(page_param => paginator.current.previous)) + ' '
377 end
377 end
378
378
379 html << (pagination_links_each(paginator, options) do |n|
379 html << (pagination_links_each(paginator, options) do |n|
380 link_to_content_update(n.to_s, url_param.merge(page_param => n))
380 link_to_content_update(n.to_s, url_param.merge(page_param => n))
381 end || '')
381 end || '')
382
382
383 if paginator.current.next
383 if paginator.current.next
384 # \xc2\xbb(utf-8) = &#187;
384 # \xc2\xbb(utf-8) = &#187;
385 html << ' ' + link_to_content_update(
385 html << ' ' + link_to_content_update(
386 (l(:label_next) + " \xc2\xbb"),
386 (l(:label_next) + " \xc2\xbb"),
387 url_param.merge(page_param => paginator.current.next))
387 url_param.merge(page_param => paginator.current.next))
388 end
388 end
389
389
390 unless count.nil?
390 unless count.nil?
391 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
391 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
392 if per_page_links != false && links = per_page_links(paginator.items_per_page)
392 if per_page_links != false && links = per_page_links(paginator.items_per_page)
393 html << " | #{links}"
393 html << " | #{links}"
394 end
394 end
395 end
395 end
396
396
397 html.html_safe
397 html.html_safe
398 end
398 end
399
399
400 def per_page_links(selected=nil)
400 def per_page_links(selected=nil)
401 links = Setting.per_page_options_array.collect do |n|
401 links = Setting.per_page_options_array.collect do |n|
402 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
402 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
403 end
403 end
404 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
404 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
405 end
405 end
406
406
407 def reorder_links(name, url, method = :post)
407 def reorder_links(name, url, method = :post)
408 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
408 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
409 url.merge({"#{name}[move_to]" => 'highest'}),
409 url.merge({"#{name}[move_to]" => 'highest'}),
410 :method => method, :title => l(:label_sort_highest)) +
410 :method => method, :title => l(:label_sort_highest)) +
411 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
411 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
412 url.merge({"#{name}[move_to]" => 'higher'}),
412 url.merge({"#{name}[move_to]" => 'higher'}),
413 :method => method, :title => l(:label_sort_higher)) +
413 :method => method, :title => l(:label_sort_higher)) +
414 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
414 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
415 url.merge({"#{name}[move_to]" => 'lower'}),
415 url.merge({"#{name}[move_to]" => 'lower'}),
416 :method => method, :title => l(:label_sort_lower)) +
416 :method => method, :title => l(:label_sort_lower)) +
417 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
417 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
418 url.merge({"#{name}[move_to]" => 'lowest'}),
418 url.merge({"#{name}[move_to]" => 'lowest'}),
419 :method => method, :title => l(:label_sort_lowest))
419 :method => method, :title => l(:label_sort_lowest))
420 end
420 end
421
421
422 def breadcrumb(*args)
422 def breadcrumb(*args)
423 elements = args.flatten
423 elements = args.flatten
424 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
424 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
425 end
425 end
426
426
427 def other_formats_links(&block)
427 def other_formats_links(&block)
428 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
428 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
429 yield Redmine::Views::OtherFormatsBuilder.new(self)
429 yield Redmine::Views::OtherFormatsBuilder.new(self)
430 concat('</p>'.html_safe)
430 concat('</p>'.html_safe)
431 end
431 end
432
432
433 def page_header_title
433 def page_header_title
434 if @project.nil? || @project.new_record?
434 if @project.nil? || @project.new_record?
435 h(Setting.app_title)
435 h(Setting.app_title)
436 else
436 else
437 b = []
437 b = []
438 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
438 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
439 if ancestors.any?
439 if ancestors.any?
440 root = ancestors.shift
440 root = ancestors.shift
441 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
441 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
442 if ancestors.size > 2
442 if ancestors.size > 2
443 b << "\xe2\x80\xa6"
443 b << "\xe2\x80\xa6"
444 ancestors = ancestors[-2, 2]
444 ancestors = ancestors[-2, 2]
445 end
445 end
446 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
446 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
447 end
447 end
448 b << h(@project)
448 b << h(@project)
449 b.join(" \xc2\xbb ").html_safe
449 b.join(" \xc2\xbb ").html_safe
450 end
450 end
451 end
451 end
452
452
453 def html_title(*args)
453 def html_title(*args)
454 if args.empty?
454 if args.empty?
455 title = @html_title || []
455 title = @html_title || []
456 title << @project.name if @project
456 title << @project.name if @project
457 title << Setting.app_title unless Setting.app_title == title.last
457 title << Setting.app_title unless Setting.app_title == title.last
458 title.select {|t| !t.blank? }.join(' - ')
458 title.select {|t| !t.blank? }.join(' - ')
459 else
459 else
460 @html_title ||= []
460 @html_title ||= []
461 @html_title += args
461 @html_title += args
462 end
462 end
463 end
463 end
464
464
465 # Returns the theme, controller name, and action as css classes for the
465 # Returns the theme, controller name, and action as css classes for the
466 # HTML body.
466 # HTML body.
467 def body_css_classes
467 def body_css_classes
468 css = []
468 css = []
469 if theme = Redmine::Themes.theme(Setting.ui_theme)
469 if theme = Redmine::Themes.theme(Setting.ui_theme)
470 css << 'theme-' + theme.name
470 css << 'theme-' + theme.name
471 end
471 end
472
472
473 css << 'controller-' + params[:controller]
473 css << 'controller-' + params[:controller]
474 css << 'action-' + params[:action]
474 css << 'action-' + params[:action]
475 css.join(' ')
475 css.join(' ')
476 end
476 end
477
477
478 def accesskey(s)
478 def accesskey(s)
479 Redmine::AccessKeys.key_for s
479 Redmine::AccessKeys.key_for s
480 end
480 end
481
481
482 # Formats text according to system settings.
482 # Formats text according to system settings.
483 # 2 ways to call this method:
483 # 2 ways to call this method:
484 # * with a String: textilizable(text, options)
484 # * with a String: textilizable(text, options)
485 # * with an object and one of its attribute: textilizable(issue, :description, options)
485 # * with an object and one of its attribute: textilizable(issue, :description, options)
486 def textilizable(*args)
486 def textilizable(*args)
487 options = args.last.is_a?(Hash) ? args.pop : {}
487 options = args.last.is_a?(Hash) ? args.pop : {}
488 case args.size
488 case args.size
489 when 1
489 when 1
490 obj = options[:object]
490 obj = options[:object]
491 text = args.shift
491 text = args.shift
492 when 2
492 when 2
493 obj = args.shift
493 obj = args.shift
494 attr = args.shift
494 attr = args.shift
495 text = obj.send(attr).to_s
495 text = obj.send(attr).to_s
496 else
496 else
497 raise ArgumentError, 'invalid arguments to textilizable'
497 raise ArgumentError, 'invalid arguments to textilizable'
498 end
498 end
499 return '' if text.blank?
499 return '' if text.blank?
500 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
500 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
501 only_path = options.delete(:only_path) == false ? false : true
501 only_path = options.delete(:only_path) == false ? false : true
502
502
503 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
503 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
504
504
505 @parsed_headings = []
505 @parsed_headings = []
506 @heading_anchors = {}
506 @heading_anchors = {}
507 @current_section = 0 if options[:edit_section_links]
507 @current_section = 0 if options[:edit_section_links]
508
508
509 parse_sections(text, project, obj, attr, only_path, options)
509 parse_sections(text, project, obj, attr, only_path, options)
510 text = parse_non_pre_blocks(text) do |text|
510 text = parse_non_pre_blocks(text) do |text|
511 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros].each do |method_name|
511 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros].each do |method_name|
512 send method_name, text, project, obj, attr, only_path, options
512 send method_name, text, project, obj, attr, only_path, options
513 end
513 end
514 end
514 end
515 parse_headings(text, project, obj, attr, only_path, options)
515 parse_headings(text, project, obj, attr, only_path, options)
516
516
517 if @parsed_headings.any?
517 if @parsed_headings.any?
518 replace_toc(text, @parsed_headings)
518 replace_toc(text, @parsed_headings)
519 end
519 end
520
520
521 text.html_safe
521 text.html_safe
522 end
522 end
523
523
524 def parse_non_pre_blocks(text)
524 def parse_non_pre_blocks(text)
525 s = StringScanner.new(text)
525 s = StringScanner.new(text)
526 tags = []
526 tags = []
527 parsed = ''
527 parsed = ''
528 while !s.eos?
528 while !s.eos?
529 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
529 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
530 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
530 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
531 if tags.empty?
531 if tags.empty?
532 yield text
532 yield text
533 end
533 end
534 parsed << text
534 parsed << text
535 if tag
535 if tag
536 if closing
536 if closing
537 if tags.last == tag.downcase
537 if tags.last == tag.downcase
538 tags.pop
538 tags.pop
539 end
539 end
540 else
540 else
541 tags << tag.downcase
541 tags << tag.downcase
542 end
542 end
543 parsed << full_tag
543 parsed << full_tag
544 end
544 end
545 end
545 end
546 # Close any non closing tags
546 # Close any non closing tags
547 while tag = tags.pop
547 while tag = tags.pop
548 parsed << "</#{tag}>"
548 parsed << "</#{tag}>"
549 end
549 end
550 parsed.html_safe
550 parsed
551 end
551 end
552
552
553 def parse_inline_attachments(text, project, obj, attr, only_path, options)
553 def parse_inline_attachments(text, project, obj, attr, only_path, options)
554 # when using an image link, try to use an attachment, if possible
554 # when using an image link, try to use an attachment, if possible
555 if options[:attachments] || (obj && obj.respond_to?(:attachments))
555 if options[:attachments] || (obj && obj.respond_to?(:attachments))
556 attachments = options[:attachments] || obj.attachments
556 attachments = options[:attachments] || obj.attachments
557 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
557 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
558 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
558 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
559 # search for the picture in attachments
559 # search for the picture in attachments
560 if found = Attachment.latest_attach(attachments, filename)
560 if found = Attachment.latest_attach(attachments, filename)
561 image_url = url_for :only_path => only_path, :controller => 'attachments',
561 image_url = url_for :only_path => only_path, :controller => 'attachments',
562 :action => 'download', :id => found
562 :action => 'download', :id => found
563 desc = found.description.to_s.gsub('"', '')
563 desc = found.description.to_s.gsub('"', '')
564 if !desc.blank? && alttext.blank?
564 if !desc.blank? && alttext.blank?
565 alt = " title=\"#{desc}\" alt=\"#{desc}\""
565 alt = " title=\"#{desc}\" alt=\"#{desc}\""
566 end
566 end
567 "src=\"#{image_url}\"#{alt}".html_safe
567 "src=\"#{image_url}\"#{alt}"
568 else
568 else
569 m.html_safe
569 m
570 end
570 end
571 end
571 end
572 end
572 end
573 end
573 end
574
574
575 # Wiki links
575 # Wiki links
576 #
576 #
577 # Examples:
577 # Examples:
578 # [[mypage]]
578 # [[mypage]]
579 # [[mypage|mytext]]
579 # [[mypage|mytext]]
580 # wiki links can refer other project wikis, using project name or identifier:
580 # wiki links can refer other project wikis, using project name or identifier:
581 # [[project:]] -> wiki starting page
581 # [[project:]] -> wiki starting page
582 # [[project:|mytext]]
582 # [[project:|mytext]]
583 # [[project:mypage]]
583 # [[project:mypage]]
584 # [[project:mypage|mytext]]
584 # [[project:mypage|mytext]]
585 def parse_wiki_links(text, project, obj, attr, only_path, options)
585 def parse_wiki_links(text, project, obj, attr, only_path, options)
586 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
586 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
587 link_project = project
587 link_project = project
588 esc, all, page, title = $1, $2, $3, $5
588 esc, all, page, title = $1, $2, $3, $5
589 if esc.nil?
589 if esc.nil?
590 if page =~ /^([^\:]+)\:(.*)$/
590 if page =~ /^([^\:]+)\:(.*)$/
591 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
591 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
592 page = $2
592 page = $2
593 title ||= $1 if page.blank?
593 title ||= $1 if page.blank?
594 end
594 end
595
595
596 if link_project && link_project.wiki
596 if link_project && link_project.wiki
597 # extract anchor
597 # extract anchor
598 anchor = nil
598 anchor = nil
599 if page =~ /^(.+?)\#(.+)$/
599 if page =~ /^(.+?)\#(.+)$/
600 page, anchor = $1, $2
600 page, anchor = $1, $2
601 end
601 end
602 anchor = sanitize_anchor_name(anchor) if anchor.present?
602 anchor = sanitize_anchor_name(anchor) if anchor.present?
603 # check if page exists
603 # check if page exists
604 wiki_page = link_project.wiki.find_page(page)
604 wiki_page = link_project.wiki.find_page(page)
605 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
605 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
606 "##{anchor}"
606 "##{anchor}"
607 else
607 else
608 case options[:wiki_links]
608 case options[:wiki_links]
609 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
609 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
610 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
610 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
611 else
611 else
612 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
612 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
613 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
613 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
614 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
614 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
615 :id => wiki_page_id, :anchor => anchor, :parent => parent)
615 :id => wiki_page_id, :anchor => anchor, :parent => parent)
616 end
616 end
617 end
617 end
618 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
618 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
619 else
619 else
620 # project or wiki doesn't exist
620 # project or wiki doesn't exist
621 all.html_safe
621 all
622 end
622 end
623 else
623 else
624 all.html_safe
624 all
625 end
625 end
626 end
626 end
627 end
627 end
628
628
629 # Redmine links
629 # Redmine links
630 #
630 #
631 # Examples:
631 # Examples:
632 # Issues:
632 # Issues:
633 # #52 -> Link to issue #52
633 # #52 -> Link to issue #52
634 # Changesets:
634 # Changesets:
635 # r52 -> Link to revision 52
635 # r52 -> Link to revision 52
636 # commit:a85130f -> Link to scmid starting with a85130f
636 # commit:a85130f -> Link to scmid starting with a85130f
637 # Documents:
637 # Documents:
638 # document#17 -> Link to document with id 17
638 # document#17 -> Link to document with id 17
639 # document:Greetings -> Link to the document with title "Greetings"
639 # document:Greetings -> Link to the document with title "Greetings"
640 # document:"Some document" -> Link to the document with title "Some document"
640 # document:"Some document" -> Link to the document with title "Some document"
641 # Versions:
641 # Versions:
642 # version#3 -> Link to version with id 3
642 # version#3 -> Link to version with id 3
643 # version:1.0.0 -> Link to version named "1.0.0"
643 # version:1.0.0 -> Link to version named "1.0.0"
644 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
644 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
645 # Attachments:
645 # Attachments:
646 # attachment:file.zip -> Link to the attachment of the current object named file.zip
646 # attachment:file.zip -> Link to the attachment of the current object named file.zip
647 # Source files:
647 # Source files:
648 # source:some/file -> Link to the file located at /some/file in the project's repository
648 # source:some/file -> Link to the file located at /some/file in the project's repository
649 # source:some/file@52 -> Link to the file's revision 52
649 # source:some/file@52 -> Link to the file's revision 52
650 # source:some/file#L120 -> Link to line 120 of the file
650 # source:some/file#L120 -> Link to line 120 of the file
651 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
651 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
652 # export:some/file -> Force the download of the file
652 # export:some/file -> Force the download of the file
653 # Forum messages:
653 # Forum messages:
654 # message#1218 -> Link to message with id 1218
654 # message#1218 -> Link to message with id 1218
655 #
655 #
656 # Links can refer other objects from other projects, using project identifier:
656 # Links can refer other objects from other projects, using project identifier:
657 # identifier:r52
657 # identifier:r52
658 # identifier:document:"Some document"
658 # identifier:document:"Some document"
659 # identifier:version:1.0.0
659 # identifier:version:1.0.0
660 # identifier:source:some/file
660 # identifier:source:some/file
661 def parse_redmine_links(text, project, obj, attr, only_path, options)
661 def parse_redmine_links(text, project, obj, attr, only_path, options)
662 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|
662 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|
663 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
663 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
664 link = nil
664 link = nil
665 if project_identifier
665 if project_identifier
666 project = Project.visible.find_by_identifier(project_identifier)
666 project = Project.visible.find_by_identifier(project_identifier)
667 end
667 end
668 if esc.nil?
668 if esc.nil?
669 if prefix.nil? && sep == 'r'
669 if prefix.nil? && sep == 'r'
670 if project
670 if project
671 repository = nil
671 repository = nil
672 if repo_identifier
672 if repo_identifier
673 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
673 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
674 else
674 else
675 repository = project.repository
675 repository = project.repository
676 end
676 end
677 # project.changesets.visible raises an SQL error because of a double join on repositories
677 # project.changesets.visible raises an SQL error because of a double join on repositories
678 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
678 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
679 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},
679 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},
680 :class => 'changeset',
680 :class => 'changeset',
681 :title => truncate_single_line(changeset.comments, :length => 100))
681 :title => truncate_single_line(changeset.comments, :length => 100))
682 end
682 end
683 end
683 end
684 elsif sep == '#'
684 elsif sep == '#'
685 oid = identifier.to_i
685 oid = identifier.to_i
686 case prefix
686 case prefix
687 when nil
687 when nil
688 if issue = Issue.visible.find_by_id(oid, :include => :status)
688 if issue = Issue.visible.find_by_id(oid, :include => :status)
689 anchor = comment_id ? "note-#{comment_id}" : nil
689 anchor = comment_id ? "note-#{comment_id}" : nil
690 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
690 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
691 :class => issue.css_classes,
691 :class => issue.css_classes,
692 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
692 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
693 end
693 end
694 when 'document'
694 when 'document'
695 if document = Document.visible.find_by_id(oid)
695 if document = Document.visible.find_by_id(oid)
696 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
696 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
697 :class => 'document'
697 :class => 'document'
698 end
698 end
699 when 'version'
699 when 'version'
700 if version = Version.visible.find_by_id(oid)
700 if version = Version.visible.find_by_id(oid)
701 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
701 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
702 :class => 'version'
702 :class => 'version'
703 end
703 end
704 when 'message'
704 when 'message'
705 if message = Message.visible.find_by_id(oid, :include => :parent)
705 if message = Message.visible.find_by_id(oid, :include => :parent)
706 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
706 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
707 end
707 end
708 when 'forum'
708 when 'forum'
709 if board = Board.visible.find_by_id(oid)
709 if board = Board.visible.find_by_id(oid)
710 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
710 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
711 :class => 'board'
711 :class => 'board'
712 end
712 end
713 when 'news'
713 when 'news'
714 if news = News.visible.find_by_id(oid)
714 if news = News.visible.find_by_id(oid)
715 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
715 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
716 :class => 'news'
716 :class => 'news'
717 end
717 end
718 when 'project'
718 when 'project'
719 if p = Project.visible.find_by_id(oid)
719 if p = Project.visible.find_by_id(oid)
720 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
720 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
721 end
721 end
722 end
722 end
723 elsif sep == ':'
723 elsif sep == ':'
724 # removes the double quotes if any
724 # removes the double quotes if any
725 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
725 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
726 case prefix
726 case prefix
727 when 'document'
727 when 'document'
728 if project && document = project.documents.visible.find_by_title(name)
728 if project && document = project.documents.visible.find_by_title(name)
729 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
729 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
730 :class => 'document'
730 :class => 'document'
731 end
731 end
732 when 'version'
732 when 'version'
733 if project && version = project.versions.visible.find_by_name(name)
733 if project && version = project.versions.visible.find_by_name(name)
734 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
734 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
735 :class => 'version'
735 :class => 'version'
736 end
736 end
737 when 'forum'
737 when 'forum'
738 if project && board = project.boards.visible.find_by_name(name)
738 if project && board = project.boards.visible.find_by_name(name)
739 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
739 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
740 :class => 'board'
740 :class => 'board'
741 end
741 end
742 when 'news'
742 when 'news'
743 if project && news = project.news.visible.find_by_title(name)
743 if project && news = project.news.visible.find_by_title(name)
744 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
744 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
745 :class => 'news'
745 :class => 'news'
746 end
746 end
747 when 'commit', 'source', 'export'
747 when 'commit', 'source', 'export'
748 if project
748 if project
749 repository = nil
749 repository = nil
750 if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
750 if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
751 repo_prefix, repo_identifier, name = $1, $2, $3
751 repo_prefix, repo_identifier, name = $1, $2, $3
752 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
752 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
753 else
753 else
754 repository = project.repository
754 repository = project.repository
755 end
755 end
756 if prefix == 'commit'
756 if prefix == 'commit'
757 if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"]))
757 if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"]))
758 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},
758 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},
759 :class => 'changeset',
759 :class => 'changeset',
760 :title => truncate_single_line(h(changeset.comments), :length => 100)
760 :title => truncate_single_line(h(changeset.comments), :length => 100)
761 end
761 end
762 else
762 else
763 if repository && User.current.allowed_to?(:browse_repository, project)
763 if repository && User.current.allowed_to?(:browse_repository, project)
764 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
764 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
765 path, rev, anchor = $1, $3, $5
765 path, rev, anchor = $1, $3, $5
766 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :repository_id => repository.identifier_param,
766 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :repository_id => repository.identifier_param,
767 :path => to_path_param(path),
767 :path => to_path_param(path),
768 :rev => rev,
768 :rev => rev,
769 :anchor => anchor,
769 :anchor => anchor,
770 :format => (prefix == 'export' ? 'raw' : nil)},
770 :format => (prefix == 'export' ? 'raw' : nil)},
771 :class => (prefix == 'export' ? 'source download' : 'source')
771 :class => (prefix == 'export' ? 'source download' : 'source')
772 end
772 end
773 end
773 end
774 repo_prefix = nil
774 repo_prefix = nil
775 end
775 end
776 when 'attachment'
776 when 'attachment'
777 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
777 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
778 if attachments && attachment = attachments.detect {|a| a.filename == name }
778 if attachments && attachment = attachments.detect {|a| a.filename == name }
779 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
779 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
780 :class => 'attachment'
780 :class => 'attachment'
781 end
781 end
782 when 'project'
782 when 'project'
783 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
783 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
784 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
784 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
785 end
785 end
786 end
786 end
787 end
787 end
788 end
788 end
789 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}")).html_safe
789 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
790 end
790 end
791 end
791 end
792
792
793 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
793 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
794
794
795 def parse_sections(text, project, obj, attr, only_path, options)
795 def parse_sections(text, project, obj, attr, only_path, options)
796 return unless options[:edit_section_links]
796 return unless options[:edit_section_links]
797 text.gsub!(HEADING_RE) do
797 text.gsub!(HEADING_RE) do
798 heading = $1
798 @current_section += 1
799 @current_section += 1
799 if @current_section > 1
800 if @current_section > 1
800 content_tag('div',
801 content_tag('div',
801 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
802 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
802 :class => 'contextual',
803 :class => 'contextual',
803 :title => l(:button_edit_section)) + $1
804 :title => l(:button_edit_section)) + heading.html_safe
804 else
805 else
805 $1
806 heading
806 end
807 end
807 end
808 end
808 end
809 end
809
810
810 # Headings and TOC
811 # Headings and TOC
811 # Adds ids and links to headings unless options[:headings] is set to false
812 # Adds ids and links to headings unless options[:headings] is set to false
812 def parse_headings(text, project, obj, attr, only_path, options)
813 def parse_headings(text, project, obj, attr, only_path, options)
813 return if options[:headings] == false
814 return if options[:headings] == false
814
815
815 text.gsub!(HEADING_RE) do
816 text.gsub!(HEADING_RE) do
816 level, attrs, content = $2.to_i, $3, $4
817 level, attrs, content = $2.to_i, $3, $4
817 item = strip_tags(content).strip
818 item = strip_tags(content).strip
818 anchor = sanitize_anchor_name(item)
819 anchor = sanitize_anchor_name(item)
819 # used for single-file wiki export
820 # used for single-file wiki export
820 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
821 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
821 @heading_anchors[anchor] ||= 0
822 @heading_anchors[anchor] ||= 0
822 idx = (@heading_anchors[anchor] += 1)
823 idx = (@heading_anchors[anchor] += 1)
823 if idx > 1
824 if idx > 1
824 anchor = "#{anchor}-#{idx}"
825 anchor = "#{anchor}-#{idx}"
825 end
826 end
826 @parsed_headings << [level, anchor, item]
827 @parsed_headings << [level, anchor, item]
827 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
828 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
828 end
829 end
829 end
830 end
830
831
831 MACROS_RE = /
832 MACROS_RE = /
832 (!)? # escaping
833 (!)? # escaping
833 (
834 (
834 \{\{ # opening tag
835 \{\{ # opening tag
835 ([\w]+) # macro name
836 ([\w]+) # macro name
836 (\(([^\}]*)\))? # optional arguments
837 (\(([^\}]*)\))? # optional arguments
837 \}\} # closing tag
838 \}\} # closing tag
838 )
839 )
839 /x unless const_defined?(:MACROS_RE)
840 /x unless const_defined?(:MACROS_RE)
840
841
841 # Macros substitution
842 # Macros substitution
842 def parse_macros(text, project, obj, attr, only_path, options)
843 def parse_macros(text, project, obj, attr, only_path, options)
843 text.gsub!(MACROS_RE) do
844 text.gsub!(MACROS_RE) do
844 esc, all, macro = $1, $2, $3.downcase
845 esc, all, macro = $1, $2, $3.downcase
845 args = ($5 || '').split(',').each(&:strip)
846 args = ($5 || '').split(',').each(&:strip)
846 if esc.nil?
847 if esc.nil?
847 begin
848 begin
848 exec_macro(macro, obj, args)
849 exec_macro(macro, obj, args)
849 rescue => e
850 rescue => e
850 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
851 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
851 end || all
852 end || all
852 else
853 else
853 all
854 all
854 end
855 end
855 end
856 end
856 end
857 end
857
858
858 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
859 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
859
860
860 # Renders the TOC with given headings
861 # Renders the TOC with given headings
861 def replace_toc(text, headings)
862 def replace_toc(text, headings)
862 text.gsub!(TOC_RE) do
863 text.gsub!(TOC_RE) do
863 if headings.empty?
864 if headings.empty?
864 ''
865 ''
865 else
866 else
866 div_class = 'toc'
867 div_class = 'toc'
867 div_class << ' right' if $1 == '>'
868 div_class << ' right' if $1 == '>'
868 div_class << ' left' if $1 == '<'
869 div_class << ' left' if $1 == '<'
869 out = "<ul class=\"#{div_class}\"><li>"
870 out = "<ul class=\"#{div_class}\"><li>"
870 root = headings.map(&:first).min
871 root = headings.map(&:first).min
871 current = root
872 current = root
872 started = false
873 started = false
873 headings.each do |level, anchor, item|
874 headings.each do |level, anchor, item|
874 if level > current
875 if level > current
875 out << '<ul><li>' * (level - current)
876 out << '<ul><li>' * (level - current)
876 elsif level < current
877 elsif level < current
877 out << "</li></ul>\n" * (current - level) + "</li><li>"
878 out << "</li></ul>\n" * (current - level) + "</li><li>"
878 elsif started
879 elsif started
879 out << '</li><li>'
880 out << '</li><li>'
880 end
881 end
881 out << "<a href=\"##{anchor}\">#{item}</a>"
882 out << "<a href=\"##{anchor}\">#{item}</a>"
882 current = level
883 current = level
883 started = true
884 started = true
884 end
885 end
885 out << '</li></ul>' * (current - root)
886 out << '</li></ul>' * (current - root)
886 out << '</li></ul>'
887 out << '</li></ul>'
887 end
888 end
888 end
889 end
889 end
890 end
890
891
891 # Same as Rails' simple_format helper without using paragraphs
892 # Same as Rails' simple_format helper without using paragraphs
892 def simple_format_without_paragraph(text)
893 def simple_format_without_paragraph(text)
893 text.to_s.
894 text.to_s.
894 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
895 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
895 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
896 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
896 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
897 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
897 html_safe
898 html_safe
898 end
899 end
899
900
900 def lang_options_for_select(blank=true)
901 def lang_options_for_select(blank=true)
901 (blank ? [["(auto)", ""]] : []) +
902 (blank ? [["(auto)", ""]] : []) +
902 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
903 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
903 end
904 end
904
905
905 def label_tag_for(name, option_tags = nil, options = {})
906 def label_tag_for(name, option_tags = nil, options = {})
906 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
907 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
907 content_tag("label", label_text)
908 content_tag("label", label_text)
908 end
909 end
909
910
910 def labelled_tabular_form_for(*args, &proc)
911 def labelled_tabular_form_for(*args, &proc)
911 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_tabular_form_for is deprecated and will be removed in Redmine 1.5. Use #labelled_form_for instead."
912 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_tabular_form_for is deprecated and will be removed in Redmine 1.5. Use #labelled_form_for instead."
912 args << {} unless args.last.is_a?(Hash)
913 args << {} unless args.last.is_a?(Hash)
913 options = args.last
914 options = args.last
914 options[:html] ||= {}
915 options[:html] ||= {}
915 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
916 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
916 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
917 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
917 form_for(*args, &proc)
918 form_for(*args, &proc)
918 end
919 end
919
920
920 def labelled_form_for(*args, &proc)
921 def labelled_form_for(*args, &proc)
921 args << {} unless args.last.is_a?(Hash)
922 args << {} unless args.last.is_a?(Hash)
922 options = args.last
923 options = args.last
923 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
924 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
924 form_for(*args, &proc)
925 form_for(*args, &proc)
925 end
926 end
926
927
927 def labelled_fields_for(*args, &proc)
928 def labelled_fields_for(*args, &proc)
928 args << {} unless args.last.is_a?(Hash)
929 args << {} unless args.last.is_a?(Hash)
929 options = args.last
930 options = args.last
930 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
931 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
931 fields_for(*args, &proc)
932 fields_for(*args, &proc)
932 end
933 end
933
934
934 def labelled_remote_form_for(*args, &proc)
935 def labelled_remote_form_for(*args, &proc)
935 args << {} unless args.last.is_a?(Hash)
936 args << {} unless args.last.is_a?(Hash)
936 options = args.last
937 options = args.last
937 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
938 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
938 remote_form_for(*args, &proc)
939 remote_form_for(*args, &proc)
939 end
940 end
940
941
941 def back_url_hidden_field_tag
942 def back_url_hidden_field_tag
942 back_url = params[:back_url] || request.env['HTTP_REFERER']
943 back_url = params[:back_url] || request.env['HTTP_REFERER']
943 back_url = CGI.unescape(back_url.to_s)
944 back_url = CGI.unescape(back_url.to_s)
944 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
945 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
945 end
946 end
946
947
947 def check_all_links(form_name)
948 def check_all_links(form_name)
948 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
949 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
949 " | ".html_safe +
950 " | ".html_safe +
950 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
951 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
951 end
952 end
952
953
953 def progress_bar(pcts, options={})
954 def progress_bar(pcts, options={})
954 pcts = [pcts, pcts] unless pcts.is_a?(Array)
955 pcts = [pcts, pcts] unless pcts.is_a?(Array)
955 pcts = pcts.collect(&:round)
956 pcts = pcts.collect(&:round)
956 pcts[1] = pcts[1] - pcts[0]
957 pcts[1] = pcts[1] - pcts[0]
957 pcts << (100 - pcts[1] - pcts[0])
958 pcts << (100 - pcts[1] - pcts[0])
958 width = options[:width] || '100px;'
959 width = options[:width] || '100px;'
959 legend = options[:legend] || ''
960 legend = options[:legend] || ''
960 content_tag('table',
961 content_tag('table',
961 content_tag('tr',
962 content_tag('tr',
962 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
963 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
963 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
964 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
964 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
965 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
965 ), :class => 'progress', :style => "width: #{width};").html_safe +
966 ), :class => 'progress', :style => "width: #{width};").html_safe +
966 content_tag('p', legend, :class => 'pourcent').html_safe
967 content_tag('p', legend, :class => 'pourcent').html_safe
967 end
968 end
968
969
969 def checked_image(checked=true)
970 def checked_image(checked=true)
970 if checked
971 if checked
971 image_tag 'toggle_check.png'
972 image_tag 'toggle_check.png'
972 end
973 end
973 end
974 end
974
975
975 def context_menu(url)
976 def context_menu(url)
976 unless @context_menu_included
977 unless @context_menu_included
977 content_for :header_tags do
978 content_for :header_tags do
978 javascript_include_tag('context_menu') +
979 javascript_include_tag('context_menu') +
979 stylesheet_link_tag('context_menu')
980 stylesheet_link_tag('context_menu')
980 end
981 end
981 if l(:direction) == 'rtl'
982 if l(:direction) == 'rtl'
982 content_for :header_tags do
983 content_for :header_tags do
983 stylesheet_link_tag('context_menu_rtl')
984 stylesheet_link_tag('context_menu_rtl')
984 end
985 end
985 end
986 end
986 @context_menu_included = true
987 @context_menu_included = true
987 end
988 end
988 javascript_tag "new ContextMenu('#{ url_for(url) }')"
989 javascript_tag "new ContextMenu('#{ url_for(url) }')"
989 end
990 end
990
991
991 def calendar_for(field_id)
992 def calendar_for(field_id)
992 include_calendar_headers_tags
993 include_calendar_headers_tags
993 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
994 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
994 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
995 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
995 end
996 end
996
997
997 def include_calendar_headers_tags
998 def include_calendar_headers_tags
998 unless @calendar_headers_tags_included
999 unless @calendar_headers_tags_included
999 @calendar_headers_tags_included = true
1000 @calendar_headers_tags_included = true
1000 content_for :header_tags do
1001 content_for :header_tags do
1001 start_of_week = case Setting.start_of_week.to_i
1002 start_of_week = case Setting.start_of_week.to_i
1002 when 1
1003 when 1
1003 'Calendar._FD = 1;' # Monday
1004 'Calendar._FD = 1;' # Monday
1004 when 7
1005 when 7
1005 'Calendar._FD = 0;' # Sunday
1006 'Calendar._FD = 0;' # Sunday
1006 when 6
1007 when 6
1007 'Calendar._FD = 6;' # Saturday
1008 'Calendar._FD = 6;' # Saturday
1008 else
1009 else
1009 '' # use language
1010 '' # use language
1010 end
1011 end
1011
1012
1012 javascript_include_tag('calendar/calendar') +
1013 javascript_include_tag('calendar/calendar') +
1013 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
1014 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
1014 javascript_tag(start_of_week) +
1015 javascript_tag(start_of_week) +
1015 javascript_include_tag('calendar/calendar-setup') +
1016 javascript_include_tag('calendar/calendar-setup') +
1016 stylesheet_link_tag('calendar')
1017 stylesheet_link_tag('calendar')
1017 end
1018 end
1018 end
1019 end
1019 end
1020 end
1020
1021
1021 def content_for(name, content = nil, &block)
1022 def content_for(name, content = nil, &block)
1022 @has_content ||= {}
1023 @has_content ||= {}
1023 @has_content[name] = true
1024 @has_content[name] = true
1024 super(name, content, &block)
1025 super(name, content, &block)
1025 end
1026 end
1026
1027
1027 def has_content?(name)
1028 def has_content?(name)
1028 (@has_content && @has_content[name]) || false
1029 (@has_content && @has_content[name]) || false
1029 end
1030 end
1030
1031
1031 def email_delivery_enabled?
1032 def email_delivery_enabled?
1032 !!ActionMailer::Base.perform_deliveries
1033 !!ActionMailer::Base.perform_deliveries
1033 end
1034 end
1034
1035
1035 # Returns the avatar image tag for the given +user+ if avatars are enabled
1036 # Returns the avatar image tag for the given +user+ if avatars are enabled
1036 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1037 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1037 def avatar(user, options = { })
1038 def avatar(user, options = { })
1038 if Setting.gravatar_enabled?
1039 if Setting.gravatar_enabled?
1039 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
1040 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
1040 email = nil
1041 email = nil
1041 if user.respond_to?(:mail)
1042 if user.respond_to?(:mail)
1042 email = user.mail
1043 email = user.mail
1043 elsif user.to_s =~ %r{<(.+?)>}
1044 elsif user.to_s =~ %r{<(.+?)>}
1044 email = $1
1045 email = $1
1045 end
1046 end
1046 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1047 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1047 else
1048 else
1048 ''
1049 ''
1049 end
1050 end
1050 end
1051 end
1051
1052
1052 def sanitize_anchor_name(anchor)
1053 def sanitize_anchor_name(anchor)
1053 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1054 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1054 end
1055 end
1055
1056
1056 # Returns the javascript tags that are included in the html layout head
1057 # Returns the javascript tags that are included in the html layout head
1057 def javascript_heads
1058 def javascript_heads
1058 tags = javascript_include_tag(:defaults)
1059 tags = javascript_include_tag(:defaults)
1059 unless User.current.pref.warn_on_leaving_unsaved == '0'
1060 unless User.current.pref.warn_on_leaving_unsaved == '0'
1060 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
1061 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
1061 end
1062 end
1062 tags
1063 tags
1063 end
1064 end
1064
1065
1065 def favicon
1066 def favicon
1066 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1067 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1067 end
1068 end
1068
1069
1069 def robot_exclusion_tag
1070 def robot_exclusion_tag
1070 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1071 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1071 end
1072 end
1072
1073
1073 # Returns true if arg is expected in the API response
1074 # Returns true if arg is expected in the API response
1074 def include_in_api_response?(arg)
1075 def include_in_api_response?(arg)
1075 unless @included_in_api_response
1076 unless @included_in_api_response
1076 param = params[:include]
1077 param = params[:include]
1077 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1078 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1078 @included_in_api_response.collect!(&:strip)
1079 @included_in_api_response.collect!(&:strip)
1079 end
1080 end
1080 @included_in_api_response.include?(arg.to_s)
1081 @included_in_api_response.include?(arg.to_s)
1081 end
1082 end
1082
1083
1083 # Returns options or nil if nometa param or X-Redmine-Nometa header
1084 # Returns options or nil if nometa param or X-Redmine-Nometa header
1084 # was set in the request
1085 # was set in the request
1085 def api_meta(options)
1086 def api_meta(options)
1086 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1087 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1087 # compatibility mode for activeresource clients that raise
1088 # compatibility mode for activeresource clients that raise
1088 # an error when unserializing an array with attributes
1089 # an error when unserializing an array with attributes
1089 nil
1090 nil
1090 else
1091 else
1091 options
1092 options
1092 end
1093 end
1093 end
1094 end
1094
1095
1095 private
1096 private
1096
1097
1097 def wiki_helper
1098 def wiki_helper
1098 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1099 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1099 extend helper
1100 extend helper
1100 return self
1101 return self
1101 end
1102 end
1102
1103
1103 def link_to_content_update(text, url_params = {}, html_options = {})
1104 def link_to_content_update(text, url_params = {}, html_options = {})
1104 link_to(text, url_params, html_options)
1105 link_to(text, url_params, html_options)
1105 end
1106 end
1106 end
1107 end
General Comments 0
You need to be logged in to leave comments. Login now