##// END OF EJS Templates
Redmine links not working with html escpaed characters (#16668)....
Jean-Philippe Lang -
r12831:a4fa0364d459
parent child
Show More
@@ -1,1352 +1,1353
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2014 Jean-Philippe Lang
4 # Copyright (C) 2006-2014 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 = issue.subject.truncate(60)
75 title = issue.subject.truncate(60)
76 else
76 else
77 subject = issue.subject
77 subject = issue.subject
78 if truncate_length = options[:truncate]
78 if truncate_length = options[:truncate]
79 subject = subject.truncate(truncate_length)
79 subject = subject.truncate(truncate_length)
80 end
80 end
81 end
81 end
82 only_path = options[:only_path].nil? ? true : options[:only_path]
82 only_path = options[:only_path].nil? ? true : options[:only_path]
83 s = link_to(text, issue_path(issue, :only_path => only_path),
83 s = link_to(text, issue_path(issue, :only_path => only_path),
84 :class => issue.css_classes, :title => title)
84 :class => issue.css_classes, :title => title)
85 s << h(": #{subject}") if subject
85 s << h(": #{subject}") if subject
86 s = h("#{issue.project} - ") + s if options[:project]
86 s = h("#{issue.project} - ") + s if options[:project]
87 s
87 s
88 end
88 end
89
89
90 # Generates a link to an attachment.
90 # Generates a link to an attachment.
91 # Options:
91 # Options:
92 # * :text - Link text (default to attachment filename)
92 # * :text - Link text (default to attachment filename)
93 # * :download - Force download (default: false)
93 # * :download - Force download (default: false)
94 def link_to_attachment(attachment, options={})
94 def link_to_attachment(attachment, options={})
95 text = options.delete(:text) || attachment.filename
95 text = options.delete(:text) || attachment.filename
96 route_method = options.delete(:download) ? :download_named_attachment_path : :named_attachment_path
96 route_method = options.delete(:download) ? :download_named_attachment_path : :named_attachment_path
97 html_options = options.slice!(:only_path)
97 html_options = options.slice!(:only_path)
98 url = send(route_method, attachment, attachment.filename, options)
98 url = send(route_method, attachment, attachment.filename, options)
99 link_to text, url, html_options
99 link_to text, url, html_options
100 end
100 end
101
101
102 # Generates a link to a SCM revision
102 # Generates a link to a SCM revision
103 # Options:
103 # Options:
104 # * :text - Link text (default to the formatted revision)
104 # * :text - Link text (default to the formatted revision)
105 def link_to_revision(revision, repository, options={})
105 def link_to_revision(revision, repository, options={})
106 if repository.is_a?(Project)
106 if repository.is_a?(Project)
107 repository = repository.repository
107 repository = repository.repository
108 end
108 end
109 text = options.delete(:text) || format_revision(revision)
109 text = options.delete(:text) || format_revision(revision)
110 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
110 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
111 link_to(
111 link_to(
112 h(text),
112 h(text),
113 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
113 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
114 :title => l(:label_revision_id, format_revision(revision))
114 :title => l(:label_revision_id, format_revision(revision))
115 )
115 )
116 end
116 end
117
117
118 # Generates a link to a message
118 # Generates a link to a message
119 def link_to_message(message, options={}, html_options = nil)
119 def link_to_message(message, options={}, html_options = nil)
120 link_to(
120 link_to(
121 message.subject.truncate(60),
121 message.subject.truncate(60),
122 board_message_path(message.board_id, message.parent_id || message.id, {
122 board_message_path(message.board_id, message.parent_id || message.id, {
123 :r => (message.parent_id && message.id),
123 :r => (message.parent_id && message.id),
124 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
124 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
125 }.merge(options)),
125 }.merge(options)),
126 html_options
126 html_options
127 )
127 )
128 end
128 end
129
129
130 # Generates a link to a project if active
130 # Generates a link to a project if active
131 # Examples:
131 # Examples:
132 #
132 #
133 # link_to_project(project) # => link to the specified project overview
133 # link_to_project(project) # => link to the specified project overview
134 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
134 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
135 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
135 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
136 #
136 #
137 def link_to_project(project, options={}, html_options = nil)
137 def link_to_project(project, options={}, html_options = nil)
138 if project.archived?
138 if project.archived?
139 h(project.name)
139 h(project.name)
140 elsif options.key?(:action)
140 elsif options.key?(:action)
141 ActiveSupport::Deprecation.warn "#link_to_project with :action option is deprecated and will be removed in Redmine 3.0."
141 ActiveSupport::Deprecation.warn "#link_to_project with :action option is deprecated and will be removed in Redmine 3.0."
142 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
142 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
143 link_to project.name, url, html_options
143 link_to project.name, url, html_options
144 else
144 else
145 link_to project.name, project_path(project, options), html_options
145 link_to project.name, project_path(project, options), html_options
146 end
146 end
147 end
147 end
148
148
149 # Generates a link to a project settings if active
149 # Generates a link to a project settings if active
150 def link_to_project_settings(project, options={}, html_options=nil)
150 def link_to_project_settings(project, options={}, html_options=nil)
151 if project.active?
151 if project.active?
152 link_to project.name, settings_project_path(project, options), html_options
152 link_to project.name, settings_project_path(project, options), html_options
153 elsif project.archived?
153 elsif project.archived?
154 h(project.name)
154 h(project.name)
155 else
155 else
156 link_to project.name, project_path(project, options), html_options
156 link_to project.name, project_path(project, options), html_options
157 end
157 end
158 end
158 end
159
159
160 # Helper that formats object for html or text rendering
160 # Helper that formats object for html or text rendering
161 def format_object(object, html=true, &block)
161 def format_object(object, html=true, &block)
162 if block_given?
162 if block_given?
163 object = yield object
163 object = yield object
164 end
164 end
165 case object.class.name
165 case object.class.name
166 when 'Array'
166 when 'Array'
167 object.map {|o| format_object(o, html)}.join(', ').html_safe
167 object.map {|o| format_object(o, html)}.join(', ').html_safe
168 when 'Time'
168 when 'Time'
169 format_time(object)
169 format_time(object)
170 when 'Date'
170 when 'Date'
171 format_date(object)
171 format_date(object)
172 when 'Fixnum'
172 when 'Fixnum'
173 object.to_s
173 object.to_s
174 when 'Float'
174 when 'Float'
175 sprintf "%.2f", object
175 sprintf "%.2f", object
176 when 'User'
176 when 'User'
177 html ? link_to_user(object) : object.to_s
177 html ? link_to_user(object) : object.to_s
178 when 'Project'
178 when 'Project'
179 html ? link_to_project(object) : object.to_s
179 html ? link_to_project(object) : object.to_s
180 when 'Version'
180 when 'Version'
181 html ? link_to(object.name, version_path(object)) : object.to_s
181 html ? link_to(object.name, version_path(object)) : object.to_s
182 when 'TrueClass'
182 when 'TrueClass'
183 l(:general_text_Yes)
183 l(:general_text_Yes)
184 when 'FalseClass'
184 when 'FalseClass'
185 l(:general_text_No)
185 l(:general_text_No)
186 when 'Issue'
186 when 'Issue'
187 object.visible? && html ? link_to_issue(object) : "##{object.id}"
187 object.visible? && html ? link_to_issue(object) : "##{object.id}"
188 when 'CustomValue', 'CustomFieldValue'
188 when 'CustomValue', 'CustomFieldValue'
189 if object.custom_field
189 if object.custom_field
190 f = object.custom_field.format.formatted_custom_value(self, object, html)
190 f = object.custom_field.format.formatted_custom_value(self, object, html)
191 if f.nil? || f.is_a?(String)
191 if f.nil? || f.is_a?(String)
192 f
192 f
193 else
193 else
194 format_object(f, html, &block)
194 format_object(f, html, &block)
195 end
195 end
196 else
196 else
197 object.value.to_s
197 object.value.to_s
198 end
198 end
199 else
199 else
200 html ? h(object) : object.to_s
200 html ? h(object) : object.to_s
201 end
201 end
202 end
202 end
203
203
204 def wiki_page_path(page, options={})
204 def wiki_page_path(page, options={})
205 url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options))
205 url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options))
206 end
206 end
207
207
208 def thumbnail_tag(attachment)
208 def thumbnail_tag(attachment)
209 link_to image_tag(thumbnail_path(attachment)),
209 link_to image_tag(thumbnail_path(attachment)),
210 named_attachment_path(attachment, attachment.filename),
210 named_attachment_path(attachment, attachment.filename),
211 :title => attachment.filename
211 :title => attachment.filename
212 end
212 end
213
213
214 def toggle_link(name, id, options={})
214 def toggle_link(name, id, options={})
215 onclick = "$('##{id}').toggle(); "
215 onclick = "$('##{id}').toggle(); "
216 onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ")
216 onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ")
217 onclick << "return false;"
217 onclick << "return false;"
218 link_to(name, "#", :onclick => onclick)
218 link_to(name, "#", :onclick => onclick)
219 end
219 end
220
220
221 def image_to_function(name, function, html_options = {})
221 def image_to_function(name, function, html_options = {})
222 html_options.symbolize_keys!
222 html_options.symbolize_keys!
223 tag(:input, html_options.merge({
223 tag(:input, html_options.merge({
224 :type => "image", :src => image_path(name),
224 :type => "image", :src => image_path(name),
225 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
225 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
226 }))
226 }))
227 end
227 end
228
228
229 def format_activity_title(text)
229 def format_activity_title(text)
230 h(truncate_single_line_raw(text, 100))
230 h(truncate_single_line_raw(text, 100))
231 end
231 end
232
232
233 def format_activity_day(date)
233 def format_activity_day(date)
234 date == User.current.today ? l(:label_today).titleize : format_date(date)
234 date == User.current.today ? l(:label_today).titleize : format_date(date)
235 end
235 end
236
236
237 def format_activity_description(text)
237 def format_activity_description(text)
238 h(text.to_s.truncate(120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
238 h(text.to_s.truncate(120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
239 ).gsub(/[\r\n]+/, "<br />").html_safe
239 ).gsub(/[\r\n]+/, "<br />").html_safe
240 end
240 end
241
241
242 def format_version_name(version)
242 def format_version_name(version)
243 if version.project == @project
243 if version.project == @project
244 h(version)
244 h(version)
245 else
245 else
246 h("#{version.project} - #{version}")
246 h("#{version.project} - #{version}")
247 end
247 end
248 end
248 end
249
249
250 def due_date_distance_in_words(date)
250 def due_date_distance_in_words(date)
251 if date
251 if date
252 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
252 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
253 end
253 end
254 end
254 end
255
255
256 # Renders a tree of projects as a nested set of unordered lists
256 # Renders a tree of projects as a nested set of unordered lists
257 # The given collection may be a subset of the whole project tree
257 # The given collection may be a subset of the whole project tree
258 # (eg. some intermediate nodes are private and can not be seen)
258 # (eg. some intermediate nodes are private and can not be seen)
259 def render_project_nested_lists(projects)
259 def render_project_nested_lists(projects)
260 s = ''
260 s = ''
261 if projects.any?
261 if projects.any?
262 ancestors = []
262 ancestors = []
263 original_project = @project
263 original_project = @project
264 projects.sort_by(&:lft).each do |project|
264 projects.sort_by(&:lft).each do |project|
265 # set the project environment to please macros.
265 # set the project environment to please macros.
266 @project = project
266 @project = project
267 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
267 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
268 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
268 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
269 else
269 else
270 ancestors.pop
270 ancestors.pop
271 s << "</li>"
271 s << "</li>"
272 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
272 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
273 ancestors.pop
273 ancestors.pop
274 s << "</ul></li>\n"
274 s << "</ul></li>\n"
275 end
275 end
276 end
276 end
277 classes = (ancestors.empty? ? 'root' : 'child')
277 classes = (ancestors.empty? ? 'root' : 'child')
278 s << "<li class='#{classes}'><div class='#{classes}'>"
278 s << "<li class='#{classes}'><div class='#{classes}'>"
279 s << h(block_given? ? yield(project) : project.name)
279 s << h(block_given? ? yield(project) : project.name)
280 s << "</div>\n"
280 s << "</div>\n"
281 ancestors << project
281 ancestors << project
282 end
282 end
283 s << ("</li></ul>\n" * ancestors.size)
283 s << ("</li></ul>\n" * ancestors.size)
284 @project = original_project
284 @project = original_project
285 end
285 end
286 s.html_safe
286 s.html_safe
287 end
287 end
288
288
289 def render_page_hierarchy(pages, node=nil, options={})
289 def render_page_hierarchy(pages, node=nil, options={})
290 content = ''
290 content = ''
291 if pages[node]
291 if pages[node]
292 content << "<ul class=\"pages-hierarchy\">\n"
292 content << "<ul class=\"pages-hierarchy\">\n"
293 pages[node].each do |page|
293 pages[node].each do |page|
294 content << "<li>"
294 content << "<li>"
295 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil},
295 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil},
296 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
296 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
297 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
297 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
298 content << "</li>\n"
298 content << "</li>\n"
299 end
299 end
300 content << "</ul>\n"
300 content << "</ul>\n"
301 end
301 end
302 content.html_safe
302 content.html_safe
303 end
303 end
304
304
305 # Renders flash messages
305 # Renders flash messages
306 def render_flash_messages
306 def render_flash_messages
307 s = ''
307 s = ''
308 flash.each do |k,v|
308 flash.each do |k,v|
309 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
309 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
310 end
310 end
311 s.html_safe
311 s.html_safe
312 end
312 end
313
313
314 # Renders tabs and their content
314 # Renders tabs and their content
315 def render_tabs(tabs, selected=params[:tab])
315 def render_tabs(tabs, selected=params[:tab])
316 if tabs.any?
316 if tabs.any?
317 unless tabs.detect {|tab| tab[:name] == selected}
317 unless tabs.detect {|tab| tab[:name] == selected}
318 selected = nil
318 selected = nil
319 end
319 end
320 selected ||= tabs.first[:name]
320 selected ||= tabs.first[:name]
321 render :partial => 'common/tabs', :locals => {:tabs => tabs, :selected_tab => selected}
321 render :partial => 'common/tabs', :locals => {:tabs => tabs, :selected_tab => selected}
322 else
322 else
323 content_tag 'p', l(:label_no_data), :class => "nodata"
323 content_tag 'p', l(:label_no_data), :class => "nodata"
324 end
324 end
325 end
325 end
326
326
327 # Renders the project quick-jump box
327 # Renders the project quick-jump box
328 def render_project_jump_box
328 def render_project_jump_box
329 return unless User.current.logged?
329 return unless User.current.logged?
330 projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq
330 projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq
331 if projects.any?
331 if projects.any?
332 options =
332 options =
333 ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
333 ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
334 '<option value="" disabled="disabled">---</option>').html_safe
334 '<option value="" disabled="disabled">---</option>').html_safe
335
335
336 options << project_tree_options_for_select(projects, :selected => @project) do |p|
336 options << project_tree_options_for_select(projects, :selected => @project) do |p|
337 { :value => project_path(:id => p, :jump => current_menu_item) }
337 { :value => project_path(:id => p, :jump => current_menu_item) }
338 end
338 end
339
339
340 select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
340 select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
341 end
341 end
342 end
342 end
343
343
344 def project_tree_options_for_select(projects, options = {})
344 def project_tree_options_for_select(projects, options = {})
345 s = ''
345 s = ''
346 project_tree(projects) do |project, level|
346 project_tree(projects) do |project, level|
347 name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
347 name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
348 tag_options = {:value => project.id}
348 tag_options = {:value => project.id}
349 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
349 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
350 tag_options[:selected] = 'selected'
350 tag_options[:selected] = 'selected'
351 else
351 else
352 tag_options[:selected] = nil
352 tag_options[:selected] = nil
353 end
353 end
354 tag_options.merge!(yield(project)) if block_given?
354 tag_options.merge!(yield(project)) if block_given?
355 s << content_tag('option', name_prefix + h(project), tag_options)
355 s << content_tag('option', name_prefix + h(project), tag_options)
356 end
356 end
357 s.html_safe
357 s.html_safe
358 end
358 end
359
359
360 # Yields the given block for each project with its level in the tree
360 # Yields the given block for each project with its level in the tree
361 #
361 #
362 # Wrapper for Project#project_tree
362 # Wrapper for Project#project_tree
363 def project_tree(projects, &block)
363 def project_tree(projects, &block)
364 Project.project_tree(projects, &block)
364 Project.project_tree(projects, &block)
365 end
365 end
366
366
367 def principals_check_box_tags(name, principals)
367 def principals_check_box_tags(name, principals)
368 s = ''
368 s = ''
369 principals.each do |principal|
369 principals.each do |principal|
370 s << "<label>#{ check_box_tag name, principal.id, false, :id => nil } #{h principal}</label>\n"
370 s << "<label>#{ check_box_tag name, principal.id, false, :id => nil } #{h principal}</label>\n"
371 end
371 end
372 s.html_safe
372 s.html_safe
373 end
373 end
374
374
375 # Returns a string for users/groups option tags
375 # Returns a string for users/groups option tags
376 def principals_options_for_select(collection, selected=nil)
376 def principals_options_for_select(collection, selected=nil)
377 s = ''
377 s = ''
378 if collection.include?(User.current)
378 if collection.include?(User.current)
379 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
379 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
380 end
380 end
381 groups = ''
381 groups = ''
382 collection.sort.each do |element|
382 collection.sort.each do |element|
383 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) || element.id.to_s == selected
383 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) || element.id.to_s == selected
384 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
384 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
385 end
385 end
386 unless groups.empty?
386 unless groups.empty?
387 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
387 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
388 end
388 end
389 s.html_safe
389 s.html_safe
390 end
390 end
391
391
392 # Options for the new membership projects combo-box
392 # Options for the new membership projects combo-box
393 def options_for_membership_project_select(principal, projects)
393 def options_for_membership_project_select(principal, projects)
394 options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
394 options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
395 options << project_tree_options_for_select(projects) do |p|
395 options << project_tree_options_for_select(projects) do |p|
396 {:disabled => principal.projects.to_a.include?(p)}
396 {:disabled => principal.projects.to_a.include?(p)}
397 end
397 end
398 options
398 options
399 end
399 end
400
400
401 def option_tag(name, text, value, selected=nil, options={})
401 def option_tag(name, text, value, selected=nil, options={})
402 content_tag 'option', value, options.merge(:value => value, :selected => (value == selected))
402 content_tag 'option', value, options.merge(:value => value, :selected => (value == selected))
403 end
403 end
404
404
405 # Truncates and returns the string as a single line
405 # Truncates and returns the string as a single line
406 def truncate_single_line(string, *args)
406 def truncate_single_line(string, *args)
407 ActiveSupport::Deprecation.warn(
407 ActiveSupport::Deprecation.warn(
408 "ApplicationHelper#truncate_single_line is deprecated and will be removed in Rails 4 poring")
408 "ApplicationHelper#truncate_single_line is deprecated and will be removed in Rails 4 poring")
409 # Rails 4 ActionView::Helpers::TextHelper#truncate escapes.
409 # Rails 4 ActionView::Helpers::TextHelper#truncate escapes.
410 # So, result is broken.
410 # So, result is broken.
411 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
411 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
412 end
412 end
413
413
414 def truncate_single_line_raw(string, length)
414 def truncate_single_line_raw(string, length)
415 string.truncate(length).gsub(%r{[\r\n]+}m, ' ')
415 string.truncate(length).gsub(%r{[\r\n]+}m, ' ')
416 end
416 end
417
417
418 # Truncates at line break after 250 characters or options[:length]
418 # Truncates at line break after 250 characters or options[:length]
419 def truncate_lines(string, options={})
419 def truncate_lines(string, options={})
420 length = options[:length] || 250
420 length = options[:length] || 250
421 if string.to_s =~ /\A(.{#{length}}.*?)$/m
421 if string.to_s =~ /\A(.{#{length}}.*?)$/m
422 "#{$1}..."
422 "#{$1}..."
423 else
423 else
424 string
424 string
425 end
425 end
426 end
426 end
427
427
428 def anchor(text)
428 def anchor(text)
429 text.to_s.gsub(' ', '_')
429 text.to_s.gsub(' ', '_')
430 end
430 end
431
431
432 def html_hours(text)
432 def html_hours(text)
433 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
433 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
434 end
434 end
435
435
436 def authoring(created, author, options={})
436 def authoring(created, author, options={})
437 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
437 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
438 end
438 end
439
439
440 def time_tag(time)
440 def time_tag(time)
441 text = distance_of_time_in_words(Time.now, time)
441 text = distance_of_time_in_words(Time.now, time)
442 if @project
442 if @project
443 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
443 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
444 else
444 else
445 content_tag('abbr', text, :title => format_time(time))
445 content_tag('abbr', text, :title => format_time(time))
446 end
446 end
447 end
447 end
448
448
449 def syntax_highlight_lines(name, content)
449 def syntax_highlight_lines(name, content)
450 lines = []
450 lines = []
451 syntax_highlight(name, content).each_line { |line| lines << line }
451 syntax_highlight(name, content).each_line { |line| lines << line }
452 lines
452 lines
453 end
453 end
454
454
455 def syntax_highlight(name, content)
455 def syntax_highlight(name, content)
456 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
456 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
457 end
457 end
458
458
459 def to_path_param(path)
459 def to_path_param(path)
460 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
460 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
461 str.blank? ? nil : str
461 str.blank? ? nil : str
462 end
462 end
463
463
464 def reorder_links(name, url, method = :post)
464 def reorder_links(name, url, method = :post)
465 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
465 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
466 url.merge({"#{name}[move_to]" => 'highest'}),
466 url.merge({"#{name}[move_to]" => 'highest'}),
467 :method => method, :title => l(:label_sort_highest)) +
467 :method => method, :title => l(:label_sort_highest)) +
468 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
468 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
469 url.merge({"#{name}[move_to]" => 'higher'}),
469 url.merge({"#{name}[move_to]" => 'higher'}),
470 :method => method, :title => l(:label_sort_higher)) +
470 :method => method, :title => l(:label_sort_higher)) +
471 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
471 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
472 url.merge({"#{name}[move_to]" => 'lower'}),
472 url.merge({"#{name}[move_to]" => 'lower'}),
473 :method => method, :title => l(:label_sort_lower)) +
473 :method => method, :title => l(:label_sort_lower)) +
474 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
474 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
475 url.merge({"#{name}[move_to]" => 'lowest'}),
475 url.merge({"#{name}[move_to]" => 'lowest'}),
476 :method => method, :title => l(:label_sort_lowest))
476 :method => method, :title => l(:label_sort_lowest))
477 end
477 end
478
478
479 def breadcrumb(*args)
479 def breadcrumb(*args)
480 elements = args.flatten
480 elements = args.flatten
481 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
481 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
482 end
482 end
483
483
484 def other_formats_links(&block)
484 def other_formats_links(&block)
485 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
485 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
486 yield Redmine::Views::OtherFormatsBuilder.new(self)
486 yield Redmine::Views::OtherFormatsBuilder.new(self)
487 concat('</p>'.html_safe)
487 concat('</p>'.html_safe)
488 end
488 end
489
489
490 def page_header_title
490 def page_header_title
491 if @project.nil? || @project.new_record?
491 if @project.nil? || @project.new_record?
492 h(Setting.app_title)
492 h(Setting.app_title)
493 else
493 else
494 b = []
494 b = []
495 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
495 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
496 if ancestors.any?
496 if ancestors.any?
497 root = ancestors.shift
497 root = ancestors.shift
498 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
498 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
499 if ancestors.size > 2
499 if ancestors.size > 2
500 b << "\xe2\x80\xa6"
500 b << "\xe2\x80\xa6"
501 ancestors = ancestors[-2, 2]
501 ancestors = ancestors[-2, 2]
502 end
502 end
503 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
503 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
504 end
504 end
505 b << h(@project)
505 b << h(@project)
506 b.join(" \xc2\xbb ").html_safe
506 b.join(" \xc2\xbb ").html_safe
507 end
507 end
508 end
508 end
509
509
510 # Returns a h2 tag and sets the html title with the given arguments
510 # Returns a h2 tag and sets the html title with the given arguments
511 def title(*args)
511 def title(*args)
512 strings = args.map do |arg|
512 strings = args.map do |arg|
513 if arg.is_a?(Array) && arg.size >= 2
513 if arg.is_a?(Array) && arg.size >= 2
514 link_to(*arg)
514 link_to(*arg)
515 else
515 else
516 h(arg.to_s)
516 h(arg.to_s)
517 end
517 end
518 end
518 end
519 html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
519 html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
520 content_tag('h2', strings.join(' &#187; ').html_safe)
520 content_tag('h2', strings.join(' &#187; ').html_safe)
521 end
521 end
522
522
523 # Sets the html title
523 # Sets the html title
524 # Returns the html title when called without arguments
524 # Returns the html title when called without arguments
525 # Current project name and app_title and automatically appended
525 # Current project name and app_title and automatically appended
526 # Exemples:
526 # Exemples:
527 # html_title 'Foo', 'Bar'
527 # html_title 'Foo', 'Bar'
528 # html_title # => 'Foo - Bar - My Project - Redmine'
528 # html_title # => 'Foo - Bar - My Project - Redmine'
529 def html_title(*args)
529 def html_title(*args)
530 if args.empty?
530 if args.empty?
531 title = @html_title || []
531 title = @html_title || []
532 title << @project.name if @project
532 title << @project.name if @project
533 title << Setting.app_title unless Setting.app_title == title.last
533 title << Setting.app_title unless Setting.app_title == title.last
534 title.reject(&:blank?).join(' - ')
534 title.reject(&:blank?).join(' - ')
535 else
535 else
536 @html_title ||= []
536 @html_title ||= []
537 @html_title += args
537 @html_title += args
538 end
538 end
539 end
539 end
540
540
541 # Returns the theme, controller name, and action as css classes for the
541 # Returns the theme, controller name, and action as css classes for the
542 # HTML body.
542 # HTML body.
543 def body_css_classes
543 def body_css_classes
544 css = []
544 css = []
545 if theme = Redmine::Themes.theme(Setting.ui_theme)
545 if theme = Redmine::Themes.theme(Setting.ui_theme)
546 css << 'theme-' + theme.name
546 css << 'theme-' + theme.name
547 end
547 end
548
548
549 css << 'project-' + @project.identifier if @project && @project.identifier.present?
549 css << 'project-' + @project.identifier if @project && @project.identifier.present?
550 css << 'controller-' + controller_name
550 css << 'controller-' + controller_name
551 css << 'action-' + action_name
551 css << 'action-' + action_name
552 css.join(' ')
552 css.join(' ')
553 end
553 end
554
554
555 def accesskey(s)
555 def accesskey(s)
556 @used_accesskeys ||= []
556 @used_accesskeys ||= []
557 key = Redmine::AccessKeys.key_for(s)
557 key = Redmine::AccessKeys.key_for(s)
558 return nil if @used_accesskeys.include?(key)
558 return nil if @used_accesskeys.include?(key)
559 @used_accesskeys << key
559 @used_accesskeys << key
560 key
560 key
561 end
561 end
562
562
563 # Formats text according to system settings.
563 # Formats text according to system settings.
564 # 2 ways to call this method:
564 # 2 ways to call this method:
565 # * with a String: textilizable(text, options)
565 # * with a String: textilizable(text, options)
566 # * with an object and one of its attribute: textilizable(issue, :description, options)
566 # * with an object and one of its attribute: textilizable(issue, :description, options)
567 def textilizable(*args)
567 def textilizable(*args)
568 options = args.last.is_a?(Hash) ? args.pop : {}
568 options = args.last.is_a?(Hash) ? args.pop : {}
569 case args.size
569 case args.size
570 when 1
570 when 1
571 obj = options[:object]
571 obj = options[:object]
572 text = args.shift
572 text = args.shift
573 when 2
573 when 2
574 obj = args.shift
574 obj = args.shift
575 attr = args.shift
575 attr = args.shift
576 text = obj.send(attr).to_s
576 text = obj.send(attr).to_s
577 else
577 else
578 raise ArgumentError, 'invalid arguments to textilizable'
578 raise ArgumentError, 'invalid arguments to textilizable'
579 end
579 end
580 return '' if text.blank?
580 return '' if text.blank?
581 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
581 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
582 only_path = options.delete(:only_path) == false ? false : true
582 only_path = options.delete(:only_path) == false ? false : true
583
583
584 text = text.dup
584 text = text.dup
585 macros = catch_macros(text)
585 macros = catch_macros(text)
586 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
586 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
587
587
588 @parsed_headings = []
588 @parsed_headings = []
589 @heading_anchors = {}
589 @heading_anchors = {}
590 @current_section = 0 if options[:edit_section_links]
590 @current_section = 0 if options[:edit_section_links]
591
591
592 parse_sections(text, project, obj, attr, only_path, options)
592 parse_sections(text, project, obj, attr, only_path, options)
593 text = parse_non_pre_blocks(text, obj, macros) do |text|
593 text = parse_non_pre_blocks(text, obj, macros) do |text|
594 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
594 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
595 send method_name, text, project, obj, attr, only_path, options
595 send method_name, text, project, obj, attr, only_path, options
596 end
596 end
597 end
597 end
598 parse_headings(text, project, obj, attr, only_path, options)
598 parse_headings(text, project, obj, attr, only_path, options)
599
599
600 if @parsed_headings.any?
600 if @parsed_headings.any?
601 replace_toc(text, @parsed_headings)
601 replace_toc(text, @parsed_headings)
602 end
602 end
603
603
604 text.html_safe
604 text.html_safe
605 end
605 end
606
606
607 def parse_non_pre_blocks(text, obj, macros)
607 def parse_non_pre_blocks(text, obj, macros)
608 s = StringScanner.new(text)
608 s = StringScanner.new(text)
609 tags = []
609 tags = []
610 parsed = ''
610 parsed = ''
611 while !s.eos?
611 while !s.eos?
612 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
612 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
613 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
613 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
614 if tags.empty?
614 if tags.empty?
615 yield text
615 yield text
616 inject_macros(text, obj, macros) if macros.any?
616 inject_macros(text, obj, macros) if macros.any?
617 else
617 else
618 inject_macros(text, obj, macros, false) if macros.any?
618 inject_macros(text, obj, macros, false) if macros.any?
619 end
619 end
620 parsed << text
620 parsed << text
621 if tag
621 if tag
622 if closing
622 if closing
623 if tags.last == tag.downcase
623 if tags.last == tag.downcase
624 tags.pop
624 tags.pop
625 end
625 end
626 else
626 else
627 tags << tag.downcase
627 tags << tag.downcase
628 end
628 end
629 parsed << full_tag
629 parsed << full_tag
630 end
630 end
631 end
631 end
632 # Close any non closing tags
632 # Close any non closing tags
633 while tag = tags.pop
633 while tag = tags.pop
634 parsed << "</#{tag}>"
634 parsed << "</#{tag}>"
635 end
635 end
636 parsed
636 parsed
637 end
637 end
638
638
639 def parse_inline_attachments(text, project, obj, attr, only_path, options)
639 def parse_inline_attachments(text, project, obj, attr, only_path, options)
640 # when using an image link, try to use an attachment, if possible
640 # when using an image link, try to use an attachment, if possible
641 attachments = options[:attachments] || []
641 attachments = options[:attachments] || []
642 attachments += obj.attachments if obj.respond_to?(:attachments)
642 attachments += obj.attachments if obj.respond_to?(:attachments)
643 if attachments.present?
643 if attachments.present?
644 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
644 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
645 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
645 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
646 # search for the picture in attachments
646 # search for the picture in attachments
647 if found = Attachment.latest_attach(attachments, filename)
647 if found = Attachment.latest_attach(attachments, filename)
648 image_url = download_named_attachment_path(found, found.filename, :only_path => only_path)
648 image_url = download_named_attachment_path(found, found.filename, :only_path => only_path)
649 desc = found.description.to_s.gsub('"', '')
649 desc = found.description.to_s.gsub('"', '')
650 if !desc.blank? && alttext.blank?
650 if !desc.blank? && alttext.blank?
651 alt = " title=\"#{desc}\" alt=\"#{desc}\""
651 alt = " title=\"#{desc}\" alt=\"#{desc}\""
652 end
652 end
653 "src=\"#{image_url}\"#{alt}"
653 "src=\"#{image_url}\"#{alt}"
654 else
654 else
655 m
655 m
656 end
656 end
657 end
657 end
658 end
658 end
659 end
659 end
660
660
661 # Wiki links
661 # Wiki links
662 #
662 #
663 # Examples:
663 # Examples:
664 # [[mypage]]
664 # [[mypage]]
665 # [[mypage|mytext]]
665 # [[mypage|mytext]]
666 # wiki links can refer other project wikis, using project name or identifier:
666 # wiki links can refer other project wikis, using project name or identifier:
667 # [[project:]] -> wiki starting page
667 # [[project:]] -> wiki starting page
668 # [[project:|mytext]]
668 # [[project:|mytext]]
669 # [[project:mypage]]
669 # [[project:mypage]]
670 # [[project:mypage|mytext]]
670 # [[project:mypage|mytext]]
671 def parse_wiki_links(text, project, obj, attr, only_path, options)
671 def parse_wiki_links(text, project, obj, attr, only_path, options)
672 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
672 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
673 link_project = project
673 link_project = project
674 esc, all, page, title = $1, $2, $3, $5
674 esc, all, page, title = $1, $2, $3, $5
675 if esc.nil?
675 if esc.nil?
676 if page =~ /^([^\:]+)\:(.*)$/
676 if page =~ /^([^\:]+)\:(.*)$/
677 identifier, page = $1, $2
677 identifier, page = $1, $2
678 link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
678 link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
679 title ||= identifier if page.blank?
679 title ||= identifier if page.blank?
680 end
680 end
681
681
682 if link_project && link_project.wiki
682 if link_project && link_project.wiki
683 # extract anchor
683 # extract anchor
684 anchor = nil
684 anchor = nil
685 if page =~ /^(.+?)\#(.+)$/
685 if page =~ /^(.+?)\#(.+)$/
686 page, anchor = $1, $2
686 page, anchor = $1, $2
687 end
687 end
688 anchor = sanitize_anchor_name(anchor) if anchor.present?
688 anchor = sanitize_anchor_name(anchor) if anchor.present?
689 # check if page exists
689 # check if page exists
690 wiki_page = link_project.wiki.find_page(page)
690 wiki_page = link_project.wiki.find_page(page)
691 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
691 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
692 "##{anchor}"
692 "##{anchor}"
693 else
693 else
694 case options[:wiki_links]
694 case options[:wiki_links]
695 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
695 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
696 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
696 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
697 else
697 else
698 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
698 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
699 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
699 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
700 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
700 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
701 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
701 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
702 end
702 end
703 end
703 end
704 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
704 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
705 else
705 else
706 # project or wiki doesn't exist
706 # project or wiki doesn't exist
707 all
707 all
708 end
708 end
709 else
709 else
710 all
710 all
711 end
711 end
712 end
712 end
713 end
713 end
714
714
715 # Redmine links
715 # Redmine links
716 #
716 #
717 # Examples:
717 # Examples:
718 # Issues:
718 # Issues:
719 # #52 -> Link to issue #52
719 # #52 -> Link to issue #52
720 # Changesets:
720 # Changesets:
721 # r52 -> Link to revision 52
721 # r52 -> Link to revision 52
722 # commit:a85130f -> Link to scmid starting with a85130f
722 # commit:a85130f -> Link to scmid starting with a85130f
723 # Documents:
723 # Documents:
724 # document#17 -> Link to document with id 17
724 # document#17 -> Link to document with id 17
725 # document:Greetings -> Link to the document with title "Greetings"
725 # document:Greetings -> Link to the document with title "Greetings"
726 # document:"Some document" -> Link to the document with title "Some document"
726 # document:"Some document" -> Link to the document with title "Some document"
727 # Versions:
727 # Versions:
728 # version#3 -> Link to version with id 3
728 # version#3 -> Link to version with id 3
729 # version:1.0.0 -> Link to version named "1.0.0"
729 # version:1.0.0 -> Link to version named "1.0.0"
730 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
730 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
731 # Attachments:
731 # Attachments:
732 # attachment:file.zip -> Link to the attachment of the current object named file.zip
732 # attachment:file.zip -> Link to the attachment of the current object named file.zip
733 # Source files:
733 # Source files:
734 # source:some/file -> Link to the file located at /some/file in the project's repository
734 # source:some/file -> Link to the file located at /some/file in the project's repository
735 # source:some/file@52 -> Link to the file's revision 52
735 # source:some/file@52 -> Link to the file's revision 52
736 # source:some/file#L120 -> Link to line 120 of the file
736 # source:some/file#L120 -> Link to line 120 of the file
737 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
737 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
738 # export:some/file -> Force the download of the file
738 # export:some/file -> Force the download of the file
739 # Forum messages:
739 # Forum messages:
740 # message#1218 -> Link to message with id 1218
740 # message#1218 -> Link to message with id 1218
741 # Projects:
741 # Projects:
742 # project:someproject -> Link to project named "someproject"
742 # project:someproject -> Link to project named "someproject"
743 # project#3 -> Link to project with id 3
743 # project#3 -> Link to project with id 3
744 #
744 #
745 # Links can refer other objects from other projects, using project identifier:
745 # Links can refer other objects from other projects, using project identifier:
746 # identifier:r52
746 # identifier:r52
747 # identifier:document:"Some document"
747 # identifier:document:"Some document"
748 # identifier:version:1.0.0
748 # identifier:version:1.0.0
749 # identifier:source:some/file
749 # identifier:source:some/file
750 def parse_redmine_links(text, default_project, obj, attr, only_path, options)
750 def parse_redmine_links(text, default_project, obj, attr, only_path, options)
751 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|
751 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|
752 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
752 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
753 link = nil
753 link = nil
754 project = default_project
754 project = default_project
755 if project_identifier
755 if project_identifier
756 project = Project.visible.find_by_identifier(project_identifier)
756 project = Project.visible.find_by_identifier(project_identifier)
757 end
757 end
758 if esc.nil?
758 if esc.nil?
759 if prefix.nil? && sep == 'r'
759 if prefix.nil? && sep == 'r'
760 if project
760 if project
761 repository = nil
761 repository = nil
762 if repo_identifier
762 if repo_identifier
763 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
763 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
764 else
764 else
765 repository = project.repository
765 repository = project.repository
766 end
766 end
767 # project.changesets.visible raises an SQL error because of a double join on repositories
767 # project.changesets.visible raises an SQL error because of a double join on repositories
768 if repository &&
768 if repository &&
769 (changeset = Changeset.visible.
769 (changeset = Changeset.visible.
770 find_by_repository_id_and_revision(repository.id, identifier))
770 find_by_repository_id_and_revision(repository.id, identifier))
771 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"),
771 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"),
772 {:only_path => only_path, :controller => 'repositories',
772 {:only_path => only_path, :controller => 'repositories',
773 :action => 'revision', :id => project,
773 :action => 'revision', :id => project,
774 :repository_id => repository.identifier_param,
774 :repository_id => repository.identifier_param,
775 :rev => changeset.revision},
775 :rev => changeset.revision},
776 :class => 'changeset',
776 :class => 'changeset',
777 :title => truncate_single_line_raw(changeset.comments, 100))
777 :title => truncate_single_line_raw(changeset.comments, 100))
778 end
778 end
779 end
779 end
780 elsif sep == '#'
780 elsif sep == '#'
781 oid = identifier.to_i
781 oid = identifier.to_i
782 case prefix
782 case prefix
783 when nil
783 when nil
784 if oid.to_s == identifier &&
784 if oid.to_s == identifier &&
785 issue = Issue.visible.includes(:status).find_by_id(oid)
785 issue = Issue.visible.includes(:status).find_by_id(oid)
786 anchor = comment_id ? "note-#{comment_id}" : nil
786 anchor = comment_id ? "note-#{comment_id}" : nil
787 link = link_to(h("##{oid}#{comment_suffix}"),
787 link = link_to(h("##{oid}#{comment_suffix}"),
788 {:only_path => only_path, :controller => 'issues',
788 {:only_path => only_path, :controller => 'issues',
789 :action => 'show', :id => oid, :anchor => anchor},
789 :action => 'show', :id => oid, :anchor => anchor},
790 :class => issue.css_classes,
790 :class => issue.css_classes,
791 :title => "#{issue.subject.truncate(100)} (#{issue.status.name})")
791 :title => "#{issue.subject.truncate(100)} (#{issue.status.name})")
792 end
792 end
793 when 'document'
793 when 'document'
794 if document = Document.visible.find_by_id(oid)
794 if document = Document.visible.find_by_id(oid)
795 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
795 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
796 :class => 'document'
796 :class => 'document'
797 end
797 end
798 when 'version'
798 when 'version'
799 if version = Version.visible.find_by_id(oid)
799 if version = Version.visible.find_by_id(oid)
800 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
800 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
801 :class => 'version'
801 :class => 'version'
802 end
802 end
803 when 'message'
803 when 'message'
804 if message = Message.visible.includes(:parent).find_by_id(oid)
804 if message = Message.visible.includes(:parent).find_by_id(oid)
805 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
805 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
806 end
806 end
807 when 'forum'
807 when 'forum'
808 if board = Board.visible.find_by_id(oid)
808 if board = Board.visible.find_by_id(oid)
809 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
809 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
810 :class => 'board'
810 :class => 'board'
811 end
811 end
812 when 'news'
812 when 'news'
813 if news = News.visible.find_by_id(oid)
813 if news = News.visible.find_by_id(oid)
814 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
814 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
815 :class => 'news'
815 :class => 'news'
816 end
816 end
817 when 'project'
817 when 'project'
818 if p = Project.visible.find_by_id(oid)
818 if p = Project.visible.find_by_id(oid)
819 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
819 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
820 end
820 end
821 end
821 end
822 elsif sep == ':'
822 elsif sep == ':'
823 # removes the double quotes if any
823 # removes the double quotes if any
824 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
824 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
825 name = CGI.unescapeHTML(name)
825 case prefix
826 case prefix
826 when 'document'
827 when 'document'
827 if project && document = project.documents.visible.find_by_title(name)
828 if project && document = project.documents.visible.find_by_title(name)
828 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
829 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
829 :class => 'document'
830 :class => 'document'
830 end
831 end
831 when 'version'
832 when 'version'
832 if project && version = project.versions.visible.find_by_name(name)
833 if project && version = project.versions.visible.find_by_name(name)
833 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
834 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
834 :class => 'version'
835 :class => 'version'
835 end
836 end
836 when 'forum'
837 when 'forum'
837 if project && board = project.boards.visible.find_by_name(name)
838 if project && board = project.boards.visible.find_by_name(name)
838 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
839 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
839 :class => 'board'
840 :class => 'board'
840 end
841 end
841 when 'news'
842 when 'news'
842 if project && news = project.news.visible.find_by_title(name)
843 if project && news = project.news.visible.find_by_title(name)
843 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
844 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
844 :class => 'news'
845 :class => 'news'
845 end
846 end
846 when 'commit', 'source', 'export'
847 when 'commit', 'source', 'export'
847 if project
848 if project
848 repository = nil
849 repository = nil
849 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
850 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
850 repo_prefix, repo_identifier, name = $1, $2, $3
851 repo_prefix, repo_identifier, name = $1, $2, $3
851 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
852 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
852 else
853 else
853 repository = project.repository
854 repository = project.repository
854 end
855 end
855 if prefix == 'commit'
856 if prefix == 'commit'
856 if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
857 if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
857 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},
858 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},
858 :class => 'changeset',
859 :class => 'changeset',
859 :title => truncate_single_line_raw(changeset.comments, 100)
860 :title => truncate_single_line_raw(changeset.comments, 100)
860 end
861 end
861 else
862 else
862 if repository && User.current.allowed_to?(:browse_repository, project)
863 if repository && User.current.allowed_to?(:browse_repository, project)
863 name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
864 name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
864 path, rev, anchor = $1, $3, $5
865 path, rev, anchor = $1, $3, $5
865 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param,
866 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param,
866 :path => to_path_param(path),
867 :path => to_path_param(path),
867 :rev => rev,
868 :rev => rev,
868 :anchor => anchor},
869 :anchor => anchor},
869 :class => (prefix == 'export' ? 'source download' : 'source')
870 :class => (prefix == 'export' ? 'source download' : 'source')
870 end
871 end
871 end
872 end
872 repo_prefix = nil
873 repo_prefix = nil
873 end
874 end
874 when 'attachment'
875 when 'attachment'
875 attachments = options[:attachments] || []
876 attachments = options[:attachments] || []
876 attachments += obj.attachments if obj.respond_to?(:attachments)
877 attachments += obj.attachments if obj.respond_to?(:attachments)
877 if attachments && attachment = Attachment.latest_attach(attachments, name)
878 if attachments && attachment = Attachment.latest_attach(attachments, name)
878 link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
879 link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
879 end
880 end
880 when 'project'
881 when 'project'
881 if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
882 if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
882 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
883 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
883 end
884 end
884 end
885 end
885 end
886 end
886 end
887 end
887 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
888 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
888 end
889 end
889 end
890 end
890
891
891 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
892 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
892
893
893 def parse_sections(text, project, obj, attr, only_path, options)
894 def parse_sections(text, project, obj, attr, only_path, options)
894 return unless options[:edit_section_links]
895 return unless options[:edit_section_links]
895 text.gsub!(HEADING_RE) do
896 text.gsub!(HEADING_RE) do
896 heading = $1
897 heading = $1
897 @current_section += 1
898 @current_section += 1
898 if @current_section > 1
899 if @current_section > 1
899 content_tag('div',
900 content_tag('div',
900 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
901 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
901 :class => 'contextual',
902 :class => 'contextual',
902 :title => l(:button_edit_section),
903 :title => l(:button_edit_section),
903 :id => "section-#{@current_section}") + heading.html_safe
904 :id => "section-#{@current_section}") + heading.html_safe
904 else
905 else
905 heading
906 heading
906 end
907 end
907 end
908 end
908 end
909 end
909
910
910 # Headings and TOC
911 # Headings and TOC
911 # Adds ids and links to headings unless options[:headings] is set to false
912 # Adds ids and links to headings unless options[:headings] is set to false
912 def parse_headings(text, project, obj, attr, only_path, options)
913 def parse_headings(text, project, obj, attr, only_path, options)
913 return if options[:headings] == false
914 return if options[:headings] == false
914
915
915 text.gsub!(HEADING_RE) do
916 text.gsub!(HEADING_RE) do
916 level, attrs, content = $2.to_i, $3, $4
917 level, attrs, content = $2.to_i, $3, $4
917 item = strip_tags(content).strip
918 item = strip_tags(content).strip
918 anchor = sanitize_anchor_name(item)
919 anchor = sanitize_anchor_name(item)
919 # used for single-file wiki export
920 # used for single-file wiki export
920 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
921 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
921 @heading_anchors[anchor] ||= 0
922 @heading_anchors[anchor] ||= 0
922 idx = (@heading_anchors[anchor] += 1)
923 idx = (@heading_anchors[anchor] += 1)
923 if idx > 1
924 if idx > 1
924 anchor = "#{anchor}-#{idx}"
925 anchor = "#{anchor}-#{idx}"
925 end
926 end
926 @parsed_headings << [level, anchor, item]
927 @parsed_headings << [level, anchor, item]
927 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
928 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
928 end
929 end
929 end
930 end
930
931
931 MACROS_RE = /(
932 MACROS_RE = /(
932 (!)? # escaping
933 (!)? # escaping
933 (
934 (
934 \{\{ # opening tag
935 \{\{ # opening tag
935 ([\w]+) # macro name
936 ([\w]+) # macro name
936 (\(([^\n\r]*?)\))? # optional arguments
937 (\(([^\n\r]*?)\))? # optional arguments
937 ([\n\r].*?[\n\r])? # optional block of text
938 ([\n\r].*?[\n\r])? # optional block of text
938 \}\} # closing tag
939 \}\} # closing tag
939 )
940 )
940 )/mx unless const_defined?(:MACROS_RE)
941 )/mx unless const_defined?(:MACROS_RE)
941
942
942 MACRO_SUB_RE = /(
943 MACRO_SUB_RE = /(
943 \{\{
944 \{\{
944 macro\((\d+)\)
945 macro\((\d+)\)
945 \}\}
946 \}\}
946 )/x unless const_defined?(:MACRO_SUB_RE)
947 )/x unless const_defined?(:MACRO_SUB_RE)
947
948
948 # Extracts macros from text
949 # Extracts macros from text
949 def catch_macros(text)
950 def catch_macros(text)
950 macros = {}
951 macros = {}
951 text.gsub!(MACROS_RE) do
952 text.gsub!(MACROS_RE) do
952 all, macro = $1, $4.downcase
953 all, macro = $1, $4.downcase
953 if macro_exists?(macro) || all =~ MACRO_SUB_RE
954 if macro_exists?(macro) || all =~ MACRO_SUB_RE
954 index = macros.size
955 index = macros.size
955 macros[index] = all
956 macros[index] = all
956 "{{macro(#{index})}}"
957 "{{macro(#{index})}}"
957 else
958 else
958 all
959 all
959 end
960 end
960 end
961 end
961 macros
962 macros
962 end
963 end
963
964
964 # Executes and replaces macros in text
965 # Executes and replaces macros in text
965 def inject_macros(text, obj, macros, execute=true)
966 def inject_macros(text, obj, macros, execute=true)
966 text.gsub!(MACRO_SUB_RE) do
967 text.gsub!(MACRO_SUB_RE) do
967 all, index = $1, $2.to_i
968 all, index = $1, $2.to_i
968 orig = macros.delete(index)
969 orig = macros.delete(index)
969 if execute && orig && orig =~ MACROS_RE
970 if execute && orig && orig =~ MACROS_RE
970 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
971 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
971 if esc.nil?
972 if esc.nil?
972 h(exec_macro(macro, obj, args, block) || all)
973 h(exec_macro(macro, obj, args, block) || all)
973 else
974 else
974 h(all)
975 h(all)
975 end
976 end
976 elsif orig
977 elsif orig
977 h(orig)
978 h(orig)
978 else
979 else
979 h(all)
980 h(all)
980 end
981 end
981 end
982 end
982 end
983 end
983
984
984 TOC_RE = /<p>\{\{((<|&lt;)|(>|&gt;))?toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
985 TOC_RE = /<p>\{\{((<|&lt;)|(>|&gt;))?toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
985
986
986 # Renders the TOC with given headings
987 # Renders the TOC with given headings
987 def replace_toc(text, headings)
988 def replace_toc(text, headings)
988 text.gsub!(TOC_RE) do
989 text.gsub!(TOC_RE) do
989 left_align, right_align = $2, $3
990 left_align, right_align = $2, $3
990 # Keep only the 4 first levels
991 # Keep only the 4 first levels
991 headings = headings.select{|level, anchor, item| level <= 4}
992 headings = headings.select{|level, anchor, item| level <= 4}
992 if headings.empty?
993 if headings.empty?
993 ''
994 ''
994 else
995 else
995 div_class = 'toc'
996 div_class = 'toc'
996 div_class << ' right' if right_align
997 div_class << ' right' if right_align
997 div_class << ' left' if left_align
998 div_class << ' left' if left_align
998 out = "<ul class=\"#{div_class}\"><li>"
999 out = "<ul class=\"#{div_class}\"><li>"
999 root = headings.map(&:first).min
1000 root = headings.map(&:first).min
1000 current = root
1001 current = root
1001 started = false
1002 started = false
1002 headings.each do |level, anchor, item|
1003 headings.each do |level, anchor, item|
1003 if level > current
1004 if level > current
1004 out << '<ul><li>' * (level - current)
1005 out << '<ul><li>' * (level - current)
1005 elsif level < current
1006 elsif level < current
1006 out << "</li></ul>\n" * (current - level) + "</li><li>"
1007 out << "</li></ul>\n" * (current - level) + "</li><li>"
1007 elsif started
1008 elsif started
1008 out << '</li><li>'
1009 out << '</li><li>'
1009 end
1010 end
1010 out << "<a href=\"##{anchor}\">#{item}</a>"
1011 out << "<a href=\"##{anchor}\">#{item}</a>"
1011 current = level
1012 current = level
1012 started = true
1013 started = true
1013 end
1014 end
1014 out << '</li></ul>' * (current - root)
1015 out << '</li></ul>' * (current - root)
1015 out << '</li></ul>'
1016 out << '</li></ul>'
1016 end
1017 end
1017 end
1018 end
1018 end
1019 end
1019
1020
1020 # Same as Rails' simple_format helper without using paragraphs
1021 # Same as Rails' simple_format helper without using paragraphs
1021 def simple_format_without_paragraph(text)
1022 def simple_format_without_paragraph(text)
1022 text.to_s.
1023 text.to_s.
1023 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
1024 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
1024 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
1025 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
1025 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
1026 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
1026 html_safe
1027 html_safe
1027 end
1028 end
1028
1029
1029 def lang_options_for_select(blank=true)
1030 def lang_options_for_select(blank=true)
1030 (blank ? [["(auto)", ""]] : []) + languages_options
1031 (blank ? [["(auto)", ""]] : []) + languages_options
1031 end
1032 end
1032
1033
1033 def label_tag_for(name, option_tags = nil, options = {})
1034 def label_tag_for(name, option_tags = nil, options = {})
1034 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
1035 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
1035 content_tag("label", label_text)
1036 content_tag("label", label_text)
1036 end
1037 end
1037
1038
1038 def labelled_form_for(*args, &proc)
1039 def labelled_form_for(*args, &proc)
1039 args << {} unless args.last.is_a?(Hash)
1040 args << {} unless args.last.is_a?(Hash)
1040 options = args.last
1041 options = args.last
1041 if args.first.is_a?(Symbol)
1042 if args.first.is_a?(Symbol)
1042 options.merge!(:as => args.shift)
1043 options.merge!(:as => args.shift)
1043 end
1044 end
1044 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1045 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1045 form_for(*args, &proc)
1046 form_for(*args, &proc)
1046 end
1047 end
1047
1048
1048 def labelled_fields_for(*args, &proc)
1049 def labelled_fields_for(*args, &proc)
1049 args << {} unless args.last.is_a?(Hash)
1050 args << {} unless args.last.is_a?(Hash)
1050 options = args.last
1051 options = args.last
1051 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1052 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1052 fields_for(*args, &proc)
1053 fields_for(*args, &proc)
1053 end
1054 end
1054
1055
1055 def labelled_remote_form_for(*args, &proc)
1056 def labelled_remote_form_for(*args, &proc)
1056 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
1057 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
1057 args << {} unless args.last.is_a?(Hash)
1058 args << {} unless args.last.is_a?(Hash)
1058 options = args.last
1059 options = args.last
1059 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
1060 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
1060 form_for(*args, &proc)
1061 form_for(*args, &proc)
1061 end
1062 end
1062
1063
1063 def error_messages_for(*objects)
1064 def error_messages_for(*objects)
1064 html = ""
1065 html = ""
1065 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
1066 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
1066 errors = objects.map {|o| o.errors.full_messages}.flatten
1067 errors = objects.map {|o| o.errors.full_messages}.flatten
1067 if errors.any?
1068 if errors.any?
1068 html << "<div id='errorExplanation'><ul>\n"
1069 html << "<div id='errorExplanation'><ul>\n"
1069 errors.each do |error|
1070 errors.each do |error|
1070 html << "<li>#{h error}</li>\n"
1071 html << "<li>#{h error}</li>\n"
1071 end
1072 end
1072 html << "</ul></div>\n"
1073 html << "</ul></div>\n"
1073 end
1074 end
1074 html.html_safe
1075 html.html_safe
1075 end
1076 end
1076
1077
1077 def delete_link(url, options={})
1078 def delete_link(url, options={})
1078 options = {
1079 options = {
1079 :method => :delete,
1080 :method => :delete,
1080 :data => {:confirm => l(:text_are_you_sure)},
1081 :data => {:confirm => l(:text_are_you_sure)},
1081 :class => 'icon icon-del'
1082 :class => 'icon icon-del'
1082 }.merge(options)
1083 }.merge(options)
1083
1084
1084 link_to l(:button_delete), url, options
1085 link_to l(:button_delete), url, options
1085 end
1086 end
1086
1087
1087 def preview_link(url, form, target='preview', options={})
1088 def preview_link(url, form, target='preview', options={})
1088 content_tag 'a', l(:label_preview), {
1089 content_tag 'a', l(:label_preview), {
1089 :href => "#",
1090 :href => "#",
1090 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1091 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1091 :accesskey => accesskey(:preview)
1092 :accesskey => accesskey(:preview)
1092 }.merge(options)
1093 }.merge(options)
1093 end
1094 end
1094
1095
1095 def link_to_function(name, function, html_options={})
1096 def link_to_function(name, function, html_options={})
1096 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1097 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1097 end
1098 end
1098
1099
1099 # Helper to render JSON in views
1100 # Helper to render JSON in views
1100 def raw_json(arg)
1101 def raw_json(arg)
1101 arg.to_json.to_s.gsub('/', '\/').html_safe
1102 arg.to_json.to_s.gsub('/', '\/').html_safe
1102 end
1103 end
1103
1104
1104 def back_url
1105 def back_url
1105 url = params[:back_url]
1106 url = params[:back_url]
1106 if url.nil? && referer = request.env['HTTP_REFERER']
1107 if url.nil? && referer = request.env['HTTP_REFERER']
1107 url = CGI.unescape(referer.to_s)
1108 url = CGI.unescape(referer.to_s)
1108 end
1109 end
1109 url
1110 url
1110 end
1111 end
1111
1112
1112 def back_url_hidden_field_tag
1113 def back_url_hidden_field_tag
1113 url = back_url
1114 url = back_url
1114 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1115 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1115 end
1116 end
1116
1117
1117 def check_all_links(form_name)
1118 def check_all_links(form_name)
1118 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1119 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1119 " | ".html_safe +
1120 " | ".html_safe +
1120 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1121 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1121 end
1122 end
1122
1123
1123 def progress_bar(pcts, options={})
1124 def progress_bar(pcts, options={})
1124 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1125 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1125 pcts = pcts.collect(&:round)
1126 pcts = pcts.collect(&:round)
1126 pcts[1] = pcts[1] - pcts[0]
1127 pcts[1] = pcts[1] - pcts[0]
1127 pcts << (100 - pcts[1] - pcts[0])
1128 pcts << (100 - pcts[1] - pcts[0])
1128 width = options[:width] || '100px;'
1129 width = options[:width] || '100px;'
1129 legend = options[:legend] || ''
1130 legend = options[:legend] || ''
1130 content_tag('table',
1131 content_tag('table',
1131 content_tag('tr',
1132 content_tag('tr',
1132 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1133 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1133 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1134 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1134 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1135 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1135 ), :class => "progress progress-#{pcts[0]}", :style => "width: #{width};").html_safe +
1136 ), :class => "progress progress-#{pcts[0]}", :style => "width: #{width};").html_safe +
1136 content_tag('p', legend, :class => 'percent').html_safe
1137 content_tag('p', legend, :class => 'percent').html_safe
1137 end
1138 end
1138
1139
1139 def checked_image(checked=true)
1140 def checked_image(checked=true)
1140 if checked
1141 if checked
1141 image_tag 'toggle_check.png'
1142 image_tag 'toggle_check.png'
1142 end
1143 end
1143 end
1144 end
1144
1145
1145 def context_menu(url)
1146 def context_menu(url)
1146 unless @context_menu_included
1147 unless @context_menu_included
1147 content_for :header_tags do
1148 content_for :header_tags do
1148 javascript_include_tag('context_menu') +
1149 javascript_include_tag('context_menu') +
1149 stylesheet_link_tag('context_menu')
1150 stylesheet_link_tag('context_menu')
1150 end
1151 end
1151 if l(:direction) == 'rtl'
1152 if l(:direction) == 'rtl'
1152 content_for :header_tags do
1153 content_for :header_tags do
1153 stylesheet_link_tag('context_menu_rtl')
1154 stylesheet_link_tag('context_menu_rtl')
1154 end
1155 end
1155 end
1156 end
1156 @context_menu_included = true
1157 @context_menu_included = true
1157 end
1158 end
1158 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1159 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1159 end
1160 end
1160
1161
1161 def calendar_for(field_id)
1162 def calendar_for(field_id)
1162 include_calendar_headers_tags
1163 include_calendar_headers_tags
1163 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
1164 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
1164 end
1165 end
1165
1166
1166 def include_calendar_headers_tags
1167 def include_calendar_headers_tags
1167 unless @calendar_headers_tags_included
1168 unless @calendar_headers_tags_included
1168 tags = javascript_include_tag("datepicker")
1169 tags = javascript_include_tag("datepicker")
1169 @calendar_headers_tags_included = true
1170 @calendar_headers_tags_included = true
1170 content_for :header_tags do
1171 content_for :header_tags do
1171 start_of_week = Setting.start_of_week
1172 start_of_week = Setting.start_of_week
1172 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1173 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1173 # Redmine uses 1..7 (monday..sunday) in settings and locales
1174 # Redmine uses 1..7 (monday..sunday) in settings and locales
1174 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1175 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1175 start_of_week = start_of_week.to_i % 7
1176 start_of_week = start_of_week.to_i % 7
1176 tags << javascript_tag(
1177 tags << javascript_tag(
1177 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1178 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1178 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1179 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1179 path_to_image('/images/calendar.png') +
1180 path_to_image('/images/calendar.png') +
1180 "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " +
1181 "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " +
1181 "selectOtherMonths: true, changeMonth: true, changeYear: true, " +
1182 "selectOtherMonths: true, changeMonth: true, changeYear: true, " +
1182 "beforeShow: beforeShowDatePicker};")
1183 "beforeShow: beforeShowDatePicker};")
1183 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1184 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1184 unless jquery_locale == 'en'
1185 unless jquery_locale == 'en'
1185 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
1186 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
1186 end
1187 end
1187 tags
1188 tags
1188 end
1189 end
1189 end
1190 end
1190 end
1191 end
1191
1192
1192 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1193 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1193 # Examples:
1194 # Examples:
1194 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1195 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1195 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1196 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1196 #
1197 #
1197 def stylesheet_link_tag(*sources)
1198 def stylesheet_link_tag(*sources)
1198 options = sources.last.is_a?(Hash) ? sources.pop : {}
1199 options = sources.last.is_a?(Hash) ? sources.pop : {}
1199 plugin = options.delete(:plugin)
1200 plugin = options.delete(:plugin)
1200 sources = sources.map do |source|
1201 sources = sources.map do |source|
1201 if plugin
1202 if plugin
1202 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1203 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1203 elsif current_theme && current_theme.stylesheets.include?(source)
1204 elsif current_theme && current_theme.stylesheets.include?(source)
1204 current_theme.stylesheet_path(source)
1205 current_theme.stylesheet_path(source)
1205 else
1206 else
1206 source
1207 source
1207 end
1208 end
1208 end
1209 end
1209 super sources, options
1210 super sources, options
1210 end
1211 end
1211
1212
1212 # Overrides Rails' image_tag with themes and plugins support.
1213 # Overrides Rails' image_tag with themes and plugins support.
1213 # Examples:
1214 # Examples:
1214 # image_tag('image.png') # => picks image.png from the current theme or defaults
1215 # image_tag('image.png') # => picks image.png from the current theme or defaults
1215 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1216 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1216 #
1217 #
1217 def image_tag(source, options={})
1218 def image_tag(source, options={})
1218 if plugin = options.delete(:plugin)
1219 if plugin = options.delete(:plugin)
1219 source = "/plugin_assets/#{plugin}/images/#{source}"
1220 source = "/plugin_assets/#{plugin}/images/#{source}"
1220 elsif current_theme && current_theme.images.include?(source)
1221 elsif current_theme && current_theme.images.include?(source)
1221 source = current_theme.image_path(source)
1222 source = current_theme.image_path(source)
1222 end
1223 end
1223 super source, options
1224 super source, options
1224 end
1225 end
1225
1226
1226 # Overrides Rails' javascript_include_tag with plugins support
1227 # Overrides Rails' javascript_include_tag with plugins support
1227 # Examples:
1228 # Examples:
1228 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1229 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1229 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1230 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1230 #
1231 #
1231 def javascript_include_tag(*sources)
1232 def javascript_include_tag(*sources)
1232 options = sources.last.is_a?(Hash) ? sources.pop : {}
1233 options = sources.last.is_a?(Hash) ? sources.pop : {}
1233 if plugin = options.delete(:plugin)
1234 if plugin = options.delete(:plugin)
1234 sources = sources.map do |source|
1235 sources = sources.map do |source|
1235 if plugin
1236 if plugin
1236 "/plugin_assets/#{plugin}/javascripts/#{source}"
1237 "/plugin_assets/#{plugin}/javascripts/#{source}"
1237 else
1238 else
1238 source
1239 source
1239 end
1240 end
1240 end
1241 end
1241 end
1242 end
1242 super sources, options
1243 super sources, options
1243 end
1244 end
1244
1245
1245 # TODO: remove this in 2.5.0
1246 # TODO: remove this in 2.5.0
1246 def has_content?(name)
1247 def has_content?(name)
1247 content_for?(name)
1248 content_for?(name)
1248 end
1249 end
1249
1250
1250 def sidebar_content?
1251 def sidebar_content?
1251 content_for?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1252 content_for?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1252 end
1253 end
1253
1254
1254 def view_layouts_base_sidebar_hook_response
1255 def view_layouts_base_sidebar_hook_response
1255 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1256 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1256 end
1257 end
1257
1258
1258 def email_delivery_enabled?
1259 def email_delivery_enabled?
1259 !!ActionMailer::Base.perform_deliveries
1260 !!ActionMailer::Base.perform_deliveries
1260 end
1261 end
1261
1262
1262 # Returns the avatar image tag for the given +user+ if avatars are enabled
1263 # Returns the avatar image tag for the given +user+ if avatars are enabled
1263 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1264 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1264 def avatar(user, options = { })
1265 def avatar(user, options = { })
1265 if Setting.gravatar_enabled?
1266 if Setting.gravatar_enabled?
1266 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1267 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1267 email = nil
1268 email = nil
1268 if user.respond_to?(:mail)
1269 if user.respond_to?(:mail)
1269 email = user.mail
1270 email = user.mail
1270 elsif user.to_s =~ %r{<(.+?)>}
1271 elsif user.to_s =~ %r{<(.+?)>}
1271 email = $1
1272 email = $1
1272 end
1273 end
1273 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1274 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1274 else
1275 else
1275 ''
1276 ''
1276 end
1277 end
1277 end
1278 end
1278
1279
1279 def sanitize_anchor_name(anchor)
1280 def sanitize_anchor_name(anchor)
1280 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
1281 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
1281 anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1282 anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1282 else
1283 else
1283 # TODO: remove when ruby1.8 is no longer supported
1284 # TODO: remove when ruby1.8 is no longer supported
1284 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1285 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1285 end
1286 end
1286 end
1287 end
1287
1288
1288 # Returns the javascript tags that are included in the html layout head
1289 # Returns the javascript tags that are included in the html layout head
1289 def javascript_heads
1290 def javascript_heads
1290 tags = javascript_include_tag('jquery-1.8.3-ui-1.9.2-ujs-2.0.3', 'application')
1291 tags = javascript_include_tag('jquery-1.8.3-ui-1.9.2-ujs-2.0.3', 'application')
1291 unless User.current.pref.warn_on_leaving_unsaved == '0'
1292 unless User.current.pref.warn_on_leaving_unsaved == '0'
1292 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1293 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1293 end
1294 end
1294 tags
1295 tags
1295 end
1296 end
1296
1297
1297 def favicon
1298 def favicon
1298 "<link rel='shortcut icon' href='#{favicon_path}' />".html_safe
1299 "<link rel='shortcut icon' href='#{favicon_path}' />".html_safe
1299 end
1300 end
1300
1301
1301 # Returns the path to the favicon
1302 # Returns the path to the favicon
1302 def favicon_path
1303 def favicon_path
1303 icon = (current_theme && current_theme.favicon?) ? current_theme.favicon_path : '/favicon.ico'
1304 icon = (current_theme && current_theme.favicon?) ? current_theme.favicon_path : '/favicon.ico'
1304 image_path(icon)
1305 image_path(icon)
1305 end
1306 end
1306
1307
1307 # Returns the full URL to the favicon
1308 # Returns the full URL to the favicon
1308 def favicon_url
1309 def favicon_url
1309 # TODO: use #image_url introduced in Rails4
1310 # TODO: use #image_url introduced in Rails4
1310 path = favicon_path
1311 path = favicon_path
1311 base = url_for(:controller => 'welcome', :action => 'index', :only_path => false)
1312 base = url_for(:controller => 'welcome', :action => 'index', :only_path => false)
1312 base.sub(%r{/+$},'') + '/' + path.sub(%r{^/+},'')
1313 base.sub(%r{/+$},'') + '/' + path.sub(%r{^/+},'')
1313 end
1314 end
1314
1315
1315 def robot_exclusion_tag
1316 def robot_exclusion_tag
1316 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1317 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1317 end
1318 end
1318
1319
1319 # Returns true if arg is expected in the API response
1320 # Returns true if arg is expected in the API response
1320 def include_in_api_response?(arg)
1321 def include_in_api_response?(arg)
1321 unless @included_in_api_response
1322 unless @included_in_api_response
1322 param = params[:include]
1323 param = params[:include]
1323 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1324 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1324 @included_in_api_response.collect!(&:strip)
1325 @included_in_api_response.collect!(&:strip)
1325 end
1326 end
1326 @included_in_api_response.include?(arg.to_s)
1327 @included_in_api_response.include?(arg.to_s)
1327 end
1328 end
1328
1329
1329 # Returns options or nil if nometa param or X-Redmine-Nometa header
1330 # Returns options or nil if nometa param or X-Redmine-Nometa header
1330 # was set in the request
1331 # was set in the request
1331 def api_meta(options)
1332 def api_meta(options)
1332 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1333 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1333 # compatibility mode for activeresource clients that raise
1334 # compatibility mode for activeresource clients that raise
1334 # an error when deserializing an array with attributes
1335 # an error when deserializing an array with attributes
1335 nil
1336 nil
1336 else
1337 else
1337 options
1338 options
1338 end
1339 end
1339 end
1340 end
1340
1341
1341 private
1342 private
1342
1343
1343 def wiki_helper
1344 def wiki_helper
1344 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1345 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1345 extend helper
1346 extend helper
1346 return self
1347 return self
1347 end
1348 end
1348
1349
1349 def link_to_content_update(text, url_params = {}, html_options = {})
1350 def link_to_content_update(text, url_params = {}, html_options = {})
1350 link_to(text, url_params, html_options)
1351 link_to(text, url_params, html_options)
1351 end
1352 end
1352 end
1353 end
@@ -1,1508 +1,1516
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2014 Jean-Philippe Lang
4 # Copyright (C) 2006-2014 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 File.expand_path('../../../test_helper', __FILE__)
20 require File.expand_path('../../../test_helper', __FILE__)
21
21
22 class ApplicationHelperTest < ActionView::TestCase
22 class ApplicationHelperTest < ActionView::TestCase
23 include Redmine::I18n
23 include Redmine::I18n
24 include ERB::Util
24 include ERB::Util
25 include Rails.application.routes.url_helpers
25 include Rails.application.routes.url_helpers
26
26
27 fixtures :projects, :roles, :enabled_modules, :users,
27 fixtures :projects, :roles, :enabled_modules, :users,
28 :repositories, :changesets,
28 :repositories, :changesets,
29 :trackers, :issue_statuses, :issues, :versions, :documents,
29 :trackers, :issue_statuses, :issues, :versions, :documents,
30 :wikis, :wiki_pages, :wiki_contents,
30 :wikis, :wiki_pages, :wiki_contents,
31 :boards, :messages, :news,
31 :boards, :messages, :news,
32 :attachments, :enumerations
32 :attachments, :enumerations
33
33
34 def setup
34 def setup
35 super
35 super
36 set_tmp_attachments_directory
36 set_tmp_attachments_directory
37 @russian_test = "\xd1\x82\xd0\xb5\xd1\x81\xd1\x82"
37 @russian_test = "\xd1\x82\xd0\xb5\xd1\x81\xd1\x82"
38 if @russian_test.respond_to?(:force_encoding)
38 if @russian_test.respond_to?(:force_encoding)
39 @russian_test.force_encoding('UTF-8')
39 @russian_test.force_encoding('UTF-8')
40 end
40 end
41 end
41 end
42
42
43 test "#link_to_if_authorized for authorized user should allow using the :controller and :action for the target link" do
43 test "#link_to_if_authorized for authorized user should allow using the :controller and :action for the target link" do
44 User.current = User.find_by_login('admin')
44 User.current = User.find_by_login('admin')
45
45
46 @project = Issue.first.project # Used by helper
46 @project = Issue.first.project # Used by helper
47 response = link_to_if_authorized('By controller/actionr',
47 response = link_to_if_authorized('By controller/actionr',
48 {:controller => 'issues', :action => 'edit', :id => Issue.first.id})
48 {:controller => 'issues', :action => 'edit', :id => Issue.first.id})
49 assert_match /href/, response
49 assert_match /href/, response
50 end
50 end
51
51
52 test "#link_to_if_authorized for unauthorized user should display nothing if user isn't authorized" do
52 test "#link_to_if_authorized for unauthorized user should display nothing if user isn't authorized" do
53 User.current = User.find_by_login('dlopper')
53 User.current = User.find_by_login('dlopper')
54 @project = Project.find('private-child')
54 @project = Project.find('private-child')
55 issue = @project.issues.first
55 issue = @project.issues.first
56 assert !issue.visible?
56 assert !issue.visible?
57
57
58 response = link_to_if_authorized('Never displayed',
58 response = link_to_if_authorized('Never displayed',
59 {:controller => 'issues', :action => 'show', :id => issue})
59 {:controller => 'issues', :action => 'show', :id => issue})
60 assert_nil response
60 assert_nil response
61 end
61 end
62
62
63 def test_auto_links
63 def test_auto_links
64 to_test = {
64 to_test = {
65 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
65 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
66 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
66 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
67 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
67 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
68 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
68 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
69 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
69 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
70 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
70 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
71 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
71 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
72 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
72 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
73 '(see inline link : http://www.foo.bar/Test_(foobar))' => '(see inline link : <a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>)',
73 '(see inline link : http://www.foo.bar/Test_(foobar))' => '(see inline link : <a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>)',
74 '(see inline link : http://www.foo.bar/Test)' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>)',
74 '(see inline link : http://www.foo.bar/Test)' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>)',
75 '(see inline link : http://www.foo.bar/Test).' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>).',
75 '(see inline link : http://www.foo.bar/Test).' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>).',
76 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
76 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
77 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
77 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
78 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
78 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
79 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
79 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
80 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
80 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
81 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
81 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
82 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
82 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
83 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
83 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
84 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
84 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
85 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
85 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
86 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
86 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
87 # two exclamation marks
87 # two exclamation marks
88 'http://example.net/path!602815048C7B5C20!302.html' => '<a class="external" href="http://example.net/path!602815048C7B5C20!302.html">http://example.net/path!602815048C7B5C20!302.html</a>',
88 'http://example.net/path!602815048C7B5C20!302.html' => '<a class="external" href="http://example.net/path!602815048C7B5C20!302.html">http://example.net/path!602815048C7B5C20!302.html</a>',
89 # escaping
89 # escaping
90 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo&quot;bar</a>',
90 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo&quot;bar</a>',
91 # wrap in angle brackets
91 # wrap in angle brackets
92 '<http://foo.bar>' => '&lt;<a class="external" href="http://foo.bar">http://foo.bar</a>&gt;',
92 '<http://foo.bar>' => '&lt;<a class="external" href="http://foo.bar">http://foo.bar</a>&gt;',
93 # invalid urls
93 # invalid urls
94 'http://' => 'http://',
94 'http://' => 'http://',
95 'www.' => 'www.',
95 'www.' => 'www.',
96 'test-www.bar.com' => 'test-www.bar.com',
96 'test-www.bar.com' => 'test-www.bar.com',
97 }
97 }
98 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
98 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
99 end
99 end
100
100
101 if 'ruby'.respond_to?(:encoding)
101 if 'ruby'.respond_to?(:encoding)
102 def test_auto_links_with_non_ascii_characters
102 def test_auto_links_with_non_ascii_characters
103 to_test = {
103 to_test = {
104 "http://foo.bar/#{@russian_test}" =>
104 "http://foo.bar/#{@russian_test}" =>
105 %|<a class="external" href="http://foo.bar/#{@russian_test}">http://foo.bar/#{@russian_test}</a>|
105 %|<a class="external" href="http://foo.bar/#{@russian_test}">http://foo.bar/#{@russian_test}</a>|
106 }
106 }
107 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
107 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
108 end
108 end
109 else
109 else
110 puts 'Skipping test_auto_links_with_non_ascii_characters, unsupported ruby version'
110 puts 'Skipping test_auto_links_with_non_ascii_characters, unsupported ruby version'
111 end
111 end
112
112
113 def test_auto_mailto
113 def test_auto_mailto
114 to_test = {
114 to_test = {
115 'test@foo.bar' => '<a class="email" href="mailto:test@foo.bar">test@foo.bar</a>',
115 'test@foo.bar' => '<a class="email" href="mailto:test@foo.bar">test@foo.bar</a>',
116 'test@www.foo.bar' => '<a class="email" href="mailto:test@www.foo.bar">test@www.foo.bar</a>',
116 'test@www.foo.bar' => '<a class="email" href="mailto:test@www.foo.bar">test@www.foo.bar</a>',
117 }
117 }
118 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
118 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
119 end
119 end
120
120
121 def test_inline_images
121 def test_inline_images
122 to_test = {
122 to_test = {
123 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
123 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
124 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
124 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
125 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
125 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
126 'with style !{width:100px;height:100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height:100px;" alt="" />',
126 'with style !{width:100px;height:100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height:100px;" alt="" />',
127 'with title !http://foo.bar/image.jpg(This is a title)!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a title" alt="This is a title" />',
127 'with title !http://foo.bar/image.jpg(This is a title)!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a title" alt="This is a title" />',
128 'with title !http://foo.bar/image.jpg(This is a double-quoted "title")!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a double-quoted &quot;title&quot;" alt="This is a double-quoted &quot;title&quot;" />',
128 'with title !http://foo.bar/image.jpg(This is a double-quoted "title")!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a double-quoted &quot;title&quot;" alt="This is a double-quoted &quot;title&quot;" />',
129 }
129 }
130 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
130 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
131 end
131 end
132
132
133 def test_inline_images_inside_tags
133 def test_inline_images_inside_tags
134 raw = <<-RAW
134 raw = <<-RAW
135 h1. !foo.png! Heading
135 h1. !foo.png! Heading
136
136
137 Centered image:
137 Centered image:
138
138
139 p=. !bar.gif!
139 p=. !bar.gif!
140 RAW
140 RAW
141
141
142 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
142 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
143 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
143 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
144 end
144 end
145
145
146 def test_attached_images
146 def test_attached_images
147 to_test = {
147 to_test = {
148 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" />',
148 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" />',
149 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" />',
149 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" />',
150 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
150 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
151 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
151 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
152 # link image
152 # link image
153 '!logo.gif!:http://foo.bar/' => '<a href="http://foo.bar/"><img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" /></a>',
153 '!logo.gif!:http://foo.bar/' => '<a href="http://foo.bar/"><img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" /></a>',
154 }
154 }
155 attachments = Attachment.all
155 attachments = Attachment.all
156 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
156 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
157 end
157 end
158
158
159 def test_attached_images_filename_extension
159 def test_attached_images_filename_extension
160 set_tmp_attachments_directory
160 set_tmp_attachments_directory
161 a1 = Attachment.new(
161 a1 = Attachment.new(
162 :container => Issue.find(1),
162 :container => Issue.find(1),
163 :file => mock_file_with_options({:original_filename => "testtest.JPG"}),
163 :file => mock_file_with_options({:original_filename => "testtest.JPG"}),
164 :author => User.find(1))
164 :author => User.find(1))
165 assert a1.save
165 assert a1.save
166 assert_equal "testtest.JPG", a1.filename
166 assert_equal "testtest.JPG", a1.filename
167 assert_equal "image/jpeg", a1.content_type
167 assert_equal "image/jpeg", a1.content_type
168 assert a1.image?
168 assert a1.image?
169
169
170 a2 = Attachment.new(
170 a2 = Attachment.new(
171 :container => Issue.find(1),
171 :container => Issue.find(1),
172 :file => mock_file_with_options({:original_filename => "testtest.jpeg"}),
172 :file => mock_file_with_options({:original_filename => "testtest.jpeg"}),
173 :author => User.find(1))
173 :author => User.find(1))
174 assert a2.save
174 assert a2.save
175 assert_equal "testtest.jpeg", a2.filename
175 assert_equal "testtest.jpeg", a2.filename
176 assert_equal "image/jpeg", a2.content_type
176 assert_equal "image/jpeg", a2.content_type
177 assert a2.image?
177 assert a2.image?
178
178
179 a3 = Attachment.new(
179 a3 = Attachment.new(
180 :container => Issue.find(1),
180 :container => Issue.find(1),
181 :file => mock_file_with_options({:original_filename => "testtest.JPE"}),
181 :file => mock_file_with_options({:original_filename => "testtest.JPE"}),
182 :author => User.find(1))
182 :author => User.find(1))
183 assert a3.save
183 assert a3.save
184 assert_equal "testtest.JPE", a3.filename
184 assert_equal "testtest.JPE", a3.filename
185 assert_equal "image/jpeg", a3.content_type
185 assert_equal "image/jpeg", a3.content_type
186 assert a3.image?
186 assert a3.image?
187
187
188 a4 = Attachment.new(
188 a4 = Attachment.new(
189 :container => Issue.find(1),
189 :container => Issue.find(1),
190 :file => mock_file_with_options({:original_filename => "Testtest.BMP"}),
190 :file => mock_file_with_options({:original_filename => "Testtest.BMP"}),
191 :author => User.find(1))
191 :author => User.find(1))
192 assert a4.save
192 assert a4.save
193 assert_equal "Testtest.BMP", a4.filename
193 assert_equal "Testtest.BMP", a4.filename
194 assert_equal "image/x-ms-bmp", a4.content_type
194 assert_equal "image/x-ms-bmp", a4.content_type
195 assert a4.image?
195 assert a4.image?
196
196
197 to_test = {
197 to_test = {
198 'Inline image: !testtest.jpg!' =>
198 'Inline image: !testtest.jpg!' =>
199 'Inline image: <img src="/attachments/download/' + a1.id.to_s + '/testtest.JPG" alt="" />',
199 'Inline image: <img src="/attachments/download/' + a1.id.to_s + '/testtest.JPG" alt="" />',
200 'Inline image: !testtest.jpeg!' =>
200 'Inline image: !testtest.jpeg!' =>
201 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testtest.jpeg" alt="" />',
201 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testtest.jpeg" alt="" />',
202 'Inline image: !testtest.jpe!' =>
202 'Inline image: !testtest.jpe!' =>
203 'Inline image: <img src="/attachments/download/' + a3.id.to_s + '/testtest.JPE" alt="" />',
203 'Inline image: <img src="/attachments/download/' + a3.id.to_s + '/testtest.JPE" alt="" />',
204 'Inline image: !testtest.bmp!' =>
204 'Inline image: !testtest.bmp!' =>
205 'Inline image: <img src="/attachments/download/' + a4.id.to_s + '/Testtest.BMP" alt="" />',
205 'Inline image: <img src="/attachments/download/' + a4.id.to_s + '/Testtest.BMP" alt="" />',
206 }
206 }
207
207
208 attachments = [a1, a2, a3, a4]
208 attachments = [a1, a2, a3, a4]
209 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
209 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
210 end
210 end
211
211
212 def test_attached_images_should_read_later
212 def test_attached_images_should_read_later
213 set_fixtures_attachments_directory
213 set_fixtures_attachments_directory
214 a1 = Attachment.find(16)
214 a1 = Attachment.find(16)
215 assert_equal "testfile.png", a1.filename
215 assert_equal "testfile.png", a1.filename
216 assert a1.readable?
216 assert a1.readable?
217 assert (! a1.visible?(User.anonymous))
217 assert (! a1.visible?(User.anonymous))
218 assert a1.visible?(User.find(2))
218 assert a1.visible?(User.find(2))
219 a2 = Attachment.find(17)
219 a2 = Attachment.find(17)
220 assert_equal "testfile.PNG", a2.filename
220 assert_equal "testfile.PNG", a2.filename
221 assert a2.readable?
221 assert a2.readable?
222 assert (! a2.visible?(User.anonymous))
222 assert (! a2.visible?(User.anonymous))
223 assert a2.visible?(User.find(2))
223 assert a2.visible?(User.find(2))
224 assert a1.created_on < a2.created_on
224 assert a1.created_on < a2.created_on
225
225
226 to_test = {
226 to_test = {
227 'Inline image: !testfile.png!' =>
227 'Inline image: !testfile.png!' =>
228 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testfile.PNG" alt="" />',
228 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testfile.PNG" alt="" />',
229 'Inline image: !Testfile.PNG!' =>
229 'Inline image: !Testfile.PNG!' =>
230 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testfile.PNG" alt="" />',
230 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testfile.PNG" alt="" />',
231 }
231 }
232 attachments = [a1, a2]
232 attachments = [a1, a2]
233 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
233 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
234 set_tmp_attachments_directory
234 set_tmp_attachments_directory
235 end
235 end
236
236
237 def test_textile_external_links
237 def test_textile_external_links
238 to_test = {
238 to_test = {
239 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
239 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
240 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
240 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
241 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
241 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
242 '"link (Link title with "double-quotes")":http://foo.bar' => '<a href="http://foo.bar" title="Link title with &quot;double-quotes&quot;" class="external">link</a>',
242 '"link (Link title with "double-quotes")":http://foo.bar' => '<a href="http://foo.bar" title="Link title with &quot;double-quotes&quot;" class="external">link</a>',
243 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
243 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
244 # no multiline link text
244 # no multiline link text
245 "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />and another on a second line\":test",
245 "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />and another on a second line\":test",
246 # mailto link
246 # mailto link
247 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
247 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
248 # two exclamation marks
248 # two exclamation marks
249 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
249 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
250 # escaping
250 # escaping
251 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
251 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
252 }
252 }
253 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
253 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
254 end
254 end
255
255
256 if 'ruby'.respond_to?(:encoding)
256 if 'ruby'.respond_to?(:encoding)
257 def test_textile_external_links_with_non_ascii_characters
257 def test_textile_external_links_with_non_ascii_characters
258 to_test = {
258 to_test = {
259 %|This is a "link":http://foo.bar/#{@russian_test}| =>
259 %|This is a "link":http://foo.bar/#{@russian_test}| =>
260 %|This is a <a href="http://foo.bar/#{@russian_test}" class="external">link</a>|
260 %|This is a <a href="http://foo.bar/#{@russian_test}" class="external">link</a>|
261 }
261 }
262 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
262 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
263 end
263 end
264 else
264 else
265 puts 'Skipping test_textile_external_links_with_non_ascii_characters, unsupported ruby version'
265 puts 'Skipping test_textile_external_links_with_non_ascii_characters, unsupported ruby version'
266 end
266 end
267
267
268 def test_redmine_links
268 def test_redmine_links
269 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
269 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
270 :class => Issue.find(3).css_classes, :title => 'Error 281 when updating a recipe (New)')
270 :class => Issue.find(3).css_classes, :title => 'Error 281 when updating a recipe (New)')
271 note_link = link_to('#3-14', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'},
271 note_link = link_to('#3-14', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'},
272 :class => Issue.find(3).css_classes, :title => 'Error 281 when updating a recipe (New)')
272 :class => Issue.find(3).css_classes, :title => 'Error 281 when updating a recipe (New)')
273 note_link2 = link_to('#3#note-14', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'},
273 note_link2 = link_to('#3#note-14', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'},
274 :class => Issue.find(3).css_classes, :title => 'Error 281 when updating a recipe (New)')
274 :class => Issue.find(3).css_classes, :title => 'Error 281 when updating a recipe (New)')
275
275
276 revision_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
276 revision_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
277 :class => 'changeset', :title => 'My very first commit do not escaping #<>&')
277 :class => 'changeset', :title => 'My very first commit do not escaping #<>&')
278 revision_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
278 revision_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
279 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
279 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
280
280
281 changeset_link2 = link_to('691322a8eb01e11fd7',
281 changeset_link2 = link_to('691322a8eb01e11fd7',
282 {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
282 {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
283 :class => 'changeset', :title => 'My very first commit do not escaping #<>&')
283 :class => 'changeset', :title => 'My very first commit do not escaping #<>&')
284
284
285 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
285 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
286 :class => 'document')
286 :class => 'document')
287
287
288 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
288 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
289 :class => 'version')
289 :class => 'version')
290
290
291 board_url = {:controller => 'boards', :action => 'show', :id => 2, :project_id => 'ecookbook'}
291 board_url = {:controller => 'boards', :action => 'show', :id => 2, :project_id => 'ecookbook'}
292
292
293 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
293 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
294
294
295 news_url = {:controller => 'news', :action => 'show', :id => 1}
295 news_url = {:controller => 'news', :action => 'show', :id => 1}
296
296
297 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
297 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
298
298
299 source_url = '/projects/ecookbook/repository/entry/some/file'
299 source_url = '/projects/ecookbook/repository/entry/some/file'
300 source_url_with_rev = '/projects/ecookbook/repository/revisions/52/entry/some/file'
300 source_url_with_rev = '/projects/ecookbook/repository/revisions/52/entry/some/file'
301 source_url_with_ext = '/projects/ecookbook/repository/entry/some/file.ext'
301 source_url_with_ext = '/projects/ecookbook/repository/entry/some/file.ext'
302 source_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/entry/some/file.ext'
302 source_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/entry/some/file.ext'
303 source_url_with_branch = '/projects/ecookbook/repository/revisions/branch/entry/some/file'
303 source_url_with_branch = '/projects/ecookbook/repository/revisions/branch/entry/some/file'
304
304
305 export_url = '/projects/ecookbook/repository/raw/some/file'
305 export_url = '/projects/ecookbook/repository/raw/some/file'
306 export_url_with_rev = '/projects/ecookbook/repository/revisions/52/raw/some/file'
306 export_url_with_rev = '/projects/ecookbook/repository/revisions/52/raw/some/file'
307 export_url_with_ext = '/projects/ecookbook/repository/raw/some/file.ext'
307 export_url_with_ext = '/projects/ecookbook/repository/raw/some/file.ext'
308 export_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/raw/some/file.ext'
308 export_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/raw/some/file.ext'
309 export_url_with_branch = '/projects/ecookbook/repository/revisions/branch/raw/some/file'
309 export_url_with_branch = '/projects/ecookbook/repository/revisions/branch/raw/some/file'
310
310
311 to_test = {
311 to_test = {
312 # tickets
312 # tickets
313 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
313 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
314 # ticket notes
314 # ticket notes
315 '#3-14' => note_link,
315 '#3-14' => note_link,
316 '#3#note-14' => note_link2,
316 '#3#note-14' => note_link2,
317 # should not ignore leading zero
317 # should not ignore leading zero
318 '#03' => '#03',
318 '#03' => '#03',
319 # changesets
319 # changesets
320 'r1' => revision_link,
320 'r1' => revision_link,
321 'r1.' => "#{revision_link}.",
321 'r1.' => "#{revision_link}.",
322 'r1, r2' => "#{revision_link}, #{revision_link2}",
322 'r1, r2' => "#{revision_link}, #{revision_link2}",
323 'r1,r2' => "#{revision_link},#{revision_link2}",
323 'r1,r2' => "#{revision_link},#{revision_link2}",
324 'commit:691322a8eb01e11fd7' => changeset_link2,
324 'commit:691322a8eb01e11fd7' => changeset_link2,
325 # documents
325 # documents
326 'document#1' => document_link,
326 'document#1' => document_link,
327 'document:"Test document"' => document_link,
327 'document:"Test document"' => document_link,
328 # versions
328 # versions
329 'version#2' => version_link,
329 'version#2' => version_link,
330 'version:1.0' => version_link,
330 'version:1.0' => version_link,
331 'version:"1.0"' => version_link,
331 'version:"1.0"' => version_link,
332 # source
332 # source
333 'source:some/file' => link_to('source:some/file', source_url, :class => 'source'),
333 'source:some/file' => link_to('source:some/file', source_url, :class => 'source'),
334 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
334 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
335 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
335 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
336 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
336 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
337 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
337 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
338 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
338 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
339 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
339 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
340 'source:/some/file@52' => link_to('source:/some/file@52', source_url_with_rev, :class => 'source'),
340 'source:/some/file@52' => link_to('source:/some/file@52', source_url_with_rev, :class => 'source'),
341 'source:/some/file@branch' => link_to('source:/some/file@branch', source_url_with_branch, :class => 'source'),
341 'source:/some/file@branch' => link_to('source:/some/file@branch', source_url_with_branch, :class => 'source'),
342 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_rev_and_ext, :class => 'source'),
342 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_rev_and_ext, :class => 'source'),
343 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url + "#L110", :class => 'source'),
343 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url + "#L110", :class => 'source'),
344 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext + "#L110", :class => 'source'),
344 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext + "#L110", :class => 'source'),
345 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url_with_rev + "#L110", :class => 'source'),
345 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url_with_rev + "#L110", :class => 'source'),
346 # export
346 # export
347 'export:/some/file' => link_to('export:/some/file', export_url, :class => 'source download'),
347 'export:/some/file' => link_to('export:/some/file', export_url, :class => 'source download'),
348 'export:/some/file.ext' => link_to('export:/some/file.ext', export_url_with_ext, :class => 'source download'),
348 'export:/some/file.ext' => link_to('export:/some/file.ext', export_url_with_ext, :class => 'source download'),
349 'export:/some/file@52' => link_to('export:/some/file@52', export_url_with_rev, :class => 'source download'),
349 'export:/some/file@52' => link_to('export:/some/file@52', export_url_with_rev, :class => 'source download'),
350 'export:/some/file.ext@52' => link_to('export:/some/file.ext@52', export_url_with_rev_and_ext, :class => 'source download'),
350 'export:/some/file.ext@52' => link_to('export:/some/file.ext@52', export_url_with_rev_and_ext, :class => 'source download'),
351 'export:/some/file@branch' => link_to('export:/some/file@branch', export_url_with_branch, :class => 'source download'),
351 'export:/some/file@branch' => link_to('export:/some/file@branch', export_url_with_branch, :class => 'source download'),
352 # forum
352 # forum
353 'forum#2' => link_to('Discussion', board_url, :class => 'board'),
353 'forum#2' => link_to('Discussion', board_url, :class => 'board'),
354 'forum:Discussion' => link_to('Discussion', board_url, :class => 'board'),
354 'forum:Discussion' => link_to('Discussion', board_url, :class => 'board'),
355 # message
355 # message
356 'message#4' => link_to('Post 2', message_url, :class => 'message'),
356 'message#4' => link_to('Post 2', message_url, :class => 'message'),
357 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5', :r => 5), :class => 'message'),
357 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5', :r => 5), :class => 'message'),
358 # news
358 # news
359 'news#1' => link_to('eCookbook first release !', news_url, :class => 'news'),
359 'news#1' => link_to('eCookbook first release !', news_url, :class => 'news'),
360 'news:"eCookbook first release !"' => link_to('eCookbook first release !', news_url, :class => 'news'),
360 'news:"eCookbook first release !"' => link_to('eCookbook first release !', news_url, :class => 'news'),
361 # project
361 # project
362 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
362 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
363 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
363 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
364 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
364 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
365 # not found
365 # not found
366 '#0123456789' => '#0123456789',
366 '#0123456789' => '#0123456789',
367 # invalid expressions
367 # invalid expressions
368 'source:' => 'source:',
368 'source:' => 'source:',
369 # url hash
369 # url hash
370 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
370 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
371 }
371 }
372 @project = Project.find(1)
372 @project = Project.find(1)
373 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
373 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
374 end
374 end
375
375
376 def test_redmine_links_with_a_different_project_before_current_project
376 def test_redmine_links_with_a_different_project_before_current_project
377 vp1 = Version.generate!(:project_id => 1, :name => '1.4.4')
377 vp1 = Version.generate!(:project_id => 1, :name => '1.4.4')
378 vp3 = Version.generate!(:project_id => 3, :name => '1.4.4')
378 vp3 = Version.generate!(:project_id => 3, :name => '1.4.4')
379 @project = Project.find(3)
379 @project = Project.find(3)
380 result1 = link_to("1.4.4", "/versions/#{vp1.id}", :class => "version")
380 result1 = link_to("1.4.4", "/versions/#{vp1.id}", :class => "version")
381 result2 = link_to("1.4.4", "/versions/#{vp3.id}", :class => "version")
381 result2 = link_to("1.4.4", "/versions/#{vp3.id}", :class => "version")
382 assert_equal "<p>#{result1} #{result2}</p>",
382 assert_equal "<p>#{result1} #{result2}</p>",
383 textilizable("ecookbook:version:1.4.4 version:1.4.4")
383 textilizable("ecookbook:version:1.4.4 version:1.4.4")
384 end
384 end
385
385
386 def test_escaped_redmine_links_should_not_be_parsed
386 def test_escaped_redmine_links_should_not_be_parsed
387 to_test = [
387 to_test = [
388 '#3.',
388 '#3.',
389 '#3-14.',
389 '#3-14.',
390 '#3#-note14.',
390 '#3#-note14.',
391 'r1',
391 'r1',
392 'document#1',
392 'document#1',
393 'document:"Test document"',
393 'document:"Test document"',
394 'version#2',
394 'version#2',
395 'version:1.0',
395 'version:1.0',
396 'version:"1.0"',
396 'version:"1.0"',
397 'source:/some/file'
397 'source:/some/file'
398 ]
398 ]
399 @project = Project.find(1)
399 @project = Project.find(1)
400 to_test.each { |text| assert_equal "<p>#{text}</p>", textilizable("!" + text), "#{text} failed" }
400 to_test.each { |text| assert_equal "<p>#{text}</p>", textilizable("!" + text), "#{text} failed" }
401 end
401 end
402
402
403 def test_cross_project_redmine_links
403 def test_cross_project_redmine_links
404 source_link = link_to('ecookbook:source:/some/file',
404 source_link = link_to('ecookbook:source:/some/file',
405 {:controller => 'repositories', :action => 'entry',
405 {:controller => 'repositories', :action => 'entry',
406 :id => 'ecookbook', :path => ['some', 'file']},
406 :id => 'ecookbook', :path => ['some', 'file']},
407 :class => 'source')
407 :class => 'source')
408 changeset_link = link_to('ecookbook:r2',
408 changeset_link = link_to('ecookbook:r2',
409 {:controller => 'repositories', :action => 'revision',
409 {:controller => 'repositories', :action => 'revision',
410 :id => 'ecookbook', :rev => 2},
410 :id => 'ecookbook', :rev => 2},
411 :class => 'changeset',
411 :class => 'changeset',
412 :title => 'This commit fixes #1, #2 and references #1 & #3')
412 :title => 'This commit fixes #1, #2 and references #1 & #3')
413 to_test = {
413 to_test = {
414 # documents
414 # documents
415 'document:"Test document"' => 'document:"Test document"',
415 'document:"Test document"' => 'document:"Test document"',
416 'ecookbook:document:"Test document"' =>
416 'ecookbook:document:"Test document"' =>
417 link_to("Test document", "/documents/1", :class => "document"),
417 link_to("Test document", "/documents/1", :class => "document"),
418 'invalid:document:"Test document"' => 'invalid:document:"Test document"',
418 'invalid:document:"Test document"' => 'invalid:document:"Test document"',
419 # versions
419 # versions
420 'version:"1.0"' => 'version:"1.0"',
420 'version:"1.0"' => 'version:"1.0"',
421 'ecookbook:version:"1.0"' =>
421 'ecookbook:version:"1.0"' =>
422 link_to("1.0", "/versions/2", :class => "version"),
422 link_to("1.0", "/versions/2", :class => "version"),
423 'invalid:version:"1.0"' => 'invalid:version:"1.0"',
423 'invalid:version:"1.0"' => 'invalid:version:"1.0"',
424 # changeset
424 # changeset
425 'r2' => 'r2',
425 'r2' => 'r2',
426 'ecookbook:r2' => changeset_link,
426 'ecookbook:r2' => changeset_link,
427 'invalid:r2' => 'invalid:r2',
427 'invalid:r2' => 'invalid:r2',
428 # source
428 # source
429 'source:/some/file' => 'source:/some/file',
429 'source:/some/file' => 'source:/some/file',
430 'ecookbook:source:/some/file' => source_link,
430 'ecookbook:source:/some/file' => source_link,
431 'invalid:source:/some/file' => 'invalid:source:/some/file',
431 'invalid:source:/some/file' => 'invalid:source:/some/file',
432 }
432 }
433 @project = Project.find(3)
433 @project = Project.find(3)
434 to_test.each do |text, result|
434 to_test.each do |text, result|
435 assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed"
435 assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed"
436 end
436 end
437 end
437 end
438
438
439 def test_redmine_links_by_name_should_work_with_html_escaped_characters
440 v = Version.generate!(:name => "Test & Show.txt", :project_id => 1)
441 link = link_to("Test & Show.txt", "/versions/#{v.id}", :class => "version")
442
443 @project = v.project
444 assert_equal "<p>#{link}</p>", textilizable('version:"Test & Show.txt"')
445 end
446
439 def test_link_to_issue_subject
447 def test_link_to_issue_subject
440 issue = Issue.generate!(:subject => "01234567890123456789")
448 issue = Issue.generate!(:subject => "01234567890123456789")
441 str = link_to_issue(issue, :truncate => 10)
449 str = link_to_issue(issue, :truncate => 10)
442 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
450 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
443 assert_equal "#{result}: 0123456...", str
451 assert_equal "#{result}: 0123456...", str
444
452
445 issue = Issue.generate!(:subject => "<&>")
453 issue = Issue.generate!(:subject => "<&>")
446 str = link_to_issue(issue)
454 str = link_to_issue(issue)
447 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
455 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
448 assert_equal "#{result}: &lt;&amp;&gt;", str
456 assert_equal "#{result}: &lt;&amp;&gt;", str
449
457
450 issue = Issue.generate!(:subject => "<&>0123456789012345")
458 issue = Issue.generate!(:subject => "<&>0123456789012345")
451 str = link_to_issue(issue, :truncate => 10)
459 str = link_to_issue(issue, :truncate => 10)
452 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
460 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
453 assert_equal "#{result}: &lt;&amp;&gt;0123...", str
461 assert_equal "#{result}: &lt;&amp;&gt;0123...", str
454 end
462 end
455
463
456 def test_link_to_issue_title
464 def test_link_to_issue_title
457 long_str = "0123456789" * 5
465 long_str = "0123456789" * 5
458
466
459 issue = Issue.generate!(:subject => "#{long_str}01234567890123456789")
467 issue = Issue.generate!(:subject => "#{long_str}01234567890123456789")
460 str = link_to_issue(issue, :subject => false)
468 str = link_to_issue(issue, :subject => false)
461 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}",
469 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}",
462 :class => issue.css_classes,
470 :class => issue.css_classes,
463 :title => "#{long_str}0123456...")
471 :title => "#{long_str}0123456...")
464 assert_equal result, str
472 assert_equal result, str
465
473
466 issue = Issue.generate!(:subject => "<&>#{long_str}01234567890123456789")
474 issue = Issue.generate!(:subject => "<&>#{long_str}01234567890123456789")
467 str = link_to_issue(issue, :subject => false)
475 str = link_to_issue(issue, :subject => false)
468 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}",
476 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}",
469 :class => issue.css_classes,
477 :class => issue.css_classes,
470 :title => "<&>#{long_str}0123...")
478 :title => "<&>#{long_str}0123...")
471 assert_equal result, str
479 assert_equal result, str
472 end
480 end
473
481
474 def test_multiple_repositories_redmine_links
482 def test_multiple_repositories_redmine_links
475 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn_repo-1', :url => 'file:///foo/hg')
483 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn_repo-1', :url => 'file:///foo/hg')
476 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
484 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
477 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
485 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
478 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
486 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
479
487
480 changeset_link = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
488 changeset_link = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
481 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
489 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
482 svn_changeset_link = link_to('svn_repo-1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn_repo-1', :rev => 123},
490 svn_changeset_link = link_to('svn_repo-1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn_repo-1', :rev => 123},
483 :class => 'changeset', :title => '')
491 :class => 'changeset', :title => '')
484 hg_changeset_link = link_to('hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
492 hg_changeset_link = link_to('hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
485 :class => 'changeset', :title => '')
493 :class => 'changeset', :title => '')
486
494
487 source_link = link_to('source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
495 source_link = link_to('source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
488 hg_source_link = link_to('source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
496 hg_source_link = link_to('source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
489
497
490 to_test = {
498 to_test = {
491 'r2' => changeset_link,
499 'r2' => changeset_link,
492 'svn_repo-1|r123' => svn_changeset_link,
500 'svn_repo-1|r123' => svn_changeset_link,
493 'invalid|r123' => 'invalid|r123',
501 'invalid|r123' => 'invalid|r123',
494 'commit:hg1|abcd' => hg_changeset_link,
502 'commit:hg1|abcd' => hg_changeset_link,
495 'commit:invalid|abcd' => 'commit:invalid|abcd',
503 'commit:invalid|abcd' => 'commit:invalid|abcd',
496 # source
504 # source
497 'source:some/file' => source_link,
505 'source:some/file' => source_link,
498 'source:hg1|some/file' => hg_source_link,
506 'source:hg1|some/file' => hg_source_link,
499 'source:invalid|some/file' => 'source:invalid|some/file',
507 'source:invalid|some/file' => 'source:invalid|some/file',
500 }
508 }
501
509
502 @project = Project.find(1)
510 @project = Project.find(1)
503 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
511 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
504 end
512 end
505
513
506 def test_cross_project_multiple_repositories_redmine_links
514 def test_cross_project_multiple_repositories_redmine_links
507 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn1', :url => 'file:///foo/hg')
515 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn1', :url => 'file:///foo/hg')
508 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
516 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
509 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
517 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
510 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
518 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
511
519
512 changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
520 changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
513 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
521 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
514 svn_changeset_link = link_to('ecookbook:svn1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn1', :rev => 123},
522 svn_changeset_link = link_to('ecookbook:svn1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn1', :rev => 123},
515 :class => 'changeset', :title => '')
523 :class => 'changeset', :title => '')
516 hg_changeset_link = link_to('ecookbook:hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
524 hg_changeset_link = link_to('ecookbook:hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
517 :class => 'changeset', :title => '')
525 :class => 'changeset', :title => '')
518
526
519 source_link = link_to('ecookbook:source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
527 source_link = link_to('ecookbook:source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
520 hg_source_link = link_to('ecookbook:source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
528 hg_source_link = link_to('ecookbook:source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
521
529
522 to_test = {
530 to_test = {
523 'ecookbook:r2' => changeset_link,
531 'ecookbook:r2' => changeset_link,
524 'ecookbook:svn1|r123' => svn_changeset_link,
532 'ecookbook:svn1|r123' => svn_changeset_link,
525 'ecookbook:invalid|r123' => 'ecookbook:invalid|r123',
533 'ecookbook:invalid|r123' => 'ecookbook:invalid|r123',
526 'ecookbook:commit:hg1|abcd' => hg_changeset_link,
534 'ecookbook:commit:hg1|abcd' => hg_changeset_link,
527 'ecookbook:commit:invalid|abcd' => 'ecookbook:commit:invalid|abcd',
535 'ecookbook:commit:invalid|abcd' => 'ecookbook:commit:invalid|abcd',
528 'invalid:commit:invalid|abcd' => 'invalid:commit:invalid|abcd',
536 'invalid:commit:invalid|abcd' => 'invalid:commit:invalid|abcd',
529 # source
537 # source
530 'ecookbook:source:some/file' => source_link,
538 'ecookbook:source:some/file' => source_link,
531 'ecookbook:source:hg1|some/file' => hg_source_link,
539 'ecookbook:source:hg1|some/file' => hg_source_link,
532 'ecookbook:source:invalid|some/file' => 'ecookbook:source:invalid|some/file',
540 'ecookbook:source:invalid|some/file' => 'ecookbook:source:invalid|some/file',
533 'invalid:source:invalid|some/file' => 'invalid:source:invalid|some/file',
541 'invalid:source:invalid|some/file' => 'invalid:source:invalid|some/file',
534 }
542 }
535
543
536 @project = Project.find(3)
544 @project = Project.find(3)
537 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
545 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
538 end
546 end
539
547
540 def test_redmine_links_git_commit
548 def test_redmine_links_git_commit
541 changeset_link = link_to('abcd',
549 changeset_link = link_to('abcd',
542 {
550 {
543 :controller => 'repositories',
551 :controller => 'repositories',
544 :action => 'revision',
552 :action => 'revision',
545 :id => 'subproject1',
553 :id => 'subproject1',
546 :rev => 'abcd',
554 :rev => 'abcd',
547 },
555 },
548 :class => 'changeset', :title => 'test commit')
556 :class => 'changeset', :title => 'test commit')
549 to_test = {
557 to_test = {
550 'commit:abcd' => changeset_link,
558 'commit:abcd' => changeset_link,
551 }
559 }
552 @project = Project.find(3)
560 @project = Project.find(3)
553 r = Repository::Git.create!(:project => @project, :url => '/tmp/test/git')
561 r = Repository::Git.create!(:project => @project, :url => '/tmp/test/git')
554 assert r
562 assert r
555 c = Changeset.new(:repository => r,
563 c = Changeset.new(:repository => r,
556 :committed_on => Time.now,
564 :committed_on => Time.now,
557 :revision => 'abcd',
565 :revision => 'abcd',
558 :scmid => 'abcd',
566 :scmid => 'abcd',
559 :comments => 'test commit')
567 :comments => 'test commit')
560 assert( c.save )
568 assert( c.save )
561 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
569 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
562 end
570 end
563
571
564 # TODO: Bazaar commit id contains mail address, so it contains '@' and '_'.
572 # TODO: Bazaar commit id contains mail address, so it contains '@' and '_'.
565 def test_redmine_links_darcs_commit
573 def test_redmine_links_darcs_commit
566 changeset_link = link_to('20080308225258-98289-abcd456efg.gz',
574 changeset_link = link_to('20080308225258-98289-abcd456efg.gz',
567 {
575 {
568 :controller => 'repositories',
576 :controller => 'repositories',
569 :action => 'revision',
577 :action => 'revision',
570 :id => 'subproject1',
578 :id => 'subproject1',
571 :rev => '123',
579 :rev => '123',
572 },
580 },
573 :class => 'changeset', :title => 'test commit')
581 :class => 'changeset', :title => 'test commit')
574 to_test = {
582 to_test = {
575 'commit:20080308225258-98289-abcd456efg.gz' => changeset_link,
583 'commit:20080308225258-98289-abcd456efg.gz' => changeset_link,
576 }
584 }
577 @project = Project.find(3)
585 @project = Project.find(3)
578 r = Repository::Darcs.create!(
586 r = Repository::Darcs.create!(
579 :project => @project, :url => '/tmp/test/darcs',
587 :project => @project, :url => '/tmp/test/darcs',
580 :log_encoding => 'UTF-8')
588 :log_encoding => 'UTF-8')
581 assert r
589 assert r
582 c = Changeset.new(:repository => r,
590 c = Changeset.new(:repository => r,
583 :committed_on => Time.now,
591 :committed_on => Time.now,
584 :revision => '123',
592 :revision => '123',
585 :scmid => '20080308225258-98289-abcd456efg.gz',
593 :scmid => '20080308225258-98289-abcd456efg.gz',
586 :comments => 'test commit')
594 :comments => 'test commit')
587 assert( c.save )
595 assert( c.save )
588 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
596 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
589 end
597 end
590
598
591 def test_redmine_links_mercurial_commit
599 def test_redmine_links_mercurial_commit
592 changeset_link_rev = link_to('r123',
600 changeset_link_rev = link_to('r123',
593 {
601 {
594 :controller => 'repositories',
602 :controller => 'repositories',
595 :action => 'revision',
603 :action => 'revision',
596 :id => 'subproject1',
604 :id => 'subproject1',
597 :rev => '123' ,
605 :rev => '123' ,
598 },
606 },
599 :class => 'changeset', :title => 'test commit')
607 :class => 'changeset', :title => 'test commit')
600 changeset_link_commit = link_to('abcd',
608 changeset_link_commit = link_to('abcd',
601 {
609 {
602 :controller => 'repositories',
610 :controller => 'repositories',
603 :action => 'revision',
611 :action => 'revision',
604 :id => 'subproject1',
612 :id => 'subproject1',
605 :rev => 'abcd' ,
613 :rev => 'abcd' ,
606 },
614 },
607 :class => 'changeset', :title => 'test commit')
615 :class => 'changeset', :title => 'test commit')
608 to_test = {
616 to_test = {
609 'r123' => changeset_link_rev,
617 'r123' => changeset_link_rev,
610 'commit:abcd' => changeset_link_commit,
618 'commit:abcd' => changeset_link_commit,
611 }
619 }
612 @project = Project.find(3)
620 @project = Project.find(3)
613 r = Repository::Mercurial.create!(:project => @project, :url => '/tmp/test')
621 r = Repository::Mercurial.create!(:project => @project, :url => '/tmp/test')
614 assert r
622 assert r
615 c = Changeset.new(:repository => r,
623 c = Changeset.new(:repository => r,
616 :committed_on => Time.now,
624 :committed_on => Time.now,
617 :revision => '123',
625 :revision => '123',
618 :scmid => 'abcd',
626 :scmid => 'abcd',
619 :comments => 'test commit')
627 :comments => 'test commit')
620 assert( c.save )
628 assert( c.save )
621 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
629 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
622 end
630 end
623
631
624 def test_attachment_links
632 def test_attachment_links
625 text = 'attachment:error281.txt'
633 text = 'attachment:error281.txt'
626 result = link_to("error281.txt", "/attachments/download/1/error281.txt",
634 result = link_to("error281.txt", "/attachments/download/1/error281.txt",
627 :class => "attachment")
635 :class => "attachment")
628 assert_equal "<p>#{result}</p>",
636 assert_equal "<p>#{result}</p>",
629 textilizable(text,
637 textilizable(text,
630 :attachments => Issue.find(3).attachments),
638 :attachments => Issue.find(3).attachments),
631 "#{text} failed"
639 "#{text} failed"
632 end
640 end
633
641
634 def test_attachment_link_should_link_to_latest_attachment
642 def test_attachment_link_should_link_to_latest_attachment
635 set_tmp_attachments_directory
643 set_tmp_attachments_directory
636 a1 = Attachment.generate!(:filename => "test.txt", :created_on => 1.hour.ago)
644 a1 = Attachment.generate!(:filename => "test.txt", :created_on => 1.hour.ago)
637 a2 = Attachment.generate!(:filename => "test.txt")
645 a2 = Attachment.generate!(:filename => "test.txt")
638 result = link_to("test.txt", "/attachments/download/#{a2.id}/test.txt",
646 result = link_to("test.txt", "/attachments/download/#{a2.id}/test.txt",
639 :class => "attachment")
647 :class => "attachment")
640 assert_equal "<p>#{result}</p>",
648 assert_equal "<p>#{result}</p>",
641 textilizable('attachment:test.txt', :attachments => [a1, a2])
649 textilizable('attachment:test.txt', :attachments => [a1, a2])
642 end
650 end
643
651
644 def test_wiki_links
652 def test_wiki_links
645 russian_eacape = CGI.escape(@russian_test)
653 russian_eacape = CGI.escape(@russian_test)
646 to_test = {
654 to_test = {
647 '[[CookBook documentation]]' =>
655 '[[CookBook documentation]]' =>
648 link_to("CookBook documentation",
656 link_to("CookBook documentation",
649 "/projects/ecookbook/wiki/CookBook_documentation",
657 "/projects/ecookbook/wiki/CookBook_documentation",
650 :class => "wiki-page"),
658 :class => "wiki-page"),
651 '[[Another page|Page]]' =>
659 '[[Another page|Page]]' =>
652 link_to("Page",
660 link_to("Page",
653 "/projects/ecookbook/wiki/Another_page",
661 "/projects/ecookbook/wiki/Another_page",
654 :class => "wiki-page"),
662 :class => "wiki-page"),
655 # title content should be formatted
663 # title content should be formatted
656 '[[Another page|With _styled_ *title*]]' =>
664 '[[Another page|With _styled_ *title*]]' =>
657 link_to("With <em>styled</em> <strong>title</strong>".html_safe,
665 link_to("With <em>styled</em> <strong>title</strong>".html_safe,
658 "/projects/ecookbook/wiki/Another_page",
666 "/projects/ecookbook/wiki/Another_page",
659 :class => "wiki-page"),
667 :class => "wiki-page"),
660 '[[Another page|With title containing <strong>HTML entities &amp; markups</strong>]]' =>
668 '[[Another page|With title containing <strong>HTML entities &amp; markups</strong>]]' =>
661 link_to("With title containing &lt;strong&gt;HTML entities &amp; markups&lt;/strong&gt;".html_safe,
669 link_to("With title containing &lt;strong&gt;HTML entities &amp; markups&lt;/strong&gt;".html_safe,
662 "/projects/ecookbook/wiki/Another_page",
670 "/projects/ecookbook/wiki/Another_page",
663 :class => "wiki-page"),
671 :class => "wiki-page"),
664 # link with anchor
672 # link with anchor
665 '[[CookBook documentation#One-section]]' =>
673 '[[CookBook documentation#One-section]]' =>
666 link_to("CookBook documentation",
674 link_to("CookBook documentation",
667 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
675 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
668 :class => "wiki-page"),
676 :class => "wiki-page"),
669 '[[Another page#anchor|Page]]' =>
677 '[[Another page#anchor|Page]]' =>
670 link_to("Page",
678 link_to("Page",
671 "/projects/ecookbook/wiki/Another_page#anchor",
679 "/projects/ecookbook/wiki/Another_page#anchor",
672 :class => "wiki-page"),
680 :class => "wiki-page"),
673 # UTF8 anchor
681 # UTF8 anchor
674 "[[Another_page##{@russian_test}|#{@russian_test}]]" =>
682 "[[Another_page##{@russian_test}|#{@russian_test}]]" =>
675 link_to(@russian_test,
683 link_to(@russian_test,
676 "/projects/ecookbook/wiki/Another_page##{russian_eacape}",
684 "/projects/ecookbook/wiki/Another_page##{russian_eacape}",
677 :class => "wiki-page"),
685 :class => "wiki-page"),
678 # page that doesn't exist
686 # page that doesn't exist
679 '[[Unknown page]]' =>
687 '[[Unknown page]]' =>
680 link_to("Unknown page",
688 link_to("Unknown page",
681 "/projects/ecookbook/wiki/Unknown_page",
689 "/projects/ecookbook/wiki/Unknown_page",
682 :class => "wiki-page new"),
690 :class => "wiki-page new"),
683 '[[Unknown page|404]]' =>
691 '[[Unknown page|404]]' =>
684 link_to("404",
692 link_to("404",
685 "/projects/ecookbook/wiki/Unknown_page",
693 "/projects/ecookbook/wiki/Unknown_page",
686 :class => "wiki-page new"),
694 :class => "wiki-page new"),
687 # link to another project wiki
695 # link to another project wiki
688 '[[onlinestore:]]' =>
696 '[[onlinestore:]]' =>
689 link_to("onlinestore",
697 link_to("onlinestore",
690 "/projects/onlinestore/wiki",
698 "/projects/onlinestore/wiki",
691 :class => "wiki-page"),
699 :class => "wiki-page"),
692 '[[onlinestore:|Wiki]]' =>
700 '[[onlinestore:|Wiki]]' =>
693 link_to("Wiki",
701 link_to("Wiki",
694 "/projects/onlinestore/wiki",
702 "/projects/onlinestore/wiki",
695 :class => "wiki-page"),
703 :class => "wiki-page"),
696 '[[onlinestore:Start page]]' =>
704 '[[onlinestore:Start page]]' =>
697 link_to("Start page",
705 link_to("Start page",
698 "/projects/onlinestore/wiki/Start_page",
706 "/projects/onlinestore/wiki/Start_page",
699 :class => "wiki-page"),
707 :class => "wiki-page"),
700 '[[onlinestore:Start page|Text]]' =>
708 '[[onlinestore:Start page|Text]]' =>
701 link_to("Text",
709 link_to("Text",
702 "/projects/onlinestore/wiki/Start_page",
710 "/projects/onlinestore/wiki/Start_page",
703 :class => "wiki-page"),
711 :class => "wiki-page"),
704 '[[onlinestore:Unknown page]]' =>
712 '[[onlinestore:Unknown page]]' =>
705 link_to("Unknown page",
713 link_to("Unknown page",
706 "/projects/onlinestore/wiki/Unknown_page",
714 "/projects/onlinestore/wiki/Unknown_page",
707 :class => "wiki-page new"),
715 :class => "wiki-page new"),
708 # struck through link
716 # struck through link
709 '-[[Another page|Page]]-' =>
717 '-[[Another page|Page]]-' =>
710 "<del>".html_safe +
718 "<del>".html_safe +
711 link_to("Page",
719 link_to("Page",
712 "/projects/ecookbook/wiki/Another_page",
720 "/projects/ecookbook/wiki/Another_page",
713 :class => "wiki-page").html_safe +
721 :class => "wiki-page").html_safe +
714 "</del>".html_safe,
722 "</del>".html_safe,
715 '-[[Another page|Page]] link-' =>
723 '-[[Another page|Page]] link-' =>
716 "<del>".html_safe +
724 "<del>".html_safe +
717 link_to("Page",
725 link_to("Page",
718 "/projects/ecookbook/wiki/Another_page",
726 "/projects/ecookbook/wiki/Another_page",
719 :class => "wiki-page").html_safe +
727 :class => "wiki-page").html_safe +
720 " link</del>".html_safe,
728 " link</del>".html_safe,
721 # escaping
729 # escaping
722 '![[Another page|Page]]' => '[[Another page|Page]]',
730 '![[Another page|Page]]' => '[[Another page|Page]]',
723 # project does not exist
731 # project does not exist
724 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
732 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
725 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
733 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
726 }
734 }
727 @project = Project.find(1)
735 @project = Project.find(1)
728 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
736 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
729 end
737 end
730
738
731 def test_wiki_links_within_local_file_generation_context
739 def test_wiki_links_within_local_file_generation_context
732 to_test = {
740 to_test = {
733 # link to a page
741 # link to a page
734 '[[CookBook documentation]]' =>
742 '[[CookBook documentation]]' =>
735 link_to("CookBook documentation", "CookBook_documentation.html",
743 link_to("CookBook documentation", "CookBook_documentation.html",
736 :class => "wiki-page"),
744 :class => "wiki-page"),
737 '[[CookBook documentation|documentation]]' =>
745 '[[CookBook documentation|documentation]]' =>
738 link_to("documentation", "CookBook_documentation.html",
746 link_to("documentation", "CookBook_documentation.html",
739 :class => "wiki-page"),
747 :class => "wiki-page"),
740 '[[CookBook documentation#One-section]]' =>
748 '[[CookBook documentation#One-section]]' =>
741 link_to("CookBook documentation", "CookBook_documentation.html#One-section",
749 link_to("CookBook documentation", "CookBook_documentation.html#One-section",
742 :class => "wiki-page"),
750 :class => "wiki-page"),
743 '[[CookBook documentation#One-section|documentation]]' =>
751 '[[CookBook documentation#One-section|documentation]]' =>
744 link_to("documentation", "CookBook_documentation.html#One-section",
752 link_to("documentation", "CookBook_documentation.html#One-section",
745 :class => "wiki-page"),
753 :class => "wiki-page"),
746 # page that doesn't exist
754 # page that doesn't exist
747 '[[Unknown page]]' =>
755 '[[Unknown page]]' =>
748 link_to("Unknown page", "Unknown_page.html",
756 link_to("Unknown page", "Unknown_page.html",
749 :class => "wiki-page new"),
757 :class => "wiki-page new"),
750 '[[Unknown page|404]]' =>
758 '[[Unknown page|404]]' =>
751 link_to("404", "Unknown_page.html",
759 link_to("404", "Unknown_page.html",
752 :class => "wiki-page new"),
760 :class => "wiki-page new"),
753 '[[Unknown page#anchor]]' =>
761 '[[Unknown page#anchor]]' =>
754 link_to("Unknown page", "Unknown_page.html#anchor",
762 link_to("Unknown page", "Unknown_page.html#anchor",
755 :class => "wiki-page new"),
763 :class => "wiki-page new"),
756 '[[Unknown page#anchor|404]]' =>
764 '[[Unknown page#anchor|404]]' =>
757 link_to("404", "Unknown_page.html#anchor",
765 link_to("404", "Unknown_page.html#anchor",
758 :class => "wiki-page new"),
766 :class => "wiki-page new"),
759 }
767 }
760 @project = Project.find(1)
768 @project = Project.find(1)
761 to_test.each do |text, result|
769 to_test.each do |text, result|
762 assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :local)
770 assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :local)
763 end
771 end
764 end
772 end
765
773
766 def test_wiki_links_within_wiki_page_context
774 def test_wiki_links_within_wiki_page_context
767 page = WikiPage.find_by_title('Another_page' )
775 page = WikiPage.find_by_title('Another_page' )
768 to_test = {
776 to_test = {
769 '[[CookBook documentation]]' =>
777 '[[CookBook documentation]]' =>
770 link_to("CookBook documentation",
778 link_to("CookBook documentation",
771 "/projects/ecookbook/wiki/CookBook_documentation",
779 "/projects/ecookbook/wiki/CookBook_documentation",
772 :class => "wiki-page"),
780 :class => "wiki-page"),
773 '[[CookBook documentation|documentation]]' =>
781 '[[CookBook documentation|documentation]]' =>
774 link_to("documentation",
782 link_to("documentation",
775 "/projects/ecookbook/wiki/CookBook_documentation",
783 "/projects/ecookbook/wiki/CookBook_documentation",
776 :class => "wiki-page"),
784 :class => "wiki-page"),
777 '[[CookBook documentation#One-section]]' =>
785 '[[CookBook documentation#One-section]]' =>
778 link_to("CookBook documentation",
786 link_to("CookBook documentation",
779 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
787 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
780 :class => "wiki-page"),
788 :class => "wiki-page"),
781 '[[CookBook documentation#One-section|documentation]]' =>
789 '[[CookBook documentation#One-section|documentation]]' =>
782 link_to("documentation",
790 link_to("documentation",
783 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
791 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
784 :class => "wiki-page"),
792 :class => "wiki-page"),
785 # link to the current page
793 # link to the current page
786 '[[Another page]]' =>
794 '[[Another page]]' =>
787 link_to("Another page",
795 link_to("Another page",
788 "/projects/ecookbook/wiki/Another_page",
796 "/projects/ecookbook/wiki/Another_page",
789 :class => "wiki-page"),
797 :class => "wiki-page"),
790 '[[Another page|Page]]' =>
798 '[[Another page|Page]]' =>
791 link_to("Page",
799 link_to("Page",
792 "/projects/ecookbook/wiki/Another_page",
800 "/projects/ecookbook/wiki/Another_page",
793 :class => "wiki-page"),
801 :class => "wiki-page"),
794 '[[Another page#anchor]]' =>
802 '[[Another page#anchor]]' =>
795 link_to("Another page",
803 link_to("Another page",
796 "#anchor",
804 "#anchor",
797 :class => "wiki-page"),
805 :class => "wiki-page"),
798 '[[Another page#anchor|Page]]' =>
806 '[[Another page#anchor|Page]]' =>
799 link_to("Page",
807 link_to("Page",
800 "#anchor",
808 "#anchor",
801 :class => "wiki-page"),
809 :class => "wiki-page"),
802 # page that doesn't exist
810 # page that doesn't exist
803 '[[Unknown page]]' =>
811 '[[Unknown page]]' =>
804 link_to("Unknown page",
812 link_to("Unknown page",
805 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page",
813 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page",
806 :class => "wiki-page new"),
814 :class => "wiki-page new"),
807 '[[Unknown page|404]]' =>
815 '[[Unknown page|404]]' =>
808 link_to("404",
816 link_to("404",
809 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page",
817 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page",
810 :class => "wiki-page new"),
818 :class => "wiki-page new"),
811 '[[Unknown page#anchor]]' =>
819 '[[Unknown page#anchor]]' =>
812 link_to("Unknown page",
820 link_to("Unknown page",
813 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor",
821 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor",
814 :class => "wiki-page new"),
822 :class => "wiki-page new"),
815 '[[Unknown page#anchor|404]]' =>
823 '[[Unknown page#anchor|404]]' =>
816 link_to("404",
824 link_to("404",
817 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor",
825 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor",
818 :class => "wiki-page new"),
826 :class => "wiki-page new"),
819 }
827 }
820 @project = Project.find(1)
828 @project = Project.find(1)
821 to_test.each do |text, result|
829 to_test.each do |text, result|
822 assert_equal "<p>#{result}</p>",
830 assert_equal "<p>#{result}</p>",
823 textilizable(WikiContent.new( :text => text, :page => page ), :text)
831 textilizable(WikiContent.new( :text => text, :page => page ), :text)
824 end
832 end
825 end
833 end
826
834
827 def test_wiki_links_anchor_option_should_prepend_page_title_to_href
835 def test_wiki_links_anchor_option_should_prepend_page_title_to_href
828 to_test = {
836 to_test = {
829 # link to a page
837 # link to a page
830 '[[CookBook documentation]]' =>
838 '[[CookBook documentation]]' =>
831 link_to("CookBook documentation",
839 link_to("CookBook documentation",
832 "#CookBook_documentation",
840 "#CookBook_documentation",
833 :class => "wiki-page"),
841 :class => "wiki-page"),
834 '[[CookBook documentation|documentation]]' =>
842 '[[CookBook documentation|documentation]]' =>
835 link_to("documentation",
843 link_to("documentation",
836 "#CookBook_documentation",
844 "#CookBook_documentation",
837 :class => "wiki-page"),
845 :class => "wiki-page"),
838 '[[CookBook documentation#One-section]]' =>
846 '[[CookBook documentation#One-section]]' =>
839 link_to("CookBook documentation",
847 link_to("CookBook documentation",
840 "#CookBook_documentation_One-section",
848 "#CookBook_documentation_One-section",
841 :class => "wiki-page"),
849 :class => "wiki-page"),
842 '[[CookBook documentation#One-section|documentation]]' =>
850 '[[CookBook documentation#One-section|documentation]]' =>
843 link_to("documentation",
851 link_to("documentation",
844 "#CookBook_documentation_One-section",
852 "#CookBook_documentation_One-section",
845 :class => "wiki-page"),
853 :class => "wiki-page"),
846 # page that doesn't exist
854 # page that doesn't exist
847 '[[Unknown page]]' =>
855 '[[Unknown page]]' =>
848 link_to("Unknown page",
856 link_to("Unknown page",
849 "#Unknown_page",
857 "#Unknown_page",
850 :class => "wiki-page new"),
858 :class => "wiki-page new"),
851 '[[Unknown page|404]]' =>
859 '[[Unknown page|404]]' =>
852 link_to("404",
860 link_to("404",
853 "#Unknown_page",
861 "#Unknown_page",
854 :class => "wiki-page new"),
862 :class => "wiki-page new"),
855 '[[Unknown page#anchor]]' =>
863 '[[Unknown page#anchor]]' =>
856 link_to("Unknown page",
864 link_to("Unknown page",
857 "#Unknown_page_anchor",
865 "#Unknown_page_anchor",
858 :class => "wiki-page new"),
866 :class => "wiki-page new"),
859 '[[Unknown page#anchor|404]]' =>
867 '[[Unknown page#anchor|404]]' =>
860 link_to("404",
868 link_to("404",
861 "#Unknown_page_anchor",
869 "#Unknown_page_anchor",
862 :class => "wiki-page new"),
870 :class => "wiki-page new"),
863 }
871 }
864 @project = Project.find(1)
872 @project = Project.find(1)
865 to_test.each do |text, result|
873 to_test.each do |text, result|
866 assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :anchor)
874 assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :anchor)
867 end
875 end
868 end
876 end
869
877
870 def test_html_tags
878 def test_html_tags
871 to_test = {
879 to_test = {
872 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
880 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
873 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
881 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
874 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
882 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
875 # do not escape pre/code tags
883 # do not escape pre/code tags
876 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
884 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
877 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
885 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
878 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
886 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
879 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
887 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
880 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
888 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
881 # remove attributes except class
889 # remove attributes except class
882 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
890 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
883 '<pre class="foo">some text</pre>' => '<pre class="foo">some text</pre>',
891 '<pre class="foo">some text</pre>' => '<pre class="foo">some text</pre>',
884 "<pre class='foo bar'>some text</pre>" => "<pre class='foo bar'>some text</pre>",
892 "<pre class='foo bar'>some text</pre>" => "<pre class='foo bar'>some text</pre>",
885 '<pre class="foo bar">some text</pre>' => '<pre class="foo bar">some text</pre>',
893 '<pre class="foo bar">some text</pre>' => '<pre class="foo bar">some text</pre>',
886 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
894 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
887 # xss
895 # xss
888 '<pre><code class=""onmouseover="alert(1)">text</code></pre>' => '<pre><code>text</code></pre>',
896 '<pre><code class=""onmouseover="alert(1)">text</code></pre>' => '<pre><code>text</code></pre>',
889 '<pre class=""onmouseover="alert(1)">text</pre>' => '<pre>text</pre>',
897 '<pre class=""onmouseover="alert(1)">text</pre>' => '<pre>text</pre>',
890 }
898 }
891 to_test.each { |text, result| assert_equal result, textilizable(text) }
899 to_test.each { |text, result| assert_equal result, textilizable(text) }
892 end
900 end
893
901
894 def test_allowed_html_tags
902 def test_allowed_html_tags
895 to_test = {
903 to_test = {
896 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
904 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
897 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
905 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
898 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
906 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
899 }
907 }
900 to_test.each { |text, result| assert_equal result, textilizable(text) }
908 to_test.each { |text, result| assert_equal result, textilizable(text) }
901 end
909 end
902
910
903 def test_pre_tags
911 def test_pre_tags
904 raw = <<-RAW
912 raw = <<-RAW
905 Before
913 Before
906
914
907 <pre>
915 <pre>
908 <prepared-statement-cache-size>32</prepared-statement-cache-size>
916 <prepared-statement-cache-size>32</prepared-statement-cache-size>
909 </pre>
917 </pre>
910
918
911 After
919 After
912 RAW
920 RAW
913
921
914 expected = <<-EXPECTED
922 expected = <<-EXPECTED
915 <p>Before</p>
923 <p>Before</p>
916 <pre>
924 <pre>
917 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
925 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
918 </pre>
926 </pre>
919 <p>After</p>
927 <p>After</p>
920 EXPECTED
928 EXPECTED
921
929
922 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
930 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
923 end
931 end
924
932
925 def test_pre_content_should_not_parse_wiki_and_redmine_links
933 def test_pre_content_should_not_parse_wiki_and_redmine_links
926 raw = <<-RAW
934 raw = <<-RAW
927 [[CookBook documentation]]
935 [[CookBook documentation]]
928
936
929 #1
937 #1
930
938
931 <pre>
939 <pre>
932 [[CookBook documentation]]
940 [[CookBook documentation]]
933
941
934 #1
942 #1
935 </pre>
943 </pre>
936 RAW
944 RAW
937
945
938 result1 = link_to("CookBook documentation",
946 result1 = link_to("CookBook documentation",
939 "/projects/ecookbook/wiki/CookBook_documentation",
947 "/projects/ecookbook/wiki/CookBook_documentation",
940 :class => "wiki-page")
948 :class => "wiki-page")
941 result2 = link_to('#1',
949 result2 = link_to('#1',
942 "/issues/1",
950 "/issues/1",
943 :class => Issue.find(1).css_classes,
951 :class => Issue.find(1).css_classes,
944 :title => "Can't print recipes (New)")
952 :title => "Can't print recipes (New)")
945
953
946 expected = <<-EXPECTED
954 expected = <<-EXPECTED
947 <p>#{result1}</p>
955 <p>#{result1}</p>
948 <p>#{result2}</p>
956 <p>#{result2}</p>
949 <pre>
957 <pre>
950 [[CookBook documentation]]
958 [[CookBook documentation]]
951
959
952 #1
960 #1
953 </pre>
961 </pre>
954 EXPECTED
962 EXPECTED
955
963
956 @project = Project.find(1)
964 @project = Project.find(1)
957 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
965 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
958 end
966 end
959
967
960 def test_non_closing_pre_blocks_should_be_closed
968 def test_non_closing_pre_blocks_should_be_closed
961 raw = <<-RAW
969 raw = <<-RAW
962 <pre><code>
970 <pre><code>
963 RAW
971 RAW
964
972
965 expected = <<-EXPECTED
973 expected = <<-EXPECTED
966 <pre><code>
974 <pre><code>
967 </code></pre>
975 </code></pre>
968 EXPECTED
976 EXPECTED
969
977
970 @project = Project.find(1)
978 @project = Project.find(1)
971 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
979 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
972 end
980 end
973
981
974 def test_syntax_highlight
982 def test_syntax_highlight
975 raw = <<-RAW
983 raw = <<-RAW
976 <pre><code class="ruby">
984 <pre><code class="ruby">
977 # Some ruby code here
985 # Some ruby code here
978 </code></pre>
986 </code></pre>
979 RAW
987 RAW
980
988
981 expected = <<-EXPECTED
989 expected = <<-EXPECTED
982 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="comment"># Some ruby code here</span></span>
990 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="comment"># Some ruby code here</span></span>
983 </code></pre>
991 </code></pre>
984 EXPECTED
992 EXPECTED
985
993
986 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
994 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
987 end
995 end
988
996
989 def test_to_path_param
997 def test_to_path_param
990 assert_equal 'test1/test2', to_path_param('test1/test2')
998 assert_equal 'test1/test2', to_path_param('test1/test2')
991 assert_equal 'test1/test2', to_path_param('/test1/test2/')
999 assert_equal 'test1/test2', to_path_param('/test1/test2/')
992 assert_equal 'test1/test2', to_path_param('//test1/test2/')
1000 assert_equal 'test1/test2', to_path_param('//test1/test2/')
993 assert_equal nil, to_path_param('/')
1001 assert_equal nil, to_path_param('/')
994 end
1002 end
995
1003
996 def test_wiki_links_in_tables
1004 def test_wiki_links_in_tables
997 text = "|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|"
1005 text = "|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|"
998 link1 = link_to("Link title", "/projects/ecookbook/wiki/Page", :class => "wiki-page new")
1006 link1 = link_to("Link title", "/projects/ecookbook/wiki/Page", :class => "wiki-page new")
999 link2 = link_to("Other title", "/projects/ecookbook/wiki/Other_Page", :class => "wiki-page new")
1007 link2 = link_to("Other title", "/projects/ecookbook/wiki/Other_Page", :class => "wiki-page new")
1000 link3 = link_to("Last page", "/projects/ecookbook/wiki/Last_page", :class => "wiki-page new")
1008 link3 = link_to("Last page", "/projects/ecookbook/wiki/Last_page", :class => "wiki-page new")
1001 result = "<tr><td>#{link1}</td>" +
1009 result = "<tr><td>#{link1}</td>" +
1002 "<td>#{link2}</td>" +
1010 "<td>#{link2}</td>" +
1003 "</tr><tr><td>Cell 21</td><td>#{link3}</td></tr>"
1011 "</tr><tr><td>Cell 21</td><td>#{link3}</td></tr>"
1004 @project = Project.find(1)
1012 @project = Project.find(1)
1005 assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '')
1013 assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '')
1006 end
1014 end
1007
1015
1008 def test_text_formatting
1016 def test_text_formatting
1009 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
1017 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
1010 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
1018 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
1011 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
1019 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
1012 'a H *umane* W *eb* T *ext* G *enerator*' => 'a H <strong>umane</strong> W <strong>eb</strong> T <strong>ext</strong> G <strong>enerator</strong>',
1020 'a H *umane* W *eb* T *ext* G *enerator*' => 'a H <strong>umane</strong> W <strong>eb</strong> T <strong>ext</strong> G <strong>enerator</strong>',
1013 'a *H* umane *W* eb *T* ext *G* enerator' => 'a <strong>H</strong> umane <strong>W</strong> eb <strong>T</strong> ext <strong>G</strong> enerator',
1021 'a *H* umane *W* eb *T* ext *G* enerator' => 'a <strong>H</strong> umane <strong>W</strong> eb <strong>T</strong> ext <strong>G</strong> enerator',
1014 }
1022 }
1015 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
1023 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
1016 end
1024 end
1017
1025
1018 def test_wiki_horizontal_rule
1026 def test_wiki_horizontal_rule
1019 assert_equal '<hr />', textilizable('---')
1027 assert_equal '<hr />', textilizable('---')
1020 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
1028 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
1021 end
1029 end
1022
1030
1023 def test_footnotes
1031 def test_footnotes
1024 raw = <<-RAW
1032 raw = <<-RAW
1025 This is some text[1].
1033 This is some text[1].
1026
1034
1027 fn1. This is the foot note
1035 fn1. This is the foot note
1028 RAW
1036 RAW
1029
1037
1030 expected = <<-EXPECTED
1038 expected = <<-EXPECTED
1031 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
1039 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
1032 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
1040 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
1033 EXPECTED
1041 EXPECTED
1034
1042
1035 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
1043 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
1036 end
1044 end
1037
1045
1038 def test_headings
1046 def test_headings
1039 raw = 'h1. Some heading'
1047 raw = 'h1. Some heading'
1040 expected = %|<a name="Some-heading"></a>\n<h1 >Some heading<a href="#Some-heading" class="wiki-anchor">&para;</a></h1>|
1048 expected = %|<a name="Some-heading"></a>\n<h1 >Some heading<a href="#Some-heading" class="wiki-anchor">&para;</a></h1>|
1041
1049
1042 assert_equal expected, textilizable(raw)
1050 assert_equal expected, textilizable(raw)
1043 end
1051 end
1044
1052
1045 def test_headings_with_special_chars
1053 def test_headings_with_special_chars
1046 # This test makes sure that the generated anchor names match the expected
1054 # This test makes sure that the generated anchor names match the expected
1047 # ones even if the heading text contains unconventional characters
1055 # ones even if the heading text contains unconventional characters
1048 raw = 'h1. Some heading related to version 0.5'
1056 raw = 'h1. Some heading related to version 0.5'
1049 anchor = sanitize_anchor_name("Some-heading-related-to-version-0.5")
1057 anchor = sanitize_anchor_name("Some-heading-related-to-version-0.5")
1050 expected = %|<a name="#{anchor}"></a>\n<h1 >Some heading related to version 0.5<a href="##{anchor}" class="wiki-anchor">&para;</a></h1>|
1058 expected = %|<a name="#{anchor}"></a>\n<h1 >Some heading related to version 0.5<a href="##{anchor}" class="wiki-anchor">&para;</a></h1>|
1051
1059
1052 assert_equal expected, textilizable(raw)
1060 assert_equal expected, textilizable(raw)
1053 end
1061 end
1054
1062
1055 def test_headings_in_wiki_single_page_export_should_be_prepended_with_page_title
1063 def test_headings_in_wiki_single_page_export_should_be_prepended_with_page_title
1056 page = WikiPage.new( :title => 'Page Title', :wiki_id => 1 )
1064 page = WikiPage.new( :title => 'Page Title', :wiki_id => 1 )
1057 content = WikiContent.new( :text => 'h1. Some heading', :page => page )
1065 content = WikiContent.new( :text => 'h1. Some heading', :page => page )
1058
1066
1059 expected = %|<a name="Page_Title_Some-heading"></a>\n<h1 >Some heading<a href="#Page_Title_Some-heading" class="wiki-anchor">&para;</a></h1>|
1067 expected = %|<a name="Page_Title_Some-heading"></a>\n<h1 >Some heading<a href="#Page_Title_Some-heading" class="wiki-anchor">&para;</a></h1>|
1060
1068
1061 assert_equal expected, textilizable(content, :text, :wiki_links => :anchor )
1069 assert_equal expected, textilizable(content, :text, :wiki_links => :anchor )
1062 end
1070 end
1063
1071
1064 def test_table_of_content
1072 def test_table_of_content
1065 raw = <<-RAW
1073 raw = <<-RAW
1066 {{toc}}
1074 {{toc}}
1067
1075
1068 h1. Title
1076 h1. Title
1069
1077
1070 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
1078 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
1071
1079
1072 h2. Subtitle with a [[Wiki]] link
1080 h2. Subtitle with a [[Wiki]] link
1073
1081
1074 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
1082 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
1075
1083
1076 h2. Subtitle with [[Wiki|another Wiki]] link
1084 h2. Subtitle with [[Wiki|another Wiki]] link
1077
1085
1078 h2. Subtitle with %{color:red}red text%
1086 h2. Subtitle with %{color:red}red text%
1079
1087
1080 <pre>
1088 <pre>
1081 some code
1089 some code
1082 </pre>
1090 </pre>
1083
1091
1084 h3. Subtitle with *some* _modifiers_
1092 h3. Subtitle with *some* _modifiers_
1085
1093
1086 h3. Subtitle with @inline code@
1094 h3. Subtitle with @inline code@
1087
1095
1088 h1. Another title
1096 h1. Another title
1089
1097
1090 h3. An "Internet link":http://www.redmine.org/ inside subtitle
1098 h3. An "Internet link":http://www.redmine.org/ inside subtitle
1091
1099
1092 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
1100 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
1093
1101
1094 RAW
1102 RAW
1095
1103
1096 expected = '<ul class="toc">' +
1104 expected = '<ul class="toc">' +
1097 '<li><a href="#Title">Title</a>' +
1105 '<li><a href="#Title">Title</a>' +
1098 '<ul>' +
1106 '<ul>' +
1099 '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
1107 '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
1100 '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
1108 '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
1101 '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
1109 '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
1102 '<ul>' +
1110 '<ul>' +
1103 '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
1111 '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
1104 '<li><a href="#Subtitle-with-inline-code">Subtitle with inline code</a></li>' +
1112 '<li><a href="#Subtitle-with-inline-code">Subtitle with inline code</a></li>' +
1105 '</ul>' +
1113 '</ul>' +
1106 '</li>' +
1114 '</li>' +
1107 '</ul>' +
1115 '</ul>' +
1108 '</li>' +
1116 '</li>' +
1109 '<li><a href="#Another-title">Another title</a>' +
1117 '<li><a href="#Another-title">Another title</a>' +
1110 '<ul>' +
1118 '<ul>' +
1111 '<li>' +
1119 '<li>' +
1112 '<ul>' +
1120 '<ul>' +
1113 '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
1121 '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
1114 '</ul>' +
1122 '</ul>' +
1115 '</li>' +
1123 '</li>' +
1116 '<li><a href="#Project-Name">Project Name</a></li>' +
1124 '<li><a href="#Project-Name">Project Name</a></li>' +
1117 '</ul>' +
1125 '</ul>' +
1118 '</li>' +
1126 '</li>' +
1119 '</ul>'
1127 '</ul>'
1120
1128
1121 @project = Project.find(1)
1129 @project = Project.find(1)
1122 assert textilizable(raw).gsub("\n", "").include?(expected)
1130 assert textilizable(raw).gsub("\n", "").include?(expected)
1123 end
1131 end
1124
1132
1125 def test_table_of_content_should_generate_unique_anchors
1133 def test_table_of_content_should_generate_unique_anchors
1126 raw = <<-RAW
1134 raw = <<-RAW
1127 {{toc}}
1135 {{toc}}
1128
1136
1129 h1. Title
1137 h1. Title
1130
1138
1131 h2. Subtitle
1139 h2. Subtitle
1132
1140
1133 h2. Subtitle
1141 h2. Subtitle
1134 RAW
1142 RAW
1135
1143
1136 expected = '<ul class="toc">' +
1144 expected = '<ul class="toc">' +
1137 '<li><a href="#Title">Title</a>' +
1145 '<li><a href="#Title">Title</a>' +
1138 '<ul>' +
1146 '<ul>' +
1139 '<li><a href="#Subtitle">Subtitle</a></li>' +
1147 '<li><a href="#Subtitle">Subtitle</a></li>' +
1140 '<li><a href="#Subtitle-2">Subtitle</a></li>'
1148 '<li><a href="#Subtitle-2">Subtitle</a></li>'
1141 '</ul>'
1149 '</ul>'
1142 '</li>' +
1150 '</li>' +
1143 '</ul>'
1151 '</ul>'
1144
1152
1145 @project = Project.find(1)
1153 @project = Project.find(1)
1146 result = textilizable(raw).gsub("\n", "")
1154 result = textilizable(raw).gsub("\n", "")
1147 assert_include expected, result
1155 assert_include expected, result
1148 assert_include '<a name="Subtitle">', result
1156 assert_include '<a name="Subtitle">', result
1149 assert_include '<a name="Subtitle-2">', result
1157 assert_include '<a name="Subtitle-2">', result
1150 end
1158 end
1151
1159
1152 def test_table_of_content_should_contain_included_page_headings
1160 def test_table_of_content_should_contain_included_page_headings
1153 raw = <<-RAW
1161 raw = <<-RAW
1154 {{toc}}
1162 {{toc}}
1155
1163
1156 h1. Included
1164 h1. Included
1157
1165
1158 {{include(Child_1)}}
1166 {{include(Child_1)}}
1159 RAW
1167 RAW
1160
1168
1161 expected = '<ul class="toc">' +
1169 expected = '<ul class="toc">' +
1162 '<li><a href="#Included">Included</a></li>' +
1170 '<li><a href="#Included">Included</a></li>' +
1163 '<li><a href="#Child-page-1">Child page 1</a></li>' +
1171 '<li><a href="#Child-page-1">Child page 1</a></li>' +
1164 '</ul>'
1172 '</ul>'
1165
1173
1166 @project = Project.find(1)
1174 @project = Project.find(1)
1167 assert textilizable(raw).gsub("\n", "").include?(expected)
1175 assert textilizable(raw).gsub("\n", "").include?(expected)
1168 end
1176 end
1169
1177
1170 def test_toc_with_textile_formatting_should_be_parsed
1178 def test_toc_with_textile_formatting_should_be_parsed
1171 with_settings :text_formatting => 'textile' do
1179 with_settings :text_formatting => 'textile' do
1172 assert_select_in textilizable("{{toc}}\n\nh1. Heading"), 'ul.toc li', :text => 'Heading'
1180 assert_select_in textilizable("{{toc}}\n\nh1. Heading"), 'ul.toc li', :text => 'Heading'
1173 assert_select_in textilizable("{{<toc}}\n\nh1. Heading"), 'ul.toc.left li', :text => 'Heading'
1181 assert_select_in textilizable("{{<toc}}\n\nh1. Heading"), 'ul.toc.left li', :text => 'Heading'
1174 assert_select_in textilizable("{{>toc}}\n\nh1. Heading"), 'ul.toc.right li', :text => 'Heading'
1182 assert_select_in textilizable("{{>toc}}\n\nh1. Heading"), 'ul.toc.right li', :text => 'Heading'
1175 end
1183 end
1176 end
1184 end
1177
1185
1178 if Object.const_defined?(:Redcarpet)
1186 if Object.const_defined?(:Redcarpet)
1179 def test_toc_with_markdown_formatting_should_be_parsed
1187 def test_toc_with_markdown_formatting_should_be_parsed
1180 with_settings :text_formatting => 'markdown' do
1188 with_settings :text_formatting => 'markdown' do
1181 assert_select_in textilizable("{{toc}}\n\n# Heading"), 'ul.toc li', :text => 'Heading'
1189 assert_select_in textilizable("{{toc}}\n\n# Heading"), 'ul.toc li', :text => 'Heading'
1182 assert_select_in textilizable("{{<toc}}\n\n# Heading"), 'ul.toc.left li', :text => 'Heading'
1190 assert_select_in textilizable("{{<toc}}\n\n# Heading"), 'ul.toc.left li', :text => 'Heading'
1183 assert_select_in textilizable("{{>toc}}\n\n# Heading"), 'ul.toc.right li', :text => 'Heading'
1191 assert_select_in textilizable("{{>toc}}\n\n# Heading"), 'ul.toc.right li', :text => 'Heading'
1184 end
1192 end
1185 end
1193 end
1186 end
1194 end
1187
1195
1188 def test_section_edit_links
1196 def test_section_edit_links
1189 raw = <<-RAW
1197 raw = <<-RAW
1190 h1. Title
1198 h1. Title
1191
1199
1192 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
1200 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
1193
1201
1194 h2. Subtitle with a [[Wiki]] link
1202 h2. Subtitle with a [[Wiki]] link
1195
1203
1196 h2. Subtitle with *some* _modifiers_
1204 h2. Subtitle with *some* _modifiers_
1197
1205
1198 h2. Subtitle with @inline code@
1206 h2. Subtitle with @inline code@
1199
1207
1200 <pre>
1208 <pre>
1201 some code
1209 some code
1202
1210
1203 h2. heading inside pre
1211 h2. heading inside pre
1204
1212
1205 <h2>html heading inside pre</h2>
1213 <h2>html heading inside pre</h2>
1206 </pre>
1214 </pre>
1207
1215
1208 h2. Subtitle after pre tag
1216 h2. Subtitle after pre tag
1209 RAW
1217 RAW
1210
1218
1211 @project = Project.find(1)
1219 @project = Project.find(1)
1212 set_language_if_valid 'en'
1220 set_language_if_valid 'en'
1213 result = textilizable(raw, :edit_section_links => {:controller => 'wiki', :action => 'edit', :project_id => '1', :id => 'Test'}).gsub("\n", "")
1221 result = textilizable(raw, :edit_section_links => {:controller => 'wiki', :action => 'edit', :project_id => '1', :id => 'Test'}).gsub("\n", "")
1214
1222
1215 # heading that contains inline code
1223 # heading that contains inline code
1216 assert_match Regexp.new('<div class="contextual" id="section-4" title="Edit this section">' +
1224 assert_match Regexp.new('<div class="contextual" id="section-4" title="Edit this section">' +
1217 '<a href="/projects/1/wiki/Test/edit\?section=4"><img alt="Edit" src="/images/edit.png(\?\d+)?" /></a></div>' +
1225 '<a href="/projects/1/wiki/Test/edit\?section=4"><img alt="Edit" src="/images/edit.png(\?\d+)?" /></a></div>' +
1218 '<a name="Subtitle-with-inline-code"></a>' +
1226 '<a name="Subtitle-with-inline-code"></a>' +
1219 '<h2 >Subtitle with <code>inline code</code><a href="#Subtitle-with-inline-code" class="wiki-anchor">&para;</a></h2>'),
1227 '<h2 >Subtitle with <code>inline code</code><a href="#Subtitle-with-inline-code" class="wiki-anchor">&para;</a></h2>'),
1220 result
1228 result
1221
1229
1222 # last heading
1230 # last heading
1223 assert_match Regexp.new('<div class="contextual" id="section-5" title="Edit this section">' +
1231 assert_match Regexp.new('<div class="contextual" id="section-5" title="Edit this section">' +
1224 '<a href="/projects/1/wiki/Test/edit\?section=5"><img alt="Edit" src="/images/edit.png(\?\d+)?" /></a></div>' +
1232 '<a href="/projects/1/wiki/Test/edit\?section=5"><img alt="Edit" src="/images/edit.png(\?\d+)?" /></a></div>' +
1225 '<a name="Subtitle-after-pre-tag"></a>' +
1233 '<a name="Subtitle-after-pre-tag"></a>' +
1226 '<h2 >Subtitle after pre tag<a href="#Subtitle-after-pre-tag" class="wiki-anchor">&para;</a></h2>'),
1234 '<h2 >Subtitle after pre tag<a href="#Subtitle-after-pre-tag" class="wiki-anchor">&para;</a></h2>'),
1227 result
1235 result
1228 end
1236 end
1229
1237
1230 def test_default_formatter
1238 def test_default_formatter
1231 with_settings :text_formatting => 'unknown' do
1239 with_settings :text_formatting => 'unknown' do
1232 text = 'a *link*: http://www.example.net/'
1240 text = 'a *link*: http://www.example.net/'
1233 assert_equal '<p>a *link*: <a class="external" href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
1241 assert_equal '<p>a *link*: <a class="external" href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
1234 end
1242 end
1235 end
1243 end
1236
1244
1237 def test_due_date_distance_in_words
1245 def test_due_date_distance_in_words
1238 to_test = { Date.today => 'Due in 0 days',
1246 to_test = { Date.today => 'Due in 0 days',
1239 Date.today + 1 => 'Due in 1 day',
1247 Date.today + 1 => 'Due in 1 day',
1240 Date.today + 100 => 'Due in about 3 months',
1248 Date.today + 100 => 'Due in about 3 months',
1241 Date.today + 20000 => 'Due in over 54 years',
1249 Date.today + 20000 => 'Due in over 54 years',
1242 Date.today - 1 => '1 day late',
1250 Date.today - 1 => '1 day late',
1243 Date.today - 100 => 'about 3 months late',
1251 Date.today - 100 => 'about 3 months late',
1244 Date.today - 20000 => 'over 54 years late',
1252 Date.today - 20000 => 'over 54 years late',
1245 }
1253 }
1246 ::I18n.locale = :en
1254 ::I18n.locale = :en
1247 to_test.each do |date, expected|
1255 to_test.each do |date, expected|
1248 assert_equal expected, due_date_distance_in_words(date)
1256 assert_equal expected, due_date_distance_in_words(date)
1249 end
1257 end
1250 end
1258 end
1251
1259
1252 def test_avatar_enabled
1260 def test_avatar_enabled
1253 with_settings :gravatar_enabled => '1' do
1261 with_settings :gravatar_enabled => '1' do
1254 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
1262 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
1255 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
1263 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
1256 # Default size is 50
1264 # Default size is 50
1257 assert avatar('jsmith <jsmith@somenet.foo>').include?('size=50')
1265 assert avatar('jsmith <jsmith@somenet.foo>').include?('size=50')
1258 assert avatar('jsmith <jsmith@somenet.foo>', :size => 24).include?('size=24')
1266 assert avatar('jsmith <jsmith@somenet.foo>', :size => 24).include?('size=24')
1259 # Non-avatar options should be considered html options
1267 # Non-avatar options should be considered html options
1260 assert avatar('jsmith <jsmith@somenet.foo>', :title => 'John Smith').include?('title="John Smith"')
1268 assert avatar('jsmith <jsmith@somenet.foo>', :title => 'John Smith').include?('title="John Smith"')
1261 # The default class of the img tag should be gravatar
1269 # The default class of the img tag should be gravatar
1262 assert avatar('jsmith <jsmith@somenet.foo>').include?('class="gravatar"')
1270 assert avatar('jsmith <jsmith@somenet.foo>').include?('class="gravatar"')
1263 assert !avatar('jsmith <jsmith@somenet.foo>', :class => 'picture').include?('class="gravatar"')
1271 assert !avatar('jsmith <jsmith@somenet.foo>', :class => 'picture').include?('class="gravatar"')
1264 assert_nil avatar('jsmith')
1272 assert_nil avatar('jsmith')
1265 assert_nil avatar(nil)
1273 assert_nil avatar(nil)
1266 end
1274 end
1267 end
1275 end
1268
1276
1269 def test_avatar_disabled
1277 def test_avatar_disabled
1270 with_settings :gravatar_enabled => '0' do
1278 with_settings :gravatar_enabled => '0' do
1271 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
1279 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
1272 end
1280 end
1273 end
1281 end
1274
1282
1275 def test_link_to_user
1283 def test_link_to_user
1276 user = User.find(2)
1284 user = User.find(2)
1277 result = link_to("John Smith", "/users/2", :class => "user active")
1285 result = link_to("John Smith", "/users/2", :class => "user active")
1278 assert_equal result, link_to_user(user)
1286 assert_equal result, link_to_user(user)
1279 end
1287 end
1280
1288
1281 def test_link_to_user_should_not_link_to_locked_user
1289 def test_link_to_user_should_not_link_to_locked_user
1282 with_current_user nil do
1290 with_current_user nil do
1283 user = User.find(5)
1291 user = User.find(5)
1284 assert user.locked?
1292 assert user.locked?
1285 assert_equal 'Dave2 Lopper2', link_to_user(user)
1293 assert_equal 'Dave2 Lopper2', link_to_user(user)
1286 end
1294 end
1287 end
1295 end
1288
1296
1289 def test_link_to_user_should_link_to_locked_user_if_current_user_is_admin
1297 def test_link_to_user_should_link_to_locked_user_if_current_user_is_admin
1290 with_current_user User.find(1) do
1298 with_current_user User.find(1) do
1291 user = User.find(5)
1299 user = User.find(5)
1292 assert user.locked?
1300 assert user.locked?
1293 result = link_to("Dave2 Lopper2", "/users/5", :class => "user locked")
1301 result = link_to("Dave2 Lopper2", "/users/5", :class => "user locked")
1294 assert_equal result, link_to_user(user)
1302 assert_equal result, link_to_user(user)
1295 end
1303 end
1296 end
1304 end
1297
1305
1298 def test_link_to_user_should_not_link_to_anonymous
1306 def test_link_to_user_should_not_link_to_anonymous
1299 user = User.anonymous
1307 user = User.anonymous
1300 assert user.anonymous?
1308 assert user.anonymous?
1301 t = link_to_user(user)
1309 t = link_to_user(user)
1302 assert_equal ::I18n.t(:label_user_anonymous), t
1310 assert_equal ::I18n.t(:label_user_anonymous), t
1303 end
1311 end
1304
1312
1305 def test_link_to_attachment
1313 def test_link_to_attachment
1306 a = Attachment.find(3)
1314 a = Attachment.find(3)
1307 assert_equal '<a href="/attachments/3/logo.gif">logo.gif</a>',
1315 assert_equal '<a href="/attachments/3/logo.gif">logo.gif</a>',
1308 link_to_attachment(a)
1316 link_to_attachment(a)
1309 assert_equal '<a href="/attachments/3/logo.gif">Text</a>',
1317 assert_equal '<a href="/attachments/3/logo.gif">Text</a>',
1310 link_to_attachment(a, :text => 'Text')
1318 link_to_attachment(a, :text => 'Text')
1311 result = link_to("logo.gif", "/attachments/3/logo.gif", :class => "foo")
1319 result = link_to("logo.gif", "/attachments/3/logo.gif", :class => "foo")
1312 assert_equal result,
1320 assert_equal result,
1313 link_to_attachment(a, :class => 'foo')
1321 link_to_attachment(a, :class => 'foo')
1314 assert_equal '<a href="/attachments/download/3/logo.gif">logo.gif</a>',
1322 assert_equal '<a href="/attachments/download/3/logo.gif">logo.gif</a>',
1315 link_to_attachment(a, :download => true)
1323 link_to_attachment(a, :download => true)
1316 assert_equal '<a href="http://test.host/attachments/3/logo.gif">logo.gif</a>',
1324 assert_equal '<a href="http://test.host/attachments/3/logo.gif">logo.gif</a>',
1317 link_to_attachment(a, :only_path => false)
1325 link_to_attachment(a, :only_path => false)
1318 end
1326 end
1319
1327
1320 def test_thumbnail_tag
1328 def test_thumbnail_tag
1321 a = Attachment.find(3)
1329 a = Attachment.find(3)
1322 assert_equal '<a href="/attachments/3/logo.gif" title="logo.gif"><img alt="3" src="/attachments/thumbnail/3" /></a>',
1330 assert_equal '<a href="/attachments/3/logo.gif" title="logo.gif"><img alt="3" src="/attachments/thumbnail/3" /></a>',
1323 thumbnail_tag(a)
1331 thumbnail_tag(a)
1324 end
1332 end
1325
1333
1326 def test_link_to_project
1334 def test_link_to_project
1327 project = Project.find(1)
1335 project = Project.find(1)
1328 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
1336 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
1329 link_to_project(project)
1337 link_to_project(project)
1330 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
1338 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
1331 link_to_project(project, :action => 'settings')
1339 link_to_project(project, :action => 'settings')
1332 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
1340 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
1333 link_to_project(project, {:only_path => false, :jump => 'blah'})
1341 link_to_project(project, {:only_path => false, :jump => 'blah'})
1334 result = link_to("eCookbook", "/projects/ecookbook/settings", :class => "project")
1342 result = link_to("eCookbook", "/projects/ecookbook/settings", :class => "project")
1335 assert_equal result,
1343 assert_equal result,
1336 link_to_project(project, {:action => 'settings'}, :class => "project")
1344 link_to_project(project, {:action => 'settings'}, :class => "project")
1337 end
1345 end
1338
1346
1339 def test_link_to_project_settings
1347 def test_link_to_project_settings
1340 project = Project.find(1)
1348 project = Project.find(1)
1341 assert_equal '<a href="/projects/ecookbook/settings">eCookbook</a>', link_to_project_settings(project)
1349 assert_equal '<a href="/projects/ecookbook/settings">eCookbook</a>', link_to_project_settings(project)
1342
1350
1343 project.status = Project::STATUS_CLOSED
1351 project.status = Project::STATUS_CLOSED
1344 assert_equal '<a href="/projects/ecookbook">eCookbook</a>', link_to_project_settings(project)
1352 assert_equal '<a href="/projects/ecookbook">eCookbook</a>', link_to_project_settings(project)
1345
1353
1346 project.status = Project::STATUS_ARCHIVED
1354 project.status = Project::STATUS_ARCHIVED
1347 assert_equal 'eCookbook', link_to_project_settings(project)
1355 assert_equal 'eCookbook', link_to_project_settings(project)
1348 end
1356 end
1349
1357
1350 def test_link_to_legacy_project_with_numerical_identifier_should_use_id
1358 def test_link_to_legacy_project_with_numerical_identifier_should_use_id
1351 # numeric identifier are no longer allowed
1359 # numeric identifier are no longer allowed
1352 Project.where(:id => 1).update_all(:identifier => 25)
1360 Project.where(:id => 1).update_all(:identifier => 25)
1353 assert_equal '<a href="/projects/1">eCookbook</a>',
1361 assert_equal '<a href="/projects/1">eCookbook</a>',
1354 link_to_project(Project.find(1))
1362 link_to_project(Project.find(1))
1355 end
1363 end
1356
1364
1357 def test_principals_options_for_select_with_users
1365 def test_principals_options_for_select_with_users
1358 User.current = nil
1366 User.current = nil
1359 users = [User.find(2), User.find(4)]
1367 users = [User.find(2), User.find(4)]
1360 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
1368 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
1361 principals_options_for_select(users)
1369 principals_options_for_select(users)
1362 end
1370 end
1363
1371
1364 def test_principals_options_for_select_with_selected
1372 def test_principals_options_for_select_with_selected
1365 User.current = nil
1373 User.current = nil
1366 users = [User.find(2), User.find(4)]
1374 users = [User.find(2), User.find(4)]
1367 assert_equal %(<option value="2">John Smith</option><option value="4" selected="selected">Robert Hill</option>),
1375 assert_equal %(<option value="2">John Smith</option><option value="4" selected="selected">Robert Hill</option>),
1368 principals_options_for_select(users, User.find(4))
1376 principals_options_for_select(users, User.find(4))
1369 end
1377 end
1370
1378
1371 def test_principals_options_for_select_with_users_and_groups
1379 def test_principals_options_for_select_with_users_and_groups
1372 User.current = nil
1380 User.current = nil
1373 users = [User.find(2), Group.find(11), User.find(4), Group.find(10)]
1381 users = [User.find(2), Group.find(11), User.find(4), Group.find(10)]
1374 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>) +
1382 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>) +
1375 %(<optgroup label="Groups"><option value="10">A Team</option><option value="11">B Team</option></optgroup>),
1383 %(<optgroup label="Groups"><option value="10">A Team</option><option value="11">B Team</option></optgroup>),
1376 principals_options_for_select(users)
1384 principals_options_for_select(users)
1377 end
1385 end
1378
1386
1379 def test_principals_options_for_select_with_empty_collection
1387 def test_principals_options_for_select_with_empty_collection
1380 assert_equal '', principals_options_for_select([])
1388 assert_equal '', principals_options_for_select([])
1381 end
1389 end
1382
1390
1383 def test_principals_options_for_select_should_include_me_option_when_current_user_is_in_collection
1391 def test_principals_options_for_select_should_include_me_option_when_current_user_is_in_collection
1384 users = [User.find(2), User.find(4)]
1392 users = [User.find(2), User.find(4)]
1385 User.current = User.find(4)
1393 User.current = User.find(4)
1386 assert_include '<option value="4">&lt;&lt; me &gt;&gt;</option>', principals_options_for_select(users)
1394 assert_include '<option value="4">&lt;&lt; me &gt;&gt;</option>', principals_options_for_select(users)
1387 end
1395 end
1388
1396
1389 def test_stylesheet_link_tag_should_pick_the_default_stylesheet
1397 def test_stylesheet_link_tag_should_pick_the_default_stylesheet
1390 assert_match 'href="/stylesheets/styles.css"', stylesheet_link_tag("styles")
1398 assert_match 'href="/stylesheets/styles.css"', stylesheet_link_tag("styles")
1391 end
1399 end
1392
1400
1393 def test_stylesheet_link_tag_for_plugin_should_pick_the_plugin_stylesheet
1401 def test_stylesheet_link_tag_for_plugin_should_pick_the_plugin_stylesheet
1394 assert_match 'href="/plugin_assets/foo/stylesheets/styles.css"', stylesheet_link_tag("styles", :plugin => :foo)
1402 assert_match 'href="/plugin_assets/foo/stylesheets/styles.css"', stylesheet_link_tag("styles", :plugin => :foo)
1395 end
1403 end
1396
1404
1397 def test_image_tag_should_pick_the_default_image
1405 def test_image_tag_should_pick_the_default_image
1398 assert_match 'src="/images/image.png"', image_tag("image.png")
1406 assert_match 'src="/images/image.png"', image_tag("image.png")
1399 end
1407 end
1400
1408
1401 def test_image_tag_should_pick_the_theme_image_if_it_exists
1409 def test_image_tag_should_pick_the_theme_image_if_it_exists
1402 theme = Redmine::Themes.themes.last
1410 theme = Redmine::Themes.themes.last
1403 theme.images << 'image.png'
1411 theme.images << 'image.png'
1404
1412
1405 with_settings :ui_theme => theme.id do
1413 with_settings :ui_theme => theme.id do
1406 assert_match %|src="/themes/#{theme.dir}/images/image.png"|, image_tag("image.png")
1414 assert_match %|src="/themes/#{theme.dir}/images/image.png"|, image_tag("image.png")
1407 assert_match %|src="/images/other.png"|, image_tag("other.png")
1415 assert_match %|src="/images/other.png"|, image_tag("other.png")
1408 end
1416 end
1409 ensure
1417 ensure
1410 theme.images.delete 'image.png'
1418 theme.images.delete 'image.png'
1411 end
1419 end
1412
1420
1413 def test_image_tag_sfor_plugin_should_pick_the_plugin_image
1421 def test_image_tag_sfor_plugin_should_pick_the_plugin_image
1414 assert_match 'src="/plugin_assets/foo/images/image.png"', image_tag("image.png", :plugin => :foo)
1422 assert_match 'src="/plugin_assets/foo/images/image.png"', image_tag("image.png", :plugin => :foo)
1415 end
1423 end
1416
1424
1417 def test_javascript_include_tag_should_pick_the_default_javascript
1425 def test_javascript_include_tag_should_pick_the_default_javascript
1418 assert_match 'src="/javascripts/scripts.js"', javascript_include_tag("scripts")
1426 assert_match 'src="/javascripts/scripts.js"', javascript_include_tag("scripts")
1419 end
1427 end
1420
1428
1421 def test_javascript_include_tag_for_plugin_should_pick_the_plugin_javascript
1429 def test_javascript_include_tag_for_plugin_should_pick_the_plugin_javascript
1422 assert_match 'src="/plugin_assets/foo/javascripts/scripts.js"', javascript_include_tag("scripts", :plugin => :foo)
1430 assert_match 'src="/plugin_assets/foo/javascripts/scripts.js"', javascript_include_tag("scripts", :plugin => :foo)
1423 end
1431 end
1424
1432
1425 def test_raw_json_should_escape_closing_tags
1433 def test_raw_json_should_escape_closing_tags
1426 s = raw_json(["<foo>bar</foo>"])
1434 s = raw_json(["<foo>bar</foo>"])
1427 assert_equal '["<foo>bar<\/foo>"]', s
1435 assert_equal '["<foo>bar<\/foo>"]', s
1428 end
1436 end
1429
1437
1430 def test_raw_json_should_be_html_safe
1438 def test_raw_json_should_be_html_safe
1431 s = raw_json(["foo"])
1439 s = raw_json(["foo"])
1432 assert s.html_safe?
1440 assert s.html_safe?
1433 end
1441 end
1434
1442
1435 def test_html_title_should_app_title_if_not_set
1443 def test_html_title_should_app_title_if_not_set
1436 assert_equal 'Redmine', html_title
1444 assert_equal 'Redmine', html_title
1437 end
1445 end
1438
1446
1439 def test_html_title_should_join_items
1447 def test_html_title_should_join_items
1440 html_title 'Foo', 'Bar'
1448 html_title 'Foo', 'Bar'
1441 assert_equal 'Foo - Bar - Redmine', html_title
1449 assert_equal 'Foo - Bar - Redmine', html_title
1442 end
1450 end
1443
1451
1444 def test_html_title_should_append_current_project_name
1452 def test_html_title_should_append_current_project_name
1445 @project = Project.find(1)
1453 @project = Project.find(1)
1446 html_title 'Foo', 'Bar'
1454 html_title 'Foo', 'Bar'
1447 assert_equal 'Foo - Bar - eCookbook - Redmine', html_title
1455 assert_equal 'Foo - Bar - eCookbook - Redmine', html_title
1448 end
1456 end
1449
1457
1450 def test_title_should_return_a_h2_tag
1458 def test_title_should_return_a_h2_tag
1451 assert_equal '<h2>Foo</h2>', title('Foo')
1459 assert_equal '<h2>Foo</h2>', title('Foo')
1452 end
1460 end
1453
1461
1454 def test_title_should_set_html_title
1462 def test_title_should_set_html_title
1455 title('Foo')
1463 title('Foo')
1456 assert_equal 'Foo - Redmine', html_title
1464 assert_equal 'Foo - Redmine', html_title
1457 end
1465 end
1458
1466
1459 def test_title_should_turn_arrays_into_links
1467 def test_title_should_turn_arrays_into_links
1460 assert_equal '<h2><a href="/foo">Foo</a></h2>', title(['Foo', '/foo'])
1468 assert_equal '<h2><a href="/foo">Foo</a></h2>', title(['Foo', '/foo'])
1461 assert_equal 'Foo - Redmine', html_title
1469 assert_equal 'Foo - Redmine', html_title
1462 end
1470 end
1463
1471
1464 def test_title_should_join_items
1472 def test_title_should_join_items
1465 assert_equal '<h2>Foo &#187; Bar</h2>', title('Foo', 'Bar')
1473 assert_equal '<h2>Foo &#187; Bar</h2>', title('Foo', 'Bar')
1466 assert_equal 'Bar - Foo - Redmine', html_title
1474 assert_equal 'Bar - Foo - Redmine', html_title
1467 end
1475 end
1468
1476
1469 def test_favicon_path
1477 def test_favicon_path
1470 assert_match %r{^/favicon\.ico}, favicon_path
1478 assert_match %r{^/favicon\.ico}, favicon_path
1471 end
1479 end
1472
1480
1473 def test_favicon_path_with_suburi
1481 def test_favicon_path_with_suburi
1474 Redmine::Utils.relative_url_root = '/foo'
1482 Redmine::Utils.relative_url_root = '/foo'
1475 assert_match %r{^/foo/favicon\.ico}, favicon_path
1483 assert_match %r{^/foo/favicon\.ico}, favicon_path
1476 ensure
1484 ensure
1477 Redmine::Utils.relative_url_root = ''
1485 Redmine::Utils.relative_url_root = ''
1478 end
1486 end
1479
1487
1480 def test_favicon_url
1488 def test_favicon_url
1481 assert_match %r{^http://test\.host/favicon\.ico}, favicon_url
1489 assert_match %r{^http://test\.host/favicon\.ico}, favicon_url
1482 end
1490 end
1483
1491
1484 def test_favicon_url_with_suburi
1492 def test_favicon_url_with_suburi
1485 Redmine::Utils.relative_url_root = '/foo'
1493 Redmine::Utils.relative_url_root = '/foo'
1486 assert_match %r{^http://test\.host/foo/favicon\.ico}, favicon_url
1494 assert_match %r{^http://test\.host/foo/favicon\.ico}, favicon_url
1487 ensure
1495 ensure
1488 Redmine::Utils.relative_url_root = ''
1496 Redmine::Utils.relative_url_root = ''
1489 end
1497 end
1490
1498
1491 def test_truncate_single_line
1499 def test_truncate_single_line
1492 str = "01234"
1500 str = "01234"
1493 result = truncate_single_line_raw("#{str}\n#{str}", 10)
1501 result = truncate_single_line_raw("#{str}\n#{str}", 10)
1494 assert_equal "01234 0...", result
1502 assert_equal "01234 0...", result
1495 assert !result.html_safe?
1503 assert !result.html_safe?
1496 result = truncate_single_line_raw("#{str}<&#>\n#{str}#{str}", 16)
1504 result = truncate_single_line_raw("#{str}<&#>\n#{str}#{str}", 16)
1497 assert_equal "01234<&#> 012...", result
1505 assert_equal "01234<&#> 012...", result
1498 assert !result.html_safe?
1506 assert !result.html_safe?
1499 end
1507 end
1500
1508
1501 def test_truncate_single_line_non_ascii
1509 def test_truncate_single_line_non_ascii
1502 ja = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"
1510 ja = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"
1503 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
1511 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
1504 result = truncate_single_line_raw("#{ja}\n#{ja}\n#{ja}", 10)
1512 result = truncate_single_line_raw("#{ja}\n#{ja}\n#{ja}", 10)
1505 assert_equal "#{ja} #{ja}...", result
1513 assert_equal "#{ja} #{ja}...", result
1506 assert !result.html_safe?
1514 assert !result.html_safe?
1507 end
1515 end
1508 end
1516 end
General Comments 0
You need to be logged in to leave comments. Login now