##// END OF EJS Templates
Fixed relative "source" links in email notifications (#3483)....
Jean-Philippe Lang -
r12944:5f4ba3d6d5a7
parent child
Show More
@@ -1,1353 +1,1353
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 name = CGI.unescapeHTML(name)
825 name = CGI.unescapeHTML(name)
826 case prefix
826 case prefix
827 when 'document'
827 when 'document'
828 if project && document = project.documents.visible.find_by_title(name)
828 if project && document = project.documents.visible.find_by_title(name)
829 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
829 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
830 :class => 'document'
830 :class => 'document'
831 end
831 end
832 when 'version'
832 when 'version'
833 if project && version = project.versions.visible.find_by_name(name)
833 if project && version = project.versions.visible.find_by_name(name)
834 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
834 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
835 :class => 'version'
835 :class => 'version'
836 end
836 end
837 when 'forum'
837 when 'forum'
838 if project && board = project.boards.visible.find_by_name(name)
838 if project && board = project.boards.visible.find_by_name(name)
839 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
839 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
840 :class => 'board'
840 :class => 'board'
841 end
841 end
842 when 'news'
842 when 'news'
843 if project && news = project.news.visible.find_by_title(name)
843 if project && news = project.news.visible.find_by_title(name)
844 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
844 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
845 :class => 'news'
845 :class => 'news'
846 end
846 end
847 when 'commit', 'source', 'export'
847 when 'commit', 'source', 'export'
848 if project
848 if project
849 repository = nil
849 repository = nil
850 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
850 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
851 repo_prefix, repo_identifier, name = $1, $2, $3
851 repo_prefix, repo_identifier, name = $1, $2, $3
852 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
852 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
853 else
853 else
854 repository = project.repository
854 repository = project.repository
855 end
855 end
856 if prefix == 'commit'
856 if prefix == 'commit'
857 if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
857 if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
858 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 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},
859 :class => 'changeset',
859 :class => 'changeset',
860 :title => truncate_single_line_raw(changeset.comments, 100)
860 :title => truncate_single_line_raw(changeset.comments, 100)
861 end
861 end
862 else
862 else
863 if repository && User.current.allowed_to?(:browse_repository, project)
863 if repository && User.current.allowed_to?(:browse_repository, project)
864 name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
864 name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
865 path, rev, anchor = $1, $3, $5
865 path, rev, anchor = $1, $3, $5
866 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 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param,
867 :path => to_path_param(path),
867 :path => to_path_param(path),
868 :rev => rev,
868 :rev => rev,
869 :anchor => anchor},
869 :anchor => anchor},
870 :class => (prefix == 'export' ? 'source download' : 'source')
870 :class => (prefix == 'export' ? 'source download' : 'source')
871 end
871 end
872 end
872 end
873 repo_prefix = nil
873 repo_prefix = nil
874 end
874 end
875 when 'attachment'
875 when 'attachment'
876 attachments = options[:attachments] || []
876 attachments = options[:attachments] || []
877 attachments += obj.attachments if obj.respond_to?(:attachments)
877 attachments += obj.attachments if obj.respond_to?(:attachments)
878 if attachments && attachment = Attachment.latest_attach(attachments, name)
878 if attachments && attachment = Attachment.latest_attach(attachments, name)
879 link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
879 link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
880 end
880 end
881 when 'project'
881 when 'project'
882 if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
882 if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
883 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
883 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
884 end
884 end
885 end
885 end
886 end
886 end
887 end
887 end
888 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
888 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
889 end
889 end
890 end
890 end
891
891
892 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
892 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
893
893
894 def parse_sections(text, project, obj, attr, only_path, options)
894 def parse_sections(text, project, obj, attr, only_path, options)
895 return unless options[:edit_section_links]
895 return unless options[:edit_section_links]
896 text.gsub!(HEADING_RE) do
896 text.gsub!(HEADING_RE) do
897 heading = $1
897 heading = $1
898 @current_section += 1
898 @current_section += 1
899 if @current_section > 1
899 if @current_section > 1
900 content_tag('div',
900 content_tag('div',
901 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
901 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
902 :class => 'contextual',
902 :class => 'contextual',
903 :title => l(:button_edit_section),
903 :title => l(:button_edit_section),
904 :id => "section-#{@current_section}") + heading.html_safe
904 :id => "section-#{@current_section}") + heading.html_safe
905 else
905 else
906 heading
906 heading
907 end
907 end
908 end
908 end
909 end
909 end
910
910
911 # Headings and TOC
911 # Headings and TOC
912 # Adds ids and links to headings unless options[:headings] is set to false
912 # Adds ids and links to headings unless options[:headings] is set to false
913 def parse_headings(text, project, obj, attr, only_path, options)
913 def parse_headings(text, project, obj, attr, only_path, options)
914 return if options[:headings] == false
914 return if options[:headings] == false
915
915
916 text.gsub!(HEADING_RE) do
916 text.gsub!(HEADING_RE) do
917 level, attrs, content = $2.to_i, $3, $4
917 level, attrs, content = $2.to_i, $3, $4
918 item = strip_tags(content).strip
918 item = strip_tags(content).strip
919 anchor = sanitize_anchor_name(item)
919 anchor = sanitize_anchor_name(item)
920 # used for single-file wiki export
920 # used for single-file wiki export
921 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
921 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
922 @heading_anchors[anchor] ||= 0
922 @heading_anchors[anchor] ||= 0
923 idx = (@heading_anchors[anchor] += 1)
923 idx = (@heading_anchors[anchor] += 1)
924 if idx > 1
924 if idx > 1
925 anchor = "#{anchor}-#{idx}"
925 anchor = "#{anchor}-#{idx}"
926 end
926 end
927 @parsed_headings << [level, anchor, item]
927 @parsed_headings << [level, anchor, item]
928 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
928 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
929 end
929 end
930 end
930 end
931
931
932 MACROS_RE = /(
932 MACROS_RE = /(
933 (!)? # escaping
933 (!)? # escaping
934 (
934 (
935 \{\{ # opening tag
935 \{\{ # opening tag
936 ([\w]+) # macro name
936 ([\w]+) # macro name
937 (\(([^\n\r]*?)\))? # optional arguments
937 (\(([^\n\r]*?)\))? # optional arguments
938 ([\n\r].*?[\n\r])? # optional block of text
938 ([\n\r].*?[\n\r])? # optional block of text
939 \}\} # closing tag
939 \}\} # closing tag
940 )
940 )
941 )/mx unless const_defined?(:MACROS_RE)
941 )/mx unless const_defined?(:MACROS_RE)
942
942
943 MACRO_SUB_RE = /(
943 MACRO_SUB_RE = /(
944 \{\{
944 \{\{
945 macro\((\d+)\)
945 macro\((\d+)\)
946 \}\}
946 \}\}
947 )/x unless const_defined?(:MACRO_SUB_RE)
947 )/x unless const_defined?(:MACRO_SUB_RE)
948
948
949 # Extracts macros from text
949 # Extracts macros from text
950 def catch_macros(text)
950 def catch_macros(text)
951 macros = {}
951 macros = {}
952 text.gsub!(MACROS_RE) do
952 text.gsub!(MACROS_RE) do
953 all, macro = $1, $4.downcase
953 all, macro = $1, $4.downcase
954 if macro_exists?(macro) || all =~ MACRO_SUB_RE
954 if macro_exists?(macro) || all =~ MACRO_SUB_RE
955 index = macros.size
955 index = macros.size
956 macros[index] = all
956 macros[index] = all
957 "{{macro(#{index})}}"
957 "{{macro(#{index})}}"
958 else
958 else
959 all
959 all
960 end
960 end
961 end
961 end
962 macros
962 macros
963 end
963 end
964
964
965 # Executes and replaces macros in text
965 # Executes and replaces macros in text
966 def inject_macros(text, obj, macros, execute=true)
966 def inject_macros(text, obj, macros, execute=true)
967 text.gsub!(MACRO_SUB_RE) do
967 text.gsub!(MACRO_SUB_RE) do
968 all, index = $1, $2.to_i
968 all, index = $1, $2.to_i
969 orig = macros.delete(index)
969 orig = macros.delete(index)
970 if execute && orig && orig =~ MACROS_RE
970 if execute && orig && orig =~ MACROS_RE
971 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
971 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
972 if esc.nil?
972 if esc.nil?
973 h(exec_macro(macro, obj, args, block) || all)
973 h(exec_macro(macro, obj, args, block) || all)
974 else
974 else
975 h(all)
975 h(all)
976 end
976 end
977 elsif orig
977 elsif orig
978 h(orig)
978 h(orig)
979 else
979 else
980 h(all)
980 h(all)
981 end
981 end
982 end
982 end
983 end
983 end
984
984
985 TOC_RE = /<p>\{\{((<|&lt;)|(>|&gt;))?toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
985 TOC_RE = /<p>\{\{((<|&lt;)|(>|&gt;))?toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
986
986
987 # Renders the TOC with given headings
987 # Renders the TOC with given headings
988 def replace_toc(text, headings)
988 def replace_toc(text, headings)
989 text.gsub!(TOC_RE) do
989 text.gsub!(TOC_RE) do
990 left_align, right_align = $2, $3
990 left_align, right_align = $2, $3
991 # Keep only the 4 first levels
991 # Keep only the 4 first levels
992 headings = headings.select{|level, anchor, item| level <= 4}
992 headings = headings.select{|level, anchor, item| level <= 4}
993 if headings.empty?
993 if headings.empty?
994 ''
994 ''
995 else
995 else
996 div_class = 'toc'
996 div_class = 'toc'
997 div_class << ' right' if right_align
997 div_class << ' right' if right_align
998 div_class << ' left' if left_align
998 div_class << ' left' if left_align
999 out = "<ul class=\"#{div_class}\"><li>"
999 out = "<ul class=\"#{div_class}\"><li>"
1000 root = headings.map(&:first).min
1000 root = headings.map(&:first).min
1001 current = root
1001 current = root
1002 started = false
1002 started = false
1003 headings.each do |level, anchor, item|
1003 headings.each do |level, anchor, item|
1004 if level > current
1004 if level > current
1005 out << '<ul><li>' * (level - current)
1005 out << '<ul><li>' * (level - current)
1006 elsif level < current
1006 elsif level < current
1007 out << "</li></ul>\n" * (current - level) + "</li><li>"
1007 out << "</li></ul>\n" * (current - level) + "</li><li>"
1008 elsif started
1008 elsif started
1009 out << '</li><li>'
1009 out << '</li><li>'
1010 end
1010 end
1011 out << "<a href=\"##{anchor}\">#{item}</a>"
1011 out << "<a href=\"##{anchor}\">#{item}</a>"
1012 current = level
1012 current = level
1013 started = true
1013 started = true
1014 end
1014 end
1015 out << '</li></ul>' * (current - root)
1015 out << '</li></ul>' * (current - root)
1016 out << '</li></ul>'
1016 out << '</li></ul>'
1017 end
1017 end
1018 end
1018 end
1019 end
1019 end
1020
1020
1021 # Same as Rails' simple_format helper without using paragraphs
1021 # Same as Rails' simple_format helper without using paragraphs
1022 def simple_format_without_paragraph(text)
1022 def simple_format_without_paragraph(text)
1023 text.to_s.
1023 text.to_s.
1024 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
1024 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
1025 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
1025 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
1026 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
1026 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
1027 html_safe
1027 html_safe
1028 end
1028 end
1029
1029
1030 def lang_options_for_select(blank=true)
1030 def lang_options_for_select(blank=true)
1031 (blank ? [["(auto)", ""]] : []) + languages_options
1031 (blank ? [["(auto)", ""]] : []) + languages_options
1032 end
1032 end
1033
1033
1034 def label_tag_for(name, option_tags = nil, options = {})
1034 def label_tag_for(name, option_tags = nil, options = {})
1035 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
1035 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
1036 content_tag("label", label_text)
1036 content_tag("label", label_text)
1037 end
1037 end
1038
1038
1039 def labelled_form_for(*args, &proc)
1039 def labelled_form_for(*args, &proc)
1040 args << {} unless args.last.is_a?(Hash)
1040 args << {} unless args.last.is_a?(Hash)
1041 options = args.last
1041 options = args.last
1042 if args.first.is_a?(Symbol)
1042 if args.first.is_a?(Symbol)
1043 options.merge!(:as => args.shift)
1043 options.merge!(:as => args.shift)
1044 end
1044 end
1045 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1045 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1046 form_for(*args, &proc)
1046 form_for(*args, &proc)
1047 end
1047 end
1048
1048
1049 def labelled_fields_for(*args, &proc)
1049 def labelled_fields_for(*args, &proc)
1050 args << {} unless args.last.is_a?(Hash)
1050 args << {} unless args.last.is_a?(Hash)
1051 options = args.last
1051 options = args.last
1052 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1052 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1053 fields_for(*args, &proc)
1053 fields_for(*args, &proc)
1054 end
1054 end
1055
1055
1056 def labelled_remote_form_for(*args, &proc)
1056 def labelled_remote_form_for(*args, &proc)
1057 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
1057 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
1058 args << {} unless args.last.is_a?(Hash)
1058 args << {} unless args.last.is_a?(Hash)
1059 options = args.last
1059 options = args.last
1060 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
1060 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
1061 form_for(*args, &proc)
1061 form_for(*args, &proc)
1062 end
1062 end
1063
1063
1064 def error_messages_for(*objects)
1064 def error_messages_for(*objects)
1065 html = ""
1065 html = ""
1066 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
1066 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
1067 errors = objects.map {|o| o.errors.full_messages}.flatten
1067 errors = objects.map {|o| o.errors.full_messages}.flatten
1068 if errors.any?
1068 if errors.any?
1069 html << "<div id='errorExplanation'><ul>\n"
1069 html << "<div id='errorExplanation'><ul>\n"
1070 errors.each do |error|
1070 errors.each do |error|
1071 html << "<li>#{h error}</li>\n"
1071 html << "<li>#{h error}</li>\n"
1072 end
1072 end
1073 html << "</ul></div>\n"
1073 html << "</ul></div>\n"
1074 end
1074 end
1075 html.html_safe
1075 html.html_safe
1076 end
1076 end
1077
1077
1078 def delete_link(url, options={})
1078 def delete_link(url, options={})
1079 options = {
1079 options = {
1080 :method => :delete,
1080 :method => :delete,
1081 :data => {:confirm => l(:text_are_you_sure)},
1081 :data => {:confirm => l(:text_are_you_sure)},
1082 :class => 'icon icon-del'
1082 :class => 'icon icon-del'
1083 }.merge(options)
1083 }.merge(options)
1084
1084
1085 link_to l(:button_delete), url, options
1085 link_to l(:button_delete), url, options
1086 end
1086 end
1087
1087
1088 def preview_link(url, form, target='preview', options={})
1088 def preview_link(url, form, target='preview', options={})
1089 content_tag 'a', l(:label_preview), {
1089 content_tag 'a', l(:label_preview), {
1090 :href => "#",
1090 :href => "#",
1091 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1091 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1092 :accesskey => accesskey(:preview)
1092 :accesskey => accesskey(:preview)
1093 }.merge(options)
1093 }.merge(options)
1094 end
1094 end
1095
1095
1096 def link_to_function(name, function, html_options={})
1096 def link_to_function(name, function, html_options={})
1097 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1097 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1098 end
1098 end
1099
1099
1100 # Helper to render JSON in views
1100 # Helper to render JSON in views
1101 def raw_json(arg)
1101 def raw_json(arg)
1102 arg.to_json.to_s.gsub('/', '\/').html_safe
1102 arg.to_json.to_s.gsub('/', '\/').html_safe
1103 end
1103 end
1104
1104
1105 def back_url
1105 def back_url
1106 url = params[:back_url]
1106 url = params[:back_url]
1107 if url.nil? && referer = request.env['HTTP_REFERER']
1107 if url.nil? && referer = request.env['HTTP_REFERER']
1108 url = CGI.unescape(referer.to_s)
1108 url = CGI.unescape(referer.to_s)
1109 end
1109 end
1110 url
1110 url
1111 end
1111 end
1112
1112
1113 def back_url_hidden_field_tag
1113 def back_url_hidden_field_tag
1114 url = back_url
1114 url = back_url
1115 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1115 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1116 end
1116 end
1117
1117
1118 def check_all_links(form_name)
1118 def check_all_links(form_name)
1119 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1119 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1120 " | ".html_safe +
1120 " | ".html_safe +
1121 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1121 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1122 end
1122 end
1123
1123
1124 def progress_bar(pcts, options={})
1124 def progress_bar(pcts, options={})
1125 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1125 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1126 pcts = pcts.collect(&:round)
1126 pcts = pcts.collect(&:round)
1127 pcts[1] = pcts[1] - pcts[0]
1127 pcts[1] = pcts[1] - pcts[0]
1128 pcts << (100 - pcts[1] - pcts[0])
1128 pcts << (100 - pcts[1] - pcts[0])
1129 width = options[:width] || '100px;'
1129 width = options[:width] || '100px;'
1130 legend = options[:legend] || ''
1130 legend = options[:legend] || ''
1131 content_tag('table',
1131 content_tag('table',
1132 content_tag('tr',
1132 content_tag('tr',
1133 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1133 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1134 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1134 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1135 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1135 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1136 ), :class => "progress progress-#{pcts[0]}", :style => "width: #{width};").html_safe +
1136 ), :class => "progress progress-#{pcts[0]}", :style => "width: #{width};").html_safe +
1137 content_tag('p', legend, :class => 'percent').html_safe
1137 content_tag('p', legend, :class => 'percent').html_safe
1138 end
1138 end
1139
1139
1140 def checked_image(checked=true)
1140 def checked_image(checked=true)
1141 if checked
1141 if checked
1142 image_tag 'toggle_check.png'
1142 image_tag 'toggle_check.png'
1143 end
1143 end
1144 end
1144 end
1145
1145
1146 def context_menu(url)
1146 def context_menu(url)
1147 unless @context_menu_included
1147 unless @context_menu_included
1148 content_for :header_tags do
1148 content_for :header_tags do
1149 javascript_include_tag('context_menu') +
1149 javascript_include_tag('context_menu') +
1150 stylesheet_link_tag('context_menu')
1150 stylesheet_link_tag('context_menu')
1151 end
1151 end
1152 if l(:direction) == 'rtl'
1152 if l(:direction) == 'rtl'
1153 content_for :header_tags do
1153 content_for :header_tags do
1154 stylesheet_link_tag('context_menu_rtl')
1154 stylesheet_link_tag('context_menu_rtl')
1155 end
1155 end
1156 end
1156 end
1157 @context_menu_included = true
1157 @context_menu_included = true
1158 end
1158 end
1159 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1159 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1160 end
1160 end
1161
1161
1162 def calendar_for(field_id)
1162 def calendar_for(field_id)
1163 include_calendar_headers_tags
1163 include_calendar_headers_tags
1164 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
1164 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
1165 end
1165 end
1166
1166
1167 def include_calendar_headers_tags
1167 def include_calendar_headers_tags
1168 unless @calendar_headers_tags_included
1168 unless @calendar_headers_tags_included
1169 tags = javascript_include_tag("datepicker")
1169 tags = javascript_include_tag("datepicker")
1170 @calendar_headers_tags_included = true
1170 @calendar_headers_tags_included = true
1171 content_for :header_tags do
1171 content_for :header_tags do
1172 start_of_week = Setting.start_of_week
1172 start_of_week = Setting.start_of_week
1173 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1173 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1174 # Redmine uses 1..7 (monday..sunday) in settings and locales
1174 # Redmine uses 1..7 (monday..sunday) in settings and locales
1175 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1175 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1176 start_of_week = start_of_week.to_i % 7
1176 start_of_week = start_of_week.to_i % 7
1177 tags << javascript_tag(
1177 tags << javascript_tag(
1178 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1178 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1179 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1179 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1180 path_to_image('/images/calendar.png') +
1180 path_to_image('/images/calendar.png') +
1181 "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " +
1181 "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " +
1182 "selectOtherMonths: true, changeMonth: true, changeYear: true, " +
1182 "selectOtherMonths: true, changeMonth: true, changeYear: true, " +
1183 "beforeShow: beforeShowDatePicker};")
1183 "beforeShow: beforeShowDatePicker};")
1184 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1184 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1185 unless jquery_locale == 'en'
1185 unless jquery_locale == 'en'
1186 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
1186 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
1187 end
1187 end
1188 tags
1188 tags
1189 end
1189 end
1190 end
1190 end
1191 end
1191 end
1192
1192
1193 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1193 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1194 # Examples:
1194 # Examples:
1195 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1195 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1196 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1196 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1197 #
1197 #
1198 def stylesheet_link_tag(*sources)
1198 def stylesheet_link_tag(*sources)
1199 options = sources.last.is_a?(Hash) ? sources.pop : {}
1199 options = sources.last.is_a?(Hash) ? sources.pop : {}
1200 plugin = options.delete(:plugin)
1200 plugin = options.delete(:plugin)
1201 sources = sources.map do |source|
1201 sources = sources.map do |source|
1202 if plugin
1202 if plugin
1203 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1203 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1204 elsif current_theme && current_theme.stylesheets.include?(source)
1204 elsif current_theme && current_theme.stylesheets.include?(source)
1205 current_theme.stylesheet_path(source)
1205 current_theme.stylesheet_path(source)
1206 else
1206 else
1207 source
1207 source
1208 end
1208 end
1209 end
1209 end
1210 super sources, options
1210 super sources, options
1211 end
1211 end
1212
1212
1213 # Overrides Rails' image_tag with themes and plugins support.
1213 # Overrides Rails' image_tag with themes and plugins support.
1214 # Examples:
1214 # Examples:
1215 # image_tag('image.png') # => picks image.png from the current theme or defaults
1215 # image_tag('image.png') # => picks image.png from the current theme or defaults
1216 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1216 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1217 #
1217 #
1218 def image_tag(source, options={})
1218 def image_tag(source, options={})
1219 if plugin = options.delete(:plugin)
1219 if plugin = options.delete(:plugin)
1220 source = "/plugin_assets/#{plugin}/images/#{source}"
1220 source = "/plugin_assets/#{plugin}/images/#{source}"
1221 elsif current_theme && current_theme.images.include?(source)
1221 elsif current_theme && current_theme.images.include?(source)
1222 source = current_theme.image_path(source)
1222 source = current_theme.image_path(source)
1223 end
1223 end
1224 super source, options
1224 super source, options
1225 end
1225 end
1226
1226
1227 # Overrides Rails' javascript_include_tag with plugins support
1227 # Overrides Rails' javascript_include_tag with plugins support
1228 # Examples:
1228 # Examples:
1229 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1229 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1230 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1230 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1231 #
1231 #
1232 def javascript_include_tag(*sources)
1232 def javascript_include_tag(*sources)
1233 options = sources.last.is_a?(Hash) ? sources.pop : {}
1233 options = sources.last.is_a?(Hash) ? sources.pop : {}
1234 if plugin = options.delete(:plugin)
1234 if plugin = options.delete(:plugin)
1235 sources = sources.map do |source|
1235 sources = sources.map do |source|
1236 if plugin
1236 if plugin
1237 "/plugin_assets/#{plugin}/javascripts/#{source}"
1237 "/plugin_assets/#{plugin}/javascripts/#{source}"
1238 else
1238 else
1239 source
1239 source
1240 end
1240 end
1241 end
1241 end
1242 end
1242 end
1243 super sources, options
1243 super sources, options
1244 end
1244 end
1245
1245
1246 # TODO: remove this in 2.5.0
1246 # TODO: remove this in 2.5.0
1247 def has_content?(name)
1247 def has_content?(name)
1248 content_for?(name)
1248 content_for?(name)
1249 end
1249 end
1250
1250
1251 def sidebar_content?
1251 def sidebar_content?
1252 content_for?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1252 content_for?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1253 end
1253 end
1254
1254
1255 def view_layouts_base_sidebar_hook_response
1255 def view_layouts_base_sidebar_hook_response
1256 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1256 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1257 end
1257 end
1258
1258
1259 def email_delivery_enabled?
1259 def email_delivery_enabled?
1260 !!ActionMailer::Base.perform_deliveries
1260 !!ActionMailer::Base.perform_deliveries
1261 end
1261 end
1262
1262
1263 # Returns the avatar image tag for the given +user+ if avatars are enabled
1263 # Returns the avatar image tag for the given +user+ if avatars are enabled
1264 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1264 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1265 def avatar(user, options = { })
1265 def avatar(user, options = { })
1266 if Setting.gravatar_enabled?
1266 if Setting.gravatar_enabled?
1267 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1267 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1268 email = nil
1268 email = nil
1269 if user.respond_to?(:mail)
1269 if user.respond_to?(:mail)
1270 email = user.mail
1270 email = user.mail
1271 elsif user.to_s =~ %r{<(.+?)>}
1271 elsif user.to_s =~ %r{<(.+?)>}
1272 email = $1
1272 email = $1
1273 end
1273 end
1274 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1274 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1275 else
1275 else
1276 ''
1276 ''
1277 end
1277 end
1278 end
1278 end
1279
1279
1280 def sanitize_anchor_name(anchor)
1280 def sanitize_anchor_name(anchor)
1281 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
1281 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
1282 anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1282 anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1283 else
1283 else
1284 # TODO: remove when ruby1.8 is no longer supported
1284 # TODO: remove when ruby1.8 is no longer supported
1285 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1285 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1286 end
1286 end
1287 end
1287 end
1288
1288
1289 # Returns the javascript tags that are included in the html layout head
1289 # Returns the javascript tags that are included in the html layout head
1290 def javascript_heads
1290 def javascript_heads
1291 tags = javascript_include_tag('jquery-1.8.3-ui-1.9.2-ujs-2.0.3', 'application')
1291 tags = javascript_include_tag('jquery-1.8.3-ui-1.9.2-ujs-2.0.3', 'application')
1292 unless User.current.pref.warn_on_leaving_unsaved == '0'
1292 unless User.current.pref.warn_on_leaving_unsaved == '0'
1293 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1293 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1294 end
1294 end
1295 tags
1295 tags
1296 end
1296 end
1297
1297
1298 def favicon
1298 def favicon
1299 "<link rel='shortcut icon' href='#{favicon_path}' />".html_safe
1299 "<link rel='shortcut icon' href='#{favicon_path}' />".html_safe
1300 end
1300 end
1301
1301
1302 # Returns the path to the favicon
1302 # Returns the path to the favicon
1303 def favicon_path
1303 def favicon_path
1304 icon = (current_theme && current_theme.favicon?) ? current_theme.favicon_path : '/favicon.ico'
1304 icon = (current_theme && current_theme.favicon?) ? current_theme.favicon_path : '/favicon.ico'
1305 image_path(icon)
1305 image_path(icon)
1306 end
1306 end
1307
1307
1308 # Returns the full URL to the favicon
1308 # Returns the full URL to the favicon
1309 def favicon_url
1309 def favicon_url
1310 # TODO: use #image_url introduced in Rails4
1310 # TODO: use #image_url introduced in Rails4
1311 path = favicon_path
1311 path = favicon_path
1312 base = url_for(:controller => 'welcome', :action => 'index', :only_path => false)
1312 base = url_for(:controller => 'welcome', :action => 'index', :only_path => false)
1313 base.sub(%r{/+$},'') + '/' + path.sub(%r{^/+},'')
1313 base.sub(%r{/+$},'') + '/' + path.sub(%r{^/+},'')
1314 end
1314 end
1315
1315
1316 def robot_exclusion_tag
1316 def robot_exclusion_tag
1317 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1317 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1318 end
1318 end
1319
1319
1320 # Returns true if arg is expected in the API response
1320 # Returns true if arg is expected in the API response
1321 def include_in_api_response?(arg)
1321 def include_in_api_response?(arg)
1322 unless @included_in_api_response
1322 unless @included_in_api_response
1323 param = params[:include]
1323 param = params[:include]
1324 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1324 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1325 @included_in_api_response.collect!(&:strip)
1325 @included_in_api_response.collect!(&:strip)
1326 end
1326 end
1327 @included_in_api_response.include?(arg.to_s)
1327 @included_in_api_response.include?(arg.to_s)
1328 end
1328 end
1329
1329
1330 # Returns options or nil if nometa param or X-Redmine-Nometa header
1330 # Returns options or nil if nometa param or X-Redmine-Nometa header
1331 # was set in the request
1331 # was set in the request
1332 def api_meta(options)
1332 def api_meta(options)
1333 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1333 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1334 # compatibility mode for activeresource clients that raise
1334 # compatibility mode for activeresource clients that raise
1335 # an error when deserializing an array with attributes
1335 # an error when deserializing an array with attributes
1336 nil
1336 nil
1337 else
1337 else
1338 options
1338 options
1339 end
1339 end
1340 end
1340 end
1341
1341
1342 private
1342 private
1343
1343
1344 def wiki_helper
1344 def wiki_helper
1345 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1345 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1346 extend helper
1346 extend helper
1347 return self
1347 return self
1348 end
1348 end
1349
1349
1350 def link_to_content_update(text, url_params = {}, html_options = {})
1350 def link_to_content_update(text, url_params = {}, html_options = {})
1351 link_to(text, url_params, html_options)
1351 link_to(text, url_params, html_options)
1352 end
1352 end
1353 end
1353 end
General Comments 0
You need to be logged in to leave comments. Login now