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