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