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