##// END OF EJS Templates
Merged r3924 from trunk....
Eric Davis -
r3860:c7e719fc4b44
parent child
Show More
@@ -1,818 +1,833
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
35 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
36 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
36 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
37 end
37 end
38
38
39 # Display a link to remote if user is authorized
39 # Display a link to remote if user is authorized
40 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
40 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
41 url = options[:url] || {}
41 url = options[:url] || {}
42 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
42 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
43 end
43 end
44
44
45 # Displays a link to user's account page if active
45 # Displays a link to user's account page if active
46 def link_to_user(user, options={})
46 def link_to_user(user, options={})
47 if user.is_a?(User)
47 if user.is_a?(User)
48 name = h(user.name(options[:format]))
48 name = h(user.name(options[:format]))
49 if user.active?
49 if user.active?
50 link_to name, :controller => 'users', :action => 'show', :id => user
50 link_to name, :controller => 'users', :action => 'show', :id => user
51 else
51 else
52 name
52 name
53 end
53 end
54 else
54 else
55 h(user.to_s)
55 h(user.to_s)
56 end
56 end
57 end
57 end
58
58
59 # Displays a link to +issue+ with its subject.
59 # Displays a link to +issue+ with its subject.
60 # Examples:
60 # Examples:
61 #
61 #
62 # link_to_issue(issue) # => Defect #6: This is the subject
62 # link_to_issue(issue) # => Defect #6: This is the subject
63 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
63 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
64 # link_to_issue(issue, :subject => false) # => Defect #6
64 # link_to_issue(issue, :subject => false) # => Defect #6
65 # link_to_issue(issue, :project => true) # => Foo - Defect #6
65 # link_to_issue(issue, :project => true) # => Foo - Defect #6
66 #
66 #
67 def link_to_issue(issue, options={})
67 def link_to_issue(issue, options={})
68 title = nil
68 title = nil
69 subject = nil
69 subject = nil
70 if options[:subject] == false
70 if options[:subject] == false
71 title = truncate(issue.subject, :length => 60)
71 title = truncate(issue.subject, :length => 60)
72 else
72 else
73 subject = issue.subject
73 subject = issue.subject
74 if options[:truncate]
74 if options[:truncate]
75 subject = truncate(subject, :length => options[:truncate])
75 subject = truncate(subject, :length => options[:truncate])
76 end
76 end
77 end
77 end
78 s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
78 s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
79 :class => issue.css_classes,
79 :class => issue.css_classes,
80 :title => title
80 :title => title
81 s << ": #{h subject}" if subject
81 s << ": #{h subject}" if subject
82 s = "#{h issue.project} - " + s if options[:project]
82 s = "#{h issue.project} - " + s if options[:project]
83 s
83 s
84 end
84 end
85
85
86 # Generates a link to an attachment.
86 # Generates a link to an attachment.
87 # Options:
87 # Options:
88 # * :text - Link text (default to attachment filename)
88 # * :text - Link text (default to attachment filename)
89 # * :download - Force download (default: false)
89 # * :download - Force download (default: false)
90 def link_to_attachment(attachment, options={})
90 def link_to_attachment(attachment, options={})
91 text = options.delete(:text) || attachment.filename
91 text = options.delete(:text) || attachment.filename
92 action = options.delete(:download) ? 'download' : 'show'
92 action = options.delete(:download) ? 'download' : 'show'
93
93
94 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
94 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
95 end
95 end
96
96
97 # Generates a link to a SCM revision
97 # Generates a link to a SCM revision
98 # Options:
98 # Options:
99 # * :text - Link text (default to the formatted revision)
99 # * :text - Link text (default to the formatted revision)
100 def link_to_revision(revision, project, options={})
100 def link_to_revision(revision, project, options={})
101 text = options.delete(:text) || format_revision(revision)
101 text = options.delete(:text) || format_revision(revision)
102
102
103 link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision))
103 link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision))
104 end
104 end
105
105
106 # Generates a link to a project if active
107 # Examples:
108 #
109 # link_to_project(project) # => link to the specified project overview
110 # link_to_project(project, :action=>'settings') # => link to project settings
111 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
112 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
113 #
114 def link_to_project(project, options={}, html_options = nil)
115 if project.active?
116 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
117 link_to(h(project), url, html_options)
118 else
119 h(project)
120 end
121 end
122
106 def toggle_link(name, id, options={})
123 def toggle_link(name, id, options={})
107 onclick = "Element.toggle('#{id}'); "
124 onclick = "Element.toggle('#{id}'); "
108 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
125 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
109 onclick << "return false;"
126 onclick << "return false;"
110 link_to(name, "#", :onclick => onclick)
127 link_to(name, "#", :onclick => onclick)
111 end
128 end
112
129
113 def image_to_function(name, function, html_options = {})
130 def image_to_function(name, function, html_options = {})
114 html_options.symbolize_keys!
131 html_options.symbolize_keys!
115 tag(:input, html_options.merge({
132 tag(:input, html_options.merge({
116 :type => "image", :src => image_path(name),
133 :type => "image", :src => image_path(name),
117 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
134 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
118 }))
135 }))
119 end
136 end
120
137
121 def prompt_to_remote(name, text, param, url, html_options = {})
138 def prompt_to_remote(name, text, param, url, html_options = {})
122 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
139 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
123 link_to name, {}, html_options
140 link_to name, {}, html_options
124 end
141 end
125
142
126 def format_activity_title(text)
143 def format_activity_title(text)
127 h(truncate_single_line(text, :length => 100))
144 h(truncate_single_line(text, :length => 100))
128 end
145 end
129
146
130 def format_activity_day(date)
147 def format_activity_day(date)
131 date == Date.today ? l(:label_today).titleize : format_date(date)
148 date == Date.today ? l(:label_today).titleize : format_date(date)
132 end
149 end
133
150
134 def format_activity_description(text)
151 def format_activity_description(text)
135 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
152 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
136 end
153 end
137
154
138 def format_version_name(version)
155 def format_version_name(version)
139 if version.project == @project
156 if version.project == @project
140 h(version)
157 h(version)
141 else
158 else
142 h("#{version.project} - #{version}")
159 h("#{version.project} - #{version}")
143 end
160 end
144 end
161 end
145
162
146 def due_date_distance_in_words(date)
163 def due_date_distance_in_words(date)
147 if date
164 if date
148 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
165 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
149 end
166 end
150 end
167 end
151
168
152 def render_page_hierarchy(pages, node=nil)
169 def render_page_hierarchy(pages, node=nil)
153 content = ''
170 content = ''
154 if pages[node]
171 if pages[node]
155 content << "<ul class=\"pages-hierarchy\">\n"
172 content << "<ul class=\"pages-hierarchy\">\n"
156 pages[node].each do |page|
173 pages[node].each do |page|
157 content << "<li>"
174 content << "<li>"
158 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
175 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
159 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
176 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
160 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
177 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
161 content << "</li>\n"
178 content << "</li>\n"
162 end
179 end
163 content << "</ul>\n"
180 content << "</ul>\n"
164 end
181 end
165 content
182 content
166 end
183 end
167
184
168 # Renders flash messages
185 # Renders flash messages
169 def render_flash_messages
186 def render_flash_messages
170 s = ''
187 s = ''
171 flash.each do |k,v|
188 flash.each do |k,v|
172 s << content_tag('div', v, :class => "flash #{k}")
189 s << content_tag('div', v, :class => "flash #{k}")
173 end
190 end
174 s
191 s
175 end
192 end
176
193
177 # Renders tabs and their content
194 # Renders tabs and their content
178 def render_tabs(tabs)
195 def render_tabs(tabs)
179 if tabs.any?
196 if tabs.any?
180 render :partial => 'common/tabs', :locals => {:tabs => tabs}
197 render :partial => 'common/tabs', :locals => {:tabs => tabs}
181 else
198 else
182 content_tag 'p', l(:label_no_data), :class => "nodata"
199 content_tag 'p', l(:label_no_data), :class => "nodata"
183 end
200 end
184 end
201 end
185
202
186 # Renders the project quick-jump box
203 # Renders the project quick-jump box
187 def render_project_jump_box
204 def render_project_jump_box
188 # Retrieve them now to avoid a COUNT query
205 # Retrieve them now to avoid a COUNT query
189 projects = User.current.projects.all
206 projects = User.current.projects.all
190 if projects.any?
207 if projects.any?
191 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
208 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
192 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
209 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
193 '<option value="" disabled="disabled">---</option>'
210 '<option value="" disabled="disabled">---</option>'
194 s << project_tree_options_for_select(projects, :selected => @project) do |p|
211 s << project_tree_options_for_select(projects, :selected => @project) do |p|
195 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
212 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
196 end
213 end
197 s << '</select>'
214 s << '</select>'
198 s
215 s
199 end
216 end
200 end
217 end
201
218
202 def project_tree_options_for_select(projects, options = {})
219 def project_tree_options_for_select(projects, options = {})
203 s = ''
220 s = ''
204 project_tree(projects) do |project, level|
221 project_tree(projects) do |project, level|
205 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
222 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
206 tag_options = {:value => project.id}
223 tag_options = {:value => project.id}
207 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
224 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
208 tag_options[:selected] = 'selected'
225 tag_options[:selected] = 'selected'
209 else
226 else
210 tag_options[:selected] = nil
227 tag_options[:selected] = nil
211 end
228 end
212 tag_options.merge!(yield(project)) if block_given?
229 tag_options.merge!(yield(project)) if block_given?
213 s << content_tag('option', name_prefix + h(project), tag_options)
230 s << content_tag('option', name_prefix + h(project), tag_options)
214 end
231 end
215 s
232 s
216 end
233 end
217
234
218 # Yields the given block for each project with its level in the tree
235 # Yields the given block for each project with its level in the tree
219 def project_tree(projects, &block)
236 def project_tree(projects, &block)
220 ancestors = []
237 ancestors = []
221 projects.sort_by(&:lft).each do |project|
238 projects.sort_by(&:lft).each do |project|
222 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
239 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
223 ancestors.pop
240 ancestors.pop
224 end
241 end
225 yield project, ancestors.size
242 yield project, ancestors.size
226 ancestors << project
243 ancestors << project
227 end
244 end
228 end
245 end
229
246
230 def project_nested_ul(projects, &block)
247 def project_nested_ul(projects, &block)
231 s = ''
248 s = ''
232 if projects.any?
249 if projects.any?
233 ancestors = []
250 ancestors = []
234 projects.sort_by(&:lft).each do |project|
251 projects.sort_by(&:lft).each do |project|
235 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
252 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
236 s << "<ul>\n"
253 s << "<ul>\n"
237 else
254 else
238 ancestors.pop
255 ancestors.pop
239 s << "</li>"
256 s << "</li>"
240 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
257 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
241 ancestors.pop
258 ancestors.pop
242 s << "</ul></li>\n"
259 s << "</ul></li>\n"
243 end
260 end
244 end
261 end
245 s << "<li>"
262 s << "<li>"
246 s << yield(project).to_s
263 s << yield(project).to_s
247 ancestors << project
264 ancestors << project
248 end
265 end
249 s << ("</li></ul>\n" * ancestors.size)
266 s << ("</li></ul>\n" * ancestors.size)
250 end
267 end
251 s
268 s
252 end
269 end
253
270
254 def principals_check_box_tags(name, principals)
271 def principals_check_box_tags(name, principals)
255 s = ''
272 s = ''
256 principals.sort.each do |principal|
273 principals.sort.each do |principal|
257 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
274 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
258 end
275 end
259 s
276 s
260 end
277 end
261
278
262 # Truncates and returns the string as a single line
279 # Truncates and returns the string as a single line
263 def truncate_single_line(string, *args)
280 def truncate_single_line(string, *args)
264 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
281 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
265 end
282 end
266
283
267 # Truncates at line break after 250 characters or options[:length]
284 # Truncates at line break after 250 characters or options[:length]
268 def truncate_lines(string, options={})
285 def truncate_lines(string, options={})
269 length = options[:length] || 250
286 length = options[:length] || 250
270 if string.to_s =~ /\A(.{#{length}}.*?)$/m
287 if string.to_s =~ /\A(.{#{length}}.*?)$/m
271 "#{$1}..."
288 "#{$1}..."
272 else
289 else
273 string
290 string
274 end
291 end
275 end
292 end
276
293
277 def html_hours(text)
294 def html_hours(text)
278 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
295 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
279 end
296 end
280
297
281 def authoring(created, author, options={})
298 def authoring(created, author, options={})
282 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
299 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
283 end
300 end
284
301
285 def time_tag(time)
302 def time_tag(time)
286 text = distance_of_time_in_words(Time.now, time)
303 text = distance_of_time_in_words(Time.now, time)
287 if @project
304 if @project
288 link_to(text, {:controller => 'projects', :action => 'activity', :id => @project, :from => time.to_date}, :title => format_time(time))
305 link_to(text, {:controller => 'projects', :action => 'activity', :id => @project, :from => time.to_date}, :title => format_time(time))
289 else
306 else
290 content_tag('acronym', text, :title => format_time(time))
307 content_tag('acronym', text, :title => format_time(time))
291 end
308 end
292 end
309 end
293
310
294 def syntax_highlight(name, content)
311 def syntax_highlight(name, content)
295 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
312 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
296 end
313 end
297
314
298 def to_path_param(path)
315 def to_path_param(path)
299 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
316 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
300 end
317 end
301
318
302 def pagination_links_full(paginator, count=nil, options={})
319 def pagination_links_full(paginator, count=nil, options={})
303 page_param = options.delete(:page_param) || :page
320 page_param = options.delete(:page_param) || :page
304 per_page_links = options.delete(:per_page_links)
321 per_page_links = options.delete(:per_page_links)
305 url_param = params.dup
322 url_param = params.dup
306 # don't reuse query params if filters are present
323 # don't reuse query params if filters are present
307 url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
324 url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
308
325
309 html = ''
326 html = ''
310 if paginator.current.previous
327 if paginator.current.previous
311 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
328 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
312 end
329 end
313
330
314 html << (pagination_links_each(paginator, options) do |n|
331 html << (pagination_links_each(paginator, options) do |n|
315 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
332 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
316 end || '')
333 end || '')
317
334
318 if paginator.current.next
335 if paginator.current.next
319 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
336 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
320 end
337 end
321
338
322 unless count.nil?
339 unless count.nil?
323 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
340 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
324 if per_page_links != false && links = per_page_links(paginator.items_per_page)
341 if per_page_links != false && links = per_page_links(paginator.items_per_page)
325 html << " | #{links}"
342 html << " | #{links}"
326 end
343 end
327 end
344 end
328
345
329 html
346 html
330 end
347 end
331
348
332 def per_page_links(selected=nil)
349 def per_page_links(selected=nil)
333 url_param = params.dup
350 url_param = params.dup
334 url_param.clear if url_param.has_key?(:set_filter)
351 url_param.clear if url_param.has_key?(:set_filter)
335
352
336 links = Setting.per_page_options_array.collect do |n|
353 links = Setting.per_page_options_array.collect do |n|
337 n == selected ? n : link_to_remote(n, {:update => "content",
354 n == selected ? n : link_to_remote(n, {:update => "content",
338 :url => params.dup.merge(:per_page => n),
355 :url => params.dup.merge(:per_page => n),
339 :method => :get},
356 :method => :get},
340 {:href => url_for(url_param.merge(:per_page => n))})
357 {:href => url_for(url_param.merge(:per_page => n))})
341 end
358 end
342 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
359 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
343 end
360 end
344
361
345 def reorder_links(name, url)
362 def reorder_links(name, url)
346 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
363 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
347 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
364 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
348 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
365 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
349 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
366 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
350 end
367 end
351
368
352 def breadcrumb(*args)
369 def breadcrumb(*args)
353 elements = args.flatten
370 elements = args.flatten
354 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
371 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
355 end
372 end
356
373
357 def other_formats_links(&block)
374 def other_formats_links(&block)
358 concat('<p class="other-formats">' + l(:label_export_to))
375 concat('<p class="other-formats">' + l(:label_export_to))
359 yield Redmine::Views::OtherFormatsBuilder.new(self)
376 yield Redmine::Views::OtherFormatsBuilder.new(self)
360 concat('</p>')
377 concat('</p>')
361 end
378 end
362
379
363 def page_header_title
380 def page_header_title
364 if @project.nil? || @project.new_record?
381 if @project.nil? || @project.new_record?
365 h(Setting.app_title)
382 h(Setting.app_title)
366 else
383 else
367 b = []
384 b = []
368 ancestors = (@project.root? ? [] : @project.ancestors.visible)
385 ancestors = (@project.root? ? [] : @project.ancestors.visible)
369 if ancestors.any?
386 if ancestors.any?
370 root = ancestors.shift
387 root = ancestors.shift
371 b << link_to(h(root), {:controller => 'projects', :action => 'show', :id => root, :jump => current_menu_item}, :class => 'root')
388 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
372 if ancestors.size > 2
389 if ancestors.size > 2
373 b << '&#8230;'
390 b << '&#8230;'
374 ancestors = ancestors[-2, 2]
391 ancestors = ancestors[-2, 2]
375 end
392 end
376 b += ancestors.collect {|p| link_to(h(p), {:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item}, :class => 'ancestor') }
393 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
377 end
394 end
378 b << h(@project)
395 b << h(@project)
379 b.join(' &#187; ')
396 b.join(' &#187; ')
380 end
397 end
381 end
398 end
382
399
383 def html_title(*args)
400 def html_title(*args)
384 if args.empty?
401 if args.empty?
385 title = []
402 title = []
386 title << @project.name if @project
403 title << @project.name if @project
387 title += @html_title if @html_title
404 title += @html_title if @html_title
388 title << Setting.app_title
405 title << Setting.app_title
389 title.select {|t| !t.blank? }.join(' - ')
406 title.select {|t| !t.blank? }.join(' - ')
390 else
407 else
391 @html_title ||= []
408 @html_title ||= []
392 @html_title += args
409 @html_title += args
393 end
410 end
394 end
411 end
395
412
396 # Returns the theme, controller name, and action as css classes for the
413 # Returns the theme, controller name, and action as css classes for the
397 # HTML body.
414 # HTML body.
398 def body_css_classes
415 def body_css_classes
399 css = []
416 css = []
400 if theme = Redmine::Themes.theme(Setting.ui_theme)
417 if theme = Redmine::Themes.theme(Setting.ui_theme)
401 css << 'theme-' + theme.name
418 css << 'theme-' + theme.name
402 end
419 end
403
420
404 css << 'controller-' + params[:controller]
421 css << 'controller-' + params[:controller]
405 css << 'action-' + params[:action]
422 css << 'action-' + params[:action]
406 css.join(' ')
423 css.join(' ')
407 end
424 end
408
425
409 def accesskey(s)
426 def accesskey(s)
410 Redmine::AccessKeys.key_for s
427 Redmine::AccessKeys.key_for s
411 end
428 end
412
429
413 # Formats text according to system settings.
430 # Formats text according to system settings.
414 # 2 ways to call this method:
431 # 2 ways to call this method:
415 # * with a String: textilizable(text, options)
432 # * with a String: textilizable(text, options)
416 # * with an object and one of its attribute: textilizable(issue, :description, options)
433 # * with an object and one of its attribute: textilizable(issue, :description, options)
417 def textilizable(*args)
434 def textilizable(*args)
418 options = args.last.is_a?(Hash) ? args.pop : {}
435 options = args.last.is_a?(Hash) ? args.pop : {}
419 case args.size
436 case args.size
420 when 1
437 when 1
421 obj = options[:object]
438 obj = options[:object]
422 text = args.shift
439 text = args.shift
423 when 2
440 when 2
424 obj = args.shift
441 obj = args.shift
425 attr = args.shift
442 attr = args.shift
426 text = obj.send(attr).to_s
443 text = obj.send(attr).to_s
427 else
444 else
428 raise ArgumentError, 'invalid arguments to textilizable'
445 raise ArgumentError, 'invalid arguments to textilizable'
429 end
446 end
430 return '' if text.blank?
447 return '' if text.blank?
431 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
448 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
432 only_path = options.delete(:only_path) == false ? false : true
449 only_path = options.delete(:only_path) == false ? false : true
433
450
434 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
451 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
435
452
436 parse_non_pre_blocks(text) do |text|
453 parse_non_pre_blocks(text) do |text|
437 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
454 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
438 send method_name, text, project, obj, attr, only_path, options
455 send method_name, text, project, obj, attr, only_path, options
439 end
456 end
440 end
457 end
441 end
458 end
442
459
443 def parse_non_pre_blocks(text)
460 def parse_non_pre_blocks(text)
444 s = StringScanner.new(text)
461 s = StringScanner.new(text)
445 tags = []
462 tags = []
446 parsed = ''
463 parsed = ''
447 while !s.eos?
464 while !s.eos?
448 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
465 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
449 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
466 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
450 if tags.empty?
467 if tags.empty?
451 yield text
468 yield text
452 end
469 end
453 parsed << text
470 parsed << text
454 if tag
471 if tag
455 if closing
472 if closing
456 if tags.last == tag.downcase
473 if tags.last == tag.downcase
457 tags.pop
474 tags.pop
458 end
475 end
459 else
476 else
460 tags << tag.downcase
477 tags << tag.downcase
461 end
478 end
462 parsed << full_tag
479 parsed << full_tag
463 end
480 end
464 end
481 end
465 # Close any non closing tags
482 # Close any non closing tags
466 while tag = tags.pop
483 while tag = tags.pop
467 parsed << "</#{tag}>"
484 parsed << "</#{tag}>"
468 end
485 end
469 parsed
486 parsed
470 end
487 end
471
488
472 def parse_inline_attachments(text, project, obj, attr, only_path, options)
489 def parse_inline_attachments(text, project, obj, attr, only_path, options)
473 # when using an image link, try to use an attachment, if possible
490 # when using an image link, try to use an attachment, if possible
474 if options[:attachments] || (obj && obj.respond_to?(:attachments))
491 if options[:attachments] || (obj && obj.respond_to?(:attachments))
475 attachments = nil
492 attachments = nil
476 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
493 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
477 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
494 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
478 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
495 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
479 # search for the picture in attachments
496 # search for the picture in attachments
480 if found = attachments.detect { |att| att.filename.downcase == filename }
497 if found = attachments.detect { |att| att.filename.downcase == filename }
481 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
498 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
482 desc = found.description.to_s.gsub('"', '')
499 desc = found.description.to_s.gsub('"', '')
483 if !desc.blank? && alttext.blank?
500 if !desc.blank? && alttext.blank?
484 alt = " title=\"#{desc}\" alt=\"#{desc}\""
501 alt = " title=\"#{desc}\" alt=\"#{desc}\""
485 end
502 end
486 "src=\"#{image_url}\"#{alt}"
503 "src=\"#{image_url}\"#{alt}"
487 else
504 else
488 m
505 m
489 end
506 end
490 end
507 end
491 end
508 end
492 end
509 end
493
510
494 # Wiki links
511 # Wiki links
495 #
512 #
496 # Examples:
513 # Examples:
497 # [[mypage]]
514 # [[mypage]]
498 # [[mypage|mytext]]
515 # [[mypage|mytext]]
499 # wiki links can refer other project wikis, using project name or identifier:
516 # wiki links can refer other project wikis, using project name or identifier:
500 # [[project:]] -> wiki starting page
517 # [[project:]] -> wiki starting page
501 # [[project:|mytext]]
518 # [[project:|mytext]]
502 # [[project:mypage]]
519 # [[project:mypage]]
503 # [[project:mypage|mytext]]
520 # [[project:mypage|mytext]]
504 def parse_wiki_links(text, project, obj, attr, only_path, options)
521 def parse_wiki_links(text, project, obj, attr, only_path, options)
505 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
522 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
506 link_project = project
523 link_project = project
507 esc, all, page, title = $1, $2, $3, $5
524 esc, all, page, title = $1, $2, $3, $5
508 if esc.nil?
525 if esc.nil?
509 if page =~ /^([^\:]+)\:(.*)$/
526 if page =~ /^([^\:]+)\:(.*)$/
510 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
527 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
511 page = $2
528 page = $2
512 title ||= $1 if page.blank?
529 title ||= $1 if page.blank?
513 end
530 end
514
531
515 if link_project && link_project.wiki
532 if link_project && link_project.wiki
516 # extract anchor
533 # extract anchor
517 anchor = nil
534 anchor = nil
518 if page =~ /^(.+?)\#(.+)$/
535 if page =~ /^(.+?)\#(.+)$/
519 page, anchor = $1, $2
536 page, anchor = $1, $2
520 end
537 end
521 # check if page exists
538 # check if page exists
522 wiki_page = link_project.wiki.find_page(page)
539 wiki_page = link_project.wiki.find_page(page)
523 url = case options[:wiki_links]
540 url = case options[:wiki_links]
524 when :local; "#{title}.html"
541 when :local; "#{title}.html"
525 when :anchor; "##{title}" # used for single-file wiki export
542 when :anchor; "##{title}" # used for single-file wiki export
526 else
543 else
527 url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => link_project, :page => Wiki.titleize(page), :anchor => anchor)
544 url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => link_project, :page => Wiki.titleize(page), :anchor => anchor)
528 end
545 end
529 link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
546 link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
530 else
547 else
531 # project or wiki doesn't exist
548 # project or wiki doesn't exist
532 all
549 all
533 end
550 end
534 else
551 else
535 all
552 all
536 end
553 end
537 end
554 end
538 end
555 end
539
556
540 # Redmine links
557 # Redmine links
541 #
558 #
542 # Examples:
559 # Examples:
543 # Issues:
560 # Issues:
544 # #52 -> Link to issue #52
561 # #52 -> Link to issue #52
545 # Changesets:
562 # Changesets:
546 # r52 -> Link to revision 52
563 # r52 -> Link to revision 52
547 # commit:a85130f -> Link to scmid starting with a85130f
564 # commit:a85130f -> Link to scmid starting with a85130f
548 # Documents:
565 # Documents:
549 # document#17 -> Link to document with id 17
566 # document#17 -> Link to document with id 17
550 # document:Greetings -> Link to the document with title "Greetings"
567 # document:Greetings -> Link to the document with title "Greetings"
551 # document:"Some document" -> Link to the document with title "Some document"
568 # document:"Some document" -> Link to the document with title "Some document"
552 # Versions:
569 # Versions:
553 # version#3 -> Link to version with id 3
570 # version#3 -> Link to version with id 3
554 # version:1.0.0 -> Link to version named "1.0.0"
571 # version:1.0.0 -> Link to version named "1.0.0"
555 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
572 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
556 # Attachments:
573 # Attachments:
557 # attachment:file.zip -> Link to the attachment of the current object named file.zip
574 # attachment:file.zip -> Link to the attachment of the current object named file.zip
558 # Source files:
575 # Source files:
559 # source:some/file -> Link to the file located at /some/file in the project's repository
576 # source:some/file -> Link to the file located at /some/file in the project's repository
560 # source:some/file@52 -> Link to the file's revision 52
577 # source:some/file@52 -> Link to the file's revision 52
561 # source:some/file#L120 -> Link to line 120 of the file
578 # source:some/file#L120 -> Link to line 120 of the file
562 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
579 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
563 # export:some/file -> Force the download of the file
580 # export:some/file -> Force the download of the file
564 # Forum messages:
581 # Forum messages:
565 # message#1218 -> Link to message with id 1218
582 # message#1218 -> Link to message with id 1218
566 def parse_redmine_links(text, project, obj, attr, only_path, options)
583 def parse_redmine_links(text, project, obj, attr, only_path, options)
567 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
584 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
568 leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8
585 leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8
569 link = nil
586 link = nil
570 if esc.nil?
587 if esc.nil?
571 if prefix.nil? && sep == 'r'
588 if prefix.nil? && sep == 'r'
572 if project && (changeset = project.changesets.find_by_revision(identifier))
589 if project && (changeset = project.changesets.find_by_revision(identifier))
573 link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
590 link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
574 :class => 'changeset',
591 :class => 'changeset',
575 :title => truncate_single_line(changeset.comments, :length => 100))
592 :title => truncate_single_line(changeset.comments, :length => 100))
576 end
593 end
577 elsif sep == '#'
594 elsif sep == '#'
578 oid = identifier.to_i
595 oid = identifier.to_i
579 case prefix
596 case prefix
580 when nil
597 when nil
581 if issue = Issue.visible.find_by_id(oid, :include => :status)
598 if issue = Issue.visible.find_by_id(oid, :include => :status)
582 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
599 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
583 :class => issue.css_classes,
600 :class => issue.css_classes,
584 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
601 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
585 end
602 end
586 when 'document'
603 when 'document'
587 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
604 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
588 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
605 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
589 :class => 'document'
606 :class => 'document'
590 end
607 end
591 when 'version'
608 when 'version'
592 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
609 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
593 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
610 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
594 :class => 'version'
611 :class => 'version'
595 end
612 end
596 when 'message'
613 when 'message'
597 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
614 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
598 link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
615 link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
599 :controller => 'messages',
616 :controller => 'messages',
600 :action => 'show',
617 :action => 'show',
601 :board_id => message.board,
618 :board_id => message.board,
602 :id => message.root,
619 :id => message.root,
603 :anchor => (message.parent ? "message-#{message.id}" : nil)},
620 :anchor => (message.parent ? "message-#{message.id}" : nil)},
604 :class => 'message'
621 :class => 'message'
605 end
622 end
606 when 'project'
623 when 'project'
607 if p = Project.visible.find_by_id(oid)
624 if p = Project.visible.find_by_id(oid)
608 link = link_to h(p.name), {:only_path => only_path, :controller => 'projects', :action => 'show', :id => p},
625 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
609 :class => 'project'
610 end
626 end
611 end
627 end
612 elsif sep == ':'
628 elsif sep == ':'
613 # removes the double quotes if any
629 # removes the double quotes if any
614 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
630 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
615 case prefix
631 case prefix
616 when 'document'
632 when 'document'
617 if project && document = project.documents.find_by_title(name)
633 if project && document = project.documents.find_by_title(name)
618 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
634 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
619 :class => 'document'
635 :class => 'document'
620 end
636 end
621 when 'version'
637 when 'version'
622 if project && version = project.versions.find_by_name(name)
638 if project && version = project.versions.find_by_name(name)
623 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
639 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
624 :class => 'version'
640 :class => 'version'
625 end
641 end
626 when 'commit'
642 when 'commit'
627 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
643 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
628 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
644 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
629 :class => 'changeset',
645 :class => 'changeset',
630 :title => truncate_single_line(changeset.comments, :length => 100)
646 :title => truncate_single_line(changeset.comments, :length => 100)
631 end
647 end
632 when 'source', 'export'
648 when 'source', 'export'
633 if project && project.repository
649 if project && project.repository
634 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
650 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
635 path, rev, anchor = $1, $3, $5
651 path, rev, anchor = $1, $3, $5
636 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
652 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
637 :path => to_path_param(path),
653 :path => to_path_param(path),
638 :rev => rev,
654 :rev => rev,
639 :anchor => anchor,
655 :anchor => anchor,
640 :format => (prefix == 'export' ? 'raw' : nil)},
656 :format => (prefix == 'export' ? 'raw' : nil)},
641 :class => (prefix == 'export' ? 'source download' : 'source')
657 :class => (prefix == 'export' ? 'source download' : 'source')
642 end
658 end
643 when 'attachment'
659 when 'attachment'
644 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
660 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
645 if attachments && attachment = attachments.detect {|a| a.filename == name }
661 if attachments && attachment = attachments.detect {|a| a.filename == name }
646 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
662 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
647 :class => 'attachment'
663 :class => 'attachment'
648 end
664 end
649 when 'project'
665 when 'project'
650 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
666 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
651 link = link_to h(p.name), {:only_path => only_path, :controller => 'projects', :action => 'show', :id => p},
667 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
652 :class => 'project'
653 end
668 end
654 end
669 end
655 end
670 end
656 end
671 end
657 leading + (link || "#{prefix}#{sep}#{identifier}")
672 leading + (link || "#{prefix}#{sep}#{identifier}")
658 end
673 end
659 end
674 end
660
675
661 # Same as Rails' simple_format helper without using paragraphs
676 # Same as Rails' simple_format helper without using paragraphs
662 def simple_format_without_paragraph(text)
677 def simple_format_without_paragraph(text)
663 text.to_s.
678 text.to_s.
664 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
679 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
665 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
680 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
666 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
681 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
667 end
682 end
668
683
669 def lang_options_for_select(blank=true)
684 def lang_options_for_select(blank=true)
670 (blank ? [["(auto)", ""]] : []) +
685 (blank ? [["(auto)", ""]] : []) +
671 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
686 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
672 end
687 end
673
688
674 def label_tag_for(name, option_tags = nil, options = {})
689 def label_tag_for(name, option_tags = nil, options = {})
675 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
690 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
676 content_tag("label", label_text)
691 content_tag("label", label_text)
677 end
692 end
678
693
679 def labelled_tabular_form_for(name, object, options, &proc)
694 def labelled_tabular_form_for(name, object, options, &proc)
680 options[:html] ||= {}
695 options[:html] ||= {}
681 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
696 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
682 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
697 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
683 end
698 end
684
699
685 def back_url_hidden_field_tag
700 def back_url_hidden_field_tag
686 back_url = params[:back_url] || request.env['HTTP_REFERER']
701 back_url = params[:back_url] || request.env['HTTP_REFERER']
687 back_url = CGI.unescape(back_url.to_s)
702 back_url = CGI.unescape(back_url.to_s)
688 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
703 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
689 end
704 end
690
705
691 def check_all_links(form_name)
706 def check_all_links(form_name)
692 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
707 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
693 " | " +
708 " | " +
694 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
709 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
695 end
710 end
696
711
697 def progress_bar(pcts, options={})
712 def progress_bar(pcts, options={})
698 pcts = [pcts, pcts] unless pcts.is_a?(Array)
713 pcts = [pcts, pcts] unless pcts.is_a?(Array)
699 pcts = pcts.collect(&:round)
714 pcts = pcts.collect(&:round)
700 pcts[1] = pcts[1] - pcts[0]
715 pcts[1] = pcts[1] - pcts[0]
701 pcts << (100 - pcts[1] - pcts[0])
716 pcts << (100 - pcts[1] - pcts[0])
702 width = options[:width] || '100px;'
717 width = options[:width] || '100px;'
703 legend = options[:legend] || ''
718 legend = options[:legend] || ''
704 content_tag('table',
719 content_tag('table',
705 content_tag('tr',
720 content_tag('tr',
706 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
721 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
707 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
722 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
708 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
723 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
709 ), :class => 'progress', :style => "width: #{width};") +
724 ), :class => 'progress', :style => "width: #{width};") +
710 content_tag('p', legend, :class => 'pourcent')
725 content_tag('p', legend, :class => 'pourcent')
711 end
726 end
712
727
713 def checked_image(checked=true)
728 def checked_image(checked=true)
714 if checked
729 if checked
715 image_tag 'toggle_check.png'
730 image_tag 'toggle_check.png'
716 end
731 end
717 end
732 end
718
733
719 def context_menu(url)
734 def context_menu(url)
720 unless @context_menu_included
735 unless @context_menu_included
721 content_for :header_tags do
736 content_for :header_tags do
722 javascript_include_tag('context_menu') +
737 javascript_include_tag('context_menu') +
723 stylesheet_link_tag('context_menu')
738 stylesheet_link_tag('context_menu')
724 end
739 end
725 @context_menu_included = true
740 @context_menu_included = true
726 end
741 end
727 javascript_tag "new ContextMenu('#{ url_for(url) }')"
742 javascript_tag "new ContextMenu('#{ url_for(url) }')"
728 end
743 end
729
744
730 def context_menu_link(name, url, options={})
745 def context_menu_link(name, url, options={})
731 options[:class] ||= ''
746 options[:class] ||= ''
732 if options.delete(:selected)
747 if options.delete(:selected)
733 options[:class] << ' icon-checked disabled'
748 options[:class] << ' icon-checked disabled'
734 options[:disabled] = true
749 options[:disabled] = true
735 end
750 end
736 if options.delete(:disabled)
751 if options.delete(:disabled)
737 options.delete(:method)
752 options.delete(:method)
738 options.delete(:confirm)
753 options.delete(:confirm)
739 options.delete(:onclick)
754 options.delete(:onclick)
740 options[:class] << ' disabled'
755 options[:class] << ' disabled'
741 url = '#'
756 url = '#'
742 end
757 end
743 link_to name, url, options
758 link_to name, url, options
744 end
759 end
745
760
746 def calendar_for(field_id)
761 def calendar_for(field_id)
747 include_calendar_headers_tags
762 include_calendar_headers_tags
748 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
763 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
749 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
764 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
750 end
765 end
751
766
752 def include_calendar_headers_tags
767 def include_calendar_headers_tags
753 unless @calendar_headers_tags_included
768 unless @calendar_headers_tags_included
754 @calendar_headers_tags_included = true
769 @calendar_headers_tags_included = true
755 content_for :header_tags do
770 content_for :header_tags do
756 start_of_week = case Setting.start_of_week.to_i
771 start_of_week = case Setting.start_of_week.to_i
757 when 1
772 when 1
758 'Calendar._FD = 1;' # Monday
773 'Calendar._FD = 1;' # Monday
759 when 7
774 when 7
760 'Calendar._FD = 0;' # Sunday
775 'Calendar._FD = 0;' # Sunday
761 else
776 else
762 '' # use language
777 '' # use language
763 end
778 end
764
779
765 javascript_include_tag('calendar/calendar') +
780 javascript_include_tag('calendar/calendar') +
766 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
781 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
767 javascript_tag(start_of_week) +
782 javascript_tag(start_of_week) +
768 javascript_include_tag('calendar/calendar-setup') +
783 javascript_include_tag('calendar/calendar-setup') +
769 stylesheet_link_tag('calendar')
784 stylesheet_link_tag('calendar')
770 end
785 end
771 end
786 end
772 end
787 end
773
788
774 def content_for(name, content = nil, &block)
789 def content_for(name, content = nil, &block)
775 @has_content ||= {}
790 @has_content ||= {}
776 @has_content[name] = true
791 @has_content[name] = true
777 super(name, content, &block)
792 super(name, content, &block)
778 end
793 end
779
794
780 def has_content?(name)
795 def has_content?(name)
781 (@has_content && @has_content[name]) || false
796 (@has_content && @has_content[name]) || false
782 end
797 end
783
798
784 # Returns the avatar image tag for the given +user+ if avatars are enabled
799 # Returns the avatar image tag for the given +user+ if avatars are enabled
785 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
800 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
786 def avatar(user, options = { })
801 def avatar(user, options = { })
787 if Setting.gravatar_enabled?
802 if Setting.gravatar_enabled?
788 options.merge!({:ssl => Setting.protocol == 'https', :default => Setting.gravatar_default})
803 options.merge!({:ssl => Setting.protocol == 'https', :default => Setting.gravatar_default})
789 email = nil
804 email = nil
790 if user.respond_to?(:mail)
805 if user.respond_to?(:mail)
791 email = user.mail
806 email = user.mail
792 elsif user.to_s =~ %r{<(.+?)>}
807 elsif user.to_s =~ %r{<(.+?)>}
793 email = $1
808 email = $1
794 end
809 end
795 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
810 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
796 end
811 end
797 end
812 end
798
813
799 def favicon
814 def favicon
800 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
815 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
801 end
816 end
802
817
803 private
818 private
804
819
805 def wiki_helper
820 def wiki_helper
806 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
821 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
807 extend helper
822 extend helper
808 return self
823 return self
809 end
824 end
810
825
811 def link_to_remote_content_update(text, url_params)
826 def link_to_remote_content_update(text, url_params)
812 link_to_remote(text,
827 link_to_remote(text,
813 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
828 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
814 {:href => url_for(:params => url_params)}
829 {:href => url_for(:params => url_params)}
815 )
830 )
816 end
831 end
817
832
818 end
833 end
@@ -1,108 +1,108
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 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 module ProjectsHelper
18 module ProjectsHelper
19 def link_to_version(version, options = {})
19 def link_to_version(version, options = {})
20 return '' unless version && version.is_a?(Version)
20 return '' unless version && version.is_a?(Version)
21 link_to_if version.visible?, format_version_name(version), { :controller => 'versions', :action => 'show', :id => version }, options
21 link_to_if version.visible?, format_version_name(version), { :controller => 'versions', :action => 'show', :id => version }, options
22 end
22 end
23
23
24 def project_settings_tabs
24 def project_settings_tabs
25 tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural},
25 tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural},
26 {:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural},
26 {:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural},
27 {:name => 'members', :action => :manage_members, :partial => 'projects/settings/members', :label => :label_member_plural},
27 {:name => 'members', :action => :manage_members, :partial => 'projects/settings/members', :label => :label_member_plural},
28 {:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural},
28 {:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural},
29 {:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural},
29 {:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural},
30 {:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki},
30 {:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki},
31 {:name => 'repository', :action => :manage_repository, :partial => 'projects/settings/repository', :label => :label_repository},
31 {:name => 'repository', :action => :manage_repository, :partial => 'projects/settings/repository', :label => :label_repository},
32 {:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural},
32 {:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural},
33 {:name => 'activities', :action => :manage_project_activities, :partial => 'projects/settings/activities', :label => :enumeration_activities}
33 {:name => 'activities', :action => :manage_project_activities, :partial => 'projects/settings/activities', :label => :enumeration_activities}
34 ]
34 ]
35 tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)}
35 tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)}
36 end
36 end
37
37
38 def parent_project_select_tag(project)
38 def parent_project_select_tag(project)
39 selected = project.parent
39 selected = project.parent
40 # retrieve the requested parent project
40 # retrieve the requested parent project
41 parent_id = (params[:project] && params[:project][:parent_id]) || params[:parent_id]
41 parent_id = (params[:project] && params[:project][:parent_id]) || params[:parent_id]
42 if parent_id
42 if parent_id
43 selected = (parent_id.blank? ? nil : Project.find(parent_id))
43 selected = (parent_id.blank? ? nil : Project.find(parent_id))
44 end
44 end
45
45
46 options = ''
46 options = ''
47 options << "<option value=''></option>" if project.allowed_parents.include?(nil)
47 options << "<option value=''></option>" if project.allowed_parents.include?(nil)
48 options << project_tree_options_for_select(project.allowed_parents.compact, :selected => selected)
48 options << project_tree_options_for_select(project.allowed_parents.compact, :selected => selected)
49 content_tag('select', options, :name => 'project[parent_id]', :id => 'project_parent_id')
49 content_tag('select', options, :name => 'project[parent_id]', :id => 'project_parent_id')
50 end
50 end
51
51
52 # Renders a tree of projects as a nested set of unordered lists
52 # Renders a tree of projects as a nested set of unordered lists
53 # The given collection may be a subset of the whole project tree
53 # The given collection may be a subset of the whole project tree
54 # (eg. some intermediate nodes are private and can not be seen)
54 # (eg. some intermediate nodes are private and can not be seen)
55 def render_project_hierarchy(projects)
55 def render_project_hierarchy(projects)
56 s = ''
56 s = ''
57 if projects.any?
57 if projects.any?
58 ancestors = []
58 ancestors = []
59 original_project = @project
59 original_project = @project
60 projects.each do |project|
60 projects.each do |project|
61 # set the project environment to please macros.
61 # set the project environment to please macros.
62 @project = project
62 @project = project
63 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
63 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
64 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
64 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
65 else
65 else
66 ancestors.pop
66 ancestors.pop
67 s << "</li>"
67 s << "</li>"
68 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
68 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
69 ancestors.pop
69 ancestors.pop
70 s << "</ul></li>\n"
70 s << "</ul></li>\n"
71 end
71 end
72 end
72 end
73 classes = (ancestors.empty? ? 'root' : 'child')
73 classes = (ancestors.empty? ? 'root' : 'child')
74 s << "<li class='#{classes}'><div class='#{classes}'>" +
74 s << "<li class='#{classes}'><div class='#{classes}'>" +
75 link_to(h(project), {:controller => 'projects', :action => 'show', :id => project}, :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}")
75 link_to_project(project, {}, :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}")
76 s << "<div class='wiki description'>#{textilizable(project.short_description, :project => project)}</div>" unless project.description.blank?
76 s << "<div class='wiki description'>#{textilizable(project.short_description, :project => project)}</div>" unless project.description.blank?
77 s << "</div>\n"
77 s << "</div>\n"
78 ancestors << project
78 ancestors << project
79 end
79 end
80 s << ("</li></ul>\n" * ancestors.size)
80 s << ("</li></ul>\n" * ancestors.size)
81 @project = original_project
81 @project = original_project
82 end
82 end
83 s
83 s
84 end
84 end
85
85
86 # Returns a set of options for a select field, grouped by project.
86 # Returns a set of options for a select field, grouped by project.
87 def version_options_for_select(versions, selected=nil)
87 def version_options_for_select(versions, selected=nil)
88 grouped = Hash.new {|h,k| h[k] = []}
88 grouped = Hash.new {|h,k| h[k] = []}
89 versions.each do |version|
89 versions.each do |version|
90 grouped[version.project.name] << [version.name, version.id]
90 grouped[version.project.name] << [version.name, version.id]
91 end
91 end
92 # Add in the selected
92 # Add in the selected
93 if selected && !versions.include?(selected)
93 if selected && !versions.include?(selected)
94 grouped[selected.project.name] << [selected.name, selected.id]
94 grouped[selected.project.name] << [selected.name, selected.id]
95 end
95 end
96
96
97 if grouped.keys.size > 1
97 if grouped.keys.size > 1
98 grouped_options_for_select(grouped, selected && selected.id)
98 grouped_options_for_select(grouped, selected && selected.id)
99 else
99 else
100 options_for_select((grouped.values.first || []), selected && selected.id)
100 options_for_select((grouped.values.first || []), selected && selected.id)
101 end
101 end
102 end
102 end
103
103
104 def format_version_sharing(sharing)
104 def format_version_sharing(sharing)
105 sharing = 'none' unless Version::VERSION_SHARINGS.include?(sharing)
105 sharing = 'none' unless Version::VERSION_SHARINGS.include?(sharing)
106 l("label_version_sharing_#{sharing}")
106 l("label_version_sharing_#{sharing}")
107 end
107 end
108 end
108 end
@@ -1,100 +1,100
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 module QueriesHelper
18 module QueriesHelper
19
19
20 def operators_for_select(filter_type)
20 def operators_for_select(filter_type)
21 Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]}
21 Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]}
22 end
22 end
23
23
24 def column_header(column)
24 def column_header(column)
25 column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
25 column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
26 :default_order => column.default_order) :
26 :default_order => column.default_order) :
27 content_tag('th', column.caption)
27 content_tag('th', column.caption)
28 end
28 end
29
29
30 def column_content(column, issue)
30 def column_content(column, issue)
31 value = column.value(issue)
31 value = column.value(issue)
32
32
33 case value.class.name
33 case value.class.name
34 when 'String'
34 when 'String'
35 if column.name == :subject
35 if column.name == :subject
36 link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
36 link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
37 else
37 else
38 h(value)
38 h(value)
39 end
39 end
40 when 'Time'
40 when 'Time'
41 format_time(value)
41 format_time(value)
42 when 'Date'
42 when 'Date'
43 format_date(value)
43 format_date(value)
44 when 'Fixnum', 'Float'
44 when 'Fixnum', 'Float'
45 if column.name == :done_ratio
45 if column.name == :done_ratio
46 progress_bar(value, :width => '80px')
46 progress_bar(value, :width => '80px')
47 else
47 else
48 value.to_s
48 value.to_s
49 end
49 end
50 when 'User'
50 when 'User'
51 link_to_user value
51 link_to_user value
52 when 'Project'
52 when 'Project'
53 link_to(h(value), :controller => 'projects', :action => 'show', :id => value)
53 link_to_project value
54 when 'Version'
54 when 'Version'
55 link_to(h(value), :controller => 'versions', :action => 'show', :id => value)
55 link_to(h(value), :controller => 'versions', :action => 'show', :id => value)
56 when 'TrueClass'
56 when 'TrueClass'
57 l(:general_text_Yes)
57 l(:general_text_Yes)
58 when 'FalseClass'
58 when 'FalseClass'
59 l(:general_text_No)
59 l(:general_text_No)
60 when 'Issue'
60 when 'Issue'
61 link_to_issue(value, :subject => false)
61 link_to_issue(value, :subject => false)
62 else
62 else
63 h(value)
63 h(value)
64 end
64 end
65 end
65 end
66
66
67 # Retrieve query from session or build a new query
67 # Retrieve query from session or build a new query
68 def retrieve_query
68 def retrieve_query
69 if !params[:query_id].blank?
69 if !params[:query_id].blank?
70 cond = "project_id IS NULL"
70 cond = "project_id IS NULL"
71 cond << " OR project_id = #{@project.id}" if @project
71 cond << " OR project_id = #{@project.id}" if @project
72 @query = Query.find(params[:query_id], :conditions => cond)
72 @query = Query.find(params[:query_id], :conditions => cond)
73 @query.project = @project
73 @query.project = @project
74 session[:query] = {:id => @query.id, :project_id => @query.project_id}
74 session[:query] = {:id => @query.id, :project_id => @query.project_id}
75 sort_clear
75 sort_clear
76 else
76 else
77 if api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
77 if api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
78 # Give it a name, required to be valid
78 # Give it a name, required to be valid
79 @query = Query.new(:name => "_")
79 @query = Query.new(:name => "_")
80 @query.project = @project
80 @query.project = @project
81 if params[:fields] and params[:fields].is_a? Array
81 if params[:fields] and params[:fields].is_a? Array
82 params[:fields].each do |field|
82 params[:fields].each do |field|
83 @query.add_filter(field, params[:operators][field], params[:values][field])
83 @query.add_filter(field, params[:operators][field], params[:values][field])
84 end
84 end
85 else
85 else
86 @query.available_filters.keys.each do |field|
86 @query.available_filters.keys.each do |field|
87 @query.add_short_filter(field, params[field]) if params[field]
87 @query.add_short_filter(field, params[field]) if params[field]
88 end
88 end
89 end
89 end
90 @query.group_by = params[:group_by]
90 @query.group_by = params[:group_by]
91 @query.column_names = params[:query] && params[:query][:column_names]
91 @query.column_names = params[:query] && params[:query][:column_names]
92 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
92 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
93 else
93 else
94 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
94 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
95 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
95 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
96 @query.project = @project
96 @query.project = @project
97 end
97 end
98 end
98 end
99 end
99 end
100 end
100 end
@@ -1,46 +1,46
1 <div class="contextual">
1 <div class="contextual">
2 <%= link_to l(:label_project_new), {:controller => 'projects', :action => 'add'}, :class => 'icon icon-add' %>
2 <%= link_to l(:label_project_new), {:controller => 'projects', :action => 'add'}, :class => 'icon icon-add' %>
3 </div>
3 </div>
4
4
5 <h2><%=l(:label_project_plural)%></h2>
5 <h2><%=l(:label_project_plural)%></h2>
6
6
7 <% form_tag({}, :method => :get) do %>
7 <% form_tag({}, :method => :get) do %>
8 <fieldset><legend><%= l(:label_filter_plural) %></legend>
8 <fieldset><legend><%= l(:label_filter_plural) %></legend>
9 <label><%= l(:field_status) %> :</label>
9 <label><%= l(:field_status) %> :</label>
10 <%= select_tag 'status', project_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %>
10 <%= select_tag 'status', project_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %>
11 <label><%= l(:label_project) %>:</label>
11 <label><%= l(:label_project) %>:</label>
12 <%= text_field_tag 'name', params[:name], :size => 30 %>
12 <%= text_field_tag 'name', params[:name], :size => 30 %>
13 <%= submit_tag l(:button_apply), :class => "small", :name => nil %>
13 <%= submit_tag l(:button_apply), :class => "small", :name => nil %>
14 </fieldset>
14 </fieldset>
15 <% end %>
15 <% end %>
16 &nbsp;
16 &nbsp;
17
17
18 <div class="autoscroll">
18 <div class="autoscroll">
19 <table class="list">
19 <table class="list">
20 <thead><tr>
20 <thead><tr>
21 <th><%=l(:label_project)%></th>
21 <th><%=l(:label_project)%></th>
22 <th><%=l(:field_description)%></th>
22 <th><%=l(:field_description)%></th>
23 <th><%=l(:field_is_public)%></th>
23 <th><%=l(:field_is_public)%></th>
24 <th><%=l(:field_created_on)%></th>
24 <th><%=l(:field_created_on)%></th>
25 <th></th>
25 <th></th>
26 </tr></thead>
26 </tr></thead>
27 <tbody>
27 <tbody>
28 <% project_tree(@projects) do |project, level| %>
28 <% project_tree(@projects) do |project, level| %>
29 <tr class="<%= cycle("odd", "even") %> <%= css_project_classes(project) %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>">
29 <tr class="<%= cycle("odd", "even") %> <%= css_project_classes(project) %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>">
30 <td class="name"><%= project.active? ? link_to(h(project.name), :controller => 'projects', :action => 'settings', :id => project) : h(project.name) %></td>
30 <td class="name"><%= link_to_project(project, :action => 'settings') %></td>
31 <td><%= textilizable project.short_description, :project => project %></td>
31 <td><%= textilizable project.short_description, :project => project %></td>
32 <td align="center"><%= checked_image project.is_public? %></td>
32 <td align="center"><%= checked_image project.is_public? %></td>
33 <td align="center"><%= format_date(project.created_on) %></td>
33 <td align="center"><%= format_date(project.created_on) %></td>
34 <td class="buttons">
34 <td class="buttons">
35 <%= link_to(l(:button_archive), { :controller => 'projects', :action => 'archive', :id => project, :status => params[:status] }, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-lock') if project.active? %>
35 <%= link_to(l(:button_archive), { :controller => 'projects', :action => 'archive', :id => project, :status => params[:status] }, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-lock') if project.active? %>
36 <%= link_to(l(:button_unarchive), { :controller => 'projects', :action => 'unarchive', :id => project, :status => params[:status] }, :method => :post, :class => 'icon icon-unlock') if !project.active? && (project.parent.nil? || project.parent.active?) %>
36 <%= link_to(l(:button_unarchive), { :controller => 'projects', :action => 'unarchive', :id => project, :status => params[:status] }, :method => :post, :class => 'icon icon-unlock') if !project.active? && (project.parent.nil? || project.parent.active?) %>
37 <%= link_to(l(:button_copy), { :controller => 'projects', :action => 'copy', :id => project }, :class => 'icon icon-copy') %>
37 <%= link_to(l(:button_copy), { :controller => 'projects', :action => 'copy', :id => project }, :class => 'icon icon-copy') %>
38 <%= link_to(l(:button_delete), { :controller => 'projects', :action => 'destroy', :id => project }, :class => 'icon icon-del') %>
38 <%= link_to(l(:button_delete), { :controller => 'projects', :action => 'destroy', :id => project }, :class => 'icon icon-del') %>
39 </td>
39 </td>
40 </tr>
40 </tr>
41 <% end %>
41 <% end %>
42 </tbody>
42 </tbody>
43 </table>
43 </table>
44 </div>
44 </div>
45
45
46 <% html_title(l(:label_project_plural)) -%>
46 <% html_title(l(:label_project_plural)) -%>
@@ -1,29 +1,29
1 <% if issues && issues.any? %>
1 <% if issues && issues.any? %>
2 <% form_tag({}) do %>
2 <% form_tag({}) do %>
3 <table class="list issues">
3 <table class="list issues">
4 <thead><tr>
4 <thead><tr>
5 <th>#</th>
5 <th>#</th>
6 <th><%=l(:field_project)%></th>
6 <th><%=l(:field_project)%></th>
7 <th><%=l(:field_tracker)%></th>
7 <th><%=l(:field_tracker)%></th>
8 <th><%=l(:field_subject)%></th>
8 <th><%=l(:field_subject)%></th>
9 </tr></thead>
9 </tr></thead>
10 <tbody>
10 <tbody>
11 <% for issue in issues %>
11 <% for issue in issues %>
12 <tr id="issue-<%= issue.id %>" class="hascontextmenu <%= cycle('odd', 'even') %> <%= issue.css_classes %>">
12 <tr id="issue-<%= issue.id %>" class="hascontextmenu <%= cycle('odd', 'even') %> <%= issue.css_classes %>">
13 <td class="id">
13 <td class="id">
14 <%= check_box_tag("ids[]", issue.id, false, :style => 'display:none;') %>
14 <%= check_box_tag("ids[]", issue.id, false, :style => 'display:none;') %>
15 <%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %>
15 <%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %>
16 </td>
16 </td>
17 <td class="project"><%= link_to(h(issue.project), :controller => 'projects', :action => 'show', :id => issue.project) %></td>
17 <td class="project"><%= link_to_project(issue.project) %></td>
18 <td class="tracker"><%=h issue.tracker %></td>
18 <td class="tracker"><%=h issue.tracker %></td>
19 <td class="subject">
19 <td class="subject">
20 <%= link_to h(truncate(issue.subject, :length => 60)), :controller => 'issues', :action => 'show', :id => issue %> (<%=h issue.status %>)
20 <%= link_to h(truncate(issue.subject, :length => 60)), :controller => 'issues', :action => 'show', :id => issue %> (<%=h issue.status %>)
21 </td>
21 </td>
22 </tr>
22 </tr>
23 <% end %>
23 <% end %>
24 </tbody>
24 </tbody>
25 </table>
25 </table>
26 <% end %>
26 <% end %>
27 <% else %>
27 <% else %>
28 <p class="nodata"><%= l(:label_no_data) %></p>
28 <p class="nodata"><%= l(:label_no_data) %></p>
29 <% end %>
29 <% end %>
@@ -1,6 +1,6
1 <p><%= link_to(h(news.project.name), :controller => 'projects', :action => 'show', :id => news.project) + ': ' unless @project %>
1 <p><%= link_to_project(news.project) + ': ' unless @project %>
2 <%= link_to h(news.title), :controller => 'news', :action => 'show', :id => news %>
2 <%= link_to h(news.title), :controller => 'news', :action => 'show', :id => news %>
3 <%= "(#{l(:label_x_comments, :count => news.comments_count)})" if news.comments_count > 0 %>
3 <%= "(#{l(:label_x_comments, :count => news.comments_count)})" if news.comments_count > 0 %>
4 <br />
4 <br />
5 <% unless news.summary.blank? %><span class="summary"><%=h news.summary %></span><br /><% end %>
5 <% unless news.summary.blank? %><span class="summary"><%=h news.summary %></span><br /><% end %>
6 <span class="author"><%= authoring news.created_on, news.author %></span></p>
6 <span class="author"><%= authoring news.created_on, news.author %></span></p>
@@ -1,50 +1,50
1 <div class="contextual">
1 <div class="contextual">
2 <%= link_to_if_authorized(l(:label_news_new),
2 <%= link_to_if_authorized(l(:label_news_new),
3 {:controller => 'news', :action => 'new', :project_id => @project},
3 {:controller => 'news', :action => 'new', :project_id => @project},
4 :class => 'icon icon-add',
4 :class => 'icon icon-add',
5 :onclick => 'Element.show("add-news"); Form.Element.focus("news_title"); return false;') if @project %>
5 :onclick => 'Element.show("add-news"); Form.Element.focus("news_title"); return false;') if @project %>
6 </div>
6 </div>
7
7
8 <div id="add-news" style="display:none;">
8 <div id="add-news" style="display:none;">
9 <h2><%=l(:label_news_new)%></h2>
9 <h2><%=l(:label_news_new)%></h2>
10 <% labelled_tabular_form_for :news, @news, :url => { :controller => 'news', :action => 'new', :project_id => @project },
10 <% labelled_tabular_form_for :news, @news, :url => { :controller => 'news', :action => 'new', :project_id => @project },
11 :html => { :id => 'news-form' } do |f| %>
11 :html => { :id => 'news-form' } do |f| %>
12 <%= render :partial => 'news/form', :locals => { :f => f } %>
12 <%= render :partial => 'news/form', :locals => { :f => f } %>
13 <%= submit_tag l(:button_create) %>
13 <%= submit_tag l(:button_create) %>
14 <%= link_to_remote l(:label_preview),
14 <%= link_to_remote l(:label_preview),
15 { :url => { :controller => 'news', :action => 'preview', :project_id => @project },
15 { :url => { :controller => 'news', :action => 'preview', :project_id => @project },
16 :method => 'post',
16 :method => 'post',
17 :update => 'preview',
17 :update => 'preview',
18 :with => "Form.serialize('news-form')"
18 :with => "Form.serialize('news-form')"
19 }, :accesskey => accesskey(:preview) %> |
19 }, :accesskey => accesskey(:preview) %> |
20 <%= link_to l(:button_cancel), "#", :onclick => 'Element.hide("add-news")' %>
20 <%= link_to l(:button_cancel), "#", :onclick => 'Element.hide("add-news")' %>
21 <% end if @project %>
21 <% end if @project %>
22 <div id="preview" class="wiki"></div>
22 <div id="preview" class="wiki"></div>
23 </div>
23 </div>
24
24
25 <h2><%=l(:label_news_plural)%></h2>
25 <h2><%=l(:label_news_plural)%></h2>
26
26
27 <% if @newss.empty? %>
27 <% if @newss.empty? %>
28 <p class="nodata"><%= l(:label_no_data) %></p>
28 <p class="nodata"><%= l(:label_no_data) %></p>
29 <% else %>
29 <% else %>
30 <% @newss.each do |news| %>
30 <% @newss.each do |news| %>
31 <h3><%= link_to(h(news.project.name), :controller => 'projects', :action => 'show', :id => news.project) + ': ' unless news.project == @project %>
31 <h3><%= link_to_project(news.project) + ': ' unless news.project == @project %>
32 <%= link_to h(news.title), :controller => 'news', :action => 'show', :id => news %>
32 <%= link_to h(news.title), :controller => 'news', :action => 'show', :id => news %>
33 <%= "(#{l(:label_x_comments, :count => news.comments_count)})" if news.comments_count > 0 %></h3>
33 <%= "(#{l(:label_x_comments, :count => news.comments_count)})" if news.comments_count > 0 %></h3>
34 <p class="author"><%= authoring news.created_on, news.author %></p>
34 <p class="author"><%= authoring news.created_on, news.author %></p>
35 <div class="wiki">
35 <div class="wiki">
36 <%= textilizable(news.description) %>
36 <%= textilizable(news.description) %>
37 </div>
37 </div>
38 <% end %>
38 <% end %>
39 <% end %>
39 <% end %>
40 <p class="pagination"><%= pagination_links_full @news_pages %></p>
40 <p class="pagination"><%= pagination_links_full @news_pages %></p>
41
41
42 <% other_formats_links do |f| %>
42 <% other_formats_links do |f| %>
43 <%= f.link_to 'Atom', :url => {:project_id => @project, :key => User.current.rss_key} %>
43 <%= f.link_to 'Atom', :url => {:project_id => @project, :key => User.current.rss_key} %>
44 <% end %>
44 <% end %>
45
45
46 <% content_for :header_tags do %>
46 <% content_for :header_tags do %>
47 <%= auto_discovery_link_tag(:atom, params.merge({:format => 'atom', :page => nil, :key => User.current.rss_key})) %>
47 <%= auto_discovery_link_tag(:atom, params.merge({:format => 'atom', :page => nil, :key => User.current.rss_key})) %>
48 <% end %>
48 <% end %>
49
49
50 <% html_title(l(:label_news_plural)) -%>
50 <% html_title(l(:label_news_plural)) -%>
@@ -1,62 +1,62
1 <% roles = Role.find_all_givable %>
1 <% roles = Role.find_all_givable %>
2 <% projects = Project.active.find(:all, :order => 'lft') %>
2 <% projects = Project.active.find(:all, :order => 'lft') %>
3
3
4 <div class="splitcontentleft">
4 <div class="splitcontentleft">
5 <% if @user.memberships.any? %>
5 <% if @user.memberships.any? %>
6 <table class="list memberships">
6 <table class="list memberships">
7 <thead><tr>
7 <thead><tr>
8 <th><%= l(:label_project) %></th>
8 <th><%= l(:label_project) %></th>
9 <th><%= l(:label_role_plural) %></th>
9 <th><%= l(:label_role_plural) %></th>
10 <th style="width:15%"></th>
10 <th style="width:15%"></th>
11 <%= call_hook(:view_users_memberships_table_header, :user => @user )%>
11 <%= call_hook(:view_users_memberships_table_header, :user => @user )%>
12 </tr></thead>
12 </tr></thead>
13 <tbody>
13 <tbody>
14 <% @user.memberships.each do |membership| %>
14 <% @user.memberships.each do |membership| %>
15 <% next if membership.new_record? %>
15 <% next if membership.new_record? %>
16 <tr id="member-<%= membership.id %>" class="<%= cycle 'odd', 'even' %> class">
16 <tr id="member-<%= membership.id %>" class="<%= cycle 'odd', 'even' %> class">
17 <td class="project">
17 <td class="project">
18 <%= link_to h(membership.project), {:controller => 'projects', :action => 'show', :id => membership.project} %>
18 <%= link_to_project membership.project %>
19 </td>
19 </td>
20 <td class="roles">
20 <td class="roles">
21 <span id="member-<%= membership.id %>-roles"><%=h membership.roles.sort.collect(&:to_s).join(', ') %></span>
21 <span id="member-<%= membership.id %>-roles"><%=h membership.roles.sort.collect(&:to_s).join(', ') %></span>
22 <% remote_form_for(:membership, :url => { :action => 'edit_membership', :id => @user, :membership_id => membership },
22 <% remote_form_for(:membership, :url => { :action => 'edit_membership', :id => @user, :membership_id => membership },
23 :html => { :id => "member-#{membership.id}-roles-form", :style => 'display:none;'}) do %>
23 :html => { :id => "member-#{membership.id}-roles-form", :style => 'display:none;'}) do %>
24 <p><% roles.each do |role| %>
24 <p><% roles.each do |role| %>
25 <label><%= check_box_tag 'membership[role_ids][]', role.id, membership.roles.include?(role),
25 <label><%= check_box_tag 'membership[role_ids][]', role.id, membership.roles.include?(role),
26 :disabled => membership.member_roles.detect {|mr| mr.role_id == role.id && !mr.inherited_from.nil?} %> <%=h role %></label><br />
26 :disabled => membership.member_roles.detect {|mr| mr.role_id == role.id && !mr.inherited_from.nil?} %> <%=h role %></label><br />
27 <% end %></p>
27 <% end %></p>
28 <%= hidden_field_tag 'membership[role_ids][]', '' %>
28 <%= hidden_field_tag 'membership[role_ids][]', '' %>
29 <p><%= submit_tag l(:button_change) %>
29 <p><%= submit_tag l(:button_change) %>
30 <%= link_to_function l(:button_cancel), "$('member-#{membership.id}-roles').show(); $('member-#{membership.id}-roles-form').hide(); return false;" %></p>
30 <%= link_to_function l(:button_cancel), "$('member-#{membership.id}-roles').show(); $('member-#{membership.id}-roles-form').hide(); return false;" %></p>
31 <% end %>
31 <% end %>
32 </td>
32 </td>
33 <td class="buttons">
33 <td class="buttons">
34 <%= link_to_function l(:button_edit), "$('member-#{membership.id}-roles').hide(); $('member-#{membership.id}-roles-form').show(); return false;", :class => 'icon icon-edit' %>
34 <%= link_to_function l(:button_edit), "$('member-#{membership.id}-roles').hide(); $('member-#{membership.id}-roles-form').show(); return false;", :class => 'icon icon-edit' %>
35 <%= link_to_remote(l(:button_delete), { :url => { :controller => 'users', :action => 'destroy_membership', :id => @user, :membership_id => membership },
35 <%= link_to_remote(l(:button_delete), { :url => { :controller => 'users', :action => 'destroy_membership', :id => @user, :membership_id => membership },
36 :method => :post },
36 :method => :post },
37 :class => 'icon icon-del') if membership.deletable? %>
37 :class => 'icon icon-del') if membership.deletable? %>
38 </td>
38 </td>
39 <%= call_hook(:view_users_memberships_table_row, :user => @user, :membership => membership, :roles => roles, :projects => projects )%>
39 <%= call_hook(:view_users_memberships_table_row, :user => @user, :membership => membership, :roles => roles, :projects => projects )%>
40 </tr>
40 </tr>
41 </tbody>
41 </tbody>
42 <% end; reset_cycle %>
42 <% end; reset_cycle %>
43 </table>
43 </table>
44 <% else %>
44 <% else %>
45 <p class="nodata"><%= l(:label_no_data) %></p>
45 <p class="nodata"><%= l(:label_no_data) %></p>
46 <% end %>
46 <% end %>
47 </div>
47 </div>
48
48
49 <div class="splitcontentright">
49 <div class="splitcontentright">
50 <% if projects.any? %>
50 <% if projects.any? %>
51 <fieldset><legend><%=l(:label_project_new)%></legend>
51 <fieldset><legend><%=l(:label_project_new)%></legend>
52 <% remote_form_for(:membership, :url => { :action => 'edit_membership', :id => @user }) do %>
52 <% remote_form_for(:membership, :url => { :action => 'edit_membership', :id => @user }) do %>
53 <%= select_tag 'membership[project_id]', options_for_membership_project_select(@user, projects) %>
53 <%= select_tag 'membership[project_id]', options_for_membership_project_select(@user, projects) %>
54 <p><%= l(:label_role_plural) %>:
54 <p><%= l(:label_role_plural) %>:
55 <% roles.each do |role| %>
55 <% roles.each do |role| %>
56 <label><%= check_box_tag 'membership[role_ids][]', role.id %> <%=h role %></label>
56 <label><%= check_box_tag 'membership[role_ids][]', role.id %> <%=h role %></label>
57 <% end %></p>
57 <% end %></p>
58 <p><%= submit_tag l(:button_add) %></p>
58 <p><%= submit_tag l(:button_add) %></p>
59 <% end %>
59 <% end %>
60 </fieldset>
60 </fieldset>
61 <% end %>
61 <% end %>
62 </div>
62 </div>
@@ -1,70 +1,70
1 <div class="contextual">
1 <div class="contextual">
2 <%= link_to(l(:button_edit), {:controller => 'users', :action => 'edit', :id => @user}, :class => 'icon icon-edit') if User.current.admin? %>
2 <%= link_to(l(:button_edit), {:controller => 'users', :action => 'edit', :id => @user}, :class => 'icon icon-edit') if User.current.admin? %>
3 </div>
3 </div>
4
4
5 <h2><%= avatar @user, :size => "50" %> <%=h @user.name %></h2>
5 <h2><%= avatar @user, :size => "50" %> <%=h @user.name %></h2>
6
6
7 <div class="splitcontentleft">
7 <div class="splitcontentleft">
8 <ul>
8 <ul>
9 <% unless @user.pref.hide_mail %>
9 <% unless @user.pref.hide_mail %>
10 <li><%=l(:field_mail)%>: <%= mail_to(h(@user.mail), nil, :encode => 'javascript') %></li>
10 <li><%=l(:field_mail)%>: <%= mail_to(h(@user.mail), nil, :encode => 'javascript') %></li>
11 <% end %>
11 <% end %>
12 <% for custom_value in @custom_values %>
12 <% for custom_value in @custom_values %>
13 <% if !custom_value.value.blank? %>
13 <% if !custom_value.value.blank? %>
14 <li><%=h custom_value.custom_field.name%>: <%=h show_value(custom_value) %></li>
14 <li><%=h custom_value.custom_field.name%>: <%=h show_value(custom_value) %></li>
15 <% end %>
15 <% end %>
16 <% end %>
16 <% end %>
17 <li><%=l(:label_registered_on)%>: <%= format_date(@user.created_on) %></li>
17 <li><%=l(:label_registered_on)%>: <%= format_date(@user.created_on) %></li>
18 <% unless @user.last_login_on.nil? %>
18 <% unless @user.last_login_on.nil? %>
19 <li><%=l(:field_last_login_on)%>: <%= format_date(@user.last_login_on) %></li>
19 <li><%=l(:field_last_login_on)%>: <%= format_date(@user.last_login_on) %></li>
20 <% end %>
20 <% end %>
21 </ul>
21 </ul>
22
22
23 <% unless @memberships.empty? %>
23 <% unless @memberships.empty? %>
24 <h3><%=l(:label_project_plural)%></h3>
24 <h3><%=l(:label_project_plural)%></h3>
25 <ul>
25 <ul>
26 <% for membership in @memberships %>
26 <% for membership in @memberships %>
27 <li><%= link_to(h(membership.project.name), :controller => 'projects', :action => 'show', :id => membership.project) %>
27 <li><%= link_to_project(membership.project) %>
28 (<%=h membership.roles.sort.collect(&:to_s).join(', ') %>, <%= format_date(membership.created_on) %>)</li>
28 (<%=h membership.roles.sort.collect(&:to_s).join(', ') %>, <%= format_date(membership.created_on) %>)</li>
29 <% end %>
29 <% end %>
30 </ul>
30 </ul>
31 <% end %>
31 <% end %>
32 <%= call_hook :view_account_left_bottom, :user => @user %>
32 <%= call_hook :view_account_left_bottom, :user => @user %>
33 </div>
33 </div>
34
34
35 <div class="splitcontentright">
35 <div class="splitcontentright">
36
36
37 <% unless @events_by_day.empty? %>
37 <% unless @events_by_day.empty? %>
38 <h3><%= link_to l(:label_activity), :controller => 'projects', :action => 'activity', :id => nil, :user_id => @user, :from => @events_by_day.keys.first %></h3>
38 <h3><%= link_to l(:label_activity), :controller => 'projects', :action => 'activity', :id => nil, :user_id => @user, :from => @events_by_day.keys.first %></h3>
39
39
40 <p>
40 <p>
41 <%=l(:label_reported_issues)%>: <%= Issue.count(:conditions => ["author_id=?", @user.id]) %>
41 <%=l(:label_reported_issues)%>: <%= Issue.count(:conditions => ["author_id=?", @user.id]) %>
42 </p>
42 </p>
43
43
44 <div id="activity">
44 <div id="activity">
45 <% @events_by_day.keys.sort.reverse.each do |day| %>
45 <% @events_by_day.keys.sort.reverse.each do |day| %>
46 <h4><%= format_activity_day(day) %></h4>
46 <h4><%= format_activity_day(day) %></h4>
47 <dl>
47 <dl>
48 <% @events_by_day[day].sort {|x,y| y.event_datetime <=> x.event_datetime }.each do |e| -%>
48 <% @events_by_day[day].sort {|x,y| y.event_datetime <=> x.event_datetime }.each do |e| -%>
49 <dt class="<%= e.event_type %>">
49 <dt class="<%= e.event_type %>">
50 <span class="time"><%= format_time(e.event_datetime, false) %></span>
50 <span class="time"><%= format_time(e.event_datetime, false) %></span>
51 <%= content_tag('span', h(e.project), :class => 'project') %>
51 <%= content_tag('span', h(e.project), :class => 'project') %>
52 <%= link_to format_activity_title(e.event_title), e.event_url %></dt>
52 <%= link_to format_activity_title(e.event_title), e.event_url %></dt>
53 <dd><span class="description"><%= format_activity_description(e.event_description) %></span></dd>
53 <dd><span class="description"><%= format_activity_description(e.event_description) %></span></dd>
54 <% end -%>
54 <% end -%>
55 </dl>
55 </dl>
56 <% end -%>
56 <% end -%>
57 </div>
57 </div>
58
58
59 <% other_formats_links do |f| %>
59 <% other_formats_links do |f| %>
60 <%= f.link_to 'Atom', :url => {:controller => 'projects', :action => 'activity', :id => nil, :user_id => @user, :key => User.current.rss_key} %>
60 <%= f.link_to 'Atom', :url => {:controller => 'projects', :action => 'activity', :id => nil, :user_id => @user, :key => User.current.rss_key} %>
61 <% end %>
61 <% end %>
62
62
63 <% content_for :header_tags do %>
63 <% content_for :header_tags do %>
64 <%= auto_discovery_link_tag(:atom, :controller => 'projects', :action => 'activity', :user_id => @user, :format => :atom, :key => User.current.rss_key) %>
64 <%= auto_discovery_link_tag(:atom, :controller => 'projects', :action => 'activity', :user_id => @user, :format => :atom, :key => User.current.rss_key) %>
65 <% end %>
65 <% end %>
66 <% end %>
66 <% end %>
67 <%= call_hook :view_account_right_bottom, :user => @user %>
67 <%= call_hook :view_account_right_bottom, :user => @user %>
68 </div>
68 </div>
69
69
70 <% html_title @user.name %>
70 <% html_title @user.name %>
@@ -1,39 +1,39
1 <h2><%= l(:label_home) %></h2>
1 <h2><%= l(:label_home) %></h2>
2
2
3 <div class="splitcontentleft">
3 <div class="splitcontentleft">
4 <%= textilizable Setting.welcome_text %>
4 <%= textilizable Setting.welcome_text %>
5 <% if @news.any? %>
5 <% if @news.any? %>
6 <div class="news box">
6 <div class="news box">
7 <h3><%=l(:label_news_latest)%></h3>
7 <h3><%=l(:label_news_latest)%></h3>
8 <%= render :partial => 'news/news', :collection => @news %>
8 <%= render :partial => 'news/news', :collection => @news %>
9 <%= link_to l(:label_news_view_all), :controller => 'news' %>
9 <%= link_to l(:label_news_view_all), :controller => 'news' %>
10 </div>
10 </div>
11 <% end %>
11 <% end %>
12 <%= call_hook(:view_welcome_index_left, :projects => @projects) %>
12 <%= call_hook(:view_welcome_index_left, :projects => @projects) %>
13 </div>
13 </div>
14
14
15 <div class="splitcontentright">
15 <div class="splitcontentright">
16 <% if @projects.any? %>
16 <% if @projects.any? %>
17 <div class="projects box">
17 <div class="projects box">
18 <h3><%=l(:label_project_latest)%></h3>
18 <h3><%=l(:label_project_latest)%></h3>
19 <ul>
19 <ul>
20 <% for project in @projects %>
20 <% for project in @projects %>
21 <% @project = project %>
21 <% @project = project %>
22 <li>
22 <li>
23 <%= link_to h(project.name), :controller => 'projects', :action => 'show', :id => project %> (<%= format_time(project.created_on) %>)
23 <%= link_to_project project %> (<%= format_time(project.created_on) %>)
24 <%= textilizable project.short_description, :project => project %>
24 <%= textilizable project.short_description, :project => project %>
25 </li>
25 </li>
26 <% end %>
26 <% end %>
27 <% @project = nil %>
27 <% @project = nil %>
28 </ul>
28 </ul>
29 </div>
29 </div>
30 <% end %>
30 <% end %>
31 <%= call_hook(:view_welcome_index_right, :projects => @projects) %>
31 <%= call_hook(:view_welcome_index_right, :projects => @projects) %>
32 </div>
32 </div>
33
33
34 <% content_for :header_tags do %>
34 <% content_for :header_tags do %>
35 <%= auto_discovery_link_tag(:atom, {:controller => 'news', :action => 'index', :key => User.current.rss_key, :format => 'atom'},
35 <%= auto_discovery_link_tag(:atom, {:controller => 'news', :action => 'index', :key => User.current.rss_key, :format => 'atom'},
36 :title => "#{Setting.app_title}: #{l(:label_news_latest)}") %>
36 :title => "#{Setting.app_title}: #{l(:label_news_latest)}") %>
37 <%= auto_discovery_link_tag(:atom, {:controller => 'projects', :action => 'activity', :key => User.current.rss_key, :format => 'atom'},
37 <%= auto_discovery_link_tag(:atom, {:controller => 'projects', :action => 'activity', :key => User.current.rss_key, :format => 'atom'},
38 :title => "#{Setting.app_title}: #{l(:label_activity)}") %>
38 :title => "#{Setting.app_title}: #{l(:label_activity)}") %>
39 <% end %>
39 <% end %>
@@ -1,600 +1,612
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
2 # Copyright (C) 2006-2009 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.dirname(__FILE__) + '/../../test_helper'
18 require File.dirname(__FILE__) + '/../../test_helper'
19
19
20 class ApplicationHelperTest < HelperTestCase
20 class ApplicationHelperTest < HelperTestCase
21 include ApplicationHelper
21 include ApplicationHelper
22 include ActionView::Helpers::TextHelper
22 include ActionView::Helpers::TextHelper
23 include ActionView::Helpers::DateHelper
23 include ActionView::Helpers::DateHelper
24
24
25 fixtures :projects, :roles, :enabled_modules, :users,
25 fixtures :projects, :roles, :enabled_modules, :users,
26 :repositories, :changesets,
26 :repositories, :changesets,
27 :trackers, :issue_statuses, :issues, :versions, :documents,
27 :trackers, :issue_statuses, :issues, :versions, :documents,
28 :wikis, :wiki_pages, :wiki_contents,
28 :wikis, :wiki_pages, :wiki_contents,
29 :boards, :messages,
29 :boards, :messages,
30 :attachments,
30 :attachments,
31 :enumerations
31 :enumerations
32
32
33 def setup
33 def setup
34 super
34 super
35 end
35 end
36
36
37 def test_auto_links
37 def test_auto_links
38 to_test = {
38 to_test = {
39 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
39 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
40 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
40 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
41 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
41 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
42 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
42 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
43 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
43 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
44 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
44 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
45 '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>.',
45 '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>.',
46 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
46 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
47 '(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>)',
47 '(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>)',
48 '(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>)',
48 '(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>)',
49 '(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>).',
49 '(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>).',
50 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
50 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
51 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
51 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
52 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
52 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
53 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
53 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
54 '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>',
54 '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>',
55 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
55 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
56 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
56 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
57 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
57 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
58 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
58 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
59 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
59 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
60 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
60 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
61 # two exclamation marks
61 # two exclamation marks
62 '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>',
62 '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>',
63 # escaping
63 # escaping
64 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo"bar</a>',
64 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo"bar</a>',
65 }
65 }
66 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
66 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
67 end
67 end
68
68
69 def test_auto_mailto
69 def test_auto_mailto
70 assert_equal '<p><a class="email" href="mailto:test@foo.bar">test@foo.bar</a></p>',
70 assert_equal '<p><a class="email" href="mailto:test@foo.bar">test@foo.bar</a></p>',
71 textilizable('test@foo.bar')
71 textilizable('test@foo.bar')
72 end
72 end
73
73
74 def test_inline_images
74 def test_inline_images
75 to_test = {
75 to_test = {
76 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
76 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
77 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
77 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
78 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
78 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
79 # inline styles should be stripped
79 # inline styles should be stripped
80 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" alt="" />',
80 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" alt="" />',
81 '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" />',
81 '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" />',
82 '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;" />',
82 '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;" />',
83 }
83 }
84 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
84 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
85 end
85 end
86
86
87 def test_inline_images_inside_tags
87 def test_inline_images_inside_tags
88 raw = <<-RAW
88 raw = <<-RAW
89 h1. !foo.png! Heading
89 h1. !foo.png! Heading
90
90
91 Centered image:
91 Centered image:
92
92
93 p=. !bar.gif!
93 p=. !bar.gif!
94 RAW
94 RAW
95
95
96 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
96 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
97 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
97 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
98 end
98 end
99
99
100 def test_acronyms
100 def test_acronyms
101 to_test = {
101 to_test = {
102 'this is an acronym: GPL(General Public License)' => 'this is an acronym: <acronym title="General Public License">GPL</acronym>',
102 'this is an acronym: GPL(General Public License)' => 'this is an acronym: <acronym title="General Public License">GPL</acronym>',
103 'GPL(This is a double-quoted "title")' => '<acronym title="This is a double-quoted &quot;title&quot;">GPL</acronym>',
103 'GPL(This is a double-quoted "title")' => '<acronym title="This is a double-quoted &quot;title&quot;">GPL</acronym>',
104 }
104 }
105 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
105 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
106
106
107 end
107 end
108
108
109 def test_attached_images
109 def test_attached_images
110 to_test = {
110 to_test = {
111 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
111 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
112 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
112 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
113 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
113 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
114 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
114 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
115 # link image
115 # link image
116 '!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>',
116 '!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>',
117 }
117 }
118 attachments = Attachment.find(:all)
118 attachments = Attachment.find(:all)
119 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
119 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
120 end
120 end
121
121
122 def test_textile_external_links
122 def test_textile_external_links
123 to_test = {
123 to_test = {
124 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
124 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
125 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
125 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
126 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
126 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
127 '"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>',
127 '"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>',
128 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
128 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
129 # no multiline link text
129 # no multiline link text
130 "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",
130 "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",
131 # mailto link
131 # mailto link
132 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
132 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
133 # two exclamation marks
133 # two exclamation marks
134 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
134 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
135 # escaping
135 # escaping
136 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
136 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
137 }
137 }
138 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
138 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
139 end
139 end
140
140
141 def test_redmine_links
141 def test_redmine_links
142 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
142 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
143 :class => 'issue status-1 priority-1 overdue', :title => 'Error 281 when updating a recipe (New)')
143 :class => 'issue status-1 priority-1 overdue', :title => 'Error 281 when updating a recipe (New)')
144
144
145 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
145 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
146 :class => 'changeset', :title => 'My very first commit')
146 :class => 'changeset', :title => 'My very first commit')
147 changeset_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
147 changeset_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
148 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
148 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
149
149
150 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
150 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
151 :class => 'document')
151 :class => 'document')
152
152
153 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
153 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
154 :class => 'version')
154 :class => 'version')
155
155
156 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
156 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
157
157
158 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
158 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
159
159
160 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
160 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
161 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
161 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
162
162
163 to_test = {
163 to_test = {
164 # tickets
164 # tickets
165 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
165 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
166 # changesets
166 # changesets
167 'r1' => changeset_link,
167 'r1' => changeset_link,
168 'r1.' => "#{changeset_link}.",
168 'r1.' => "#{changeset_link}.",
169 'r1, r2' => "#{changeset_link}, #{changeset_link2}",
169 'r1, r2' => "#{changeset_link}, #{changeset_link2}",
170 'r1,r2' => "#{changeset_link},#{changeset_link2}",
170 'r1,r2' => "#{changeset_link},#{changeset_link2}",
171 # documents
171 # documents
172 'document#1' => document_link,
172 'document#1' => document_link,
173 'document:"Test document"' => document_link,
173 'document:"Test document"' => document_link,
174 # versions
174 # versions
175 'version#2' => version_link,
175 'version#2' => version_link,
176 'version:1.0' => version_link,
176 'version:1.0' => version_link,
177 'version:"1.0"' => version_link,
177 'version:"1.0"' => version_link,
178 # source
178 # source
179 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
179 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
180 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
180 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
181 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
181 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
182 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
182 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
183 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
183 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
184 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
184 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
185 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
185 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
186 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
186 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
187 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
187 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
188 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
188 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
189 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
189 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
190 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
190 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
191 # message
191 # message
192 'message#4' => link_to('Post 2', message_url, :class => 'message'),
192 'message#4' => link_to('Post 2', message_url, :class => 'message'),
193 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5'), :class => 'message'),
193 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5'), :class => 'message'),
194 # project
194 # project
195 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
195 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
196 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
196 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
197 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
197 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
198 # escaping
198 # escaping
199 '!#3.' => '#3.',
199 '!#3.' => '#3.',
200 '!r1' => 'r1',
200 '!r1' => 'r1',
201 '!document#1' => 'document#1',
201 '!document#1' => 'document#1',
202 '!document:"Test document"' => 'document:"Test document"',
202 '!document:"Test document"' => 'document:"Test document"',
203 '!version#2' => 'version#2',
203 '!version#2' => 'version#2',
204 '!version:1.0' => 'version:1.0',
204 '!version:1.0' => 'version:1.0',
205 '!version:"1.0"' => 'version:"1.0"',
205 '!version:"1.0"' => 'version:"1.0"',
206 '!source:/some/file' => 'source:/some/file',
206 '!source:/some/file' => 'source:/some/file',
207 # not found
207 # not found
208 '#0123456789' => '#0123456789',
208 '#0123456789' => '#0123456789',
209 # invalid expressions
209 # invalid expressions
210 'source:' => 'source:',
210 'source:' => 'source:',
211 # url hash
211 # url hash
212 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
212 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
213 }
213 }
214 @project = Project.find(1)
214 @project = Project.find(1)
215 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
215 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
216 end
216 end
217
217
218 def test_attachment_links
218 def test_attachment_links
219 attachment_link = link_to('error281.txt', {:controller => 'attachments', :action => 'download', :id => '1'}, :class => 'attachment')
219 attachment_link = link_to('error281.txt', {:controller => 'attachments', :action => 'download', :id => '1'}, :class => 'attachment')
220 to_test = {
220 to_test = {
221 'attachment:error281.txt' => attachment_link
221 'attachment:error281.txt' => attachment_link
222 }
222 }
223 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => Issue.find(3).attachments), "#{text} failed" }
223 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => Issue.find(3).attachments), "#{text} failed" }
224 end
224 end
225
225
226 def test_wiki_links
226 def test_wiki_links
227 to_test = {
227 to_test = {
228 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
228 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
229 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
229 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
230 # link with anchor
230 # link with anchor
231 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
231 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
232 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
232 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
233 # page that doesn't exist
233 # page that doesn't exist
234 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
234 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
235 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
235 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
236 # link to another project wiki
236 # link to another project wiki
237 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">onlinestore</a>',
237 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">onlinestore</a>',
238 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">Wiki</a>',
238 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">Wiki</a>',
239 '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
239 '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
240 '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
240 '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
241 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
241 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
242 # striked through link
242 # striked through link
243 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
243 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
244 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
244 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
245 # escaping
245 # escaping
246 '![[Another page|Page]]' => '[[Another page|Page]]',
246 '![[Another page|Page]]' => '[[Another page|Page]]',
247 # project does not exist
247 # project does not exist
248 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
248 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
249 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
249 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
250 }
250 }
251 @project = Project.find(1)
251 @project = Project.find(1)
252 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
252 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
253 end
253 end
254
254
255 def test_html_tags
255 def test_html_tags
256 to_test = {
256 to_test = {
257 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
257 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
258 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
258 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
259 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
259 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
260 # do not escape pre/code tags
260 # do not escape pre/code tags
261 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
261 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
262 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
262 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
263 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
263 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
264 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
264 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
265 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
265 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
266 # remove attributes except class
266 # remove attributes except class
267 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
267 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
268 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
268 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
269 }
269 }
270 to_test.each { |text, result| assert_equal result, textilizable(text) }
270 to_test.each { |text, result| assert_equal result, textilizable(text) }
271 end
271 end
272
272
273 def test_allowed_html_tags
273 def test_allowed_html_tags
274 to_test = {
274 to_test = {
275 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
275 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
276 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
276 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
277 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
277 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
278 }
278 }
279 to_test.each { |text, result| assert_equal result, textilizable(text) }
279 to_test.each { |text, result| assert_equal result, textilizable(text) }
280 end
280 end
281
281
282 def test_pre_tags
282 def test_pre_tags
283 raw = <<-RAW
283 raw = <<-RAW
284 Before
284 Before
285
285
286 <pre>
286 <pre>
287 <prepared-statement-cache-size>32</prepared-statement-cache-size>
287 <prepared-statement-cache-size>32</prepared-statement-cache-size>
288 </pre>
288 </pre>
289
289
290 After
290 After
291 RAW
291 RAW
292
292
293 expected = <<-EXPECTED
293 expected = <<-EXPECTED
294 <p>Before</p>
294 <p>Before</p>
295 <pre>
295 <pre>
296 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
296 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
297 </pre>
297 </pre>
298 <p>After</p>
298 <p>After</p>
299 EXPECTED
299 EXPECTED
300
300
301 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
301 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
302 end
302 end
303
303
304 def test_pre_content_should_not_parse_wiki_and_redmine_links
304 def test_pre_content_should_not_parse_wiki_and_redmine_links
305 raw = <<-RAW
305 raw = <<-RAW
306 [[CookBook documentation]]
306 [[CookBook documentation]]
307
307
308 #1
308 #1
309
309
310 <pre>
310 <pre>
311 [[CookBook documentation]]
311 [[CookBook documentation]]
312
312
313 #1
313 #1
314 </pre>
314 </pre>
315 RAW
315 RAW
316
316
317 expected = <<-EXPECTED
317 expected = <<-EXPECTED
318 <p><a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a></p>
318 <p><a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a></p>
319 <p><a href="/issues/1" class="issue status-1 priority-1" title="Can't print recipes (New)">#1</a></p>
319 <p><a href="/issues/1" class="issue status-1 priority-1" title="Can't print recipes (New)">#1</a></p>
320 <pre>
320 <pre>
321 [[CookBook documentation]]
321 [[CookBook documentation]]
322
322
323 #1
323 #1
324 </pre>
324 </pre>
325 EXPECTED
325 EXPECTED
326
326
327 @project = Project.find(1)
327 @project = Project.find(1)
328 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
328 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
329 end
329 end
330
330
331 def test_non_closing_pre_blocks_should_be_closed
331 def test_non_closing_pre_blocks_should_be_closed
332 raw = <<-RAW
332 raw = <<-RAW
333 <pre><code>
333 <pre><code>
334 RAW
334 RAW
335
335
336 expected = <<-EXPECTED
336 expected = <<-EXPECTED
337 <pre><code>
337 <pre><code>
338 </code></pre>
338 </code></pre>
339 EXPECTED
339 EXPECTED
340
340
341 @project = Project.find(1)
341 @project = Project.find(1)
342 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
342 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
343 end
343 end
344
344
345 def test_syntax_highlight
345 def test_syntax_highlight
346 raw = <<-RAW
346 raw = <<-RAW
347 <pre><code class="ruby">
347 <pre><code class="ruby">
348 # Some ruby code here
348 # Some ruby code here
349 </code></pre>
349 </code></pre>
350 RAW
350 RAW
351
351
352 expected = <<-EXPECTED
352 expected = <<-EXPECTED
353 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="no">1</span> <span class="c"># Some ruby code here</span></span>
353 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="no">1</span> <span class="c"># Some ruby code here</span></span>
354 </code></pre>
354 </code></pre>
355 EXPECTED
355 EXPECTED
356
356
357 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
357 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
358 end
358 end
359
359
360 def test_wiki_links_in_tables
360 def test_wiki_links_in_tables
361 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
361 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
362 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
362 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
363 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
363 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
364 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
364 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
365 }
365 }
366 @project = Project.find(1)
366 @project = Project.find(1)
367 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
367 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
368 end
368 end
369
369
370 def test_text_formatting
370 def test_text_formatting
371 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
371 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
372 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
372 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
373 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
373 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
374 '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>',
374 '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>',
375 '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',
375 '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',
376 }
376 }
377 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
377 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
378 end
378 end
379
379
380 def test_wiki_horizontal_rule
380 def test_wiki_horizontal_rule
381 assert_equal '<hr />', textilizable('---')
381 assert_equal '<hr />', textilizable('---')
382 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
382 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
383 end
383 end
384
384
385 def test_acronym
385 def test_acronym
386 assert_equal '<p>This is an acronym: <acronym title="American Civil Liberties Union">ACLU</acronym>.</p>',
386 assert_equal '<p>This is an acronym: <acronym title="American Civil Liberties Union">ACLU</acronym>.</p>',
387 textilizable('This is an acronym: ACLU(American Civil Liberties Union).')
387 textilizable('This is an acronym: ACLU(American Civil Liberties Union).')
388 end
388 end
389
389
390 def test_footnotes
390 def test_footnotes
391 raw = <<-RAW
391 raw = <<-RAW
392 This is some text[1].
392 This is some text[1].
393
393
394 fn1. This is the foot note
394 fn1. This is the foot note
395 RAW
395 RAW
396
396
397 expected = <<-EXPECTED
397 expected = <<-EXPECTED
398 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
398 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
399 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
399 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
400 EXPECTED
400 EXPECTED
401
401
402 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
402 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
403 end
403 end
404
404
405 def test_table_of_content
405 def test_table_of_content
406 raw = <<-RAW
406 raw = <<-RAW
407 {{toc}}
407 {{toc}}
408
408
409 h1. Title
409 h1. Title
410
410
411 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
411 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
412
412
413 h2. Subtitle with a [[Wiki]] link
413 h2. Subtitle with a [[Wiki]] link
414
414
415 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
415 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
416
416
417 h2. Subtitle with [[Wiki|another Wiki]] link
417 h2. Subtitle with [[Wiki|another Wiki]] link
418
418
419 h2. Subtitle with %{color:red}red text%
419 h2. Subtitle with %{color:red}red text%
420
420
421 h1. Another title
421 h1. Another title
422
422
423 h2. An "Internet link":http://www.redmine.org/ inside subtitle
423 h2. An "Internet link":http://www.redmine.org/ inside subtitle
424
424
425 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
425 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
426
426
427 RAW
427 RAW
428
428
429 expected = '<ul class="toc">' +
429 expected = '<ul class="toc">' +
430 '<li class="heading1"><a href="#Title">Title</a></li>' +
430 '<li class="heading1"><a href="#Title">Title</a></li>' +
431 '<li class="heading2"><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
431 '<li class="heading2"><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
432 '<li class="heading2"><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
432 '<li class="heading2"><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
433 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
433 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
434 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
434 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
435 '<li class="heading2"><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
435 '<li class="heading2"><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
436 '<li class="heading2"><a href="#Project-Name">Project Name</a></li>' +
436 '<li class="heading2"><a href="#Project-Name">Project Name</a></li>' +
437 '</ul>'
437 '</ul>'
438
438
439 assert textilizable(raw).gsub("\n", "").include?(expected)
439 assert textilizable(raw).gsub("\n", "").include?(expected)
440 end
440 end
441
441
442 def test_blockquote
442 def test_blockquote
443 # orig raw text
443 # orig raw text
444 raw = <<-RAW
444 raw = <<-RAW
445 John said:
445 John said:
446 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
446 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
447 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
447 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
448 > * Donec odio lorem,
448 > * Donec odio lorem,
449 > * sagittis ac,
449 > * sagittis ac,
450 > * malesuada in,
450 > * malesuada in,
451 > * adipiscing eu, dolor.
451 > * adipiscing eu, dolor.
452 >
452 >
453 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
453 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
454 > Proin a tellus. Nam vel neque.
454 > Proin a tellus. Nam vel neque.
455
455
456 He's right.
456 He's right.
457 RAW
457 RAW
458
458
459 # expected html
459 # expected html
460 expected = <<-EXPECTED
460 expected = <<-EXPECTED
461 <p>John said:</p>
461 <p>John said:</p>
462 <blockquote>
462 <blockquote>
463 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
463 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
464 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
464 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
465 <ul>
465 <ul>
466 <li>Donec odio lorem,</li>
466 <li>Donec odio lorem,</li>
467 <li>sagittis ac,</li>
467 <li>sagittis ac,</li>
468 <li>malesuada in,</li>
468 <li>malesuada in,</li>
469 <li>adipiscing eu, dolor.</li>
469 <li>adipiscing eu, dolor.</li>
470 </ul>
470 </ul>
471 <blockquote>
471 <blockquote>
472 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
472 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
473 </blockquote>
473 </blockquote>
474 <p>Proin a tellus. Nam vel neque.</p>
474 <p>Proin a tellus. Nam vel neque.</p>
475 </blockquote>
475 </blockquote>
476 <p>He's right.</p>
476 <p>He's right.</p>
477 EXPECTED
477 EXPECTED
478
478
479 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
479 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
480 end
480 end
481
481
482 def test_table
482 def test_table
483 raw = <<-RAW
483 raw = <<-RAW
484 This is a table with empty cells:
484 This is a table with empty cells:
485
485
486 |cell11|cell12||
486 |cell11|cell12||
487 |cell21||cell23|
487 |cell21||cell23|
488 |cell31|cell32|cell33|
488 |cell31|cell32|cell33|
489 RAW
489 RAW
490
490
491 expected = <<-EXPECTED
491 expected = <<-EXPECTED
492 <p>This is a table with empty cells:</p>
492 <p>This is a table with empty cells:</p>
493
493
494 <table>
494 <table>
495 <tr><td>cell11</td><td>cell12</td><td></td></tr>
495 <tr><td>cell11</td><td>cell12</td><td></td></tr>
496 <tr><td>cell21</td><td></td><td>cell23</td></tr>
496 <tr><td>cell21</td><td></td><td>cell23</td></tr>
497 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
497 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
498 </table>
498 </table>
499 EXPECTED
499 EXPECTED
500
500
501 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
501 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
502 end
502 end
503
503
504 def test_table_with_line_breaks
504 def test_table_with_line_breaks
505 raw = <<-RAW
505 raw = <<-RAW
506 This is a table with line breaks:
506 This is a table with line breaks:
507
507
508 |cell11
508 |cell11
509 continued|cell12||
509 continued|cell12||
510 |-cell21-||cell23
510 |-cell21-||cell23
511 cell23 line2
511 cell23 line2
512 cell23 *line3*|
512 cell23 *line3*|
513 |cell31|cell32
513 |cell31|cell32
514 cell32 line2|cell33|
514 cell32 line2|cell33|
515
515
516 RAW
516 RAW
517
517
518 expected = <<-EXPECTED
518 expected = <<-EXPECTED
519 <p>This is a table with line breaks:</p>
519 <p>This is a table with line breaks:</p>
520
520
521 <table>
521 <table>
522 <tr>
522 <tr>
523 <td>cell11<br />continued</td>
523 <td>cell11<br />continued</td>
524 <td>cell12</td>
524 <td>cell12</td>
525 <td></td>
525 <td></td>
526 </tr>
526 </tr>
527 <tr>
527 <tr>
528 <td><del>cell21</del></td>
528 <td><del>cell21</del></td>
529 <td></td>
529 <td></td>
530 <td>cell23<br/>cell23 line2<br/>cell23 <strong>line3</strong></td>
530 <td>cell23<br/>cell23 line2<br/>cell23 <strong>line3</strong></td>
531 </tr>
531 </tr>
532 <tr>
532 <tr>
533 <td>cell31</td>
533 <td>cell31</td>
534 <td>cell32<br/>cell32 line2</td>
534 <td>cell32<br/>cell32 line2</td>
535 <td>cell33</td>
535 <td>cell33</td>
536 </tr>
536 </tr>
537 </table>
537 </table>
538 EXPECTED
538 EXPECTED
539
539
540 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
540 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
541 end
541 end
542
542
543 def test_textile_should_not_mangle_brackets
543 def test_textile_should_not_mangle_brackets
544 assert_equal '<p>[msg1][msg2]</p>', textilizable('[msg1][msg2]')
544 assert_equal '<p>[msg1][msg2]</p>', textilizable('[msg1][msg2]')
545 end
545 end
546
546
547 def test_default_formatter
547 def test_default_formatter
548 Setting.text_formatting = 'unknown'
548 Setting.text_formatting = 'unknown'
549 text = 'a *link*: http://www.example.net/'
549 text = 'a *link*: http://www.example.net/'
550 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
550 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
551 Setting.text_formatting = 'textile'
551 Setting.text_formatting = 'textile'
552 end
552 end
553
553
554 def test_due_date_distance_in_words
554 def test_due_date_distance_in_words
555 to_test = { Date.today => 'Due in 0 days',
555 to_test = { Date.today => 'Due in 0 days',
556 Date.today + 1 => 'Due in 1 day',
556 Date.today + 1 => 'Due in 1 day',
557 Date.today + 100 => 'Due in about 3 months',
557 Date.today + 100 => 'Due in about 3 months',
558 Date.today + 20000 => 'Due in over 54 years',
558 Date.today + 20000 => 'Due in over 54 years',
559 Date.today - 1 => '1 day late',
559 Date.today - 1 => '1 day late',
560 Date.today - 100 => 'about 3 months late',
560 Date.today - 100 => 'about 3 months late',
561 Date.today - 20000 => 'over 54 years late',
561 Date.today - 20000 => 'over 54 years late',
562 }
562 }
563 to_test.each do |date, expected|
563 to_test.each do |date, expected|
564 assert_equal expected, due_date_distance_in_words(date)
564 assert_equal expected, due_date_distance_in_words(date)
565 end
565 end
566 end
566 end
567
567
568 def test_avatar
568 def test_avatar
569 # turn on avatars
569 # turn on avatars
570 Setting.gravatar_enabled = '1'
570 Setting.gravatar_enabled = '1'
571 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
571 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
572 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
572 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
573 assert_nil avatar('jsmith')
573 assert_nil avatar('jsmith')
574 assert_nil avatar(nil)
574 assert_nil avatar(nil)
575
575
576 # turn off avatars
576 # turn off avatars
577 Setting.gravatar_enabled = '0'
577 Setting.gravatar_enabled = '0'
578 assert_nil avatar(User.find_by_mail('jsmith@somenet.foo'))
578 assert_nil avatar(User.find_by_mail('jsmith@somenet.foo'))
579 end
579 end
580
580
581 def test_link_to_user
581 def test_link_to_user
582 user = User.find(2)
582 user = User.find(2)
583 t = link_to_user(user)
583 t = link_to_user(user)
584 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
584 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
585 end
585 end
586
586
587 def test_link_to_user_should_not_link_to_locked_user
587 def test_link_to_user_should_not_link_to_locked_user
588 user = User.find(5)
588 user = User.find(5)
589 assert user.locked?
589 assert user.locked?
590 t = link_to_user(user)
590 t = link_to_user(user)
591 assert_equal user.name, t
591 assert_equal user.name, t
592 end
592 end
593
593
594 def test_link_to_user_should_not_link_to_anonymous
594 def test_link_to_user_should_not_link_to_anonymous
595 user = User.anonymous
595 user = User.anonymous
596 assert user.anonymous?
596 assert user.anonymous?
597 t = link_to_user(user)
597 t = link_to_user(user)
598 assert_equal ::I18n.t(:label_user_anonymous), t
598 assert_equal ::I18n.t(:label_user_anonymous), t
599 end
599 end
600
601 def test_link_to_project
602 project = Project.find(1)
603 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
604 link_to_project(project)
605 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
606 link_to_project(project, :action => 'settings')
607 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
608 link_to_project(project, {:only_path => false, :jump => 'blah'})
609 assert_equal %(<a href="/projects/ecookbook/settings" class="project">eCookbook</a>),
610 link_to_project(project, {:action => 'settings'}, :class => "project")
611 end
600 end
612 end
General Comments 0
You need to be logged in to leave comments. Login now