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

1 <?php
2 // $Id: node.admin.inc,v 1.60 2009/07/30 19:24:21 dries Exp $
3
4 /**
5 * @file
6 * Content administration and module settings UI.
7 */
8
9 /**
10 * Menu callback: confirm rebuilding of permissions.
11 */
12 function node_configure_rebuild_confirm() {
13 return confirm_form(array(), t('Are you sure you want to rebuild the permissions on site content?'),
14 'admin/reports/status', t('This action rebuilds all permissions on site content, and may be a lengthy process. This action cannot be undone.'), t('Rebuild permissions'), t('Cancel'));
15 }
16
17 /**
18 * Handler for wipe confirmation
19 */
20 function node_configure_rebuild_confirm_submit($form, &$form_state) {
21 node_access_rebuild(TRUE);
22 $form_state['redirect'] = 'admin/reports/status';
23 }
24
25 /**
26 * Implement hook_node_operations().
27 */
28 function node_node_operations() {
29 $operations = array(
30 'publish' => array(
31 'label' => t('Publish'),
32 'callback' => 'node_mass_update',
33 'callback arguments' => array('updates' => array('status' => 1)),
34 ),
35 'unpublish' => array(
36 'label' => t('Unpublish'),
37 'callback' => 'node_mass_update',
38 'callback arguments' => array('updates' => array('status' => 0)),
39 ),
40 'promote' => array(
41 'label' => t('Promote to front page'),
42 'callback' => 'node_mass_update',
43 'callback arguments' => array('updates' => array('status' => 1, 'promote' => 1)),
44 ),
45 'demote' => array(
46 'label' => t('Demote from front page'),
47 'callback' => 'node_mass_update',
48 'callback arguments' => array('updates' => array('promote' => 0)),
49 ),
50 'sticky' => array(
51 'label' => t('Make sticky'),
52 'callback' => 'node_mass_update',
53 'callback arguments' => array('updates' => array('status' => 1, 'sticky' => 1)),
54 ),
55 'unsticky' => array(
56 'label' => t('Remove stickiness'),
57 'callback' => 'node_mass_update',
58 'callback arguments' => array('updates' => array('sticky' => 0)),
59 ),
60 'delete' => array(
61 'label' => t('Delete'),
62 'callback' => NULL,
63 ),
64 );
65 return $operations;
66 }
67
68 /**
69 * List node administration filters that can be applied.
70 */
71 function node_filters() {
72 // Regular filters
73 $filters['status'] = array(
74 'title' => t('status'),
75 'options' => array(
76 'status-1' => t('published'),
77 'status-0' => t('not published'),
78 'promote-1' => t('promoted'),
79 'promote-0' => t('not promoted'),
80 'sticky-1' => t('sticky'),
81 'sticky-0' => t('not sticky'),
82 ),
83 );
84 // Include translation states if we have this module enabled
85 if (module_exists('translation')) {
86 $filters['status']['options'] += array(
87 'translate-0' => t('Up to date translation'),
88 'translate-1' => t('Outdated translation'),
89 );
90 }
91
92 $filters['type'] = array('title' => t('type'), 'options' => node_type_get_names());
93
94 // The taxonomy filter
95 if ($taxonomy = module_invoke('taxonomy', 'form_all', 1)) {
96 $filters['term'] = array('title' => t('term'), 'options' => $taxonomy);
97 }
98 // Language filter if there is a list of languages
99 if ($languages = module_invoke('locale', 'language_list')) {
100 $languages = array('' => t('Language neutral')) + $languages;
101 $filters['language'] = array('title' => t('language'), 'options' => $languages);
102 }
103 return $filters;
104 }
105
106 /**
107 * Apply filters for node administration filters based on session.
108 *
109 * @param $query
110 * A SelectQuery to which the filters should be applied.
111 */
112 function node_build_filter_query(SelectQueryInterface $query) {
113 // Build query
114 $filter_data = isset($_SESSION['node_overview_filter']) ? $_SESSION['node_overview_filter'] : array();
115 $counter = 0;
116 foreach ($filter_data as $index => $filter) {
117 list($key, $value) = $filter;
118 switch ($key) {
119 case 'term':
120 $index = 'tn' . $counter++;
121 $query->join('taxonomy_term_node', $index, "n.nid = $index.nid");
122 $query->condition($index . '.tid', $value);
123 break;
124 case 'status':
125 // Note: no exploitable hole as $key/$value have already been checked when submitted
126 list($key, $value) = explode('-', $value, 2);
127 case 'type':
128 case 'language':
129 $query->condition('n.' . $key, $value);
130 break;
131 }
132 }
133 }
134
135 /**
136 * Return form for node administration filters.
137 */
138 function node_filter_form() {
139 $session = isset($_SESSION['node_overview_filter']) ? $_SESSION['node_overview_filter'] : array();
140 $filters = node_filters();
141
142 $i = 0;
143 $form['filters'] = array(
144 '#type' => 'fieldset',
145 '#title' => t('Show only items where'),
146 '#theme' => 'node_filters',
147 );
148 $form['#submit'][] = 'node_filter_form_submit';
149 foreach ($session as $filter) {
150 list($type, $value) = $filter;
151 if ($type == 'term') {
152 // Load term name from DB rather than search and parse options array.
153 $value = module_invoke('taxonomy', 'term_load', $value);
154 $value = $value->name;
155 }
156 elseif ($type == 'language') {
157 $value = empty($value) ? t('Language neutral') : module_invoke('locale', 'language_name', $value);
158 }
159 else {
160 $value = $filters[$type]['options'][$value];
161 }
162 if ($i++) {
163 $form['filters']['current'][] = array('#markup' => t('<em>and</em> where <strong>%a</strong> is <strong>%b</strong>', array('%a' => $filters[$type]['title'], '%b' => $value)));
164 }
165 else {
166 $form['filters']['current'][] = array('#markup' => t('<strong>%a</strong> is <strong>%b</strong>', array('%a' => $filters[$type]['title'], '%b' => $value)));
167 }
168 if (in_array($type, array('type', 'language'))) {
169 // Remove the option if it is already being filtered on.
170 unset($filters[$type]);
171 }
172 }
173
174 foreach ($filters as $key => $filter) {
175 $names[$key] = $filter['title'];
176 $form['filters']['status'][$key] = array('#type' => 'select', '#options' => $filter['options']);
177 }
178
179 $form['filters']['filter'] = array('#type' => 'radios', '#options' => $names, '#default_value' => 'status');
180 $form['filters']['buttons']['submit'] = array('#type' => 'submit', '#value' => (count($session) ? t('Refine') : t('Filter')));
181 if (count($session)) {
182 $form['filters']['buttons']['undo'] = array('#type' => 'submit', '#value' => t('Undo'));
183 $form['filters']['buttons']['reset'] = array('#type' => 'submit', '#value' => t('Reset'));
184 }
185
186 drupal_add_js('misc/form.js');
187
188 return $form;
189 }
190
191 /**
192 * Theme node administration filter form.
193 *
194 * @ingroup themeable
195 */
196 function theme_node_filter_form($form) {
197 $output = '';
198 $output .= '<div id="node-admin-filter">';
199 $output .= drupal_render($form['filters']);
200 $output .= '</div>';
201 $output .= drupal_render_children($form);
202 return $output;
203 }
204
205 /**
206 * Theme node administration filter selector.
207 *
208 * @ingroup themeable
209 */
210 function theme_node_filters($form) {
211 $output = '';
212 $output .= '<ul class="clearfix">';
213 if (!empty($form['current'])) {
214 foreach (element_children($form['current']) as $key) {
215 $output .= '<li>' . drupal_render($form['current'][$key]) . '</li>';
216 }
217 }
218
219 $output .= '<li><dl class="multiselect">' . (!empty($form['current']) ? '<dt><em>' . t('and') . '</em> ' . t('where') . '</dt>' : '') . '<dd class="a">';
220 foreach (element_children($form['filter']) as $key) {
221 $output .= drupal_render($form['filter'][$key]);
222 }
223 $output .= '</dd>';
224
225 $output .= '<dt>' . t('is') . '</dt><dd class="b">';
226
227 foreach (element_children($form['status']) as $key) {
228 $output .= drupal_render($form['status'][$key]);
229 }
230 $output .= '</dd>';
231
232 $output .= '</dl>';
233 $output .= '<div class="container-inline" id="node-admin-buttons">' . drupal_render($form['buttons']) . '</div>';
234 $output .= '</li></ul>';
235
236 return $output;
237 }
238
239 /**
240 * Process result from node administration filter form.
241 */
242 function node_filter_form_submit($form, &$form_state) {
243 $filters = node_filters();
244 switch ($form_state['values']['op']) {
245 case t('Filter'):
246 case t('Refine'):
247 if (isset($form_state['values']['filter'])) {
248 $filter = $form_state['values']['filter'];
249
250 // Flatten the options array to accommodate hierarchical/nested options.
251 $flat_options = form_options_flatten($filters[$filter]['options']);
252
253 if (isset($flat_options[$form_state['values'][$filter]])) {
254 $_SESSION['node_overview_filter'][] = array($filter, $form_state['values'][$filter]);
255 }
256 }
257 break;
258 case t('Undo'):
259 array_pop($_SESSION['node_overview_filter']);
260 break;
261 case t('Reset'):
262 $_SESSION['node_overview_filter'] = array();
263 break;
264 }
265 }
266
267 /**
268 * Make mass update of nodes, changing all nodes in the $nodes array
269 * to update them with the field values in $updates.
270 *
271 * IMPORTANT NOTE: This function is intended to work when called
272 * from a form submit handler. Calling it outside of the form submission
273 * process may not work correctly.
274 *
275 * @param array $nodes
276 * Array of node nids to update.
277 * @param array $updates
278 * Array of key/value pairs with node field names and the
279 * value to update that field to.
280 */
281 function node_mass_update($nodes, $updates) {
282 // We use batch processing to prevent timeout when updating a large number
283 // of nodes.
284 if (count($nodes) > 10) {
285 $batch = array(
286 'operations' => array(
287 array('_node_mass_update_batch_process', array($nodes, $updates))
288 ),
289 'finished' => '_node_mass_update_batch_finished',
290 'title' => t('Processing'),
291 // We use a single multi-pass operation, so the default
292 // 'Remaining x of y operations' message will be confusing here.
293 'progress_message' => '',
294 'error_message' => t('The update has encountered an error.'),
295 // The operations do not live in the .module file, so we need to
296 // tell the batch engine which file to load before calling them.
297 'file' => drupal_get_path('module', 'node') . '/node.admin.inc',
298 );
299 batch_set($batch);
300 }
301 else {
302 foreach ($nodes as $nid) {
303 _node_mass_update_helper($nid, $updates);
304 }
305 drupal_set_message(t('The update has been performed.'));
306 }
307 }
308
309 /**
310 * Node Mass Update - helper function.
311 */
312 function _node_mass_update_helper($nid, $updates) {
313 $node = node_load($nid, NULL, TRUE);
314 foreach ($updates as $name => $value) {
315 $node->$name = $value;
316 }
317 node_save($node);
318 return $node;
319 }
320
321 /**
322 * Node Mass Update Batch operation
323 */
324 function _node_mass_update_batch_process($nodes, $updates, &$context) {
325 if (!isset($context['sandbox']['progress'])) {
326 $context['sandbox']['progress'] = 0;
327 $context['sandbox']['max'] = count($nodes);
328 $context['sandbox']['nodes'] = $nodes;
329 }
330
331 // Process nodes by groups of 5.
332 $count = min(5, count($context['sandbox']['nodes']));
333 for ($i = 1; $i <= $count; $i++) {
334 // For each nid, load the node, reset the values, and save it.
335 $nid = array_shift($context['sandbox']['nodes']);
336 $node = _node_mass_update_helper($nid, $updates);
337
338 // Store result for post-processing in the finished callback.
339 $context['results'][] = l($node->title, 'node/' . $node->nid);
340
341 // Update our progress information.
342 $context['sandbox']['progress']++;
343 }
344
345 // Inform the batch engine that we are not finished,
346 // and provide an estimation of the completion level we reached.
347 if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
348 $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
349 }
350 }
351
352 /**
353 * Node Mass Update Batch 'finished' callback.
354 */
355 function _node_mass_update_batch_finished($success, $results, $operations) {
356 if ($success) {
357 drupal_set_message(t('The update has been performed.'));
358 }
359 else {
360 drupal_set_message(t('An error occurred and processing did not complete.'), 'error');
361 $message = format_plural(count($results), '1 item successfully processed:', '@count items successfully processed:');
362 $message .= theme('item_list', $results);
363 drupal_set_message($message);
364 }
365 }
366
367 /**
368 * Menu callback: content administration.
369 */
370 function node_admin_content($form_state) {
371 if (isset($form_state['values']['operation']) && $form_state['values']['operation'] == 'delete') {
372 return node_multiple_delete_confirm($form_state, array_filter($form_state['values']['nodes']));
373 }
374 $form = array();
375 // Show the 'add new content' link.
376 if (_node_add_access()) {
377 $form['add_content'] = array(
378 '#type' => 'markup',
379 '#markup' => l(t('Add new content'), 'node/add', array('attributes' => array('class' => 'node-admin-add-content'))),
380 );
381 }
382 $form[] = node_filter_form();
383
384 $form['#theme'] = 'node_filter_form';
385 $form['admin'] = node_admin_nodes();
386
387 return $form;
388 }
389
390 /**
391 * Form builder: Builds the node administration overview.
392 */
393 function node_admin_nodes() {
394 // Enable language column if translation module is enabled
395 // or if we have any node with language.
396 $multilanguage = (module_exists('translation') || db_query("SELECT COUNT(*) FROM {node} WHERE language <> ''")->fetchField());
397
398 // Build the sortable table header.
399 $header = array();
400 $header[] = theme('table_select_header_cell');
401 $header[] = array('data' => t('Title'), 'field' => 'n.title');
402 $header[] = array('data' => t('Type'), 'field' => 'n.type');
403 $header[] = array('data' => t('Author'), 'field' => 'u.name');
404 $header[] = array('data' => t('Status'), 'field' => 'n.status');
405 $header[] = array('data' => t('Updated'), 'field' => 'n.changed', 'sort' => 'desc');
406 if ($multilanguage) {
407 $header[] = array('data' => t('Language'), 'field' => 'n.language');
408 }
409 $header[] = array('data' => t('Operations'));
410
411 $form['header'] = array(
412 '#type' => 'value',
413 '#value' => $header,
414 );
415
416 $query = db_select('node', 'n')->extend('PagerDefault')->extend('TableSort');
417 $query->join('users', 'u', 'n.uid = u.uid');
418 node_build_filter_query($query);
419
420 $result = $query
421 ->fields('n')
422 ->fields('u', array('name'))
423 ->limit(50)
424 ->execute();
425
426 // Build the 'Update options' form.
427 $form['options'] = array(
428 '#type' => 'fieldset',
429 '#title' => t('Update options'),
430 '#prefix' => '<div class="container-inline">',
431 '#suffix' => '</div>',
432 );
433 $options = array();
434 foreach (module_invoke_all('node_operations') as $operation => $array) {
435 $options[$operation] = $array['label'];
436 }
437 $form['options']['operation'] = array(
438 '#type' => 'select',
439 '#options' => $options,
440 '#default_value' => 'approve',
441 );
442 $form['options']['submit'] = array(
443 '#type' => 'submit',
444 '#value' => t('Update'),
445 '#submit' => array('node_admin_nodes_submit'),
446 );
447
448 $languages = language_list();
449 $destination = drupal_get_destination();
450 $nodes = array();
451 foreach ($result as $node) {
452 $nodes[$node->nid] = '';
453 $options = empty($node->language) ? array() : array('language' => $languages[$node->language]);
454 $form['title'][$node->nid] = array('#markup' => l($node->title, 'node/' . $node->nid, $options) . ' ' . theme('mark', node_mark($node->nid, $node->changed)));
455 $form['name'][$node->nid] = array('#markup' => check_plain(node_type_get_name($node)));
456 $form['username'][$node->nid] = array('#markup' => theme('username', $node));
457 $form['status'][$node->nid] = array('#markup' => ($node->status ? t('published') : t('not published')));
458 $form['changed'][$node->nid] = array('#markup' => format_date($node->changed, 'small'));
459 if ($multilanguage) {
460 $form['language'][$node->nid] = array('#markup' => empty($node->language) ? t('Language neutral') : t($languages[$node->language]->name));
461 }
462 $form['operations'][$node->nid] = array('#markup' => l(t('edit'), 'node/' . $node->nid . '/edit', array('query' => $destination)));
463 }
464 $form['nodes'] = array(
465 '#type' => 'checkboxes',
466 '#options' => $nodes,
467 );
468 $form['pager'] = array('#markup' => theme('pager', NULL));
469 $form['#theme'] = 'node_admin_nodes';
470 return $form;
471 }
472
473 /**
474 * Validate node_admin_nodes form submissions.
475 *
476 * Check if any nodes have been selected to perform the chosen
477 * 'Update option' on.
478 */
479 function node_admin_nodes_validate($form, &$form_state) {
480 $nodes = array_filter($form_state['values']['nodes']);
481 if (count($nodes) == 0) {
482 form_set_error('', t('No items selected.'));
483 }
484 }
485
486 /**
487 * Process node_admin_nodes form submissions.
488 *
489 * Execute the chosen 'Update option' on the selected nodes.
490 */
491 function node_admin_nodes_submit($form, &$form_state) {
492 $operations = module_invoke_all('node_operations');
493 $operation = $operations[$form_state['values']['operation']];
494 // Filter out unchecked nodes
495 $nodes = array_filter($form_state['values']['nodes']);
496 if ($function = $operation['callback']) {
497 // Add in callback arguments if present.
498 if (isset($operation['callback arguments'])) {
499 $args = array_merge(array($nodes), $operation['callback arguments']);
500 }
501 else {
502 $args = array($nodes);
503 }
504 call_user_func_array($function, $args);
505
506 cache_clear_all();
507 }
508 else {
509 // We need to rebuild the form to go to a second step. For example, to
510 // show the confirmation form for the deletion of nodes.
511 $form_state['rebuild'] = TRUE;
512 }
513 }
514
515
516 /**
517 * Theme node administration overview.
518 *
519 * @ingroup themeable
520 */
521 function theme_node_admin_nodes($form) {
522 $output = '';
523 $output .= drupal_render($form['options']);
524
525 $header = $form['header']['#value'];
526
527 $has_posts = isset($form['title']) && is_array($form['title']);
528 if ($has_posts) {
529 $rows = array();
530 foreach (element_children($form['title']) as $key) {
531 $row = array();
532 $row[] = drupal_render($form['nodes'][$key]);
533 $row[] = drupal_render($form['title'][$key]);
534 $row[] = drupal_render($form['name'][$key]);
535 $row[] = drupal_render($form['username'][$key]);
536 $row[] = drupal_render($form['status'][$key]);
537 $row[] = drupal_render($form['changed'][$key]);
538 if (isset($form['language'])) {
539 $row[] = drupal_render($form['language'][$key]);
540 }
541 $row[] = drupal_render($form['operations'][$key]);
542 $rows[] = $row;
543 }
544 }
545 else {
546 $rows[] = array(
547 array('data' => t('No content available.'), 'colspan' => count($header)),
548 );
549 }
550
551 $output .= theme('table', $header, $rows);
552
553 if ($form['pager']['#markup']) {
554 $output .= drupal_render($form['pager']);
555 }
556
557 $output .= drupal_render_children($form);
558
559 return $output;
560 }
561
562 function node_multiple_delete_confirm(&$form_state, $nodes) {
563 $form['nodes'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE);
564 // array_filter returns only elements with TRUE values
565 foreach ($nodes as $nid => $value) {
566 $title = db_query('SELECT title FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetchField();
567 $form['nodes'][$nid] = array(
568 '#type' => 'hidden',
569 '#value' => $nid,
570 '#prefix' => '<li>',
571 '#suffix' => check_plain($title) . "</li>\n",
572 );
573 }
574 $form['operation'] = array('#type' => 'hidden', '#value' => 'delete');
575 $form['#submit'][] = 'node_multiple_delete_confirm_submit';
576 $confirm_question = format_plural(count($nodes),
577 'Are you sure you want to delete this item?',
578 'Are you sure you want to delete these items?');
579 return confirm_form($form,
580 $confirm_question,
581 'admin/content', t('This action cannot be undone.'),
582 t('Delete'), t('Cancel'));
583 }
584
585 function node_multiple_delete_confirm_submit($form, &$form_state) {
586 if ($form_state['values']['confirm']) {
587 node_delete_multiple(array_keys($form_state['values']['nodes']));
588 $count = count($form_state['values']['nodes']);
589 watchdog('content', 'Deleted @count posts.', array('@count' => $count));
590 drupal_set_message(t('Deleted @count posts.', array('@count' => $count)));
591 }
592 $form_state['redirect'] = 'admin/content';
593 return;
594 }
595
596 /**
597 * Implement hook_modules_installed()
598 */
599 function node_modules_installed($modules) {
600 // Clear node type cache for node permissions.
601 node_type_clear();
602 }

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.