##// END OF EJS Templates
Code cleanup....
Etienne Massip -
r9716:56cf38135720
parent child
Show More
@@ -1,1207 +1,1208
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2012 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 require 'forwardable'
21 21 require 'cgi'
22 22
23 23 module ApplicationHelper
24 24 include Redmine::WikiFormatting::Macros::Definitions
25 25 include Redmine::I18n
26 26 include GravatarHelper::PublicMethods
27 27
28 28 extend Forwardable
29 29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
30 30
31 31 # Return true if user is authorized for controller/action, otherwise false
32 32 def authorize_for(controller, action)
33 33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
34 34 end
35 35
36 36 # Display a link if user is authorized
37 37 #
38 38 # @param [String] name Anchor text (passed to link_to)
39 39 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
40 40 # @param [optional, Hash] html_options Options passed to link_to
41 41 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
42 42 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
43 43 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
44 44 end
45 45
46 46 # Display a link to remote if user is authorized
47 47 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
48 48 url = options[:url] || {}
49 49 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
50 50 end
51 51
52 52 # Displays a link to user's account page if active
53 53 def link_to_user(user, options={})
54 54 if user.is_a?(User)
55 55 name = h(user.name(options[:format]))
56 56 if user.active?
57 57 link_to name, :controller => 'users', :action => 'show', :id => user
58 58 else
59 59 name
60 60 end
61 61 else
62 62 h(user.to_s)
63 63 end
64 64 end
65 65
66 66 # Displays a link to +issue+ with its subject.
67 67 # Examples:
68 68 #
69 69 # link_to_issue(issue) # => Defect #6: This is the subject
70 70 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
71 71 # link_to_issue(issue, :subject => false) # => Defect #6
72 72 # link_to_issue(issue, :project => true) # => Foo - Defect #6
73 73 #
74 74 def link_to_issue(issue, options={})
75 75 title = nil
76 76 subject = nil
77 77 if options[:subject] == false
78 78 title = truncate(issue.subject, :length => 60)
79 79 else
80 80 subject = issue.subject
81 81 if options[:truncate]
82 82 subject = truncate(subject, :length => options[:truncate])
83 83 end
84 84 end
85 85 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
86 86 :class => issue.css_classes,
87 87 :title => title
88 88 s << h(": #{subject}") if subject
89 89 s = h("#{issue.project} - ") + s if options[:project]
90 90 s
91 91 end
92 92
93 93 # Generates a link to an attachment.
94 94 # Options:
95 95 # * :text - Link text (default to attachment filename)
96 96 # * :download - Force download (default: false)
97 97 def link_to_attachment(attachment, options={})
98 98 text = options.delete(:text) || attachment.filename
99 99 action = options.delete(:download) ? 'download' : 'show'
100 100 opt_only_path = {}
101 101 opt_only_path[:only_path] = (options[:only_path] == false ? false : true)
102 102 options.delete(:only_path)
103 103 link_to(h(text),
104 104 {:controller => 'attachments', :action => action,
105 105 :id => attachment, :filename => attachment.filename}.merge(opt_only_path),
106 106 options)
107 107 end
108 108
109 109 # Generates a link to a SCM revision
110 110 # Options:
111 111 # * :text - Link text (default to the formatted revision)
112 112 def link_to_revision(revision, repository, options={})
113 113 if repository.is_a?(Project)
114 114 repository = repository.repository
115 115 end
116 116 text = options.delete(:text) || format_revision(revision)
117 117 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
118 118 link_to(
119 119 h(text),
120 120 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
121 121 :title => l(:label_revision_id, format_revision(revision))
122 122 )
123 123 end
124 124
125 125 # Generates a link to a message
126 126 def link_to_message(message, options={}, html_options = nil)
127 127 link_to(
128 128 h(truncate(message.subject, :length => 60)),
129 129 { :controller => 'messages', :action => 'show',
130 130 :board_id => message.board_id,
131 131 :id => message.root,
132 132 :r => (message.parent_id && message.id),
133 133 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
134 134 }.merge(options),
135 135 html_options
136 136 )
137 137 end
138 138
139 139 # Generates a link to a project if active
140 140 # Examples:
141 141 #
142 142 # link_to_project(project) # => link to the specified project overview
143 143 # link_to_project(project, :action=>'settings') # => link to project settings
144 144 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
145 145 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
146 146 #
147 147 def link_to_project(project, options={}, html_options = nil)
148 148 if project.archived?
149 149 h(project)
150 150 else
151 151 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
152 152 link_to(h(project), url, html_options)
153 153 end
154 154 end
155 155
156 156 def toggle_link(name, id, options={})
157 157 onclick = "Element.toggle('#{id}'); "
158 158 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
159 159 onclick << "return false;"
160 160 link_to(name, "#", :onclick => onclick)
161 161 end
162 162
163 163 def image_to_function(name, function, html_options = {})
164 164 html_options.symbolize_keys!
165 165 tag(:input, html_options.merge({
166 166 :type => "image", :src => image_path(name),
167 167 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
168 168 }))
169 169 end
170 170
171 171 def prompt_to_remote(name, text, param, url, html_options = {})
172 172 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
173 173 link_to name, {}, html_options
174 174 end
175 175
176 176 def format_activity_title(text)
177 177 h(truncate_single_line(text, :length => 100))
178 178 end
179 179
180 180 def format_activity_day(date)
181 181 date == User.current.today ? l(:label_today).titleize : format_date(date)
182 182 end
183 183
184 184 def format_activity_description(text)
185 185 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
186 186 ).gsub(/[\r\n]+/, "<br />").html_safe
187 187 end
188 188
189 189 def format_version_name(version)
190 190 if version.project == @project
191 191 h(version)
192 192 else
193 193 h("#{version.project} - #{version}")
194 194 end
195 195 end
196 196
197 197 def due_date_distance_in_words(date)
198 198 if date
199 199 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
200 200 end
201 201 end
202 202
203 203 def render_page_hierarchy(pages, node=nil, options={})
204 204 content = ''
205 205 if pages[node]
206 206 content << "<ul class=\"pages-hierarchy\">\n"
207 207 pages[node].each do |page|
208 208 content << "<li>"
209 209 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
210 210 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
211 211 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
212 212 content << "</li>\n"
213 213 end
214 214 content << "</ul>\n"
215 215 end
216 216 content.html_safe
217 217 end
218 218
219 219 # Renders flash messages
220 220 def render_flash_messages
221 221 s = ''
222 222 flash.each do |k,v|
223 223 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
224 224 end
225 225 s.html_safe
226 226 end
227 227
228 228 # Renders tabs and their content
229 229 def render_tabs(tabs)
230 230 if tabs.any?
231 231 render :partial => 'common/tabs', :locals => {:tabs => tabs}
232 232 else
233 233 content_tag 'p', l(:label_no_data), :class => "nodata"
234 234 end
235 235 end
236 236
237 237 # Renders the project quick-jump box
238 238 def render_project_jump_box
239 239 return unless User.current.logged?
240 240 projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq
241 241 if projects.any?
242 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
243 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
244 '<option value="" disabled="disabled">---</option>'
245 s << project_tree_options_for_select(projects, :selected => @project) do |p|
246 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
242 options =
243 ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
244 '<option value="" disabled="disabled">---</option>').html_safe
245
246 options << project_tree_options_for_select(projects, :selected => @project) do |p|
247 { :value => project_path(:id => p, :jump => current_menu_item) }
247 248 end
248 s << '</select>'
249 s.html_safe
249
250 select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
250 251 end
251 252 end
252 253
253 254 def project_tree_options_for_select(projects, options = {})
254 255 s = ''
255 256 project_tree(projects) do |project, level|
256 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '').html_safe
257 name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
257 258 tag_options = {:value => project.id}
258 259 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
259 260 tag_options[:selected] = 'selected'
260 261 else
261 262 tag_options[:selected] = nil
262 263 end
263 264 tag_options.merge!(yield(project)) if block_given?
264 265 s << content_tag('option', name_prefix + h(project), tag_options)
265 266 end
266 267 s.html_safe
267 268 end
268 269
269 270 # Yields the given block for each project with its level in the tree
270 271 #
271 272 # Wrapper for Project#project_tree
272 273 def project_tree(projects, &block)
273 274 Project.project_tree(projects, &block)
274 275 end
275 276
276 277 def project_nested_ul(projects, &block)
277 278 s = ''
278 279 if projects.any?
279 280 ancestors = []
280 281 projects.sort_by(&:lft).each do |project|
281 282 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
282 283 s << "<ul>\n"
283 284 else
284 285 ancestors.pop
285 286 s << "</li>"
286 287 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
287 288 ancestors.pop
288 289 s << "</ul></li>\n"
289 290 end
290 291 end
291 292 s << "<li>"
292 293 s << yield(project).to_s
293 294 ancestors << project
294 295 end
295 296 s << ("</li></ul>\n" * ancestors.size)
296 297 end
297 298 s.html_safe
298 299 end
299 300
300 301 def principals_check_box_tags(name, principals)
301 302 s = ''
302 303 principals.sort.each do |principal|
303 304 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
304 305 end
305 306 s.html_safe
306 307 end
307 308
308 309 # Returns a string for users/groups option tags
309 310 def principals_options_for_select(collection, selected=nil)
310 311 s = ''
311 312 if collection.include?(User.current)
312 313 s << content_tag('option', "<< #{l(:label_me)} >>".html_safe, :value => User.current.id)
313 314 end
314 315 groups = ''
315 316 collection.sort.each do |element|
316 317 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
317 318 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
318 319 end
319 320 unless groups.empty?
320 321 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
321 322 end
322 323 s.html_safe
323 324 end
324 325
325 326 # Truncates and returns the string as a single line
326 327 def truncate_single_line(string, *args)
327 328 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
328 329 end
329 330
330 331 # Truncates at line break after 250 characters or options[:length]
331 332 def truncate_lines(string, options={})
332 333 length = options[:length] || 250
333 334 if string.to_s =~ /\A(.{#{length}}.*?)$/m
334 335 "#{$1}..."
335 336 else
336 337 string
337 338 end
338 339 end
339 340
340 341 def anchor(text)
341 342 text.to_s.gsub(' ', '_')
342 343 end
343 344
344 345 def html_hours(text)
345 346 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
346 347 end
347 348
348 349 def authoring(created, author, options={})
349 350 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
350 351 end
351 352
352 353 def time_tag(time)
353 354 text = distance_of_time_in_words(Time.now, time)
354 355 if @project
355 356 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
356 357 else
357 358 content_tag('acronym', text, :title => format_time(time))
358 359 end
359 360 end
360 361
361 362 def syntax_highlight_lines(name, content)
362 363 lines = []
363 364 syntax_highlight(name, content).each_line { |line| lines << line }
364 365 lines
365 366 end
366 367
367 368 def syntax_highlight(name, content)
368 369 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
369 370 end
370 371
371 372 def to_path_param(path)
372 373 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
373 374 str.blank? ? nil : str
374 375 end
375 376
376 377 def pagination_links_full(paginator, count=nil, options={})
377 378 page_param = options.delete(:page_param) || :page
378 379 per_page_links = options.delete(:per_page_links)
379 380 url_param = params.dup
380 381
381 382 html = ''
382 383 if paginator.current.previous
383 384 # \xc2\xab(utf-8) = &#171;
384 385 html << link_to_content_update(
385 386 "\xc2\xab " + l(:label_previous),
386 387 url_param.merge(page_param => paginator.current.previous)) + ' '
387 388 end
388 389
389 390 html << (pagination_links_each(paginator, options) do |n|
390 391 link_to_content_update(n.to_s, url_param.merge(page_param => n))
391 392 end || '')
392 393
393 394 if paginator.current.next
394 395 # \xc2\xbb(utf-8) = &#187;
395 396 html << ' ' + link_to_content_update(
396 397 (l(:label_next) + " \xc2\xbb"),
397 398 url_param.merge(page_param => paginator.current.next))
398 399 end
399 400
400 401 unless count.nil?
401 402 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
402 403 if per_page_links != false && links = per_page_links(paginator.items_per_page, count)
403 404 html << " | #{links}"
404 405 end
405 406 end
406 407
407 408 html.html_safe
408 409 end
409 410
410 411 def per_page_links(selected=nil, item_count=nil)
411 412 values = Setting.per_page_options_array
412 413 if item_count && values.any?
413 414 if item_count > values.first
414 415 max = values.detect {|value| value >= item_count} || item_count
415 416 else
416 417 max = item_count
417 418 end
418 419 values = values.select {|value| value <= max || value == selected}
419 420 end
420 421 if values.empty? || (values.size == 1 && values.first == selected)
421 422 return nil
422 423 end
423 424 links = values.collect do |n|
424 425 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
425 426 end
426 427 l(:label_display_per_page, links.join(', '))
427 428 end
428 429
429 430 def reorder_links(name, url, method = :post)
430 431 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
431 432 url.merge({"#{name}[move_to]" => 'highest'}),
432 433 :method => method, :title => l(:label_sort_highest)) +
433 434 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
434 435 url.merge({"#{name}[move_to]" => 'higher'}),
435 436 :method => method, :title => l(:label_sort_higher)) +
436 437 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
437 438 url.merge({"#{name}[move_to]" => 'lower'}),
438 439 :method => method, :title => l(:label_sort_lower)) +
439 440 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
440 441 url.merge({"#{name}[move_to]" => 'lowest'}),
441 442 :method => method, :title => l(:label_sort_lowest))
442 443 end
443 444
444 445 def breadcrumb(*args)
445 446 elements = args.flatten
446 447 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
447 448 end
448 449
449 450 def other_formats_links(&block)
450 451 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
451 452 yield Redmine::Views::OtherFormatsBuilder.new(self)
452 453 concat('</p>'.html_safe)
453 454 end
454 455
455 456 def page_header_title
456 457 if @project.nil? || @project.new_record?
457 458 h(Setting.app_title)
458 459 else
459 460 b = []
460 461 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
461 462 if ancestors.any?
462 463 root = ancestors.shift
463 464 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
464 465 if ancestors.size > 2
465 466 b << "\xe2\x80\xa6"
466 467 ancestors = ancestors[-2, 2]
467 468 end
468 469 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
469 470 end
470 471 b << h(@project)
471 472 b.join(" \xc2\xbb ").html_safe
472 473 end
473 474 end
474 475
475 476 def html_title(*args)
476 477 if args.empty?
477 478 title = @html_title || []
478 479 title << @project.name if @project
479 480 title << Setting.app_title unless Setting.app_title == title.last
480 481 title.select {|t| !t.blank? }.join(' - ')
481 482 else
482 483 @html_title ||= []
483 484 @html_title += args
484 485 end
485 486 end
486 487
487 488 # Returns the theme, controller name, and action as css classes for the
488 489 # HTML body.
489 490 def body_css_classes
490 491 css = []
491 492 if theme = Redmine::Themes.theme(Setting.ui_theme)
492 493 css << 'theme-' + theme.name
493 494 end
494 495
495 496 css << 'controller-' + controller_name
496 497 css << 'action-' + action_name
497 498 css.join(' ')
498 499 end
499 500
500 501 def accesskey(s)
501 502 Redmine::AccessKeys.key_for s
502 503 end
503 504
504 505 # Formats text according to system settings.
505 506 # 2 ways to call this method:
506 507 # * with a String: textilizable(text, options)
507 508 # * with an object and one of its attribute: textilizable(issue, :description, options)
508 509 def textilizable(*args)
509 510 options = args.last.is_a?(Hash) ? args.pop : {}
510 511 case args.size
511 512 when 1
512 513 obj = options[:object]
513 514 text = args.shift
514 515 when 2
515 516 obj = args.shift
516 517 attr = args.shift
517 518 text = obj.send(attr).to_s
518 519 else
519 520 raise ArgumentError, 'invalid arguments to textilizable'
520 521 end
521 522 return '' if text.blank?
522 523 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
523 524 only_path = options.delete(:only_path) == false ? false : true
524 525
525 526 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
526 527
527 528 @parsed_headings = []
528 529 @heading_anchors = {}
529 530 @current_section = 0 if options[:edit_section_links]
530 531
531 532 parse_sections(text, project, obj, attr, only_path, options)
532 533 text = parse_non_pre_blocks(text) do |text|
533 534 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros].each do |method_name|
534 535 send method_name, text, project, obj, attr, only_path, options
535 536 end
536 537 end
537 538 parse_headings(text, project, obj, attr, only_path, options)
538 539
539 540 if @parsed_headings.any?
540 541 replace_toc(text, @parsed_headings)
541 542 end
542 543
543 544 text.html_safe
544 545 end
545 546
546 547 def parse_non_pre_blocks(text)
547 548 s = StringScanner.new(text)
548 549 tags = []
549 550 parsed = ''
550 551 while !s.eos?
551 552 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
552 553 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
553 554 if tags.empty?
554 555 yield text
555 556 end
556 557 parsed << text
557 558 if tag
558 559 if closing
559 560 if tags.last == tag.downcase
560 561 tags.pop
561 562 end
562 563 else
563 564 tags << tag.downcase
564 565 end
565 566 parsed << full_tag
566 567 end
567 568 end
568 569 # Close any non closing tags
569 570 while tag = tags.pop
570 571 parsed << "</#{tag}>"
571 572 end
572 573 parsed
573 574 end
574 575
575 576 def parse_inline_attachments(text, project, obj, attr, only_path, options)
576 577 # when using an image link, try to use an attachment, if possible
577 578 if options[:attachments] || (obj && obj.respond_to?(:attachments))
578 579 attachments = options[:attachments] || obj.attachments
579 580 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
580 581 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
581 582 # search for the picture in attachments
582 583 if found = Attachment.latest_attach(attachments, filename)
583 584 image_url = url_for :only_path => only_path, :controller => 'attachments',
584 585 :action => 'download', :id => found
585 586 desc = found.description.to_s.gsub('"', '')
586 587 if !desc.blank? && alttext.blank?
587 588 alt = " title=\"#{desc}\" alt=\"#{desc}\""
588 589 end
589 590 "src=\"#{image_url}\"#{alt}"
590 591 else
591 592 m
592 593 end
593 594 end
594 595 end
595 596 end
596 597
597 598 # Wiki links
598 599 #
599 600 # Examples:
600 601 # [[mypage]]
601 602 # [[mypage|mytext]]
602 603 # wiki links can refer other project wikis, using project name or identifier:
603 604 # [[project:]] -> wiki starting page
604 605 # [[project:|mytext]]
605 606 # [[project:mypage]]
606 607 # [[project:mypage|mytext]]
607 608 def parse_wiki_links(text, project, obj, attr, only_path, options)
608 609 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
609 610 link_project = project
610 611 esc, all, page, title = $1, $2, $3, $5
611 612 if esc.nil?
612 613 if page =~ /^([^\:]+)\:(.*)$/
613 614 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
614 615 page = $2
615 616 title ||= $1 if page.blank?
616 617 end
617 618
618 619 if link_project && link_project.wiki
619 620 # extract anchor
620 621 anchor = nil
621 622 if page =~ /^(.+?)\#(.+)$/
622 623 page, anchor = $1, $2
623 624 end
624 625 anchor = sanitize_anchor_name(anchor) if anchor.present?
625 626 # check if page exists
626 627 wiki_page = link_project.wiki.find_page(page)
627 628 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
628 629 "##{anchor}"
629 630 else
630 631 case options[:wiki_links]
631 632 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
632 633 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
633 634 else
634 635 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
635 636 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
636 637 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
637 638 :id => wiki_page_id, :anchor => anchor, :parent => parent)
638 639 end
639 640 end
640 641 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
641 642 else
642 643 # project or wiki doesn't exist
643 644 all
644 645 end
645 646 else
646 647 all
647 648 end
648 649 end
649 650 end
650 651
651 652 # Redmine links
652 653 #
653 654 # Examples:
654 655 # Issues:
655 656 # #52 -> Link to issue #52
656 657 # Changesets:
657 658 # r52 -> Link to revision 52
658 659 # commit:a85130f -> Link to scmid starting with a85130f
659 660 # Documents:
660 661 # document#17 -> Link to document with id 17
661 662 # document:Greetings -> Link to the document with title "Greetings"
662 663 # document:"Some document" -> Link to the document with title "Some document"
663 664 # Versions:
664 665 # version#3 -> Link to version with id 3
665 666 # version:1.0.0 -> Link to version named "1.0.0"
666 667 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
667 668 # Attachments:
668 669 # attachment:file.zip -> Link to the attachment of the current object named file.zip
669 670 # Source files:
670 671 # source:some/file -> Link to the file located at /some/file in the project's repository
671 672 # source:some/file@52 -> Link to the file's revision 52
672 673 # source:some/file#L120 -> Link to line 120 of the file
673 674 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
674 675 # export:some/file -> Force the download of the file
675 676 # Forum messages:
676 677 # message#1218 -> Link to message with id 1218
677 678 #
678 679 # Links can refer other objects from other projects, using project identifier:
679 680 # identifier:r52
680 681 # identifier:document:"Some document"
681 682 # identifier:version:1.0.0
682 683 # identifier:source:some/file
683 684 def parse_redmine_links(text, project, obj, attr, only_path, options)
684 685 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
685 686 leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
686 687 link = nil
687 688 if project_identifier
688 689 project = Project.visible.find_by_identifier(project_identifier)
689 690 end
690 691 if esc.nil?
691 692 if prefix.nil? && sep == 'r'
692 693 if project
693 694 repository = nil
694 695 if repo_identifier
695 696 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
696 697 else
697 698 repository = project.repository
698 699 end
699 700 # project.changesets.visible raises an SQL error because of a double join on repositories
700 701 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
701 702 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision},
702 703 :class => 'changeset',
703 704 :title => truncate_single_line(changeset.comments, :length => 100))
704 705 end
705 706 end
706 707 elsif sep == '#'
707 708 oid = identifier.to_i
708 709 case prefix
709 710 when nil
710 711 if issue = Issue.visible.find_by_id(oid, :include => :status)
711 712 anchor = comment_id ? "note-#{comment_id}" : nil
712 713 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
713 714 :class => issue.css_classes,
714 715 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
715 716 end
716 717 when 'document'
717 718 if document = Document.visible.find_by_id(oid)
718 719 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
719 720 :class => 'document'
720 721 end
721 722 when 'version'
722 723 if version = Version.visible.find_by_id(oid)
723 724 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
724 725 :class => 'version'
725 726 end
726 727 when 'message'
727 728 if message = Message.visible.find_by_id(oid, :include => :parent)
728 729 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
729 730 end
730 731 when 'forum'
731 732 if board = Board.visible.find_by_id(oid)
732 733 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
733 734 :class => 'board'
734 735 end
735 736 when 'news'
736 737 if news = News.visible.find_by_id(oid)
737 738 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
738 739 :class => 'news'
739 740 end
740 741 when 'project'
741 742 if p = Project.visible.find_by_id(oid)
742 743 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
743 744 end
744 745 end
745 746 elsif sep == ':'
746 747 # removes the double quotes if any
747 748 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
748 749 case prefix
749 750 when 'document'
750 751 if project && document = project.documents.visible.find_by_title(name)
751 752 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
752 753 :class => 'document'
753 754 end
754 755 when 'version'
755 756 if project && version = project.versions.visible.find_by_name(name)
756 757 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
757 758 :class => 'version'
758 759 end
759 760 when 'forum'
760 761 if project && board = project.boards.visible.find_by_name(name)
761 762 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
762 763 :class => 'board'
763 764 end
764 765 when 'news'
765 766 if project && news = project.news.visible.find_by_title(name)
766 767 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
767 768 :class => 'news'
768 769 end
769 770 when 'commit', 'source', 'export'
770 771 if project
771 772 repository = nil
772 773 if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
773 774 repo_prefix, repo_identifier, name = $1, $2, $3
774 775 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
775 776 else
776 777 repository = project.repository
777 778 end
778 779 if prefix == 'commit'
779 780 if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"]))
780 781 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},
781 782 :class => 'changeset',
782 783 :title => truncate_single_line(h(changeset.comments), :length => 100)
783 784 end
784 785 else
785 786 if repository && User.current.allowed_to?(:browse_repository, project)
786 787 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
787 788 path, rev, anchor = $1, $3, $5
788 789 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :repository_id => repository.identifier_param,
789 790 :path => to_path_param(path),
790 791 :rev => rev,
791 792 :anchor => anchor,
792 793 :format => (prefix == 'export' ? 'raw' : nil)},
793 794 :class => (prefix == 'export' ? 'source download' : 'source')
794 795 end
795 796 end
796 797 repo_prefix = nil
797 798 end
798 799 when 'attachment'
799 800 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
800 801 if attachments && attachment = attachments.detect {|a| a.filename == name }
801 802 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
802 803 :class => 'attachment'
803 804 end
804 805 when 'project'
805 806 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
806 807 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
807 808 end
808 809 end
809 810 end
810 811 end
811 812 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
812 813 end
813 814 end
814 815
815 816 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
816 817
817 818 def parse_sections(text, project, obj, attr, only_path, options)
818 819 return unless options[:edit_section_links]
819 820 text.gsub!(HEADING_RE) do
820 821 heading = $1
821 822 @current_section += 1
822 823 if @current_section > 1
823 824 content_tag('div',
824 825 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
825 826 :class => 'contextual',
826 827 :title => l(:button_edit_section)) + heading.html_safe
827 828 else
828 829 heading
829 830 end
830 831 end
831 832 end
832 833
833 834 # Headings and TOC
834 835 # Adds ids and links to headings unless options[:headings] is set to false
835 836 def parse_headings(text, project, obj, attr, only_path, options)
836 837 return if options[:headings] == false
837 838
838 839 text.gsub!(HEADING_RE) do
839 840 level, attrs, content = $2.to_i, $3, $4
840 841 item = strip_tags(content).strip
841 842 anchor = sanitize_anchor_name(item)
842 843 # used for single-file wiki export
843 844 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
844 845 @heading_anchors[anchor] ||= 0
845 846 idx = (@heading_anchors[anchor] += 1)
846 847 if idx > 1
847 848 anchor = "#{anchor}-#{idx}"
848 849 end
849 850 @parsed_headings << [level, anchor, item]
850 851 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
851 852 end
852 853 end
853 854
854 855 MACROS_RE = /
855 856 (!)? # escaping
856 857 (
857 858 \{\{ # opening tag
858 859 ([\w]+) # macro name
859 860 (\(([^\}]*)\))? # optional arguments
860 861 \}\} # closing tag
861 862 )
862 863 /x unless const_defined?(:MACROS_RE)
863 864
864 865 # Macros substitution
865 866 def parse_macros(text, project, obj, attr, only_path, options)
866 867 text.gsub!(MACROS_RE) do
867 868 esc, all, macro = $1, $2, $3.downcase
868 869 args = ($5 || '').split(',').each(&:strip)
869 870 if esc.nil?
870 871 begin
871 872 exec_macro(macro, obj, args)
872 873 rescue => e
873 874 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
874 875 end || all
875 876 else
876 877 all
877 878 end
878 879 end
879 880 end
880 881
881 882 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
882 883
883 884 # Renders the TOC with given headings
884 885 def replace_toc(text, headings)
885 886 text.gsub!(TOC_RE) do
886 887 if headings.empty?
887 888 ''
888 889 else
889 890 div_class = 'toc'
890 891 div_class << ' right' if $1 == '>'
891 892 div_class << ' left' if $1 == '<'
892 893 out = "<ul class=\"#{div_class}\"><li>"
893 894 root = headings.map(&:first).min
894 895 current = root
895 896 started = false
896 897 headings.each do |level, anchor, item|
897 898 if level > current
898 899 out << '<ul><li>' * (level - current)
899 900 elsif level < current
900 901 out << "</li></ul>\n" * (current - level) + "</li><li>"
901 902 elsif started
902 903 out << '</li><li>'
903 904 end
904 905 out << "<a href=\"##{anchor}\">#{item}</a>"
905 906 current = level
906 907 started = true
907 908 end
908 909 out << '</li></ul>' * (current - root)
909 910 out << '</li></ul>'
910 911 end
911 912 end
912 913 end
913 914
914 915 # Same as Rails' simple_format helper without using paragraphs
915 916 def simple_format_without_paragraph(text)
916 917 text.to_s.
917 918 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
918 919 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
919 920 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
920 921 html_safe
921 922 end
922 923
923 924 def lang_options_for_select(blank=true)
924 925 (blank ? [["(auto)", ""]] : []) +
925 926 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
926 927 end
927 928
928 929 def label_tag_for(name, option_tags = nil, options = {})
929 930 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
930 931 content_tag("label", label_text)
931 932 end
932 933
933 934 def labelled_tabular_form_for(*args, &proc)
934 935 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_tabular_form_for is deprecated and will be removed in Redmine 1.5. Use #labelled_form_for instead."
935 936 args << {} unless args.last.is_a?(Hash)
936 937 options = args.last
937 938 options[:html] ||= {}
938 939 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
939 940 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
940 941 form_for(*args, &proc)
941 942 end
942 943
943 944 def labelled_form_for(*args, &proc)
944 945 args << {} unless args.last.is_a?(Hash)
945 946 options = args.last
946 947 if args.first.is_a?(Symbol)
947 948 options.merge!(:as => args.shift)
948 949 end
949 950 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
950 951 form_for(*args, &proc)
951 952 end
952 953
953 954 def labelled_fields_for(*args, &proc)
954 955 args << {} unless args.last.is_a?(Hash)
955 956 options = args.last
956 957 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
957 958 fields_for(*args, &proc)
958 959 end
959 960
960 961 def labelled_remote_form_for(*args, &proc)
961 962 args << {} unless args.last.is_a?(Hash)
962 963 options = args.last
963 964 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
964 965 form_for(*args, &proc)
965 966 end
966 967
967 968 def error_messages_for(*objects)
968 969 html = ""
969 970 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
970 971 errors = objects.map {|o| o.errors.full_messages}.flatten
971 972 if errors.any?
972 973 html << "<div id='errorExplanation'><ul>\n"
973 974 errors.each do |error|
974 975 html << "<li>#{h error}</li>\n"
975 976 end
976 977 html << "</ul></div>\n"
977 978 end
978 979 html.html_safe
979 980 end
980 981
981 982 def back_url_hidden_field_tag
982 983 back_url = params[:back_url] || request.env['HTTP_REFERER']
983 984 back_url = CGI.unescape(back_url.to_s)
984 985 hidden_field_tag('back_url', CGI.escape(back_url), :id => nil) unless back_url.blank?
985 986 end
986 987
987 988 def check_all_links(form_name)
988 989 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
989 990 " | ".html_safe +
990 991 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
991 992 end
992 993
993 994 def progress_bar(pcts, options={})
994 995 pcts = [pcts, pcts] unless pcts.is_a?(Array)
995 996 pcts = pcts.collect(&:round)
996 997 pcts[1] = pcts[1] - pcts[0]
997 998 pcts << (100 - pcts[1] - pcts[0])
998 999 width = options[:width] || '100px;'
999 1000 legend = options[:legend] || ''
1000 1001 content_tag('table',
1001 1002 content_tag('tr',
1002 1003 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1003 1004 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1004 1005 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1005 1006 ), :class => 'progress', :style => "width: #{width};").html_safe +
1006 1007 content_tag('p', legend, :class => 'pourcent').html_safe
1007 1008 end
1008 1009
1009 1010 def checked_image(checked=true)
1010 1011 if checked
1011 1012 image_tag 'toggle_check.png'
1012 1013 end
1013 1014 end
1014 1015
1015 1016 def context_menu(url)
1016 1017 unless @context_menu_included
1017 1018 content_for :header_tags do
1018 1019 javascript_include_tag('context_menu') +
1019 1020 stylesheet_link_tag('context_menu')
1020 1021 end
1021 1022 if l(:direction) == 'rtl'
1022 1023 content_for :header_tags do
1023 1024 stylesheet_link_tag('context_menu_rtl')
1024 1025 end
1025 1026 end
1026 1027 @context_menu_included = true
1027 1028 end
1028 1029 javascript_tag "new ContextMenu('#{ url_for(url) }')"
1029 1030 end
1030 1031
1031 1032 def calendar_for(field_id)
1032 1033 include_calendar_headers_tags
1033 1034 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
1034 1035 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
1035 1036 end
1036 1037
1037 1038 def include_calendar_headers_tags
1038 1039 unless @calendar_headers_tags_included
1039 1040 @calendar_headers_tags_included = true
1040 1041 content_for :header_tags do
1041 1042 start_of_week = case Setting.start_of_week.to_i
1042 1043 when 1
1043 1044 'Calendar._FD = 1;' # Monday
1044 1045 when 7
1045 1046 'Calendar._FD = 0;' # Sunday
1046 1047 when 6
1047 1048 'Calendar._FD = 6;' # Saturday
1048 1049 else
1049 1050 '' # use language
1050 1051 end
1051 1052
1052 1053 javascript_include_tag('calendar/calendar') +
1053 1054 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
1054 1055 javascript_tag(start_of_week) +
1055 1056 javascript_include_tag('calendar/calendar-setup') +
1056 1057 stylesheet_link_tag('calendar')
1057 1058 end
1058 1059 end
1059 1060 end
1060 1061
1061 1062 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1062 1063 # Examples:
1063 1064 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1064 1065 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1065 1066 #
1066 1067 def stylesheet_link_tag(*sources)
1067 1068 options = sources.last.is_a?(Hash) ? sources.pop : {}
1068 1069 plugin = options.delete(:plugin)
1069 1070 sources = sources.map do |source|
1070 1071 if plugin
1071 1072 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1072 1073 elsif current_theme && current_theme.stylesheets.include?(source)
1073 1074 current_theme.stylesheet_path(source)
1074 1075 else
1075 1076 source
1076 1077 end
1077 1078 end
1078 1079 super sources, options
1079 1080 end
1080 1081
1081 1082 # Overrides Rails' image_tag with themes and plugins support.
1082 1083 # Examples:
1083 1084 # image_tag('image.png') # => picks image.png from the current theme or defaults
1084 1085 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1085 1086 #
1086 1087 def image_tag(source, options={})
1087 1088 if plugin = options.delete(:plugin)
1088 1089 source = "/plugin_assets/#{plugin}/images/#{source}"
1089 1090 elsif current_theme && current_theme.images.include?(source)
1090 1091 source = current_theme.image_path(source)
1091 1092 end
1092 1093 super source, options
1093 1094 end
1094 1095
1095 1096 # Overrides Rails' javascript_include_tag with plugins support
1096 1097 # Examples:
1097 1098 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1098 1099 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1099 1100 #
1100 1101 def javascript_include_tag(*sources)
1101 1102 options = sources.last.is_a?(Hash) ? sources.pop : {}
1102 1103 if plugin = options.delete(:plugin)
1103 1104 sources = sources.map do |source|
1104 1105 if plugin
1105 1106 "/plugin_assets/#{plugin}/javascripts/#{source}"
1106 1107 else
1107 1108 source
1108 1109 end
1109 1110 end
1110 1111 end
1111 1112 super sources, options
1112 1113 end
1113 1114
1114 1115 def content_for(name, content = nil, &block)
1115 1116 @has_content ||= {}
1116 1117 @has_content[name] = true
1117 1118 super(name, content, &block)
1118 1119 end
1119 1120
1120 1121 def has_content?(name)
1121 1122 (@has_content && @has_content[name]) || false
1122 1123 end
1123 1124
1124 1125 def sidebar_content?
1125 1126 has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1126 1127 end
1127 1128
1128 1129 def view_layouts_base_sidebar_hook_response
1129 1130 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1130 1131 end
1131 1132
1132 1133 def email_delivery_enabled?
1133 1134 !!ActionMailer::Base.perform_deliveries
1134 1135 end
1135 1136
1136 1137 # Returns the avatar image tag for the given +user+ if avatars are enabled
1137 1138 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1138 1139 def avatar(user, options = { })
1139 1140 if Setting.gravatar_enabled?
1140 1141 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1141 1142 email = nil
1142 1143 if user.respond_to?(:mail)
1143 1144 email = user.mail
1144 1145 elsif user.to_s =~ %r{<(.+?)>}
1145 1146 email = $1
1146 1147 end
1147 1148 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1148 1149 else
1149 1150 ''
1150 1151 end
1151 1152 end
1152 1153
1153 1154 def sanitize_anchor_name(anchor)
1154 1155 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1155 1156 end
1156 1157
1157 1158 # Returns the javascript tags that are included in the html layout head
1158 1159 def javascript_heads
1159 1160 tags = javascript_include_tag('prototype', 'effects', 'dragdrop', 'controls', 'rails', 'application')
1160 1161 unless User.current.pref.warn_on_leaving_unsaved == '0'
1161 1162 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
1162 1163 end
1163 1164 tags
1164 1165 end
1165 1166
1166 1167 def favicon
1167 1168 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1168 1169 end
1169 1170
1170 1171 def robot_exclusion_tag
1171 1172 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1172 1173 end
1173 1174
1174 1175 # Returns true if arg is expected in the API response
1175 1176 def include_in_api_response?(arg)
1176 1177 unless @included_in_api_response
1177 1178 param = params[:include]
1178 1179 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1179 1180 @included_in_api_response.collect!(&:strip)
1180 1181 end
1181 1182 @included_in_api_response.include?(arg.to_s)
1182 1183 end
1183 1184
1184 1185 # Returns options or nil if nometa param or X-Redmine-Nometa header
1185 1186 # was set in the request
1186 1187 def api_meta(options)
1187 1188 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1188 1189 # compatibility mode for activeresource clients that raise
1189 1190 # an error when unserializing an array with attributes
1190 1191 nil
1191 1192 else
1192 1193 options
1193 1194 end
1194 1195 end
1195 1196
1196 1197 private
1197 1198
1198 1199 def wiki_helper
1199 1200 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1200 1201 extend helper
1201 1202 return self
1202 1203 end
1203 1204
1204 1205 def link_to_content_update(text, url_params = {}, html_options = {})
1205 1206 link_to(text, url_params, html_options)
1206 1207 end
1207 1208 end
General Comments 0
You need to be logged in to leave comments. Login now