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