##// END OF EJS Templates
Qualify searchable @text@ column to prevent exception thrown when :content association is not eargerly fetched by AR (#9308)....
Etienne Massip -
r7448:0aa63a13bc42
parent child
Show More
@@ -1,225 +1,225
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'diff'
18 require 'diff'
19 require 'enumerator'
19 require 'enumerator'
20
20
21 class WikiPage < ActiveRecord::Base
21 class WikiPage < ActiveRecord::Base
22 belongs_to :wiki
22 belongs_to :wiki
23 has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy
23 has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy
24 acts_as_attachable :delete_permission => :delete_wiki_pages_attachments
24 acts_as_attachable :delete_permission => :delete_wiki_pages_attachments
25 acts_as_tree :dependent => :nullify, :order => 'title'
25 acts_as_tree :dependent => :nullify, :order => 'title'
26
26
27 acts_as_watchable
27 acts_as_watchable
28 acts_as_event :title => Proc.new {|o| "#{l(:label_wiki)}: #{o.title}"},
28 acts_as_event :title => Proc.new {|o| "#{l(:label_wiki)}: #{o.title}"},
29 :description => :text,
29 :description => :text,
30 :datetime => :created_on,
30 :datetime => :created_on,
31 :url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.wiki.project, :id => o.title}}
31 :url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.wiki.project, :id => o.title}}
32
32
33 acts_as_searchable :columns => ['title', 'text'],
33 acts_as_searchable :columns => ['title', "#{WikiContent.table_name}.text"],
34 :include => [{:wiki => :project}, :content],
34 :include => [{:wiki => :project}, :content],
35 :permission => :view_wiki_pages,
35 :permission => :view_wiki_pages,
36 :project_key => "#{Wiki.table_name}.project_id"
36 :project_key => "#{Wiki.table_name}.project_id"
37
37
38 attr_accessor :redirect_existing_links
38 attr_accessor :redirect_existing_links
39
39
40 validates_presence_of :title
40 validates_presence_of :title
41 validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/
41 validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/
42 validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false
42 validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false
43 validates_associated :content
43 validates_associated :content
44
44
45 validate :validate_parent_title
45 validate :validate_parent_title
46 before_destroy :remove_redirects
46 before_destroy :remove_redirects
47 before_save :handle_redirects
47 before_save :handle_redirects
48
48
49 # eager load information about last updates, without loading text
49 # eager load information about last updates, without loading text
50 named_scope :with_updated_on, {
50 named_scope :with_updated_on, {
51 :select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on",
51 :select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on",
52 :joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id"
52 :joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id"
53 }
53 }
54
54
55 # Wiki pages that are protected by default
55 # Wiki pages that are protected by default
56 DEFAULT_PROTECTED_PAGES = %w(sidebar)
56 DEFAULT_PROTECTED_PAGES = %w(sidebar)
57
57
58 def after_initialize
58 def after_initialize
59 if new_record? && DEFAULT_PROTECTED_PAGES.include?(title.to_s.downcase)
59 if new_record? && DEFAULT_PROTECTED_PAGES.include?(title.to_s.downcase)
60 self.protected = true
60 self.protected = true
61 end
61 end
62 end
62 end
63
63
64 def visible?(user=User.current)
64 def visible?(user=User.current)
65 !user.nil? && user.allowed_to?(:view_wiki_pages, project)
65 !user.nil? && user.allowed_to?(:view_wiki_pages, project)
66 end
66 end
67
67
68 def title=(value)
68 def title=(value)
69 value = Wiki.titleize(value)
69 value = Wiki.titleize(value)
70 @previous_title = read_attribute(:title) if @previous_title.blank?
70 @previous_title = read_attribute(:title) if @previous_title.blank?
71 write_attribute(:title, value)
71 write_attribute(:title, value)
72 end
72 end
73
73
74 def handle_redirects
74 def handle_redirects
75 self.title = Wiki.titleize(title)
75 self.title = Wiki.titleize(title)
76 # Manage redirects if the title has changed
76 # Manage redirects if the title has changed
77 if !@previous_title.blank? && (@previous_title != title) && !new_record?
77 if !@previous_title.blank? && (@previous_title != title) && !new_record?
78 # Update redirects that point to the old title
78 # Update redirects that point to the old title
79 wiki.redirects.find_all_by_redirects_to(@previous_title).each do |r|
79 wiki.redirects.find_all_by_redirects_to(@previous_title).each do |r|
80 r.redirects_to = title
80 r.redirects_to = title
81 r.title == r.redirects_to ? r.destroy : r.save
81 r.title == r.redirects_to ? r.destroy : r.save
82 end
82 end
83 # Remove redirects for the new title
83 # Remove redirects for the new title
84 wiki.redirects.find_all_by_title(title).each(&:destroy)
84 wiki.redirects.find_all_by_title(title).each(&:destroy)
85 # Create a redirect to the new title
85 # Create a redirect to the new title
86 wiki.redirects << WikiRedirect.new(:title => @previous_title, :redirects_to => title) unless redirect_existing_links == "0"
86 wiki.redirects << WikiRedirect.new(:title => @previous_title, :redirects_to => title) unless redirect_existing_links == "0"
87 @previous_title = nil
87 @previous_title = nil
88 end
88 end
89 end
89 end
90
90
91 def remove_redirects
91 def remove_redirects
92 # Remove redirects to this page
92 # Remove redirects to this page
93 wiki.redirects.find_all_by_redirects_to(title).each(&:destroy)
93 wiki.redirects.find_all_by_redirects_to(title).each(&:destroy)
94 end
94 end
95
95
96 def pretty_title
96 def pretty_title
97 WikiPage.pretty_title(title)
97 WikiPage.pretty_title(title)
98 end
98 end
99
99
100 def content_for_version(version=nil)
100 def content_for_version(version=nil)
101 result = content.versions.find_by_version(version.to_i) if version
101 result = content.versions.find_by_version(version.to_i) if version
102 result ||= content
102 result ||= content
103 result
103 result
104 end
104 end
105
105
106 def diff(version_to=nil, version_from=nil)
106 def diff(version_to=nil, version_from=nil)
107 version_to = version_to ? version_to.to_i : self.content.version
107 version_to = version_to ? version_to.to_i : self.content.version
108 version_from = version_from ? version_from.to_i : version_to - 1
108 version_from = version_from ? version_from.to_i : version_to - 1
109 version_to, version_from = version_from, version_to unless version_from < version_to
109 version_to, version_from = version_from, version_to unless version_from < version_to
110
110
111 content_to = content.versions.find_by_version(version_to)
111 content_to = content.versions.find_by_version(version_to)
112 content_from = content.versions.find_by_version(version_from)
112 content_from = content.versions.find_by_version(version_from)
113
113
114 (content_to && content_from) ? WikiDiff.new(content_to, content_from) : nil
114 (content_to && content_from) ? WikiDiff.new(content_to, content_from) : nil
115 end
115 end
116
116
117 def annotate(version=nil)
117 def annotate(version=nil)
118 version = version ? version.to_i : self.content.version
118 version = version ? version.to_i : self.content.version
119 c = content.versions.find_by_version(version)
119 c = content.versions.find_by_version(version)
120 c ? WikiAnnotate.new(c) : nil
120 c ? WikiAnnotate.new(c) : nil
121 end
121 end
122
122
123 def self.pretty_title(str)
123 def self.pretty_title(str)
124 (str && str.is_a?(String)) ? str.tr('_', ' ') : str
124 (str && str.is_a?(String)) ? str.tr('_', ' ') : str
125 end
125 end
126
126
127 def project
127 def project
128 wiki.project
128 wiki.project
129 end
129 end
130
130
131 def text
131 def text
132 content.text if content
132 content.text if content
133 end
133 end
134
134
135 def updated_on
135 def updated_on
136 unless @updated_on
136 unless @updated_on
137 if time = read_attribute(:updated_on)
137 if time = read_attribute(:updated_on)
138 # content updated_on was eager loaded with the page
138 # content updated_on was eager loaded with the page
139 @updated_on = Time.parse(time) rescue nil
139 @updated_on = Time.parse(time) rescue nil
140 else
140 else
141 @updated_on = content && content.updated_on
141 @updated_on = content && content.updated_on
142 end
142 end
143 end
143 end
144 @updated_on
144 @updated_on
145 end
145 end
146
146
147 # Returns true if usr is allowed to edit the page, otherwise false
147 # Returns true if usr is allowed to edit the page, otherwise false
148 def editable_by?(usr)
148 def editable_by?(usr)
149 !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project)
149 !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project)
150 end
150 end
151
151
152 def attachments_deletable?(usr=User.current)
152 def attachments_deletable?(usr=User.current)
153 editable_by?(usr) && super(usr)
153 editable_by?(usr) && super(usr)
154 end
154 end
155
155
156 def parent_title
156 def parent_title
157 @parent_title || (self.parent && self.parent.pretty_title)
157 @parent_title || (self.parent && self.parent.pretty_title)
158 end
158 end
159
159
160 def parent_title=(t)
160 def parent_title=(t)
161 @parent_title = t
161 @parent_title = t
162 parent_page = t.blank? ? nil : self.wiki.find_page(t)
162 parent_page = t.blank? ? nil : self.wiki.find_page(t)
163 self.parent = parent_page
163 self.parent = parent_page
164 end
164 end
165
165
166 protected
166 protected
167
167
168 def validate_parent_title
168 def validate_parent_title
169 errors.add(:parent_title, :invalid) if !@parent_title.blank? && parent.nil?
169 errors.add(:parent_title, :invalid) if !@parent_title.blank? && parent.nil?
170 errors.add(:parent_title, :circular_dependency) if parent && (parent == self || parent.ancestors.include?(self))
170 errors.add(:parent_title, :circular_dependency) if parent && (parent == self || parent.ancestors.include?(self))
171 errors.add(:parent_title, :not_same_project) if parent && (parent.wiki_id != wiki_id)
171 errors.add(:parent_title, :not_same_project) if parent && (parent.wiki_id != wiki_id)
172 end
172 end
173 end
173 end
174
174
175 class WikiDiff < Redmine::Helpers::Diff
175 class WikiDiff < Redmine::Helpers::Diff
176 attr_reader :content_to, :content_from
176 attr_reader :content_to, :content_from
177
177
178 def initialize(content_to, content_from)
178 def initialize(content_to, content_from)
179 @content_to = content_to
179 @content_to = content_to
180 @content_from = content_from
180 @content_from = content_from
181 super(content_to.text, content_from.text)
181 super(content_to.text, content_from.text)
182 end
182 end
183 end
183 end
184
184
185 class WikiAnnotate
185 class WikiAnnotate
186 attr_reader :lines, :content
186 attr_reader :lines, :content
187
187
188 def initialize(content)
188 def initialize(content)
189 @content = content
189 @content = content
190 current = content
190 current = content
191 current_lines = current.text.split(/\r?\n/)
191 current_lines = current.text.split(/\r?\n/)
192 @lines = current_lines.collect {|t| [nil, nil, t]}
192 @lines = current_lines.collect {|t| [nil, nil, t]}
193 positions = []
193 positions = []
194 current_lines.size.times {|i| positions << i}
194 current_lines.size.times {|i| positions << i}
195 while (current.previous)
195 while (current.previous)
196 d = current.previous.text.split(/\r?\n/).diff(current.text.split(/\r?\n/)).diffs.flatten
196 d = current.previous.text.split(/\r?\n/).diff(current.text.split(/\r?\n/)).diffs.flatten
197 d.each_slice(3) do |s|
197 d.each_slice(3) do |s|
198 sign, line = s[0], s[1]
198 sign, line = s[0], s[1]
199 if sign == '+' && positions[line] && positions[line] != -1
199 if sign == '+' && positions[line] && positions[line] != -1
200 if @lines[positions[line]][0].nil?
200 if @lines[positions[line]][0].nil?
201 @lines[positions[line]][0] = current.version
201 @lines[positions[line]][0] = current.version
202 @lines[positions[line]][1] = current.author
202 @lines[positions[line]][1] = current.author
203 end
203 end
204 end
204 end
205 end
205 end
206 d.each_slice(3) do |s|
206 d.each_slice(3) do |s|
207 sign, line = s[0], s[1]
207 sign, line = s[0], s[1]
208 if sign == '-'
208 if sign == '-'
209 positions.insert(line, -1)
209 positions.insert(line, -1)
210 else
210 else
211 positions[line] = nil
211 positions[line] = nil
212 end
212 end
213 end
213 end
214 positions.compact!
214 positions.compact!
215 # Stop if every line is annotated
215 # Stop if every line is annotated
216 break unless @lines.detect { |line| line[0].nil? }
216 break unless @lines.detect { |line| line[0].nil? }
217 current = current.previous
217 current = current.previous
218 end
218 end
219 @lines.each { |line|
219 @lines.each { |line|
220 line[0] ||= current.version
220 line[0] ||= current.version
221 # if the last known version is > 1 (eg. history was cleared), we don't know the author
221 # if the last known version is > 1 (eg. history was cleared), we don't know the author
222 line[1] ||= current.author if current.version == 1
222 line[1] ||= current.author if current.version == 1
223 }
223 }
224 end
224 end
225 end
225 end
General Comments 0
You need to be logged in to leave comments. Login now