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