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