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