##// END OF EJS Templates
Code cleanup....
Jean-Philippe Lang -
r10615:f36b3fff6047
parent child
Show More
@@ -1,363 +1,354
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'diff'
19 19
20 20 # The WikiController follows the Rails REST controller pattern but with
21 21 # a few differences
22 22 #
23 23 # * index - shows a list of WikiPages grouped by page or date
24 24 # * new - not used
25 25 # * create - not used
26 26 # * show - will also show the form for creating a new wiki page
27 27 # * edit - used to edit an existing or new page
28 28 # * update - used to save a wiki page update to the database, including new pages
29 29 # * destroy - normal
30 30 #
31 31 # Other member and collection methods are also used
32 32 #
33 33 # TODO: still being worked on
34 34 class WikiController < ApplicationController
35 35 default_search_scope :wiki_pages
36 36 before_filter :find_wiki, :authorize
37 37 before_filter :find_existing_or_new_page, :only => [:show, :edit, :update]
38 38 before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy, :destroy_version]
39 39 accept_api_auth :index, :show, :update, :destroy
40 40
41 41 helper :attachments
42 42 include AttachmentsHelper
43 43 helper :watchers
44 44 include Redmine::Export::PDF
45 45
46 46 # List of pages, sorted alphabetically and by parent (hierarchy)
47 47 def index
48 48 load_pages_for_index
49 49
50 50 respond_to do |format|
51 51 format.html {
52 52 @pages_by_parent_id = @pages.group_by(&:parent_id)
53 53 }
54 54 format.api
55 55 end
56 56 end
57 57
58 58 # List of page, by last update
59 59 def date_index
60 60 load_pages_for_index
61 61 @pages_by_date = @pages.group_by {|p| p.updated_on.to_date}
62 62 end
63 63
64 64 # display a page (in editing mode if it doesn't exist)
65 65 def show
66 66 if @page.new_record?
67 67 if User.current.allowed_to?(:edit_wiki_pages, @project) && editable? && !api_request?
68 68 edit
69 69 render :action => 'edit'
70 70 else
71 71 render_404
72 72 end
73 73 return
74 74 end
75 75 if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project)
76 76 deny_access
77 77 return
78 78 end
79 79 @content = @page.content_for_version(params[:version])
80 80 if User.current.allowed_to?(:export_wiki_pages, @project)
81 81 if params[:format] == 'pdf'
82 82 send_data(wiki_page_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf")
83 83 return
84 84 elsif params[:format] == 'html'
85 85 export = render_to_string :action => 'export', :layout => false
86 86 send_data(export, :type => 'text/html', :filename => "#{@page.title}.html")
87 87 return
88 88 elsif params[:format] == 'txt'
89 89 send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt")
90 90 return
91 91 end
92 92 end
93 93 @editable = editable?
94 94 @sections_editable = @editable && User.current.allowed_to?(:edit_wiki_pages, @page.project) &&
95 95 @content.current_version? &&
96 96 Redmine::WikiFormatting.supports_section_edit?
97 97
98 98 respond_to do |format|
99 99 format.html
100 100 format.api
101 101 end
102 102 end
103 103
104 104 # edit an existing page or a new one
105 105 def edit
106 106 return render_403 unless editable?
107 107 if @page.new_record?
108 108 @page.content = WikiContent.new(:page => @page)
109 109 if params[:parent].present?
110 110 @page.parent = @page.wiki.find_page(params[:parent].to_s)
111 111 end
112 112 end
113 113
114 114 @content = @page.content_for_version(params[:version])
115 115 @content.text = initial_page_content(@page) if @content.text.blank?
116 116 # don't keep previous comment
117 117 @content.comments = nil
118 118
119 119 # To prevent StaleObjectError exception when reverting to a previous version
120 120 @content.version = @page.content.version
121 121
122 122 @text = @content.text
123 123 if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
124 124 @section = params[:section].to_i
125 125 @text, @section_hash = Redmine::WikiFormatting.formatter.new(@text).get_section(@section)
126 126 render_404 if @text.blank?
127 127 end
128 128 end
129 129
130 130 # Creates a new page or updates an existing one
131 131 def update
132 132 return render_403 unless editable?
133 133 was_new_page = @page.new_record?
134 134 @page.content = WikiContent.new(:page => @page) if @page.new_record?
135 135 @page.safe_attributes = params[:wiki_page]
136 136
137 137 @content = @page.content
138 138 content_params = params[:content]
139 139 if content_params.nil? && params[:wiki_page].is_a?(Hash)
140 140 content_params = params[:wiki_page].slice(:text, :comments, :version)
141 141 end
142 142 content_params ||= {}
143 143
144 if !@page.new_record? && content_params.present? && @content.text == content_params[:text]
145 attachments = Attachment.attach_files(@page, params[:attachments])
146 render_attachment_warning_if_needed(@page)
147 # don't save content if text wasn't changed
148 @page.save
149 redirect_to :action => 'show', :project_id => @project, :id => @page.title
150 return
151 end
152
153 144 @content.comments = content_params[:comments]
154 145 @text = content_params[:text]
155 146 if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
156 147 @section = params[:section].to_i
157 148 @section_hash = params[:section_hash]
158 149 @content.text = Redmine::WikiFormatting.formatter.new(@content.text).update_section(params[:section].to_i, @text, @section_hash)
159 150 else
160 151 @content.version = content_params[:version] if content_params[:version]
161 152 @content.text = @text
162 153 end
163 154 @content.author = User.current
164 @page.content = @content
165 if @page.save
155
156 if @page.save_with_content
166 157 attachments = Attachment.attach_files(@page, params[:attachments])
167 158 render_attachment_warning_if_needed(@page)
168 159 call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page})
169 160
170 161 respond_to do |format|
171 162 format.html { redirect_to :action => 'show', :project_id => @project, :id => @page.title }
172 163 format.api {
173 164 if was_new_page
174 165 render :action => 'show', :status => :created, :location => url_for(:controller => 'wiki', :action => 'show', :project_id => @project, :id => @page.title)
175 166 else
176 167 render_api_ok
177 168 end
178 169 }
179 170 end
180 171 else
181 172 respond_to do |format|
182 173 format.html { render :action => 'edit' }
183 174 format.api { render_validation_errors(@content) }
184 175 end
185 176 end
186 177
187 178 rescue ActiveRecord::StaleObjectError, Redmine::WikiFormatting::StaleSectionError
188 179 # Optimistic locking exception
189 180 respond_to do |format|
190 181 format.html {
191 182 flash.now[:error] = l(:notice_locking_conflict)
192 183 render :action => 'edit'
193 184 }
194 185 format.api { render_api_head :conflict }
195 186 end
196 187 rescue ActiveRecord::RecordNotSaved
197 188 respond_to do |format|
198 189 format.html { render :action => 'edit' }
199 190 format.api { render_validation_errors(@content) }
200 191 end
201 192 end
202 193
203 194 # rename a page
204 195 def rename
205 196 return render_403 unless editable?
206 197 @page.redirect_existing_links = true
207 198 # used to display the *original* title if some AR validation errors occur
208 199 @original_title = @page.pretty_title
209 200 if request.post? && @page.update_attributes(params[:wiki_page])
210 201 flash[:notice] = l(:notice_successful_update)
211 202 redirect_to :action => 'show', :project_id => @project, :id => @page.title
212 203 end
213 204 end
214 205
215 206 def protect
216 207 @page.update_attribute :protected, params[:protected]
217 208 redirect_to :action => 'show', :project_id => @project, :id => @page.title
218 209 end
219 210
220 211 # show page history
221 212 def history
222 213 @version_count = @page.content.versions.count
223 214 @version_pages = Paginator.new self, @version_count, per_page_option, params['page']
224 215 # don't load text
225 216 @versions = @page.content.versions.find :all,
226 217 :select => "id, author_id, comments, updated_on, version",
227 218 :order => 'version DESC',
228 219 :limit => @version_pages.items_per_page + 1,
229 220 :offset => @version_pages.current.offset
230 221
231 222 render :layout => false if request.xhr?
232 223 end
233 224
234 225 def diff
235 226 @diff = @page.diff(params[:version], params[:version_from])
236 227 render_404 unless @diff
237 228 end
238 229
239 230 def annotate
240 231 @annotate = @page.annotate(params[:version])
241 232 render_404 unless @annotate
242 233 end
243 234
244 235 # Removes a wiki page and its history
245 236 # Children can be either set as root pages, removed or reassigned to another parent page
246 237 def destroy
247 238 return render_403 unless editable?
248 239
249 240 @descendants_count = @page.descendants.size
250 241 if @descendants_count > 0
251 242 case params[:todo]
252 243 when 'nullify'
253 244 # Nothing to do
254 245 when 'destroy'
255 246 # Removes all its descendants
256 247 @page.descendants.each(&:destroy)
257 248 when 'reassign'
258 249 # Reassign children to another parent page
259 250 reassign_to = @wiki.pages.find_by_id(params[:reassign_to_id].to_i)
260 251 return unless reassign_to
261 252 @page.children.each do |child|
262 253 child.update_attribute(:parent, reassign_to)
263 254 end
264 255 else
265 256 @reassignable_to = @wiki.pages - @page.self_and_descendants
266 257 # display the destroy form if it's a user request
267 258 return unless api_request?
268 259 end
269 260 end
270 261 @page.destroy
271 262 respond_to do |format|
272 263 format.html { redirect_to :action => 'index', :project_id => @project }
273 264 format.api { render_api_ok }
274 265 end
275 266 end
276 267
277 268 def destroy_version
278 269 return render_403 unless editable?
279 270
280 271 @content = @page.content_for_version(params[:version])
281 272 @content.destroy
282 273 redirect_to_referer_or :action => 'history', :id => @page.title, :project_id => @project
283 274 end
284 275
285 276 # Export wiki to a single pdf or html file
286 277 def export
287 278 @pages = @wiki.pages.all(:order => 'title', :include => [:content, {:attachments => :author}])
288 279 respond_to do |format|
289 280 format.html {
290 281 export = render_to_string :action => 'export_multiple', :layout => false
291 282 send_data(export, :type => 'text/html', :filename => "wiki.html")
292 283 }
293 284 format.pdf {
294 285 send_data(wiki_pages_to_pdf(@pages, @project), :type => 'application/pdf', :filename => "#{@project.identifier}.pdf")
295 286 }
296 287 end
297 288 end
298 289
299 290 def preview
300 291 page = @wiki.find_page(params[:id])
301 292 # page is nil when previewing a new page
302 293 return render_403 unless page.nil? || editable?(page)
303 294 if page
304 295 @attachements = page.attachments
305 296 @previewed = page.content
306 297 end
307 298 @text = params[:content][:text]
308 299 render :partial => 'common/preview'
309 300 end
310 301
311 302 def add_attachment
312 303 return render_403 unless editable?
313 304 attachments = Attachment.attach_files(@page, params[:attachments])
314 305 render_attachment_warning_if_needed(@page)
315 306 redirect_to :action => 'show', :id => @page.title, :project_id => @project
316 307 end
317 308
318 309 private
319 310
320 311 def find_wiki
321 312 @project = Project.find(params[:project_id])
322 313 @wiki = @project.wiki
323 314 render_404 unless @wiki
324 315 rescue ActiveRecord::RecordNotFound
325 316 render_404
326 317 end
327 318
328 319 # Finds the requested page or a new page if it doesn't exist
329 320 def find_existing_or_new_page
330 321 @page = @wiki.find_or_new_page(params[:id])
331 322 if @wiki.page_found_with_redirect?
332 323 redirect_to params.update(:id => @page.title)
333 324 end
334 325 end
335 326
336 327 # Finds the requested page and returns a 404 error if it doesn't exist
337 328 def find_existing_page
338 329 @page = @wiki.find_page(params[:id])
339 330 if @page.nil?
340 331 render_404
341 332 return
342 333 end
343 334 if @wiki.page_found_with_redirect?
344 335 redirect_to params.update(:id => @page.title)
345 336 end
346 337 end
347 338
348 339 # Returns true if the current user is allowed to edit the page, otherwise false
349 340 def editable?(page = @page)
350 341 page.editable_by?(User.current)
351 342 end
352 343
353 344 # Returns the default content of a new wiki page
354 345 def initial_page_content(page)
355 346 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
356 347 extend helper unless self.instance_of?(helper)
357 348 helper.instance_method(:initial_page_content).bind(self).call(page)
358 349 end
359 350
360 351 def load_pages_for_index
361 352 @pages = @wiki.pages.with_updated_on.order("#{WikiPage.table_name}.title").includes(:wiki => :project).includes(:parent).all
362 353 end
363 354 end
@@ -1,235 +1,250
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require '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 :include => [{:wiki => :project}, :content],
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 => /^[^,\.\/\?\;\|\s]*$/
44 44 validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false
45 45 validates_associated :content
46 46
47 47 validate :validate_parent_title
48 48 before_destroy :remove_redirects
49 49 before_save :handle_redirects
50 50
51 51 # eager load information about last updates, without loading text
52 52 scope :with_updated_on, {
53 53 :select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on, #{WikiContent.table_name}.version",
54 54 :joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id"
55 55 }
56 56
57 57 # Wiki pages that are protected by default
58 58 DEFAULT_PROTECTED_PAGES = %w(sidebar)
59 59
60 60 safe_attributes 'parent_id', 'parent_title',
61 61 :if => lambda {|page, user| page.new_record? || user.allowed_to?(:rename_wiki_pages, page.project)}
62 62
63 63 def initialize(attributes=nil, *args)
64 64 super
65 65 if new_record? && DEFAULT_PROTECTED_PAGES.include?(title.to_s.downcase)
66 66 self.protected = true
67 67 end
68 68 end
69 69
70 70 def visible?(user=User.current)
71 71 !user.nil? && user.allowed_to?(:view_wiki_pages, project)
72 72 end
73 73
74 74 def title=(value)
75 75 value = Wiki.titleize(value)
76 76 @previous_title = read_attribute(:title) if @previous_title.blank?
77 77 write_attribute(:title, value)
78 78 end
79 79
80 80 def handle_redirects
81 81 self.title = Wiki.titleize(title)
82 82 # Manage redirects if the title has changed
83 83 if !@previous_title.blank? && (@previous_title != title) && !new_record?
84 84 # Update redirects that point to the old title
85 85 wiki.redirects.find_all_by_redirects_to(@previous_title).each do |r|
86 86 r.redirects_to = title
87 87 r.title == r.redirects_to ? r.destroy : r.save
88 88 end
89 89 # Remove redirects for the new title
90 90 wiki.redirects.find_all_by_title(title).each(&:destroy)
91 91 # Create a redirect to the new title
92 92 wiki.redirects << WikiRedirect.new(:title => @previous_title, :redirects_to => title) unless redirect_existing_links == "0"
93 93 @previous_title = nil
94 94 end
95 95 end
96 96
97 97 def remove_redirects
98 98 # Remove redirects to this page
99 99 wiki.redirects.find_all_by_redirects_to(title).each(&:destroy)
100 100 end
101 101
102 102 def pretty_title
103 103 WikiPage.pretty_title(title)
104 104 end
105 105
106 106 def content_for_version(version=nil)
107 107 result = content.versions.find_by_version(version.to_i) if version
108 108 result ||= content
109 109 result
110 110 end
111 111
112 112 def diff(version_to=nil, version_from=nil)
113 113 version_to = version_to ? version_to.to_i : self.content.version
114 114 content_to = content.versions.find_by_version(version_to)
115 115 content_from = version_from ? content.versions.find_by_version(version_from.to_i) : content_to.previous
116 116
117 117 if content_from.version > content_to.version
118 118 content_to, content_from = content_from, content_to
119 119 end
120 120
121 121 (content_to && content_from) ? WikiDiff.new(content_to, content_from) : nil
122 122 end
123 123
124 124 def annotate(version=nil)
125 125 version = version ? version.to_i : self.content.version
126 126 c = content.versions.find_by_version(version)
127 127 c ? WikiAnnotate.new(c) : nil
128 128 end
129 129
130 130 def self.pretty_title(str)
131 131 (str && str.is_a?(String)) ? str.tr('_', ' ') : str
132 132 end
133 133
134 134 def project
135 135 wiki.project
136 136 end
137 137
138 138 def text
139 139 content.text if content
140 140 end
141 141
142 142 def updated_on
143 143 unless @updated_on
144 144 if time = read_attribute(:updated_on)
145 145 # content updated_on was eager loaded with the page
146 146 begin
147 147 @updated_on = (self.class.default_timezone == :utc ? Time.parse(time.to_s).utc : Time.parse(time.to_s).localtime)
148 148 rescue
149 149 end
150 150 else
151 151 @updated_on = content && content.updated_on
152 152 end
153 153 end
154 154 @updated_on
155 155 end
156 156
157 157 # Returns true if usr is allowed to edit the page, otherwise false
158 158 def editable_by?(usr)
159 159 !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project)
160 160 end
161 161
162 162 def attachments_deletable?(usr=User.current)
163 163 editable_by?(usr) && super(usr)
164 164 end
165 165
166 166 def parent_title
167 167 @parent_title || (self.parent && self.parent.pretty_title)
168 168 end
169 169
170 170 def parent_title=(t)
171 171 @parent_title = t
172 172 parent_page = t.blank? ? nil : self.wiki.find_page(t)
173 173 self.parent = parent_page
174 174 end
175 175
176 # Saves the page and its content if text was changed
177 def save_with_content
178 ret = nil
179 transaction do
180 if new_record?
181 # Rails automatically saves associated content
182 ret = save
183 else
184 ret = save && (content.text_changed? ? content.save : true)
185 end
186 raise ActiveRecord::Rollback unless ret
187 end
188 ret
189 end
190
176 191 protected
177 192
178 193 def validate_parent_title
179 194 errors.add(:parent_title, :invalid) if !@parent_title.blank? && parent.nil?
180 195 errors.add(:parent_title, :circular_dependency) if parent && (parent == self || parent.ancestors.include?(self))
181 196 errors.add(:parent_title, :not_same_project) if parent && (parent.wiki_id != wiki_id)
182 197 end
183 198 end
184 199
185 200 class WikiDiff < Redmine::Helpers::Diff
186 201 attr_reader :content_to, :content_from
187 202
188 203 def initialize(content_to, content_from)
189 204 @content_to = content_to
190 205 @content_from = content_from
191 206 super(content_to.text, content_from.text)
192 207 end
193 208 end
194 209
195 210 class WikiAnnotate
196 211 attr_reader :lines, :content
197 212
198 213 def initialize(content)
199 214 @content = content
200 215 current = content
201 216 current_lines = current.text.split(/\r?\n/)
202 217 @lines = current_lines.collect {|t| [nil, nil, t]}
203 218 positions = []
204 219 current_lines.size.times {|i| positions << i}
205 220 while (current.previous)
206 221 d = current.previous.text.split(/\r?\n/).diff(current.text.split(/\r?\n/)).diffs.flatten
207 222 d.each_slice(3) do |s|
208 223 sign, line = s[0], s[1]
209 224 if sign == '+' && positions[line] && positions[line] != -1
210 225 if @lines[positions[line]][0].nil?
211 226 @lines[positions[line]][0] = current.version
212 227 @lines[positions[line]][1] = current.author
213 228 end
214 229 end
215 230 end
216 231 d.each_slice(3) do |s|
217 232 sign, line = s[0], s[1]
218 233 if sign == '-'
219 234 positions.insert(line, -1)
220 235 else
221 236 positions[line] = nil
222 237 end
223 238 end
224 239 positions.compact!
225 240 # Stop if every line is annotated
226 241 break unless @lines.detect { |line| line[0].nil? }
227 242 current = current.previous
228 243 end
229 244 @lines.each { |line|
230 245 line[0] ||= current.version
231 246 # if the last known version is > 1 (eg. history was cleared), we don't know the author
232 247 line[1] ||= current.author if current.version == 1
233 248 }
234 249 end
235 250 end
General Comments 0
You need to be logged in to leave comments. Login now