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