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