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