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