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