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