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