##// END OF EJS Templates
Move wiki page to other project (#5450)....
Jean-Philippe Lang -
r13261:e0a034164f34
parent child
Show More
@@ -0,0 +1,11
1 class AddWikiRedirectsRedirectsToWikiId < ActiveRecord::Migration
2 def self.up
3 add_column :wiki_redirects, :redirects_to_wiki_id, :integer
4 WikiRedirect.update_all "redirects_to_wiki_id = wiki_id"
5 change_column :wiki_redirects, :redirects_to_wiki_id, :integer, :null => false
6 end
7
8 def self.down
9 remove_column :wiki_redirects, :redirects_to_wiki_id
10 end
11 end
@@ -1,369 +1,374
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 # The WikiController follows the Rails REST controller pattern but with
19 19 # a few differences
20 20 #
21 21 # * index - shows a list of WikiPages grouped by page or date
22 22 # * new - not used
23 23 # * create - not used
24 24 # * show - will also show the form for creating a new wiki page
25 25 # * edit - used to edit an existing or new page
26 26 # * update - used to save a wiki page update to the database, including new pages
27 27 # * destroy - normal
28 28 #
29 29 # Other member and collection methods are also used
30 30 #
31 31 # TODO: still being worked on
32 32 class WikiController < ApplicationController
33 33 default_search_scope :wiki_pages
34 34 before_filter :find_wiki, :authorize
35 35 before_filter :find_existing_or_new_page, :only => [:show, :edit, :update]
36 36 before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy, :destroy_version]
37 37 accept_api_auth :index, :show, :update, :destroy
38 38 before_filter :find_attachments, :only => [:preview]
39 39
40 40 helper :attachments
41 41 include AttachmentsHelper
42 42 helper :watchers
43 43 include Redmine::Export::PDF
44 44
45 45 # List of pages, sorted alphabetically and by parent (hierarchy)
46 46 def index
47 47 load_pages_for_index
48 48
49 49 respond_to do |format|
50 50 format.html {
51 51 @pages_by_parent_id = @pages.group_by(&:parent_id)
52 52 }
53 53 format.api
54 54 end
55 55 end
56 56
57 57 # List of page, by last update
58 58 def date_index
59 59 load_pages_for_index
60 60 @pages_by_date = @pages.group_by {|p| p.updated_on.to_date}
61 61 end
62 62
63 63 # display a page (in editing mode if it doesn't exist)
64 64 def show
65 65 if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project)
66 66 deny_access
67 67 return
68 68 end
69 69 @content = @page.content_for_version(params[:version])
70 70 if @content.nil?
71 71 if User.current.allowed_to?(:edit_wiki_pages, @project) && editable? && !api_request?
72 72 edit
73 73 render :action => 'edit'
74 74 else
75 75 render_404
76 76 end
77 77 return
78 78 end
79 79 if User.current.allowed_to?(:export_wiki_pages, @project)
80 80 if params[:format] == 'pdf'
81 81 send_data(wiki_page_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf")
82 82 return
83 83 elsif params[:format] == 'html'
84 84 export = render_to_string :action => 'export', :layout => false
85 85 send_data(export, :type => 'text/html', :filename => "#{@page.title}.html")
86 86 return
87 87 elsif params[:format] == 'txt'
88 88 send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt")
89 89 return
90 90 end
91 91 end
92 92 @editable = editable?
93 93 @sections_editable = @editable && User.current.allowed_to?(:edit_wiki_pages, @page.project) &&
94 94 @content.current_version? &&
95 95 Redmine::WikiFormatting.supports_section_edit?
96 96
97 97 respond_to do |format|
98 98 format.html
99 99 format.api
100 100 end
101 101 end
102 102
103 103 # edit an existing page or a new one
104 104 def edit
105 105 return render_403 unless editable?
106 106 if @page.new_record?
107 107 if params[:parent].present?
108 108 @page.parent = @page.wiki.find_page(params[:parent].to_s)
109 109 end
110 110 end
111 111
112 112 @content = @page.content_for_version(params[:version])
113 113 @content ||= WikiContent.new(:page => @page)
114 114 @content.text = initial_page_content(@page) if @content.text.blank?
115 115 # don't keep previous comment
116 116 @content.comments = nil
117 117
118 118 # To prevent StaleObjectError exception when reverting to a previous version
119 119 @content.version = @page.content.version if @page.content
120 120
121 121 @text = @content.text
122 122 if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
123 123 @section = params[:section].to_i
124 124 @text, @section_hash = Redmine::WikiFormatting.formatter.new(@text).get_section(@section)
125 125 render_404 if @text.blank?
126 126 end
127 127 end
128 128
129 129 # Creates a new page or updates an existing one
130 130 def update
131 131 return render_403 unless editable?
132 132 was_new_page = @page.new_record?
133 133 @page.safe_attributes = params[:wiki_page]
134 134
135 135 @content = @page.content || WikiContent.new(:page => @page)
136 136 content_params = params[:content]
137 137 if content_params.nil? && params[:wiki_page].is_a?(Hash)
138 138 content_params = params[:wiki_page].slice(:text, :comments, :version)
139 139 end
140 140 content_params ||= {}
141 141
142 142 @content.comments = content_params[:comments]
143 143 @text = content_params[:text]
144 144 if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
145 145 @section = params[:section].to_i
146 146 @section_hash = params[:section_hash]
147 147 @content.text = Redmine::WikiFormatting.formatter.new(@content.text).update_section(@section, @text, @section_hash)
148 148 else
149 149 @content.version = content_params[:version] if content_params[:version]
150 150 @content.text = @text
151 151 end
152 152 @content.author = User.current
153 153
154 154 if @page.save_with_content(@content)
155 155 attachments = Attachment.attach_files(@page, params[:attachments])
156 156 render_attachment_warning_if_needed(@page)
157 157 call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page})
158 158
159 159 respond_to do |format|
160 160 format.html {
161 161 anchor = @section ? "section-#{@section}" : nil
162 162 redirect_to project_wiki_page_path(@project, @page.title, :anchor => anchor)
163 163 }
164 164 format.api {
165 165 if was_new_page
166 166 render :action => 'show', :status => :created, :location => project_wiki_page_path(@project, @page.title)
167 167 else
168 168 render_api_ok
169 169 end
170 170 }
171 171 end
172 172 else
173 173 respond_to do |format|
174 174 format.html { render :action => 'edit' }
175 175 format.api { render_validation_errors(@content) }
176 176 end
177 177 end
178 178
179 179 rescue ActiveRecord::StaleObjectError, Redmine::WikiFormatting::StaleSectionError
180 180 # Optimistic locking exception
181 181 respond_to do |format|
182 182 format.html {
183 183 flash.now[:error] = l(:notice_locking_conflict)
184 184 render :action => 'edit'
185 185 }
186 186 format.api { render_api_head :conflict }
187 187 end
188 188 rescue ActiveRecord::RecordNotSaved
189 189 respond_to do |format|
190 190 format.html { render :action => 'edit' }
191 191 format.api { render_validation_errors(@content) }
192 192 end
193 193 end
194 194
195 195 # rename a page
196 196 def rename
197 197 return render_403 unless editable?
198 198 @page.redirect_existing_links = true
199 199 # used to display the *original* title if some AR validation errors occur
200 200 @original_title = @page.pretty_title
201 if request.post? && @page.update_attributes(params[:wiki_page])
201 @page.safe_attributes = params[:wiki_page]
202 if request.post? && @page.save
202 203 flash[:notice] = l(:notice_successful_update)
203 redirect_to project_wiki_page_path(@project, @page.title)
204 redirect_to project_wiki_page_path(@page.project, @page.title)
204 205 end
205 206 end
206 207
207 208 def protect
208 209 @page.update_attribute :protected, params[:protected]
209 210 redirect_to project_wiki_page_path(@project, @page.title)
210 211 end
211 212
212 213 # show page history
213 214 def history
214 215 @version_count = @page.content.versions.count
215 216 @version_pages = Paginator.new @version_count, per_page_option, params['page']
216 217 # don't load text
217 218 @versions = @page.content.versions.
218 219 select("id, author_id, comments, updated_on, version").
219 220 reorder('version DESC').
220 221 limit(@version_pages.per_page + 1).
221 222 offset(@version_pages.offset).
222 223 to_a
223 224
224 225 render :layout => false if request.xhr?
225 226 end
226 227
227 228 def diff
228 229 @diff = @page.diff(params[:version], params[:version_from])
229 230 render_404 unless @diff
230 231 end
231 232
232 233 def annotate
233 234 @annotate = @page.annotate(params[:version])
234 235 render_404 unless @annotate
235 236 end
236 237
237 238 # Removes a wiki page and its history
238 239 # Children can be either set as root pages, removed or reassigned to another parent page
239 240 def destroy
240 241 return render_403 unless editable?
241 242
242 243 @descendants_count = @page.descendants.size
243 244 if @descendants_count > 0
244 245 case params[:todo]
245 246 when 'nullify'
246 247 # Nothing to do
247 248 when 'destroy'
248 249 # Removes all its descendants
249 250 @page.descendants.each(&:destroy)
250 251 when 'reassign'
251 252 # Reassign children to another parent page
252 253 reassign_to = @wiki.pages.find_by_id(params[:reassign_to_id].to_i)
253 254 return unless reassign_to
254 255 @page.children.each do |child|
255 256 child.update_attribute(:parent, reassign_to)
256 257 end
257 258 else
258 259 @reassignable_to = @wiki.pages - @page.self_and_descendants
259 260 # display the destroy form if it's a user request
260 261 return unless api_request?
261 262 end
262 263 end
263 264 @page.destroy
264 265 respond_to do |format|
265 266 format.html { redirect_to project_wiki_index_path(@project) }
266 267 format.api { render_api_ok }
267 268 end
268 269 end
269 270
270 271 def destroy_version
271 272 return render_403 unless editable?
272 273
273 274 @content = @page.content_for_version(params[:version])
274 275 @content.destroy
275 276 redirect_to_referer_or history_project_wiki_page_path(@project, @page.title)
276 277 end
277 278
278 279 # Export wiki to a single pdf or html file
279 280 def export
280 281 @pages = @wiki.pages.
281 282 order('title').
282 283 includes([:content, {:attachments => :author}]).
283 284 to_a
284 285 respond_to do |format|
285 286 format.html {
286 287 export = render_to_string :action => 'export_multiple', :layout => false
287 288 send_data(export, :type => 'text/html', :filename => "wiki.html")
288 289 }
289 290 format.pdf {
290 291 send_data(wiki_pages_to_pdf(@pages, @project),
291 292 :type => 'application/pdf',
292 293 :filename => "#{@project.identifier}.pdf")
293 294 }
294 295 end
295 296 end
296 297
297 298 def preview
298 299 page = @wiki.find_page(params[:id])
299 300 # page is nil when previewing a new page
300 301 return render_403 unless page.nil? || editable?(page)
301 302 if page
302 303 @attachments += page.attachments
303 304 @previewed = page.content
304 305 end
305 306 @text = params[:content][:text]
306 307 render :partial => 'common/preview'
307 308 end
308 309
309 310 def add_attachment
310 311 return render_403 unless editable?
311 312 attachments = Attachment.attach_files(@page, params[:attachments])
312 313 render_attachment_warning_if_needed(@page)
313 314 redirect_to :action => 'show', :id => @page.title, :project_id => @project
314 315 end
315 316
316 317 private
317 318
318 319 def find_wiki
319 320 @project = Project.find(params[:project_id])
320 321 @wiki = @project.wiki
321 322 render_404 unless @wiki
322 323 rescue ActiveRecord::RecordNotFound
323 324 render_404
324 325 end
325 326
326 327 # Finds the requested page or a new page if it doesn't exist
327 328 def find_existing_or_new_page
328 329 @page = @wiki.find_or_new_page(params[:id])
329 330 if @wiki.page_found_with_redirect?
330 331 redirect_to_page @page
331 332 end
332 333 end
333 334
334 335 # Finds the requested page and returns a 404 error if it doesn't exist
335 336 def find_existing_page
336 337 @page = @wiki.find_page(params[:id])
337 338 if @page.nil?
338 339 render_404
339 340 return
340 341 end
341 342 if @wiki.page_found_with_redirect?
342 343 redirect_to_page @page
343 344 end
344 345 end
345 346
346 347 def redirect_to_page(page)
347 redirect_to :action => action_name, :project_id => page.wiki.project, :id => page.title
348 if page.project && page.project.visible?
349 redirect_to :action => action_name, :project_id => page.project, :id => page.title
350 else
351 render_404
352 end
348 353 end
349 354
350 355 # Returns true if the current user is allowed to edit the page, otherwise false
351 356 def editable?(page = @page)
352 357 page.editable_by?(User.current)
353 358 end
354 359
355 360 # Returns the default content of a new wiki page
356 361 def initial_page_content(page)
357 362 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
358 363 extend helper unless self.instance_of?(helper)
359 364 helper.instance_method(:initial_page_content).bind(self).call(page)
360 365 end
361 366
362 367 def load_pages_for_index
363 368 @pages = @wiki.pages.with_updated_on.
364 369 reorder("#{WikiPage.table_name}.title").
365 370 includes(:wiki => :project).
366 371 includes(:parent).
367 372 to_a
368 373 end
369 374 end
@@ -1,43 +1,53
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2014 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 module WikiHelper
21 21
22 22 def wiki_page_options_for_select(pages, selected = nil, parent = nil, level = 0)
23 23 pages = pages.group_by(&:parent) unless pages.is_a?(Hash)
24 24 s = ''.html_safe
25 25 if pages.has_key?(parent)
26 26 pages[parent].each do |page|
27 27 attrs = "value='#{page.id}'"
28 28 attrs << " selected='selected'" if selected == page
29 29 indent = (level > 0) ? ('&nbsp;' * level * 2 + '&#187; ') : ''
30 30
31 31 s << content_tag('option', (indent + h(page.pretty_title)).html_safe, :value => page.id.to_s, :selected => selected == page) +
32 32 wiki_page_options_for_select(pages, selected, page, level + 1)
33 33 end
34 34 end
35 35 s
36 36 end
37 37
38 def wiki_page_wiki_options_for_select(page)
39 projects = Project.allowed_to(:rename_wiki_pages).joins(:wiki).preload(:wiki).to_a
40 projects << page.project unless projects.include?(page.project)
41
42 project_tree_options_for_select(projects, :selected => page.project) do |project|
43 wiki_id = project.wiki.try(:id)
44 {:value => wiki_id, :selected => wiki_id == page.wiki_id}
45 end
46 end
47
38 48 def wiki_page_breadcrumb(page)
39 49 breadcrumb(page.ancestors.reverse.collect {|parent|
40 50 link_to(h(parent.pretty_title), {:controller => 'wiki', :action => 'show', :id => parent.title, :project_id => parent.project, :version => nil})
41 51 })
42 52 end
43 53 end
@@ -1,98 +1,106
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Wiki < ActiveRecord::Base
19 19 include Redmine::SafeAttributes
20 20 belongs_to :project
21 21 has_many :pages, lambda {order('title')}, :class_name => 'WikiPage', :dependent => :destroy
22 has_many :redirects, :class_name => 'WikiRedirect', :dependent => :delete_all
22 has_many :redirects, :class_name => 'WikiRedirect'
23 23
24 24 acts_as_watchable
25 25
26 26 validates_presence_of :start_page
27 27 validates_format_of :start_page, :with => /\A[^,\.\/\?\;\|\:]*\z/
28 28 attr_protected :id
29 29
30 before_destroy :delete_redirects
31
30 32 safe_attributes 'start_page'
31 33
32 34 def visible?(user=User.current)
33 35 !user.nil? && user.allowed_to?(:view_wiki_pages, project)
34 36 end
35 37
36 38 # Returns the wiki page that acts as the sidebar content
37 39 # or nil if no such page exists
38 40 def sidebar
39 41 @sidebar ||= find_page('Sidebar', :with_redirect => false)
40 42 end
41 43
42 44 # find the page with the given title
43 45 # if page doesn't exist, return a new page
44 46 def find_or_new_page(title)
45 47 title = start_page if title.blank?
46 48 find_page(title) || WikiPage.new(:wiki => self, :title => Wiki.titleize(title))
47 49 end
48 50
49 51 # find the page with the given title
50 52 def find_page(title, options = {})
51 53 @page_found_with_redirect = false
52 54 title = start_page if title.blank?
53 55 title = Wiki.titleize(title)
54 56 page = pages.where("LOWER(title) = LOWER(?)", title).first
55 if !page && !(options[:with_redirect] == false)
57 if page.nil? && options[:with_redirect] != false
56 58 # search for a redirect
57 59 redirect = redirects.where("LOWER(title) = LOWER(?)", title).first
58 60 if redirect
59 page = find_page(redirect.redirects_to, :with_redirect => false)
61 page = redirect.target_page
60 62 @page_found_with_redirect = true
61 63 end
62 64 end
63 65 page
64 66 end
65 67
66 68 # Returns true if the last page was found with a redirect
67 69 def page_found_with_redirect?
68 70 @page_found_with_redirect
69 71 end
70 72
73 # Deletes all redirects from/to the wiki
74 def delete_redirects
75 WikiRedirect.where(:wiki_id => id).delete_all
76 WikiRedirect.where(:redirects_to_wiki_id => id).delete_all
77 end
78
71 79 # Finds a page by title
72 80 # The given string can be of one of the forms: "title" or "project:title"
73 81 # Examples:
74 82 # Wiki.find_page("bar", project => foo)
75 83 # Wiki.find_page("foo:bar")
76 84 def self.find_page(title, options = {})
77 85 project = options[:project]
78 86 if title.to_s =~ %r{^([^\:]+)\:(.*)$}
79 87 project_identifier, title = $1, $2
80 88 project = Project.find_by_identifier(project_identifier) || Project.find_by_name(project_identifier)
81 89 end
82 90 if project && project.wiki
83 91 page = project.wiki.find_page(title)
84 92 if page && page.content
85 93 page
86 94 end
87 95 end
88 96 end
89 97
90 98 # turn a string into a valid page title
91 99 def self.titleize(title)
92 100 # replace spaces with _ and remove unwanted caracters
93 101 title = title.gsub(/\s+/, '_').delete(',./?;|:') if title
94 102 # upcase the first letter
95 103 title = (title.slice(0..0).upcase + (title.slice(1..-1) || '')) if title
96 104 title
97 105 end
98 106 end
@@ -1,253 +1,293
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'diff'
19 19 require 'enumerator'
20 20
21 21 class WikiPage < ActiveRecord::Base
22 22 include Redmine::SafeAttributes
23 23
24 24 belongs_to :wiki
25 25 has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy
26 26 acts_as_attachable :delete_permission => :delete_wiki_pages_attachments
27 27 acts_as_tree :dependent => :nullify, :order => 'title'
28 28
29 29 acts_as_watchable
30 30 acts_as_event :title => Proc.new {|o| "#{l(:label_wiki)}: #{o.title}"},
31 31 :description => :text,
32 32 :datetime => :created_on,
33 33 :url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.wiki.project, :id => o.title}}
34 34
35 35 acts_as_searchable :columns => ['title', "#{WikiContent.table_name}.text"],
36 36 :scope => preload(:wiki => :project).joins(:content, {:wiki => :project}),
37 37 :permission => :view_wiki_pages,
38 38 :project_key => "#{Wiki.table_name}.project_id"
39 39
40 40 attr_accessor :redirect_existing_links
41 41
42 42 validates_presence_of :title
43 43 validates_format_of :title, :with => /\A[^,\.\/\?\;\|\s]*\z/
44 44 validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false
45 45 validates_associated :content
46 46 attr_protected :id
47 47
48 48 validate :validate_parent_title
49 before_destroy :remove_redirects
50 before_save :handle_redirects
49 before_destroy :delete_redirects
50 before_save :handle_rename_or_move
51 after_save :handle_children_move
51 52
52 53 # eager load information about last updates, without loading text
53 54 scope :with_updated_on, lambda {
54 55 select("#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on, #{WikiContent.table_name}.version").
55 56 joins("LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id")
56 57 }
57 58
58 59 # Wiki pages that are protected by default
59 60 DEFAULT_PROTECTED_PAGES = %w(sidebar)
60 61
61 safe_attributes 'parent_id', 'parent_title',
62 safe_attributes 'parent_id', 'parent_title', 'title', 'redirect_existing_links', 'wiki_id',
62 63 :if => lambda {|page, user| page.new_record? || user.allowed_to?(:rename_wiki_pages, page.project)}
63 64
64 65 def initialize(attributes=nil, *args)
65 66 super
66 67 if new_record? && DEFAULT_PROTECTED_PAGES.include?(title.to_s.downcase)
67 68 self.protected = true
68 69 end
69 70 end
70 71
71 72 def visible?(user=User.current)
72 73 !user.nil? && user.allowed_to?(:view_wiki_pages, project)
73 74 end
74 75
75 76 def title=(value)
76 77 value = Wiki.titleize(value)
77 @previous_title = read_attribute(:title) if @previous_title.blank?
78 78 write_attribute(:title, value)
79 79 end
80 80
81 def handle_redirects
82 self.title = Wiki.titleize(title)
83 # Manage redirects if the title has changed
84 if !@previous_title.blank? && (@previous_title != title) && !new_record?
81 def safe_attributes=(attrs, user=User.current)
82 return unless attrs.is_a?(Hash)
83 attrs = attrs.deep_dup
84
85 # Project and Tracker must be set before since new_statuses_allowed_to depends on it.
86 if (w_id = attrs.delete('wiki_id')) && safe_attribute?('wiki_id')
87 if (w = Wiki.find_by_id(w_id)) && w.project && user.allowed_to?(:rename_wiki_pages, w.project)
88 self.wiki = w
89 end
90 end
91
92 super attrs, user
93 end
94
95 # Manages redirects if page is renamed or moved
96 def handle_rename_or_move
97 if !new_record? && (title_changed? || wiki_id_changed?)
85 98 # Update redirects that point to the old title
86 wiki.redirects.where(:redirects_to => @previous_title).each do |r|
99 WikiRedirect.where(:redirects_to => title_was, :redirects_to_wiki_id => wiki_id_was).each do |r|
87 100 r.redirects_to = title
88 r.title == r.redirects_to ? r.destroy : r.save
101 r.redirects_to_wiki_id = wiki_id
102 (r.title == r.redirects_to && r.wiki_id == r.redirects_to_wiki_id) ? r.destroy : r.save
89 103 end
90 104 # Remove redirects for the new title
91 wiki.redirects.where(:title => title).each(&:destroy)
105 WikiRedirect.where(:wiki_id => wiki_id, :title => title).delete_all
92 106 # Create a redirect to the new title
93 wiki.redirects << WikiRedirect.new(:title => @previous_title, :redirects_to => title) unless redirect_existing_links == "0"
94 @previous_title = nil
107 unless redirect_existing_links == "0"
108 WikiRedirect.create(
109 :wiki_id => wiki_id_was, :title => title_was,
110 :redirects_to_wiki_id => wiki_id, :redirects_to => title
111 )
112 end
113 end
114 if !new_record? && wiki_id_changed? && parent.present?
115 unless parent.wiki_id == wiki_id
116 self.parent_id = nil
117 end
95 118 end
96 119 end
120 private :handle_rename_or_move
121
122 # Moves child pages if page was moved
123 def handle_children_move
124 if !new_record? && wiki_id_changed?
125 children.each do |child|
126 child.wiki_id = wiki_id
127 child.redirect_existing_links = redirect_existing_links
128 unless child.save
129 WikiPage.where(:id => child.id).update_all :parent_nil => nil
130 end
131 end
132 end
133 end
134 private :handle_children_move
97 135
98 def remove_redirects
99 # Remove redirects to this page
100 wiki.redirects.where(:redirects_to => title).each(&:destroy)
136 # Deletes redirects to this page
137 def delete_redirects
138 WikiRedirect.where(:redirects_to_wiki_id => wiki_id, :redirects_to => title).delete_all
101 139 end
102 140
103 141 def pretty_title
104 142 WikiPage.pretty_title(title)
105 143 end
106 144
107 145 def content_for_version(version=nil)
108 146 if content
109 147 result = content.versions.find_by_version(version.to_i) if version
110 148 result ||= content
111 149 result
112 150 end
113 151 end
114 152
115 153 def diff(version_to=nil, version_from=nil)
116 154 version_to = version_to ? version_to.to_i : self.content.version
117 155 content_to = content.versions.find_by_version(version_to)
118 156 content_from = version_from ? content.versions.find_by_version(version_from.to_i) : content_to.try(:previous)
119 157 return nil unless content_to && content_from
120 158
121 159 if content_from.version > content_to.version
122 160 content_to, content_from = content_from, content_to
123 161 end
124 162
125 163 (content_to && content_from) ? WikiDiff.new(content_to, content_from) : nil
126 164 end
127 165
128 166 def annotate(version=nil)
129 167 version = version ? version.to_i : self.content.version
130 168 c = content.versions.find_by_version(version)
131 169 c ? WikiAnnotate.new(c) : nil
132 170 end
133 171
134 172 def self.pretty_title(str)
135 173 (str && str.is_a?(String)) ? str.tr('_', ' ') : str
136 174 end
137 175
138 176 def project
139 wiki.project
177 wiki.try(:project)
140 178 end
141 179
142 180 def text
143 181 content.text if content
144 182 end
145 183
146 184 def updated_on
147 185 unless @updated_on
148 186 if time = read_attribute(:updated_on)
149 187 # content updated_on was eager loaded with the page
150 188 begin
151 189 @updated_on = (self.class.default_timezone == :utc ? Time.parse(time.to_s).utc : Time.parse(time.to_s).localtime)
152 190 rescue
153 191 end
154 192 else
155 193 @updated_on = content && content.updated_on
156 194 end
157 195 end
158 196 @updated_on
159 197 end
160 198
161 199 # Returns true if usr is allowed to edit the page, otherwise false
162 200 def editable_by?(usr)
163 201 !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project)
164 202 end
165 203
166 204 def attachments_deletable?(usr=User.current)
167 205 editable_by?(usr) && super(usr)
168 206 end
169 207
170 208 def parent_title
171 209 @parent_title || (self.parent && self.parent.pretty_title)
172 210 end
173 211
174 212 def parent_title=(t)
175 213 @parent_title = t
176 214 parent_page = t.blank? ? nil : self.wiki.find_page(t)
177 215 self.parent = parent_page
178 216 end
179 217
180 218 # Saves the page and its content if text was changed
181 219 def save_with_content(content)
182 220 ret = nil
183 221 transaction do
184 222 ret = save
185 223 if content.text_changed?
186 224 self.content = content
187 225 ret = ret && content.changed?
188 226 end
189 227 raise ActiveRecord::Rollback unless ret
190 228 end
191 229 ret
192 230 end
193 231
194 232 protected
195 233
196 234 def validate_parent_title
197 235 errors.add(:parent_title, :invalid) if !@parent_title.blank? && parent.nil?
198 236 errors.add(:parent_title, :circular_dependency) if parent && (parent == self || parent.ancestors.include?(self))
199 errors.add(:parent_title, :not_same_project) if parent && (parent.wiki_id != wiki_id)
237 if parent_id_changed? && parent && (parent.wiki_id != wiki_id)
238 errors.add(:parent_title, :not_same_project)
239 end
200 240 end
201 241 end
202 242
203 243 class WikiDiff < Redmine::Helpers::Diff
204 244 attr_reader :content_to, :content_from
205 245
206 246 def initialize(content_to, content_from)
207 247 @content_to = content_to
208 248 @content_from = content_from
209 249 super(content_to.text, content_from.text)
210 250 end
211 251 end
212 252
213 253 class WikiAnnotate
214 254 attr_reader :lines, :content
215 255
216 256 def initialize(content)
217 257 @content = content
218 258 current = content
219 259 current_lines = current.text.split(/\r?\n/)
220 260 @lines = current_lines.collect {|t| [nil, nil, t]}
221 261 positions = []
222 262 current_lines.size.times {|i| positions << i}
223 263 while (current.previous)
224 264 d = current.previous.text.split(/\r?\n/).diff(current.text.split(/\r?\n/)).diffs.flatten
225 265 d.each_slice(3) do |s|
226 266 sign, line = s[0], s[1]
227 267 if sign == '+' && positions[line] && positions[line] != -1
228 268 if @lines[positions[line]][0].nil?
229 269 @lines[positions[line]][0] = current.version
230 270 @lines[positions[line]][1] = current.author
231 271 end
232 272 end
233 273 end
234 274 d.each_slice(3) do |s|
235 275 sign, line = s[0], s[1]
236 276 if sign == '-'
237 277 positions.insert(line, -1)
238 278 else
239 279 positions[line] = nil
240 280 end
241 281 end
242 282 positions.compact!
243 283 # Stop if every line is annotated
244 284 break unless @lines.detect { |line| line[0].nil? }
245 285 current = current.previous
246 286 end
247 287 @lines.each { |line|
248 288 line[0] ||= current.version
249 289 # if the last known version is > 1 (eg. history was cleared), we don't know the author
250 290 line[1] ||= current.author if current.version == 1
251 291 }
252 292 end
253 293 end
@@ -1,24 +1,39
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class WikiRedirect < ActiveRecord::Base
19 19 belongs_to :wiki
20 20
21 validates_presence_of :title, :redirects_to
21 validates_presence_of :wiki_id, :title, :redirects_to
22 22 validates_length_of :title, :redirects_to, :maximum => 255
23 23 attr_protected :id
24
25 before_save :set_redirects_to_wiki_id
26
27 def target_page
28 wiki = Wiki.find_by_id(redirects_to_wiki_id)
29 if wiki
30 wiki.find_page(redirects_to, :with_redirect => false)
31 end
32 end
33
34 private
35
36 def set_redirects_to_wiki_id
37 self.redirects_to_wiki_id ||= wiki_id
38 end
24 39 end
@@ -1,21 +1,26
1 1 <%= wiki_page_breadcrumb(@page) %>
2 2
3 3 <h2><%= h @original_title %></h2>
4 4
5 5 <%= error_messages_for 'page' %>
6 6
7 7 <%= labelled_form_for :wiki_page, @page,
8 8 :url => { :action => 'rename' },
9 9 :html => { :method => :post } do |f| %>
10 10 <div class="box tabular">
11 11 <p><%= f.text_field :title, :required => true, :size => 100 %></p>
12 12 <p><%= f.check_box :redirect_existing_links %></p>
13 13 <p><%= f.select :parent_id,
14 14 content_tag('option', '', :value => '') +
15 15 wiki_page_options_for_select(
16 16 @wiki.pages.includes(:parent).to_a - @page.self_and_descendants,
17 17 @page.parent),
18 18 :label => :field_parent_title %></p>
19
20 <% if @page.safe_attribute? 'wiki_id' %>
21 <p><%= f.select :wiki_id, wiki_page_wiki_options_for_select(@page), :label => :label_project %></p>
22 <% end %>
23
19 24 </div>
20 25 <%= submit_tag l(:button_rename) %>
21 26 <% end %>
@@ -1,926 +1,959
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class WikiControllerTest < ActionController::TestCase
21 21 fixtures :projects, :users, :roles, :members, :member_roles,
22 22 :enabled_modules, :wikis, :wiki_pages, :wiki_contents,
23 23 :wiki_content_versions, :attachments,
24 24 :issues, :issue_statuses
25 25
26 26 def setup
27 27 User.current = nil
28 28 end
29 29
30 30 def test_show_start_page
31 31 get :show, :project_id => 'ecookbook'
32 32 assert_response :success
33 33 assert_template 'show'
34 34 assert_select 'h1', :text => /CookBook documentation/
35 35
36 36 # child_pages macro
37 37 assert_select 'ul.pages-hierarchy>li>a[href=?]', '/projects/ecookbook/wiki/Page_with_an_inline_image',
38 38 :text => 'Page with an inline image'
39 39 end
40 40
41 41 def test_export_link
42 42 Role.anonymous.add_permission! :export_wiki_pages
43 43 get :show, :project_id => 'ecookbook'
44 44 assert_response :success
45 45 assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation.txt'
46 46 end
47 47
48 48 def test_show_page_with_name
49 49 get :show, :project_id => 1, :id => 'Another_page'
50 50 assert_response :success
51 51 assert_template 'show'
52 52 assert_select 'h1', :text => /Another page/
53 53 # Included page with an inline image
54 54 assert_select 'p', :text => /This is an inline image/
55 55 assert_select 'img[src=?][alt=?]', '/attachments/download/3/logo.gif', 'This is a logo'
56 56 end
57 57
58 58 def test_show_old_version
59 59 with_settings :default_language => 'en' do
60 60 get :show, :project_id => 'ecookbook', :id => 'CookBook_documentation', :version => '2'
61 61 end
62 62 assert_response :success
63 63 assert_template 'show'
64 64
65 65 assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/1', :text => /Previous/
66 66 assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/2/diff', :text => /diff/
67 67 assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/3', :text => /Next/
68 68 assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation', :text => /Current version/
69 69 end
70 70
71 71 def test_show_old_version_with_attachments
72 72 page = WikiPage.find(4)
73 73 assert page.attachments.any?
74 74 content = page.content
75 75 content.text = "update"
76 76 content.save!
77 77
78 78 get :show, :project_id => 'ecookbook', :id => page.title, :version => '1'
79 79 assert_kind_of WikiContent::Version, assigns(:content)
80 80 assert_response :success
81 81 assert_template 'show'
82 82 end
83 83
84 84 def test_show_old_version_without_permission_should_be_denied
85 85 Role.anonymous.remove_permission! :view_wiki_edits
86 86
87 87 get :show, :project_id => 'ecookbook', :id => 'CookBook_documentation', :version => '2'
88 88 assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fprojects%2Fecookbook%2Fwiki%2FCookBook_documentation%2F2'
89 89 end
90 90
91 91 def test_show_first_version
92 92 with_settings :default_language => 'en' do
93 93 get :show, :project_id => 'ecookbook', :id => 'CookBook_documentation', :version => '1'
94 94 end
95 95 assert_response :success
96 96 assert_template 'show'
97 97
98 98 assert_select 'a', :text => /Previous/, :count => 0
99 99 assert_select 'a', :text => /diff/, :count => 0
100 100 assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/2', :text => /Next/
101 101 assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation', :text => /Current version/
102 102 end
103 103
104 104 def test_show_redirected_page
105 105 WikiRedirect.create!(:wiki_id => 1, :title => 'Old_title', :redirects_to => 'Another_page')
106 106
107 107 get :show, :project_id => 'ecookbook', :id => 'Old_title'
108 108 assert_redirected_to '/projects/ecookbook/wiki/Another_page'
109 109 end
110 110
111 111 def test_show_with_sidebar
112 112 page = Project.find(1).wiki.pages.new(:title => 'Sidebar')
113 113 page.content = WikiContent.new(:text => 'Side bar content for test_show_with_sidebar')
114 114 page.save!
115 115
116 116 get :show, :project_id => 1, :id => 'Another_page'
117 117 assert_response :success
118 118 assert_select 'div#sidebar', :text => /Side bar content for test_show_with_sidebar/
119 119 end
120 120
121 121 def test_show_should_display_section_edit_links
122 122 @request.session[:user_id] = 2
123 123 get :show, :project_id => 1, :id => 'Page with sections'
124 124
125 125 assert_select 'a[href=?]', '/projects/ecookbook/wiki/Page_with_sections/edit?section=1', 0
126 126 assert_select 'a[href=?]', '/projects/ecookbook/wiki/Page_with_sections/edit?section=2'
127 127 assert_select 'a[href=?]', '/projects/ecookbook/wiki/Page_with_sections/edit?section=3'
128 128 end
129 129
130 130 def test_show_current_version_should_display_section_edit_links
131 131 @request.session[:user_id] = 2
132 132 get :show, :project_id => 1, :id => 'Page with sections', :version => 3
133 133
134 134 assert_select 'a[href=?]', '/projects/ecookbook/wiki/Page_with_sections/edit?section=2'
135 135 end
136 136
137 137 def test_show_old_version_should_not_display_section_edit_links
138 138 @request.session[:user_id] = 2
139 139 get :show, :project_id => 1, :id => 'Page with sections', :version => 2
140 140
141 141 assert_select 'a[href=?]', '/projects/ecookbook/wiki/Page_with_sections/edit?section=2', 0
142 142 end
143 143
144 144 def test_show_unexistent_page_without_edit_right
145 145 get :show, :project_id => 1, :id => 'Unexistent page'
146 146 assert_response 404
147 147 end
148 148
149 149 def test_show_unexistent_page_with_edit_right
150 150 @request.session[:user_id] = 2
151 151 get :show, :project_id => 1, :id => 'Unexistent page'
152 152 assert_response :success
153 153 assert_template 'edit'
154 154 end
155 155
156 156 def test_show_specific_version_of_an_unexistent_page_without_edit_right
157 157 get :show, :project_id => 1, :id => 'Unexistent page', :version => 1
158 158 assert_response 404
159 159 end
160 160
161 161 def test_show_unexistent_page_with_parent_should_preselect_parent
162 162 @request.session[:user_id] = 2
163 163 get :show, :project_id => 1, :id => 'Unexistent page', :parent => 'Another_page'
164 164 assert_response :success
165 165 assert_template 'edit'
166 166 assert_select 'select[name=?] option[value="2"][selected=selected]', 'wiki_page[parent_id]'
167 167 end
168 168
169 169 def test_show_should_not_show_history_without_permission
170 170 Role.anonymous.remove_permission! :view_wiki_edits
171 171 get :show, :project_id => 1, :id => 'Page with sections', :version => 2
172 172
173 173 assert_response 302
174 174 end
175 175
176 176 def test_show_page_without_content_should_display_the_edit_form
177 177 @request.session[:user_id] = 2
178 178 WikiPage.create!(:title => 'NoContent', :wiki => Project.find(1).wiki)
179 179
180 180 get :show, :project_id => 1, :id => 'NoContent'
181 181 assert_response :success
182 182 assert_template 'edit'
183 183 assert_select 'textarea[name=?]', 'content[text]'
184 184 end
185 185
186 186 def test_create_page
187 187 @request.session[:user_id] = 2
188 188 assert_difference 'WikiPage.count' do
189 189 assert_difference 'WikiContent.count' do
190 190 put :update, :project_id => 1,
191 191 :id => 'New page',
192 192 :content => {:comments => 'Created the page',
193 193 :text => "h1. New page\n\nThis is a new page",
194 194 :version => 0}
195 195 end
196 196 end
197 197 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'New_page'
198 198 page = Project.find(1).wiki.find_page('New page')
199 199 assert !page.new_record?
200 200 assert_not_nil page.content
201 201 assert_nil page.parent
202 202 assert_equal 'Created the page', page.content.comments
203 203 end
204 204
205 205 def test_create_page_with_attachments
206 206 @request.session[:user_id] = 2
207 207 assert_difference 'WikiPage.count' do
208 208 assert_difference 'Attachment.count' do
209 209 put :update, :project_id => 1,
210 210 :id => 'New page',
211 211 :content => {:comments => 'Created the page',
212 212 :text => "h1. New page\n\nThis is a new page",
213 213 :version => 0},
214 214 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
215 215 end
216 216 end
217 217 page = Project.find(1).wiki.find_page('New page')
218 218 assert_equal 1, page.attachments.count
219 219 assert_equal 'testfile.txt', page.attachments.first.filename
220 220 end
221 221
222 222 def test_create_page_with_parent
223 223 @request.session[:user_id] = 2
224 224 assert_difference 'WikiPage.count' do
225 225 put :update, :project_id => 1, :id => 'New page',
226 226 :content => {:text => "h1. New page\n\nThis is a new page", :version => 0},
227 227 :wiki_page => {:parent_id => 2}
228 228 end
229 229 page = Project.find(1).wiki.find_page('New page')
230 230 assert_equal WikiPage.find(2), page.parent
231 231 end
232 232
233 233 def test_edit_page
234 234 @request.session[:user_id] = 2
235 235 get :edit, :project_id => 'ecookbook', :id => 'Another_page'
236 236
237 237 assert_response :success
238 238 assert_template 'edit'
239 239
240 240 assert_select 'textarea[name=?]', 'content[text]',
241 241 :text => WikiPage.find_by_title('Another_page').content.text
242 242 end
243 243
244 244 def test_edit_section
245 245 @request.session[:user_id] = 2
246 246 get :edit, :project_id => 'ecookbook', :id => 'Page_with_sections', :section => 2
247 247
248 248 assert_response :success
249 249 assert_template 'edit'
250 250
251 251 page = WikiPage.find_by_title('Page_with_sections')
252 252 section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2)
253 253
254 254 assert_select 'textarea[name=?]', 'content[text]', :text => section
255 255 assert_select 'input[name=section][type=hidden][value="2"]'
256 256 assert_select 'input[name=section_hash][type=hidden][value=?]', hash
257 257 end
258 258
259 259 def test_edit_invalid_section_should_respond_with_404
260 260 @request.session[:user_id] = 2
261 261 get :edit, :project_id => 'ecookbook', :id => 'Page_with_sections', :section => 10
262 262
263 263 assert_response 404
264 264 end
265 265
266 266 def test_update_page
267 267 @request.session[:user_id] = 2
268 268 assert_no_difference 'WikiPage.count' do
269 269 assert_no_difference 'WikiContent.count' do
270 270 assert_difference 'WikiContent::Version.count' do
271 271 put :update, :project_id => 1,
272 272 :id => 'Another_page',
273 273 :content => {
274 274 :comments => "my comments",
275 275 :text => "edited",
276 276 :version => 1
277 277 }
278 278 end
279 279 end
280 280 end
281 281 assert_redirected_to '/projects/ecookbook/wiki/Another_page'
282 282
283 283 page = Wiki.find(1).pages.find_by_title('Another_page')
284 284 assert_equal "edited", page.content.text
285 285 assert_equal 2, page.content.version
286 286 assert_equal "my comments", page.content.comments
287 287 end
288 288
289 289 def test_update_page_with_parent
290 290 @request.session[:user_id] = 2
291 291 assert_no_difference 'WikiPage.count' do
292 292 assert_no_difference 'WikiContent.count' do
293 293 assert_difference 'WikiContent::Version.count' do
294 294 put :update, :project_id => 1,
295 295 :id => 'Another_page',
296 296 :content => {
297 297 :comments => "my comments",
298 298 :text => "edited",
299 299 :version => 1
300 300 },
301 301 :wiki_page => {:parent_id => '1'}
302 302 end
303 303 end
304 304 end
305 305 assert_redirected_to '/projects/ecookbook/wiki/Another_page'
306 306
307 307 page = Wiki.find(1).pages.find_by_title('Another_page')
308 308 assert_equal "edited", page.content.text
309 309 assert_equal 2, page.content.version
310 310 assert_equal "my comments", page.content.comments
311 311 assert_equal WikiPage.find(1), page.parent
312 312 end
313 313
314 314 def test_update_page_with_failure
315 315 @request.session[:user_id] = 2
316 316 assert_no_difference 'WikiPage.count' do
317 317 assert_no_difference 'WikiContent.count' do
318 318 assert_no_difference 'WikiContent::Version.count' do
319 319 put :update, :project_id => 1,
320 320 :id => 'Another_page',
321 321 :content => {
322 322 :comments => 'a' * 300, # failure here, comment is too long
323 323 :text => 'edited',
324 324 :version => 1
325 325 }
326 326 end
327 327 end
328 328 end
329 329 assert_response :success
330 330 assert_template 'edit'
331 331
332 332 assert_select_error /Comment is too long/
333 333 assert_select 'textarea#content_text', :text => "edited"
334 334 assert_select 'input#content_version[value="1"]'
335 335 end
336 336
337 337 def test_update_page_with_parent_change_only_should_not_create_content_version
338 338 @request.session[:user_id] = 2
339 339 assert_no_difference 'WikiPage.count' do
340 340 assert_no_difference 'WikiContent.count' do
341 341 assert_no_difference 'WikiContent::Version.count' do
342 342 put :update, :project_id => 1,
343 343 :id => 'Another_page',
344 344 :content => {
345 345 :comments => '',
346 346 :text => Wiki.find(1).find_page('Another_page').content.text,
347 347 :version => 1
348 348 },
349 349 :wiki_page => {:parent_id => '1'}
350 350 end
351 351 end
352 352 end
353 353 page = Wiki.find(1).pages.find_by_title('Another_page')
354 354 assert_equal 1, page.content.version
355 355 assert_equal WikiPage.find(1), page.parent
356 356 end
357 357
358 358 def test_update_page_with_attachments_only_should_not_create_content_version
359 359 @request.session[:user_id] = 2
360 360 assert_no_difference 'WikiPage.count' do
361 361 assert_no_difference 'WikiContent.count' do
362 362 assert_no_difference 'WikiContent::Version.count' do
363 363 assert_difference 'Attachment.count' do
364 364 put :update, :project_id => 1,
365 365 :id => 'Another_page',
366 366 :content => {
367 367 :comments => '',
368 368 :text => Wiki.find(1).find_page('Another_page').content.text,
369 369 :version => 1
370 370 },
371 371 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
372 372 end
373 373 end
374 374 end
375 375 end
376 376 page = Wiki.find(1).pages.find_by_title('Another_page')
377 377 assert_equal 1, page.content.version
378 378 end
379 379
380 380 def test_update_stale_page_should_not_raise_an_error
381 381 @request.session[:user_id] = 2
382 382 c = Wiki.find(1).find_page('Another_page').content
383 383 c.text = 'Previous text'
384 384 c.save!
385 385 assert_equal 2, c.version
386 386
387 387 assert_no_difference 'WikiPage.count' do
388 388 assert_no_difference 'WikiContent.count' do
389 389 assert_no_difference 'WikiContent::Version.count' do
390 390 put :update, :project_id => 1,
391 391 :id => 'Another_page',
392 392 :content => {
393 393 :comments => 'My comments',
394 394 :text => 'Text should not be lost',
395 395 :version => 1
396 396 }
397 397 end
398 398 end
399 399 end
400 400 assert_response :success
401 401 assert_template 'edit'
402 402 assert_select 'div.error', :text => /Data has been updated by another user/
403 403 assert_select 'textarea[name=?]', 'content[text]', :text => /Text should not be lost/
404 404 assert_select 'input[name=?][value=?]', 'content[comments]', 'My comments'
405 405
406 406 c.reload
407 407 assert_equal 'Previous text', c.text
408 408 assert_equal 2, c.version
409 409 end
410 410
411 411 def test_update_page_without_content_should_create_content
412 412 @request.session[:user_id] = 2
413 413 page = WikiPage.create!(:title => 'NoContent', :wiki => Project.find(1).wiki)
414 414
415 415 assert_no_difference 'WikiPage.count' do
416 416 assert_difference 'WikiContent.count' do
417 417 put :update, :project_id => 1, :id => 'NoContent', :content => {:text => 'Some content'}
418 418 assert_response 302
419 419 end
420 420 end
421 421 assert_equal 'Some content', page.reload.content.text
422 422 end
423 423
424 424 def test_update_section
425 425 @request.session[:user_id] = 2
426 426 page = WikiPage.find_by_title('Page_with_sections')
427 427 section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2)
428 428 text = page.content.text
429 429
430 430 assert_no_difference 'WikiPage.count' do
431 431 assert_no_difference 'WikiContent.count' do
432 432 assert_difference 'WikiContent::Version.count' do
433 433 put :update, :project_id => 1, :id => 'Page_with_sections',
434 434 :content => {
435 435 :text => "New section content",
436 436 :version => 3
437 437 },
438 438 :section => 2,
439 439 :section_hash => hash
440 440 end
441 441 end
442 442 end
443 443 assert_redirected_to '/projects/ecookbook/wiki/Page_with_sections#section-2'
444 444 assert_equal Redmine::WikiFormatting::Textile::Formatter.new(text).update_section(2, "New section content"), page.reload.content.text
445 445 end
446 446
447 447 def test_update_section_should_allow_stale_page_update
448 448 @request.session[:user_id] = 2
449 449 page = WikiPage.find_by_title('Page_with_sections')
450 450 section, hash = Redmine::WikiFormatting::Textile::Formatter.new(page.content.text).get_section(2)
451 451 text = page.content.text
452 452
453 453 assert_no_difference 'WikiPage.count' do
454 454 assert_no_difference 'WikiContent.count' do
455 455 assert_difference 'WikiContent::Version.count' do
456 456 put :update, :project_id => 1, :id => 'Page_with_sections',
457 457 :content => {
458 458 :text => "New section content",
459 459 :version => 2 # Current version is 3
460 460 },
461 461 :section => 2,
462 462 :section_hash => hash
463 463 end
464 464 end
465 465 end
466 466 assert_redirected_to '/projects/ecookbook/wiki/Page_with_sections#section-2'
467 467 page.reload
468 468 assert_equal Redmine::WikiFormatting::Textile::Formatter.new(text).update_section(2, "New section content"), page.content.text
469 469 assert_equal 4, page.content.version
470 470 end
471 471
472 472 def test_update_section_should_not_allow_stale_section_update
473 473 @request.session[:user_id] = 2
474 474
475 475 assert_no_difference 'WikiPage.count' do
476 476 assert_no_difference 'WikiContent.count' do
477 477 assert_no_difference 'WikiContent::Version.count' do
478 478 put :update, :project_id => 1, :id => 'Page_with_sections',
479 479 :content => {
480 480 :comments => 'My comments',
481 481 :text => "Text should not be lost",
482 482 :version => 3
483 483 },
484 484 :section => 2,
485 485 :section_hash => Digest::MD5.hexdigest("wrong hash")
486 486 end
487 487 end
488 488 end
489 489 assert_response :success
490 490 assert_template 'edit'
491 491 assert_select 'div.error', :text => /Data has been updated by another user/
492 492 assert_select 'textarea[name=?]', 'content[text]', :text => /Text should not be lost/
493 493 assert_select 'input[name=?][value=?]', 'content[comments]', 'My comments'
494 494 end
495 495
496 496 def test_preview
497 497 @request.session[:user_id] = 2
498 498 xhr :post, :preview, :project_id => 1, :id => 'CookBook_documentation',
499 499 :content => { :comments => '',
500 500 :text => 'this is a *previewed text*',
501 501 :version => 3 }
502 502 assert_response :success
503 503 assert_template 'common/_preview'
504 504 assert_select 'strong', :text => /previewed text/
505 505 end
506 506
507 507 def test_preview_new_page
508 508 @request.session[:user_id] = 2
509 509 xhr :post, :preview, :project_id => 1, :id => 'New page',
510 510 :content => { :text => 'h1. New page',
511 511 :comments => '',
512 512 :version => 0 }
513 513 assert_response :success
514 514 assert_template 'common/_preview'
515 515 assert_select 'h1', :text => /New page/
516 516 end
517 517
518 518 def test_history
519 519 @request.session[:user_id] = 2
520 520 get :history, :project_id => 'ecookbook', :id => 'CookBook_documentation'
521 521 assert_response :success
522 522 assert_template 'history'
523 523 assert_not_nil assigns(:versions)
524 524 assert_equal 3, assigns(:versions).size
525 525
526 526 assert_select "input[type=submit][name=commit]"
527 527 assert_select 'td' do
528 528 assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/2', :text => '2'
529 529 assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/2/annotate', :text => 'Annotate'
530 530 assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/2', :text => 'Delete'
531 531 end
532 532 end
533 533
534 534 def test_history_with_one_version
535 535 @request.session[:user_id] = 2
536 536 get :history, :project_id => 'ecookbook', :id => 'Another_page'
537 537 assert_response :success
538 538 assert_template 'history'
539 539 assert_not_nil assigns(:versions)
540 540 assert_equal 1, assigns(:versions).size
541 541 assert_select "input[type=submit][name=commit]", false
542 542 assert_select 'td' do
543 543 assert_select 'a[href=?]', '/projects/ecookbook/wiki/Another_page/1', :text => '1'
544 544 assert_select 'a[href=?]', '/projects/ecookbook/wiki/Another_page/1/annotate', :text => 'Annotate'
545 545 assert_select 'a[href=?]', '/projects/ecookbook/wiki/Another_page/1', :text => 'Delete', :count => 0
546 546 end
547 547 end
548 548
549 549 def test_diff
550 550 content = WikiPage.find(1).content
551 551 assert_difference 'WikiContent::Version.count', 2 do
552 552 content.text = "Line removed\nThis is a sample text for testing diffs"
553 553 content.save!
554 554 content.text = "This is a sample text for testing diffs\nLine added"
555 555 content.save!
556 556 end
557 557
558 558 get :diff, :project_id => 1, :id => 'CookBook_documentation', :version => content.version, :version_from => (content.version - 1)
559 559 assert_response :success
560 560 assert_template 'diff'
561 561 assert_select 'span.diff_out', :text => 'Line removed'
562 562 assert_select 'span.diff_in', :text => 'Line added'
563 563 end
564 564
565 565 def test_diff_with_invalid_version_should_respond_with_404
566 566 get :diff, :project_id => 1, :id => 'CookBook_documentation', :version => '99'
567 567 assert_response 404
568 568 end
569 569
570 570 def test_diff_with_invalid_version_from_should_respond_with_404
571 571 get :diff, :project_id => 1, :id => 'CookBook_documentation', :version => '99', :version_from => '98'
572 572 assert_response 404
573 573 end
574 574
575 575 def test_annotate
576 576 get :annotate, :project_id => 1, :id => 'CookBook_documentation', :version => 2
577 577 assert_response :success
578 578 assert_template 'annotate'
579 579
580 580 # Line 1
581 581 assert_select 'table.annotate tr:nth-child(1)' do
582 582 assert_select 'th.line-num', :text => '1'
583 583 assert_select 'td.author', :text => /John Smith/
584 584 assert_select 'td', :text => /h1\. CookBook documentation/
585 585 end
586 586
587 587 # Line 5
588 588 assert_select 'table.annotate tr:nth-child(5)' do
589 589 assert_select 'th.line-num', :text => '5'
590 590 assert_select 'td.author', :text => /Redmine Admin/
591 591 assert_select 'td', :text => /Some updated \[\[documentation\]\] here/
592 592 end
593 593 end
594 594
595 595 def test_annotate_with_invalid_version_should_respond_with_404
596 596 get :annotate, :project_id => 1, :id => 'CookBook_documentation', :version => '99'
597 597 assert_response 404
598 598 end
599 599
600 600 def test_get_rename
601 601 @request.session[:user_id] = 2
602 602 get :rename, :project_id => 1, :id => 'Another_page'
603 603 assert_response :success
604 604 assert_template 'rename'
605 605
606 606 assert_select 'select[name=?]', 'wiki_page[parent_id]' do
607 607 assert_select 'option[value=""]', :text => ''
608 608 assert_select 'option[selected=selected]', 0
609 609 end
610 610 end
611 611
612 612 def test_get_rename_child_page
613 613 @request.session[:user_id] = 2
614 614 get :rename, :project_id => 1, :id => 'Child_1'
615 615 assert_response :success
616 616 assert_template 'rename'
617 617
618 618 assert_select 'select[name=?]', 'wiki_page[parent_id]' do
619 619 assert_select 'option[value=""]', :text => ''
620 620 assert_select 'option[value="2"][selected=selected]', :text => /Another page/
621 621 end
622 622 end
623 623
624 624 def test_rename_with_redirect
625 625 @request.session[:user_id] = 2
626 626 post :rename, :project_id => 1, :id => 'Another_page',
627 627 :wiki_page => { :title => 'Another renamed page',
628 628 :redirect_existing_links => 1 }
629 629 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_renamed_page'
630 630 wiki = Project.find(1).wiki
631 631 # Check redirects
632 632 assert_not_nil wiki.find_page('Another page')
633 633 assert_nil wiki.find_page('Another page', :with_redirect => false)
634 634 end
635 635
636 636 def test_rename_without_redirect
637 637 @request.session[:user_id] = 2
638 638 post :rename, :project_id => 1, :id => 'Another_page',
639 639 :wiki_page => { :title => 'Another renamed page',
640 640 :redirect_existing_links => "0" }
641 641 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_renamed_page'
642 642 wiki = Project.find(1).wiki
643 643 # Check that there's no redirects
644 644 assert_nil wiki.find_page('Another page')
645 645 end
646 646
647 647 def test_rename_with_parent_assignment
648 648 @request.session[:user_id] = 2
649 649 post :rename, :project_id => 1, :id => 'Another_page',
650 650 :wiki_page => { :title => 'Another page', :redirect_existing_links => "0", :parent_id => '4' }
651 651 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_page'
652 652 assert_equal WikiPage.find(4), WikiPage.find_by_title('Another_page').parent
653 653 end
654 654
655 655 def test_rename_with_parent_unassignment
656 656 @request.session[:user_id] = 2
657 657 post :rename, :project_id => 1, :id => 'Child_1',
658 658 :wiki_page => { :title => 'Child 1', :redirect_existing_links => "0", :parent_id => '' }
659 659 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Child_1'
660 660 assert_nil WikiPage.find_by_title('Child_1').parent
661 661 end
662 662
663 def test_get_rename_should_show_target_projects_list
664 @request.session[:user_id] = 2
665 project = Project.find(5)
666 project.enable_module! :wiki
667
668 get :rename, :project_id => 1, :id => 'Another_page'
669 assert_response :success
670 assert_template 'rename'
671
672 assert_select 'select[name=?]', 'wiki_page[wiki_id]' do
673 assert_select 'option', 2
674 assert_select 'option[value=?][selected=selected]', '1', :text => /eCookbook/
675 assert_select 'option[value=?]', project.wiki.id.to_s, :text => /#{project.name}/
676 end
677 end
678
679 def test_rename_with_move
680 @request.session[:user_id] = 2
681 project = Project.find(5)
682 project.enable_module! :wiki
683
684 post :rename, :project_id => 1, :id => 'Another_page',
685 :wiki_page => {
686 :wiki_id => project.wiki.id.to_s,
687 :title => 'Another renamed page',
688 :redirect_existing_links => 1
689 }
690 assert_redirected_to '/projects/private-child/wiki/Another_renamed_page'
691
692 page = WikiPage.find(2)
693 assert_equal project.wiki.id, page.wiki_id
694 end
695
663 696 def test_destroy_a_page_without_children_should_not_ask_confirmation
664 697 @request.session[:user_id] = 2
665 698 delete :destroy, :project_id => 1, :id => 'Child_2'
666 699 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
667 700 end
668 701
669 702 def test_destroy_parent_should_ask_confirmation
670 703 @request.session[:user_id] = 2
671 704 assert_no_difference('WikiPage.count') do
672 705 delete :destroy, :project_id => 1, :id => 'Another_page'
673 706 end
674 707 assert_response :success
675 708 assert_template 'destroy'
676 709 assert_select 'form' do
677 710 assert_select 'input[name=todo][value=nullify]'
678 711 assert_select 'input[name=todo][value=destroy]'
679 712 assert_select 'input[name=todo][value=reassign]'
680 713 end
681 714 end
682 715
683 716 def test_destroy_parent_with_nullify_should_delete_parent_only
684 717 @request.session[:user_id] = 2
685 718 assert_difference('WikiPage.count', -1) do
686 719 delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'nullify'
687 720 end
688 721 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
689 722 assert_nil WikiPage.find_by_id(2)
690 723 end
691 724
692 725 def test_destroy_parent_with_cascade_should_delete_descendants
693 726 @request.session[:user_id] = 2
694 727 assert_difference('WikiPage.count', -4) do
695 728 delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'destroy'
696 729 end
697 730 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
698 731 assert_nil WikiPage.find_by_id(2)
699 732 assert_nil WikiPage.find_by_id(5)
700 733 end
701 734
702 735 def test_destroy_parent_with_reassign
703 736 @request.session[:user_id] = 2
704 737 assert_difference('WikiPage.count', -1) do
705 738 delete :destroy, :project_id => 1, :id => 'Another_page', :todo => 'reassign', :reassign_to_id => 1
706 739 end
707 740 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
708 741 assert_nil WikiPage.find_by_id(2)
709 742 assert_equal WikiPage.find(1), WikiPage.find_by_id(5).parent
710 743 end
711 744
712 745 def test_destroy_version
713 746 @request.session[:user_id] = 2
714 747 assert_difference 'WikiContent::Version.count', -1 do
715 748 assert_no_difference 'WikiContent.count' do
716 749 assert_no_difference 'WikiPage.count' do
717 750 delete :destroy_version, :project_id => 'ecookbook', :id => 'CookBook_documentation', :version => 2
718 751 assert_redirected_to '/projects/ecookbook/wiki/CookBook_documentation/history'
719 752 end
720 753 end
721 754 end
722 755 end
723 756
724 757 def test_index
725 758 get :index, :project_id => 'ecookbook'
726 759 assert_response :success
727 760 assert_template 'index'
728 761 pages = assigns(:pages)
729 762 assert_not_nil pages
730 763 assert_equal Project.find(1).wiki.pages.size, pages.size
731 764 assert_equal pages.first.content.updated_on, pages.first.updated_on
732 765
733 766 assert_select 'ul.pages-hierarchy' do
734 767 assert_select 'li' do
735 768 assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation', :text => 'CookBook documentation'
736 769 assert_select 'ul li a[href=?]', '/projects/ecookbook/wiki/Page_with_an_inline_image', :text => 'Page with an inline image'
737 770 end
738 771 assert_select 'li a[href=?]', '/projects/ecookbook/wiki/Another_page', :text => 'Another page'
739 772 end
740 773 end
741 774
742 775 def test_index_should_include_atom_link
743 776 get :index, :project_id => 'ecookbook'
744 777 assert_select 'a[href=?]', '/projects/ecookbook/activity.atom?show_wiki_edits=1'
745 778 end
746 779
747 780 def test_export_to_html
748 781 @request.session[:user_id] = 2
749 782 get :export, :project_id => 'ecookbook'
750 783
751 784 assert_response :success
752 785 assert_not_nil assigns(:pages)
753 786 assert assigns(:pages).any?
754 787 assert_equal "text/html", @response.content_type
755 788
756 789 assert_select "a[name=?]", "CookBook_documentation"
757 790 assert_select "a[name=?]", "Another_page"
758 791 assert_select "a[name=?]", "Page_with_an_inline_image"
759 792 end
760 793
761 794 def test_export_to_pdf
762 795 @request.session[:user_id] = 2
763 796 get :export, :project_id => 'ecookbook', :format => 'pdf'
764 797
765 798 assert_response :success
766 799 assert_not_nil assigns(:pages)
767 800 assert assigns(:pages).any?
768 801 assert_equal 'application/pdf', @response.content_type
769 802 assert_equal 'attachment; filename="ecookbook.pdf"', @response.headers['Content-Disposition']
770 803 assert @response.body.starts_with?('%PDF')
771 804 end
772 805
773 806 def test_export_without_permission_should_be_denied
774 807 @request.session[:user_id] = 2
775 808 Role.find_by_name('Manager').remove_permission! :export_wiki_pages
776 809 get :export, :project_id => 'ecookbook'
777 810
778 811 assert_response 403
779 812 end
780 813
781 814 def test_date_index
782 815 get :date_index, :project_id => 'ecookbook'
783 816
784 817 assert_response :success
785 818 assert_template 'date_index'
786 819 assert_not_nil assigns(:pages)
787 820 assert_not_nil assigns(:pages_by_date)
788 821
789 822 assert_select 'a[href=?]', '/projects/ecookbook/activity.atom?show_wiki_edits=1'
790 823 end
791 824
792 825 def test_not_found
793 826 get :show, :project_id => 999
794 827 assert_response 404
795 828 end
796 829
797 830 def test_protect_page
798 831 page = WikiPage.find_by_wiki_id_and_title(1, 'Another_page')
799 832 assert !page.protected?
800 833 @request.session[:user_id] = 2
801 834 post :protect, :project_id => 1, :id => page.title, :protected => '1'
802 835 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_page'
803 836 assert page.reload.protected?
804 837 end
805 838
806 839 def test_unprotect_page
807 840 page = WikiPage.find_by_wiki_id_and_title(1, 'CookBook_documentation')
808 841 assert page.protected?
809 842 @request.session[:user_id] = 2
810 843 post :protect, :project_id => 1, :id => page.title, :protected => '0'
811 844 assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'CookBook_documentation'
812 845 assert !page.reload.protected?
813 846 end
814 847
815 848 def test_show_page_with_edit_link
816 849 @request.session[:user_id] = 2
817 850 get :show, :project_id => 1
818 851 assert_response :success
819 852 assert_template 'show'
820 853 assert_select 'a[href=?]', '/projects/1/wiki/CookBook_documentation/edit'
821 854 end
822 855
823 856 def test_show_page_without_edit_link
824 857 @request.session[:user_id] = 4
825 858 get :show, :project_id => 1
826 859 assert_response :success
827 860 assert_template 'show'
828 861 assert_select 'a[href=?]', '/projects/1/wiki/CookBook_documentation/edit', 0
829 862 end
830 863
831 864 def test_show_pdf
832 865 @request.session[:user_id] = 2
833 866 get :show, :project_id => 1, :format => 'pdf'
834 867 assert_response :success
835 868 assert_not_nil assigns(:page)
836 869 assert_equal 'application/pdf', @response.content_type
837 870 assert_equal 'attachment; filename="CookBook_documentation.pdf"',
838 871 @response.headers['Content-Disposition']
839 872 end
840 873
841 874 def test_show_html
842 875 @request.session[:user_id] = 2
843 876 get :show, :project_id => 1, :format => 'html'
844 877 assert_response :success
845 878 assert_not_nil assigns(:page)
846 879 assert_equal 'text/html', @response.content_type
847 880 assert_equal 'attachment; filename="CookBook_documentation.html"',
848 881 @response.headers['Content-Disposition']
849 882 assert_select 'h1', :text => /CookBook documentation/
850 883 end
851 884
852 885 def test_show_versioned_html
853 886 @request.session[:user_id] = 2
854 887 get :show, :project_id => 1, :format => 'html', :version => 2
855 888 assert_response :success
856 889 assert_not_nil assigns(:content)
857 890 assert_equal 2, assigns(:content).version
858 891 assert_equal 'text/html', @response.content_type
859 892 assert_equal 'attachment; filename="CookBook_documentation.html"',
860 893 @response.headers['Content-Disposition']
861 894 assert_select 'h1', :text => /CookBook documentation/
862 895 end
863 896
864 897 def test_show_txt
865 898 @request.session[:user_id] = 2
866 899 get :show, :project_id => 1, :format => 'txt'
867 900 assert_response :success
868 901 assert_not_nil assigns(:page)
869 902 assert_equal 'text/plain', @response.content_type
870 903 assert_equal 'attachment; filename="CookBook_documentation.txt"',
871 904 @response.headers['Content-Disposition']
872 905 assert_include 'h1. CookBook documentation', @response.body
873 906 end
874 907
875 908 def test_show_versioned_txt
876 909 @request.session[:user_id] = 2
877 910 get :show, :project_id => 1, :format => 'txt', :version => 2
878 911 assert_response :success
879 912 assert_not_nil assigns(:content)
880 913 assert_equal 2, assigns(:content).version
881 914 assert_equal 'text/plain', @response.content_type
882 915 assert_equal 'attachment; filename="CookBook_documentation.txt"',
883 916 @response.headers['Content-Disposition']
884 917 assert_include 'h1. CookBook documentation', @response.body
885 918 end
886 919
887 920 def test_edit_unprotected_page
888 921 # Non members can edit unprotected wiki pages
889 922 @request.session[:user_id] = 4
890 923 get :edit, :project_id => 1, :id => 'Another_page'
891 924 assert_response :success
892 925 assert_template 'edit'
893 926 end
894 927
895 928 def test_edit_protected_page_by_nonmember
896 929 # Non members can't edit protected wiki pages
897 930 @request.session[:user_id] = 4
898 931 get :edit, :project_id => 1, :id => 'CookBook_documentation'
899 932 assert_response 403
900 933 end
901 934
902 935 def test_edit_protected_page_by_member
903 936 @request.session[:user_id] = 2
904 937 get :edit, :project_id => 1, :id => 'CookBook_documentation'
905 938 assert_response :success
906 939 assert_template 'edit'
907 940 end
908 941
909 942 def test_history_of_non_existing_page_should_return_404
910 943 get :history, :project_id => 1, :id => 'Unknown_page'
911 944 assert_response 404
912 945 end
913 946
914 947 def test_add_attachment
915 948 @request.session[:user_id] = 2
916 949 assert_difference 'Attachment.count' do
917 950 post :add_attachment, :project_id => 1, :id => 'CookBook_documentation',
918 951 :attachments => {
919 952 '1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'),
920 953 'description' => 'test file'}
921 954 }
922 955 end
923 956 attachment = Attachment.order('id DESC').first
924 957 assert_equal Wiki.find(1).find_page('CookBook_documentation'), attachment.container
925 958 end
926 959 end
@@ -1,163 +1,183
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class WikiPageTest < ActiveSupport::TestCase
21 21 fixtures :projects, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions
22 22
23 23 def setup
24 24 @wiki = Wiki.find(1)
25 25 @page = @wiki.pages.first
26 26 end
27 27
28 28 def test_create
29 29 page = WikiPage.new(:wiki => @wiki)
30 30 assert !page.save
31 31 assert_equal 1, page.errors.count
32 32
33 33 page.title = "Page"
34 34 assert page.save
35 35 page.reload
36 36 assert !page.protected?
37 37
38 38 @wiki.reload
39 39 assert @wiki.pages.include?(page)
40 40 end
41 41
42 42 def test_sidebar_should_be_protected_by_default
43 43 page = @wiki.find_or_new_page('sidebar')
44 44 assert page.new_record?
45 45 assert page.protected?
46 46 end
47 47
48 48 def test_find_or_new_page
49 49 page = @wiki.find_or_new_page("CookBook documentation")
50 50 assert_kind_of WikiPage, page
51 51 assert !page.new_record?
52 52
53 53 page = @wiki.find_or_new_page("Non existing page")
54 54 assert_kind_of WikiPage, page
55 55 assert page.new_record?
56 56 end
57 57
58 58 def test_parent_title
59 59 page = WikiPage.find_by_title('Another_page')
60 60 assert_nil page.parent_title
61 61
62 62 page = WikiPage.find_by_title('Page_with_an_inline_image')
63 63 assert_equal 'CookBook documentation', page.parent_title
64 64 end
65 65
66 66 def test_assign_parent
67 67 page = WikiPage.find_by_title('Another_page')
68 68 page.parent_title = 'CookBook documentation'
69 69 assert page.save
70 70 page.reload
71 71 assert_equal WikiPage.find_by_title('CookBook_documentation'), page.parent
72 72 end
73 73
74 74 def test_unassign_parent
75 75 page = WikiPage.find_by_title('Page_with_an_inline_image')
76 76 page.parent_title = ''
77 77 assert page.save
78 78 page.reload
79 79 assert_nil page.parent
80 80 end
81 81
82 82 def test_parent_validation
83 83 page = WikiPage.find_by_title('CookBook_documentation')
84 84
85 85 # A page that doesn't exist
86 86 page.parent_title = 'Unknown title'
87 87 assert !page.save
88 88 assert_include I18n.translate('activerecord.errors.messages.invalid'),
89 89 page.errors[:parent_title]
90 90 # A child page
91 91 page.parent_title = 'Page_with_an_inline_image'
92 92 assert !page.save
93 93 assert_include I18n.translate('activerecord.errors.messages.circular_dependency'),
94 94 page.errors[:parent_title]
95 95 # The page itself
96 96 page.parent_title = 'CookBook_documentation'
97 97 assert !page.save
98 98 assert_include I18n.translate('activerecord.errors.messages.circular_dependency'),
99 99 page.errors[:parent_title]
100 100 page.parent_title = 'Another_page'
101 101 assert page.save
102 102 end
103 103
104 def test_move_child_should_clear_parent
105 parent = WikiPage.create!(:wiki_id => 1, :title => 'Parent')
106 child = WikiPage.create!(:wiki_id => 1, :title => 'Child', :parent => parent)
107
108 child.wiki_id = 2
109 child.save!
110 assert_equal nil, child.reload.parent_id
111 end
112
113 def test_move_parent_should_move_child_page
114 parent = WikiPage.create!(:wiki_id => 1, :title => 'Parent')
115 child = WikiPage.create!(:wiki_id => 1, :title => 'Child', :parent => parent)
116 parent.reload
117
118 parent.wiki_id = 2
119 parent.save!
120 assert_equal 2, child.reload.wiki_id
121 assert_equal parent, child.parent
122 end
123
104 124 def test_destroy
105 125 page = WikiPage.find(1)
106 126 page.destroy
107 127 assert_nil WikiPage.find_by_id(1)
108 128 # make sure that page content and its history are deleted
109 129 assert_equal 0, WikiContent.where(:page_id => 1).count
110 130 assert_equal 0, WikiContent.versioned_class.where(:page_id => 1).count
111 131 end
112 132
113 133 def test_destroy_should_not_nullify_children
114 134 page = WikiPage.find(2)
115 135 child_ids = page.child_ids
116 136 assert child_ids.any?
117 137 page.destroy
118 138 assert_nil WikiPage.find_by_id(2)
119 139
120 140 children = WikiPage.where(:id => child_ids)
121 141 assert_equal child_ids.size, children.count
122 142 children.each do |child|
123 143 assert_nil child.parent_id
124 144 end
125 145 end
126 146
127 147 def test_updated_on_eager_load
128 148 page = WikiPage.with_updated_on.order('id').first
129 149 assert page.is_a?(WikiPage)
130 150 assert_not_nil page.read_attribute(:updated_on)
131 151 assert_equal Time.gm(2007, 3, 6, 23, 10, 51), page.content.updated_on
132 152 assert_equal page.content.updated_on, page.updated_on
133 153 assert_not_nil page.read_attribute(:version)
134 154 end
135 155
136 156 def test_descendants
137 157 page = WikiPage.create!(:wiki => @wiki, :title => 'Parent')
138 158 child1 = WikiPage.create!(:wiki => @wiki, :title => 'Child1', :parent => page)
139 159 child11 = WikiPage.create!(:wiki => @wiki, :title => 'Child11', :parent => child1)
140 160 child111 = WikiPage.create!(:wiki => @wiki, :title => 'Child111', :parent => child11)
141 161 child2 = WikiPage.create!(:wiki => @wiki, :title => 'Child2', :parent => page)
142 162
143 163 assert_equal %w(Child1 Child11 Child111 Child2), page.descendants.map(&:title).sort
144 164 assert_equal %w(Child1 Child11 Child111 Child2), page.descendants(nil).map(&:title).sort
145 165 assert_equal %w(Child1 Child11 Child2), page.descendants(2).map(&:title).sort
146 166 assert_equal %w(Child1 Child2), page.descendants(1).map(&:title).sort
147 167
148 168 assert_equal %w(Child1 Child11 Child111 Child2 Parent), page.self_and_descendants.map(&:title).sort
149 169 assert_equal %w(Child1 Child11 Child111 Child2 Parent), page.self_and_descendants(nil).map(&:title).sort
150 170 assert_equal %w(Child1 Child11 Child2 Parent), page.self_and_descendants(2).map(&:title).sort
151 171 assert_equal %w(Child1 Child2 Parent), page.self_and_descendants(1).map(&:title).sort
152 172 end
153 173
154 174 def test_diff_for_page_with_deleted_version_should_pick_the_previous_available_version
155 175 WikiContent::Version.find_by_page_id_and_version(1, 2).destroy
156 176
157 177 page = WikiPage.find(1)
158 178 diff = page.diff(3)
159 179 assert_not_nil diff
160 180 assert_equal 3, diff.content_to.version
161 181 assert_equal 1, diff.content_from.version
162 182 end
163 183 end
@@ -1,74 +1,97
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class WikiRedirectTest < ActiveSupport::TestCase
21 21 fixtures :projects, :wikis, :wiki_pages
22 22
23 23 def setup
24 24 @wiki = Wiki.find(1)
25 25 @original = WikiPage.create(:wiki => @wiki, :title => 'Original title')
26 26 end
27 27
28 def test_create_redirect
28 def test_create_redirect_on_rename
29 29 @original.title = 'New title'
30 assert @original.save
31 @original.reload
30 @original.save!
32 31
33 assert_equal 'New_title', @original.title
34 assert @wiki.redirects.find_by_title('Original_title')
35 assert @wiki.find_page('Original title')
36 assert @wiki.find_page('ORIGINAL title')
32 redirect = @wiki.redirects.find_by_title('Original_title')
33 assert_not_nil redirect
34 assert_equal 1, redirect.redirects_to_wiki_id
35 assert_equal 'New_title', redirect.redirects_to
36 assert_equal @original, redirect.target_page
37 end
38
39 def test_create_redirect_on_move
40 @original.wiki_id = 2
41 @original.save!
42
43 redirect = @wiki.redirects.find_by_title('Original_title')
44 assert_not_nil redirect
45 assert_equal 2, redirect.redirects_to_wiki_id
46 assert_equal 'Original_title', redirect.redirects_to
47 assert_equal @original, redirect.target_page
48 end
49
50 def test_create_redirect_on_rename_and_move
51 @original.title = 'New title'
52 @original.wiki_id = 2
53 @original.save!
54
55 redirect = @wiki.redirects.find_by_title('Original_title')
56 assert_not_nil redirect
57 assert_equal 2, redirect.redirects_to_wiki_id
58 assert_equal 'New_title', redirect.redirects_to
59 assert_equal @original, redirect.target_page
37 60 end
38 61
39 62 def test_update_redirect
40 63 # create a redirect that point to this page
41 64 assert WikiRedirect.create(:wiki => @wiki, :title => 'An_old_page', :redirects_to => 'Original_title')
42 65
43 66 @original.title = 'New title'
44 67 @original.save
45 68 # make sure the old page now points to the new page
46 69 assert_equal 'New_title', @wiki.find_page('An old page').title
47 70 end
48 71
49 72 def test_reverse_rename
50 73 # create a redirect that point to this page
51 74 assert WikiRedirect.create(:wiki => @wiki, :title => 'An_old_page', :redirects_to => 'Original_title')
52 75
53 76 @original.title = 'An old page'
54 77 @original.save
55 78 assert !@wiki.redirects.find_by_title_and_redirects_to('An_old_page', 'An_old_page')
56 79 assert @wiki.redirects.find_by_title_and_redirects_to('Original_title', 'An_old_page')
57 80 end
58 81
59 82 def test_rename_to_already_redirected
60 83 assert WikiRedirect.create(:wiki => @wiki, :title => 'An_old_page', :redirects_to => 'Other_page')
61 84
62 85 @original.title = 'An old page'
63 86 @original.save
64 87 # this redirect have to be removed since 'An old page' page now exists
65 88 assert !@wiki.redirects.find_by_title_and_redirects_to('An_old_page', 'Other_page')
66 89 end
67 90
68 91 def test_redirects_removed_when_deleting_page
69 92 assert WikiRedirect.create(:wiki => @wiki, :title => 'An_old_page', :redirects_to => 'Original_title')
70 93
71 94 @original.destroy
72 95 assert_nil @wiki.redirects.first
73 96 end
74 97 end
@@ -1,100 +1,114
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2014 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 require File.expand_path('../../test_helper', __FILE__)
21 21
22 22 class WikiTest < ActiveSupport::TestCase
23 23 fixtures :projects, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions
24 24
25 25 def test_create
26 26 wiki = Wiki.new(:project => Project.find(3))
27 27 assert !wiki.save
28 28 assert_equal 1, wiki.errors.count
29 29
30 30 wiki.start_page = "Start page"
31 31 assert wiki.save
32 32 end
33 33
34 34 def test_update
35 35 @wiki = Wiki.find(1)
36 36 @wiki.start_page = "Another start page"
37 37 assert @wiki.save
38 38 @wiki.reload
39 39 assert_equal "Another start page", @wiki.start_page
40 40 end
41 41
42 42 def test_find_page_should_not_be_case_sensitive
43 43 wiki = Wiki.find(1)
44 44 page = WikiPage.find(2)
45 45
46 46 assert_equal page, wiki.find_page('Another_page')
47 47 assert_equal page, wiki.find_page('Another page')
48 48 assert_equal page, wiki.find_page('ANOTHER page')
49 49 end
50 50
51 51 def test_find_page_with_cyrillic_characters
52 52 wiki = Wiki.find(1)
53 53 page = WikiPage.find(10)
54 54 assert_equal page, wiki.find_page('Π­Ρ‚ΠΈΠΊΠ°_ΠΌΠ΅Π½Π΅Π΄ΠΆΠΌΠ΅Π½Ρ‚Π°')
55 55 end
56 56
57 57 def test_find_page_with_backslashes
58 58 wiki = Wiki.find(1)
59 59 page = WikiPage.create!(:wiki => wiki, :title => '2009\\02\\09')
60 60 assert_equal page, wiki.find_page('2009\\02\\09')
61 61 end
62 62
63 63 def test_find_page_without_redirect
64 64 wiki = Wiki.find(1)
65 65 page = wiki.find_page('Another_page')
66 66 assert_not_nil page
67 67 assert_equal 'Another_page', page.title
68 68 assert_equal false, wiki.page_found_with_redirect?
69 69 end
70 70
71 71 def test_find_page_with_redirect
72 72 wiki = Wiki.find(1)
73 73 WikiRedirect.create!(:wiki => wiki, :title => 'Old_title', :redirects_to => 'Another_page')
74 74 page = wiki.find_page('Old_title')
75 75 assert_not_nil page
76 76 assert_equal 'Another_page', page.title
77 77 assert_equal true, wiki.page_found_with_redirect?
78 78 end
79 79
80 80 def test_titleize
81 81 ja_test = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88".force_encoding('UTF-8')
82 82 assert_equal 'Page_title_with_CAPITALES', Wiki.titleize('page title with CAPITALES')
83 83 assert_equal ja_test, Wiki.titleize(ja_test)
84 84 end
85 85
86 86 def test_sidebar_should_return_nil_if_undefined
87 87 @wiki = Wiki.find(1)
88 88 assert_nil @wiki.sidebar
89 89 end
90 90
91 91 def test_sidebar_should_return_a_wiki_page_if_defined
92 92 @wiki = Wiki.find(1)
93 93 page = @wiki.pages.new(:title => 'Sidebar')
94 94 page.content = WikiContent.new(:text => 'Side bar content for test_show_with_sidebar')
95 95 page.save!
96 96
97 97 assert_kind_of WikiPage, @wiki.sidebar
98 98 assert_equal 'Sidebar', @wiki.sidebar.title
99 99 end
100
101 def test_destroy_should_remove_redirects_from_the_wiki
102 WikiRedirect.create!(:wiki_id => 1, :title => 'Foo', :redirects_to_wiki_id => 2, :redirects_to => 'Bar')
103
104 Wiki.find(1).destroy
105 assert_equal 0, WikiRedirect.where(:wiki_id => 1).count
106 end
107
108 def test_destroy_should_remove_redirects_to_the_wiki
109 WikiRedirect.create!(:wiki_id => 2, :title => 'Foo', :redirects_to_wiki_id => 1, :redirects_to => 'Bar')
110
111 Wiki.find(1).destroy
112 assert_equal 0, WikiRedirect.where(:redirects_to_wiki_id => 1).count
113 end
100 114 end
General Comments 0
You need to be logged in to leave comments. Login now