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