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