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