-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrevisioning_api.inc
476 lines (444 loc) · 16.2 KB
/
revisioning_api.inc
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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
<?php
// $Id$
/**
* @file
* API functions of Revisioning module
*
* Reusable functions that do the dirty work.
*/
define('REVISION_ARCHIVED', 0);
define('REVISION_CURRENT', 1);
define('REVISION_PENDING', 2);
/**
* Some naming conventions
*
* Pending:
* - revision with (vid > current_vid) of ANY node
* OR single revision of UNPUBLISHED node
* Current, published:
* - revision with (vid == current_vid) of PUBLISHED node
* Archived:
* - all other revisions, i.e.
* revision with (vid < current_vid) of ANY node
* OR revision with (vid == current_vid) of UNPUBLISHED node
*
* Note: these will change when Revisioning is going to store revision states
* independently from vid number (e.g. in different table).
*/
/**
* Return a single or all possible revision state names.
*
* @param $state
* optional state id, as defined in revisioning_api.inc
* @return
* if $state is provided, state name. Otherwise, state names array keyed by state id.
*/
function revisioning_revision_states($state = NULL) {
static $states;
$states = array(
REVISION_ARCHIVED => t('Archived'),
REVISION_CURRENT => t('Current, published'),
REVISION_PENDING => t('Pending'),
);
return $state === NULL ? $states : $states[$state];
}
/**
* Return TRUE when either of the following is true:
* o the supplied node has at least one revision more recent than the current
* o the node is not yet published and consists of a single revision
*
* Relies on vid, current_revision_id and num_revisions set on the node object,
* see function node_tools_nodeapi()
*
* @param $node
* @return TRUE, if node is pending according to the above definition
*/
function _revisioning_node_is_pending($node) {
return ($node->vid > $node->current_revision_id) || (!$node->status && $node->num_revisions == 1);
}
/**
* Implementation of hook_revisionapi().
*
* Act on various revision events.
*
* @param $op
* Operation
* @param $node
* Node of current operation (loaded with vid of the operation).
*
* "Pre" operations can be useful to get values before they are lost or changed,
* for example, to save a backup of revision before it's deleted.
* Also, for "pre" operations vetoing mechanics could be implemented, so it
* would be possible to veto an operation via hook_revisionapi(). For example,
* when the hook is returning FALSE, operation will be vetoed.
*
* @TODO: Add more operations if needed.
*/
function revisioning_revisionapi($op, $node) {
switch ($op) {
case 'pre revert':
// Invoke corresponding Rules event
if (module_exists('rules')) {
rules_invoke_event('revisioning_pre_revert', $node);
}
break;
case 'post revert':
// Invoke the revisioning trigger passing 'revert' as the operation
if (module_exists('trigger')) {
module_invoke_all('revisioning', 'revert', $node, $node->vid);
}
// Invoke corresponding Rules event
if (module_exists('rules')) {
rules_invoke_event('revisioning_post_revert', $node);
}
break;
case 'pre publish':
// Invoke corresponding Rules event
if (module_exists('rules')) {
rules_invoke_event('revisioning_pre_publish', $node);
}
break;
case 'post publish':
// Invoke the revisioning trigger passing 'publish' as the operation
if (module_exists('trigger')) {
module_invoke_all('revisioning', 'publish', $node);
}
// Invoke corresponding Rules event
if (module_exists('rules')) {
rules_invoke_event('revisioning_post_publish', $node);
}
break;
//case 'pre unpublish':
// Not implemented: do we really need it ?
case 'post unpublish':
// Invoke the revisioning trigger passing 'unpublish' as the operation
if (module_exists('trigger')) {
module_invoke_all('revisioning', 'unpublish', $node);
}
// Invoke corresponding Rules event
if (module_exists('rules')) {
rules_invoke_event('revisioning_post_unpublish', $node);
}
break;
case 'pre delete':
// Invoke corresponding Rules event
if (module_exists('rules')) {
rules_invoke_event('revisioning_pre_delete', $node);
}
break;
case 'post delete':
break;
}
}
/**
* Get the id of the latest revision belonging to a node.
* @param
* $nid, id of the node
* @return
* ID of the latest revision.
*/
function revisioning_get_latest_revision_id($nid) {
return db_result(db_query('SELECT MAX(vid) FROM {node_revisions} WHERE nid=%d', $nid));
}
/**
* Revert node to selected revision without changing its publication status.
*
* @param $node
* Target $node object (loaded with target revision) or nid of target node
* @param $vid
* Optional vid of revision to revert to, if provided $node must not be an object.
*/
function _revisioning_revertpublish_revision(&$node, $vid = NULL) {
$node_revision = is_object($node) ? $node : node_load($node, $vid);
module_invoke_all('revisionapi', 'pre revert', $node_revision);
_revisioning_revert_revision($node_revision);
module_invoke_all('revisionapi', 'post revert', $node_revision);
}
/**
* Revert node to selected revision without publishing it.
*
* This is same as node_revision_revert_confirm_submit() in node_pages.inc,
* except it doesn't put any messages on screen.
*
* @param $node
* Target $node object (loaded with target revision) or nid of target node
* @param $vid
* optional vid of revision to revert to, if provided $node is not an object.
*/
function _revisioning_revert_revision(&$node, $vid = NULL) {
$node_revision = is_object($node) ? $node : node_load($node, $vid);
$node_revision->revision = 1;
$node_revision->log = t('Copy of the revision from %date.', array('%date' => format_date($node_revision->revision_timestamp)));
if (module_exists('taxonomy')) {
$node_revision->taxonomy = array_keys($node_revision->taxonomy);
}
node_save($node_revision);
watchdog('content', '@type: reverted %title revision %revision.', array('@type' => $node_revision->type, '%title' => $node_revision->title, '%revision' => $node_revision->vid));
}
/**
* Publish node, without calling node_save()
*
* @param $node
* Target $node object or nid of target node
*/
function _revisioning_publish_node($node) {
$nid = is_object($node) ? $node->nid : $node;
db_query("UPDATE {node} SET status=1 WHERE nid=%d", $nid);
cache_clear_all();
}
/**
* Unpublish node, without calling node_save().
*
* @param $node
* Target $node object or nid of target node
*/
function _revisioning_unpublish_node($node) {
$nid = is_object($node) ? $node->nid : $node;
db_query("UPDATE {node} SET status=0 WHERE nid=%d", $nid);
cache_clear_all();
}
/**
* Delete selected revision of node, provided it's not current.
*
* This is same as node_revision_delete_confirm_submit() in node_pages.inc,
* except it doesn't put any messages on the screen. This way it becomes
* reusable (eg. in actions).
* Since we are calling nodeapi as in node_revision_delete_confirm_submit(), we
* invoke our "post delete" revisionapi hook in nodeapi. This way revisionapi
* hooks work the same way both with "delete revision" submit handler and when
* this function is called, and we don't invoke revisionapi "post delete" hook
* twice.
*
* @param $node
* Target $node object (loaded with target revision) or nid of target node
* @param $vid
* optional vid of revision to delete, if provided $node is not object.
*
* @TODO: Insert check to prevent deletion of current revision of node.
*/
function _revisioning_delete_revision(&$node, $vid = NULL) {
$node_revision = is_object($node) ? $node : node_load($node, $vid);
module_invoke_all('revisionapi', 'pre delete', $node_revision);
db_query("DELETE FROM {node_revisions} WHERE nid = %d AND vid = %d", $node_revision->nid, $node_revision->vid);
node_invoke_nodeapi($node_revision, 'delete revision');
watchdog('content', '@type: deleted %title revision %revision.', array('@type' => $node_revision->type, '%title' => $node_revision->title, '%revision' => $node_revision->vid));
}
/**
* Unpublish revision (i.e. the node).
*
* Note that no check is made as to whether the initiating user has permission
* to unpublish this node.
*
* @param $node
* Target $node object or nid of target node
*
* @TODO: Shouldn't we invoke hook_nodeapi('update'..) too, since we are updating node?
*/
function _revisioning_unpublish_revision(&$node) {
$node_revision = is_object($node) ? $node : node_load($node);
module_invoke_all('revisionapi', 'pre unpublish', $node_revision);
_revisioning_unpublish_node($node_revision->nid);
watchdog('content', 'Unpublished @type %title', array('@type' => $node_revision->type, '%title' => $node_revision->title), WATCHDOG_NOTICE, l(t('view'), "node/$node_revision->nid"));
module_invoke_all('revisionapi', 'post unpublish', $node_revision);
}
/**
* Make the supplied revision of the node current and publish it.
* It is the caller's responsibility to provide proper revision.
* Note that no check is made as to whether the initiating user has permission
* to publish this revision.
*
* @param $node
* Target $node object (loaded with target revision) or nid of target node
* @param $vid
* optional vid of revision to make current, if provided $node is not object.
*
* @TODO: Shouldn't we invoke hook_nodeapi('update'..) too, since we are updating node ?
*/
function _revisioning_publish_revision(&$node, $vid = NULL) {
$node_revision = is_object($node) ? $node : node_load($node, $vid);
module_invoke_all('revisionapi', 'pre publish', $node_revision);
// Update node table, making sure the "published" (ie. status) flag is set
db_query("UPDATE {node} SET vid=%d, title='%s', status=1 WHERE nid=%d", $node_revision->vid, $node_revision->title, $node_revision->nid);
cache_clear_all();
watchdog('content', 'Published rev #%revision of @type %title', array('@type' => $node_revision->type, '%title' => $node_revision->title, '%revision' => $node_revision->vid), WATCHDOG_NOTICE, l(t('view'), "node/$node_revision->nid/revisions/$node_revision->vid/view"));
module_invoke_all('revisionapi', 'post publish', $node_revision);
}
/**
* Find the most recent pending revision, make it current, unless it already is
* and publish node.
* Note that no check is made as to whether the initiating user has permission
* to publish this node.
*
* @param $node
* The node object whose latest pending revision is to be published
* @return
* TRUE if operation was successful, FALSE if there is no pending revision to
* publish
*/
function _revisioning_publish_latest_revision(&$node) {
// Get latest pending revision or take the current provided it's UNpublished
$latest_pending = array_shift(_revisioning_get_pending_revisions($node->nid));
if (!$latest_pending && !$node->status && $node->is_current) {
_revisioning_publish_revision($node);
return TRUE;
}
if ($latest_pending) {
_revisioning_publish_revision($node->nid, $latest_pending->vid);
return TRUE;
}
return FALSE;
}
/**
* Return a count of the number of revisions newer than the supplied vid.
*
* @param $vid
* The reference vid.
* @param $nid
* The id of the node.
* @return
* integer
*/
function _revisioning_get_number_of_revisions_newer_than($vid, $nid) {
return db_result(db_query("SELECT COUNT(*) FROM {node} n INNER JOIN {node_revisions} r ON n.nid=r.nid WHERE (r.vid>%d AND n.nid=%d)", $vid, $nid));
}
/**
* Return a count of the number of revisions newer than the current revision.
*
* @param $nid
* The id of the node.
* @return
* integer
*/
function _revisioning_get_number_of_pending_revisions($nid) {
return db_result(db_query("SELECT COUNT(*) FROM {node} n INNER JOIN {node_revisions} r ON n.nid=r.nid WHERE (r.vid>n.vid AND n.nid=%d)", $nid));
}
/**
* Retrieve a list of revisions with a vid greater than the current.
*
* @param $nid
* The node id to retrieve.
* @return
* An array of revisions (latest first), each containing vid, title and
* content type.
*/
function _revisioning_get_pending_revisions($nid) {
$sql = "SELECT r.vid, r.title, n.type FROM {node} n INNER JOIN {node_revisions} r ON n.nid=r.nid WHERE (r.vid>n.vid AND n.nid=%d) ORDER BY r.vid DESC";
$result = db_query($sql, $nid);
$revisions = array();
while ($revision = db_fetch_object($result)) {
$revisions[$revision->vid] = $revision;
}
return $revisions;
}
/**
* Retrieve a list of all revisions (archive, current, pending) belonging to
* the supplied node.
*
* @param $nid
* The node id to retrieve.
* @param $include_taxonomy_terms
* Whether to also retrieve the taxonomy terms for each revision
* @return
* An array of revision objects, each with published flag, log message, vid,
* title, timestamp and name of user that created the revision
*/
function _revisioning_get_all_revisions_for_node($nid, $include_taxonomy_terms = FALSE) {
$sql_select = 'SELECT n.type, n.status, r.vid, r.title, r.log, r.uid, r.timestamp, u.name';
$sql_from = ' FROM {node_revisions} r LEFT JOIN {node} n ON n.vid=r.vid INNER JOIN {users} u ON u.uid=r.uid';
$sql_where = ' WHERE r.nid=%d ORDER BY r.vid DESC';
if ($include_taxonomy_terms) {
$sql_select .= ', td.name AS term';
$sql_from .= ' LEFT JOIN {term_node} tn ON r.vid=tn.vid LEFT JOIN {term_data} td ON tn.tid=td.tid';
$sql_where .= ', term ASC';
}
$sql = $sql_select . $sql_from . $sql_where;
$result = db_query($sql, $nid);
$revisions = array();
while ($revision = db_fetch_object($result)) {
if (empty($revisions[$revision->vid])) {
$revisions[$revision->vid] = $revision;
}
elseif ($include_taxonomy_terms) {
// If a revision has more than one taxonomy term, these will be returned
// by the query as seperate objects differing only in their term fields.
$existing_revision = $revisions[$revision->vid];
$existing_revision->term .= '/'. $revision->term;
}
}
return $revisions;
}
/**
* Return revision type of the supplied node.
*
* @param &$node
* Node object to check
* @return
* Revision type
*/
function _revisioning_revision_is(&$node) {
if ($node->is_pending) {
return REVISION_PENDING;
}
return ($node->is_current && $node->status) ? REVISION_CURRENT : REVISION_ARCHIVED;
}
/**
* Return a string with details about the node that is about to be displayed.
*
* Called from revisioning_nodeapi().
*
* @param $node
* The node that is about to be viewed
* @return
* A translatable message containing details about the node
*/
function _revisioning_node_info_msg($node) {
// Get username for the revision, not the creator of the node
$revision_author = user_load($node->revision_uid);
$placeholder_data = array(
'@content_type' => $node->type,
'%title' => $node->title,
'!author' => theme('username', $revision_author),
'@date' => format_date($node->revision_timestamp, 'small'),
);
$revision_type = _revisioning_revision_is($node);
switch ($revision_type) {
case REVISION_PENDING:
return t('Displaying <em>pending</em> revision of @content_type %title, last modified by !author on @date', $placeholder_data);
case REVISION_CURRENT:
return t('Displaying <em>current, published</em> revision of @content_type %title, last modified by !author on @date', $placeholder_data);
case REVISION_ARCHIVED:
return t('Displaying <em>archived</em> revision of @content_type %title, last modified by !author on @date', $placeholder_data);
}
}
/**
* Return TRUE only if the user account has ALL of the supplied permissions.
*
* @param $permissions
* An array of permissions (strings)
* @param $account
* The user account object. Defaults to the current user if omitted.
* @return bool
*/
function revisioning_user_all_access($permissions, $account = NULL) {
foreach ($permissions as $permission) {
if (!user_access($permission, $account)) {
return FALSE;
}
}
return TRUE;
}
/**
* Return an array of names of content types that are subject to moderation.
*
* @return array of strings, may be empty
*/
function revisioning_moderated_content_types() {
$moderated_content_types = array();
foreach (node_get_types() as $type) {
$content_type = check_plain($type->type);
if (node_tools_content_is_moderated($content_type)) {
$moderated_content_types[] = $content_type;
}
}
return $moderated_content_types;
}