##// END OF EJS Templates
Fix wiki links on the activity page. #6746...
Eric Davis -
r4180:17eab0f5f9ec
parent child
Show More
@@ -1,110 +1,110
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 'zlib'
18 require 'zlib'
19
19
20 class WikiContent < ActiveRecord::Base
20 class WikiContent < ActiveRecord::Base
21 set_locking_column :version
21 set_locking_column :version
22 belongs_to :page, :class_name => 'WikiPage', :foreign_key => 'page_id'
22 belongs_to :page, :class_name => 'WikiPage', :foreign_key => 'page_id'
23 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
23 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
24 validates_presence_of :text
24 validates_presence_of :text
25 validates_length_of :comments, :maximum => 255, :allow_nil => true
25 validates_length_of :comments, :maximum => 255, :allow_nil => true
26
26
27 acts_as_versioned
27 acts_as_versioned
28
28
29 def visible?(user=User.current)
29 def visible?(user=User.current)
30 page.visible?(user)
30 page.visible?(user)
31 end
31 end
32
32
33 def project
33 def project
34 page.project
34 page.project
35 end
35 end
36
36
37 def attachments
37 def attachments
38 page.nil? ? [] : page.attachments
38 page.nil? ? [] : page.attachments
39 end
39 end
40
40
41 # Returns the mail adresses of users that should be notified
41 # Returns the mail adresses of users that should be notified
42 def recipients
42 def recipients
43 notified = project.notified_users
43 notified = project.notified_users
44 notified.reject! {|user| !visible?(user)}
44 notified.reject! {|user| !visible?(user)}
45 notified.collect(&:mail)
45 notified.collect(&:mail)
46 end
46 end
47
47
48 class Version
48 class Version
49 belongs_to :page, :class_name => '::WikiPage', :foreign_key => 'page_id'
49 belongs_to :page, :class_name => '::WikiPage', :foreign_key => 'page_id'
50 belongs_to :author, :class_name => '::User', :foreign_key => 'author_id'
50 belongs_to :author, :class_name => '::User', :foreign_key => 'author_id'
51 attr_protected :data
51 attr_protected :data
52
52
53 acts_as_event :title => Proc.new {|o| "#{l(:label_wiki_edit)}: #{o.page.title} (##{o.version})"},
53 acts_as_event :title => Proc.new {|o| "#{l(:label_wiki_edit)}: #{o.page.title} (##{o.version})"},
54 :description => :comments,
54 :description => :comments,
55 :datetime => :updated_on,
55 :datetime => :updated_on,
56 :type => 'wiki-page',
56 :type => 'wiki-page',
57 :url => Proc.new {|o| {:controller => 'wiki', :id => o.page.wiki.project_id, :page => o.page.title, :version => o.version}}
57 :url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.page.wiki.project, :page => o.page.title, :version => o.version}}
58
58
59 acts_as_activity_provider :type => 'wiki_edits',
59 acts_as_activity_provider :type => 'wiki_edits',
60 :timestamp => "#{WikiContent.versioned_table_name}.updated_on",
60 :timestamp => "#{WikiContent.versioned_table_name}.updated_on",
61 :author_key => "#{WikiContent.versioned_table_name}.author_id",
61 :author_key => "#{WikiContent.versioned_table_name}.author_id",
62 :permission => :view_wiki_edits,
62 :permission => :view_wiki_edits,
63 :find_options => {:select => "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
63 :find_options => {:select => "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
64 "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " +
64 "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " +
65 "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " +
65 "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " +
66 "#{WikiContent.versioned_table_name}.id",
66 "#{WikiContent.versioned_table_name}.id",
67 :joins => "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
67 :joins => "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
68 "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " +
68 "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " +
69 "LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id"}
69 "LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id"}
70
70
71 def text=(plain)
71 def text=(plain)
72 case Setting.wiki_compression
72 case Setting.wiki_compression
73 when 'gzip'
73 when 'gzip'
74 begin
74 begin
75 self.data = Zlib::Deflate.deflate(plain, Zlib::BEST_COMPRESSION)
75 self.data = Zlib::Deflate.deflate(plain, Zlib::BEST_COMPRESSION)
76 self.compression = 'gzip'
76 self.compression = 'gzip'
77 rescue
77 rescue
78 self.data = plain
78 self.data = plain
79 self.compression = ''
79 self.compression = ''
80 end
80 end
81 else
81 else
82 self.data = plain
82 self.data = plain
83 self.compression = ''
83 self.compression = ''
84 end
84 end
85 plain
85 plain
86 end
86 end
87
87
88 def text
88 def text
89 @text ||= case compression
89 @text ||= case compression
90 when 'gzip'
90 when 'gzip'
91 Zlib::Inflate.inflate(data)
91 Zlib::Inflate.inflate(data)
92 else
92 else
93 # uncompressed data
93 # uncompressed data
94 data
94 data
95 end
95 end
96 end
96 end
97
97
98 def project
98 def project
99 page.project
99 page.project
100 end
100 end
101
101
102 # Returns the previous version or nil
102 # Returns the previous version or nil
103 def previous
103 def previous
104 @previous ||= WikiContent::Version.find(:first,
104 @previous ||= WikiContent::Version.find(:first,
105 :order => 'version DESC',
105 :order => 'version DESC',
106 :include => :author,
106 :include => :author,
107 :conditions => ["wiki_content_id = ? AND version < ?", wiki_content_id, version])
107 :conditions => ["wiki_content_id = ? AND version < ?", wiki_content_id, version])
108 end
108 end
109 end
109 end
110 end
110 end
@@ -1,202 +1,202
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
2 # Copyright (C) 2006-2009 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', :id => o.wiki.project, :page => o.title}}
31 :url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.wiki.project, :page => o.title}}
32
32
33 acts_as_searchable :columns => ['title', 'text'],
33 acts_as_searchable :columns => ['title', 'text'],
34 :include => [{:wiki => :project}, :content],
34 :include => [{:wiki => :project}, :content],
35 :project_key => "#{Wiki.table_name}.project_id"
35 :project_key => "#{Wiki.table_name}.project_id"
36
36
37 attr_accessor :redirect_existing_links
37 attr_accessor :redirect_existing_links
38
38
39 validates_presence_of :title
39 validates_presence_of :title
40 validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/
40 validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/
41 validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false
41 validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false
42 validates_associated :content
42 validates_associated :content
43
43
44 # Wiki pages that are protected by default
44 # Wiki pages that are protected by default
45 DEFAULT_PROTECTED_PAGES = %w(sidebar)
45 DEFAULT_PROTECTED_PAGES = %w(sidebar)
46
46
47 def after_initialize
47 def after_initialize
48 if new_record? && DEFAULT_PROTECTED_PAGES.include?(title.to_s.downcase)
48 if new_record? && DEFAULT_PROTECTED_PAGES.include?(title.to_s.downcase)
49 self.protected = true
49 self.protected = true
50 end
50 end
51 end
51 end
52
52
53 def visible?(user=User.current)
53 def visible?(user=User.current)
54 !user.nil? && user.allowed_to?(:view_wiki_pages, project)
54 !user.nil? && user.allowed_to?(:view_wiki_pages, project)
55 end
55 end
56
56
57 def title=(value)
57 def title=(value)
58 value = Wiki.titleize(value)
58 value = Wiki.titleize(value)
59 @previous_title = read_attribute(:title) if @previous_title.blank?
59 @previous_title = read_attribute(:title) if @previous_title.blank?
60 write_attribute(:title, value)
60 write_attribute(:title, value)
61 end
61 end
62
62
63 def before_save
63 def before_save
64 self.title = Wiki.titleize(title)
64 self.title = Wiki.titleize(title)
65 # Manage redirects if the title has changed
65 # Manage redirects if the title has changed
66 if !@previous_title.blank? && (@previous_title != title) && !new_record?
66 if !@previous_title.blank? && (@previous_title != title) && !new_record?
67 # Update redirects that point to the old title
67 # Update redirects that point to the old title
68 wiki.redirects.find_all_by_redirects_to(@previous_title).each do |r|
68 wiki.redirects.find_all_by_redirects_to(@previous_title).each do |r|
69 r.redirects_to = title
69 r.redirects_to = title
70 r.title == r.redirects_to ? r.destroy : r.save
70 r.title == r.redirects_to ? r.destroy : r.save
71 end
71 end
72 # Remove redirects for the new title
72 # Remove redirects for the new title
73 wiki.redirects.find_all_by_title(title).each(&:destroy)
73 wiki.redirects.find_all_by_title(title).each(&:destroy)
74 # Create a redirect to the new title
74 # Create a redirect to the new title
75 wiki.redirects << WikiRedirect.new(:title => @previous_title, :redirects_to => title) unless redirect_existing_links == "0"
75 wiki.redirects << WikiRedirect.new(:title => @previous_title, :redirects_to => title) unless redirect_existing_links == "0"
76 @previous_title = nil
76 @previous_title = nil
77 end
77 end
78 end
78 end
79
79
80 def before_destroy
80 def before_destroy
81 # Remove redirects to this page
81 # Remove redirects to this page
82 wiki.redirects.find_all_by_redirects_to(title).each(&:destroy)
82 wiki.redirects.find_all_by_redirects_to(title).each(&:destroy)
83 end
83 end
84
84
85 def pretty_title
85 def pretty_title
86 WikiPage.pretty_title(title)
86 WikiPage.pretty_title(title)
87 end
87 end
88
88
89 def content_for_version(version=nil)
89 def content_for_version(version=nil)
90 result = content.versions.find_by_version(version.to_i) if version
90 result = content.versions.find_by_version(version.to_i) if version
91 result ||= content
91 result ||= content
92 result
92 result
93 end
93 end
94
94
95 def diff(version_to=nil, version_from=nil)
95 def diff(version_to=nil, version_from=nil)
96 version_to = version_to ? version_to.to_i : self.content.version
96 version_to = version_to ? version_to.to_i : self.content.version
97 version_from = version_from ? version_from.to_i : version_to - 1
97 version_from = version_from ? version_from.to_i : version_to - 1
98 version_to, version_from = version_from, version_to unless version_from < version_to
98 version_to, version_from = version_from, version_to unless version_from < version_to
99
99
100 content_to = content.versions.find_by_version(version_to)
100 content_to = content.versions.find_by_version(version_to)
101 content_from = content.versions.find_by_version(version_from)
101 content_from = content.versions.find_by_version(version_from)
102
102
103 (content_to && content_from) ? WikiDiff.new(content_to, content_from) : nil
103 (content_to && content_from) ? WikiDiff.new(content_to, content_from) : nil
104 end
104 end
105
105
106 def annotate(version=nil)
106 def annotate(version=nil)
107 version = version ? version.to_i : self.content.version
107 version = version ? version.to_i : self.content.version
108 c = content.versions.find_by_version(version)
108 c = content.versions.find_by_version(version)
109 c ? WikiAnnotate.new(c) : nil
109 c ? WikiAnnotate.new(c) : nil
110 end
110 end
111
111
112 def self.pretty_title(str)
112 def self.pretty_title(str)
113 (str && str.is_a?(String)) ? str.tr('_', ' ') : str
113 (str && str.is_a?(String)) ? str.tr('_', ' ') : str
114 end
114 end
115
115
116 def project
116 def project
117 wiki.project
117 wiki.project
118 end
118 end
119
119
120 def text
120 def text
121 content.text if content
121 content.text if content
122 end
122 end
123
123
124 # Returns true if usr is allowed to edit the page, otherwise false
124 # Returns true if usr is allowed to edit the page, otherwise false
125 def editable_by?(usr)
125 def editable_by?(usr)
126 !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project)
126 !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project)
127 end
127 end
128
128
129 def attachments_deletable?(usr=User.current)
129 def attachments_deletable?(usr=User.current)
130 editable_by?(usr) && super(usr)
130 editable_by?(usr) && super(usr)
131 end
131 end
132
132
133 def parent_title
133 def parent_title
134 @parent_title || (self.parent && self.parent.pretty_title)
134 @parent_title || (self.parent && self.parent.pretty_title)
135 end
135 end
136
136
137 def parent_title=(t)
137 def parent_title=(t)
138 @parent_title = t
138 @parent_title = t
139 parent_page = t.blank? ? nil : self.wiki.find_page(t)
139 parent_page = t.blank? ? nil : self.wiki.find_page(t)
140 self.parent = parent_page
140 self.parent = parent_page
141 end
141 end
142
142
143 protected
143 protected
144
144
145 def validate
145 def validate
146 errors.add(:parent_title, :invalid) if !@parent_title.blank? && parent.nil?
146 errors.add(:parent_title, :invalid) if !@parent_title.blank? && parent.nil?
147 errors.add(:parent_title, :circular_dependency) if parent && (parent == self || parent.ancestors.include?(self))
147 errors.add(:parent_title, :circular_dependency) if parent && (parent == self || parent.ancestors.include?(self))
148 errors.add(:parent_title, :not_same_project) if parent && (parent.wiki_id != wiki_id)
148 errors.add(:parent_title, :not_same_project) if parent && (parent.wiki_id != wiki_id)
149 end
149 end
150 end
150 end
151
151
152 class WikiDiff
152 class WikiDiff
153 attr_reader :diff, :words, :content_to, :content_from
153 attr_reader :diff, :words, :content_to, :content_from
154
154
155 def initialize(content_to, content_from)
155 def initialize(content_to, content_from)
156 @content_to = content_to
156 @content_to = content_to
157 @content_from = content_from
157 @content_from = content_from
158 @words = content_to.text.split(/(\s+)/)
158 @words = content_to.text.split(/(\s+)/)
159 @words = @words.select {|word| word != ' '}
159 @words = @words.select {|word| word != ' '}
160 words_from = content_from.text.split(/(\s+)/)
160 words_from = content_from.text.split(/(\s+)/)
161 words_from = words_from.select {|word| word != ' '}
161 words_from = words_from.select {|word| word != ' '}
162 @diff = words_from.diff @words
162 @diff = words_from.diff @words
163 end
163 end
164 end
164 end
165
165
166 class WikiAnnotate
166 class WikiAnnotate
167 attr_reader :lines, :content
167 attr_reader :lines, :content
168
168
169 def initialize(content)
169 def initialize(content)
170 @content = content
170 @content = content
171 current = content
171 current = content
172 current_lines = current.text.split(/\r?\n/)
172 current_lines = current.text.split(/\r?\n/)
173 @lines = current_lines.collect {|t| [nil, nil, t]}
173 @lines = current_lines.collect {|t| [nil, nil, t]}
174 positions = []
174 positions = []
175 current_lines.size.times {|i| positions << i}
175 current_lines.size.times {|i| positions << i}
176 while (current.previous)
176 while (current.previous)
177 d = current.previous.text.split(/\r?\n/).diff(current.text.split(/\r?\n/)).diffs.flatten
177 d = current.previous.text.split(/\r?\n/).diff(current.text.split(/\r?\n/)).diffs.flatten
178 d.each_slice(3) do |s|
178 d.each_slice(3) do |s|
179 sign, line = s[0], s[1]
179 sign, line = s[0], s[1]
180 if sign == '+' && positions[line] && positions[line] != -1
180 if sign == '+' && positions[line] && positions[line] != -1
181 if @lines[positions[line]][0].nil?
181 if @lines[positions[line]][0].nil?
182 @lines[positions[line]][0] = current.version
182 @lines[positions[line]][0] = current.version
183 @lines[positions[line]][1] = current.author
183 @lines[positions[line]][1] = current.author
184 end
184 end
185 end
185 end
186 end
186 end
187 d.each_slice(3) do |s|
187 d.each_slice(3) do |s|
188 sign, line = s[0], s[1]
188 sign, line = s[0], s[1]
189 if sign == '-'
189 if sign == '-'
190 positions.insert(line, -1)
190 positions.insert(line, -1)
191 else
191 else
192 positions[line] = nil
192 positions[line] = nil
193 end
193 end
194 end
194 end
195 positions.compact!
195 positions.compact!
196 # Stop if every line is annotated
196 # Stop if every line is annotated
197 break unless @lines.detect { |line| line[0].nil? }
197 break unless @lines.detect { |line| line[0].nil? }
198 current = current.previous
198 current = current.previous
199 end
199 end
200 @lines.each { |line| line[0] ||= current.version }
200 @lines.each { |line| line[0] ||= current.version }
201 end
201 end
202 end
202 end
General Comments 0
You need to be logged in to leave comments. Login now