##// END OF EJS Templates
Rails3: use String#html_safe for principals_check_box_tags() at ApplicationHelper....
Toshi MARUYAMA -
r6360:e9579b10ba40
parent child
Show More
@@ -1,966 +1,966
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(h(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.html_safe
212 s.html_safe
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.html_safe
236 s.html_safe
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.html_safe
253 s.html_safe
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.html_safe
284 s.html_safe
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.html_safe
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 # \xc2\xab(utf-8) = &#171;
356 # \xc2\xab(utf-8) = &#171;
357 html << link_to_content_update(
357 html << link_to_content_update(
358 "\xc2\xab " + l(:label_previous),
358 "\xc2\xab " + l(:label_previous),
359 url_param.merge(page_param => paginator.current.previous)) + ' '
359 url_param.merge(page_param => paginator.current.previous)) + ' '
360 end
360 end
361
361
362 html << (pagination_links_each(paginator, options) do |n|
362 html << (pagination_links_each(paginator, options) do |n|
363 link_to_content_update(n.to_s, url_param.merge(page_param => n))
363 link_to_content_update(n.to_s, url_param.merge(page_param => n))
364 end || '')
364 end || '')
365
365
366 if paginator.current.next
366 if paginator.current.next
367 # \xc2\xbb(utf-8) = &#187;
367 # \xc2\xbb(utf-8) = &#187;
368 html << ' ' + link_to_content_update(
368 html << ' ' + link_to_content_update(
369 (l(:label_next) + " \xc2\xbb"),
369 (l(:label_next) + " \xc2\xbb"),
370 url_param.merge(page_param => paginator.current.next))
370 url_param.merge(page_param => paginator.current.next))
371 end
371 end
372
372
373 unless count.nil?
373 unless count.nil?
374 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
374 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
375 if per_page_links != false && links = per_page_links(paginator.items_per_page)
375 if per_page_links != false && links = per_page_links(paginator.items_per_page)
376 html << " | #{links}"
376 html << " | #{links}"
377 end
377 end
378 end
378 end
379
379
380 html
380 html
381 end
381 end
382
382
383 def per_page_links(selected=nil)
383 def per_page_links(selected=nil)
384 links = Setting.per_page_options_array.collect do |n|
384 links = Setting.per_page_options_array.collect do |n|
385 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
385 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
386 end
386 end
387 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
387 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
388 end
388 end
389
389
390 def reorder_links(name, url)
390 def reorder_links(name, url)
391 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
391 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
392 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
392 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
393 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
393 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
394 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
394 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
395 end
395 end
396
396
397 def breadcrumb(*args)
397 def breadcrumb(*args)
398 elements = args.flatten
398 elements = args.flatten
399 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
399 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
400 end
400 end
401
401
402 def other_formats_links(&block)
402 def other_formats_links(&block)
403 concat('<p class="other-formats">' + l(:label_export_to))
403 concat('<p class="other-formats">' + l(:label_export_to))
404 yield Redmine::Views::OtherFormatsBuilder.new(self)
404 yield Redmine::Views::OtherFormatsBuilder.new(self)
405 concat('</p>')
405 concat('</p>')
406 end
406 end
407
407
408 def page_header_title
408 def page_header_title
409 if @project.nil? || @project.new_record?
409 if @project.nil? || @project.new_record?
410 h(Setting.app_title)
410 h(Setting.app_title)
411 else
411 else
412 b = []
412 b = []
413 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
413 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
414 if ancestors.any?
414 if ancestors.any?
415 root = ancestors.shift
415 root = ancestors.shift
416 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
416 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
417 if ancestors.size > 2
417 if ancestors.size > 2
418 b << '&#8230;'
418 b << '&#8230;'
419 ancestors = ancestors[-2, 2]
419 ancestors = ancestors[-2, 2]
420 end
420 end
421 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
421 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
422 end
422 end
423 b << h(@project)
423 b << h(@project)
424 b.join(' &#187; ')
424 b.join(' &#187; ')
425 end
425 end
426 end
426 end
427
427
428 def html_title(*args)
428 def html_title(*args)
429 if args.empty?
429 if args.empty?
430 title = []
430 title = []
431 title << h(@project.name) if @project
431 title << h(@project.name) if @project
432 title += @html_title if @html_title
432 title += @html_title if @html_title
433 title << Setting.app_title
433 title << Setting.app_title
434 title.select {|t| !t.blank? }.join(' - ')
434 title.select {|t| !t.blank? }.join(' - ')
435 else
435 else
436 @html_title ||= []
436 @html_title ||= []
437 @html_title += args
437 @html_title += args
438 end
438 end
439 end
439 end
440
440
441 # Returns the theme, controller name, and action as css classes for the
441 # Returns the theme, controller name, and action as css classes for the
442 # HTML body.
442 # HTML body.
443 def body_css_classes
443 def body_css_classes
444 css = []
444 css = []
445 if theme = Redmine::Themes.theme(Setting.ui_theme)
445 if theme = Redmine::Themes.theme(Setting.ui_theme)
446 css << 'theme-' + theme.name
446 css << 'theme-' + theme.name
447 end
447 end
448
448
449 css << 'controller-' + params[:controller]
449 css << 'controller-' + params[:controller]
450 css << 'action-' + params[:action]
450 css << 'action-' + params[:action]
451 css.join(' ')
451 css.join(' ')
452 end
452 end
453
453
454 def accesskey(s)
454 def accesskey(s)
455 Redmine::AccessKeys.key_for s
455 Redmine::AccessKeys.key_for s
456 end
456 end
457
457
458 # Formats text according to system settings.
458 # Formats text according to system settings.
459 # 2 ways to call this method:
459 # 2 ways to call this method:
460 # * with a String: textilizable(text, options)
460 # * with a String: textilizable(text, options)
461 # * with an object and one of its attribute: textilizable(issue, :description, options)
461 # * with an object and one of its attribute: textilizable(issue, :description, options)
462 def textilizable(*args)
462 def textilizable(*args)
463 options = args.last.is_a?(Hash) ? args.pop : {}
463 options = args.last.is_a?(Hash) ? args.pop : {}
464 case args.size
464 case args.size
465 when 1
465 when 1
466 obj = options[:object]
466 obj = options[:object]
467 text = args.shift
467 text = args.shift
468 when 2
468 when 2
469 obj = args.shift
469 obj = args.shift
470 attr = args.shift
470 attr = args.shift
471 text = obj.send(attr).to_s
471 text = obj.send(attr).to_s
472 else
472 else
473 raise ArgumentError, 'invalid arguments to textilizable'
473 raise ArgumentError, 'invalid arguments to textilizable'
474 end
474 end
475 return '' if text.blank?
475 return '' if text.blank?
476 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
476 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
477 only_path = options.delete(:only_path) == false ? false : true
477 only_path = options.delete(:only_path) == false ? false : true
478
478
479 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
479 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
480
480
481 @parsed_headings = []
481 @parsed_headings = []
482 text = parse_non_pre_blocks(text) do |text|
482 text = parse_non_pre_blocks(text) do |text|
483 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings].each do |method_name|
483 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings].each do |method_name|
484 send method_name, text, project, obj, attr, only_path, options
484 send method_name, text, project, obj, attr, only_path, options
485 end
485 end
486 end
486 end
487
487
488 if @parsed_headings.any?
488 if @parsed_headings.any?
489 replace_toc(text, @parsed_headings)
489 replace_toc(text, @parsed_headings)
490 end
490 end
491
491
492 text
492 text
493 end
493 end
494
494
495 def parse_non_pre_blocks(text)
495 def parse_non_pre_blocks(text)
496 s = StringScanner.new(text)
496 s = StringScanner.new(text)
497 tags = []
497 tags = []
498 parsed = ''
498 parsed = ''
499 while !s.eos?
499 while !s.eos?
500 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
500 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
501 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
501 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
502 if tags.empty?
502 if tags.empty?
503 yield text
503 yield text
504 end
504 end
505 parsed << text
505 parsed << text
506 if tag
506 if tag
507 if closing
507 if closing
508 if tags.last == tag.downcase
508 if tags.last == tag.downcase
509 tags.pop
509 tags.pop
510 end
510 end
511 else
511 else
512 tags << tag.downcase
512 tags << tag.downcase
513 end
513 end
514 parsed << full_tag
514 parsed << full_tag
515 end
515 end
516 end
516 end
517 # Close any non closing tags
517 # Close any non closing tags
518 while tag = tags.pop
518 while tag = tags.pop
519 parsed << "</#{tag}>"
519 parsed << "</#{tag}>"
520 end
520 end
521 parsed
521 parsed
522 end
522 end
523
523
524 def parse_inline_attachments(text, project, obj, attr, only_path, options)
524 def parse_inline_attachments(text, project, obj, attr, only_path, options)
525 # when using an image link, try to use an attachment, if possible
525 # when using an image link, try to use an attachment, if possible
526 if options[:attachments] || (obj && obj.respond_to?(:attachments))
526 if options[:attachments] || (obj && obj.respond_to?(:attachments))
527 attachments = nil
527 attachments = nil
528 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
528 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
529 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
529 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
530 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
530 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
531 # search for the picture in attachments
531 # search for the picture in attachments
532 if found = attachments.detect { |att| att.filename.downcase == filename }
532 if found = attachments.detect { |att| att.filename.downcase == filename }
533 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
533 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
534 desc = found.description.to_s.gsub('"', '')
534 desc = found.description.to_s.gsub('"', '')
535 if !desc.blank? && alttext.blank?
535 if !desc.blank? && alttext.blank?
536 alt = " title=\"#{desc}\" alt=\"#{desc}\""
536 alt = " title=\"#{desc}\" alt=\"#{desc}\""
537 end
537 end
538 "src=\"#{image_url}\"#{alt}"
538 "src=\"#{image_url}\"#{alt}"
539 else
539 else
540 m
540 m
541 end
541 end
542 end
542 end
543 end
543 end
544 end
544 end
545
545
546 # Wiki links
546 # Wiki links
547 #
547 #
548 # Examples:
548 # Examples:
549 # [[mypage]]
549 # [[mypage]]
550 # [[mypage|mytext]]
550 # [[mypage|mytext]]
551 # wiki links can refer other project wikis, using project name or identifier:
551 # wiki links can refer other project wikis, using project name or identifier:
552 # [[project:]] -> wiki starting page
552 # [[project:]] -> wiki starting page
553 # [[project:|mytext]]
553 # [[project:|mytext]]
554 # [[project:mypage]]
554 # [[project:mypage]]
555 # [[project:mypage|mytext]]
555 # [[project:mypage|mytext]]
556 def parse_wiki_links(text, project, obj, attr, only_path, options)
556 def parse_wiki_links(text, project, obj, attr, only_path, options)
557 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
557 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
558 link_project = project
558 link_project = project
559 esc, all, page, title = $1, $2, $3, $5
559 esc, all, page, title = $1, $2, $3, $5
560 if esc.nil?
560 if esc.nil?
561 if page =~ /^([^\:]+)\:(.*)$/
561 if page =~ /^([^\:]+)\:(.*)$/
562 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
562 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
563 page = $2
563 page = $2
564 title ||= $1 if page.blank?
564 title ||= $1 if page.blank?
565 end
565 end
566
566
567 if link_project && link_project.wiki
567 if link_project && link_project.wiki
568 # extract anchor
568 # extract anchor
569 anchor = nil
569 anchor = nil
570 if page =~ /^(.+?)\#(.+)$/
570 if page =~ /^(.+?)\#(.+)$/
571 page, anchor = $1, $2
571 page, anchor = $1, $2
572 end
572 end
573 # check if page exists
573 # check if page exists
574 wiki_page = link_project.wiki.find_page(page)
574 wiki_page = link_project.wiki.find_page(page)
575 url = case options[:wiki_links]
575 url = case options[:wiki_links]
576 when :local; "#{title}.html"
576 when :local; "#{title}.html"
577 when :anchor; "##{title}" # used for single-file wiki export
577 when :anchor; "##{title}" # used for single-file wiki export
578 else
578 else
579 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
579 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
580 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
580 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
581 end
581 end
582 link_to(h(title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
582 link_to(h(title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
583 else
583 else
584 # project or wiki doesn't exist
584 # project or wiki doesn't exist
585 all
585 all
586 end
586 end
587 else
587 else
588 all
588 all
589 end
589 end
590 end
590 end
591 end
591 end
592
592
593 # Redmine links
593 # Redmine links
594 #
594 #
595 # Examples:
595 # Examples:
596 # Issues:
596 # Issues:
597 # #52 -> Link to issue #52
597 # #52 -> Link to issue #52
598 # Changesets:
598 # Changesets:
599 # r52 -> Link to revision 52
599 # r52 -> Link to revision 52
600 # commit:a85130f -> Link to scmid starting with a85130f
600 # commit:a85130f -> Link to scmid starting with a85130f
601 # Documents:
601 # Documents:
602 # document#17 -> Link to document with id 17
602 # document#17 -> Link to document with id 17
603 # document:Greetings -> Link to the document with title "Greetings"
603 # document:Greetings -> Link to the document with title "Greetings"
604 # document:"Some document" -> Link to the document with title "Some document"
604 # document:"Some document" -> Link to the document with title "Some document"
605 # Versions:
605 # Versions:
606 # version#3 -> Link to version with id 3
606 # version#3 -> Link to version with id 3
607 # version:1.0.0 -> Link to version named "1.0.0"
607 # version:1.0.0 -> Link to version named "1.0.0"
608 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
608 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
609 # Attachments:
609 # Attachments:
610 # attachment:file.zip -> Link to the attachment of the current object named file.zip
610 # attachment:file.zip -> Link to the attachment of the current object named file.zip
611 # Source files:
611 # Source files:
612 # source:some/file -> Link to the file located at /some/file in the project's repository
612 # source:some/file -> Link to the file located at /some/file in the project's repository
613 # source:some/file@52 -> Link to the file's revision 52
613 # source:some/file@52 -> Link to the file's revision 52
614 # source:some/file#L120 -> Link to line 120 of the file
614 # source:some/file#L120 -> Link to line 120 of the file
615 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
615 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
616 # export:some/file -> Force the download of the file
616 # export:some/file -> Force the download of the file
617 # Forum messages:
617 # Forum messages:
618 # message#1218 -> Link to message with id 1218
618 # message#1218 -> Link to message with id 1218
619 #
619 #
620 # Links can refer other objects from other projects, using project identifier:
620 # Links can refer other objects from other projects, using project identifier:
621 # identifier:r52
621 # identifier:r52
622 # identifier:document:"Some document"
622 # identifier:document:"Some document"
623 # identifier:version:1.0.0
623 # identifier:version:1.0.0
624 # identifier:source:some/file
624 # identifier:source:some/file
625 def parse_redmine_links(text, project, obj, attr, only_path, options)
625 def parse_redmine_links(text, project, obj, attr, only_path, options)
626 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
626 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
627 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
627 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
628 link = nil
628 link = nil
629 if project_identifier
629 if project_identifier
630 project = Project.visible.find_by_identifier(project_identifier)
630 project = Project.visible.find_by_identifier(project_identifier)
631 end
631 end
632 if esc.nil?
632 if esc.nil?
633 if prefix.nil? && sep == 'r'
633 if prefix.nil? && sep == 'r'
634 # project.changesets.visible raises an SQL error because of a double join on repositories
634 # project.changesets.visible raises an SQL error because of a double join on repositories
635 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
635 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
636 link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
636 link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
637 :class => 'changeset',
637 :class => 'changeset',
638 :title => truncate_single_line(changeset.comments, :length => 100))
638 :title => truncate_single_line(changeset.comments, :length => 100))
639 end
639 end
640 elsif sep == '#'
640 elsif sep == '#'
641 oid = identifier.to_i
641 oid = identifier.to_i
642 case prefix
642 case prefix
643 when nil
643 when nil
644 if issue = Issue.visible.find_by_id(oid, :include => :status)
644 if issue = Issue.visible.find_by_id(oid, :include => :status)
645 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
645 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
646 :class => issue.css_classes,
646 :class => issue.css_classes,
647 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
647 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
648 end
648 end
649 when 'document'
649 when 'document'
650 if document = Document.visible.find_by_id(oid)
650 if document = Document.visible.find_by_id(oid)
651 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
651 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
652 :class => 'document'
652 :class => 'document'
653 end
653 end
654 when 'version'
654 when 'version'
655 if version = Version.visible.find_by_id(oid)
655 if version = Version.visible.find_by_id(oid)
656 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
656 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
657 :class => 'version'
657 :class => 'version'
658 end
658 end
659 when 'message'
659 when 'message'
660 if message = Message.visible.find_by_id(oid, :include => :parent)
660 if message = Message.visible.find_by_id(oid, :include => :parent)
661 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
661 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
662 end
662 end
663 when 'project'
663 when 'project'
664 if p = Project.visible.find_by_id(oid)
664 if p = Project.visible.find_by_id(oid)
665 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
665 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
666 end
666 end
667 end
667 end
668 elsif sep == ':'
668 elsif sep == ':'
669 # removes the double quotes if any
669 # removes the double quotes if any
670 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
670 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
671 case prefix
671 case prefix
672 when 'document'
672 when 'document'
673 if project && document = project.documents.visible.find_by_title(name)
673 if project && document = project.documents.visible.find_by_title(name)
674 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
674 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
675 :class => 'document'
675 :class => 'document'
676 end
676 end
677 when 'version'
677 when 'version'
678 if project && version = project.versions.visible.find_by_name(name)
678 if project && version = project.versions.visible.find_by_name(name)
679 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
679 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
680 :class => 'version'
680 :class => 'version'
681 end
681 end
682 when 'commit'
682 when 'commit'
683 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
683 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
684 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
684 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
685 :class => 'changeset',
685 :class => 'changeset',
686 :title => truncate_single_line(h(changeset.comments), :length => 100)
686 :title => truncate_single_line(h(changeset.comments), :length => 100)
687 end
687 end
688 when 'source', 'export'
688 when 'source', 'export'
689 if project && project.repository && User.current.allowed_to?(:browse_repository, project)
689 if project && project.repository && User.current.allowed_to?(:browse_repository, project)
690 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
690 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
691 path, rev, anchor = $1, $3, $5
691 path, rev, anchor = $1, $3, $5
692 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
692 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
693 :path => to_path_param(path),
693 :path => to_path_param(path),
694 :rev => rev,
694 :rev => rev,
695 :anchor => anchor,
695 :anchor => anchor,
696 :format => (prefix == 'export' ? 'raw' : nil)},
696 :format => (prefix == 'export' ? 'raw' : nil)},
697 :class => (prefix == 'export' ? 'source download' : 'source')
697 :class => (prefix == 'export' ? 'source download' : 'source')
698 end
698 end
699 when 'attachment'
699 when 'attachment'
700 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
700 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
701 if attachments && attachment = attachments.detect {|a| a.filename == name }
701 if attachments && attachment = attachments.detect {|a| a.filename == name }
702 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
702 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
703 :class => 'attachment'
703 :class => 'attachment'
704 end
704 end
705 when 'project'
705 when 'project'
706 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
706 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
707 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
707 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
708 end
708 end
709 end
709 end
710 end
710 end
711 end
711 end
712 leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")
712 leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")
713 end
713 end
714 end
714 end
715
715
716 HEADING_RE = /<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>/i unless const_defined?(:HEADING_RE)
716 HEADING_RE = /<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>/i unless const_defined?(:HEADING_RE)
717
717
718 # Headings and TOC
718 # Headings and TOC
719 # Adds ids and links to headings unless options[:headings] is set to false
719 # Adds ids and links to headings unless options[:headings] is set to false
720 def parse_headings(text, project, obj, attr, only_path, options)
720 def parse_headings(text, project, obj, attr, only_path, options)
721 return if options[:headings] == false
721 return if options[:headings] == false
722
722
723 text.gsub!(HEADING_RE) do
723 text.gsub!(HEADING_RE) do
724 level, attrs, content = $1.to_i, $2, $3
724 level, attrs, content = $1.to_i, $2, $3
725 item = strip_tags(content).strip
725 item = strip_tags(content).strip
726 anchor = item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
726 anchor = item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
727 @parsed_headings << [level, anchor, item]
727 @parsed_headings << [level, anchor, item]
728 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
728 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
729 end
729 end
730 end
730 end
731
731
732 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
732 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
733
733
734 # Renders the TOC with given headings
734 # Renders the TOC with given headings
735 def replace_toc(text, headings)
735 def replace_toc(text, headings)
736 text.gsub!(TOC_RE) do
736 text.gsub!(TOC_RE) do
737 if headings.empty?
737 if headings.empty?
738 ''
738 ''
739 else
739 else
740 div_class = 'toc'
740 div_class = 'toc'
741 div_class << ' right' if $1 == '>'
741 div_class << ' right' if $1 == '>'
742 div_class << ' left' if $1 == '<'
742 div_class << ' left' if $1 == '<'
743 out = "<ul class=\"#{div_class}\"><li>"
743 out = "<ul class=\"#{div_class}\"><li>"
744 root = headings.map(&:first).min
744 root = headings.map(&:first).min
745 current = root
745 current = root
746 started = false
746 started = false
747 headings.each do |level, anchor, item|
747 headings.each do |level, anchor, item|
748 if level > current
748 if level > current
749 out << '<ul><li>' * (level - current)
749 out << '<ul><li>' * (level - current)
750 elsif level < current
750 elsif level < current
751 out << "</li></ul>\n" * (current - level) + "</li><li>"
751 out << "</li></ul>\n" * (current - level) + "</li><li>"
752 elsif started
752 elsif started
753 out << '</li><li>'
753 out << '</li><li>'
754 end
754 end
755 out << "<a href=\"##{anchor}\">#{item}</a>"
755 out << "<a href=\"##{anchor}\">#{item}</a>"
756 current = level
756 current = level
757 started = true
757 started = true
758 end
758 end
759 out << '</li></ul>' * (current - root)
759 out << '</li></ul>' * (current - root)
760 out << '</li></ul>'
760 out << '</li></ul>'
761 end
761 end
762 end
762 end
763 end
763 end
764
764
765 # Same as Rails' simple_format helper without using paragraphs
765 # Same as Rails' simple_format helper without using paragraphs
766 def simple_format_without_paragraph(text)
766 def simple_format_without_paragraph(text)
767 text.to_s.
767 text.to_s.
768 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
768 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
769 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
769 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
770 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
770 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
771 end
771 end
772
772
773 def lang_options_for_select(blank=true)
773 def lang_options_for_select(blank=true)
774 (blank ? [["(auto)", ""]] : []) +
774 (blank ? [["(auto)", ""]] : []) +
775 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
775 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
776 end
776 end
777
777
778 def label_tag_for(name, option_tags = nil, options = {})
778 def label_tag_for(name, option_tags = nil, options = {})
779 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
779 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
780 content_tag("label", label_text)
780 content_tag("label", label_text)
781 end
781 end
782
782
783 def labelled_tabular_form_for(name, object, options, &proc)
783 def labelled_tabular_form_for(name, object, options, &proc)
784 options[:html] ||= {}
784 options[:html] ||= {}
785 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
785 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
786 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
786 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
787 end
787 end
788
788
789 def back_url_hidden_field_tag
789 def back_url_hidden_field_tag
790 back_url = params[:back_url] || request.env['HTTP_REFERER']
790 back_url = params[:back_url] || request.env['HTTP_REFERER']
791 back_url = CGI.unescape(back_url.to_s)
791 back_url = CGI.unescape(back_url.to_s)
792 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
792 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
793 end
793 end
794
794
795 def check_all_links(form_name)
795 def check_all_links(form_name)
796 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
796 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
797 " | " +
797 " | " +
798 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
798 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
799 end
799 end
800
800
801 def progress_bar(pcts, options={})
801 def progress_bar(pcts, options={})
802 pcts = [pcts, pcts] unless pcts.is_a?(Array)
802 pcts = [pcts, pcts] unless pcts.is_a?(Array)
803 pcts = pcts.collect(&:round)
803 pcts = pcts.collect(&:round)
804 pcts[1] = pcts[1] - pcts[0]
804 pcts[1] = pcts[1] - pcts[0]
805 pcts << (100 - pcts[1] - pcts[0])
805 pcts << (100 - pcts[1] - pcts[0])
806 width = options[:width] || '100px;'
806 width = options[:width] || '100px;'
807 legend = options[:legend] || ''
807 legend = options[:legend] || ''
808 content_tag('table',
808 content_tag('table',
809 content_tag('tr',
809 content_tag('tr',
810 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
810 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
811 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
811 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
812 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
812 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
813 ), :class => 'progress', :style => "width: #{width};") +
813 ), :class => 'progress', :style => "width: #{width};") +
814 content_tag('p', legend, :class => 'pourcent')
814 content_tag('p', legend, :class => 'pourcent')
815 end
815 end
816
816
817 def checked_image(checked=true)
817 def checked_image(checked=true)
818 if checked
818 if checked
819 image_tag 'toggle_check.png'
819 image_tag 'toggle_check.png'
820 end
820 end
821 end
821 end
822
822
823 def context_menu(url)
823 def context_menu(url)
824 unless @context_menu_included
824 unless @context_menu_included
825 content_for :header_tags do
825 content_for :header_tags do
826 javascript_include_tag('context_menu') +
826 javascript_include_tag('context_menu') +
827 stylesheet_link_tag('context_menu')
827 stylesheet_link_tag('context_menu')
828 end
828 end
829 if l(:direction) == 'rtl'
829 if l(:direction) == 'rtl'
830 content_for :header_tags do
830 content_for :header_tags do
831 stylesheet_link_tag('context_menu_rtl')
831 stylesheet_link_tag('context_menu_rtl')
832 end
832 end
833 end
833 end
834 @context_menu_included = true
834 @context_menu_included = true
835 end
835 end
836 javascript_tag "new ContextMenu('#{ url_for(url) }')"
836 javascript_tag "new ContextMenu('#{ url_for(url) }')"
837 end
837 end
838
838
839 def context_menu_link(name, url, options={})
839 def context_menu_link(name, url, options={})
840 options[:class] ||= ''
840 options[:class] ||= ''
841 if options.delete(:selected)
841 if options.delete(:selected)
842 options[:class] << ' icon-checked disabled'
842 options[:class] << ' icon-checked disabled'
843 options[:disabled] = true
843 options[:disabled] = true
844 end
844 end
845 if options.delete(:disabled)
845 if options.delete(:disabled)
846 options.delete(:method)
846 options.delete(:method)
847 options.delete(:confirm)
847 options.delete(:confirm)
848 options.delete(:onclick)
848 options.delete(:onclick)
849 options[:class] << ' disabled'
849 options[:class] << ' disabled'
850 url = '#'
850 url = '#'
851 end
851 end
852 link_to h(name), url, options
852 link_to h(name), url, options
853 end
853 end
854
854
855 def calendar_for(field_id)
855 def calendar_for(field_id)
856 include_calendar_headers_tags
856 include_calendar_headers_tags
857 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
857 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
858 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
858 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
859 end
859 end
860
860
861 def include_calendar_headers_tags
861 def include_calendar_headers_tags
862 unless @calendar_headers_tags_included
862 unless @calendar_headers_tags_included
863 @calendar_headers_tags_included = true
863 @calendar_headers_tags_included = true
864 content_for :header_tags do
864 content_for :header_tags do
865 start_of_week = case Setting.start_of_week.to_i
865 start_of_week = case Setting.start_of_week.to_i
866 when 1
866 when 1
867 'Calendar._FD = 1;' # Monday
867 'Calendar._FD = 1;' # Monday
868 when 7
868 when 7
869 'Calendar._FD = 0;' # Sunday
869 'Calendar._FD = 0;' # Sunday
870 when 6
870 when 6
871 'Calendar._FD = 6;' # Saturday
871 'Calendar._FD = 6;' # Saturday
872 else
872 else
873 '' # use language
873 '' # use language
874 end
874 end
875
875
876 javascript_include_tag('calendar/calendar') +
876 javascript_include_tag('calendar/calendar') +
877 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
877 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
878 javascript_tag(start_of_week) +
878 javascript_tag(start_of_week) +
879 javascript_include_tag('calendar/calendar-setup') +
879 javascript_include_tag('calendar/calendar-setup') +
880 stylesheet_link_tag('calendar')
880 stylesheet_link_tag('calendar')
881 end
881 end
882 end
882 end
883 end
883 end
884
884
885 def content_for(name, content = nil, &block)
885 def content_for(name, content = nil, &block)
886 @has_content ||= {}
886 @has_content ||= {}
887 @has_content[name] = true
887 @has_content[name] = true
888 super(name, content, &block)
888 super(name, content, &block)
889 end
889 end
890
890
891 def has_content?(name)
891 def has_content?(name)
892 (@has_content && @has_content[name]) || false
892 (@has_content && @has_content[name]) || false
893 end
893 end
894
894
895 def email_delivery_enabled?
895 def email_delivery_enabled?
896 !!ActionMailer::Base.perform_deliveries
896 !!ActionMailer::Base.perform_deliveries
897 end
897 end
898
898
899 # Returns the avatar image tag for the given +user+ if avatars are enabled
899 # Returns the avatar image tag for the given +user+ if avatars are enabled
900 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
900 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
901 def avatar(user, options = { })
901 def avatar(user, options = { })
902 if Setting.gravatar_enabled?
902 if Setting.gravatar_enabled?
903 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
903 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
904 email = nil
904 email = nil
905 if user.respond_to?(:mail)
905 if user.respond_to?(:mail)
906 email = user.mail
906 email = user.mail
907 elsif user.to_s =~ %r{<(.+?)>}
907 elsif user.to_s =~ %r{<(.+?)>}
908 email = $1
908 email = $1
909 end
909 end
910 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
910 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
911 else
911 else
912 ''
912 ''
913 end
913 end
914 end
914 end
915
915
916 # Returns the javascript tags that are included in the html layout head
916 # Returns the javascript tags that are included in the html layout head
917 def javascript_heads
917 def javascript_heads
918 tags = javascript_include_tag(:defaults)
918 tags = javascript_include_tag(:defaults)
919 unless User.current.pref.warn_on_leaving_unsaved == '0'
919 unless User.current.pref.warn_on_leaving_unsaved == '0'
920 tags << "\n" + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
920 tags << "\n" + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
921 end
921 end
922 tags
922 tags
923 end
923 end
924
924
925 def favicon
925 def favicon
926 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
926 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
927 end
927 end
928
928
929 def robot_exclusion_tag
929 def robot_exclusion_tag
930 '<meta name="robots" content="noindex,follow,noarchive" />'
930 '<meta name="robots" content="noindex,follow,noarchive" />'
931 end
931 end
932
932
933 # Returns true if arg is expected in the API response
933 # Returns true if arg is expected in the API response
934 def include_in_api_response?(arg)
934 def include_in_api_response?(arg)
935 unless @included_in_api_response
935 unless @included_in_api_response
936 param = params[:include]
936 param = params[:include]
937 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
937 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
938 @included_in_api_response.collect!(&:strip)
938 @included_in_api_response.collect!(&:strip)
939 end
939 end
940 @included_in_api_response.include?(arg.to_s)
940 @included_in_api_response.include?(arg.to_s)
941 end
941 end
942
942
943 # Returns options or nil if nometa param or X-Redmine-Nometa header
943 # Returns options or nil if nometa param or X-Redmine-Nometa header
944 # was set in the request
944 # was set in the request
945 def api_meta(options)
945 def api_meta(options)
946 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
946 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
947 # compatibility mode for activeresource clients that raise
947 # compatibility mode for activeresource clients that raise
948 # an error when unserializing an array with attributes
948 # an error when unserializing an array with attributes
949 nil
949 nil
950 else
950 else
951 options
951 options
952 end
952 end
953 end
953 end
954
954
955 private
955 private
956
956
957 def wiki_helper
957 def wiki_helper
958 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
958 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
959 extend helper
959 extend helper
960 return self
960 return self
961 end
961 end
962
962
963 def link_to_content_update(text, url_params = {}, html_options = {})
963 def link_to_content_update(text, url_params = {}, html_options = {})
964 link_to(text, url_params, html_options)
964 link_to(text, url_params, html_options)
965 end
965 end
966 end
966 end
General Comments 0
You need to be logged in to leave comments. Login now