To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.
root / app / controllers / wiki_controller.rb @ 1298:4f746d8966dd
History | View | Annotate | Download (11.6 KB)
| 1 | 441:cbce1fd3b1b7 | Chris | # Redmine - project management software
|
|---|---|---|---|
| 2 | 1295:622f24f53b42 | Chris | # Copyright (C) 2006-2013 Jean-Philippe Lang
|
| 3 | 0:513646585e45 | Chris | #
|
| 4 | # This program is free software; you can redistribute it and/or
|
||
| 5 | # modify it under the terms of the GNU General Public License
|
||
| 6 | # as published by the Free Software Foundation; either version 2
|
||
| 7 | # of the License, or (at your option) any later version.
|
||
| 8 | 441:cbce1fd3b1b7 | Chris | #
|
| 9 | 0:513646585e45 | Chris | # This program is distributed in the hope that it will be useful,
|
| 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
| 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
| 12 | # GNU General Public License for more details.
|
||
| 13 | 441:cbce1fd3b1b7 | Chris | #
|
| 14 | 0:513646585e45 | Chris | # You should have received a copy of the GNU General Public License
|
| 15 | # along with this program; if not, write to the Free Software
|
||
| 16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||
| 17 | |||
| 18 | require 'diff'
|
||
| 19 | |||
| 20 | 37:94944d00e43c | chris | # The WikiController follows the Rails REST controller pattern but with
|
| 21 | # a few differences
|
||
| 22 | #
|
||
| 23 | # * index - shows a list of WikiPages grouped by page or date
|
||
| 24 | # * new - not used
|
||
| 25 | # * create - not used
|
||
| 26 | # * show - will also show the form for creating a new wiki page
|
||
| 27 | # * edit - used to edit an existing or new page
|
||
| 28 | # * update - used to save a wiki page update to the database, including new pages
|
||
| 29 | # * destroy - normal
|
||
| 30 | #
|
||
| 31 | # Other member and collection methods are also used
|
||
| 32 | #
|
||
| 33 | # TODO: still being worked on
|
||
| 34 | 0:513646585e45 | Chris | class WikiController < ApplicationController |
| 35 | default_search_scope :wiki_pages
|
||
| 36 | before_filter :find_wiki, :authorize |
||
| 37 | 441:cbce1fd3b1b7 | Chris | before_filter :find_existing_or_new_page, :only => [:show, :edit, :update] |
| 38 | 1115:433d4f72a19b | Chris | before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy, :destroy_version] |
| 39 | accept_api_auth :index, :show, :update, :destroy |
||
| 40 | 1295:622f24f53b42 | Chris | before_filter :find_attachments, :only => [:preview] |
| 41 | 0:513646585e45 | Chris | |
| 42 | helper :attachments
|
||
| 43 | 441:cbce1fd3b1b7 | Chris | include AttachmentsHelper
|
| 44 | 0:513646585e45 | Chris | helper :watchers
|
| 45 | 909:cbb26bc654de | Chris | include Redmine::Export::PDF |
| 46 | 37:94944d00e43c | chris | |
| 47 | # List of pages, sorted alphabetically and by parent (hierarchy)
|
||
| 48 | def index |
||
| 49 | 441:cbce1fd3b1b7 | Chris | load_pages_for_index |
| 50 | 1115:433d4f72a19b | Chris | |
| 51 | respond_to do |format|
|
||
| 52 | format.html {
|
||
| 53 | @pages_by_parent_id = @pages.group_by(&:parent_id) |
||
| 54 | } |
||
| 55 | format.api |
||
| 56 | end
|
||
| 57 | 441:cbce1fd3b1b7 | Chris | end
|
| 58 | |||
| 59 | # List of page, by last update
|
||
| 60 | def date_index |
||
| 61 | load_pages_for_index |
||
| 62 | @pages_by_date = @pages.group_by {|p| p.updated_on.to_date} |
||
| 63 | 37:94944d00e43c | chris | end
|
| 64 | |||
| 65 | 0:513646585e45 | Chris | # display a page (in editing mode if it doesn't exist)
|
| 66 | 37:94944d00e43c | chris | def show |
| 67 | 0:513646585e45 | Chris | if @page.new_record? |
| 68 | 1115:433d4f72a19b | Chris | if User.current.allowed_to?(:edit_wiki_pages, @project) && editable? && !api_request? |
| 69 | 0:513646585e45 | Chris | edit |
| 70 | render :action => 'edit' |
||
| 71 | else
|
||
| 72 | render_404 |
||
| 73 | end
|
||
| 74 | return
|
||
| 75 | end
|
||
| 76 | if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project) |
||
| 77 | 1115:433d4f72a19b | Chris | deny_access |
| 78 | 0:513646585e45 | Chris | return
|
| 79 | end
|
||
| 80 | @content = @page.content_for_version(params[:version]) |
||
| 81 | if User.current.allowed_to?(:export_wiki_pages, @project) |
||
| 82 | 909:cbb26bc654de | Chris | if params[:format] == 'pdf' |
| 83 | 1115:433d4f72a19b | Chris | send_data(wiki_page_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf") |
| 84 | 909:cbb26bc654de | Chris | return
|
| 85 | elsif params[:format] == 'html' |
||
| 86 | 0:513646585e45 | Chris | export = render_to_string :action => 'export', :layout => false |
| 87 | send_data(export, :type => 'text/html', :filename => "#{@page.title}.html") |
||
| 88 | return
|
||
| 89 | elsif params[:format] == 'txt' |
||
| 90 | send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt") |
||
| 91 | return
|
||
| 92 | end
|
||
| 93 | end
|
||
| 94 | @editable = editable?
|
||
| 95 | 909:cbb26bc654de | Chris | @sections_editable = @editable && User.current.allowed_to?(:edit_wiki_pages, @page.project) && |
| 96 | @content.current_version? &&
|
||
| 97 | Redmine::WikiFormatting.supports_section_edit? |
||
| 98 | |||
| 99 | 1115:433d4f72a19b | Chris | respond_to do |format|
|
| 100 | format.html |
||
| 101 | format.api |
||
| 102 | end
|
||
| 103 | 0:513646585e45 | Chris | end
|
| 104 | 441:cbce1fd3b1b7 | Chris | |
| 105 | 0:513646585e45 | Chris | # edit an existing page or a new one
|
| 106 | def edit |
||
| 107 | return render_403 unless editable? |
||
| 108 | 1115:433d4f72a19b | Chris | if @page.new_record? |
| 109 | @page.content = WikiContent.new(:page => @page) |
||
| 110 | if params[:parent].present? |
||
| 111 | @page.parent = @page.wiki.find_page(params[:parent].to_s) |
||
| 112 | end
|
||
| 113 | end
|
||
| 114 | 441:cbce1fd3b1b7 | Chris | |
| 115 | 0:513646585e45 | Chris | @content = @page.content_for_version(params[:version]) |
| 116 | @content.text = initial_page_content(@page) if @content.text.blank? |
||
| 117 | # don't keep previous comment
|
||
| 118 | @content.comments = nil |
||
| 119 | 37:94944d00e43c | chris | |
| 120 | # To prevent StaleObjectError exception when reverting to a previous version
|
||
| 121 | @content.version = @page.content.version |
||
| 122 | 1115:433d4f72a19b | Chris | |
| 123 | 909:cbb26bc654de | Chris | @text = @content.text |
| 124 | if params[:section].present? && Redmine::WikiFormatting.supports_section_edit? |
||
| 125 | @section = params[:section].to_i |
||
| 126 | @text, @section_hash = Redmine::WikiFormatting.formatter.new(@text).get_section(@section) |
||
| 127 | render_404 if @text.blank? |
||
| 128 | end
|
||
| 129 | 0:513646585e45 | Chris | end
|
| 130 | 37:94944d00e43c | chris | |
| 131 | # Creates a new page or updates an existing one
|
||
| 132 | def update |
||
| 133 | return render_403 unless editable? |
||
| 134 | 1115:433d4f72a19b | Chris | was_new_page = @page.new_record?
|
| 135 | 37:94944d00e43c | chris | @page.content = WikiContent.new(:page => @page) if @page.new_record? |
| 136 | 1115:433d4f72a19b | Chris | @page.safe_attributes = params[:wiki_page] |
| 137 | 441:cbce1fd3b1b7 | Chris | |
| 138 | 1115:433d4f72a19b | Chris | @content = @page.content |
| 139 | content_params = params[:content]
|
||
| 140 | if content_params.nil? && params[:wiki_page].is_a?(Hash) |
||
| 141 | content_params = params[:wiki_page].slice(:text, :comments, :version) |
||
| 142 | end
|
||
| 143 | content_params ||= {}
|
||
| 144 | 37:94944d00e43c | chris | |
| 145 | 1115:433d4f72a19b | Chris | @content.comments = content_params[:comments] |
| 146 | @text = content_params[:text] |
||
| 147 | 909:cbb26bc654de | Chris | if params[:section].present? && Redmine::WikiFormatting.supports_section_edit? |
| 148 | @section = params[:section].to_i |
||
| 149 | @section_hash = params[:section_hash] |
||
| 150 | @content.text = Redmine::WikiFormatting.formatter.new(@content.text).update_section(params[:section].to_i, @text, @section_hash) |
||
| 151 | else
|
||
| 152 | 1115:433d4f72a19b | Chris | @content.version = content_params[:version] if content_params[:version] |
| 153 | 909:cbb26bc654de | Chris | @content.text = @text |
| 154 | end
|
||
| 155 | 37:94944d00e43c | chris | @content.author = User.current |
| 156 | 1115:433d4f72a19b | Chris | |
| 157 | if @page.save_with_content |
||
| 158 | 37:94944d00e43c | chris | attachments = Attachment.attach_files(@page, params[:attachments]) |
| 159 | render_attachment_warning_if_needed(@page)
|
||
| 160 | call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page}) |
||
| 161 | 1115:433d4f72a19b | Chris | |
| 162 | respond_to do |format|
|
||
| 163 | 1295:622f24f53b42 | Chris | format.html { redirect_to project_wiki_page_path(@project, @page.title) }
|
| 164 | 1115:433d4f72a19b | Chris | format.api {
|
| 165 | if was_new_page
|
||
| 166 | 1295:622f24f53b42 | Chris | render :action => 'show', :status => :created, :location => project_wiki_page_path(@project, @page.title) |
| 167 | 1115:433d4f72a19b | Chris | else
|
| 168 | render_api_ok |
||
| 169 | end
|
||
| 170 | } |
||
| 171 | end
|
||
| 172 | 119:8661b858af72 | Chris | else
|
| 173 | 1115:433d4f72a19b | Chris | respond_to do |format|
|
| 174 | format.html { render :action => 'edit' }
|
||
| 175 | format.api { render_validation_errors(@content) }
|
||
| 176 | end
|
||
| 177 | 37:94944d00e43c | chris | end
|
| 178 | |||
| 179 | 909:cbb26bc654de | Chris | rescue ActiveRecord::StaleObjectError, Redmine::WikiFormatting::StaleSectionError |
| 180 | 37:94944d00e43c | chris | # Optimistic locking exception
|
| 181 | 1115:433d4f72a19b | Chris | respond_to do |format|
|
| 182 | format.html {
|
||
| 183 | flash.now[:error] = l(:notice_locking_conflict) |
||
| 184 | render :action => 'edit' |
||
| 185 | } |
||
| 186 | format.api { render_api_head :conflict }
|
||
| 187 | end
|
||
| 188 | rescue ActiveRecord::RecordNotSaved |
||
| 189 | respond_to do |format|
|
||
| 190 | format.html { render :action => 'edit' }
|
||
| 191 | format.api { render_validation_errors(@content) }
|
||
| 192 | end
|
||
| 193 | 37:94944d00e43c | chris | end
|
| 194 | |||
| 195 | 0:513646585e45 | Chris | # rename a page
|
| 196 | def rename |
||
| 197 | return render_403 unless editable? |
||
| 198 | @page.redirect_existing_links = true |
||
| 199 | # used to display the *original* title if some AR validation errors occur
|
||
| 200 | @original_title = @page.pretty_title |
||
| 201 | if request.post? && @page.update_attributes(params[:wiki_page]) |
||
| 202 | flash[:notice] = l(:notice_successful_update) |
||
| 203 | 1295:622f24f53b42 | Chris | redirect_to project_wiki_page_path(@project, @page.title) |
| 204 | 0:513646585e45 | Chris | end
|
| 205 | end
|
||
| 206 | 441:cbce1fd3b1b7 | Chris | |
| 207 | 0:513646585e45 | Chris | def protect |
| 208 | @page.update_attribute :protected, params[:protected] |
||
| 209 | 1295:622f24f53b42 | Chris | redirect_to project_wiki_page_path(@project, @page.title) |
| 210 | 0:513646585e45 | Chris | end
|
| 211 | |||
| 212 | # show page history
|
||
| 213 | def history |
||
| 214 | @version_count = @page.content.versions.count |
||
| 215 | 1295:622f24f53b42 | Chris | @version_pages = Paginator.new @version_count, per_page_option, params['page'] |
| 216 | 441:cbce1fd3b1b7 | Chris | # don't load text
|
| 217 | 1295:622f24f53b42 | Chris | @versions = @page.content.versions. |
| 218 | select("id, author_id, comments, updated_on, version").
|
||
| 219 | reorder('version DESC').
|
||
| 220 | limit(@version_pages.per_page + 1). |
||
| 221 | offset(@version_pages.offset).
|
||
| 222 | all |
||
| 223 | 0:513646585e45 | Chris | |
| 224 | render :layout => false if request.xhr? |
||
| 225 | end
|
||
| 226 | 441:cbce1fd3b1b7 | Chris | |
| 227 | 0:513646585e45 | Chris | def diff |
| 228 | @diff = @page.diff(params[:version], params[:version_from]) |
||
| 229 | render_404 unless @diff |
||
| 230 | end
|
||
| 231 | 441:cbce1fd3b1b7 | Chris | |
| 232 | 0:513646585e45 | Chris | def annotate |
| 233 | @annotate = @page.annotate(params[:version]) |
||
| 234 | render_404 unless @annotate |
||
| 235 | end
|
||
| 236 | 37:94944d00e43c | chris | |
| 237 | 0:513646585e45 | Chris | # Removes a wiki page and its history
|
| 238 | # Children can be either set as root pages, removed or reassigned to another parent page
|
||
| 239 | def destroy |
||
| 240 | return render_403 unless editable? |
||
| 241 | 441:cbce1fd3b1b7 | Chris | |
| 242 | 0:513646585e45 | Chris | @descendants_count = @page.descendants.size |
| 243 | if @descendants_count > 0 |
||
| 244 | case params[:todo] |
||
| 245 | when 'nullify' |
||
| 246 | # Nothing to do
|
||
| 247 | when 'destroy' |
||
| 248 | # Removes all its descendants
|
||
| 249 | @page.descendants.each(&:destroy) |
||
| 250 | when 'reassign' |
||
| 251 | # Reassign children to another parent page
|
||
| 252 | reassign_to = @wiki.pages.find_by_id(params[:reassign_to_id].to_i) |
||
| 253 | return unless reassign_to |
||
| 254 | @page.children.each do |child| |
||
| 255 | child.update_attribute(:parent, reassign_to)
|
||
| 256 | end
|
||
| 257 | else
|
||
| 258 | @reassignable_to = @wiki.pages - @page.self_and_descendants |
||
| 259 | 1115:433d4f72a19b | Chris | # display the destroy form if it's a user request
|
| 260 | return unless api_request? |
||
| 261 | 0:513646585e45 | Chris | end
|
| 262 | end
|
||
| 263 | @page.destroy
|
||
| 264 | 1115:433d4f72a19b | Chris | respond_to do |format|
|
| 265 | 1295:622f24f53b42 | Chris | format.html { redirect_to project_wiki_index_path(@project) }
|
| 266 | 1115:433d4f72a19b | Chris | format.api { render_api_ok }
|
| 267 | end
|
||
| 268 | 0:513646585e45 | Chris | end
|
| 269 | |||
| 270 | 1115:433d4f72a19b | Chris | def destroy_version |
| 271 | return render_403 unless editable? |
||
| 272 | |||
| 273 | @content = @page.content_for_version(params[:version]) |
||
| 274 | @content.destroy
|
||
| 275 | 1295:622f24f53b42 | Chris | redirect_to_referer_or history_project_wiki_page_path(@project, @page.title) |
| 276 | 1115:433d4f72a19b | Chris | end
|
| 277 | |||
| 278 | # Export wiki to a single pdf or html file
|
||
| 279 | 37:94944d00e43c | chris | def export |
| 280 | 1115:433d4f72a19b | Chris | @pages = @wiki.pages.all(:order => 'title', :include => [:content, {:attachments => :author}]) |
| 281 | respond_to do |format|
|
||
| 282 | format.html {
|
||
| 283 | export = render_to_string :action => 'export_multiple', :layout => false |
||
| 284 | send_data(export, :type => 'text/html', :filename => "wiki.html") |
||
| 285 | } |
||
| 286 | format.pdf {
|
||
| 287 | send_data(wiki_pages_to_pdf(@pages, @project), :type => 'application/pdf', :filename => "#{@project.identifier}.pdf") |
||
| 288 | } |
||
| 289 | 0:513646585e45 | Chris | end
|
| 290 | 37:94944d00e43c | chris | end
|
| 291 | |||
| 292 | 0:513646585e45 | Chris | def preview |
| 293 | 37:94944d00e43c | chris | page = @wiki.find_page(params[:id]) |
| 294 | 0:513646585e45 | Chris | # page is nil when previewing a new page
|
| 295 | return render_403 unless page.nil? || editable?(page) |
||
| 296 | if page
|
||
| 297 | 1295:622f24f53b42 | Chris | @attachments += page.attachments
|
| 298 | 0:513646585e45 | Chris | @previewed = page.content
|
| 299 | end
|
||
| 300 | @text = params[:content][:text] |
||
| 301 | render :partial => 'common/preview' |
||
| 302 | end
|
||
| 303 | |||
| 304 | def add_attachment |
||
| 305 | return render_403 unless editable? |
||
| 306 | attachments = Attachment.attach_files(@page, params[:attachments]) |
||
| 307 | render_attachment_warning_if_needed(@page)
|
||
| 308 | 37:94944d00e43c | chris | redirect_to :action => 'show', :id => @page.title, :project_id => @project |
| 309 | 0:513646585e45 | Chris | end
|
| 310 | |||
| 311 | private |
||
| 312 | 441:cbce1fd3b1b7 | Chris | |
| 313 | 0:513646585e45 | Chris | def find_wiki |
| 314 | 37:94944d00e43c | chris | @project = Project.find(params[:project_id]) |
| 315 | 0:513646585e45 | Chris | @wiki = @project.wiki |
| 316 | render_404 unless @wiki |
||
| 317 | rescue ActiveRecord::RecordNotFound |
||
| 318 | render_404 |
||
| 319 | end
|
||
| 320 | 441:cbce1fd3b1b7 | Chris | |
| 321 | # Finds the requested page or a new page if it doesn't exist
|
||
| 322 | def find_existing_or_new_page |
||
| 323 | @page = @wiki.find_or_new_page(params[:id]) |
||
| 324 | if @wiki.page_found_with_redirect? |
||
| 325 | redirect_to params.update(:id => @page.title) |
||
| 326 | end
|
||
| 327 | end
|
||
| 328 | |||
| 329 | 0:513646585e45 | Chris | # Finds the requested page and returns a 404 error if it doesn't exist
|
| 330 | def find_existing_page |
||
| 331 | 37:94944d00e43c | chris | @page = @wiki.find_page(params[:id]) |
| 332 | 441:cbce1fd3b1b7 | Chris | if @page.nil? |
| 333 | render_404 |
||
| 334 | return
|
||
| 335 | end
|
||
| 336 | if @wiki.page_found_with_redirect? |
||
| 337 | redirect_to params.update(:id => @page.title) |
||
| 338 | end
|
||
| 339 | 0:513646585e45 | Chris | end
|
| 340 | 441:cbce1fd3b1b7 | Chris | |
| 341 | 0:513646585e45 | Chris | # Returns true if the current user is allowed to edit the page, otherwise false
|
| 342 | def editable?(page = @page) |
||
| 343 | page.editable_by?(User.current)
|
||
| 344 | end
|
||
| 345 | |||
| 346 | # Returns the default content of a new wiki page
|
||
| 347 | def initial_page_content(page) |
||
| 348 | helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting) |
||
| 349 | extend helper unless self.instance_of?(helper) |
||
| 350 | helper.instance_method(:initial_page_content).bind(self).call(page) |
||
| 351 | end
|
||
| 352 | 37:94944d00e43c | chris | |
| 353 | 441:cbce1fd3b1b7 | Chris | def load_pages_for_index |
| 354 | 1295:622f24f53b42 | Chris | @pages = @wiki.pages.with_updated_on.reorder("#{WikiPage.table_name}.title").includes(:wiki => :project).includes(:parent).all |
| 355 | 37:94944d00e43c | chris | end
|
| 356 | 0:513646585e45 | Chris | end |