1 | <?php |
---|
2 | // $Id: revision_moderation.module,v 1.39.2.12 2009/12/17 06:18:07 brauerranch Exp $ |
---|
3 | |
---|
4 | /** |
---|
5 | * @file |
---|
6 | * Allows moderation of node revisions while existing revisions stay visible. |
---|
7 | */ |
---|
8 | |
---|
9 | // Actions module support. |
---|
10 | include_once drupal_get_path('module', 'revision_moderation') .'/revision_moderation_actions.inc'; |
---|
11 | |
---|
12 | /** |
---|
13 | * Implementation of hook_menu(). |
---|
14 | */ |
---|
15 | function revision_moderation_menu() { |
---|
16 | $items = array(); |
---|
17 | |
---|
18 | // Admin menu |
---|
19 | $items['admin/content/node/revisions'] = array( |
---|
20 | 'title' => t('Pending revisions'), |
---|
21 | 'page callback' => 'revision_moderation_pending_revisions_admin', |
---|
22 | 'access arguments' => array('administer nodes'), |
---|
23 | 'type' => MENU_LOCAL_TASK, |
---|
24 | ); |
---|
25 | |
---|
26 | // Admin menu |
---|
27 | $items['admin/settings/revision_moderation'] = array( |
---|
28 | 'title' => t('Revision moderation'), |
---|
29 | 'page callback' => 'drupal_get_form', |
---|
30 | 'page arguments' => array('revision_moderation_settings'), |
---|
31 | 'description' => t('Configure revision publishing options.'), |
---|
32 | 'access arguments' => array('administer nodes'), |
---|
33 | ); |
---|
34 | |
---|
35 | // Callback to allow users to edit revisions. |
---|
36 | $items['node/%node/revisions/%/edit'] = array( |
---|
37 | 'title' => t('Edit revision'), |
---|
38 | 'load arguments' => array(3), |
---|
39 | 'page callback' => 'revision_moderation_edit', |
---|
40 | 'page arguments' => array(1), |
---|
41 | 'access callback' => '_node_revision_access', |
---|
42 | 'access arguments' => array(1, 'update'), |
---|
43 | 'file' => 'node.pages.inc', |
---|
44 | 'file path' => drupal_get_path('module', 'node'), |
---|
45 | 'type' => MENU_CALLBACK, |
---|
46 | ); |
---|
47 | |
---|
48 | // Callback to allow users to publish revisions directly. |
---|
49 | $items['node/%node/revisions/%/publish'] = array( |
---|
50 | 'title' => t('Publish revision'), |
---|
51 | 'load arguments' => array(3), |
---|
52 | 'page callback' => 'drupal_get_form', |
---|
53 | 'page arguments' => array('revision_moderation_publish_confirm', 1), |
---|
54 | 'access callback' => '_node_revision_access', |
---|
55 | 'access arguments' => array(1, 'update'), |
---|
56 | 'type' => MENU_CALLBACK, |
---|
57 | ); |
---|
58 | |
---|
59 | return $items; |
---|
60 | } |
---|
61 | |
---|
62 | /** |
---|
63 | * Menu permission callback. |
---|
64 | */ |
---|
65 | function revision_moderation_admin_perm($nid) { |
---|
66 | $node = node_load($nid); |
---|
67 | $access = user_access('administer nodes') || (user_access('view revisions') && node_access('update', $node)); |
---|
68 | return $access; |
---|
69 | } |
---|
70 | |
---|
71 | /** |
---|
72 | * Menu callback; admin settings page. |
---|
73 | */ |
---|
74 | function revision_moderation_settings() { |
---|
75 | $form['revision_moderation_exempt'] = array( |
---|
76 | '#type' => 'checkbox', |
---|
77 | '#title' => t('Exempt administrators from revision moderation'), |
---|
78 | '#default_value' => variable_get('revision_moderation_exempt', 1), |
---|
79 | '#description' => t('With this option enabled, users with the "administer nodes" privilege will bypass the moderation system, and their revisions will be published immediately.'), |
---|
80 | ); |
---|
81 | |
---|
82 | return system_settings_form($form); |
---|
83 | } |
---|
84 | |
---|
85 | /** |
---|
86 | * Implementation of hook_form_alter(). |
---|
87 | */ |
---|
88 | function revision_moderation_form_alter(&$form, $form_state, $form_id) { |
---|
89 | // On node edit forms, add in the "New revisions in moderation" option. |
---|
90 | if (isset($form['#id']) && $form['#id'] == 'node-form') { |
---|
91 | $default_value = in_array('revision_moderation', variable_get("node_options_{$form['type']['#value']}", array('status', 'promote'))); |
---|
92 | if ($form['nid']['#value']) { |
---|
93 | $result = db_result(db_query('SELECT revision_moderation FROM {revision_moderation} WHERE nid = %d', $form['nid']['#value'])); |
---|
94 | if ($result !== FALSE) { |
---|
95 | $default_value = $result; |
---|
96 | } |
---|
97 | } |
---|
98 | // Only show the checkbox if user has 'administer nodes' privileges. |
---|
99 | if (!empty($node->revision) || user_access('administer nodes')) { |
---|
100 | $form['revision_information']['revision_moderation'] = array( |
---|
101 | '#type' => 'checkbox', |
---|
102 | '#title' => t('New revisions in moderation'), |
---|
103 | '#default_value' => $default_value, |
---|
104 | ); |
---|
105 | } |
---|
106 | else { |
---|
107 | $form['revision_moderation'] = array( |
---|
108 | '#type' => 'value', |
---|
109 | '#value' => $default_value, |
---|
110 | ); |
---|
111 | } |
---|
112 | } |
---|
113 | // Also add option to node settings form |
---|
114 | elseif ($form_id == 'node_type_form') { |
---|
115 | $form['workflow']['node_options']['#options']['revision_moderation'] = t('New revisions in moderation'); |
---|
116 | } |
---|
117 | } |
---|
118 | |
---|
119 | /** |
---|
120 | * Implementation of hook_nodeapi(). |
---|
121 | */ |
---|
122 | function revision_moderation_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) { |
---|
123 | $args = arg(); |
---|
124 | switch ($op) { |
---|
125 | case 'insert': |
---|
126 | // Store revision moderation setting of this node. |
---|
127 | drupal_write_record('revision_moderation', $node); |
---|
128 | break; |
---|
129 | |
---|
130 | case 'update': |
---|
131 | // Update revision moderation setting of this node. |
---|
132 | drupal_write_record('revision_moderation', $node, 'nid'); |
---|
133 | break; |
---|
134 | |
---|
135 | case 'delete': |
---|
136 | // Delete record from revision_moderation table when node is deleted. |
---|
137 | db_query('DELETE FROM {revision_moderation} WHERE nid = %d', $node->nid); |
---|
138 | break; |
---|
139 | |
---|
140 | case 'load': |
---|
141 | // Set a revision_moderation property which can be checked later. |
---|
142 | $node->revision_moderation = db_result(db_query('SELECT revision_moderation FROM {revision_moderation} WHERE nid = %d', $node->nid)); |
---|
143 | break; |
---|
144 | |
---|
145 | case 'view': |
---|
146 | // Cannot use _node_revision_access() here, it's static cached with 1 op |
---|
147 | $access_update = user_access('revert revisions'); |
---|
148 | $access_delete = user_access('delete revisions'); |
---|
149 | // Display more descriptive message at the top of node revision views, including operations |
---|
150 | // that the current user has available to them. |
---|
151 | $current_vid = db_result(db_query('SELECT vid FROM {node} WHERE nid = %d', $node->nid)); |
---|
152 | if ($node->vid != $current_vid) { |
---|
153 | $links = array(); // Array of links to show along with the message. |
---|
154 | if ($access_update) { |
---|
155 | // Add a link directly to the diff if we have Diff module installed. |
---|
156 | if (module_exists('diff')) { |
---|
157 | if ($node->vid > $current_vid) { |
---|
158 | $difflink = "node/$node->nid/revisions/view/$current_vid/$node->vid"; |
---|
159 | } |
---|
160 | else { |
---|
161 | $difflink = "node/$node->nid/revisions/view/$node->vid/$current_vid"; |
---|
162 | } |
---|
163 | $links[] = l(t('Compare revisions'), $difflink); |
---|
164 | } |
---|
165 | $links[] = l(t('Edit revision'), "node/$node->nid/revisions/$node->vid/edit"); |
---|
166 | // If this revision is old, show an option to revert to it. |
---|
167 | // Otherwise, show an option to publish it. |
---|
168 | if ($node->vid < $current_vid) { |
---|
169 | $links[] = l(t('Revert to revision'), "node/$node->nid/revisions/$node->vid/revert"); |
---|
170 | } |
---|
171 | else { |
---|
172 | $links[] = l(t('Publish revision'), "node/$node->nid/revisions/$node->vid/publish"); |
---|
173 | } |
---|
174 | } |
---|
175 | if ($access_delete) { |
---|
176 | $links[] = l(t('Delete revision'), "node/$node->nid/revisions/$node->vid/delete"); |
---|
177 | } |
---|
178 | // Get username for the revision rather than the original node. |
---|
179 | $revision_author = user_load($node->revision_uid); |
---|
180 | drupal_set_message(t('You are currently viewing a revision of this post created on @date by !author.', array('@date' => format_date($node->revision_timestamp, 'small'), '!author' => theme('username', $revision_author))) . theme('item_list', $links)); |
---|
181 | } |
---|
182 | elseif ($node->revision_moderation == 1 && !$teaser) { |
---|
183 | // Notify admin if a node has pending revisions. |
---|
184 | if ($access_update && arg(2) != 'revisions' && revision_moderation_get_node_pending_revisions($node->nid)) { |
---|
185 | drupal_set_message(t('This post has one or more pending revisions: <a href="@list">view list of revisions</a>.', array('@list' => url("node/$node->nid/revisions")))); |
---|
186 | } |
---|
187 | } |
---|
188 | break; |
---|
189 | } |
---|
190 | |
---|
191 | // Only do this logic for non-admin users on nodes with revision moderation |
---|
192 | // turned on. |
---|
193 | // And not editing a chose revision |
---|
194 | if ($node->nid && $node->revision_moderation == 1 && arg(2) != 'revisions' |
---|
195 | && (!user_access('administer nodes') || !variable_get('revision_moderation_exempt', 1))) { |
---|
196 | switch ($op) { |
---|
197 | case 'prepare': |
---|
198 | // If user has a pending revision for this node, load the latest version of |
---|
199 | // it instead. |
---|
200 | if ($revisions = revision_moderation_get_node_pending_revisions($node->nid)) { |
---|
201 | global $user; |
---|
202 | foreach ($revisions as $revision) { |
---|
203 | if ($revision->uid == $user->uid) { |
---|
204 | drupal_set_message(t('Editing your latest revision, which is still pending moderation.')); |
---|
205 | $node = node_load($node->nid, $revision->vid); |
---|
206 | break; |
---|
207 | } |
---|
208 | } |
---|
209 | } |
---|
210 | break; |
---|
211 | |
---|
212 | case 'presave': |
---|
213 | $current_vid = db_result(db_query('SELECT vid FROM {node} WHERE nid = %d', $node->nid)); |
---|
214 | $node->original_node = node_load($node->nid, $current_vid); |
---|
215 | break; |
---|
216 | |
---|
217 | case 'update': |
---|
218 | if (isset($node->original_node)) { |
---|
219 | // Update node table's vid to the original value. |
---|
220 | |
---|
221 | db_query("UPDATE {node} SET vid = %d, title = '%s', status = %d, moderate = %d WHERE nid = %d", $node->original_node->vid, $node->original_node->title, $node->original_node->status, $node->original_node->moderate, $node->nid); |
---|
222 | |
---|
223 | // If node doesn't exist in revision_moderation table, add it. |
---|
224 | $in_db = db_result(db_query("SELECT revision_moderation FROM {revision_moderation} WHERE nid = %d", $node->nid)); |
---|
225 | if ($in_db === FALSE) { |
---|
226 | db_query("INSERT INTO {revision_moderation} (nid, revision_moderation) VALUES(%d, 1)", $node->nid); |
---|
227 | } |
---|
228 | |
---|
229 | drupal_set_message(t('Your changes have been submitted for moderation.')); |
---|
230 | } |
---|
231 | break; |
---|
232 | } |
---|
233 | } |
---|
234 | else if ($node->nid && $node->revision_moderation == 1 && end($args) == 'edit') { |
---|
235 | switch ($op) { |
---|
236 | case 'prepare': |
---|
237 | $revision_author = user_load($node->revision_uid); |
---|
238 | drupal_set_message(t('You are currently editing a revision of this post created on @date by !author.', array('@date' => format_date($node->revision_timestamp, 'small'), '!author' => theme('username', $revision_author)))); |
---|
239 | break; |
---|
240 | case 'presave': |
---|
241 | $current_vid = db_result(db_query('SELECT vid FROM {node} WHERE nid = %d', $node->nid)); |
---|
242 | $node->original_node = node_load($node->nid, $current_vid); |
---|
243 | break; |
---|
244 | |
---|
245 | case 'update': |
---|
246 | if (isset($node->original_node)) { |
---|
247 | // Update node table's vid to the original value. |
---|
248 | |
---|
249 | db_query("UPDATE {node} SET vid = %d, title = '%s', status = %d, moderate = %d WHERE nid = %d", $node->original_node->vid, $node->original_node->title, $node->original_node->status, $node->original_node->moderate, $node->nid); |
---|
250 | drupal_set_message(t('Your changes have been submitted for moderation.')); |
---|
251 | } |
---|
252 | break; |
---|
253 | } |
---|
254 | } |
---|
255 | |
---|
256 | } |
---|
257 | |
---|
258 | /** |
---|
259 | * Implementation of hook_block(). |
---|
260 | */ |
---|
261 | function revision_moderation_block($op = 'list', $delta = 0, $edit = array()) { |
---|
262 | if ($op == 'list') { |
---|
263 | $blocks[0]['info'] = t('Pending revisions'); |
---|
264 | |
---|
265 | return $blocks; |
---|
266 | } |
---|
267 | elseif ($op == 'view') { |
---|
268 | $block = array(); |
---|
269 | |
---|
270 | if (user_access('administer nodes')) { |
---|
271 | $output = ''; |
---|
272 | $list = array(); |
---|
273 | |
---|
274 | $nodes = revision_moderation_get_all_pending_revisions(10); |
---|
275 | if (count($nodes)) { |
---|
276 | foreach ($nodes as $node) { |
---|
277 | $list[] = l($node->title, "node/$node->nid/revisions/$node->vid/view"); |
---|
278 | } |
---|
279 | $output .= theme('item_list', $list); |
---|
280 | $output .= '<p>'. l(t('View all pending revisions'), 'admin/content/node/revisions') .'</p>'; |
---|
281 | } |
---|
282 | else { |
---|
283 | $output .= t('No pending revisions found.'); |
---|
284 | } |
---|
285 | |
---|
286 | $block['subject'] = t('Pending revisions'); |
---|
287 | $block['content'] = $output; |
---|
288 | } |
---|
289 | |
---|
290 | return $block; |
---|
291 | } |
---|
292 | } |
---|
293 | |
---|
294 | /** |
---|
295 | * Menu callback to display list of nodes with pending revisions. |
---|
296 | */ |
---|
297 | function revision_moderation_pending_revisions_admin() { |
---|
298 | return theme('revision_moderation_pending_revisions_admin'); |
---|
299 | } |
---|
300 | |
---|
301 | /** |
---|
302 | * Implementation of hook_theme(). |
---|
303 | */ |
---|
304 | function revision_moderation_theme() { |
---|
305 | return array( |
---|
306 | 'revision_moderation_pending_revisions_admin' => array( |
---|
307 | 'arguments' => array(), |
---|
308 | ), |
---|
309 | ); |
---|
310 | } |
---|
311 | |
---|
312 | /** |
---|
313 | * Displays list of nodes with pending revisions. |
---|
314 | */ |
---|
315 | function theme_revision_moderation_pending_revisions_admin() { |
---|
316 | $nodes = revision_moderation_get_all_pending_revisions(50); |
---|
317 | if (count($nodes)) { |
---|
318 | $header = array( |
---|
319 | t('Title'), |
---|
320 | t('Type'), |
---|
321 | t('Updated by'), |
---|
322 | t('Last updated'), |
---|
323 | ); |
---|
324 | $rows = array(); |
---|
325 | foreach ($nodes as $node) { |
---|
326 | $rows[] = array( |
---|
327 | l($node->title, "node/$node->nid/revisions"), |
---|
328 | check_plain(node_get_types('name', $node)), |
---|
329 | theme('username', user_load(array('uid' => $node->uid))), |
---|
330 | format_date($node->timestamp), |
---|
331 | ); |
---|
332 | } |
---|
333 | return theme('table', $header, $rows); |
---|
334 | } |
---|
335 | else { |
---|
336 | return '<p>'. t('No pending revisions found.') .'</p>'; |
---|
337 | } |
---|
338 | } |
---|
339 | |
---|
340 | /** |
---|
341 | * Retrieve list of all pending revisions. |
---|
342 | * |
---|
343 | * @param $limit |
---|
344 | * The number of pending revisions to retrieve. |
---|
345 | */ |
---|
346 | function revision_moderation_get_all_pending_revisions($limit) { |
---|
347 | // Obtain a list of nodes with revisions higher than current published revision. |
---|
348 | $sql = "SELECT n.nid, r.vid, n.type, r.title, r.body, r.uid, r.timestamp FROM {node} n INNER JOIN {node_revisions} r ON n.nid = r.nid WHERE r.vid > n.vid ORDER BY r.vid DESC LIMIT %d"; |
---|
349 | $result = db_query($sql, $limit); |
---|
350 | $revisions = array(); |
---|
351 | while ($revision = db_fetch_object($result)) { |
---|
352 | $revisions[$revision->nid] = $revision; |
---|
353 | } |
---|
354 | |
---|
355 | return $revisions; |
---|
356 | } |
---|
357 | |
---|
358 | /** |
---|
359 | * Retrieve list of all pending revisions for a given node. |
---|
360 | * |
---|
361 | * @param $nid |
---|
362 | * The node ID to retrieve. |
---|
363 | */ |
---|
364 | function revision_moderation_get_node_pending_revisions($nid) { |
---|
365 | // Obtain a list of revisions higher than current published revision for a given node. |
---|
366 | $sql = "SELECT n.nid, r.vid, r.uid 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"; |
---|
367 | $result = db_query($sql, $nid); |
---|
368 | $revisions = array(); |
---|
369 | while ($revision = db_fetch_object($result)) { |
---|
370 | $revisions[$revision->vid] = $revision; |
---|
371 | } |
---|
372 | |
---|
373 | return $revisions; |
---|
374 | } |
---|
375 | |
---|
376 | /** |
---|
377 | * Menu callback; edit revision. |
---|
378 | */ |
---|
379 | function revision_moderation_edit($node) { |
---|
380 | // Get username for the revision rather than the original node. |
---|
381 | //$revision_author = user_load($node->revision_uid); |
---|
382 | //drupal_set_message(t('You are currently editing a revision of this post created on @date by !author.', array('@date' => format_date($node->revision_timestamp, 'small'), '!author' => theme('username', $revision_author)))); |
---|
383 | return drupal_get_form($node->type .'_node_form', $node); |
---|
384 | } |
---|
385 | |
---|
386 | /** |
---|
387 | * Returns a confirmation page for publishing a revision. |
---|
388 | * |
---|
389 | * @param $node |
---|
390 | * The node object for which revision is to be published. |
---|
391 | */ |
---|
392 | function revision_moderation_publish_confirm($form_state, $node) { |
---|
393 | $form['node_id'] = array('#type' => 'value', '#value' => $node->nid); |
---|
394 | $form['title'] = array('#type' => 'value', '#value' => $node->title); |
---|
395 | $form['revision'] = array('#type' => 'value', '#value' => $node->vid); |
---|
396 | $form['type'] = array('#type' => 'value', '#value' => $node->type); |
---|
397 | |
---|
398 | return confirm_form($form, t('Are you sure you want to publish this revision for %title?', array('%title' => $node->title)), 'node/'. $node->nid .'/revisions/'. $node->vid, t('Publishing this revision will make it public for all users.'), t('Publish'), t('Cancel')); |
---|
399 | } |
---|
400 | |
---|
401 | /** |
---|
402 | * Submission handler for the publish confirm form. |
---|
403 | * Publishes a revision directly. |
---|
404 | */ |
---|
405 | function revision_moderation_publish_confirm_submit($form, &$form_state) { |
---|
406 | $nid = $form_state['values']['node_id']; |
---|
407 | $title = $form_state['values']['title']; |
---|
408 | $vid = $form_state['values']['revision']; |
---|
409 | $type = $form_state['values']['type']; |
---|
410 | |
---|
411 | db_query("UPDATE {node} SET vid = %d, title = '%s' WHERE nid = %d", $vid, $title, $nid); |
---|
412 | // Clear the cache so an anonymous poster can see the changes |
---|
413 | cache_clear_all(); |
---|
414 | drupal_set_message('The selected revision has been published.'); |
---|
415 | watchdog('content', '@type: published %title revision %revision', array('@type' => t($type), '%title' => $title, '%revision' => $vid), WATCHDOG_NOTICE, l(t('view'), "node/$nid/revisions/$vid/view")); |
---|
416 | $form_state['redirect'] = 'node/'. $nid; |
---|
417 | } |
---|