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