##// END OF EJS Templates
Adds a optgroup for groups in users/groups select tags....
Jean-Philippe Lang -
r6187:ed01ae121daf
parent child
Show More
@@ -1,946 +1,960
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 require 'forwardable'
18 require 'forwardable'
19 require 'cgi'
19 require 'cgi'
20
20
21 module ApplicationHelper
21 module ApplicationHelper
22 include Redmine::WikiFormatting::Macros::Definitions
22 include Redmine::WikiFormatting::Macros::Definitions
23 include Redmine::I18n
23 include Redmine::I18n
24 include GravatarHelper::PublicMethods
24 include GravatarHelper::PublicMethods
25
25
26 extend Forwardable
26 extend Forwardable
27 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
27 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
28
28
29 # Return true if user is authorized for controller/action, otherwise false
29 # Return true if user is authorized for controller/action, otherwise false
30 def authorize_for(controller, action)
30 def authorize_for(controller, action)
31 User.current.allowed_to?({:controller => controller, :action => action}, @project)
31 User.current.allowed_to?({:controller => controller, :action => action}, @project)
32 end
32 end
33
33
34 # Display a link if user is authorized
34 # Display a link if user is authorized
35 #
35 #
36 # @param [String] name Anchor text (passed to link_to)
36 # @param [String] name Anchor text (passed to link_to)
37 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
37 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
38 # @param [optional, Hash] html_options Options passed to link_to
38 # @param [optional, Hash] html_options Options passed to link_to
39 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
39 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
40 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
40 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
41 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
41 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
42 end
42 end
43
43
44 # Display a link to remote if user is authorized
44 # Display a link to remote if user is authorized
45 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
45 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
46 url = options[:url] || {}
46 url = options[:url] || {}
47 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
47 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
48 end
48 end
49
49
50 # Displays a link to user's account page if active
50 # Displays a link to user's account page if active
51 def link_to_user(user, options={})
51 def link_to_user(user, options={})
52 if user.is_a?(User)
52 if user.is_a?(User)
53 name = h(user.name(options[:format]))
53 name = h(user.name(options[:format]))
54 if user.active?
54 if user.active?
55 link_to name, :controller => 'users', :action => 'show', :id => user
55 link_to name, :controller => 'users', :action => 'show', :id => user
56 else
56 else
57 name
57 name
58 end
58 end
59 else
59 else
60 h(user.to_s)
60 h(user.to_s)
61 end
61 end
62 end
62 end
63
63
64 # Displays a link to +issue+ with its subject.
64 # Displays a link to +issue+ with its subject.
65 # Examples:
65 # Examples:
66 #
66 #
67 # link_to_issue(issue) # => Defect #6: This is the subject
67 # link_to_issue(issue) # => Defect #6: This is the subject
68 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
68 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
69 # link_to_issue(issue, :subject => false) # => Defect #6
69 # link_to_issue(issue, :subject => false) # => Defect #6
70 # link_to_issue(issue, :project => true) # => Foo - Defect #6
70 # link_to_issue(issue, :project => true) # => Foo - Defect #6
71 #
71 #
72 def link_to_issue(issue, options={})
72 def link_to_issue(issue, options={})
73 title = nil
73 title = nil
74 subject = nil
74 subject = nil
75 if options[:subject] == false
75 if options[:subject] == false
76 title = truncate(issue.subject, :length => 60)
76 title = truncate(issue.subject, :length => 60)
77 else
77 else
78 subject = issue.subject
78 subject = issue.subject
79 if options[:truncate]
79 if options[:truncate]
80 subject = truncate(subject, :length => options[:truncate])
80 subject = truncate(subject, :length => options[:truncate])
81 end
81 end
82 end
82 end
83 s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
83 s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
84 :class => issue.css_classes,
84 :class => issue.css_classes,
85 :title => title
85 :title => title
86 s << ": #{h subject}" if subject
86 s << ": #{h subject}" if subject
87 s = "#{h issue.project} - " + s if options[:project]
87 s = "#{h issue.project} - " + s if options[:project]
88 s
88 s
89 end
89 end
90
90
91 # Generates a link to an attachment.
91 # Generates a link to an attachment.
92 # Options:
92 # Options:
93 # * :text - Link text (default to attachment filename)
93 # * :text - Link text (default to attachment filename)
94 # * :download - Force download (default: false)
94 # * :download - Force download (default: false)
95 def link_to_attachment(attachment, options={})
95 def link_to_attachment(attachment, options={})
96 text = options.delete(:text) || attachment.filename
96 text = options.delete(:text) || attachment.filename
97 action = options.delete(:download) ? 'download' : 'show'
97 action = options.delete(:download) ? 'download' : 'show'
98
98
99 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
99 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
100 end
100 end
101
101
102 # Generates a link to a SCM revision
102 # Generates a link to a SCM revision
103 # Options:
103 # Options:
104 # * :text - Link text (default to the formatted revision)
104 # * :text - Link text (default to the formatted revision)
105 def link_to_revision(revision, project, options={})
105 def link_to_revision(revision, project, options={})
106 text = options.delete(:text) || format_revision(revision)
106 text = options.delete(:text) || format_revision(revision)
107 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
107 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
108
108
109 link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev},
109 link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev},
110 :title => l(:label_revision_id, format_revision(revision)))
110 :title => l(:label_revision_id, format_revision(revision)))
111 end
111 end
112
112
113 # Generates a link to a message
113 # Generates a link to a message
114 def link_to_message(message, options={}, html_options = nil)
114 def link_to_message(message, options={}, html_options = nil)
115 link_to(
115 link_to(
116 h(truncate(message.subject, :length => 60)),
116 h(truncate(message.subject, :length => 60)),
117 { :controller => 'messages', :action => 'show',
117 { :controller => 'messages', :action => 'show',
118 :board_id => message.board_id,
118 :board_id => message.board_id,
119 :id => message.root,
119 :id => message.root,
120 :r => (message.parent_id && message.id),
120 :r => (message.parent_id && message.id),
121 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
121 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
122 }.merge(options),
122 }.merge(options),
123 html_options
123 html_options
124 )
124 )
125 end
125 end
126
126
127 # Generates a link to a project if active
127 # Generates a link to a project if active
128 # Examples:
128 # Examples:
129 #
129 #
130 # link_to_project(project) # => link to the specified project overview
130 # link_to_project(project) # => link to the specified project overview
131 # link_to_project(project, :action=>'settings') # => link to project settings
131 # link_to_project(project, :action=>'settings') # => link to project settings
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.active?
136 if project.active?
137 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
137 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
138 link_to(h(project), url, html_options)
138 link_to(h(project), url, html_options)
139 else
139 else
140 h(project)
140 h(project)
141 end
141 end
142 end
142 end
143
143
144 def toggle_link(name, id, options={})
144 def toggle_link(name, id, options={})
145 onclick = "Element.toggle('#{id}'); "
145 onclick = "Element.toggle('#{id}'); "
146 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
146 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
147 onclick << "return false;"
147 onclick << "return false;"
148 link_to(name, "#", :onclick => onclick)
148 link_to(name, "#", :onclick => onclick)
149 end
149 end
150
150
151 def image_to_function(name, function, html_options = {})
151 def image_to_function(name, function, html_options = {})
152 html_options.symbolize_keys!
152 html_options.symbolize_keys!
153 tag(:input, html_options.merge({
153 tag(:input, html_options.merge({
154 :type => "image", :src => image_path(name),
154 :type => "image", :src => image_path(name),
155 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
155 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
156 }))
156 }))
157 end
157 end
158
158
159 def prompt_to_remote(name, text, param, url, html_options = {})
159 def prompt_to_remote(name, text, param, url, html_options = {})
160 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
160 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
161 link_to name, {}, html_options
161 link_to name, {}, html_options
162 end
162 end
163
163
164 def format_activity_title(text)
164 def format_activity_title(text)
165 h(truncate_single_line(text, :length => 100))
165 h(truncate_single_line(text, :length => 100))
166 end
166 end
167
167
168 def format_activity_day(date)
168 def format_activity_day(date)
169 date == Date.today ? l(:label_today).titleize : format_date(date)
169 date == Date.today ? l(:label_today).titleize : format_date(date)
170 end
170 end
171
171
172 def format_activity_description(text)
172 def format_activity_description(text)
173 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
173 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
174 end
174 end
175
175
176 def format_version_name(version)
176 def format_version_name(version)
177 if version.project == @project
177 if version.project == @project
178 h(version)
178 h(version)
179 else
179 else
180 h("#{version.project} - #{version}")
180 h("#{version.project} - #{version}")
181 end
181 end
182 end
182 end
183
183
184 def due_date_distance_in_words(date)
184 def due_date_distance_in_words(date)
185 if date
185 if date
186 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
186 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
187 end
187 end
188 end
188 end
189
189
190 def render_page_hierarchy(pages, node=nil, options={})
190 def render_page_hierarchy(pages, node=nil, options={})
191 content = ''
191 content = ''
192 if pages[node]
192 if pages[node]
193 content << "<ul class=\"pages-hierarchy\">\n"
193 content << "<ul class=\"pages-hierarchy\">\n"
194 pages[node].each do |page|
194 pages[node].each do |page|
195 content << "<li>"
195 content << "<li>"
196 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
196 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
197 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
197 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
198 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
198 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
199 content << "</li>\n"
199 content << "</li>\n"
200 end
200 end
201 content << "</ul>\n"
201 content << "</ul>\n"
202 end
202 end
203 content
203 content
204 end
204 end
205
205
206 # Renders flash messages
206 # Renders flash messages
207 def render_flash_messages
207 def render_flash_messages
208 s = ''
208 s = ''
209 flash.each do |k,v|
209 flash.each do |k,v|
210 s << content_tag('div', v, :class => "flash #{k}")
210 s << content_tag('div', v, :class => "flash #{k}")
211 end
211 end
212 s
212 s
213 end
213 end
214
214
215 # Renders tabs and their content
215 # Renders tabs and their content
216 def render_tabs(tabs)
216 def render_tabs(tabs)
217 if tabs.any?
217 if tabs.any?
218 render :partial => 'common/tabs', :locals => {:tabs => tabs}
218 render :partial => 'common/tabs', :locals => {:tabs => tabs}
219 else
219 else
220 content_tag 'p', l(:label_no_data), :class => "nodata"
220 content_tag 'p', l(:label_no_data), :class => "nodata"
221 end
221 end
222 end
222 end
223
223
224 # Renders the project quick-jump box
224 # Renders the project quick-jump box
225 def render_project_jump_box
225 def render_project_jump_box
226 return unless User.current.logged?
226 return unless User.current.logged?
227 projects = User.current.memberships.collect(&:project).compact.uniq
227 projects = User.current.memberships.collect(&:project).compact.uniq
228 if projects.any?
228 if projects.any?
229 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
229 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
230 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
230 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
231 '<option value="" disabled="disabled">---</option>'
231 '<option value="" disabled="disabled">---</option>'
232 s << project_tree_options_for_select(projects, :selected => @project) do |p|
232 s << project_tree_options_for_select(projects, :selected => @project) do |p|
233 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
233 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
234 end
234 end
235 s << '</select>'
235 s << '</select>'
236 s
236 s
237 end
237 end
238 end
238 end
239
239
240 def project_tree_options_for_select(projects, options = {})
240 def project_tree_options_for_select(projects, options = {})
241 s = ''
241 s = ''
242 project_tree(projects) do |project, level|
242 project_tree(projects) do |project, level|
243 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
243 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
244 tag_options = {:value => project.id}
244 tag_options = {:value => project.id}
245 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
245 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
246 tag_options[:selected] = 'selected'
246 tag_options[:selected] = 'selected'
247 else
247 else
248 tag_options[:selected] = nil
248 tag_options[:selected] = nil
249 end
249 end
250 tag_options.merge!(yield(project)) if block_given?
250 tag_options.merge!(yield(project)) if block_given?
251 s << content_tag('option', name_prefix + h(project), tag_options)
251 s << content_tag('option', name_prefix + h(project), tag_options)
252 end
252 end
253 s
253 s
254 end
254 end
255
255
256 # Yields the given block for each project with its level in the tree
256 # Yields the given block for each project with its level in the tree
257 #
257 #
258 # Wrapper for Project#project_tree
258 # Wrapper for Project#project_tree
259 def project_tree(projects, &block)
259 def project_tree(projects, &block)
260 Project.project_tree(projects, &block)
260 Project.project_tree(projects, &block)
261 end
261 end
262
262
263 def project_nested_ul(projects, &block)
263 def project_nested_ul(projects, &block)
264 s = ''
264 s = ''
265 if projects.any?
265 if projects.any?
266 ancestors = []
266 ancestors = []
267 projects.sort_by(&:lft).each do |project|
267 projects.sort_by(&:lft).each do |project|
268 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
268 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
269 s << "<ul>\n"
269 s << "<ul>\n"
270 else
270 else
271 ancestors.pop
271 ancestors.pop
272 s << "</li>"
272 s << "</li>"
273 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
273 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
274 ancestors.pop
274 ancestors.pop
275 s << "</ul></li>\n"
275 s << "</ul></li>\n"
276 end
276 end
277 end
277 end
278 s << "<li>"
278 s << "<li>"
279 s << yield(project).to_s
279 s << yield(project).to_s
280 ancestors << project
280 ancestors << project
281 end
281 end
282 s << ("</li></ul>\n" * ancestors.size)
282 s << ("</li></ul>\n" * ancestors.size)
283 end
283 end
284 s
284 s
285 end
285 end
286
286
287 def principals_check_box_tags(name, principals)
287 def principals_check_box_tags(name, principals)
288 s = ''
288 s = ''
289 principals.sort.each do |principal|
289 principals.sort.each do |principal|
290 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
290 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
291 end
291 end
292 s
292 s
293 end
293 end
294
295 # Returns a string for users/groups option tags
296 def principals_options_for_select(collection, selected=nil)
297 s = ''
298 groups = ''
299 collection.sort.each do |element|
300 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
301 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
302 end
303 unless groups.empty?
304 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
305 end
306 s
307 end
294
308
295 # Truncates and returns the string as a single line
309 # Truncates and returns the string as a single line
296 def truncate_single_line(string, *args)
310 def truncate_single_line(string, *args)
297 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
311 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
298 end
312 end
299
313
300 # Truncates at line break after 250 characters or options[:length]
314 # Truncates at line break after 250 characters or options[:length]
301 def truncate_lines(string, options={})
315 def truncate_lines(string, options={})
302 length = options[:length] || 250
316 length = options[:length] || 250
303 if string.to_s =~ /\A(.{#{length}}.*?)$/m
317 if string.to_s =~ /\A(.{#{length}}.*?)$/m
304 "#{$1}..."
318 "#{$1}..."
305 else
319 else
306 string
320 string
307 end
321 end
308 end
322 end
309
323
310 def html_hours(text)
324 def html_hours(text)
311 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
325 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
312 end
326 end
313
327
314 def authoring(created, author, options={})
328 def authoring(created, author, options={})
315 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
329 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
316 end
330 end
317
331
318 def time_tag(time)
332 def time_tag(time)
319 text = distance_of_time_in_words(Time.now, time)
333 text = distance_of_time_in_words(Time.now, time)
320 if @project
334 if @project
321 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
335 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
322 else
336 else
323 content_tag('acronym', text, :title => format_time(time))
337 content_tag('acronym', text, :title => format_time(time))
324 end
338 end
325 end
339 end
326
340
327 def syntax_highlight(name, content)
341 def syntax_highlight(name, content)
328 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
342 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
329 end
343 end
330
344
331 def to_path_param(path)
345 def to_path_param(path)
332 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
346 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
333 end
347 end
334
348
335 def pagination_links_full(paginator, count=nil, options={})
349 def pagination_links_full(paginator, count=nil, options={})
336 page_param = options.delete(:page_param) || :page
350 page_param = options.delete(:page_param) || :page
337 per_page_links = options.delete(:per_page_links)
351 per_page_links = options.delete(:per_page_links)
338 url_param = params.dup
352 url_param = params.dup
339
353
340 html = ''
354 html = ''
341 if paginator.current.previous
355 if paginator.current.previous
342 html << link_to_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
356 html << link_to_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
343 end
357 end
344
358
345 html << (pagination_links_each(paginator, options) do |n|
359 html << (pagination_links_each(paginator, options) do |n|
346 link_to_content_update(n.to_s, url_param.merge(page_param => n))
360 link_to_content_update(n.to_s, url_param.merge(page_param => n))
347 end || '')
361 end || '')
348
362
349 if paginator.current.next
363 if paginator.current.next
350 html << ' ' + link_to_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
364 html << ' ' + link_to_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
351 end
365 end
352
366
353 unless count.nil?
367 unless count.nil?
354 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
368 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
355 if per_page_links != false && links = per_page_links(paginator.items_per_page)
369 if per_page_links != false && links = per_page_links(paginator.items_per_page)
356 html << " | #{links}"
370 html << " | #{links}"
357 end
371 end
358 end
372 end
359
373
360 html
374 html
361 end
375 end
362
376
363 def per_page_links(selected=nil)
377 def per_page_links(selected=nil)
364 links = Setting.per_page_options_array.collect do |n|
378 links = Setting.per_page_options_array.collect do |n|
365 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
379 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
366 end
380 end
367 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
381 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
368 end
382 end
369
383
370 def reorder_links(name, url)
384 def reorder_links(name, url)
371 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
385 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
372 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
386 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
373 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
387 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
374 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
388 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
375 end
389 end
376
390
377 def breadcrumb(*args)
391 def breadcrumb(*args)
378 elements = args.flatten
392 elements = args.flatten
379 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
393 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
380 end
394 end
381
395
382 def other_formats_links(&block)
396 def other_formats_links(&block)
383 concat('<p class="other-formats">' + l(:label_export_to))
397 concat('<p class="other-formats">' + l(:label_export_to))
384 yield Redmine::Views::OtherFormatsBuilder.new(self)
398 yield Redmine::Views::OtherFormatsBuilder.new(self)
385 concat('</p>')
399 concat('</p>')
386 end
400 end
387
401
388 def page_header_title
402 def page_header_title
389 if @project.nil? || @project.new_record?
403 if @project.nil? || @project.new_record?
390 h(Setting.app_title)
404 h(Setting.app_title)
391 else
405 else
392 b = []
406 b = []
393 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
407 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
394 if ancestors.any?
408 if ancestors.any?
395 root = ancestors.shift
409 root = ancestors.shift
396 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
410 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
397 if ancestors.size > 2
411 if ancestors.size > 2
398 b << '&#8230;'
412 b << '&#8230;'
399 ancestors = ancestors[-2, 2]
413 ancestors = ancestors[-2, 2]
400 end
414 end
401 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
415 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
402 end
416 end
403 b << h(@project)
417 b << h(@project)
404 b.join(' &#187; ')
418 b.join(' &#187; ')
405 end
419 end
406 end
420 end
407
421
408 def html_title(*args)
422 def html_title(*args)
409 if args.empty?
423 if args.empty?
410 title = []
424 title = []
411 title << @project.name if @project
425 title << @project.name if @project
412 title += @html_title if @html_title
426 title += @html_title if @html_title
413 title << Setting.app_title
427 title << Setting.app_title
414 title.select {|t| !t.blank? }.join(' - ')
428 title.select {|t| !t.blank? }.join(' - ')
415 else
429 else
416 @html_title ||= []
430 @html_title ||= []
417 @html_title += args
431 @html_title += args
418 end
432 end
419 end
433 end
420
434
421 # Returns the theme, controller name, and action as css classes for the
435 # Returns the theme, controller name, and action as css classes for the
422 # HTML body.
436 # HTML body.
423 def body_css_classes
437 def body_css_classes
424 css = []
438 css = []
425 if theme = Redmine::Themes.theme(Setting.ui_theme)
439 if theme = Redmine::Themes.theme(Setting.ui_theme)
426 css << 'theme-' + theme.name
440 css << 'theme-' + theme.name
427 end
441 end
428
442
429 css << 'controller-' + params[:controller]
443 css << 'controller-' + params[:controller]
430 css << 'action-' + params[:action]
444 css << 'action-' + params[:action]
431 css.join(' ')
445 css.join(' ')
432 end
446 end
433
447
434 def accesskey(s)
448 def accesskey(s)
435 Redmine::AccessKeys.key_for s
449 Redmine::AccessKeys.key_for s
436 end
450 end
437
451
438 # Formats text according to system settings.
452 # Formats text according to system settings.
439 # 2 ways to call this method:
453 # 2 ways to call this method:
440 # * with a String: textilizable(text, options)
454 # * with a String: textilizable(text, options)
441 # * with an object and one of its attribute: textilizable(issue, :description, options)
455 # * with an object and one of its attribute: textilizable(issue, :description, options)
442 def textilizable(*args)
456 def textilizable(*args)
443 options = args.last.is_a?(Hash) ? args.pop : {}
457 options = args.last.is_a?(Hash) ? args.pop : {}
444 case args.size
458 case args.size
445 when 1
459 when 1
446 obj = options[:object]
460 obj = options[:object]
447 text = args.shift
461 text = args.shift
448 when 2
462 when 2
449 obj = args.shift
463 obj = args.shift
450 attr = args.shift
464 attr = args.shift
451 text = obj.send(attr).to_s
465 text = obj.send(attr).to_s
452 else
466 else
453 raise ArgumentError, 'invalid arguments to textilizable'
467 raise ArgumentError, 'invalid arguments to textilizable'
454 end
468 end
455 return '' if text.blank?
469 return '' if text.blank?
456 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
470 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
457 only_path = options.delete(:only_path) == false ? false : true
471 only_path = options.delete(:only_path) == false ? false : true
458
472
459 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
473 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
460
474
461 @parsed_headings = []
475 @parsed_headings = []
462 text = parse_non_pre_blocks(text) do |text|
476 text = parse_non_pre_blocks(text) do |text|
463 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings].each do |method_name|
477 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings].each do |method_name|
464 send method_name, text, project, obj, attr, only_path, options
478 send method_name, text, project, obj, attr, only_path, options
465 end
479 end
466 end
480 end
467
481
468 if @parsed_headings.any?
482 if @parsed_headings.any?
469 replace_toc(text, @parsed_headings)
483 replace_toc(text, @parsed_headings)
470 end
484 end
471
485
472 text
486 text
473 end
487 end
474
488
475 def parse_non_pre_blocks(text)
489 def parse_non_pre_blocks(text)
476 s = StringScanner.new(text)
490 s = StringScanner.new(text)
477 tags = []
491 tags = []
478 parsed = ''
492 parsed = ''
479 while !s.eos?
493 while !s.eos?
480 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
494 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
481 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
495 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
482 if tags.empty?
496 if tags.empty?
483 yield text
497 yield text
484 end
498 end
485 parsed << text
499 parsed << text
486 if tag
500 if tag
487 if closing
501 if closing
488 if tags.last == tag.downcase
502 if tags.last == tag.downcase
489 tags.pop
503 tags.pop
490 end
504 end
491 else
505 else
492 tags << tag.downcase
506 tags << tag.downcase
493 end
507 end
494 parsed << full_tag
508 parsed << full_tag
495 end
509 end
496 end
510 end
497 # Close any non closing tags
511 # Close any non closing tags
498 while tag = tags.pop
512 while tag = tags.pop
499 parsed << "</#{tag}>"
513 parsed << "</#{tag}>"
500 end
514 end
501 parsed
515 parsed
502 end
516 end
503
517
504 def parse_inline_attachments(text, project, obj, attr, only_path, options)
518 def parse_inline_attachments(text, project, obj, attr, only_path, options)
505 # when using an image link, try to use an attachment, if possible
519 # when using an image link, try to use an attachment, if possible
506 if options[:attachments] || (obj && obj.respond_to?(:attachments))
520 if options[:attachments] || (obj && obj.respond_to?(:attachments))
507 attachments = nil
521 attachments = nil
508 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
522 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
509 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
523 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
510 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
524 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
511 # search for the picture in attachments
525 # search for the picture in attachments
512 if found = attachments.detect { |att| att.filename.downcase == filename }
526 if found = attachments.detect { |att| att.filename.downcase == filename }
513 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
527 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
514 desc = found.description.to_s.gsub('"', '')
528 desc = found.description.to_s.gsub('"', '')
515 if !desc.blank? && alttext.blank?
529 if !desc.blank? && alttext.blank?
516 alt = " title=\"#{desc}\" alt=\"#{desc}\""
530 alt = " title=\"#{desc}\" alt=\"#{desc}\""
517 end
531 end
518 "src=\"#{image_url}\"#{alt}"
532 "src=\"#{image_url}\"#{alt}"
519 else
533 else
520 m
534 m
521 end
535 end
522 end
536 end
523 end
537 end
524 end
538 end
525
539
526 # Wiki links
540 # Wiki links
527 #
541 #
528 # Examples:
542 # Examples:
529 # [[mypage]]
543 # [[mypage]]
530 # [[mypage|mytext]]
544 # [[mypage|mytext]]
531 # wiki links can refer other project wikis, using project name or identifier:
545 # wiki links can refer other project wikis, using project name or identifier:
532 # [[project:]] -> wiki starting page
546 # [[project:]] -> wiki starting page
533 # [[project:|mytext]]
547 # [[project:|mytext]]
534 # [[project:mypage]]
548 # [[project:mypage]]
535 # [[project:mypage|mytext]]
549 # [[project:mypage|mytext]]
536 def parse_wiki_links(text, project, obj, attr, only_path, options)
550 def parse_wiki_links(text, project, obj, attr, only_path, options)
537 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
551 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
538 link_project = project
552 link_project = project
539 esc, all, page, title = $1, $2, $3, $5
553 esc, all, page, title = $1, $2, $3, $5
540 if esc.nil?
554 if esc.nil?
541 if page =~ /^([^\:]+)\:(.*)$/
555 if page =~ /^([^\:]+)\:(.*)$/
542 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
556 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
543 page = $2
557 page = $2
544 title ||= $1 if page.blank?
558 title ||= $1 if page.blank?
545 end
559 end
546
560
547 if link_project && link_project.wiki
561 if link_project && link_project.wiki
548 # extract anchor
562 # extract anchor
549 anchor = nil
563 anchor = nil
550 if page =~ /^(.+?)\#(.+)$/
564 if page =~ /^(.+?)\#(.+)$/
551 page, anchor = $1, $2
565 page, anchor = $1, $2
552 end
566 end
553 # check if page exists
567 # check if page exists
554 wiki_page = link_project.wiki.find_page(page)
568 wiki_page = link_project.wiki.find_page(page)
555 url = case options[:wiki_links]
569 url = case options[:wiki_links]
556 when :local; "#{title}.html"
570 when :local; "#{title}.html"
557 when :anchor; "##{title}" # used for single-file wiki export
571 when :anchor; "##{title}" # used for single-file wiki export
558 else
572 else
559 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
573 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
560 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
574 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
561 end
575 end
562 link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
576 link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
563 else
577 else
564 # project or wiki doesn't exist
578 # project or wiki doesn't exist
565 all
579 all
566 end
580 end
567 else
581 else
568 all
582 all
569 end
583 end
570 end
584 end
571 end
585 end
572
586
573 # Redmine links
587 # Redmine links
574 #
588 #
575 # Examples:
589 # Examples:
576 # Issues:
590 # Issues:
577 # #52 -> Link to issue #52
591 # #52 -> Link to issue #52
578 # Changesets:
592 # Changesets:
579 # r52 -> Link to revision 52
593 # r52 -> Link to revision 52
580 # commit:a85130f -> Link to scmid starting with a85130f
594 # commit:a85130f -> Link to scmid starting with a85130f
581 # Documents:
595 # Documents:
582 # document#17 -> Link to document with id 17
596 # document#17 -> Link to document with id 17
583 # document:Greetings -> Link to the document with title "Greetings"
597 # document:Greetings -> Link to the document with title "Greetings"
584 # document:"Some document" -> Link to the document with title "Some document"
598 # document:"Some document" -> Link to the document with title "Some document"
585 # Versions:
599 # Versions:
586 # version#3 -> Link to version with id 3
600 # version#3 -> Link to version with id 3
587 # version:1.0.0 -> Link to version named "1.0.0"
601 # version:1.0.0 -> Link to version named "1.0.0"
588 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
602 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
589 # Attachments:
603 # Attachments:
590 # attachment:file.zip -> Link to the attachment of the current object named file.zip
604 # attachment:file.zip -> Link to the attachment of the current object named file.zip
591 # Source files:
605 # Source files:
592 # source:some/file -> Link to the file located at /some/file in the project's repository
606 # source:some/file -> Link to the file located at /some/file in the project's repository
593 # source:some/file@52 -> Link to the file's revision 52
607 # source:some/file@52 -> Link to the file's revision 52
594 # source:some/file#L120 -> Link to line 120 of the file
608 # source:some/file#L120 -> Link to line 120 of the file
595 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
609 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
596 # export:some/file -> Force the download of the file
610 # export:some/file -> Force the download of the file
597 # Forum messages:
611 # Forum messages:
598 # message#1218 -> Link to message with id 1218
612 # message#1218 -> Link to message with id 1218
599 #
613 #
600 # Links can refer other objects from other projects, using project identifier:
614 # Links can refer other objects from other projects, using project identifier:
601 # identifier:r52
615 # identifier:r52
602 # identifier:document:"Some document"
616 # identifier:document:"Some document"
603 # identifier:version:1.0.0
617 # identifier:version:1.0.0
604 # identifier:source:some/file
618 # identifier:source:some/file
605 def parse_redmine_links(text, project, obj, attr, only_path, options)
619 def parse_redmine_links(text, project, obj, attr, only_path, options)
606 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
620 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
607 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
621 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
608 link = nil
622 link = nil
609 if project_identifier
623 if project_identifier
610 project = Project.visible.find_by_identifier(project_identifier)
624 project = Project.visible.find_by_identifier(project_identifier)
611 end
625 end
612 if esc.nil?
626 if esc.nil?
613 if prefix.nil? && sep == 'r'
627 if prefix.nil? && sep == 'r'
614 # project.changesets.visible raises an SQL error because of a double join on repositories
628 # project.changesets.visible raises an SQL error because of a double join on repositories
615 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
629 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
616 link = link_to("#{project_prefix}r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
630 link = link_to("#{project_prefix}r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
617 :class => 'changeset',
631 :class => 'changeset',
618 :title => truncate_single_line(changeset.comments, :length => 100))
632 :title => truncate_single_line(changeset.comments, :length => 100))
619 end
633 end
620 elsif sep == '#'
634 elsif sep == '#'
621 oid = identifier.to_i
635 oid = identifier.to_i
622 case prefix
636 case prefix
623 when nil
637 when nil
624 if issue = Issue.visible.find_by_id(oid, :include => :status)
638 if issue = Issue.visible.find_by_id(oid, :include => :status)
625 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
639 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
626 :class => issue.css_classes,
640 :class => issue.css_classes,
627 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
641 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
628 end
642 end
629 when 'document'
643 when 'document'
630 if document = Document.visible.find_by_id(oid)
644 if document = Document.visible.find_by_id(oid)
631 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
645 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
632 :class => 'document'
646 :class => 'document'
633 end
647 end
634 when 'version'
648 when 'version'
635 if version = Version.visible.find_by_id(oid)
649 if version = Version.visible.find_by_id(oid)
636 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
650 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
637 :class => 'version'
651 :class => 'version'
638 end
652 end
639 when 'message'
653 when 'message'
640 if message = Message.visible.find_by_id(oid, :include => :parent)
654 if message = Message.visible.find_by_id(oid, :include => :parent)
641 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
655 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
642 end
656 end
643 when 'project'
657 when 'project'
644 if p = Project.visible.find_by_id(oid)
658 if p = Project.visible.find_by_id(oid)
645 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
659 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
646 end
660 end
647 end
661 end
648 elsif sep == ':'
662 elsif sep == ':'
649 # removes the double quotes if any
663 # removes the double quotes if any
650 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
664 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
651 case prefix
665 case prefix
652 when 'document'
666 when 'document'
653 if project && document = project.documents.visible.find_by_title(name)
667 if project && document = project.documents.visible.find_by_title(name)
654 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
668 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
655 :class => 'document'
669 :class => 'document'
656 end
670 end
657 when 'version'
671 when 'version'
658 if project && version = project.versions.visible.find_by_name(name)
672 if project && version = project.versions.visible.find_by_name(name)
659 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
673 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
660 :class => 'version'
674 :class => 'version'
661 end
675 end
662 when 'commit'
676 when 'commit'
663 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
677 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
664 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
678 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
665 :class => 'changeset',
679 :class => 'changeset',
666 :title => truncate_single_line(changeset.comments, :length => 100)
680 :title => truncate_single_line(changeset.comments, :length => 100)
667 end
681 end
668 when 'source', 'export'
682 when 'source', 'export'
669 if project && project.repository && User.current.allowed_to?(:browse_repository, project)
683 if project && project.repository && User.current.allowed_to?(:browse_repository, project)
670 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
684 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
671 path, rev, anchor = $1, $3, $5
685 path, rev, anchor = $1, $3, $5
672 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
686 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
673 :path => to_path_param(path),
687 :path => to_path_param(path),
674 :rev => rev,
688 :rev => rev,
675 :anchor => anchor,
689 :anchor => anchor,
676 :format => (prefix == 'export' ? 'raw' : nil)},
690 :format => (prefix == 'export' ? 'raw' : nil)},
677 :class => (prefix == 'export' ? 'source download' : 'source')
691 :class => (prefix == 'export' ? 'source download' : 'source')
678 end
692 end
679 when 'attachment'
693 when 'attachment'
680 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
694 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
681 if attachments && attachment = attachments.detect {|a| a.filename == name }
695 if attachments && attachment = attachments.detect {|a| a.filename == name }
682 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
696 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
683 :class => 'attachment'
697 :class => 'attachment'
684 end
698 end
685 when 'project'
699 when 'project'
686 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
700 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
687 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
701 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
688 end
702 end
689 end
703 end
690 end
704 end
691 end
705 end
692 leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")
706 leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")
693 end
707 end
694 end
708 end
695
709
696 HEADING_RE = /<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>/i unless const_defined?(:HEADING_RE)
710 HEADING_RE = /<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>/i unless const_defined?(:HEADING_RE)
697
711
698 # Headings and TOC
712 # Headings and TOC
699 # Adds ids and links to headings unless options[:headings] is set to false
713 # Adds ids and links to headings unless options[:headings] is set to false
700 def parse_headings(text, project, obj, attr, only_path, options)
714 def parse_headings(text, project, obj, attr, only_path, options)
701 return if options[:headings] == false
715 return if options[:headings] == false
702
716
703 text.gsub!(HEADING_RE) do
717 text.gsub!(HEADING_RE) do
704 level, attrs, content = $1.to_i, $2, $3
718 level, attrs, content = $1.to_i, $2, $3
705 item = strip_tags(content).strip
719 item = strip_tags(content).strip
706 anchor = item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
720 anchor = item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
707 @parsed_headings << [level, anchor, item]
721 @parsed_headings << [level, anchor, item]
708 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
722 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
709 end
723 end
710 end
724 end
711
725
712 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
726 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
713
727
714 # Renders the TOC with given headings
728 # Renders the TOC with given headings
715 def replace_toc(text, headings)
729 def replace_toc(text, headings)
716 text.gsub!(TOC_RE) do
730 text.gsub!(TOC_RE) do
717 if headings.empty?
731 if headings.empty?
718 ''
732 ''
719 else
733 else
720 div_class = 'toc'
734 div_class = 'toc'
721 div_class << ' right' if $1 == '>'
735 div_class << ' right' if $1 == '>'
722 div_class << ' left' if $1 == '<'
736 div_class << ' left' if $1 == '<'
723 out = "<ul class=\"#{div_class}\"><li>"
737 out = "<ul class=\"#{div_class}\"><li>"
724 root = headings.map(&:first).min
738 root = headings.map(&:first).min
725 current = root
739 current = root
726 started = false
740 started = false
727 headings.each do |level, anchor, item|
741 headings.each do |level, anchor, item|
728 if level > current
742 if level > current
729 out << '<ul><li>' * (level - current)
743 out << '<ul><li>' * (level - current)
730 elsif level < current
744 elsif level < current
731 out << "</li></ul>\n" * (current - level) + "</li><li>"
745 out << "</li></ul>\n" * (current - level) + "</li><li>"
732 elsif started
746 elsif started
733 out << '</li><li>'
747 out << '</li><li>'
734 end
748 end
735 out << "<a href=\"##{anchor}\">#{item}</a>"
749 out << "<a href=\"##{anchor}\">#{item}</a>"
736 current = level
750 current = level
737 started = true
751 started = true
738 end
752 end
739 out << '</li></ul>' * (current - root)
753 out << '</li></ul>' * (current - root)
740 out << '</li></ul>'
754 out << '</li></ul>'
741 end
755 end
742 end
756 end
743 end
757 end
744
758
745 # Same as Rails' simple_format helper without using paragraphs
759 # Same as Rails' simple_format helper without using paragraphs
746 def simple_format_without_paragraph(text)
760 def simple_format_without_paragraph(text)
747 text.to_s.
761 text.to_s.
748 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
762 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
749 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
763 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
750 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
764 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
751 end
765 end
752
766
753 def lang_options_for_select(blank=true)
767 def lang_options_for_select(blank=true)
754 (blank ? [["(auto)", ""]] : []) +
768 (blank ? [["(auto)", ""]] : []) +
755 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
769 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
756 end
770 end
757
771
758 def label_tag_for(name, option_tags = nil, options = {})
772 def label_tag_for(name, option_tags = nil, options = {})
759 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
773 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
760 content_tag("label", label_text)
774 content_tag("label", label_text)
761 end
775 end
762
776
763 def labelled_tabular_form_for(name, object, options, &proc)
777 def labelled_tabular_form_for(name, object, options, &proc)
764 options[:html] ||= {}
778 options[:html] ||= {}
765 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
779 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
766 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
780 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
767 end
781 end
768
782
769 def back_url_hidden_field_tag
783 def back_url_hidden_field_tag
770 back_url = params[:back_url] || request.env['HTTP_REFERER']
784 back_url = params[:back_url] || request.env['HTTP_REFERER']
771 back_url = CGI.unescape(back_url.to_s)
785 back_url = CGI.unescape(back_url.to_s)
772 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
786 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
773 end
787 end
774
788
775 def check_all_links(form_name)
789 def check_all_links(form_name)
776 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
790 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
777 " | " +
791 " | " +
778 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
792 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
779 end
793 end
780
794
781 def progress_bar(pcts, options={})
795 def progress_bar(pcts, options={})
782 pcts = [pcts, pcts] unless pcts.is_a?(Array)
796 pcts = [pcts, pcts] unless pcts.is_a?(Array)
783 pcts = pcts.collect(&:round)
797 pcts = pcts.collect(&:round)
784 pcts[1] = pcts[1] - pcts[0]
798 pcts[1] = pcts[1] - pcts[0]
785 pcts << (100 - pcts[1] - pcts[0])
799 pcts << (100 - pcts[1] - pcts[0])
786 width = options[:width] || '100px;'
800 width = options[:width] || '100px;'
787 legend = options[:legend] || ''
801 legend = options[:legend] || ''
788 content_tag('table',
802 content_tag('table',
789 content_tag('tr',
803 content_tag('tr',
790 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
804 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
791 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
805 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
792 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
806 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
793 ), :class => 'progress', :style => "width: #{width};") +
807 ), :class => 'progress', :style => "width: #{width};") +
794 content_tag('p', legend, :class => 'pourcent')
808 content_tag('p', legend, :class => 'pourcent')
795 end
809 end
796
810
797 def checked_image(checked=true)
811 def checked_image(checked=true)
798 if checked
812 if checked
799 image_tag 'toggle_check.png'
813 image_tag 'toggle_check.png'
800 end
814 end
801 end
815 end
802
816
803 def context_menu(url)
817 def context_menu(url)
804 unless @context_menu_included
818 unless @context_menu_included
805 content_for :header_tags do
819 content_for :header_tags do
806 javascript_include_tag('context_menu') +
820 javascript_include_tag('context_menu') +
807 stylesheet_link_tag('context_menu')
821 stylesheet_link_tag('context_menu')
808 end
822 end
809 if l(:direction) == 'rtl'
823 if l(:direction) == 'rtl'
810 content_for :header_tags do
824 content_for :header_tags do
811 stylesheet_link_tag('context_menu_rtl')
825 stylesheet_link_tag('context_menu_rtl')
812 end
826 end
813 end
827 end
814 @context_menu_included = true
828 @context_menu_included = true
815 end
829 end
816 javascript_tag "new ContextMenu('#{ url_for(url) }')"
830 javascript_tag "new ContextMenu('#{ url_for(url) }')"
817 end
831 end
818
832
819 def context_menu_link(name, url, options={})
833 def context_menu_link(name, url, options={})
820 options[:class] ||= ''
834 options[:class] ||= ''
821 if options.delete(:selected)
835 if options.delete(:selected)
822 options[:class] << ' icon-checked disabled'
836 options[:class] << ' icon-checked disabled'
823 options[:disabled] = true
837 options[:disabled] = true
824 end
838 end
825 if options.delete(:disabled)
839 if options.delete(:disabled)
826 options.delete(:method)
840 options.delete(:method)
827 options.delete(:confirm)
841 options.delete(:confirm)
828 options.delete(:onclick)
842 options.delete(:onclick)
829 options[:class] << ' disabled'
843 options[:class] << ' disabled'
830 url = '#'
844 url = '#'
831 end
845 end
832 link_to name, url, options
846 link_to name, url, options
833 end
847 end
834
848
835 def calendar_for(field_id)
849 def calendar_for(field_id)
836 include_calendar_headers_tags
850 include_calendar_headers_tags
837 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
851 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
838 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
852 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
839 end
853 end
840
854
841 def include_calendar_headers_tags
855 def include_calendar_headers_tags
842 unless @calendar_headers_tags_included
856 unless @calendar_headers_tags_included
843 @calendar_headers_tags_included = true
857 @calendar_headers_tags_included = true
844 content_for :header_tags do
858 content_for :header_tags do
845 start_of_week = case Setting.start_of_week.to_i
859 start_of_week = case Setting.start_of_week.to_i
846 when 1
860 when 1
847 'Calendar._FD = 1;' # Monday
861 'Calendar._FD = 1;' # Monday
848 when 7
862 when 7
849 'Calendar._FD = 0;' # Sunday
863 'Calendar._FD = 0;' # Sunday
850 when 6
864 when 6
851 'Calendar._FD = 6;' # Saturday
865 'Calendar._FD = 6;' # Saturday
852 else
866 else
853 '' # use language
867 '' # use language
854 end
868 end
855
869
856 javascript_include_tag('calendar/calendar') +
870 javascript_include_tag('calendar/calendar') +
857 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
871 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
858 javascript_tag(start_of_week) +
872 javascript_tag(start_of_week) +
859 javascript_include_tag('calendar/calendar-setup') +
873 javascript_include_tag('calendar/calendar-setup') +
860 stylesheet_link_tag('calendar')
874 stylesheet_link_tag('calendar')
861 end
875 end
862 end
876 end
863 end
877 end
864
878
865 def content_for(name, content = nil, &block)
879 def content_for(name, content = nil, &block)
866 @has_content ||= {}
880 @has_content ||= {}
867 @has_content[name] = true
881 @has_content[name] = true
868 super(name, content, &block)
882 super(name, content, &block)
869 end
883 end
870
884
871 def has_content?(name)
885 def has_content?(name)
872 (@has_content && @has_content[name]) || false
886 (@has_content && @has_content[name]) || false
873 end
887 end
874
888
875 def email_delivery_enabled?
889 def email_delivery_enabled?
876 !!ActionMailer::Base.perform_deliveries
890 !!ActionMailer::Base.perform_deliveries
877 end
891 end
878
892
879 # Returns the avatar image tag for the given +user+ if avatars are enabled
893 # Returns the avatar image tag for the given +user+ if avatars are enabled
880 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
894 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
881 def avatar(user, options = { })
895 def avatar(user, options = { })
882 if Setting.gravatar_enabled?
896 if Setting.gravatar_enabled?
883 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
897 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
884 email = nil
898 email = nil
885 if user.respond_to?(:mail)
899 if user.respond_to?(:mail)
886 email = user.mail
900 email = user.mail
887 elsif user.to_s =~ %r{<(.+?)>}
901 elsif user.to_s =~ %r{<(.+?)>}
888 email = $1
902 email = $1
889 end
903 end
890 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
904 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
891 else
905 else
892 ''
906 ''
893 end
907 end
894 end
908 end
895
909
896 # Returns the javascript tags that are included in the html layout head
910 # Returns the javascript tags that are included in the html layout head
897 def javascript_heads
911 def javascript_heads
898 tags = javascript_include_tag(:defaults)
912 tags = javascript_include_tag(:defaults)
899 unless User.current.pref.warn_on_leaving_unsaved == '0'
913 unless User.current.pref.warn_on_leaving_unsaved == '0'
900 tags << "\n" + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
914 tags << "\n" + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
901 end
915 end
902 tags
916 tags
903 end
917 end
904
918
905 def favicon
919 def favicon
906 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
920 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
907 end
921 end
908
922
909 def robot_exclusion_tag
923 def robot_exclusion_tag
910 '<meta name="robots" content="noindex,follow,noarchive" />'
924 '<meta name="robots" content="noindex,follow,noarchive" />'
911 end
925 end
912
926
913 # Returns true if arg is expected in the API response
927 # Returns true if arg is expected in the API response
914 def include_in_api_response?(arg)
928 def include_in_api_response?(arg)
915 unless @included_in_api_response
929 unless @included_in_api_response
916 param = params[:include]
930 param = params[:include]
917 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
931 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
918 @included_in_api_response.collect!(&:strip)
932 @included_in_api_response.collect!(&:strip)
919 end
933 end
920 @included_in_api_response.include?(arg.to_s)
934 @included_in_api_response.include?(arg.to_s)
921 end
935 end
922
936
923 # Returns options or nil if nometa param or X-Redmine-Nometa header
937 # Returns options or nil if nometa param or X-Redmine-Nometa header
924 # was set in the request
938 # was set in the request
925 def api_meta(options)
939 def api_meta(options)
926 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
940 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
927 # compatibility mode for activeresource clients that raise
941 # compatibility mode for activeresource clients that raise
928 # an error when unserializing an array with attributes
942 # an error when unserializing an array with attributes
929 nil
943 nil
930 else
944 else
931 options
945 options
932 end
946 end
933 end
947 end
934
948
935 private
949 private
936
950
937 def wiki_helper
951 def wiki_helper
938 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
952 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
939 extend helper
953 extend helper
940 return self
954 return self
941 end
955 end
942
956
943 def link_to_content_update(text, url_params = {}, html_options = {})
957 def link_to_content_update(text, url_params = {}, html_options = {})
944 link_to(text, url_params, html_options)
958 link_to(text, url_params, html_options)
945 end
959 end
946 end
960 end
@@ -1,6 +1,6
1 <%= error_messages_for 'category' %>
1 <%= error_messages_for 'category' %>
2
2
3 <div class="box">
3 <div class="box">
4 <p><%= f.text_field :name, :size => 30, :required => true %></p>
4 <p><%= f.text_field :name, :size => 30, :required => true %></p>
5 <p><%= f.select :assigned_to_id, @project.assignable_users.sort.collect{|u| [u.name, u.id]}, :include_blank => true %></p>
5 <p><%= f.select :assigned_to_id, principals_options_for_select(@project.assignable_users, @category.assigned_to), :include_blank => true %></p>
6 </div>
6 </div>
@@ -1,79 +1,79
1 <h2><%= @copy ? l(:button_copy) : l(:button_move) %></h2>
1 <h2><%= @copy ? l(:button_copy) : l(:button_move) %></h2>
2
2
3 <ul>
3 <ul>
4 <% @issues.each do |issue| -%>
4 <% @issues.each do |issue| -%>
5 <li><%= link_to_issue issue %></li>
5 <li><%= link_to_issue issue %></li>
6 <% end -%>
6 <% end -%>
7 </ul>
7 </ul>
8
8
9 <% form_tag({:action => 'create'}, :id => 'move_form') do %>
9 <% form_tag({:action => 'create'}, :id => 'move_form') do %>
10 <%= @issues.collect {|i| hidden_field_tag('ids[]', i.id)}.join %>
10 <%= @issues.collect {|i| hidden_field_tag('ids[]', i.id)}.join %>
11
11
12 <div class="box tabular">
12 <div class="box tabular">
13 <fieldset class="attributes">
13 <fieldset class="attributes">
14 <legend><%= l(:label_change_properties) %></legend>
14 <legend><%= l(:label_change_properties) %></legend>
15
15
16 <div class="splitcontentleft">
16 <div class="splitcontentleft">
17 <p><label for="new_project_id"><%=l(:field_project)%>:</label>
17 <p><label for="new_project_id"><%=l(:field_project)%>:</label>
18 <%= select_tag "new_project_id",
18 <%= select_tag "new_project_id",
19 project_tree_options_for_select(@allowed_projects, :selected => @target_project),
19 project_tree_options_for_select(@allowed_projects, :selected => @target_project),
20 :onchange => remote_function(:url => { :action => 'new' },
20 :onchange => remote_function(:url => { :action => 'new' },
21 :method => :get,
21 :method => :get,
22 :update => 'content',
22 :update => 'content',
23 :with => "Form.serialize('move_form')") %></p>
23 :with => "Form.serialize('move_form')") %></p>
24
24
25 <p><label for="new_tracker_id"><%=l(:field_tracker)%>:</label>
25 <p><label for="new_tracker_id"><%=l(:field_tracker)%>:</label>
26 <%= select_tag "new_tracker_id", "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@trackers, "id", "name") %></p>
26 <%= select_tag "new_tracker_id", "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@trackers, "id", "name") %></p>
27
27
28 <p>
28 <p>
29 <label><%= l(:field_status) %></label>
29 <label><%= l(:field_status) %></label>
30 <%= select_tag('status_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@available_statuses, :id, :name)) %>
30 <%= select_tag('status_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@available_statuses, :id, :name)) %>
31 </p>
31 </p>
32
32
33 <p>
33 <p>
34 <label><%= l(:field_priority) %></label>
34 <label><%= l(:field_priority) %></label>
35 <%= select_tag('priority_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(IssuePriority.active, :id, :name)) %>
35 <%= select_tag('priority_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(IssuePriority.active, :id, :name)) %>
36 </p>
36 </p>
37
37
38 <p>
38 <p>
39 <label><%= l(:field_assigned_to) %></label>
39 <label><%= l(:field_assigned_to) %></label>
40 <%= select_tag('assigned_to_id', content_tag('option', l(:label_no_change_option), :value => '') +
40 <%= select_tag('assigned_to_id', content_tag('option', l(:label_no_change_option), :value => '') +
41 content_tag('option', l(:label_nobody), :value => 'none') +
41 content_tag('option', l(:label_nobody), :value => 'none') +
42 options_from_collection_for_select(@target_project.assignable_users, :id, :name)) %>
42 principals_options_for_select(@target_project.assignable_users)) %>
43 </p>
43 </p>
44 </div>
44 </div>
45
45
46 <div class="splitcontentright">
46 <div class="splitcontentright">
47 <p>
47 <p>
48 <label><%= l(:field_start_date) %></label>
48 <label><%= l(:field_start_date) %></label>
49 <%= text_field_tag 'start_date', '', :size => 10 %><%= calendar_for('start_date') %>
49 <%= text_field_tag 'start_date', '', :size => 10 %><%= calendar_for('start_date') %>
50 </p>
50 </p>
51
51
52 <p>
52 <p>
53 <label><%= l(:field_due_date) %></label>
53 <label><%= l(:field_due_date) %></label>
54 <%= text_field_tag 'due_date', '', :size => 10 %><%= calendar_for('due_date') %>
54 <%= text_field_tag 'due_date', '', :size => 10 %><%= calendar_for('due_date') %>
55 </p>
55 </p>
56 </div>
56 </div>
57
57
58 </fieldset>
58 </fieldset>
59
59
60 <fieldset><legend><%= l(:field_notes) %></legend>
60 <fieldset><legend><%= l(:field_notes) %></legend>
61 <%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
61 <%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
62 <%= wikitoolbar_for 'notes' %>
62 <%= wikitoolbar_for 'notes' %>
63 </fieldset>
63 </fieldset>
64
64
65 <%= call_hook(:view_issues_move_bottom, :issues => @issues, :target_project => @target_project, :copy => !!@copy) %>
65 <%= call_hook(:view_issues_move_bottom, :issues => @issues, :target_project => @target_project, :copy => !!@copy) %>
66 </div>
66 </div>
67
67
68 <% if @copy %>
68 <% if @copy %>
69 <%= hidden_field_tag("copy_options[copy]", "1") %>
69 <%= hidden_field_tag("copy_options[copy]", "1") %>
70 <%= submit_tag l(:button_copy) %>
70 <%= submit_tag l(:button_copy) %>
71 <%= submit_tag l(:button_copy_and_follow), :name => 'follow' %>
71 <%= submit_tag l(:button_copy_and_follow), :name => 'follow' %>
72 <% else %>
72 <% else %>
73 <%= submit_tag l(:button_move) %>
73 <%= submit_tag l(:button_move) %>
74 <%= submit_tag l(:button_move_and_follow), :name => 'follow' %>
74 <%= submit_tag l(:button_move_and_follow), :name => 'follow' %>
75 <% end %>
75 <% end %>
76 <% end %>
76 <% end %>
77 <% content_for :header_tags do %>
77 <% content_for :header_tags do %>
78 <%= robot_exclusion_tag %>
78 <%= robot_exclusion_tag %>
79 <% end %>
79 <% end %>
@@ -1,50 +1,50
1 <% fields_for :issue, @issue, :builder => TabularFormBuilder do |f| %>
1 <% fields_for :issue, @issue, :builder => TabularFormBuilder do |f| %>
2
2
3 <div class="splitcontentleft">
3 <div class="splitcontentleft">
4 <% if @issue.new_record? || @allowed_statuses.any? %>
4 <% if @issue.new_record? || @allowed_statuses.any? %>
5 <p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p>
5 <p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p>
6 <% else %>
6 <% else %>
7 <p><label><%= l(:field_status) %></label> <%= @issue.status.name %></p>
7 <p><label><%= l(:field_status) %></label> <%= @issue.status.name %></p>
8 <% end %>
8 <% end %>
9
9
10 <p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), {:required => true}, :disabled => !@issue.leaf? %></p>
10 <p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), {:required => true}, :disabled => !@issue.leaf? %></p>
11 <p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p>
11 <p><%= f.select :assigned_to_id, principals_options_for_select(@issue.assignable_users, @issue.assigned_to), :include_blank => true %></p>
12 <% unless @project.issue_categories.empty? %>
12 <% unless @project.issue_categories.empty? %>
13 <p><%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
13 <p><%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
14 <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
14 <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
15 l(:label_issue_category_new),
15 l(:label_issue_category_new),
16 'category[name]',
16 'category[name]',
17 {:controller => 'issue_categories', :action => 'new', :project_id => @project},
17 {:controller => 'issue_categories', :action => 'new', :project_id => @project},
18 :title => l(:label_issue_category_new),
18 :title => l(:label_issue_category_new),
19 :tabindex => 199) if authorize_for('issue_categories', 'new') %></p>
19 :tabindex => 199) if authorize_for('issue_categories', 'new') %></p>
20 <% end %>
20 <% end %>
21 <% unless @issue.assignable_versions.empty? %>
21 <% unless @issue.assignable_versions.empty? %>
22 <p><%= f.select :fixed_version_id, version_options_for_select(@issue.assignable_versions, @issue.fixed_version), :include_blank => true %>
22 <p><%= f.select :fixed_version_id, version_options_for_select(@issue.assignable_versions, @issue.fixed_version), :include_blank => true %>
23 <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
23 <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
24 l(:label_version_new),
24 l(:label_version_new),
25 'version[name]',
25 'version[name]',
26 {:controller => 'versions', :action => 'create', :project_id => @project},
26 {:controller => 'versions', :action => 'create', :project_id => @project},
27 :title => l(:label_version_new),
27 :title => l(:label_version_new),
28 :tabindex => 200) if authorize_for('versions', 'new') %>
28 :tabindex => 200) if authorize_for('versions', 'new') %>
29 </p>
29 </p>
30 <% end %>
30 <% end %>
31 </div>
31 </div>
32
32
33 <div class="splitcontentright">
33 <div class="splitcontentright">
34 <% if User.current.allowed_to?(:manage_subtasks, @project) %>
34 <% if User.current.allowed_to?(:manage_subtasks, @project) %>
35 <p id="parent_issue"><%= f.text_field :parent_issue_id, :size => 10 %></p>
35 <p id="parent_issue"><%= f.text_field :parent_issue_id, :size => 10 %></p>
36 <div id="parent_issue_candidates" class="autocomplete"></div>
36 <div id="parent_issue_candidates" class="autocomplete"></div>
37 <%= javascript_tag "observeParentIssueField('#{auto_complete_issues_path(:id => @issue, :project_id => @project) }')" %>
37 <%= javascript_tag "observeParentIssueField('#{auto_complete_issues_path(:id => @issue, :project_id => @project) }')" %>
38 <% end %>
38 <% end %>
39 <p><%= f.text_field :start_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_start_date') if @issue.leaf? %></p>
39 <p><%= f.text_field :start_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_start_date') if @issue.leaf? %></p>
40 <p><%= f.text_field :due_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_due_date') if @issue.leaf? %></p>
40 <p><%= f.text_field :due_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_due_date') if @issue.leaf? %></p>
41 <p><%= f.text_field :estimated_hours, :size => 3, :disabled => !@issue.leaf? %> <%= l(:field_hours) %></p>
41 <p><%= f.text_field :estimated_hours, :size => 3, :disabled => !@issue.leaf? %> <%= l(:field_hours) %></p>
42 <% if @issue.leaf? && Issue.use_field_for_done_ratio? %>
42 <% if @issue.leaf? && Issue.use_field_for_done_ratio? %>
43 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
43 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
44 <% end %>
44 <% end %>
45 </div>
45 </div>
46
46
47 <div style="clear:both;"> </div>
47 <div style="clear:both;"> </div>
48 <%= render :partial => 'issues/form_custom_fields' %>
48 <%= render :partial => 'issues/form_custom_fields' %>
49
49
50 <% end %>
50 <% end %>
@@ -1,14 +1,14
1 <div class="attributes">
1 <div class="attributes">
2 <div class="splitcontentleft">
2 <div class="splitcontentleft">
3 <p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p>
3 <p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p>
4 <p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p>
4 <p><%= f.select :assigned_to_id, principals_options_for_select(@issue.assignable_users, @issue.assigned_to), :include_blank => true %></p>
5 </div>
5 </div>
6 <div class="splitcontentright">
6 <div class="splitcontentright">
7 <% if Issue.use_field_for_done_ratio? %>
7 <% if Issue.use_field_for_done_ratio? %>
8 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
8 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
9 <% end %>
9 <% end %>
10 <% unless @issue.assignable_versions.empty? %>
10 <% unless @issue.assignable_versions.empty? %>
11 <p><%= f.select :fixed_version_id, (@issue.assignable_versions.collect {|v| [v.name, v.id]}), :include_blank => true %></p>
11 <p><%= f.select :fixed_version_id, (@issue.assignable_versions.collect {|v| [v.name, v.id]}), :include_blank => true %></p>
12 <% end %>
12 <% end %>
13 </div>
13 </div>
14 </div>
14 </div>
@@ -1,91 +1,91
1 <h2><%= l(:label_bulk_edit_selected_issues) %></h2>
1 <h2><%= l(:label_bulk_edit_selected_issues) %></h2>
2
2
3 <ul><%= @issues.collect {|i| content_tag('li', link_to(h("#{i.tracker} ##{i.id}"), { :action => 'show', :id => i }) + h(": #{i.subject}")) }.join("\n") %></ul>
3 <ul><%= @issues.collect {|i| content_tag('li', link_to(h("#{i.tracker} ##{i.id}"), { :action => 'show', :id => i }) + h(": #{i.subject}")) }.join("\n") %></ul>
4
4
5 <% form_tag(:action => 'bulk_update') do %>
5 <% form_tag(:action => 'bulk_update') do %>
6 <%= @issues.collect {|i| hidden_field_tag('ids[]', i.id)}.join %>
6 <%= @issues.collect {|i| hidden_field_tag('ids[]', i.id)}.join %>
7 <div class="box tabular">
7 <div class="box tabular">
8 <fieldset class="attributes">
8 <fieldset class="attributes">
9 <legend><%= l(:label_change_properties) %></legend>
9 <legend><%= l(:label_change_properties) %></legend>
10
10
11 <div class="splitcontentleft">
11 <div class="splitcontentleft">
12 <p>
12 <p>
13 <label><%= l(:field_tracker) %></label>
13 <label><%= l(:field_tracker) %></label>
14 <%= select_tag('issue[tracker_id]', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@trackers, :id, :name)) %>
14 <%= select_tag('issue[tracker_id]', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@trackers, :id, :name)) %>
15 </p>
15 </p>
16 <% if @available_statuses.any? %>
16 <% if @available_statuses.any? %>
17 <p>
17 <p>
18 <label><%= l(:field_status) %></label>
18 <label><%= l(:field_status) %></label>
19 <%= select_tag('issue[status_id]', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@available_statuses, :id, :name)) %>
19 <%= select_tag('issue[status_id]', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@available_statuses, :id, :name)) %>
20 </p>
20 </p>
21 <% end %>
21 <% end %>
22 <p>
22 <p>
23 <label><%= l(:field_priority) %></label>
23 <label><%= l(:field_priority) %></label>
24 <%= select_tag('issue[priority_id]', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(IssuePriority.active, :id, :name)) %>
24 <%= select_tag('issue[priority_id]', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(IssuePriority.active, :id, :name)) %>
25 </p>
25 </p>
26 <p>
26 <p>
27 <label><%= l(:field_assigned_to) %></label>
27 <label><%= l(:field_assigned_to) %></label>
28 <%= select_tag('issue[assigned_to_id]', content_tag('option', l(:label_no_change_option), :value => '') +
28 <%= select_tag('issue[assigned_to_id]', content_tag('option', l(:label_no_change_option), :value => '') +
29 content_tag('option', l(:label_nobody), :value => 'none') +
29 content_tag('option', l(:label_nobody), :value => 'none') +
30 options_from_collection_for_select(@assignables, :id, :name)) %>
30 principals_options_for_select(@assignables)) %>
31 </p>
31 </p>
32 <% if @project %>
32 <% if @project %>
33 <p>
33 <p>
34 <label><%= l(:field_category) %></label>
34 <label><%= l(:field_category) %></label>
35 <%= select_tag('issue[category_id]', content_tag('option', l(:label_no_change_option), :value => '') +
35 <%= select_tag('issue[category_id]', content_tag('option', l(:label_no_change_option), :value => '') +
36 content_tag('option', l(:label_none), :value => 'none') +
36 content_tag('option', l(:label_none), :value => 'none') +
37 options_from_collection_for_select(@project.issue_categories, :id, :name)) %>
37 options_from_collection_for_select(@project.issue_categories, :id, :name)) %>
38 </p>
38 </p>
39 <% end %>
39 <% end %>
40 <% #TODO: allow editing versions when multiple projects %>
40 <% #TODO: allow editing versions when multiple projects %>
41 <% if @project %>
41 <% if @project %>
42 <p>
42 <p>
43 <label><%= l(:field_fixed_version) %></label>
43 <label><%= l(:field_fixed_version) %></label>
44 <%= select_tag('issue[fixed_version_id]', content_tag('option', l(:label_no_change_option), :value => '') +
44 <%= select_tag('issue[fixed_version_id]', content_tag('option', l(:label_no_change_option), :value => '') +
45 content_tag('option', l(:label_none), :value => 'none') +
45 content_tag('option', l(:label_none), :value => 'none') +
46 version_options_for_select(@project.shared_versions.open.sort)) %>
46 version_options_for_select(@project.shared_versions.open.sort)) %>
47 </p>
47 </p>
48 <% end %>
48 <% end %>
49
49
50 <% @custom_fields.each do |custom_field| %>
50 <% @custom_fields.each do |custom_field| %>
51 <p><label><%= h(custom_field.name) %></label> <%= custom_field_tag_for_bulk_edit('issue', custom_field, @projects) %></p>
51 <p><label><%= h(custom_field.name) %></label> <%= custom_field_tag_for_bulk_edit('issue', custom_field, @projects) %></p>
52 <% end %>
52 <% end %>
53
53
54 <%= call_hook(:view_issues_bulk_edit_details_bottom, { :issues => @issues }) %>
54 <%= call_hook(:view_issues_bulk_edit_details_bottom, { :issues => @issues }) %>
55 </div>
55 </div>
56
56
57 <div class="splitcontentright">
57 <div class="splitcontentright">
58 <% if @project && User.current.allowed_to?(:manage_subtasks, @project) %>
58 <% if @project && User.current.allowed_to?(:manage_subtasks, @project) %>
59 <p>
59 <p>
60 <label><%= l(:field_parent_issue) %></label>
60 <label><%= l(:field_parent_issue) %></label>
61 <%= text_field_tag 'issue[parent_issue_id]', '', :size => 10 %>
61 <%= text_field_tag 'issue[parent_issue_id]', '', :size => 10 %>
62 </p>
62 </p>
63 <div id="parent_issue_candidates" class="autocomplete"></div>
63 <div id="parent_issue_candidates" class="autocomplete"></div>
64 <%= javascript_tag "observeParentIssueField('#{auto_complete_issues_path(:project_id => @project) }')" %>
64 <%= javascript_tag "observeParentIssueField('#{auto_complete_issues_path(:project_id => @project) }')" %>
65 <% end %>
65 <% end %>
66 <p>
66 <p>
67 <label><%= l(:field_start_date) %></label>
67 <label><%= l(:field_start_date) %></label>
68 <%= text_field_tag 'issue[start_date]', '', :size => 10 %><%= calendar_for('issue_start_date') %>
68 <%= text_field_tag 'issue[start_date]', '', :size => 10 %><%= calendar_for('issue_start_date') %>
69 </p>
69 </p>
70 <p>
70 <p>
71 <label><%= l(:field_due_date) %></label>
71 <label><%= l(:field_due_date) %></label>
72 <%= text_field_tag 'issue[due_date]', '', :size => 10 %><%= calendar_for('issue_due_date') %>
72 <%= text_field_tag 'issue[due_date]', '', :size => 10 %><%= calendar_for('issue_due_date') %>
73 </p>
73 </p>
74 <% if Issue.use_field_for_done_ratio? %>
74 <% if Issue.use_field_for_done_ratio? %>
75 <p>
75 <p>
76 <label><%= l(:field_done_ratio) %></label>
76 <label><%= l(:field_done_ratio) %></label>
77 <%= select_tag 'issue[done_ratio]', options_for_select([[l(:label_no_change_option), '']] + (0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %>
77 <%= select_tag 'issue[done_ratio]', options_for_select([[l(:label_no_change_option), '']] + (0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %>
78 </p>
78 </p>
79 <% end %>
79 <% end %>
80 </div>
80 </div>
81
81
82 </fieldset>
82 </fieldset>
83
83
84 <fieldset><legend><%= l(:field_notes) %></legend>
84 <fieldset><legend><%= l(:field_notes) %></legend>
85 <%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
85 <%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
86 <%= wikitoolbar_for 'notes' %>
86 <%= wikitoolbar_for 'notes' %>
87 </fieldset>
87 </fieldset>
88 </div>
88 </div>
89
89
90 <p><%= submit_tag l(:button_submit) %></p>
90 <p><%= submit_tag l(:button_submit) %></p>
91 <% end %>
91 <% end %>
@@ -1,678 +1,701
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 require File.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class ApplicationHelperTest < ActionView::TestCase
20 class ApplicationHelperTest < ActionView::TestCase
21
21
22 fixtures :projects, :roles, :enabled_modules, :users,
22 fixtures :projects, :roles, :enabled_modules, :users,
23 :repositories, :changesets,
23 :repositories, :changesets,
24 :trackers, :issue_statuses, :issues, :versions, :documents,
24 :trackers, :issue_statuses, :issues, :versions, :documents,
25 :wikis, :wiki_pages, :wiki_contents,
25 :wikis, :wiki_pages, :wiki_contents,
26 :boards, :messages,
26 :boards, :messages,
27 :attachments,
27 :attachments,
28 :enumerations
28 :enumerations
29
29
30 def setup
30 def setup
31 super
31 super
32 end
32 end
33
33
34 context "#link_to_if_authorized" do
34 context "#link_to_if_authorized" do
35 context "authorized user" do
35 context "authorized user" do
36 should "be tested"
36 should "be tested"
37 end
37 end
38
38
39 context "unauthorized user" do
39 context "unauthorized user" do
40 should "be tested"
40 should "be tested"
41 end
41 end
42
42
43 should "allow using the :controller and :action for the target link" do
43 should "allow using the :controller and :action for the target link" do
44 User.current = User.find_by_login('admin')
44 User.current = User.find_by_login('admin')
45
45
46 @project = Issue.first.project # Used by helper
46 @project = Issue.first.project # Used by helper
47 response = link_to_if_authorized("By controller/action",
47 response = link_to_if_authorized("By controller/action",
48 {:controller => 'issues', :action => 'edit', :id => Issue.first.id})
48 {:controller => 'issues', :action => 'edit', :id => Issue.first.id})
49 assert_match /href/, response
49 assert_match /href/, response
50 end
50 end
51
51
52 end
52 end
53
53
54 def test_auto_links
54 def test_auto_links
55 to_test = {
55 to_test = {
56 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
56 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
57 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
57 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
58 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
58 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
59 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
59 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
60 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
60 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
61 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
61 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
62 '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>.',
62 '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>.',
63 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
63 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
64 '(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>)',
64 '(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>)',
65 '(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>)',
65 '(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>)',
66 '(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>).',
66 '(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>).',
67 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
67 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
68 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
68 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
69 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
69 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
70 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
70 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
71 '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>',
71 '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>',
72 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
72 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
73 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
73 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
74 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
74 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
75 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
75 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
76 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
76 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
77 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
77 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
78 # two exclamation marks
78 # two exclamation marks
79 '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>',
79 '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>',
80 # escaping
80 # escaping
81 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo"bar</a>',
81 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo"bar</a>',
82 # wrap in angle brackets
82 # wrap in angle brackets
83 '<http://foo.bar>' => '&lt;<a class="external" href="http://foo.bar">http://foo.bar</a>&gt;'
83 '<http://foo.bar>' => '&lt;<a class="external" href="http://foo.bar">http://foo.bar</a>&gt;'
84 }
84 }
85 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
85 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
86 end
86 end
87
87
88 def test_auto_mailto
88 def test_auto_mailto
89 assert_equal '<p><a class="email" href="mailto:test@foo.bar">test@foo.bar</a></p>',
89 assert_equal '<p><a class="email" href="mailto:test@foo.bar">test@foo.bar</a></p>',
90 textilizable('test@foo.bar')
90 textilizable('test@foo.bar')
91 end
91 end
92
92
93 def test_inline_images
93 def test_inline_images
94 to_test = {
94 to_test = {
95 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
95 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
96 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
96 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
97 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
97 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
98 # inline styles should be stripped
98 # inline styles should be stripped
99 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" alt="" />',
99 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" alt="" />',
100 '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" />',
100 '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" />',
101 '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;" />',
101 '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;" />',
102 }
102 }
103 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
103 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
104 end
104 end
105
105
106 def test_inline_images_inside_tags
106 def test_inline_images_inside_tags
107 raw = <<-RAW
107 raw = <<-RAW
108 h1. !foo.png! Heading
108 h1. !foo.png! Heading
109
109
110 Centered image:
110 Centered image:
111
111
112 p=. !bar.gif!
112 p=. !bar.gif!
113 RAW
113 RAW
114
114
115 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
115 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
116 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
116 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
117 end
117 end
118
118
119 def test_attached_images
119 def test_attached_images
120 to_test = {
120 to_test = {
121 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
121 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
122 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
122 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
123 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
123 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
124 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
124 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
125 # link image
125 # link image
126 '!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>',
126 '!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>',
127 }
127 }
128 attachments = Attachment.find(:all)
128 attachments = Attachment.find(:all)
129 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
129 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
130 end
130 end
131
131
132 def test_textile_external_links
132 def test_textile_external_links
133 to_test = {
133 to_test = {
134 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
134 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
135 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
135 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
136 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
136 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
137 '"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>',
137 '"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>',
138 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
138 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
139 # no multiline link text
139 # no multiline link text
140 "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",
140 "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",
141 # mailto link
141 # mailto link
142 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
142 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
143 # two exclamation marks
143 # two exclamation marks
144 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
144 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
145 # escaping
145 # escaping
146 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
146 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
147 }
147 }
148 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
148 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
149 end
149 end
150
150
151 def test_redmine_links
151 def test_redmine_links
152 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
152 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
153 :class => 'issue status-1 priority-1 overdue', :title => 'Error 281 when updating a recipe (New)')
153 :class => 'issue status-1 priority-1 overdue', :title => 'Error 281 when updating a recipe (New)')
154
154
155 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
155 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
156 :class => 'changeset', :title => 'My very first commit')
156 :class => 'changeset', :title => 'My very first commit')
157 changeset_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
157 changeset_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
158 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
158 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
159
159
160 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
160 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
161 :class => 'document')
161 :class => 'document')
162
162
163 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
163 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
164 :class => 'version')
164 :class => 'version')
165
165
166 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
166 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
167
167
168 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
168 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
169
169
170 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
170 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
171 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
171 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
172
172
173 to_test = {
173 to_test = {
174 # tickets
174 # tickets
175 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
175 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
176 # changesets
176 # changesets
177 'r1' => changeset_link,
177 'r1' => changeset_link,
178 'r1.' => "#{changeset_link}.",
178 'r1.' => "#{changeset_link}.",
179 'r1, r2' => "#{changeset_link}, #{changeset_link2}",
179 'r1, r2' => "#{changeset_link}, #{changeset_link2}",
180 'r1,r2' => "#{changeset_link},#{changeset_link2}",
180 'r1,r2' => "#{changeset_link},#{changeset_link2}",
181 # documents
181 # documents
182 'document#1' => document_link,
182 'document#1' => document_link,
183 'document:"Test document"' => document_link,
183 'document:"Test document"' => document_link,
184 # versions
184 # versions
185 'version#2' => version_link,
185 'version#2' => version_link,
186 'version:1.0' => version_link,
186 'version:1.0' => version_link,
187 'version:"1.0"' => version_link,
187 'version:"1.0"' => version_link,
188 # source
188 # source
189 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
189 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
190 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
190 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
191 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
191 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
192 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
192 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
193 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
193 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
194 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
194 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
195 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
195 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
196 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
196 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
197 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
197 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
198 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
198 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
199 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
199 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
200 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
200 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
201 # message
201 # message
202 'message#4' => link_to('Post 2', message_url, :class => 'message'),
202 'message#4' => link_to('Post 2', message_url, :class => 'message'),
203 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5', :r => 5), :class => 'message'),
203 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5', :r => 5), :class => 'message'),
204 # project
204 # project
205 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
205 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
206 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
206 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
207 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
207 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
208 # escaping
208 # escaping
209 '!#3.' => '#3.',
209 '!#3.' => '#3.',
210 '!r1' => 'r1',
210 '!r1' => 'r1',
211 '!document#1' => 'document#1',
211 '!document#1' => 'document#1',
212 '!document:"Test document"' => 'document:"Test document"',
212 '!document:"Test document"' => 'document:"Test document"',
213 '!version#2' => 'version#2',
213 '!version#2' => 'version#2',
214 '!version:1.0' => 'version:1.0',
214 '!version:1.0' => 'version:1.0',
215 '!version:"1.0"' => 'version:"1.0"',
215 '!version:"1.0"' => 'version:"1.0"',
216 '!source:/some/file' => 'source:/some/file',
216 '!source:/some/file' => 'source:/some/file',
217 # not found
217 # not found
218 '#0123456789' => '#0123456789',
218 '#0123456789' => '#0123456789',
219 # invalid expressions
219 # invalid expressions
220 'source:' => 'source:',
220 'source:' => 'source:',
221 # url hash
221 # url hash
222 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
222 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
223 }
223 }
224 @project = Project.find(1)
224 @project = Project.find(1)
225 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
225 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
226 end
226 end
227
227
228 def test_cross_project_redmine_links
228 def test_cross_project_redmine_links
229 source_link = link_to('ecookbook:source:/some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']},
229 source_link = link_to('ecookbook:source:/some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']},
230 :class => 'source')
230 :class => 'source')
231
231
232 changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
232 changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
233 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
233 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
234
234
235 to_test = {
235 to_test = {
236 # documents
236 # documents
237 'document:"Test document"' => 'document:"Test document"',
237 'document:"Test document"' => 'document:"Test document"',
238 'ecookbook:document:"Test document"' => '<a href="/documents/1" class="document">Test document</a>',
238 'ecookbook:document:"Test document"' => '<a href="/documents/1" class="document">Test document</a>',
239 'invalid:document:"Test document"' => 'invalid:document:"Test document"',
239 'invalid:document:"Test document"' => 'invalid:document:"Test document"',
240 # versions
240 # versions
241 'version:"1.0"' => 'version:"1.0"',
241 'version:"1.0"' => 'version:"1.0"',
242 'ecookbook:version:"1.0"' => '<a href="/versions/2" class="version">1.0</a>',
242 'ecookbook:version:"1.0"' => '<a href="/versions/2" class="version">1.0</a>',
243 'invalid:version:"1.0"' => 'invalid:version:"1.0"',
243 'invalid:version:"1.0"' => 'invalid:version:"1.0"',
244 # changeset
244 # changeset
245 'r2' => 'r2',
245 'r2' => 'r2',
246 'ecookbook:r2' => changeset_link,
246 'ecookbook:r2' => changeset_link,
247 'invalid:r2' => 'invalid:r2',
247 'invalid:r2' => 'invalid:r2',
248 # source
248 # source
249 'source:/some/file' => 'source:/some/file',
249 'source:/some/file' => 'source:/some/file',
250 'ecookbook:source:/some/file' => source_link,
250 'ecookbook:source:/some/file' => source_link,
251 'invalid:source:/some/file' => 'invalid:source:/some/file',
251 'invalid:source:/some/file' => 'invalid:source:/some/file',
252 }
252 }
253 @project = Project.find(3)
253 @project = Project.find(3)
254 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
254 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
255 end
255 end
256
256
257 def test_redmine_links_git_commit
257 def test_redmine_links_git_commit
258 changeset_link = link_to('abcd',
258 changeset_link = link_to('abcd',
259 {
259 {
260 :controller => 'repositories',
260 :controller => 'repositories',
261 :action => 'revision',
261 :action => 'revision',
262 :id => 'subproject1',
262 :id => 'subproject1',
263 :rev => 'abcd',
263 :rev => 'abcd',
264 },
264 },
265 :class => 'changeset', :title => 'test commit')
265 :class => 'changeset', :title => 'test commit')
266 to_test = {
266 to_test = {
267 'commit:abcd' => changeset_link,
267 'commit:abcd' => changeset_link,
268 }
268 }
269 @project = Project.find(3)
269 @project = Project.find(3)
270 r = Repository::Git.create!(:project => @project, :url => '/tmp/test/git')
270 r = Repository::Git.create!(:project => @project, :url => '/tmp/test/git')
271 assert r
271 assert r
272 c = Changeset.new(:repository => r,
272 c = Changeset.new(:repository => r,
273 :committed_on => Time.now,
273 :committed_on => Time.now,
274 :revision => 'abcd',
274 :revision => 'abcd',
275 :scmid => 'abcd',
275 :scmid => 'abcd',
276 :comments => 'test commit')
276 :comments => 'test commit')
277 assert( c.save )
277 assert( c.save )
278 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
278 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
279 end
279 end
280
280
281 # TODO: Bazaar commit id contains mail address, so it contains '@' and '_'.
281 # TODO: Bazaar commit id contains mail address, so it contains '@' and '_'.
282 def test_redmine_links_darcs_commit
282 def test_redmine_links_darcs_commit
283 changeset_link = link_to('20080308225258-98289-abcd456efg.gz',
283 changeset_link = link_to('20080308225258-98289-abcd456efg.gz',
284 {
284 {
285 :controller => 'repositories',
285 :controller => 'repositories',
286 :action => 'revision',
286 :action => 'revision',
287 :id => 'subproject1',
287 :id => 'subproject1',
288 :rev => '123',
288 :rev => '123',
289 },
289 },
290 :class => 'changeset', :title => 'test commit')
290 :class => 'changeset', :title => 'test commit')
291 to_test = {
291 to_test = {
292 'commit:20080308225258-98289-abcd456efg.gz' => changeset_link,
292 'commit:20080308225258-98289-abcd456efg.gz' => changeset_link,
293 }
293 }
294 @project = Project.find(3)
294 @project = Project.find(3)
295 r = Repository::Darcs.create!(
295 r = Repository::Darcs.create!(
296 :project => @project, :url => '/tmp/test/darcs',
296 :project => @project, :url => '/tmp/test/darcs',
297 :log_encoding => 'UTF-8')
297 :log_encoding => 'UTF-8')
298 assert r
298 assert r
299 c = Changeset.new(:repository => r,
299 c = Changeset.new(:repository => r,
300 :committed_on => Time.now,
300 :committed_on => Time.now,
301 :revision => '123',
301 :revision => '123',
302 :scmid => '20080308225258-98289-abcd456efg.gz',
302 :scmid => '20080308225258-98289-abcd456efg.gz',
303 :comments => 'test commit')
303 :comments => 'test commit')
304 assert( c.save )
304 assert( c.save )
305 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
305 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
306 end
306 end
307
307
308 def test_redmine_links_mercurial_commit
308 def test_redmine_links_mercurial_commit
309 changeset_link_rev = link_to('r123',
309 changeset_link_rev = link_to('r123',
310 {
310 {
311 :controller => 'repositories',
311 :controller => 'repositories',
312 :action => 'revision',
312 :action => 'revision',
313 :id => 'subproject1',
313 :id => 'subproject1',
314 :rev => '123' ,
314 :rev => '123' ,
315 },
315 },
316 :class => 'changeset', :title => 'test commit')
316 :class => 'changeset', :title => 'test commit')
317 changeset_link_commit = link_to('abcd',
317 changeset_link_commit = link_to('abcd',
318 {
318 {
319 :controller => 'repositories',
319 :controller => 'repositories',
320 :action => 'revision',
320 :action => 'revision',
321 :id => 'subproject1',
321 :id => 'subproject1',
322 :rev => 'abcd' ,
322 :rev => 'abcd' ,
323 },
323 },
324 :class => 'changeset', :title => 'test commit')
324 :class => 'changeset', :title => 'test commit')
325 to_test = {
325 to_test = {
326 'r123' => changeset_link_rev,
326 'r123' => changeset_link_rev,
327 'commit:abcd' => changeset_link_commit,
327 'commit:abcd' => changeset_link_commit,
328 }
328 }
329 @project = Project.find(3)
329 @project = Project.find(3)
330 r = Repository::Mercurial.create!(:project => @project, :url => '/tmp/test')
330 r = Repository::Mercurial.create!(:project => @project, :url => '/tmp/test')
331 assert r
331 assert r
332 c = Changeset.new(:repository => r,
332 c = Changeset.new(:repository => r,
333 :committed_on => Time.now,
333 :committed_on => Time.now,
334 :revision => '123',
334 :revision => '123',
335 :scmid => 'abcd',
335 :scmid => 'abcd',
336 :comments => 'test commit')
336 :comments => 'test commit')
337 assert( c.save )
337 assert( c.save )
338 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
338 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
339 end
339 end
340
340
341 def test_attachment_links
341 def test_attachment_links
342 attachment_link = link_to('error281.txt', {:controller => 'attachments', :action => 'download', :id => '1'}, :class => 'attachment')
342 attachment_link = link_to('error281.txt', {:controller => 'attachments', :action => 'download', :id => '1'}, :class => 'attachment')
343 to_test = {
343 to_test = {
344 'attachment:error281.txt' => attachment_link
344 'attachment:error281.txt' => attachment_link
345 }
345 }
346 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => Issue.find(3).attachments), "#{text} failed" }
346 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => Issue.find(3).attachments), "#{text} failed" }
347 end
347 end
348
348
349 def test_wiki_links
349 def test_wiki_links
350 to_test = {
350 to_test = {
351 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
351 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
352 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
352 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
353 # link with anchor
353 # link with anchor
354 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
354 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
355 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
355 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
356 # page that doesn't exist
356 # page that doesn't exist
357 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
357 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
358 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
358 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
359 # link to another project wiki
359 # link to another project wiki
360 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">onlinestore</a>',
360 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">onlinestore</a>',
361 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">Wiki</a>',
361 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">Wiki</a>',
362 '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
362 '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
363 '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
363 '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
364 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
364 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
365 # striked through link
365 # striked through link
366 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
366 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
367 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
367 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
368 # escaping
368 # escaping
369 '![[Another page|Page]]' => '[[Another page|Page]]',
369 '![[Another page|Page]]' => '[[Another page|Page]]',
370 # project does not exist
370 # project does not exist
371 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
371 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
372 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
372 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
373 }
373 }
374 @project = Project.find(1)
374 @project = Project.find(1)
375 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
375 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
376 end
376 end
377
377
378 def test_html_tags
378 def test_html_tags
379 to_test = {
379 to_test = {
380 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
380 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
381 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
381 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
382 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
382 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
383 # do not escape pre/code tags
383 # do not escape pre/code tags
384 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
384 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
385 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
385 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
386 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
386 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
387 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
387 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
388 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
388 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
389 # remove attributes except class
389 # remove attributes except class
390 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
390 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
391 '<pre class="foo">some text</pre>' => '<pre class="foo">some text</pre>',
391 '<pre class="foo">some text</pre>' => '<pre class="foo">some text</pre>',
392 "<pre class='foo bar'>some text</pre>" => "<pre class='foo bar'>some text</pre>",
392 "<pre class='foo bar'>some text</pre>" => "<pre class='foo bar'>some text</pre>",
393 '<pre class="foo bar">some text</pre>' => '<pre class="foo bar">some text</pre>',
393 '<pre class="foo bar">some text</pre>' => '<pre class="foo bar">some text</pre>',
394 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
394 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
395 # xss
395 # xss
396 '<pre><code class=""onmouseover="alert(1)">text</code></pre>' => '<pre><code>text</code></pre>',
396 '<pre><code class=""onmouseover="alert(1)">text</code></pre>' => '<pre><code>text</code></pre>',
397 '<pre class=""onmouseover="alert(1)">text</pre>' => '<pre>text</pre>',
397 '<pre class=""onmouseover="alert(1)">text</pre>' => '<pre>text</pre>',
398 }
398 }
399 to_test.each { |text, result| assert_equal result, textilizable(text) }
399 to_test.each { |text, result| assert_equal result, textilizable(text) }
400 end
400 end
401
401
402 def test_allowed_html_tags
402 def test_allowed_html_tags
403 to_test = {
403 to_test = {
404 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
404 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
405 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
405 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
406 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
406 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
407 }
407 }
408 to_test.each { |text, result| assert_equal result, textilizable(text) }
408 to_test.each { |text, result| assert_equal result, textilizable(text) }
409 end
409 end
410
410
411 def test_pre_tags
411 def test_pre_tags
412 raw = <<-RAW
412 raw = <<-RAW
413 Before
413 Before
414
414
415 <pre>
415 <pre>
416 <prepared-statement-cache-size>32</prepared-statement-cache-size>
416 <prepared-statement-cache-size>32</prepared-statement-cache-size>
417 </pre>
417 </pre>
418
418
419 After
419 After
420 RAW
420 RAW
421
421
422 expected = <<-EXPECTED
422 expected = <<-EXPECTED
423 <p>Before</p>
423 <p>Before</p>
424 <pre>
424 <pre>
425 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
425 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
426 </pre>
426 </pre>
427 <p>After</p>
427 <p>After</p>
428 EXPECTED
428 EXPECTED
429
429
430 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
430 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
431 end
431 end
432
432
433 def test_pre_content_should_not_parse_wiki_and_redmine_links
433 def test_pre_content_should_not_parse_wiki_and_redmine_links
434 raw = <<-RAW
434 raw = <<-RAW
435 [[CookBook documentation]]
435 [[CookBook documentation]]
436
436
437 #1
437 #1
438
438
439 <pre>
439 <pre>
440 [[CookBook documentation]]
440 [[CookBook documentation]]
441
441
442 #1
442 #1
443 </pre>
443 </pre>
444 RAW
444 RAW
445
445
446 expected = <<-EXPECTED
446 expected = <<-EXPECTED
447 <p><a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a></p>
447 <p><a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a></p>
448 <p><a href="/issues/1" class="issue status-1 priority-1" title="Can't print recipes (New)">#1</a></p>
448 <p><a href="/issues/1" class="issue status-1 priority-1" title="Can't print recipes (New)">#1</a></p>
449 <pre>
449 <pre>
450 [[CookBook documentation]]
450 [[CookBook documentation]]
451
451
452 #1
452 #1
453 </pre>
453 </pre>
454 EXPECTED
454 EXPECTED
455
455
456 @project = Project.find(1)
456 @project = Project.find(1)
457 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
457 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
458 end
458 end
459
459
460 def test_non_closing_pre_blocks_should_be_closed
460 def test_non_closing_pre_blocks_should_be_closed
461 raw = <<-RAW
461 raw = <<-RAW
462 <pre><code>
462 <pre><code>
463 RAW
463 RAW
464
464
465 expected = <<-EXPECTED
465 expected = <<-EXPECTED
466 <pre><code>
466 <pre><code>
467 </code></pre>
467 </code></pre>
468 EXPECTED
468 EXPECTED
469
469
470 @project = Project.find(1)
470 @project = Project.find(1)
471 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
471 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
472 end
472 end
473
473
474 def test_syntax_highlight
474 def test_syntax_highlight
475 raw = <<-RAW
475 raw = <<-RAW
476 <pre><code class="ruby">
476 <pre><code class="ruby">
477 # Some ruby code here
477 # Some ruby code here
478 </code></pre>
478 </code></pre>
479 RAW
479 RAW
480
480
481 expected = <<-EXPECTED
481 expected = <<-EXPECTED
482 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="no">1</span> <span class="c"># Some ruby code here</span></span>
482 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="no">1</span> <span class="c"># Some ruby code here</span></span>
483 </code></pre>
483 </code></pre>
484 EXPECTED
484 EXPECTED
485
485
486 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
486 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
487 end
487 end
488
488
489 def test_wiki_links_in_tables
489 def test_wiki_links_in_tables
490 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
490 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
491 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
491 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
492 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
492 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
493 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
493 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
494 }
494 }
495 @project = Project.find(1)
495 @project = Project.find(1)
496 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
496 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
497 end
497 end
498
498
499 def test_text_formatting
499 def test_text_formatting
500 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
500 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
501 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
501 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
502 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
502 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
503 '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>',
503 '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>',
504 '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',
504 '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',
505 }
505 }
506 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
506 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
507 end
507 end
508
508
509 def test_wiki_horizontal_rule
509 def test_wiki_horizontal_rule
510 assert_equal '<hr />', textilizable('---')
510 assert_equal '<hr />', textilizable('---')
511 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
511 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
512 end
512 end
513
513
514 def test_footnotes
514 def test_footnotes
515 raw = <<-RAW
515 raw = <<-RAW
516 This is some text[1].
516 This is some text[1].
517
517
518 fn1. This is the foot note
518 fn1. This is the foot note
519 RAW
519 RAW
520
520
521 expected = <<-EXPECTED
521 expected = <<-EXPECTED
522 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
522 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
523 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
523 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
524 EXPECTED
524 EXPECTED
525
525
526 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
526 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
527 end
527 end
528
528
529 def test_headings
529 def test_headings
530 raw = 'h1. Some heading'
530 raw = 'h1. Some heading'
531 expected = %|<a name="Some-heading"></a>\n<h1 >Some heading<a href="#Some-heading" class="wiki-anchor">&para;</a></h1>|
531 expected = %|<a name="Some-heading"></a>\n<h1 >Some heading<a href="#Some-heading" class="wiki-anchor">&para;</a></h1>|
532
532
533 assert_equal expected, textilizable(raw)
533 assert_equal expected, textilizable(raw)
534 end
534 end
535
535
536 def test_table_of_content
536 def test_table_of_content
537 raw = <<-RAW
537 raw = <<-RAW
538 {{toc}}
538 {{toc}}
539
539
540 h1. Title
540 h1. Title
541
541
542 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
542 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
543
543
544 h2. Subtitle with a [[Wiki]] link
544 h2. Subtitle with a [[Wiki]] link
545
545
546 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
546 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
547
547
548 h2. Subtitle with [[Wiki|another Wiki]] link
548 h2. Subtitle with [[Wiki|another Wiki]] link
549
549
550 h2. Subtitle with %{color:red}red text%
550 h2. Subtitle with %{color:red}red text%
551
551
552 <pre>
552 <pre>
553 some code
553 some code
554 </pre>
554 </pre>
555
555
556 h3. Subtitle with *some* _modifiers_
556 h3. Subtitle with *some* _modifiers_
557
557
558 h1. Another title
558 h1. Another title
559
559
560 h3. An "Internet link":http://www.redmine.org/ inside subtitle
560 h3. An "Internet link":http://www.redmine.org/ inside subtitle
561
561
562 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
562 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
563
563
564 RAW
564 RAW
565
565
566 expected = '<ul class="toc">' +
566 expected = '<ul class="toc">' +
567 '<li><a href="#Title">Title</a>' +
567 '<li><a href="#Title">Title</a>' +
568 '<ul>' +
568 '<ul>' +
569 '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
569 '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
570 '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
570 '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
571 '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
571 '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
572 '<ul>' +
572 '<ul>' +
573 '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
573 '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
574 '</ul>' +
574 '</ul>' +
575 '</li>' +
575 '</li>' +
576 '</ul>' +
576 '</ul>' +
577 '</li>' +
577 '</li>' +
578 '<li><a href="#Another-title">Another title</a>' +
578 '<li><a href="#Another-title">Another title</a>' +
579 '<ul>' +
579 '<ul>' +
580 '<li>' +
580 '<li>' +
581 '<ul>' +
581 '<ul>' +
582 '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
582 '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
583 '</ul>' +
583 '</ul>' +
584 '</li>' +
584 '</li>' +
585 '<li><a href="#Project-Name">Project Name</a></li>' +
585 '<li><a href="#Project-Name">Project Name</a></li>' +
586 '</ul>' +
586 '</ul>' +
587 '</li>' +
587 '</li>' +
588 '</ul>'
588 '</ul>'
589
589
590 @project = Project.find(1)
590 @project = Project.find(1)
591 assert textilizable(raw).gsub("\n", "").include?(expected), textilizable(raw)
591 assert textilizable(raw).gsub("\n", "").include?(expected), textilizable(raw)
592 end
592 end
593
593
594 def test_table_of_content_should_contain_included_page_headings
594 def test_table_of_content_should_contain_included_page_headings
595 raw = <<-RAW
595 raw = <<-RAW
596 {{toc}}
596 {{toc}}
597
597
598 h1. Included
598 h1. Included
599
599
600 {{include(Child_1)}}
600 {{include(Child_1)}}
601 RAW
601 RAW
602
602
603 expected = '<ul class="toc">' +
603 expected = '<ul class="toc">' +
604 '<li><a href="#Included">Included</a></li>' +
604 '<li><a href="#Included">Included</a></li>' +
605 '<li><a href="#Child-page-1">Child page 1</a></li>' +
605 '<li><a href="#Child-page-1">Child page 1</a></li>' +
606 '</ul>'
606 '</ul>'
607
607
608 @project = Project.find(1)
608 @project = Project.find(1)
609 assert textilizable(raw).gsub("\n", "").include?(expected)
609 assert textilizable(raw).gsub("\n", "").include?(expected)
610 end
610 end
611
611
612 def test_default_formatter
612 def test_default_formatter
613 Setting.text_formatting = 'unknown'
613 Setting.text_formatting = 'unknown'
614 text = 'a *link*: http://www.example.net/'
614 text = 'a *link*: http://www.example.net/'
615 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
615 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
616 Setting.text_formatting = 'textile'
616 Setting.text_formatting = 'textile'
617 end
617 end
618
618
619 def test_due_date_distance_in_words
619 def test_due_date_distance_in_words
620 to_test = { Date.today => 'Due in 0 days',
620 to_test = { Date.today => 'Due in 0 days',
621 Date.today + 1 => 'Due in 1 day',
621 Date.today + 1 => 'Due in 1 day',
622 Date.today + 100 => 'Due in about 3 months',
622 Date.today + 100 => 'Due in about 3 months',
623 Date.today + 20000 => 'Due in over 54 years',
623 Date.today + 20000 => 'Due in over 54 years',
624 Date.today - 1 => '1 day late',
624 Date.today - 1 => '1 day late',
625 Date.today - 100 => 'about 3 months late',
625 Date.today - 100 => 'about 3 months late',
626 Date.today - 20000 => 'over 54 years late',
626 Date.today - 20000 => 'over 54 years late',
627 }
627 }
628 ::I18n.locale = :en
628 ::I18n.locale = :en
629 to_test.each do |date, expected|
629 to_test.each do |date, expected|
630 assert_equal expected, due_date_distance_in_words(date)
630 assert_equal expected, due_date_distance_in_words(date)
631 end
631 end
632 end
632 end
633
633
634 def test_avatar
634 def test_avatar
635 # turn on avatars
635 # turn on avatars
636 Setting.gravatar_enabled = '1'
636 Setting.gravatar_enabled = '1'
637 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
637 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
638 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
638 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
639 assert_nil avatar('jsmith')
639 assert_nil avatar('jsmith')
640 assert_nil avatar(nil)
640 assert_nil avatar(nil)
641
641
642 # turn off avatars
642 # turn off avatars
643 Setting.gravatar_enabled = '0'
643 Setting.gravatar_enabled = '0'
644 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
644 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
645 end
645 end
646
646
647 def test_link_to_user
647 def test_link_to_user
648 user = User.find(2)
648 user = User.find(2)
649 t = link_to_user(user)
649 t = link_to_user(user)
650 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
650 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
651 end
651 end
652
652
653 def test_link_to_user_should_not_link_to_locked_user
653 def test_link_to_user_should_not_link_to_locked_user
654 user = User.find(5)
654 user = User.find(5)
655 assert user.locked?
655 assert user.locked?
656 t = link_to_user(user)
656 t = link_to_user(user)
657 assert_equal user.name, t
657 assert_equal user.name, t
658 end
658 end
659
659
660 def test_link_to_user_should_not_link_to_anonymous
660 def test_link_to_user_should_not_link_to_anonymous
661 user = User.anonymous
661 user = User.anonymous
662 assert user.anonymous?
662 assert user.anonymous?
663 t = link_to_user(user)
663 t = link_to_user(user)
664 assert_equal ::I18n.t(:label_user_anonymous), t
664 assert_equal ::I18n.t(:label_user_anonymous), t
665 end
665 end
666
666
667 def test_link_to_project
667 def test_link_to_project
668 project = Project.find(1)
668 project = Project.find(1)
669 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
669 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
670 link_to_project(project)
670 link_to_project(project)
671 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
671 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
672 link_to_project(project, :action => 'settings')
672 link_to_project(project, :action => 'settings')
673 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
673 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
674 link_to_project(project, {:only_path => false, :jump => 'blah'})
674 link_to_project(project, {:only_path => false, :jump => 'blah'})
675 assert_equal %(<a href="/projects/ecookbook/settings" class="project">eCookbook</a>),
675 assert_equal %(<a href="/projects/ecookbook/settings" class="project">eCookbook</a>),
676 link_to_project(project, {:action => 'settings'}, :class => "project")
676 link_to_project(project, {:action => 'settings'}, :class => "project")
677 end
677 end
678
679 def test_principals_options_for_select_with_users
680 users = [User.find(2), User.find(4)]
681 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
682 principals_options_for_select(users)
683 end
684
685 def test_principals_options_for_select_with_selected
686 users = [User.find(2), User.find(4)]
687 assert_equal %(<option value="2">John Smith</option><option value="4" selected="selected">Robert Hill</option>),
688 principals_options_for_select(users, User.find(4))
689 end
690
691 def test_principals_options_for_select_with_users_and_groups
692 users = [User.find(2), Group.find(11), User.find(4), Group.find(10)]
693 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>) +
694 %(<optgroup label="Groups"><option value="10">A Team</option><option value="11">B Team</option></optgroup>),
695 principals_options_for_select(users)
696 end
697
698 def test_principals_options_for_select_with_empty_collection
699 assert_equal '', principals_options_for_select([])
700 end
678 end
701 end
General Comments 0
You need to be logged in to leave comments. Login now