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