annotate app/models/wiki_page.rb @ 8:0c83d98252d9 yuya

* Add custom repo prefix and proper auth realm, remove auth cache (seems like an unwise feature), pass DB handle around, various other bits of tidying
author Chris Cannam
date Thu, 12 Aug 2010 15:31:37 +0100
parents 513646585e45
children 94944d00e43c
rev   line source
Chris@0 1 # Redmine - project management software
Chris@0 2 # Copyright (C) 2006-2009 Jean-Philippe Lang
Chris@0 3 #
Chris@0 4 # This program is free software; you can redistribute it and/or
Chris@0 5 # modify it under the terms of the GNU General Public License
Chris@0 6 # as published by the Free Software Foundation; either version 2
Chris@0 7 # of the License, or (at your option) any later version.
Chris@0 8 #
Chris@0 9 # This program is distributed in the hope that it will be useful,
Chris@0 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
Chris@0 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Chris@0 12 # GNU General Public License for more details.
Chris@0 13 #
Chris@0 14 # You should have received a copy of the GNU General Public License
Chris@0 15 # along with this program; if not, write to the Free Software
Chris@0 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Chris@0 17
Chris@0 18 require 'diff'
Chris@0 19 require 'enumerator'
Chris@0 20
Chris@0 21 class WikiPage < ActiveRecord::Base
Chris@0 22 belongs_to :wiki
Chris@0 23 has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy
Chris@0 24 acts_as_attachable :delete_permission => :delete_wiki_pages_attachments
Chris@0 25 acts_as_tree :dependent => :nullify, :order => 'title'
Chris@0 26
Chris@0 27 acts_as_watchable
Chris@0 28 acts_as_event :title => Proc.new {|o| "#{l(:label_wiki)}: #{o.title}"},
Chris@0 29 :description => :text,
Chris@0 30 :datetime => :created_on,
Chris@0 31 :url => Proc.new {|o| {:controller => 'wiki', :id => o.wiki.project, :page => o.title}}
Chris@0 32
Chris@0 33 acts_as_searchable :columns => ['title', 'text'],
Chris@0 34 :include => [{:wiki => :project}, :content],
Chris@0 35 :project_key => "#{Wiki.table_name}.project_id"
Chris@0 36
Chris@0 37 attr_accessor :redirect_existing_links
Chris@0 38
Chris@0 39 validates_presence_of :title
Chris@0 40 validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/
Chris@0 41 validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false
Chris@0 42 validates_associated :content
Chris@0 43
Chris@0 44 # Wiki pages that are protected by default
Chris@0 45 DEFAULT_PROTECTED_PAGES = %w(sidebar)
Chris@0 46
Chris@0 47 def after_initialize
Chris@0 48 if new_record? && DEFAULT_PROTECTED_PAGES.include?(title.to_s.downcase)
Chris@0 49 self.protected = true
Chris@0 50 end
Chris@0 51 end
Chris@0 52
Chris@0 53 def visible?(user=User.current)
Chris@0 54 !user.nil? && user.allowed_to?(:view_wiki_pages, project)
Chris@0 55 end
Chris@0 56
Chris@0 57 def title=(value)
Chris@0 58 value = Wiki.titleize(value)
Chris@0 59 @previous_title = read_attribute(:title) if @previous_title.blank?
Chris@0 60 write_attribute(:title, value)
Chris@0 61 end
Chris@0 62
Chris@0 63 def before_save
Chris@0 64 self.title = Wiki.titleize(title)
Chris@0 65 # Manage redirects if the title has changed
Chris@0 66 if !@previous_title.blank? && (@previous_title != title) && !new_record?
Chris@0 67 # Update redirects that point to the old title
Chris@0 68 wiki.redirects.find_all_by_redirects_to(@previous_title).each do |r|
Chris@0 69 r.redirects_to = title
Chris@0 70 r.title == r.redirects_to ? r.destroy : r.save
Chris@0 71 end
Chris@0 72 # Remove redirects for the new title
Chris@0 73 wiki.redirects.find_all_by_title(title).each(&:destroy)
Chris@0 74 # Create a redirect to the new title
Chris@0 75 wiki.redirects << WikiRedirect.new(:title => @previous_title, :redirects_to => title) unless redirect_existing_links == "0"
Chris@0 76 @previous_title = nil
Chris@0 77 end
Chris@0 78 end
Chris@0 79
Chris@0 80 def before_destroy
Chris@0 81 # Remove redirects to this page
Chris@0 82 wiki.redirects.find_all_by_redirects_to(title).each(&:destroy)
Chris@0 83 end
Chris@0 84
Chris@0 85 def pretty_title
Chris@0 86 WikiPage.pretty_title(title)
Chris@0 87 end
Chris@0 88
Chris@0 89 def content_for_version(version=nil)
Chris@0 90 result = content.versions.find_by_version(version.to_i) if version
Chris@0 91 result ||= content
Chris@0 92 result
Chris@0 93 end
Chris@0 94
Chris@0 95 def diff(version_to=nil, version_from=nil)
Chris@0 96 version_to = version_to ? version_to.to_i : self.content.version
Chris@0 97 version_from = version_from ? version_from.to_i : version_to - 1
Chris@0 98 version_to, version_from = version_from, version_to unless version_from < version_to
Chris@0 99
Chris@0 100 content_to = content.versions.find_by_version(version_to)
Chris@0 101 content_from = content.versions.find_by_version(version_from)
Chris@0 102
Chris@0 103 (content_to && content_from) ? WikiDiff.new(content_to, content_from) : nil
Chris@0 104 end
Chris@0 105
Chris@0 106 def annotate(version=nil)
Chris@0 107 version = version ? version.to_i : self.content.version
Chris@0 108 c = content.versions.find_by_version(version)
Chris@0 109 c ? WikiAnnotate.new(c) : nil
Chris@0 110 end
Chris@0 111
Chris@0 112 def self.pretty_title(str)
Chris@0 113 (str && str.is_a?(String)) ? str.tr('_', ' ') : str
Chris@0 114 end
Chris@0 115
Chris@0 116 def project
Chris@0 117 wiki.project
Chris@0 118 end
Chris@0 119
Chris@0 120 def text
Chris@0 121 content.text if content
Chris@0 122 end
Chris@0 123
Chris@0 124 # Returns true if usr is allowed to edit the page, otherwise false
Chris@0 125 def editable_by?(usr)
Chris@0 126 !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project)
Chris@0 127 end
Chris@0 128
Chris@0 129 def attachments_deletable?(usr=User.current)
Chris@0 130 editable_by?(usr) && super(usr)
Chris@0 131 end
Chris@0 132
Chris@0 133 def parent_title
Chris@0 134 @parent_title || (self.parent && self.parent.pretty_title)
Chris@0 135 end
Chris@0 136
Chris@0 137 def parent_title=(t)
Chris@0 138 @parent_title = t
Chris@0 139 parent_page = t.blank? ? nil : self.wiki.find_page(t)
Chris@0 140 self.parent = parent_page
Chris@0 141 end
Chris@0 142
Chris@0 143 protected
Chris@0 144
Chris@0 145 def validate
Chris@0 146 errors.add(:parent_title, :invalid) if !@parent_title.blank? && parent.nil?
Chris@0 147 errors.add(:parent_title, :circular_dependency) if parent && (parent == self || parent.ancestors.include?(self))
Chris@0 148 errors.add(:parent_title, :not_same_project) if parent && (parent.wiki_id != wiki_id)
Chris@0 149 end
Chris@0 150 end
Chris@0 151
Chris@0 152 class WikiDiff
Chris@0 153 attr_reader :diff, :words, :content_to, :content_from
Chris@0 154
Chris@0 155 def initialize(content_to, content_from)
Chris@0 156 @content_to = content_to
Chris@0 157 @content_from = content_from
Chris@0 158 @words = content_to.text.split(/(\s+)/)
Chris@0 159 @words = @words.select {|word| word != ' '}
Chris@0 160 words_from = content_from.text.split(/(\s+)/)
Chris@0 161 words_from = words_from.select {|word| word != ' '}
Chris@0 162 @diff = words_from.diff @words
Chris@0 163 end
Chris@0 164 end
Chris@0 165
Chris@0 166 class WikiAnnotate
Chris@0 167 attr_reader :lines, :content
Chris@0 168
Chris@0 169 def initialize(content)
Chris@0 170 @content = content
Chris@0 171 current = content
Chris@0 172 current_lines = current.text.split(/\r?\n/)
Chris@0 173 @lines = current_lines.collect {|t| [nil, nil, t]}
Chris@0 174 positions = []
Chris@0 175 current_lines.size.times {|i| positions << i}
Chris@0 176 while (current.previous)
Chris@0 177 d = current.previous.text.split(/\r?\n/).diff(current.text.split(/\r?\n/)).diffs.flatten
Chris@0 178 d.each_slice(3) do |s|
Chris@0 179 sign, line = s[0], s[1]
Chris@0 180 if sign == '+' && positions[line] && positions[line] != -1
Chris@0 181 if @lines[positions[line]][0].nil?
Chris@0 182 @lines[positions[line]][0] = current.version
Chris@0 183 @lines[positions[line]][1] = current.author
Chris@0 184 end
Chris@0 185 end
Chris@0 186 end
Chris@0 187 d.each_slice(3) do |s|
Chris@0 188 sign, line = s[0], s[1]
Chris@0 189 if sign == '-'
Chris@0 190 positions.insert(line, -1)
Chris@0 191 else
Chris@0 192 positions[line] = nil
Chris@0 193 end
Chris@0 194 end
Chris@0 195 positions.compact!
Chris@0 196 # Stop if every line is annotated
Chris@0 197 break unless @lines.detect { |line| line[0].nil? }
Chris@0 198 current = current.previous
Chris@0 199 end
Chris@0 200 @lines.each { |line| line[0] ||= current.version }
Chris@0 201 end
Chris@0 202 end