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