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