Simpletest Coverage - modules/node/node.pages.inc

1 <?php
2 // $Id: node.pages.inc,v 1.73 2009/08/04 06:44:48 webchick Exp $
3
4 /**
5 * @file
6 * Page callbacks for adding, editing, deleting, and revisions management for content.
7 */
8
9
10 /**
11 * Menu callback; presents the node editing form, or redirects to delete confirmation.
12 */
13 function node_page_edit($node) {
14 $type_name = node_type_get_name($node);
15 drupal_set_title(t('<em>Edit @type</em> @title', array('@type' => $type_name, '@title' => $node->title)), PASS_THROUGH);
16 return drupal_get_form($node->type . '_node_form', $node);
17 }
18
19 function node_add_page() {
20 $item = menu_get_item();
21 $content = system_admin_menu_block($item);
22 // Bypass the node/add listing if only one content type is available.
23 if (count($content) == 1) {
24 $item = array_shift($content);
25 drupal_goto($item['href']);
26 }
27 return theme('node_add_list', $content);
28 }
29
30 /**
31 * Display the list of available node types for node creation.
32 *
33 * @ingroup themeable
34 */
35 function theme_node_add_list($content) {
36 $output = '';
37
38 if ($content) {
39 $output = '<dl class="node-type-list">';
40 foreach ($content as $item) {
41 $output .= '<dt>' . l($item['title'], $item['href'], $item['localized_options']) . '</dt>';
42 $output .= '<dd>' . filter_xss_admin($item['description']) . '</dd>';
43 }
44 $output .= '</dl>';
45 }
46 else {
47 $output = '<p>' . t('You have not created any content types yet. Please 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>';
48 }
49 return $output;
50 }
51
52
53 /**
54 * Present a node submission form or a set of links to such forms.
55 */
56 function node_add($type) {
57 global $user;
58
59 $types = node_type_get_types();
60 $type = isset($type) ? str_replace('-', '_', $type) : NULL;
61 // If a node type has been specified, validate its existence.
62 if (isset($types[$type]) && node_access('create', $type)) {
63 // Initialize settings:
64 $node = array('uid' => $user->uid, 'name' => (isset($user->name) ? $user->name : ''), 'type' => $type, 'language' => '');
65
66 drupal_set_title(t('Create @name', array('@name' => $types[$type]->name)), PASS_THROUGH);
67 $output = drupal_get_form($type . '_node_form', $node);
68 }
69
70 return $output;
71 }
72
73 function node_form_validate($form, &$form_state) {
74 $node = $form_state['values'];
75 node_validate($node, $form);
76
77 // Field validation. Requires access to $form_state, so this cannot be
78 // done in node_validate() as it currently exists.
79 $node = (object)$node;
80 field_attach_form_validate('node', $node, $form, $form_state);
81 }
82
83 function node_object_prepare($node) {
84 // Set up default values, if required.
85 $node_options = variable_get('node_options_' . $node->type, array('status', 'promote'));
86 // If this is a new node, fill in the default values.
87 if (!isset($node->nid)) {
88 foreach (array('status', 'promote', 'sticky') as $key) {
89 $node->$key = in_array($key, $node_options);
90 }
91 global $user;
92 $node->uid = $user->uid;
93 $node->created = REQUEST_TIME;
94 }
95 else {
96 $node->date = format_date($node->created, 'custom', 'Y-m-d H:i:s O');
97 // Remove the log message from the original node object.
98 $node->log = NULL;
99 }
100 // Always use the default revision setting.
101 $node->revision = in_array('revision', $node_options);
102
103 node_invoke($node, 'prepare');
104 module_invoke_all('node_prepare', $node);
105 }
106
107 /**
108 * Generate the node add/edit form array.
109 */
110 function node_form(&$form_state, $node) {
111 global $user;
112
113 if (isset($form_state['node'])) {
114 $node = $form_state['node'] + (array)$node;
115 }
116 if (isset($form_state['node_preview'])) {
117 $form['#prefix'] = $form_state['node_preview'];
118 }
119 $node = (object)$node;
120 foreach (array('title') as $key) {
121 if (!isset($node->$key)) {
122 $node->$key = NULL;
123 }
124 }
125 if (!isset($form_state['node_preview'])) {
126 node_object_prepare($node);
127 }
128 else {
129 $node->in_preview = TRUE;
130 }
131
132 // Set the id and identify this as a node edit form.
133 $form['#id'] = 'node-form';
134 $form['#node_edit_form'] = TRUE;
135
136 // Basic node information.
137 // These elements are just values so they are not even sent to the client.
138 foreach (array('nid', 'vid', 'uid', 'created', 'type', 'language') as $key) {
139 $form[$key] = array(
140 '#type' => 'value',
141 '#value' => isset($node->$key) ? $node->$key : NULL,
142 );
143 }
144
145 // Changed must be sent to the client, for later overwrite error checking.
146 $form['changed'] = array(
147 '#type' => 'hidden',
148 '#default_value' => isset($node->changed) ? $node->changed : NULL,
149 );
150 // Get the node-specific bits.
151 if ($extra = node_invoke($node, 'form', $form_state)) {
152 $form = array_merge_recursive($form, $extra);
153 }
154 if (!isset($form['title']['#weight'])) {
155 $form['title']['#weight'] = -5;
156 }
157
158 $form['#node'] = $node;
159
160 $form['additional_settings'] = array(
161 '#type' => 'vertical_tabs',
162 );
163
164 // Add a log field if the "Create new revision" option is checked, or if the
165 // current user has the ability to check that option.
166 if (!empty($node->revision) || user_access('administer nodes')) {
167 $form['revision_information'] = array(
168 '#type' => 'fieldset',
169 '#title' => t('Revision information'),
170 '#collapsible' => TRUE,
171 // Collapsed by default when "Create new revision" is unchecked
172 '#collapsed' => !$node->revision,
173 '#group' => 'additional_settings',
174 '#attached_js' => array(drupal_get_path('module', 'node') . '/node.js'),
175 '#weight' => 20,
176 );
177 $form['revision_information']['revision'] = array(
178 '#access' => user_access('administer nodes'),
179 '#type' => 'checkbox',
180 '#title' => t('Create new revision'),
181 '#default_value' => $node->revision,
182 );
183 $form['revision_information']['log'] = array(
184 '#type' => 'textarea',
185 '#title' => t('Revision log message'),
186 '#rows' => 4,
187 '#default_value' => !empty($node->log) ? $node->log : '',
188 '#description' => t('Provide an explanation of the changes you are making. This will help other authors understand your motivations.'),
189 );
190 }
191
192 // Node author information for administrators
193 $form['author'] = array(
194 '#type' => 'fieldset',
195 '#access' => user_access('administer nodes'),
196 '#title' => t('Authoring information'),
197 '#collapsible' => TRUE,
198 '#collapsed' => TRUE,
199 '#group' => 'additional_settings',
200 '#attached_js' => array(drupal_get_path('module', 'node') . '/node.js'),
201 '#weight' => 90,
202 );
203 $form['author']['name'] = array(
204 '#type' => 'textfield',
205 '#title' => t('Authored by'),
206 '#maxlength' => 60,
207 '#autocomplete_path' => 'user/autocomplete',
208 '#default_value' => !empty($node->name) ? $node->name : '',
209 '#weight' => -1,
210 '#description' => t('Leave blank for %anonymous.', array('%anonymous' => variable_get('anonymous', t('Anonymous')))),
211 );
212 $form['author']['date'] = array(
213 '#type' => 'textfield',
214 '#title' => t('Authored on'),
215 '#maxlength' => 25,
216 '#description' => t('Format: %time. The date format is YYYY-MM-DD and %timezone is the timezone offset from UTC. Leave blank to use the time of form submission.', array('%time' => !empty($node->date) ? $node->date : format_date($node->created, 'custom', 'Y-m-d H:i:s O'), '%timezone' => !empty($node->date) ? $node->date : format_date($node->created, 'custom', 'O'))),
217 );
218
219 if (isset($node->date)) {
220 $form['author']['date']['#default_value'] = $node->date;
221 }
222
223 // Node options for administrators
224 $form['options'] = array(
225 '#type' => 'fieldset',
226 '#access' => user_access('administer nodes'),
227 '#title' => t('Publishing options'),
228 '#collapsible' => TRUE,
229 '#collapsed' => TRUE,
230 '#group' => 'additional_settings',
231 '#attached_js' => array(drupal_get_path('module', 'node') . '/node.js'),
232 '#weight' => 95,
233 );
234 $form['options']['status'] = array(
235 '#type' => 'checkbox',
236 '#title' => t('Published'),
237 '#default_value' => $node->status,
238 );
239 $form['options']['promote'] = array(
240 '#type' => 'checkbox',
241 '#title' => t('Promoted to front page'),
242 '#default_value' => $node->promote,
243 );
244 $form['options']['sticky'] = array(
245 '#type' => 'checkbox',
246 '#title' => t('Sticky at top of lists'),
247 '#default_value' => $node->sticky,
248 );
249
250 // These values are used when the user has no administrator access.
251 foreach (array('uid', 'created') as $key) {
252 $form[$key] = array(
253 '#type' => 'value',
254 '#value' => $node->$key,
255 );
256 }
257
258 // Add the buttons.
259 $form['buttons'] = array();
260 $form['buttons']['#weight'] = 100;
261 $form['buttons']['submit'] = array(
262 '#type' => 'submit',
263 '#access' => variable_get('node_preview_' . $node->type, 1) != DRUPAL_REQUIRED || (!form_get_errors() && isset($form_state['node_preview'])),
264 '#value' => t('Save'),
265 '#weight' => 5,
266 '#submit' => array('node_form_submit'),
267 );
268 $form['buttons']['preview'] = array(
269 '#access' => variable_get('node_preview_' . $node->type, 1) != DRUPAL_DISABLED,
270 '#type' => 'submit',
271 '#value' => t('Preview'),
272 '#weight' => 10,
273 '#submit' => array('node_form_build_preview'),
274 );
275 if (!empty($node->nid) && node_access('delete', $node)) {
276 $form['buttons']['delete'] = array(
277 '#type' => 'submit',
278 '#value' => t('Delete'),
279 '#weight' => 15,
280 '#submit' => array('node_form_delete_submit'),
281 );
282 }
283 $form['#validate'][] = 'node_form_validate';
284 $form['#theme'] = array($node->type . '_node_form', 'node_form');
285
286 $form['#builder_function'] = 'node_form_submit_build_node';
287 field_attach_form('node', $node, $form, $form_state);
288
289 return $form;
290 }
291
292 /**
293 * Button submit function: handle the 'Delete' button on the node form.
294 */
295 function node_form_delete_submit($form, &$form_state) {
296 $destination = '';
297 if (isset($_REQUEST['destination'])) {
298 $destination = drupal_get_destination();
299 unset($_REQUEST['destination']);
300 }
301 $node = $form['#node'];
302 $form_state['redirect'] = array('node/' . $node->nid . '/delete', $destination);
303 }
304
305
306 function node_form_build_preview($form, &$form_state) {
307 $node = node_form_submit_build_node($form, $form_state);
308 $form_state['node_preview'] = node_preview($node);
309 }
310
311 /**
312 * Present a node submission form.
313 *
314 * @ingroup themeable
315 */
316 function theme_node_form($form) {
317 $output = "\n<div class=\"node-form\">\n";
318
319 $output .= " <div class=\"standard\">\n";
320 $output .= drupal_render_children($form);
321 $output .= " </div>\n";
322
323 $output .= "</div>\n";
324
325 return $output;
326 }
327
328 /**
329 * Generate a node preview.
330 */
331 function node_preview($node) {
332 if (node_access('create', $node) || node_access('update', $node)) {
333 // Load the user's name when needed.
334 if (isset($node->name)) {
335 // The use of isset() is mandatory in the context of user IDs, because
336 // user ID 0 denotes the anonymous user.
337 if ($user = user_load_by_name($node->name)) {
338 $node->uid = $user->uid;
339 $node->picture = $user->picture;
340 }
341 else {
342 $node->uid = 0; // anonymous user
343 }
344 }
345 elseif ($node->uid) {
346 $user = user_load($node->uid);
347 $node->name = $user->name;
348 $node->picture = $user->picture;
349 }
350
351 $node->changed = REQUEST_TIME;
352
353 // Display a preview of the node.
354 // Previewing alters $node so it needs to be cloned.
355 if (!form_get_errors()) {
356 $cloned_node = clone $node;
357 $cloned_node->in_preview = TRUE;
358 $output = theme('node_preview', $cloned_node);
359 }
360 drupal_set_title(t('Preview'), PASS_THROUGH);
361
362 return $output;
363 }
364 }
365
366 /**
367 * Display a node preview for display during node creation and editing.
368 *
369 * @param $node
370 * The node object which is being previewed.
371 *
372 * @ingroup themeable
373 */
374 function theme_node_preview($node) {
375 $output = '<div class="preview">';
376
377 $preview_trimmed_version = FALSE;
378
379 $trimmed = drupal_render(node_build(clone $node, 'teaser'));
380 $full = drupal_render(node_build($node, 'full'));
381
382 // Do we need to preview trimmed version of post as well as full version?
383 if ($trimmed != $full) {
384 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>'));
385 $output .= '<h3>' . t('Preview trimmed version') . '</h3>';
386 $output .= $trimmed;
387 $output .= '<h3>' . t('Preview full version') . '</h3>';
388 $output .= $full;
389 }
390 else {
391 $output .= $full;
392 }
393 $output .= "</div>\n";
394
395 return $output;
396 }
397
398 function node_form_submit($form, &$form_state) {
399 global $user;
400
401 $node = node_form_submit_build_node($form, $form_state);
402 $insert = empty($node->nid);
403 node_save($node);
404 $node_link = l(t('view'), 'node/' . $node->nid);
405 $watchdog_args = array('@type' => $node->type, '%title' => $node->title);
406 $t_args = array('@type' => node_type_get_name($node), '%title' => $node->title);
407
408 if ($insert) {
409 watchdog('content', '@type: added %title.', $watchdog_args, WATCHDOG_NOTICE, $node_link);
410 drupal_set_message(t('@type %title has been created.', $t_args));
411 }
412 else {
413 watchdog('content', '@type: updated %title.', $watchdog_args, WATCHDOG_NOTICE, $node_link);
414 drupal_set_message(t('@type %title has been updated.', $t_args));
415 }
416 if ($node->nid) {
417 unset($form_state['rebuild']);
418 $form_state['nid'] = $node->nid;
419 $form_state['redirect'] = 'node/' . $node->nid;
420 }
421 else {
422 // In the unlikely case something went wrong on save, the node will be
423 // rebuilt and node form redisplayed the same way as in preview.
424 drupal_set_message(t('The post could not be saved.'), 'error');
425 }
426 }
427
428 /**
429 * Build a node by processing submitted form values and prepare for a form rebuild.
430 */
431 function node_form_submit_build_node($form, &$form_state) {
432 // Unset any button-level handlers, execute all the form-level submit
433 // functions to process the form values into an updated node.
434 unset($form_state['submit_handlers']);
435 form_execute_handlers('submit', $form, $form_state);
436 $node = node_submit($form_state['values']);
437
438 field_attach_submit('node', $node, $form, $form_state);
439
440 $form_state['node'] = (array)$node;
441 $form_state['rebuild'] = TRUE;
442 return $node;
443 }
444
445 /**
446 * Menu callback -- ask for confirmation of node deletion
447 */
448 function node_delete_confirm(&$form_state, $node) {
449 $form['nid'] = array(
450 '#type' => 'value',
451 '#value' => $node->nid,
452 );
453
454 return confirm_form($form,
455 t('Are you sure you want to delete %title?', array('%title' => $node->title)),
456 isset($_GET['destination']) ? $_GET['destination'] : 'node/' . $node->nid,
457 t('This action cannot be undone.'),
458 t('Delete'),
459 t('Cancel')
460 );
461 }
462
463 /**
464 * Execute node deletion
465 */
466 function node_delete_confirm_submit($form, &$form_state) {
467 if ($form_state['values']['confirm']) {
468 $node = node_load($form_state['values']['nid']);
469 node_delete($form_state['values']['nid']);
470 watchdog('content', '@type: deleted %title.', array('@type' => $node->type, '%title' => $node->title));
471 drupal_set_message(t('@type %title has been deleted.', array('@type' => node_type_get_name($node), '%title' => $node->title)));
472 }
473
474 $form_state['redirect'] = '<front>';
475 }
476
477 /**
478 * Generate an overview table of older revisions of a node.
479 */
480 function node_revision_overview($node) {
481 drupal_set_title(t('Revisions for %title', array('%title' => $node->title)), PASS_THROUGH);
482
483 $header = array(t('Revision'), array('data' => t('Operations'), 'colspan' => 2));
484
485 $revisions = node_revision_list($node);
486
487 $rows = array();
488 $revert_permission = FALSE;
489 if ((user_access('revert revisions') || user_access('administer nodes')) && node_access('update', $node)) {
490 $revert_permission = TRUE;
491 }
492 $delete_permission = FALSE;
493 if ((user_access('delete revisions') || user_access('administer nodes')) && node_access('delete', $node)) {
494 $delete_permission = TRUE;
495 }
496 foreach ($revisions as $revision) {
497 $row = array();
498 $operations = array();
499
500 if ($revision->current_vid > 0) {
501 $row[] = array('data' => t('!date by !username', array('!date' => l(format_date($revision->timestamp, 'small'), "node/$node->nid"), '!username' => theme('username', $revision)))
502 . (($revision->log != '') ? '<p class="revision-log">' . filter_xss($revision->log) . '</p>' : ''),
503 'class' => 'revision-current');
504 $operations[] = array('data' => theme('placeholder', t('current revision')), 'class' => 'revision-current', 'colspan' => 2);
505 }
506 else {
507 $row[] = t('!date by !username', array('!date' => l(format_date($revision->timestamp, 'small'), "node/$node->nid/revisions/$revision->vid/view"), '!username' => theme('username', $revision)))
508 . (($revision->log != '') ? '<p class="revision-log">' . filter_xss($revision->log) . '</p>' : '');
509 if ($revert_permission) {
510 $operations[] = l(t('revert'), "node/$node->nid/revisions/$revision->vid/revert");
511 }
512 if ($delete_permission) {
513 $operations[] = l(t('delete'), "node/$node->nid/revisions/$revision->vid/delete");
514 }
515 }
516 $rows[] = array_merge($row, $operations);
517 }
518
519 $build['node_revisions_table'] = array(
520 '#theme' => 'table',
521 '#rows' => $rows,
522 '#header' => $header,
523 );
524
525 return $build;
526 }
527
528 /**
529 * Ask for confirmation of the reversion to prevent against CSRF attacks.
530 */
531 function node_revision_revert_confirm($form_state, $node_revision) {
532 $form['#node_revision'] = $node_revision;
533 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'));
534 }
535
536 function node_revision_revert_confirm_submit($form, &$form_state) {
537 $node_revision = $form['#node_revision'];
538 $node_revision->revision = 1;
539 $node_revision->log = t('Copy of the revision from %date.', array('%date' => format_date($node_revision->revision_timestamp)));
540 if (module_exists('taxonomy')) {
541 $node_revision->taxonomy = array_keys($node_revision->taxonomy);
542 }
543
544 node_save($node_revision);
545
546 watchdog('content', '@type: reverted %title revision %revision.', array('@type' => $node_revision->type, '%title' => $node_revision->title, '%revision' => $node_revision->vid));
547 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($node_revision->revision_timestamp))));
548 $form_state['redirect'] = 'node/' . $node_revision->nid . '/revisions';
549 }
550
551 function node_revision_delete_confirm($form_state, $node_revision) {
552 $form['#node_revision'] = $node_revision;
553 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'));
554 }
555
556 function node_revision_delete_confirm_submit($form, &$form_state) {
557 $node_revision = $form['#node_revision'];
558 db_delete('node_revision')
559 ->condition('nid', $node_revision->nid)
560 ->condition('vid', $node_revision->vid)
561 ->execute();
562 module_invoke_all('node_delete_revision', $node_revision);
563 field_attach_delete_revision('node', $node_revision);
564 watchdog('content', '@type: deleted %title revision %revision.', array('@type' => $node_revision->type, '%title' => $node_revision->title, '%revision' => $node_revision->vid));
565 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)));
566 $form_state['redirect'] = 'node/' . $node_revision->nid;
567 if (db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid', array(':nid' => $node_revision->nid))->fetchField() > 1) {
568 $form_state['redirect'] .= '/revisions';
569 }
570 }
571

Legend

Missed
lines code that were not excersized during program execution.
Covered
lines code were excersized during program execution.
Comment/non executable
Comment or non-executable line of code.
Dead
lines of code that according to xdebug could not be executed. This is counted as coverage code because in almost all cases it is code that runnable.