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