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