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