##// END OF EJS Templates
Wiki page versions routes cleanup....
Jean-Philippe Lang -
r10476:d66b3452db77
parent child
Show More
@@ -1,1273 +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 145 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
146 146 link_to(h(project), url, html_options)
147 147 end
148 148 end
149 149
150 150 def thumbnail_tag(attachment)
151 151 link_to image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment)),
152 152 {:controller => 'attachments', :action => 'show', :id => attachment, :filename => attachment.filename},
153 153 :title => attachment.filename
154 154 end
155 155
156 156 def toggle_link(name, id, options={})
157 157 onclick = "$('##{id}').toggle(); "
158 158 onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ")
159 159 onclick << "return false;"
160 160 link_to(name, "#", :onclick => onclick)
161 161 end
162 162
163 163 def image_to_function(name, function, html_options = {})
164 164 html_options.symbolize_keys!
165 165 tag(:input, html_options.merge({
166 166 :type => "image", :src => image_path(name),
167 167 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
168 168 }))
169 169 end
170 170
171 171 def format_activity_title(text)
172 172 h(truncate_single_line(text, :length => 100))
173 173 end
174 174
175 175 def format_activity_day(date)
176 176 date == User.current.today ? l(:label_today).titleize : format_date(date)
177 177 end
178 178
179 179 def format_activity_description(text)
180 180 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
181 181 ).gsub(/[\r\n]+/, "<br />").html_safe
182 182 end
183 183
184 184 def format_version_name(version)
185 185 if version.project == @project
186 186 h(version)
187 187 else
188 188 h("#{version.project} - #{version}")
189 189 end
190 190 end
191 191
192 192 def due_date_distance_in_words(date)
193 193 if date
194 194 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
195 195 end
196 196 end
197 197
198 198 # Renders a tree of projects as a nested set of unordered lists
199 199 # The given collection may be a subset of the whole project tree
200 200 # (eg. some intermediate nodes are private and can not be seen)
201 201 def render_project_nested_lists(projects)
202 202 s = ''
203 203 if projects.any?
204 204 ancestors = []
205 205 original_project = @project
206 206 projects.sort_by(&:lft).each do |project|
207 207 # set the project environment to please macros.
208 208 @project = project
209 209 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
210 210 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
211 211 else
212 212 ancestors.pop
213 213 s << "</li>"
214 214 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
215 215 ancestors.pop
216 216 s << "</ul></li>\n"
217 217 end
218 218 end
219 219 classes = (ancestors.empty? ? 'root' : 'child')
220 220 s << "<li class='#{classes}'><div class='#{classes}'>"
221 221 s << h(block_given? ? yield(project) : project.name)
222 222 s << "</div>\n"
223 223 ancestors << project
224 224 end
225 225 s << ("</li></ul>\n" * ancestors.size)
226 226 @project = original_project
227 227 end
228 228 s.html_safe
229 229 end
230 230
231 231 def render_page_hierarchy(pages, node=nil, options={})
232 232 content = ''
233 233 if pages[node]
234 234 content << "<ul class=\"pages-hierarchy\">\n"
235 235 pages[node].each do |page|
236 236 content << "<li>"
237 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
237 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil},
238 238 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
239 239 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
240 240 content << "</li>\n"
241 241 end
242 242 content << "</ul>\n"
243 243 end
244 244 content.html_safe
245 245 end
246 246
247 247 # Renders flash messages
248 248 def render_flash_messages
249 249 s = ''
250 250 flash.each do |k,v|
251 251 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
252 252 end
253 253 s.html_safe
254 254 end
255 255
256 256 # Renders tabs and their content
257 257 def render_tabs(tabs)
258 258 if tabs.any?
259 259 render :partial => 'common/tabs', :locals => {:tabs => tabs}
260 260 else
261 261 content_tag 'p', l(:label_no_data), :class => "nodata"
262 262 end
263 263 end
264 264
265 265 # Renders the project quick-jump box
266 266 def render_project_jump_box
267 267 return unless User.current.logged?
268 268 projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq
269 269 if projects.any?
270 270 options =
271 271 ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
272 272 '<option value="" disabled="disabled">---</option>').html_safe
273 273
274 274 options << project_tree_options_for_select(projects, :selected => @project) do |p|
275 275 { :value => project_path(:id => p, :jump => current_menu_item) }
276 276 end
277 277
278 278 select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
279 279 end
280 280 end
281 281
282 282 def project_tree_options_for_select(projects, options = {})
283 283 s = ''
284 284 project_tree(projects) do |project, level|
285 285 name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
286 286 tag_options = {:value => project.id}
287 287 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
288 288 tag_options[:selected] = 'selected'
289 289 else
290 290 tag_options[:selected] = nil
291 291 end
292 292 tag_options.merge!(yield(project)) if block_given?
293 293 s << content_tag('option', name_prefix + h(project), tag_options)
294 294 end
295 295 s.html_safe
296 296 end
297 297
298 298 # Yields the given block for each project with its level in the tree
299 299 #
300 300 # Wrapper for Project#project_tree
301 301 def project_tree(projects, &block)
302 302 Project.project_tree(projects, &block)
303 303 end
304 304
305 305 def principals_check_box_tags(name, principals)
306 306 s = ''
307 307 principals.sort.each do |principal|
308 308 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
309 309 end
310 310 s.html_safe
311 311 end
312 312
313 313 # Returns a string for users/groups option tags
314 314 def principals_options_for_select(collection, selected=nil)
315 315 s = ''
316 316 if collection.include?(User.current)
317 317 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
318 318 end
319 319 groups = ''
320 320 collection.sort.each do |element|
321 321 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
322 322 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
323 323 end
324 324 unless groups.empty?
325 325 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
326 326 end
327 327 s.html_safe
328 328 end
329 329
330 330 # Truncates and returns the string as a single line
331 331 def truncate_single_line(string, *args)
332 332 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
333 333 end
334 334
335 335 # Truncates at line break after 250 characters or options[:length]
336 336 def truncate_lines(string, options={})
337 337 length = options[:length] || 250
338 338 if string.to_s =~ /\A(.{#{length}}.*?)$/m
339 339 "#{$1}..."
340 340 else
341 341 string
342 342 end
343 343 end
344 344
345 345 def anchor(text)
346 346 text.to_s.gsub(' ', '_')
347 347 end
348 348
349 349 def html_hours(text)
350 350 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
351 351 end
352 352
353 353 def authoring(created, author, options={})
354 354 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
355 355 end
356 356
357 357 def time_tag(time)
358 358 text = distance_of_time_in_words(Time.now, time)
359 359 if @project
360 360 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
361 361 else
362 362 content_tag('acronym', text, :title => format_time(time))
363 363 end
364 364 end
365 365
366 366 def syntax_highlight_lines(name, content)
367 367 lines = []
368 368 syntax_highlight(name, content).each_line { |line| lines << line }
369 369 lines
370 370 end
371 371
372 372 def syntax_highlight(name, content)
373 373 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
374 374 end
375 375
376 376 def to_path_param(path)
377 377 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
378 378 str.blank? ? nil : str
379 379 end
380 380
381 381 def pagination_links_full(paginator, count=nil, options={})
382 382 page_param = options.delete(:page_param) || :page
383 383 per_page_links = options.delete(:per_page_links)
384 384 url_param = params.dup
385 385
386 386 html = ''
387 387 if paginator.current.previous
388 388 # \xc2\xab(utf-8) = &#171;
389 389 html << link_to_content_update(
390 390 "\xc2\xab " + l(:label_previous),
391 391 url_param.merge(page_param => paginator.current.previous)) + ' '
392 392 end
393 393
394 394 html << (pagination_links_each(paginator, options) do |n|
395 395 link_to_content_update(n.to_s, url_param.merge(page_param => n))
396 396 end || '')
397 397
398 398 if paginator.current.next
399 399 # \xc2\xbb(utf-8) = &#187;
400 400 html << ' ' + link_to_content_update(
401 401 (l(:label_next) + " \xc2\xbb"),
402 402 url_param.merge(page_param => paginator.current.next))
403 403 end
404 404
405 405 unless count.nil?
406 406 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
407 407 if per_page_links != false && links = per_page_links(paginator.items_per_page, count)
408 408 html << " | #{links}"
409 409 end
410 410 end
411 411
412 412 html.html_safe
413 413 end
414 414
415 415 def per_page_links(selected=nil, item_count=nil)
416 416 values = Setting.per_page_options_array
417 417 if item_count && values.any?
418 418 if item_count > values.first
419 419 max = values.detect {|value| value >= item_count} || item_count
420 420 else
421 421 max = item_count
422 422 end
423 423 values = values.select {|value| value <= max || value == selected}
424 424 end
425 425 if values.empty? || (values.size == 1 && values.first == selected)
426 426 return nil
427 427 end
428 428 links = values.collect do |n|
429 429 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
430 430 end
431 431 l(:label_display_per_page, links.join(', '))
432 432 end
433 433
434 434 def reorder_links(name, url, method = :post)
435 435 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
436 436 url.merge({"#{name}[move_to]" => 'highest'}),
437 437 :method => method, :title => l(:label_sort_highest)) +
438 438 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
439 439 url.merge({"#{name}[move_to]" => 'higher'}),
440 440 :method => method, :title => l(:label_sort_higher)) +
441 441 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
442 442 url.merge({"#{name}[move_to]" => 'lower'}),
443 443 :method => method, :title => l(:label_sort_lower)) +
444 444 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
445 445 url.merge({"#{name}[move_to]" => 'lowest'}),
446 446 :method => method, :title => l(:label_sort_lowest))
447 447 end
448 448
449 449 def breadcrumb(*args)
450 450 elements = args.flatten
451 451 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
452 452 end
453 453
454 454 def other_formats_links(&block)
455 455 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
456 456 yield Redmine::Views::OtherFormatsBuilder.new(self)
457 457 concat('</p>'.html_safe)
458 458 end
459 459
460 460 def page_header_title
461 461 if @project.nil? || @project.new_record?
462 462 h(Setting.app_title)
463 463 else
464 464 b = []
465 465 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
466 466 if ancestors.any?
467 467 root = ancestors.shift
468 468 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
469 469 if ancestors.size > 2
470 470 b << "\xe2\x80\xa6"
471 471 ancestors = ancestors[-2, 2]
472 472 end
473 473 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
474 474 end
475 475 b << h(@project)
476 476 b.join(" \xc2\xbb ").html_safe
477 477 end
478 478 end
479 479
480 480 def html_title(*args)
481 481 if args.empty?
482 482 title = @html_title || []
483 483 title << @project.name if @project
484 484 title << Setting.app_title unless Setting.app_title == title.last
485 485 title.select {|t| !t.blank? }.join(' - ')
486 486 else
487 487 @html_title ||= []
488 488 @html_title += args
489 489 end
490 490 end
491 491
492 492 # Returns the theme, controller name, and action as css classes for the
493 493 # HTML body.
494 494 def body_css_classes
495 495 css = []
496 496 if theme = Redmine::Themes.theme(Setting.ui_theme)
497 497 css << 'theme-' + theme.name
498 498 end
499 499
500 500 css << 'controller-' + controller_name
501 501 css << 'action-' + action_name
502 502 css.join(' ')
503 503 end
504 504
505 505 def accesskey(s)
506 506 Redmine::AccessKeys.key_for s
507 507 end
508 508
509 509 # Formats text according to system settings.
510 510 # 2 ways to call this method:
511 511 # * with a String: textilizable(text, options)
512 512 # * with an object and one of its attribute: textilizable(issue, :description, options)
513 513 def textilizable(*args)
514 514 options = args.last.is_a?(Hash) ? args.pop : {}
515 515 case args.size
516 516 when 1
517 517 obj = options[:object]
518 518 text = args.shift
519 519 when 2
520 520 obj = args.shift
521 521 attr = args.shift
522 522 text = obj.send(attr).to_s
523 523 else
524 524 raise ArgumentError, 'invalid arguments to textilizable'
525 525 end
526 526 return '' if text.blank?
527 527 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
528 528 only_path = options.delete(:only_path) == false ? false : true
529 529
530 530 text = text.dup
531 531 macros = catch_macros(text)
532 532 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
533 533
534 534 @parsed_headings = []
535 535 @heading_anchors = {}
536 536 @current_section = 0 if options[:edit_section_links]
537 537
538 538 parse_sections(text, project, obj, attr, only_path, options)
539 539 text = parse_non_pre_blocks(text, obj, macros) do |text|
540 540 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
541 541 send method_name, text, project, obj, attr, only_path, options
542 542 end
543 543 end
544 544 parse_headings(text, project, obj, attr, only_path, options)
545 545
546 546 if @parsed_headings.any?
547 547 replace_toc(text, @parsed_headings)
548 548 end
549 549
550 550 text.html_safe
551 551 end
552 552
553 553 def parse_non_pre_blocks(text, obj, macros)
554 554 s = StringScanner.new(text)
555 555 tags = []
556 556 parsed = ''
557 557 while !s.eos?
558 558 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
559 559 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
560 560 if tags.empty?
561 561 yield text
562 562 inject_macros(text, obj, macros) if macros.any?
563 563 else
564 564 inject_macros(text, obj, macros, false) if macros.any?
565 565 end
566 566 parsed << text
567 567 if tag
568 568 if closing
569 569 if tags.last == tag.downcase
570 570 tags.pop
571 571 end
572 572 else
573 573 tags << tag.downcase
574 574 end
575 575 parsed << full_tag
576 576 end
577 577 end
578 578 # Close any non closing tags
579 579 while tag = tags.pop
580 580 parsed << "</#{tag}>"
581 581 end
582 582 parsed
583 583 end
584 584
585 585 def parse_inline_attachments(text, project, obj, attr, only_path, options)
586 586 # when using an image link, try to use an attachment, if possible
587 587 if options[:attachments] || (obj && obj.respond_to?(:attachments))
588 588 attachments = options[:attachments] || obj.attachments
589 589 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
590 590 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
591 591 # search for the picture in attachments
592 592 if found = Attachment.latest_attach(attachments, filename)
593 593 image_url = url_for :only_path => only_path, :controller => 'attachments',
594 594 :action => 'download', :id => found
595 595 desc = found.description.to_s.gsub('"', '')
596 596 if !desc.blank? && alttext.blank?
597 597 alt = " title=\"#{desc}\" alt=\"#{desc}\""
598 598 end
599 599 "src=\"#{image_url}\"#{alt}"
600 600 else
601 601 m
602 602 end
603 603 end
604 604 end
605 605 end
606 606
607 607 # Wiki links
608 608 #
609 609 # Examples:
610 610 # [[mypage]]
611 611 # [[mypage|mytext]]
612 612 # wiki links can refer other project wikis, using project name or identifier:
613 613 # [[project:]] -> wiki starting page
614 614 # [[project:|mytext]]
615 615 # [[project:mypage]]
616 616 # [[project:mypage|mytext]]
617 617 def parse_wiki_links(text, project, obj, attr, only_path, options)
618 618 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
619 619 link_project = project
620 620 esc, all, page, title = $1, $2, $3, $5
621 621 if esc.nil?
622 622 if page =~ /^([^\:]+)\:(.*)$/
623 623 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
624 624 page = $2
625 625 title ||= $1 if page.blank?
626 626 end
627 627
628 628 if link_project && link_project.wiki
629 629 # extract anchor
630 630 anchor = nil
631 631 if page =~ /^(.+?)\#(.+)$/
632 632 page, anchor = $1, $2
633 633 end
634 634 anchor = sanitize_anchor_name(anchor) if anchor.present?
635 635 # check if page exists
636 636 wiki_page = link_project.wiki.find_page(page)
637 637 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
638 638 "##{anchor}"
639 639 else
640 640 case options[:wiki_links]
641 641 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
642 642 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
643 643 else
644 644 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
645 645 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
646 646 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
647 :id => wiki_page_id, :anchor => anchor, :parent => parent)
647 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
648 648 end
649 649 end
650 650 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
651 651 else
652 652 # project or wiki doesn't exist
653 653 all
654 654 end
655 655 else
656 656 all
657 657 end
658 658 end
659 659 end
660 660
661 661 # Redmine links
662 662 #
663 663 # Examples:
664 664 # Issues:
665 665 # #52 -> Link to issue #52
666 666 # Changesets:
667 667 # r52 -> Link to revision 52
668 668 # commit:a85130f -> Link to scmid starting with a85130f
669 669 # Documents:
670 670 # document#17 -> Link to document with id 17
671 671 # document:Greetings -> Link to the document with title "Greetings"
672 672 # document:"Some document" -> Link to the document with title "Some document"
673 673 # Versions:
674 674 # version#3 -> Link to version with id 3
675 675 # version:1.0.0 -> Link to version named "1.0.0"
676 676 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
677 677 # Attachments:
678 678 # attachment:file.zip -> Link to the attachment of the current object named file.zip
679 679 # Source files:
680 680 # source:some/file -> Link to the file located at /some/file in the project's repository
681 681 # source:some/file@52 -> Link to the file's revision 52
682 682 # source:some/file#L120 -> Link to line 120 of the file
683 683 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
684 684 # export:some/file -> Force the download of the file
685 685 # Forum messages:
686 686 # message#1218 -> Link to message with id 1218
687 687 #
688 688 # Links can refer other objects from other projects, using project identifier:
689 689 # identifier:r52
690 690 # identifier:document:"Some document"
691 691 # identifier:version:1.0.0
692 692 # identifier:source:some/file
693 693 def parse_redmine_links(text, project, obj, attr, only_path, options)
694 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|
695 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
696 696 link = nil
697 697 if project_identifier
698 698 project = Project.visible.find_by_identifier(project_identifier)
699 699 end
700 700 if esc.nil?
701 701 if prefix.nil? && sep == 'r'
702 702 if project
703 703 repository = nil
704 704 if repo_identifier
705 705 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
706 706 else
707 707 repository = project.repository
708 708 end
709 709 # project.changesets.visible raises an SQL error because of a double join on repositories
710 710 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
711 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},
712 712 :class => 'changeset',
713 713 :title => truncate_single_line(changeset.comments, :length => 100))
714 714 end
715 715 end
716 716 elsif sep == '#'
717 717 oid = identifier.to_i
718 718 case prefix
719 719 when nil
720 720 if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status)
721 721 anchor = comment_id ? "note-#{comment_id}" : nil
722 722 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
723 723 :class => issue.css_classes,
724 724 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
725 725 end
726 726 when 'document'
727 727 if document = Document.visible.find_by_id(oid)
728 728 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
729 729 :class => 'document'
730 730 end
731 731 when 'version'
732 732 if version = Version.visible.find_by_id(oid)
733 733 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
734 734 :class => 'version'
735 735 end
736 736 when 'message'
737 737 if message = Message.visible.find_by_id(oid, :include => :parent)
738 738 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
739 739 end
740 740 when 'forum'
741 741 if board = Board.visible.find_by_id(oid)
742 742 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
743 743 :class => 'board'
744 744 end
745 745 when 'news'
746 746 if news = News.visible.find_by_id(oid)
747 747 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
748 748 :class => 'news'
749 749 end
750 750 when 'project'
751 751 if p = Project.visible.find_by_id(oid)
752 752 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
753 753 end
754 754 end
755 755 elsif sep == ':'
756 756 # removes the double quotes if any
757 757 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
758 758 case prefix
759 759 when 'document'
760 760 if project && document = project.documents.visible.find_by_title(name)
761 761 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
762 762 :class => 'document'
763 763 end
764 764 when 'version'
765 765 if project && version = project.versions.visible.find_by_name(name)
766 766 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
767 767 :class => 'version'
768 768 end
769 769 when 'forum'
770 770 if project && board = project.boards.visible.find_by_name(name)
771 771 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
772 772 :class => 'board'
773 773 end
774 774 when 'news'
775 775 if project && news = project.news.visible.find_by_title(name)
776 776 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
777 777 :class => 'news'
778 778 end
779 779 when 'commit', 'source', 'export'
780 780 if project
781 781 repository = nil
782 782 if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
783 783 repo_prefix, repo_identifier, name = $1, $2, $3
784 784 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
785 785 else
786 786 repository = project.repository
787 787 end
788 788 if prefix == 'commit'
789 789 if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"]))
790 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},
791 791 :class => 'changeset',
792 792 :title => truncate_single_line(h(changeset.comments), :length => 100)
793 793 end
794 794 else
795 795 if repository && User.current.allowed_to?(:browse_repository, project)
796 796 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
797 797 path, rev, anchor = $1, $3, $5
798 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,
799 799 :path => to_path_param(path),
800 800 :rev => rev,
801 801 :anchor => anchor},
802 802 :class => (prefix == 'export' ? 'source download' : 'source')
803 803 end
804 804 end
805 805 repo_prefix = nil
806 806 end
807 807 when 'attachment'
808 808 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
809 809 if attachments && attachment = attachments.detect {|a| a.filename == name }
810 810 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
811 811 :class => 'attachment'
812 812 end
813 813 when 'project'
814 814 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
815 815 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
816 816 end
817 817 end
818 818 end
819 819 end
820 820 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
821 821 end
822 822 end
823 823
824 824 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
825 825
826 826 def parse_sections(text, project, obj, attr, only_path, options)
827 827 return unless options[:edit_section_links]
828 828 text.gsub!(HEADING_RE) do
829 829 heading = $1
830 830 @current_section += 1
831 831 if @current_section > 1
832 832 content_tag('div',
833 833 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
834 834 :class => 'contextual',
835 835 :title => l(:button_edit_section)) + heading.html_safe
836 836 else
837 837 heading
838 838 end
839 839 end
840 840 end
841 841
842 842 # Headings and TOC
843 843 # Adds ids and links to headings unless options[:headings] is set to false
844 844 def parse_headings(text, project, obj, attr, only_path, options)
845 845 return if options[:headings] == false
846 846
847 847 text.gsub!(HEADING_RE) do
848 848 level, attrs, content = $2.to_i, $3, $4
849 849 item = strip_tags(content).strip
850 850 anchor = sanitize_anchor_name(item)
851 851 # used for single-file wiki export
852 852 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
853 853 @heading_anchors[anchor] ||= 0
854 854 idx = (@heading_anchors[anchor] += 1)
855 855 if idx > 1
856 856 anchor = "#{anchor}-#{idx}"
857 857 end
858 858 @parsed_headings << [level, anchor, item]
859 859 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
860 860 end
861 861 end
862 862
863 863 MACROS_RE = /(
864 864 (!)? # escaping
865 865 (
866 866 \{\{ # opening tag
867 867 ([\w]+) # macro name
868 868 (\(([^\n\r]*?)\))? # optional arguments
869 869 ([\n\r].*?[\n\r])? # optional block of text
870 870 \}\} # closing tag
871 871 )
872 872 )/mx unless const_defined?(:MACROS_RE)
873 873
874 874 MACRO_SUB_RE = /(
875 875 \{\{
876 876 macro\((\d+)\)
877 877 \}\}
878 878 )/x unless const_defined?(:MACRO_SUB_RE)
879 879
880 880 # Extracts macros from text
881 881 def catch_macros(text)
882 882 macros = {}
883 883 text.gsub!(MACROS_RE) do
884 884 all, macro = $1, $4.downcase
885 885 if macro_exists?(macro) || all =~ MACRO_SUB_RE
886 886 index = macros.size
887 887 macros[index] = all
888 888 "{{macro(#{index})}}"
889 889 else
890 890 all
891 891 end
892 892 end
893 893 macros
894 894 end
895 895
896 896 # Executes and replaces macros in text
897 897 def inject_macros(text, obj, macros, execute=true)
898 898 text.gsub!(MACRO_SUB_RE) do
899 899 all, index = $1, $2.to_i
900 900 orig = macros.delete(index)
901 901 if execute && orig && orig =~ MACROS_RE
902 902 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
903 903 if esc.nil?
904 904 h(exec_macro(macro, obj, args, block) || all)
905 905 else
906 906 h(all)
907 907 end
908 908 elsif orig
909 909 h(orig)
910 910 else
911 911 h(all)
912 912 end
913 913 end
914 914 end
915 915
916 916 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
917 917
918 918 # Renders the TOC with given headings
919 919 def replace_toc(text, headings)
920 920 text.gsub!(TOC_RE) do
921 921 # Keep only the 4 first levels
922 922 headings = headings.select{|level, anchor, item| level <= 4}
923 923 if headings.empty?
924 924 ''
925 925 else
926 926 div_class = 'toc'
927 927 div_class << ' right' if $1 == '>'
928 928 div_class << ' left' if $1 == '<'
929 929 out = "<ul class=\"#{div_class}\"><li>"
930 930 root = headings.map(&:first).min
931 931 current = root
932 932 started = false
933 933 headings.each do |level, anchor, item|
934 934 if level > current
935 935 out << '<ul><li>' * (level - current)
936 936 elsif level < current
937 937 out << "</li></ul>\n" * (current - level) + "</li><li>"
938 938 elsif started
939 939 out << '</li><li>'
940 940 end
941 941 out << "<a href=\"##{anchor}\">#{item}</a>"
942 942 current = level
943 943 started = true
944 944 end
945 945 out << '</li></ul>' * (current - root)
946 946 out << '</li></ul>'
947 947 end
948 948 end
949 949 end
950 950
951 951 # Same as Rails' simple_format helper without using paragraphs
952 952 def simple_format_without_paragraph(text)
953 953 text.to_s.
954 954 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
955 955 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
956 956 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
957 957 html_safe
958 958 end
959 959
960 960 def lang_options_for_select(blank=true)
961 961 (blank ? [["(auto)", ""]] : []) +
962 962 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
963 963 end
964 964
965 965 def label_tag_for(name, option_tags = nil, options = {})
966 966 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
967 967 content_tag("label", label_text)
968 968 end
969 969
970 970 def labelled_form_for(*args, &proc)
971 971 args << {} unless args.last.is_a?(Hash)
972 972 options = args.last
973 973 if args.first.is_a?(Symbol)
974 974 options.merge!(:as => args.shift)
975 975 end
976 976 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
977 977 form_for(*args, &proc)
978 978 end
979 979
980 980 def labelled_fields_for(*args, &proc)
981 981 args << {} unless args.last.is_a?(Hash)
982 982 options = args.last
983 983 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
984 984 fields_for(*args, &proc)
985 985 end
986 986
987 987 def labelled_remote_form_for(*args, &proc)
988 988 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
989 989 args << {} unless args.last.is_a?(Hash)
990 990 options = args.last
991 991 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
992 992 form_for(*args, &proc)
993 993 end
994 994
995 995 def error_messages_for(*objects)
996 996 html = ""
997 997 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
998 998 errors = objects.map {|o| o.errors.full_messages}.flatten
999 999 if errors.any?
1000 1000 html << "<div id='errorExplanation'><ul>\n"
1001 1001 errors.each do |error|
1002 1002 html << "<li>#{h error}</li>\n"
1003 1003 end
1004 1004 html << "</ul></div>\n"
1005 1005 end
1006 1006 html.html_safe
1007 1007 end
1008 1008
1009 1009 def delete_link(url, options={})
1010 1010 options = {
1011 1011 :method => :delete,
1012 1012 :data => {:confirm => l(:text_are_you_sure)},
1013 1013 :class => 'icon icon-del'
1014 1014 }.merge(options)
1015 1015
1016 1016 link_to l(:button_delete), url, options
1017 1017 end
1018 1018
1019 1019 def preview_link(url, form, target='preview', options={})
1020 1020 content_tag 'a', l(:label_preview), {
1021 1021 :href => "#",
1022 1022 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1023 1023 :accesskey => accesskey(:preview)
1024 1024 }.merge(options)
1025 1025 end
1026 1026
1027 1027 def link_to_function(name, function, html_options={})
1028 1028 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1029 1029 end
1030 1030
1031 1031 # Helper to render JSON in views
1032 1032 def raw_json(arg)
1033 1033 arg.to_json.to_s.gsub('/', '\/').html_safe
1034 1034 end
1035 1035
1036 1036 def back_url
1037 1037 url = params[:back_url]
1038 1038 if url.nil? && referer = request.env['HTTP_REFERER']
1039 1039 url = CGI.unescape(referer.to_s)
1040 1040 end
1041 1041 url
1042 1042 end
1043 1043
1044 1044 def back_url_hidden_field_tag
1045 1045 url = back_url
1046 1046 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1047 1047 end
1048 1048
1049 1049 def check_all_links(form_name)
1050 1050 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1051 1051 " | ".html_safe +
1052 1052 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1053 1053 end
1054 1054
1055 1055 def progress_bar(pcts, options={})
1056 1056 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1057 1057 pcts = pcts.collect(&:round)
1058 1058 pcts[1] = pcts[1] - pcts[0]
1059 1059 pcts << (100 - pcts[1] - pcts[0])
1060 1060 width = options[:width] || '100px;'
1061 1061 legend = options[:legend] || ''
1062 1062 content_tag('table',
1063 1063 content_tag('tr',
1064 1064 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1065 1065 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1066 1066 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1067 1067 ), :class => 'progress', :style => "width: #{width};").html_safe +
1068 1068 content_tag('p', legend, :class => 'pourcent').html_safe
1069 1069 end
1070 1070
1071 1071 def checked_image(checked=true)
1072 1072 if checked
1073 1073 image_tag 'toggle_check.png'
1074 1074 end
1075 1075 end
1076 1076
1077 1077 def context_menu(url)
1078 1078 unless @context_menu_included
1079 1079 content_for :header_tags do
1080 1080 javascript_include_tag('context_menu') +
1081 1081 stylesheet_link_tag('context_menu')
1082 1082 end
1083 1083 if l(:direction) == 'rtl'
1084 1084 content_for :header_tags do
1085 1085 stylesheet_link_tag('context_menu_rtl')
1086 1086 end
1087 1087 end
1088 1088 @context_menu_included = true
1089 1089 end
1090 1090 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1091 1091 end
1092 1092
1093 1093 def calendar_for(field_id)
1094 1094 include_calendar_headers_tags
1095 1095 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
1096 1096 end
1097 1097
1098 1098 def include_calendar_headers_tags
1099 1099 unless @calendar_headers_tags_included
1100 1100 @calendar_headers_tags_included = true
1101 1101 content_for :header_tags do
1102 1102 start_of_week = Setting.start_of_week
1103 1103 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1104 1104 # Redmine uses 1..7 (monday..sunday) in settings and locales
1105 1105 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1106 1106 start_of_week = start_of_week.to_i % 7
1107 1107
1108 1108 tags = javascript_tag(
1109 1109 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1110 1110 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1111 1111 path_to_image('/images/calendar.png') +
1112 1112 "', showButtonPanel: true};")
1113 1113 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1114 1114 unless jquery_locale == 'en'
1115 1115 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
1116 1116 end
1117 1117 tags
1118 1118 end
1119 1119 end
1120 1120 end
1121 1121
1122 1122 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1123 1123 # Examples:
1124 1124 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1125 1125 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1126 1126 #
1127 1127 def stylesheet_link_tag(*sources)
1128 1128 options = sources.last.is_a?(Hash) ? sources.pop : {}
1129 1129 plugin = options.delete(:plugin)
1130 1130 sources = sources.map do |source|
1131 1131 if plugin
1132 1132 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1133 1133 elsif current_theme && current_theme.stylesheets.include?(source)
1134 1134 current_theme.stylesheet_path(source)
1135 1135 else
1136 1136 source
1137 1137 end
1138 1138 end
1139 1139 super sources, options
1140 1140 end
1141 1141
1142 1142 # Overrides Rails' image_tag with themes and plugins support.
1143 1143 # Examples:
1144 1144 # image_tag('image.png') # => picks image.png from the current theme or defaults
1145 1145 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1146 1146 #
1147 1147 def image_tag(source, options={})
1148 1148 if plugin = options.delete(:plugin)
1149 1149 source = "/plugin_assets/#{plugin}/images/#{source}"
1150 1150 elsif current_theme && current_theme.images.include?(source)
1151 1151 source = current_theme.image_path(source)
1152 1152 end
1153 1153 super source, options
1154 1154 end
1155 1155
1156 1156 # Overrides Rails' javascript_include_tag with plugins support
1157 1157 # Examples:
1158 1158 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1159 1159 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1160 1160 #
1161 1161 def javascript_include_tag(*sources)
1162 1162 options = sources.last.is_a?(Hash) ? sources.pop : {}
1163 1163 if plugin = options.delete(:plugin)
1164 1164 sources = sources.map do |source|
1165 1165 if plugin
1166 1166 "/plugin_assets/#{plugin}/javascripts/#{source}"
1167 1167 else
1168 1168 source
1169 1169 end
1170 1170 end
1171 1171 end
1172 1172 super sources, options
1173 1173 end
1174 1174
1175 1175 def content_for(name, content = nil, &block)
1176 1176 @has_content ||= {}
1177 1177 @has_content[name] = true
1178 1178 super(name, content, &block)
1179 1179 end
1180 1180
1181 1181 def has_content?(name)
1182 1182 (@has_content && @has_content[name]) || false
1183 1183 end
1184 1184
1185 1185 def sidebar_content?
1186 1186 has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1187 1187 end
1188 1188
1189 1189 def view_layouts_base_sidebar_hook_response
1190 1190 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1191 1191 end
1192 1192
1193 1193 def email_delivery_enabled?
1194 1194 !!ActionMailer::Base.perform_deliveries
1195 1195 end
1196 1196
1197 1197 # Returns the avatar image tag for the given +user+ if avatars are enabled
1198 1198 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1199 1199 def avatar(user, options = { })
1200 1200 if Setting.gravatar_enabled?
1201 1201 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1202 1202 email = nil
1203 1203 if user.respond_to?(:mail)
1204 1204 email = user.mail
1205 1205 elsif user.to_s =~ %r{<(.+?)>}
1206 1206 email = $1
1207 1207 end
1208 1208 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1209 1209 else
1210 1210 ''
1211 1211 end
1212 1212 end
1213 1213
1214 1214 def sanitize_anchor_name(anchor)
1215 1215 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
1216 1216 anchor.gsub(%r{[^\p{Word}\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1217 1217 else
1218 1218 # TODO: remove when ruby1.8 is no longer supported
1219 1219 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1220 1220 end
1221 1221 end
1222 1222
1223 1223 # Returns the javascript tags that are included in the html layout head
1224 1224 def javascript_heads
1225 1225 tags = javascript_include_tag('jquery-1.7.2-ui-1.8.21-ujs-2.0.3', 'application')
1226 1226 unless User.current.pref.warn_on_leaving_unsaved == '0'
1227 1227 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1228 1228 end
1229 1229 tags
1230 1230 end
1231 1231
1232 1232 def favicon
1233 1233 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1234 1234 end
1235 1235
1236 1236 def robot_exclusion_tag
1237 1237 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1238 1238 end
1239 1239
1240 1240 # Returns true if arg is expected in the API response
1241 1241 def include_in_api_response?(arg)
1242 1242 unless @included_in_api_response
1243 1243 param = params[:include]
1244 1244 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1245 1245 @included_in_api_response.collect!(&:strip)
1246 1246 end
1247 1247 @included_in_api_response.include?(arg.to_s)
1248 1248 end
1249 1249
1250 1250 # Returns options or nil if nometa param or X-Redmine-Nometa header
1251 1251 # was set in the request
1252 1252 def api_meta(options)
1253 1253 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1254 1254 # compatibility mode for activeresource clients that raise
1255 1255 # an error when unserializing an array with attributes
1256 1256 nil
1257 1257 else
1258 1258 options
1259 1259 end
1260 1260 end
1261 1261
1262 1262 private
1263 1263
1264 1264 def wiki_helper
1265 1265 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1266 1266 extend helper
1267 1267 return self
1268 1268 end
1269 1269
1270 1270 def link_to_content_update(text, url_params = {}, html_options = {})
1271 1271 link_to(text, url_params, html_options)
1272 1272 end
1273 1273 end
@@ -1,43 +1,43
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 module WikiHelper
21 21
22 22 def wiki_page_options_for_select(pages, selected = nil, parent = nil, level = 0)
23 23 pages = pages.group_by(&:parent) unless pages.is_a?(Hash)
24 24 s = ''.html_safe
25 25 if pages.has_key?(parent)
26 26 pages[parent].each do |page|
27 27 attrs = "value='#{page.id}'"
28 28 attrs << " selected='selected'" if selected == page
29 29 indent = (level > 0) ? ('&nbsp;' * level * 2 + '&#187; ') : ''
30 30
31 31 s << content_tag('option', (indent + h(page.pretty_title)).html_safe, :value => page.id.to_s, :selected => selected == page) +
32 32 wiki_page_options_for_select(pages, selected, page, level + 1)
33 33 end
34 34 end
35 35 s
36 36 end
37 37
38 38 def wiki_page_breadcrumb(page)
39 39 breadcrumb(page.ancestors.reverse.collect {|parent|
40 link_to(h(parent.pretty_title), {:controller => 'wiki', :action => 'show', :id => parent.title, :project_id => parent.project})
40 link_to(h(parent.pretty_title), {:controller => 'wiki', :action => 'show', :id => parent.title, :project_id => parent.project, :version => nil})
41 41 })
42 42 end
43 43 end
@@ -1,71 +1,71
1 1 <div class="contextual">
2 2 <% if @editable %>
3 3 <% if @content.current_version? %>
4 4 <%= link_to_if_authorized(l(:button_edit), {:action => 'edit', :id => @page.title}, :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %>
5 5 <%= watcher_tag(@page, User.current) %>
6 6 <%= link_to_if_authorized(l(:button_lock), {:action => 'protect', :id => @page.title, :protected => 1}, :method => :post, :class => 'icon icon-lock') if !@page.protected? %>
7 7 <%= link_to_if_authorized(l(:button_unlock), {:action => 'protect', :id => @page.title, :protected => 0}, :method => :post, :class => 'icon icon-unlock') if @page.protected? %>
8 8 <%= link_to_if_authorized(l(:button_rename), {:action => 'rename', :id => @page.title}, :class => 'icon icon-move') %>
9 9 <%= link_to_if_authorized(l(:button_delete), {:action => 'destroy', :id => @page.title}, :method => :delete, :data => {:confirm => l(:text_are_you_sure)}, :class => 'icon icon-del') %>
10 10 <% else %>
11 11 <%= link_to_if_authorized(l(:button_rollback), {:action => 'edit', :id => @page.title, :version => @content.version }, :class => 'icon icon-cancel') %>
12 12 <% end %>
13 13 <% end %>
14 14 <%= link_to_if_authorized(l(:label_history), {:action => 'history', :id => @page.title}, :class => 'icon icon-history') %>
15 15 </div>
16 16
17 17 <%= wiki_page_breadcrumb(@page) %>
18 18
19 19 <% unless @content.current_version? %>
20 20 <p>
21 21 <%= link_to(("\xc2\xab " + l(:label_previous)),
22 22 :action => 'show', :id => @page.title, :project_id => @page.project,
23 23 :version => @content.previous.version) + " - " if @content.previous %>
24 24 <%= "#{l(:label_version)} #{@content.version}/#{@page.content.version}" %>
25 25 <%= '('.html_safe + link_to(l(:label_diff), :controller => 'wiki', :action => 'diff',
26 26 :id => @page.title, :project_id => @page.project,
27 27 :version => @content.version) + ')'.html_safe if @content.previous %> -
28 28 <%= link_to((l(:label_next) + " \xc2\xbb"), :action => 'show',
29 29 :id => @page.title, :project_id => @page.project,
30 30 :version => @content.next.version) + " - " if @content.next %>
31 <%= link_to(l(:label_current_version), :action => 'show', :id => @page.title, :project_id => @page.project) %>
31 <%= link_to(l(:label_current_version), :action => 'show', :id => @page.title, :project_id => @page.project, :version => nil) %>
32 32 <br />
33 33 <em><%= @content.author ? link_to_user(@content.author) : l(:label_user_anonymous)
34 34 %>, <%= format_time(@content.updated_on) %> </em><br />
35 35 <%=h @content.comments %>
36 36 </p>
37 37 <hr />
38 38 <% end %>
39 39
40 40 <%= render(:partial => "wiki/content", :locals => {:content => @content}) %>
41 41
42 42 <%= link_to_attachments @page %>
43 43
44 44 <% if @editable && authorize_for('wiki', 'add_attachment') %>
45 45 <div id="wiki_add_attachment">
46 46 <p><%= link_to l(:label_attachment_new), {}, :onclick => "$('#add_attachment_form').show(); return false;",
47 47 :id => 'attach_files_link' %></p>
48 48 <%= form_tag({:controller => 'wiki', :action => 'add_attachment',
49 49 :project_id => @project, :id => @page.title},
50 50 :multipart => true, :id => "add_attachment_form",
51 51 :style => "display:none;") do %>
52 52 <div class="box">
53 53 <p><%= render :partial => 'attachments/form' %></p>
54 54 </div>
55 55 <%= submit_tag l(:button_add) %>
56 56 <%= link_to l(:button_cancel), {}, :onclick => "$('#add_attachment_form').hide(); return false;" %>
57 57 <% end %>
58 58 </div>
59 59 <% end %>
60 60
61 61 <% other_formats_links do |f| %>
62 62 <%= f.link_to 'PDF', :url => {:id => @page.title, :version => params[:version]} %>
63 63 <%= f.link_to 'HTML', :url => {:id => @page.title, :version => params[:version]} %>
64 64 <%= f.link_to 'TXT', :url => {:id => @page.title, :version => params[:version]} %>
65 65 <% end if User.current.allowed_to?(:export_wiki_pages, @project) %>
66 66
67 67 <% content_for :sidebar do %>
68 68 <%= render :partial => 'sidebar' %>
69 69 <% end %>
70 70
71 71 <% html_title @page.pretty_title %>
@@ -1,345 +1,345
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 RedmineApp::Application.routes.draw do
19 19 root :to => 'welcome#index', :as => 'home'
20 20
21 21 match 'login', :to => 'account#login', :as => 'signin'
22 22 match 'logout', :to => 'account#logout', :as => 'signout'
23 23 match 'account/register', :to => 'account#register', :via => [:get, :post], :as => 'register'
24 24 match 'account/lost_password', :to => 'account#lost_password', :via => [:get, :post], :as => 'lost_password'
25 25 match 'account/activate', :to => 'account#activate', :via => :get
26 26
27 27 match '/news/preview', :controller => 'previews', :action => 'news', :as => 'preview_news'
28 28 match '/issues/preview/new/:project_id', :to => 'previews#issue', :as => 'preview_new_issue'
29 29 match '/issues/preview/edit/:id', :to => 'previews#issue', :as => 'preview_edit_issue'
30 30 match '/issues/preview', :to => 'previews#issue', :as => 'preview_issue'
31 31
32 32 match 'projects/:id/wiki', :to => 'wikis#edit', :via => :post
33 33 match 'projects/:id/wiki/destroy', :to => 'wikis#destroy', :via => [:get, :post]
34 34
35 35 match 'boards/:board_id/topics/new', :to => 'messages#new', :via => [:get, :post]
36 36 get 'boards/:board_id/topics/:id', :to => 'messages#show', :as => 'board_message'
37 37 match 'boards/:board_id/topics/quote/:id', :to => 'messages#quote', :via => [:get, :post]
38 38 get 'boards/:board_id/topics/:id/edit', :to => 'messages#edit'
39 39
40 40 post 'boards/:board_id/topics/preview', :to => 'messages#preview'
41 41 post 'boards/:board_id/topics/:id/replies', :to => 'messages#reply'
42 42 post 'boards/:board_id/topics/:id/edit', :to => 'messages#edit'
43 43 post 'boards/:board_id/topics/:id/destroy', :to => 'messages#destroy'
44 44
45 45 # Misc issue routes. TODO: move into resources
46 46 match '/issues/auto_complete', :to => 'auto_completes#issues', :via => :get, :as => 'auto_complete_issues'
47 47 match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu'
48 48 match '/issues/changes', :to => 'journals#index', :as => 'issue_changes'
49 49 match '/issues/:id/quoted', :to => 'journals#new', :id => /\d+/, :via => :post, :as => 'quoted_issue'
50 50
51 51 match '/journals/diff/:id', :to => 'journals#diff', :id => /\d+/, :via => :get
52 52 match '/journals/edit/:id', :to => 'journals#edit', :id => /\d+/, :via => [:get, :post]
53 53
54 54 match '/projects/:project_id/issues/gantt', :to => 'gantts#show'
55 55 match '/issues/gantt', :to => 'gantts#show'
56 56
57 57 match '/projects/:project_id/issues/calendar', :to => 'calendars#show'
58 58 match '/issues/calendar', :to => 'calendars#show'
59 59
60 60 match 'projects/:id/issues/report', :to => 'reports#issue_report', :via => :get
61 61 match 'projects/:id/issues/report/:detail', :to => 'reports#issue_report_details', :via => :get
62 62
63 63 match 'my/account', :controller => 'my', :action => 'account', :via => [:get, :post]
64 64 match 'my/account/destroy', :controller => 'my', :action => 'destroy', :via => [:get, :post]
65 65 match 'my/page', :controller => 'my', :action => 'page', :via => :get
66 66 match 'my', :controller => 'my', :action => 'index', :via => :get # Redirects to my/page
67 67 match 'my/reset_rss_key', :controller => 'my', :action => 'reset_rss_key', :via => :post
68 68 match 'my/reset_api_key', :controller => 'my', :action => 'reset_api_key', :via => :post
69 69 match 'my/password', :controller => 'my', :action => 'password', :via => [:get, :post]
70 70 match 'my/page_layout', :controller => 'my', :action => 'page_layout', :via => :get
71 71 match 'my/add_block', :controller => 'my', :action => 'add_block', :via => :post
72 72 match 'my/remove_block', :controller => 'my', :action => 'remove_block', :via => :post
73 73 match 'my/order_blocks', :controller => 'my', :action => 'order_blocks', :via => :post
74 74
75 75 resources :users
76 76 match 'users/:id/memberships/:membership_id', :to => 'users#edit_membership', :via => :put, :as => 'user_membership'
77 77 match 'users/:id/memberships/:membership_id', :to => 'users#destroy_membership', :via => :delete
78 78 match 'users/:id/memberships', :to => 'users#edit_membership', :via => :post, :as => 'user_memberships'
79 79
80 80 match 'watchers/new', :controller=> 'watchers', :action => 'new', :via => :get
81 81 match 'watchers', :controller=> 'watchers', :action => 'create', :via => :post
82 82 match 'watchers/append', :controller=> 'watchers', :action => 'append', :via => :post
83 83 match 'watchers/destroy', :controller=> 'watchers', :action => 'destroy', :via => :post
84 84 match 'watchers/watch', :controller=> 'watchers', :action => 'watch', :via => :post
85 85 match 'watchers/unwatch', :controller=> 'watchers', :action => 'unwatch', :via => :post
86 86 match 'watchers/autocomplete_for_user', :controller=> 'watchers', :action => 'autocomplete_for_user', :via => :get
87 87
88 88 match 'projects/:id/settings/:tab', :to => "projects#settings"
89 89
90 90 resources :projects do
91 91 member do
92 92 get 'settings'
93 93 post 'modules'
94 94 post 'archive'
95 95 post 'unarchive'
96 96 post 'close'
97 97 post 'reopen'
98 98 match 'copy', :via => [:get, :post]
99 99 end
100 100
101 101 resources :memberships, :shallow => true, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do
102 102 collection do
103 103 get 'autocomplete'
104 104 end
105 105 end
106 106
107 107 resource :enumerations, :controller => 'project_enumerations', :only => [:update, :destroy]
108 108
109 109 match 'issues/:copy_from/copy', :to => 'issues#new'
110 110 resources :issues, :only => [:index, :new, :create] do
111 111 resources :time_entries, :controller => 'timelog' do
112 112 collection do
113 113 get 'report'
114 114 end
115 115 end
116 116 end
117 117 # issue form update
118 118 match 'issues/new', :controller => 'issues', :action => 'new', :via => [:put, :post], :as => 'issue_form'
119 119
120 120 resources :files, :only => [:index, :new, :create]
121 121
122 122 resources :versions, :except => [:index, :show, :edit, :update, :destroy] do
123 123 collection do
124 124 put 'close_completed'
125 125 end
126 126 end
127 127 match 'versions.:format', :to => 'versions#index'
128 128 match 'roadmap', :to => 'versions#index', :format => false
129 129 match 'versions', :to => 'versions#index'
130 130
131 131 resources :news, :except => [:show, :edit, :update, :destroy]
132 132 resources :time_entries, :controller => 'timelog' do
133 133 get 'report', :on => :collection
134 134 end
135 135 resources :queries, :only => [:new, :create]
136 136 resources :issue_categories, :shallow => true
137 137 resources :documents, :except => [:show, :edit, :update, :destroy]
138 138 resources :boards
139 139 resources :repositories, :shallow => true, :except => [:index, :show] do
140 140 member do
141 141 match 'committers', :via => [:get, :post]
142 142 end
143 143 end
144 144
145 145 match 'wiki/index', :controller => 'wiki', :action => 'index', :via => :get
146 match 'wiki/:id/diff/:version/vs/:version_from', :controller => 'wiki', :action => 'diff'
147 match 'wiki/:id/diff/:version', :controller => 'wiki', :action => 'diff'
148 146 resources :wiki, :except => [:index, :new, :create] do
149 147 member do
150 148 get 'rename'
151 149 post 'rename'
152 150 get 'history'
153 151 get 'diff'
154 152 match 'preview', :via => [:post, :put]
155 153 post 'protect'
156 154 post 'add_attachment'
157 155 end
158 156 collection do
159 157 get 'export'
160 158 get 'date_index'
161 159 end
162 160 end
163 161 match 'wiki', :controller => 'wiki', :action => 'show', :via => :get
164 match 'wiki/:id/annotate/:version', :controller => 'wiki', :action => 'annotate'
162 get 'wiki/:id/:version', :to => 'wiki#show'
163 get 'wiki/:id/:version/annotate', :to => 'wiki#annotate'
164 get 'wiki/:id/:version/diff', :to => 'wiki#diff'
165 165 end
166 166
167 167 resources :issues do
168 168 collection do
169 169 match 'bulk_edit', :via => [:get, :post]
170 170 post 'bulk_update'
171 171 end
172 172 resources :time_entries, :controller => 'timelog' do
173 173 collection do
174 174 get 'report'
175 175 end
176 176 end
177 177 resources :relations, :shallow => true, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy]
178 178 end
179 179 match '/issues', :controller => 'issues', :action => 'destroy', :via => :delete
180 180
181 181 resources :queries, :except => [:show]
182 182
183 183 resources :news, :only => [:index, :show, :edit, :update, :destroy]
184 184 match '/news/:id/comments', :to => 'comments#create', :via => :post
185 185 match '/news/:id/comments/:comment_id', :to => 'comments#destroy', :via => :delete
186 186
187 187 resources :versions, :only => [:show, :edit, :update, :destroy] do
188 188 post 'status_by', :on => :member
189 189 end
190 190
191 191 resources :documents, :only => [:show, :edit, :update, :destroy] do
192 192 post 'add_attachment', :on => :member
193 193 end
194 194
195 195 match '/time_entries/context_menu', :to => 'context_menus#time_entries', :as => :time_entries_context_menu
196 196
197 197 resources :time_entries, :controller => 'timelog', :except => :destroy do
198 198 collection do
199 199 get 'report'
200 200 get 'bulk_edit'
201 201 post 'bulk_update'
202 202 end
203 203 end
204 204 match '/time_entries/:id', :to => 'timelog#destroy', :via => :delete, :id => /\d+/
205 205 # TODO: delete /time_entries for bulk deletion
206 206 match '/time_entries/destroy', :to => 'timelog#destroy', :via => :delete
207 207
208 208 # TODO: port to be part of the resources route(s)
209 209 match 'projects/:id/settings/:tab', :to => 'projects#settings', :via => :get
210 210
211 211 get 'projects/:id/activity', :to => 'activities#index'
212 212 get 'projects/:id/activity.:format', :to => 'activities#index'
213 213 get 'activity', :to => 'activities#index'
214 214
215 215 # repositories routes
216 216 get 'projects/:id/repository/:repository_id/statistics', :to => 'repositories#stats'
217 217 get 'projects/:id/repository/:repository_id/graph', :to => 'repositories#graph'
218 218
219 219 get 'projects/:id/repository/:repository_id/changes(/*path(.:ext))',
220 220 :to => 'repositories#changes'
221 221
222 222 get 'projects/:id/repository/:repository_id/revisions/:rev', :to => 'repositories#revision'
223 223 get 'projects/:id/repository/:repository_id/revision', :to => 'repositories#revision'
224 224 post 'projects/:id/repository/:repository_id/revisions/:rev/issues', :to => 'repositories#add_related_issue'
225 225 delete 'projects/:id/repository/:repository_id/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue'
226 226 get 'projects/:id/repository/:repository_id/revisions', :to => 'repositories#revisions'
227 227 get 'projects/:id/repository/:repository_id/revisions/:rev/:action(/*path(.:ext))',
228 228 :controller => 'repositories',
229 229 :format => false,
230 230 :constraints => {
231 231 :action => /(browse|show|entry|raw|annotate|diff)/,
232 232 :rev => /[a-z0-9\.\-_]+/
233 233 }
234 234
235 235 get 'projects/:id/repository/statistics', :to => 'repositories#stats'
236 236 get 'projects/:id/repository/graph', :to => 'repositories#graph'
237 237
238 238 get 'projects/:id/repository/changes(/*path(.:ext))',
239 239 :to => 'repositories#changes'
240 240
241 241 get 'projects/:id/repository/revisions', :to => 'repositories#revisions'
242 242 get 'projects/:id/repository/revisions/:rev', :to => 'repositories#revision'
243 243 get 'projects/:id/repository/revision', :to => 'repositories#revision'
244 244 post 'projects/:id/repository/revisions/:rev/issues', :to => 'repositories#add_related_issue'
245 245 delete 'projects/:id/repository/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue'
246 246 get 'projects/:id/repository/revisions/:rev/:action(/*path(.:ext))',
247 247 :controller => 'repositories',
248 248 :format => false,
249 249 :constraints => {
250 250 :action => /(browse|show|entry|raw|annotate|diff)/,
251 251 :rev => /[a-z0-9\.\-_]+/
252 252 }
253 253 get 'projects/:id/repository/:repository_id/:action(/*path(.:ext))',
254 254 :controller => 'repositories',
255 255 :action => /(browse|show|entry|raw|changes|annotate|diff)/
256 256 get 'projects/:id/repository/:action(/*path(.:ext))',
257 257 :controller => 'repositories',
258 258 :action => /(browse|show|entry|raw|changes|annotate|diff)/
259 259
260 260 get 'projects/:id/repository/:repository_id', :to => 'repositories#show', :path => nil
261 261 get 'projects/:id/repository', :to => 'repositories#show', :path => nil
262 262
263 263 # additional routes for having the file name at the end of url
264 264 match 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/, :via => :get
265 265 match 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/, :via => :get
266 266 match 'attachments/download/:id', :controller => 'attachments', :action => 'download', :id => /\d+/, :via => :get
267 267 match 'attachments/thumbnail/:id(/:size)', :controller => 'attachments', :action => 'thumbnail', :id => /\d+/, :via => :get, :size => /\d+/
268 268 resources :attachments, :only => [:show, :destroy]
269 269
270 270 resources :groups do
271 271 member do
272 272 get 'autocomplete_for_user'
273 273 end
274 274 end
275 275
276 276 match 'groups/:id/users', :controller => 'groups', :action => 'add_users', :id => /\d+/, :via => :post, :as => 'group_users'
277 277 match 'groups/:id/users/:user_id', :controller => 'groups', :action => 'remove_user', :id => /\d+/, :via => :delete, :as => 'group_user'
278 278 match 'groups/destroy_membership/:id', :controller => 'groups', :action => 'destroy_membership', :id => /\d+/, :via => :post
279 279 match 'groups/edit_membership/:id', :controller => 'groups', :action => 'edit_membership', :id => /\d+/, :via => :post
280 280
281 281 resources :trackers, :except => :show do
282 282 collection do
283 283 match 'fields', :via => [:get, :post]
284 284 end
285 285 end
286 286 resources :issue_statuses, :except => :show do
287 287 collection do
288 288 post 'update_issue_done_ratio'
289 289 end
290 290 end
291 291 resources :custom_fields, :except => :show
292 292 resources :roles do
293 293 collection do
294 294 match 'permissions', :via => [:get, :post]
295 295 end
296 296 end
297 297 resources :enumerations, :except => :show
298 298 match 'enumerations/:type', :to => 'enumerations#index', :via => :get
299 299
300 300 get 'projects/:id/search', :controller => 'search', :action => 'index'
301 301 get 'search', :controller => 'search', :action => 'index'
302 302
303 303 match 'mail_handler', :controller => 'mail_handler', :action => 'index', :via => :post
304 304
305 305 match 'admin', :controller => 'admin', :action => 'index', :via => :get
306 306 match 'admin/projects', :controller => 'admin', :action => 'projects', :via => :get
307 307 match 'admin/plugins', :controller => 'admin', :action => 'plugins', :via => :get
308 308 match 'admin/info', :controller => 'admin', :action => 'info', :via => :get
309 309 match 'admin/test_email', :controller => 'admin', :action => 'test_email', :via => :get
310 310 match 'admin/default_configuration', :controller => 'admin', :action => 'default_configuration', :via => :post
311 311
312 312 resources :auth_sources do
313 313 member do
314 314 get 'test_connection'
315 315 end
316 316 end
317 317
318 318 match 'workflows', :controller => 'workflows', :action => 'index', :via => :get
319 319 match 'workflows/edit', :controller => 'workflows', :action => 'edit', :via => [:get, :post]
320 320 match 'workflows/permissions', :controller => 'workflows', :action => 'permissions', :via => [:get, :post]
321 321 match 'workflows/copy', :controller => 'workflows', :action => 'copy', :via => [:get, :post]
322 322 match 'settings', :controller => 'settings', :action => 'index', :via => :get
323 323 match 'settings/edit', :controller => 'settings', :action => 'edit', :via => [:get, :post]
324 324 match 'settings/plugin/:id', :controller => 'settings', :action => 'plugin', :via => [:get, :post]
325 325
326 326 match 'sys/projects', :to => 'sys#projects', :via => :get
327 327 match 'sys/projects/:id/repository', :to => 'sys#create_project_repository', :via => :post
328 328 match 'sys/fetch_changesets', :to => 'sys#fetch_changesets', :via => :get
329 329
330 330 match 'uploads', :to => 'attachments#upload', :via => :post
331 331
332 332 get 'robots.txt', :to => 'welcome#robots'
333 333
334 334 Dir.glob File.expand_path("plugins/*", Rails.root) do |plugin_dir|
335 335 file = File.join(plugin_dir, "config/routes.rb")
336 336 if File.exists?(file)
337 337 begin
338 338 instance_eval File.read(file)
339 339 rescue Exception => e
340 340 puts "An error occurred while loading the routes definition of #{File.basename(plugin_dir)} plugin (#{file}): #{e.message}."
341 341 exit 1
342 342 end
343 343 end
344 344 end
345 345 end
@@ -1,858 +1,885
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19 require 'wiki_controller'
20 20
21 21 # Re-raise errors caught by the controller.
22 22 class WikiController; def rescue_action(e) raise e end; end
23 23
24 24 class WikiControllerTest < ActionController::TestCase
25 25 fixtures :projects, :users, :roles, :members, :member_roles,
26 26 :enabled_modules, :wikis, :wiki_pages, :wiki_contents,
27 27 :wiki_content_versions, :attachments
28 28
29 29 def setup
30 30 @controller = WikiController.new
31 31 @request = ActionController::TestRequest.new
32 32 @response = ActionController::TestResponse.new
33 33 User.current = nil
34 34 end
35 35
36 36 def test_show_start_page
37 37 get :show, :project_id => 'ecookbook'
38 38 assert_response :success
39 39 assert_template 'show'
40 40 assert_tag :tag => 'h1', :content => /CookBook documentation/
41 41
42 42 # child_pages macro
43 43 assert_tag :ul, :attributes => { :class => 'pages-hierarchy' },
44 44 :child => { :tag => 'li',
45 45 :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Page_with_an_inline_image' },
46 46 :content => 'Page with an inline image' } }
47 47 end
48 48
49 49 def test_export_link
50 50 Role.anonymous.add_permission! :export_wiki_pages
51 51 get :show, :project_id => 'ecookbook'
52 52 assert_response :success
53 53 assert_tag 'a', :attributes => {:href => '/projects/ecookbook/wiki/CookBook_documentation.txt'}
54 54 end
55 55
56 56 def test_show_page_with_name
57 57 get :show, :project_id => 1, :id => 'Another_page'
58 58 assert_response :success
59 59 assert_template 'show'
60 60 assert_tag :tag => 'h1', :content => /Another page/
61 61 # Included page with an inline image
62 62 assert_tag :tag => 'p', :content => /This is an inline image/
63 63 assert_tag :tag => 'img', :attributes => { :src => '/attachments/download/3',
64 64 :alt => 'This is a logo' }
65 65 end
66 66
67 def test_show_old_version
68 get :show, :project_id => 'ecookbook', :id => 'CookBook_documentation', :version => '2'
69 assert_response :success
70 assert_template 'show'
71
72 assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/1', :text => /Previous/
73 assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/2/diff', :text => /diff/
74 assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/3', :text => /Next/
75 assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation', :text => /Current version/
76 end
77
78 def test_show_first_version
79 get :show, :project_id => 'ecookbook', :id => 'CookBook_documentation', :version => '1'
80 assert_response :success
81 assert_template 'show'
82
83 assert_select 'a', :text => /Previous/, :count => 0
84 assert_select 'a', :text => /diff/, :count => 0
85 assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/2', :text => /Next/
86 assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation', :text => /Current version/
87 end
88
67 89 def test_show_redirected_page
68 90 WikiRedirect.create!(:wiki_id => 1, :title => 'Old_title', :redirects_to => 'Another_page')
69 91
70 92 get :show, :project_id => 'ecookbook', :id => 'Old_title'
71 93 assert_redirected_to '/projects/ecookbook/wiki/Another_page'
72 94 end
73 95
74 96 def test_show_with_sidebar
75 97 page = Project.find(1).wiki.pages.new(:title => 'Sidebar')
76 98 page.content = WikiContent.new(:text => 'Side bar content for test_show_with_sidebar')
77 99 page.save!
78 100
79 101 get :show, :project_id => 1, :id => 'Another_page'
80 102 assert_response :success
81 103 assert_tag :tag => 'div', :attributes => {:id => 'sidebar'},
82 104 :content => /Side bar content for test_show_with_sidebar/
83 105 end
84 106
85 107 def test_show_should_display_section_edit_links
86 108 @request.session[:user_id] = 2
87 109 get :show, :project_id => 1, :id => 'Page with sections'
88 110 assert_no_tag 'a', :attributes => {
89 111 :href => '/projects/ecookbook/wiki/Page_with_sections/edit?section=1'
90 112 }
91 113 assert_tag 'a', :attributes => {
92 114 :href => '/projects/ecookbook/wiki/Page_with_sections/edit?section=2'
93 115 }
94 116 assert_tag 'a', :attributes => {
95 117 :href => '/projects/ecookbook/wiki/Page_with_sections/edit?section=3'
96 118 }
97 119 end
98 120
99 121 def test_show_current_version_should_display_section_edit_links
100 122 @request.session[:user_id] = 2
101 123 get :show, :project_id => 1, :id => 'Page with sections', :version => 3
102 124
103 125 assert_tag 'a', :attributes => {
104 126 :href => '/projects/ecookbook/wiki/Page_with_sections/edit?section=2'
105 127 }
106 128 end
107 129
108 130 def test_show_old_version_should_not_display_section_edit_links
109 131 @request.session[:user_id] = 2
110 132 get :show, :project_id => 1, :id => 'Page with sections', :version => 2
111 133
112 134 assert_no_tag 'a', :attributes => {
113 135 :href => '/projects/ecookbook/wiki/Page_with_sections/edit?section=2'
114 136 }
115 137 end
116 138
117 139 def test_show_unexistent_page_without_edit_right
118 140 get :show, :project_id => 1, :id => 'Unexistent page'
119 141 assert_response 404
120 142 end
121 143
122 144 def test_show_unexistent_page_with_edit_right
123 145 @request.session[:user_id] = 2
124 146 get :show, :project_id => 1, :id => 'Unexistent page'
125 147 assert_response :success
126 148 assert_template 'edit'
127 149 end
128 150
129 151 def test_show_unexistent_page_with_parent_should_preselect_parent
130 152 @request.session[:user_id] = 2
131 153 get :show, :project_id => 1, :id => 'Unexistent page', :parent => 'Another_page'
132 154 assert_response :success
133 155 assert_template 'edit'
134 156 assert_tag 'select', :attributes => {:name => 'wiki_page[parent_id]'},
135 157 :child => {:tag => 'option', :attributes => {:value => '2', :selected => 'selected'}}
136 158 end
137 159
138 160 def test_show_should_not_show_history_without_permission
139 161 Role.anonymous.remove_permission! :view_wiki_edits
140 162 get :show, :project_id => 1, :id => 'Page with sections', :version => 2
141 163
142 164 assert_response 302
143 165 end
144 166
145 167 def test_create_page
146 168 @request.session[:user_id] = 2
147 169 assert_difference 'WikiPage.count' do
148 170 assert_difference 'WikiContent.count' do
149 171 put :update, :project_id => 1,
150 172 :id => 'New page',
151 173 :content => {:comments => 'Created the page',
152 174 :text => "h1. New page\n\nThis is a new page",
153 175 :version => 0}
154 176 end
155 177 end
156 178 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'New_page'
157 179 page = Project.find(1).wiki.find_page('New page')
158 180 assert !page.new_record?
159 181 assert_not_nil page.content
160 182 assert_nil page.parent
161 183 assert_equal 'Created the page', page.content.comments
162 184 end
163 185
164 186 def test_create_page_with_attachments
165 187 @request.session[:user_id] = 2
166 188 assert_difference 'WikiPage.count' do
167 189 assert_difference 'Attachment.count' do
168 190 put :update, :project_id => 1,
169 191 :id => 'New page',
170 192 :content => {:comments => 'Created the page',
171 193 :text => "h1. New page\n\nThis is a new page",
172 194 :version => 0},
173 195 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
174 196 end
175 197 end
176 198 page = Project.find(1).wiki.find_page('New page')
177 199 assert_equal 1, page.attachments.count
178 200 assert_equal 'testfile.txt', page.attachments.first.filename
179 201 end
180 202
181 203 def test_create_page_with_parent
182 204 @request.session[:user_id] = 2
183 205 assert_difference 'WikiPage.count' do
184 206 put :update, :project_id => 1, :id => 'New page',
185 207 :content => {:text => "h1. New page\n\nThis is a new page", :version => 0},
186 208 :wiki_page => {:parent_id => 2}
187 209 end
188 210 page = Project.find(1).wiki.find_page('New page')
189 211 assert_equal WikiPage.find(2), page.parent
190 212 end
191 213
192 214 def test_edit_page
193 215 @request.session[:user_id] = 2
194 216 get :edit, :project_id => 'ecookbook', :id => 'Another_page'
195 217
196 218 assert_response :success
197 219 assert_template 'edit'
198 220
199 221 assert_tag 'textarea',
200 222 :attributes => { :name => 'content[text]' },
201 223 :content => "\n"+WikiPage.find_by_title('Another_page').content.text
202 224 end
203 225
204 226 def test_edit_section
205 227 @request.session[:user_id] = 2
206 228 get :edit, :project_id => 'ecookbook', :id => 'Page_with_sections', :section => 2
207 229
208 230 assert_response :success
209 231 assert_template 'edit'
210 232
211 233 page = WikiPage.find_by_title('Page_with_sections')
212 234 section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2)
213 235
214 236 assert_tag 'textarea',
215 237 :attributes => { :name => 'content[text]' },
216 238 :content => "\n"+section
217 239 assert_tag 'input',
218 240 :attributes => { :name => 'section', :type => 'hidden', :value => '2' }
219 241 assert_tag 'input',
220 242 :attributes => { :name => 'section_hash', :type => 'hidden', :value => hash }
221 243 end
222 244
223 245 def test_edit_invalid_section_should_respond_with_404
224 246 @request.session[:user_id] = 2
225 247 get :edit, :project_id => 'ecookbook', :id => 'Page_with_sections', :section => 10
226 248
227 249 assert_response 404
228 250 end
229 251
230 252 def test_update_page
231 253 @request.session[:user_id] = 2
232 254 assert_no_difference 'WikiPage.count' do
233 255 assert_no_difference 'WikiContent.count' do
234 256 assert_difference 'WikiContent::Version.count' do
235 257 put :update, :project_id => 1,
236 258 :id => 'Another_page',
237 259 :content => {
238 260 :comments => "my comments",
239 261 :text => "edited",
240 262 :version => 1
241 263 }
242 264 end
243 265 end
244 266 end
245 267 assert_redirected_to '/projects/ecookbook/wiki/Another_page'
246 268
247 269 page = Wiki.find(1).pages.find_by_title('Another_page')
248 270 assert_equal "edited", page.content.text
249 271 assert_equal 2, page.content.version
250 272 assert_equal "my comments", page.content.comments
251 273 end
252 274
253 275 def test_update_page_with_parent
254 276 @request.session[:user_id] = 2
255 277 assert_no_difference 'WikiPage.count' do
256 278 assert_no_difference 'WikiContent.count' do
257 279 assert_difference 'WikiContent::Version.count' do
258 280 put :update, :project_id => 1,
259 281 :id => 'Another_page',
260 282 :content => {
261 283 :comments => "my comments",
262 284 :text => "edited",
263 285 :version => 1
264 286 },
265 287 :wiki_page => {:parent_id => '1'}
266 288 end
267 289 end
268 290 end
269 291 assert_redirected_to '/projects/ecookbook/wiki/Another_page'
270 292
271 293 page = Wiki.find(1).pages.find_by_title('Another_page')
272 294 assert_equal "edited", page.content.text
273 295 assert_equal 2, page.content.version
274 296 assert_equal "my comments", page.content.comments
275 297 assert_equal WikiPage.find(1), page.parent
276 298 end
277 299
278 300 def test_update_page_with_failure
279 301 @request.session[:user_id] = 2
280 302 assert_no_difference 'WikiPage.count' do
281 303 assert_no_difference 'WikiContent.count' do
282 304 assert_no_difference 'WikiContent::Version.count' do
283 305 put :update, :project_id => 1,
284 306 :id => 'Another_page',
285 307 :content => {
286 308 :comments => 'a' * 300, # failure here, comment is too long
287 309 :text => 'edited',
288 310 :version => 1
289 311 }
290 312 end
291 313 end
292 314 end
293 315 assert_response :success
294 316 assert_template 'edit'
295 317
296 318 assert_error_tag :descendant => {:content => /Comment is too long/}
297 319 assert_tag :tag => 'textarea', :attributes => {:id => 'content_text'}, :content => "\nedited"
298 320 assert_tag :tag => 'input', :attributes => {:id => 'content_version', :value => '1'}
299 321 end
300 322
301 323 def test_update_page_with_parent_change_only_should_not_create_content_version
302 324 @request.session[:user_id] = 2
303 325 assert_no_difference 'WikiPage.count' do
304 326 assert_no_difference 'WikiContent.count' do
305 327 assert_no_difference 'WikiContent::Version.count' do
306 328 put :update, :project_id => 1,
307 329 :id => 'Another_page',
308 330 :content => {
309 331 :comments => '',
310 332 :text => Wiki.find(1).find_page('Another_page').content.text,
311 333 :version => 1
312 334 },
313 335 :wiki_page => {:parent_id => '1'}
314 336 end
315 337 end
316 338 end
317 339 page = Wiki.find(1).pages.find_by_title('Another_page')
318 340 assert_equal 1, page.content.version
319 341 assert_equal WikiPage.find(1), page.parent
320 342 end
321 343
322 344 def test_update_page_with_attachments_only_should_not_create_content_version
323 345 @request.session[:user_id] = 2
324 346 assert_no_difference 'WikiPage.count' do
325 347 assert_no_difference 'WikiContent.count' do
326 348 assert_no_difference 'WikiContent::Version.count' do
327 349 assert_difference 'Attachment.count' do
328 350 put :update, :project_id => 1,
329 351 :id => 'Another_page',
330 352 :content => {
331 353 :comments => '',
332 354 :text => Wiki.find(1).find_page('Another_page').content.text,
333 355 :version => 1
334 356 },
335 357 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
336 358 end
337 359 end
338 360 end
339 361 end
340 362 page = Wiki.find(1).pages.find_by_title('Another_page')
341 363 assert_equal 1, page.content.version
342 364 end
343 365
344 366 def test_update_stale_page_should_not_raise_an_error
345 367 @request.session[:user_id] = 2
346 368 c = Wiki.find(1).find_page('Another_page').content
347 369 c.text = 'Previous text'
348 370 c.save!
349 371 assert_equal 2, c.version
350 372
351 373 assert_no_difference 'WikiPage.count' do
352 374 assert_no_difference 'WikiContent.count' do
353 375 assert_no_difference 'WikiContent::Version.count' do
354 376 put :update, :project_id => 1,
355 377 :id => 'Another_page',
356 378 :content => {
357 379 :comments => 'My comments',
358 380 :text => 'Text should not be lost',
359 381 :version => 1
360 382 }
361 383 end
362 384 end
363 385 end
364 386 assert_response :success
365 387 assert_template 'edit'
366 388 assert_tag :div,
367 389 :attributes => { :class => /error/ },
368 390 :content => /Data has been updated by another user/
369 391 assert_tag 'textarea',
370 392 :attributes => { :name => 'content[text]' },
371 393 :content => /Text should not be lost/
372 394 assert_tag 'input',
373 395 :attributes => { :name => 'content[comments]', :value => 'My comments' }
374 396
375 397 c.reload
376 398 assert_equal 'Previous text', c.text
377 399 assert_equal 2, c.version
378 400 end
379 401
380 402 def test_update_section
381 403 @request.session[:user_id] = 2
382 404 page = WikiPage.find_by_title('Page_with_sections')
383 405 section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2)
384 406 text = page.content.text
385 407
386 408 assert_no_difference 'WikiPage.count' do
387 409 assert_no_difference 'WikiContent.count' do
388 410 assert_difference 'WikiContent::Version.count' do
389 411 put :update, :project_id => 1, :id => 'Page_with_sections',
390 412 :content => {
391 413 :text => "New section content",
392 414 :version => 3
393 415 },
394 416 :section => 2,
395 417 :section_hash => hash
396 418 end
397 419 end
398 420 end
399 421 assert_redirected_to '/projects/ecookbook/wiki/Page_with_sections'
400 422 assert_equal Redmine::WikiFormatting::Textile::Formatter.new(text).update_section(2, "New section content"), page.reload.content.text
401 423 end
402 424
403 425 def test_update_section_should_allow_stale_page_update
404 426 @request.session[:user_id] = 2
405 427 page = WikiPage.find_by_title('Page_with_sections')
406 428 section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2)
407 429 text = page.content.text
408 430
409 431 assert_no_difference 'WikiPage.count' do
410 432 assert_no_difference 'WikiContent.count' do
411 433 assert_difference 'WikiContent::Version.count' do
412 434 put :update, :project_id => 1, :id => 'Page_with_sections',
413 435 :content => {
414 436 :text => "New section content",
415 437 :version => 2 # Current version is 3
416 438 },
417 439 :section => 2,
418 440 :section_hash => hash
419 441 end
420 442 end
421 443 end
422 444 assert_redirected_to '/projects/ecookbook/wiki/Page_with_sections'
423 445 page.reload
424 446 assert_equal Redmine::WikiFormatting::Textile::Formatter.new(text).update_section(2, "New section content"), page.content.text
425 447 assert_equal 4, page.content.version
426 448 end
427 449
428 450 def test_update_section_should_not_allow_stale_section_update
429 451 @request.session[:user_id] = 2
430 452
431 453 assert_no_difference 'WikiPage.count' do
432 454 assert_no_difference 'WikiContent.count' do
433 455 assert_no_difference 'WikiContent::Version.count' do
434 456 put :update, :project_id => 1, :id => 'Page_with_sections',
435 457 :content => {
436 458 :comments => 'My comments',
437 459 :text => "Text should not be lost",
438 460 :version => 3
439 461 },
440 462 :section => 2,
441 463 :section_hash => Digest::MD5.hexdigest("wrong hash")
442 464 end
443 465 end
444 466 end
445 467 assert_response :success
446 468 assert_template 'edit'
447 469 assert_tag :div,
448 470 :attributes => { :class => /error/ },
449 471 :content => /Data has been updated by another user/
450 472 assert_tag 'textarea',
451 473 :attributes => { :name => 'content[text]' },
452 474 :content => /Text should not be lost/
453 475 assert_tag 'input',
454 476 :attributes => { :name => 'content[comments]', :value => 'My comments' }
455 477 end
456 478
457 479 def test_preview
458 480 @request.session[:user_id] = 2
459 481 xhr :post, :preview, :project_id => 1, :id => 'CookBook_documentation',
460 482 :content => { :comments => '',
461 483 :text => 'this is a *previewed text*',
462 484 :version => 3 }
463 485 assert_response :success
464 486 assert_template 'common/_preview'
465 487 assert_tag :tag => 'strong', :content => /previewed text/
466 488 end
467 489
468 490 def test_preview_new_page
469 491 @request.session[:user_id] = 2
470 492 xhr :post, :preview, :project_id => 1, :id => 'New page',
471 493 :content => { :text => 'h1. New page',
472 494 :comments => '',
473 495 :version => 0 }
474 496 assert_response :success
475 497 assert_template 'common/_preview'
476 498 assert_tag :tag => 'h1', :content => /New page/
477 499 end
478 500
479 501 def test_history
480 get :history, :project_id => 1, :id => 'CookBook_documentation'
502 get :history, :project_id => 'ecookbook', :id => 'CookBook_documentation'
481 503 assert_response :success
482 504 assert_template 'history'
483 505 assert_not_nil assigns(:versions)
484 506 assert_equal 3, assigns(:versions).size
507
485 508 assert_select "input[type=submit][name=commit]"
509 assert_select 'td' do
510 assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/2', :text => '2'
511 assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/2/annotate'
512 end
486 513 end
487 514
488 515 def test_history_with_one_version
489 516 get :history, :project_id => 1, :id => 'Another_page'
490 517 assert_response :success
491 518 assert_template 'history'
492 519 assert_not_nil assigns(:versions)
493 520 assert_equal 1, assigns(:versions).size
494 521 assert_select "input[type=submit][name=commit]", false
495 522 end
496 523
497 524 def test_diff
498 525 content = WikiPage.find(1).content
499 526 assert_difference 'WikiContent::Version.count', 2 do
500 527 content.text = "Line removed\nThis is a sample text for testing diffs"
501 528 content.save!
502 529 content.text = "This is a sample text for testing diffs\nLine added"
503 530 content.save!
504 531 end
505 532
506 533 get :diff, :project_id => 1, :id => 'CookBook_documentation', :version => content.version, :version_from => (content.version - 1)
507 534 assert_response :success
508 535 assert_template 'diff'
509 536 assert_select 'span.diff_out', :text => 'Line removed'
510 537 assert_select 'span.diff_in', :text => 'Line added'
511 538 end
512 539
513 540 def test_annotate
514 541 get :annotate, :project_id => 1, :id => 'CookBook_documentation', :version => 2
515 542 assert_response :success
516 543 assert_template 'annotate'
517 544
518 545 # Line 1
519 546 assert_tag :tag => 'tr', :child => {
520 547 :tag => 'th', :attributes => {:class => 'line-num'}, :content => '1', :sibling => {
521 548 :tag => 'td', :attributes => {:class => 'author'}, :content => /John Smith/, :sibling => {
522 549 :tag => 'td', :content => /h1\. CookBook documentation/
523 550 }
524 551 }
525 552 }
526 553
527 554 # Line 5
528 555 assert_tag :tag => 'tr', :child => {
529 556 :tag => 'th', :attributes => {:class => 'line-num'}, :content => '5', :sibling => {
530 557 :tag => 'td', :attributes => {:class => 'author'}, :content => /redMine Admin/, :sibling => {
531 558 :tag => 'td', :content => /Some updated \[\[documentation\]\] here/
532 559 }
533 560 }
534 561 }
535 562 end
536 563
537 564 def test_get_rename
538 565 @request.session[:user_id] = 2
539 566 get :rename, :project_id => 1, :id => 'Another_page'
540 567 assert_response :success
541 568 assert_template 'rename'
542 569 assert_tag 'option',
543 570 :attributes => {:value => ''},
544 571 :content => '',
545 572 :parent => {:tag => 'select', :attributes => {:name => 'wiki_page[parent_id]'}}
546 573 assert_no_tag 'option',
547 574 :attributes => {:selected => 'selected'},
548 575 :parent => {:tag => 'select', :attributes => {:name => 'wiki_page[parent_id]'}}
549 576 end
550 577
551 578 def test_get_rename_child_page
552 579 @request.session[:user_id] = 2
553 580 get :rename, :project_id => 1, :id => 'Child_1'
554 581 assert_response :success
555 582 assert_template 'rename'
556 583 assert_tag 'option',
557 584 :attributes => {:value => ''},
558 585 :content => '',
559 586 :parent => {:tag => 'select', :attributes => {:name => 'wiki_page[parent_id]'}}
560 587 assert_tag 'option',
561 588 :attributes => {:value => '2', :selected => 'selected'},
562 589 :content => /Another page/,
563 590 :parent => {
564 591 :tag => 'select',
565 592 :attributes => {:name => 'wiki_page[parent_id]'}
566 593 }
567 594 end
568 595
569 596 def test_rename_with_redirect
570 597 @request.session[:user_id] = 2
571 598 post :rename, :project_id => 1, :id => 'Another_page',
572 599 :wiki_page => { :title => 'Another renamed page',
573 600 :redirect_existing_links => 1 }
574 601 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_renamed_page'
575 602 wiki = Project.find(1).wiki
576 603 # Check redirects
577 604 assert_not_nil wiki.find_page('Another page')
578 605 assert_nil wiki.find_page('Another page', :with_redirect => false)
579 606 end
580 607
581 608 def test_rename_without_redirect
582 609 @request.session[:user_id] = 2
583 610 post :rename, :project_id => 1, :id => 'Another_page',
584 611 :wiki_page => { :title => 'Another renamed page',
585 612 :redirect_existing_links => "0" }
586 613 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_renamed_page'
587 614 wiki = Project.find(1).wiki
588 615 # Check that there's no redirects
589 616 assert_nil wiki.find_page('Another page')
590 617 end
591 618
592 619 def test_rename_with_parent_assignment
593 620 @request.session[:user_id] = 2
594 621 post :rename, :project_id => 1, :id => 'Another_page',
595 622 :wiki_page => { :title => 'Another page', :redirect_existing_links => "0", :parent_id => '4' }
596 623 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_page'
597 624 assert_equal WikiPage.find(4), WikiPage.find_by_title('Another_page').parent
598 625 end
599 626
600 627 def test_rename_with_parent_unassignment
601 628 @request.session[:user_id] = 2
602 629 post :rename, :project_id => 1, :id => 'Child_1',
603 630 :wiki_page => { :title => 'Child 1', :redirect_existing_links => "0", :parent_id => '' }
604 631 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Child_1'
605 632 assert_nil WikiPage.find_by_title('Child_1').parent
606 633 end
607 634
608 635 def test_destroy_a_page_without_children_should_not_ask_confirmation
609 636 @request.session[:user_id] = 2
610 637 delete :destroy, :project_id => 1, :id => 'Child_2'
611 638 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
612 639 end
613 640
614 641 def test_destroy_parent_should_ask_confirmation
615 642 @request.session[:user_id] = 2
616 643 assert_no_difference('WikiPage.count') do
617 644 delete :destroy, :project_id => 1, :id => 'Another_page'
618 645 end
619 646 assert_response :success
620 647 assert_template 'destroy'
621 648 assert_select 'form' do
622 649 assert_select 'input[name=todo][value=nullify]'
623 650 assert_select 'input[name=todo][value=destroy]'
624 651 assert_select 'input[name=todo][value=reassign]'
625 652 end
626 653 end
627 654
628 655 def test_destroy_parent_with_nullify_should_delete_parent_only
629 656 @request.session[:user_id] = 2
630 657 assert_difference('WikiPage.count', -1) do
631 658 delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'nullify'
632 659 end
633 660 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
634 661 assert_nil WikiPage.find_by_id(2)
635 662 end
636 663
637 664 def test_destroy_parent_with_cascade_should_delete_descendants
638 665 @request.session[:user_id] = 2
639 666 assert_difference('WikiPage.count', -4) do
640 667 delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'destroy'
641 668 end
642 669 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
643 670 assert_nil WikiPage.find_by_id(2)
644 671 assert_nil WikiPage.find_by_id(5)
645 672 end
646 673
647 674 def test_destroy_parent_with_reassign
648 675 @request.session[:user_id] = 2
649 676 assert_difference('WikiPage.count', -1) do
650 677 delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'reassign', :reassign_to_id => 1
651 678 end
652 679 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
653 680 assert_nil WikiPage.find_by_id(2)
654 681 assert_equal WikiPage.find(1), WikiPage.find_by_id(5).parent
655 682 end
656 683
657 684 def test_index
658 685 get :index, :project_id => 'ecookbook'
659 686 assert_response :success
660 687 assert_template 'index'
661 688 pages = assigns(:pages)
662 689 assert_not_nil pages
663 690 assert_equal Project.find(1).wiki.pages.size, pages.size
664 691 assert_equal pages.first.content.updated_on, pages.first.updated_on
665 692
666 693 assert_tag :ul, :attributes => { :class => 'pages-hierarchy' },
667 694 :child => { :tag => 'li', :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/CookBook_documentation' },
668 695 :content => 'CookBook documentation' },
669 696 :child => { :tag => 'ul',
670 697 :child => { :tag => 'li',
671 698 :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Page_with_an_inline_image' },
672 699 :content => 'Page with an inline image' } } } },
673 700 :child => { :tag => 'li', :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Another_page' },
674 701 :content => 'Another page' } }
675 702 end
676 703
677 704 def test_index_should_include_atom_link
678 705 get :index, :project_id => 'ecookbook'
679 706 assert_tag 'a', :attributes => { :href => '/projects/ecookbook/activity.atom?show_wiki_edits=1'}
680 707 end
681 708
682 709 def test_export_to_html
683 710 @request.session[:user_id] = 2
684 711 get :export, :project_id => 'ecookbook'
685 712
686 713 assert_response :success
687 714 assert_not_nil assigns(:pages)
688 715 assert assigns(:pages).any?
689 716 assert_equal "text/html", @response.content_type
690 717
691 718 assert_select "a[name=?]", "CookBook_documentation"
692 719 assert_select "a[name=?]", "Another_page"
693 720 assert_select "a[name=?]", "Page_with_an_inline_image"
694 721 end
695 722
696 723 def test_export_to_pdf
697 724 @request.session[:user_id] = 2
698 725 get :export, :project_id => 'ecookbook', :format => 'pdf'
699 726
700 727 assert_response :success
701 728 assert_not_nil assigns(:pages)
702 729 assert assigns(:pages).any?
703 730 assert_equal 'application/pdf', @response.content_type
704 731 assert_equal 'attachment; filename="ecookbook.pdf"', @response.headers['Content-Disposition']
705 732 assert @response.body.starts_with?('%PDF')
706 733 end
707 734
708 735 def test_export_without_permission_should_be_denied
709 736 @request.session[:user_id] = 2
710 737 Role.find_by_name('Manager').remove_permission! :export_wiki_pages
711 738 get :export, :project_id => 'ecookbook'
712 739
713 740 assert_response 403
714 741 end
715 742
716 743 def test_date_index
717 744 get :date_index, :project_id => 'ecookbook'
718 745
719 746 assert_response :success
720 747 assert_template 'date_index'
721 748 assert_not_nil assigns(:pages)
722 749 assert_not_nil assigns(:pages_by_date)
723 750
724 751 assert_tag 'a', :attributes => { :href => '/projects/ecookbook/activity.atom?show_wiki_edits=1'}
725 752 end
726 753
727 754 def test_not_found
728 755 get :show, :project_id => 999
729 756 assert_response 404
730 757 end
731 758
732 759 def test_protect_page
733 760 page = WikiPage.find_by_wiki_id_and_title(1, 'Another_page')
734 761 assert !page.protected?
735 762 @request.session[:user_id] = 2
736 763 post :protect, :project_id => 1, :id => page.title, :protected => '1'
737 764 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_page'
738 765 assert page.reload.protected?
739 766 end
740 767
741 768 def test_unprotect_page
742 769 page = WikiPage.find_by_wiki_id_and_title(1, 'CookBook_documentation')
743 770 assert page.protected?
744 771 @request.session[:user_id] = 2
745 772 post :protect, :project_id => 1, :id => page.title, :protected => '0'
746 773 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'CookBook_documentation'
747 774 assert !page.reload.protected?
748 775 end
749 776
750 777 def test_show_page_with_edit_link
751 778 @request.session[:user_id] = 2
752 779 get :show, :project_id => 1
753 780 assert_response :success
754 781 assert_template 'show'
755 782 assert_tag :tag => 'a', :attributes => { :href => '/projects/1/wiki/CookBook_documentation/edit' }
756 783 end
757 784
758 785 def test_show_page_without_edit_link
759 786 @request.session[:user_id] = 4
760 787 get :show, :project_id => 1
761 788 assert_response :success
762 789 assert_template 'show'
763 790 assert_no_tag :tag => 'a', :attributes => { :href => '/projects/1/wiki/CookBook_documentation/edit' }
764 791 end
765 792
766 793 def test_show_pdf
767 794 @request.session[:user_id] = 2
768 795 get :show, :project_id => 1, :format => 'pdf'
769 796 assert_response :success
770 797 assert_not_nil assigns(:page)
771 798 assert_equal 'application/pdf', @response.content_type
772 799 assert_equal 'attachment; filename="CookBook_documentation.pdf"',
773 800 @response.headers['Content-Disposition']
774 801 end
775 802
776 803 def test_show_html
777 804 @request.session[:user_id] = 2
778 805 get :show, :project_id => 1, :format => 'html'
779 806 assert_response :success
780 807 assert_not_nil assigns(:page)
781 808 assert_equal 'text/html', @response.content_type
782 809 assert_equal 'attachment; filename="CookBook_documentation.html"',
783 810 @response.headers['Content-Disposition']
784 811 assert_tag 'h1', :content => 'CookBook documentation'
785 812 end
786 813
787 814 def test_show_versioned_html
788 815 @request.session[:user_id] = 2
789 816 get :show, :project_id => 1, :format => 'html', :version => 2
790 817 assert_response :success
791 818 assert_not_nil assigns(:content)
792 819 assert_equal 2, assigns(:content).version
793 820 assert_equal 'text/html', @response.content_type
794 821 assert_equal 'attachment; filename="CookBook_documentation.html"',
795 822 @response.headers['Content-Disposition']
796 823 assert_tag 'h1', :content => 'CookBook documentation'
797 824 end
798 825
799 826 def test_show_txt
800 827 @request.session[:user_id] = 2
801 828 get :show, :project_id => 1, :format => 'txt'
802 829 assert_response :success
803 830 assert_not_nil assigns(:page)
804 831 assert_equal 'text/plain', @response.content_type
805 832 assert_equal 'attachment; filename="CookBook_documentation.txt"',
806 833 @response.headers['Content-Disposition']
807 834 assert_include 'h1. CookBook documentation', @response.body
808 835 end
809 836
810 837 def test_show_versioned_txt
811 838 @request.session[:user_id] = 2
812 839 get :show, :project_id => 1, :format => 'txt', :version => 2
813 840 assert_response :success
814 841 assert_not_nil assigns(:content)
815 842 assert_equal 2, assigns(:content).version
816 843 assert_equal 'text/plain', @response.content_type
817 844 assert_equal 'attachment; filename="CookBook_documentation.txt"',
818 845 @response.headers['Content-Disposition']
819 846 assert_include 'h1. CookBook documentation', @response.body
820 847 end
821 848
822 849 def test_edit_unprotected_page
823 850 # Non members can edit unprotected wiki pages
824 851 @request.session[:user_id] = 4
825 852 get :edit, :project_id => 1, :id => 'Another_page'
826 853 assert_response :success
827 854 assert_template 'edit'
828 855 end
829 856
830 857 def test_edit_protected_page_by_nonmember
831 858 # Non members can't edit protected wiki pages
832 859 @request.session[:user_id] = 4
833 860 get :edit, :project_id => 1, :id => 'CookBook_documentation'
834 861 assert_response 403
835 862 end
836 863
837 864 def test_edit_protected_page_by_member
838 865 @request.session[:user_id] = 2
839 866 get :edit, :project_id => 1, :id => 'CookBook_documentation'
840 867 assert_response :success
841 868 assert_template 'edit'
842 869 end
843 870
844 871 def test_history_of_non_existing_page_should_return_404
845 872 get :history, :project_id => 1, :id => 'Unknown_page'
846 873 assert_response 404
847 874 end
848 875
849 876 def test_add_attachment
850 877 @request.session[:user_id] = 2
851 878 assert_difference 'Attachment.count' do
852 879 post :add_attachment, :project_id => 1, :id => 'CookBook_documentation',
853 880 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
854 881 end
855 882 attachment = Attachment.first(:order => 'id DESC')
856 883 assert_equal Wiki.find(1).find_page('CookBook_documentation'), attachment.container
857 884 end
858 885 end
@@ -1,126 +1,121
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../../test_helper', __FILE__)
19 19
20 20 class RoutingWikiTest < ActionController::IntegrationTest
21 21 def test_wiki_matching
22 22 assert_routing(
23 23 { :method => 'get', :path => "/projects/567/wiki" },
24 24 { :controller => 'wiki', :action => 'show', :project_id => '567' }
25 25 )
26 26 assert_routing(
27 27 { :method => 'get', :path => "/projects/567/wiki/lalala" },
28 28 { :controller => 'wiki', :action => 'show', :project_id => '567',
29 29 :id => 'lalala' }
30 30 )
31 31 assert_routing(
32 32 { :method => 'get', :path => "/projects/567/wiki/lalala.pdf" },
33 33 { :controller => 'wiki', :action => 'show', :project_id => '567',
34 34 :id => 'lalala', :format => 'pdf' }
35 35 )
36 36 assert_routing(
37 37 { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/diff" },
38 38 { :controller => 'wiki', :action => 'diff', :project_id => '1',
39 39 :id => 'CookBook_documentation' }
40 40 )
41 41 assert_routing(
42 { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/diff/2" },
42 { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/2/diff" },
43 43 { :controller => 'wiki', :action => 'diff', :project_id => '1',
44 44 :id => 'CookBook_documentation', :version => '2' }
45 45 )
46 46 assert_routing(
47 { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/diff/2/vs/1" },
48 { :controller => 'wiki', :action => 'diff', :project_id => '1',
49 :id => 'CookBook_documentation', :version => '2', :version_from => '1' }
50 )
51 assert_routing(
52 { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/annotate/2" },
47 { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/2/annotate" },
53 48 { :controller => 'wiki', :action => 'annotate', :project_id => '1',
54 49 :id => 'CookBook_documentation', :version => '2' }
55 50 )
56 51 end
57 52
58 53 def test_wiki_misc
59 54 assert_routing(
60 55 { :method => 'get', :path => "/projects/567/wiki/date_index" },
61 56 { :controller => 'wiki', :action => 'date_index', :project_id => '567' }
62 57 )
63 58 assert_routing(
64 59 { :method => 'get', :path => "/projects/567/wiki/export" },
65 60 { :controller => 'wiki', :action => 'export', :project_id => '567' }
66 61 )
67 62 assert_routing(
68 63 { :method => 'get', :path => "/projects/567/wiki/export.pdf" },
69 64 { :controller => 'wiki', :action => 'export', :project_id => '567', :format => 'pdf' }
70 65 )
71 66 assert_routing(
72 67 { :method => 'get', :path => "/projects/567/wiki/index" },
73 68 { :controller => 'wiki', :action => 'index', :project_id => '567' }
74 69 )
75 70 end
76 71
77 72 def test_wiki_resources
78 73 assert_routing(
79 74 { :method => 'get', :path => "/projects/567/wiki/my_page/edit" },
80 75 { :controller => 'wiki', :action => 'edit', :project_id => '567',
81 76 :id => 'my_page' }
82 77 )
83 78 assert_routing(
84 79 { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/history" },
85 80 { :controller => 'wiki', :action => 'history', :project_id => '1',
86 81 :id => 'CookBook_documentation' }
87 82 )
88 83 assert_routing(
89 84 { :method => 'get', :path => "/projects/22/wiki/ladida/rename" },
90 85 { :controller => 'wiki', :action => 'rename', :project_id => '22',
91 86 :id => 'ladida' }
92 87 )
93 88 ["post", "put"].each do |method|
94 89 assert_routing(
95 90 { :method => method, :path => "/projects/567/wiki/CookBook_documentation/preview" },
96 91 { :controller => 'wiki', :action => 'preview', :project_id => '567',
97 92 :id => 'CookBook_documentation' }
98 93 )
99 94 end
100 95 assert_routing(
101 96 { :method => 'post', :path => "/projects/22/wiki/ladida/rename" },
102 97 { :controller => 'wiki', :action => 'rename', :project_id => '22',
103 98 :id => 'ladida' }
104 99 )
105 100 assert_routing(
106 101 { :method => 'post', :path => "/projects/22/wiki/ladida/protect" },
107 102 { :controller => 'wiki', :action => 'protect', :project_id => '22',
108 103 :id => 'ladida' }
109 104 )
110 105 assert_routing(
111 106 { :method => 'post', :path => "/projects/22/wiki/ladida/add_attachment" },
112 107 { :controller => 'wiki', :action => 'add_attachment', :project_id => '22',
113 108 :id => 'ladida' }
114 109 )
115 110 assert_routing(
116 111 { :method => 'put', :path => "/projects/567/wiki/my_page" },
117 112 { :controller => 'wiki', :action => 'update', :project_id => '567',
118 113 :id => 'my_page' }
119 114 )
120 115 assert_routing(
121 116 { :method => 'delete', :path => "/projects/22/wiki/ladida" },
122 117 { :controller => 'wiki', :action => 'destroy', :project_id => '22',
123 118 :id => 'ladida' }
124 119 )
125 120 end
126 121 end
General Comments 0
You need to be logged in to leave comments. Login now