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