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