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