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