##// END OF EJS Templates
Fixed that root projects are escaped twice in the project drop down (#11217)....
Jean-Philippe Lang -
r9711:7f6ac407ef11
parent child
Show More
@@ -1,1207 +1,1207
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2012 Jean-Philippe Lang
4 # Copyright (C) 2006-2012 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require 'forwardable'
20 require 'forwardable'
21 require 'cgi'
21 require 'cgi'
22
22
23 module ApplicationHelper
23 module ApplicationHelper
24 include Redmine::WikiFormatting::Macros::Definitions
24 include Redmine::WikiFormatting::Macros::Definitions
25 include Redmine::I18n
25 include Redmine::I18n
26 include GravatarHelper::PublicMethods
26 include GravatarHelper::PublicMethods
27
27
28 extend Forwardable
28 extend Forwardable
29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
30
30
31 # Return true if user is authorized for controller/action, otherwise false
31 # Return true if user is authorized for controller/action, otherwise false
32 def authorize_for(controller, action)
32 def authorize_for(controller, action)
33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
34 end
34 end
35
35
36 # Display a link if user is authorized
36 # Display a link if user is authorized
37 #
37 #
38 # @param [String] name Anchor text (passed to link_to)
38 # @param [String] name Anchor text (passed to link_to)
39 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
39 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
40 # @param [optional, Hash] html_options Options passed to link_to
40 # @param [optional, Hash] html_options Options passed to link_to
41 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
41 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
42 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
42 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
43 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
43 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
44 end
44 end
45
45
46 # Display a link to remote if user is authorized
46 # Display a link to remote if user is authorized
47 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
47 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
48 url = options[:url] || {}
48 url = options[:url] || {}
49 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
49 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
50 end
50 end
51
51
52 # Displays a link to user's account page if active
52 # Displays a link to user's account page if active
53 def link_to_user(user, options={})
53 def link_to_user(user, options={})
54 if user.is_a?(User)
54 if user.is_a?(User)
55 name = h(user.name(options[:format]))
55 name = h(user.name(options[:format]))
56 if user.active?
56 if user.active?
57 link_to name, :controller => 'users', :action => 'show', :id => user
57 link_to name, :controller => 'users', :action => 'show', :id => user
58 else
58 else
59 name
59 name
60 end
60 end
61 else
61 else
62 h(user.to_s)
62 h(user.to_s)
63 end
63 end
64 end
64 end
65
65
66 # Displays a link to +issue+ with its subject.
66 # Displays a link to +issue+ with its subject.
67 # Examples:
67 # Examples:
68 #
68 #
69 # link_to_issue(issue) # => Defect #6: This is the subject
69 # link_to_issue(issue) # => Defect #6: This is the subject
70 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
70 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
71 # link_to_issue(issue, :subject => false) # => Defect #6
71 # link_to_issue(issue, :subject => false) # => Defect #6
72 # link_to_issue(issue, :project => true) # => Foo - Defect #6
72 # link_to_issue(issue, :project => true) # => Foo - Defect #6
73 #
73 #
74 def link_to_issue(issue, options={})
74 def link_to_issue(issue, options={})
75 title = nil
75 title = nil
76 subject = nil
76 subject = nil
77 if options[:subject] == false
77 if options[:subject] == false
78 title = truncate(issue.subject, :length => 60)
78 title = truncate(issue.subject, :length => 60)
79 else
79 else
80 subject = issue.subject
80 subject = issue.subject
81 if options[:truncate]
81 if options[:truncate]
82 subject = truncate(subject, :length => options[:truncate])
82 subject = truncate(subject, :length => options[:truncate])
83 end
83 end
84 end
84 end
85 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
85 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
86 :class => issue.css_classes,
86 :class => issue.css_classes,
87 :title => title
87 :title => title
88 s << h(": #{subject}") if subject
88 s << h(": #{subject}") if subject
89 s = h("#{issue.project} - ") + s if options[:project]
89 s = h("#{issue.project} - ") + s if options[:project]
90 s
90 s
91 end
91 end
92
92
93 # Generates a link to an attachment.
93 # Generates a link to an attachment.
94 # Options:
94 # Options:
95 # * :text - Link text (default to attachment filename)
95 # * :text - Link text (default to attachment filename)
96 # * :download - Force download (default: false)
96 # * :download - Force download (default: false)
97 def link_to_attachment(attachment, options={})
97 def link_to_attachment(attachment, options={})
98 text = options.delete(:text) || attachment.filename
98 text = options.delete(:text) || attachment.filename
99 action = options.delete(:download) ? 'download' : 'show'
99 action = options.delete(:download) ? 'download' : 'show'
100 opt_only_path = {}
100 opt_only_path = {}
101 opt_only_path[:only_path] = (options[:only_path] == false ? false : true)
101 opt_only_path[:only_path] = (options[:only_path] == false ? false : true)
102 options.delete(:only_path)
102 options.delete(:only_path)
103 link_to(h(text),
103 link_to(h(text),
104 {:controller => 'attachments', :action => action,
104 {:controller => 'attachments', :action => action,
105 :id => attachment, :filename => attachment.filename}.merge(opt_only_path),
105 :id => attachment, :filename => attachment.filename}.merge(opt_only_path),
106 options)
106 options)
107 end
107 end
108
108
109 # Generates a link to a SCM revision
109 # Generates a link to a SCM revision
110 # Options:
110 # Options:
111 # * :text - Link text (default to the formatted revision)
111 # * :text - Link text (default to the formatted revision)
112 def link_to_revision(revision, repository, options={})
112 def link_to_revision(revision, repository, options={})
113 if repository.is_a?(Project)
113 if repository.is_a?(Project)
114 repository = repository.repository
114 repository = repository.repository
115 end
115 end
116 text = options.delete(:text) || format_revision(revision)
116 text = options.delete(:text) || format_revision(revision)
117 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
117 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
118 link_to(
118 link_to(
119 h(text),
119 h(text),
120 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
120 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
121 :title => l(:label_revision_id, format_revision(revision))
121 :title => l(:label_revision_id, format_revision(revision))
122 )
122 )
123 end
123 end
124
124
125 # Generates a link to a message
125 # Generates a link to a message
126 def link_to_message(message, options={}, html_options = nil)
126 def link_to_message(message, options={}, html_options = nil)
127 link_to(
127 link_to(
128 h(truncate(message.subject, :length => 60)),
128 h(truncate(message.subject, :length => 60)),
129 { :controller => 'messages', :action => 'show',
129 { :controller => 'messages', :action => 'show',
130 :board_id => message.board_id,
130 :board_id => message.board_id,
131 :id => message.root,
131 :id => message.root,
132 :r => (message.parent_id && message.id),
132 :r => (message.parent_id && message.id),
133 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
133 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
134 }.merge(options),
134 }.merge(options),
135 html_options
135 html_options
136 )
136 )
137 end
137 end
138
138
139 # Generates a link to a project if active
139 # Generates a link to a project if active
140 # Examples:
140 # Examples:
141 #
141 #
142 # link_to_project(project) # => link to the specified project overview
142 # link_to_project(project) # => link to the specified project overview
143 # link_to_project(project, :action=>'settings') # => link to project settings
143 # link_to_project(project, :action=>'settings') # => link to project settings
144 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
144 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
145 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
145 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
146 #
146 #
147 def link_to_project(project, options={}, html_options = nil)
147 def link_to_project(project, options={}, html_options = nil)
148 if project.archived?
148 if project.archived?
149 h(project)
149 h(project)
150 else
150 else
151 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
151 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
152 link_to(h(project), url, html_options)
152 link_to(h(project), url, html_options)
153 end
153 end
154 end
154 end
155
155
156 def toggle_link(name, id, options={})
156 def toggle_link(name, id, options={})
157 onclick = "Element.toggle('#{id}'); "
157 onclick = "Element.toggle('#{id}'); "
158 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
158 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
159 onclick << "return false;"
159 onclick << "return false;"
160 link_to(name, "#", :onclick => onclick)
160 link_to(name, "#", :onclick => onclick)
161 end
161 end
162
162
163 def image_to_function(name, function, html_options = {})
163 def image_to_function(name, function, html_options = {})
164 html_options.symbolize_keys!
164 html_options.symbolize_keys!
165 tag(:input, html_options.merge({
165 tag(:input, html_options.merge({
166 :type => "image", :src => image_path(name),
166 :type => "image", :src => image_path(name),
167 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
167 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
168 }))
168 }))
169 end
169 end
170
170
171 def prompt_to_remote(name, text, param, url, html_options = {})
171 def prompt_to_remote(name, text, param, url, html_options = {})
172 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
172 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
173 link_to name, {}, html_options
173 link_to name, {}, html_options
174 end
174 end
175
175
176 def format_activity_title(text)
176 def format_activity_title(text)
177 h(truncate_single_line(text, :length => 100))
177 h(truncate_single_line(text, :length => 100))
178 end
178 end
179
179
180 def format_activity_day(date)
180 def format_activity_day(date)
181 date == User.current.today ? l(:label_today).titleize : format_date(date)
181 date == User.current.today ? l(:label_today).titleize : format_date(date)
182 end
182 end
183
183
184 def format_activity_description(text)
184 def format_activity_description(text)
185 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
185 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
186 ).gsub(/[\r\n]+/, "<br />").html_safe
186 ).gsub(/[\r\n]+/, "<br />").html_safe
187 end
187 end
188
188
189 def format_version_name(version)
189 def format_version_name(version)
190 if version.project == @project
190 if version.project == @project
191 h(version)
191 h(version)
192 else
192 else
193 h("#{version.project} - #{version}")
193 h("#{version.project} - #{version}")
194 end
194 end
195 end
195 end
196
196
197 def due_date_distance_in_words(date)
197 def due_date_distance_in_words(date)
198 if date
198 if date
199 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
199 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
200 end
200 end
201 end
201 end
202
202
203 def render_page_hierarchy(pages, node=nil, options={})
203 def render_page_hierarchy(pages, node=nil, options={})
204 content = ''
204 content = ''
205 if pages[node]
205 if pages[node]
206 content << "<ul class=\"pages-hierarchy\">\n"
206 content << "<ul class=\"pages-hierarchy\">\n"
207 pages[node].each do |page|
207 pages[node].each do |page|
208 content << "<li>"
208 content << "<li>"
209 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
209 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
210 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
210 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
211 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
211 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
212 content << "</li>\n"
212 content << "</li>\n"
213 end
213 end
214 content << "</ul>\n"
214 content << "</ul>\n"
215 end
215 end
216 content.html_safe
216 content.html_safe
217 end
217 end
218
218
219 # Renders flash messages
219 # Renders flash messages
220 def render_flash_messages
220 def render_flash_messages
221 s = ''
221 s = ''
222 flash.each do |k,v|
222 flash.each do |k,v|
223 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
223 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
224 end
224 end
225 s.html_safe
225 s.html_safe
226 end
226 end
227
227
228 # Renders tabs and their content
228 # Renders tabs and their content
229 def render_tabs(tabs)
229 def render_tabs(tabs)
230 if tabs.any?
230 if tabs.any?
231 render :partial => 'common/tabs', :locals => {:tabs => tabs}
231 render :partial => 'common/tabs', :locals => {:tabs => tabs}
232 else
232 else
233 content_tag 'p', l(:label_no_data), :class => "nodata"
233 content_tag 'p', l(:label_no_data), :class => "nodata"
234 end
234 end
235 end
235 end
236
236
237 # Renders the project quick-jump box
237 # Renders the project quick-jump box
238 def render_project_jump_box
238 def render_project_jump_box
239 return unless User.current.logged?
239 return unless User.current.logged?
240 projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq
240 projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq
241 if projects.any?
241 if projects.any?
242 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
242 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
243 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
243 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
244 '<option value="" disabled="disabled">---</option>'
244 '<option value="" disabled="disabled">---</option>'
245 s << project_tree_options_for_select(projects, :selected => @project) do |p|
245 s << project_tree_options_for_select(projects, :selected => @project) do |p|
246 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
246 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
247 end
247 end
248 s << '</select>'
248 s << '</select>'
249 s.html_safe
249 s.html_safe
250 end
250 end
251 end
251 end
252
252
253 def project_tree_options_for_select(projects, options = {})
253 def project_tree_options_for_select(projects, options = {})
254 s = ''
254 s = ''
255 project_tree(projects) do |project, level|
255 project_tree(projects) do |project, level|
256 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ').html_safe : '')
256 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '').html_safe
257 tag_options = {:value => project.id}
257 tag_options = {:value => project.id}
258 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
258 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
259 tag_options[:selected] = 'selected'
259 tag_options[:selected] = 'selected'
260 else
260 else
261 tag_options[:selected] = nil
261 tag_options[:selected] = nil
262 end
262 end
263 tag_options.merge!(yield(project)) if block_given?
263 tag_options.merge!(yield(project)) if block_given?
264 s << content_tag('option', name_prefix + h(project), tag_options)
264 s << content_tag('option', name_prefix + h(project), tag_options)
265 end
265 end
266 s.html_safe
266 s.html_safe
267 end
267 end
268
268
269 # Yields the given block for each project with its level in the tree
269 # Yields the given block for each project with its level in the tree
270 #
270 #
271 # Wrapper for Project#project_tree
271 # Wrapper for Project#project_tree
272 def project_tree(projects, &block)
272 def project_tree(projects, &block)
273 Project.project_tree(projects, &block)
273 Project.project_tree(projects, &block)
274 end
274 end
275
275
276 def project_nested_ul(projects, &block)
276 def project_nested_ul(projects, &block)
277 s = ''
277 s = ''
278 if projects.any?
278 if projects.any?
279 ancestors = []
279 ancestors = []
280 projects.sort_by(&:lft).each do |project|
280 projects.sort_by(&:lft).each do |project|
281 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
281 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
282 s << "<ul>\n"
282 s << "<ul>\n"
283 else
283 else
284 ancestors.pop
284 ancestors.pop
285 s << "</li>"
285 s << "</li>"
286 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
286 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
287 ancestors.pop
287 ancestors.pop
288 s << "</ul></li>\n"
288 s << "</ul></li>\n"
289 end
289 end
290 end
290 end
291 s << "<li>"
291 s << "<li>"
292 s << yield(project).to_s
292 s << yield(project).to_s
293 ancestors << project
293 ancestors << project
294 end
294 end
295 s << ("</li></ul>\n" * ancestors.size)
295 s << ("</li></ul>\n" * ancestors.size)
296 end
296 end
297 s.html_safe
297 s.html_safe
298 end
298 end
299
299
300 def principals_check_box_tags(name, principals)
300 def principals_check_box_tags(name, principals)
301 s = ''
301 s = ''
302 principals.sort.each do |principal|
302 principals.sort.each do |principal|
303 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
303 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
304 end
304 end
305 s.html_safe
305 s.html_safe
306 end
306 end
307
307
308 # Returns a string for users/groups option tags
308 # Returns a string for users/groups option tags
309 def principals_options_for_select(collection, selected=nil)
309 def principals_options_for_select(collection, selected=nil)
310 s = ''
310 s = ''
311 if collection.include?(User.current)
311 if collection.include?(User.current)
312 s << content_tag('option', "<< #{l(:label_me)} >>".html_safe, :value => User.current.id)
312 s << content_tag('option', "<< #{l(:label_me)} >>".html_safe, :value => User.current.id)
313 end
313 end
314 groups = ''
314 groups = ''
315 collection.sort.each do |element|
315 collection.sort.each do |element|
316 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
316 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
317 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
317 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
318 end
318 end
319 unless groups.empty?
319 unless groups.empty?
320 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
320 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
321 end
321 end
322 s.html_safe
322 s.html_safe
323 end
323 end
324
324
325 # Truncates and returns the string as a single line
325 # Truncates and returns the string as a single line
326 def truncate_single_line(string, *args)
326 def truncate_single_line(string, *args)
327 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
327 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
328 end
328 end
329
329
330 # Truncates at line break after 250 characters or options[:length]
330 # Truncates at line break after 250 characters or options[:length]
331 def truncate_lines(string, options={})
331 def truncate_lines(string, options={})
332 length = options[:length] || 250
332 length = options[:length] || 250
333 if string.to_s =~ /\A(.{#{length}}.*?)$/m
333 if string.to_s =~ /\A(.{#{length}}.*?)$/m
334 "#{$1}..."
334 "#{$1}..."
335 else
335 else
336 string
336 string
337 end
337 end
338 end
338 end
339
339
340 def anchor(text)
340 def anchor(text)
341 text.to_s.gsub(' ', '_')
341 text.to_s.gsub(' ', '_')
342 end
342 end
343
343
344 def html_hours(text)
344 def html_hours(text)
345 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
345 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
346 end
346 end
347
347
348 def authoring(created, author, options={})
348 def authoring(created, author, options={})
349 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
349 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
350 end
350 end
351
351
352 def time_tag(time)
352 def time_tag(time)
353 text = distance_of_time_in_words(Time.now, time)
353 text = distance_of_time_in_words(Time.now, time)
354 if @project
354 if @project
355 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
355 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
356 else
356 else
357 content_tag('acronym', text, :title => format_time(time))
357 content_tag('acronym', text, :title => format_time(time))
358 end
358 end
359 end
359 end
360
360
361 def syntax_highlight_lines(name, content)
361 def syntax_highlight_lines(name, content)
362 lines = []
362 lines = []
363 syntax_highlight(name, content).each_line { |line| lines << line }
363 syntax_highlight(name, content).each_line { |line| lines << line }
364 lines
364 lines
365 end
365 end
366
366
367 def syntax_highlight(name, content)
367 def syntax_highlight(name, content)
368 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
368 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
369 end
369 end
370
370
371 def to_path_param(path)
371 def to_path_param(path)
372 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
372 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
373 str.blank? ? nil : str
373 str.blank? ? nil : str
374 end
374 end
375
375
376 def pagination_links_full(paginator, count=nil, options={})
376 def pagination_links_full(paginator, count=nil, options={})
377 page_param = options.delete(:page_param) || :page
377 page_param = options.delete(:page_param) || :page
378 per_page_links = options.delete(:per_page_links)
378 per_page_links = options.delete(:per_page_links)
379 url_param = params.dup
379 url_param = params.dup
380
380
381 html = ''
381 html = ''
382 if paginator.current.previous
382 if paginator.current.previous
383 # \xc2\xab(utf-8) = &#171;
383 # \xc2\xab(utf-8) = &#171;
384 html << link_to_content_update(
384 html << link_to_content_update(
385 "\xc2\xab " + l(:label_previous),
385 "\xc2\xab " + l(:label_previous),
386 url_param.merge(page_param => paginator.current.previous)) + ' '
386 url_param.merge(page_param => paginator.current.previous)) + ' '
387 end
387 end
388
388
389 html << (pagination_links_each(paginator, options) do |n|
389 html << (pagination_links_each(paginator, options) do |n|
390 link_to_content_update(n.to_s, url_param.merge(page_param => n))
390 link_to_content_update(n.to_s, url_param.merge(page_param => n))
391 end || '')
391 end || '')
392
392
393 if paginator.current.next
393 if paginator.current.next
394 # \xc2\xbb(utf-8) = &#187;
394 # \xc2\xbb(utf-8) = &#187;
395 html << ' ' + link_to_content_update(
395 html << ' ' + link_to_content_update(
396 (l(:label_next) + " \xc2\xbb"),
396 (l(:label_next) + " \xc2\xbb"),
397 url_param.merge(page_param => paginator.current.next))
397 url_param.merge(page_param => paginator.current.next))
398 end
398 end
399
399
400 unless count.nil?
400 unless count.nil?
401 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
401 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
402 if per_page_links != false && links = per_page_links(paginator.items_per_page, count)
402 if per_page_links != false && links = per_page_links(paginator.items_per_page, count)
403 html << " | #{links}"
403 html << " | #{links}"
404 end
404 end
405 end
405 end
406
406
407 html.html_safe
407 html.html_safe
408 end
408 end
409
409
410 def per_page_links(selected=nil, item_count=nil)
410 def per_page_links(selected=nil, item_count=nil)
411 values = Setting.per_page_options_array
411 values = Setting.per_page_options_array
412 if item_count && values.any?
412 if item_count && values.any?
413 if item_count > values.first
413 if item_count > values.first
414 max = values.detect {|value| value >= item_count} || item_count
414 max = values.detect {|value| value >= item_count} || item_count
415 else
415 else
416 max = item_count
416 max = item_count
417 end
417 end
418 values = values.select {|value| value <= max || value == selected}
418 values = values.select {|value| value <= max || value == selected}
419 end
419 end
420 if values.empty? || (values.size == 1 && values.first == selected)
420 if values.empty? || (values.size == 1 && values.first == selected)
421 return nil
421 return nil
422 end
422 end
423 links = values.collect do |n|
423 links = values.collect do |n|
424 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
424 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
425 end
425 end
426 l(:label_display_per_page, links.join(', '))
426 l(:label_display_per_page, links.join(', '))
427 end
427 end
428
428
429 def reorder_links(name, url, method = :post)
429 def reorder_links(name, url, method = :post)
430 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
430 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
431 url.merge({"#{name}[move_to]" => 'highest'}),
431 url.merge({"#{name}[move_to]" => 'highest'}),
432 :method => method, :title => l(:label_sort_highest)) +
432 :method => method, :title => l(:label_sort_highest)) +
433 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
433 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
434 url.merge({"#{name}[move_to]" => 'higher'}),
434 url.merge({"#{name}[move_to]" => 'higher'}),
435 :method => method, :title => l(:label_sort_higher)) +
435 :method => method, :title => l(:label_sort_higher)) +
436 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
436 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
437 url.merge({"#{name}[move_to]" => 'lower'}),
437 url.merge({"#{name}[move_to]" => 'lower'}),
438 :method => method, :title => l(:label_sort_lower)) +
438 :method => method, :title => l(:label_sort_lower)) +
439 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
439 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
440 url.merge({"#{name}[move_to]" => 'lowest'}),
440 url.merge({"#{name}[move_to]" => 'lowest'}),
441 :method => method, :title => l(:label_sort_lowest))
441 :method => method, :title => l(:label_sort_lowest))
442 end
442 end
443
443
444 def breadcrumb(*args)
444 def breadcrumb(*args)
445 elements = args.flatten
445 elements = args.flatten
446 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
446 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
447 end
447 end
448
448
449 def other_formats_links(&block)
449 def other_formats_links(&block)
450 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
450 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
451 yield Redmine::Views::OtherFormatsBuilder.new(self)
451 yield Redmine::Views::OtherFormatsBuilder.new(self)
452 concat('</p>'.html_safe)
452 concat('</p>'.html_safe)
453 end
453 end
454
454
455 def page_header_title
455 def page_header_title
456 if @project.nil? || @project.new_record?
456 if @project.nil? || @project.new_record?
457 h(Setting.app_title)
457 h(Setting.app_title)
458 else
458 else
459 b = []
459 b = []
460 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
460 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
461 if ancestors.any?
461 if ancestors.any?
462 root = ancestors.shift
462 root = ancestors.shift
463 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
463 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
464 if ancestors.size > 2
464 if ancestors.size > 2
465 b << "\xe2\x80\xa6"
465 b << "\xe2\x80\xa6"
466 ancestors = ancestors[-2, 2]
466 ancestors = ancestors[-2, 2]
467 end
467 end
468 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
468 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
469 end
469 end
470 b << h(@project)
470 b << h(@project)
471 b.join(" \xc2\xbb ").html_safe
471 b.join(" \xc2\xbb ").html_safe
472 end
472 end
473 end
473 end
474
474
475 def html_title(*args)
475 def html_title(*args)
476 if args.empty?
476 if args.empty?
477 title = @html_title || []
477 title = @html_title || []
478 title << @project.name if @project
478 title << @project.name if @project
479 title << Setting.app_title unless Setting.app_title == title.last
479 title << Setting.app_title unless Setting.app_title == title.last
480 title.select {|t| !t.blank? }.join(' - ')
480 title.select {|t| !t.blank? }.join(' - ')
481 else
481 else
482 @html_title ||= []
482 @html_title ||= []
483 @html_title += args
483 @html_title += args
484 end
484 end
485 end
485 end
486
486
487 # Returns the theme, controller name, and action as css classes for the
487 # Returns the theme, controller name, and action as css classes for the
488 # HTML body.
488 # HTML body.
489 def body_css_classes
489 def body_css_classes
490 css = []
490 css = []
491 if theme = Redmine::Themes.theme(Setting.ui_theme)
491 if theme = Redmine::Themes.theme(Setting.ui_theme)
492 css << 'theme-' + theme.name
492 css << 'theme-' + theme.name
493 end
493 end
494
494
495 css << 'controller-' + controller_name
495 css << 'controller-' + controller_name
496 css << 'action-' + action_name
496 css << 'action-' + action_name
497 css.join(' ')
497 css.join(' ')
498 end
498 end
499
499
500 def accesskey(s)
500 def accesskey(s)
501 Redmine::AccessKeys.key_for s
501 Redmine::AccessKeys.key_for s
502 end
502 end
503
503
504 # Formats text according to system settings.
504 # Formats text according to system settings.
505 # 2 ways to call this method:
505 # 2 ways to call this method:
506 # * with a String: textilizable(text, options)
506 # * with a String: textilizable(text, options)
507 # * with an object and one of its attribute: textilizable(issue, :description, options)
507 # * with an object and one of its attribute: textilizable(issue, :description, options)
508 def textilizable(*args)
508 def textilizable(*args)
509 options = args.last.is_a?(Hash) ? args.pop : {}
509 options = args.last.is_a?(Hash) ? args.pop : {}
510 case args.size
510 case args.size
511 when 1
511 when 1
512 obj = options[:object]
512 obj = options[:object]
513 text = args.shift
513 text = args.shift
514 when 2
514 when 2
515 obj = args.shift
515 obj = args.shift
516 attr = args.shift
516 attr = args.shift
517 text = obj.send(attr).to_s
517 text = obj.send(attr).to_s
518 else
518 else
519 raise ArgumentError, 'invalid arguments to textilizable'
519 raise ArgumentError, 'invalid arguments to textilizable'
520 end
520 end
521 return '' if text.blank?
521 return '' if text.blank?
522 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
522 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
523 only_path = options.delete(:only_path) == false ? false : true
523 only_path = options.delete(:only_path) == false ? false : true
524
524
525 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
525 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
526
526
527 @parsed_headings = []
527 @parsed_headings = []
528 @heading_anchors = {}
528 @heading_anchors = {}
529 @current_section = 0 if options[:edit_section_links]
529 @current_section = 0 if options[:edit_section_links]
530
530
531 parse_sections(text, project, obj, attr, only_path, options)
531 parse_sections(text, project, obj, attr, only_path, options)
532 text = parse_non_pre_blocks(text) do |text|
532 text = parse_non_pre_blocks(text) do |text|
533 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros].each do |method_name|
533 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros].each do |method_name|
534 send method_name, text, project, obj, attr, only_path, options
534 send method_name, text, project, obj, attr, only_path, options
535 end
535 end
536 end
536 end
537 parse_headings(text, project, obj, attr, only_path, options)
537 parse_headings(text, project, obj, attr, only_path, options)
538
538
539 if @parsed_headings.any?
539 if @parsed_headings.any?
540 replace_toc(text, @parsed_headings)
540 replace_toc(text, @parsed_headings)
541 end
541 end
542
542
543 text.html_safe
543 text.html_safe
544 end
544 end
545
545
546 def parse_non_pre_blocks(text)
546 def parse_non_pre_blocks(text)
547 s = StringScanner.new(text)
547 s = StringScanner.new(text)
548 tags = []
548 tags = []
549 parsed = ''
549 parsed = ''
550 while !s.eos?
550 while !s.eos?
551 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
551 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
552 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
552 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
553 if tags.empty?
553 if tags.empty?
554 yield text
554 yield text
555 end
555 end
556 parsed << text
556 parsed << text
557 if tag
557 if tag
558 if closing
558 if closing
559 if tags.last == tag.downcase
559 if tags.last == tag.downcase
560 tags.pop
560 tags.pop
561 end
561 end
562 else
562 else
563 tags << tag.downcase
563 tags << tag.downcase
564 end
564 end
565 parsed << full_tag
565 parsed << full_tag
566 end
566 end
567 end
567 end
568 # Close any non closing tags
568 # Close any non closing tags
569 while tag = tags.pop
569 while tag = tags.pop
570 parsed << "</#{tag}>"
570 parsed << "</#{tag}>"
571 end
571 end
572 parsed
572 parsed
573 end
573 end
574
574
575 def parse_inline_attachments(text, project, obj, attr, only_path, options)
575 def parse_inline_attachments(text, project, obj, attr, only_path, options)
576 # when using an image link, try to use an attachment, if possible
576 # when using an image link, try to use an attachment, if possible
577 if options[:attachments] || (obj && obj.respond_to?(:attachments))
577 if options[:attachments] || (obj && obj.respond_to?(:attachments))
578 attachments = options[:attachments] || obj.attachments
578 attachments = options[:attachments] || obj.attachments
579 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
579 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
580 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
580 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
581 # search for the picture in attachments
581 # search for the picture in attachments
582 if found = Attachment.latest_attach(attachments, filename)
582 if found = Attachment.latest_attach(attachments, filename)
583 image_url = url_for :only_path => only_path, :controller => 'attachments',
583 image_url = url_for :only_path => only_path, :controller => 'attachments',
584 :action => 'download', :id => found
584 :action => 'download', :id => found
585 desc = found.description.to_s.gsub('"', '')
585 desc = found.description.to_s.gsub('"', '')
586 if !desc.blank? && alttext.blank?
586 if !desc.blank? && alttext.blank?
587 alt = " title=\"#{desc}\" alt=\"#{desc}\""
587 alt = " title=\"#{desc}\" alt=\"#{desc}\""
588 end
588 end
589 "src=\"#{image_url}\"#{alt}"
589 "src=\"#{image_url}\"#{alt}"
590 else
590 else
591 m
591 m
592 end
592 end
593 end
593 end
594 end
594 end
595 end
595 end
596
596
597 # Wiki links
597 # Wiki links
598 #
598 #
599 # Examples:
599 # Examples:
600 # [[mypage]]
600 # [[mypage]]
601 # [[mypage|mytext]]
601 # [[mypage|mytext]]
602 # wiki links can refer other project wikis, using project name or identifier:
602 # wiki links can refer other project wikis, using project name or identifier:
603 # [[project:]] -> wiki starting page
603 # [[project:]] -> wiki starting page
604 # [[project:|mytext]]
604 # [[project:|mytext]]
605 # [[project:mypage]]
605 # [[project:mypage]]
606 # [[project:mypage|mytext]]
606 # [[project:mypage|mytext]]
607 def parse_wiki_links(text, project, obj, attr, only_path, options)
607 def parse_wiki_links(text, project, obj, attr, only_path, options)
608 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
608 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
609 link_project = project
609 link_project = project
610 esc, all, page, title = $1, $2, $3, $5
610 esc, all, page, title = $1, $2, $3, $5
611 if esc.nil?
611 if esc.nil?
612 if page =~ /^([^\:]+)\:(.*)$/
612 if page =~ /^([^\:]+)\:(.*)$/
613 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
613 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
614 page = $2
614 page = $2
615 title ||= $1 if page.blank?
615 title ||= $1 if page.blank?
616 end
616 end
617
617
618 if link_project && link_project.wiki
618 if link_project && link_project.wiki
619 # extract anchor
619 # extract anchor
620 anchor = nil
620 anchor = nil
621 if page =~ /^(.+?)\#(.+)$/
621 if page =~ /^(.+?)\#(.+)$/
622 page, anchor = $1, $2
622 page, anchor = $1, $2
623 end
623 end
624 anchor = sanitize_anchor_name(anchor) if anchor.present?
624 anchor = sanitize_anchor_name(anchor) if anchor.present?
625 # check if page exists
625 # check if page exists
626 wiki_page = link_project.wiki.find_page(page)
626 wiki_page = link_project.wiki.find_page(page)
627 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
627 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
628 "##{anchor}"
628 "##{anchor}"
629 else
629 else
630 case options[:wiki_links]
630 case options[:wiki_links]
631 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
631 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
632 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
632 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
633 else
633 else
634 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
634 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
635 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
635 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
636 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
636 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
637 :id => wiki_page_id, :anchor => anchor, :parent => parent)
637 :id => wiki_page_id, :anchor => anchor, :parent => parent)
638 end
638 end
639 end
639 end
640 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
640 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
641 else
641 else
642 # project or wiki doesn't exist
642 # project or wiki doesn't exist
643 all
643 all
644 end
644 end
645 else
645 else
646 all
646 all
647 end
647 end
648 end
648 end
649 end
649 end
650
650
651 # Redmine links
651 # Redmine links
652 #
652 #
653 # Examples:
653 # Examples:
654 # Issues:
654 # Issues:
655 # #52 -> Link to issue #52
655 # #52 -> Link to issue #52
656 # Changesets:
656 # Changesets:
657 # r52 -> Link to revision 52
657 # r52 -> Link to revision 52
658 # commit:a85130f -> Link to scmid starting with a85130f
658 # commit:a85130f -> Link to scmid starting with a85130f
659 # Documents:
659 # Documents:
660 # document#17 -> Link to document with id 17
660 # document#17 -> Link to document with id 17
661 # document:Greetings -> Link to the document with title "Greetings"
661 # document:Greetings -> Link to the document with title "Greetings"
662 # document:"Some document" -> Link to the document with title "Some document"
662 # document:"Some document" -> Link to the document with title "Some document"
663 # Versions:
663 # Versions:
664 # version#3 -> Link to version with id 3
664 # version#3 -> Link to version with id 3
665 # version:1.0.0 -> Link to version named "1.0.0"
665 # version:1.0.0 -> Link to version named "1.0.0"
666 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
666 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
667 # Attachments:
667 # Attachments:
668 # attachment:file.zip -> Link to the attachment of the current object named file.zip
668 # attachment:file.zip -> Link to the attachment of the current object named file.zip
669 # Source files:
669 # Source files:
670 # source:some/file -> Link to the file located at /some/file in the project's repository
670 # source:some/file -> Link to the file located at /some/file in the project's repository
671 # source:some/file@52 -> Link to the file's revision 52
671 # source:some/file@52 -> Link to the file's revision 52
672 # source:some/file#L120 -> Link to line 120 of the file
672 # source:some/file#L120 -> Link to line 120 of the file
673 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
673 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
674 # export:some/file -> Force the download of the file
674 # export:some/file -> Force the download of the file
675 # Forum messages:
675 # Forum messages:
676 # message#1218 -> Link to message with id 1218
676 # message#1218 -> Link to message with id 1218
677 #
677 #
678 # Links can refer other objects from other projects, using project identifier:
678 # Links can refer other objects from other projects, using project identifier:
679 # identifier:r52
679 # identifier:r52
680 # identifier:document:"Some document"
680 # identifier:document:"Some document"
681 # identifier:version:1.0.0
681 # identifier:version:1.0.0
682 # identifier:source:some/file
682 # identifier:source:some/file
683 def parse_redmine_links(text, project, obj, attr, only_path, options)
683 def parse_redmine_links(text, project, obj, attr, only_path, options)
684 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
684 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
685 leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
685 leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
686 link = nil
686 link = nil
687 if project_identifier
687 if project_identifier
688 project = Project.visible.find_by_identifier(project_identifier)
688 project = Project.visible.find_by_identifier(project_identifier)
689 end
689 end
690 if esc.nil?
690 if esc.nil?
691 if prefix.nil? && sep == 'r'
691 if prefix.nil? && sep == 'r'
692 if project
692 if project
693 repository = nil
693 repository = nil
694 if repo_identifier
694 if repo_identifier
695 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
695 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
696 else
696 else
697 repository = project.repository
697 repository = project.repository
698 end
698 end
699 # project.changesets.visible raises an SQL error because of a double join on repositories
699 # project.changesets.visible raises an SQL error because of a double join on repositories
700 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
700 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
701 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision},
701 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision},
702 :class => 'changeset',
702 :class => 'changeset',
703 :title => truncate_single_line(changeset.comments, :length => 100))
703 :title => truncate_single_line(changeset.comments, :length => 100))
704 end
704 end
705 end
705 end
706 elsif sep == '#'
706 elsif sep == '#'
707 oid = identifier.to_i
707 oid = identifier.to_i
708 case prefix
708 case prefix
709 when nil
709 when nil
710 if issue = Issue.visible.find_by_id(oid, :include => :status)
710 if issue = Issue.visible.find_by_id(oid, :include => :status)
711 anchor = comment_id ? "note-#{comment_id}" : nil
711 anchor = comment_id ? "note-#{comment_id}" : nil
712 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
712 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
713 :class => issue.css_classes,
713 :class => issue.css_classes,
714 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
714 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
715 end
715 end
716 when 'document'
716 when 'document'
717 if document = Document.visible.find_by_id(oid)
717 if document = Document.visible.find_by_id(oid)
718 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
718 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
719 :class => 'document'
719 :class => 'document'
720 end
720 end
721 when 'version'
721 when 'version'
722 if version = Version.visible.find_by_id(oid)
722 if version = Version.visible.find_by_id(oid)
723 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
723 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
724 :class => 'version'
724 :class => 'version'
725 end
725 end
726 when 'message'
726 when 'message'
727 if message = Message.visible.find_by_id(oid, :include => :parent)
727 if message = Message.visible.find_by_id(oid, :include => :parent)
728 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
728 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
729 end
729 end
730 when 'forum'
730 when 'forum'
731 if board = Board.visible.find_by_id(oid)
731 if board = Board.visible.find_by_id(oid)
732 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
732 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
733 :class => 'board'
733 :class => 'board'
734 end
734 end
735 when 'news'
735 when 'news'
736 if news = News.visible.find_by_id(oid)
736 if news = News.visible.find_by_id(oid)
737 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
737 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
738 :class => 'news'
738 :class => 'news'
739 end
739 end
740 when 'project'
740 when 'project'
741 if p = Project.visible.find_by_id(oid)
741 if p = Project.visible.find_by_id(oid)
742 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
742 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
743 end
743 end
744 end
744 end
745 elsif sep == ':'
745 elsif sep == ':'
746 # removes the double quotes if any
746 # removes the double quotes if any
747 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
747 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
748 case prefix
748 case prefix
749 when 'document'
749 when 'document'
750 if project && document = project.documents.visible.find_by_title(name)
750 if project && document = project.documents.visible.find_by_title(name)
751 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
751 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
752 :class => 'document'
752 :class => 'document'
753 end
753 end
754 when 'version'
754 when 'version'
755 if project && version = project.versions.visible.find_by_name(name)
755 if project && version = project.versions.visible.find_by_name(name)
756 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
756 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
757 :class => 'version'
757 :class => 'version'
758 end
758 end
759 when 'forum'
759 when 'forum'
760 if project && board = project.boards.visible.find_by_name(name)
760 if project && board = project.boards.visible.find_by_name(name)
761 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
761 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
762 :class => 'board'
762 :class => 'board'
763 end
763 end
764 when 'news'
764 when 'news'
765 if project && news = project.news.visible.find_by_title(name)
765 if project && news = project.news.visible.find_by_title(name)
766 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
766 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
767 :class => 'news'
767 :class => 'news'
768 end
768 end
769 when 'commit', 'source', 'export'
769 when 'commit', 'source', 'export'
770 if project
770 if project
771 repository = nil
771 repository = nil
772 if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
772 if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
773 repo_prefix, repo_identifier, name = $1, $2, $3
773 repo_prefix, repo_identifier, name = $1, $2, $3
774 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
774 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
775 else
775 else
776 repository = project.repository
776 repository = project.repository
777 end
777 end
778 if prefix == 'commit'
778 if prefix == 'commit'
779 if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"]))
779 if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"]))
780 link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
780 link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
781 :class => 'changeset',
781 :class => 'changeset',
782 :title => truncate_single_line(h(changeset.comments), :length => 100)
782 :title => truncate_single_line(h(changeset.comments), :length => 100)
783 end
783 end
784 else
784 else
785 if repository && User.current.allowed_to?(:browse_repository, project)
785 if repository && User.current.allowed_to?(:browse_repository, project)
786 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
786 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
787 path, rev, anchor = $1, $3, $5
787 path, rev, anchor = $1, $3, $5
788 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :repository_id => repository.identifier_param,
788 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :repository_id => repository.identifier_param,
789 :path => to_path_param(path),
789 :path => to_path_param(path),
790 :rev => rev,
790 :rev => rev,
791 :anchor => anchor,
791 :anchor => anchor,
792 :format => (prefix == 'export' ? 'raw' : nil)},
792 :format => (prefix == 'export' ? 'raw' : nil)},
793 :class => (prefix == 'export' ? 'source download' : 'source')
793 :class => (prefix == 'export' ? 'source download' : 'source')
794 end
794 end
795 end
795 end
796 repo_prefix = nil
796 repo_prefix = nil
797 end
797 end
798 when 'attachment'
798 when 'attachment'
799 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
799 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
800 if attachments && attachment = attachments.detect {|a| a.filename == name }
800 if attachments && attachment = attachments.detect {|a| a.filename == name }
801 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
801 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
802 :class => 'attachment'
802 :class => 'attachment'
803 end
803 end
804 when 'project'
804 when 'project'
805 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
805 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
806 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
806 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
807 end
807 end
808 end
808 end
809 end
809 end
810 end
810 end
811 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
811 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
812 end
812 end
813 end
813 end
814
814
815 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
815 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
816
816
817 def parse_sections(text, project, obj, attr, only_path, options)
817 def parse_sections(text, project, obj, attr, only_path, options)
818 return unless options[:edit_section_links]
818 return unless options[:edit_section_links]
819 text.gsub!(HEADING_RE) do
819 text.gsub!(HEADING_RE) do
820 heading = $1
820 heading = $1
821 @current_section += 1
821 @current_section += 1
822 if @current_section > 1
822 if @current_section > 1
823 content_tag('div',
823 content_tag('div',
824 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
824 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
825 :class => 'contextual',
825 :class => 'contextual',
826 :title => l(:button_edit_section)) + heading.html_safe
826 :title => l(:button_edit_section)) + heading.html_safe
827 else
827 else
828 heading
828 heading
829 end
829 end
830 end
830 end
831 end
831 end
832
832
833 # Headings and TOC
833 # Headings and TOC
834 # Adds ids and links to headings unless options[:headings] is set to false
834 # Adds ids and links to headings unless options[:headings] is set to false
835 def parse_headings(text, project, obj, attr, only_path, options)
835 def parse_headings(text, project, obj, attr, only_path, options)
836 return if options[:headings] == false
836 return if options[:headings] == false
837
837
838 text.gsub!(HEADING_RE) do
838 text.gsub!(HEADING_RE) do
839 level, attrs, content = $2.to_i, $3, $4
839 level, attrs, content = $2.to_i, $3, $4
840 item = strip_tags(content).strip
840 item = strip_tags(content).strip
841 anchor = sanitize_anchor_name(item)
841 anchor = sanitize_anchor_name(item)
842 # used for single-file wiki export
842 # used for single-file wiki export
843 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
843 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
844 @heading_anchors[anchor] ||= 0
844 @heading_anchors[anchor] ||= 0
845 idx = (@heading_anchors[anchor] += 1)
845 idx = (@heading_anchors[anchor] += 1)
846 if idx > 1
846 if idx > 1
847 anchor = "#{anchor}-#{idx}"
847 anchor = "#{anchor}-#{idx}"
848 end
848 end
849 @parsed_headings << [level, anchor, item]
849 @parsed_headings << [level, anchor, item]
850 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
850 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
851 end
851 end
852 end
852 end
853
853
854 MACROS_RE = /
854 MACROS_RE = /
855 (!)? # escaping
855 (!)? # escaping
856 (
856 (
857 \{\{ # opening tag
857 \{\{ # opening tag
858 ([\w]+) # macro name
858 ([\w]+) # macro name
859 (\(([^\}]*)\))? # optional arguments
859 (\(([^\}]*)\))? # optional arguments
860 \}\} # closing tag
860 \}\} # closing tag
861 )
861 )
862 /x unless const_defined?(:MACROS_RE)
862 /x unless const_defined?(:MACROS_RE)
863
863
864 # Macros substitution
864 # Macros substitution
865 def parse_macros(text, project, obj, attr, only_path, options)
865 def parse_macros(text, project, obj, attr, only_path, options)
866 text.gsub!(MACROS_RE) do
866 text.gsub!(MACROS_RE) do
867 esc, all, macro = $1, $2, $3.downcase
867 esc, all, macro = $1, $2, $3.downcase
868 args = ($5 || '').split(',').each(&:strip)
868 args = ($5 || '').split(',').each(&:strip)
869 if esc.nil?
869 if esc.nil?
870 begin
870 begin
871 exec_macro(macro, obj, args)
871 exec_macro(macro, obj, args)
872 rescue => e
872 rescue => e
873 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
873 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
874 end || all
874 end || all
875 else
875 else
876 all
876 all
877 end
877 end
878 end
878 end
879 end
879 end
880
880
881 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
881 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
882
882
883 # Renders the TOC with given headings
883 # Renders the TOC with given headings
884 def replace_toc(text, headings)
884 def replace_toc(text, headings)
885 text.gsub!(TOC_RE) do
885 text.gsub!(TOC_RE) do
886 if headings.empty?
886 if headings.empty?
887 ''
887 ''
888 else
888 else
889 div_class = 'toc'
889 div_class = 'toc'
890 div_class << ' right' if $1 == '>'
890 div_class << ' right' if $1 == '>'
891 div_class << ' left' if $1 == '<'
891 div_class << ' left' if $1 == '<'
892 out = "<ul class=\"#{div_class}\"><li>"
892 out = "<ul class=\"#{div_class}\"><li>"
893 root = headings.map(&:first).min
893 root = headings.map(&:first).min
894 current = root
894 current = root
895 started = false
895 started = false
896 headings.each do |level, anchor, item|
896 headings.each do |level, anchor, item|
897 if level > current
897 if level > current
898 out << '<ul><li>' * (level - current)
898 out << '<ul><li>' * (level - current)
899 elsif level < current
899 elsif level < current
900 out << "</li></ul>\n" * (current - level) + "</li><li>"
900 out << "</li></ul>\n" * (current - level) + "</li><li>"
901 elsif started
901 elsif started
902 out << '</li><li>'
902 out << '</li><li>'
903 end
903 end
904 out << "<a href=\"##{anchor}\">#{item}</a>"
904 out << "<a href=\"##{anchor}\">#{item}</a>"
905 current = level
905 current = level
906 started = true
906 started = true
907 end
907 end
908 out << '</li></ul>' * (current - root)
908 out << '</li></ul>' * (current - root)
909 out << '</li></ul>'
909 out << '</li></ul>'
910 end
910 end
911 end
911 end
912 end
912 end
913
913
914 # Same as Rails' simple_format helper without using paragraphs
914 # Same as Rails' simple_format helper without using paragraphs
915 def simple_format_without_paragraph(text)
915 def simple_format_without_paragraph(text)
916 text.to_s.
916 text.to_s.
917 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
917 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
918 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
918 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
919 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
919 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
920 html_safe
920 html_safe
921 end
921 end
922
922
923 def lang_options_for_select(blank=true)
923 def lang_options_for_select(blank=true)
924 (blank ? [["(auto)", ""]] : []) +
924 (blank ? [["(auto)", ""]] : []) +
925 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
925 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
926 end
926 end
927
927
928 def label_tag_for(name, option_tags = nil, options = {})
928 def label_tag_for(name, option_tags = nil, options = {})
929 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
929 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
930 content_tag("label", label_text)
930 content_tag("label", label_text)
931 end
931 end
932
932
933 def labelled_tabular_form_for(*args, &proc)
933 def labelled_tabular_form_for(*args, &proc)
934 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_tabular_form_for is deprecated and will be removed in Redmine 1.5. Use #labelled_form_for instead."
934 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_tabular_form_for is deprecated and will be removed in Redmine 1.5. Use #labelled_form_for instead."
935 args << {} unless args.last.is_a?(Hash)
935 args << {} unless args.last.is_a?(Hash)
936 options = args.last
936 options = args.last
937 options[:html] ||= {}
937 options[:html] ||= {}
938 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
938 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
939 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
939 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
940 form_for(*args, &proc)
940 form_for(*args, &proc)
941 end
941 end
942
942
943 def labelled_form_for(*args, &proc)
943 def labelled_form_for(*args, &proc)
944 args << {} unless args.last.is_a?(Hash)
944 args << {} unless args.last.is_a?(Hash)
945 options = args.last
945 options = args.last
946 if args.first.is_a?(Symbol)
946 if args.first.is_a?(Symbol)
947 options.merge!(:as => args.shift)
947 options.merge!(:as => args.shift)
948 end
948 end
949 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
949 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
950 form_for(*args, &proc)
950 form_for(*args, &proc)
951 end
951 end
952
952
953 def labelled_fields_for(*args, &proc)
953 def labelled_fields_for(*args, &proc)
954 args << {} unless args.last.is_a?(Hash)
954 args << {} unless args.last.is_a?(Hash)
955 options = args.last
955 options = args.last
956 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
956 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
957 fields_for(*args, &proc)
957 fields_for(*args, &proc)
958 end
958 end
959
959
960 def labelled_remote_form_for(*args, &proc)
960 def labelled_remote_form_for(*args, &proc)
961 args << {} unless args.last.is_a?(Hash)
961 args << {} unless args.last.is_a?(Hash)
962 options = args.last
962 options = args.last
963 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
963 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
964 form_for(*args, &proc)
964 form_for(*args, &proc)
965 end
965 end
966
966
967 def error_messages_for(*objects)
967 def error_messages_for(*objects)
968 html = ""
968 html = ""
969 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
969 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
970 errors = objects.map {|o| o.errors.full_messages}.flatten
970 errors = objects.map {|o| o.errors.full_messages}.flatten
971 if errors.any?
971 if errors.any?
972 html << "<div id='errorExplanation'><ul>\n"
972 html << "<div id='errorExplanation'><ul>\n"
973 errors.each do |error|
973 errors.each do |error|
974 html << "<li>#{h error}</li>\n"
974 html << "<li>#{h error}</li>\n"
975 end
975 end
976 html << "</ul></div>\n"
976 html << "</ul></div>\n"
977 end
977 end
978 html.html_safe
978 html.html_safe
979 end
979 end
980
980
981 def back_url_hidden_field_tag
981 def back_url_hidden_field_tag
982 back_url = params[:back_url] || request.env['HTTP_REFERER']
982 back_url = params[:back_url] || request.env['HTTP_REFERER']
983 back_url = CGI.unescape(back_url.to_s)
983 back_url = CGI.unescape(back_url.to_s)
984 hidden_field_tag('back_url', CGI.escape(back_url), :id => nil) unless back_url.blank?
984 hidden_field_tag('back_url', CGI.escape(back_url), :id => nil) unless back_url.blank?
985 end
985 end
986
986
987 def check_all_links(form_name)
987 def check_all_links(form_name)
988 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
988 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
989 " | ".html_safe +
989 " | ".html_safe +
990 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
990 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
991 end
991 end
992
992
993 def progress_bar(pcts, options={})
993 def progress_bar(pcts, options={})
994 pcts = [pcts, pcts] unless pcts.is_a?(Array)
994 pcts = [pcts, pcts] unless pcts.is_a?(Array)
995 pcts = pcts.collect(&:round)
995 pcts = pcts.collect(&:round)
996 pcts[1] = pcts[1] - pcts[0]
996 pcts[1] = pcts[1] - pcts[0]
997 pcts << (100 - pcts[1] - pcts[0])
997 pcts << (100 - pcts[1] - pcts[0])
998 width = options[:width] || '100px;'
998 width = options[:width] || '100px;'
999 legend = options[:legend] || ''
999 legend = options[:legend] || ''
1000 content_tag('table',
1000 content_tag('table',
1001 content_tag('tr',
1001 content_tag('tr',
1002 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1002 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1003 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1003 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1004 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1004 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1005 ), :class => 'progress', :style => "width: #{width};").html_safe +
1005 ), :class => 'progress', :style => "width: #{width};").html_safe +
1006 content_tag('p', legend, :class => 'pourcent').html_safe
1006 content_tag('p', legend, :class => 'pourcent').html_safe
1007 end
1007 end
1008
1008
1009 def checked_image(checked=true)
1009 def checked_image(checked=true)
1010 if checked
1010 if checked
1011 image_tag 'toggle_check.png'
1011 image_tag 'toggle_check.png'
1012 end
1012 end
1013 end
1013 end
1014
1014
1015 def context_menu(url)
1015 def context_menu(url)
1016 unless @context_menu_included
1016 unless @context_menu_included
1017 content_for :header_tags do
1017 content_for :header_tags do
1018 javascript_include_tag('context_menu') +
1018 javascript_include_tag('context_menu') +
1019 stylesheet_link_tag('context_menu')
1019 stylesheet_link_tag('context_menu')
1020 end
1020 end
1021 if l(:direction) == 'rtl'
1021 if l(:direction) == 'rtl'
1022 content_for :header_tags do
1022 content_for :header_tags do
1023 stylesheet_link_tag('context_menu_rtl')
1023 stylesheet_link_tag('context_menu_rtl')
1024 end
1024 end
1025 end
1025 end
1026 @context_menu_included = true
1026 @context_menu_included = true
1027 end
1027 end
1028 javascript_tag "new ContextMenu('#{ url_for(url) }')"
1028 javascript_tag "new ContextMenu('#{ url_for(url) }')"
1029 end
1029 end
1030
1030
1031 def calendar_for(field_id)
1031 def calendar_for(field_id)
1032 include_calendar_headers_tags
1032 include_calendar_headers_tags
1033 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
1033 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
1034 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
1034 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
1035 end
1035 end
1036
1036
1037 def include_calendar_headers_tags
1037 def include_calendar_headers_tags
1038 unless @calendar_headers_tags_included
1038 unless @calendar_headers_tags_included
1039 @calendar_headers_tags_included = true
1039 @calendar_headers_tags_included = true
1040 content_for :header_tags do
1040 content_for :header_tags do
1041 start_of_week = case Setting.start_of_week.to_i
1041 start_of_week = case Setting.start_of_week.to_i
1042 when 1
1042 when 1
1043 'Calendar._FD = 1;' # Monday
1043 'Calendar._FD = 1;' # Monday
1044 when 7
1044 when 7
1045 'Calendar._FD = 0;' # Sunday
1045 'Calendar._FD = 0;' # Sunday
1046 when 6
1046 when 6
1047 'Calendar._FD = 6;' # Saturday
1047 'Calendar._FD = 6;' # Saturday
1048 else
1048 else
1049 '' # use language
1049 '' # use language
1050 end
1050 end
1051
1051
1052 javascript_include_tag('calendar/calendar') +
1052 javascript_include_tag('calendar/calendar') +
1053 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
1053 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
1054 javascript_tag(start_of_week) +
1054 javascript_tag(start_of_week) +
1055 javascript_include_tag('calendar/calendar-setup') +
1055 javascript_include_tag('calendar/calendar-setup') +
1056 stylesheet_link_tag('calendar')
1056 stylesheet_link_tag('calendar')
1057 end
1057 end
1058 end
1058 end
1059 end
1059 end
1060
1060
1061 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1061 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1062 # Examples:
1062 # Examples:
1063 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1063 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1064 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1064 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1065 #
1065 #
1066 def stylesheet_link_tag(*sources)
1066 def stylesheet_link_tag(*sources)
1067 options = sources.last.is_a?(Hash) ? sources.pop : {}
1067 options = sources.last.is_a?(Hash) ? sources.pop : {}
1068 plugin = options.delete(:plugin)
1068 plugin = options.delete(:plugin)
1069 sources = sources.map do |source|
1069 sources = sources.map do |source|
1070 if plugin
1070 if plugin
1071 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1071 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1072 elsif current_theme && current_theme.stylesheets.include?(source)
1072 elsif current_theme && current_theme.stylesheets.include?(source)
1073 current_theme.stylesheet_path(source)
1073 current_theme.stylesheet_path(source)
1074 else
1074 else
1075 source
1075 source
1076 end
1076 end
1077 end
1077 end
1078 super sources, options
1078 super sources, options
1079 end
1079 end
1080
1080
1081 # Overrides Rails' image_tag with themes and plugins support.
1081 # Overrides Rails' image_tag with themes and plugins support.
1082 # Examples:
1082 # Examples:
1083 # image_tag('image.png') # => picks image.png from the current theme or defaults
1083 # image_tag('image.png') # => picks image.png from the current theme or defaults
1084 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1084 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1085 #
1085 #
1086 def image_tag(source, options={})
1086 def image_tag(source, options={})
1087 if plugin = options.delete(:plugin)
1087 if plugin = options.delete(:plugin)
1088 source = "/plugin_assets/#{plugin}/images/#{source}"
1088 source = "/plugin_assets/#{plugin}/images/#{source}"
1089 elsif current_theme && current_theme.images.include?(source)
1089 elsif current_theme && current_theme.images.include?(source)
1090 source = current_theme.image_path(source)
1090 source = current_theme.image_path(source)
1091 end
1091 end
1092 super source, options
1092 super source, options
1093 end
1093 end
1094
1094
1095 # Overrides Rails' javascript_include_tag with plugins support
1095 # Overrides Rails' javascript_include_tag with plugins support
1096 # Examples:
1096 # Examples:
1097 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1097 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1098 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1098 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1099 #
1099 #
1100 def javascript_include_tag(*sources)
1100 def javascript_include_tag(*sources)
1101 options = sources.last.is_a?(Hash) ? sources.pop : {}
1101 options = sources.last.is_a?(Hash) ? sources.pop : {}
1102 if plugin = options.delete(:plugin)
1102 if plugin = options.delete(:plugin)
1103 sources = sources.map do |source|
1103 sources = sources.map do |source|
1104 if plugin
1104 if plugin
1105 "/plugin_assets/#{plugin}/javascripts/#{source}"
1105 "/plugin_assets/#{plugin}/javascripts/#{source}"
1106 else
1106 else
1107 source
1107 source
1108 end
1108 end
1109 end
1109 end
1110 end
1110 end
1111 super sources, options
1111 super sources, options
1112 end
1112 end
1113
1113
1114 def content_for(name, content = nil, &block)
1114 def content_for(name, content = nil, &block)
1115 @has_content ||= {}
1115 @has_content ||= {}
1116 @has_content[name] = true
1116 @has_content[name] = true
1117 super(name, content, &block)
1117 super(name, content, &block)
1118 end
1118 end
1119
1119
1120 def has_content?(name)
1120 def has_content?(name)
1121 (@has_content && @has_content[name]) || false
1121 (@has_content && @has_content[name]) || false
1122 end
1122 end
1123
1123
1124 def sidebar_content?
1124 def sidebar_content?
1125 has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1125 has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1126 end
1126 end
1127
1127
1128 def view_layouts_base_sidebar_hook_response
1128 def view_layouts_base_sidebar_hook_response
1129 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1129 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1130 end
1130 end
1131
1131
1132 def email_delivery_enabled?
1132 def email_delivery_enabled?
1133 !!ActionMailer::Base.perform_deliveries
1133 !!ActionMailer::Base.perform_deliveries
1134 end
1134 end
1135
1135
1136 # Returns the avatar image tag for the given +user+ if avatars are enabled
1136 # Returns the avatar image tag for the given +user+ if avatars are enabled
1137 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1137 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1138 def avatar(user, options = { })
1138 def avatar(user, options = { })
1139 if Setting.gravatar_enabled?
1139 if Setting.gravatar_enabled?
1140 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1140 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1141 email = nil
1141 email = nil
1142 if user.respond_to?(:mail)
1142 if user.respond_to?(:mail)
1143 email = user.mail
1143 email = user.mail
1144 elsif user.to_s =~ %r{<(.+?)>}
1144 elsif user.to_s =~ %r{<(.+?)>}
1145 email = $1
1145 email = $1
1146 end
1146 end
1147 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1147 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1148 else
1148 else
1149 ''
1149 ''
1150 end
1150 end
1151 end
1151 end
1152
1152
1153 def sanitize_anchor_name(anchor)
1153 def sanitize_anchor_name(anchor)
1154 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1154 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1155 end
1155 end
1156
1156
1157 # Returns the javascript tags that are included in the html layout head
1157 # Returns the javascript tags that are included in the html layout head
1158 def javascript_heads
1158 def javascript_heads
1159 tags = javascript_include_tag('prototype', 'effects', 'dragdrop', 'controls', 'rails', 'application')
1159 tags = javascript_include_tag('prototype', 'effects', 'dragdrop', 'controls', 'rails', 'application')
1160 unless User.current.pref.warn_on_leaving_unsaved == '0'
1160 unless User.current.pref.warn_on_leaving_unsaved == '0'
1161 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
1161 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
1162 end
1162 end
1163 tags
1163 tags
1164 end
1164 end
1165
1165
1166 def favicon
1166 def favicon
1167 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1167 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1168 end
1168 end
1169
1169
1170 def robot_exclusion_tag
1170 def robot_exclusion_tag
1171 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1171 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1172 end
1172 end
1173
1173
1174 # Returns true if arg is expected in the API response
1174 # Returns true if arg is expected in the API response
1175 def include_in_api_response?(arg)
1175 def include_in_api_response?(arg)
1176 unless @included_in_api_response
1176 unless @included_in_api_response
1177 param = params[:include]
1177 param = params[:include]
1178 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1178 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1179 @included_in_api_response.collect!(&:strip)
1179 @included_in_api_response.collect!(&:strip)
1180 end
1180 end
1181 @included_in_api_response.include?(arg.to_s)
1181 @included_in_api_response.include?(arg.to_s)
1182 end
1182 end
1183
1183
1184 # Returns options or nil if nometa param or X-Redmine-Nometa header
1184 # Returns options or nil if nometa param or X-Redmine-Nometa header
1185 # was set in the request
1185 # was set in the request
1186 def api_meta(options)
1186 def api_meta(options)
1187 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1187 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1188 # compatibility mode for activeresource clients that raise
1188 # compatibility mode for activeresource clients that raise
1189 # an error when unserializing an array with attributes
1189 # an error when unserializing an array with attributes
1190 nil
1190 nil
1191 else
1191 else
1192 options
1192 options
1193 end
1193 end
1194 end
1194 end
1195
1195
1196 private
1196 private
1197
1197
1198 def wiki_helper
1198 def wiki_helper
1199 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1199 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1200 extend helper
1200 extend helper
1201 return self
1201 return self
1202 end
1202 end
1203
1203
1204 def link_to_content_update(text, url_params = {}, html_options = {})
1204 def link_to_content_update(text, url_params = {}, html_options = {})
1205 link_to(text, url_params, html_options)
1205 link_to(text, url_params, html_options)
1206 end
1206 end
1207 end
1207 end
@@ -1,155 +1,165
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19 require 'welcome_controller'
19 require 'welcome_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class WelcomeController; def rescue_action(e) raise e end; end
22 class WelcomeController; def rescue_action(e) raise e end; end
23
23
24 class WelcomeControllerTest < ActionController::TestCase
24 class WelcomeControllerTest < ActionController::TestCase
25 fixtures :projects, :news
25 fixtures :projects, :news
26
26
27 def setup
27 def setup
28 @controller = WelcomeController.new
28 @controller = WelcomeController.new
29 @request = ActionController::TestRequest.new
29 @request = ActionController::TestRequest.new
30 @response = ActionController::TestResponse.new
30 @response = ActionController::TestResponse.new
31 User.current = nil
31 User.current = nil
32 end
32 end
33
33
34 def test_index
34 def test_index
35 get :index
35 get :index
36 assert_response :success
36 assert_response :success
37 assert_template 'index'
37 assert_template 'index'
38 assert_not_nil assigns(:news)
38 assert_not_nil assigns(:news)
39 assert_not_nil assigns(:projects)
39 assert_not_nil assigns(:projects)
40 assert !assigns(:projects).include?(Project.find(:first, :conditions => {:is_public => false}))
40 assert !assigns(:projects).include?(Project.find(:first, :conditions => {:is_public => false}))
41 end
41 end
42
42
43 def test_browser_language
43 def test_browser_language
44 Setting.default_language = 'en'
44 Setting.default_language = 'en'
45 @request.env['HTTP_ACCEPT_LANGUAGE'] = 'fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3'
45 @request.env['HTTP_ACCEPT_LANGUAGE'] = 'fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3'
46 get :index
46 get :index
47 assert_equal :fr, @controller.current_language
47 assert_equal :fr, @controller.current_language
48 end
48 end
49
49
50 def test_browser_language_alternate
50 def test_browser_language_alternate
51 Setting.default_language = 'en'
51 Setting.default_language = 'en'
52 @request.env['HTTP_ACCEPT_LANGUAGE'] = 'zh-TW'
52 @request.env['HTTP_ACCEPT_LANGUAGE'] = 'zh-TW'
53 get :index
53 get :index
54 assert_equal :"zh-TW", @controller.current_language
54 assert_equal :"zh-TW", @controller.current_language
55 end
55 end
56
56
57 def test_browser_language_alternate_not_valid
57 def test_browser_language_alternate_not_valid
58 Setting.default_language = 'en'
58 Setting.default_language = 'en'
59 @request.env['HTTP_ACCEPT_LANGUAGE'] = 'fr-CA'
59 @request.env['HTTP_ACCEPT_LANGUAGE'] = 'fr-CA'
60 get :index
60 get :index
61 assert_equal :fr, @controller.current_language
61 assert_equal :fr, @controller.current_language
62 end
62 end
63
63
64 def test_robots
64 def test_robots
65 get :robots
65 get :robots
66 assert_response :success
66 assert_response :success
67 assert_equal 'text/plain', @response.content_type
67 assert_equal 'text/plain', @response.content_type
68 assert @response.body.match(%r{^Disallow: /projects/ecookbook/issues\r?$})
68 assert @response.body.match(%r{^Disallow: /projects/ecookbook/issues\r?$})
69 end
69 end
70
70
71 def test_warn_on_leaving_unsaved_turn_on
71 def test_warn_on_leaving_unsaved_turn_on
72 user = User.find(2)
72 user = User.find(2)
73 user.pref.warn_on_leaving_unsaved = '1'
73 user.pref.warn_on_leaving_unsaved = '1'
74 user.pref.save!
74 user.pref.save!
75 @request.session[:user_id] = 2
75 @request.session[:user_id] = 2
76
76
77 get :index
77 get :index
78 assert_tag 'script',
78 assert_tag 'script',
79 :attributes => {:type => "text/javascript"},
79 :attributes => {:type => "text/javascript"},
80 :content => %r{new WarnLeavingUnsaved}
80 :content => %r{new WarnLeavingUnsaved}
81 end
81 end
82
82
83 def test_warn_on_leaving_unsaved_turn_off
83 def test_warn_on_leaving_unsaved_turn_off
84 user = User.find(2)
84 user = User.find(2)
85 user.pref.warn_on_leaving_unsaved = '0'
85 user.pref.warn_on_leaving_unsaved = '0'
86 user.pref.save!
86 user.pref.save!
87 @request.session[:user_id] = 2
87 @request.session[:user_id] = 2
88
88
89 get :index
89 get :index
90 assert_no_tag 'script',
90 assert_no_tag 'script',
91 :attributes => {:type => "text/javascript"},
91 :attributes => {:type => "text/javascript"},
92 :content => %r{new WarnLeavingUnsaved}
92 :content => %r{new WarnLeavingUnsaved}
93 end
93 end
94
94
95 def test_call_hook_mixed_in
95 def test_call_hook_mixed_in
96 assert @controller.respond_to?(:call_hook)
96 assert @controller.respond_to?(:call_hook)
97 end
97 end
98
98
99 def test_project_jump_box_should_escape_names_once
100 Project.find(1).update_attribute :name, 'Foo & Bar'
101 @request.session[:user_id] = 2
102
103 get :index
104 assert_select "#header select" do
105 assert_select "option", :text => 'Foo &amp; Bar'
106 end
107 end
108
99 context "test_api_offset_and_limit" do
109 context "test_api_offset_and_limit" do
100 context "without params" do
110 context "without params" do
101 should "return 0, 25" do
111 should "return 0, 25" do
102 assert_equal [0, 25], @controller.api_offset_and_limit({})
112 assert_equal [0, 25], @controller.api_offset_and_limit({})
103 end
113 end
104 end
114 end
105
115
106 context "with limit" do
116 context "with limit" do
107 should "return 0, limit" do
117 should "return 0, limit" do
108 assert_equal [0, 30], @controller.api_offset_and_limit({:limit => 30})
118 assert_equal [0, 30], @controller.api_offset_and_limit({:limit => 30})
109 end
119 end
110
120
111 should "not exceed 100" do
121 should "not exceed 100" do
112 assert_equal [0, 100], @controller.api_offset_and_limit({:limit => 120})
122 assert_equal [0, 100], @controller.api_offset_and_limit({:limit => 120})
113 end
123 end
114
124
115 should "not be negative" do
125 should "not be negative" do
116 assert_equal [0, 25], @controller.api_offset_and_limit({:limit => -10})
126 assert_equal [0, 25], @controller.api_offset_and_limit({:limit => -10})
117 end
127 end
118 end
128 end
119
129
120 context "with offset" do
130 context "with offset" do
121 should "return offset, 25" do
131 should "return offset, 25" do
122 assert_equal [10, 25], @controller.api_offset_and_limit({:offset => 10})
132 assert_equal [10, 25], @controller.api_offset_and_limit({:offset => 10})
123 end
133 end
124
134
125 should "not be negative" do
135 should "not be negative" do
126 assert_equal [0, 25], @controller.api_offset_and_limit({:offset => -10})
136 assert_equal [0, 25], @controller.api_offset_and_limit({:offset => -10})
127 end
137 end
128
138
129 context "and limit" do
139 context "and limit" do
130 should "return offset, limit" do
140 should "return offset, limit" do
131 assert_equal [10, 50], @controller.api_offset_and_limit({:offset => 10, :limit => 50})
141 assert_equal [10, 50], @controller.api_offset_and_limit({:offset => 10, :limit => 50})
132 end
142 end
133 end
143 end
134 end
144 end
135
145
136 context "with page" do
146 context "with page" do
137 should "return offset, 25" do
147 should "return offset, 25" do
138 assert_equal [0, 25], @controller.api_offset_and_limit({:page => 1})
148 assert_equal [0, 25], @controller.api_offset_and_limit({:page => 1})
139 assert_equal [50, 25], @controller.api_offset_and_limit({:page => 3})
149 assert_equal [50, 25], @controller.api_offset_and_limit({:page => 3})
140 end
150 end
141
151
142 should "not be negative" do
152 should "not be negative" do
143 assert_equal [0, 25], @controller.api_offset_and_limit({:page => 0})
153 assert_equal [0, 25], @controller.api_offset_and_limit({:page => 0})
144 assert_equal [0, 25], @controller.api_offset_and_limit({:page => -2})
154 assert_equal [0, 25], @controller.api_offset_and_limit({:page => -2})
145 end
155 end
146
156
147 context "and limit" do
157 context "and limit" do
148 should "return offset, limit" do
158 should "return offset, limit" do
149 assert_equal [0, 100], @controller.api_offset_and_limit({:page => 1, :limit => 100})
159 assert_equal [0, 100], @controller.api_offset_and_limit({:page => 1, :limit => 100})
150 assert_equal [200, 100], @controller.api_offset_and_limit({:page => 3, :limit => 100})
160 assert_equal [200, 100], @controller.api_offset_and_limit({:page => 3, :limit => 100})
151 end
161 end
152 end
162 end
153 end
163 end
154 end
164 end
155 end
165 end
General Comments 0
You need to be logged in to leave comments. Login now