##// END OF EJS Templates
More readable regexp for parse_redmine_links (#24382)....
Jean-Philippe Lang -
r15770:79c2e4288991
parent child
Show More
@@ -1,1403 +1,1448
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)),
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 if projects.any?
361 if projects.any?
362 text = @project.try(:name) || l(:label_jump_to_a_project)
362 text = @project.try(:name) || l(:label_jump_to_a_project)
363 trigger = content_tag('span', text, :class => 'drdn-trigger')
363 trigger = content_tag('span', text, :class => 'drdn-trigger')
364 q = text_field_tag('q', '', :id => 'projects-quick-search', :class => 'autocomplete', :data => {:automcomplete_url => projects_path(:format => 'js')})
364 q = text_field_tag('q', '', :id => 'projects-quick-search', :class => 'autocomplete', :data => {:automcomplete_url => projects_path(:format => 'js')})
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 selection'),
367 content_tag('div', render_projects_for_jump_box(projects, @project), :class => 'drdn-items selection'),
368 :class => 'drdn-content'
368 :class => 'drdn-content'
369 )
369 )
370
370
371 content_tag('span', trigger + content, :id => "project-jump", :class => "drdn")
371 content_tag('span', trigger + content, :id => "project-jump", :class => "drdn")
372 end
372 end
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 lines = []
469 lines = []
470 syntax_highlight(name, content).each_line { |line| lines << line }
470 syntax_highlight(name, content).each_line { |line| lines << line }
471 lines
471 lines
472 end
472 end
473
473
474 def syntax_highlight(name, content)
474 def syntax_highlight(name, content)
475 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
475 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
476 end
476 end
477
477
478 def to_path_param(path)
478 def to_path_param(path)
479 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
479 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
480 str.blank? ? nil : str
480 str.blank? ? nil : str
481 end
481 end
482
482
483 def reorder_links(name, url, method = :post)
483 def reorder_links(name, url, method = :post)
484 # TODO: remove associated styles from application.css too
484 # TODO: remove associated styles from application.css too
485 ActiveSupport::Deprecation.warn "Application#reorder_links will be removed in Redmine 4."
485 ActiveSupport::Deprecation.warn "Application#reorder_links will be removed in Redmine 4."
486
486
487 link_to(l(:label_sort_highest),
487 link_to(l(:label_sort_highest),
488 url.merge({"#{name}[move_to]" => 'highest'}), :method => method,
488 url.merge({"#{name}[move_to]" => 'highest'}), :method => method,
489 :title => l(:label_sort_highest), :class => 'icon-only icon-move-top') +
489 :title => l(:label_sort_highest), :class => 'icon-only icon-move-top') +
490 link_to(l(:label_sort_higher),
490 link_to(l(:label_sort_higher),
491 url.merge({"#{name}[move_to]" => 'higher'}), :method => method,
491 url.merge({"#{name}[move_to]" => 'higher'}), :method => method,
492 :title => l(:label_sort_higher), :class => 'icon-only icon-move-up') +
492 :title => l(:label_sort_higher), :class => 'icon-only icon-move-up') +
493 link_to(l(:label_sort_lower),
493 link_to(l(:label_sort_lower),
494 url.merge({"#{name}[move_to]" => 'lower'}), :method => method,
494 url.merge({"#{name}[move_to]" => 'lower'}), :method => method,
495 :title => l(:label_sort_lower), :class => 'icon-only icon-move-down') +
495 :title => l(:label_sort_lower), :class => 'icon-only icon-move-down') +
496 link_to(l(:label_sort_lowest),
496 link_to(l(:label_sort_lowest),
497 url.merge({"#{name}[move_to]" => 'lowest'}), :method => method,
497 url.merge({"#{name}[move_to]" => 'lowest'}), :method => method,
498 :title => l(:label_sort_lowest), :class => 'icon-only icon-move-bottom')
498 :title => l(:label_sort_lowest), :class => 'icon-only icon-move-bottom')
499 end
499 end
500
500
501 def reorder_handle(object, options={})
501 def reorder_handle(object, options={})
502 data = {
502 data = {
503 :reorder_url => options[:url] || url_for(object),
503 :reorder_url => options[:url] || url_for(object),
504 :reorder_param => options[:param] || object.class.name.underscore
504 :reorder_param => options[:param] || object.class.name.underscore
505 }
505 }
506 content_tag('span', '',
506 content_tag('span', '',
507 :class => "sort-handle",
507 :class => "sort-handle",
508 :data => data,
508 :data => data,
509 :title => l(:button_sort))
509 :title => l(:button_sort))
510 end
510 end
511
511
512 def breadcrumb(*args)
512 def breadcrumb(*args)
513 elements = args.flatten
513 elements = args.flatten
514 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
514 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
515 end
515 end
516
516
517 def other_formats_links(&block)
517 def other_formats_links(&block)
518 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
518 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
519 yield Redmine::Views::OtherFormatsBuilder.new(self)
519 yield Redmine::Views::OtherFormatsBuilder.new(self)
520 concat('</p>'.html_safe)
520 concat('</p>'.html_safe)
521 end
521 end
522
522
523 def page_header_title
523 def page_header_title
524 if @project.nil? || @project.new_record?
524 if @project.nil? || @project.new_record?
525 h(Setting.app_title)
525 h(Setting.app_title)
526 else
526 else
527 b = []
527 b = []
528 ancestors = (@project.root? ? [] : @project.ancestors.visible.to_a)
528 ancestors = (@project.root? ? [] : @project.ancestors.visible.to_a)
529 if ancestors.any?
529 if ancestors.any?
530 root = ancestors.shift
530 root = ancestors.shift
531 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
531 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
532 if ancestors.size > 2
532 if ancestors.size > 2
533 b << "\xe2\x80\xa6"
533 b << "\xe2\x80\xa6"
534 ancestors = ancestors[-2, 2]
534 ancestors = ancestors[-2, 2]
535 end
535 end
536 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
536 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
537 end
537 end
538 b << content_tag(:span, h(@project), class: 'current-project')
538 b << content_tag(:span, h(@project), class: 'current-project')
539 if b.size > 1
539 if b.size > 1
540 separator = content_tag(:span, ' &raquo; '.html_safe, class: 'separator')
540 separator = content_tag(:span, ' &raquo; '.html_safe, class: 'separator')
541 path = safe_join(b[0..-2], separator) + separator
541 path = safe_join(b[0..-2], separator) + separator
542 b = [content_tag(:span, path.html_safe, class: 'breadcrumbs'), b[-1]]
542 b = [content_tag(:span, path.html_safe, class: 'breadcrumbs'), b[-1]]
543 end
543 end
544 safe_join b
544 safe_join b
545 end
545 end
546 end
546 end
547
547
548 # Returns a h2 tag and sets the html title with the given arguments
548 # Returns a h2 tag and sets the html title with the given arguments
549 def title(*args)
549 def title(*args)
550 strings = args.map do |arg|
550 strings = args.map do |arg|
551 if arg.is_a?(Array) && arg.size >= 2
551 if arg.is_a?(Array) && arg.size >= 2
552 link_to(*arg)
552 link_to(*arg)
553 else
553 else
554 h(arg.to_s)
554 h(arg.to_s)
555 end
555 end
556 end
556 end
557 html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
557 html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
558 content_tag('h2', strings.join(' &#187; ').html_safe)
558 content_tag('h2', strings.join(' &#187; ').html_safe)
559 end
559 end
560
560
561 # Sets the html title
561 # Sets the html title
562 # Returns the html title when called without arguments
562 # Returns the html title when called without arguments
563 # Current project name and app_title and automatically appended
563 # Current project name and app_title and automatically appended
564 # Exemples:
564 # Exemples:
565 # html_title 'Foo', 'Bar'
565 # html_title 'Foo', 'Bar'
566 # html_title # => 'Foo - Bar - My Project - Redmine'
566 # html_title # => 'Foo - Bar - My Project - Redmine'
567 def html_title(*args)
567 def html_title(*args)
568 if args.empty?
568 if args.empty?
569 title = @html_title || []
569 title = @html_title || []
570 title << @project.name if @project
570 title << @project.name if @project
571 title << Setting.app_title unless Setting.app_title == title.last
571 title << Setting.app_title unless Setting.app_title == title.last
572 title.reject(&:blank?).join(' - ')
572 title.reject(&:blank?).join(' - ')
573 else
573 else
574 @html_title ||= []
574 @html_title ||= []
575 @html_title += args
575 @html_title += args
576 end
576 end
577 end
577 end
578
578
579 # Returns the theme, controller name, and action as css classes for the
579 # Returns the theme, controller name, and action as css classes for the
580 # HTML body.
580 # HTML body.
581 def body_css_classes
581 def body_css_classes
582 css = []
582 css = []
583 if theme = Redmine::Themes.theme(Setting.ui_theme)
583 if theme = Redmine::Themes.theme(Setting.ui_theme)
584 css << 'theme-' + theme.name
584 css << 'theme-' + theme.name
585 end
585 end
586
586
587 css << 'project-' + @project.identifier if @project && @project.identifier.present?
587 css << 'project-' + @project.identifier if @project && @project.identifier.present?
588 css << 'controller-' + controller_name
588 css << 'controller-' + controller_name
589 css << 'action-' + action_name
589 css << 'action-' + action_name
590 if UserPreference::TEXTAREA_FONT_OPTIONS.include?(User.current.pref.textarea_font)
590 if UserPreference::TEXTAREA_FONT_OPTIONS.include?(User.current.pref.textarea_font)
591 css << "textarea-#{User.current.pref.textarea_font}"
591 css << "textarea-#{User.current.pref.textarea_font}"
592 end
592 end
593 css.join(' ')
593 css.join(' ')
594 end
594 end
595
595
596 def accesskey(s)
596 def accesskey(s)
597 @used_accesskeys ||= []
597 @used_accesskeys ||= []
598 key = Redmine::AccessKeys.key_for(s)
598 key = Redmine::AccessKeys.key_for(s)
599 return nil if @used_accesskeys.include?(key)
599 return nil if @used_accesskeys.include?(key)
600 @used_accesskeys << key
600 @used_accesskeys << key
601 key
601 key
602 end
602 end
603
603
604 # Formats text according to system settings.
604 # Formats text according to system settings.
605 # 2 ways to call this method:
605 # 2 ways to call this method:
606 # * with a String: textilizable(text, options)
606 # * with a String: textilizable(text, options)
607 # * with an object and one of its attribute: textilizable(issue, :description, options)
607 # * with an object and one of its attribute: textilizable(issue, :description, options)
608 def textilizable(*args)
608 def textilizable(*args)
609 options = args.last.is_a?(Hash) ? args.pop : {}
609 options = args.last.is_a?(Hash) ? args.pop : {}
610 case args.size
610 case args.size
611 when 1
611 when 1
612 obj = options[:object]
612 obj = options[:object]
613 text = args.shift
613 text = args.shift
614 when 2
614 when 2
615 obj = args.shift
615 obj = args.shift
616 attr = args.shift
616 attr = args.shift
617 text = obj.send(attr).to_s
617 text = obj.send(attr).to_s
618 else
618 else
619 raise ArgumentError, 'invalid arguments to textilizable'
619 raise ArgumentError, 'invalid arguments to textilizable'
620 end
620 end
621 return '' if text.blank?
621 return '' if text.blank?
622 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
622 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
623 @only_path = only_path = options.delete(:only_path) == false ? false : true
623 @only_path = only_path = options.delete(:only_path) == false ? false : true
624
624
625 text = text.dup
625 text = text.dup
626 macros = catch_macros(text)
626 macros = catch_macros(text)
627
627
628 if options[:formatting] == false
628 if options[:formatting] == false
629 text = h(text)
629 text = h(text)
630 else
630 else
631 formatting = options[:formatting] || Setting.text_formatting
631 formatting = options[:formatting] || Setting.text_formatting
632 text = Redmine::WikiFormatting.to_html(formatting, text, :object => obj, :attribute => attr)
632 text = Redmine::WikiFormatting.to_html(formatting, text, :object => obj, :attribute => attr)
633 end
633 end
634
634
635 @parsed_headings = []
635 @parsed_headings = []
636 @heading_anchors = {}
636 @heading_anchors = {}
637 @current_section = 0 if options[:edit_section_links]
637 @current_section = 0 if options[:edit_section_links]
638
638
639 parse_sections(text, project, obj, attr, only_path, options)
639 parse_sections(text, project, obj, attr, only_path, options)
640 text = parse_non_pre_blocks(text, obj, macros) do |text|
640 text = parse_non_pre_blocks(text, obj, macros) do |text|
641 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
641 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
642 send method_name, text, project, obj, attr, only_path, options
642 send method_name, text, project, obj, attr, only_path, options
643 end
643 end
644 end
644 end
645 parse_headings(text, project, obj, attr, only_path, options)
645 parse_headings(text, project, obj, attr, only_path, options)
646
646
647 if @parsed_headings.any?
647 if @parsed_headings.any?
648 replace_toc(text, @parsed_headings)
648 replace_toc(text, @parsed_headings)
649 end
649 end
650
650
651 text.html_safe
651 text.html_safe
652 end
652 end
653
653
654 def parse_non_pre_blocks(text, obj, macros)
654 def parse_non_pre_blocks(text, obj, macros)
655 s = StringScanner.new(text)
655 s = StringScanner.new(text)
656 tags = []
656 tags = []
657 parsed = ''
657 parsed = ''
658 while !s.eos?
658 while !s.eos?
659 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
659 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
660 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
660 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
661 if tags.empty?
661 if tags.empty?
662 yield text
662 yield text
663 inject_macros(text, obj, macros) if macros.any?
663 inject_macros(text, obj, macros) if macros.any?
664 else
664 else
665 inject_macros(text, obj, macros, false) if macros.any?
665 inject_macros(text, obj, macros, false) if macros.any?
666 end
666 end
667 parsed << text
667 parsed << text
668 if tag
668 if tag
669 if closing
669 if closing
670 if tags.last && tags.last.casecmp(tag) == 0
670 if tags.last && tags.last.casecmp(tag) == 0
671 tags.pop
671 tags.pop
672 end
672 end
673 else
673 else
674 tags << tag.downcase
674 tags << tag.downcase
675 end
675 end
676 parsed << full_tag
676 parsed << full_tag
677 end
677 end
678 end
678 end
679 # Close any non closing tags
679 # Close any non closing tags
680 while tag = tags.pop
680 while tag = tags.pop
681 parsed << "</#{tag}>"
681 parsed << "</#{tag}>"
682 end
682 end
683 parsed
683 parsed
684 end
684 end
685
685
686 def parse_inline_attachments(text, project, obj, attr, only_path, options)
686 def parse_inline_attachments(text, project, obj, attr, only_path, options)
687 return if options[:inline_attachments] == false
687 return if options[:inline_attachments] == false
688
688
689 # when using an image link, try to use an attachment, if possible
689 # when using an image link, try to use an attachment, if possible
690 attachments = options[:attachments] || []
690 attachments = options[:attachments] || []
691 attachments += obj.attachments if obj.respond_to?(:attachments)
691 attachments += obj.attachments if obj.respond_to?(:attachments)
692 if attachments.present?
692 if attachments.present?
693 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
693 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
694 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
694 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
695 # search for the picture in attachments
695 # search for the picture in attachments
696 if found = Attachment.latest_attach(attachments, CGI.unescape(filename))
696 if found = Attachment.latest_attach(attachments, CGI.unescape(filename))
697 image_url = download_named_attachment_url(found, found.filename, :only_path => only_path)
697 image_url = download_named_attachment_url(found, found.filename, :only_path => only_path)
698 desc = found.description.to_s.gsub('"', '')
698 desc = found.description.to_s.gsub('"', '')
699 if !desc.blank? && alttext.blank?
699 if !desc.blank? && alttext.blank?
700 alt = " title=\"#{desc}\" alt=\"#{desc}\""
700 alt = " title=\"#{desc}\" alt=\"#{desc}\""
701 end
701 end
702 "src=\"#{image_url}\"#{alt}"
702 "src=\"#{image_url}\"#{alt}"
703 else
703 else
704 m
704 m
705 end
705 end
706 end
706 end
707 end
707 end
708 end
708 end
709
709
710 # Wiki links
710 # Wiki links
711 #
711 #
712 # Examples:
712 # Examples:
713 # [[mypage]]
713 # [[mypage]]
714 # [[mypage|mytext]]
714 # [[mypage|mytext]]
715 # wiki links can refer other project wikis, using project name or identifier:
715 # wiki links can refer other project wikis, using project name or identifier:
716 # [[project:]] -> wiki starting page
716 # [[project:]] -> wiki starting page
717 # [[project:|mytext]]
717 # [[project:|mytext]]
718 # [[project:mypage]]
718 # [[project:mypage]]
719 # [[project:mypage|mytext]]
719 # [[project:mypage|mytext]]
720 def parse_wiki_links(text, project, obj, attr, only_path, options)
720 def parse_wiki_links(text, project, obj, attr, only_path, options)
721 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
721 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
722 link_project = project
722 link_project = project
723 esc, all, page, title = $1, $2, $3, $5
723 esc, all, page, title = $1, $2, $3, $5
724 if esc.nil?
724 if esc.nil?
725 if page =~ /^([^\:]+)\:(.*)$/
725 if page =~ /^([^\:]+)\:(.*)$/
726 identifier, page = $1, $2
726 identifier, page = $1, $2
727 link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
727 link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
728 title ||= identifier if page.blank?
728 title ||= identifier if page.blank?
729 end
729 end
730
730
731 if link_project && link_project.wiki
731 if link_project && link_project.wiki
732 # extract anchor
732 # extract anchor
733 anchor = nil
733 anchor = nil
734 if page =~ /^(.+?)\#(.+)$/
734 if page =~ /^(.+?)\#(.+)$/
735 page, anchor = $1, $2
735 page, anchor = $1, $2
736 end
736 end
737 anchor = sanitize_anchor_name(anchor) if anchor.present?
737 anchor = sanitize_anchor_name(anchor) if anchor.present?
738 # check if page exists
738 # check if page exists
739 wiki_page = link_project.wiki.find_page(page)
739 wiki_page = link_project.wiki.find_page(page)
740 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
740 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
741 "##{anchor}"
741 "##{anchor}"
742 else
742 else
743 case options[:wiki_links]
743 case options[:wiki_links]
744 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
744 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
745 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
745 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
746 else
746 else
747 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
747 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
748 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
748 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
749 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
749 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
750 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
750 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
751 end
751 end
752 end
752 end
753 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
753 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
754 else
754 else
755 # project or wiki doesn't exist
755 # project or wiki doesn't exist
756 all
756 all
757 end
757 end
758 else
758 else
759 all
759 all
760 end
760 end
761 end
761 end
762 end
762 end
763
763
764 # Redmine links
764 # Redmine links
765 #
765 #
766 # Examples:
766 # Examples:
767 # Issues:
767 # Issues:
768 # #52 -> Link to issue #52
768 # #52 -> Link to issue #52
769 # Changesets:
769 # Changesets:
770 # r52 -> Link to revision 52
770 # r52 -> Link to revision 52
771 # commit:a85130f -> Link to scmid starting with a85130f
771 # commit:a85130f -> Link to scmid starting with a85130f
772 # Documents:
772 # Documents:
773 # document#17 -> Link to document with id 17
773 # document#17 -> Link to document with id 17
774 # document:Greetings -> Link to the document with title "Greetings"
774 # document:Greetings -> Link to the document with title "Greetings"
775 # document:"Some document" -> Link to the document with title "Some document"
775 # document:"Some document" -> Link to the document with title "Some document"
776 # Versions:
776 # Versions:
777 # version#3 -> Link to version with id 3
777 # version#3 -> Link to version with id 3
778 # version:1.0.0 -> Link to version named "1.0.0"
778 # version:1.0.0 -> Link to version named "1.0.0"
779 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
779 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
780 # Attachments:
780 # Attachments:
781 # attachment:file.zip -> Link to the attachment of the current object named file.zip
781 # attachment:file.zip -> Link to the attachment of the current object named file.zip
782 # Source files:
782 # Source files:
783 # source:some/file -> Link to the file located at /some/file in the project's repository
783 # source:some/file -> Link to the file located at /some/file in the project's repository
784 # source:some/file@52 -> Link to the file's revision 52
784 # source:some/file@52 -> Link to the file's revision 52
785 # source:some/file#L120 -> Link to line 120 of the file
785 # source:some/file#L120 -> Link to line 120 of the file
786 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
786 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
787 # export:some/file -> Force the download of the file
787 # export:some/file -> Force the download of the file
788 # Forum messages:
788 # Forum messages:
789 # message#1218 -> Link to message with id 1218
789 # message#1218 -> Link to message with id 1218
790 # Projects:
790 # Projects:
791 # project:someproject -> Link to project named "someproject"
791 # project:someproject -> Link to project named "someproject"
792 # project#3 -> Link to project with id 3
792 # project#3 -> Link to project with id 3
793 #
793 #
794 # Links can refer other objects from other projects, using project identifier:
794 # Links can refer other objects from other projects, using project identifier:
795 # identifier:r52
795 # identifier:r52
796 # identifier:document:"Some document"
796 # identifier:document:"Some document"
797 # identifier:version:1.0.0
797 # identifier:version:1.0.0
798 # identifier:source:some/file
798 # identifier:source:some/file
799 def parse_redmine_links(text, default_project, obj, attr, only_path, options)
799 def parse_redmine_links(text, default_project, obj, attr, only_path, options)
800 text.gsub!(%r{<a( [^>]+?)?>(.*?)</a>|([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
800 text.gsub!(LINKS_RE) do |_|
801 tag_content, leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $2, $3, $4, $5, $6, $7, $12, $13, $10 || $14 || $20, $16 || $21, $17, $19
801 tag_content = $~[:tag_content]
802 leading = $~[:leading]
803 esc = $~[:esc]
804 project_prefix = $~[:project_prefix]
805 project_identifier = $~[:project_identifier]
806 prefix = $~[:prefix]
807 repo_prefix = $~[:repo_prefix]
808 repo_identifier = $~[:repo_identifier]
809 sep = $~[:sep1] || $~[:sep2] || $~[:sep3]
810 identifier = $~[:identifier1] || $~[:identifier2]
811 comment_suffix = $~[:comment_suffix]
812 comment_id = $~[:comment_id]
813
802 if tag_content
814 if tag_content
803 $&
815 $&
804 else
816 else
805 link = nil
817 link = nil
806 project = default_project
818 project = default_project
807 if project_identifier
819 if project_identifier
808 project = Project.visible.find_by_identifier(project_identifier)
820 project = Project.visible.find_by_identifier(project_identifier)
809 end
821 end
810 if esc.nil?
822 if esc.nil?
811 if prefix.nil? && sep == 'r'
823 if prefix.nil? && sep == 'r'
812 if project
824 if project
813 repository = nil
825 repository = nil
814 if repo_identifier
826 if repo_identifier
815 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
827 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
816 else
828 else
817 repository = project.repository
829 repository = project.repository
818 end
830 end
819 # project.changesets.visible raises an SQL error because of a double join on repositories
831 # project.changesets.visible raises an SQL error because of a double join on repositories
820 if repository &&
832 if repository &&
821 (changeset = Changeset.visible.
833 (changeset = Changeset.visible.
822 find_by_repository_id_and_revision(repository.id, identifier))
834 find_by_repository_id_and_revision(repository.id, identifier))
823 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"),
835 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"),
824 {:only_path => only_path, :controller => 'repositories',
836 {:only_path => only_path, :controller => 'repositories',
825 :action => 'revision', :id => project,
837 :action => 'revision', :id => project,
826 :repository_id => repository.identifier_param,
838 :repository_id => repository.identifier_param,
827 :rev => changeset.revision},
839 :rev => changeset.revision},
828 :class => 'changeset',
840 :class => 'changeset',
829 :title => truncate_single_line_raw(changeset.comments, 100))
841 :title => truncate_single_line_raw(changeset.comments, 100))
830 end
842 end
831 end
843 end
832 elsif sep == '#'
844 elsif sep == '#'
833 oid = identifier.to_i
845 oid = identifier.to_i
834 case prefix
846 case prefix
835 when nil
847 when nil
836 if oid.to_s == identifier &&
848 if oid.to_s == identifier &&
837 issue = Issue.visible.find_by_id(oid)
849 issue = Issue.visible.find_by_id(oid)
838 anchor = comment_id ? "note-#{comment_id}" : nil
850 anchor = comment_id ? "note-#{comment_id}" : nil
839 link = link_to("##{oid}#{comment_suffix}",
851 link = link_to("##{oid}#{comment_suffix}",
840 issue_url(issue, :only_path => only_path, :anchor => anchor),
852 issue_url(issue, :only_path => only_path, :anchor => anchor),
841 :class => issue.css_classes,
853 :class => issue.css_classes,
842 :title => "#{issue.tracker.name}: #{issue.subject.truncate(100)} (#{issue.status.name})")
854 :title => "#{issue.tracker.name}: #{issue.subject.truncate(100)} (#{issue.status.name})")
843 end
855 end
844 when 'document'
856 when 'document'
845 if document = Document.visible.find_by_id(oid)
857 if document = Document.visible.find_by_id(oid)
846 link = link_to(document.title, document_url(document, :only_path => only_path), :class => 'document')
858 link = link_to(document.title, document_url(document, :only_path => only_path), :class => 'document')
847 end
859 end
848 when 'version'
860 when 'version'
849 if version = Version.visible.find_by_id(oid)
861 if version = Version.visible.find_by_id(oid)
850 link = link_to(version.name, version_url(version, :only_path => only_path), :class => 'version')
862 link = link_to(version.name, version_url(version, :only_path => only_path), :class => 'version')
851 end
863 end
852 when 'message'
864 when 'message'
853 if message = Message.visible.find_by_id(oid)
865 if message = Message.visible.find_by_id(oid)
854 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
866 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
855 end
867 end
856 when 'forum'
868 when 'forum'
857 if board = Board.visible.find_by_id(oid)
869 if board = Board.visible.find_by_id(oid)
858 link = link_to(board.name, project_board_url(board.project, board, :only_path => only_path), :class => 'board')
870 link = link_to(board.name, project_board_url(board.project, board, :only_path => only_path), :class => 'board')
859 end
871 end
860 when 'news'
872 when 'news'
861 if news = News.visible.find_by_id(oid)
873 if news = News.visible.find_by_id(oid)
862 link = link_to(news.title, news_url(news, :only_path => only_path), :class => 'news')
874 link = link_to(news.title, news_url(news, :only_path => only_path), :class => 'news')
863 end
875 end
864 when 'project'
876 when 'project'
865 if p = Project.visible.find_by_id(oid)
877 if p = Project.visible.find_by_id(oid)
866 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
878 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
867 end
879 end
868 end
880 end
869 elsif sep == ':'
881 elsif sep == ':'
870 # removes the double quotes if any
882 # removes the double quotes if any
871 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
883 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
872 name = CGI.unescapeHTML(name)
884 name = CGI.unescapeHTML(name)
873 case prefix
885 case prefix
874 when 'document'
886 when 'document'
875 if project && document = project.documents.visible.find_by_title(name)
887 if project && document = project.documents.visible.find_by_title(name)
876 link = link_to(document.title, document_url(document, :only_path => only_path), :class => 'document')
888 link = link_to(document.title, document_url(document, :only_path => only_path), :class => 'document')
877 end
889 end
878 when 'version'
890 when 'version'
879 if project && version = project.versions.visible.find_by_name(name)
891 if project && version = project.versions.visible.find_by_name(name)
880 link = link_to(version.name, version_url(version, :only_path => only_path), :class => 'version')
892 link = link_to(version.name, version_url(version, :only_path => only_path), :class => 'version')
881 end
893 end
882 when 'forum'
894 when 'forum'
883 if project && board = project.boards.visible.find_by_name(name)
895 if project && board = project.boards.visible.find_by_name(name)
884 link = link_to(board.name, project_board_url(board.project, board, :only_path => only_path), :class => 'board')
896 link = link_to(board.name, project_board_url(board.project, board, :only_path => only_path), :class => 'board')
885 end
897 end
886 when 'news'
898 when 'news'
887 if project && news = project.news.visible.find_by_title(name)
899 if project && news = project.news.visible.find_by_title(name)
888 link = link_to(news.title, news_url(news, :only_path => only_path), :class => 'news')
900 link = link_to(news.title, news_url(news, :only_path => only_path), :class => 'news')
889 end
901 end
890 when 'commit', 'source', 'export'
902 when 'commit', 'source', 'export'
891 if project
903 if project
892 repository = nil
904 repository = nil
893 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
905 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
894 repo_prefix, repo_identifier, name = $1, $2, $3
906 repo_prefix, repo_identifier, name = $1, $2, $3
895 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
907 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
896 else
908 else
897 repository = project.repository
909 repository = project.repository
898 end
910 end
899 if prefix == 'commit'
911 if prefix == 'commit'
900 if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
912 if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
901 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},
913 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},
902 :class => 'changeset',
914 :class => 'changeset',
903 :title => truncate_single_line_raw(changeset.comments, 100)
915 :title => truncate_single_line_raw(changeset.comments, 100)
904 end
916 end
905 else
917 else
906 if repository && User.current.allowed_to?(:browse_repository, project)
918 if repository && User.current.allowed_to?(:browse_repository, project)
907 name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
919 name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
908 path, rev, anchor = $1, $3, $5
920 path, rev, anchor = $1, $3, $5
909 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,
921 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,
910 :path => to_path_param(path),
922 :path => to_path_param(path),
911 :rev => rev,
923 :rev => rev,
912 :anchor => anchor},
924 :anchor => anchor},
913 :class => (prefix == 'export' ? 'source download' : 'source')
925 :class => (prefix == 'export' ? 'source download' : 'source')
914 end
926 end
915 end
927 end
916 repo_prefix = nil
928 repo_prefix = nil
917 end
929 end
918 when 'attachment'
930 when 'attachment'
919 attachments = options[:attachments] || []
931 attachments = options[:attachments] || []
920 attachments += obj.attachments if obj.respond_to?(:attachments)
932 attachments += obj.attachments if obj.respond_to?(:attachments)
921 if attachments && attachment = Attachment.latest_attach(attachments, name)
933 if attachments && attachment = Attachment.latest_attach(attachments, name)
922 link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
934 link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
923 end
935 end
924 when 'project'
936 when 'project'
925 if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
937 if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
926 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
938 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
927 end
939 end
928 end
940 end
929 end
941 end
930 end
942 end
931 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
943 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
932 end
944 end
933 end
945 end
934 end
946 end
935
947
948 LINKS_RE =
949 %r{
950 <a( [^>]+?)?>(?<tag_content>.*?)</a>|
951 (?<leading>[\s\(,\-\[\>]|^)
952 (?<esc>!)?
953 (?<project_prefix>(?<project_identifier>[a-z0-9\-_]+):)?
954 (?<prefix>attachment|document|version|forum|news|message|project|commit|source|export)?
955 (
956 (
957 (?<sep1>\#)|
958 (
959 (?<repo_prefix>(?<repo_identifier>[a-z0-9\-_]+)\|)?
960 (?<sep2>r)
961 )
962 )
963 (
964 (?<identifier1>\d+)
965 (?<comment_suffix>
966 (\#note)?
967 -(?<comment_id>\d+)
968 )?
969 )|
970 (?<sep3>:)
971 (?<identifier2>[^"\s<>][^\s<>]*?|"[^"]+?")
972 )
973 (?=
974 (?=[[:punct:]][^A-Za-z0-9_/])|
975 ,|
976 \s|
977 \]|
978 <|
979 $)
980 }x
936 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
981 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
937
982
938 def parse_sections(text, project, obj, attr, only_path, options)
983 def parse_sections(text, project, obj, attr, only_path, options)
939 return unless options[:edit_section_links]
984 return unless options[:edit_section_links]
940 text.gsub!(HEADING_RE) do
985 text.gsub!(HEADING_RE) do
941 heading, level = $1, $2
986 heading, level = $1, $2
942 @current_section += 1
987 @current_section += 1
943 if @current_section > 1
988 if @current_section > 1
944 content_tag('div',
989 content_tag('div',
945 link_to(l(:button_edit_section), options[:edit_section_links].merge(:section => @current_section),
990 link_to(l(:button_edit_section), options[:edit_section_links].merge(:section => @current_section),
946 :class => 'icon-only icon-edit'),
991 :class => 'icon-only icon-edit'),
947 :class => "contextual heading-#{level}",
992 :class => "contextual heading-#{level}",
948 :title => l(:button_edit_section),
993 :title => l(:button_edit_section),
949 :id => "section-#{@current_section}") + heading.html_safe
994 :id => "section-#{@current_section}") + heading.html_safe
950 else
995 else
951 heading
996 heading
952 end
997 end
953 end
998 end
954 end
999 end
955
1000
956 # Headings and TOC
1001 # Headings and TOC
957 # Adds ids and links to headings unless options[:headings] is set to false
1002 # Adds ids and links to headings unless options[:headings] is set to false
958 def parse_headings(text, project, obj, attr, only_path, options)
1003 def parse_headings(text, project, obj, attr, only_path, options)
959 return if options[:headings] == false
1004 return if options[:headings] == false
960
1005
961 text.gsub!(HEADING_RE) do
1006 text.gsub!(HEADING_RE) do
962 level, attrs, content = $2.to_i, $3, $4
1007 level, attrs, content = $2.to_i, $3, $4
963 item = strip_tags(content).strip
1008 item = strip_tags(content).strip
964 anchor = sanitize_anchor_name(item)
1009 anchor = sanitize_anchor_name(item)
965 # used for single-file wiki export
1010 # used for single-file wiki export
966 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
1011 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
967 @heading_anchors[anchor] ||= 0
1012 @heading_anchors[anchor] ||= 0
968 idx = (@heading_anchors[anchor] += 1)
1013 idx = (@heading_anchors[anchor] += 1)
969 if idx > 1
1014 if idx > 1
970 anchor = "#{anchor}-#{idx}"
1015 anchor = "#{anchor}-#{idx}"
971 end
1016 end
972 @parsed_headings << [level, anchor, item]
1017 @parsed_headings << [level, anchor, item]
973 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
1018 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
974 end
1019 end
975 end
1020 end
976
1021
977 MACROS_RE = /(
1022 MACROS_RE = /(
978 (!)? # escaping
1023 (!)? # escaping
979 (
1024 (
980 \{\{ # opening tag
1025 \{\{ # opening tag
981 ([\w]+) # macro name
1026 ([\w]+) # macro name
982 (\(([^\n\r]*?)\))? # optional arguments
1027 (\(([^\n\r]*?)\))? # optional arguments
983 ([\n\r].*?[\n\r])? # optional block of text
1028 ([\n\r].*?[\n\r])? # optional block of text
984 \}\} # closing tag
1029 \}\} # closing tag
985 )
1030 )
986 )/mx unless const_defined?(:MACROS_RE)
1031 )/mx unless const_defined?(:MACROS_RE)
987
1032
988 MACRO_SUB_RE = /(
1033 MACRO_SUB_RE = /(
989 \{\{
1034 \{\{
990 macro\((\d+)\)
1035 macro\((\d+)\)
991 \}\}
1036 \}\}
992 )/x unless const_defined?(:MACRO_SUB_RE)
1037 )/x unless const_defined?(:MACRO_SUB_RE)
993
1038
994 # Extracts macros from text
1039 # Extracts macros from text
995 def catch_macros(text)
1040 def catch_macros(text)
996 macros = {}
1041 macros = {}
997 text.gsub!(MACROS_RE) do
1042 text.gsub!(MACROS_RE) do
998 all, macro = $1, $4.downcase
1043 all, macro = $1, $4.downcase
999 if macro_exists?(macro) || all =~ MACRO_SUB_RE
1044 if macro_exists?(macro) || all =~ MACRO_SUB_RE
1000 index = macros.size
1045 index = macros.size
1001 macros[index] = all
1046 macros[index] = all
1002 "{{macro(#{index})}}"
1047 "{{macro(#{index})}}"
1003 else
1048 else
1004 all
1049 all
1005 end
1050 end
1006 end
1051 end
1007 macros
1052 macros
1008 end
1053 end
1009
1054
1010 # Executes and replaces macros in text
1055 # Executes and replaces macros in text
1011 def inject_macros(text, obj, macros, execute=true)
1056 def inject_macros(text, obj, macros, execute=true)
1012 text.gsub!(MACRO_SUB_RE) do
1057 text.gsub!(MACRO_SUB_RE) do
1013 all, index = $1, $2.to_i
1058 all, index = $1, $2.to_i
1014 orig = macros.delete(index)
1059 orig = macros.delete(index)
1015 if execute && orig && orig =~ MACROS_RE
1060 if execute && orig && orig =~ MACROS_RE
1016 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
1061 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
1017 if esc.nil?
1062 if esc.nil?
1018 h(exec_macro(macro, obj, args, block) || all)
1063 h(exec_macro(macro, obj, args, block) || all)
1019 else
1064 else
1020 h(all)
1065 h(all)
1021 end
1066 end
1022 elsif orig
1067 elsif orig
1023 h(orig)
1068 h(orig)
1024 else
1069 else
1025 h(all)
1070 h(all)
1026 end
1071 end
1027 end
1072 end
1028 end
1073 end
1029
1074
1030 TOC_RE = /<p>\{\{((<|&lt;)|(>|&gt;))?toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
1075 TOC_RE = /<p>\{\{((<|&lt;)|(>|&gt;))?toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
1031
1076
1032 # Renders the TOC with given headings
1077 # Renders the TOC with given headings
1033 def replace_toc(text, headings)
1078 def replace_toc(text, headings)
1034 text.gsub!(TOC_RE) do
1079 text.gsub!(TOC_RE) do
1035 left_align, right_align = $2, $3
1080 left_align, right_align = $2, $3
1036 # Keep only the 4 first levels
1081 # Keep only the 4 first levels
1037 headings = headings.select{|level, anchor, item| level <= 4}
1082 headings = headings.select{|level, anchor, item| level <= 4}
1038 if headings.empty?
1083 if headings.empty?
1039 ''
1084 ''
1040 else
1085 else
1041 div_class = 'toc'
1086 div_class = 'toc'
1042 div_class << ' right' if right_align
1087 div_class << ' right' if right_align
1043 div_class << ' left' if left_align
1088 div_class << ' left' if left_align
1044 out = "<ul class=\"#{div_class}\"><li><strong>#{l :label_table_of_contents}</strong></li><li>"
1089 out = "<ul class=\"#{div_class}\"><li><strong>#{l :label_table_of_contents}</strong></li><li>"
1045 root = headings.map(&:first).min
1090 root = headings.map(&:first).min
1046 current = root
1091 current = root
1047 started = false
1092 started = false
1048 headings.each do |level, anchor, item|
1093 headings.each do |level, anchor, item|
1049 if level > current
1094 if level > current
1050 out << '<ul><li>' * (level - current)
1095 out << '<ul><li>' * (level - current)
1051 elsif level < current
1096 elsif level < current
1052 out << "</li></ul>\n" * (current - level) + "</li><li>"
1097 out << "</li></ul>\n" * (current - level) + "</li><li>"
1053 elsif started
1098 elsif started
1054 out << '</li><li>'
1099 out << '</li><li>'
1055 end
1100 end
1056 out << "<a href=\"##{anchor}\">#{item}</a>"
1101 out << "<a href=\"##{anchor}\">#{item}</a>"
1057 current = level
1102 current = level
1058 started = true
1103 started = true
1059 end
1104 end
1060 out << '</li></ul>' * (current - root)
1105 out << '</li></ul>' * (current - root)
1061 out << '</li></ul>'
1106 out << '</li></ul>'
1062 end
1107 end
1063 end
1108 end
1064 end
1109 end
1065
1110
1066 # Same as Rails' simple_format helper without using paragraphs
1111 # Same as Rails' simple_format helper without using paragraphs
1067 def simple_format_without_paragraph(text)
1112 def simple_format_without_paragraph(text)
1068 text.to_s.
1113 text.to_s.
1069 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
1114 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
1070 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
1115 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
1071 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
1116 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
1072 html_safe
1117 html_safe
1073 end
1118 end
1074
1119
1075 def lang_options_for_select(blank=true)
1120 def lang_options_for_select(blank=true)
1076 (blank ? [["(auto)", ""]] : []) + languages_options
1121 (blank ? [["(auto)", ""]] : []) + languages_options
1077 end
1122 end
1078
1123
1079 def labelled_form_for(*args, &proc)
1124 def labelled_form_for(*args, &proc)
1080 args << {} unless args.last.is_a?(Hash)
1125 args << {} unless args.last.is_a?(Hash)
1081 options = args.last
1126 options = args.last
1082 if args.first.is_a?(Symbol)
1127 if args.first.is_a?(Symbol)
1083 options.merge!(:as => args.shift)
1128 options.merge!(:as => args.shift)
1084 end
1129 end
1085 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1130 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1086 form_for(*args, &proc)
1131 form_for(*args, &proc)
1087 end
1132 end
1088
1133
1089 def labelled_fields_for(*args, &proc)
1134 def labelled_fields_for(*args, &proc)
1090 args << {} unless args.last.is_a?(Hash)
1135 args << {} unless args.last.is_a?(Hash)
1091 options = args.last
1136 options = args.last
1092 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1137 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1093 fields_for(*args, &proc)
1138 fields_for(*args, &proc)
1094 end
1139 end
1095
1140
1096 # Render the error messages for the given objects
1141 # Render the error messages for the given objects
1097 def error_messages_for(*objects)
1142 def error_messages_for(*objects)
1098 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
1143 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
1099 errors = objects.map {|o| o.errors.full_messages}.flatten
1144 errors = objects.map {|o| o.errors.full_messages}.flatten
1100 render_error_messages(errors)
1145 render_error_messages(errors)
1101 end
1146 end
1102
1147
1103 # Renders a list of error messages
1148 # Renders a list of error messages
1104 def render_error_messages(errors)
1149 def render_error_messages(errors)
1105 html = ""
1150 html = ""
1106 if errors.present?
1151 if errors.present?
1107 html << "<div id='errorExplanation'><ul>\n"
1152 html << "<div id='errorExplanation'><ul>\n"
1108 errors.each do |error|
1153 errors.each do |error|
1109 html << "<li>#{h error}</li>\n"
1154 html << "<li>#{h error}</li>\n"
1110 end
1155 end
1111 html << "</ul></div>\n"
1156 html << "</ul></div>\n"
1112 end
1157 end
1113 html.html_safe
1158 html.html_safe
1114 end
1159 end
1115
1160
1116 def delete_link(url, options={})
1161 def delete_link(url, options={})
1117 options = {
1162 options = {
1118 :method => :delete,
1163 :method => :delete,
1119 :data => {:confirm => l(:text_are_you_sure)},
1164 :data => {:confirm => l(:text_are_you_sure)},
1120 :class => 'icon icon-del'
1165 :class => 'icon icon-del'
1121 }.merge(options)
1166 }.merge(options)
1122
1167
1123 link_to l(:button_delete), url, options
1168 link_to l(:button_delete), url, options
1124 end
1169 end
1125
1170
1126 def preview_link(url, form, target='preview', options={})
1171 def preview_link(url, form, target='preview', options={})
1127 content_tag 'a', l(:label_preview), {
1172 content_tag 'a', l(:label_preview), {
1128 :href => "#",
1173 :href => "#",
1129 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1174 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1130 :accesskey => accesskey(:preview)
1175 :accesskey => accesskey(:preview)
1131 }.merge(options)
1176 }.merge(options)
1132 end
1177 end
1133
1178
1134 def link_to_function(name, function, html_options={})
1179 def link_to_function(name, function, html_options={})
1135 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1180 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1136 end
1181 end
1137
1182
1138 # Helper to render JSON in views
1183 # Helper to render JSON in views
1139 def raw_json(arg)
1184 def raw_json(arg)
1140 arg.to_json.to_s.gsub('/', '\/').html_safe
1185 arg.to_json.to_s.gsub('/', '\/').html_safe
1141 end
1186 end
1142
1187
1143 def back_url
1188 def back_url
1144 url = params[:back_url]
1189 url = params[:back_url]
1145 if url.nil? && referer = request.env['HTTP_REFERER']
1190 if url.nil? && referer = request.env['HTTP_REFERER']
1146 url = CGI.unescape(referer.to_s)
1191 url = CGI.unescape(referer.to_s)
1147 # URLs that contains the utf8=[checkmark] parameter added by Rails are
1192 # URLs that contains the utf8=[checkmark] parameter added by Rails are
1148 # parsed as invalid by URI.parse so the redirect to the back URL would
1193 # parsed as invalid by URI.parse so the redirect to the back URL would
1149 # not be accepted (ApplicationController#validate_back_url would return
1194 # not be accepted (ApplicationController#validate_back_url would return
1150 # false)
1195 # false)
1151 url.gsub!(/(\?|&)utf8=\u2713&?/, '\1')
1196 url.gsub!(/(\?|&)utf8=\u2713&?/, '\1')
1152 end
1197 end
1153 url
1198 url
1154 end
1199 end
1155
1200
1156 def back_url_hidden_field_tag
1201 def back_url_hidden_field_tag
1157 url = back_url
1202 url = back_url
1158 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1203 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1159 end
1204 end
1160
1205
1161 def check_all_links(form_name)
1206 def check_all_links(form_name)
1162 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1207 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1163 " | ".html_safe +
1208 " | ".html_safe +
1164 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1209 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1165 end
1210 end
1166
1211
1167 def toggle_checkboxes_link(selector)
1212 def toggle_checkboxes_link(selector)
1168 link_to_function '',
1213 link_to_function '',
1169 "toggleCheckboxesBySelector('#{selector}')",
1214 "toggleCheckboxesBySelector('#{selector}')",
1170 :title => "#{l(:button_check_all)} / #{l(:button_uncheck_all)}",
1215 :title => "#{l(:button_check_all)} / #{l(:button_uncheck_all)}",
1171 :class => 'toggle-checkboxes'
1216 :class => 'toggle-checkboxes'
1172 end
1217 end
1173
1218
1174 def progress_bar(pcts, options={})
1219 def progress_bar(pcts, options={})
1175 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1220 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1176 pcts = pcts.collect(&:round)
1221 pcts = pcts.collect(&:round)
1177 pcts[1] = pcts[1] - pcts[0]
1222 pcts[1] = pcts[1] - pcts[0]
1178 pcts << (100 - pcts[1] - pcts[0])
1223 pcts << (100 - pcts[1] - pcts[0])
1179 titles = options[:titles].to_a
1224 titles = options[:titles].to_a
1180 titles[0] = "#{pcts[0]}%" if titles[0].blank?
1225 titles[0] = "#{pcts[0]}%" if titles[0].blank?
1181 legend = options[:legend] || ''
1226 legend = options[:legend] || ''
1182 content_tag('table',
1227 content_tag('table',
1183 content_tag('tr',
1228 content_tag('tr',
1184 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed', :title => titles[0]) : ''.html_safe) +
1229 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed', :title => titles[0]) : ''.html_safe) +
1185 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done', :title => titles[1]) : ''.html_safe) +
1230 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done', :title => titles[1]) : ''.html_safe) +
1186 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo', :title => titles[2]) : ''.html_safe)
1231 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo', :title => titles[2]) : ''.html_safe)
1187 ), :class => "progress progress-#{pcts[0]}").html_safe +
1232 ), :class => "progress progress-#{pcts[0]}").html_safe +
1188 content_tag('p', legend, :class => 'percent').html_safe
1233 content_tag('p', legend, :class => 'percent').html_safe
1189 end
1234 end
1190
1235
1191 def checked_image(checked=true)
1236 def checked_image(checked=true)
1192 if checked
1237 if checked
1193 @checked_image_tag ||= content_tag(:span, nil, :class => 'icon-only icon-checked')
1238 @checked_image_tag ||= content_tag(:span, nil, :class => 'icon-only icon-checked')
1194 end
1239 end
1195 end
1240 end
1196
1241
1197 def context_menu
1242 def context_menu
1198 unless @context_menu_included
1243 unless @context_menu_included
1199 content_for :header_tags do
1244 content_for :header_tags do
1200 javascript_include_tag('context_menu') +
1245 javascript_include_tag('context_menu') +
1201 stylesheet_link_tag('context_menu')
1246 stylesheet_link_tag('context_menu')
1202 end
1247 end
1203 if l(:direction) == 'rtl'
1248 if l(:direction) == 'rtl'
1204 content_for :header_tags do
1249 content_for :header_tags do
1205 stylesheet_link_tag('context_menu_rtl')
1250 stylesheet_link_tag('context_menu_rtl')
1206 end
1251 end
1207 end
1252 end
1208 @context_menu_included = true
1253 @context_menu_included = true
1209 end
1254 end
1210 nil
1255 nil
1211 end
1256 end
1212
1257
1213 def calendar_for(field_id)
1258 def calendar_for(field_id)
1214 include_calendar_headers_tags
1259 include_calendar_headers_tags
1215 javascript_tag("$(function() { $('##{field_id}').addClass('date').datepickerFallback(datepickerOptions); });")
1260 javascript_tag("$(function() { $('##{field_id}').addClass('date').datepickerFallback(datepickerOptions); });")
1216 end
1261 end
1217
1262
1218 def include_calendar_headers_tags
1263 def include_calendar_headers_tags
1219 unless @calendar_headers_tags_included
1264 unless @calendar_headers_tags_included
1220 tags = ''.html_safe
1265 tags = ''.html_safe
1221 @calendar_headers_tags_included = true
1266 @calendar_headers_tags_included = true
1222 content_for :header_tags do
1267 content_for :header_tags do
1223 start_of_week = Setting.start_of_week
1268 start_of_week = Setting.start_of_week
1224 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1269 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1225 # Redmine uses 1..7 (monday..sunday) in settings and locales
1270 # Redmine uses 1..7 (monday..sunday) in settings and locales
1226 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1271 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1227 start_of_week = start_of_week.to_i % 7
1272 start_of_week = start_of_week.to_i % 7
1228 tags << javascript_tag(
1273 tags << javascript_tag(
1229 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1274 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1230 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1275 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1231 path_to_image('/images/calendar.png') +
1276 path_to_image('/images/calendar.png') +
1232 "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " +
1277 "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " +
1233 "selectOtherMonths: true, changeMonth: true, changeYear: true, " +
1278 "selectOtherMonths: true, changeMonth: true, changeYear: true, " +
1234 "beforeShow: beforeShowDatePicker};")
1279 "beforeShow: beforeShowDatePicker};")
1235 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1280 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1236 unless jquery_locale == 'en'
1281 unless jquery_locale == 'en'
1237 tags << javascript_include_tag("i18n/datepicker-#{jquery_locale}.js")
1282 tags << javascript_include_tag("i18n/datepicker-#{jquery_locale}.js")
1238 end
1283 end
1239 tags
1284 tags
1240 end
1285 end
1241 end
1286 end
1242 end
1287 end
1243
1288
1244 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1289 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1245 # Examples:
1290 # Examples:
1246 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1291 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1247 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1292 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1248 #
1293 #
1249 def stylesheet_link_tag(*sources)
1294 def stylesheet_link_tag(*sources)
1250 options = sources.last.is_a?(Hash) ? sources.pop : {}
1295 options = sources.last.is_a?(Hash) ? sources.pop : {}
1251 plugin = options.delete(:plugin)
1296 plugin = options.delete(:plugin)
1252 sources = sources.map do |source|
1297 sources = sources.map do |source|
1253 if plugin
1298 if plugin
1254 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1299 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1255 elsif current_theme && current_theme.stylesheets.include?(source)
1300 elsif current_theme && current_theme.stylesheets.include?(source)
1256 current_theme.stylesheet_path(source)
1301 current_theme.stylesheet_path(source)
1257 else
1302 else
1258 source
1303 source
1259 end
1304 end
1260 end
1305 end
1261 super *sources, options
1306 super *sources, options
1262 end
1307 end
1263
1308
1264 # Overrides Rails' image_tag with themes and plugins support.
1309 # Overrides Rails' image_tag with themes and plugins support.
1265 # Examples:
1310 # Examples:
1266 # image_tag('image.png') # => picks image.png from the current theme or defaults
1311 # image_tag('image.png') # => picks image.png from the current theme or defaults
1267 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1312 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1268 #
1313 #
1269 def image_tag(source, options={})
1314 def image_tag(source, options={})
1270 if plugin = options.delete(:plugin)
1315 if plugin = options.delete(:plugin)
1271 source = "/plugin_assets/#{plugin}/images/#{source}"
1316 source = "/plugin_assets/#{plugin}/images/#{source}"
1272 elsif current_theme && current_theme.images.include?(source)
1317 elsif current_theme && current_theme.images.include?(source)
1273 source = current_theme.image_path(source)
1318 source = current_theme.image_path(source)
1274 end
1319 end
1275 super source, options
1320 super source, options
1276 end
1321 end
1277
1322
1278 # Overrides Rails' javascript_include_tag with plugins support
1323 # Overrides Rails' javascript_include_tag with plugins support
1279 # Examples:
1324 # Examples:
1280 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1325 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1281 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1326 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1282 #
1327 #
1283 def javascript_include_tag(*sources)
1328 def javascript_include_tag(*sources)
1284 options = sources.last.is_a?(Hash) ? sources.pop : {}
1329 options = sources.last.is_a?(Hash) ? sources.pop : {}
1285 if plugin = options.delete(:plugin)
1330 if plugin = options.delete(:plugin)
1286 sources = sources.map do |source|
1331 sources = sources.map do |source|
1287 if plugin
1332 if plugin
1288 "/plugin_assets/#{plugin}/javascripts/#{source}"
1333 "/plugin_assets/#{plugin}/javascripts/#{source}"
1289 else
1334 else
1290 source
1335 source
1291 end
1336 end
1292 end
1337 end
1293 end
1338 end
1294 super *sources, options
1339 super *sources, options
1295 end
1340 end
1296
1341
1297 def sidebar_content?
1342 def sidebar_content?
1298 content_for?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1343 content_for?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1299 end
1344 end
1300
1345
1301 def view_layouts_base_sidebar_hook_response
1346 def view_layouts_base_sidebar_hook_response
1302 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1347 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1303 end
1348 end
1304
1349
1305 def email_delivery_enabled?
1350 def email_delivery_enabled?
1306 !!ActionMailer::Base.perform_deliveries
1351 !!ActionMailer::Base.perform_deliveries
1307 end
1352 end
1308
1353
1309 # Returns the avatar image tag for the given +user+ if avatars are enabled
1354 # Returns the avatar image tag for the given +user+ if avatars are enabled
1310 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1355 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1311 def avatar(user, options = { })
1356 def avatar(user, options = { })
1312 if Setting.gravatar_enabled?
1357 if Setting.gravatar_enabled?
1313 options.merge!(:default => Setting.gravatar_default)
1358 options.merge!(:default => Setting.gravatar_default)
1314 email = nil
1359 email = nil
1315 if user.respond_to?(:mail)
1360 if user.respond_to?(:mail)
1316 email = user.mail
1361 email = user.mail
1317 elsif user.to_s =~ %r{<(.+?)>}
1362 elsif user.to_s =~ %r{<(.+?)>}
1318 email = $1
1363 email = $1
1319 end
1364 end
1320 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1365 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1321 else
1366 else
1322 ''
1367 ''
1323 end
1368 end
1324 end
1369 end
1325
1370
1326 # Returns a link to edit user's avatar if avatars are enabled
1371 # Returns a link to edit user's avatar if avatars are enabled
1327 def avatar_edit_link(user, options={})
1372 def avatar_edit_link(user, options={})
1328 if Setting.gravatar_enabled?
1373 if Setting.gravatar_enabled?
1329 url = "https://gravatar.com"
1374 url = "https://gravatar.com"
1330 link_to avatar(user, {:title => l(:button_edit)}.merge(options)), url, :target => '_blank'
1375 link_to avatar(user, {:title => l(:button_edit)}.merge(options)), url, :target => '_blank'
1331 end
1376 end
1332 end
1377 end
1333
1378
1334 def sanitize_anchor_name(anchor)
1379 def sanitize_anchor_name(anchor)
1335 anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1380 anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1336 end
1381 end
1337
1382
1338 # Returns the javascript tags that are included in the html layout head
1383 # Returns the javascript tags that are included in the html layout head
1339 def javascript_heads
1384 def javascript_heads
1340 tags = javascript_include_tag('jquery-1.11.1-ui-1.11.0-ujs-3.1.4', 'application', 'responsive')
1385 tags = javascript_include_tag('jquery-1.11.1-ui-1.11.0-ujs-3.1.4', 'application', 'responsive')
1341 unless User.current.pref.warn_on_leaving_unsaved == '0'
1386 unless User.current.pref.warn_on_leaving_unsaved == '0'
1342 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1387 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1343 end
1388 end
1344 tags
1389 tags
1345 end
1390 end
1346
1391
1347 def favicon
1392 def favicon
1348 "<link rel='shortcut icon' href='#{favicon_path}' />".html_safe
1393 "<link rel='shortcut icon' href='#{favicon_path}' />".html_safe
1349 end
1394 end
1350
1395
1351 # Returns the path to the favicon
1396 # Returns the path to the favicon
1352 def favicon_path
1397 def favicon_path
1353 icon = (current_theme && current_theme.favicon?) ? current_theme.favicon_path : '/favicon.ico'
1398 icon = (current_theme && current_theme.favicon?) ? current_theme.favicon_path : '/favicon.ico'
1354 image_path(icon)
1399 image_path(icon)
1355 end
1400 end
1356
1401
1357 # Returns the full URL to the favicon
1402 # Returns the full URL to the favicon
1358 def favicon_url
1403 def favicon_url
1359 # TODO: use #image_url introduced in Rails4
1404 # TODO: use #image_url introduced in Rails4
1360 path = favicon_path
1405 path = favicon_path
1361 base = url_for(:controller => 'welcome', :action => 'index', :only_path => false)
1406 base = url_for(:controller => 'welcome', :action => 'index', :only_path => false)
1362 base.sub(%r{/+$},'') + '/' + path.sub(%r{^/+},'')
1407 base.sub(%r{/+$},'') + '/' + path.sub(%r{^/+},'')
1363 end
1408 end
1364
1409
1365 def robot_exclusion_tag
1410 def robot_exclusion_tag
1366 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1411 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1367 end
1412 end
1368
1413
1369 # Returns true if arg is expected in the API response
1414 # Returns true if arg is expected in the API response
1370 def include_in_api_response?(arg)
1415 def include_in_api_response?(arg)
1371 unless @included_in_api_response
1416 unless @included_in_api_response
1372 param = params[:include]
1417 param = params[:include]
1373 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1418 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1374 @included_in_api_response.collect!(&:strip)
1419 @included_in_api_response.collect!(&:strip)
1375 end
1420 end
1376 @included_in_api_response.include?(arg.to_s)
1421 @included_in_api_response.include?(arg.to_s)
1377 end
1422 end
1378
1423
1379 # Returns options or nil if nometa param or X-Redmine-Nometa header
1424 # Returns options or nil if nometa param or X-Redmine-Nometa header
1380 # was set in the request
1425 # was set in the request
1381 def api_meta(options)
1426 def api_meta(options)
1382 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1427 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1383 # compatibility mode for activeresource clients that raise
1428 # compatibility mode for activeresource clients that raise
1384 # an error when deserializing an array with attributes
1429 # an error when deserializing an array with attributes
1385 nil
1430 nil
1386 else
1431 else
1387 options
1432 options
1388 end
1433 end
1389 end
1434 end
1390
1435
1391 def generate_csv(&block)
1436 def generate_csv(&block)
1392 decimal_separator = l(:general_csv_decimal_separator)
1437 decimal_separator = l(:general_csv_decimal_separator)
1393 encoding = l(:general_csv_encoding)
1438 encoding = l(:general_csv_encoding)
1394 end
1439 end
1395
1440
1396 private
1441 private
1397
1442
1398 def wiki_helper
1443 def wiki_helper
1399 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1444 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1400 extend helper
1445 extend helper
1401 return self
1446 return self
1402 end
1447 end
1403 end
1448 end
General Comments 0
You need to be logged in to leave comments. Login now