##// END OF EJS Templates
Merged r12989 and r12997 (#16236)....
Jean-Philippe Lang -
r12745:c5b52ec29fcb
parent child
Show More
@@ -1,1348 +1,1349
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2014 Jean-Philippe Lang
4 # Copyright (C) 2006-2014 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 include Redmine::Pagination::Helper
27 include Redmine::Pagination::Helper
28
28
29 extend Forwardable
29 extend Forwardable
30 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
30 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
31
31
32 # Return true if user is authorized for controller/action, otherwise false
32 # Return true if user is authorized for controller/action, otherwise false
33 def authorize_for(controller, action)
33 def authorize_for(controller, action)
34 User.current.allowed_to?({:controller => controller, :action => action}, @project)
34 User.current.allowed_to?({:controller => controller, :action => action}, @project)
35 end
35 end
36
36
37 # Display a link if user is authorized
37 # Display a link if user is authorized
38 #
38 #
39 # @param [String] name Anchor text (passed to link_to)
39 # @param [String] name Anchor text (passed to link_to)
40 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
40 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
41 # @param [optional, Hash] html_options Options passed to link_to
41 # @param [optional, Hash] html_options Options passed to link_to
42 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
42 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
43 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
43 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
44 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
44 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
45 end
45 end
46
46
47 # Displays a link to user's account page if active
47 # Displays a link to user's account page if active
48 def link_to_user(user, options={})
48 def link_to_user(user, options={})
49 if user.is_a?(User)
49 if user.is_a?(User)
50 name = h(user.name(options[:format]))
50 name = h(user.name(options[:format]))
51 if user.active? || (User.current.admin? && user.logged?)
51 if user.active? || (User.current.admin? && user.logged?)
52 link_to name, user_path(user), :class => user.css_classes
52 link_to name, user_path(user), :class => user.css_classes
53 else
53 else
54 name
54 name
55 end
55 end
56 else
56 else
57 h(user.to_s)
57 h(user.to_s)
58 end
58 end
59 end
59 end
60
60
61 # Displays a link to +issue+ with its subject.
61 # Displays a link to +issue+ with its subject.
62 # Examples:
62 # Examples:
63 #
63 #
64 # link_to_issue(issue) # => Defect #6: This is the subject
64 # link_to_issue(issue) # => Defect #6: This is the subject
65 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
65 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
66 # link_to_issue(issue, :subject => false) # => Defect #6
66 # link_to_issue(issue, :subject => false) # => Defect #6
67 # link_to_issue(issue, :project => true) # => Foo - Defect #6
67 # link_to_issue(issue, :project => true) # => Foo - Defect #6
68 # link_to_issue(issue, :subject => false, :tracker => false) # => #6
68 # link_to_issue(issue, :subject => false, :tracker => false) # => #6
69 #
69 #
70 def link_to_issue(issue, options={})
70 def link_to_issue(issue, options={})
71 title = nil
71 title = nil
72 subject = nil
72 subject = nil
73 text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}"
73 text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}"
74 if options[:subject] == false
74 if options[:subject] == false
75 title = issue.subject.truncate(60)
75 title = issue.subject.truncate(60)
76 else
76 else
77 subject = issue.subject
77 subject = issue.subject
78 if truncate_length = options[:truncate]
78 if truncate_length = options[:truncate]
79 subject = subject.truncate(truncate_length)
79 subject = subject.truncate(truncate_length)
80 end
80 end
81 end
81 end
82 only_path = options[:only_path].nil? ? true : options[:only_path]
82 only_path = options[:only_path].nil? ? true : options[:only_path]
83 s = link_to(text, issue_path(issue, :only_path => only_path),
83 s = link_to(text, issue_path(issue, :only_path => only_path),
84 :class => issue.css_classes, :title => title)
84 :class => issue.css_classes, :title => title)
85 s << h(": #{subject}") if subject
85 s << h(": #{subject}") if subject
86 s = h("#{issue.project} - ") + s if options[:project]
86 s = h("#{issue.project} - ") + s if options[:project]
87 s
87 s
88 end
88 end
89
89
90 # Generates a link to an attachment.
90 # Generates a link to an attachment.
91 # Options:
91 # Options:
92 # * :text - Link text (default to attachment filename)
92 # * :text - Link text (default to attachment filename)
93 # * :download - Force download (default: false)
93 # * :download - Force download (default: false)
94 def link_to_attachment(attachment, options={})
94 def link_to_attachment(attachment, options={})
95 text = options.delete(:text) || attachment.filename
95 text = options.delete(:text) || attachment.filename
96 route_method = options.delete(:download) ? :download_named_attachment_path : :named_attachment_path
96 route_method = options.delete(:download) ? :download_named_attachment_path : :named_attachment_path
97 html_options = options.slice!(:only_path)
97 html_options = options.slice!(:only_path)
98 url = send(route_method, attachment, attachment.filename, options)
98 url = send(route_method, attachment, attachment.filename, options)
99 link_to text, url, html_options
99 link_to text, url, html_options
100 end
100 end
101
101
102 # Generates a link to a SCM revision
102 # Generates a link to a SCM revision
103 # Options:
103 # Options:
104 # * :text - Link text (default to the formatted revision)
104 # * :text - Link text (default to the formatted revision)
105 def link_to_revision(revision, repository, options={})
105 def link_to_revision(revision, repository, options={})
106 if repository.is_a?(Project)
106 if repository.is_a?(Project)
107 repository = repository.repository
107 repository = repository.repository
108 end
108 end
109 text = options.delete(:text) || format_revision(revision)
109 text = options.delete(:text) || format_revision(revision)
110 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
110 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
111 link_to(
111 link_to(
112 h(text),
112 h(text),
113 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
113 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
114 :title => l(:label_revision_id, format_revision(revision))
114 :title => l(:label_revision_id, format_revision(revision))
115 )
115 )
116 end
116 end
117
117
118 # Generates a link to a message
118 # Generates a link to a message
119 def link_to_message(message, options={}, html_options = nil)
119 def link_to_message(message, options={}, html_options = nil)
120 link_to(
120 link_to(
121 message.subject.truncate(60),
121 message.subject.truncate(60),
122 board_message_path(message.board_id, message.parent_id || message.id, {
122 board_message_path(message.board_id, message.parent_id || message.id, {
123 :r => (message.parent_id && message.id),
123 :r => (message.parent_id && message.id),
124 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
124 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
125 }.merge(options)),
125 }.merge(options)),
126 html_options
126 html_options
127 )
127 )
128 end
128 end
129
129
130 # Generates a link to a project if active
130 # Generates a link to a project if active
131 # Examples:
131 # Examples:
132 #
132 #
133 # link_to_project(project) # => link to the specified project overview
133 # link_to_project(project) # => link to the specified project overview
134 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
134 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
135 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
135 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
136 #
136 #
137 def link_to_project(project, options={}, html_options = nil)
137 def link_to_project(project, options={}, html_options = nil)
138 if project.archived?
138 if project.archived?
139 h(project.name)
139 h(project.name)
140 elsif options.key?(:action)
140 elsif options.key?(:action)
141 ActiveSupport::Deprecation.warn "#link_to_project with :action option is deprecated and will be removed in Redmine 3.0."
141 ActiveSupport::Deprecation.warn "#link_to_project with :action option is deprecated and will be removed in Redmine 3.0."
142 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
142 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
143 link_to project.name, url, html_options
143 link_to project.name, url, html_options
144 else
144 else
145 link_to project.name, project_path(project, options), html_options
145 link_to project.name, project_path(project, options), html_options
146 end
146 end
147 end
147 end
148
148
149 # Generates a link to a project settings if active
149 # Generates a link to a project settings if active
150 def link_to_project_settings(project, options={}, html_options=nil)
150 def link_to_project_settings(project, options={}, html_options=nil)
151 if project.active?
151 if project.active?
152 link_to project.name, settings_project_path(project, options), html_options
152 link_to project.name, settings_project_path(project, options), html_options
153 elsif project.archived?
153 elsif project.archived?
154 h(project.name)
154 h(project.name)
155 else
155 else
156 link_to project.name, project_path(project, options), html_options
156 link_to project.name, project_path(project, options), html_options
157 end
157 end
158 end
158 end
159
159
160 # Helper that formats object for html or text rendering
160 # Helper that formats object for html or text rendering
161 def format_object(object, html=true)
161 def format_object(object, html=true)
162 case object.class.name
162 case object.class.name
163 when 'Array'
163 when 'Array'
164 object.map {|o| format_object(o, html)}.join(', ').html_safe
164 object.map {|o| format_object(o, html)}.join(', ').html_safe
165 when 'Time'
165 when 'Time'
166 format_time(object)
166 format_time(object)
167 when 'Date'
167 when 'Date'
168 format_date(object)
168 format_date(object)
169 when 'Fixnum'
169 when 'Fixnum'
170 object.to_s
170 object.to_s
171 when 'Float'
171 when 'Float'
172 sprintf "%.2f", object
172 sprintf "%.2f", object
173 when 'User'
173 when 'User'
174 html ? link_to_user(object) : object.to_s
174 html ? link_to_user(object) : object.to_s
175 when 'Project'
175 when 'Project'
176 html ? link_to_project(object) : object.to_s
176 html ? link_to_project(object) : object.to_s
177 when 'Version'
177 when 'Version'
178 html ? link_to(object.name, version_path(object)) : object.to_s
178 html ? link_to(object.name, version_path(object)) : object.to_s
179 when 'TrueClass'
179 when 'TrueClass'
180 l(:general_text_Yes)
180 l(:general_text_Yes)
181 when 'FalseClass'
181 when 'FalseClass'
182 l(:general_text_No)
182 l(:general_text_No)
183 when 'Issue'
183 when 'Issue'
184 object.visible? && html ? link_to_issue(object) : "##{object.id}"
184 object.visible? && html ? link_to_issue(object) : "##{object.id}"
185 when 'CustomValue', 'CustomFieldValue'
185 when 'CustomValue', 'CustomFieldValue'
186 if object.custom_field
186 if object.custom_field
187 f = object.custom_field.format.formatted_custom_value(self, object, html)
187 f = object.custom_field.format.formatted_custom_value(self, object, html)
188 if f.nil? || f.is_a?(String)
188 if f.nil? || f.is_a?(String)
189 f
189 f
190 else
190 else
191 format_object(f, html)
191 format_object(f, html)
192 end
192 end
193 else
193 else
194 object.value.to_s
194 object.value.to_s
195 end
195 end
196 else
196 else
197 html ? h(object) : object.to_s
197 html ? h(object) : object.to_s
198 end
198 end
199 end
199 end
200
200
201 def wiki_page_path(page, options={})
201 def wiki_page_path(page, options={})
202 url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options))
202 url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options))
203 end
203 end
204
204
205 def thumbnail_tag(attachment)
205 def thumbnail_tag(attachment)
206 link_to image_tag(thumbnail_path(attachment)),
206 link_to image_tag(thumbnail_path(attachment)),
207 named_attachment_path(attachment, attachment.filename),
207 named_attachment_path(attachment, attachment.filename),
208 :title => attachment.filename
208 :title => attachment.filename
209 end
209 end
210
210
211 def toggle_link(name, id, options={})
211 def toggle_link(name, id, options={})
212 onclick = "$('##{id}').toggle(); "
212 onclick = "$('##{id}').toggle(); "
213 onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ")
213 onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ")
214 onclick << "return false;"
214 onclick << "return false;"
215 link_to(name, "#", :onclick => onclick)
215 link_to(name, "#", :onclick => onclick)
216 end
216 end
217
217
218 def image_to_function(name, function, html_options = {})
218 def image_to_function(name, function, html_options = {})
219 html_options.symbolize_keys!
219 html_options.symbolize_keys!
220 tag(:input, html_options.merge({
220 tag(:input, html_options.merge({
221 :type => "image", :src => image_path(name),
221 :type => "image", :src => image_path(name),
222 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
222 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
223 }))
223 }))
224 end
224 end
225
225
226 def format_activity_title(text)
226 def format_activity_title(text)
227 h(truncate_single_line_raw(text, 100))
227 h(truncate_single_line_raw(text, 100))
228 end
228 end
229
229
230 def format_activity_day(date)
230 def format_activity_day(date)
231 date == User.current.today ? l(:label_today).titleize : format_date(date)
231 date == User.current.today ? l(:label_today).titleize : format_date(date)
232 end
232 end
233
233
234 def format_activity_description(text)
234 def format_activity_description(text)
235 h(text.to_s.truncate(120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
235 h(text.to_s.truncate(120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
236 ).gsub(/[\r\n]+/, "<br />").html_safe
236 ).gsub(/[\r\n]+/, "<br />").html_safe
237 end
237 end
238
238
239 def format_version_name(version)
239 def format_version_name(version)
240 if version.project == @project
240 if version.project == @project
241 h(version)
241 h(version)
242 else
242 else
243 h("#{version.project} - #{version}")
243 h("#{version.project} - #{version}")
244 end
244 end
245 end
245 end
246
246
247 def due_date_distance_in_words(date)
247 def due_date_distance_in_words(date)
248 if date
248 if date
249 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
249 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
250 end
250 end
251 end
251 end
252
252
253 # Renders a tree of projects as a nested set of unordered lists
253 # Renders a tree of projects as a nested set of unordered lists
254 # The given collection may be a subset of the whole project tree
254 # The given collection may be a subset of the whole project tree
255 # (eg. some intermediate nodes are private and can not be seen)
255 # (eg. some intermediate nodes are private and can not be seen)
256 def render_project_nested_lists(projects)
256 def render_project_nested_lists(projects)
257 s = ''
257 s = ''
258 if projects.any?
258 if projects.any?
259 ancestors = []
259 ancestors = []
260 original_project = @project
260 original_project = @project
261 projects.sort_by(&:lft).each do |project|
261 projects.sort_by(&:lft).each do |project|
262 # set the project environment to please macros.
262 # set the project environment to please macros.
263 @project = project
263 @project = project
264 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
264 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
265 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
265 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
266 else
266 else
267 ancestors.pop
267 ancestors.pop
268 s << "</li>"
268 s << "</li>"
269 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
269 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
270 ancestors.pop
270 ancestors.pop
271 s << "</ul></li>\n"
271 s << "</ul></li>\n"
272 end
272 end
273 end
273 end
274 classes = (ancestors.empty? ? 'root' : 'child')
274 classes = (ancestors.empty? ? 'root' : 'child')
275 s << "<li class='#{classes}'><div class='#{classes}'>"
275 s << "<li class='#{classes}'><div class='#{classes}'>"
276 s << h(block_given? ? yield(project) : project.name)
276 s << h(block_given? ? yield(project) : project.name)
277 s << "</div>\n"
277 s << "</div>\n"
278 ancestors << project
278 ancestors << project
279 end
279 end
280 s << ("</li></ul>\n" * ancestors.size)
280 s << ("</li></ul>\n" * ancestors.size)
281 @project = original_project
281 @project = original_project
282 end
282 end
283 s.html_safe
283 s.html_safe
284 end
284 end
285
285
286 def render_page_hierarchy(pages, node=nil, options={})
286 def render_page_hierarchy(pages, node=nil, options={})
287 content = ''
287 content = ''
288 if pages[node]
288 if pages[node]
289 content << "<ul class=\"pages-hierarchy\">\n"
289 content << "<ul class=\"pages-hierarchy\">\n"
290 pages[node].each do |page|
290 pages[node].each do |page|
291 content << "<li>"
291 content << "<li>"
292 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil},
292 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil},
293 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
293 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
294 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
294 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
295 content << "</li>\n"
295 content << "</li>\n"
296 end
296 end
297 content << "</ul>\n"
297 content << "</ul>\n"
298 end
298 end
299 content.html_safe
299 content.html_safe
300 end
300 end
301
301
302 # Renders flash messages
302 # Renders flash messages
303 def render_flash_messages
303 def render_flash_messages
304 s = ''
304 s = ''
305 flash.each do |k,v|
305 flash.each do |k,v|
306 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
306 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
307 end
307 end
308 s.html_safe
308 s.html_safe
309 end
309 end
310
310
311 # Renders tabs and their content
311 # Renders tabs and their content
312 def render_tabs(tabs, selected=params[:tab])
312 def render_tabs(tabs, selected=params[:tab])
313 if tabs.any?
313 if tabs.any?
314 unless tabs.detect {|tab| tab[:name] == selected}
314 unless tabs.detect {|tab| tab[:name] == selected}
315 selected = nil
315 selected = nil
316 end
316 end
317 selected ||= tabs.first[:name]
317 selected ||= tabs.first[:name]
318 render :partial => 'common/tabs', :locals => {:tabs => tabs, :selected_tab => selected}
318 render :partial => 'common/tabs', :locals => {:tabs => tabs, :selected_tab => selected}
319 else
319 else
320 content_tag 'p', l(:label_no_data), :class => "nodata"
320 content_tag 'p', l(:label_no_data), :class => "nodata"
321 end
321 end
322 end
322 end
323
323
324 # Renders the project quick-jump box
324 # Renders the project quick-jump box
325 def render_project_jump_box
325 def render_project_jump_box
326 return unless User.current.logged?
326 return unless User.current.logged?
327 projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq
327 projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq
328 if projects.any?
328 if projects.any?
329 options =
329 options =
330 ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
330 ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
331 '<option value="" disabled="disabled">---</option>').html_safe
331 '<option value="" disabled="disabled">---</option>').html_safe
332
332
333 options << project_tree_options_for_select(projects, :selected => @project) do |p|
333 options << project_tree_options_for_select(projects, :selected => @project) do |p|
334 { :value => project_path(:id => p, :jump => current_menu_item) }
334 { :value => project_path(:id => p, :jump => current_menu_item) }
335 end
335 end
336
336
337 select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
337 select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
338 end
338 end
339 end
339 end
340
340
341 def project_tree_options_for_select(projects, options = {})
341 def project_tree_options_for_select(projects, options = {})
342 s = ''
342 s = ''
343 project_tree(projects) do |project, level|
343 project_tree(projects) do |project, level|
344 name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
344 name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
345 tag_options = {:value => project.id}
345 tag_options = {:value => project.id}
346 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
346 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
347 tag_options[:selected] = 'selected'
347 tag_options[:selected] = 'selected'
348 else
348 else
349 tag_options[:selected] = nil
349 tag_options[:selected] = nil
350 end
350 end
351 tag_options.merge!(yield(project)) if block_given?
351 tag_options.merge!(yield(project)) if block_given?
352 s << content_tag('option', name_prefix + h(project), tag_options)
352 s << content_tag('option', name_prefix + h(project), tag_options)
353 end
353 end
354 s.html_safe
354 s.html_safe
355 end
355 end
356
356
357 # Yields the given block for each project with its level in the tree
357 # Yields the given block for each project with its level in the tree
358 #
358 #
359 # Wrapper for Project#project_tree
359 # Wrapper for Project#project_tree
360 def project_tree(projects, &block)
360 def project_tree(projects, &block)
361 Project.project_tree(projects, &block)
361 Project.project_tree(projects, &block)
362 end
362 end
363
363
364 def principals_check_box_tags(name, principals)
364 def principals_check_box_tags(name, principals)
365 s = ''
365 s = ''
366 principals.each do |principal|
366 principals.each do |principal|
367 s << "<label>#{ check_box_tag name, principal.id, false, :id => nil } #{h principal}</label>\n"
367 s << "<label>#{ check_box_tag name, principal.id, false, :id => nil } #{h principal}</label>\n"
368 end
368 end
369 s.html_safe
369 s.html_safe
370 end
370 end
371
371
372 # Returns a string for users/groups option tags
372 # Returns a string for users/groups option tags
373 def principals_options_for_select(collection, selected=nil)
373 def principals_options_for_select(collection, selected=nil)
374 s = ''
374 s = ''
375 if collection.include?(User.current)
375 if collection.include?(User.current)
376 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
376 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
377 end
377 end
378 groups = ''
378 groups = ''
379 collection.sort.each do |element|
379 collection.sort.each do |element|
380 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) || element.id.to_s == selected
380 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) || element.id.to_s == selected
381 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
381 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
382 end
382 end
383 unless groups.empty?
383 unless groups.empty?
384 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
384 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
385 end
385 end
386 s.html_safe
386 s.html_safe
387 end
387 end
388
388
389 # Options for the new membership projects combo-box
389 # Options for the new membership projects combo-box
390 def options_for_membership_project_select(principal, projects)
390 def options_for_membership_project_select(principal, projects)
391 options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
391 options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
392 options << project_tree_options_for_select(projects) do |p|
392 options << project_tree_options_for_select(projects) do |p|
393 {:disabled => principal.projects.to_a.include?(p)}
393 {:disabled => principal.projects.to_a.include?(p)}
394 end
394 end
395 options
395 options
396 end
396 end
397
397
398 def option_tag(name, text, value, selected=nil, options={})
398 def option_tag(name, text, value, selected=nil, options={})
399 content_tag 'option', value, options.merge(:value => value, :selected => (value == selected))
399 content_tag 'option', value, options.merge(:value => value, :selected => (value == selected))
400 end
400 end
401
401
402 # Truncates and returns the string as a single line
402 # Truncates and returns the string as a single line
403 def truncate_single_line(string, *args)
403 def truncate_single_line(string, *args)
404 ActiveSupport::Deprecation.warn(
404 ActiveSupport::Deprecation.warn(
405 "ApplicationHelper#truncate_single_line is deprecated and will be removed in Rails 4 poring")
405 "ApplicationHelper#truncate_single_line is deprecated and will be removed in Rails 4 poring")
406 # Rails 4 ActionView::Helpers::TextHelper#truncate escapes.
406 # Rails 4 ActionView::Helpers::TextHelper#truncate escapes.
407 # So, result is broken.
407 # So, result is broken.
408 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
408 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
409 end
409 end
410
410
411 def truncate_single_line_raw(string, length)
411 def truncate_single_line_raw(string, length)
412 string.truncate(length).gsub(%r{[\r\n]+}m, ' ')
412 string.truncate(length).gsub(%r{[\r\n]+}m, ' ')
413 end
413 end
414
414
415 # Truncates at line break after 250 characters or options[:length]
415 # Truncates at line break after 250 characters or options[:length]
416 def truncate_lines(string, options={})
416 def truncate_lines(string, options={})
417 length = options[:length] || 250
417 length = options[:length] || 250
418 if string.to_s =~ /\A(.{#{length}}.*?)$/m
418 if string.to_s =~ /\A(.{#{length}}.*?)$/m
419 "#{$1}..."
419 "#{$1}..."
420 else
420 else
421 string
421 string
422 end
422 end
423 end
423 end
424
424
425 def anchor(text)
425 def anchor(text)
426 text.to_s.gsub(' ', '_')
426 text.to_s.gsub(' ', '_')
427 end
427 end
428
428
429 def html_hours(text)
429 def html_hours(text)
430 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
430 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
431 end
431 end
432
432
433 def authoring(created, author, options={})
433 def authoring(created, author, options={})
434 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
434 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
435 end
435 end
436
436
437 def time_tag(time)
437 def time_tag(time)
438 text = distance_of_time_in_words(Time.now, time)
438 text = distance_of_time_in_words(Time.now, time)
439 if @project
439 if @project
440 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
440 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
441 else
441 else
442 content_tag('abbr', text, :title => format_time(time))
442 content_tag('abbr', text, :title => format_time(time))
443 end
443 end
444 end
444 end
445
445
446 def syntax_highlight_lines(name, content)
446 def syntax_highlight_lines(name, content)
447 lines = []
447 lines = []
448 syntax_highlight(name, content).each_line { |line| lines << line }
448 syntax_highlight(name, content).each_line { |line| lines << line }
449 lines
449 lines
450 end
450 end
451
451
452 def syntax_highlight(name, content)
452 def syntax_highlight(name, content)
453 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
453 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
454 end
454 end
455
455
456 def to_path_param(path)
456 def to_path_param(path)
457 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
457 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
458 str.blank? ? nil : str
458 str.blank? ? nil : str
459 end
459 end
460
460
461 def reorder_links(name, url, method = :post)
461 def reorder_links(name, url, method = :post)
462 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
462 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
463 url.merge({"#{name}[move_to]" => 'highest'}),
463 url.merge({"#{name}[move_to]" => 'highest'}),
464 :method => method, :title => l(:label_sort_highest)) +
464 :method => method, :title => l(:label_sort_highest)) +
465 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
465 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
466 url.merge({"#{name}[move_to]" => 'higher'}),
466 url.merge({"#{name}[move_to]" => 'higher'}),
467 :method => method, :title => l(:label_sort_higher)) +
467 :method => method, :title => l(:label_sort_higher)) +
468 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
468 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
469 url.merge({"#{name}[move_to]" => 'lower'}),
469 url.merge({"#{name}[move_to]" => 'lower'}),
470 :method => method, :title => l(:label_sort_lower)) +
470 :method => method, :title => l(:label_sort_lower)) +
471 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
471 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
472 url.merge({"#{name}[move_to]" => 'lowest'}),
472 url.merge({"#{name}[move_to]" => 'lowest'}),
473 :method => method, :title => l(:label_sort_lowest))
473 :method => method, :title => l(:label_sort_lowest))
474 end
474 end
475
475
476 def breadcrumb(*args)
476 def breadcrumb(*args)
477 elements = args.flatten
477 elements = args.flatten
478 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
478 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
479 end
479 end
480
480
481 def other_formats_links(&block)
481 def other_formats_links(&block)
482 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
482 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
483 yield Redmine::Views::OtherFormatsBuilder.new(self)
483 yield Redmine::Views::OtherFormatsBuilder.new(self)
484 concat('</p>'.html_safe)
484 concat('</p>'.html_safe)
485 end
485 end
486
486
487 def page_header_title
487 def page_header_title
488 if @project.nil? || @project.new_record?
488 if @project.nil? || @project.new_record?
489 h(Setting.app_title)
489 h(Setting.app_title)
490 else
490 else
491 b = []
491 b = []
492 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
492 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
493 if ancestors.any?
493 if ancestors.any?
494 root = ancestors.shift
494 root = ancestors.shift
495 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
495 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
496 if ancestors.size > 2
496 if ancestors.size > 2
497 b << "\xe2\x80\xa6"
497 b << "\xe2\x80\xa6"
498 ancestors = ancestors[-2, 2]
498 ancestors = ancestors[-2, 2]
499 end
499 end
500 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
500 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
501 end
501 end
502 b << h(@project)
502 b << h(@project)
503 b.join(" \xc2\xbb ").html_safe
503 b.join(" \xc2\xbb ").html_safe
504 end
504 end
505 end
505 end
506
506
507 # Returns a h2 tag and sets the html title with the given arguments
507 # Returns a h2 tag and sets the html title with the given arguments
508 def title(*args)
508 def title(*args)
509 strings = args.map do |arg|
509 strings = args.map do |arg|
510 if arg.is_a?(Array) && arg.size >= 2
510 if arg.is_a?(Array) && arg.size >= 2
511 link_to(*arg)
511 link_to(*arg)
512 else
512 else
513 h(arg.to_s)
513 h(arg.to_s)
514 end
514 end
515 end
515 end
516 html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
516 html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
517 content_tag('h2', strings.join(' &#187; ').html_safe)
517 content_tag('h2', strings.join(' &#187; ').html_safe)
518 end
518 end
519
519
520 # Sets the html title
520 # Sets the html title
521 # Returns the html title when called without arguments
521 # Returns the html title when called without arguments
522 # Current project name and app_title and automatically appended
522 # Current project name and app_title and automatically appended
523 # Exemples:
523 # Exemples:
524 # html_title 'Foo', 'Bar'
524 # html_title 'Foo', 'Bar'
525 # html_title # => 'Foo - Bar - My Project - Redmine'
525 # html_title # => 'Foo - Bar - My Project - Redmine'
526 def html_title(*args)
526 def html_title(*args)
527 if args.empty?
527 if args.empty?
528 title = @html_title || []
528 title = @html_title || []
529 title << @project.name if @project
529 title << @project.name if @project
530 title << Setting.app_title unless Setting.app_title == title.last
530 title << Setting.app_title unless Setting.app_title == title.last
531 title.reject(&:blank?).join(' - ')
531 title.reject(&:blank?).join(' - ')
532 else
532 else
533 @html_title ||= []
533 @html_title ||= []
534 @html_title += args
534 @html_title += args
535 end
535 end
536 end
536 end
537
537
538 # Returns the theme, controller name, and action as css classes for the
538 # Returns the theme, controller name, and action as css classes for the
539 # HTML body.
539 # HTML body.
540 def body_css_classes
540 def body_css_classes
541 css = []
541 css = []
542 if theme = Redmine::Themes.theme(Setting.ui_theme)
542 if theme = Redmine::Themes.theme(Setting.ui_theme)
543 css << 'theme-' + theme.name
543 css << 'theme-' + theme.name
544 end
544 end
545
545
546 css << 'project-' + @project.identifier if @project && @project.identifier.present?
546 css << 'project-' + @project.identifier if @project && @project.identifier.present?
547 css << 'controller-' + controller_name
547 css << 'controller-' + controller_name
548 css << 'action-' + action_name
548 css << 'action-' + action_name
549 css.join(' ')
549 css.join(' ')
550 end
550 end
551
551
552 def accesskey(s)
552 def accesskey(s)
553 @used_accesskeys ||= []
553 @used_accesskeys ||= []
554 key = Redmine::AccessKeys.key_for(s)
554 key = Redmine::AccessKeys.key_for(s)
555 return nil if @used_accesskeys.include?(key)
555 return nil if @used_accesskeys.include?(key)
556 @used_accesskeys << key
556 @used_accesskeys << key
557 key
557 key
558 end
558 end
559
559
560 # Formats text according to system settings.
560 # Formats text according to system settings.
561 # 2 ways to call this method:
561 # 2 ways to call this method:
562 # * with a String: textilizable(text, options)
562 # * with a String: textilizable(text, options)
563 # * with an object and one of its attribute: textilizable(issue, :description, options)
563 # * with an object and one of its attribute: textilizable(issue, :description, options)
564 def textilizable(*args)
564 def textilizable(*args)
565 options = args.last.is_a?(Hash) ? args.pop : {}
565 options = args.last.is_a?(Hash) ? args.pop : {}
566 case args.size
566 case args.size
567 when 1
567 when 1
568 obj = options[:object]
568 obj = options[:object]
569 text = args.shift
569 text = args.shift
570 when 2
570 when 2
571 obj = args.shift
571 obj = args.shift
572 attr = args.shift
572 attr = args.shift
573 text = obj.send(attr).to_s
573 text = obj.send(attr).to_s
574 else
574 else
575 raise ArgumentError, 'invalid arguments to textilizable'
575 raise ArgumentError, 'invalid arguments to textilizable'
576 end
576 end
577 return '' if text.blank?
577 return '' if text.blank?
578 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
578 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
579 only_path = options.delete(:only_path) == false ? false : true
579 only_path = options.delete(:only_path) == false ? false : true
580
580
581 text = text.dup
581 text = text.dup
582 macros = catch_macros(text)
582 macros = catch_macros(text)
583 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
583 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
584
584
585 @parsed_headings = []
585 @parsed_headings = []
586 @heading_anchors = {}
586 @heading_anchors = {}
587 @current_section = 0 if options[:edit_section_links]
587 @current_section = 0 if options[:edit_section_links]
588
588
589 parse_sections(text, project, obj, attr, only_path, options)
589 parse_sections(text, project, obj, attr, only_path, options)
590 text = parse_non_pre_blocks(text, obj, macros) do |text|
590 text = parse_non_pre_blocks(text, obj, macros) do |text|
591 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
591 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
592 send method_name, text, project, obj, attr, only_path, options
592 send method_name, text, project, obj, attr, only_path, options
593 end
593 end
594 end
594 end
595 parse_headings(text, project, obj, attr, only_path, options)
595 parse_headings(text, project, obj, attr, only_path, options)
596
596
597 if @parsed_headings.any?
597 if @parsed_headings.any?
598 replace_toc(text, @parsed_headings)
598 replace_toc(text, @parsed_headings)
599 end
599 end
600
600
601 text.html_safe
601 text.html_safe
602 end
602 end
603
603
604 def parse_non_pre_blocks(text, obj, macros)
604 def parse_non_pre_blocks(text, obj, macros)
605 s = StringScanner.new(text)
605 s = StringScanner.new(text)
606 tags = []
606 tags = []
607 parsed = ''
607 parsed = ''
608 while !s.eos?
608 while !s.eos?
609 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
609 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
610 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
610 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
611 if tags.empty?
611 if tags.empty?
612 yield text
612 yield text
613 inject_macros(text, obj, macros) if macros.any?
613 inject_macros(text, obj, macros) if macros.any?
614 else
614 else
615 inject_macros(text, obj, macros, false) if macros.any?
615 inject_macros(text, obj, macros, false) if macros.any?
616 end
616 end
617 parsed << text
617 parsed << text
618 if tag
618 if tag
619 if closing
619 if closing
620 if tags.last == tag.downcase
620 if tags.last == tag.downcase
621 tags.pop
621 tags.pop
622 end
622 end
623 else
623 else
624 tags << tag.downcase
624 tags << tag.downcase
625 end
625 end
626 parsed << full_tag
626 parsed << full_tag
627 end
627 end
628 end
628 end
629 # Close any non closing tags
629 # Close any non closing tags
630 while tag = tags.pop
630 while tag = tags.pop
631 parsed << "</#{tag}>"
631 parsed << "</#{tag}>"
632 end
632 end
633 parsed
633 parsed
634 end
634 end
635
635
636 def parse_inline_attachments(text, project, obj, attr, only_path, options)
636 def parse_inline_attachments(text, project, obj, attr, only_path, options)
637 # when using an image link, try to use an attachment, if possible
637 # when using an image link, try to use an attachment, if possible
638 attachments = options[:attachments] || []
638 attachments = options[:attachments] || []
639 attachments += obj.attachments if obj.respond_to?(:attachments)
639 attachments += obj.attachments if obj.respond_to?(:attachments)
640 if attachments.present?
640 if attachments.present?
641 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
641 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
642 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
642 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
643 # search for the picture in attachments
643 # search for the picture in attachments
644 if found = Attachment.latest_attach(attachments, filename)
644 if found = Attachment.latest_attach(attachments, filename)
645 image_url = download_named_attachment_path(found, found.filename, :only_path => only_path)
645 image_url = download_named_attachment_path(found, found.filename, :only_path => only_path)
646 desc = found.description.to_s.gsub('"', '')
646 desc = found.description.to_s.gsub('"', '')
647 if !desc.blank? && alttext.blank?
647 if !desc.blank? && alttext.blank?
648 alt = " title=\"#{desc}\" alt=\"#{desc}\""
648 alt = " title=\"#{desc}\" alt=\"#{desc}\""
649 end
649 end
650 "src=\"#{image_url}\"#{alt}"
650 "src=\"#{image_url}\"#{alt}"
651 else
651 else
652 m
652 m
653 end
653 end
654 end
654 end
655 end
655 end
656 end
656 end
657
657
658 # Wiki links
658 # Wiki links
659 #
659 #
660 # Examples:
660 # Examples:
661 # [[mypage]]
661 # [[mypage]]
662 # [[mypage|mytext]]
662 # [[mypage|mytext]]
663 # wiki links can refer other project wikis, using project name or identifier:
663 # wiki links can refer other project wikis, using project name or identifier:
664 # [[project:]] -> wiki starting page
664 # [[project:]] -> wiki starting page
665 # [[project:|mytext]]
665 # [[project:|mytext]]
666 # [[project:mypage]]
666 # [[project:mypage]]
667 # [[project:mypage|mytext]]
667 # [[project:mypage|mytext]]
668 def parse_wiki_links(text, project, obj, attr, only_path, options)
668 def parse_wiki_links(text, project, obj, attr, only_path, options)
669 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
669 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
670 link_project = project
670 link_project = project
671 esc, all, page, title = $1, $2, $3, $5
671 esc, all, page, title = $1, $2, $3, $5
672 if esc.nil?
672 if esc.nil?
673 if page =~ /^([^\:]+)\:(.*)$/
673 if page =~ /^([^\:]+)\:(.*)$/
674 identifier, page = $1, $2
674 identifier, page = $1, $2
675 link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
675 link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
676 title ||= identifier if page.blank?
676 title ||= identifier if page.blank?
677 end
677 end
678
678
679 if link_project && link_project.wiki
679 if link_project && link_project.wiki
680 # extract anchor
680 # extract anchor
681 anchor = nil
681 anchor = nil
682 if page =~ /^(.+?)\#(.+)$/
682 if page =~ /^(.+?)\#(.+)$/
683 page, anchor = $1, $2
683 page, anchor = $1, $2
684 end
684 end
685 anchor = sanitize_anchor_name(anchor) if anchor.present?
685 anchor = sanitize_anchor_name(anchor) if anchor.present?
686 # check if page exists
686 # check if page exists
687 wiki_page = link_project.wiki.find_page(page)
687 wiki_page = link_project.wiki.find_page(page)
688 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
688 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
689 "##{anchor}"
689 "##{anchor}"
690 else
690 else
691 case options[:wiki_links]
691 case options[:wiki_links]
692 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
692 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
693 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
693 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
694 else
694 else
695 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
695 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
696 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
696 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
697 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
697 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
698 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
698 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
699 end
699 end
700 end
700 end
701 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
701 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
702 else
702 else
703 # project or wiki doesn't exist
703 # project or wiki doesn't exist
704 all
704 all
705 end
705 end
706 else
706 else
707 all
707 all
708 end
708 end
709 end
709 end
710 end
710 end
711
711
712 # Redmine links
712 # Redmine links
713 #
713 #
714 # Examples:
714 # Examples:
715 # Issues:
715 # Issues:
716 # #52 -> Link to issue #52
716 # #52 -> Link to issue #52
717 # Changesets:
717 # Changesets:
718 # r52 -> Link to revision 52
718 # r52 -> Link to revision 52
719 # commit:a85130f -> Link to scmid starting with a85130f
719 # commit:a85130f -> Link to scmid starting with a85130f
720 # Documents:
720 # Documents:
721 # document#17 -> Link to document with id 17
721 # document#17 -> Link to document with id 17
722 # document:Greetings -> Link to the document with title "Greetings"
722 # document:Greetings -> Link to the document with title "Greetings"
723 # document:"Some document" -> Link to the document with title "Some document"
723 # document:"Some document" -> Link to the document with title "Some document"
724 # Versions:
724 # Versions:
725 # version#3 -> Link to version with id 3
725 # version#3 -> Link to version with id 3
726 # version:1.0.0 -> Link to version named "1.0.0"
726 # version:1.0.0 -> Link to version named "1.0.0"
727 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
727 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
728 # Attachments:
728 # Attachments:
729 # attachment:file.zip -> Link to the attachment of the current object named file.zip
729 # attachment:file.zip -> Link to the attachment of the current object named file.zip
730 # Source files:
730 # Source files:
731 # source:some/file -> Link to the file located at /some/file in the project's repository
731 # source:some/file -> Link to the file located at /some/file in the project's repository
732 # source:some/file@52 -> Link to the file's revision 52
732 # source:some/file@52 -> Link to the file's revision 52
733 # source:some/file#L120 -> Link to line 120 of the file
733 # source:some/file#L120 -> Link to line 120 of the file
734 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
734 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
735 # export:some/file -> Force the download of the file
735 # export:some/file -> Force the download of the file
736 # Forum messages:
736 # Forum messages:
737 # message#1218 -> Link to message with id 1218
737 # message#1218 -> Link to message with id 1218
738 # Projects:
738 # Projects:
739 # project:someproject -> Link to project named "someproject"
739 # project:someproject -> Link to project named "someproject"
740 # project#3 -> Link to project with id 3
740 # project#3 -> Link to project with id 3
741 #
741 #
742 # Links can refer other objects from other projects, using project identifier:
742 # Links can refer other objects from other projects, using project identifier:
743 # identifier:r52
743 # identifier:r52
744 # identifier:document:"Some document"
744 # identifier:document:"Some document"
745 # identifier:version:1.0.0
745 # identifier:version:1.0.0
746 # identifier:source:some/file
746 # identifier:source:some/file
747 def parse_redmine_links(text, default_project, obj, attr, only_path, options)
747 def parse_redmine_links(text, default_project, obj, attr, only_path, options)
748 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
748 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
749 leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
749 leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
750 link = nil
750 link = nil
751 project = default_project
751 project = default_project
752 if project_identifier
752 if project_identifier
753 project = Project.visible.find_by_identifier(project_identifier)
753 project = Project.visible.find_by_identifier(project_identifier)
754 end
754 end
755 if esc.nil?
755 if esc.nil?
756 if prefix.nil? && sep == 'r'
756 if prefix.nil? && sep == 'r'
757 if project
757 if project
758 repository = nil
758 repository = nil
759 if repo_identifier
759 if repo_identifier
760 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
760 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
761 else
761 else
762 repository = project.repository
762 repository = project.repository
763 end
763 end
764 # project.changesets.visible raises an SQL error because of a double join on repositories
764 # project.changesets.visible raises an SQL error because of a double join on repositories
765 if repository &&
765 if repository &&
766 (changeset = Changeset.visible.
766 (changeset = Changeset.visible.
767 find_by_repository_id_and_revision(repository.id, identifier))
767 find_by_repository_id_and_revision(repository.id, identifier))
768 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"),
768 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"),
769 {:only_path => only_path, :controller => 'repositories',
769 {:only_path => only_path, :controller => 'repositories',
770 :action => 'revision', :id => project,
770 :action => 'revision', :id => project,
771 :repository_id => repository.identifier_param,
771 :repository_id => repository.identifier_param,
772 :rev => changeset.revision},
772 :rev => changeset.revision},
773 :class => 'changeset',
773 :class => 'changeset',
774 :title => truncate_single_line_raw(changeset.comments, 100))
774 :title => truncate_single_line_raw(changeset.comments, 100))
775 end
775 end
776 end
776 end
777 elsif sep == '#'
777 elsif sep == '#'
778 oid = identifier.to_i
778 oid = identifier.to_i
779 case prefix
779 case prefix
780 when nil
780 when nil
781 if oid.to_s == identifier &&
781 if oid.to_s == identifier &&
782 issue = Issue.visible.includes(:status).find_by_id(oid)
782 issue = Issue.visible.includes(:status).find_by_id(oid)
783 anchor = comment_id ? "note-#{comment_id}" : nil
783 anchor = comment_id ? "note-#{comment_id}" : nil
784 link = link_to(h("##{oid}#{comment_suffix}"),
784 link = link_to(h("##{oid}#{comment_suffix}"),
785 {:only_path => only_path, :controller => 'issues',
785 {:only_path => only_path, :controller => 'issues',
786 :action => 'show', :id => oid, :anchor => anchor},
786 :action => 'show', :id => oid, :anchor => anchor},
787 :class => issue.css_classes,
787 :class => issue.css_classes,
788 :title => "#{issue.subject.truncate(100)} (#{issue.status.name})")
788 :title => "#{issue.subject.truncate(100)} (#{issue.status.name})")
789 end
789 end
790 when 'document'
790 when 'document'
791 if document = Document.visible.find_by_id(oid)
791 if document = Document.visible.find_by_id(oid)
792 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
792 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
793 :class => 'document'
793 :class => 'document'
794 end
794 end
795 when 'version'
795 when 'version'
796 if version = Version.visible.find_by_id(oid)
796 if version = Version.visible.find_by_id(oid)
797 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
797 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
798 :class => 'version'
798 :class => 'version'
799 end
799 end
800 when 'message'
800 when 'message'
801 if message = Message.visible.includes(:parent).find_by_id(oid)
801 if message = Message.visible.includes(:parent).find_by_id(oid)
802 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
802 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
803 end
803 end
804 when 'forum'
804 when 'forum'
805 if board = Board.visible.find_by_id(oid)
805 if board = Board.visible.find_by_id(oid)
806 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
806 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
807 :class => 'board'
807 :class => 'board'
808 end
808 end
809 when 'news'
809 when 'news'
810 if news = News.visible.find_by_id(oid)
810 if news = News.visible.find_by_id(oid)
811 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
811 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
812 :class => 'news'
812 :class => 'news'
813 end
813 end
814 when 'project'
814 when 'project'
815 if p = Project.visible.find_by_id(oid)
815 if p = Project.visible.find_by_id(oid)
816 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
816 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
817 end
817 end
818 end
818 end
819 elsif sep == ':'
819 elsif sep == ':'
820 # removes the double quotes if any
820 # removes the double quotes if any
821 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
821 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
822 case prefix
822 case prefix
823 when 'document'
823 when 'document'
824 if project && document = project.documents.visible.find_by_title(name)
824 if project && document = project.documents.visible.find_by_title(name)
825 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
825 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
826 :class => 'document'
826 :class => 'document'
827 end
827 end
828 when 'version'
828 when 'version'
829 if project && version = project.versions.visible.find_by_name(name)
829 if project && version = project.versions.visible.find_by_name(name)
830 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
830 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
831 :class => 'version'
831 :class => 'version'
832 end
832 end
833 when 'forum'
833 when 'forum'
834 if project && board = project.boards.visible.find_by_name(name)
834 if project && board = project.boards.visible.find_by_name(name)
835 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
835 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
836 :class => 'board'
836 :class => 'board'
837 end
837 end
838 when 'news'
838 when 'news'
839 if project && news = project.news.visible.find_by_title(name)
839 if project && news = project.news.visible.find_by_title(name)
840 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
840 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
841 :class => 'news'
841 :class => 'news'
842 end
842 end
843 when 'commit', 'source', 'export'
843 when 'commit', 'source', 'export'
844 if project
844 if project
845 repository = nil
845 repository = nil
846 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
846 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
847 repo_prefix, repo_identifier, name = $1, $2, $3
847 repo_prefix, repo_identifier, name = $1, $2, $3
848 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
848 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
849 else
849 else
850 repository = project.repository
850 repository = project.repository
851 end
851 end
852 if prefix == 'commit'
852 if prefix == 'commit'
853 if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
853 if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
854 link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
854 link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
855 :class => 'changeset',
855 :class => 'changeset',
856 :title => truncate_single_line_raw(changeset.comments, 100)
856 :title => truncate_single_line_raw(changeset.comments, 100)
857 end
857 end
858 else
858 else
859 if repository && User.current.allowed_to?(:browse_repository, project)
859 if repository && User.current.allowed_to?(:browse_repository, project)
860 name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
860 name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
861 path, rev, anchor = $1, $3, $5
861 path, rev, anchor = $1, $3, $5
862 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param,
862 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param,
863 :path => to_path_param(path),
863 :path => to_path_param(path),
864 :rev => rev,
864 :rev => rev,
865 :anchor => anchor},
865 :anchor => anchor},
866 :class => (prefix == 'export' ? 'source download' : 'source')
866 :class => (prefix == 'export' ? 'source download' : 'source')
867 end
867 end
868 end
868 end
869 repo_prefix = nil
869 repo_prefix = nil
870 end
870 end
871 when 'attachment'
871 when 'attachment'
872 attachments = options[:attachments] || []
872 attachments = options[:attachments] || []
873 attachments += obj.attachments if obj.respond_to?(:attachments)
873 attachments += obj.attachments if obj.respond_to?(:attachments)
874 if attachments && attachment = Attachment.latest_attach(attachments, name)
874 if attachments && attachment = Attachment.latest_attach(attachments, name)
875 link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
875 link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
876 end
876 end
877 when 'project'
877 when 'project'
878 if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
878 if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
879 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
879 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
880 end
880 end
881 end
881 end
882 end
882 end
883 end
883 end
884 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
884 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
885 end
885 end
886 end
886 end
887
887
888 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
888 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
889
889
890 def parse_sections(text, project, obj, attr, only_path, options)
890 def parse_sections(text, project, obj, attr, only_path, options)
891 return unless options[:edit_section_links]
891 return unless options[:edit_section_links]
892 text.gsub!(HEADING_RE) do
892 text.gsub!(HEADING_RE) do
893 heading = $1
893 heading = $1
894 @current_section += 1
894 @current_section += 1
895 if @current_section > 1
895 if @current_section > 1
896 content_tag('div',
896 content_tag('div',
897 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
897 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
898 :class => 'contextual',
898 :class => 'contextual',
899 :title => l(:button_edit_section),
899 :title => l(:button_edit_section),
900 :id => "section-#{@current_section}") + heading.html_safe
900 :id => "section-#{@current_section}") + heading.html_safe
901 else
901 else
902 heading
902 heading
903 end
903 end
904 end
904 end
905 end
905 end
906
906
907 # Headings and TOC
907 # Headings and TOC
908 # Adds ids and links to headings unless options[:headings] is set to false
908 # Adds ids and links to headings unless options[:headings] is set to false
909 def parse_headings(text, project, obj, attr, only_path, options)
909 def parse_headings(text, project, obj, attr, only_path, options)
910 return if options[:headings] == false
910 return if options[:headings] == false
911
911
912 text.gsub!(HEADING_RE) do
912 text.gsub!(HEADING_RE) do
913 level, attrs, content = $2.to_i, $3, $4
913 level, attrs, content = $2.to_i, $3, $4
914 item = strip_tags(content).strip
914 item = strip_tags(content).strip
915 anchor = sanitize_anchor_name(item)
915 anchor = sanitize_anchor_name(item)
916 # used for single-file wiki export
916 # used for single-file wiki export
917 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
917 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
918 @heading_anchors[anchor] ||= 0
918 @heading_anchors[anchor] ||= 0
919 idx = (@heading_anchors[anchor] += 1)
919 idx = (@heading_anchors[anchor] += 1)
920 if idx > 1
920 if idx > 1
921 anchor = "#{anchor}-#{idx}"
921 anchor = "#{anchor}-#{idx}"
922 end
922 end
923 @parsed_headings << [level, anchor, item]
923 @parsed_headings << [level, anchor, item]
924 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
924 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
925 end
925 end
926 end
926 end
927
927
928 MACROS_RE = /(
928 MACROS_RE = /(
929 (!)? # escaping
929 (!)? # escaping
930 (
930 (
931 \{\{ # opening tag
931 \{\{ # opening tag
932 ([\w]+) # macro name
932 ([\w]+) # macro name
933 (\(([^\n\r]*?)\))? # optional arguments
933 (\(([^\n\r]*?)\))? # optional arguments
934 ([\n\r].*?[\n\r])? # optional block of text
934 ([\n\r].*?[\n\r])? # optional block of text
935 \}\} # closing tag
935 \}\} # closing tag
936 )
936 )
937 )/mx unless const_defined?(:MACROS_RE)
937 )/mx unless const_defined?(:MACROS_RE)
938
938
939 MACRO_SUB_RE = /(
939 MACRO_SUB_RE = /(
940 \{\{
940 \{\{
941 macro\((\d+)\)
941 macro\((\d+)\)
942 \}\}
942 \}\}
943 )/x unless const_defined?(:MACRO_SUB_RE)
943 )/x unless const_defined?(:MACRO_SUB_RE)
944
944
945 # Extracts macros from text
945 # Extracts macros from text
946 def catch_macros(text)
946 def catch_macros(text)
947 macros = {}
947 macros = {}
948 text.gsub!(MACROS_RE) do
948 text.gsub!(MACROS_RE) do
949 all, macro = $1, $4.downcase
949 all, macro = $1, $4.downcase
950 if macro_exists?(macro) || all =~ MACRO_SUB_RE
950 if macro_exists?(macro) || all =~ MACRO_SUB_RE
951 index = macros.size
951 index = macros.size
952 macros[index] = all
952 macros[index] = all
953 "{{macro(#{index})}}"
953 "{{macro(#{index})}}"
954 else
954 else
955 all
955 all
956 end
956 end
957 end
957 end
958 macros
958 macros
959 end
959 end
960
960
961 # Executes and replaces macros in text
961 # Executes and replaces macros in text
962 def inject_macros(text, obj, macros, execute=true)
962 def inject_macros(text, obj, macros, execute=true)
963 text.gsub!(MACRO_SUB_RE) do
963 text.gsub!(MACRO_SUB_RE) do
964 all, index = $1, $2.to_i
964 all, index = $1, $2.to_i
965 orig = macros.delete(index)
965 orig = macros.delete(index)
966 if execute && orig && orig =~ MACROS_RE
966 if execute && orig && orig =~ MACROS_RE
967 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
967 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
968 if esc.nil?
968 if esc.nil?
969 h(exec_macro(macro, obj, args, block) || all)
969 h(exec_macro(macro, obj, args, block) || all)
970 else
970 else
971 h(all)
971 h(all)
972 end
972 end
973 elsif orig
973 elsif orig
974 h(orig)
974 h(orig)
975 else
975 else
976 h(all)
976 h(all)
977 end
977 end
978 end
978 end
979 end
979 end
980
980
981 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
981 TOC_RE = /<p>\{\{((<|&lt;)|(>|&gt;))?toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
982
982
983 # Renders the TOC with given headings
983 # Renders the TOC with given headings
984 def replace_toc(text, headings)
984 def replace_toc(text, headings)
985 text.gsub!(TOC_RE) do
985 text.gsub!(TOC_RE) do
986 left_align, right_align = $2, $3
986 # Keep only the 4 first levels
987 # Keep only the 4 first levels
987 headings = headings.select{|level, anchor, item| level <= 4}
988 headings = headings.select{|level, anchor, item| level <= 4}
988 if headings.empty?
989 if headings.empty?
989 ''
990 ''
990 else
991 else
991 div_class = 'toc'
992 div_class = 'toc'
992 div_class << ' right' if $1 == '>'
993 div_class << ' right' if right_align
993 div_class << ' left' if $1 == '<'
994 div_class << ' left' if left_align
994 out = "<ul class=\"#{div_class}\"><li>"
995 out = "<ul class=\"#{div_class}\"><li>"
995 root = headings.map(&:first).min
996 root = headings.map(&:first).min
996 current = root
997 current = root
997 started = false
998 started = false
998 headings.each do |level, anchor, item|
999 headings.each do |level, anchor, item|
999 if level > current
1000 if level > current
1000 out << '<ul><li>' * (level - current)
1001 out << '<ul><li>' * (level - current)
1001 elsif level < current
1002 elsif level < current
1002 out << "</li></ul>\n" * (current - level) + "</li><li>"
1003 out << "</li></ul>\n" * (current - level) + "</li><li>"
1003 elsif started
1004 elsif started
1004 out << '</li><li>'
1005 out << '</li><li>'
1005 end
1006 end
1006 out << "<a href=\"##{anchor}\">#{item}</a>"
1007 out << "<a href=\"##{anchor}\">#{item}</a>"
1007 current = level
1008 current = level
1008 started = true
1009 started = true
1009 end
1010 end
1010 out << '</li></ul>' * (current - root)
1011 out << '</li></ul>' * (current - root)
1011 out << '</li></ul>'
1012 out << '</li></ul>'
1012 end
1013 end
1013 end
1014 end
1014 end
1015 end
1015
1016
1016 # Same as Rails' simple_format helper without using paragraphs
1017 # Same as Rails' simple_format helper without using paragraphs
1017 def simple_format_without_paragraph(text)
1018 def simple_format_without_paragraph(text)
1018 text.to_s.
1019 text.to_s.
1019 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
1020 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
1020 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
1021 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
1021 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
1022 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
1022 html_safe
1023 html_safe
1023 end
1024 end
1024
1025
1025 def lang_options_for_select(blank=true)
1026 def lang_options_for_select(blank=true)
1026 (blank ? [["(auto)", ""]] : []) + languages_options
1027 (blank ? [["(auto)", ""]] : []) + languages_options
1027 end
1028 end
1028
1029
1029 def label_tag_for(name, option_tags = nil, options = {})
1030 def label_tag_for(name, option_tags = nil, options = {})
1030 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
1031 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
1031 content_tag("label", label_text)
1032 content_tag("label", label_text)
1032 end
1033 end
1033
1034
1034 def labelled_form_for(*args, &proc)
1035 def labelled_form_for(*args, &proc)
1035 args << {} unless args.last.is_a?(Hash)
1036 args << {} unless args.last.is_a?(Hash)
1036 options = args.last
1037 options = args.last
1037 if args.first.is_a?(Symbol)
1038 if args.first.is_a?(Symbol)
1038 options.merge!(:as => args.shift)
1039 options.merge!(:as => args.shift)
1039 end
1040 end
1040 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1041 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1041 form_for(*args, &proc)
1042 form_for(*args, &proc)
1042 end
1043 end
1043
1044
1044 def labelled_fields_for(*args, &proc)
1045 def labelled_fields_for(*args, &proc)
1045 args << {} unless args.last.is_a?(Hash)
1046 args << {} unless args.last.is_a?(Hash)
1046 options = args.last
1047 options = args.last
1047 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1048 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1048 fields_for(*args, &proc)
1049 fields_for(*args, &proc)
1049 end
1050 end
1050
1051
1051 def labelled_remote_form_for(*args, &proc)
1052 def labelled_remote_form_for(*args, &proc)
1052 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
1053 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
1053 args << {} unless args.last.is_a?(Hash)
1054 args << {} unless args.last.is_a?(Hash)
1054 options = args.last
1055 options = args.last
1055 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
1056 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
1056 form_for(*args, &proc)
1057 form_for(*args, &proc)
1057 end
1058 end
1058
1059
1059 def error_messages_for(*objects)
1060 def error_messages_for(*objects)
1060 html = ""
1061 html = ""
1061 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
1062 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
1062 errors = objects.map {|o| o.errors.full_messages}.flatten
1063 errors = objects.map {|o| o.errors.full_messages}.flatten
1063 if errors.any?
1064 if errors.any?
1064 html << "<div id='errorExplanation'><ul>\n"
1065 html << "<div id='errorExplanation'><ul>\n"
1065 errors.each do |error|
1066 errors.each do |error|
1066 html << "<li>#{h error}</li>\n"
1067 html << "<li>#{h error}</li>\n"
1067 end
1068 end
1068 html << "</ul></div>\n"
1069 html << "</ul></div>\n"
1069 end
1070 end
1070 html.html_safe
1071 html.html_safe
1071 end
1072 end
1072
1073
1073 def delete_link(url, options={})
1074 def delete_link(url, options={})
1074 options = {
1075 options = {
1075 :method => :delete,
1076 :method => :delete,
1076 :data => {:confirm => l(:text_are_you_sure)},
1077 :data => {:confirm => l(:text_are_you_sure)},
1077 :class => 'icon icon-del'
1078 :class => 'icon icon-del'
1078 }.merge(options)
1079 }.merge(options)
1079
1080
1080 link_to l(:button_delete), url, options
1081 link_to l(:button_delete), url, options
1081 end
1082 end
1082
1083
1083 def preview_link(url, form, target='preview', options={})
1084 def preview_link(url, form, target='preview', options={})
1084 content_tag 'a', l(:label_preview), {
1085 content_tag 'a', l(:label_preview), {
1085 :href => "#",
1086 :href => "#",
1086 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1087 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1087 :accesskey => accesskey(:preview)
1088 :accesskey => accesskey(:preview)
1088 }.merge(options)
1089 }.merge(options)
1089 end
1090 end
1090
1091
1091 def link_to_function(name, function, html_options={})
1092 def link_to_function(name, function, html_options={})
1092 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1093 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1093 end
1094 end
1094
1095
1095 # Helper to render JSON in views
1096 # Helper to render JSON in views
1096 def raw_json(arg)
1097 def raw_json(arg)
1097 arg.to_json.to_s.gsub('/', '\/').html_safe
1098 arg.to_json.to_s.gsub('/', '\/').html_safe
1098 end
1099 end
1099
1100
1100 def back_url
1101 def back_url
1101 url = params[:back_url]
1102 url = params[:back_url]
1102 if url.nil? && referer = request.env['HTTP_REFERER']
1103 if url.nil? && referer = request.env['HTTP_REFERER']
1103 url = CGI.unescape(referer.to_s)
1104 url = CGI.unescape(referer.to_s)
1104 end
1105 end
1105 url
1106 url
1106 end
1107 end
1107
1108
1108 def back_url_hidden_field_tag
1109 def back_url_hidden_field_tag
1109 url = back_url
1110 url = back_url
1110 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1111 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1111 end
1112 end
1112
1113
1113 def check_all_links(form_name)
1114 def check_all_links(form_name)
1114 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1115 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1115 " | ".html_safe +
1116 " | ".html_safe +
1116 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1117 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1117 end
1118 end
1118
1119
1119 def progress_bar(pcts, options={})
1120 def progress_bar(pcts, options={})
1120 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1121 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1121 pcts = pcts.collect(&:round)
1122 pcts = pcts.collect(&:round)
1122 pcts[1] = pcts[1] - pcts[0]
1123 pcts[1] = pcts[1] - pcts[0]
1123 pcts << (100 - pcts[1] - pcts[0])
1124 pcts << (100 - pcts[1] - pcts[0])
1124 width = options[:width] || '100px;'
1125 width = options[:width] || '100px;'
1125 legend = options[:legend] || ''
1126 legend = options[:legend] || ''
1126 content_tag('table',
1127 content_tag('table',
1127 content_tag('tr',
1128 content_tag('tr',
1128 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1129 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1129 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1130 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1130 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1131 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1131 ), :class => "progress progress-#{pcts[0]}", :style => "width: #{width};").html_safe +
1132 ), :class => "progress progress-#{pcts[0]}", :style => "width: #{width};").html_safe +
1132 content_tag('p', legend, :class => 'percent').html_safe
1133 content_tag('p', legend, :class => 'percent').html_safe
1133 end
1134 end
1134
1135
1135 def checked_image(checked=true)
1136 def checked_image(checked=true)
1136 if checked
1137 if checked
1137 image_tag 'toggle_check.png'
1138 image_tag 'toggle_check.png'
1138 end
1139 end
1139 end
1140 end
1140
1141
1141 def context_menu(url)
1142 def context_menu(url)
1142 unless @context_menu_included
1143 unless @context_menu_included
1143 content_for :header_tags do
1144 content_for :header_tags do
1144 javascript_include_tag('context_menu') +
1145 javascript_include_tag('context_menu') +
1145 stylesheet_link_tag('context_menu')
1146 stylesheet_link_tag('context_menu')
1146 end
1147 end
1147 if l(:direction) == 'rtl'
1148 if l(:direction) == 'rtl'
1148 content_for :header_tags do
1149 content_for :header_tags do
1149 stylesheet_link_tag('context_menu_rtl')
1150 stylesheet_link_tag('context_menu_rtl')
1150 end
1151 end
1151 end
1152 end
1152 @context_menu_included = true
1153 @context_menu_included = true
1153 end
1154 end
1154 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1155 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1155 end
1156 end
1156
1157
1157 def calendar_for(field_id)
1158 def calendar_for(field_id)
1158 include_calendar_headers_tags
1159 include_calendar_headers_tags
1159 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
1160 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
1160 end
1161 end
1161
1162
1162 def include_calendar_headers_tags
1163 def include_calendar_headers_tags
1163 unless @calendar_headers_tags_included
1164 unless @calendar_headers_tags_included
1164 tags = javascript_include_tag("datepicker")
1165 tags = javascript_include_tag("datepicker")
1165 @calendar_headers_tags_included = true
1166 @calendar_headers_tags_included = true
1166 content_for :header_tags do
1167 content_for :header_tags do
1167 start_of_week = Setting.start_of_week
1168 start_of_week = Setting.start_of_week
1168 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1169 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1169 # Redmine uses 1..7 (monday..sunday) in settings and locales
1170 # Redmine uses 1..7 (monday..sunday) in settings and locales
1170 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1171 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1171 start_of_week = start_of_week.to_i % 7
1172 start_of_week = start_of_week.to_i % 7
1172 tags << javascript_tag(
1173 tags << javascript_tag(
1173 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1174 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1174 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1175 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1175 path_to_image('/images/calendar.png') +
1176 path_to_image('/images/calendar.png') +
1176 "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " +
1177 "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " +
1177 "selectOtherMonths: true, changeMonth: true, changeYear: true, " +
1178 "selectOtherMonths: true, changeMonth: true, changeYear: true, " +
1178 "beforeShow: beforeShowDatePicker};")
1179 "beforeShow: beforeShowDatePicker};")
1179 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1180 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1180 unless jquery_locale == 'en'
1181 unless jquery_locale == 'en'
1181 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
1182 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
1182 end
1183 end
1183 tags
1184 tags
1184 end
1185 end
1185 end
1186 end
1186 end
1187 end
1187
1188
1188 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1189 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1189 # Examples:
1190 # Examples:
1190 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1191 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1191 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1192 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1192 #
1193 #
1193 def stylesheet_link_tag(*sources)
1194 def stylesheet_link_tag(*sources)
1194 options = sources.last.is_a?(Hash) ? sources.pop : {}
1195 options = sources.last.is_a?(Hash) ? sources.pop : {}
1195 plugin = options.delete(:plugin)
1196 plugin = options.delete(:plugin)
1196 sources = sources.map do |source|
1197 sources = sources.map do |source|
1197 if plugin
1198 if plugin
1198 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1199 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1199 elsif current_theme && current_theme.stylesheets.include?(source)
1200 elsif current_theme && current_theme.stylesheets.include?(source)
1200 current_theme.stylesheet_path(source)
1201 current_theme.stylesheet_path(source)
1201 else
1202 else
1202 source
1203 source
1203 end
1204 end
1204 end
1205 end
1205 super sources, options
1206 super sources, options
1206 end
1207 end
1207
1208
1208 # Overrides Rails' image_tag with themes and plugins support.
1209 # Overrides Rails' image_tag with themes and plugins support.
1209 # Examples:
1210 # Examples:
1210 # image_tag('image.png') # => picks image.png from the current theme or defaults
1211 # image_tag('image.png') # => picks image.png from the current theme or defaults
1211 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1212 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1212 #
1213 #
1213 def image_tag(source, options={})
1214 def image_tag(source, options={})
1214 if plugin = options.delete(:plugin)
1215 if plugin = options.delete(:plugin)
1215 source = "/plugin_assets/#{plugin}/images/#{source}"
1216 source = "/plugin_assets/#{plugin}/images/#{source}"
1216 elsif current_theme && current_theme.images.include?(source)
1217 elsif current_theme && current_theme.images.include?(source)
1217 source = current_theme.image_path(source)
1218 source = current_theme.image_path(source)
1218 end
1219 end
1219 super source, options
1220 super source, options
1220 end
1221 end
1221
1222
1222 # Overrides Rails' javascript_include_tag with plugins support
1223 # Overrides Rails' javascript_include_tag with plugins support
1223 # Examples:
1224 # Examples:
1224 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1225 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1225 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1226 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1226 #
1227 #
1227 def javascript_include_tag(*sources)
1228 def javascript_include_tag(*sources)
1228 options = sources.last.is_a?(Hash) ? sources.pop : {}
1229 options = sources.last.is_a?(Hash) ? sources.pop : {}
1229 if plugin = options.delete(:plugin)
1230 if plugin = options.delete(:plugin)
1230 sources = sources.map do |source|
1231 sources = sources.map do |source|
1231 if plugin
1232 if plugin
1232 "/plugin_assets/#{plugin}/javascripts/#{source}"
1233 "/plugin_assets/#{plugin}/javascripts/#{source}"
1233 else
1234 else
1234 source
1235 source
1235 end
1236 end
1236 end
1237 end
1237 end
1238 end
1238 super sources, options
1239 super sources, options
1239 end
1240 end
1240
1241
1241 # TODO: remove this in 2.5.0
1242 # TODO: remove this in 2.5.0
1242 def has_content?(name)
1243 def has_content?(name)
1243 content_for?(name)
1244 content_for?(name)
1244 end
1245 end
1245
1246
1246 def sidebar_content?
1247 def sidebar_content?
1247 content_for?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1248 content_for?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1248 end
1249 end
1249
1250
1250 def view_layouts_base_sidebar_hook_response
1251 def view_layouts_base_sidebar_hook_response
1251 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1252 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1252 end
1253 end
1253
1254
1254 def email_delivery_enabled?
1255 def email_delivery_enabled?
1255 !!ActionMailer::Base.perform_deliveries
1256 !!ActionMailer::Base.perform_deliveries
1256 end
1257 end
1257
1258
1258 # Returns the avatar image tag for the given +user+ if avatars are enabled
1259 # Returns the avatar image tag for the given +user+ if avatars are enabled
1259 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1260 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1260 def avatar(user, options = { })
1261 def avatar(user, options = { })
1261 if Setting.gravatar_enabled?
1262 if Setting.gravatar_enabled?
1262 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1263 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1263 email = nil
1264 email = nil
1264 if user.respond_to?(:mail)
1265 if user.respond_to?(:mail)
1265 email = user.mail
1266 email = user.mail
1266 elsif user.to_s =~ %r{<(.+?)>}
1267 elsif user.to_s =~ %r{<(.+?)>}
1267 email = $1
1268 email = $1
1268 end
1269 end
1269 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1270 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1270 else
1271 else
1271 ''
1272 ''
1272 end
1273 end
1273 end
1274 end
1274
1275
1275 def sanitize_anchor_name(anchor)
1276 def sanitize_anchor_name(anchor)
1276 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
1277 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
1277 anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1278 anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1278 else
1279 else
1279 # TODO: remove when ruby1.8 is no longer supported
1280 # TODO: remove when ruby1.8 is no longer supported
1280 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1281 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1281 end
1282 end
1282 end
1283 end
1283
1284
1284 # Returns the javascript tags that are included in the html layout head
1285 # Returns the javascript tags that are included in the html layout head
1285 def javascript_heads
1286 def javascript_heads
1286 tags = javascript_include_tag('jquery-1.8.3-ui-1.9.2-ujs-2.0.3', 'application')
1287 tags = javascript_include_tag('jquery-1.8.3-ui-1.9.2-ujs-2.0.3', 'application')
1287 unless User.current.pref.warn_on_leaving_unsaved == '0'
1288 unless User.current.pref.warn_on_leaving_unsaved == '0'
1288 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1289 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1289 end
1290 end
1290 tags
1291 tags
1291 end
1292 end
1292
1293
1293 def favicon
1294 def favicon
1294 "<link rel='shortcut icon' href='#{favicon_path}' />".html_safe
1295 "<link rel='shortcut icon' href='#{favicon_path}' />".html_safe
1295 end
1296 end
1296
1297
1297 # Returns the path to the favicon
1298 # Returns the path to the favicon
1298 def favicon_path
1299 def favicon_path
1299 icon = (current_theme && current_theme.favicon?) ? current_theme.favicon_path : '/favicon.ico'
1300 icon = (current_theme && current_theme.favicon?) ? current_theme.favicon_path : '/favicon.ico'
1300 image_path(icon)
1301 image_path(icon)
1301 end
1302 end
1302
1303
1303 # Returns the full URL to the favicon
1304 # Returns the full URL to the favicon
1304 def favicon_url
1305 def favicon_url
1305 # TODO: use #image_url introduced in Rails4
1306 # TODO: use #image_url introduced in Rails4
1306 path = favicon_path
1307 path = favicon_path
1307 base = url_for(:controller => 'welcome', :action => 'index', :only_path => false)
1308 base = url_for(:controller => 'welcome', :action => 'index', :only_path => false)
1308 base.sub(%r{/+$},'') + '/' + path.sub(%r{^/+},'')
1309 base.sub(%r{/+$},'') + '/' + path.sub(%r{^/+},'')
1309 end
1310 end
1310
1311
1311 def robot_exclusion_tag
1312 def robot_exclusion_tag
1312 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1313 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1313 end
1314 end
1314
1315
1315 # Returns true if arg is expected in the API response
1316 # Returns true if arg is expected in the API response
1316 def include_in_api_response?(arg)
1317 def include_in_api_response?(arg)
1317 unless @included_in_api_response
1318 unless @included_in_api_response
1318 param = params[:include]
1319 param = params[:include]
1319 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1320 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1320 @included_in_api_response.collect!(&:strip)
1321 @included_in_api_response.collect!(&:strip)
1321 end
1322 end
1322 @included_in_api_response.include?(arg.to_s)
1323 @included_in_api_response.include?(arg.to_s)
1323 end
1324 end
1324
1325
1325 # Returns options or nil if nometa param or X-Redmine-Nometa header
1326 # Returns options or nil if nometa param or X-Redmine-Nometa header
1326 # was set in the request
1327 # was set in the request
1327 def api_meta(options)
1328 def api_meta(options)
1328 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1329 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1329 # compatibility mode for activeresource clients that raise
1330 # compatibility mode for activeresource clients that raise
1330 # an error when unserializing an array with attributes
1331 # an error when unserializing an array with attributes
1331 nil
1332 nil
1332 else
1333 else
1333 options
1334 options
1334 end
1335 end
1335 end
1336 end
1336
1337
1337 private
1338 private
1338
1339
1339 def wiki_helper
1340 def wiki_helper
1340 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1341 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1341 extend helper
1342 extend helper
1342 return self
1343 return self
1343 end
1344 end
1344
1345
1345 def link_to_content_update(text, url_params = {}, html_options = {})
1346 def link_to_content_update(text, url_params = {}, html_options = {})
1346 link_to(text, url_params, html_options)
1347 link_to(text, url_params, html_options)
1347 end
1348 end
1348 end
1349 end
@@ -1,1490 +1,1508
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2014 Jean-Philippe Lang
4 # Copyright (C) 2006-2014 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 File.expand_path('../../../test_helper', __FILE__)
20 require File.expand_path('../../../test_helper', __FILE__)
21
21
22 class ApplicationHelperTest < ActionView::TestCase
22 class ApplicationHelperTest < ActionView::TestCase
23 include Redmine::I18n
23 include Redmine::I18n
24 include ERB::Util
24 include ERB::Util
25 include Rails.application.routes.url_helpers
25 include Rails.application.routes.url_helpers
26
26
27 fixtures :projects, :roles, :enabled_modules, :users,
27 fixtures :projects, :roles, :enabled_modules, :users,
28 :repositories, :changesets,
28 :repositories, :changesets,
29 :trackers, :issue_statuses, :issues, :versions, :documents,
29 :trackers, :issue_statuses, :issues, :versions, :documents,
30 :wikis, :wiki_pages, :wiki_contents,
30 :wikis, :wiki_pages, :wiki_contents,
31 :boards, :messages, :news,
31 :boards, :messages, :news,
32 :attachments, :enumerations
32 :attachments, :enumerations
33
33
34 def setup
34 def setup
35 super
35 super
36 set_tmp_attachments_directory
36 set_tmp_attachments_directory
37 @russian_test = "\xd1\x82\xd0\xb5\xd1\x81\xd1\x82"
37 @russian_test = "\xd1\x82\xd0\xb5\xd1\x81\xd1\x82"
38 if @russian_test.respond_to?(:force_encoding)
38 if @russian_test.respond_to?(:force_encoding)
39 @russian_test.force_encoding('UTF-8')
39 @russian_test.force_encoding('UTF-8')
40 end
40 end
41 end
41 end
42
42
43 test "#link_to_if_authorized for authorized user should allow using the :controller and :action for the target link" do
43 test "#link_to_if_authorized for authorized user should allow using the :controller and :action for the target link" do
44 User.current = User.find_by_login('admin')
44 User.current = User.find_by_login('admin')
45
45
46 @project = Issue.first.project # Used by helper
46 @project = Issue.first.project # Used by helper
47 response = link_to_if_authorized('By controller/actionr',
47 response = link_to_if_authorized('By controller/actionr',
48 {:controller => 'issues', :action => 'edit', :id => Issue.first.id})
48 {:controller => 'issues', :action => 'edit', :id => Issue.first.id})
49 assert_match /href/, response
49 assert_match /href/, response
50 end
50 end
51
51
52 test "#link_to_if_authorized for unauthorized user should display nothing if user isn't authorized" do
52 test "#link_to_if_authorized for unauthorized user should display nothing if user isn't authorized" do
53 User.current = User.find_by_login('dlopper')
53 User.current = User.find_by_login('dlopper')
54 @project = Project.find('private-child')
54 @project = Project.find('private-child')
55 issue = @project.issues.first
55 issue = @project.issues.first
56 assert !issue.visible?
56 assert !issue.visible?
57
57
58 response = link_to_if_authorized('Never displayed',
58 response = link_to_if_authorized('Never displayed',
59 {:controller => 'issues', :action => 'show', :id => issue})
59 {:controller => 'issues', :action => 'show', :id => issue})
60 assert_nil response
60 assert_nil response
61 end
61 end
62
62
63 def test_auto_links
63 def test_auto_links
64 to_test = {
64 to_test = {
65 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
65 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
66 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
66 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
67 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
67 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
68 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
68 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
69 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
69 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
70 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
70 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
71 '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>.',
71 '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>.',
72 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
72 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
73 '(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>)',
73 '(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>)',
74 '(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>)',
74 '(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>)',
75 '(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>).',
75 '(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>).',
76 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
76 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
77 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
77 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
78 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
78 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
79 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
79 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
80 '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>',
80 '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>',
81 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
81 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
82 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
82 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
83 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
83 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
84 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
84 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
85 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
85 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
86 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
86 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
87 # two exclamation marks
87 # two exclamation marks
88 '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>',
88 '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>',
89 # escaping
89 # escaping
90 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo&quot;bar</a>',
90 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo&quot;bar</a>',
91 # wrap in angle brackets
91 # wrap in angle brackets
92 '<http://foo.bar>' => '&lt;<a class="external" href="http://foo.bar">http://foo.bar</a>&gt;',
92 '<http://foo.bar>' => '&lt;<a class="external" href="http://foo.bar">http://foo.bar</a>&gt;',
93 # invalid urls
93 # invalid urls
94 'http://' => 'http://',
94 'http://' => 'http://',
95 'www.' => 'www.',
95 'www.' => 'www.',
96 'test-www.bar.com' => 'test-www.bar.com',
96 'test-www.bar.com' => 'test-www.bar.com',
97 }
97 }
98 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
98 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
99 end
99 end
100
100
101 if 'ruby'.respond_to?(:encoding)
101 if 'ruby'.respond_to?(:encoding)
102 def test_auto_links_with_non_ascii_characters
102 def test_auto_links_with_non_ascii_characters
103 to_test = {
103 to_test = {
104 "http://foo.bar/#{@russian_test}" =>
104 "http://foo.bar/#{@russian_test}" =>
105 %|<a class="external" href="http://foo.bar/#{@russian_test}">http://foo.bar/#{@russian_test}</a>|
105 %|<a class="external" href="http://foo.bar/#{@russian_test}">http://foo.bar/#{@russian_test}</a>|
106 }
106 }
107 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
107 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
108 end
108 end
109 else
109 else
110 puts 'Skipping test_auto_links_with_non_ascii_characters, unsupported ruby version'
110 puts 'Skipping test_auto_links_with_non_ascii_characters, unsupported ruby version'
111 end
111 end
112
112
113 def test_auto_mailto
113 def test_auto_mailto
114 to_test = {
114 to_test = {
115 'test@foo.bar' => '<a class="email" href="mailto:test@foo.bar">test@foo.bar</a>',
115 'test@foo.bar' => '<a class="email" href="mailto:test@foo.bar">test@foo.bar</a>',
116 'test@www.foo.bar' => '<a class="email" href="mailto:test@www.foo.bar">test@www.foo.bar</a>',
116 'test@www.foo.bar' => '<a class="email" href="mailto:test@www.foo.bar">test@www.foo.bar</a>',
117 }
117 }
118 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
118 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
119 end
119 end
120
120
121 def test_inline_images
121 def test_inline_images
122 to_test = {
122 to_test = {
123 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
123 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
124 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
124 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
125 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
125 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
126 'with style !{width:100px;height:100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height:100px;" alt="" />',
126 'with style !{width:100px;height:100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height:100px;" alt="" />',
127 '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" />',
127 '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" />',
128 '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;" />',
128 '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;" />',
129 }
129 }
130 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
130 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
131 end
131 end
132
132
133 def test_inline_images_inside_tags
133 def test_inline_images_inside_tags
134 raw = <<-RAW
134 raw = <<-RAW
135 h1. !foo.png! Heading
135 h1. !foo.png! Heading
136
136
137 Centered image:
137 Centered image:
138
138
139 p=. !bar.gif!
139 p=. !bar.gif!
140 RAW
140 RAW
141
141
142 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
142 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
143 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
143 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
144 end
144 end
145
145
146 def test_attached_images
146 def test_attached_images
147 to_test = {
147 to_test = {
148 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" />',
148 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" />',
149 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" />',
149 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" />',
150 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
150 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
151 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
151 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
152 # link image
152 # link image
153 '!logo.gif!:http://foo.bar/' => '<a href="http://foo.bar/"><img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" /></a>',
153 '!logo.gif!:http://foo.bar/' => '<a href="http://foo.bar/"><img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" /></a>',
154 }
154 }
155 attachments = Attachment.all
155 attachments = Attachment.all
156 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
156 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
157 end
157 end
158
158
159 def test_attached_images_filename_extension
159 def test_attached_images_filename_extension
160 set_tmp_attachments_directory
160 set_tmp_attachments_directory
161 a1 = Attachment.new(
161 a1 = Attachment.new(
162 :container => Issue.find(1),
162 :container => Issue.find(1),
163 :file => mock_file_with_options({:original_filename => "testtest.JPG"}),
163 :file => mock_file_with_options({:original_filename => "testtest.JPG"}),
164 :author => User.find(1))
164 :author => User.find(1))
165 assert a1.save
165 assert a1.save
166 assert_equal "testtest.JPG", a1.filename
166 assert_equal "testtest.JPG", a1.filename
167 assert_equal "image/jpeg", a1.content_type
167 assert_equal "image/jpeg", a1.content_type
168 assert a1.image?
168 assert a1.image?
169
169
170 a2 = Attachment.new(
170 a2 = Attachment.new(
171 :container => Issue.find(1),
171 :container => Issue.find(1),
172 :file => mock_file_with_options({:original_filename => "testtest.jpeg"}),
172 :file => mock_file_with_options({:original_filename => "testtest.jpeg"}),
173 :author => User.find(1))
173 :author => User.find(1))
174 assert a2.save
174 assert a2.save
175 assert_equal "testtest.jpeg", a2.filename
175 assert_equal "testtest.jpeg", a2.filename
176 assert_equal "image/jpeg", a2.content_type
176 assert_equal "image/jpeg", a2.content_type
177 assert a2.image?
177 assert a2.image?
178
178
179 a3 = Attachment.new(
179 a3 = Attachment.new(
180 :container => Issue.find(1),
180 :container => Issue.find(1),
181 :file => mock_file_with_options({:original_filename => "testtest.JPE"}),
181 :file => mock_file_with_options({:original_filename => "testtest.JPE"}),
182 :author => User.find(1))
182 :author => User.find(1))
183 assert a3.save
183 assert a3.save
184 assert_equal "testtest.JPE", a3.filename
184 assert_equal "testtest.JPE", a3.filename
185 assert_equal "image/jpeg", a3.content_type
185 assert_equal "image/jpeg", a3.content_type
186 assert a3.image?
186 assert a3.image?
187
187
188 a4 = Attachment.new(
188 a4 = Attachment.new(
189 :container => Issue.find(1),
189 :container => Issue.find(1),
190 :file => mock_file_with_options({:original_filename => "Testtest.BMP"}),
190 :file => mock_file_with_options({:original_filename => "Testtest.BMP"}),
191 :author => User.find(1))
191 :author => User.find(1))
192 assert a4.save
192 assert a4.save
193 assert_equal "Testtest.BMP", a4.filename
193 assert_equal "Testtest.BMP", a4.filename
194 assert_equal "image/x-ms-bmp", a4.content_type
194 assert_equal "image/x-ms-bmp", a4.content_type
195 assert a4.image?
195 assert a4.image?
196
196
197 to_test = {
197 to_test = {
198 'Inline image: !testtest.jpg!' =>
198 'Inline image: !testtest.jpg!' =>
199 'Inline image: <img src="/attachments/download/' + a1.id.to_s + '/testtest.JPG" alt="" />',
199 'Inline image: <img src="/attachments/download/' + a1.id.to_s + '/testtest.JPG" alt="" />',
200 'Inline image: !testtest.jpeg!' =>
200 'Inline image: !testtest.jpeg!' =>
201 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testtest.jpeg" alt="" />',
201 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testtest.jpeg" alt="" />',
202 'Inline image: !testtest.jpe!' =>
202 'Inline image: !testtest.jpe!' =>
203 'Inline image: <img src="/attachments/download/' + a3.id.to_s + '/testtest.JPE" alt="" />',
203 'Inline image: <img src="/attachments/download/' + a3.id.to_s + '/testtest.JPE" alt="" />',
204 'Inline image: !testtest.bmp!' =>
204 'Inline image: !testtest.bmp!' =>
205 'Inline image: <img src="/attachments/download/' + a4.id.to_s + '/Testtest.BMP" alt="" />',
205 'Inline image: <img src="/attachments/download/' + a4.id.to_s + '/Testtest.BMP" alt="" />',
206 }
206 }
207
207
208 attachments = [a1, a2, a3, a4]
208 attachments = [a1, a2, a3, a4]
209 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
209 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
210 end
210 end
211
211
212 def test_attached_images_should_read_later
212 def test_attached_images_should_read_later
213 set_fixtures_attachments_directory
213 set_fixtures_attachments_directory
214 a1 = Attachment.find(16)
214 a1 = Attachment.find(16)
215 assert_equal "testfile.png", a1.filename
215 assert_equal "testfile.png", a1.filename
216 assert a1.readable?
216 assert a1.readable?
217 assert (! a1.visible?(User.anonymous))
217 assert (! a1.visible?(User.anonymous))
218 assert a1.visible?(User.find(2))
218 assert a1.visible?(User.find(2))
219 a2 = Attachment.find(17)
219 a2 = Attachment.find(17)
220 assert_equal "testfile.PNG", a2.filename
220 assert_equal "testfile.PNG", a2.filename
221 assert a2.readable?
221 assert a2.readable?
222 assert (! a2.visible?(User.anonymous))
222 assert (! a2.visible?(User.anonymous))
223 assert a2.visible?(User.find(2))
223 assert a2.visible?(User.find(2))
224 assert a1.created_on < a2.created_on
224 assert a1.created_on < a2.created_on
225
225
226 to_test = {
226 to_test = {
227 'Inline image: !testfile.png!' =>
227 'Inline image: !testfile.png!' =>
228 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testfile.PNG" alt="" />',
228 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testfile.PNG" alt="" />',
229 'Inline image: !Testfile.PNG!' =>
229 'Inline image: !Testfile.PNG!' =>
230 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testfile.PNG" alt="" />',
230 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testfile.PNG" alt="" />',
231 }
231 }
232 attachments = [a1, a2]
232 attachments = [a1, a2]
233 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
233 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
234 set_tmp_attachments_directory
234 set_tmp_attachments_directory
235 end
235 end
236
236
237 def test_textile_external_links
237 def test_textile_external_links
238 to_test = {
238 to_test = {
239 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
239 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
240 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
240 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
241 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
241 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
242 '"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>',
242 '"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>',
243 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
243 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
244 # no multiline link text
244 # no multiline link text
245 "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",
245 "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",
246 # mailto link
246 # mailto link
247 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
247 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
248 # two exclamation marks
248 # two exclamation marks
249 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
249 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
250 # escaping
250 # escaping
251 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
251 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
252 }
252 }
253 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
253 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
254 end
254 end
255
255
256 if 'ruby'.respond_to?(:encoding)
256 if 'ruby'.respond_to?(:encoding)
257 def test_textile_external_links_with_non_ascii_characters
257 def test_textile_external_links_with_non_ascii_characters
258 to_test = {
258 to_test = {
259 %|This is a "link":http://foo.bar/#{@russian_test}| =>
259 %|This is a "link":http://foo.bar/#{@russian_test}| =>
260 %|This is a <a href="http://foo.bar/#{@russian_test}" class="external">link</a>|
260 %|This is a <a href="http://foo.bar/#{@russian_test}" class="external">link</a>|
261 }
261 }
262 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
262 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
263 end
263 end
264 else
264 else
265 puts 'Skipping test_textile_external_links_with_non_ascii_characters, unsupported ruby version'
265 puts 'Skipping test_textile_external_links_with_non_ascii_characters, unsupported ruby version'
266 end
266 end
267
267
268 def test_redmine_links
268 def test_redmine_links
269 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
269 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
270 :class => Issue.find(3).css_classes, :title => 'Error 281 when updating a recipe (New)')
270 :class => Issue.find(3).css_classes, :title => 'Error 281 when updating a recipe (New)')
271 note_link = link_to('#3-14', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'},
271 note_link = link_to('#3-14', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'},
272 :class => Issue.find(3).css_classes, :title => 'Error 281 when updating a recipe (New)')
272 :class => Issue.find(3).css_classes, :title => 'Error 281 when updating a recipe (New)')
273 note_link2 = link_to('#3#note-14', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'},
273 note_link2 = link_to('#3#note-14', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'},
274 :class => Issue.find(3).css_classes, :title => 'Error 281 when updating a recipe (New)')
274 :class => Issue.find(3).css_classes, :title => 'Error 281 when updating a recipe (New)')
275
275
276 revision_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
276 revision_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
277 :class => 'changeset', :title => 'My very first commit do not escaping #<>&')
277 :class => 'changeset', :title => 'My very first commit do not escaping #<>&')
278 revision_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
278 revision_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
279 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
279 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
280
280
281 changeset_link2 = link_to('691322a8eb01e11fd7',
281 changeset_link2 = link_to('691322a8eb01e11fd7',
282 {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
282 {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
283 :class => 'changeset', :title => 'My very first commit do not escaping #<>&')
283 :class => 'changeset', :title => 'My very first commit do not escaping #<>&')
284
284
285 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
285 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
286 :class => 'document')
286 :class => 'document')
287
287
288 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
288 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
289 :class => 'version')
289 :class => 'version')
290
290
291 board_url = {:controller => 'boards', :action => 'show', :id => 2, :project_id => 'ecookbook'}
291 board_url = {:controller => 'boards', :action => 'show', :id => 2, :project_id => 'ecookbook'}
292
292
293 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
293 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
294
294
295 news_url = {:controller => 'news', :action => 'show', :id => 1}
295 news_url = {:controller => 'news', :action => 'show', :id => 1}
296
296
297 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
297 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
298
298
299 source_url = '/projects/ecookbook/repository/entry/some/file'
299 source_url = '/projects/ecookbook/repository/entry/some/file'
300 source_url_with_rev = '/projects/ecookbook/repository/revisions/52/entry/some/file'
300 source_url_with_rev = '/projects/ecookbook/repository/revisions/52/entry/some/file'
301 source_url_with_ext = '/projects/ecookbook/repository/entry/some/file.ext'
301 source_url_with_ext = '/projects/ecookbook/repository/entry/some/file.ext'
302 source_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/entry/some/file.ext'
302 source_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/entry/some/file.ext'
303 source_url_with_branch = '/projects/ecookbook/repository/revisions/branch/entry/some/file'
303 source_url_with_branch = '/projects/ecookbook/repository/revisions/branch/entry/some/file'
304
304
305 export_url = '/projects/ecookbook/repository/raw/some/file'
305 export_url = '/projects/ecookbook/repository/raw/some/file'
306 export_url_with_rev = '/projects/ecookbook/repository/revisions/52/raw/some/file'
306 export_url_with_rev = '/projects/ecookbook/repository/revisions/52/raw/some/file'
307 export_url_with_ext = '/projects/ecookbook/repository/raw/some/file.ext'
307 export_url_with_ext = '/projects/ecookbook/repository/raw/some/file.ext'
308 export_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/raw/some/file.ext'
308 export_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/raw/some/file.ext'
309 export_url_with_branch = '/projects/ecookbook/repository/revisions/branch/raw/some/file'
309 export_url_with_branch = '/projects/ecookbook/repository/revisions/branch/raw/some/file'
310
310
311 to_test = {
311 to_test = {
312 # tickets
312 # tickets
313 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
313 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
314 # ticket notes
314 # ticket notes
315 '#3-14' => note_link,
315 '#3-14' => note_link,
316 '#3#note-14' => note_link2,
316 '#3#note-14' => note_link2,
317 # should not ignore leading zero
317 # should not ignore leading zero
318 '#03' => '#03',
318 '#03' => '#03',
319 # changesets
319 # changesets
320 'r1' => revision_link,
320 'r1' => revision_link,
321 'r1.' => "#{revision_link}.",
321 'r1.' => "#{revision_link}.",
322 'r1, r2' => "#{revision_link}, #{revision_link2}",
322 'r1, r2' => "#{revision_link}, #{revision_link2}",
323 'r1,r2' => "#{revision_link},#{revision_link2}",
323 'r1,r2' => "#{revision_link},#{revision_link2}",
324 'commit:691322a8eb01e11fd7' => changeset_link2,
324 'commit:691322a8eb01e11fd7' => changeset_link2,
325 # documents
325 # documents
326 'document#1' => document_link,
326 'document#1' => document_link,
327 'document:"Test document"' => document_link,
327 'document:"Test document"' => document_link,
328 # versions
328 # versions
329 'version#2' => version_link,
329 'version#2' => version_link,
330 'version:1.0' => version_link,
330 'version:1.0' => version_link,
331 'version:"1.0"' => version_link,
331 'version:"1.0"' => version_link,
332 # source
332 # source
333 'source:some/file' => link_to('source:some/file', source_url, :class => 'source'),
333 'source:some/file' => link_to('source:some/file', source_url, :class => 'source'),
334 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
334 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
335 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
335 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
336 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
336 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
337 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
337 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
338 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
338 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
339 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
339 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
340 'source:/some/file@52' => link_to('source:/some/file@52', source_url_with_rev, :class => 'source'),
340 'source:/some/file@52' => link_to('source:/some/file@52', source_url_with_rev, :class => 'source'),
341 'source:/some/file@branch' => link_to('source:/some/file@branch', source_url_with_branch, :class => 'source'),
341 'source:/some/file@branch' => link_to('source:/some/file@branch', source_url_with_branch, :class => 'source'),
342 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_rev_and_ext, :class => 'source'),
342 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_rev_and_ext, :class => 'source'),
343 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url + "#L110", :class => 'source'),
343 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url + "#L110", :class => 'source'),
344 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext + "#L110", :class => 'source'),
344 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext + "#L110", :class => 'source'),
345 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url_with_rev + "#L110", :class => 'source'),
345 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url_with_rev + "#L110", :class => 'source'),
346 # export
346 # export
347 'export:/some/file' => link_to('export:/some/file', export_url, :class => 'source download'),
347 'export:/some/file' => link_to('export:/some/file', export_url, :class => 'source download'),
348 'export:/some/file.ext' => link_to('export:/some/file.ext', export_url_with_ext, :class => 'source download'),
348 'export:/some/file.ext' => link_to('export:/some/file.ext', export_url_with_ext, :class => 'source download'),
349 'export:/some/file@52' => link_to('export:/some/file@52', export_url_with_rev, :class => 'source download'),
349 'export:/some/file@52' => link_to('export:/some/file@52', export_url_with_rev, :class => 'source download'),
350 'export:/some/file.ext@52' => link_to('export:/some/file.ext@52', export_url_with_rev_and_ext, :class => 'source download'),
350 'export:/some/file.ext@52' => link_to('export:/some/file.ext@52', export_url_with_rev_and_ext, :class => 'source download'),
351 'export:/some/file@branch' => link_to('export:/some/file@branch', export_url_with_branch, :class => 'source download'),
351 'export:/some/file@branch' => link_to('export:/some/file@branch', export_url_with_branch, :class => 'source download'),
352 # forum
352 # forum
353 'forum#2' => link_to('Discussion', board_url, :class => 'board'),
353 'forum#2' => link_to('Discussion', board_url, :class => 'board'),
354 'forum:Discussion' => link_to('Discussion', board_url, :class => 'board'),
354 'forum:Discussion' => link_to('Discussion', board_url, :class => 'board'),
355 # message
355 # message
356 'message#4' => link_to('Post 2', message_url, :class => 'message'),
356 'message#4' => link_to('Post 2', message_url, :class => 'message'),
357 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5', :r => 5), :class => 'message'),
357 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5', :r => 5), :class => 'message'),
358 # news
358 # news
359 'news#1' => link_to('eCookbook first release !', news_url, :class => 'news'),
359 'news#1' => link_to('eCookbook first release !', news_url, :class => 'news'),
360 'news:"eCookbook first release !"' => link_to('eCookbook first release !', news_url, :class => 'news'),
360 'news:"eCookbook first release !"' => link_to('eCookbook first release !', news_url, :class => 'news'),
361 # project
361 # project
362 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
362 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
363 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
363 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
364 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
364 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
365 # not found
365 # not found
366 '#0123456789' => '#0123456789',
366 '#0123456789' => '#0123456789',
367 # invalid expressions
367 # invalid expressions
368 'source:' => 'source:',
368 'source:' => 'source:',
369 # url hash
369 # url hash
370 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
370 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
371 }
371 }
372 @project = Project.find(1)
372 @project = Project.find(1)
373 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
373 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
374 end
374 end
375
375
376 def test_redmine_links_with_a_different_project_before_current_project
376 def test_redmine_links_with_a_different_project_before_current_project
377 vp1 = Version.generate!(:project_id => 1, :name => '1.4.4')
377 vp1 = Version.generate!(:project_id => 1, :name => '1.4.4')
378 vp3 = Version.generate!(:project_id => 3, :name => '1.4.4')
378 vp3 = Version.generate!(:project_id => 3, :name => '1.4.4')
379 @project = Project.find(3)
379 @project = Project.find(3)
380 result1 = link_to("1.4.4", "/versions/#{vp1.id}", :class => "version")
380 result1 = link_to("1.4.4", "/versions/#{vp1.id}", :class => "version")
381 result2 = link_to("1.4.4", "/versions/#{vp3.id}", :class => "version")
381 result2 = link_to("1.4.4", "/versions/#{vp3.id}", :class => "version")
382 assert_equal "<p>#{result1} #{result2}</p>",
382 assert_equal "<p>#{result1} #{result2}</p>",
383 textilizable("ecookbook:version:1.4.4 version:1.4.4")
383 textilizable("ecookbook:version:1.4.4 version:1.4.4")
384 end
384 end
385
385
386 def test_escaped_redmine_links_should_not_be_parsed
386 def test_escaped_redmine_links_should_not_be_parsed
387 to_test = [
387 to_test = [
388 '#3.',
388 '#3.',
389 '#3-14.',
389 '#3-14.',
390 '#3#-note14.',
390 '#3#-note14.',
391 'r1',
391 'r1',
392 'document#1',
392 'document#1',
393 'document:"Test document"',
393 'document:"Test document"',
394 'version#2',
394 'version#2',
395 'version:1.0',
395 'version:1.0',
396 'version:"1.0"',
396 'version:"1.0"',
397 'source:/some/file'
397 'source:/some/file'
398 ]
398 ]
399 @project = Project.find(1)
399 @project = Project.find(1)
400 to_test.each { |text| assert_equal "<p>#{text}</p>", textilizable("!" + text), "#{text} failed" }
400 to_test.each { |text| assert_equal "<p>#{text}</p>", textilizable("!" + text), "#{text} failed" }
401 end
401 end
402
402
403 def test_cross_project_redmine_links
403 def test_cross_project_redmine_links
404 source_link = link_to('ecookbook:source:/some/file',
404 source_link = link_to('ecookbook:source:/some/file',
405 {:controller => 'repositories', :action => 'entry',
405 {:controller => 'repositories', :action => 'entry',
406 :id => 'ecookbook', :path => ['some', 'file']},
406 :id => 'ecookbook', :path => ['some', 'file']},
407 :class => 'source')
407 :class => 'source')
408 changeset_link = link_to('ecookbook:r2',
408 changeset_link = link_to('ecookbook:r2',
409 {:controller => 'repositories', :action => 'revision',
409 {:controller => 'repositories', :action => 'revision',
410 :id => 'ecookbook', :rev => 2},
410 :id => 'ecookbook', :rev => 2},
411 :class => 'changeset',
411 :class => 'changeset',
412 :title => 'This commit fixes #1, #2 and references #1 & #3')
412 :title => 'This commit fixes #1, #2 and references #1 & #3')
413 to_test = {
413 to_test = {
414 # documents
414 # documents
415 'document:"Test document"' => 'document:"Test document"',
415 'document:"Test document"' => 'document:"Test document"',
416 'ecookbook:document:"Test document"' =>
416 'ecookbook:document:"Test document"' =>
417 link_to("Test document", "/documents/1", :class => "document"),
417 link_to("Test document", "/documents/1", :class => "document"),
418 'invalid:document:"Test document"' => 'invalid:document:"Test document"',
418 'invalid:document:"Test document"' => 'invalid:document:"Test document"',
419 # versions
419 # versions
420 'version:"1.0"' => 'version:"1.0"',
420 'version:"1.0"' => 'version:"1.0"',
421 'ecookbook:version:"1.0"' =>
421 'ecookbook:version:"1.0"' =>
422 link_to("1.0", "/versions/2", :class => "version"),
422 link_to("1.0", "/versions/2", :class => "version"),
423 'invalid:version:"1.0"' => 'invalid:version:"1.0"',
423 'invalid:version:"1.0"' => 'invalid:version:"1.0"',
424 # changeset
424 # changeset
425 'r2' => 'r2',
425 'r2' => 'r2',
426 'ecookbook:r2' => changeset_link,
426 'ecookbook:r2' => changeset_link,
427 'invalid:r2' => 'invalid:r2',
427 'invalid:r2' => 'invalid:r2',
428 # source
428 # source
429 'source:/some/file' => 'source:/some/file',
429 'source:/some/file' => 'source:/some/file',
430 'ecookbook:source:/some/file' => source_link,
430 'ecookbook:source:/some/file' => source_link,
431 'invalid:source:/some/file' => 'invalid:source:/some/file',
431 'invalid:source:/some/file' => 'invalid:source:/some/file',
432 }
432 }
433 @project = Project.find(3)
433 @project = Project.find(3)
434 to_test.each do |text, result|
434 to_test.each do |text, result|
435 assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed"
435 assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed"
436 end
436 end
437 end
437 end
438
438
439 def test_link_to_issue_subject
439 def test_link_to_issue_subject
440 issue = Issue.generate!(:subject => "01234567890123456789")
440 issue = Issue.generate!(:subject => "01234567890123456789")
441 str = link_to_issue(issue, :truncate => 10)
441 str = link_to_issue(issue, :truncate => 10)
442 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
442 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
443 assert_equal "#{result}: 0123456...", str
443 assert_equal "#{result}: 0123456...", str
444
444
445 issue = Issue.generate!(:subject => "<&>")
445 issue = Issue.generate!(:subject => "<&>")
446 str = link_to_issue(issue)
446 str = link_to_issue(issue)
447 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
447 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
448 assert_equal "#{result}: &lt;&amp;&gt;", str
448 assert_equal "#{result}: &lt;&amp;&gt;", str
449
449
450 issue = Issue.generate!(:subject => "<&>0123456789012345")
450 issue = Issue.generate!(:subject => "<&>0123456789012345")
451 str = link_to_issue(issue, :truncate => 10)
451 str = link_to_issue(issue, :truncate => 10)
452 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
452 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
453 assert_equal "#{result}: &lt;&amp;&gt;0123...", str
453 assert_equal "#{result}: &lt;&amp;&gt;0123...", str
454 end
454 end
455
455
456 def test_link_to_issue_title
456 def test_link_to_issue_title
457 long_str = "0123456789" * 5
457 long_str = "0123456789" * 5
458
458
459 issue = Issue.generate!(:subject => "#{long_str}01234567890123456789")
459 issue = Issue.generate!(:subject => "#{long_str}01234567890123456789")
460 str = link_to_issue(issue, :subject => false)
460 str = link_to_issue(issue, :subject => false)
461 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}",
461 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}",
462 :class => issue.css_classes,
462 :class => issue.css_classes,
463 :title => "#{long_str}0123456...")
463 :title => "#{long_str}0123456...")
464 assert_equal result, str
464 assert_equal result, str
465
465
466 issue = Issue.generate!(:subject => "<&>#{long_str}01234567890123456789")
466 issue = Issue.generate!(:subject => "<&>#{long_str}01234567890123456789")
467 str = link_to_issue(issue, :subject => false)
467 str = link_to_issue(issue, :subject => false)
468 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}",
468 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}",
469 :class => issue.css_classes,
469 :class => issue.css_classes,
470 :title => "<&>#{long_str}0123...")
470 :title => "<&>#{long_str}0123...")
471 assert_equal result, str
471 assert_equal result, str
472 end
472 end
473
473
474 def test_multiple_repositories_redmine_links
474 def test_multiple_repositories_redmine_links
475 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn_repo-1', :url => 'file:///foo/hg')
475 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn_repo-1', :url => 'file:///foo/hg')
476 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
476 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
477 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
477 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
478 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
478 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
479
479
480 changeset_link = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
480 changeset_link = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
481 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
481 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
482 svn_changeset_link = link_to('svn_repo-1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn_repo-1', :rev => 123},
482 svn_changeset_link = link_to('svn_repo-1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn_repo-1', :rev => 123},
483 :class => 'changeset', :title => '')
483 :class => 'changeset', :title => '')
484 hg_changeset_link = link_to('hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
484 hg_changeset_link = link_to('hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
485 :class => 'changeset', :title => '')
485 :class => 'changeset', :title => '')
486
486
487 source_link = link_to('source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
487 source_link = link_to('source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
488 hg_source_link = link_to('source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
488 hg_source_link = link_to('source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
489
489
490 to_test = {
490 to_test = {
491 'r2' => changeset_link,
491 'r2' => changeset_link,
492 'svn_repo-1|r123' => svn_changeset_link,
492 'svn_repo-1|r123' => svn_changeset_link,
493 'invalid|r123' => 'invalid|r123',
493 'invalid|r123' => 'invalid|r123',
494 'commit:hg1|abcd' => hg_changeset_link,
494 'commit:hg1|abcd' => hg_changeset_link,
495 'commit:invalid|abcd' => 'commit:invalid|abcd',
495 'commit:invalid|abcd' => 'commit:invalid|abcd',
496 # source
496 # source
497 'source:some/file' => source_link,
497 'source:some/file' => source_link,
498 'source:hg1|some/file' => hg_source_link,
498 'source:hg1|some/file' => hg_source_link,
499 'source:invalid|some/file' => 'source:invalid|some/file',
499 'source:invalid|some/file' => 'source:invalid|some/file',
500 }
500 }
501
501
502 @project = Project.find(1)
502 @project = Project.find(1)
503 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
503 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
504 end
504 end
505
505
506 def test_cross_project_multiple_repositories_redmine_links
506 def test_cross_project_multiple_repositories_redmine_links
507 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn1', :url => 'file:///foo/hg')
507 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn1', :url => 'file:///foo/hg')
508 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
508 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
509 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
509 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
510 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
510 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
511
511
512 changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
512 changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
513 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
513 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
514 svn_changeset_link = link_to('ecookbook:svn1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn1', :rev => 123},
514 svn_changeset_link = link_to('ecookbook:svn1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn1', :rev => 123},
515 :class => 'changeset', :title => '')
515 :class => 'changeset', :title => '')
516 hg_changeset_link = link_to('ecookbook:hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
516 hg_changeset_link = link_to('ecookbook:hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
517 :class => 'changeset', :title => '')
517 :class => 'changeset', :title => '')
518
518
519 source_link = link_to('ecookbook:source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
519 source_link = link_to('ecookbook:source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
520 hg_source_link = link_to('ecookbook:source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
520 hg_source_link = link_to('ecookbook:source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
521
521
522 to_test = {
522 to_test = {
523 'ecookbook:r2' => changeset_link,
523 'ecookbook:r2' => changeset_link,
524 'ecookbook:svn1|r123' => svn_changeset_link,
524 'ecookbook:svn1|r123' => svn_changeset_link,
525 'ecookbook:invalid|r123' => 'ecookbook:invalid|r123',
525 'ecookbook:invalid|r123' => 'ecookbook:invalid|r123',
526 'ecookbook:commit:hg1|abcd' => hg_changeset_link,
526 'ecookbook:commit:hg1|abcd' => hg_changeset_link,
527 'ecookbook:commit:invalid|abcd' => 'ecookbook:commit:invalid|abcd',
527 'ecookbook:commit:invalid|abcd' => 'ecookbook:commit:invalid|abcd',
528 'invalid:commit:invalid|abcd' => 'invalid:commit:invalid|abcd',
528 'invalid:commit:invalid|abcd' => 'invalid:commit:invalid|abcd',
529 # source
529 # source
530 'ecookbook:source:some/file' => source_link,
530 'ecookbook:source:some/file' => source_link,
531 'ecookbook:source:hg1|some/file' => hg_source_link,
531 'ecookbook:source:hg1|some/file' => hg_source_link,
532 'ecookbook:source:invalid|some/file' => 'ecookbook:source:invalid|some/file',
532 'ecookbook:source:invalid|some/file' => 'ecookbook:source:invalid|some/file',
533 'invalid:source:invalid|some/file' => 'invalid:source:invalid|some/file',
533 'invalid:source:invalid|some/file' => 'invalid:source:invalid|some/file',
534 }
534 }
535
535
536 @project = Project.find(3)
536 @project = Project.find(3)
537 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
537 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
538 end
538 end
539
539
540 def test_redmine_links_git_commit
540 def test_redmine_links_git_commit
541 changeset_link = link_to('abcd',
541 changeset_link = link_to('abcd',
542 {
542 {
543 :controller => 'repositories',
543 :controller => 'repositories',
544 :action => 'revision',
544 :action => 'revision',
545 :id => 'subproject1',
545 :id => 'subproject1',
546 :rev => 'abcd',
546 :rev => 'abcd',
547 },
547 },
548 :class => 'changeset', :title => 'test commit')
548 :class => 'changeset', :title => 'test commit')
549 to_test = {
549 to_test = {
550 'commit:abcd' => changeset_link,
550 'commit:abcd' => changeset_link,
551 }
551 }
552 @project = Project.find(3)
552 @project = Project.find(3)
553 r = Repository::Git.create!(:project => @project, :url => '/tmp/test/git')
553 r = Repository::Git.create!(:project => @project, :url => '/tmp/test/git')
554 assert r
554 assert r
555 c = Changeset.new(:repository => r,
555 c = Changeset.new(:repository => r,
556 :committed_on => Time.now,
556 :committed_on => Time.now,
557 :revision => 'abcd',
557 :revision => 'abcd',
558 :scmid => 'abcd',
558 :scmid => 'abcd',
559 :comments => 'test commit')
559 :comments => 'test commit')
560 assert( c.save )
560 assert( c.save )
561 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
561 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
562 end
562 end
563
563
564 # TODO: Bazaar commit id contains mail address, so it contains '@' and '_'.
564 # TODO: Bazaar commit id contains mail address, so it contains '@' and '_'.
565 def test_redmine_links_darcs_commit
565 def test_redmine_links_darcs_commit
566 changeset_link = link_to('20080308225258-98289-abcd456efg.gz',
566 changeset_link = link_to('20080308225258-98289-abcd456efg.gz',
567 {
567 {
568 :controller => 'repositories',
568 :controller => 'repositories',
569 :action => 'revision',
569 :action => 'revision',
570 :id => 'subproject1',
570 :id => 'subproject1',
571 :rev => '123',
571 :rev => '123',
572 },
572 },
573 :class => 'changeset', :title => 'test commit')
573 :class => 'changeset', :title => 'test commit')
574 to_test = {
574 to_test = {
575 'commit:20080308225258-98289-abcd456efg.gz' => changeset_link,
575 'commit:20080308225258-98289-abcd456efg.gz' => changeset_link,
576 }
576 }
577 @project = Project.find(3)
577 @project = Project.find(3)
578 r = Repository::Darcs.create!(
578 r = Repository::Darcs.create!(
579 :project => @project, :url => '/tmp/test/darcs',
579 :project => @project, :url => '/tmp/test/darcs',
580 :log_encoding => 'UTF-8')
580 :log_encoding => 'UTF-8')
581 assert r
581 assert r
582 c = Changeset.new(:repository => r,
582 c = Changeset.new(:repository => r,
583 :committed_on => Time.now,
583 :committed_on => Time.now,
584 :revision => '123',
584 :revision => '123',
585 :scmid => '20080308225258-98289-abcd456efg.gz',
585 :scmid => '20080308225258-98289-abcd456efg.gz',
586 :comments => 'test commit')
586 :comments => 'test commit')
587 assert( c.save )
587 assert( c.save )
588 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
588 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
589 end
589 end
590
590
591 def test_redmine_links_mercurial_commit
591 def test_redmine_links_mercurial_commit
592 changeset_link_rev = link_to('r123',
592 changeset_link_rev = link_to('r123',
593 {
593 {
594 :controller => 'repositories',
594 :controller => 'repositories',
595 :action => 'revision',
595 :action => 'revision',
596 :id => 'subproject1',
596 :id => 'subproject1',
597 :rev => '123' ,
597 :rev => '123' ,
598 },
598 },
599 :class => 'changeset', :title => 'test commit')
599 :class => 'changeset', :title => 'test commit')
600 changeset_link_commit = link_to('abcd',
600 changeset_link_commit = link_to('abcd',
601 {
601 {
602 :controller => 'repositories',
602 :controller => 'repositories',
603 :action => 'revision',
603 :action => 'revision',
604 :id => 'subproject1',
604 :id => 'subproject1',
605 :rev => 'abcd' ,
605 :rev => 'abcd' ,
606 },
606 },
607 :class => 'changeset', :title => 'test commit')
607 :class => 'changeset', :title => 'test commit')
608 to_test = {
608 to_test = {
609 'r123' => changeset_link_rev,
609 'r123' => changeset_link_rev,
610 'commit:abcd' => changeset_link_commit,
610 'commit:abcd' => changeset_link_commit,
611 }
611 }
612 @project = Project.find(3)
612 @project = Project.find(3)
613 r = Repository::Mercurial.create!(:project => @project, :url => '/tmp/test')
613 r = Repository::Mercurial.create!(:project => @project, :url => '/tmp/test')
614 assert r
614 assert r
615 c = Changeset.new(:repository => r,
615 c = Changeset.new(:repository => r,
616 :committed_on => Time.now,
616 :committed_on => Time.now,
617 :revision => '123',
617 :revision => '123',
618 :scmid => 'abcd',
618 :scmid => 'abcd',
619 :comments => 'test commit')
619 :comments => 'test commit')
620 assert( c.save )
620 assert( c.save )
621 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
621 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
622 end
622 end
623
623
624 def test_attachment_links
624 def test_attachment_links
625 text = 'attachment:error281.txt'
625 text = 'attachment:error281.txt'
626 result = link_to("error281.txt", "/attachments/download/1/error281.txt",
626 result = link_to("error281.txt", "/attachments/download/1/error281.txt",
627 :class => "attachment")
627 :class => "attachment")
628 assert_equal "<p>#{result}</p>",
628 assert_equal "<p>#{result}</p>",
629 textilizable(text,
629 textilizable(text,
630 :attachments => Issue.find(3).attachments),
630 :attachments => Issue.find(3).attachments),
631 "#{text} failed"
631 "#{text} failed"
632 end
632 end
633
633
634 def test_attachment_link_should_link_to_latest_attachment
634 def test_attachment_link_should_link_to_latest_attachment
635 set_tmp_attachments_directory
635 set_tmp_attachments_directory
636 a1 = Attachment.generate!(:filename => "test.txt", :created_on => 1.hour.ago)
636 a1 = Attachment.generate!(:filename => "test.txt", :created_on => 1.hour.ago)
637 a2 = Attachment.generate!(:filename => "test.txt")
637 a2 = Attachment.generate!(:filename => "test.txt")
638 result = link_to("test.txt", "/attachments/download/#{a2.id}/test.txt",
638 result = link_to("test.txt", "/attachments/download/#{a2.id}/test.txt",
639 :class => "attachment")
639 :class => "attachment")
640 assert_equal "<p>#{result}</p>",
640 assert_equal "<p>#{result}</p>",
641 textilizable('attachment:test.txt', :attachments => [a1, a2])
641 textilizable('attachment:test.txt', :attachments => [a1, a2])
642 end
642 end
643
643
644 def test_wiki_links
644 def test_wiki_links
645 russian_eacape = CGI.escape(@russian_test)
645 russian_eacape = CGI.escape(@russian_test)
646 to_test = {
646 to_test = {
647 '[[CookBook documentation]]' =>
647 '[[CookBook documentation]]' =>
648 link_to("CookBook documentation",
648 link_to("CookBook documentation",
649 "/projects/ecookbook/wiki/CookBook_documentation",
649 "/projects/ecookbook/wiki/CookBook_documentation",
650 :class => "wiki-page"),
650 :class => "wiki-page"),
651 '[[Another page|Page]]' =>
651 '[[Another page|Page]]' =>
652 link_to("Page",
652 link_to("Page",
653 "/projects/ecookbook/wiki/Another_page",
653 "/projects/ecookbook/wiki/Another_page",
654 :class => "wiki-page"),
654 :class => "wiki-page"),
655 # title content should be formatted
655 # title content should be formatted
656 '[[Another page|With _styled_ *title*]]' =>
656 '[[Another page|With _styled_ *title*]]' =>
657 link_to("With <em>styled</em> <strong>title</strong>".html_safe,
657 link_to("With <em>styled</em> <strong>title</strong>".html_safe,
658 "/projects/ecookbook/wiki/Another_page",
658 "/projects/ecookbook/wiki/Another_page",
659 :class => "wiki-page"),
659 :class => "wiki-page"),
660 '[[Another page|With title containing <strong>HTML entities &amp; markups</strong>]]' =>
660 '[[Another page|With title containing <strong>HTML entities &amp; markups</strong>]]' =>
661 link_to("With title containing &lt;strong&gt;HTML entities &amp; markups&lt;/strong&gt;".html_safe,
661 link_to("With title containing &lt;strong&gt;HTML entities &amp; markups&lt;/strong&gt;".html_safe,
662 "/projects/ecookbook/wiki/Another_page",
662 "/projects/ecookbook/wiki/Another_page",
663 :class => "wiki-page"),
663 :class => "wiki-page"),
664 # link with anchor
664 # link with anchor
665 '[[CookBook documentation#One-section]]' =>
665 '[[CookBook documentation#One-section]]' =>
666 link_to("CookBook documentation",
666 link_to("CookBook documentation",
667 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
667 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
668 :class => "wiki-page"),
668 :class => "wiki-page"),
669 '[[Another page#anchor|Page]]' =>
669 '[[Another page#anchor|Page]]' =>
670 link_to("Page",
670 link_to("Page",
671 "/projects/ecookbook/wiki/Another_page#anchor",
671 "/projects/ecookbook/wiki/Another_page#anchor",
672 :class => "wiki-page"),
672 :class => "wiki-page"),
673 # UTF8 anchor
673 # UTF8 anchor
674 "[[Another_page##{@russian_test}|#{@russian_test}]]" =>
674 "[[Another_page##{@russian_test}|#{@russian_test}]]" =>
675 link_to(@russian_test,
675 link_to(@russian_test,
676 "/projects/ecookbook/wiki/Another_page##{russian_eacape}",
676 "/projects/ecookbook/wiki/Another_page##{russian_eacape}",
677 :class => "wiki-page"),
677 :class => "wiki-page"),
678 # page that doesn't exist
678 # page that doesn't exist
679 '[[Unknown page]]' =>
679 '[[Unknown page]]' =>
680 link_to("Unknown page",
680 link_to("Unknown page",
681 "/projects/ecookbook/wiki/Unknown_page",
681 "/projects/ecookbook/wiki/Unknown_page",
682 :class => "wiki-page new"),
682 :class => "wiki-page new"),
683 '[[Unknown page|404]]' =>
683 '[[Unknown page|404]]' =>
684 link_to("404",
684 link_to("404",
685 "/projects/ecookbook/wiki/Unknown_page",
685 "/projects/ecookbook/wiki/Unknown_page",
686 :class => "wiki-page new"),
686 :class => "wiki-page new"),
687 # link to another project wiki
687 # link to another project wiki
688 '[[onlinestore:]]' =>
688 '[[onlinestore:]]' =>
689 link_to("onlinestore",
689 link_to("onlinestore",
690 "/projects/onlinestore/wiki",
690 "/projects/onlinestore/wiki",
691 :class => "wiki-page"),
691 :class => "wiki-page"),
692 '[[onlinestore:|Wiki]]' =>
692 '[[onlinestore:|Wiki]]' =>
693 link_to("Wiki",
693 link_to("Wiki",
694 "/projects/onlinestore/wiki",
694 "/projects/onlinestore/wiki",
695 :class => "wiki-page"),
695 :class => "wiki-page"),
696 '[[onlinestore:Start page]]' =>
696 '[[onlinestore:Start page]]' =>
697 link_to("Start page",
697 link_to("Start page",
698 "/projects/onlinestore/wiki/Start_page",
698 "/projects/onlinestore/wiki/Start_page",
699 :class => "wiki-page"),
699 :class => "wiki-page"),
700 '[[onlinestore:Start page|Text]]' =>
700 '[[onlinestore:Start page|Text]]' =>
701 link_to("Text",
701 link_to("Text",
702 "/projects/onlinestore/wiki/Start_page",
702 "/projects/onlinestore/wiki/Start_page",
703 :class => "wiki-page"),
703 :class => "wiki-page"),
704 '[[onlinestore:Unknown page]]' =>
704 '[[onlinestore:Unknown page]]' =>
705 link_to("Unknown page",
705 link_to("Unknown page",
706 "/projects/onlinestore/wiki/Unknown_page",
706 "/projects/onlinestore/wiki/Unknown_page",
707 :class => "wiki-page new"),
707 :class => "wiki-page new"),
708 # striked through link
708 # striked through link
709 '-[[Another page|Page]]-' =>
709 '-[[Another page|Page]]-' =>
710 "<del>".html_safe +
710 "<del>".html_safe +
711 link_to("Page",
711 link_to("Page",
712 "/projects/ecookbook/wiki/Another_page",
712 "/projects/ecookbook/wiki/Another_page",
713 :class => "wiki-page").html_safe +
713 :class => "wiki-page").html_safe +
714 "</del>".html_safe,
714 "</del>".html_safe,
715 '-[[Another page|Page]] link-' =>
715 '-[[Another page|Page]] link-' =>
716 "<del>".html_safe +
716 "<del>".html_safe +
717 link_to("Page",
717 link_to("Page",
718 "/projects/ecookbook/wiki/Another_page",
718 "/projects/ecookbook/wiki/Another_page",
719 :class => "wiki-page").html_safe +
719 :class => "wiki-page").html_safe +
720 " link</del>".html_safe,
720 " link</del>".html_safe,
721 # escaping
721 # escaping
722 '![[Another page|Page]]' => '[[Another page|Page]]',
722 '![[Another page|Page]]' => '[[Another page|Page]]',
723 # project does not exist
723 # project does not exist
724 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
724 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
725 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
725 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
726 }
726 }
727 @project = Project.find(1)
727 @project = Project.find(1)
728 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
728 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
729 end
729 end
730
730
731 def test_wiki_links_within_local_file_generation_context
731 def test_wiki_links_within_local_file_generation_context
732 to_test = {
732 to_test = {
733 # link to a page
733 # link to a page
734 '[[CookBook documentation]]' =>
734 '[[CookBook documentation]]' =>
735 link_to("CookBook documentation", "CookBook_documentation.html",
735 link_to("CookBook documentation", "CookBook_documentation.html",
736 :class => "wiki-page"),
736 :class => "wiki-page"),
737 '[[CookBook documentation|documentation]]' =>
737 '[[CookBook documentation|documentation]]' =>
738 link_to("documentation", "CookBook_documentation.html",
738 link_to("documentation", "CookBook_documentation.html",
739 :class => "wiki-page"),
739 :class => "wiki-page"),
740 '[[CookBook documentation#One-section]]' =>
740 '[[CookBook documentation#One-section]]' =>
741 link_to("CookBook documentation", "CookBook_documentation.html#One-section",
741 link_to("CookBook documentation", "CookBook_documentation.html#One-section",
742 :class => "wiki-page"),
742 :class => "wiki-page"),
743 '[[CookBook documentation#One-section|documentation]]' =>
743 '[[CookBook documentation#One-section|documentation]]' =>
744 link_to("documentation", "CookBook_documentation.html#One-section",
744 link_to("documentation", "CookBook_documentation.html#One-section",
745 :class => "wiki-page"),
745 :class => "wiki-page"),
746 # page that doesn't exist
746 # page that doesn't exist
747 '[[Unknown page]]' =>
747 '[[Unknown page]]' =>
748 link_to("Unknown page", "Unknown_page.html",
748 link_to("Unknown page", "Unknown_page.html",
749 :class => "wiki-page new"),
749 :class => "wiki-page new"),
750 '[[Unknown page|404]]' =>
750 '[[Unknown page|404]]' =>
751 link_to("404", "Unknown_page.html",
751 link_to("404", "Unknown_page.html",
752 :class => "wiki-page new"),
752 :class => "wiki-page new"),
753 '[[Unknown page#anchor]]' =>
753 '[[Unknown page#anchor]]' =>
754 link_to("Unknown page", "Unknown_page.html#anchor",
754 link_to("Unknown page", "Unknown_page.html#anchor",
755 :class => "wiki-page new"),
755 :class => "wiki-page new"),
756 '[[Unknown page#anchor|404]]' =>
756 '[[Unknown page#anchor|404]]' =>
757 link_to("404", "Unknown_page.html#anchor",
757 link_to("404", "Unknown_page.html#anchor",
758 :class => "wiki-page new"),
758 :class => "wiki-page new"),
759 }
759 }
760 @project = Project.find(1)
760 @project = Project.find(1)
761 to_test.each do |text, result|
761 to_test.each do |text, result|
762 assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :local)
762 assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :local)
763 end
763 end
764 end
764 end
765
765
766 def test_wiki_links_within_wiki_page_context
766 def test_wiki_links_within_wiki_page_context
767 page = WikiPage.find_by_title('Another_page' )
767 page = WikiPage.find_by_title('Another_page' )
768 to_test = {
768 to_test = {
769 '[[CookBook documentation]]' =>
769 '[[CookBook documentation]]' =>
770 link_to("CookBook documentation",
770 link_to("CookBook documentation",
771 "/projects/ecookbook/wiki/CookBook_documentation",
771 "/projects/ecookbook/wiki/CookBook_documentation",
772 :class => "wiki-page"),
772 :class => "wiki-page"),
773 '[[CookBook documentation|documentation]]' =>
773 '[[CookBook documentation|documentation]]' =>
774 link_to("documentation",
774 link_to("documentation",
775 "/projects/ecookbook/wiki/CookBook_documentation",
775 "/projects/ecookbook/wiki/CookBook_documentation",
776 :class => "wiki-page"),
776 :class => "wiki-page"),
777 '[[CookBook documentation#One-section]]' =>
777 '[[CookBook documentation#One-section]]' =>
778 link_to("CookBook documentation",
778 link_to("CookBook documentation",
779 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
779 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
780 :class => "wiki-page"),
780 :class => "wiki-page"),
781 '[[CookBook documentation#One-section|documentation]]' =>
781 '[[CookBook documentation#One-section|documentation]]' =>
782 link_to("documentation",
782 link_to("documentation",
783 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
783 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
784 :class => "wiki-page"),
784 :class => "wiki-page"),
785 # link to the current page
785 # link to the current page
786 '[[Another page]]' =>
786 '[[Another page]]' =>
787 link_to("Another page",
787 link_to("Another page",
788 "/projects/ecookbook/wiki/Another_page",
788 "/projects/ecookbook/wiki/Another_page",
789 :class => "wiki-page"),
789 :class => "wiki-page"),
790 '[[Another page|Page]]' =>
790 '[[Another page|Page]]' =>
791 link_to("Page",
791 link_to("Page",
792 "/projects/ecookbook/wiki/Another_page",
792 "/projects/ecookbook/wiki/Another_page",
793 :class => "wiki-page"),
793 :class => "wiki-page"),
794 '[[Another page#anchor]]' =>
794 '[[Another page#anchor]]' =>
795 link_to("Another page",
795 link_to("Another page",
796 "#anchor",
796 "#anchor",
797 :class => "wiki-page"),
797 :class => "wiki-page"),
798 '[[Another page#anchor|Page]]' =>
798 '[[Another page#anchor|Page]]' =>
799 link_to("Page",
799 link_to("Page",
800 "#anchor",
800 "#anchor",
801 :class => "wiki-page"),
801 :class => "wiki-page"),
802 # page that doesn't exist
802 # page that doesn't exist
803 '[[Unknown page]]' =>
803 '[[Unknown page]]' =>
804 link_to("Unknown page",
804 link_to("Unknown page",
805 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page",
805 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page",
806 :class => "wiki-page new"),
806 :class => "wiki-page new"),
807 '[[Unknown page|404]]' =>
807 '[[Unknown page|404]]' =>
808 link_to("404",
808 link_to("404",
809 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page",
809 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page",
810 :class => "wiki-page new"),
810 :class => "wiki-page new"),
811 '[[Unknown page#anchor]]' =>
811 '[[Unknown page#anchor]]' =>
812 link_to("Unknown page",
812 link_to("Unknown page",
813 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor",
813 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor",
814 :class => "wiki-page new"),
814 :class => "wiki-page new"),
815 '[[Unknown page#anchor|404]]' =>
815 '[[Unknown page#anchor|404]]' =>
816 link_to("404",
816 link_to("404",
817 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor",
817 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor",
818 :class => "wiki-page new"),
818 :class => "wiki-page new"),
819 }
819 }
820 @project = Project.find(1)
820 @project = Project.find(1)
821 to_test.each do |text, result|
821 to_test.each do |text, result|
822 assert_equal "<p>#{result}</p>",
822 assert_equal "<p>#{result}</p>",
823 textilizable(WikiContent.new( :text => text, :page => page ), :text)
823 textilizable(WikiContent.new( :text => text, :page => page ), :text)
824 end
824 end
825 end
825 end
826
826
827 def test_wiki_links_anchor_option_should_prepend_page_title_to_href
827 def test_wiki_links_anchor_option_should_prepend_page_title_to_href
828 to_test = {
828 to_test = {
829 # link to a page
829 # link to a page
830 '[[CookBook documentation]]' =>
830 '[[CookBook documentation]]' =>
831 link_to("CookBook documentation",
831 link_to("CookBook documentation",
832 "#CookBook_documentation",
832 "#CookBook_documentation",
833 :class => "wiki-page"),
833 :class => "wiki-page"),
834 '[[CookBook documentation|documentation]]' =>
834 '[[CookBook documentation|documentation]]' =>
835 link_to("documentation",
835 link_to("documentation",
836 "#CookBook_documentation",
836 "#CookBook_documentation",
837 :class => "wiki-page"),
837 :class => "wiki-page"),
838 '[[CookBook documentation#One-section]]' =>
838 '[[CookBook documentation#One-section]]' =>
839 link_to("CookBook documentation",
839 link_to("CookBook documentation",
840 "#CookBook_documentation_One-section",
840 "#CookBook_documentation_One-section",
841 :class => "wiki-page"),
841 :class => "wiki-page"),
842 '[[CookBook documentation#One-section|documentation]]' =>
842 '[[CookBook documentation#One-section|documentation]]' =>
843 link_to("documentation",
843 link_to("documentation",
844 "#CookBook_documentation_One-section",
844 "#CookBook_documentation_One-section",
845 :class => "wiki-page"),
845 :class => "wiki-page"),
846 # page that doesn't exist
846 # page that doesn't exist
847 '[[Unknown page]]' =>
847 '[[Unknown page]]' =>
848 link_to("Unknown page",
848 link_to("Unknown page",
849 "#Unknown_page",
849 "#Unknown_page",
850 :class => "wiki-page new"),
850 :class => "wiki-page new"),
851 '[[Unknown page|404]]' =>
851 '[[Unknown page|404]]' =>
852 link_to("404",
852 link_to("404",
853 "#Unknown_page",
853 "#Unknown_page",
854 :class => "wiki-page new"),
854 :class => "wiki-page new"),
855 '[[Unknown page#anchor]]' =>
855 '[[Unknown page#anchor]]' =>
856 link_to("Unknown page",
856 link_to("Unknown page",
857 "#Unknown_page_anchor",
857 "#Unknown_page_anchor",
858 :class => "wiki-page new"),
858 :class => "wiki-page new"),
859 '[[Unknown page#anchor|404]]' =>
859 '[[Unknown page#anchor|404]]' =>
860 link_to("404",
860 link_to("404",
861 "#Unknown_page_anchor",
861 "#Unknown_page_anchor",
862 :class => "wiki-page new"),
862 :class => "wiki-page new"),
863 }
863 }
864 @project = Project.find(1)
864 @project = Project.find(1)
865 to_test.each do |text, result|
865 to_test.each do |text, result|
866 assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :anchor)
866 assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :anchor)
867 end
867 end
868 end
868 end
869
869
870 def test_html_tags
870 def test_html_tags
871 to_test = {
871 to_test = {
872 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
872 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
873 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
873 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
874 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
874 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
875 # do not escape pre/code tags
875 # do not escape pre/code tags
876 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
876 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
877 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
877 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
878 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
878 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
879 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
879 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
880 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
880 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
881 # remove attributes except class
881 # remove attributes except class
882 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
882 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
883 '<pre class="foo">some text</pre>' => '<pre class="foo">some text</pre>',
883 '<pre class="foo">some text</pre>' => '<pre class="foo">some text</pre>',
884 "<pre class='foo bar'>some text</pre>" => "<pre class='foo bar'>some text</pre>",
884 "<pre class='foo bar'>some text</pre>" => "<pre class='foo bar'>some text</pre>",
885 '<pre class="foo bar">some text</pre>' => '<pre class="foo bar">some text</pre>',
885 '<pre class="foo bar">some text</pre>' => '<pre class="foo bar">some text</pre>',
886 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
886 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
887 # xss
887 # xss
888 '<pre><code class=""onmouseover="alert(1)">text</code></pre>' => '<pre><code>text</code></pre>',
888 '<pre><code class=""onmouseover="alert(1)">text</code></pre>' => '<pre><code>text</code></pre>',
889 '<pre class=""onmouseover="alert(1)">text</pre>' => '<pre>text</pre>',
889 '<pre class=""onmouseover="alert(1)">text</pre>' => '<pre>text</pre>',
890 }
890 }
891 to_test.each { |text, result| assert_equal result, textilizable(text) }
891 to_test.each { |text, result| assert_equal result, textilizable(text) }
892 end
892 end
893
893
894 def test_allowed_html_tags
894 def test_allowed_html_tags
895 to_test = {
895 to_test = {
896 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
896 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
897 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
897 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
898 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
898 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
899 }
899 }
900 to_test.each { |text, result| assert_equal result, textilizable(text) }
900 to_test.each { |text, result| assert_equal result, textilizable(text) }
901 end
901 end
902
902
903 def test_pre_tags
903 def test_pre_tags
904 raw = <<-RAW
904 raw = <<-RAW
905 Before
905 Before
906
906
907 <pre>
907 <pre>
908 <prepared-statement-cache-size>32</prepared-statement-cache-size>
908 <prepared-statement-cache-size>32</prepared-statement-cache-size>
909 </pre>
909 </pre>
910
910
911 After
911 After
912 RAW
912 RAW
913
913
914 expected = <<-EXPECTED
914 expected = <<-EXPECTED
915 <p>Before</p>
915 <p>Before</p>
916 <pre>
916 <pre>
917 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
917 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
918 </pre>
918 </pre>
919 <p>After</p>
919 <p>After</p>
920 EXPECTED
920 EXPECTED
921
921
922 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
922 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
923 end
923 end
924
924
925 def test_pre_content_should_not_parse_wiki_and_redmine_links
925 def test_pre_content_should_not_parse_wiki_and_redmine_links
926 raw = <<-RAW
926 raw = <<-RAW
927 [[CookBook documentation]]
927 [[CookBook documentation]]
928
928
929 #1
929 #1
930
930
931 <pre>
931 <pre>
932 [[CookBook documentation]]
932 [[CookBook documentation]]
933
933
934 #1
934 #1
935 </pre>
935 </pre>
936 RAW
936 RAW
937
937
938 result1 = link_to("CookBook documentation",
938 result1 = link_to("CookBook documentation",
939 "/projects/ecookbook/wiki/CookBook_documentation",
939 "/projects/ecookbook/wiki/CookBook_documentation",
940 :class => "wiki-page")
940 :class => "wiki-page")
941 result2 = link_to('#1',
941 result2 = link_to('#1',
942 "/issues/1",
942 "/issues/1",
943 :class => Issue.find(1).css_classes,
943 :class => Issue.find(1).css_classes,
944 :title => "Can't print recipes (New)")
944 :title => "Can't print recipes (New)")
945
945
946 expected = <<-EXPECTED
946 expected = <<-EXPECTED
947 <p>#{result1}</p>
947 <p>#{result1}</p>
948 <p>#{result2}</p>
948 <p>#{result2}</p>
949 <pre>
949 <pre>
950 [[CookBook documentation]]
950 [[CookBook documentation]]
951
951
952 #1
952 #1
953 </pre>
953 </pre>
954 EXPECTED
954 EXPECTED
955
955
956 @project = Project.find(1)
956 @project = Project.find(1)
957 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
957 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
958 end
958 end
959
959
960 def test_non_closing_pre_blocks_should_be_closed
960 def test_non_closing_pre_blocks_should_be_closed
961 raw = <<-RAW
961 raw = <<-RAW
962 <pre><code>
962 <pre><code>
963 RAW
963 RAW
964
964
965 expected = <<-EXPECTED
965 expected = <<-EXPECTED
966 <pre><code>
966 <pre><code>
967 </code></pre>
967 </code></pre>
968 EXPECTED
968 EXPECTED
969
969
970 @project = Project.find(1)
970 @project = Project.find(1)
971 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
971 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
972 end
972 end
973
973
974 def test_syntax_highlight
974 def test_syntax_highlight
975 raw = <<-RAW
975 raw = <<-RAW
976 <pre><code class="ruby">
976 <pre><code class="ruby">
977 # Some ruby code here
977 # Some ruby code here
978 </code></pre>
978 </code></pre>
979 RAW
979 RAW
980
980
981 expected = <<-EXPECTED
981 expected = <<-EXPECTED
982 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="comment"># Some ruby code here</span></span>
982 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="comment"># Some ruby code here</span></span>
983 </code></pre>
983 </code></pre>
984 EXPECTED
984 EXPECTED
985
985
986 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
986 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
987 end
987 end
988
988
989 def test_to_path_param
989 def test_to_path_param
990 assert_equal 'test1/test2', to_path_param('test1/test2')
990 assert_equal 'test1/test2', to_path_param('test1/test2')
991 assert_equal 'test1/test2', to_path_param('/test1/test2/')
991 assert_equal 'test1/test2', to_path_param('/test1/test2/')
992 assert_equal 'test1/test2', to_path_param('//test1/test2/')
992 assert_equal 'test1/test2', to_path_param('//test1/test2/')
993 assert_equal nil, to_path_param('/')
993 assert_equal nil, to_path_param('/')
994 end
994 end
995
995
996 def test_wiki_links_in_tables
996 def test_wiki_links_in_tables
997 text = "|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|"
997 text = "|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|"
998 link1 = link_to("Link title", "/projects/ecookbook/wiki/Page", :class => "wiki-page new")
998 link1 = link_to("Link title", "/projects/ecookbook/wiki/Page", :class => "wiki-page new")
999 link2 = link_to("Other title", "/projects/ecookbook/wiki/Other_Page", :class => "wiki-page new")
999 link2 = link_to("Other title", "/projects/ecookbook/wiki/Other_Page", :class => "wiki-page new")
1000 link3 = link_to("Last page", "/projects/ecookbook/wiki/Last_page", :class => "wiki-page new")
1000 link3 = link_to("Last page", "/projects/ecookbook/wiki/Last_page", :class => "wiki-page new")
1001 result = "<tr><td>#{link1}</td>" +
1001 result = "<tr><td>#{link1}</td>" +
1002 "<td>#{link2}</td>" +
1002 "<td>#{link2}</td>" +
1003 "</tr><tr><td>Cell 21</td><td>#{link3}</td></tr>"
1003 "</tr><tr><td>Cell 21</td><td>#{link3}</td></tr>"
1004 @project = Project.find(1)
1004 @project = Project.find(1)
1005 assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '')
1005 assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '')
1006 end
1006 end
1007
1007
1008 def test_text_formatting
1008 def test_text_formatting
1009 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
1009 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
1010 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
1010 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
1011 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
1011 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
1012 '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>',
1012 '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>',
1013 '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',
1013 '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',
1014 }
1014 }
1015 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
1015 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
1016 end
1016 end
1017
1017
1018 def test_wiki_horizontal_rule
1018 def test_wiki_horizontal_rule
1019 assert_equal '<hr />', textilizable('---')
1019 assert_equal '<hr />', textilizable('---')
1020 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
1020 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
1021 end
1021 end
1022
1022
1023 def test_footnotes
1023 def test_footnotes
1024 raw = <<-RAW
1024 raw = <<-RAW
1025 This is some text[1].
1025 This is some text[1].
1026
1026
1027 fn1. This is the foot note
1027 fn1. This is the foot note
1028 RAW
1028 RAW
1029
1029
1030 expected = <<-EXPECTED
1030 expected = <<-EXPECTED
1031 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
1031 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
1032 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
1032 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
1033 EXPECTED
1033 EXPECTED
1034
1034
1035 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
1035 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
1036 end
1036 end
1037
1037
1038 def test_headings
1038 def test_headings
1039 raw = 'h1. Some heading'
1039 raw = 'h1. Some heading'
1040 expected = %|<a name="Some-heading"></a>\n<h1 >Some heading<a href="#Some-heading" class="wiki-anchor">&para;</a></h1>|
1040 expected = %|<a name="Some-heading"></a>\n<h1 >Some heading<a href="#Some-heading" class="wiki-anchor">&para;</a></h1>|
1041
1041
1042 assert_equal expected, textilizable(raw)
1042 assert_equal expected, textilizable(raw)
1043 end
1043 end
1044
1044
1045 def test_headings_with_special_chars
1045 def test_headings_with_special_chars
1046 # This test makes sure that the generated anchor names match the expected
1046 # This test makes sure that the generated anchor names match the expected
1047 # ones even if the heading text contains unconventional characters
1047 # ones even if the heading text contains unconventional characters
1048 raw = 'h1. Some heading related to version 0.5'
1048 raw = 'h1. Some heading related to version 0.5'
1049 anchor = sanitize_anchor_name("Some-heading-related-to-version-0.5")
1049 anchor = sanitize_anchor_name("Some-heading-related-to-version-0.5")
1050 expected = %|<a name="#{anchor}"></a>\n<h1 >Some heading related to version 0.5<a href="##{anchor}" class="wiki-anchor">&para;</a></h1>|
1050 expected = %|<a name="#{anchor}"></a>\n<h1 >Some heading related to version 0.5<a href="##{anchor}" class="wiki-anchor">&para;</a></h1>|
1051
1051
1052 assert_equal expected, textilizable(raw)
1052 assert_equal expected, textilizable(raw)
1053 end
1053 end
1054
1054
1055 def test_headings_in_wiki_single_page_export_should_be_prepended_with_page_title
1055 def test_headings_in_wiki_single_page_export_should_be_prepended_with_page_title
1056 page = WikiPage.new( :title => 'Page Title', :wiki_id => 1 )
1056 page = WikiPage.new( :title => 'Page Title', :wiki_id => 1 )
1057 content = WikiContent.new( :text => 'h1. Some heading', :page => page )
1057 content = WikiContent.new( :text => 'h1. Some heading', :page => page )
1058
1058
1059 expected = %|<a name="Page_Title_Some-heading"></a>\n<h1 >Some heading<a href="#Page_Title_Some-heading" class="wiki-anchor">&para;</a></h1>|
1059 expected = %|<a name="Page_Title_Some-heading"></a>\n<h1 >Some heading<a href="#Page_Title_Some-heading" class="wiki-anchor">&para;</a></h1>|
1060
1060
1061 assert_equal expected, textilizable(content, :text, :wiki_links => :anchor )
1061 assert_equal expected, textilizable(content, :text, :wiki_links => :anchor )
1062 end
1062 end
1063
1063
1064 def test_table_of_content
1064 def test_table_of_content
1065 raw = <<-RAW
1065 raw = <<-RAW
1066 {{toc}}
1066 {{toc}}
1067
1067
1068 h1. Title
1068 h1. Title
1069
1069
1070 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
1070 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
1071
1071
1072 h2. Subtitle with a [[Wiki]] link
1072 h2. Subtitle with a [[Wiki]] link
1073
1073
1074 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
1074 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
1075
1075
1076 h2. Subtitle with [[Wiki|another Wiki]] link
1076 h2. Subtitle with [[Wiki|another Wiki]] link
1077
1077
1078 h2. Subtitle with %{color:red}red text%
1078 h2. Subtitle with %{color:red}red text%
1079
1079
1080 <pre>
1080 <pre>
1081 some code
1081 some code
1082 </pre>
1082 </pre>
1083
1083
1084 h3. Subtitle with *some* _modifiers_
1084 h3. Subtitle with *some* _modifiers_
1085
1085
1086 h3. Subtitle with @inline code@
1086 h3. Subtitle with @inline code@
1087
1087
1088 h1. Another title
1088 h1. Another title
1089
1089
1090 h3. An "Internet link":http://www.redmine.org/ inside subtitle
1090 h3. An "Internet link":http://www.redmine.org/ inside subtitle
1091
1091
1092 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
1092 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
1093
1093
1094 RAW
1094 RAW
1095
1095
1096 expected = '<ul class="toc">' +
1096 expected = '<ul class="toc">' +
1097 '<li><a href="#Title">Title</a>' +
1097 '<li><a href="#Title">Title</a>' +
1098 '<ul>' +
1098 '<ul>' +
1099 '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
1099 '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
1100 '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
1100 '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
1101 '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
1101 '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
1102 '<ul>' +
1102 '<ul>' +
1103 '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
1103 '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
1104 '<li><a href="#Subtitle-with-inline-code">Subtitle with inline code</a></li>' +
1104 '<li><a href="#Subtitle-with-inline-code">Subtitle with inline code</a></li>' +
1105 '</ul>' +
1105 '</ul>' +
1106 '</li>' +
1106 '</li>' +
1107 '</ul>' +
1107 '</ul>' +
1108 '</li>' +
1108 '</li>' +
1109 '<li><a href="#Another-title">Another title</a>' +
1109 '<li><a href="#Another-title">Another title</a>' +
1110 '<ul>' +
1110 '<ul>' +
1111 '<li>' +
1111 '<li>' +
1112 '<ul>' +
1112 '<ul>' +
1113 '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
1113 '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
1114 '</ul>' +
1114 '</ul>' +
1115 '</li>' +
1115 '</li>' +
1116 '<li><a href="#Project-Name">Project Name</a></li>' +
1116 '<li><a href="#Project-Name">Project Name</a></li>' +
1117 '</ul>' +
1117 '</ul>' +
1118 '</li>' +
1118 '</li>' +
1119 '</ul>'
1119 '</ul>'
1120
1120
1121 @project = Project.find(1)
1121 @project = Project.find(1)
1122 assert textilizable(raw).gsub("\n", "").include?(expected)
1122 assert textilizable(raw).gsub("\n", "").include?(expected)
1123 end
1123 end
1124
1124
1125 def test_table_of_content_should_generate_unique_anchors
1125 def test_table_of_content_should_generate_unique_anchors
1126 raw = <<-RAW
1126 raw = <<-RAW
1127 {{toc}}
1127 {{toc}}
1128
1128
1129 h1. Title
1129 h1. Title
1130
1130
1131 h2. Subtitle
1131 h2. Subtitle
1132
1132
1133 h2. Subtitle
1133 h2. Subtitle
1134 RAW
1134 RAW
1135
1135
1136 expected = '<ul class="toc">' +
1136 expected = '<ul class="toc">' +
1137 '<li><a href="#Title">Title</a>' +
1137 '<li><a href="#Title">Title</a>' +
1138 '<ul>' +
1138 '<ul>' +
1139 '<li><a href="#Subtitle">Subtitle</a></li>' +
1139 '<li><a href="#Subtitle">Subtitle</a></li>' +
1140 '<li><a href="#Subtitle-2">Subtitle</a></li>'
1140 '<li><a href="#Subtitle-2">Subtitle</a></li>'
1141 '</ul>'
1141 '</ul>'
1142 '</li>' +
1142 '</li>' +
1143 '</ul>'
1143 '</ul>'
1144
1144
1145 @project = Project.find(1)
1145 @project = Project.find(1)
1146 result = textilizable(raw).gsub("\n", "")
1146 result = textilizable(raw).gsub("\n", "")
1147 assert_include expected, result
1147 assert_include expected, result
1148 assert_include '<a name="Subtitle">', result
1148 assert_include '<a name="Subtitle">', result
1149 assert_include '<a name="Subtitle-2">', result
1149 assert_include '<a name="Subtitle-2">', result
1150 end
1150 end
1151
1151
1152 def test_table_of_content_should_contain_included_page_headings
1152 def test_table_of_content_should_contain_included_page_headings
1153 raw = <<-RAW
1153 raw = <<-RAW
1154 {{toc}}
1154 {{toc}}
1155
1155
1156 h1. Included
1156 h1. Included
1157
1157
1158 {{include(Child_1)}}
1158 {{include(Child_1)}}
1159 RAW
1159 RAW
1160
1160
1161 expected = '<ul class="toc">' +
1161 expected = '<ul class="toc">' +
1162 '<li><a href="#Included">Included</a></li>' +
1162 '<li><a href="#Included">Included</a></li>' +
1163 '<li><a href="#Child-page-1">Child page 1</a></li>' +
1163 '<li><a href="#Child-page-1">Child page 1</a></li>' +
1164 '</ul>'
1164 '</ul>'
1165
1165
1166 @project = Project.find(1)
1166 @project = Project.find(1)
1167 assert textilizable(raw).gsub("\n", "").include?(expected)
1167 assert textilizable(raw).gsub("\n", "").include?(expected)
1168 end
1168 end
1169
1169
1170 def test_toc_with_textile_formatting_should_be_parsed
1171 with_settings :text_formatting => 'textile' do
1172 assert_select_in textilizable("{{toc}}\n\nh1. Heading"), 'ul.toc li', :text => 'Heading'
1173 assert_select_in textilizable("{{<toc}}\n\nh1. Heading"), 'ul.toc.left li', :text => 'Heading'
1174 assert_select_in textilizable("{{>toc}}\n\nh1. Heading"), 'ul.toc.right li', :text => 'Heading'
1175 end
1176 end
1177
1178 if Object.const_defined?(:Redcarpet)
1179 def test_toc_with_markdown_formatting_should_be_parsed
1180 with_settings :text_formatting => 'markdown' do
1181 assert_select_in textilizable("{{toc}}\n\n# Heading"), 'ul.toc li', :text => 'Heading'
1182 assert_select_in textilizable("{{<toc}}\n\n# Heading"), 'ul.toc.left li', :text => 'Heading'
1183 assert_select_in textilizable("{{>toc}}\n\n# Heading"), 'ul.toc.right li', :text => 'Heading'
1184 end
1185 end
1186 end
1187
1170 def test_section_edit_links
1188 def test_section_edit_links
1171 raw = <<-RAW
1189 raw = <<-RAW
1172 h1. Title
1190 h1. Title
1173
1191
1174 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
1192 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
1175
1193
1176 h2. Subtitle with a [[Wiki]] link
1194 h2. Subtitle with a [[Wiki]] link
1177
1195
1178 h2. Subtitle with *some* _modifiers_
1196 h2. Subtitle with *some* _modifiers_
1179
1197
1180 h2. Subtitle with @inline code@
1198 h2. Subtitle with @inline code@
1181
1199
1182 <pre>
1200 <pre>
1183 some code
1201 some code
1184
1202
1185 h2. heading inside pre
1203 h2. heading inside pre
1186
1204
1187 <h2>html heading inside pre</h2>
1205 <h2>html heading inside pre</h2>
1188 </pre>
1206 </pre>
1189
1207
1190 h2. Subtitle after pre tag
1208 h2. Subtitle after pre tag
1191 RAW
1209 RAW
1192
1210
1193 @project = Project.find(1)
1211 @project = Project.find(1)
1194 set_language_if_valid 'en'
1212 set_language_if_valid 'en'
1195 result = textilizable(raw, :edit_section_links => {:controller => 'wiki', :action => 'edit', :project_id => '1', :id => 'Test'}).gsub("\n", "")
1213 result = textilizable(raw, :edit_section_links => {:controller => 'wiki', :action => 'edit', :project_id => '1', :id => 'Test'}).gsub("\n", "")
1196
1214
1197 # heading that contains inline code
1215 # heading that contains inline code
1198 assert_match Regexp.new('<div class="contextual" id="section-4" title="Edit this section">' +
1216 assert_match Regexp.new('<div class="contextual" id="section-4" title="Edit this section">' +
1199 '<a href="/projects/1/wiki/Test/edit\?section=4"><img alt="Edit" src="/images/edit.png(\?\d+)?" /></a></div>' +
1217 '<a href="/projects/1/wiki/Test/edit\?section=4"><img alt="Edit" src="/images/edit.png(\?\d+)?" /></a></div>' +
1200 '<a name="Subtitle-with-inline-code"></a>' +
1218 '<a name="Subtitle-with-inline-code"></a>' +
1201 '<h2 >Subtitle with <code>inline code</code><a href="#Subtitle-with-inline-code" class="wiki-anchor">&para;</a></h2>'),
1219 '<h2 >Subtitle with <code>inline code</code><a href="#Subtitle-with-inline-code" class="wiki-anchor">&para;</a></h2>'),
1202 result
1220 result
1203
1221
1204 # last heading
1222 # last heading
1205 assert_match Regexp.new('<div class="contextual" id="section-5" title="Edit this section">' +
1223 assert_match Regexp.new('<div class="contextual" id="section-5" title="Edit this section">' +
1206 '<a href="/projects/1/wiki/Test/edit\?section=5"><img alt="Edit" src="/images/edit.png(\?\d+)?" /></a></div>' +
1224 '<a href="/projects/1/wiki/Test/edit\?section=5"><img alt="Edit" src="/images/edit.png(\?\d+)?" /></a></div>' +
1207 '<a name="Subtitle-after-pre-tag"></a>' +
1225 '<a name="Subtitle-after-pre-tag"></a>' +
1208 '<h2 >Subtitle after pre tag<a href="#Subtitle-after-pre-tag" class="wiki-anchor">&para;</a></h2>'),
1226 '<h2 >Subtitle after pre tag<a href="#Subtitle-after-pre-tag" class="wiki-anchor">&para;</a></h2>'),
1209 result
1227 result
1210 end
1228 end
1211
1229
1212 def test_default_formatter
1230 def test_default_formatter
1213 with_settings :text_formatting => 'unknown' do
1231 with_settings :text_formatting => 'unknown' do
1214 text = 'a *link*: http://www.example.net/'
1232 text = 'a *link*: http://www.example.net/'
1215 assert_equal '<p>a *link*: <a class="external" href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
1233 assert_equal '<p>a *link*: <a class="external" href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
1216 end
1234 end
1217 end
1235 end
1218
1236
1219 def test_due_date_distance_in_words
1237 def test_due_date_distance_in_words
1220 to_test = { Date.today => 'Due in 0 days',
1238 to_test = { Date.today => 'Due in 0 days',
1221 Date.today + 1 => 'Due in 1 day',
1239 Date.today + 1 => 'Due in 1 day',
1222 Date.today + 100 => 'Due in about 3 months',
1240 Date.today + 100 => 'Due in about 3 months',
1223 Date.today + 20000 => 'Due in over 54 years',
1241 Date.today + 20000 => 'Due in over 54 years',
1224 Date.today - 1 => '1 day late',
1242 Date.today - 1 => '1 day late',
1225 Date.today - 100 => 'about 3 months late',
1243 Date.today - 100 => 'about 3 months late',
1226 Date.today - 20000 => 'over 54 years late',
1244 Date.today - 20000 => 'over 54 years late',
1227 }
1245 }
1228 ::I18n.locale = :en
1246 ::I18n.locale = :en
1229 to_test.each do |date, expected|
1247 to_test.each do |date, expected|
1230 assert_equal expected, due_date_distance_in_words(date)
1248 assert_equal expected, due_date_distance_in_words(date)
1231 end
1249 end
1232 end
1250 end
1233
1251
1234 def test_avatar_enabled
1252 def test_avatar_enabled
1235 with_settings :gravatar_enabled => '1' do
1253 with_settings :gravatar_enabled => '1' do
1236 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
1254 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
1237 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
1255 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
1238 # Default size is 50
1256 # Default size is 50
1239 assert avatar('jsmith <jsmith@somenet.foo>').include?('size=50')
1257 assert avatar('jsmith <jsmith@somenet.foo>').include?('size=50')
1240 assert avatar('jsmith <jsmith@somenet.foo>', :size => 24).include?('size=24')
1258 assert avatar('jsmith <jsmith@somenet.foo>', :size => 24).include?('size=24')
1241 # Non-avatar options should be considered html options
1259 # Non-avatar options should be considered html options
1242 assert avatar('jsmith <jsmith@somenet.foo>', :title => 'John Smith').include?('title="John Smith"')
1260 assert avatar('jsmith <jsmith@somenet.foo>', :title => 'John Smith').include?('title="John Smith"')
1243 # The default class of the img tag should be gravatar
1261 # The default class of the img tag should be gravatar
1244 assert avatar('jsmith <jsmith@somenet.foo>').include?('class="gravatar"')
1262 assert avatar('jsmith <jsmith@somenet.foo>').include?('class="gravatar"')
1245 assert !avatar('jsmith <jsmith@somenet.foo>', :class => 'picture').include?('class="gravatar"')
1263 assert !avatar('jsmith <jsmith@somenet.foo>', :class => 'picture').include?('class="gravatar"')
1246 assert_nil avatar('jsmith')
1264 assert_nil avatar('jsmith')
1247 assert_nil avatar(nil)
1265 assert_nil avatar(nil)
1248 end
1266 end
1249 end
1267 end
1250
1268
1251 def test_avatar_disabled
1269 def test_avatar_disabled
1252 with_settings :gravatar_enabled => '0' do
1270 with_settings :gravatar_enabled => '0' do
1253 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
1271 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
1254 end
1272 end
1255 end
1273 end
1256
1274
1257 def test_link_to_user
1275 def test_link_to_user
1258 user = User.find(2)
1276 user = User.find(2)
1259 result = link_to("John Smith", "/users/2", :class => "user active")
1277 result = link_to("John Smith", "/users/2", :class => "user active")
1260 assert_equal result, link_to_user(user)
1278 assert_equal result, link_to_user(user)
1261 end
1279 end
1262
1280
1263 def test_link_to_user_should_not_link_to_locked_user
1281 def test_link_to_user_should_not_link_to_locked_user
1264 with_current_user nil do
1282 with_current_user nil do
1265 user = User.find(5)
1283 user = User.find(5)
1266 assert user.locked?
1284 assert user.locked?
1267 assert_equal 'Dave2 Lopper2', link_to_user(user)
1285 assert_equal 'Dave2 Lopper2', link_to_user(user)
1268 end
1286 end
1269 end
1287 end
1270
1288
1271 def test_link_to_user_should_link_to_locked_user_if_current_user_is_admin
1289 def test_link_to_user_should_link_to_locked_user_if_current_user_is_admin
1272 with_current_user User.find(1) do
1290 with_current_user User.find(1) do
1273 user = User.find(5)
1291 user = User.find(5)
1274 assert user.locked?
1292 assert user.locked?
1275 result = link_to("Dave2 Lopper2", "/users/5", :class => "user locked")
1293 result = link_to("Dave2 Lopper2", "/users/5", :class => "user locked")
1276 assert_equal result, link_to_user(user)
1294 assert_equal result, link_to_user(user)
1277 end
1295 end
1278 end
1296 end
1279
1297
1280 def test_link_to_user_should_not_link_to_anonymous
1298 def test_link_to_user_should_not_link_to_anonymous
1281 user = User.anonymous
1299 user = User.anonymous
1282 assert user.anonymous?
1300 assert user.anonymous?
1283 t = link_to_user(user)
1301 t = link_to_user(user)
1284 assert_equal ::I18n.t(:label_user_anonymous), t
1302 assert_equal ::I18n.t(:label_user_anonymous), t
1285 end
1303 end
1286
1304
1287 def test_link_to_attachment
1305 def test_link_to_attachment
1288 a = Attachment.find(3)
1306 a = Attachment.find(3)
1289 assert_equal '<a href="/attachments/3/logo.gif">logo.gif</a>',
1307 assert_equal '<a href="/attachments/3/logo.gif">logo.gif</a>',
1290 link_to_attachment(a)
1308 link_to_attachment(a)
1291 assert_equal '<a href="/attachments/3/logo.gif">Text</a>',
1309 assert_equal '<a href="/attachments/3/logo.gif">Text</a>',
1292 link_to_attachment(a, :text => 'Text')
1310 link_to_attachment(a, :text => 'Text')
1293 result = link_to("logo.gif", "/attachments/3/logo.gif", :class => "foo")
1311 result = link_to("logo.gif", "/attachments/3/logo.gif", :class => "foo")
1294 assert_equal result,
1312 assert_equal result,
1295 link_to_attachment(a, :class => 'foo')
1313 link_to_attachment(a, :class => 'foo')
1296 assert_equal '<a href="/attachments/download/3/logo.gif">logo.gif</a>',
1314 assert_equal '<a href="/attachments/download/3/logo.gif">logo.gif</a>',
1297 link_to_attachment(a, :download => true)
1315 link_to_attachment(a, :download => true)
1298 assert_equal '<a href="http://test.host/attachments/3/logo.gif">logo.gif</a>',
1316 assert_equal '<a href="http://test.host/attachments/3/logo.gif">logo.gif</a>',
1299 link_to_attachment(a, :only_path => false)
1317 link_to_attachment(a, :only_path => false)
1300 end
1318 end
1301
1319
1302 def test_thumbnail_tag
1320 def test_thumbnail_tag
1303 a = Attachment.find(3)
1321 a = Attachment.find(3)
1304 assert_equal '<a href="/attachments/3/logo.gif" title="logo.gif"><img alt="3" src="/attachments/thumbnail/3" /></a>',
1322 assert_equal '<a href="/attachments/3/logo.gif" title="logo.gif"><img alt="3" src="/attachments/thumbnail/3" /></a>',
1305 thumbnail_tag(a)
1323 thumbnail_tag(a)
1306 end
1324 end
1307
1325
1308 def test_link_to_project
1326 def test_link_to_project
1309 project = Project.find(1)
1327 project = Project.find(1)
1310 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
1328 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
1311 link_to_project(project)
1329 link_to_project(project)
1312 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
1330 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
1313 link_to_project(project, :action => 'settings')
1331 link_to_project(project, :action => 'settings')
1314 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
1332 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
1315 link_to_project(project, {:only_path => false, :jump => 'blah'})
1333 link_to_project(project, {:only_path => false, :jump => 'blah'})
1316 result = link_to("eCookbook", "/projects/ecookbook/settings", :class => "project")
1334 result = link_to("eCookbook", "/projects/ecookbook/settings", :class => "project")
1317 assert_equal result,
1335 assert_equal result,
1318 link_to_project(project, {:action => 'settings'}, :class => "project")
1336 link_to_project(project, {:action => 'settings'}, :class => "project")
1319 end
1337 end
1320
1338
1321 def test_link_to_project_settings
1339 def test_link_to_project_settings
1322 project = Project.find(1)
1340 project = Project.find(1)
1323 assert_equal '<a href="/projects/ecookbook/settings">eCookbook</a>', link_to_project_settings(project)
1341 assert_equal '<a href="/projects/ecookbook/settings">eCookbook</a>', link_to_project_settings(project)
1324
1342
1325 project.status = Project::STATUS_CLOSED
1343 project.status = Project::STATUS_CLOSED
1326 assert_equal '<a href="/projects/ecookbook">eCookbook</a>', link_to_project_settings(project)
1344 assert_equal '<a href="/projects/ecookbook">eCookbook</a>', link_to_project_settings(project)
1327
1345
1328 project.status = Project::STATUS_ARCHIVED
1346 project.status = Project::STATUS_ARCHIVED
1329 assert_equal 'eCookbook', link_to_project_settings(project)
1347 assert_equal 'eCookbook', link_to_project_settings(project)
1330 end
1348 end
1331
1349
1332 def test_link_to_legacy_project_with_numerical_identifier_should_use_id
1350 def test_link_to_legacy_project_with_numerical_identifier_should_use_id
1333 # numeric identifier are no longer allowed
1351 # numeric identifier are no longer allowed
1334 Project.where(:id => 1).update_all(:identifier => 25)
1352 Project.where(:id => 1).update_all(:identifier => 25)
1335 assert_equal '<a href="/projects/1">eCookbook</a>',
1353 assert_equal '<a href="/projects/1">eCookbook</a>',
1336 link_to_project(Project.find(1))
1354 link_to_project(Project.find(1))
1337 end
1355 end
1338
1356
1339 def test_principals_options_for_select_with_users
1357 def test_principals_options_for_select_with_users
1340 User.current = nil
1358 User.current = nil
1341 users = [User.find(2), User.find(4)]
1359 users = [User.find(2), User.find(4)]
1342 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
1360 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
1343 principals_options_for_select(users)
1361 principals_options_for_select(users)
1344 end
1362 end
1345
1363
1346 def test_principals_options_for_select_with_selected
1364 def test_principals_options_for_select_with_selected
1347 User.current = nil
1365 User.current = nil
1348 users = [User.find(2), User.find(4)]
1366 users = [User.find(2), User.find(4)]
1349 assert_equal %(<option value="2">John Smith</option><option value="4" selected="selected">Robert Hill</option>),
1367 assert_equal %(<option value="2">John Smith</option><option value="4" selected="selected">Robert Hill</option>),
1350 principals_options_for_select(users, User.find(4))
1368 principals_options_for_select(users, User.find(4))
1351 end
1369 end
1352
1370
1353 def test_principals_options_for_select_with_users_and_groups
1371 def test_principals_options_for_select_with_users_and_groups
1354 User.current = nil
1372 User.current = nil
1355 users = [User.find(2), Group.find(11), User.find(4), Group.find(10)]
1373 users = [User.find(2), Group.find(11), User.find(4), Group.find(10)]
1356 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>) +
1374 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>) +
1357 %(<optgroup label="Groups"><option value="10">A Team</option><option value="11">B Team</option></optgroup>),
1375 %(<optgroup label="Groups"><option value="10">A Team</option><option value="11">B Team</option></optgroup>),
1358 principals_options_for_select(users)
1376 principals_options_for_select(users)
1359 end
1377 end
1360
1378
1361 def test_principals_options_for_select_with_empty_collection
1379 def test_principals_options_for_select_with_empty_collection
1362 assert_equal '', principals_options_for_select([])
1380 assert_equal '', principals_options_for_select([])
1363 end
1381 end
1364
1382
1365 def test_principals_options_for_select_should_include_me_option_when_current_user_is_in_collection
1383 def test_principals_options_for_select_should_include_me_option_when_current_user_is_in_collection
1366 users = [User.find(2), User.find(4)]
1384 users = [User.find(2), User.find(4)]
1367 User.current = User.find(4)
1385 User.current = User.find(4)
1368 assert_include '<option value="4">&lt;&lt; me &gt;&gt;</option>', principals_options_for_select(users)
1386 assert_include '<option value="4">&lt;&lt; me &gt;&gt;</option>', principals_options_for_select(users)
1369 end
1387 end
1370
1388
1371 def test_stylesheet_link_tag_should_pick_the_default_stylesheet
1389 def test_stylesheet_link_tag_should_pick_the_default_stylesheet
1372 assert_match 'href="/stylesheets/styles.css"', stylesheet_link_tag("styles")
1390 assert_match 'href="/stylesheets/styles.css"', stylesheet_link_tag("styles")
1373 end
1391 end
1374
1392
1375 def test_stylesheet_link_tag_for_plugin_should_pick_the_plugin_stylesheet
1393 def test_stylesheet_link_tag_for_plugin_should_pick_the_plugin_stylesheet
1376 assert_match 'href="/plugin_assets/foo/stylesheets/styles.css"', stylesheet_link_tag("styles", :plugin => :foo)
1394 assert_match 'href="/plugin_assets/foo/stylesheets/styles.css"', stylesheet_link_tag("styles", :plugin => :foo)
1377 end
1395 end
1378
1396
1379 def test_image_tag_should_pick_the_default_image
1397 def test_image_tag_should_pick_the_default_image
1380 assert_match 'src="/images/image.png"', image_tag("image.png")
1398 assert_match 'src="/images/image.png"', image_tag("image.png")
1381 end
1399 end
1382
1400
1383 def test_image_tag_should_pick_the_theme_image_if_it_exists
1401 def test_image_tag_should_pick_the_theme_image_if_it_exists
1384 theme = Redmine::Themes.themes.last
1402 theme = Redmine::Themes.themes.last
1385 theme.images << 'image.png'
1403 theme.images << 'image.png'
1386
1404
1387 with_settings :ui_theme => theme.id do
1405 with_settings :ui_theme => theme.id do
1388 assert_match %|src="/themes/#{theme.dir}/images/image.png"|, image_tag("image.png")
1406 assert_match %|src="/themes/#{theme.dir}/images/image.png"|, image_tag("image.png")
1389 assert_match %|src="/images/other.png"|, image_tag("other.png")
1407 assert_match %|src="/images/other.png"|, image_tag("other.png")
1390 end
1408 end
1391 ensure
1409 ensure
1392 theme.images.delete 'image.png'
1410 theme.images.delete 'image.png'
1393 end
1411 end
1394
1412
1395 def test_image_tag_sfor_plugin_should_pick_the_plugin_image
1413 def test_image_tag_sfor_plugin_should_pick_the_plugin_image
1396 assert_match 'src="/plugin_assets/foo/images/image.png"', image_tag("image.png", :plugin => :foo)
1414 assert_match 'src="/plugin_assets/foo/images/image.png"', image_tag("image.png", :plugin => :foo)
1397 end
1415 end
1398
1416
1399 def test_javascript_include_tag_should_pick_the_default_javascript
1417 def test_javascript_include_tag_should_pick_the_default_javascript
1400 assert_match 'src="/javascripts/scripts.js"', javascript_include_tag("scripts")
1418 assert_match 'src="/javascripts/scripts.js"', javascript_include_tag("scripts")
1401 end
1419 end
1402
1420
1403 def test_javascript_include_tag_for_plugin_should_pick_the_plugin_javascript
1421 def test_javascript_include_tag_for_plugin_should_pick_the_plugin_javascript
1404 assert_match 'src="/plugin_assets/foo/javascripts/scripts.js"', javascript_include_tag("scripts", :plugin => :foo)
1422 assert_match 'src="/plugin_assets/foo/javascripts/scripts.js"', javascript_include_tag("scripts", :plugin => :foo)
1405 end
1423 end
1406
1424
1407 def test_raw_json_should_escape_closing_tags
1425 def test_raw_json_should_escape_closing_tags
1408 s = raw_json(["<foo>bar</foo>"])
1426 s = raw_json(["<foo>bar</foo>"])
1409 assert_equal '["<foo>bar<\/foo>"]', s
1427 assert_equal '["<foo>bar<\/foo>"]', s
1410 end
1428 end
1411
1429
1412 def test_raw_json_should_be_html_safe
1430 def test_raw_json_should_be_html_safe
1413 s = raw_json(["foo"])
1431 s = raw_json(["foo"])
1414 assert s.html_safe?
1432 assert s.html_safe?
1415 end
1433 end
1416
1434
1417 def test_html_title_should_app_title_if_not_set
1435 def test_html_title_should_app_title_if_not_set
1418 assert_equal 'Redmine', html_title
1436 assert_equal 'Redmine', html_title
1419 end
1437 end
1420
1438
1421 def test_html_title_should_join_items
1439 def test_html_title_should_join_items
1422 html_title 'Foo', 'Bar'
1440 html_title 'Foo', 'Bar'
1423 assert_equal 'Foo - Bar - Redmine', html_title
1441 assert_equal 'Foo - Bar - Redmine', html_title
1424 end
1442 end
1425
1443
1426 def test_html_title_should_append_current_project_name
1444 def test_html_title_should_append_current_project_name
1427 @project = Project.find(1)
1445 @project = Project.find(1)
1428 html_title 'Foo', 'Bar'
1446 html_title 'Foo', 'Bar'
1429 assert_equal 'Foo - Bar - eCookbook - Redmine', html_title
1447 assert_equal 'Foo - Bar - eCookbook - Redmine', html_title
1430 end
1448 end
1431
1449
1432 def test_title_should_return_a_h2_tag
1450 def test_title_should_return_a_h2_tag
1433 assert_equal '<h2>Foo</h2>', title('Foo')
1451 assert_equal '<h2>Foo</h2>', title('Foo')
1434 end
1452 end
1435
1453
1436 def test_title_should_set_html_title
1454 def test_title_should_set_html_title
1437 title('Foo')
1455 title('Foo')
1438 assert_equal 'Foo - Redmine', html_title
1456 assert_equal 'Foo - Redmine', html_title
1439 end
1457 end
1440
1458
1441 def test_title_should_turn_arrays_into_links
1459 def test_title_should_turn_arrays_into_links
1442 assert_equal '<h2><a href="/foo">Foo</a></h2>', title(['Foo', '/foo'])
1460 assert_equal '<h2><a href="/foo">Foo</a></h2>', title(['Foo', '/foo'])
1443 assert_equal 'Foo - Redmine', html_title
1461 assert_equal 'Foo - Redmine', html_title
1444 end
1462 end
1445
1463
1446 def test_title_should_join_items
1464 def test_title_should_join_items
1447 assert_equal '<h2>Foo &#187; Bar</h2>', title('Foo', 'Bar')
1465 assert_equal '<h2>Foo &#187; Bar</h2>', title('Foo', 'Bar')
1448 assert_equal 'Bar - Foo - Redmine', html_title
1466 assert_equal 'Bar - Foo - Redmine', html_title
1449 end
1467 end
1450
1468
1451 def test_favicon_path
1469 def test_favicon_path
1452 assert_match %r{^/favicon\.ico}, favicon_path
1470 assert_match %r{^/favicon\.ico}, favicon_path
1453 end
1471 end
1454
1472
1455 def test_favicon_path_with_suburi
1473 def test_favicon_path_with_suburi
1456 Redmine::Utils.relative_url_root = '/foo'
1474 Redmine::Utils.relative_url_root = '/foo'
1457 assert_match %r{^/foo/favicon\.ico}, favicon_path
1475 assert_match %r{^/foo/favicon\.ico}, favicon_path
1458 ensure
1476 ensure
1459 Redmine::Utils.relative_url_root = ''
1477 Redmine::Utils.relative_url_root = ''
1460 end
1478 end
1461
1479
1462 def test_favicon_url
1480 def test_favicon_url
1463 assert_match %r{^http://test\.host/favicon\.ico}, favicon_url
1481 assert_match %r{^http://test\.host/favicon\.ico}, favicon_url
1464 end
1482 end
1465
1483
1466 def test_favicon_url_with_suburi
1484 def test_favicon_url_with_suburi
1467 Redmine::Utils.relative_url_root = '/foo'
1485 Redmine::Utils.relative_url_root = '/foo'
1468 assert_match %r{^http://test\.host/foo/favicon\.ico}, favicon_url
1486 assert_match %r{^http://test\.host/foo/favicon\.ico}, favicon_url
1469 ensure
1487 ensure
1470 Redmine::Utils.relative_url_root = ''
1488 Redmine::Utils.relative_url_root = ''
1471 end
1489 end
1472
1490
1473 def test_truncate_single_line
1491 def test_truncate_single_line
1474 str = "01234"
1492 str = "01234"
1475 result = truncate_single_line_raw("#{str}\n#{str}", 10)
1493 result = truncate_single_line_raw("#{str}\n#{str}", 10)
1476 assert_equal "01234 0...", result
1494 assert_equal "01234 0...", result
1477 assert !result.html_safe?
1495 assert !result.html_safe?
1478 result = truncate_single_line_raw("#{str}<&#>\n#{str}#{str}", 16)
1496 result = truncate_single_line_raw("#{str}<&#>\n#{str}#{str}", 16)
1479 assert_equal "01234<&#> 012...", result
1497 assert_equal "01234<&#> 012...", result
1480 assert !result.html_safe?
1498 assert !result.html_safe?
1481 end
1499 end
1482
1500
1483 def test_truncate_single_line_non_ascii
1501 def test_truncate_single_line_non_ascii
1484 ja = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"
1502 ja = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"
1485 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
1503 ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
1486 result = truncate_single_line_raw("#{ja}\n#{ja}\n#{ja}", 10)
1504 result = truncate_single_line_raw("#{ja}\n#{ja}\n#{ja}", 10)
1487 assert_equal "#{ja} #{ja}...", result
1505 assert_equal "#{ja} #{ja}...", result
1488 assert !result.html_safe?
1506 assert !result.html_safe?
1489 end
1507 end
1490 end
1508 end
General Comments 0
You need to be logged in to leave comments. Login now