1 | <?php |
---|
2 | |
---|
3 | /** |
---|
4 | * @file |
---|
5 | * Menu callbacks for hook_menu(). |
---|
6 | */ |
---|
7 | |
---|
8 | /** |
---|
9 | * Menu callback - show latest diff for a given node. |
---|
10 | */ |
---|
11 | function diff_latest($node) { |
---|
12 | $revisions = node_revision_list($node); |
---|
13 | $new = array_shift($revisions); |
---|
14 | $old = array_shift($revisions); |
---|
15 | drupal_goto("node/{$node->nid}/revisions/view/{$old->vid}/{$new->vid}"); |
---|
16 | } |
---|
17 | |
---|
18 | /** |
---|
19 | * Generate an overview table of older revisions of a node and provide |
---|
20 | * an input form to select two revisions for a comparison. |
---|
21 | */ |
---|
22 | function diff_diffs_overview($node) { |
---|
23 | drupal_set_title(t('Revisions for %title', array('%title' => $node->title))); |
---|
24 | return drupal_get_form('diff_node_revisions', $node); |
---|
25 | } |
---|
26 | |
---|
27 | /** |
---|
28 | * Input form to select two revisions. |
---|
29 | * |
---|
30 | * @param $node |
---|
31 | * Node whose revisions are displayed for selection. |
---|
32 | */ |
---|
33 | function diff_node_revisions($form_state, $node) { |
---|
34 | global $form_values; |
---|
35 | $form = array(); |
---|
36 | |
---|
37 | $form['nid'] = array( |
---|
38 | '#type' => 'hidden', |
---|
39 | '#value' => $node->nid, |
---|
40 | ); |
---|
41 | |
---|
42 | $revision_list = node_revision_list($node); |
---|
43 | |
---|
44 | if (count($revision_list) > REVISION_LIST_SIZE) { |
---|
45 | // If the list of revisions is longer than the number shown on one page split the array. |
---|
46 | $page = isset($_GET['page']) ? $_GET['page'] : '0'; |
---|
47 | $revision_chunks = array_chunk(node_revision_list($node), REVISION_LIST_SIZE); |
---|
48 | $revisions = $revision_chunks[$page]; |
---|
49 | // Set up global pager variables as would 'pager_query' do. |
---|
50 | // These variables are then used in the theme('pager') call later. |
---|
51 | global $pager_page_array, $pager_total, $pager_total_items; |
---|
52 | $pager_total_items[0] = count($revision_list); |
---|
53 | $pager_total[0] = ceil(count($revision_list) / REVISION_LIST_SIZE); |
---|
54 | $pager_page_array[0] = max(0, min($page, ((int)$pager_total[0]) - 1)); |
---|
55 | } |
---|
56 | else { |
---|
57 | $revisions = $revision_list; |
---|
58 | } |
---|
59 | |
---|
60 | $revert_permission = FALSE; |
---|
61 | if ((user_access('revert revisions') || user_access('administer nodes')) && node_access('update', $node)) { |
---|
62 | $revert_permission = TRUE; |
---|
63 | } |
---|
64 | $delete_permission = FALSE; |
---|
65 | if ((user_access('delete revisions') || user_access('administer nodes')) && node_access('delete', $node)) { |
---|
66 | $delete_permission = TRUE; |
---|
67 | } |
---|
68 | |
---|
69 | foreach ($revisions as $revision) { |
---|
70 | $operations = array(); |
---|
71 | $revision_ids[$revision->vid] = ''; |
---|
72 | |
---|
73 | if ($revision->current_vid > 0) { |
---|
74 | $form['info'][$revision->vid] = array( |
---|
75 | '#value' => t('!date by !username', array( |
---|
76 | '!date' => l(format_date($revision->timestamp, 'small'), "node/$node->nid"), |
---|
77 | '!username' => theme('username', $revision))) |
---|
78 | . (($revision->log != '') ? '<p class="revision-log">'. filter_xss($revision->log) .'</p>' : ''), |
---|
79 | ); |
---|
80 | } |
---|
81 | else { |
---|
82 | $form['info'][$revision->vid] = array( |
---|
83 | '#value' => t('!date by !username', array( |
---|
84 | '!date' => l(format_date($revision->timestamp, 'small'), "node/$node->nid/revisions/$revision->vid/view"), |
---|
85 | '!username' => theme('username', $revision))) |
---|
86 | . (($revision->log != '') ? '<p class="revision-log">'. filter_xss($revision->log) .'</p>' : '') |
---|
87 | ); |
---|
88 | if ($revert_permission) { |
---|
89 | $operations[] = array('#value' => l(t('revert'), "node/$node->nid/revisions/$revision->vid/revert")); |
---|
90 | } |
---|
91 | if ($delete_permission) { |
---|
92 | $operations[] = array('#value' => l(t('delete'), "node/$node->nid/revisions/$revision->vid/delete")); |
---|
93 | } |
---|
94 | // Set a dummy, even if the user has no permission for the other |
---|
95 | // operations, so that we can check if the operations array is |
---|
96 | // empty to know if the row denotes the current revision. |
---|
97 | $operations[] = array(); |
---|
98 | } |
---|
99 | $form['operations'][$revision->vid] = $operations; |
---|
100 | |
---|
101 | } |
---|
102 | $new_vid = key($revision_ids); |
---|
103 | next($revision_ids); |
---|
104 | $old_vid = key($revision_ids); |
---|
105 | $form['diff']['old'] = array( |
---|
106 | '#type' => 'radios', |
---|
107 | '#options' => $revision_ids, |
---|
108 | '#default_value' => $old_vid |
---|
109 | ); |
---|
110 | $form['diff']['new'] = array( |
---|
111 | '#type' => 'radios', |
---|
112 | '#options' => $revision_ids, |
---|
113 | '#default_value' => $new_vid |
---|
114 | ); |
---|
115 | $form['submit'] = array('#type' => 'submit', '#value' => t('Show diff')); |
---|
116 | |
---|
117 | if (count($revision_list) > REVISION_LIST_SIZE) { |
---|
118 | $form['#suffix'] = theme('pager', NULL, REVISION_LIST_SIZE, 0); |
---|
119 | } |
---|
120 | |
---|
121 | return $form; |
---|
122 | } |
---|
123 | |
---|
124 | /** |
---|
125 | * Submit code for input form to select two revisions. |
---|
126 | */ |
---|
127 | function diff_node_revisions_submit($form, &$form_state) { |
---|
128 | // the ids are ordered so the old revision is always on the left |
---|
129 | $old_vid = min($form_state['values']['old'], $form_state['values']['new']); |
---|
130 | $new_vid = max($form_state['values']['old'], $form_state['values']['new']); |
---|
131 | $form_state['redirect'] = 'node/'. $form_state['values']['nid'] .'/revisions/view/'. $old_vid .'/'. $new_vid; |
---|
132 | } |
---|
133 | |
---|
134 | /** |
---|
135 | * Validation for input form to select two revisions. |
---|
136 | */ |
---|
137 | function diff_node_revisions_validate($form, &$form_state) { |
---|
138 | $old_vid = $form_state['values']['old']; |
---|
139 | $new_vid = $form_state['values']['new']; |
---|
140 | if ($old_vid==$new_vid || !$old_vid || !$new_vid) { |
---|
141 | form_set_error('diff', t('Select different revisions to compare.')); |
---|
142 | } |
---|
143 | } |
---|
144 | |
---|
145 | /** |
---|
146 | * Create output string for a comparison of 'node' between |
---|
147 | * versions 'old_vid' and 'new_vid'. |
---|
148 | * |
---|
149 | * @param $node |
---|
150 | * Node on which to perform comparison |
---|
151 | * @param $old_vid |
---|
152 | * Version ID of the old revision. |
---|
153 | * @param $new_vid |
---|
154 | * Version ID of the new revision. |
---|
155 | */ |
---|
156 | function diff_diffs_show($node, $old_vid, $new_vid) { |
---|
157 | |
---|
158 | // Set same title as on the 'Revisions' tab for consistency |
---|
159 | drupal_set_title(t('Revisions for %title', array('%title' => $node->title))); |
---|
160 | |
---|
161 | $node_revisions = node_revision_list($node); |
---|
162 | |
---|
163 | $old_node = node_load($node->nid, $old_vid); |
---|
164 | $new_node = node_load($node->nid, $new_vid); |
---|
165 | |
---|
166 | // Generate table header (date, username, logmessage). |
---|
167 | $old_header = t('!date by !username', array( |
---|
168 | '!date' => l(format_date($old_node->revision_timestamp), "node/$node->nid/revisions/$old_node->vid/view"), |
---|
169 | '!username' => theme('username', $node_revisions[$old_vid]), |
---|
170 | )); |
---|
171 | $new_header = t('!date by !username', array( |
---|
172 | '!date' => l(format_date($new_node->revision_timestamp), "node/$node->nid/revisions/$new_node->vid/view"), |
---|
173 | '!username' => theme('username', $node_revisions[$new_vid]), |
---|
174 | )); |
---|
175 | |
---|
176 | $old_log = $old_node->log != '' ? '<p class="revision-log">'. filter_xss($old_node->log) .'</p>' : ''; |
---|
177 | $new_log = $new_node->log != '' ? '<p class="revision-log">'. filter_xss($new_node->log) .'</p>' : ''; |
---|
178 | |
---|
179 | // Generate previous diff/next diff links. |
---|
180 | $next_vid = _diff_get_next_vid($node_revisions, $new_vid); |
---|
181 | if ($next_vid) { |
---|
182 | $next_link = l(t('next diff >'), 'node/'. $node->nid .'/revisions/view/'. $new_vid .'/'. $next_vid); |
---|
183 | } |
---|
184 | else { |
---|
185 | $next_link = ''; |
---|
186 | } |
---|
187 | $prev_vid = _diff_get_previous_vid($node_revisions, $old_vid); |
---|
188 | if ($prev_vid) { |
---|
189 | $prev_link = l(t('< previous diff'), 'node/'. $node->nid .'/revisions/view/'. $prev_vid .'/'. $old_vid); |
---|
190 | } |
---|
191 | else { |
---|
192 | $prev_link = ''; |
---|
193 | } |
---|
194 | |
---|
195 | $cols = _diff_default_cols(); |
---|
196 | $header = _diff_default_header($old_header, $new_header); |
---|
197 | $rows = array(); |
---|
198 | if ($old_log || $new_log) { |
---|
199 | $rows[] = array( |
---|
200 | array( |
---|
201 | 'data' => $old_log, |
---|
202 | 'colspan' => 2 |
---|
203 | ), |
---|
204 | array( |
---|
205 | 'data' => $new_log, |
---|
206 | 'colspan' => 2 |
---|
207 | ) |
---|
208 | ); |
---|
209 | } |
---|
210 | $rows[] = array( |
---|
211 | array( |
---|
212 | 'data' => $prev_link, |
---|
213 | 'class' => 'diff-prevlink', |
---|
214 | 'colspan' => 2 |
---|
215 | ), |
---|
216 | array( |
---|
217 | 'data' => $next_link, |
---|
218 | 'class' => 'diff-nextlink', |
---|
219 | 'colspan' => 2 |
---|
220 | ) |
---|
221 | ); |
---|
222 | $rows = array_merge($rows, _diff_body_rows($old_node, $new_node)); |
---|
223 | $output = theme('diff_table', $header, $rows, array('class' => 'diff'), NULL, $cols); |
---|
224 | |
---|
225 | if ($node->vid == $new_vid) { |
---|
226 | $output .= '<div class="diff-section-title">'. t('Current revision:') .'</div>'; |
---|
227 | } |
---|
228 | else { |
---|
229 | $output .= '<div class="diff-section-title">'. t('Revision of !new_date:', array('!new_date' => format_date($new_node->revision_timestamp))) .'</div>'; |
---|
230 | } |
---|
231 | // Don't include node links (final argument) when viewing the diff. |
---|
232 | $output .= node_view($new_node, FALSE, FALSE, FALSE); |
---|
233 | return $output; |
---|
234 | } |
---|
235 | |
---|
236 | /** |
---|
237 | * Creates an array of rows which represent a diff between $old_node and $new_node. |
---|
238 | * The rows can be used via theme('diff_table') to be displayed. |
---|
239 | * |
---|
240 | * @param $old_node |
---|
241 | * Node for comparison which will be displayed on the left side. |
---|
242 | * @param $new_node |
---|
243 | * Node for comparison which will be displayed on the right side. |
---|
244 | */ |
---|
245 | function _diff_body_rows($old_node, $new_node) { |
---|
246 | drupal_add_css(drupal_get_path('module', 'diff') .'/diff.css', 'module', 'all', FALSE); |
---|
247 | |
---|
248 | module_load_include('inc', 'diff', 'includes/node'); |
---|
249 | if (module_exists('taxonomy')) { |
---|
250 | module_load_include('inc', 'diff', 'includes/taxonomy'); |
---|
251 | } |
---|
252 | if (module_exists('upload')) { |
---|
253 | module_load_include('inc', 'diff', 'includes/upload'); |
---|
254 | } |
---|
255 | |
---|
256 | $rows = array(); |
---|
257 | $any_visible_change = FALSE; |
---|
258 | // @todo quick workaround for PHP >= 5.3.0 date_diff() conflict. |
---|
259 | $node_diffs = _diff_module_invoke_all($old_node, $new_node); |
---|
260 | |
---|
261 | // We start off assuming all form elements are in the correct order. |
---|
262 | $node_diffs['#sorted'] = TRUE; |
---|
263 | |
---|
264 | // Recurse through all child elements. |
---|
265 | $count = 0; |
---|
266 | foreach (element_children($node_diffs) as $key) { |
---|
267 | // Assign a decimal placeholder weight to preserve original array order. |
---|
268 | if (!isset($node_diffs[$key]['#weight'])) { |
---|
269 | $node_diffs[$key]['#weight'] = $count/1000; |
---|
270 | } |
---|
271 | else { |
---|
272 | // If one of the child elements has a weight then we will need to sort |
---|
273 | // later. |
---|
274 | unset($node_diffs['#sorted']); |
---|
275 | } |
---|
276 | $count++; |
---|
277 | } |
---|
278 | |
---|
279 | // One of the children has a #weight. |
---|
280 | if (!isset($node_diffs['#sorted'])) { |
---|
281 | uasort($node_diffs, "element_sort"); |
---|
282 | } |
---|
283 | |
---|
284 | // Render diffs for each. |
---|
285 | foreach ($node_diffs as $node_diff) { |
---|
286 | $show_header = isset($node_diff['#format']['show_header']) ? $node_diff['#format']['show_header'] : FALSE; |
---|
287 | if ($node_diff_rows = diff_get_rows($node_diff['#old'], $node_diff['#new'], $show_header)) { |
---|
288 | $rows[] = array(array( |
---|
289 | 'data' => t('Changes to %name', array('%name' => $node_diff['#name'])), |
---|
290 | 'class' => 'diff-section-title', |
---|
291 | 'colspan' => 4 |
---|
292 | )); |
---|
293 | $rows = array_merge($rows, $node_diff_rows); |
---|
294 | $any_visible_change = TRUE; |
---|
295 | } |
---|
296 | } |
---|
297 | if (!$any_visible_change) { |
---|
298 | $rows[] = array(array( |
---|
299 | 'data' => t('No visible changes'), |
---|
300 | 'class' => 'diff-section-title', |
---|
301 | 'colspan' => 4 |
---|
302 | )); |
---|
303 | // Needed to keep safari happy |
---|
304 | $rows[] = array( |
---|
305 | array('data' => ''), |
---|
306 | array('data' => ''), |
---|
307 | array('data' => ''), |
---|
308 | array('data' => ''), |
---|
309 | ); |
---|
310 | } |
---|
311 | |
---|
312 | return $rows; |
---|
313 | } |
---|
314 | |
---|
315 | /** |
---|
316 | * Helper function to invoke hook_diff in all enabled modules that implement it. |
---|
317 | * |
---|
318 | * Don't use module_invoke_all() since if date.module is enabled will clash with |
---|
319 | * PHP 5.3's date_diff() function. |
---|
320 | * |
---|
321 | * @todo figure out any else possible solution but not workaround. |
---|
322 | * @link http://drupal.org/node/639320 |
---|
323 | * @see module_invoke_all() |
---|
324 | */ |
---|
325 | function _diff_module_invoke_all($old_node, $new_node) { |
---|
326 | $return = array(); |
---|
327 | foreach (module_implements('diff') as $module) { |
---|
328 | if ($module == 'date') { |
---|
329 | continue; // Avoid function name collision with date_diff(). |
---|
330 | } |
---|
331 | $result = module_invoke($module, 'diff', $old_node, $new_node); |
---|
332 | if (isset($result) && is_array($result)) { |
---|
333 | $return = array_merge_recursive($return, $result); |
---|
334 | } |
---|
335 | elseif (isset($result)) { |
---|
336 | $return[] = $result; |
---|
337 | } |
---|
338 | } |
---|
339 | return $return; |
---|
340 | } |
---|
341 | |
---|
342 | /** |
---|
343 | * Get the entry in the revisions list after $vid. |
---|
344 | * Returns FALSE if $vid is the last entry. |
---|
345 | * |
---|
346 | * @param $node_revisions |
---|
347 | * Array of node revision IDs in descending order. |
---|
348 | * @param $vid |
---|
349 | * Version ID to look for. |
---|
350 | */ |
---|
351 | function _diff_get_next_vid($node_revisions, $vid) { |
---|
352 | $previous = NULL; |
---|
353 | foreach ($node_revisions as $revision) { |
---|
354 | if ($revision->vid == $vid) { |
---|
355 | return ($previous ? $previous->vid : FALSE); |
---|
356 | } |
---|
357 | $previous = $revision; |
---|
358 | } |
---|
359 | return FALSE; |
---|
360 | } |
---|
361 | |
---|
362 | /** |
---|
363 | * Get the entry in the revision list before $vid. |
---|
364 | * Returns FALSE if $vid is the first entry. |
---|
365 | * |
---|
366 | * @param $node_revisions |
---|
367 | * Array of node revision IDs in descending order. |
---|
368 | * @param $vid |
---|
369 | * Version ID to look for. |
---|
370 | */ |
---|
371 | function _diff_get_previous_vid($node_revisions, $vid) { |
---|
372 | $previous = NULL; |
---|
373 | foreach ($node_revisions as $revision) { |
---|
374 | if ($previous && $previous->vid == $vid) { |
---|
375 | return $revision->vid; |
---|
376 | } |
---|
377 | $previous = $revision; |
---|
378 | } |
---|
379 | return FALSE; |
---|
380 | } |
---|
381 | |
---|
382 | /** |
---|
383 | * Helper function to create default 'cols' array for diff table. |
---|
384 | */ |
---|
385 | function _diff_default_cols() { |
---|
386 | return array( |
---|
387 | array( |
---|
388 | array( |
---|
389 | 'class' => 'diff-marker', |
---|
390 | ), |
---|
391 | array( |
---|
392 | 'class' => 'diff-content', |
---|
393 | ), |
---|
394 | array( |
---|
395 | 'class' => 'diff-marker', |
---|
396 | ), |
---|
397 | array( |
---|
398 | 'class' => 'diff-content', |
---|
399 | ), |
---|
400 | ), |
---|
401 | ); |
---|
402 | } |
---|
403 | |
---|
404 | /** |
---|
405 | * Helper function to create default 'header' array for diff table. |
---|
406 | */ |
---|
407 | function _diff_default_header($old_header = '', $new_header = '') { |
---|
408 | return array( |
---|
409 | array( |
---|
410 | 'data' => $old_header, |
---|
411 | 'colspan' => 2 |
---|
412 | ), |
---|
413 | array( |
---|
414 | 'data' => $new_header, |
---|
415 | 'colspan' => 2 |
---|
416 | ) |
---|
417 | ); |
---|
418 | } |
---|
419 | |
---|
420 | /** |
---|
421 | * AHAH callback for rendering the inline diff of a node. |
---|
422 | */ |
---|
423 | function diff_inline_ahah($node) { |
---|
424 | $form_state = array('values' => $_POST); |
---|
425 | if ($form = form_get_cache($form_state['values']['form_build_id'], $form_state)) { |
---|
426 | if (!empty($form_state['values']['revision']) && is_numeric($form_state['values']['revision'])) { |
---|
427 | $vid = $form_state['values']['revision']; |
---|
428 | } |
---|
429 | else { |
---|
430 | $vid = 0; |
---|
431 | } |
---|
432 | drupal_json(array('status' => TRUE, 'data' => diff_inline_show($node, $vid))); |
---|
433 | exit; |
---|
434 | } |
---|
435 | drupal_json(array('status' => FALSE, 'data' => '')); |
---|
436 | exit; |
---|
437 | } |
---|
438 | |
---|
439 | /** |
---|
440 | * Show the inline diff for a given node, vid. If vid = 0 or no previous vid |
---|
441 | * exists for the given revision returns the normally rendered content of the |
---|
442 | * specified revision. |
---|
443 | */ |
---|
444 | function diff_inline_show($node, $vid = 0, $metadata = TRUE) { |
---|
445 | $new_node = $vid ? node_load($node->nid, $vid, TRUE) : drupal_clone($node); |
---|
446 | $new = node_build_content($new_node); |
---|
447 | $new = drupal_render($new->content); |
---|
448 | |
---|
449 | $old = $vid ? _diff_get_previous_vid(node_revision_list($node), $vid) : 0; |
---|
450 | if ($old) { |
---|
451 | $old = node_build_content(node_load($node->nid, $old, TRUE)); |
---|
452 | $old = drupal_render($old->content); |
---|
453 | $output = $metadata ? theme('diff_inline_metadata', $new_node) : ''; |
---|
454 | $output .= diff_get_inline($old, $new); |
---|
455 | return $output; |
---|
456 | } |
---|
457 | return $new; |
---|
458 | } |
---|