Issue #1635084 follow-up by xjm: Fix random test failures.
[project/drupal.git] / core / modules / node / node.pages.inc
1 <?php
2
3 /**
4 * @file
5 * Callbacks for adding, editing, and deleting content and managing revisions.
6 *
7 * Also includes validation, submission and other helper functions.
8 *
9 * @see node_menu()
10 */
11
12 use Drupal\node\Node;
13
14 /**
15 * Page callback: Presents the node editing form.
16 *
17 * @see node_menu()
18 */
19 function node_page_edit($node) {
20 $type_name = node_type_get_name($node);
21 drupal_set_title(t('<em>Edit @type</em> @title', array('@type' => $type_name, '@title' => $node->title)), PASS_THROUGH);
22 return drupal_get_form($node->type . '_node_form', $node);
23 }
24
25 /**
26 * Page callback: Presents the node add form.
27 *
28 * @see node_menu()
29 */
30 function node_add_page() {
31 $content = array();
32 // Only use node types the user has access to.
33 foreach (node_type_get_types() as $type) {
34 if (node_hook($type->type, 'form') && node_access('create', $type->type)) {
35 $content[$type->type] = $type;
36 }
37 }
38 // Bypass the node/add listing if only one content type is available.
39 if (count($content) == 1) {
40 $type = array_shift($content);
41 drupal_goto('node/add/' . $type->type);
42 }
43 return array('#theme' => 'node_add_list', '#content' => $content);
44 }
45
46 /**
47 * Returns HTML for a list of available node types for node creation.
48 *
49 * @param $variables
50 * An associative array containing:
51 * - content: An array of content types.
52 *
53 * @see node_add_page()
54 * @ingroup themeable
55 */
56 function theme_node_add_list($variables) {
57 $content = $variables['content'];
58 $output = '';
59
60 if ($content) {
61 $output = '<dl class="node-type-list">';
62 foreach ($content as $type) {
63 $output .= '<dt>' . l($type->name, 'node/add/' . $type->type) . '</dt>';
64 $output .= '<dd>' . filter_xss_admin($type->description) . '</dd>';
65 }
66 $output .= '</dl>';
67 }
68 else {
69 $output = '<p>' . t('You have not created any content types yet. Go to the <a href="@create-content">content type creation page</a> to add a new content type.', array('@create-content' => url('admin/structure/types/add'))) . '</p>';
70 }
71 return $output;
72 }
73
74
75 /**
76 * Page callback: Provides the node submission form.
77 *
78 * @param $type
79 * The node type for the submitted node.
80 *
81 * @return
82 * Returns a node submission form.
83 *
84 * @see node_menu()
85 */
86 function node_add($type) {
87 global $user;
88
89 $types = node_type_get_types();
90 $node = entity_create('node', array(
91 'uid' => $user->uid,
92 'name' => (isset($user->name) ? $user->name : ''),
93 'type' => $type,
94 'langcode' => node_type_get_default_langcode($type)
95 ));
96 drupal_set_title(t('Create @name', array('@name' => $types[$type]->name)), PASS_THROUGH);
97 $output = drupal_get_form($type . '_node_form', $node);
98
99 return $output;
100 }
101
102 /**
103 * Form validation handler for node_form().
104 *
105 * @see node_form_delete_submit()
106 * @see node_form_build_preview()
107 * @see node_form_submit()
108 * @see node_form_submit_build_node()
109 */
110 function node_form_validate($form, &$form_state) {
111 // $form_state['node'] contains the actual entity being edited, but we must
112 // not update it with form values that have not yet been validated, so we
113 // create a pseudo-entity to use during validation.
114 $node = clone $form_state['node'];
115 foreach ($form_state['values'] as $key => $value) {
116 $node->{$key} = $value;
117 }
118 node_validate($node, $form, $form_state);
119 entity_form_field_validate('node', $form, $form_state);
120 }
121
122 /**
123 * Form constructor for the node add/edit form.
124 *
125 * @see node_form_delete_submit()
126 * @see node_form_build_preview()
127 * @see node_form_validate()
128 * @see node_form_submit()
129 * @see node_form_submit_build_node()
130 * @ingroup forms
131 */
132 function node_form($form, &$form_state, Node $node) {
133 global $user;
134
135 // During initial form build, add the node entity to the form state for use
136 // during form building and processing. During a rebuild, use what is in the
137 // form state.
138 if (!isset($form_state['node'])) {
139 node_object_prepare($node);
140 $form_state['node'] = $node;
141 }
142 else {
143 $node = $form_state['node'];
144 }
145
146 // Some special stuff when previewing a node.
147 if (isset($form_state['node_preview'])) {
148 $form['#prefix'] = $form_state['node_preview'];
149 $node->in_preview = TRUE;
150 }
151 else {
152 unset($node->in_preview);
153 }
154
155 // Override the default CSS class name, since the user-defined node type name
156 // in 'TYPE-node-form' potentially clashes with third-party class names.
157 $form['#attributes']['class'][0] = drupal_html_class('node-' . $node->type . '-form');
158
159 // Basic node information.
160 // These elements are just values so they are not even sent to the client.
161 foreach (array('nid', 'vid', 'uid', 'created', 'type') as $key) {
162 $form[$key] = array(
163 '#type' => 'value',
164 '#value' => isset($node->$key) ? $node->$key : NULL,
165 );
166 }
167
168 // Changed must be sent to the client, for later overwrite error checking.
169 $form['changed'] = array(
170 '#type' => 'hidden',
171 '#default_value' => isset($node->changed) ? $node->changed : NULL,
172 );
173 // Invoke hook_form() to get the node-specific bits. Can't use node_invoke(),
174 // because hook_form() needs to be able to receive $form_state by reference.
175 // @todo hook_form() implementations are unable to add #validate or #submit
176 // handlers to the form buttons below. Remove hook_form() entirely.
177 $function = node_type_get_base($node) . '_form';
178 if (function_exists($function) && ($extra = $function($node, $form_state))) {
179 $form = array_merge_recursive($form, $extra);
180 }
181 // If the node type has a title, and the node type form defined no special
182 // weight for it, we default to a weight of -5 for consistency.
183 if (isset($form['title']) && !isset($form['title']['#weight'])) {
184 $form['title']['#weight'] = -5;
185 }
186 // @todo D8: Remove. Modules should access the node using $form_state['node'].
187 $form['#node'] = $node;
188
189 if (module_exists('language')) {
190 $languages = language_list(LANGUAGE_ALL);
191 $language_options = array();
192 foreach ($languages as $langcode => $language) {
193 // Make locked languages appear special in the list.
194 $language_options[$langcode] = $language->locked ? t('- @name -', array('@name' => $language->name)) : $language->name;
195 }
196 $form['langcode'] = array(
197 '#type' => 'select',
198 '#title' => t('Language'),
199 '#default_value' => $node->langcode,
200 '#options' => $language_options,
201 '#access' => !variable_get('node_type_language_hidden_' . $node->type, TRUE),
202 );
203 }
204 else {
205 $form['langcode'] = array(
206 '#type' => 'value',
207 '#value' => $node->langcode,
208 );
209 }
210
211 $form['additional_settings'] = array(
212 '#type' => 'vertical_tabs',
213 '#weight' => 99,
214 );
215
216 // Add a log field if the "Create new revision" option is checked, or if the
217 // current user has the ability to check that option.
218 $form['revision_information'] = array(
219 '#type' => 'fieldset',
220 '#title' => t('Revision information'),
221 '#collapsible' => TRUE,
222 // Collapsed by default when "Create new revision" is unchecked
223 '#collapsed' => !$node->revision,
224 '#group' => 'additional_settings',
225 '#attributes' => array(
226 'class' => array('node-form-revision-information'),
227 ),
228 '#attached' => array(
229 'js' => array(drupal_get_path('module', 'node') . '/node.js'),
230 ),
231 '#weight' => 20,
232 '#access' => $node->revision || user_access('administer nodes'),
233 );
234 $form['revision_information']['revision'] = array(
235 '#type' => 'checkbox',
236 '#title' => t('Create new revision'),
237 '#default_value' => $node->revision,
238 '#access' => user_access('administer nodes'),
239 );
240 // Check the revision log checkbox when the log textarea is filled in.
241 // This must not happen if "Create new revision" is enabled by default, since
242 // the state would auto-disable the checkbox otherwise.
243 if (!$node->revision) {
244 $form['revision_information']['revision']['#states'] = array(
245 'checked' => array(
246 'textarea[name="log"]' => array('empty' => FALSE),
247 ),
248 );
249 }
250 $form['revision_information']['log'] = array(
251 '#type' => 'textarea',
252 '#title' => t('Revision log message'),
253 '#rows' => 4,
254 '#default_value' => !empty($node->log) ? $node->log : '',
255 '#description' => t('Briefly describe the changes you have made.'),
256 );
257
258 // Node author information for administrators
259 $form['author'] = array(
260 '#type' => 'fieldset',
261 '#access' => user_access('administer nodes'),
262 '#title' => t('Authoring information'),
263 '#collapsible' => TRUE,
264 '#collapsed' => TRUE,
265 '#group' => 'additional_settings',
266 '#attributes' => array(
267 'class' => array('node-form-author'),
268 ),
269 '#attached' => array(
270 'js' => array(
271 drupal_get_path('module', 'node') . '/node.js',
272 array(
273 'type' => 'setting',
274 'data' => array('anonymous' => variable_get('anonymous', t('Anonymous'))),
275 ),
276 ),
277 ),
278 '#weight' => 90,
279 );
280 $form['author']['name'] = array(
281 '#type' => 'textfield',
282 '#title' => t('Authored by'),
283 '#maxlength' => 60,
284 '#autocomplete_path' => 'user/autocomplete',
285 '#default_value' => !empty($node->name) ? $node->name : '',
286 '#weight' => -1,
287 '#description' => t('Leave blank for %anonymous.', array('%anonymous' => variable_get('anonymous', t('Anonymous')))),
288 );
289 $form['author']['date'] = array(
290 '#type' => 'textfield',
291 '#title' => t('Authored on'),
292 '#maxlength' => 25,
293 '#description' => t('Format: %time. The date format is YYYY-MM-DD and %timezone is the time zone offset from UTC. Leave blank to use the time of form submission.', array('%time' => !empty($node->date) ? date_format(date_create($node->date), 'Y-m-d H:i:s O') : format_date($node->created, 'custom', 'Y-m-d H:i:s O'), '%timezone' => !empty($node->date) ? date_format(date_create($node->date), 'O') : format_date($node->created, 'custom', 'O'))),
294 '#default_value' => !empty($node->date) ? $node->date : '',
295 );
296
297 // Node options for administrators
298 $form['options'] = array(
299 '#type' => 'fieldset',
300 '#access' => user_access('administer nodes'),
301 '#title' => t('Publishing options'),
302 '#collapsible' => TRUE,
303 '#collapsed' => TRUE,
304 '#group' => 'additional_settings',
305 '#attributes' => array(
306 'class' => array('node-form-options'),
307 ),
308 '#attached' => array(
309 'js' => array(drupal_get_path('module', 'node') . '/node.js'),
310 ),
311 '#weight' => 95,
312 );
313 $form['options']['status'] = array(
314 '#type' => 'checkbox',
315 '#title' => t('Published'),
316 '#default_value' => $node->status,
317 );
318 $form['options']['promote'] = array(
319 '#type' => 'checkbox',
320 '#title' => t('Promoted to front page'),
321 '#default_value' => $node->promote,
322 );
323 $form['options']['sticky'] = array(
324 '#type' => 'checkbox',
325 '#title' => t('Sticky at top of lists'),
326 '#default_value' => $node->sticky,
327 );
328
329 // Add the buttons.
330 $form['actions'] = array('#type' => 'actions');
331 $form['actions']['submit'] = array(
332 '#type' => 'submit',
333 '#access' => variable_get('node_preview_' . $node->type, DRUPAL_OPTIONAL) != DRUPAL_REQUIRED || (!form_get_errors() && isset($form_state['node_preview'])),
334 '#value' => t('Save'),
335 '#weight' => 5,
336 '#submit' => array('node_form_submit'),
337 );
338 $form['actions']['preview'] = array(
339 '#access' => variable_get('node_preview_' . $node->type, DRUPAL_OPTIONAL) != DRUPAL_DISABLED,
340 '#type' => 'submit',
341 '#value' => t('Preview'),
342 '#weight' => 10,
343 '#submit' => array('node_form_build_preview'),
344 );
345 if (!empty($node->nid) && node_access('delete', $node)) {
346 $form['actions']['delete'] = array(
347 '#type' => 'submit',
348 '#value' => t('Delete'),
349 '#weight' => 15,
350 '#submit' => array('node_form_delete_submit'),
351 );
352 }
353 // This form uses a button-level #submit handler for the form's main submit
354 // action. node_form_submit() manually invokes all form-level #submit handlers
355 // of the form. Without explicitly setting #submit, Form API would auto-detect
356 // node_form_submit() as submit handler, but that is the button-level #submit
357 // handler for the 'Save' action. To maintain backwards compatibility, a
358 // #submit handler is auto-suggested for custom node type modules.
359 $form['#validate'][] = 'node_form_validate';
360 if (!isset($form['#submit']) && function_exists($node->type . '_node_form_submit')) {
361 $form['#submit'][] = $node->type . '_node_form_submit';
362 }
363 $form += array('#submit' => array());
364
365 field_attach_form('node', $node, $form, $form_state, $node->langcode);
366 return $form;
367 }
368
369 /**
370 * Form submission handler for the 'Delete' button for node_form().
371 *
372 * @see node_form_build_preview()
373 * @see node_form_validate()
374 * @see node_form_submit()
375 * @see node_form_submit_build_node()
376 */
377 function node_form_delete_submit($form, &$form_state) {
378 $destination = array();
379 if (isset($_GET['destination'])) {
380 $destination = drupal_get_destination();
381 unset($_GET['destination']);
382 }
383 $node = $form['#node'];
384 $form_state['redirect'] = array('node/' . $node->nid . '/delete', array('query' => $destination));
385 }
386
387 /**
388 * Form submission handler for the 'Preview' button for node_form().
389 *
390 * @see node_form_delete_submit()
391 * @see node_form_validate()
392 * @see node_form_submit()
393 * @see node_form_submit_build_node()
394 */
395 function node_form_build_preview($form, &$form_state) {
396 $node = node_form_submit_build_node($form, $form_state);
397 $form_state['node_preview'] = node_preview($node);
398 $form_state['rebuild'] = TRUE;
399 }
400
401 /**
402 * Generates a node preview.
403 *
404 * @param Drupal\node\Node $node
405 * The node to preview.
406 *
407 * @return
408 * Themed node preview.
409 *
410 * @see node_form_build_preview()
411 */
412 function node_preview(Node $node) {
413 if (node_access('create', $node) || node_access('update', $node)) {
414 _field_invoke_multiple('load', 'node', array($node->nid => $node));
415 // Load the user's name when needed.
416 if (isset($node->name)) {
417 // The use of isset() is mandatory in the context of user IDs, because
418 // user ID 0 denotes the anonymous user.
419 if ($user = user_load_by_name($node->name)) {
420 $node->uid = $user->uid;
421 $node->picture = $user->picture;
422 }
423 else {
424 $node->uid = 0; // anonymous user
425 }
426 }
427 elseif ($node->uid) {
428 $user = user_load($node->uid);
429 $node->name = $user->name;
430 $node->picture = $user->picture;
431 }
432
433 $node->changed = REQUEST_TIME;
434 $nodes = array($node->nid => $node);
435 field_attach_prepare_view('node', $nodes, 'full');
436
437 // Display a preview of the node.
438 if (!form_get_errors()) {
439 $node->in_preview = TRUE;
440 $output = theme('node_preview', array('node' => $node));
441 unset($node->in_preview);
442 }
443 drupal_set_title(t('Preview'), PASS_THROUGH);
444
445 return $output;
446 }
447 }
448
449 /**
450 * Returns HTML for a node preview for display during node creation and editing.
451 *
452 * @param $variables
453 * An associative array containing:
454 * - node: The node entity which is being previewed.
455 *
456 * @see node_preview()
457 * @ingroup themeable
458 */
459 function theme_node_preview($variables) {
460 $node = $variables['node'];
461
462 $output = '';
463
464 $preview_trimmed_version = FALSE;
465
466 $elements = node_view(clone $node, 'teaser');
467 $trimmed = drupal_render($elements);
468 $elements = node_view($node, 'full');
469 $full = drupal_render($elements);
470
471 // Do we need to preview trimmed version of post as well as full version?
472 if ($trimmed != $full) {
473 drupal_set_message(t('The trimmed version of your post shows what your post looks like when promoted to the main page or when exported for syndication.<span class="no-js"> You can insert the delimiter "&lt;!--break--&gt;" (without the quotes) to fine-tune where your post gets split.</span>'));
474 $output .= '<h3>' . t('Preview trimmed version') . '</h3>';
475 $output .= $trimmed;
476 $output .= '<h3>' . t('Preview full version') . '</h3>';
477 $output .= $full;
478 }
479 else {
480 $output .= $full;
481 }
482
483 return $output;
484 }
485
486 /**
487 * Form submission handler that saves the node for node_form().
488 *
489 * @see node_form_delete_submit()
490 * @see node_form_build_preview()
491 * @see node_form_validate()
492 * @see node_form_submit_build_node()
493 */
494 function node_form_submit($form, &$form_state) {
495 // Handle possible field translations first and then build the node from the
496 // submitted values.
497 node_field_language_form_submit($form, $form_state);
498 $node = node_form_submit_build_node($form, $form_state);
499 $insert = empty($node->nid);
500 $node->save();
501 $node_link = l(t('view'), 'node/' . $node->nid);
502 $watchdog_args = array('@type' => $node->type, '%title' => $node->title);
503 $t_args = array('@type' => node_type_get_name($node), '%title' => $node->title);
504
505 if ($insert) {
506 watchdog('content', '@type: added %title.', $watchdog_args, WATCHDOG_NOTICE, $node_link);
507 drupal_set_message(t('@type %title has been created.', $t_args));
508 }
509 else {
510 watchdog('content', '@type: updated %title.', $watchdog_args, WATCHDOG_NOTICE, $node_link);
511 drupal_set_message(t('@type %title has been updated.', $t_args));
512 }
513 if ($node->nid) {
514 $form_state['values']['nid'] = $node->nid;
515 $form_state['nid'] = $node->nid;
516 $form_state['redirect'] = 'node/' . $node->nid;
517 }
518 else {
519 // In the unlikely case something went wrong on save, the node will be
520 // rebuilt and node form redisplayed the same way as in preview.
521 drupal_set_message(t('The post could not be saved.'), 'error');
522 $form_state['rebuild'] = TRUE;
523 }
524 // Clear the page and block caches.
525 cache_invalidate(array('content' => TRUE));
526 }
527
528 /**
529 * Handles possible node language changes.
530 *
531 */
532 function node_field_language_form_submit($form, &$form_state) {
533 if (field_has_translation_handler('node', 'node')) {
534 $bundle = $form_state['values']['type'];
535 $node_language = $form_state['values']['langcode'];
536
537 foreach (field_info_instances('node', $bundle) as $instance) {
538 $field_name = $instance['field_name'];
539 $field = field_info_field($field_name);
540 $previous_langcode = $form[$field_name]['#language'];
541
542 // Handle a possible language change: New language values are inserted,
543 // previous ones are deleted.
544 if ($field['translatable'] && $previous_langcode != $node_language) {
545 $form_state['values'][$field_name][$node_language] = $form_state['values'][$field_name][$previous_langcode];
546 $form_state['values'][$field_name][$previous_langcode] = array();
547 }
548 }
549 }
550 }
551
552 /**
553 * Updates the form state's node entity by processing this submission's values.
554 *
555 * This is the default builder function for the node form. It is called
556 * during the "Save" and "Preview" submit handlers to retrieve the entity to
557 * save or preview. This function can also be called by a "Next" button of a
558 * wizard to update the form state's entity with the current step's values
559 * before proceeding to the next step.
560 *
561 * @see node_form()
562 * @see node_form_delete_submit()
563 * @see node_form_build_preview()
564 * @see node_form_validate()
565 * @see node_form_submit()
566 */
567 function node_form_submit_build_node($form, &$form_state) {
568 // @todo Legacy support for modules that extend the node form with form-level
569 // submit handlers that adjust $form_state['values'] prior to those values
570 // being used to update the entity. Module authors are encouraged to instead
571 // adjust the node directly within a hook_node_submit() implementation. For
572 // Drupal 8, evaluate whether the pattern of triggering form-level submit
573 // handlers during button-level submit processing is worth supporting
574 // properly, and if so, add a Form API function for doing so.
575 unset($form_state['submit_handlers']);
576 form_execute_handlers('submit', $form, $form_state);
577
578 $node = $form_state['node'];
579 entity_form_submit_build_entity('node', $node, $form, $form_state);
580
581 node_submit($node);
582 foreach (module_implements('node_submit') as $module) {
583 $function = $module . '_node_submit';
584 $function($node, $form, $form_state);
585 }
586 return $node;
587 }
588
589 /**
590 * Page callback: Form constructor for node deletion confirmation form.
591 *
592 * @see node_menu()
593 */
594 function node_delete_confirm($form, &$form_state, $node) {
595 $form['#node'] = $node;
596 // Always provide entity id in the same form key as in the entity edit form.
597 $form['nid'] = array('#type' => 'value', '#value' => $node->nid);
598 return confirm_form($form,
599 t('Are you sure you want to delete %title?', array('%title' => $node->title)),
600 'node/' . $node->nid,
601 t('This action cannot be undone.'),
602 t('Delete'),
603 t('Cancel')
604 );
605 }
606
607 /**
608 * Form submission handler for node_delete_confirm().
609 */
610 function node_delete_confirm_submit($form, &$form_state) {
611 if ($form_state['values']['confirm']) {
612 $node = node_load($form_state['values']['nid']);
613 node_delete($form_state['values']['nid']);
614 watchdog('content', '@type: deleted %title.', array('@type' => $node->type, '%title' => $node->title));
615 drupal_set_message(t('@type %title has been deleted.', array('@type' => node_type_get_name($node), '%title' => $node->title)));
616 }
617
618 $form_state['redirect'] = '<front>';
619 }
620
621 /**
622 * Page callback: Generates an overview table of older revisions of a node.
623 *
624 * @see node_menu()
625 */
626 function node_revision_overview($node) {
627 drupal_set_title(t('Revisions for %title', array('%title' => $node->title)), PASS_THROUGH);
628
629 $header = array(t('Revision'), array('data' => t('Operations'), 'colspan' => 2));
630
631 $revisions = node_revision_list($node);
632
633 $rows = array();
634 $revert_permission = FALSE;
635 if ((user_access('revert revisions') || user_access('administer nodes')) && node_access('update', $node)) {
636 $revert_permission = TRUE;
637 }
638 $delete_permission = FALSE;
639 if ((user_access('delete revisions') || user_access('administer nodes')) && node_access('delete', $node)) {
640 $delete_permission = TRUE;
641 }
642 foreach ($revisions as $revision) {
643 $row = array();
644 $operations = array();
645
646 if ($revision->current_vid > 0) {
647 $row[] = array('data' => t('!date by !username', array('!date' => l(format_date($revision->timestamp, 'short'), "node/$node->nid"), '!username' => theme('username', array('account' => $revision))))
648 . (($revision->log != '') ? '<p class="revision-log">' . filter_xss($revision->log) . '</p>' : ''),
649 'class' => array('revision-current'));
650 $operations[] = array('data' => drupal_placeholder(t('current revision')), 'class' => array('revision-current'), 'colspan' => 2);
651 }
652 else {
653 $row[] = t('!date by !username', array('!date' => l(format_date($revision->timestamp, 'short'), "node/$node->nid/revisions/$revision->vid/view"), '!username' => theme('username', array('account' => $revision))))
654 . (($revision->log != '') ? '<p class="revision-log">' . filter_xss($revision->log) . '</p>' : '');
655 if ($revert_permission) {
656 $operations[] = l(t('revert'), "node/$node->nid/revisions/$revision->vid/revert");
657 }
658 if ($delete_permission) {
659 $operations[] = l(t('delete'), "node/$node->nid/revisions/$revision->vid/delete");
660 }
661 }
662 $rows[] = array_merge($row, $operations);
663 }
664
665 $build['node_revisions_table'] = array(
666 '#theme' => 'table',
667 '#rows' => $rows,
668 '#header' => $header,
669 '#attached' => array (
670 'css' => array(drupal_get_path('module', 'node') . '/node.admin.css'),
671 ),
672 );
673
674 return $build;
675 }
676
677 /**
678 * Page callback: Form constructor for the reversion confirmation form.
679 *
680 * This form prevents against CSRF attacks.
681 *
682 * @see node_menu()
683 * @see node_revision_revert_confirm_submit()
684 */
685 function node_revision_revert_confirm($form, $form_state, $node_revision) {
686 $form['#node_revision'] = $node_revision;
687 return confirm_form($form, t('Are you sure you want to revert to the revision from %revision-date?', array('%revision-date' => format_date($node_revision->revision_timestamp))), 'node/' . $node_revision->nid . '/revisions', '', t('Revert'), t('Cancel'));
688 }
689
690 /**
691 * Form submission handler for node_revision_revert_confirm().
692 */
693 function node_revision_revert_confirm_submit($form, &$form_state) {
694 $node_revision = $form['#node_revision'];
695 $node_revision->revision = 1;
696
697 // The revision timestamp will be updated when the revision is saved. Keep the
698 // original one for the confirmation message.
699 $original_revision_timestamp = $node_revision->revision_timestamp;
700
701 $node_revision->log = t('Copy of the revision from %date.', array('%date' => format_date($original_revision_timestamp)));
702
703 $node_revision->save();
704
705 watchdog('content', '@type: reverted %title revision %revision.', array('@type' => $node_revision->type, '%title' => $node_revision->title, '%revision' => $node_revision->vid));
706 drupal_set_message(t('@type %title has been reverted back to the revision from %revision-date.', array('@type' => node_type_get_name($node_revision), '%title' => $node_revision->title, '%revision-date' => format_date($original_revision_timestamp))));
707 $form_state['redirect'] = 'node/' . $node_revision->nid . '/revisions';
708 }
709
710 /**
711 * Page callback: Form constructor for the revision deletion confirmation form.
712 *
713 * This form prevents against CSRF attacks.
714 *
715 * @see node_menu()
716 * @see node_revision_delete_confirm_submit()
717 */
718 function node_revision_delete_confirm($form, $form_state, $node_revision) {
719 $form['#node_revision'] = $node_revision;
720 return confirm_form($form, t('Are you sure you want to delete the revision from %revision-date?', array('%revision-date' => format_date($node_revision->revision_timestamp))), 'node/' . $node_revision->nid . '/revisions', t('This action cannot be undone.'), t('Delete'), t('Cancel'));
721 }
722
723 /**
724 * Form submission handler for node_revision_delete_confirm().
725 */
726 function node_revision_delete_confirm_submit($form, &$form_state) {
727 $node_revision = $form['#node_revision'];
728 node_revision_delete($node_revision->vid);
729
730 watchdog('content', '@type: deleted %title revision %revision.', array('@type' => $node_revision->type, '%title' => $node_revision->title, '%revision' => $node_revision->vid));
731 drupal_set_message(t('Revision from %revision-date of @type %title has been deleted.', array('%revision-date' => format_date($node_revision->revision_timestamp), '@type' => node_type_get_name($node_revision), '%title' => $node_revision->title)));
732 $form_state['redirect'] = 'node/' . $node_revision->nid;
733 if (db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid', array(':nid' => $node_revision->nid))->fetchField() > 1) {
734 $form_state['redirect'] .= '/revisions';
735 }
736 }