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