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