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