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