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