-
Notifications
You must be signed in to change notification settings - Fork 9
/
History.py
230 lines (188 loc) · 8.76 KB
/
History.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
"""
Permanent revision history for zwiki pages.
Warning: this implementation might be zodb-cache-unfriendly as revisions
increase.
"""
from AccessControl import getSecurityManager, ClassSecurityInfo
from Globals import InitializeClass
try: from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2 as Folder
except ImportError: from OFS.Folder import Folder # zope 2.7
from Utils import safe_hasattr, sorted, registerSupportFolderId
import re
import Permissions
from OutlineSupport import PersistentOutline
REVISIONS_FOLDER_ID = 'revisions'
registerSupportFolderId(REVISIONS_FOLDER_ID)
class PageHistorySupport:
"""
This mixin provides methods to save, browse and restore zwiki page
revisions. Unlike zope's built-in transaction history, these are
saved forever, as page objects in a revisions subfolder. Individual
revisions may be deleted manually without ill effect.
The methods below should generally work whether they are called on the
latest revision (in the wiki folder), or on an older revision (in the
revisions subfolder), except where noted.
"""
security = ClassSecurityInfo()
def ensureRevisionsFolder(self):
if self.revisionsFolder() is None:
self.folder()._setObject(REVISIONS_FOLDER_ID,Folder(REVISIONS_FOLDER_ID))
def inRevisionsFolder(self):
return self.folder().getId() == REVISIONS_FOLDER_ID
def revisionsFolder(self):
"""Get the revisions subfolder, even called from within it, or
None if it does not exist yet."""
if self.inRevisionsFolder():
return self.folder()
elif safe_hasattr(self.folder().aq_base, REVISIONS_FOLDER_ID):
f = self.folder()[REVISIONS_FOLDER_ID]
if f.isPrincipiaFolderish:
return f
return None
security.declareProtected(Permissions.View, 'revisions')
def revisions(self):
"""
Get a list of this page's revisions, oldest first.
A page's revisions are all the page objects with the same root id
plus a possible dot-number suffix. The one with no suffix is the
latest revision, kept in the main wiki folder; older revisions
have a suffix and are kept in the revisions subfolder.
"""
return self.oldRevisions() + [self.latestRevision()]
def latestRevision(self):
if self.inRevisionsFolder(): f = self.wikiFolder()
else: f = self.folder()
return f[self.getIdBase()]
def oldRevisionIds(self):
f = self.revisionsFolder()
if not f:
return []
else:
isrev = re.compile(r'%s\.\d+$' % self.getIdBase()).match
ids = filter(isrev, list(f.objectIds(spec=self.meta_type)))
# probably in the right order, but let's make sure
ids.sort(lambda a,b: cmp(int(a.split('.')[1]), int(b.split('.')[1])))
return ids
def oldRevisions(self):
return [self.revisionsFolder()[id] for id in self.oldRevisionIds()]
def getIdBase(self):
"""This page's id with any revision number suffix removed."""
return re.sub(r'^(.*)\.\d+$', r'\1', self.getId())
security.declareProtected(Permissions.View, 'revisionCount')
def revisionCount(self):
"""The number of revisions existing for this page."""
return len(self.revisions())
security.declareProtected(Permissions.View, 'revision')
def revision(self, rev):
"""Get the specified revision of this page object (starting from 1)."""
if rev:
# should be no more than one, but you never know
revs = [r for r in self.revisions() if r.revisionNumber()==rev]
if revs: return revs[0]
return None
security.declareProtected(Permissions.View, 'previousRevision')
def previousRevision(self):
"""Get the oldest saved revision of this page previous to this one."""
r = self.previousRevisionNumber()
if r: return self.revision(r)
else: return None
security.declareProtected(Permissions.View, 'nextRevision')
def nextRevision(self):
"""Get the next saved revision of this page after this one."""
r = self.nextRevisionNumber()
if r: return self.revision(r)
else: return None
security.declareProtected(Permissions.View, 'revisionNumber')
def revisionNumber(self):
"""Get this page's revision number."""
return getattr(self.aq_base,'revision_number',1)
def revisionNumberFromId(self):
m = re.search(r'\.(\d+)$',self.getId())
if m: return int(m.group(1))
else: return None
def revisionNumbers(self):
"""The revision numbers of all available revisions of this page
(sorted)."""
return sorted([r.revisionNumber() for r in self.revisions()])
def oldRevisionNumbers(self):
"""The revision numbers of all old revisions, excluding the latest
one (sorted)."""
return sorted([r.revisionNumber() for r in self.oldRevisions()])
def firstRevisionNumber(self):
"""The revision number of the earliest saved revision of this page."""
return self.revisionNumbers()[0]
def lastRevisionNumber(self):
"""The revision number of the latest saved revision of this page."""
return self.revisionNumbers()[-1]
def previousRevisionNumber(self):
"""The number of the latest saved revision before this one, or None."""
revnos = self.revisionNumbers()
i = revnos.index(self.revisionNumber())
if i: return revnos[i-1]
else: return None
def nextRevisionNumber(self):
"""The number of the next saved revision after this one, or None."""
revnos = self.revisionNumbers()
i = revnos.index(self.revisionNumber())
if i < len(revnos)-1: return revnos[i+1]
else: return None
def revisionNumberBefore(self, username): # -> revision number | none
# depends on: self, revisions
"""The revision number of the last edit not by username, or None."""
for r in range(self.revisionCount(),0,-1):
if self.revision(r).lastEditor() != username:
return r
return None
def ensureMyRevisionNumberIsLatest(self):
"""Make sure this page's revision number is larger than that of
any existing revisions. Don't bother updating catalog."""
oldrevs = self.oldRevisionNumbers()
r = oldrevs and (oldrevs[-1] + 1) or 1
if self.revisionNumber() != r: self.revision_number = r
def saveRevision(self, REQUEST=None):
"""Save a copy of this page as a new revision in the revisions
folder and increment its revision number. This has no effect if
called on a revision object, or a non-ZODB object (such as a
temporary page object created by plone's portal_factory).
NB normally the revision number just increments by 1, but if there
is already a revision object with that number (which can happen
from renaming, eg), we first bump this page's revision number to
the number after all existing revisions.
"""
def inPortalFactory(self):
return self.inCMF() and self.folder().getId() == 'portal_factory'
if self.inRevisionsFolder() or inPortalFactory(self): return
self.ensureRevisionsFolder()
self.ensureMyRevisionNumberIsLatest()
rid = '%s.%d' % (self.getId(), self.revisionNumber())
ob = self._getCopy(self.folder())
ob._setId(rid)
# kludge so the following won't update an outline cache
# in the revisions folder (hopefully thread-safe, otherwise
# escalate to "horrible kludge"): XXX how to test ?
manage_afterAdd = self.__class__.manage_afterAdd
wikiOutline = self.__class__.wikiOutline
self.__class__.manage_afterAdd = lambda self,item,container:None
self.__class__.wikiOutline = lambda self:PersistentOutline()
self.revisionsFolder()._setObject(rid, ob)
# clean up after kludge
self.__class__.manage_afterAdd = manage_afterAdd
self.__class__.wikiOutline = wikiOutline
# and increment
self.revision_number = self.revisionNumber() + 1
# backwards compatibility / temporary
def forwardRev(self,rev): return self.revisionCount() - rev - 1
def lastlog(self, rev=0, withQuotes=0):
"""
Get the log note from an earlier revision of this page.
Just a quick helper for diff browsing.
"""
rev = self.forwardRev(int(rev))
note = self.revisions()[rev].lastLog()
match = re.search(r'"(.*)"',note)
if match:
if withQuotes: return match.group()
else: return match.group(1)
else:
return ''
InitializeClass(PageHistorySupport)