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