##// END OF EJS Templates
Don't escape already parsed wiki link title (#9471)....
Etienne Massip -
r7578:1032210edd00
parent child
Show More
@@ -1,986 +1,986
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'forwardable'
18 require 'forwardable'
19 require 'cgi'
19 require 'cgi'
20
20
21 module ApplicationHelper
21 module ApplicationHelper
22 include Redmine::WikiFormatting::Macros::Definitions
22 include Redmine::WikiFormatting::Macros::Definitions
23 include Redmine::I18n
23 include Redmine::I18n
24 include GravatarHelper::PublicMethods
24 include GravatarHelper::PublicMethods
25
25
26 extend Forwardable
26 extend Forwardable
27 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
27 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
28
28
29 # Return true if user is authorized for controller/action, otherwise false
29 # Return true if user is authorized for controller/action, otherwise false
30 def authorize_for(controller, action)
30 def authorize_for(controller, action)
31 User.current.allowed_to?({:controller => controller, :action => action}, @project)
31 User.current.allowed_to?({:controller => controller, :action => action}, @project)
32 end
32 end
33
33
34 # Display a link if user is authorized
34 # Display a link if user is authorized
35 #
35 #
36 # @param [String] name Anchor text (passed to link_to)
36 # @param [String] name Anchor text (passed to link_to)
37 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
37 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
38 # @param [optional, Hash] html_options Options passed to link_to
38 # @param [optional, Hash] html_options Options passed to link_to
39 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
39 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
40 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
40 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
41 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
41 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
42 end
42 end
43
43
44 # Display a link to remote if user is authorized
44 # Display a link to remote if user is authorized
45 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
45 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
46 url = options[:url] || {}
46 url = options[:url] || {}
47 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
47 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
48 end
48 end
49
49
50 # Displays a link to user's account page if active
50 # Displays a link to user's account page if active
51 def link_to_user(user, options={})
51 def link_to_user(user, options={})
52 if user.is_a?(User)
52 if user.is_a?(User)
53 name = h(user.name(options[:format]))
53 name = h(user.name(options[:format]))
54 if user.active?
54 if user.active?
55 link_to name, :controller => 'users', :action => 'show', :id => user
55 link_to name, :controller => 'users', :action => 'show', :id => user
56 else
56 else
57 name
57 name
58 end
58 end
59 else
59 else
60 h(user.to_s)
60 h(user.to_s)
61 end
61 end
62 end
62 end
63
63
64 # Displays a link to +issue+ with its subject.
64 # Displays a link to +issue+ with its subject.
65 # Examples:
65 # Examples:
66 #
66 #
67 # link_to_issue(issue) # => Defect #6: This is the subject
67 # link_to_issue(issue) # => Defect #6: This is the subject
68 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
68 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
69 # link_to_issue(issue, :subject => false) # => Defect #6
69 # link_to_issue(issue, :subject => false) # => Defect #6
70 # link_to_issue(issue, :project => true) # => Foo - Defect #6
70 # link_to_issue(issue, :project => true) # => Foo - Defect #6
71 #
71 #
72 def link_to_issue(issue, options={})
72 def link_to_issue(issue, options={})
73 title = nil
73 title = nil
74 subject = nil
74 subject = nil
75 if options[:subject] == false
75 if options[:subject] == false
76 title = truncate(issue.subject, :length => 60)
76 title = truncate(issue.subject, :length => 60)
77 else
77 else
78 subject = issue.subject
78 subject = issue.subject
79 if options[:truncate]
79 if options[:truncate]
80 subject = truncate(subject, :length => options[:truncate])
80 subject = truncate(subject, :length => options[:truncate])
81 end
81 end
82 end
82 end
83 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
83 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
84 :class => issue.css_classes,
84 :class => issue.css_classes,
85 :title => title
85 :title => title
86 s << ": #{h subject}" if subject
86 s << ": #{h subject}" if subject
87 s = "#{h issue.project} - " + s if options[:project]
87 s = "#{h issue.project} - " + s if options[:project]
88 s
88 s
89 end
89 end
90
90
91 # Generates a link to an attachment.
91 # Generates a link to an attachment.
92 # Options:
92 # Options:
93 # * :text - Link text (default to attachment filename)
93 # * :text - Link text (default to attachment filename)
94 # * :download - Force download (default: false)
94 # * :download - Force download (default: false)
95 def link_to_attachment(attachment, options={})
95 def link_to_attachment(attachment, options={})
96 text = options.delete(:text) || attachment.filename
96 text = options.delete(:text) || attachment.filename
97 action = options.delete(:download) ? 'download' : 'show'
97 action = options.delete(:download) ? 'download' : 'show'
98
98
99 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
99 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, 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, project, options={})
105 def link_to_revision(revision, project, options={})
106 text = options.delete(:text) || format_revision(revision)
106 text = options.delete(:text) || format_revision(revision)
107 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
107 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
108
108
109 link_to(h(text), {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev},
109 link_to(h(text), {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev},
110 :title => l(:label_revision_id, format_revision(revision)))
110 :title => l(:label_revision_id, format_revision(revision)))
111 end
111 end
112
112
113 # Generates a link to a message
113 # Generates a link to a message
114 def link_to_message(message, options={}, html_options = nil)
114 def link_to_message(message, options={}, html_options = nil)
115 link_to(
115 link_to(
116 h(truncate(message.subject, :length => 60)),
116 h(truncate(message.subject, :length => 60)),
117 { :controller => 'messages', :action => 'show',
117 { :controller => 'messages', :action => 'show',
118 :board_id => message.board_id,
118 :board_id => message.board_id,
119 :id => message.root,
119 :id => message.root,
120 :r => (message.parent_id && message.id),
120 :r => (message.parent_id && message.id),
121 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
121 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
122 }.merge(options),
122 }.merge(options),
123 html_options
123 html_options
124 )
124 )
125 end
125 end
126
126
127 # Generates a link to a project if active
127 # Generates a link to a project if active
128 # Examples:
128 # Examples:
129 #
129 #
130 # link_to_project(project) # => link to the specified project overview
130 # link_to_project(project) # => link to the specified project overview
131 # link_to_project(project, :action=>'settings') # => link to project settings
131 # link_to_project(project, :action=>'settings') # => link to project settings
132 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
132 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
133 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
133 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
134 #
134 #
135 def link_to_project(project, options={}, html_options = nil)
135 def link_to_project(project, options={}, html_options = nil)
136 if project.active?
136 if project.active?
137 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
137 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
138 link_to(h(project), url, html_options)
138 link_to(h(project), url, html_options)
139 else
139 else
140 h(project)
140 h(project)
141 end
141 end
142 end
142 end
143
143
144 def toggle_link(name, id, options={})
144 def toggle_link(name, id, options={})
145 onclick = "Element.toggle('#{id}'); "
145 onclick = "Element.toggle('#{id}'); "
146 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
146 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
147 onclick << "return false;"
147 onclick << "return false;"
148 link_to(name, "#", :onclick => onclick)
148 link_to(name, "#", :onclick => onclick)
149 end
149 end
150
150
151 def image_to_function(name, function, html_options = {})
151 def image_to_function(name, function, html_options = {})
152 html_options.symbolize_keys!
152 html_options.symbolize_keys!
153 tag(:input, html_options.merge({
153 tag(:input, html_options.merge({
154 :type => "image", :src => image_path(name),
154 :type => "image", :src => image_path(name),
155 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
155 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
156 }))
156 }))
157 end
157 end
158
158
159 def prompt_to_remote(name, text, param, url, html_options = {})
159 def prompt_to_remote(name, text, param, url, html_options = {})
160 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
160 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
161 link_to name, {}, html_options
161 link_to name, {}, html_options
162 end
162 end
163
163
164 def format_activity_title(text)
164 def format_activity_title(text)
165 h(truncate_single_line(text, :length => 100))
165 h(truncate_single_line(text, :length => 100))
166 end
166 end
167
167
168 def format_activity_day(date)
168 def format_activity_day(date)
169 date == Date.today ? l(:label_today).titleize : format_date(date)
169 date == Date.today ? l(:label_today).titleize : format_date(date)
170 end
170 end
171
171
172 def format_activity_description(text)
172 def format_activity_description(text)
173 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
173 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
174 end
174 end
175
175
176 def format_version_name(version)
176 def format_version_name(version)
177 if version.project == @project
177 if version.project == @project
178 h(version)
178 h(version)
179 else
179 else
180 h("#{version.project} - #{version}")
180 h("#{version.project} - #{version}")
181 end
181 end
182 end
182 end
183
183
184 def due_date_distance_in_words(date)
184 def due_date_distance_in_words(date)
185 if date
185 if date
186 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
186 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
187 end
187 end
188 end
188 end
189
189
190 def render_page_hierarchy(pages, node=nil, options={})
190 def render_page_hierarchy(pages, node=nil, options={})
191 content = ''
191 content = ''
192 if pages[node]
192 if pages[node]
193 content << "<ul class=\"pages-hierarchy\">\n"
193 content << "<ul class=\"pages-hierarchy\">\n"
194 pages[node].each do |page|
194 pages[node].each do |page|
195 content << "<li>"
195 content << "<li>"
196 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
196 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
197 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
197 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
198 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
198 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
199 content << "</li>\n"
199 content << "</li>\n"
200 end
200 end
201 content << "</ul>\n"
201 content << "</ul>\n"
202 end
202 end
203 content.html_safe
203 content.html_safe
204 end
204 end
205
205
206 # Renders flash messages
206 # Renders flash messages
207 def render_flash_messages
207 def render_flash_messages
208 s = ''
208 s = ''
209 flash.each do |k,v|
209 flash.each do |k,v|
210 s << content_tag('div', v, :class => "flash #{k}")
210 s << content_tag('div', v, :class => "flash #{k}")
211 end
211 end
212 s.html_safe
212 s.html_safe
213 end
213 end
214
214
215 # Renders tabs and their content
215 # Renders tabs and their content
216 def render_tabs(tabs)
216 def render_tabs(tabs)
217 if tabs.any?
217 if tabs.any?
218 render :partial => 'common/tabs', :locals => {:tabs => tabs}
218 render :partial => 'common/tabs', :locals => {:tabs => tabs}
219 else
219 else
220 content_tag 'p', l(:label_no_data), :class => "nodata"
220 content_tag 'p', l(:label_no_data), :class => "nodata"
221 end
221 end
222 end
222 end
223
223
224 # Renders the project quick-jump box
224 # Renders the project quick-jump box
225 def render_project_jump_box
225 def render_project_jump_box
226 return unless User.current.logged?
226 return unless User.current.logged?
227 projects = User.current.memberships.collect(&:project).compact.uniq
227 projects = User.current.memberships.collect(&:project).compact.uniq
228 if projects.any?
228 if projects.any?
229 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
229 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
230 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
230 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
231 '<option value="" disabled="disabled">---</option>'
231 '<option value="" disabled="disabled">---</option>'
232 s << project_tree_options_for_select(projects, :selected => @project) do |p|
232 s << project_tree_options_for_select(projects, :selected => @project) do |p|
233 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
233 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
234 end
234 end
235 s << '</select>'
235 s << '</select>'
236 s.html_safe
236 s.html_safe
237 end
237 end
238 end
238 end
239
239
240 def project_tree_options_for_select(projects, options = {})
240 def project_tree_options_for_select(projects, options = {})
241 s = ''
241 s = ''
242 project_tree(projects) do |project, level|
242 project_tree(projects) do |project, level|
243 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
243 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
244 tag_options = {:value => project.id}
244 tag_options = {:value => project.id}
245 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
245 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
246 tag_options[:selected] = 'selected'
246 tag_options[:selected] = 'selected'
247 else
247 else
248 tag_options[:selected] = nil
248 tag_options[:selected] = nil
249 end
249 end
250 tag_options.merge!(yield(project)) if block_given?
250 tag_options.merge!(yield(project)) if block_given?
251 s << content_tag('option', name_prefix + h(project), tag_options)
251 s << content_tag('option', name_prefix + h(project), tag_options)
252 end
252 end
253 s.html_safe
253 s.html_safe
254 end
254 end
255
255
256 # Yields the given block for each project with its level in the tree
256 # Yields the given block for each project with its level in the tree
257 #
257 #
258 # Wrapper for Project#project_tree
258 # Wrapper for Project#project_tree
259 def project_tree(projects, &block)
259 def project_tree(projects, &block)
260 Project.project_tree(projects, &block)
260 Project.project_tree(projects, &block)
261 end
261 end
262
262
263 def project_nested_ul(projects, &block)
263 def project_nested_ul(projects, &block)
264 s = ''
264 s = ''
265 if projects.any?
265 if projects.any?
266 ancestors = []
266 ancestors = []
267 projects.sort_by(&:lft).each do |project|
267 projects.sort_by(&:lft).each do |project|
268 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
268 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
269 s << "<ul>\n"
269 s << "<ul>\n"
270 else
270 else
271 ancestors.pop
271 ancestors.pop
272 s << "</li>"
272 s << "</li>"
273 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
273 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
274 ancestors.pop
274 ancestors.pop
275 s << "</ul></li>\n"
275 s << "</ul></li>\n"
276 end
276 end
277 end
277 end
278 s << "<li>"
278 s << "<li>"
279 s << yield(project).to_s
279 s << yield(project).to_s
280 ancestors << project
280 ancestors << project
281 end
281 end
282 s << ("</li></ul>\n" * ancestors.size)
282 s << ("</li></ul>\n" * ancestors.size)
283 end
283 end
284 s.html_safe
284 s.html_safe
285 end
285 end
286
286
287 def principals_check_box_tags(name, principals)
287 def principals_check_box_tags(name, principals)
288 s = ''
288 s = ''
289 principals.sort.each do |principal|
289 principals.sort.each do |principal|
290 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
290 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
291 end
291 end
292 s.html_safe
292 s.html_safe
293 end
293 end
294
294
295 # Returns a string for users/groups option tags
295 # Returns a string for users/groups option tags
296 def principals_options_for_select(collection, selected=nil)
296 def principals_options_for_select(collection, selected=nil)
297 s = ''
297 s = ''
298 groups = ''
298 groups = ''
299 collection.sort.each do |element|
299 collection.sort.each do |element|
300 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
300 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
301 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
301 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
302 end
302 end
303 unless groups.empty?
303 unless groups.empty?
304 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
304 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
305 end
305 end
306 s
306 s
307 end
307 end
308
308
309 # Truncates and returns the string as a single line
309 # Truncates and returns the string as a single line
310 def truncate_single_line(string, *args)
310 def truncate_single_line(string, *args)
311 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
311 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
312 end
312 end
313
313
314 # Truncates at line break after 250 characters or options[:length]
314 # Truncates at line break after 250 characters or options[:length]
315 def truncate_lines(string, options={})
315 def truncate_lines(string, options={})
316 length = options[:length] || 250
316 length = options[:length] || 250
317 if string.to_s =~ /\A(.{#{length}}.*?)$/m
317 if string.to_s =~ /\A(.{#{length}}.*?)$/m
318 "#{$1}..."
318 "#{$1}..."
319 else
319 else
320 string
320 string
321 end
321 end
322 end
322 end
323
323
324 def html_hours(text)
324 def html_hours(text)
325 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
325 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
326 end
326 end
327
327
328 def authoring(created, author, options={})
328 def authoring(created, author, options={})
329 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
329 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
330 end
330 end
331
331
332 def time_tag(time)
332 def time_tag(time)
333 text = distance_of_time_in_words(Time.now, time)
333 text = distance_of_time_in_words(Time.now, time)
334 if @project
334 if @project
335 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
335 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
336 else
336 else
337 content_tag('acronym', text, :title => format_time(time))
337 content_tag('acronym', text, :title => format_time(time))
338 end
338 end
339 end
339 end
340
340
341 def syntax_highlight(name, content)
341 def syntax_highlight(name, content)
342 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
342 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
343 end
343 end
344
344
345 def to_path_param(path)
345 def to_path_param(path)
346 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
346 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
347 end
347 end
348
348
349 def pagination_links_full(paginator, count=nil, options={})
349 def pagination_links_full(paginator, count=nil, options={})
350 page_param = options.delete(:page_param) || :page
350 page_param = options.delete(:page_param) || :page
351 per_page_links = options.delete(:per_page_links)
351 per_page_links = options.delete(:per_page_links)
352 url_param = params.dup
352 url_param = params.dup
353
353
354 html = ''
354 html = ''
355 if paginator.current.previous
355 if paginator.current.previous
356 # \xc2\xab(utf-8) = &#171;
356 # \xc2\xab(utf-8) = &#171;
357 html << link_to_content_update(
357 html << link_to_content_update(
358 "\xc2\xab " + l(:label_previous),
358 "\xc2\xab " + l(:label_previous),
359 url_param.merge(page_param => paginator.current.previous)) + ' '
359 url_param.merge(page_param => paginator.current.previous)) + ' '
360 end
360 end
361
361
362 html << (pagination_links_each(paginator, options) do |n|
362 html << (pagination_links_each(paginator, options) do |n|
363 link_to_content_update(n.to_s, url_param.merge(page_param => n))
363 link_to_content_update(n.to_s, url_param.merge(page_param => n))
364 end || '')
364 end || '')
365
365
366 if paginator.current.next
366 if paginator.current.next
367 # \xc2\xbb(utf-8) = &#187;
367 # \xc2\xbb(utf-8) = &#187;
368 html << ' ' + link_to_content_update(
368 html << ' ' + link_to_content_update(
369 (l(:label_next) + " \xc2\xbb"),
369 (l(:label_next) + " \xc2\xbb"),
370 url_param.merge(page_param => paginator.current.next))
370 url_param.merge(page_param => paginator.current.next))
371 end
371 end
372
372
373 unless count.nil?
373 unless count.nil?
374 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
374 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
375 if per_page_links != false && links = per_page_links(paginator.items_per_page)
375 if per_page_links != false && links = per_page_links(paginator.items_per_page)
376 html << " | #{links}"
376 html << " | #{links}"
377 end
377 end
378 end
378 end
379
379
380 html.html_safe
380 html.html_safe
381 end
381 end
382
382
383 def per_page_links(selected=nil)
383 def per_page_links(selected=nil)
384 links = Setting.per_page_options_array.collect do |n|
384 links = Setting.per_page_options_array.collect do |n|
385 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
385 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
386 end
386 end
387 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
387 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
388 end
388 end
389
389
390 def reorder_links(name, url)
390 def reorder_links(name, url)
391 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
391 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
392 url.merge({"#{name}[move_to]" => 'highest'}),
392 url.merge({"#{name}[move_to]" => 'highest'}),
393 :method => :post, :title => l(:label_sort_highest)) +
393 :method => :post, :title => l(:label_sort_highest)) +
394 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
394 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
395 url.merge({"#{name}[move_to]" => 'higher'}),
395 url.merge({"#{name}[move_to]" => 'higher'}),
396 :method => :post, :title => l(:label_sort_higher)) +
396 :method => :post, :title => l(:label_sort_higher)) +
397 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
397 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
398 url.merge({"#{name}[move_to]" => 'lower'}),
398 url.merge({"#{name}[move_to]" => 'lower'}),
399 :method => :post, :title => l(:label_sort_lower)) +
399 :method => :post, :title => l(:label_sort_lower)) +
400 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
400 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
401 url.merge({"#{name}[move_to]" => 'lowest'}),
401 url.merge({"#{name}[move_to]" => 'lowest'}),
402 :method => :post, :title => l(:label_sort_lowest))
402 :method => :post, :title => l(:label_sort_lowest))
403 end
403 end
404
404
405 def breadcrumb(*args)
405 def breadcrumb(*args)
406 elements = args.flatten
406 elements = args.flatten
407 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
407 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
408 end
408 end
409
409
410 def other_formats_links(&block)
410 def other_formats_links(&block)
411 concat('<p class="other-formats">' + l(:label_export_to))
411 concat('<p class="other-formats">' + l(:label_export_to))
412 yield Redmine::Views::OtherFormatsBuilder.new(self)
412 yield Redmine::Views::OtherFormatsBuilder.new(self)
413 concat('</p>')
413 concat('</p>')
414 end
414 end
415
415
416 def page_header_title
416 def page_header_title
417 if @project.nil? || @project.new_record?
417 if @project.nil? || @project.new_record?
418 h(Setting.app_title)
418 h(Setting.app_title)
419 else
419 else
420 b = []
420 b = []
421 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
421 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
422 if ancestors.any?
422 if ancestors.any?
423 root = ancestors.shift
423 root = ancestors.shift
424 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
424 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
425 if ancestors.size > 2
425 if ancestors.size > 2
426 b << "\xe2\x80\xa6"
426 b << "\xe2\x80\xa6"
427 ancestors = ancestors[-2, 2]
427 ancestors = ancestors[-2, 2]
428 end
428 end
429 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
429 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
430 end
430 end
431 b << h(@project)
431 b << h(@project)
432 b.join(" \xc2\xbb ").html_safe
432 b.join(" \xc2\xbb ").html_safe
433 end
433 end
434 end
434 end
435
435
436 def html_title(*args)
436 def html_title(*args)
437 if args.empty?
437 if args.empty?
438 title = []
438 title = []
439 title << @project.name if @project
439 title << @project.name if @project
440 title += @html_title if @html_title
440 title += @html_title if @html_title
441 title << Setting.app_title
441 title << Setting.app_title
442 title.select {|t| !t.blank? }.join(' - ')
442 title.select {|t| !t.blank? }.join(' - ')
443 else
443 else
444 @html_title ||= []
444 @html_title ||= []
445 @html_title += args
445 @html_title += args
446 end
446 end
447 end
447 end
448
448
449 # Returns the theme, controller name, and action as css classes for the
449 # Returns the theme, controller name, and action as css classes for the
450 # HTML body.
450 # HTML body.
451 def body_css_classes
451 def body_css_classes
452 css = []
452 css = []
453 if theme = Redmine::Themes.theme(Setting.ui_theme)
453 if theme = Redmine::Themes.theme(Setting.ui_theme)
454 css << 'theme-' + theme.name
454 css << 'theme-' + theme.name
455 end
455 end
456
456
457 css << 'controller-' + params[:controller]
457 css << 'controller-' + params[:controller]
458 css << 'action-' + params[:action]
458 css << 'action-' + params[:action]
459 css.join(' ')
459 css.join(' ')
460 end
460 end
461
461
462 def accesskey(s)
462 def accesskey(s)
463 Redmine::AccessKeys.key_for s
463 Redmine::AccessKeys.key_for s
464 end
464 end
465
465
466 # Formats text according to system settings.
466 # Formats text according to system settings.
467 # 2 ways to call this method:
467 # 2 ways to call this method:
468 # * with a String: textilizable(text, options)
468 # * with a String: textilizable(text, options)
469 # * with an object and one of its attribute: textilizable(issue, :description, options)
469 # * with an object and one of its attribute: textilizable(issue, :description, options)
470 def textilizable(*args)
470 def textilizable(*args)
471 options = args.last.is_a?(Hash) ? args.pop : {}
471 options = args.last.is_a?(Hash) ? args.pop : {}
472 case args.size
472 case args.size
473 when 1
473 when 1
474 obj = options[:object]
474 obj = options[:object]
475 text = args.shift
475 text = args.shift
476 when 2
476 when 2
477 obj = args.shift
477 obj = args.shift
478 attr = args.shift
478 attr = args.shift
479 text = obj.send(attr).to_s
479 text = obj.send(attr).to_s
480 else
480 else
481 raise ArgumentError, 'invalid arguments to textilizable'
481 raise ArgumentError, 'invalid arguments to textilizable'
482 end
482 end
483 return '' if text.blank?
483 return '' if text.blank?
484 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
484 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
485 only_path = options.delete(:only_path) == false ? false : true
485 only_path = options.delete(:only_path) == false ? false : true
486
486
487 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
487 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
488
488
489 @parsed_headings = []
489 @parsed_headings = []
490 text = parse_non_pre_blocks(text) do |text|
490 text = parse_non_pre_blocks(text) do |text|
491 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings].each do |method_name|
491 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings].each do |method_name|
492 send method_name, text, project, obj, attr, only_path, options
492 send method_name, text, project, obj, attr, only_path, options
493 end
493 end
494 end
494 end
495
495
496 if @parsed_headings.any?
496 if @parsed_headings.any?
497 replace_toc(text, @parsed_headings)
497 replace_toc(text, @parsed_headings)
498 end
498 end
499
499
500 text
500 text
501 end
501 end
502
502
503 def parse_non_pre_blocks(text)
503 def parse_non_pre_blocks(text)
504 s = StringScanner.new(text)
504 s = StringScanner.new(text)
505 tags = []
505 tags = []
506 parsed = ''
506 parsed = ''
507 while !s.eos?
507 while !s.eos?
508 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
508 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
509 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
509 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
510 if tags.empty?
510 if tags.empty?
511 yield text
511 yield text
512 end
512 end
513 parsed << text
513 parsed << text
514 if tag
514 if tag
515 if closing
515 if closing
516 if tags.last == tag.downcase
516 if tags.last == tag.downcase
517 tags.pop
517 tags.pop
518 end
518 end
519 else
519 else
520 tags << tag.downcase
520 tags << tag.downcase
521 end
521 end
522 parsed << full_tag
522 parsed << full_tag
523 end
523 end
524 end
524 end
525 # Close any non closing tags
525 # Close any non closing tags
526 while tag = tags.pop
526 while tag = tags.pop
527 parsed << "</#{tag}>"
527 parsed << "</#{tag}>"
528 end
528 end
529 parsed.html_safe
529 parsed.html_safe
530 end
530 end
531
531
532 def parse_inline_attachments(text, project, obj, attr, only_path, options)
532 def parse_inline_attachments(text, project, obj, attr, only_path, options)
533 # when using an image link, try to use an attachment, if possible
533 # when using an image link, try to use an attachment, if possible
534 if options[:attachments] || (obj && obj.respond_to?(:attachments))
534 if options[:attachments] || (obj && obj.respond_to?(:attachments))
535 attachments = nil
535 attachments = nil
536 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
536 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
537 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
537 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
538 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
538 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
539 # search for the picture in attachments
539 # search for the picture in attachments
540 if found = attachments.detect { |att| att.filename.downcase == filename }
540 if found = attachments.detect { |att| att.filename.downcase == filename }
541 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
541 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
542 desc = found.description.to_s.gsub('"', '')
542 desc = found.description.to_s.gsub('"', '')
543 if !desc.blank? && alttext.blank?
543 if !desc.blank? && alttext.blank?
544 alt = " title=\"#{desc}\" alt=\"#{desc}\""
544 alt = " title=\"#{desc}\" alt=\"#{desc}\""
545 end
545 end
546 "src=\"#{image_url}\"#{alt}".html_safe
546 "src=\"#{image_url}\"#{alt}".html_safe
547 else
547 else
548 m.html_safe
548 m.html_safe
549 end
549 end
550 end
550 end
551 end
551 end
552 end
552 end
553
553
554 # Wiki links
554 # Wiki links
555 #
555 #
556 # Examples:
556 # Examples:
557 # [[mypage]]
557 # [[mypage]]
558 # [[mypage|mytext]]
558 # [[mypage|mytext]]
559 # wiki links can refer other project wikis, using project name or identifier:
559 # wiki links can refer other project wikis, using project name or identifier:
560 # [[project:]] -> wiki starting page
560 # [[project:]] -> wiki starting page
561 # [[project:|mytext]]
561 # [[project:|mytext]]
562 # [[project:mypage]]
562 # [[project:mypage]]
563 # [[project:mypage|mytext]]
563 # [[project:mypage|mytext]]
564 def parse_wiki_links(text, project, obj, attr, only_path, options)
564 def parse_wiki_links(text, project, obj, attr, only_path, options)
565 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
565 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
566 link_project = project
566 link_project = project
567 esc, all, page, title = $1, $2, $3, $5
567 esc, all, page, title = $1, $2, $3, $5
568 if esc.nil?
568 if esc.nil?
569 if page =~ /^([^\:]+)\:(.*)$/
569 if page =~ /^([^\:]+)\:(.*)$/
570 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
570 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
571 page = $2
571 page = $2
572 title ||= $1 if page.blank?
572 title ||= $1 if page.blank?
573 end
573 end
574
574
575 if link_project && link_project.wiki
575 if link_project && link_project.wiki
576 # extract anchor
576 # extract anchor
577 anchor = nil
577 anchor = nil
578 if page =~ /^(.+?)\#(.+)$/
578 if page =~ /^(.+?)\#(.+)$/
579 page, anchor = $1, $2
579 page, anchor = $1, $2
580 end
580 end
581 anchor = sanitize_anchor_name(anchor) if anchor.present?
581 anchor = sanitize_anchor_name(anchor) if anchor.present?
582 # check if page exists
582 # check if page exists
583 wiki_page = link_project.wiki.find_page(page)
583 wiki_page = link_project.wiki.find_page(page)
584 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
584 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
585 "##{anchor}"
585 "##{anchor}"
586 else
586 else
587 case options[:wiki_links]
587 case options[:wiki_links]
588 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
588 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
589 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
589 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
590 else
590 else
591 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
591 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
592 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
592 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
593 end
593 end
594 end
594 end
595 link_to(h(title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
595 link_to(title || h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
596 else
596 else
597 # project or wiki doesn't exist
597 # project or wiki doesn't exist
598 all.html_safe
598 all.html_safe
599 end
599 end
600 else
600 else
601 all.html_safe
601 all.html_safe
602 end
602 end
603 end
603 end
604 end
604 end
605
605
606 # Redmine links
606 # Redmine links
607 #
607 #
608 # Examples:
608 # Examples:
609 # Issues:
609 # Issues:
610 # #52 -> Link to issue #52
610 # #52 -> Link to issue #52
611 # Changesets:
611 # Changesets:
612 # r52 -> Link to revision 52
612 # r52 -> Link to revision 52
613 # commit:a85130f -> Link to scmid starting with a85130f
613 # commit:a85130f -> Link to scmid starting with a85130f
614 # Documents:
614 # Documents:
615 # document#17 -> Link to document with id 17
615 # document#17 -> Link to document with id 17
616 # document:Greetings -> Link to the document with title "Greetings"
616 # document:Greetings -> Link to the document with title "Greetings"
617 # document:"Some document" -> Link to the document with title "Some document"
617 # document:"Some document" -> Link to the document with title "Some document"
618 # Versions:
618 # Versions:
619 # version#3 -> Link to version with id 3
619 # version#3 -> Link to version with id 3
620 # version:1.0.0 -> Link to version named "1.0.0"
620 # version:1.0.0 -> Link to version named "1.0.0"
621 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
621 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
622 # Attachments:
622 # Attachments:
623 # attachment:file.zip -> Link to the attachment of the current object named file.zip
623 # attachment:file.zip -> Link to the attachment of the current object named file.zip
624 # Source files:
624 # Source files:
625 # source:some/file -> Link to the file located at /some/file in the project's repository
625 # source:some/file -> Link to the file located at /some/file in the project's repository
626 # source:some/file@52 -> Link to the file's revision 52
626 # source:some/file@52 -> Link to the file's revision 52
627 # source:some/file#L120 -> Link to line 120 of the file
627 # source:some/file#L120 -> Link to line 120 of the file
628 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
628 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
629 # export:some/file -> Force the download of the file
629 # export:some/file -> Force the download of the file
630 # Forum messages:
630 # Forum messages:
631 # message#1218 -> Link to message with id 1218
631 # message#1218 -> Link to message with id 1218
632 #
632 #
633 # Links can refer other objects from other projects, using project identifier:
633 # Links can refer other objects from other projects, using project identifier:
634 # identifier:r52
634 # identifier:r52
635 # identifier:document:"Some document"
635 # identifier:document:"Some document"
636 # identifier:version:1.0.0
636 # identifier:version:1.0.0
637 # identifier:source:some/file
637 # identifier:source:some/file
638 def parse_redmine_links(text, project, obj, attr, only_path, options)
638 def parse_redmine_links(text, project, obj, attr, only_path, options)
639 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
639 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
640 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
640 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
641 link = nil
641 link = nil
642 if project_identifier
642 if project_identifier
643 project = Project.visible.find_by_identifier(project_identifier)
643 project = Project.visible.find_by_identifier(project_identifier)
644 end
644 end
645 if esc.nil?
645 if esc.nil?
646 if prefix.nil? && sep == 'r'
646 if prefix.nil? && sep == 'r'
647 # project.changesets.visible raises an SQL error because of a double join on repositories
647 # project.changesets.visible raises an SQL error because of a double join on repositories
648 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
648 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
649 link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
649 link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
650 :class => 'changeset',
650 :class => 'changeset',
651 :title => truncate_single_line(changeset.comments, :length => 100))
651 :title => truncate_single_line(changeset.comments, :length => 100))
652 end
652 end
653 elsif sep == '#'
653 elsif sep == '#'
654 oid = identifier.to_i
654 oid = identifier.to_i
655 case prefix
655 case prefix
656 when nil
656 when nil
657 if issue = Issue.visible.find_by_id(oid, :include => :status)
657 if issue = Issue.visible.find_by_id(oid, :include => :status)
658 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
658 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
659 :class => issue.css_classes,
659 :class => issue.css_classes,
660 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
660 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
661 end
661 end
662 when 'document'
662 when 'document'
663 if document = Document.visible.find_by_id(oid)
663 if document = Document.visible.find_by_id(oid)
664 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
664 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
665 :class => 'document'
665 :class => 'document'
666 end
666 end
667 when 'version'
667 when 'version'
668 if version = Version.visible.find_by_id(oid)
668 if version = Version.visible.find_by_id(oid)
669 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
669 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
670 :class => 'version'
670 :class => 'version'
671 end
671 end
672 when 'message'
672 when 'message'
673 if message = Message.visible.find_by_id(oid, :include => :parent)
673 if message = Message.visible.find_by_id(oid, :include => :parent)
674 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
674 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
675 end
675 end
676 when 'project'
676 when 'project'
677 if p = Project.visible.find_by_id(oid)
677 if p = Project.visible.find_by_id(oid)
678 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
678 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
679 end
679 end
680 end
680 end
681 elsif sep == ':'
681 elsif sep == ':'
682 # removes the double quotes if any
682 # removes the double quotes if any
683 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
683 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
684 case prefix
684 case prefix
685 when 'document'
685 when 'document'
686 if project && document = project.documents.visible.find_by_title(name)
686 if project && document = project.documents.visible.find_by_title(name)
687 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
687 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
688 :class => 'document'
688 :class => 'document'
689 end
689 end
690 when 'version'
690 when 'version'
691 if project && version = project.versions.visible.find_by_name(name)
691 if project && version = project.versions.visible.find_by_name(name)
692 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
692 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
693 :class => 'version'
693 :class => 'version'
694 end
694 end
695 when 'commit'
695 when 'commit'
696 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
696 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
697 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
697 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
698 :class => 'changeset',
698 :class => 'changeset',
699 :title => truncate_single_line(h(changeset.comments), :length => 100)
699 :title => truncate_single_line(h(changeset.comments), :length => 100)
700 end
700 end
701 when 'source', 'export'
701 when 'source', 'export'
702 if project && project.repository && User.current.allowed_to?(:browse_repository, project)
702 if project && project.repository && User.current.allowed_to?(:browse_repository, project)
703 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
703 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
704 path, rev, anchor = $1, $3, $5
704 path, rev, anchor = $1, $3, $5
705 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
705 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
706 :path => to_path_param(path),
706 :path => to_path_param(path),
707 :rev => rev,
707 :rev => rev,
708 :anchor => anchor,
708 :anchor => anchor,
709 :format => (prefix == 'export' ? 'raw' : nil)},
709 :format => (prefix == 'export' ? 'raw' : nil)},
710 :class => (prefix == 'export' ? 'source download' : 'source')
710 :class => (prefix == 'export' ? 'source download' : 'source')
711 end
711 end
712 when 'attachment'
712 when 'attachment'
713 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
713 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
714 if attachments && attachment = attachments.detect {|a| a.filename == name }
714 if attachments && attachment = attachments.detect {|a| a.filename == name }
715 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
715 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
716 :class => 'attachment'
716 :class => 'attachment'
717 end
717 end
718 when 'project'
718 when 'project'
719 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
719 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
720 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
720 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
721 end
721 end
722 end
722 end
723 end
723 end
724 end
724 end
725 (leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")).html_safe
725 (leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")).html_safe
726 end
726 end
727 end
727 end
728
728
729 HEADING_RE = /<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>/i unless const_defined?(:HEADING_RE)
729 HEADING_RE = /<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>/i unless const_defined?(:HEADING_RE)
730
730
731 # Headings and TOC
731 # Headings and TOC
732 # Adds ids and links to headings unless options[:headings] is set to false
732 # Adds ids and links to headings unless options[:headings] is set to false
733 def parse_headings(text, project, obj, attr, only_path, options)
733 def parse_headings(text, project, obj, attr, only_path, options)
734 return if options[:headings] == false
734 return if options[:headings] == false
735
735
736 text.gsub!(HEADING_RE) do
736 text.gsub!(HEADING_RE) do
737 level, attrs, content = $1.to_i, $2, $3
737 level, attrs, content = $1.to_i, $2, $3
738 item = strip_tags(content).strip
738 item = strip_tags(content).strip
739 anchor = sanitize_anchor_name(item)
739 anchor = sanitize_anchor_name(item)
740 # used for single-file wiki export
740 # used for single-file wiki export
741 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
741 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
742 @parsed_headings << [level, anchor, item]
742 @parsed_headings << [level, anchor, item]
743 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
743 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
744 end
744 end
745 end
745 end
746
746
747 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
747 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
748
748
749 # Renders the TOC with given headings
749 # Renders the TOC with given headings
750 def replace_toc(text, headings)
750 def replace_toc(text, headings)
751 text.gsub!(TOC_RE) do
751 text.gsub!(TOC_RE) do
752 if headings.empty?
752 if headings.empty?
753 ''
753 ''
754 else
754 else
755 div_class = 'toc'
755 div_class = 'toc'
756 div_class << ' right' if $1 == '>'
756 div_class << ' right' if $1 == '>'
757 div_class << ' left' if $1 == '<'
757 div_class << ' left' if $1 == '<'
758 out = "<ul class=\"#{div_class}\"><li>"
758 out = "<ul class=\"#{div_class}\"><li>"
759 root = headings.map(&:first).min
759 root = headings.map(&:first).min
760 current = root
760 current = root
761 started = false
761 started = false
762 headings.each do |level, anchor, item|
762 headings.each do |level, anchor, item|
763 if level > current
763 if level > current
764 out << '<ul><li>' * (level - current)
764 out << '<ul><li>' * (level - current)
765 elsif level < current
765 elsif level < current
766 out << "</li></ul>\n" * (current - level) + "</li><li>"
766 out << "</li></ul>\n" * (current - level) + "</li><li>"
767 elsif started
767 elsif started
768 out << '</li><li>'
768 out << '</li><li>'
769 end
769 end
770 out << "<a href=\"##{anchor}\">#{item}</a>"
770 out << "<a href=\"##{anchor}\">#{item}</a>"
771 current = level
771 current = level
772 started = true
772 started = true
773 end
773 end
774 out << '</li></ul>' * (current - root)
774 out << '</li></ul>' * (current - root)
775 out << '</li></ul>'
775 out << '</li></ul>'
776 end
776 end
777 end
777 end
778 end
778 end
779
779
780 # Same as Rails' simple_format helper without using paragraphs
780 # Same as Rails' simple_format helper without using paragraphs
781 def simple_format_without_paragraph(text)
781 def simple_format_without_paragraph(text)
782 text.to_s.
782 text.to_s.
783 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
783 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
784 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
784 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
785 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
785 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
786 html_safe
786 html_safe
787 end
787 end
788
788
789 def lang_options_for_select(blank=true)
789 def lang_options_for_select(blank=true)
790 (blank ? [["(auto)", ""]] : []) +
790 (blank ? [["(auto)", ""]] : []) +
791 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
791 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
792 end
792 end
793
793
794 def label_tag_for(name, option_tags = nil, options = {})
794 def label_tag_for(name, option_tags = nil, options = {})
795 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
795 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
796 content_tag("label", label_text)
796 content_tag("label", label_text)
797 end
797 end
798
798
799 def labelled_tabular_form_for(name, object, options, &proc)
799 def labelled_tabular_form_for(name, object, options, &proc)
800 options[:html] ||= {}
800 options[:html] ||= {}
801 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
801 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
802 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
802 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
803 end
803 end
804
804
805 def back_url_hidden_field_tag
805 def back_url_hidden_field_tag
806 back_url = params[:back_url] || request.env['HTTP_REFERER']
806 back_url = params[:back_url] || request.env['HTTP_REFERER']
807 back_url = CGI.unescape(back_url.to_s)
807 back_url = CGI.unescape(back_url.to_s)
808 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
808 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
809 end
809 end
810
810
811 def check_all_links(form_name)
811 def check_all_links(form_name)
812 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
812 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
813 " | ".html_safe +
813 " | ".html_safe +
814 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
814 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
815 end
815 end
816
816
817 def progress_bar(pcts, options={})
817 def progress_bar(pcts, options={})
818 pcts = [pcts, pcts] unless pcts.is_a?(Array)
818 pcts = [pcts, pcts] unless pcts.is_a?(Array)
819 pcts = pcts.collect(&:round)
819 pcts = pcts.collect(&:round)
820 pcts[1] = pcts[1] - pcts[0]
820 pcts[1] = pcts[1] - pcts[0]
821 pcts << (100 - pcts[1] - pcts[0])
821 pcts << (100 - pcts[1] - pcts[0])
822 width = options[:width] || '100px;'
822 width = options[:width] || '100px;'
823 legend = options[:legend] || ''
823 legend = options[:legend] || ''
824 content_tag('table',
824 content_tag('table',
825 content_tag('tr',
825 content_tag('tr',
826 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
826 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
827 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
827 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
828 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
828 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
829 ), :class => 'progress', :style => "width: #{width};").html_safe +
829 ), :class => 'progress', :style => "width: #{width};").html_safe +
830 content_tag('p', legend, :class => 'pourcent').html_safe
830 content_tag('p', legend, :class => 'pourcent').html_safe
831 end
831 end
832
832
833 def checked_image(checked=true)
833 def checked_image(checked=true)
834 if checked
834 if checked
835 image_tag 'toggle_check.png'
835 image_tag 'toggle_check.png'
836 end
836 end
837 end
837 end
838
838
839 def context_menu(url)
839 def context_menu(url)
840 unless @context_menu_included
840 unless @context_menu_included
841 content_for :header_tags do
841 content_for :header_tags do
842 javascript_include_tag('context_menu') +
842 javascript_include_tag('context_menu') +
843 stylesheet_link_tag('context_menu')
843 stylesheet_link_tag('context_menu')
844 end
844 end
845 if l(:direction) == 'rtl'
845 if l(:direction) == 'rtl'
846 content_for :header_tags do
846 content_for :header_tags do
847 stylesheet_link_tag('context_menu_rtl')
847 stylesheet_link_tag('context_menu_rtl')
848 end
848 end
849 end
849 end
850 @context_menu_included = true
850 @context_menu_included = true
851 end
851 end
852 javascript_tag "new ContextMenu('#{ url_for(url) }')"
852 javascript_tag "new ContextMenu('#{ url_for(url) }')"
853 end
853 end
854
854
855 def context_menu_link(name, url, options={})
855 def context_menu_link(name, url, options={})
856 options[:class] ||= ''
856 options[:class] ||= ''
857 if options.delete(:selected)
857 if options.delete(:selected)
858 options[:class] << ' icon-checked disabled'
858 options[:class] << ' icon-checked disabled'
859 options[:disabled] = true
859 options[:disabled] = true
860 end
860 end
861 if options.delete(:disabled)
861 if options.delete(:disabled)
862 options.delete(:method)
862 options.delete(:method)
863 options.delete(:confirm)
863 options.delete(:confirm)
864 options.delete(:onclick)
864 options.delete(:onclick)
865 options[:class] << ' disabled'
865 options[:class] << ' disabled'
866 url = '#'
866 url = '#'
867 end
867 end
868 link_to h(name), url, options
868 link_to h(name), url, options
869 end
869 end
870
870
871 def calendar_for(field_id)
871 def calendar_for(field_id)
872 include_calendar_headers_tags
872 include_calendar_headers_tags
873 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
873 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
874 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
874 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
875 end
875 end
876
876
877 def include_calendar_headers_tags
877 def include_calendar_headers_tags
878 unless @calendar_headers_tags_included
878 unless @calendar_headers_tags_included
879 @calendar_headers_tags_included = true
879 @calendar_headers_tags_included = true
880 content_for :header_tags do
880 content_for :header_tags do
881 start_of_week = case Setting.start_of_week.to_i
881 start_of_week = case Setting.start_of_week.to_i
882 when 1
882 when 1
883 'Calendar._FD = 1;' # Monday
883 'Calendar._FD = 1;' # Monday
884 when 7
884 when 7
885 'Calendar._FD = 0;' # Sunday
885 'Calendar._FD = 0;' # Sunday
886 when 6
886 when 6
887 'Calendar._FD = 6;' # Saturday
887 'Calendar._FD = 6;' # Saturday
888 else
888 else
889 '' # use language
889 '' # use language
890 end
890 end
891
891
892 javascript_include_tag('calendar/calendar') +
892 javascript_include_tag('calendar/calendar') +
893 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
893 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
894 javascript_tag(start_of_week) +
894 javascript_tag(start_of_week) +
895 javascript_include_tag('calendar/calendar-setup') +
895 javascript_include_tag('calendar/calendar-setup') +
896 stylesheet_link_tag('calendar')
896 stylesheet_link_tag('calendar')
897 end
897 end
898 end
898 end
899 end
899 end
900
900
901 def content_for(name, content = nil, &block)
901 def content_for(name, content = nil, &block)
902 @has_content ||= {}
902 @has_content ||= {}
903 @has_content[name] = true
903 @has_content[name] = true
904 super(name, content, &block)
904 super(name, content, &block)
905 end
905 end
906
906
907 def has_content?(name)
907 def has_content?(name)
908 (@has_content && @has_content[name]) || false
908 (@has_content && @has_content[name]) || false
909 end
909 end
910
910
911 def email_delivery_enabled?
911 def email_delivery_enabled?
912 !!ActionMailer::Base.perform_deliveries
912 !!ActionMailer::Base.perform_deliveries
913 end
913 end
914
914
915 # Returns the avatar image tag for the given +user+ if avatars are enabled
915 # Returns the avatar image tag for the given +user+ if avatars are enabled
916 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
916 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
917 def avatar(user, options = { })
917 def avatar(user, options = { })
918 if Setting.gravatar_enabled?
918 if Setting.gravatar_enabled?
919 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
919 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
920 email = nil
920 email = nil
921 if user.respond_to?(:mail)
921 if user.respond_to?(:mail)
922 email = user.mail
922 email = user.mail
923 elsif user.to_s =~ %r{<(.+?)>}
923 elsif user.to_s =~ %r{<(.+?)>}
924 email = $1
924 email = $1
925 end
925 end
926 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
926 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
927 else
927 else
928 ''
928 ''
929 end
929 end
930 end
930 end
931
931
932 def sanitize_anchor_name(anchor)
932 def sanitize_anchor_name(anchor)
933 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
933 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
934 end
934 end
935
935
936 # Returns the javascript tags that are included in the html layout head
936 # Returns the javascript tags that are included in the html layout head
937 def javascript_heads
937 def javascript_heads
938 tags = javascript_include_tag(:defaults)
938 tags = javascript_include_tag(:defaults)
939 unless User.current.pref.warn_on_leaving_unsaved == '0'
939 unless User.current.pref.warn_on_leaving_unsaved == '0'
940 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
940 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
941 end
941 end
942 tags
942 tags
943 end
943 end
944
944
945 def favicon
945 def favicon
946 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
946 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
947 end
947 end
948
948
949 def robot_exclusion_tag
949 def robot_exclusion_tag
950 '<meta name="robots" content="noindex,follow,noarchive" />'
950 '<meta name="robots" content="noindex,follow,noarchive" />'
951 end
951 end
952
952
953 # Returns true if arg is expected in the API response
953 # Returns true if arg is expected in the API response
954 def include_in_api_response?(arg)
954 def include_in_api_response?(arg)
955 unless @included_in_api_response
955 unless @included_in_api_response
956 param = params[:include]
956 param = params[:include]
957 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
957 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
958 @included_in_api_response.collect!(&:strip)
958 @included_in_api_response.collect!(&:strip)
959 end
959 end
960 @included_in_api_response.include?(arg.to_s)
960 @included_in_api_response.include?(arg.to_s)
961 end
961 end
962
962
963 # Returns options or nil if nometa param or X-Redmine-Nometa header
963 # Returns options or nil if nometa param or X-Redmine-Nometa header
964 # was set in the request
964 # was set in the request
965 def api_meta(options)
965 def api_meta(options)
966 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
966 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
967 # compatibility mode for activeresource clients that raise
967 # compatibility mode for activeresource clients that raise
968 # an error when unserializing an array with attributes
968 # an error when unserializing an array with attributes
969 nil
969 nil
970 else
970 else
971 options
971 options
972 end
972 end
973 end
973 end
974
974
975 private
975 private
976
976
977 def wiki_helper
977 def wiki_helper
978 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
978 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
979 extend helper
979 extend helper
980 return self
980 return self
981 end
981 end
982
982
983 def link_to_content_update(text, url_params = {}, html_options = {})
983 def link_to_content_update(text, url_params = {}, html_options = {})
984 link_to(text, url_params, html_options)
984 link_to(text, url_params, html_options)
985 end
985 end
986 end
986 end
@@ -1,787 +1,791
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class ApplicationHelperTest < ActionView::TestCase
20 class ApplicationHelperTest < ActionView::TestCase
21
21
22 fixtures :projects, :roles, :enabled_modules, :users,
22 fixtures :projects, :roles, :enabled_modules, :users,
23 :repositories, :changesets,
23 :repositories, :changesets,
24 :trackers, :issue_statuses, :issues, :versions, :documents,
24 :trackers, :issue_statuses, :issues, :versions, :documents,
25 :wikis, :wiki_pages, :wiki_contents,
25 :wikis, :wiki_pages, :wiki_contents,
26 :boards, :messages,
26 :boards, :messages,
27 :attachments,
27 :attachments,
28 :enumerations
28 :enumerations
29
29
30 def setup
30 def setup
31 super
31 super
32 end
32 end
33
33
34 context "#link_to_if_authorized" do
34 context "#link_to_if_authorized" do
35 context "authorized user" do
35 context "authorized user" do
36 should "be tested"
36 should "be tested"
37 end
37 end
38
38
39 context "unauthorized user" do
39 context "unauthorized user" do
40 should "be tested"
40 should "be tested"
41 end
41 end
42
42
43 should "allow using the :controller and :action for the target link" do
43 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/action",
47 response = link_to_if_authorized("By controller/action",
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 end
52 end
53
53
54 def test_auto_links
54 def test_auto_links
55 to_test = {
55 to_test = {
56 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
56 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
57 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
57 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
58 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
58 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
59 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
59 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
60 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
60 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
61 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
61 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
62 '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>.',
62 '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>.',
63 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
63 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
64 '(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>)',
64 '(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>)',
65 '(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>)',
65 '(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>)',
66 '(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>).',
66 '(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>).',
67 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
67 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
68 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
68 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
69 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
69 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
70 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
70 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
71 '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>',
71 '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>',
72 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
72 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
73 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
73 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
74 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
74 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
75 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
75 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
76 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
76 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
77 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
77 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
78 # two exclamation marks
78 # two exclamation marks
79 '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>',
79 '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>',
80 # escaping
80 # escaping
81 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo"bar</a>',
81 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo"bar</a>',
82 # wrap in angle brackets
82 # wrap in angle brackets
83 '<http://foo.bar>' => '&lt;<a class="external" href="http://foo.bar">http://foo.bar</a>&gt;'
83 '<http://foo.bar>' => '&lt;<a class="external" href="http://foo.bar">http://foo.bar</a>&gt;'
84 }
84 }
85 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
85 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
86 end
86 end
87
87
88 def test_auto_mailto
88 def test_auto_mailto
89 assert_equal '<p><a class="email" href="mailto:test@foo.bar">test@foo.bar</a></p>',
89 assert_equal '<p><a class="email" href="mailto:test@foo.bar">test@foo.bar</a></p>',
90 textilizable('test@foo.bar')
90 textilizable('test@foo.bar')
91 end
91 end
92
92
93 def test_inline_images
93 def test_inline_images
94 to_test = {
94 to_test = {
95 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
95 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
96 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
96 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
97 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
97 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
98 # inline styles should be stripped
98 # inline styles should be stripped
99 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" alt="" />',
99 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" alt="" />',
100 '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" />',
100 '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" />',
101 '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;" />',
101 '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;" />',
102 }
102 }
103 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
103 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
104 end
104 end
105
105
106 def test_inline_images_inside_tags
106 def test_inline_images_inside_tags
107 raw = <<-RAW
107 raw = <<-RAW
108 h1. !foo.png! Heading
108 h1. !foo.png! Heading
109
109
110 Centered image:
110 Centered image:
111
111
112 p=. !bar.gif!
112 p=. !bar.gif!
113 RAW
113 RAW
114
114
115 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
115 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
116 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
116 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
117 end
117 end
118
118
119 def test_attached_images
119 def test_attached_images
120 to_test = {
120 to_test = {
121 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
121 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
122 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
122 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
123 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
123 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
124 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
124 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
125 # link image
125 # link image
126 '!logo.gif!:http://foo.bar/' => '<a href="http://foo.bar/"><img src="/attachments/download/3" title="This is a logo" alt="This is a logo" /></a>',
126 '!logo.gif!:http://foo.bar/' => '<a href="http://foo.bar/"><img src="/attachments/download/3" title="This is a logo" alt="This is a logo" /></a>',
127 }
127 }
128 attachments = Attachment.find(:all)
128 attachments = Attachment.find(:all)
129 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
129 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
130 end
130 end
131
131
132 def test_textile_external_links
132 def test_textile_external_links
133 to_test = {
133 to_test = {
134 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
134 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
135 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
135 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
136 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
136 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
137 '"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>',
137 '"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>',
138 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
138 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
139 # no multiline link text
139 # no multiline link text
140 "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",
140 "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",
141 # mailto link
141 # mailto link
142 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
142 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
143 # two exclamation marks
143 # two exclamation marks
144 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
144 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
145 # escaping
145 # escaping
146 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
146 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
147 }
147 }
148 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
148 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
149 end
149 end
150
150
151 def test_redmine_links
151 def test_redmine_links
152 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
152 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
153 :class => 'issue status-1 priority-1 overdue', :title => 'Error 281 when updating a recipe (New)')
153 :class => 'issue status-1 priority-1 overdue', :title => 'Error 281 when updating a recipe (New)')
154
154
155 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
155 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
156 :class => 'changeset', :title => 'My very first commit')
156 :class => 'changeset', :title => 'My very first commit')
157 changeset_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
157 changeset_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
158 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
158 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
159
159
160 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
160 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
161 :class => 'document')
161 :class => 'document')
162
162
163 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
163 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
164 :class => 'version')
164 :class => 'version')
165
165
166 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
166 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
167
167
168 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
168 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
169
169
170 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
170 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
171 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
171 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
172
172
173 to_test = {
173 to_test = {
174 # tickets
174 # tickets
175 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
175 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
176 # changesets
176 # changesets
177 'r1' => changeset_link,
177 'r1' => changeset_link,
178 'r1.' => "#{changeset_link}.",
178 'r1.' => "#{changeset_link}.",
179 'r1, r2' => "#{changeset_link}, #{changeset_link2}",
179 'r1, r2' => "#{changeset_link}, #{changeset_link2}",
180 'r1,r2' => "#{changeset_link},#{changeset_link2}",
180 'r1,r2' => "#{changeset_link},#{changeset_link2}",
181 # documents
181 # documents
182 'document#1' => document_link,
182 'document#1' => document_link,
183 'document:"Test document"' => document_link,
183 'document:"Test document"' => document_link,
184 # versions
184 # versions
185 'version#2' => version_link,
185 'version#2' => version_link,
186 'version:1.0' => version_link,
186 'version:1.0' => version_link,
187 'version:"1.0"' => version_link,
187 'version:"1.0"' => version_link,
188 # source
188 # source
189 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
189 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
190 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
190 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
191 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
191 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
192 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
192 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
193 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
193 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
194 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
194 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
195 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
195 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
196 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
196 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
197 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
197 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
198 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
198 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
199 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
199 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
200 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
200 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
201 # message
201 # message
202 'message#4' => link_to('Post 2', message_url, :class => 'message'),
202 'message#4' => link_to('Post 2', message_url, :class => 'message'),
203 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5', :r => 5), :class => 'message'),
203 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5', :r => 5), :class => 'message'),
204 # project
204 # project
205 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
205 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
206 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
206 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
207 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
207 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
208 # escaping
208 # escaping
209 '!#3.' => '#3.',
209 '!#3.' => '#3.',
210 '!r1' => 'r1',
210 '!r1' => 'r1',
211 '!document#1' => 'document#1',
211 '!document#1' => 'document#1',
212 '!document:"Test document"' => 'document:"Test document"',
212 '!document:"Test document"' => 'document:"Test document"',
213 '!version#2' => 'version#2',
213 '!version#2' => 'version#2',
214 '!version:1.0' => 'version:1.0',
214 '!version:1.0' => 'version:1.0',
215 '!version:"1.0"' => 'version:"1.0"',
215 '!version:"1.0"' => 'version:"1.0"',
216 '!source:/some/file' => 'source:/some/file',
216 '!source:/some/file' => 'source:/some/file',
217 # not found
217 # not found
218 '#0123456789' => '#0123456789',
218 '#0123456789' => '#0123456789',
219 # invalid expressions
219 # invalid expressions
220 'source:' => 'source:',
220 'source:' => 'source:',
221 # url hash
221 # url hash
222 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
222 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
223 }
223 }
224 @project = Project.find(1)
224 @project = Project.find(1)
225 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
225 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
226 end
226 end
227
227
228 def test_cross_project_redmine_links
228 def test_cross_project_redmine_links
229 source_link = link_to('ecookbook:source:/some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']},
229 source_link = link_to('ecookbook:source:/some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']},
230 :class => 'source')
230 :class => 'source')
231
231
232 changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
232 changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
233 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
233 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
234
234
235 to_test = {
235 to_test = {
236 # documents
236 # documents
237 'document:"Test document"' => 'document:"Test document"',
237 'document:"Test document"' => 'document:"Test document"',
238 'ecookbook:document:"Test document"' => '<a href="/documents/1" class="document">Test document</a>',
238 'ecookbook:document:"Test document"' => '<a href="/documents/1" class="document">Test document</a>',
239 'invalid:document:"Test document"' => 'invalid:document:"Test document"',
239 'invalid:document:"Test document"' => 'invalid:document:"Test document"',
240 # versions
240 # versions
241 'version:"1.0"' => 'version:"1.0"',
241 'version:"1.0"' => 'version:"1.0"',
242 'ecookbook:version:"1.0"' => '<a href="/versions/2" class="version">1.0</a>',
242 'ecookbook:version:"1.0"' => '<a href="/versions/2" class="version">1.0</a>',
243 'invalid:version:"1.0"' => 'invalid:version:"1.0"',
243 'invalid:version:"1.0"' => 'invalid:version:"1.0"',
244 # changeset
244 # changeset
245 'r2' => 'r2',
245 'r2' => 'r2',
246 'ecookbook:r2' => changeset_link,
246 'ecookbook:r2' => changeset_link,
247 'invalid:r2' => 'invalid:r2',
247 'invalid:r2' => 'invalid:r2',
248 # source
248 # source
249 'source:/some/file' => 'source:/some/file',
249 'source:/some/file' => 'source:/some/file',
250 'ecookbook:source:/some/file' => source_link,
250 'ecookbook:source:/some/file' => source_link,
251 'invalid:source:/some/file' => 'invalid:source:/some/file',
251 'invalid:source:/some/file' => 'invalid:source:/some/file',
252 }
252 }
253 @project = Project.find(3)
253 @project = Project.find(3)
254 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
254 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
255 end
255 end
256
256
257 def test_redmine_links_git_commit
257 def test_redmine_links_git_commit
258 changeset_link = link_to('abcd',
258 changeset_link = link_to('abcd',
259 {
259 {
260 :controller => 'repositories',
260 :controller => 'repositories',
261 :action => 'revision',
261 :action => 'revision',
262 :id => 'subproject1',
262 :id => 'subproject1',
263 :rev => 'abcd',
263 :rev => 'abcd',
264 },
264 },
265 :class => 'changeset', :title => 'test commit')
265 :class => 'changeset', :title => 'test commit')
266 to_test = {
266 to_test = {
267 'commit:abcd' => changeset_link,
267 'commit:abcd' => changeset_link,
268 }
268 }
269 @project = Project.find(3)
269 @project = Project.find(3)
270 r = Repository::Git.create!(:project => @project, :url => '/tmp/test/git')
270 r = Repository::Git.create!(:project => @project, :url => '/tmp/test/git')
271 assert r
271 assert r
272 c = Changeset.new(:repository => r,
272 c = Changeset.new(:repository => r,
273 :committed_on => Time.now,
273 :committed_on => Time.now,
274 :revision => 'abcd',
274 :revision => 'abcd',
275 :scmid => 'abcd',
275 :scmid => 'abcd',
276 :comments => 'test commit')
276 :comments => 'test commit')
277 assert( c.save )
277 assert( c.save )
278 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
278 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
279 end
279 end
280
280
281 # TODO: Bazaar commit id contains mail address, so it contains '@' and '_'.
281 # TODO: Bazaar commit id contains mail address, so it contains '@' and '_'.
282 def test_redmine_links_darcs_commit
282 def test_redmine_links_darcs_commit
283 changeset_link = link_to('20080308225258-98289-abcd456efg.gz',
283 changeset_link = link_to('20080308225258-98289-abcd456efg.gz',
284 {
284 {
285 :controller => 'repositories',
285 :controller => 'repositories',
286 :action => 'revision',
286 :action => 'revision',
287 :id => 'subproject1',
287 :id => 'subproject1',
288 :rev => '123',
288 :rev => '123',
289 },
289 },
290 :class => 'changeset', :title => 'test commit')
290 :class => 'changeset', :title => 'test commit')
291 to_test = {
291 to_test = {
292 'commit:20080308225258-98289-abcd456efg.gz' => changeset_link,
292 'commit:20080308225258-98289-abcd456efg.gz' => changeset_link,
293 }
293 }
294 @project = Project.find(3)
294 @project = Project.find(3)
295 r = Repository::Darcs.create!(
295 r = Repository::Darcs.create!(
296 :project => @project, :url => '/tmp/test/darcs',
296 :project => @project, :url => '/tmp/test/darcs',
297 :log_encoding => 'UTF-8')
297 :log_encoding => 'UTF-8')
298 assert r
298 assert r
299 c = Changeset.new(:repository => r,
299 c = Changeset.new(:repository => r,
300 :committed_on => Time.now,
300 :committed_on => Time.now,
301 :revision => '123',
301 :revision => '123',
302 :scmid => '20080308225258-98289-abcd456efg.gz',
302 :scmid => '20080308225258-98289-abcd456efg.gz',
303 :comments => 'test commit')
303 :comments => 'test commit')
304 assert( c.save )
304 assert( c.save )
305 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
305 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
306 end
306 end
307
307
308 def test_redmine_links_mercurial_commit
308 def test_redmine_links_mercurial_commit
309 changeset_link_rev = link_to('r123',
309 changeset_link_rev = link_to('r123',
310 {
310 {
311 :controller => 'repositories',
311 :controller => 'repositories',
312 :action => 'revision',
312 :action => 'revision',
313 :id => 'subproject1',
313 :id => 'subproject1',
314 :rev => '123' ,
314 :rev => '123' ,
315 },
315 },
316 :class => 'changeset', :title => 'test commit')
316 :class => 'changeset', :title => 'test commit')
317 changeset_link_commit = link_to('abcd',
317 changeset_link_commit = link_to('abcd',
318 {
318 {
319 :controller => 'repositories',
319 :controller => 'repositories',
320 :action => 'revision',
320 :action => 'revision',
321 :id => 'subproject1',
321 :id => 'subproject1',
322 :rev => 'abcd' ,
322 :rev => 'abcd' ,
323 },
323 },
324 :class => 'changeset', :title => 'test commit')
324 :class => 'changeset', :title => 'test commit')
325 to_test = {
325 to_test = {
326 'r123' => changeset_link_rev,
326 'r123' => changeset_link_rev,
327 'commit:abcd' => changeset_link_commit,
327 'commit:abcd' => changeset_link_commit,
328 }
328 }
329 @project = Project.find(3)
329 @project = Project.find(3)
330 r = Repository::Mercurial.create!(:project => @project, :url => '/tmp/test')
330 r = Repository::Mercurial.create!(:project => @project, :url => '/tmp/test')
331 assert r
331 assert r
332 c = Changeset.new(:repository => r,
332 c = Changeset.new(:repository => r,
333 :committed_on => Time.now,
333 :committed_on => Time.now,
334 :revision => '123',
334 :revision => '123',
335 :scmid => 'abcd',
335 :scmid => 'abcd',
336 :comments => 'test commit')
336 :comments => 'test commit')
337 assert( c.save )
337 assert( c.save )
338 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
338 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
339 end
339 end
340
340
341 def test_attachment_links
341 def test_attachment_links
342 attachment_link = link_to('error281.txt', {:controller => 'attachments', :action => 'download', :id => '1'}, :class => 'attachment')
342 attachment_link = link_to('error281.txt', {:controller => 'attachments', :action => 'download', :id => '1'}, :class => 'attachment')
343 to_test = {
343 to_test = {
344 'attachment:error281.txt' => attachment_link
344 'attachment:error281.txt' => attachment_link
345 }
345 }
346 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => Issue.find(3).attachments), "#{text} failed" }
346 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => Issue.find(3).attachments), "#{text} failed" }
347 end
347 end
348
348
349 def test_wiki_links
349 def test_wiki_links
350 to_test = {
350 to_test = {
351 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
351 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
352 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
352 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
353 # title content should be formatted
354 '[[Another page|With _styled_ *title*]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">With <em>styled</em> <strong>title</strong></a>',
355 '[[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>',
353 # link with anchor
356 # link with anchor
354 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
357 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
355 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
358 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
356 # page that doesn't exist
359 # page that doesn't exist
357 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
360 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
358 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
361 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
359 # link to another project wiki
362 # link to another project wiki
360 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">onlinestore</a>',
363 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">onlinestore</a>',
361 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">Wiki</a>',
364 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">Wiki</a>',
362 '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
365 '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
363 '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
366 '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
364 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
367 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
365 # striked through link
368 # striked through link
366 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
369 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
367 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
370 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
368 # escaping
371 # escaping
369 '![[Another page|Page]]' => '[[Another page|Page]]',
372 '![[Another page|Page]]' => '[[Another page|Page]]',
370 # project does not exist
373 # project does not exist
371 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
374 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
372 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
375 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
373 }
376 }
377
374 @project = Project.find(1)
378 @project = Project.find(1)
375 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
379 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
376 end
380 end
377
381
378 def test_wiki_links_within_local_file_generation_context
382 def test_wiki_links_within_local_file_generation_context
379
383
380 to_test = {
384 to_test = {
381 # link to a page
385 # link to a page
382 '[[CookBook documentation]]' => '<a href="CookBook_documentation.html" class="wiki-page">CookBook documentation</a>',
386 '[[CookBook documentation]]' => '<a href="CookBook_documentation.html" class="wiki-page">CookBook documentation</a>',
383 '[[CookBook documentation|documentation]]' => '<a href="CookBook_documentation.html" class="wiki-page">documentation</a>',
387 '[[CookBook documentation|documentation]]' => '<a href="CookBook_documentation.html" class="wiki-page">documentation</a>',
384 '[[CookBook documentation#One-section]]' => '<a href="CookBook_documentation.html#One-section" class="wiki-page">CookBook documentation</a>',
388 '[[CookBook documentation#One-section]]' => '<a href="CookBook_documentation.html#One-section" class="wiki-page">CookBook documentation</a>',
385 '[[CookBook documentation#One-section|documentation]]' => '<a href="CookBook_documentation.html#One-section" class="wiki-page">documentation</a>',
389 '[[CookBook documentation#One-section|documentation]]' => '<a href="CookBook_documentation.html#One-section" class="wiki-page">documentation</a>',
386 # page that doesn't exist
390 # page that doesn't exist
387 '[[Unknown page]]' => '<a href="Unknown_page.html" class="wiki-page new">Unknown page</a>',
391 '[[Unknown page]]' => '<a href="Unknown_page.html" class="wiki-page new">Unknown page</a>',
388 '[[Unknown page|404]]' => '<a href="Unknown_page.html" class="wiki-page new">404</a>',
392 '[[Unknown page|404]]' => '<a href="Unknown_page.html" class="wiki-page new">404</a>',
389 '[[Unknown page#anchor]]' => '<a href="Unknown_page.html#anchor" class="wiki-page new">Unknown page</a>',
393 '[[Unknown page#anchor]]' => '<a href="Unknown_page.html#anchor" class="wiki-page new">Unknown page</a>',
390 '[[Unknown page#anchor|404]]' => '<a href="Unknown_page.html#anchor" class="wiki-page new">404</a>',
394 '[[Unknown page#anchor|404]]' => '<a href="Unknown_page.html#anchor" class="wiki-page new">404</a>',
391 }
395 }
392
396
393 @project = Project.find(1)
397 @project = Project.find(1)
394
398
395 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :local) }
399 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :local) }
396 end
400 end
397
401
398 def test_html_tags
402 def test_html_tags
399 to_test = {
403 to_test = {
400 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
404 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
401 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
405 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
402 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
406 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
403 # do not escape pre/code tags
407 # do not escape pre/code tags
404 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
408 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
405 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
409 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
406 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
410 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
407 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
411 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
408 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
412 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
409 # remove attributes except class
413 # remove attributes except class
410 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
414 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
411 '<pre class="foo">some text</pre>' => '<pre class="foo">some text</pre>',
415 '<pre class="foo">some text</pre>' => '<pre class="foo">some text</pre>',
412 "<pre class='foo bar'>some text</pre>" => "<pre class='foo bar'>some text</pre>",
416 "<pre class='foo bar'>some text</pre>" => "<pre class='foo bar'>some text</pre>",
413 '<pre class="foo bar">some text</pre>' => '<pre class="foo bar">some text</pre>',
417 '<pre class="foo bar">some text</pre>' => '<pre class="foo bar">some text</pre>',
414 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
418 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
415 # xss
419 # xss
416 '<pre><code class=""onmouseover="alert(1)">text</code></pre>' => '<pre><code>text</code></pre>',
420 '<pre><code class=""onmouseover="alert(1)">text</code></pre>' => '<pre><code>text</code></pre>',
417 '<pre class=""onmouseover="alert(1)">text</pre>' => '<pre>text</pre>',
421 '<pre class=""onmouseover="alert(1)">text</pre>' => '<pre>text</pre>',
418 }
422 }
419 to_test.each { |text, result| assert_equal result, textilizable(text) }
423 to_test.each { |text, result| assert_equal result, textilizable(text) }
420 end
424 end
421
425
422 def test_allowed_html_tags
426 def test_allowed_html_tags
423 to_test = {
427 to_test = {
424 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
428 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
425 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
429 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
426 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
430 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
427 }
431 }
428 to_test.each { |text, result| assert_equal result, textilizable(text) }
432 to_test.each { |text, result| assert_equal result, textilizable(text) }
429 end
433 end
430
434
431 def test_pre_tags
435 def test_pre_tags
432 raw = <<-RAW
436 raw = <<-RAW
433 Before
437 Before
434
438
435 <pre>
439 <pre>
436 <prepared-statement-cache-size>32</prepared-statement-cache-size>
440 <prepared-statement-cache-size>32</prepared-statement-cache-size>
437 </pre>
441 </pre>
438
442
439 After
443 After
440 RAW
444 RAW
441
445
442 expected = <<-EXPECTED
446 expected = <<-EXPECTED
443 <p>Before</p>
447 <p>Before</p>
444 <pre>
448 <pre>
445 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
449 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
446 </pre>
450 </pre>
447 <p>After</p>
451 <p>After</p>
448 EXPECTED
452 EXPECTED
449
453
450 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
454 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
451 end
455 end
452
456
453 def test_pre_content_should_not_parse_wiki_and_redmine_links
457 def test_pre_content_should_not_parse_wiki_and_redmine_links
454 raw = <<-RAW
458 raw = <<-RAW
455 [[CookBook documentation]]
459 [[CookBook documentation]]
456
460
457 #1
461 #1
458
462
459 <pre>
463 <pre>
460 [[CookBook documentation]]
464 [[CookBook documentation]]
461
465
462 #1
466 #1
463 </pre>
467 </pre>
464 RAW
468 RAW
465
469
466 expected = <<-EXPECTED
470 expected = <<-EXPECTED
467 <p><a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a></p>
471 <p><a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a></p>
468 <p><a href="/issues/1" class="issue status-1 priority-1" title="Can't print recipes (New)">#1</a></p>
472 <p><a href="/issues/1" class="issue status-1 priority-1" title="Can't print recipes (New)">#1</a></p>
469 <pre>
473 <pre>
470 [[CookBook documentation]]
474 [[CookBook documentation]]
471
475
472 #1
476 #1
473 </pre>
477 </pre>
474 EXPECTED
478 EXPECTED
475
479
476 @project = Project.find(1)
480 @project = Project.find(1)
477 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
481 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
478 end
482 end
479
483
480 def test_non_closing_pre_blocks_should_be_closed
484 def test_non_closing_pre_blocks_should_be_closed
481 raw = <<-RAW
485 raw = <<-RAW
482 <pre><code>
486 <pre><code>
483 RAW
487 RAW
484
488
485 expected = <<-EXPECTED
489 expected = <<-EXPECTED
486 <pre><code>
490 <pre><code>
487 </code></pre>
491 </code></pre>
488 EXPECTED
492 EXPECTED
489
493
490 @project = Project.find(1)
494 @project = Project.find(1)
491 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
495 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
492 end
496 end
493
497
494 def test_syntax_highlight
498 def test_syntax_highlight
495 raw = <<-RAW
499 raw = <<-RAW
496 <pre><code class="ruby">
500 <pre><code class="ruby">
497 # Some ruby code here
501 # Some ruby code here
498 </code></pre>
502 </code></pre>
499 RAW
503 RAW
500
504
501 expected = <<-EXPECTED
505 expected = <<-EXPECTED
502 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="line-numbers">1</span><span class="comment"># Some ruby code here</span></span>
506 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="line-numbers">1</span><span class="comment"># Some ruby code here</span></span>
503 </code></pre>
507 </code></pre>
504 EXPECTED
508 EXPECTED
505
509
506 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
510 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
507 end
511 end
508
512
509 def test_wiki_links_in_tables
513 def test_wiki_links_in_tables
510 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
514 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
511 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
515 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
512 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
516 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
513 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
517 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
514 }
518 }
515 @project = Project.find(1)
519 @project = Project.find(1)
516 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
520 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
517 end
521 end
518
522
519 def test_text_formatting
523 def test_text_formatting
520 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
524 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
521 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
525 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
522 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
526 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
523 '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>',
527 '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>',
524 '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',
528 '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',
525 }
529 }
526 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
530 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
527 end
531 end
528
532
529 def test_wiki_horizontal_rule
533 def test_wiki_horizontal_rule
530 assert_equal '<hr />', textilizable('---')
534 assert_equal '<hr />', textilizable('---')
531 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
535 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
532 end
536 end
533
537
534 def test_footnotes
538 def test_footnotes
535 raw = <<-RAW
539 raw = <<-RAW
536 This is some text[1].
540 This is some text[1].
537
541
538 fn1. This is the foot note
542 fn1. This is the foot note
539 RAW
543 RAW
540
544
541 expected = <<-EXPECTED
545 expected = <<-EXPECTED
542 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
546 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
543 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
547 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
544 EXPECTED
548 EXPECTED
545
549
546 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
550 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
547 end
551 end
548
552
549 def test_headings
553 def test_headings
550 raw = 'h1. Some heading'
554 raw = 'h1. Some heading'
551 expected = %|<a name="Some-heading"></a>\n<h1 >Some heading<a href="#Some-heading" class="wiki-anchor">&para;</a></h1>|
555 expected = %|<a name="Some-heading"></a>\n<h1 >Some heading<a href="#Some-heading" class="wiki-anchor">&para;</a></h1>|
552
556
553 assert_equal expected, textilizable(raw)
557 assert_equal expected, textilizable(raw)
554 end
558 end
555
559
556 def test_headings_with_special_chars
560 def test_headings_with_special_chars
557 # This test makes sure that the generated anchor names match the expected
561 # This test makes sure that the generated anchor names match the expected
558 # ones even if the heading text contains unconventional characters
562 # ones even if the heading text contains unconventional characters
559 raw = 'h1. Some heading related to version 0.5'
563 raw = 'h1. Some heading related to version 0.5'
560 anchor = sanitize_anchor_name("Some-heading-related-to-version-0.5")
564 anchor = sanitize_anchor_name("Some-heading-related-to-version-0.5")
561 expected = %|<a name="#{anchor}"></a>\n<h1 >Some heading related to version 0.5<a href="##{anchor}" class="wiki-anchor">&para;</a></h1>|
565 expected = %|<a name="#{anchor}"></a>\n<h1 >Some heading related to version 0.5<a href="##{anchor}" class="wiki-anchor">&para;</a></h1>|
562
566
563 assert_equal expected, textilizable(raw)
567 assert_equal expected, textilizable(raw)
564 end
568 end
565
569
566 def test_wiki_links_within_wiki_page_context
570 def test_wiki_links_within_wiki_page_context
567
571
568 page = WikiPage.find_by_title('Another_page' )
572 page = WikiPage.find_by_title('Another_page' )
569
573
570 to_test = {
574 to_test = {
571 # link to another page
575 # link to another page
572 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
576 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
573 '[[CookBook documentation|documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">documentation</a>',
577 '[[CookBook documentation|documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">documentation</a>',
574 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
578 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
575 '[[CookBook documentation#One-section|documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">documentation</a>',
579 '[[CookBook documentation#One-section|documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">documentation</a>',
576 # link to the current page
580 # link to the current page
577 '[[Another page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Another page</a>',
581 '[[Another page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Another page</a>',
578 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
582 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
579 '[[Another page#anchor]]' => '<a href="#anchor" class="wiki-page">Another page</a>',
583 '[[Another page#anchor]]' => '<a href="#anchor" class="wiki-page">Another page</a>',
580 '[[Another page#anchor|Page]]' => '<a href="#anchor" class="wiki-page">Page</a>',
584 '[[Another page#anchor|Page]]' => '<a href="#anchor" class="wiki-page">Page</a>',
581 # page that doesn't exist
585 # page that doesn't exist
582 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
586 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
583 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
587 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
584 '[[Unknown page#anchor]]' => '<a href="/projects/ecookbook/wiki/Unknown_page#anchor" class="wiki-page new">Unknown page</a>',
588 '[[Unknown page#anchor]]' => '<a href="/projects/ecookbook/wiki/Unknown_page#anchor" class="wiki-page new">Unknown page</a>',
585 '[[Unknown page#anchor|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page#anchor" class="wiki-page new">404</a>',
589 '[[Unknown page#anchor|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page#anchor" class="wiki-page new">404</a>',
586 }
590 }
587
591
588 @project = Project.find(1)
592 @project = Project.find(1)
589
593
590 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(WikiContent.generate!( :text => text, :page => page ), :text) }
594 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(WikiContent.generate!( :text => text, :page => page ), :text) }
591 end
595 end
592
596
593 def test_wiki_links_anchor_option_should_prepend_page_title_to_href
597 def test_wiki_links_anchor_option_should_prepend_page_title_to_href
594
598
595 to_test = {
599 to_test = {
596 # link to a page
600 # link to a page
597 '[[CookBook documentation]]' => '<a href="#CookBook_documentation" class="wiki-page">CookBook documentation</a>',
601 '[[CookBook documentation]]' => '<a href="#CookBook_documentation" class="wiki-page">CookBook documentation</a>',
598 '[[CookBook documentation|documentation]]' => '<a href="#CookBook_documentation" class="wiki-page">documentation</a>',
602 '[[CookBook documentation|documentation]]' => '<a href="#CookBook_documentation" class="wiki-page">documentation</a>',
599 '[[CookBook documentation#One-section]]' => '<a href="#CookBook_documentation_One-section" class="wiki-page">CookBook documentation</a>',
603 '[[CookBook documentation#One-section]]' => '<a href="#CookBook_documentation_One-section" class="wiki-page">CookBook documentation</a>',
600 '[[CookBook documentation#One-section|documentation]]' => '<a href="#CookBook_documentation_One-section" class="wiki-page">documentation</a>',
604 '[[CookBook documentation#One-section|documentation]]' => '<a href="#CookBook_documentation_One-section" class="wiki-page">documentation</a>',
601 # page that doesn't exist
605 # page that doesn't exist
602 '[[Unknown page]]' => '<a href="#Unknown_page" class="wiki-page new">Unknown page</a>',
606 '[[Unknown page]]' => '<a href="#Unknown_page" class="wiki-page new">Unknown page</a>',
603 '[[Unknown page|404]]' => '<a href="#Unknown_page" class="wiki-page new">404</a>',
607 '[[Unknown page|404]]' => '<a href="#Unknown_page" class="wiki-page new">404</a>',
604 '[[Unknown page#anchor]]' => '<a href="#Unknown_page_anchor" class="wiki-page new">Unknown page</a>',
608 '[[Unknown page#anchor]]' => '<a href="#Unknown_page_anchor" class="wiki-page new">Unknown page</a>',
605 '[[Unknown page#anchor|404]]' => '<a href="#Unknown_page_anchor" class="wiki-page new">404</a>',
609 '[[Unknown page#anchor|404]]' => '<a href="#Unknown_page_anchor" class="wiki-page new">404</a>',
606 }
610 }
607
611
608 @project = Project.find(1)
612 @project = Project.find(1)
609
613
610 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :anchor) }
614 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :anchor) }
611 end
615 end
612
616
613 def test_headings_in_wiki_single_page_export_should_be_prepended_with_page_title
617 def test_headings_in_wiki_single_page_export_should_be_prepended_with_page_title
614 page = WikiPage.generate!( :title => 'Page Title' )
618 page = WikiPage.generate!( :title => 'Page Title' )
615 content = WikiContent.generate!( :text => 'h1. Some heading', :page => page )
619 content = WikiContent.generate!( :text => 'h1. Some heading', :page => page )
616
620
617 expected = %|<a name="Page_Title_Some-heading"></a>\n<h1 >Some heading<a href="#Page_Title_Some-heading" class="wiki-anchor">&para;</a></h1>|
621 expected = %|<a name="Page_Title_Some-heading"></a>\n<h1 >Some heading<a href="#Page_Title_Some-heading" class="wiki-anchor">&para;</a></h1>|
618
622
619 assert_equal expected, textilizable(content, :text, :wiki_links => :anchor )
623 assert_equal expected, textilizable(content, :text, :wiki_links => :anchor )
620 end
624 end
621
625
622 def test_table_of_content
626 def test_table_of_content
623 raw = <<-RAW
627 raw = <<-RAW
624 {{toc}}
628 {{toc}}
625
629
626 h1. Title
630 h1. Title
627
631
628 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
632 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
629
633
630 h2. Subtitle with a [[Wiki]] link
634 h2. Subtitle with a [[Wiki]] link
631
635
632 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
636 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
633
637
634 h2. Subtitle with [[Wiki|another Wiki]] link
638 h2. Subtitle with [[Wiki|another Wiki]] link
635
639
636 h2. Subtitle with %{color:red}red text%
640 h2. Subtitle with %{color:red}red text%
637
641
638 <pre>
642 <pre>
639 some code
643 some code
640 </pre>
644 </pre>
641
645
642 h3. Subtitle with *some* _modifiers_
646 h3. Subtitle with *some* _modifiers_
643
647
644 h1. Another title
648 h1. Another title
645
649
646 h3. An "Internet link":http://www.redmine.org/ inside subtitle
650 h3. An "Internet link":http://www.redmine.org/ inside subtitle
647
651
648 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
652 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
649
653
650 RAW
654 RAW
651
655
652 expected = '<ul class="toc">' +
656 expected = '<ul class="toc">' +
653 '<li><a href="#Title">Title</a>' +
657 '<li><a href="#Title">Title</a>' +
654 '<ul>' +
658 '<ul>' +
655 '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
659 '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
656 '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
660 '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
657 '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
661 '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
658 '<ul>' +
662 '<ul>' +
659 '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
663 '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
660 '</ul>' +
664 '</ul>' +
661 '</li>' +
665 '</li>' +
662 '</ul>' +
666 '</ul>' +
663 '</li>' +
667 '</li>' +
664 '<li><a href="#Another-title">Another title</a>' +
668 '<li><a href="#Another-title">Another title</a>' +
665 '<ul>' +
669 '<ul>' +
666 '<li>' +
670 '<li>' +
667 '<ul>' +
671 '<ul>' +
668 '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
672 '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
669 '</ul>' +
673 '</ul>' +
670 '</li>' +
674 '</li>' +
671 '<li><a href="#Project-Name">Project Name</a></li>' +
675 '<li><a href="#Project-Name">Project Name</a></li>' +
672 '</ul>' +
676 '</ul>' +
673 '</li>' +
677 '</li>' +
674 '</ul>'
678 '</ul>'
675
679
676 @project = Project.find(1)
680 @project = Project.find(1)
677 assert textilizable(raw).gsub("\n", "").include?(expected), textilizable(raw)
681 assert textilizable(raw).gsub("\n", "").include?(expected), textilizable(raw)
678 end
682 end
679
683
680 def test_table_of_content_should_contain_included_page_headings
684 def test_table_of_content_should_contain_included_page_headings
681 raw = <<-RAW
685 raw = <<-RAW
682 {{toc}}
686 {{toc}}
683
687
684 h1. Included
688 h1. Included
685
689
686 {{include(Child_1)}}
690 {{include(Child_1)}}
687 RAW
691 RAW
688
692
689 expected = '<ul class="toc">' +
693 expected = '<ul class="toc">' +
690 '<li><a href="#Included">Included</a></li>' +
694 '<li><a href="#Included">Included</a></li>' +
691 '<li><a href="#Child-page-1">Child page 1</a></li>' +
695 '<li><a href="#Child-page-1">Child page 1</a></li>' +
692 '</ul>'
696 '</ul>'
693
697
694 @project = Project.find(1)
698 @project = Project.find(1)
695 assert textilizable(raw).gsub("\n", "").include?(expected)
699 assert textilizable(raw).gsub("\n", "").include?(expected)
696 end
700 end
697
701
698 def test_default_formatter
702 def test_default_formatter
699 Setting.text_formatting = 'unknown'
703 Setting.text_formatting = 'unknown'
700 text = 'a *link*: http://www.example.net/'
704 text = 'a *link*: http://www.example.net/'
701 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
705 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
702 Setting.text_formatting = 'textile'
706 Setting.text_formatting = 'textile'
703 end
707 end
704
708
705 def test_due_date_distance_in_words
709 def test_due_date_distance_in_words
706 to_test = { Date.today => 'Due in 0 days',
710 to_test = { Date.today => 'Due in 0 days',
707 Date.today + 1 => 'Due in 1 day',
711 Date.today + 1 => 'Due in 1 day',
708 Date.today + 100 => 'Due in about 3 months',
712 Date.today + 100 => 'Due in about 3 months',
709 Date.today + 20000 => 'Due in over 54 years',
713 Date.today + 20000 => 'Due in over 54 years',
710 Date.today - 1 => '1 day late',
714 Date.today - 1 => '1 day late',
711 Date.today - 100 => 'about 3 months late',
715 Date.today - 100 => 'about 3 months late',
712 Date.today - 20000 => 'over 54 years late',
716 Date.today - 20000 => 'over 54 years late',
713 }
717 }
714 ::I18n.locale = :en
718 ::I18n.locale = :en
715 to_test.each do |date, expected|
719 to_test.each do |date, expected|
716 assert_equal expected, due_date_distance_in_words(date)
720 assert_equal expected, due_date_distance_in_words(date)
717 end
721 end
718 end
722 end
719
723
720 def test_avatar
724 def test_avatar
721 # turn on avatars
725 # turn on avatars
722 Setting.gravatar_enabled = '1'
726 Setting.gravatar_enabled = '1'
723 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
727 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
724 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
728 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
725 assert_nil avatar('jsmith')
729 assert_nil avatar('jsmith')
726 assert_nil avatar(nil)
730 assert_nil avatar(nil)
727
731
728 # turn off avatars
732 # turn off avatars
729 Setting.gravatar_enabled = '0'
733 Setting.gravatar_enabled = '0'
730 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
734 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
731 end
735 end
732
736
733 def test_link_to_user
737 def test_link_to_user
734 user = User.find(2)
738 user = User.find(2)
735 t = link_to_user(user)
739 t = link_to_user(user)
736 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
740 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
737 end
741 end
738
742
739 def test_link_to_user_should_not_link_to_locked_user
743 def test_link_to_user_should_not_link_to_locked_user
740 user = User.find(5)
744 user = User.find(5)
741 assert user.locked?
745 assert user.locked?
742 t = link_to_user(user)
746 t = link_to_user(user)
743 assert_equal user.name, t
747 assert_equal user.name, t
744 end
748 end
745
749
746 def test_link_to_user_should_not_link_to_anonymous
750 def test_link_to_user_should_not_link_to_anonymous
747 user = User.anonymous
751 user = User.anonymous
748 assert user.anonymous?
752 assert user.anonymous?
749 t = link_to_user(user)
753 t = link_to_user(user)
750 assert_equal ::I18n.t(:label_user_anonymous), t
754 assert_equal ::I18n.t(:label_user_anonymous), t
751 end
755 end
752
756
753 def test_link_to_project
757 def test_link_to_project
754 project = Project.find(1)
758 project = Project.find(1)
755 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
759 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
756 link_to_project(project)
760 link_to_project(project)
757 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
761 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
758 link_to_project(project, :action => 'settings')
762 link_to_project(project, :action => 'settings')
759 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
763 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
760 link_to_project(project, {:only_path => false, :jump => 'blah'})
764 link_to_project(project, {:only_path => false, :jump => 'blah'})
761 assert_equal %(<a href="/projects/ecookbook/settings" class="project">eCookbook</a>),
765 assert_equal %(<a href="/projects/ecookbook/settings" class="project">eCookbook</a>),
762 link_to_project(project, {:action => 'settings'}, :class => "project")
766 link_to_project(project, {:action => 'settings'}, :class => "project")
763 end
767 end
764
768
765 def test_principals_options_for_select_with_users
769 def test_principals_options_for_select_with_users
766 users = [User.find(2), User.find(4)]
770 users = [User.find(2), User.find(4)]
767 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
771 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
768 principals_options_for_select(users)
772 principals_options_for_select(users)
769 end
773 end
770
774
771 def test_principals_options_for_select_with_selected
775 def test_principals_options_for_select_with_selected
772 users = [User.find(2), User.find(4)]
776 users = [User.find(2), User.find(4)]
773 assert_equal %(<option value="2">John Smith</option><option value="4" selected="selected">Robert Hill</option>),
777 assert_equal %(<option value="2">John Smith</option><option value="4" selected="selected">Robert Hill</option>),
774 principals_options_for_select(users, User.find(4))
778 principals_options_for_select(users, User.find(4))
775 end
779 end
776
780
777 def test_principals_options_for_select_with_users_and_groups
781 def test_principals_options_for_select_with_users_and_groups
778 users = [User.find(2), Group.find(11), User.find(4), Group.find(10)]
782 users = [User.find(2), Group.find(11), User.find(4), Group.find(10)]
779 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>) +
783 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>) +
780 %(<optgroup label="Groups"><option value="10">A Team</option><option value="11">B Team</option></optgroup>),
784 %(<optgroup label="Groups"><option value="10">A Team</option><option value="11">B Team</option></optgroup>),
781 principals_options_for_select(users)
785 principals_options_for_select(users)
782 end
786 end
783
787
784 def test_principals_options_for_select_with_empty_collection
788 def test_principals_options_for_select_with_empty_collection
785 assert_equal '', principals_options_for_select([])
789 assert_equal '', principals_options_for_select([])
786 end
790 end
787 end
791 end
General Comments 0
You need to be logged in to leave comments. Login now