5 * Content administration and module settings UI.
9 * Menu callback; presents general node configuration options.
11 function node_configure() {
12 $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>';
13 $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 $form['access'] = array(
16 '#type' => 'fieldset',
17 '#title' => t('Node access status'),
19 $form['access']['status'] = array('#value' => $status);
20 $form['access']['rebuild'] = array(
22 '#value' => t('Rebuild permissions'),
23 '#submit' => array('node_configure_access_submit'),
26 $form['default_nodes_main'] = array(
27 '#type' => 'select', '#title' => t('Number of posts on main page'), '#default_value' => variable_get('default_nodes_main', 10),
28 '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30)),
29 '#description' => t('The default maximum number of posts to display per page on overview pages such as the main page.')
31 $form['teaser_length'] = array(
32 '#type' => 'select', '#title' => t('Length of trimmed posts'), '#default_value' => variable_get('teaser_length', 600),
35 200 => t('200 characters'),
36 400 => t('400 characters'),
37 600 => t('600 characters'),
38 800 => t('800 characters'),
39 1000 => t('1000 characters'),
40 1200 => t('1200 characters'),
41 1400 => t('1400 characters'),
42 1600 => t('1600 characters'),
43 1800 => t('1800 characters'),
44 2000 => t('2000 characters'),
46 '#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.")
49 $form['node_preview'] = array(
51 '#title' => t('Preview post'),
52 '#default_value' => variable_get('node_preview', 0),
53 '#options' => array(t('Optional'), t('Required')),
54 '#description' => t('Must users preview posts before submitting?'),
57 return system_settings_form($form);
61 * Form button submit callback.
63 function node_configure_access_submit($form, &$form_state) {
64 $form_state['redirect'] = 'admin/content/node-settings/rebuild';
68 * Menu callback: confirm rebuilding of permissions.
70 function node_configure_rebuild_confirm() {
71 return confirm_form(array(), t('Are you sure you want to rebuild the permissions on site content?'),
72 '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'));
76 * Handler for wipe confirmation
78 function node_configure_rebuild_confirm_submit($form, &$form_state) {
79 node_access_rebuild(TRUE
);
80 $form_state['redirect'] = 'admin/content/node-settings';
84 * Implementation of hook_node_operations().
86 function node_node_operations() {
89 'label' => t('Publish'),
90 'callback' => 'node_mass_update',
91 'callback arguments' => array('updates' => array('status' => 1)),
94 'label' => t('Unpublish'),
95 'callback' => 'node_mass_update',
96 'callback arguments' => array('updates' => array('status' => 0)),
99 'label' => t('Promote to front page'),
100 'callback' => 'node_mass_update',
101 'callback arguments' => array('updates' => array('status' => 1, 'promote' => 1)),
104 'label' => t('Demote from front page'),
105 'callback' => 'node_mass_update',
106 'callback arguments' => array('updates' => array('promote' => 0)),
109 'label' => t('Make sticky'),
110 'callback' => 'node_mass_update',
111 'callback arguments' => array('updates' => array('status' => 1, 'sticky' => 1)),
114 'label' => t('Remove stickiness'),
115 'callback' => 'node_mass_update',
116 'callback arguments' => array('updates' => array('sticky' => 0)),
119 'label' => t('Delete'),
127 * List node administration filters that can be applied.
129 function node_filters() {
131 $filters['status'] = array(
132 'title' => t('status'),
134 'status-1' => t('published'),
135 'status-0' => t('not published'),
136 'promote-1' => t('promoted'),
137 'promote-0' => t('not promoted'),
138 'sticky-1' => t('sticky'),
139 'sticky-0' => t('not sticky'),
142 // Include translation states if we have this module enabled
143 if (module_exists('translation')) {
144 $filters['status']['options'] += array(
145 'translate-0' => t('Up to date translation'),
146 'translate-1' => t('Outdated translation'),
150 $filters['type'] = array('title' => t('type'), 'options' => node_get_types('names'));
152 // The taxonomy filter
153 if ($taxonomy = module_invoke('taxonomy', 'form_all', 1)) {
154 $filters['category'] = array('title' => t('category'), 'options' => $taxonomy);
156 // Language filter if there is a list of languages
157 if ($languages = module_invoke('locale', 'language_list')) {
158 $languages = array('' => t('Language neutral')) + $languages;
159 $filters['language'] = array('title' => t('language'), 'options' => $languages);
165 * Build query for node administration filters based on session.
167 function node_build_filter_query() {
168 $filters = node_filters();
171 $where = $args = array();
173 foreach ($_SESSION['node_overview_filter'] as
$index => $filter) {
174 list($key, $value) = $filter;
177 // Note: no exploitable hole as $key/$value have already been checked when submitted
178 list($key, $value) = explode('-', $value, 2);
179 $where[] = 'n.'.
$key .
' = %d';
183 $where[] = "$table.tid = %d";
184 $join .
= "INNER JOIN {term_node} $table ON n.vid = $table.vid ";
187 $where[] = "n.type = '%s'";
190 $where[] = "n.language = '%s'";
195 $where = count($where) ?
'WHERE '.
implode(' AND ', $where) : '';
197 return array('where' => $where, 'join' => $join, 'args' => $args);
201 * Return form for node administration filters.
203 function node_filter_form() {
204 $session = &$_SESSION['node_overview_filter'];
205 $session = is_array($session) ?
$session : array();
206 $filters = node_filters();
209 $form['filters'] = array(
210 '#type' => 'fieldset',
211 '#title' => t('Show only items where'),
212 '#theme' => 'node_filters',
214 $form['#submit'][] = 'node_filter_form_submit';
215 foreach ($session as
$filter) {
216 list($type, $value) = $filter;
217 if ($type == 'category') {
218 // Load term name from DB rather than search and parse options array.
219 $value = module_invoke('taxonomy', 'get_term', $value);
220 $value = $value->name
;
222 else if ($type == 'language') {
223 $value = empty($value) ?
t('Language neutral') : module_invoke('locale', 'language_name', $value);
226 $value = $filters[$type]['options'][$value];
229 $form['filters']['current'][] = array('#value' => t('<em>and</em> where <strong>%a</strong> is <strong>%b</strong>', array('%a' => $filters[$type]['title'], '%b' => $value)));
232 $form['filters']['current'][] = array('#value' => t('<strong>%a</strong> is <strong>%b</strong>', array('%a' => $filters[$type]['title'], '%b' => $value)));
234 if (in_array($type, array('type', 'language'))) {
235 // Remove the option if it is already being filtered on.
236 unset($filters[$type]);
240 foreach ($filters as
$key => $filter) {
241 $names[$key] = $filter['title'];
242 $form['filters']['status'][$key] = array('#type' => 'select', '#options' => $filter['options']);
245 $form['filters']['filter'] = array('#type' => 'radios', '#options' => $names, '#default_value' => 'status');
246 $form['filters']['buttons']['submit'] = array('#type' => 'submit', '#value' => (count($session) ?
t('Refine') : t('Filter')));
247 if (count($session)) {
248 $form['filters']['buttons']['undo'] = array('#type' => 'submit', '#value' => t('Undo'));
249 $form['filters']['buttons']['reset'] = array('#type' => 'submit', '#value' => t('Reset'));
252 drupal_add_js('misc/form.js', 'core');
258 * Theme node administration filter form.
262 function theme_node_filter_form($form) {
264 $output .
= '<div id="node-admin-filter">';
265 $output .
= drupal_render($form['filters']);
267 $output .
= drupal_render($form);
272 * Theme node administration filter selector.
276 function theme_node_filters($form) {
278 $output .
= '<ul class="clear-block">';
279 if (!empty($form['current'])) {
280 foreach (element_children($form['current']) as
$key) {
281 $output .
= '<li>'.
drupal_render($form['current'][$key]) .
'</li>';
285 $output .
= '<li><dl class="multiselect">'.
(!empty($form['current']) ?
'<dt><em>'.
t('and') .
'</em> '.
t('where') .
'</dt>' : '') .
'<dd class="a">';
286 foreach (element_children($form['filter']) as
$key) {
287 $output .
= drupal_render($form['filter'][$key]);
291 $output .
= '<dt>'.
t('is') .
'</dt><dd class="b">';
293 foreach (element_children($form['status']) as
$key) {
294 $output .
= drupal_render($form['status'][$key]);
299 $output .
= '<div class="container-inline" id="node-admin-buttons">'.
drupal_render($form['buttons']) .
'</div>';
300 $output .
= '</li></ul>';
306 * Process result from node administration filter form.
308 function node_filter_form_submit($form, &$form_state) {
309 $filters = node_filters();
310 switch ($form_state['values']['op']) {
313 if (isset($form_state['values']['filter'])) {
314 $filter = $form_state['values']['filter'];
316 // Flatten the options array to accommodate hierarchical/nested options.
317 $flat_options = form_options_flatten($filters[$filter]['options']);
319 if (isset($flat_options[$form_state['values'][$filter]])) {
320 $_SESSION['node_overview_filter'][] = array($filter, $form_state['values'][$filter]);
325 array_pop($_SESSION['node_overview_filter']);
328 $_SESSION['node_overview_filter'] = array();
334 * Make mass update of nodes, changing all nodes in the $nodes array
335 * to update them with the field values in $updates.
337 * IMPORTANT NOTE: This function is intended to work when called
338 * from a form submit handler. Calling it outside of the form submission
339 * process may not work correctly.
341 * @param array $nodes
342 * Array of node nids to update.
343 * @param array $updates
344 * Array of key/value pairs with node field names and the
345 * value to update that field to.
347 function node_mass_update($nodes, $updates) {
348 // We use batch processing to prevent timeout when updating a large number
350 if (count($nodes) > 10) {
352 'operations' => array(
353 array('_node_mass_update_batch_process', array($nodes, $updates))
355 'finished' => '_node_mass_update_batch_finished',
356 'title' => t('Processing'),
357 // We use a single multi-pass operation, so the default
358 // 'Remaining x of y operations' message will be confusing here.
359 'progress_message' => '',
360 'error_message' => t('The update has encountered an error.'),
361 // The operations do not live in the .module file, so we need to
362 // tell the batch engine which file to load before calling them.
363 'file' => drupal_get_path('module', 'node') .
'/node.admin.inc',
368 foreach ($nodes as
$nid) {
369 _node_mass_update_helper($nid, $updates);
371 drupal_set_message(t('The update has been performed.'));
376 * Node Mass Update - helper function.
378 function _node_mass_update_helper($nid, $updates) {
379 $node = node_load($nid, NULL
, TRUE
);
380 foreach ($updates as
$name => $value) {
381 $node->$name = $value;
388 * Node Mass Update Batch operation
390 function _node_mass_update_batch_process($nodes, $updates, &$context) {
391 if (!isset($context['sandbox']['progress'])) {
392 $context['sandbox']['progress'] = 0;
393 $context['sandbox']['max'] = count($nodes);
394 $context['sandbox']['nodes'] = $nodes;
397 // Process nodes by groups of 5.
398 $count = min(5, count($context['sandbox']['nodes']));
399 for ($i = 1; $i <= $count; $i++) {
400 // For each nid, load the node, reset the values, and save it.
401 $nid = array_shift($context['sandbox']['nodes']);
402 $node = _node_mass_update_helper($nid, $updates);
404 // Store result for post-processing in the finished callback.
405 $context['results'][] = l($node->title
, 'node/'.
$node->nid
);
407 // Update our progress information.
408 $context['sandbox']['progress']++;
411 // Inform the batch engine that we are not finished,
412 // and provide an estimation of the completion level we reached.
413 if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
414 $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
419 * Node Mass Update Batch 'finished' callback.
421 function _node_mass_update_batch_finished($success, $results, $operations) {
423 drupal_set_message(t('The update has been performed.'));
426 drupal_set_message(t('An error occurred and processing did not complete.'), 'error');
427 $message = format_plural(count($results), '1 item successfully processed:', '@count items successfully processed:');
428 $message .
= theme('item_list', $results);
429 drupal_set_message($message);
434 * Menu callback: content administration.
436 function node_admin_content($form_state) {
437 if (isset($form_state['values']['operation']) && $form_state['values']['operation'] == 'delete') {
438 return node_multiple_delete_confirm($form_state, array_filter($form_state['values']['nodes']));
440 $form = node_filter_form();
442 $form['#theme'] = 'node_filter_form';
443 $form['admin'] = node_admin_nodes();
449 * Form builder: Builds the node administration overview.
451 function node_admin_nodes() {
453 $filter = node_build_filter_query();
455 $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 // Enable language column if locale is enabled or if we have any node with language
458 $count = db_result(db_query("SELECT COUNT(*) FROM {node} n WHERE language != ''"));
459 $multilanguage = (module_exists('locale') || $count);
461 $form['options'] = array(
462 '#type' => 'fieldset',
463 '#title' => t('Update options'),
464 '#prefix' => '<div class="container-inline">',
465 '#suffix' => '</div>',
468 foreach (module_invoke_all('node_operations') as
$operation => $array) {
469 $options[$operation] = $array['label'];
471 $form['options']['operation'] = array(
473 '#options' => $options,
474 '#default_value' => 'approve',
476 $form['options']['submit'] = array(
478 '#value' => t('Update'),
479 '#submit' => array('node_admin_nodes_submit'),
482 $languages = language_list();
483 $destination = drupal_get_destination();
485 while ($node = db_fetch_object($result)) {
486 $nodes[$node->nid
] = '';
487 $options = empty($node->language
) ?
array() : array('language' => $languages[$node->language
]);
488 $form['title'][$node->nid
] = array('#value' => l($node->title
, 'node/'.
$node->nid
, $options) .
' '.
theme('mark', node_mark($node->nid
, $node->changed
)));
489 $form['name'][$node->nid
] = array('#value' => check_plain(node_get_types('name', $node)));
490 $form['username'][$node->nid
] = array('#value' => theme('username', $node));
491 $form['status'][$node->nid
] = array('#value' => ($node->status ?
t('published') : t('not published')));
492 if ($multilanguage) {
493 $form['language'][$node->nid
] = array('#value' => empty($node->language
) ?
t('Language neutral') : t($languages[$node->language
]->name
));
495 $form['operations'][$node->nid
] = array('#value' => l(t('edit'), 'node/'.
$node->nid .
'/edit', array('query' => $destination)));
497 $form['nodes'] = array('#type' => 'checkboxes', '#options' => $nodes);
498 $form['pager'] = array('#value' => theme('pager', NULL
, 50, 0));
499 $form['#theme'] = 'node_admin_nodes';
504 * Validate node_admin_nodes form submissions.
506 * Check if any nodes have been selected to perform the chosen
507 * 'Update option' on.
509 function node_admin_nodes_validate($form, &$form_state) {
510 $nodes = array_filter($form_state['values']['nodes']);
511 if (count($nodes) == 0) {
512 form_set_error('', t('No items selected.'));
517 * Process node_admin_nodes form submissions.
519 * Execute the chosen 'Update option' on the selected nodes.
521 function node_admin_nodes_submit($form, &$form_state) {
522 $operations = module_invoke_all('node_operations');
523 $operation = $operations[$form_state['values']['operation']];
524 // Filter out unchecked nodes
525 $nodes = array_filter($form_state['values']['nodes']);
526 if ($function = $operation['callback']) {
527 // Add in callback arguments if present.
528 if (isset($operation['callback arguments'])) {
529 $args = array_merge(array($nodes), $operation['callback arguments']);
532 $args = array($nodes);
534 call_user_func_array($function, $args);
539 // We need to rebuild the form to go to a second step. For example, to
540 // show the confirmation form for the deletion of nodes.
541 $form_state['rebuild'] = TRUE
;
547 * Theme node administration overview.
551 function theme_node_admin_nodes($form) {
552 // If there are rows in this form, then $form['title'] contains a list of
553 // the title form elements.
554 $has_posts = isset($form['title']) && is_array($form['title']);
555 $select_header = $has_posts ?
theme('table_select_header_cell') : '';
556 $header = array($select_header, t('Title'), t('Type'), t('Author'), t('Status'));
557 if (isset($form['language'])) {
558 $header[] = t('Language');
560 $header[] = t('Operations');
563 $output .
= drupal_render($form['options']);
565 foreach (element_children($form['title']) as
$key) {
567 $row[] = drupal_render($form['nodes'][$key]);
568 $row[] = drupal_render($form['title'][$key]);
569 $row[] = drupal_render($form['name'][$key]);
570 $row[] = drupal_render($form['username'][$key]);
571 $row[] = drupal_render($form['status'][$key]);
572 if (isset($form['language'])) {
573 $row[] = drupal_render($form['language'][$key]);
575 $row[] = drupal_render($form['operations'][$key]);
581 $rows[] = array(array('data' => t('No posts available.'), 'colspan' => '6'));
584 $output .
= theme('table', $header, $rows);
585 if ($form['pager']['#value']) {
586 $output .
= drupal_render($form['pager']);
589 $output .
= drupal_render($form);
594 function node_multiple_delete_confirm(&$form_state, $nodes) {
596 $form['nodes'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE
);
597 // array_filter returns only elements with TRUE values
598 foreach ($nodes as
$nid => $value) {
599 $title = db_result(db_query('SELECT title FROM {node} WHERE nid = %d', $nid));
600 $form['nodes'][$nid] = array(
604 '#suffix' => check_plain($title) .
"</li>\n",
607 $form['operation'] = array('#type' => 'hidden', '#value' => 'delete');
608 $form['#submit'][] = 'node_multiple_delete_confirm_submit';
609 return confirm_form($form,
610 t('Are you sure you want to delete these items?'),
611 'admin/content/node', t('This action cannot be undone.'),
612 t('Delete all'), t('Cancel'));
615 function node_multiple_delete_confirm_submit($form, &$form_state) {
616 if ($form_state['values']['confirm']) {
617 foreach ($form_state['values']['nodes'] as
$nid => $value) {
620 drupal_set_message(t('The items have been deleted.'));
622 $form_state['redirect'] = 'admin/content/node';