##// END OF EJS Templates
Make sure that anchor names generated for headings fully match wiki links (#7215)....
Etienne Massip -
r7443:65b533a8ec1f
parent child
Show More
@@ -1,972 +1,977
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
203 content
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)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
391 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
392 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
392 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
393 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
393 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
394 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
394 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
395 end
395 end
396
396
397 def breadcrumb(*args)
397 def breadcrumb(*args)
398 elements = args.flatten
398 elements = args.flatten
399 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
399 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
400 end
400 end
401
401
402 def other_formats_links(&block)
402 def other_formats_links(&block)
403 concat('<p class="other-formats">' + l(:label_export_to))
403 concat('<p class="other-formats">' + l(:label_export_to))
404 yield Redmine::Views::OtherFormatsBuilder.new(self)
404 yield Redmine::Views::OtherFormatsBuilder.new(self)
405 concat('</p>')
405 concat('</p>')
406 end
406 end
407
407
408 def page_header_title
408 def page_header_title
409 if @project.nil? || @project.new_record?
409 if @project.nil? || @project.new_record?
410 h(Setting.app_title)
410 h(Setting.app_title)
411 else
411 else
412 b = []
412 b = []
413 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
413 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
414 if ancestors.any?
414 if ancestors.any?
415 root = ancestors.shift
415 root = ancestors.shift
416 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
416 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
417 if ancestors.size > 2
417 if ancestors.size > 2
418 b << '&#8230;'
418 b << '&#8230;'
419 ancestors = ancestors[-2, 2]
419 ancestors = ancestors[-2, 2]
420 end
420 end
421 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
421 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
422 end
422 end
423 b << h(@project)
423 b << h(@project)
424 b.join(' &#187; ')
424 b.join(' &#187; ')
425 end
425 end
426 end
426 end
427
427
428 def html_title(*args)
428 def html_title(*args)
429 if args.empty?
429 if args.empty?
430 title = []
430 title = []
431 title << @project.name if @project
431 title << @project.name if @project
432 title += @html_title if @html_title
432 title += @html_title if @html_title
433 title << Setting.app_title
433 title << Setting.app_title
434 title.select {|t| !t.blank? }.join(' - ')
434 title.select {|t| !t.blank? }.join(' - ')
435 else
435 else
436 @html_title ||= []
436 @html_title ||= []
437 @html_title += args
437 @html_title += args
438 end
438 end
439 end
439 end
440
440
441 # Returns the theme, controller name, and action as css classes for the
441 # Returns the theme, controller name, and action as css classes for the
442 # HTML body.
442 # HTML body.
443 def body_css_classes
443 def body_css_classes
444 css = []
444 css = []
445 if theme = Redmine::Themes.theme(Setting.ui_theme)
445 if theme = Redmine::Themes.theme(Setting.ui_theme)
446 css << 'theme-' + theme.name
446 css << 'theme-' + theme.name
447 end
447 end
448
448
449 css << 'controller-' + params[:controller]
449 css << 'controller-' + params[:controller]
450 css << 'action-' + params[:action]
450 css << 'action-' + params[:action]
451 css.join(' ')
451 css.join(' ')
452 end
452 end
453
453
454 def accesskey(s)
454 def accesskey(s)
455 Redmine::AccessKeys.key_for s
455 Redmine::AccessKeys.key_for s
456 end
456 end
457
457
458 # Formats text according to system settings.
458 # Formats text according to system settings.
459 # 2 ways to call this method:
459 # 2 ways to call this method:
460 # * with a String: textilizable(text, options)
460 # * with a String: textilizable(text, options)
461 # * with an object and one of its attribute: textilizable(issue, :description, options)
461 # * with an object and one of its attribute: textilizable(issue, :description, options)
462 def textilizable(*args)
462 def textilizable(*args)
463 options = args.last.is_a?(Hash) ? args.pop : {}
463 options = args.last.is_a?(Hash) ? args.pop : {}
464 case args.size
464 case args.size
465 when 1
465 when 1
466 obj = options[:object]
466 obj = options[:object]
467 text = args.shift
467 text = args.shift
468 when 2
468 when 2
469 obj = args.shift
469 obj = args.shift
470 attr = args.shift
470 attr = args.shift
471 text = obj.send(attr).to_s
471 text = obj.send(attr).to_s
472 else
472 else
473 raise ArgumentError, 'invalid arguments to textilizable'
473 raise ArgumentError, 'invalid arguments to textilizable'
474 end
474 end
475 return '' if text.blank?
475 return '' if text.blank?
476 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
476 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
477 only_path = options.delete(:only_path) == false ? false : true
477 only_path = options.delete(:only_path) == false ? false : true
478
478
479 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
479 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
480
480
481 @parsed_headings = []
481 @parsed_headings = []
482 text = parse_non_pre_blocks(text) do |text|
482 text = parse_non_pre_blocks(text) do |text|
483 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings].each do |method_name|
483 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings].each do |method_name|
484 send method_name, text, project, obj, attr, only_path, options
484 send method_name, text, project, obj, attr, only_path, options
485 end
485 end
486 end
486 end
487
487
488 if @parsed_headings.any?
488 if @parsed_headings.any?
489 replace_toc(text, @parsed_headings)
489 replace_toc(text, @parsed_headings)
490 end
490 end
491
491
492 text
492 text
493 end
493 end
494
494
495 def parse_non_pre_blocks(text)
495 def parse_non_pre_blocks(text)
496 s = StringScanner.new(text)
496 s = StringScanner.new(text)
497 tags = []
497 tags = []
498 parsed = ''
498 parsed = ''
499 while !s.eos?
499 while !s.eos?
500 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
500 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
501 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
501 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
502 if tags.empty?
502 if tags.empty?
503 yield text
503 yield text
504 end
504 end
505 parsed << text
505 parsed << text
506 if tag
506 if tag
507 if closing
507 if closing
508 if tags.last == tag.downcase
508 if tags.last == tag.downcase
509 tags.pop
509 tags.pop
510 end
510 end
511 else
511 else
512 tags << tag.downcase
512 tags << tag.downcase
513 end
513 end
514 parsed << full_tag
514 parsed << full_tag
515 end
515 end
516 end
516 end
517 # Close any non closing tags
517 # Close any non closing tags
518 while tag = tags.pop
518 while tag = tags.pop
519 parsed << "</#{tag}>"
519 parsed << "</#{tag}>"
520 end
520 end
521 parsed.html_safe
521 parsed.html_safe
522 end
522 end
523
523
524 def parse_inline_attachments(text, project, obj, attr, only_path, options)
524 def parse_inline_attachments(text, project, obj, attr, only_path, options)
525 # when using an image link, try to use an attachment, if possible
525 # when using an image link, try to use an attachment, if possible
526 if options[:attachments] || (obj && obj.respond_to?(:attachments))
526 if options[:attachments] || (obj && obj.respond_to?(:attachments))
527 attachments = nil
527 attachments = nil
528 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
528 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
529 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
529 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
530 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
530 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
531 # search for the picture in attachments
531 # search for the picture in attachments
532 if found = attachments.detect { |att| att.filename.downcase == filename }
532 if found = attachments.detect { |att| att.filename.downcase == filename }
533 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
533 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
534 desc = found.description.to_s.gsub('"', '')
534 desc = found.description.to_s.gsub('"', '')
535 if !desc.blank? && alttext.blank?
535 if !desc.blank? && alttext.blank?
536 alt = " title=\"#{desc}\" alt=\"#{desc}\""
536 alt = " title=\"#{desc}\" alt=\"#{desc}\""
537 end
537 end
538 "src=\"#{image_url}\"#{alt}".html_safe
538 "src=\"#{image_url}\"#{alt}".html_safe
539 else
539 else
540 m.html_safe
540 m.html_safe
541 end
541 end
542 end
542 end
543 end
543 end
544 end
544 end
545
545
546 # Wiki links
546 # Wiki links
547 #
547 #
548 # Examples:
548 # Examples:
549 # [[mypage]]
549 # [[mypage]]
550 # [[mypage|mytext]]
550 # [[mypage|mytext]]
551 # wiki links can refer other project wikis, using project name or identifier:
551 # wiki links can refer other project wikis, using project name or identifier:
552 # [[project:]] -> wiki starting page
552 # [[project:]] -> wiki starting page
553 # [[project:|mytext]]
553 # [[project:|mytext]]
554 # [[project:mypage]]
554 # [[project:mypage]]
555 # [[project:mypage|mytext]]
555 # [[project:mypage|mytext]]
556 def parse_wiki_links(text, project, obj, attr, only_path, options)
556 def parse_wiki_links(text, project, obj, attr, only_path, options)
557 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
557 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
558 link_project = project
558 link_project = project
559 esc, all, page, title = $1, $2, $3, $5
559 esc, all, page, title = $1, $2, $3, $5
560 if esc.nil?
560 if esc.nil?
561 if page =~ /^([^\:]+)\:(.*)$/
561 if page =~ /^([^\:]+)\:(.*)$/
562 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
562 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
563 page = $2
563 page = $2
564 title ||= $1 if page.blank?
564 title ||= $1 if page.blank?
565 end
565 end
566
566
567 if link_project && link_project.wiki
567 if link_project && link_project.wiki
568 # extract anchor
568 # extract anchor
569 anchor = nil
569 anchor = nil
570 if page =~ /^(.+?)\#(.+)$/
570 if page =~ /^(.+?)\#(.+)$/
571 page, anchor = $1, $2
571 page, anchor = $1, $2
572 end
572 end
573 anchor = sanitize_anchor_name(anchor) if anchor.present?
573 # check if page exists
574 # check if page exists
574 wiki_page = link_project.wiki.find_page(page)
575 wiki_page = link_project.wiki.find_page(page)
575 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
576 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
576 "##{anchor}"
577 "##{anchor}"
577 else
578 else
578 case options[:wiki_links]
579 case options[:wiki_links]
579 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
580 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
580 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
581 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
581 else
582 else
582 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
583 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
583 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
584 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
584 end
585 end
585 end
586 end
586 link_to(h(title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
587 link_to(h(title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
587 else
588 else
588 # project or wiki doesn't exist
589 # project or wiki doesn't exist
589 all.html_safe
590 all.html_safe
590 end
591 end
591 else
592 else
592 all.html_safe
593 all.html_safe
593 end
594 end
594 end
595 end
595 end
596 end
596
597
597 # Redmine links
598 # Redmine links
598 #
599 #
599 # Examples:
600 # Examples:
600 # Issues:
601 # Issues:
601 # #52 -> Link to issue #52
602 # #52 -> Link to issue #52
602 # Changesets:
603 # Changesets:
603 # r52 -> Link to revision 52
604 # r52 -> Link to revision 52
604 # commit:a85130f -> Link to scmid starting with a85130f
605 # commit:a85130f -> Link to scmid starting with a85130f
605 # Documents:
606 # Documents:
606 # document#17 -> Link to document with id 17
607 # document#17 -> Link to document with id 17
607 # document:Greetings -> Link to the document with title "Greetings"
608 # document:Greetings -> Link to the document with title "Greetings"
608 # document:"Some document" -> Link to the document with title "Some document"
609 # document:"Some document" -> Link to the document with title "Some document"
609 # Versions:
610 # Versions:
610 # version#3 -> Link to version with id 3
611 # version#3 -> Link to version with id 3
611 # version:1.0.0 -> Link to version named "1.0.0"
612 # version:1.0.0 -> Link to version named "1.0.0"
612 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
613 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
613 # Attachments:
614 # Attachments:
614 # attachment:file.zip -> Link to the attachment of the current object named file.zip
615 # attachment:file.zip -> Link to the attachment of the current object named file.zip
615 # Source files:
616 # Source files:
616 # source:some/file -> Link to the file located at /some/file in the project's repository
617 # source:some/file -> Link to the file located at /some/file in the project's repository
617 # source:some/file@52 -> Link to the file's revision 52
618 # source:some/file@52 -> Link to the file's revision 52
618 # source:some/file#L120 -> Link to line 120 of the file
619 # source:some/file#L120 -> Link to line 120 of the file
619 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
620 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
620 # export:some/file -> Force the download of the file
621 # export:some/file -> Force the download of the file
621 # Forum messages:
622 # Forum messages:
622 # message#1218 -> Link to message with id 1218
623 # message#1218 -> Link to message with id 1218
623 #
624 #
624 # Links can refer other objects from other projects, using project identifier:
625 # Links can refer other objects from other projects, using project identifier:
625 # identifier:r52
626 # identifier:r52
626 # identifier:document:"Some document"
627 # identifier:document:"Some document"
627 # identifier:version:1.0.0
628 # identifier:version:1.0.0
628 # identifier:source:some/file
629 # identifier:source:some/file
629 def parse_redmine_links(text, project, obj, attr, only_path, options)
630 def parse_redmine_links(text, project, obj, attr, only_path, options)
630 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
631 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
631 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
632 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
632 link = nil
633 link = nil
633 if project_identifier
634 if project_identifier
634 project = Project.visible.find_by_identifier(project_identifier)
635 project = Project.visible.find_by_identifier(project_identifier)
635 end
636 end
636 if esc.nil?
637 if esc.nil?
637 if prefix.nil? && sep == 'r'
638 if prefix.nil? && sep == 'r'
638 # project.changesets.visible raises an SQL error because of a double join on repositories
639 # project.changesets.visible raises an SQL error because of a double join on repositories
639 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
640 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
640 link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
641 link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
641 :class => 'changeset',
642 :class => 'changeset',
642 :title => truncate_single_line(changeset.comments, :length => 100))
643 :title => truncate_single_line(changeset.comments, :length => 100))
643 end
644 end
644 elsif sep == '#'
645 elsif sep == '#'
645 oid = identifier.to_i
646 oid = identifier.to_i
646 case prefix
647 case prefix
647 when nil
648 when nil
648 if issue = Issue.visible.find_by_id(oid, :include => :status)
649 if issue = Issue.visible.find_by_id(oid, :include => :status)
649 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
650 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
650 :class => issue.css_classes,
651 :class => issue.css_classes,
651 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
652 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
652 end
653 end
653 when 'document'
654 when 'document'
654 if document = Document.visible.find_by_id(oid)
655 if document = Document.visible.find_by_id(oid)
655 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
656 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
656 :class => 'document'
657 :class => 'document'
657 end
658 end
658 when 'version'
659 when 'version'
659 if version = Version.visible.find_by_id(oid)
660 if version = Version.visible.find_by_id(oid)
660 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
661 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
661 :class => 'version'
662 :class => 'version'
662 end
663 end
663 when 'message'
664 when 'message'
664 if message = Message.visible.find_by_id(oid, :include => :parent)
665 if message = Message.visible.find_by_id(oid, :include => :parent)
665 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
666 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
666 end
667 end
667 when 'project'
668 when 'project'
668 if p = Project.visible.find_by_id(oid)
669 if p = Project.visible.find_by_id(oid)
669 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
670 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
670 end
671 end
671 end
672 end
672 elsif sep == ':'
673 elsif sep == ':'
673 # removes the double quotes if any
674 # removes the double quotes if any
674 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
675 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
675 case prefix
676 case prefix
676 when 'document'
677 when 'document'
677 if project && document = project.documents.visible.find_by_title(name)
678 if project && document = project.documents.visible.find_by_title(name)
678 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
679 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
679 :class => 'document'
680 :class => 'document'
680 end
681 end
681 when 'version'
682 when 'version'
682 if project && version = project.versions.visible.find_by_name(name)
683 if project && version = project.versions.visible.find_by_name(name)
683 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
684 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
684 :class => 'version'
685 :class => 'version'
685 end
686 end
686 when 'commit'
687 when 'commit'
687 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
688 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
688 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
689 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
689 :class => 'changeset',
690 :class => 'changeset',
690 :title => truncate_single_line(h(changeset.comments), :length => 100)
691 :title => truncate_single_line(h(changeset.comments), :length => 100)
691 end
692 end
692 when 'source', 'export'
693 when 'source', 'export'
693 if project && project.repository && User.current.allowed_to?(:browse_repository, project)
694 if project && project.repository && User.current.allowed_to?(:browse_repository, project)
694 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
695 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
695 path, rev, anchor = $1, $3, $5
696 path, rev, anchor = $1, $3, $5
696 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
697 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
697 :path => to_path_param(path),
698 :path => to_path_param(path),
698 :rev => rev,
699 :rev => rev,
699 :anchor => anchor,
700 :anchor => anchor,
700 :format => (prefix == 'export' ? 'raw' : nil)},
701 :format => (prefix == 'export' ? 'raw' : nil)},
701 :class => (prefix == 'export' ? 'source download' : 'source')
702 :class => (prefix == 'export' ? 'source download' : 'source')
702 end
703 end
703 when 'attachment'
704 when 'attachment'
704 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
705 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
705 if attachments && attachment = attachments.detect {|a| a.filename == name }
706 if attachments && attachment = attachments.detect {|a| a.filename == name }
706 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
707 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
707 :class => 'attachment'
708 :class => 'attachment'
708 end
709 end
709 when 'project'
710 when 'project'
710 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
711 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
711 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
712 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
712 end
713 end
713 end
714 end
714 end
715 end
715 end
716 end
716 (leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")).html_safe
717 (leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")).html_safe
717 end
718 end
718 end
719 end
719
720
720 HEADING_RE = /<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>/i unless const_defined?(:HEADING_RE)
721 HEADING_RE = /<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>/i unless const_defined?(:HEADING_RE)
721
722
722 # Headings and TOC
723 # Headings and TOC
723 # Adds ids and links to headings unless options[:headings] is set to false
724 # Adds ids and links to headings unless options[:headings] is set to false
724 def parse_headings(text, project, obj, attr, only_path, options)
725 def parse_headings(text, project, obj, attr, only_path, options)
725 return if options[:headings] == false
726 return if options[:headings] == false
726
727
727 text.gsub!(HEADING_RE) do
728 text.gsub!(HEADING_RE) do
728 level, attrs, content = $1.to_i, $2, $3
729 level, attrs, content = $1.to_i, $2, $3
729 item = strip_tags(content).strip
730 item = strip_tags(content).strip
730 anchor = item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
731 anchor = sanitize_anchor_name(item)
731 # used for single-file wiki export
732 # used for single-file wiki export
732 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
733 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
733 @parsed_headings << [level, anchor, item]
734 @parsed_headings << [level, anchor, item]
734 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
735 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
735 end
736 end
736 end
737 end
737
738
738 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
739 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
739
740
740 # Renders the TOC with given headings
741 # Renders the TOC with given headings
741 def replace_toc(text, headings)
742 def replace_toc(text, headings)
742 text.gsub!(TOC_RE) do
743 text.gsub!(TOC_RE) do
743 if headings.empty?
744 if headings.empty?
744 ''
745 ''
745 else
746 else
746 div_class = 'toc'
747 div_class = 'toc'
747 div_class << ' right' if $1 == '>'
748 div_class << ' right' if $1 == '>'
748 div_class << ' left' if $1 == '<'
749 div_class << ' left' if $1 == '<'
749 out = "<ul class=\"#{div_class}\"><li>"
750 out = "<ul class=\"#{div_class}\"><li>"
750 root = headings.map(&:first).min
751 root = headings.map(&:first).min
751 current = root
752 current = root
752 started = false
753 started = false
753 headings.each do |level, anchor, item|
754 headings.each do |level, anchor, item|
754 if level > current
755 if level > current
755 out << '<ul><li>' * (level - current)
756 out << '<ul><li>' * (level - current)
756 elsif level < current
757 elsif level < current
757 out << "</li></ul>\n" * (current - level) + "</li><li>"
758 out << "</li></ul>\n" * (current - level) + "</li><li>"
758 elsif started
759 elsif started
759 out << '</li><li>'
760 out << '</li><li>'
760 end
761 end
761 out << "<a href=\"##{anchor}\">#{item}</a>"
762 out << "<a href=\"##{anchor}\">#{item}</a>"
762 current = level
763 current = level
763 started = true
764 started = true
764 end
765 end
765 out << '</li></ul>' * (current - root)
766 out << '</li></ul>' * (current - root)
766 out << '</li></ul>'
767 out << '</li></ul>'
767 end
768 end
768 end
769 end
769 end
770 end
770
771
771 # Same as Rails' simple_format helper without using paragraphs
772 # Same as Rails' simple_format helper without using paragraphs
772 def simple_format_without_paragraph(text)
773 def simple_format_without_paragraph(text)
773 text.to_s.
774 text.to_s.
774 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
775 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
775 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
776 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
776 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
777 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
777 end
778 end
778
779
779 def lang_options_for_select(blank=true)
780 def lang_options_for_select(blank=true)
780 (blank ? [["(auto)", ""]] : []) +
781 (blank ? [["(auto)", ""]] : []) +
781 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
782 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
782 end
783 end
783
784
784 def label_tag_for(name, option_tags = nil, options = {})
785 def label_tag_for(name, option_tags = nil, options = {})
785 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
786 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
786 content_tag("label", label_text)
787 content_tag("label", label_text)
787 end
788 end
788
789
789 def labelled_tabular_form_for(name, object, options, &proc)
790 def labelled_tabular_form_for(name, object, options, &proc)
790 options[:html] ||= {}
791 options[:html] ||= {}
791 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
792 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
792 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
793 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
793 end
794 end
794
795
795 def back_url_hidden_field_tag
796 def back_url_hidden_field_tag
796 back_url = params[:back_url] || request.env['HTTP_REFERER']
797 back_url = params[:back_url] || request.env['HTTP_REFERER']
797 back_url = CGI.unescape(back_url.to_s)
798 back_url = CGI.unescape(back_url.to_s)
798 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
799 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
799 end
800 end
800
801
801 def check_all_links(form_name)
802 def check_all_links(form_name)
802 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
803 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
803 " | ".html_safe +
804 " | ".html_safe +
804 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
805 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
805 end
806 end
806
807
807 def progress_bar(pcts, options={})
808 def progress_bar(pcts, options={})
808 pcts = [pcts, pcts] unless pcts.is_a?(Array)
809 pcts = [pcts, pcts] unless pcts.is_a?(Array)
809 pcts = pcts.collect(&:round)
810 pcts = pcts.collect(&:round)
810 pcts[1] = pcts[1] - pcts[0]
811 pcts[1] = pcts[1] - pcts[0]
811 pcts << (100 - pcts[1] - pcts[0])
812 pcts << (100 - pcts[1] - pcts[0])
812 width = options[:width] || '100px;'
813 width = options[:width] || '100px;'
813 legend = options[:legend] || ''
814 legend = options[:legend] || ''
814 content_tag('table',
815 content_tag('table',
815 content_tag('tr',
816 content_tag('tr',
816 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
817 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
817 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
818 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
818 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
819 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
819 ), :class => 'progress', :style => "width: #{width};").html_safe +
820 ), :class => 'progress', :style => "width: #{width};").html_safe +
820 content_tag('p', legend, :class => 'pourcent').html_safe
821 content_tag('p', legend, :class => 'pourcent').html_safe
821 end
822 end
822
823
823 def checked_image(checked=true)
824 def checked_image(checked=true)
824 if checked
825 if checked
825 image_tag 'toggle_check.png'
826 image_tag 'toggle_check.png'
826 end
827 end
827 end
828 end
828
829
829 def context_menu(url)
830 def context_menu(url)
830 unless @context_menu_included
831 unless @context_menu_included
831 content_for :header_tags do
832 content_for :header_tags do
832 javascript_include_tag('context_menu') +
833 javascript_include_tag('context_menu') +
833 stylesheet_link_tag('context_menu')
834 stylesheet_link_tag('context_menu')
834 end
835 end
835 if l(:direction) == 'rtl'
836 if l(:direction) == 'rtl'
836 content_for :header_tags do
837 content_for :header_tags do
837 stylesheet_link_tag('context_menu_rtl')
838 stylesheet_link_tag('context_menu_rtl')
838 end
839 end
839 end
840 end
840 @context_menu_included = true
841 @context_menu_included = true
841 end
842 end
842 javascript_tag "new ContextMenu('#{ url_for(url) }')"
843 javascript_tag "new ContextMenu('#{ url_for(url) }')"
843 end
844 end
844
845
845 def context_menu_link(name, url, options={})
846 def context_menu_link(name, url, options={})
846 options[:class] ||= ''
847 options[:class] ||= ''
847 if options.delete(:selected)
848 if options.delete(:selected)
848 options[:class] << ' icon-checked disabled'
849 options[:class] << ' icon-checked disabled'
849 options[:disabled] = true
850 options[:disabled] = true
850 end
851 end
851 if options.delete(:disabled)
852 if options.delete(:disabled)
852 options.delete(:method)
853 options.delete(:method)
853 options.delete(:confirm)
854 options.delete(:confirm)
854 options.delete(:onclick)
855 options.delete(:onclick)
855 options[:class] << ' disabled'
856 options[:class] << ' disabled'
856 url = '#'
857 url = '#'
857 end
858 end
858 link_to h(name), url, options
859 link_to h(name), url, options
859 end
860 end
860
861
861 def calendar_for(field_id)
862 def calendar_for(field_id)
862 include_calendar_headers_tags
863 include_calendar_headers_tags
863 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
864 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
864 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
865 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
865 end
866 end
866
867
867 def include_calendar_headers_tags
868 def include_calendar_headers_tags
868 unless @calendar_headers_tags_included
869 unless @calendar_headers_tags_included
869 @calendar_headers_tags_included = true
870 @calendar_headers_tags_included = true
870 content_for :header_tags do
871 content_for :header_tags do
871 start_of_week = case Setting.start_of_week.to_i
872 start_of_week = case Setting.start_of_week.to_i
872 when 1
873 when 1
873 'Calendar._FD = 1;' # Monday
874 'Calendar._FD = 1;' # Monday
874 when 7
875 when 7
875 'Calendar._FD = 0;' # Sunday
876 'Calendar._FD = 0;' # Sunday
876 when 6
877 when 6
877 'Calendar._FD = 6;' # Saturday
878 'Calendar._FD = 6;' # Saturday
878 else
879 else
879 '' # use language
880 '' # use language
880 end
881 end
881
882
882 javascript_include_tag('calendar/calendar') +
883 javascript_include_tag('calendar/calendar') +
883 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
884 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
884 javascript_tag(start_of_week) +
885 javascript_tag(start_of_week) +
885 javascript_include_tag('calendar/calendar-setup') +
886 javascript_include_tag('calendar/calendar-setup') +
886 stylesheet_link_tag('calendar')
887 stylesheet_link_tag('calendar')
887 end
888 end
888 end
889 end
889 end
890 end
890
891
891 def content_for(name, content = nil, &block)
892 def content_for(name, content = nil, &block)
892 @has_content ||= {}
893 @has_content ||= {}
893 @has_content[name] = true
894 @has_content[name] = true
894 super(name, content, &block)
895 super(name, content, &block)
895 end
896 end
896
897
897 def has_content?(name)
898 def has_content?(name)
898 (@has_content && @has_content[name]) || false
899 (@has_content && @has_content[name]) || false
899 end
900 end
900
901
901 def email_delivery_enabled?
902 def email_delivery_enabled?
902 !!ActionMailer::Base.perform_deliveries
903 !!ActionMailer::Base.perform_deliveries
903 end
904 end
904
905
905 # Returns the avatar image tag for the given +user+ if avatars are enabled
906 # Returns the avatar image tag for the given +user+ if avatars are enabled
906 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
907 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
907 def avatar(user, options = { })
908 def avatar(user, options = { })
908 if Setting.gravatar_enabled?
909 if Setting.gravatar_enabled?
909 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
910 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
910 email = nil
911 email = nil
911 if user.respond_to?(:mail)
912 if user.respond_to?(:mail)
912 email = user.mail
913 email = user.mail
913 elsif user.to_s =~ %r{<(.+?)>}
914 elsif user.to_s =~ %r{<(.+?)>}
914 email = $1
915 email = $1
915 end
916 end
916 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
917 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
917 else
918 else
918 ''
919 ''
919 end
920 end
920 end
921 end
921
922
923 def sanitize_anchor_name(anchor)
924 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
925 end
926
922 # Returns the javascript tags that are included in the html layout head
927 # Returns the javascript tags that are included in the html layout head
923 def javascript_heads
928 def javascript_heads
924 tags = javascript_include_tag(:defaults)
929 tags = javascript_include_tag(:defaults)
925 unless User.current.pref.warn_on_leaving_unsaved == '0'
930 unless User.current.pref.warn_on_leaving_unsaved == '0'
926 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
931 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
927 end
932 end
928 tags
933 tags
929 end
934 end
930
935
931 def favicon
936 def favicon
932 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
937 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
933 end
938 end
934
939
935 def robot_exclusion_tag
940 def robot_exclusion_tag
936 '<meta name="robots" content="noindex,follow,noarchive" />'
941 '<meta name="robots" content="noindex,follow,noarchive" />'
937 end
942 end
938
943
939 # Returns true if arg is expected in the API response
944 # Returns true if arg is expected in the API response
940 def include_in_api_response?(arg)
945 def include_in_api_response?(arg)
941 unless @included_in_api_response
946 unless @included_in_api_response
942 param = params[:include]
947 param = params[:include]
943 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
948 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
944 @included_in_api_response.collect!(&:strip)
949 @included_in_api_response.collect!(&:strip)
945 end
950 end
946 @included_in_api_response.include?(arg.to_s)
951 @included_in_api_response.include?(arg.to_s)
947 end
952 end
948
953
949 # Returns options or nil if nometa param or X-Redmine-Nometa header
954 # Returns options or nil if nometa param or X-Redmine-Nometa header
950 # was set in the request
955 # was set in the request
951 def api_meta(options)
956 def api_meta(options)
952 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
957 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
953 # compatibility mode for activeresource clients that raise
958 # compatibility mode for activeresource clients that raise
954 # an error when unserializing an array with attributes
959 # an error when unserializing an array with attributes
955 nil
960 nil
956 else
961 else
957 options
962 options
958 end
963 end
959 end
964 end
960
965
961 private
966 private
962
967
963 def wiki_helper
968 def wiki_helper
964 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
969 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
965 extend helper
970 extend helper
966 return self
971 return self
967 end
972 end
968
973
969 def link_to_content_update(text, url_params = {}, html_options = {})
974 def link_to_content_update(text, url_params = {}, html_options = {})
970 link_to(text, url_params, html_options)
975 link_to(text, url_params, html_options)
971 end
976 end
972 end
977 end
@@ -1,777 +1,787
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 # link with anchor
353 # link with anchor
354 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
354 '[[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>',
355 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
356 # page that doesn't exist
356 # page that doesn't exist
357 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
357 '[[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>',
358 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
359 # link to another project wiki
359 # link to another project wiki
360 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">onlinestore</a>',
360 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">onlinestore</a>',
361 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">Wiki</a>',
361 '[[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>',
362 '[[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>',
363 '[[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>',
364 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
365 # striked through link
365 # striked through link
366 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
366 '-[[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>',
367 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
368 # escaping
368 # escaping
369 '![[Another page|Page]]' => '[[Another page|Page]]',
369 '![[Another page|Page]]' => '[[Another page|Page]]',
370 # project does not exist
370 # project does not exist
371 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
371 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
372 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
372 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
373 }
373 }
374 @project = Project.find(1)
374 @project = Project.find(1)
375 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
375 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
376 end
376 end
377
377
378 def test_wiki_links_within_local_file_generation_context
378 def test_wiki_links_within_local_file_generation_context
379
379
380 to_test = {
380 to_test = {
381 # link to a page
381 # link to a page
382 '[[CookBook documentation]]' => '<a href="CookBook_documentation.html" class="wiki-page">CookBook documentation</a>',
382 '[[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>',
383 '[[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>',
384 '[[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>',
385 '[[CookBook documentation#One-section|documentation]]' => '<a href="CookBook_documentation.html#One-section" class="wiki-page">documentation</a>',
386 # page that doesn't exist
386 # page that doesn't exist
387 '[[Unknown page]]' => '<a href="Unknown_page.html" class="wiki-page new">Unknown page</a>',
387 '[[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>',
388 '[[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>',
389 '[[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>',
390 '[[Unknown page#anchor|404]]' => '<a href="Unknown_page.html#anchor" class="wiki-page new">404</a>',
391 }
391 }
392
392
393 @project = Project.find(1)
393 @project = Project.find(1)
394
394
395 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :local) }
395 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :local) }
396 end
396 end
397
397
398 def test_html_tags
398 def test_html_tags
399 to_test = {
399 to_test = {
400 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
400 "<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>",
401 "<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>",
402 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
403 # do not escape pre/code tags
403 # do not escape pre/code tags
404 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
404 "<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>",
405 "<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>",
406 "<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>",
407 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
408 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
408 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
409 # remove attributes except class
409 # remove attributes except class
410 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
410 "<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>',
411 '<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>",
412 "<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>',
413 '<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>",
414 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
415 # xss
415 # xss
416 '<pre><code class=""onmouseover="alert(1)">text</code></pre>' => '<pre><code>text</code></pre>',
416 '<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>',
417 '<pre class=""onmouseover="alert(1)">text</pre>' => '<pre>text</pre>',
418 }
418 }
419 to_test.each { |text, result| assert_equal result, textilizable(text) }
419 to_test.each { |text, result| assert_equal result, textilizable(text) }
420 end
420 end
421
421
422 def test_allowed_html_tags
422 def test_allowed_html_tags
423 to_test = {
423 to_test = {
424 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
424 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
425 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
425 "<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;"
426 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
427 }
427 }
428 to_test.each { |text, result| assert_equal result, textilizable(text) }
428 to_test.each { |text, result| assert_equal result, textilizable(text) }
429 end
429 end
430
430
431 def test_pre_tags
431 def test_pre_tags
432 raw = <<-RAW
432 raw = <<-RAW
433 Before
433 Before
434
434
435 <pre>
435 <pre>
436 <prepared-statement-cache-size>32</prepared-statement-cache-size>
436 <prepared-statement-cache-size>32</prepared-statement-cache-size>
437 </pre>
437 </pre>
438
438
439 After
439 After
440 RAW
440 RAW
441
441
442 expected = <<-EXPECTED
442 expected = <<-EXPECTED
443 <p>Before</p>
443 <p>Before</p>
444 <pre>
444 <pre>
445 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
445 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
446 </pre>
446 </pre>
447 <p>After</p>
447 <p>After</p>
448 EXPECTED
448 EXPECTED
449
449
450 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
450 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
451 end
451 end
452
452
453 def test_pre_content_should_not_parse_wiki_and_redmine_links
453 def test_pre_content_should_not_parse_wiki_and_redmine_links
454 raw = <<-RAW
454 raw = <<-RAW
455 [[CookBook documentation]]
455 [[CookBook documentation]]
456
456
457 #1
457 #1
458
458
459 <pre>
459 <pre>
460 [[CookBook documentation]]
460 [[CookBook documentation]]
461
461
462 #1
462 #1
463 </pre>
463 </pre>
464 RAW
464 RAW
465
465
466 expected = <<-EXPECTED
466 expected = <<-EXPECTED
467 <p><a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a></p>
467 <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>
468 <p><a href="/issues/1" class="issue status-1 priority-1" title="Can't print recipes (New)">#1</a></p>
469 <pre>
469 <pre>
470 [[CookBook documentation]]
470 [[CookBook documentation]]
471
471
472 #1
472 #1
473 </pre>
473 </pre>
474 EXPECTED
474 EXPECTED
475
475
476 @project = Project.find(1)
476 @project = Project.find(1)
477 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
477 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
478 end
478 end
479
479
480 def test_non_closing_pre_blocks_should_be_closed
480 def test_non_closing_pre_blocks_should_be_closed
481 raw = <<-RAW
481 raw = <<-RAW
482 <pre><code>
482 <pre><code>
483 RAW
483 RAW
484
484
485 expected = <<-EXPECTED
485 expected = <<-EXPECTED
486 <pre><code>
486 <pre><code>
487 </code></pre>
487 </code></pre>
488 EXPECTED
488 EXPECTED
489
489
490 @project = Project.find(1)
490 @project = Project.find(1)
491 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
491 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
492 end
492 end
493
493
494 def test_syntax_highlight
494 def test_syntax_highlight
495 raw = <<-RAW
495 raw = <<-RAW
496 <pre><code class="ruby">
496 <pre><code class="ruby">
497 # Some ruby code here
497 # Some ruby code here
498 </code></pre>
498 </code></pre>
499 RAW
499 RAW
500
500
501 expected = <<-EXPECTED
501 expected = <<-EXPECTED
502 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="no">1</span> <span class="c"># Some ruby code here</span></span>
502 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="no">1</span> <span class="c"># Some ruby code here</span></span>
503 </code></pre>
503 </code></pre>
504 EXPECTED
504 EXPECTED
505
505
506 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
506 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
507 end
507 end
508
508
509 def test_wiki_links_in_tables
509 def test_wiki_links_in_tables
510 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
510 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>' +
511 '<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>' +
512 '<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>'
513 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
514 }
514 }
515 @project = Project.find(1)
515 @project = Project.find(1)
516 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
516 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
517 end
517 end
518
518
519 def test_text_formatting
519 def test_text_formatting
520 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
520 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>)',
521 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
522 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
522 '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>',
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>',
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',
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',
525 }
525 }
526 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
526 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
527 end
527 end
528
528
529 def test_wiki_horizontal_rule
529 def test_wiki_horizontal_rule
530 assert_equal '<hr />', textilizable('---')
530 assert_equal '<hr />', textilizable('---')
531 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
531 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
532 end
532 end
533
533
534 def test_footnotes
534 def test_footnotes
535 raw = <<-RAW
535 raw = <<-RAW
536 This is some text[1].
536 This is some text[1].
537
537
538 fn1. This is the foot note
538 fn1. This is the foot note
539 RAW
539 RAW
540
540
541 expected = <<-EXPECTED
541 expected = <<-EXPECTED
542 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
542 <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>
543 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
544 EXPECTED
544 EXPECTED
545
545
546 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
546 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
547 end
547 end
548
548
549 def test_headings
549 def test_headings
550 raw = 'h1. Some heading'
550 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>|
551 expected = %|<a name="Some-heading"></a>\n<h1 >Some heading<a href="#Some-heading" class="wiki-anchor">&para;</a></h1>|
552
552
553 assert_equal expected, textilizable(raw)
553 assert_equal expected, textilizable(raw)
554 end
554 end
555
555
556 def test_headings_with_special_chars
557 # This test makes sure that the generated anchor names match the expected
558 # ones even if the heading text contains unconventional characters
559 raw = 'h1. Some heading related to version 0.5'
560 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>|
562
563 assert_equal expected, textilizable(raw)
564 end
565
556 def test_wiki_links_within_wiki_page_context
566 def test_wiki_links_within_wiki_page_context
557
567
558 page = WikiPage.find_by_title('Another_page' )
568 page = WikiPage.find_by_title('Another_page' )
559
569
560 to_test = {
570 to_test = {
561 # link to another page
571 # link to another page
562 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
572 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
563 '[[CookBook documentation|documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">documentation</a>',
573 '[[CookBook documentation|documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">documentation</a>',
564 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
574 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
565 '[[CookBook documentation#One-section|documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">documentation</a>',
575 '[[CookBook documentation#One-section|documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">documentation</a>',
566 # link to the current page
576 # link to the current page
567 '[[Another page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Another page</a>',
577 '[[Another page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Another page</a>',
568 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
578 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
569 '[[Another page#anchor]]' => '<a href="#anchor" class="wiki-page">Another page</a>',
579 '[[Another page#anchor]]' => '<a href="#anchor" class="wiki-page">Another page</a>',
570 '[[Another page#anchor|Page]]' => '<a href="#anchor" class="wiki-page">Page</a>',
580 '[[Another page#anchor|Page]]' => '<a href="#anchor" class="wiki-page">Page</a>',
571 # page that doesn't exist
581 # page that doesn't exist
572 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
582 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
573 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
583 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
574 '[[Unknown page#anchor]]' => '<a href="/projects/ecookbook/wiki/Unknown_page#anchor" class="wiki-page new">Unknown page</a>',
584 '[[Unknown page#anchor]]' => '<a href="/projects/ecookbook/wiki/Unknown_page#anchor" class="wiki-page new">Unknown page</a>',
575 '[[Unknown page#anchor|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page#anchor" class="wiki-page new">404</a>',
585 '[[Unknown page#anchor|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page#anchor" class="wiki-page new">404</a>',
576 }
586 }
577
587
578 @project = Project.find(1)
588 @project = Project.find(1)
579
589
580 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(WikiContent.generate!( :text => text, :page => page ), :text) }
590 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(WikiContent.generate!( :text => text, :page => page ), :text) }
581 end
591 end
582
592
583 def test_wiki_links_anchor_option_should_prepend_page_title_to_href
593 def test_wiki_links_anchor_option_should_prepend_page_title_to_href
584
594
585 to_test = {
595 to_test = {
586 # link to a page
596 # link to a page
587 '[[CookBook documentation]]' => '<a href="#CookBook_documentation" class="wiki-page">CookBook documentation</a>',
597 '[[CookBook documentation]]' => '<a href="#CookBook_documentation" class="wiki-page">CookBook documentation</a>',
588 '[[CookBook documentation|documentation]]' => '<a href="#CookBook_documentation" class="wiki-page">documentation</a>',
598 '[[CookBook documentation|documentation]]' => '<a href="#CookBook_documentation" class="wiki-page">documentation</a>',
589 '[[CookBook documentation#One-section]]' => '<a href="#CookBook_documentation_One-section" class="wiki-page">CookBook documentation</a>',
599 '[[CookBook documentation#One-section]]' => '<a href="#CookBook_documentation_One-section" class="wiki-page">CookBook documentation</a>',
590 '[[CookBook documentation#One-section|documentation]]' => '<a href="#CookBook_documentation_One-section" class="wiki-page">documentation</a>',
600 '[[CookBook documentation#One-section|documentation]]' => '<a href="#CookBook_documentation_One-section" class="wiki-page">documentation</a>',
591 # page that doesn't exist
601 # page that doesn't exist
592 '[[Unknown page]]' => '<a href="#Unknown_page" class="wiki-page new">Unknown page</a>',
602 '[[Unknown page]]' => '<a href="#Unknown_page" class="wiki-page new">Unknown page</a>',
593 '[[Unknown page|404]]' => '<a href="#Unknown_page" class="wiki-page new">404</a>',
603 '[[Unknown page|404]]' => '<a href="#Unknown_page" class="wiki-page new">404</a>',
594 '[[Unknown page#anchor]]' => '<a href="#Unknown_page_anchor" class="wiki-page new">Unknown page</a>',
604 '[[Unknown page#anchor]]' => '<a href="#Unknown_page_anchor" class="wiki-page new">Unknown page</a>',
595 '[[Unknown page#anchor|404]]' => '<a href="#Unknown_page_anchor" class="wiki-page new">404</a>',
605 '[[Unknown page#anchor|404]]' => '<a href="#Unknown_page_anchor" class="wiki-page new">404</a>',
596 }
606 }
597
607
598 @project = Project.find(1)
608 @project = Project.find(1)
599
609
600 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :anchor) }
610 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :anchor) }
601 end
611 end
602
612
603 def test_headings_in_wiki_single_page_export_should_be_prepended_with_page_title
613 def test_headings_in_wiki_single_page_export_should_be_prepended_with_page_title
604 page = WikiPage.generate!( :title => 'Page Title' )
614 page = WikiPage.generate!( :title => 'Page Title' )
605 content = WikiContent.generate!( :text => 'h1. Some heading', :page => page )
615 content = WikiContent.generate!( :text => 'h1. Some heading', :page => page )
606
616
607 expected = %|<a name="Page_Title_Some-heading"></a>\n<h1 >Some heading<a href="#Page_Title_Some-heading" class="wiki-anchor">&para;</a></h1>|
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>|
608
618
609 assert_equal expected, textilizable(content, :text, :wiki_links => :anchor )
619 assert_equal expected, textilizable(content, :text, :wiki_links => :anchor )
610 end
620 end
611
621
612 def test_table_of_content
622 def test_table_of_content
613 raw = <<-RAW
623 raw = <<-RAW
614 {{toc}}
624 {{toc}}
615
625
616 h1. Title
626 h1. Title
617
627
618 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
628 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
619
629
620 h2. Subtitle with a [[Wiki]] link
630 h2. Subtitle with a [[Wiki]] link
621
631
622 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
632 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
623
633
624 h2. Subtitle with [[Wiki|another Wiki]] link
634 h2. Subtitle with [[Wiki|another Wiki]] link
625
635
626 h2. Subtitle with %{color:red}red text%
636 h2. Subtitle with %{color:red}red text%
627
637
628 <pre>
638 <pre>
629 some code
639 some code
630 </pre>
640 </pre>
631
641
632 h3. Subtitle with *some* _modifiers_
642 h3. Subtitle with *some* _modifiers_
633
643
634 h1. Another title
644 h1. Another title
635
645
636 h3. An "Internet link":http://www.redmine.org/ inside subtitle
646 h3. An "Internet link":http://www.redmine.org/ inside subtitle
637
647
638 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
648 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
639
649
640 RAW
650 RAW
641
651
642 expected = '<ul class="toc">' +
652 expected = '<ul class="toc">' +
643 '<li><a href="#Title">Title</a>' +
653 '<li><a href="#Title">Title</a>' +
644 '<ul>' +
654 '<ul>' +
645 '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
655 '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
646 '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
656 '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
647 '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
657 '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
648 '<ul>' +
658 '<ul>' +
649 '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
659 '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
650 '</ul>' +
660 '</ul>' +
651 '</li>' +
661 '</li>' +
652 '</ul>' +
662 '</ul>' +
653 '</li>' +
663 '</li>' +
654 '<li><a href="#Another-title">Another title</a>' +
664 '<li><a href="#Another-title">Another title</a>' +
655 '<ul>' +
665 '<ul>' +
656 '<li>' +
666 '<li>' +
657 '<ul>' +
667 '<ul>' +
658 '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
668 '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
659 '</ul>' +
669 '</ul>' +
660 '</li>' +
670 '</li>' +
661 '<li><a href="#Project-Name">Project Name</a></li>' +
671 '<li><a href="#Project-Name">Project Name</a></li>' +
662 '</ul>' +
672 '</ul>' +
663 '</li>' +
673 '</li>' +
664 '</ul>'
674 '</ul>'
665
675
666 @project = Project.find(1)
676 @project = Project.find(1)
667 assert textilizable(raw).gsub("\n", "").include?(expected), textilizable(raw)
677 assert textilizable(raw).gsub("\n", "").include?(expected), textilizable(raw)
668 end
678 end
669
679
670 def test_table_of_content_should_contain_included_page_headings
680 def test_table_of_content_should_contain_included_page_headings
671 raw = <<-RAW
681 raw = <<-RAW
672 {{toc}}
682 {{toc}}
673
683
674 h1. Included
684 h1. Included
675
685
676 {{include(Child_1)}}
686 {{include(Child_1)}}
677 RAW
687 RAW
678
688
679 expected = '<ul class="toc">' +
689 expected = '<ul class="toc">' +
680 '<li><a href="#Included">Included</a></li>' +
690 '<li><a href="#Included">Included</a></li>' +
681 '<li><a href="#Child-page-1">Child page 1</a></li>' +
691 '<li><a href="#Child-page-1">Child page 1</a></li>' +
682 '</ul>'
692 '</ul>'
683
693
684 @project = Project.find(1)
694 @project = Project.find(1)
685 assert textilizable(raw).gsub("\n", "").include?(expected)
695 assert textilizable(raw).gsub("\n", "").include?(expected)
686 end
696 end
687
697
688 def test_default_formatter
698 def test_default_formatter
689 Setting.text_formatting = 'unknown'
699 Setting.text_formatting = 'unknown'
690 text = 'a *link*: http://www.example.net/'
700 text = 'a *link*: http://www.example.net/'
691 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
701 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
692 Setting.text_formatting = 'textile'
702 Setting.text_formatting = 'textile'
693 end
703 end
694
704
695 def test_due_date_distance_in_words
705 def test_due_date_distance_in_words
696 to_test = { Date.today => 'Due in 0 days',
706 to_test = { Date.today => 'Due in 0 days',
697 Date.today + 1 => 'Due in 1 day',
707 Date.today + 1 => 'Due in 1 day',
698 Date.today + 100 => 'Due in about 3 months',
708 Date.today + 100 => 'Due in about 3 months',
699 Date.today + 20000 => 'Due in over 54 years',
709 Date.today + 20000 => 'Due in over 54 years',
700 Date.today - 1 => '1 day late',
710 Date.today - 1 => '1 day late',
701 Date.today - 100 => 'about 3 months late',
711 Date.today - 100 => 'about 3 months late',
702 Date.today - 20000 => 'over 54 years late',
712 Date.today - 20000 => 'over 54 years late',
703 }
713 }
704 ::I18n.locale = :en
714 ::I18n.locale = :en
705 to_test.each do |date, expected|
715 to_test.each do |date, expected|
706 assert_equal expected, due_date_distance_in_words(date)
716 assert_equal expected, due_date_distance_in_words(date)
707 end
717 end
708 end
718 end
709
719
710 def test_avatar
720 def test_avatar
711 # turn on avatars
721 # turn on avatars
712 Setting.gravatar_enabled = '1'
722 Setting.gravatar_enabled = '1'
713 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
723 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
714 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
724 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
715 assert_nil avatar('jsmith')
725 assert_nil avatar('jsmith')
716 assert_nil avatar(nil)
726 assert_nil avatar(nil)
717
727
718 # turn off avatars
728 # turn off avatars
719 Setting.gravatar_enabled = '0'
729 Setting.gravatar_enabled = '0'
720 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
730 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
721 end
731 end
722
732
723 def test_link_to_user
733 def test_link_to_user
724 user = User.find(2)
734 user = User.find(2)
725 t = link_to_user(user)
735 t = link_to_user(user)
726 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
736 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
727 end
737 end
728
738
729 def test_link_to_user_should_not_link_to_locked_user
739 def test_link_to_user_should_not_link_to_locked_user
730 user = User.find(5)
740 user = User.find(5)
731 assert user.locked?
741 assert user.locked?
732 t = link_to_user(user)
742 t = link_to_user(user)
733 assert_equal user.name, t
743 assert_equal user.name, t
734 end
744 end
735
745
736 def test_link_to_user_should_not_link_to_anonymous
746 def test_link_to_user_should_not_link_to_anonymous
737 user = User.anonymous
747 user = User.anonymous
738 assert user.anonymous?
748 assert user.anonymous?
739 t = link_to_user(user)
749 t = link_to_user(user)
740 assert_equal ::I18n.t(:label_user_anonymous), t
750 assert_equal ::I18n.t(:label_user_anonymous), t
741 end
751 end
742
752
743 def test_link_to_project
753 def test_link_to_project
744 project = Project.find(1)
754 project = Project.find(1)
745 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
755 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
746 link_to_project(project)
756 link_to_project(project)
747 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
757 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
748 link_to_project(project, :action => 'settings')
758 link_to_project(project, :action => 'settings')
749 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
759 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
750 link_to_project(project, {:only_path => false, :jump => 'blah'})
760 link_to_project(project, {:only_path => false, :jump => 'blah'})
751 assert_equal %(<a href="/projects/ecookbook/settings" class="project">eCookbook</a>),
761 assert_equal %(<a href="/projects/ecookbook/settings" class="project">eCookbook</a>),
752 link_to_project(project, {:action => 'settings'}, :class => "project")
762 link_to_project(project, {:action => 'settings'}, :class => "project")
753 end
763 end
754
764
755 def test_principals_options_for_select_with_users
765 def test_principals_options_for_select_with_users
756 users = [User.find(2), User.find(4)]
766 users = [User.find(2), User.find(4)]
757 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
767 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
758 principals_options_for_select(users)
768 principals_options_for_select(users)
759 end
769 end
760
770
761 def test_principals_options_for_select_with_selected
771 def test_principals_options_for_select_with_selected
762 users = [User.find(2), User.find(4)]
772 users = [User.find(2), User.find(4)]
763 assert_equal %(<option value="2">John Smith</option><option value="4" selected="selected">Robert Hill</option>),
773 assert_equal %(<option value="2">John Smith</option><option value="4" selected="selected">Robert Hill</option>),
764 principals_options_for_select(users, User.find(4))
774 principals_options_for_select(users, User.find(4))
765 end
775 end
766
776
767 def test_principals_options_for_select_with_users_and_groups
777 def test_principals_options_for_select_with_users_and_groups
768 users = [User.find(2), Group.find(11), User.find(4), Group.find(10)]
778 users = [User.find(2), Group.find(11), User.find(4), Group.find(10)]
769 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>) +
779 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>) +
770 %(<optgroup label="Groups"><option value="10">A Team</option><option value="11">B Team</option></optgroup>),
780 %(<optgroup label="Groups"><option value="10">A Team</option><option value="11">B Team</option></optgroup>),
771 principals_options_for_select(users)
781 principals_options_for_select(users)
772 end
782 end
773
783
774 def test_principals_options_for_select_with_empty_collection
784 def test_principals_options_for_select_with_empty_collection
775 assert_equal '', principals_options_for_select([])
785 assert_equal '', principals_options_for_select([])
776 end
786 end
777 end
787 end
General Comments 0
You need to be logged in to leave comments. Login now