summaryrefslogtreecommitdiff
path: root/kolab.org/www/drupal-7.26/modules/node
diff options
context:
space:
mode:
Diffstat (limited to 'kolab.org/www/drupal-7.26/modules/node')
-rw-r--r--kolab.org/www/drupal-7.26/modules/node/content_types.inc450
-rw-r--r--kolab.org/www/drupal-7.26/modules/node/content_types.js34
-rw-r--r--kolab.org/www/drupal-7.26/modules/node/node.admin.inc704
-rw-r--r--kolab.org/www/drupal-7.26/modules/node/node.api.php1298
-rw-r--r--kolab.org/www/drupal-7.26/modules/node/node.css10
-rw-r--r--kolab.org/www/drupal-7.26/modules/node/node.info16
-rw-r--r--kolab.org/www/drupal-7.26/modules/node/node.install938
-rw-r--r--kolab.org/www/drupal-7.26/modules/node/node.js43
-rw-r--r--kolab.org/www/drupal-7.26/modules/node/node.module4174
-rw-r--r--kolab.org/www/drupal-7.26/modules/node/node.pages.inc678
-rw-r--r--kolab.org/www/drupal-7.26/modules/node/node.test2848
-rw-r--r--kolab.org/www/drupal-7.26/modules/node/node.tokens.inc191
-rw-r--r--kolab.org/www/drupal-7.26/modules/node/node.tpl.php112
-rw-r--r--kolab.org/www/drupal-7.26/modules/node/tests/node_access_test.info12
-rw-r--r--kolab.org/www/drupal-7.26/modules/node/tests/node_access_test.install42
-rw-r--r--kolab.org/www/drupal-7.26/modules/node/tests/node_access_test.module230
-rw-r--r--kolab.org/www/drupal-7.26/modules/node/tests/node_test.info12
-rw-r--r--kolab.org/www/drupal-7.26/modules/node/tests/node_test.module181
-rw-r--r--kolab.org/www/drupal-7.26/modules/node/tests/node_test_exception.info12
-rw-r--r--kolab.org/www/drupal-7.26/modules/node/tests/node_test_exception.module15
20 files changed, 12000 insertions, 0 deletions
diff --git a/kolab.org/www/drupal-7.26/modules/node/content_types.inc b/kolab.org/www/drupal-7.26/modules/node/content_types.inc
new file mode 100644
index 0000000..55af667
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/node/content_types.inc
@@ -0,0 +1,450 @@
+<?php
+
+/**
+ * @file
+ * Content type editing user interface.
+ */
+
+/**
+ * Displays the content type admin overview page.
+ */
+function node_overview_types() {
+ $types = node_type_get_types();
+ $names = node_type_get_names();
+ $field_ui = module_exists('field_ui');
+ $header = array(t('Name'), array('data' => t('Operations'), 'colspan' => $field_ui ? '4' : '2'));
+ $rows = array();
+
+ foreach ($names as $key => $name) {
+ $type = $types[$key];
+ if (node_hook($type->type, 'form')) {
+ $type_url_str = str_replace('_', '-', $type->type);
+ $row = array(theme('node_admin_overview', array('name' => $name, 'type' => $type)));
+ // Set the edit column.
+ $row[] = array('data' => l(t('edit'), 'admin/structure/types/manage/' . $type_url_str));
+
+ if ($field_ui) {
+ // Manage fields.
+ $row[] = array('data' => l(t('manage fields'), 'admin/structure/types/manage/' . $type_url_str . '/fields'));
+
+ // Display fields.
+ $row[] = array('data' => l(t('manage display'), 'admin/structure/types/manage/' . $type_url_str . '/display'));
+ }
+
+ // Set the delete column.
+ if ($type->custom) {
+ $row[] = array('data' => l(t('delete'), 'admin/structure/types/manage/' . $type_url_str . '/delete'));
+ }
+ else {
+ $row[] = array('data' => '');
+ }
+
+ $rows[] = $row;
+ }
+ }
+
+ $build['node_table'] = array(
+ '#theme' => 'table',
+ '#header' => $header,
+ '#rows' => $rows,
+ '#empty' => t('No content types available. <a href="@link">Add content type</a>.', array('@link' => url('admin/structure/types/add'))),
+ );
+
+ return $build;
+}
+
+/**
+ * Returns HTML for a node type description for the content type admin overview page.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - name: The human-readable name of the content type.
+ * - type: An object containing the 'type' (machine name) and 'description' of
+ * the content type.
+ *
+ * @ingroup themeable
+ */
+function theme_node_admin_overview($variables) {
+ $name = $variables['name'];
+ $type = $variables['type'];
+
+ $output = check_plain($name);
+ $output .= ' <small>' . t('(Machine name: @type)', array('@type' => $type->type)) . '</small>';
+ $output .= '<div class="description">' . filter_xss_admin($type->description) . '</div>';
+ return $output;
+}
+
+/**
+ * Form constructor for the node type editing form.
+ *
+ * @param $type
+ * (optional) An object representing the node type, when editing an existing
+ * node type.
+ *
+ * @see node_type_form_validate()
+ * @see node_type_form_submit()
+ * @ingroup forms
+ */
+function node_type_form($form, &$form_state, $type = NULL) {
+ if (!isset($type->type)) {
+ // This is a new type. Node module managed types are custom and unlocked.
+ $type = node_type_set_defaults(array('custom' => 1, 'locked' => 0));
+ }
+
+ // Make the type object available to implementations of hook_form_alter.
+ $form['#node_type'] = $type;
+
+ $form['name'] = array(
+ '#title' => t('Name'),
+ '#type' => 'textfield',
+ '#default_value' => $type->name,
+ '#description' => t('The human-readable name of this content type. This text will be displayed as part of the list on the <em>Add new content</em> page. It is recommended that this name begin with a capital letter and contain only letters, numbers, and spaces. This name must be unique.'),
+ '#required' => TRUE,
+ '#size' => 30,
+ );
+
+ $form['type'] = array(
+ '#type' => 'machine_name',
+ '#default_value' => $type->type,
+ '#maxlength' => 32,
+ '#disabled' => $type->locked,
+ '#machine_name' => array(
+ 'exists' => 'node_type_load',
+ ),
+ '#description' => t('A unique machine-readable name for this content type. It must only contain lowercase letters, numbers, and underscores. This name will be used for constructing the URL of the %node-add page, in which underscores will be converted into hyphens.', array(
+ '%node-add' => t('Add new content'),
+ )),
+ );
+
+ $form['description'] = array(
+ '#title' => t('Description'),
+ '#type' => 'textarea',
+ '#default_value' => $type->description,
+ '#description' => t('Describe this content type. The text will be displayed on the <em>Add new content</em> page.'),
+ );
+
+ $form['additional_settings'] = array(
+ '#type' => 'vertical_tabs',
+ '#attached' => array(
+ 'js' => array(drupal_get_path('module', 'node') . '/content_types.js'),
+ ),
+ );
+
+ $form['submission'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Submission form settings'),
+ '#collapsible' => TRUE,
+ '#group' => 'additional_settings',
+ );
+ $form['submission']['title_label'] = array(
+ '#title' => t('Title field label'),
+ '#type' => 'textfield',
+ '#default_value' => $type->title_label,
+ '#required' => TRUE,
+ );
+ if (!$type->has_title) {
+ // Avoid overwriting a content type that intentionally does not have a
+ // title field.
+ $form['submission']['title_label']['#attributes'] = array('disabled' => 'disabled');
+ $form['submission']['title_label']['#description'] = t('This content type does not have a title field.');
+ $form['submission']['title_label']['#required'] = FALSE;
+ }
+ $form['submission']['node_preview'] = array(
+ '#type' => 'radios',
+ '#title' => t('Preview before submitting'),
+ '#default_value' => variable_get('node_preview_' . $type->type, DRUPAL_OPTIONAL),
+ '#options' => array(
+ DRUPAL_DISABLED => t('Disabled'),
+ DRUPAL_OPTIONAL => t('Optional'),
+ DRUPAL_REQUIRED => t('Required'),
+ ),
+ );
+ $form['submission']['help'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Explanation or submission guidelines'),
+ '#default_value' => $type->help,
+ '#description' => t('This text will be displayed at the top of the page when creating or editing content of this type.'),
+ );
+ $form['workflow'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Publishing options'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#group' => 'additional_settings',
+ );
+ $form['workflow']['node_options'] = array('#type' => 'checkboxes',
+ '#title' => t('Default options'),
+ '#default_value' => variable_get('node_options_' . $type->type, array('status', 'promote')),
+ '#options' => array(
+ 'status' => t('Published'),
+ 'promote' => t('Promoted to front page'),
+ 'sticky' => t('Sticky at top of lists'),
+ 'revision' => t('Create new revision'),
+ ),
+ '#description' => t('Users with the <em>Administer content</em> permission will be able to override these options.'),
+ );
+ $form['display'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Display settings'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#group' => 'additional_settings',
+ );
+ $form['display']['node_submitted'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Display author and date information.'),
+ '#default_value' => variable_get('node_submitted_' . $type->type, TRUE),
+ '#description' => t('Author username and publish date will be displayed.'),
+ );
+ $form['old_type'] = array(
+ '#type' => 'value',
+ '#value' => $type->type,
+ );
+ $form['orig_type'] = array(
+ '#type' => 'value',
+ '#value' => isset($type->orig_type) ? $type->orig_type : '',
+ );
+ $form['base'] = array(
+ '#type' => 'value',
+ '#value' => $type->base,
+ );
+ $form['custom'] = array(
+ '#type' => 'value',
+ '#value' => $type->custom,
+ );
+ $form['modified'] = array(
+ '#type' => 'value',
+ '#value' => $type->modified,
+ );
+ $form['locked'] = array(
+ '#type' => 'value',
+ '#value' => $type->locked,
+ );
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save content type'),
+ '#weight' => 40,
+ );
+
+ if ($type->custom) {
+ if (!empty($type->type)) {
+ $form['actions']['delete'] = array(
+ '#type' => 'submit',
+ '#value' => t('Delete content type'),
+ '#weight' => 45,
+ );
+ }
+ }
+
+ return $form;
+}
+
+/**
+ * Helper function for teaser length choices.
+ */
+function _node_characters($length) {
+ return ($length == 0) ? t('Unlimited') : format_plural($length, '1 character', '@count characters');
+}
+
+/**
+ * Form validation handler for node_type_form().
+ *
+ * @see node_type_form_submit()
+ */
+function node_type_form_validate($form, &$form_state) {
+ $type = new stdClass();
+ $type->type = $form_state['values']['type'];
+ $type->name = trim($form_state['values']['name']);
+
+ // Work out what the type was before the user submitted this form
+ $old_type = $form_state['values']['old_type'];
+
+ $types = node_type_get_names();
+
+ if (!$form_state['values']['locked']) {
+ // 'theme' conflicts with theme_node_form().
+ // '0' is invalid, since elsewhere we check it using empty().
+ if (in_array($type->type, array('0', 'theme'))) {
+ form_set_error('type', t("Invalid machine-readable name. Enter a name other than %invalid.", array('%invalid' => $type->type)));
+ }
+ }
+
+ $names = array_flip($types);
+
+ if (isset($names[$type->name]) && $names[$type->name] != $old_type) {
+ form_set_error('name', t('The human-readable name %name is already taken.', array('%name' => $type->name)));
+ }
+}
+
+/**
+ * Form submission handler for node_type_form().
+ *
+ * @see node_type_form_validate()
+ */
+function node_type_form_submit($form, &$form_state) {
+ $op = isset($form_state['values']['op']) ? $form_state['values']['op'] : '';
+
+ $type = node_type_set_defaults();
+
+ $type->type = $form_state['values']['type'];
+ $type->name = trim($form_state['values']['name']);
+ $type->orig_type = trim($form_state['values']['orig_type']);
+ $type->old_type = isset($form_state['values']['old_type']) ? $form_state['values']['old_type'] : $type->type;
+
+ $type->description = $form_state['values']['description'];
+ $type->help = $form_state['values']['help'];
+ $type->title_label = $form_state['values']['title_label'];
+ // title_label is required in core; has_title will always be true, unless a
+ // module alters the title field.
+ $type->has_title = ($type->title_label != '');
+
+ $type->base = !empty($form_state['values']['base']) ? $form_state['values']['base'] : 'node_content';
+ $type->custom = $form_state['values']['custom'];
+ $type->modified = TRUE;
+ $type->locked = $form_state['values']['locked'];
+ if (isset($form['#node_type']->module)) {
+ $type->module = $form['#node_type']->module;
+ }
+
+ if ($op == t('Delete content type')) {
+ $form_state['redirect'] = 'admin/structure/types/manage/' . str_replace('_', '-', $type->old_type) . '/delete';
+ return;
+ }
+
+ $variables = $form_state['values'];
+
+ // Remove everything that's been saved already - whatever's left is assumed
+ // to be a persistent variable.
+ foreach ($variables as $key => $value) {
+ if (isset($type->$key)) {
+ unset($variables[$key]);
+ }
+ }
+
+ unset($variables['form_token'], $variables['op'], $variables['submit'], $variables['delete'], $variables['reset'], $variables['form_id'], $variables['form_build_id']);
+
+ // Save or reset persistent variable values.
+ foreach ($variables as $key => $value) {
+ $variable_new = $key . '_' . $type->type;
+ $variable_old = $key . '_' . $type->old_type;
+
+ if (is_array($value)) {
+ $value = array_keys(array_filter($value));
+ }
+ variable_set($variable_new, $value);
+
+ if ($variable_new != $variable_old) {
+ variable_del($variable_old);
+ }
+ }
+
+ // Saving the content type after saving the variables allows modules to act
+ // on those variables via hook_node_type_insert().
+ $status = node_type_save($type);
+
+ node_types_rebuild();
+ menu_rebuild();
+ $t_args = array('%name' => $type->name);
+
+ if ($status == SAVED_UPDATED) {
+ drupal_set_message(t('The content type %name has been updated.', $t_args));
+ }
+ elseif ($status == SAVED_NEW) {
+ node_add_body_field($type);
+ drupal_set_message(t('The content type %name has been added.', $t_args));
+ watchdog('node', 'Added content type %name.', $t_args, WATCHDOG_NOTICE, l(t('view'), 'admin/structure/types'));
+ }
+
+ $form_state['redirect'] = 'admin/structure/types';
+ return;
+}
+
+/**
+ * Implements hook_node_type_insert().
+ */
+function node_node_type_insert($info) {
+ if (!empty($info->old_type) && $info->old_type != $info->type) {
+ $update_count = node_type_update_nodes($info->old_type, $info->type);
+
+ if ($update_count) {
+ drupal_set_message(format_plural($update_count, 'Changed the content type of 1 post from %old-type to %type.', 'Changed the content type of @count posts from %old-type to %type.', array('%old-type' => $info->old_type, '%type' => $info->type)));
+ }
+ }
+}
+
+/**
+ * Implements hook_node_type_update().
+ */
+function node_node_type_update($info) {
+ if (!empty($info->old_type) && $info->old_type != $info->type) {
+ $update_count = node_type_update_nodes($info->old_type, $info->type);
+
+ if ($update_count) {
+ drupal_set_message(format_plural($update_count, 'Changed the content type of 1 post from %old-type to %type.', 'Changed the content type of @count posts from %old-type to %type.', array('%old-type' => $info->old_type, '%type' => $info->type)));
+ }
+ }
+}
+
+/**
+ * Resets relevant fields of a module-defined node type to their default values.
+ *
+ * @param $type
+ * The node type to reset. The node type is passed back by reference with its
+ * resetted values. If there is no module-defined info for this node type,
+ * then nothing happens.
+ */
+function node_type_reset($type) {
+ $info_array = module_invoke_all('node_info');
+ if (isset($info_array[$type->orig_type])) {
+ $info_array[$type->orig_type]['type'] = $type->orig_type;
+ $info = node_type_set_defaults($info_array[$type->orig_type]);
+
+ foreach ($info as $field => $value) {
+ $type->$field = $value;
+ }
+ }
+}
+
+/**
+ * Menu callback; delete a single content type.
+ *
+ * @ingroup forms
+ */
+function node_type_delete_confirm($form, &$form_state, $type) {
+ $form['type'] = array('#type' => 'value', '#value' => $type->type);
+ $form['name'] = array('#type' => 'value', '#value' => $type->name);
+
+ $message = t('Are you sure you want to delete the content type %type?', array('%type' => $type->name));
+ $caption = '';
+
+ $num_nodes = db_query("SELECT COUNT(*) FROM {node} WHERE type = :type", array(':type' => $type->type))->fetchField();
+ if ($num_nodes) {
+ $caption .= '<p>' . format_plural($num_nodes, '%type is used by 1 piece of content on your site. If you remove this content type, you will not be able to edit the %type content and it may not display correctly.', '%type is used by @count pieces of content on your site. If you remove %type, you will not be able to edit the %type content and it may not display correctly.', array('%type' => $type->name)) . '</p>';
+ }
+
+ $caption .= '<p>' . t('This action cannot be undone.') . '</p>';
+
+ return confirm_form($form, $message, 'admin/structure/types', $caption, t('Delete'));
+}
+
+/**
+ * Process content type delete confirm submissions.
+ *
+ * @see node_type_delete_confirm()
+ */
+function node_type_delete_confirm_submit($form, &$form_state) {
+ node_type_delete($form_state['values']['type']);
+
+ variable_del('node_preview_' . $form_state['values']['type']);
+ $t_args = array('%name' => $form_state['values']['name']);
+ drupal_set_message(t('The content type %name has been deleted.', $t_args));
+ watchdog('node', 'Deleted content type %name.', $t_args, WATCHDOG_NOTICE);
+
+ node_types_rebuild();
+ menu_rebuild();
+
+ $form_state['redirect'] = 'admin/structure/types';
+ return;
+}
diff --git a/kolab.org/www/drupal-7.26/modules/node/content_types.js b/kolab.org/www/drupal-7.26/modules/node/content_types.js
new file mode 100644
index 0000000..0031c32
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/node/content_types.js
@@ -0,0 +1,34 @@
+(function ($) {
+
+Drupal.behaviors.contentTypes = {
+ attach: function (context) {
+ // Provide the vertical tab summaries.
+ $('fieldset#edit-submission', context).drupalSetSummary(function(context) {
+ var vals = [];
+ vals.push(Drupal.checkPlain($('#edit-title-label', context).val()) || Drupal.t('Requires a title'));
+ return vals.join(', ');
+ });
+ $('fieldset#edit-workflow', context).drupalSetSummary(function(context) {
+ var vals = [];
+ $("input[name^='node_options']:checked", context).parent().each(function() {
+ vals.push(Drupal.checkPlain($(this).text()));
+ });
+ if (!$('#edit-node-options-status', context).is(':checked')) {
+ vals.unshift(Drupal.t('Not published'));
+ }
+ return vals.join(', ');
+ });
+ $('fieldset#edit-display', context).drupalSetSummary(function(context) {
+ var vals = [];
+ $('input:checked', context).next('label').each(function() {
+ vals.push(Drupal.checkPlain($(this).text()));
+ });
+ if (!$('#edit-node-submitted', context).is(':checked')) {
+ vals.unshift(Drupal.t("Don't display post information"));
+ }
+ return vals.join(', ');
+ });
+ }
+};
+
+})(jQuery);
diff --git a/kolab.org/www/drupal-7.26/modules/node/node.admin.inc b/kolab.org/www/drupal-7.26/modules/node/node.admin.inc
new file mode 100644
index 0000000..0d0bbc0
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/node/node.admin.inc
@@ -0,0 +1,704 @@
+<?php
+
+/**
+ * @file
+ * Content administration and module settings UI.
+ */
+
+/**
+ * Menu callback: confirm rebuilding of permissions.
+ *
+ * @see node_configure_rebuild_confirm_submit()
+ * @see node_menu()
+ * @ingroup forms
+ */
+function node_configure_rebuild_confirm() {
+ return confirm_form(array(), t('Are you sure you want to rebuild the permissions on site content?'),
+ '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'));
+}
+
+/**
+ * Handler for wipe confirmation
+ *
+ * @see node_configure_rebuild_confirm()
+ */
+function node_configure_rebuild_confirm_submit($form, &$form_state) {
+ node_access_rebuild(TRUE);
+ $form_state['redirect'] = 'admin/reports/status';
+}
+
+/**
+ * Implements hook_node_operations().
+ */
+function node_node_operations() {
+ $operations = array(
+ 'publish' => array(
+ 'label' => t('Publish selected content'),
+ 'callback' => 'node_mass_update',
+ 'callback arguments' => array('updates' => array('status' => NODE_PUBLISHED)),
+ ),
+ 'unpublish' => array(
+ 'label' => t('Unpublish selected content'),
+ 'callback' => 'node_mass_update',
+ 'callback arguments' => array('updates' => array('status' => NODE_NOT_PUBLISHED)),
+ ),
+ 'promote' => array(
+ 'label' => t('Promote selected content to front page'),
+ 'callback' => 'node_mass_update',
+ 'callback arguments' => array('updates' => array('status' => NODE_PUBLISHED, 'promote' => NODE_PROMOTED)),
+ ),
+ 'demote' => array(
+ 'label' => t('Demote selected content from front page'),
+ 'callback' => 'node_mass_update',
+ 'callback arguments' => array('updates' => array('promote' => NODE_NOT_PROMOTED)),
+ ),
+ 'sticky' => array(
+ 'label' => t('Make selected content sticky'),
+ 'callback' => 'node_mass_update',
+ 'callback arguments' => array('updates' => array('status' => NODE_PUBLISHED, 'sticky' => NODE_STICKY)),
+ ),
+ 'unsticky' => array(
+ 'label' => t('Make selected content not sticky'),
+ 'callback' => 'node_mass_update',
+ 'callback arguments' => array('updates' => array('sticky' => NODE_NOT_STICKY)),
+ ),
+ 'delete' => array(
+ 'label' => t('Delete selected content'),
+ 'callback' => NULL,
+ ),
+ );
+ return $operations;
+}
+
+/**
+ * List node administration filters that can be applied.
+ *
+ * @return
+ * An associative array of filters.
+ */
+function node_filters() {
+ // Regular filters
+ $filters['status'] = array(
+ 'title' => t('status'),
+ 'options' => array(
+ '[any]' => t('any'),
+ 'status-1' => t('published'),
+ 'status-0' => t('not published'),
+ 'promote-1' => t('promoted'),
+ 'promote-0' => t('not promoted'),
+ 'sticky-1' => t('sticky'),
+ 'sticky-0' => t('not sticky'),
+ ),
+ );
+ // Include translation states if we have this module enabled
+ if (module_exists('translation')) {
+ $filters['status']['options'] += array(
+ 'translate-0' => t('Up to date translation'),
+ 'translate-1' => t('Outdated translation'),
+ );
+ }
+
+ $filters['type'] = array(
+ 'title' => t('type'),
+ 'options' => array(
+ '[any]' => t('any'),
+ ) + node_type_get_names(),
+ );
+
+ // Language filter if there is a list of languages
+ if ($languages = module_invoke('locale', 'language_list')) {
+ $languages = array(LANGUAGE_NONE => t('Language neutral')) + $languages;
+ $filters['language'] = array(
+ 'title' => t('language'),
+ 'options' => array(
+ '[any]' => t('any'),
+ ) + $languages,
+ );
+ }
+ return $filters;
+}
+
+/**
+ * Applies filters for node administration filters based on session.
+ *
+ * @param $query
+ * A SelectQuery to which the filters should be applied.
+ */
+function node_build_filter_query(SelectQueryInterface $query) {
+ // Build query
+ $filter_data = isset($_SESSION['node_overview_filter']) ? $_SESSION['node_overview_filter'] : array();
+ foreach ($filter_data as $index => $filter) {
+ list($key, $value) = $filter;
+ switch ($key) {
+ case 'status':
+ // Note: no exploitable hole as $key/$value have already been checked when submitted
+ list($key, $value) = explode('-', $value, 2);
+ case 'type':
+ case 'language':
+ $query->condition('n.' . $key, $value);
+ break;
+ }
+ }
+}
+
+/**
+ * Returns the node administration filters form array to node_admin_content().
+ *
+ * @see node_admin_nodes()
+ * @see node_admin_nodes_submit()
+ * @see node_admin_nodes_validate()
+ * @see node_filter_form_submit()
+ * @see node_multiple_delete_confirm()
+ * @see node_multiple_delete_confirm_submit()
+ *
+ * @ingroup forms
+ */
+function node_filter_form() {
+ $session = isset($_SESSION['node_overview_filter']) ? $_SESSION['node_overview_filter'] : array();
+ $filters = node_filters();
+
+ $i = 0;
+ $form['filters'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Show only items where'),
+ '#theme' => 'exposed_filters__node',
+ );
+ foreach ($session as $filter) {
+ list($type, $value) = $filter;
+ if ($type == 'term') {
+ // Load term name from DB rather than search and parse options array.
+ $value = module_invoke('taxonomy', 'term_load', $value);
+ $value = $value->name;
+ }
+ elseif ($type == 'language') {
+ $value = $value == LANGUAGE_NONE ? t('Language neutral') : module_invoke('locale', 'language_name', $value);
+ }
+ else {
+ $value = $filters[$type]['options'][$value];
+ }
+ $t_args = array('%property' => $filters[$type]['title'], '%value' => $value);
+ if ($i++) {
+ $form['filters']['current'][] = array('#markup' => t('and where %property is %value', $t_args));
+ }
+ else {
+ $form['filters']['current'][] = array('#markup' => t('where %property is %value', $t_args));
+ }
+ if (in_array($type, array('type', 'language'))) {
+ // Remove the option if it is already being filtered on.
+ unset($filters[$type]);
+ }
+ }
+
+ $form['filters']['status'] = array(
+ '#type' => 'container',
+ '#attributes' => array('class' => array('clearfix')),
+ '#prefix' => ($i ? '<div class="additional-filters">' . t('and where') . '</div>' : ''),
+ );
+ $form['filters']['status']['filters'] = array(
+ '#type' => 'container',
+ '#attributes' => array('class' => array('filters')),
+ );
+ foreach ($filters as $key => $filter) {
+ $form['filters']['status']['filters'][$key] = array(
+ '#type' => 'select',
+ '#options' => $filter['options'],
+ '#title' => $filter['title'],
+ '#default_value' => '[any]',
+ );
+ }
+
+ $form['filters']['status']['actions'] = array(
+ '#type' => 'actions',
+ '#attributes' => array('class' => array('container-inline')),
+ );
+ $form['filters']['status']['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => count($session) ? t('Refine') : t('Filter'),
+ );
+ if (count($session)) {
+ $form['filters']['status']['actions']['undo'] = array('#type' => 'submit', '#value' => t('Undo'));
+ $form['filters']['status']['actions']['reset'] = array('#type' => 'submit', '#value' => t('Reset'));
+ }
+
+ drupal_add_js('misc/form.js');
+
+ return $form;
+}
+
+/**
+ * Form submission handler for node_filter_form().
+ *
+ * @see node_admin_content()
+ * @see node_admin_nodes()
+ * @see node_admin_nodes_submit()
+ * @see node_admin_nodes_validate()
+ * @see node_filter_form()
+ * @see node_multiple_delete_confirm()
+ * @see node_multiple_delete_confirm_submit()
+ */
+function node_filter_form_submit($form, &$form_state) {
+ $filters = node_filters();
+ switch ($form_state['values']['op']) {
+ case t('Filter'):
+ case t('Refine'):
+ // Apply every filter that has a choice selected other than 'any'.
+ foreach ($filters as $filter => $options) {
+ if (isset($form_state['values'][$filter]) && $form_state['values'][$filter] != '[any]') {
+ // Flatten the options array to accommodate hierarchical/nested options.
+ $flat_options = form_options_flatten($filters[$filter]['options']);
+ // Only accept valid selections offered on the dropdown, block bad input.
+ if (isset($flat_options[$form_state['values'][$filter]])) {
+ $_SESSION['node_overview_filter'][] = array($filter, $form_state['values'][$filter]);
+ }
+ }
+ }
+ break;
+ case t('Undo'):
+ array_pop($_SESSION['node_overview_filter']);
+ break;
+ case t('Reset'):
+ $_SESSION['node_overview_filter'] = array();
+ break;
+ }
+}
+
+/**
+ * Make mass update of nodes, changing all nodes in the $nodes array
+ * to update them with the field values in $updates.
+ *
+ * IMPORTANT NOTE: This function is intended to work when called from a form
+ * submission handler. Calling it outside of the form submission process may not
+ * work correctly.
+ *
+ * @param array $nodes
+ * Array of node nids to update.
+ * @param array $updates
+ * Array of key/value pairs with node field names and the value to update that
+ * field to.
+ */
+function node_mass_update($nodes, $updates) {
+ // We use batch processing to prevent timeout when updating a large number
+ // of nodes.
+ if (count($nodes) > 10) {
+ $batch = array(
+ 'operations' => array(
+ array('_node_mass_update_batch_process', array($nodes, $updates))
+ ),
+ 'finished' => '_node_mass_update_batch_finished',
+ 'title' => t('Processing'),
+ // We use a single multi-pass operation, so the default
+ // 'Remaining x of y operations' message will be confusing here.
+ 'progress_message' => '',
+ 'error_message' => t('The update has encountered an error.'),
+ // The operations do not live in the .module file, so we need to
+ // tell the batch engine which file to load before calling them.
+ 'file' => drupal_get_path('module', 'node') . '/node.admin.inc',
+ );
+ batch_set($batch);
+ }
+ else {
+ foreach ($nodes as $nid) {
+ _node_mass_update_helper($nid, $updates);
+ }
+ drupal_set_message(t('The update has been performed.'));
+ }
+}
+
+/**
+ * Updates individual nodes when fewer than 10 are queued.
+ *
+ * @param $nid
+ * ID of node to update.
+ * @param $updates
+ * Associative array of updates.
+ *
+ * @return object
+ * An updated node object.
+ *
+ * @see node_mass_update()
+ */
+function _node_mass_update_helper($nid, $updates) {
+ $node = node_load($nid, NULL, TRUE);
+ // For efficiency manually save the original node before applying any changes.
+ $node->original = clone $node;
+ foreach ($updates as $name => $value) {
+ $node->$name = $value;
+ }
+ node_save($node);
+ return $node;
+}
+
+/**
+ * Executes a batch operation for node_mass_update().
+ *
+ * @param array $nodes
+ * An array of node IDs.
+ * @param array $updates
+ * Associative array of updates.
+ * @param array $context
+ * An array of contextual key/values.
+ */
+function _node_mass_update_batch_process($nodes, $updates, &$context) {
+ if (!isset($context['sandbox']['progress'])) {
+ $context['sandbox']['progress'] = 0;
+ $context['sandbox']['max'] = count($nodes);
+ $context['sandbox']['nodes'] = $nodes;
+ }
+
+ // Process nodes by groups of 5.
+ $count = min(5, count($context['sandbox']['nodes']));
+ for ($i = 1; $i <= $count; $i++) {
+ // For each nid, load the node, reset the values, and save it.
+ $nid = array_shift($context['sandbox']['nodes']);
+ $node = _node_mass_update_helper($nid, $updates);
+
+ // Store result for post-processing in the finished callback.
+ $context['results'][] = l($node->title, 'node/' . $node->nid);
+
+ // Update our progress information.
+ $context['sandbox']['progress']++;
+ }
+
+ // Inform the batch engine that we are not finished,
+ // and provide an estimation of the completion level we reached.
+ if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
+ $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+ }
+}
+
+/**
+ * Menu callback: Reports the status of batch operation for node_mass_update().
+ *
+ * @param bool $success
+ * A boolean indicating whether the batch mass update operation successfully
+ * concluded.
+ * @param int $results
+ * The number of nodes updated via the batch mode process.
+ * @param array $operations
+ * An array of function calls (not used in this function).
+ */
+function _node_mass_update_batch_finished($success, $results, $operations) {
+ if ($success) {
+ drupal_set_message(t('The update has been performed.'));
+ }
+ else {
+ drupal_set_message(t('An error occurred and processing did not complete.'), 'error');
+ $message = format_plural(count($results), '1 item successfully processed:', '@count items successfully processed:');
+ $message .= theme('item_list', array('items' => $results));
+ drupal_set_message($message);
+ }
+}
+
+/**
+ * Page callback: Form constructor for the content administration form.
+ *
+ * @see node_admin_nodes()
+ * @see node_admin_nodes_submit()
+ * @see node_admin_nodes_validate()
+ * @see node_filter_form()
+ * @see node_filter_form_submit()
+ * @see node_menu()
+ * @see node_multiple_delete_confirm()
+ * @see node_multiple_delete_confirm_submit()
+ * @ingroup forms
+ */
+function node_admin_content($form, $form_state) {
+ if (isset($form_state['values']['operation']) && $form_state['values']['operation'] == 'delete') {
+ return node_multiple_delete_confirm($form, $form_state, array_filter($form_state['values']['nodes']));
+ }
+ $form['filter'] = node_filter_form();
+ $form['#submit'][] = 'node_filter_form_submit';
+ $form['admin'] = node_admin_nodes();
+
+ return $form;
+}
+
+/**
+ * Form builder: Builds the node administration overview.
+ *
+ * @see node_admin_nodes_submit()
+ * @see node_admin_nodes_validate()
+ * @see node_filter_form()
+ * @see node_filter_form_submit()
+ * @see node_multiple_delete_confirm()
+ * @see node_multiple_delete_confirm_submit()
+ *
+ * @ingroup forms
+ */
+function node_admin_nodes() {
+ $admin_access = user_access('administer nodes');
+
+ // Build the 'Update options' form.
+ $form['options'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Update options'),
+ '#attributes' => array('class' => array('container-inline')),
+ '#access' => $admin_access,
+ );
+ $options = array();
+ foreach (module_invoke_all('node_operations') as $operation => $array) {
+ $options[$operation] = $array['label'];
+ }
+ $form['options']['operation'] = array(
+ '#type' => 'select',
+ '#title' => t('Operation'),
+ '#title_display' => 'invisible',
+ '#options' => $options,
+ '#default_value' => 'approve',
+ );
+ $form['options']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Update'),
+ '#validate' => array('node_admin_nodes_validate'),
+ '#submit' => array('node_admin_nodes_submit'),
+ );
+
+ // Enable language column if translation module is enabled or if we have any
+ // node with language.
+ $multilanguage = (module_exists('translation') || db_query_range("SELECT 1 FROM {node} WHERE language <> :language", 0, 1, array(':language' => LANGUAGE_NONE))->fetchField());
+
+ // Build the sortable table header.
+ $header = array(
+ 'title' => array('data' => t('Title'), 'field' => 'n.title'),
+ 'type' => array('data' => t('Type'), 'field' => 'n.type'),
+ 'author' => t('Author'),
+ 'status' => array('data' => t('Status'), 'field' => 'n.status'),
+ 'changed' => array('data' => t('Updated'), 'field' => 'n.changed', 'sort' => 'desc')
+ );
+ if ($multilanguage) {
+ $header['language'] = array('data' => t('Language'), 'field' => 'n.language');
+ }
+ $header['operations'] = array('data' => t('Operations'));
+
+ $query = db_select('node', 'n')->extend('PagerDefault')->extend('TableSort');
+ node_build_filter_query($query);
+
+ if (!user_access('bypass node access')) {
+ // If the user is able to view their own unpublished nodes, allow them
+ // to see these in addition to published nodes. Check that they actually
+ // have some unpublished nodes to view before adding the condition.
+ if (user_access('view own unpublished content') && $own_unpublished = db_query('SELECT nid FROM {node} WHERE uid = :uid AND status = :status', array(':uid' => $GLOBALS['user']->uid, ':status' => 0))->fetchCol()) {
+ $query->condition(db_or()
+ ->condition('n.status', 1)
+ ->condition('n.nid', $own_unpublished, 'IN')
+ );
+ }
+ else {
+ // If not, restrict the query to published nodes.
+ $query->condition('n.status', 1);
+ }
+ }
+ $nids = $query
+ ->fields('n',array('nid'))
+ ->limit(50)
+ ->orderByHeader($header)
+ ->addTag('node_access')
+ ->execute()
+ ->fetchCol();
+ $nodes = node_load_multiple($nids);
+
+ // Prepare the list of nodes.
+ $languages = language_list();
+ $destination = drupal_get_destination();
+ $options = array();
+ foreach ($nodes as $node) {
+ $langcode = entity_language('node', $node);
+ $l_options = $langcode != LANGUAGE_NONE && isset($languages[$langcode]) ? array('language' => $languages[$langcode]) : array();
+ $options[$node->nid] = array(
+ 'title' => array(
+ 'data' => array(
+ '#type' => 'link',
+ '#title' => $node->title,
+ '#href' => 'node/' . $node->nid,
+ '#options' => $l_options,
+ '#suffix' => ' ' . theme('mark', array('type' => node_mark($node->nid, $node->changed))),
+ ),
+ ),
+ 'type' => check_plain(node_type_get_name($node)),
+ 'author' => theme('username', array('account' => $node)),
+ 'status' => $node->status ? t('published') : t('not published'),
+ 'changed' => format_date($node->changed, 'short'),
+ );
+ if ($multilanguage) {
+ if ($langcode == LANGUAGE_NONE || isset($languages[$langcode])) {
+ $options[$node->nid]['language'] = $langcode == LANGUAGE_NONE ? t('Language neutral') : t($languages[$langcode]->name);
+ }
+ else {
+ $options[$node->nid]['language'] = t('Undefined language (@langcode)', array('@langcode' => $langcode));
+ }
+ }
+ // Build a list of all the accessible operations for the current node.
+ $operations = array();
+ if (node_access('update', $node)) {
+ $operations['edit'] = array(
+ 'title' => t('edit'),
+ 'href' => 'node/' . $node->nid . '/edit',
+ 'query' => $destination,
+ );
+ }
+ if (node_access('delete', $node)) {
+ $operations['delete'] = array(
+ 'title' => t('delete'),
+ 'href' => 'node/' . $node->nid . '/delete',
+ 'query' => $destination,
+ );
+ }
+ $options[$node->nid]['operations'] = array();
+ if (count($operations) > 1) {
+ // Render an unordered list of operations links.
+ $options[$node->nid]['operations'] = array(
+ 'data' => array(
+ '#theme' => 'links__node_operations',
+ '#links' => $operations,
+ '#attributes' => array('class' => array('links', 'inline')),
+ ),
+ );
+ }
+ elseif (!empty($operations)) {
+ // Render the first and only operation as a link.
+ $link = reset($operations);
+ $options[$node->nid]['operations'] = array(
+ 'data' => array(
+ '#type' => 'link',
+ '#title' => $link['title'],
+ '#href' => $link['href'],
+ '#options' => array('query' => $link['query']),
+ ),
+ );
+ }
+ }
+
+ // Only use a tableselect when the current user is able to perform any
+ // operations.
+ if ($admin_access) {
+ $form['nodes'] = array(
+ '#type' => 'tableselect',
+ '#header' => $header,
+ '#options' => $options,
+ '#empty' => t('No content available.'),
+ );
+ }
+ // Otherwise, use a simple table.
+ else {
+ $form['nodes'] = array(
+ '#theme' => 'table',
+ '#header' => $header,
+ '#rows' => $options,
+ '#empty' => t('No content available.'),
+ );
+ }
+
+ $form['pager'] = array('#markup' => theme('pager'));
+ return $form;
+}
+
+/**
+ * Validate node_admin_nodes form submissions.
+ *
+ * Checks whether any nodes have been selected to perform the chosen 'Update
+ * option' on.
+ *
+ * @see node_admin_nodes()
+ * @see node_admin_nodes_submit()
+ * @see node_filter_form()
+ * @see node_filter_form_submit()
+ * @see node_multiple_delete_confirm()
+ * @see node_multiple_delete_confirm_submit()
+ */
+function node_admin_nodes_validate($form, &$form_state) {
+ // Error if there are no items to select.
+ if (!is_array($form_state['values']['nodes']) || !count(array_filter($form_state['values']['nodes']))) {
+ form_set_error('', t('No items selected.'));
+ }
+}
+
+/**
+ * Process node_admin_nodes form submissions.
+ *
+ * Executes the chosen 'Update option' on the selected nodes.
+ *
+ * @see node_admin_nodes()
+ * @see node_admin_nodes_validate()
+ * @see node_filter_form()
+ * @see node_filter_form_submit()
+ * @see node_multiple_delete_confirm()
+ * @see node_multiple_delete_confirm_submit()
+ */
+function node_admin_nodes_submit($form, &$form_state) {
+ $operations = module_invoke_all('node_operations');
+ $operation = $operations[$form_state['values']['operation']];
+ // Filter out unchecked nodes
+ $nodes = array_filter($form_state['values']['nodes']);
+ if ($function = $operation['callback']) {
+ // Add in callback arguments if present.
+ if (isset($operation['callback arguments'])) {
+ $args = array_merge(array($nodes), $operation['callback arguments']);
+ }
+ else {
+ $args = array($nodes);
+ }
+ call_user_func_array($function, $args);
+
+ cache_clear_all();
+ }
+ else {
+ // We need to rebuild the form to go to a second step. For example, to
+ // show the confirmation form for the deletion of nodes.
+ $form_state['rebuild'] = TRUE;
+ }
+}
+
+/**
+ * Multiple node deletion confirmation form for node_admin_content().
+ *
+ * @see node_admin_nodes()
+ * @see node_admin_nodes_submit()
+ * @see node_admin_nodes_validate()
+ * @see node_filter_form()
+ * @see node_filter_form_submit()
+ * @see node_multiple_delete_confirm_submit()
+ * @ingroup forms
+ */
+function node_multiple_delete_confirm($form, &$form_state, $nodes) {
+ $form['nodes'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE);
+ // array_filter returns only elements with TRUE values
+ foreach ($nodes as $nid => $value) {
+ $title = db_query('SELECT title FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetchField();
+ $form['nodes'][$nid] = array(
+ '#type' => 'hidden',
+ '#value' => $nid,
+ '#prefix' => '<li>',
+ '#suffix' => check_plain($title) . "</li>\n",
+ );
+ }
+ $form['operation'] = array('#type' => 'hidden', '#value' => 'delete');
+ $form['#submit'][] = 'node_multiple_delete_confirm_submit';
+ $confirm_question = format_plural(count($nodes),
+ 'Are you sure you want to delete this item?',
+ 'Are you sure you want to delete these items?');
+ return confirm_form($form,
+ $confirm_question,
+ 'admin/content', t('This action cannot be undone.'),
+ t('Delete'), t('Cancel'));
+}
+
+/**
+ * Form submission handler for node_multiple_delete_confirm().
+ *
+ * @see node_admin_nodes()
+ * @see node_admin_nodes_submit()
+ * @see node_admin_nodes_validate()
+ * @see node_filter_form()
+ * @see node_filter_form_submit()
+ * @see node_multiple_delete_confirm()
+ */
+function node_multiple_delete_confirm_submit($form, &$form_state) {
+ if ($form_state['values']['confirm']) {
+ node_delete_multiple(array_keys($form_state['values']['nodes']));
+ cache_clear_all();
+ $count = count($form_state['values']['nodes']);
+ watchdog('content', 'Deleted @count posts.', array('@count' => $count));
+ drupal_set_message(format_plural($count, 'Deleted 1 post.', 'Deleted @count posts.'));
+ }
+ $form_state['redirect'] = 'admin/content';
+}
diff --git a/kolab.org/www/drupal-7.26/modules/node/node.api.php b/kolab.org/www/drupal-7.26/modules/node/node.api.php
new file mode 100644
index 0000000..f8dcfde
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/node/node.api.php
@@ -0,0 +1,1298 @@
+<?php
+
+/**
+ * @file
+ * Hooks provided by the Node module.
+ */
+
+/**
+ * @defgroup node_api_hooks Node API Hooks
+ * @{
+ * Functions to define and modify content types.
+ *
+ * Each content type is maintained by a primary module, which is either
+ * node.module (for content types created in the user interface) or the module
+ * that implements hook_node_info() to define the content type.
+ *
+ * During node operations (create, update, view, delete, etc.), there are
+ * several sets of hooks that get invoked to allow modules to modify the base
+ * node operation:
+ * - Node-type-specific hooks: These hooks are only invoked on the primary
+ * module, using the "base" return component of hook_node_info() as the
+ * function prefix. For example, poll.module defines the base for the Poll
+ * content type as "poll", so during creation of a poll node, hook_insert() is
+ * only invoked by calling poll_insert().
+ * - All-module hooks: This set of hooks is invoked on all implementing modules,
+ * to allow other modules to modify what the primary node module is doing. For
+ * example, hook_node_insert() is invoked on all modules when creating a poll
+ * node.
+ * - Field hooks: Hooks related to the fields attached to the node. These are
+ * invoked from the field operations functions described below, and can be
+ * either field-type-specific or all-module hooks.
+ * - Entity hooks: Generic hooks for "entity" operations. These are always
+ * invoked on all modules.
+ *
+ * Here is a list of the node and entity hooks that are invoked, field
+ * operations, and other steps that take place during node operations:
+ * - Creating a new node (calling node_save() on a new node):
+ * - field_attach_presave()
+ * - hook_node_presave() (all)
+ * - hook_entity_presave() (all)
+ * - Node and revision records are written to the database
+ * - hook_insert() (node-type-specific)
+ * - field_attach_insert()
+ * - hook_node_insert() (all)
+ * - hook_entity_insert() (all)
+ * - hook_node_access_records() (all)
+ * - hook_node_access_records_alter() (all)
+ * - Updating an existing node (calling node_save() on an existing node):
+ * - field_attach_presave()
+ * - hook_node_presave() (all)
+ * - hook_entity_presave() (all)
+ * - Node and revision records are written to the database
+ * - hook_update() (node-type-specific)
+ * - field_attach_update()
+ * - hook_node_update() (all)
+ * - hook_entity_update() (all)
+ * - hook_node_access_records() (all)
+ * - hook_node_access_records_alter() (all)
+ * - Loading a node (calling node_load(), node_load_multiple() or entity_load()
+ * with $entity_type of 'node'):
+ * - Node and revision information is read from database.
+ * - hook_load() (node-type-specific)
+ * - field_attach_load_revision() and field_attach_load()
+ * - hook_entity_load() (all)
+ * - hook_node_load() (all)
+ * - Viewing a single node (calling node_view() - note that the input to
+ * node_view() is a loaded node, so the Loading steps above are already done):
+ * - hook_view() (node-type-specific)
+ * - field_attach_prepare_view()
+ * - hook_entity_prepare_view() (all)
+ * - field_attach_view()
+ * - hook_node_view() (all)
+ * - hook_entity_view() (all)
+ * - hook_node_view_alter() (all)
+ * - hook_entity_view_alter() (all)
+ * - Viewing multiple nodes (calling node_view_multiple() - note that the input
+ * to node_view_multiple() is a set of loaded nodes, so the Loading steps
+ * above are already done):
+ * - field_attach_prepare_view()
+ * - hook_entity_prepare_view() (all)
+ * - hook_view() (node-type-specific)
+ * - field_attach_view()
+ * - hook_node_view() (all)
+ * - hook_entity_view() (all)
+ * - hook_node_view_alter() (all)
+ * - hook_entity_view_alter() (all)
+ * - Deleting a node (calling node_delete() or node_delete_multiple()):
+ * - Node is loaded (see Loading section above)
+ * - hook_delete() (node-type-specific)
+ * - hook_node_delete() (all)
+ * - hook_entity_delete() (all)
+ * - field_attach_delete()
+ * - Node and revision information are deleted from database
+ * - Deleting a node revision (calling node_revision_delete()):
+ * - Node is loaded (see Loading section above)
+ * - Revision information is deleted from database
+ * - hook_node_revision_delete() (all)
+ * - field_attach_delete_revision()
+ * - Preparing a node for editing (calling node_form() - note that if it is an
+ * existing node, it will already be loaded; see the Loading section above):
+ * - hook_prepare() (node-type-specific)
+ * - hook_node_prepare() (all)
+ * - hook_form() (node-type-specific)
+ * - field_attach_form()
+ * - Validating a node during editing form submit (calling
+ * node_form_validate()):
+ * - hook_validate() (node-type-specific)
+ * - hook_node_validate() (all)
+ * - field_attach_form_validate()
+ * - Searching (calling node_search_execute()):
+ * - hook_ranking() (all)
+ * - Query is executed to find matching nodes
+ * - Resulting node is loaded (see Loading section above)
+ * - Resulting node is prepared for viewing (see Viewing a single node above)
+ * - comment_node_update_index() is called.
+ * - hook_node_search_result() (all)
+ * - Search indexing (calling node_update_index()):
+ * - Node is loaded (see Loading section above)
+ * - Node is prepared for viewing (see Viewing a single node above)
+ * - hook_node_update_index() (all)
+ * @}
+ */
+
+/**
+ * @addtogroup hooks
+ * @{
+ */
+
+/**
+ * Inform the node access system what permissions the user has.
+ *
+ * This hook is for implementation by node access modules. In this hook,
+ * the module grants a user different "grant IDs" within one or more
+ * "realms". In hook_node_access_records(), the realms and grant IDs are
+ * associated with permission to view, edit, and delete individual nodes.
+ *
+ * The realms and grant IDs can be arbitrarily defined by your node access
+ * module; it is common to use role IDs as grant IDs, but that is not required.
+ * Your module could instead maintain its own list of users, where each list has
+ * an ID. In that case, the return value of this hook would be an array of the
+ * list IDs that this user is a member of.
+ *
+ * A node access module may implement as many realms as necessary to properly
+ * define the access privileges for the nodes. Note that the system makes no
+ * distinction between published and unpublished nodes. It is the module's
+ * responsibility to provide appropriate realms to limit access to unpublished
+ * content.
+ *
+ * Node access records are stored in the {node_access} table and define which
+ * grants are required to access a node. There is a special case for the view
+ * operation -- a record with node ID 0 corresponds to a "view all" grant for
+ * the realm and grant ID of that record. If there are no node access modules
+ * enabled, the core node module adds a node ID 0 record for realm 'all'. Node
+ * access modules can also grant "view all" permission on their custom realms;
+ * for example, a module could create a record in {node_access} with:
+ * @code
+ * $record = array(
+ * 'nid' => 0,
+ * 'gid' => 888,
+ * 'realm' => 'example_realm',
+ * 'grant_view' => 1,
+ * 'grant_update' => 0,
+ * 'grant_delete' => 0,
+ * );
+ * drupal_write_record('node_access', $record);
+ * @endcode
+ * And then in its hook_node_grants() implementation, it would need to return:
+ * @code
+ * if ($op == 'view') {
+ * $grants['example_realm'] = array(888);
+ * }
+ * @endcode
+ * If you decide to do this, be aware that the node_access_rebuild() function
+ * will erase any node ID 0 entry when it is called, so you will need to make
+ * sure to restore your {node_access} record after node_access_rebuild() is
+ * called.
+ *
+ * @see node_access_view_all_nodes()
+ * @see node_access_rebuild()
+ *
+ * @param $account
+ * The user object whose grants are requested.
+ * @param $op
+ * The node operation to be performed, such as 'view', 'update', or 'delete'.
+ *
+ * @return
+ * An array whose keys are "realms" of grants, and whose values are arrays of
+ * the grant IDs within this realm that this user is being granted.
+ *
+ * For a detailed example, see node_access_example.module.
+ *
+ * @ingroup node_access
+ */
+function hook_node_grants($account, $op) {
+ if (user_access('access private content', $account)) {
+ $grants['example'] = array(1);
+ }
+ $grants['example_owner'] = array($account->uid);
+ return $grants;
+}
+
+/**
+ * Set permissions for a node to be written to the database.
+ *
+ * When a node is saved, a module implementing hook_node_access_records() will
+ * be asked if it is interested in the access permissions for a node. If it is
+ * interested, it must respond with an array of permissions arrays for that
+ * node.
+ *
+ * Node access grants apply regardless of the published or unpublished status
+ * of the node. Implementations must make sure not to grant access to
+ * unpublished nodes if they don't want to change the standard access control
+ * behavior. Your module may need to create a separate access realm to handle
+ * access to unpublished nodes.
+ *
+ * Note that the grant values in the return value from your hook must be
+ * integers and not boolean TRUE and FALSE.
+ *
+ * Each permissions item in the array is an array with the following elements:
+ * - 'realm': The name of a realm that the module has defined in
+ * hook_node_grants().
+ * - 'gid': A 'grant ID' from hook_node_grants().
+ * - 'grant_view': If set to 1 a user that has been identified as a member
+ * of this gid within this realm can view this node. This should usually be
+ * set to $node->status. Failure to do so may expose unpublished content
+ * to some users.
+ * - 'grant_update': If set to 1 a user that has been identified as a member
+ * of this gid within this realm can edit this node.
+ * - 'grant_delete': If set to 1 a user that has been identified as a member
+ * of this gid within this realm can delete this node.
+ * - 'priority': If multiple modules seek to set permissions on a node, the
+ * realms that have the highest priority will win out, and realms with a lower
+ * priority will not be written. If there is any doubt, it is best to
+ * leave this 0.
+ *
+ *
+ * When an implementation is interested in a node but want to deny access to
+ * everyone, it may return a "deny all" grant:
+ *
+ * @code
+ * $grants[] = array(
+ * 'realm' => 'all',
+ * 'gid' => 0,
+ * 'grant_view' => 0,
+ * 'grant_update' => 0,
+ * 'grant_delete' => 0,
+ * 'priority' => 1,
+ * );
+ * @endcode
+ *
+ * Setting the priority should cancel out other grants. In the case of a
+ * conflict between modules, it is safer to use hook_node_access_records_alter()
+ * to return only the deny grant.
+ *
+ * Note: a deny all grant is not written to the database; denies are implicit.
+ *
+ * @see node_access_write_grants()
+ *
+ * @param $node
+ * The node that has just been saved.
+ *
+ * @return
+ * An array of grants as defined above.
+ *
+ * @see hook_node_access_records_alter()
+ * @ingroup node_access
+ */
+function hook_node_access_records($node) {
+ // We only care about the node if it has been marked private. If not, it is
+ // treated just like any other node and we completely ignore it.
+ if ($node->private) {
+ $grants = array();
+ // Only published nodes should be viewable to all users. If we allow access
+ // blindly here, then all users could view an unpublished node.
+ if ($node->status) {
+ $grants[] = array(
+ 'realm' => 'example',
+ 'gid' => 1,
+ 'grant_view' => 1,
+ 'grant_update' => 0,
+ 'grant_delete' => 0,
+ 'priority' => 0,
+ );
+ }
+ // For the example_author array, the GID is equivalent to a UID, which
+ // means there are many groups of just 1 user.
+ // Note that an author can always view his or her nodes, even if they
+ // have status unpublished.
+ $grants[] = array(
+ 'realm' => 'example_author',
+ 'gid' => $node->uid,
+ 'grant_view' => 1,
+ 'grant_update' => 1,
+ 'grant_delete' => 1,
+ 'priority' => 0,
+ );
+
+ return $grants;
+ }
+}
+
+/**
+ * Alter permissions for a node before it is written to the database.
+ *
+ * Node access modules establish rules for user access to content. Node access
+ * records are stored in the {node_access} table and define which permissions
+ * are required to access a node. This hook is invoked after node access modules
+ * returned their requirements via hook_node_access_records(); doing so allows
+ * modules to modify the $grants array by reference before it is stored, so
+ * custom or advanced business logic can be applied.
+ *
+ * @see hook_node_access_records()
+ *
+ * Upon viewing, editing or deleting a node, hook_node_grants() builds a
+ * permissions array that is compared against the stored access records. The
+ * user must have one or more matching permissions in order to complete the
+ * requested operation.
+ *
+ * A module may deny all access to a node by setting $grants to an empty array.
+ *
+ * @see hook_node_grants()
+ * @see hook_node_grants_alter()
+ *
+ * @param $grants
+ * The $grants array returned by hook_node_access_records().
+ * @param $node
+ * The node for which the grants were acquired.
+ *
+ * The preferred use of this hook is in a module that bridges multiple node
+ * access modules with a configurable behavior, as shown in the example with the
+ * 'is_preview' field.
+ *
+ * @ingroup node_access
+ */
+function hook_node_access_records_alter(&$grants, $node) {
+ // Our module allows editors to mark specific articles with the 'is_preview'
+ // field. If the node being saved has a TRUE value for that field, then only
+ // our grants are retained, and other grants are removed. Doing so ensures
+ // that our rules are enforced no matter what priority other grants are given.
+ if ($node->is_preview) {
+ // Our module grants are set in $grants['example'].
+ $temp = $grants['example'];
+ // Now remove all module grants but our own.
+ $grants = array('example' => $temp);
+ }
+}
+
+/**
+ * Alter user access rules when trying to view, edit or delete a node.
+ *
+ * Node access modules establish rules for user access to content.
+ * hook_node_grants() defines permissions for a user to view, edit or delete
+ * nodes by building a $grants array that indicates the permissions assigned to
+ * the user by each node access module. This hook is called to allow modules to
+ * modify the $grants array by reference, so the interaction of multiple node
+ * access modules can be altered or advanced business logic can be applied.
+ *
+ * @see hook_node_grants()
+ *
+ * The resulting grants are then checked against the records stored in the
+ * {node_access} table to determine if the operation may be completed.
+ *
+ * A module may deny all access to a user by setting $grants to an empty array.
+ *
+ * @see hook_node_access_records()
+ * @see hook_node_access_records_alter()
+ *
+ * @param $grants
+ * The $grants array returned by hook_node_grants().
+ * @param $account
+ * The user account requesting access to content.
+ * @param $op
+ * The operation being performed, 'view', 'update' or 'delete'.
+ *
+ * Developers may use this hook to either add additional grants to a user or to
+ * remove existing grants. These rules are typically based on either the
+ * permissions assigned to a user role, or specific attributes of a user
+ * account.
+ *
+ * @ingroup node_access
+ */
+function hook_node_grants_alter(&$grants, $account, $op) {
+ // Our sample module never allows certain roles to edit or delete
+ // content. Since some other node access modules might allow this
+ // permission, we expressly remove it by returning an empty $grants
+ // array for roles specified in our variable setting.
+
+ // Get our list of banned roles.
+ $restricted = variable_get('example_restricted_roles', array());
+
+ if ($op != 'view' && !empty($restricted)) {
+ // Now check the roles for this account against the restrictions.
+ foreach ($restricted as $role_id) {
+ if (isset($account->roles[$role_id])) {
+ $grants = array();
+ }
+ }
+ }
+}
+
+/**
+ * Add mass node operations.
+ *
+ * This hook enables modules to inject custom operations into the mass
+ * operations dropdown found at admin/content, by associating a callback
+ * function with the operation, which is called when the form is submitted. The
+ * callback function receives one initial argument, which is an array of the
+ * checked nodes.
+ *
+ * @return
+ * An array of operations. Each operation is an associative array that may
+ * contain the following key-value pairs:
+ * - label: (required) The label for the operation, displayed in the dropdown
+ * menu.
+ * - callback: (required) The function to call for the operation.
+ * - callback arguments: (optional) An array of additional arguments to pass
+ * to the callback function.
+ */
+function hook_node_operations() {
+ $operations = array(
+ 'publish' => array(
+ 'label' => t('Publish selected content'),
+ 'callback' => 'node_mass_update',
+ 'callback arguments' => array('updates' => array('status' => NODE_PUBLISHED)),
+ ),
+ 'unpublish' => array(
+ 'label' => t('Unpublish selected content'),
+ 'callback' => 'node_mass_update',
+ 'callback arguments' => array('updates' => array('status' => NODE_NOT_PUBLISHED)),
+ ),
+ 'promote' => array(
+ 'label' => t('Promote selected content to front page'),
+ 'callback' => 'node_mass_update',
+ 'callback arguments' => array('updates' => array('status' => NODE_PUBLISHED, 'promote' => NODE_PROMOTED)),
+ ),
+ 'demote' => array(
+ 'label' => t('Demote selected content from front page'),
+ 'callback' => 'node_mass_update',
+ 'callback arguments' => array('updates' => array('promote' => NODE_NOT_PROMOTED)),
+ ),
+ 'sticky' => array(
+ 'label' => t('Make selected content sticky'),
+ 'callback' => 'node_mass_update',
+ 'callback arguments' => array('updates' => array('status' => NODE_PUBLISHED, 'sticky' => NODE_STICKY)),
+ ),
+ 'unsticky' => array(
+ 'label' => t('Make selected content not sticky'),
+ 'callback' => 'node_mass_update',
+ 'callback arguments' => array('updates' => array('sticky' => NODE_NOT_STICKY)),
+ ),
+ 'delete' => array(
+ 'label' => t('Delete selected content'),
+ 'callback' => NULL,
+ ),
+ );
+ return $operations;
+}
+
+/**
+ * Respond to node deletion.
+ *
+ * This hook is invoked from node_delete_multiple() after the type-specific
+ * hook_delete() has been invoked, but before hook_entity_delete and
+ * field_attach_delete() are called, and before the node is removed from the
+ * node table in the database.
+ *
+ * @param $node
+ * The node that is being deleted.
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_node_delete($node) {
+ db_delete('mytable')
+ ->condition('nid', $node->nid)
+ ->execute();
+}
+
+/**
+ * Respond to deletion of a node revision.
+ *
+ * This hook is invoked from node_revision_delete() after the revision has been
+ * removed from the node_revision table, and before
+ * field_attach_delete_revision() is called.
+ *
+ * @param $node
+ * The node revision (node object) that is being deleted.
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_node_revision_delete($node) {
+ db_delete('mytable')
+ ->condition('vid', $node->vid)
+ ->execute();
+}
+
+/**
+ * Respond to creation of a new node.
+ *
+ * This hook is invoked from node_save() after the database query that will
+ * insert the node into the node table is scheduled for execution, after the
+ * type-specific hook_insert() is invoked, and after field_attach_insert() is
+ * called.
+ *
+ * Note that when this hook is invoked, the changes have not yet been written to
+ * the database, because a database transaction is still in progress. The
+ * transaction is not finalized until the save operation is entirely completed
+ * and node_save() goes out of scope. You should not rely on data in the
+ * database at this time as it is not updated yet. You should also note that any
+ * write/update database queries executed from this hook are also not committed
+ * immediately. Check node_save() and db_transaction() for more info.
+ *
+ * @param $node
+ * The node that is being created.
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_node_insert($node) {
+ db_insert('mytable')
+ ->fields(array(
+ 'nid' => $node->nid,
+ 'extra' => $node->extra,
+ ))
+ ->execute();
+}
+
+/**
+ * Act on arbitrary nodes being loaded from the database.
+ *
+ * This hook should be used to add information that is not in the node or node
+ * revisions table, not to replace information that is in these tables (which
+ * could interfere with the entity cache). For performance reasons, information
+ * for all available nodes should be loaded in a single query where possible.
+ *
+ * This hook is invoked during node loading, which is handled by entity_load(),
+ * via classes NodeController and DrupalDefaultEntityController. After the node
+ * information is read from the database or the entity cache, hook_load() is
+ * invoked on the node's content type module, then field_attach_load_revision()
+ * or field_attach_load() is called, then hook_entity_load() is invoked on all
+ * implementing modules, and finally hook_node_load() is invoked on all
+ * implementing modules.
+ *
+ * @param $nodes
+ * An array of the nodes being loaded, keyed by nid.
+ * @param $types
+ * An array containing the node types present in $nodes. Allows for an early
+ * return for modules that only support certain node types. However, if your
+ * module defines a content type, you can use hook_load() to respond to
+ * loading of just that content type.
+ *
+ * For a detailed usage example, see nodeapi_example.module.
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_node_load($nodes, $types) {
+ // Decide whether any of $types are relevant to our purposes.
+ if (count(array_intersect($types_we_want_to_process, $types))) {
+ // Gather our extra data for each of these nodes.
+ $result = db_query('SELECT nid, foo FROM {mytable} WHERE nid IN(:nids)', array(':nids' => array_keys($nodes)));
+ // Add our extra data to the node objects.
+ foreach ($result as $record) {
+ $nodes[$record->nid]->foo = $record->foo;
+ }
+ }
+}
+
+/**
+ * Control access to a node.
+ *
+ * Modules may implement this hook if they want to have a say in whether or not
+ * a given user has access to perform a given operation on a node.
+ *
+ * The administrative account (user ID #1) always passes any access check, so
+ * this hook is not called in that case. Users with the "bypass node access"
+ * permission may always view and edit content through the administrative
+ * interface.
+ *
+ * Note that not all modules will want to influence access on all node types. If
+ * your module does not want to actively grant or block access, return
+ * NODE_ACCESS_IGNORE or simply return nothing. Blindly returning FALSE will
+ * break other node access modules.
+ *
+ * Also note that this function isn't called for node listings (e.g., RSS feeds,
+ * the default home page at path 'node', a recent content block, etc.) See
+ * @link node_access Node access rights @endlink for a full explanation.
+ *
+ * @param $node
+ * Either a node object or the machine name of the content type on which to
+ * perform the access check.
+ * @param $op
+ * The operation to be performed. Possible values:
+ * - "create"
+ * - "delete"
+ * - "update"
+ * - "view"
+ * @param $account
+ * The user object to perform the access check operation on.
+ *
+ * @return
+ * - NODE_ACCESS_ALLOW: if the operation is to be allowed.
+ * - NODE_ACCESS_DENY: if the operation is to be denied.
+ * - NODE_ACCESS_IGNORE: to not affect this operation at all.
+ *
+ * @ingroup node_access
+ */
+function hook_node_access($node, $op, $account) {
+ $type = is_string($node) ? $node : $node->type;
+
+ if (in_array($type, node_permissions_get_configured_types())) {
+ if ($op == 'create' && user_access('create ' . $type . ' content', $account)) {
+ return NODE_ACCESS_ALLOW;
+ }
+
+ if ($op == 'update') {
+ if (user_access('edit any ' . $type . ' content', $account) || (user_access('edit own ' . $type . ' content', $account) && ($account->uid == $node->uid))) {
+ return NODE_ACCESS_ALLOW;
+ }
+ }
+
+ if ($op == 'delete') {
+ if (user_access('delete any ' . $type . ' content', $account) || (user_access('delete own ' . $type . ' content', $account) && ($account->uid == $node->uid))) {
+ return NODE_ACCESS_ALLOW;
+ }
+ }
+ }
+
+ // Returning nothing from this function would have the same effect.
+ return NODE_ACCESS_IGNORE;
+}
+
+
+/**
+ * Act on a node object about to be shown on the add/edit form.
+ *
+ * This hook is invoked from node_object_prepare() after the type-specific
+ * hook_prepare() is invoked.
+ *
+ * @param $node
+ * The node that is about to be shown on the add/edit form.
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_node_prepare($node) {
+ if (!isset($node->comment)) {
+ $node->comment = variable_get("comment_$node->type", COMMENT_NODE_OPEN);
+ }
+}
+
+/**
+ * Act on a node being displayed as a search result.
+ *
+ * This hook is invoked from node_search_execute(), after node_load() and
+ * node_view() have been called.
+ *
+ * @param $node
+ * The node being displayed in a search result.
+ *
+ * @return array
+ * Extra information to be displayed with search result. This information
+ * should be presented as an associative array. It will be concatenated with
+ * the post information (last updated, author) in the default search result
+ * theming.
+ *
+ * @see template_preprocess_search_result()
+ * @see search-result.tpl.php
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_node_search_result($node) {
+ $comments = db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = :nid', array('nid' => $node->nid))->fetchField();
+ return array('comment' => format_plural($comments, '1 comment', '@count comments'));
+}
+
+/**
+ * Act on a node being inserted or updated.
+ *
+ * This hook is invoked from node_save() before the node is saved to the
+ * database.
+ *
+ * @param $node
+ * The node that is being inserted or updated.
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_node_presave($node) {
+ if ($node->nid && $node->moderate) {
+ // Reset votes when node is updated:
+ $node->score = 0;
+ $node->users = '';
+ $node->votes = 0;
+ }
+}
+
+/**
+ * Respond to updates to a node.
+ *
+ * This hook is invoked from node_save() after the database query that will
+ * update node in the node table is scheduled for execution, after the
+ * type-specific hook_update() is invoked, and after field_attach_update() is
+ * called.
+ *
+ * Note that when this hook is invoked, the changes have not yet been written to
+ * the database, because a database transaction is still in progress. The
+ * transaction is not finalized until the save operation is entirely completed
+ * and node_save() goes out of scope. You should not rely on data in the
+ * database at this time as it is not updated yet. You should also note that any
+ * write/update database queries executed from this hook are also not committed
+ * immediately. Check node_save() and db_transaction() for more info.
+ *
+ * @param $node
+ * The node that is being updated.
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_node_update($node) {
+ db_update('mytable')
+ ->fields(array('extra' => $node->extra))
+ ->condition('nid', $node->nid)
+ ->execute();
+}
+
+/**
+ * Act on a node being indexed for searching.
+ *
+ * This hook is invoked during search indexing, after node_load(), and after the
+ * result of node_view() is added as $node->rendered to the node object.
+ *
+ * @param $node
+ * The node being indexed.
+ *
+ * @return string
+ * Additional node information to be indexed.
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_node_update_index($node) {
+ $text = '';
+ $comments = db_query('SELECT subject, comment, format FROM {comment} WHERE nid = :nid AND status = :status', array(':nid' => $node->nid, ':status' => COMMENT_PUBLISHED));
+ foreach ($comments as $comment) {
+ $text .= '<h2>' . check_plain($comment->subject) . '</h2>' . check_markup($comment->comment, $comment->format, '', TRUE);
+ }
+ return $text;
+}
+
+/**
+ * Perform node validation before a node is created or updated.
+ *
+ * This hook is invoked from node_validate(), after a user has has finished
+ * editing the node and is previewing or submitting it. It is invoked at the
+ * end of all the standard validation steps, and after the type-specific
+ * hook_validate() is invoked.
+ *
+ * To indicate a validation error, use form_set_error().
+ *
+ * Note: Changes made to the $node object within your hook implementation will
+ * have no effect. The preferred method to change a node's content is to use
+ * hook_node_presave() instead. If it is really necessary to change the node at
+ * the validate stage, you can use form_set_value().
+ *
+ * @param $node
+ * The node being validated.
+ * @param $form
+ * The form being used to edit the node.
+ * @param $form_state
+ * The form state array.
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_node_validate($node, $form, &$form_state) {
+ if (isset($node->end) && isset($node->start)) {
+ if ($node->start > $node->end) {
+ form_set_error('time', t('An event may not end before it starts.'));
+ }
+ }
+}
+
+/**
+ * Act on a node after validated form values have been copied to it.
+ *
+ * This hook is invoked when a node form is submitted with either the "Save" or
+ * "Preview" button, after form values have been copied to the form state's node
+ * object, but before the node is saved or previewed. It is a chance for modules
+ * to adjust the node's properties from what they are simply after a copy from
+ * $form_state['values']. This hook is intended for adjusting non-field-related
+ * properties. See hook_field_attach_submit() for customizing field-related
+ * properties.
+ *
+ * @param $node
+ * The node object being updated in response to a form submission.
+ * @param $form
+ * The form being used to edit the node.
+ * @param $form_state
+ * The form state array.
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_node_submit($node, $form, &$form_state) {
+ // Decompose the selected menu parent option into 'menu_name' and 'plid', if
+ // the form used the default parent selection widget.
+ if (!empty($form_state['values']['menu']['parent'])) {
+ list($node->menu['menu_name'], $node->menu['plid']) = explode(':', $form_state['values']['menu']['parent']);
+ }
+}
+
+/**
+ * Act on a node that is being assembled before rendering.
+ *
+ * The module may add elements to $node->content prior to rendering. This hook
+ * will be called after hook_view(). The structure of $node->content is a
+ * renderable array as expected by drupal_render().
+ *
+ * When $view_mode is 'rss', modules can also add extra RSS elements and
+ * namespaces to $node->rss_elements and $node->rss_namespaces respectively for
+ * the RSS item generated for this node.
+ * For details on how this is used, see node_feed().
+ *
+ * @see blog_node_view()
+ * @see forum_node_view()
+ * @see comment_node_view()
+ *
+ * @param $node
+ * The node that is being assembled for rendering.
+ * @param $view_mode
+ * The $view_mode parameter from node_view().
+ * @param $langcode
+ * The language code used for rendering.
+ *
+ * @see hook_entity_view()
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_node_view($node, $view_mode, $langcode) {
+ $node->content['my_additional_field'] = array(
+ '#markup' => $additional_field,
+ '#weight' => 10,
+ '#theme' => 'mymodule_my_additional_field',
+ );
+}
+
+/**
+ * Alter the results of node_view().
+ *
+ * This hook is called after the content has been assembled in a structured
+ * array and may be used for doing processing which requires that the complete
+ * node content structure has been built.
+ *
+ * If the module wishes to act on the rendered HTML of the node rather than the
+ * structured content array, it may use this hook to add a #post_render
+ * callback. Alternatively, it could also implement hook_preprocess_node(). See
+ * drupal_render() and theme() documentation respectively for details.
+ *
+ * @param $build
+ * A renderable array representing the node content.
+ *
+ * @see node_view()
+ * @see hook_entity_view_alter()
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_node_view_alter(&$build) {
+ if ($build['#view_mode'] == 'full' && isset($build['an_additional_field'])) {
+ // Change its weight.
+ $build['an_additional_field']['#weight'] = -10;
+ }
+
+ // Add a #post_render callback to act on the rendered HTML of the node.
+ $build['#post_render'][] = 'my_module_node_post_render';
+}
+
+/**
+ * Define module-provided node types.
+ *
+ * This hook allows a module to define one or more of its own node types. For
+ * example, the blog module uses it to define a blog node-type named "Blog
+ * entry." The name and attributes of each desired node type are specified in an
+ * array returned by the hook.
+ *
+ * Only module-provided node types should be defined through this hook. User-
+ * provided (or 'custom') node types should be defined only in the 'node_type'
+ * database table, and should be maintained by using the node_type_save() and
+ * node_type_delete() functions.
+ *
+ * @return
+ * An array of information defining the module's node types. The array
+ * contains a sub-array for each node type, with the machine-readable type
+ * name as the key. Each sub-array has up to 10 attributes. Possible
+ * attributes:
+ * - name: (required) The human-readable name of the node type.
+ * - base: (required) The base string used to construct callbacks
+ * corresponding to this node type (for example, if base is defined as
+ * example_foo, then example_foo_insert will be called when inserting a node
+ * of that type). This string is usually the name of the module, but not
+ * always.
+ * - description: (required) A brief description of the node type.
+ * - help: (optional) Help information shown to the user when creating a node
+ * of this type.
+ * - has_title: (optional) A Boolean indicating whether or not this node type
+ * has a title field.
+ * - title_label: (optional) The label for the title field of this content
+ * type.
+ * - locked: (optional) A Boolean indicating whether the administrator can
+ * change the machine name of this type. FALSE = changeable (not locked),
+ * TRUE = unchangeable (locked).
+ *
+ * The machine name of a node type should contain only letters, numbers, and
+ * underscores. Underscores will be converted into hyphens for the purpose of
+ * constructing URLs.
+ *
+ * All attributes of a node type that are defined through this hook (except for
+ * 'locked') can be edited by a site administrator. This includes the
+ * machine-readable name of a node type, if 'locked' is set to FALSE.
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_node_info() {
+ return array(
+ 'blog' => array(
+ 'name' => t('Blog entry'),
+ 'base' => 'blog',
+ 'description' => t('Use for multi-user blogs. Every user gets a personal blog.'),
+ )
+ );
+}
+
+/**
+ * Provide additional methods of scoring for core search results for nodes.
+ *
+ * A node's search score is used to rank it among other nodes matched by the
+ * search, with the highest-ranked nodes appearing first in the search listing.
+ *
+ * For example, a module allowing users to vote on content could expose an
+ * option to allow search results' rankings to be influenced by the average
+ * voting score of a node.
+ *
+ * All scoring mechanisms are provided as options to site administrators, and
+ * may be tweaked based on individual sites or disabled altogether if they do
+ * not make sense. Individual scoring mechanisms, if enabled, are assigned a
+ * weight from 1 to 10. The weight represents the factor of magnification of
+ * the ranking mechanism, with higher-weighted ranking mechanisms having more
+ * influence. In order for the weight system to work, each scoring mechanism
+ * must return a value between 0 and 1 for every node. That value is then
+ * multiplied by the administrator-assigned weight for the ranking mechanism,
+ * and then the weighted scores from all ranking mechanisms are added, which
+ * brings about the same result as a weighted average.
+ *
+ * @return
+ * An associative array of ranking data. The keys should be strings,
+ * corresponding to the internal name of the ranking mechanism, such as
+ * 'recent', or 'comments'. The values should be arrays themselves, with the
+ * following keys available:
+ * - title: (required) The human readable name of the ranking mechanism.
+ * - join: (optional) The part of a query string to join to any additional
+ * necessary table. This is not necessary if the table required is already
+ * joined to by the base query, such as for the {node} table. Other tables
+ * should use the full table name as an alias to avoid naming collisions.
+ * - score: (required) The part of a query string to calculate the score for
+ * the ranking mechanism based on values in the database. This does not need
+ * to be wrapped in parentheses, as it will be done automatically; it also
+ * does not need to take the weighted system into account, as it will be
+ * done automatically. It does, however, need to calculate a decimal between
+ * 0 and 1; be careful not to cast the entire score to an integer by
+ * inadvertently introducing a variable argument.
+ * - arguments: (optional) If any arguments are required for the score, they
+ * can be specified in an array here.
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_ranking() {
+ // If voting is disabled, we can avoid returning the array, no hard feelings.
+ if (variable_get('vote_node_enabled', TRUE)) {
+ return array(
+ 'vote_average' => array(
+ 'title' => t('Average vote'),
+ // Note that we use i.sid, the search index's search item id, rather than
+ // n.nid.
+ 'join' => 'LEFT JOIN {vote_node_data} vote_node_data ON vote_node_data.nid = i.sid',
+ // The highest possible score should be 1, and the lowest possible score,
+ // always 0, should be 0.
+ 'score' => 'vote_node_data.average / CAST(%f AS DECIMAL)',
+ // Pass in the highest possible voting score as a decimal argument.
+ 'arguments' => array(variable_get('vote_score_max', 5)),
+ ),
+ );
+ }
+}
+
+
+/**
+ * Respond to node type creation.
+ *
+ * This hook is invoked from node_type_save() after the node type is added to
+ * the database.
+ *
+ * @param $info
+ * The node type object that is being created.
+ */
+function hook_node_type_insert($info) {
+ drupal_set_message(t('You have just created a content type with a machine name %type.', array('%type' => $info->type)));
+}
+
+/**
+ * Respond to node type updates.
+ *
+ * This hook is invoked from node_type_save() after the node type is updated in
+ * the database.
+ *
+ * @param $info
+ * The node type object that is being updated.
+ */
+function hook_node_type_update($info) {
+ if (!empty($info->old_type) && $info->old_type != $info->type) {
+ $setting = variable_get('comment_' . $info->old_type, COMMENT_NODE_OPEN);
+ variable_del('comment_' . $info->old_type);
+ variable_set('comment_' . $info->type, $setting);
+ }
+}
+
+/**
+ * Respond to node type deletion.
+ *
+ * This hook is invoked from node_type_delete() after the node type is removed
+ * from the database.
+ *
+ * @param $info
+ * The node type object that is being deleted.
+ */
+function hook_node_type_delete($info) {
+ variable_del('comment_' . $info->type);
+}
+
+/**
+ * Respond to node deletion.
+ *
+ * This hook is invoked only on the module that defines the node's content type
+ * (use hook_node_delete() to respond to all node deletions).
+ *
+ * This hook is invoked from node_delete_multiple() after the node has been
+ * removed from the node table in the database, before hook_node_delete() is
+ * invoked, and before field_attach_delete() is called.
+ *
+ * @param $node
+ * The node that is being deleted.
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_delete($node) {
+ db_delete('mytable')
+ ->condition('nid', $node->nid)
+ ->execute();
+}
+
+/**
+ * Act on a node object about to be shown on the add/edit form.
+ *
+ * This hook is invoked only on the module that defines the node's content type
+ * (use hook_node_prepare() to act on all node preparations).
+ *
+ * This hook is invoked from node_object_prepare() before the general
+ * hook_node_prepare() is invoked.
+ *
+ * @param $node
+ * The node that is about to be shown on the add/edit form.
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_prepare($node) {
+ if ($file = file_check_upload($field_name)) {
+ $file = file_save_upload($field_name, _image_filename($file->filename, NULL, TRUE));
+ if ($file) {
+ if (!image_get_info($file->uri)) {
+ form_set_error($field_name, t('Uploaded file is not a valid image'));
+ return;
+ }
+ }
+ else {
+ return;
+ }
+ $node->images['_original'] = $file->uri;
+ _image_build_derivatives($node, TRUE);
+ $node->new_file = TRUE;
+ }
+}
+
+/**
+ * Display a node editing form.
+ *
+ * This hook, implemented by node modules, is called to retrieve the form
+ * that is displayed to create or edit a node. This form is displayed at path
+ * node/add/[node type] or node/[node ID]/edit.
+ *
+ * The submit and preview buttons, administrative and display controls, and
+ * sections added by other modules (such as path settings, menu settings,
+ * comment settings, and fields managed by the Field UI module) are
+ * displayed automatically by the node module. This hook just needs to
+ * return the node title and form editing fields specific to the node type.
+ *
+ * @param $node
+ * The node being added or edited.
+ * @param $form_state
+ * The form state array.
+ *
+ * @return
+ * An array containing the title and any custom form elements to be displayed
+ * in the node editing form.
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_form($node, &$form_state) {
+ $type = node_type_get_type($node);
+
+ $form['title'] = array(
+ '#type' => 'textfield',
+ '#title' => check_plain($type->title_label),
+ '#default_value' => !empty($node->title) ? $node->title : '',
+ '#required' => TRUE, '#weight' => -5
+ );
+
+ $form['field1'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Custom field'),
+ '#default_value' => $node->field1,
+ '#maxlength' => 127,
+ );
+ $form['selectbox'] = array(
+ '#type' => 'select',
+ '#title' => t('Select box'),
+ '#default_value' => $node->selectbox,
+ '#options' => array(
+ 1 => 'Option A',
+ 2 => 'Option B',
+ 3 => 'Option C',
+ ),
+ '#description' => t('Choose an option.'),
+ );
+
+ return $form;
+}
+
+/**
+ * Respond to creation of a new node.
+ *
+ * This hook is invoked only on the module that defines the node's content type
+ * (use hook_node_insert() to act on all node insertions).
+ *
+ * This hook is invoked from node_save() after the node is inserted into the
+ * node table in the database, before field_attach_insert() is called, and
+ * before hook_node_insert() is invoked.
+ *
+ * @param $node
+ * The node that is being created.
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_insert($node) {
+ db_insert('mytable')
+ ->fields(array(
+ 'nid' => $node->nid,
+ 'extra' => $node->extra,
+ ))
+ ->execute();
+}
+
+/**
+ * Act on nodes being loaded from the database.
+ *
+ * This hook is invoked only on the module that defines the node's content type
+ * (use hook_node_load() to respond to all node loads).
+ *
+ * This hook is invoked during node loading, which is handled by entity_load(),
+ * via classes NodeController and DrupalDefaultEntityController. After the node
+ * information is read from the database or the entity cache, hook_load() is
+ * invoked on the node's content type module, then field_attach_node_revision()
+ * or field_attach_load() is called, then hook_entity_load() is invoked on all
+ * implementing modules, and finally hook_node_load() is invoked on all
+ * implementing modules.
+ *
+ * This hook should only be used to add information that is not in the node or
+ * node revisions table, not to replace information that is in these tables
+ * (which could interfere with the entity cache). For performance reasons,
+ * information for all available nodes should be loaded in a single query where
+ * possible.
+ *
+ * @param $nodes
+ * An array of the nodes being loaded, keyed by nid.
+ *
+ * For a detailed usage example, see node_example.module.
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_load($nodes) {
+ $result = db_query('SELECT nid, foo FROM {mytable} WHERE nid IN (:nids)', array(':nids' => array_keys($nodes)));
+ foreach ($result as $record) {
+ $nodes[$record->nid]->foo = $record->foo;
+ }
+}
+
+/**
+ * Respond to updates to a node.
+ *
+ * This hook is invoked only on the module that defines the node's content type
+ * (use hook_node_update() to act on all node updates).
+ *
+ * This hook is invoked from node_save() after the node is updated in the
+ * node table in the database, before field_attach_update() is called, and
+ * before hook_node_update() is invoked.
+ *
+ * @param $node
+ * The node that is being updated.
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_update($node) {
+ db_update('mytable')
+ ->fields(array('extra' => $node->extra))
+ ->condition('nid', $node->nid)
+ ->execute();
+}
+
+/**
+ * Perform node validation before a node is created or updated.
+ *
+ * This hook is invoked only on the module that defines the node's content type
+ * (use hook_node_validate() to act on all node validations).
+ *
+ * This hook is invoked from node_validate(), after a user has finished
+ * editing the node and is previewing or submitting it. It is invoked at the end
+ * of all the standard validation steps, and before hook_node_validate() is
+ * invoked.
+ *
+ * To indicate a validation error, use form_set_error().
+ *
+ * Note: Changes made to the $node object within your hook implementation will
+ * have no effect. The preferred method to change a node's content is to use
+ * hook_node_presave() instead.
+ *
+ * @param $node
+ * The node being validated.
+ * @param $form
+ * The form being used to edit the node.
+ * @param $form_state
+ * The form state array.
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_validate($node, $form, &$form_state) {
+ if (isset($node->end) && isset($node->start)) {
+ if ($node->start > $node->end) {
+ form_set_error('time', t('An event may not end before it starts.'));
+ }
+ }
+}
+
+/**
+ * Display a node.
+ *
+ * This hook is invoked only on the module that defines the node's content type
+ * (use hook_node_view() to act on all node views).
+ *
+ * This hook is invoked during node viewing after the node is fully loaded, so
+ * that the node type module can define a custom method for display, or add to
+ * the default display.
+ *
+ * @param $node
+ * The node to be displayed, as returned by node_load().
+ * @param $view_mode
+ * View mode, e.g. 'full', 'teaser', ...
+ * @return
+ * The passed $node parameter should be modified as necessary and returned so
+ * it can be properly presented. Nodes are prepared for display by assembling
+ * a structured array, formatted as in the Form API, in $node->content. As
+ * with Form API arrays, the #weight property can be used to control the
+ * relative positions of added elements. After this hook is invoked,
+ * node_view() calls field_attach_view() to add field views to $node->content,
+ * and then invokes hook_node_view() and hook_node_view_alter(), so if you
+ * want to affect the final view of the node, you might consider implementing
+ * one of these hooks instead.
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_view($node, $view_mode) {
+ if ($view_mode == 'full' && node_is_page($node)) {
+ $breadcrumb = array();
+ $breadcrumb[] = l(t('Home'), NULL);
+ $breadcrumb[] = l(t('Example'), 'example');
+ $breadcrumb[] = l($node->field1, 'example/' . $node->field1);
+ drupal_set_breadcrumb($breadcrumb);
+ }
+
+ $node->content['myfield'] = array(
+ '#markup' => theme('mymodule_myfield', $node->myfield),
+ '#weight' => 1,
+ );
+
+ return $node;
+}
+
+/**
+ * @} End of "addtogroup hooks".
+ */
diff --git a/kolab.org/www/drupal-7.26/modules/node/node.css b/kolab.org/www/drupal-7.26/modules/node/node.css
new file mode 100644
index 0000000..07540fa
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/node/node.css
@@ -0,0 +1,10 @@
+
+.node-unpublished {
+ background-color: #fff4f4;
+}
+.preview .node {
+ background-color: #ffffea;
+}
+td.revision-current {
+ background: #ffc;
+}
diff --git a/kolab.org/www/drupal-7.26/modules/node/node.info b/kolab.org/www/drupal-7.26/modules/node/node.info
new file mode 100644
index 0000000..f3a977d
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/node/node.info
@@ -0,0 +1,16 @@
+name = Node
+description = Allows content to be submitted to the site and displayed on pages.
+package = Core
+version = VERSION
+core = 7.x
+files[] = node.module
+files[] = node.test
+required = TRUE
+configure = admin/structure/types
+stylesheets[all][] = node.css
+
+; Information added by Drupal.org packaging script on 2014-01-15
+version = "7.26"
+project = "drupal"
+datestamp = "1389815930"
+
diff --git a/kolab.org/www/drupal-7.26/modules/node/node.install b/kolab.org/www/drupal-7.26/modules/node/node.install
new file mode 100644
index 0000000..76c2aec
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/node/node.install
@@ -0,0 +1,938 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the node module.
+ */
+
+/**
+ * Implements hook_schema().
+ */
+function node_schema() {
+ $schema['node'] = array(
+ 'description' => 'The base table for nodes.',
+ 'fields' => array(
+ 'nid' => array(
+ 'description' => 'The primary identifier for a node.',
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ // Defaults to NULL in order to avoid a brief period of potential
+ // deadlocks on the index.
+ 'vid' => array(
+ 'description' => 'The current {node_revision}.vid version identifier.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ 'default' => NULL,
+ ),
+ 'type' => array(
+ 'description' => 'The {node_type}.type of this node.',
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'language' => array(
+ 'description' => 'The {languages}.language of this node.',
+ 'type' => 'varchar',
+ 'length' => 12,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'title' => array(
+ 'description' => 'The title of this node, always treated as non-markup plain text.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'uid' => array(
+ 'description' => 'The {users}.uid that owns this node; initially, this is the user that created it.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'status' => array(
+ 'description' => 'Boolean indicating whether the node is published (visible to non-administrators).',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 1,
+ ),
+ 'created' => array(
+ 'description' => 'The Unix timestamp when the node was created.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'changed' => array(
+ 'description' => 'The Unix timestamp when the node was most recently saved.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'comment' => array(
+ 'description' => 'Whether comments are allowed on this node: 0 = no, 1 = closed (read only), 2 = open (read/write).',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'promote' => array(
+ 'description' => 'Boolean indicating whether the node should be displayed on the front page.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'sticky' => array(
+ 'description' => 'Boolean indicating whether the node should be displayed at the top of lists in which it appears.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'tnid' => array(
+ 'description' => 'The translation set id for this node, which equals the node id of the source post in each set.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'translate' => array(
+ 'description' => 'A boolean indicating whether this translation page needs to be updated.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ ),
+ 'indexes' => array(
+ 'node_changed' => array('changed'),
+ 'node_created' => array('created'),
+ 'node_frontpage' => array('promote', 'status', 'sticky', 'created'),
+ 'node_status_type' => array('status', 'type', 'nid'),
+ 'node_title_type' => array('title', array('type', 4)),
+ 'node_type' => array(array('type', 4)),
+ 'uid' => array('uid'),
+ 'tnid' => array('tnid'),
+ 'translate' => array('translate'),
+ 'language' => array('language'),
+ ),
+ 'unique keys' => array(
+ 'vid' => array('vid'),
+ ),
+ 'foreign keys' => array(
+ 'node_revision' => array(
+ 'table' => 'node_revision',
+ 'columns' => array('vid' => 'vid'),
+ ),
+ 'node_author' => array(
+ 'table' => 'users',
+ 'columns' => array('uid' => 'uid'),
+ ),
+ ),
+ 'primary key' => array('nid'),
+ );
+
+ $schema['node_access'] = array(
+ 'description' => 'Identifies which realm/grant pairs a user must possess in order to view, update, or delete specific nodes.',
+ 'fields' => array(
+ 'nid' => array(
+ 'description' => 'The {node}.nid this record affects.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'gid' => array(
+ 'description' => "The grant ID a user must possess in the specified realm to gain this row's privileges on the node.",
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'realm' => array(
+ 'description' => 'The realm in which the user must possess the grant ID. Each node access node can define one or more realms.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'grant_view' => array(
+ 'description' => 'Boolean indicating whether a user with the realm/grant pair can view this node.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ ),
+ 'grant_update' => array(
+ 'description' => 'Boolean indicating whether a user with the realm/grant pair can edit this node.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ ),
+ 'grant_delete' => array(
+ 'description' => 'Boolean indicating whether a user with the realm/grant pair can delete this node.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ ),
+ ),
+ 'primary key' => array('nid', 'gid', 'realm'),
+ 'foreign keys' => array(
+ 'affected_node' => array(
+ 'table' => 'node',
+ 'columns' => array('nid' => 'nid'),
+ ),
+ ),
+ );
+
+ $schema['node_revision'] = array(
+ 'description' => 'Stores information about each saved version of a {node}.',
+ 'fields' => array(
+ 'nid' => array(
+ 'description' => 'The {node} this version belongs to.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'vid' => array(
+ 'description' => 'The primary identifier for this version.',
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ 'uid' => array(
+ 'description' => 'The {users}.uid that created this version.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'title' => array(
+ 'description' => 'The title of this version.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'log' => array(
+ 'description' => 'The log entry explaining the changes in this version.',
+ 'type' => 'text',
+ 'not null' => TRUE,
+ 'size' => 'big',
+ ),
+ 'timestamp' => array(
+ 'description' => 'A Unix timestamp indicating when this version was created.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'status' => array(
+ 'description' => 'Boolean indicating whether the node (at the time of this revision) is published (visible to non-administrators).',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 1,
+ ),
+ 'comment' => array(
+ 'description' => 'Whether comments are allowed on this node (at the time of this revision): 0 = no, 1 = closed (read only), 2 = open (read/write).',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'promote' => array(
+ 'description' => 'Boolean indicating whether the node (at the time of this revision) should be displayed on the front page.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'sticky' => array(
+ 'description' => 'Boolean indicating whether the node (at the time of this revision) should be displayed at the top of lists in which it appears.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ ),
+ 'indexes' => array(
+ 'nid' => array('nid'),
+ 'uid' => array('uid'),
+ ),
+ 'primary key' => array('vid'),
+ 'foreign keys' => array(
+ 'versioned_node' => array(
+ 'table' => 'node',
+ 'columns' => array('nid' => 'nid'),
+ ),
+ 'version_author' => array(
+ 'table' => 'users',
+ 'columns' => array('uid' => 'uid'),
+ ),
+ ),
+ );
+
+ $schema['node_type'] = array(
+ 'description' => 'Stores information about all defined {node} types.',
+ 'fields' => array(
+ 'type' => array(
+ 'description' => 'The machine-readable name of this type.',
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ ),
+ 'name' => array(
+ 'description' => 'The human-readable name of this type.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'translatable' => TRUE,
+ ),
+ 'base' => array(
+ 'description' => 'The base string used to construct callbacks corresponding to this node type.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ ),
+ 'module' => array(
+ 'description' => 'The module defining this node type.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ ),
+ 'description' => array(
+ 'description' => 'A brief description of this type.',
+ 'type' => 'text',
+ 'not null' => TRUE,
+ 'size' => 'medium',
+ 'translatable' => TRUE,
+ ),
+ 'help' => array(
+ 'description' => 'Help information shown to the user when creating a {node} of this type.',
+ 'type' => 'text',
+ 'not null' => TRUE,
+ 'size' => 'medium',
+ 'translatable' => TRUE,
+ ),
+ 'has_title' => array(
+ 'description' => 'Boolean indicating whether this type uses the {node}.title field.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'size' => 'tiny',
+ ),
+ 'title_label' => array(
+ 'description' => 'The label displayed for the title field on the edit form.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'translatable' => TRUE,
+ ),
+ 'custom' => array(
+ 'description' => 'A boolean indicating whether this type is defined by a module (FALSE) or by a user via Add content type (TRUE).',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ ),
+ 'modified' => array(
+ 'description' => 'A boolean indicating whether this type has been modified by an administrator; currently not used in any way.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ ),
+ 'locked' => array(
+ 'description' => 'A boolean indicating whether the administrator can change the machine name of this type.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ ),
+ 'disabled' => array(
+ 'description' => 'A boolean indicating whether the node type is disabled.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'tiny'
+ ),
+ 'orig_type' => array(
+ 'description' => 'The original machine-readable name of this node type. This may be different from the current type name if the locked field is 0.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ ),
+ 'primary key' => array('type'),
+ );
+
+ $schema['block_node_type'] = array(
+ 'description' => 'Sets up display criteria for blocks based on content types',
+ 'fields' => array(
+ 'module' => array(
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ 'description' => "The block's origin module, from {block}.module.",
+ ),
+ 'delta' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'description' => "The block's unique delta within module, from {block}.delta.",
+ ),
+ 'type' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'description' => "The machine-readable name of this type from {node_type}.type.",
+ ),
+ ),
+ 'primary key' => array('module', 'delta', 'type'),
+ 'indexes' => array(
+ 'type' => array('type'),
+ ),
+ );
+
+ $schema['history'] = array(
+ 'description' => 'A record of which {users} have read which {node}s.',
+ 'fields' => array(
+ 'uid' => array(
+ 'description' => 'The {users}.uid that read the {node} nid.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'nid' => array(
+ 'description' => 'The {node}.nid that was read.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'timestamp' => array(
+ 'description' => 'The Unix timestamp at which the read occurred.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ ),
+ 'primary key' => array('uid', 'nid'),
+ 'indexes' => array(
+ 'nid' => array('nid'),
+ ),
+ );
+
+ return $schema;
+}
+
+/**
+ * Implements hook_install().
+ */
+function node_install() {
+ // Populate the node access table.
+ db_insert('node_access')
+ ->fields(array(
+ 'nid' => 0,
+ 'gid' => 0,
+ 'realm' => 'all',
+ 'grant_view' => 1,
+ 'grant_update' => 0,
+ 'grant_delete' => 0,
+ ))
+ ->execute();
+}
+
+/**
+ * Implements hook_update_dependencies().
+ */
+function node_update_dependencies() {
+ // node_update_7006() migrates node data to fields and therefore must run
+ // after all Field modules have been enabled, which happens in
+ // system_update_7027(). It also needs to query the {filter_format} table to
+ // get a list of existing text formats, so it must run after
+ // filter_update_7000(), which creates that table.
+ $dependencies['node'][7006] = array(
+ 'system' => 7027,
+ 'filter' => 7000,
+ );
+
+ // node_update_7008() migrates role permissions and therefore must run after
+ // the {role} and {role_permission} tables are properly set up, which happens
+ // in user_update_7007().
+ $dependencies['node'][7008] = array(
+ 'user' => 7007,
+ );
+
+ return $dependencies;
+}
+
+/**
+ * Utility function: fetch the node types directly from the database.
+ *
+ * This function is valid for a database schema version 7000.
+ *
+ * @ingroup update_api
+ */
+function _update_7000_node_get_types() {
+ $node_types = db_query('SELECT * FROM {node_type}')->fetchAllAssoc('type', PDO::FETCH_OBJ);
+
+ // Create default settings for orphan nodes.
+ $all_types = db_query('SELECT DISTINCT type FROM {node}')->fetchCol();
+ $extra_types = array_diff($all_types, array_keys($node_types));
+
+ foreach ($extra_types as $type) {
+ $type_object = new stdClass();
+ $type_object->type = $type;
+
+ // In Drupal 6, whether you have a body field or not is a flag in the node
+ // type table. If it's enabled, nodes may or may not have an empty string
+ // for the bodies. As we can't detect what this setting should be in
+ // Drupal 7 without access to the Drupal 6 node type settings, we assume
+ // the default, which is to enable the body field.
+ $type_object->has_body = 1;
+ $type_object->body_label = 'Body';
+ $node_types[$type_object->type] = $type_object;
+ }
+ return $node_types;
+}
+
+/**
+ * @addtogroup updates-6.x-to-7.x
+ * @{
+ */
+
+/**
+ * Upgrade the node type table and fix node type 'module' attribute to avoid name-space conflicts.
+ */
+function node_update_7000() {
+ // Rename the module column to base.
+ db_change_field('node_type', 'module', 'base', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE));
+
+ db_add_field('node_type', 'module', array(
+ 'description' => 'The module defining this node type.',
+ 'type' => 'varchar',
+ 'default' => '',
+ 'length' => 255,
+ 'not null' => TRUE,
+ ));
+
+ db_add_field('node_type', 'disabled', array(
+ 'description' => 'A boolean indicating whether the node type is disabled.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'tiny'
+ ));
+
+ $modules = db_select('system', 's')
+ ->fields('s', array('name'))
+ ->condition('type', 'module');
+ db_update('node_type')
+ ->expression('module', 'base')
+ ->condition('base', $modules, 'IN')
+ ->execute();
+
+ db_update('node_type')
+ ->fields(array('base' => 'node_content'))
+ ->condition('base', 'node')
+ ->execute();
+}
+
+/**
+ * Rename {node_revisions} table to {node_revision}.
+ */
+function node_update_7001() {
+ db_rename_table('node_revisions', 'node_revision');
+}
+
+/**
+ * Extend the node_promote_status index to include all fields required for the node page query.
+ */
+function node_update_7002() {
+ db_drop_index('node', 'node_promote_status');
+ db_add_index('node', 'node_frontpage', array('promote', 'status', 'sticky', 'created'));
+}
+
+/**
+ * Remove the node_counter if the statistics module is uninstalled.
+ */
+function node_update_7003() {
+ if (drupal_get_installed_schema_version('statistics') == SCHEMA_UNINSTALLED) {
+ db_drop_table('node_counter');
+ }
+}
+
+/**
+ * Extend the existing default preview and teaser settings to all node types.
+ */
+function node_update_7004() {
+ // Get original settings and all types.
+ $original_length = variable_get('teaser_length', 600);
+ $original_preview = variable_get('node_preview', 0);
+
+ // Map old preview setting to new values order.
+ $original_preview ? $original_preview = 2 : $original_preview = 1;
+ node_type_cache_reset();
+
+ // Apply original settings to all types.
+ foreach (_update_7000_node_get_types() as $type => $type_object) {
+ variable_set('teaser_length_' . $type, $original_length);
+ variable_set('node_preview_' . $type, $original_preview);
+ }
+ // Delete old variable but leave 'teaser_length' for aggregator module upgrade.
+ variable_del('node_preview');
+}
+
+/**
+ * Add status/comment/promote and sticky columns to the {node_revision} table.
+ */
+function node_update_7005() {
+ foreach (array('status', 'comment', 'promote', 'sticky') as $column) {
+ db_add_field('node_revision', $column, array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ));
+ }
+}
+
+/**
+ * Convert body and teaser from node properties to fields, and migrate status/comment/promote and sticky columns to the {node_revision} table.
+ */
+function node_update_7006(&$sandbox) {
+ $sandbox['#finished'] = 0;
+
+ // Get node type info for every invocation.
+ node_type_cache_reset();
+
+ if (!isset($sandbox['total'])) {
+ // Initial invocation.
+
+ // First, create the body field.
+ $body_field = array(
+ 'field_name' => 'body',
+ 'type' => 'text_with_summary',
+ 'module' => 'text',
+ 'cardinality' => 1,
+ 'entity_types' => array('node'),
+ 'translatable' => TRUE,
+ );
+ _update_7000_field_create_field($body_field);
+
+ $default_trim_length = variable_get('teaser_length', 600);
+
+ // Get node type info, specifically the body field settings.
+ $node_types = _update_7000_node_get_types();
+
+ // Add body field instances for existing node types.
+ foreach ($node_types as $node_type) {
+ if ($node_type->has_body) {
+ $trim_length = variable_get('teaser_length_' . $node_type->type, $default_trim_length);
+
+ $instance = array(
+ 'entity_type' => 'node',
+ 'bundle' => $node_type->type,
+ 'label' => $node_type->body_label,
+ 'description' => isset($node_type->description) ? $node_type->description : '',
+ 'required' => (isset($node_type->min_word_count) && $node_type->min_word_count > 0) ? 1 : 0,
+ 'widget' => array(
+ 'type' => 'text_textarea_with_summary',
+ 'settings' => array(
+ 'rows' => 20,
+ 'summary_rows' => 5,
+ ),
+ 'weight' => -4,
+ 'module' => 'text',
+ ),
+ 'settings' => array('display_summary' => TRUE),
+ 'display' => array(
+ 'default' => array(
+ 'label' => 'hidden',
+ 'type' => 'text_default',
+ ),
+ 'teaser' => array(
+ 'label' => 'hidden',
+ 'type' => 'text_summary_or_trimmed',
+ 'trim_length' => $trim_length,
+ ),
+ ),
+ );
+ _update_7000_field_create_instance($body_field, $instance);
+ variable_del('teaser_length_' . $node_type->type);
+ }
+ // Leave 'teaser_length' variable for aggregator module upgrade.
+
+ $sandbox['node_types_info'][$node_type->type] = array(
+ 'has_body' => $node_type->has_body,
+ );
+ }
+
+ // Used below when updating the stored text format of each node body.
+ $sandbox['existing_text_formats'] = db_query("SELECT format FROM {filter_format}")->fetchCol();
+
+ // Initialize state for future calls.
+ $sandbox['last'] = 0;
+ $sandbox['count'] = 0;
+
+ $query = db_select('node', 'n');
+ $query->join('node_revision', 'nr', 'n.nid = nr.nid');
+ $sandbox['total'] = $query->countQuery()->execute()->fetchField();
+
+ $sandbox['body_field_id'] = $body_field['id'];
+ }
+ else {
+ // Subsequent invocations.
+
+ $found = FALSE;
+ if ($sandbox['total']) {
+ // Operate on every revision of every node (whee!), in batches.
+ $batch_size = 200;
+ $query = db_select('node_revision', 'nr');
+ $query->innerJoin('node', 'n', 'n.nid = nr.nid');
+ $query
+ ->fields('nr', array('nid', 'vid', 'body', 'teaser', 'format'))
+ ->fields('n', array('type', 'status', 'comment', 'promote', 'sticky', 'language'))
+ ->condition('nr.vid', $sandbox['last'], '>')
+ ->orderBy('nr.vid', 'ASC')
+ ->range(0, $batch_size);
+ $revisions = $query->execute();
+
+ // Load each revision of each node, set up 'body'
+ // appropriately, and save the node's field data. Note that
+ // node_load() will not return the body or teaser values from
+ // {node_revision} because those columns have been removed from the
+ // schema structure in memory (but not yet from the database),
+ // so we get the values from the explicit query of the table
+ // instead.
+ foreach ($revisions as $revision) {
+ $found = TRUE;
+
+ if ($sandbox['node_types_info'][$revision->type]['has_body']) {
+ $node = (object) array(
+ 'nid' => $revision->nid,
+ 'vid' => $revision->vid,
+ 'type' => $revision->type,
+ );
+ // After node_update_7009() we will always have LANGUAGE_NONE as
+ // language neutral language code, but here we still have empty
+ // strings.
+ $langcode = empty($revision->language) ? LANGUAGE_NONE : $revision->language;
+ if (!empty($revision->teaser) && $revision->teaser != text_summary($revision->body)) {
+ $node->body[$langcode][0]['summary'] = $revision->teaser;
+ }
+ // Do this after text_summary() above.
+ $break = '<!--break-->';
+ if (substr($revision->body, 0, strlen($break)) == $break) {
+ $revision->body = substr($revision->body, strlen($break));
+ }
+ $node->body[$langcode][0]['value'] = $revision->body;
+ // Update the revision's text format for the changes to the Drupal 7
+ // filter system. This uses the same kind of logic that occurs, for
+ // example, in user_update_7010(), but we do this here rather than
+ // via a separate set of database queries, since we are already
+ // migrating the data.
+ if (empty($revision->body) && empty($revision->format)) {
+ $node->body[$langcode][0]['format'] = NULL;
+ }
+ elseif (!in_array($revision->format, $sandbox['existing_text_formats'])) {
+ $node->body[$langcode][0]['format'] = variable_get('filter_default_format', 1);
+ }
+ else {
+ $node->body[$langcode][0]['format'] = $revision->format;
+ }
+ // This is a core update and no contrib modules are enabled yet, so
+ // we can assume default field storage for a faster update.
+ _update_7000_field_sql_storage_write('node', $node->type, $node->nid, $node->vid, 'body', $node->body);
+ }
+
+ // Migrate the status columns to the {node_revision} table.
+ db_update('node_revision')
+ ->fields(array(
+ 'status' => $revision->status,
+ 'comment' => $revision->comment,
+ 'promote' => $revision->promote,
+ 'sticky' => $revision->sticky,
+ ))
+ ->condition('vid', $revision->vid)
+ ->execute();
+
+ $sandbox['last'] = $revision->vid;
+ $sandbox['count'] += 1;
+ }
+
+ $sandbox['#finished'] = min(0.99, $sandbox['count'] / $sandbox['total']);
+ }
+
+ if (!$found) {
+ // All nodes are processed.
+
+ // Remove the now-obsolete body info from node_revision.
+ db_drop_field('node_revision', 'body');
+ db_drop_field('node_revision', 'teaser');
+ db_drop_field('node_revision', 'format');
+
+ // Remove node_type properties related to the former 'body'.
+ db_drop_field('node_type', 'has_body');
+ db_drop_field('node_type', 'body_label');
+
+ // We're done.
+ $sandbox['#finished'] = 1;
+ }
+ }
+}
+
+/**
+ * Remove column min_word_count.
+ */
+function node_update_7007() {
+ db_drop_field('node_type', 'min_word_count');
+}
+
+/**
+ * Split the 'administer nodes' permission from 'access content overview'.
+ */
+function node_update_7008() {
+ $roles = user_roles(FALSE, 'administer nodes');
+ foreach ($roles as $rid => $role) {
+ _update_7000_user_role_grant_permissions($rid, array('access content overview'), 'node');
+ }
+}
+
+/**
+ * Convert node languages from the empty string to LANGUAGE_NONE.
+ */
+function node_update_7009() {
+ db_update('node')
+ ->fields(array('language' => LANGUAGE_NONE))
+ ->condition('language', '')
+ ->execute();
+}
+
+/**
+ * Add the {block_node_type} table.
+ */
+function node_update_7010() {
+ $schema['block_node_type'] = array(
+ 'description' => 'Sets up display criteria for blocks based on content types',
+ 'fields' => array(
+ 'module' => array(
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ 'description' => "The block's origin module, from {block}.module.",
+ ),
+ 'delta' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'description' => "The block's unique delta within module, from {block}.delta.",
+ ),
+ 'type' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'description' => "The machine-readable name of this type from {node_type}.type.",
+ ),
+ ),
+ 'primary key' => array('module', 'delta', 'type'),
+ 'indexes' => array(
+ 'type' => array('type'),
+ ),
+ );
+
+ db_create_table('block_node_type', $schema['block_node_type']);
+}
+
+/**
+ * @} End of "addtogroup updates-6.x-to-7.x".
+ */
+
+/**
+ * @addtogroup updates-7.x-extra
+ * @{
+ */
+
+/**
+ * Update the database from Drupal 6 to match the schema.
+ */
+function node_update_7011() {
+ // Drop node moderation field.
+ db_drop_field('node', 'moderate');
+ db_drop_index('node', 'node_moderate');
+
+ // Change {node_revision}.status field to default to 1.
+ db_change_field('node_revision', 'status', 'status', array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 1,
+ ));
+
+ // Change {node_type}.module field default.
+ db_change_field('node_type', 'module', 'module', array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ ));
+}
+
+/**
+ * Switches body fields to untranslatable while upgrading from D6 and makes them language neutral.
+ */
+function node_update_7012() {
+ // If we are upgrading from D6, then body fields should be set back to
+ // untranslatable, as D6 did not know about the idea of translating fields,
+ // but only nodes. If a D7 > D7 update is running we need to skip this update,
+ // as it is a valid use case to have translatable body fields in this context.
+ if (variable_get('update_d6', FALSE)) {
+ // Make node bodies untranslatable: field_update_field() cannot be used
+ // throughout the upgrade process and we do not have an update counterpart
+ // for _update_7000_field_create_field(). Hence we are forced to update the
+ // 'field_config' table directly. This is a safe operation since it is
+ // being performed while upgrading from D6. Perfoming the same operation
+ // during a D7 update is highly discouraged.
+ db_update('field_config')
+ ->fields(array('translatable' => 0))
+ ->condition('field_name', 'body')
+ ->execute();
+
+ // Switch field languages to LANGUAGE_NONE, since initially they were
+ // assigned the node language.
+ foreach (array('field_data_body', 'field_revision_body') as $table) {
+ db_update($table)
+ ->fields(array('language' => LANGUAGE_NONE))
+ ->execute();
+ }
+
+ node_type_cache_reset();
+ }
+}
+
+/**
+ * Change {node}.vid default value from 0 to NULL to avoid deadlock issues on MySQL.
+ */
+function node_update_7013() {
+ db_drop_unique_key('node', 'vid');
+ db_change_field('node', 'vid', 'vid', array(
+ 'description' => 'The current {node_revision}.vid version identifier.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ 'default' => NULL,
+ ));
+ db_add_unique_key('node', 'vid', array('vid'));
+}
+
+/**
+ * Add an index on {node}.language.
+ */
+function node_update_7014() {
+ db_add_index('node', 'language', array('language'));
+}
+
+/**
+ * @} End of "addtogroup updates-7.x-extra".
+ */
diff --git a/kolab.org/www/drupal-7.26/modules/node/node.js b/kolab.org/www/drupal-7.26/modules/node/node.js
new file mode 100644
index 0000000..ebf68eb
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/node/node.js
@@ -0,0 +1,43 @@
+
+(function ($) {
+
+Drupal.behaviors.nodeFieldsetSummaries = {
+ attach: function (context) {
+ $('fieldset.node-form-revision-information', context).drupalSetSummary(function (context) {
+ var revisionCheckbox = $('.form-item-revision input', context);
+
+ // Return 'New revision' if the 'Create new revision' checkbox is checked,
+ // or if the checkbox doesn't exist, but the revision log does. For users
+ // without the "Administer content" permission the checkbox won't appear,
+ // but the revision log will if the content type is set to auto-revision.
+ if (revisionCheckbox.is(':checked') || (!revisionCheckbox.length && $('.form-item-log textarea', context).length)) {
+ return Drupal.t('New revision');
+ }
+
+ return Drupal.t('No revision');
+ });
+
+ $('fieldset.node-form-author', context).drupalSetSummary(function (context) {
+ var name = $('.form-item-name input', context).val() || Drupal.settings.anonymous,
+ date = $('.form-item-date input', context).val();
+ return date ?
+ Drupal.t('By @name on @date', { '@name': name, '@date': date }) :
+ Drupal.t('By @name', { '@name': name });
+ });
+
+ $('fieldset.node-form-options', context).drupalSetSummary(function (context) {
+ var vals = [];
+
+ $('input:checked', context).parent().each(function () {
+ vals.push(Drupal.checkPlain($.trim($(this).text())));
+ });
+
+ if (!$('.form-item-status input', context).is(':checked')) {
+ vals.unshift(Drupal.t('Not published'));
+ }
+ return vals.join(', ');
+ });
+ }
+};
+
+})(jQuery);
diff --git a/kolab.org/www/drupal-7.26/modules/node/node.module b/kolab.org/www/drupal-7.26/modules/node/node.module
new file mode 100644
index 0000000..8f247cd
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/node/node.module
@@ -0,0 +1,4174 @@
+<?php
+
+/**
+ * @file
+ * The core that allows content to be submitted to the site. Modules and
+ * scripts may programmatically submit nodes using the usual form API pattern.
+ */
+
+/**
+ * Node is not published.
+ */
+define('NODE_NOT_PUBLISHED', 0);
+
+/**
+ * Node is published.
+ */
+define('NODE_PUBLISHED', 1);
+
+/**
+ * Node is not promoted to front page.
+ */
+define('NODE_NOT_PROMOTED', 0);
+
+/**
+ * Node is promoted to front page.
+ */
+define('NODE_PROMOTED', 1);
+
+/**
+ * Node is not sticky at top of the page.
+ */
+define('NODE_NOT_STICKY', 0);
+
+/**
+ * Node is sticky at top of the page.
+ */
+define('NODE_STICKY', 1);
+
+/**
+ * Nodes changed before this time are always marked as read.
+ *
+ * Nodes changed after this time may be marked new, updated, or read, depending
+ * on their state for the current user. Defaults to 30 days ago.
+ */
+define('NODE_NEW_LIMIT', REQUEST_TIME - 30 * 24 * 60 * 60);
+
+/**
+ * Modules should return this value from hook_node_access() to allow access to a node.
+ */
+define('NODE_ACCESS_ALLOW', 'allow');
+
+/**
+ * Modules should return this value from hook_node_access() to deny access to a node.
+ */
+define('NODE_ACCESS_DENY', 'deny');
+
+/**
+ * Modules should return this value from hook_node_access() to not affect node access.
+ */
+define('NODE_ACCESS_IGNORE', NULL);
+
+/**
+ * Implements hook_help().
+ */
+function node_help($path, $arg) {
+ // Remind site administrators about the {node_access} table being flagged
+ // for rebuild. We don't need to issue the message on the confirm form, or
+ // while the rebuild is being processed.
+ if ($path != 'admin/reports/status/rebuild' && $path != 'batch' && strpos($path, '#') === FALSE
+ && user_access('access administration pages') && node_access_needs_rebuild()) {
+ if ($path == 'admin/reports/status') {
+ $message = t('The content access permissions need to be rebuilt.');
+ }
+ else {
+ $message = t('The content access permissions need to be rebuilt. <a href="@node_access_rebuild">Rebuild permissions</a>.', array('@node_access_rebuild' => url('admin/reports/status/rebuild')));
+ }
+ drupal_set_message($message, 'error');
+ }
+
+ switch ($path) {
+ case 'admin/help#node':
+ $output = '';
+ $output .= '<h3>' . t('About') . '</h3>';
+ $output .= '<p>' . t('The Node module manages the creation, editing, deletion, settings, and display of the main site content. Content items managed by the Node module are typically displayed as pages on your site, and include a title, some meta-data (author, creation time, content type, etc.), and optional fields containing text or other data (fields are managed by the <a href="@field">Field module</a>). For more information, see the online handbook entry for <a href="@node">Node module</a>.', array('@node' => 'http://drupal.org/documentation/modules/node', '@field' => url('admin/help/field'))) . '</p>';
+ $output .= '<h3>' . t('Uses') . '</h3>';
+ $output .= '<dl>';
+ $output .= '<dt>' . t('Creating content') . '</dt>';
+ $output .= '<dd>' . t('When new content is created, the Node module records basic information about the content, including the author, date of creation, and the <a href="@content-type">Content type</a>. It also manages the <em>publishing options</em>, which define whether or not the content is published, promoted to the front page of the site, and/or sticky at the top of content lists. Default settings can be configured for each <a href="@content-type">type of content</a> on your site.', array('@content-type' => url('admin/structure/types'))) . '</dd>';
+ $output .= '<dt>' . t('Creating custom content types') . '</dt>';
+ $output .= '<dd>' . t('The Node module gives users with the <em>Administer content types</em> permission the ability to <a href="@content-new">create new content types</a> in addition to the default ones already configured. Creating custom content types allows you the flexibility to add <a href="@field">fields</a> and configure default settings that suit the differing needs of various site content.', array('@content-new' => url('admin/structure/types/add'), '@field' => url('admin/help/field'))) . '</dd>';
+ $output .= '<dt>' . t('Administering content') . '</dt>';
+ $output .= '<dd>' . t('The <a href="@content">Content administration page</a> allows you to review and bulk manage your site content.', array('@content' => url('admin/content'))) . '</dd>';
+ $output .= '<dt>' . t('Creating revisions') . '</dt>';
+ $output .= '<dd>' . t('The Node module also enables you to create multiple versions of any content, and revert to older versions using the <em>Revision information</em> settings.') . '</dd>';
+ $output .= '<dt>' . t('User permissions') . '</dt>';
+ $output .= '<dd>' . t('The Node module makes a number of permissions available for each content type, which can be set by role on the <a href="@permissions">permissions page</a>.', array('@permissions' => url('admin/people/permissions', array('fragment' => 'module-node')))) . '</dd>';
+ $output .= '</dl>';
+ return $output;
+
+ case 'admin/structure/types/add':
+ return '<p>' . t('Individual content types can have different fields, behaviors, and permissions assigned to them.') . '</p>';
+
+ case 'admin/structure/types/manage/%/display':
+ return '<p>' . t('Content items can be displayed using different view modes: Teaser, Full content, Print, RSS, etc. <em>Teaser</em> is a short format that is typically used in lists of multiple content items. <em>Full content</em> is typically used when the content is displayed on its own page.') . '</p>' .
+ '<p>' . t('Here, you can define which fields are shown and hidden when %type content is displayed in each view mode, and define how the fields are displayed in each view mode.', array('%type' => node_type_get_name($arg[4]))) . '</p>';
+
+ case 'node/%/revisions':
+ return '<p>' . t('Revisions allow you to track differences between multiple versions of your content, and revert back to older versions.') . '</p>';
+
+ case 'node/%/edit':
+ $node = node_load($arg[1]);
+ $type = node_type_get_type($node);
+ return (!empty($type->help) ? '<p>' . filter_xss_admin($type->help) . '</p>' : '');
+ }
+
+ if ($arg[0] == 'node' && $arg[1] == 'add' && $arg[2]) {
+ $type = node_type_get_type(str_replace('-', '_', $arg[2]));
+ return (!empty($type->help) ? '<p>' . filter_xss_admin($type->help) . '</p>' : '');
+ }
+}
+
+/**
+ * Implements hook_theme().
+ */
+function node_theme() {
+ return array(
+ 'node' => array(
+ 'render element' => 'elements',
+ 'template' => 'node',
+ ),
+ 'node_search_admin' => array(
+ 'render element' => 'form',
+ ),
+ 'node_add_list' => array(
+ 'variables' => array('content' => NULL),
+ 'file' => 'node.pages.inc',
+ ),
+ 'node_preview' => array(
+ 'variables' => array('node' => NULL),
+ 'file' => 'node.pages.inc',
+ ),
+ 'node_admin_overview' => array(
+ 'variables' => array('name' => NULL, 'type' => NULL),
+ 'file' => 'content_types.inc',
+ ),
+ 'node_recent_block' => array(
+ 'variables' => array('nodes' => NULL),
+ ),
+ 'node_recent_content' => array(
+ 'variables' => array('node' => NULL),
+ ),
+ );
+}
+
+/**
+ * Implements hook_cron().
+ */
+function node_cron() {
+ db_delete('history')
+ ->condition('timestamp', NODE_NEW_LIMIT, '<')
+ ->execute();
+}
+
+/**
+ * Implements hook_entity_info().
+ */
+function node_entity_info() {
+ $return = array(
+ 'node' => array(
+ 'label' => t('Node'),
+ 'controller class' => 'NodeController',
+ 'base table' => 'node',
+ 'revision table' => 'node_revision',
+ 'uri callback' => 'node_uri',
+ 'fieldable' => TRUE,
+ 'entity keys' => array(
+ 'id' => 'nid',
+ 'revision' => 'vid',
+ 'bundle' => 'type',
+ 'label' => 'title',
+ 'language' => 'language',
+ ),
+ 'bundle keys' => array(
+ 'bundle' => 'type',
+ ),
+ 'bundles' => array(),
+ 'view modes' => array(
+ 'full' => array(
+ 'label' => t('Full content'),
+ 'custom settings' => FALSE,
+ ),
+ 'teaser' => array(
+ 'label' => t('Teaser'),
+ 'custom settings' => TRUE,
+ ),
+ 'rss' => array(
+ 'label' => t('RSS'),
+ 'custom settings' => FALSE,
+ ),
+ ),
+ ),
+ );
+
+ // Search integration is provided by node.module, so search-related view modes
+ // for nodes are defined here and not in search.module.
+ if (module_exists('search')) {
+ $return['node']['view modes'] += array(
+ 'search_index' => array(
+ 'label' => t('Search index'),
+ 'custom settings' => FALSE,
+ ),
+ 'search_result' => array(
+ 'label' => t('Search result'),
+ 'custom settings' => FALSE,
+ ),
+ );
+ }
+
+ // Bundles must provide a human readable name so we can create help and error
+ // messages, and the path to attach Field admin pages to.
+ foreach (node_type_get_names() as $type => $name) {
+ $return['node']['bundles'][$type] = array(
+ 'label' => $name,
+ 'admin' => array(
+ 'path' => 'admin/structure/types/manage/%node_type',
+ 'real path' => 'admin/structure/types/manage/' . str_replace('_', '-', $type),
+ 'bundle argument' => 4,
+ 'access arguments' => array('administer content types'),
+ ),
+ );
+ }
+
+ return $return;
+}
+
+/**
+ * Implements hook_field_display_ENTITY_TYPE_alter().
+ */
+function node_field_display_node_alter(&$display, $context) {
+ // Hide field labels in search index.
+ if ($context['view_mode'] == 'search_index') {
+ $display['label'] = 'hidden';
+ }
+}
+
+/**
+ * Implements callback_entity_info_uri().
+ */
+function node_uri($node) {
+ return array(
+ 'path' => 'node/' . $node->nid,
+ );
+}
+
+/**
+ * Implements hook_admin_paths().
+ */
+function node_admin_paths() {
+ if (variable_get('node_admin_theme')) {
+ $paths = array(
+ 'node/*/edit' => TRUE,
+ 'node/*/delete' => TRUE,
+ 'node/*/revisions' => TRUE,
+ 'node/*/revisions/*/revert' => TRUE,
+ 'node/*/revisions/*/delete' => TRUE,
+ 'node/add' => TRUE,
+ 'node/add/*' => TRUE,
+ );
+ return $paths;
+ }
+}
+
+/**
+ * Gathers a listing of links to nodes.
+ *
+ * @param $result
+ * A database result object from a query to fetch node entities. If your
+ * query joins the {node_comment_statistics} table so that the comment_count
+ * field is available, a title attribute will be added to show the number of
+ * comments.
+ * @param $title
+ * A heading for the resulting list.
+ *
+ * @return
+ * A renderable array containing a list of linked node titles fetched from
+ * $result, or FALSE if there are no rows in $result.
+ */
+function node_title_list($result, $title = NULL) {
+ $items = array();
+ $num_rows = FALSE;
+ foreach ($result as $node) {
+ $items[] = l($node->title, 'node/' . $node->nid, !empty($node->comment_count) ? array('attributes' => array('title' => format_plural($node->comment_count, '1 comment', '@count comments'))) : array());
+ $num_rows = TRUE;
+ }
+
+ return $num_rows ? array('#theme' => 'item_list__node', '#items' => $items, '#title' => $title) : FALSE;
+}
+
+/**
+ * Updates the 'last viewed' timestamp of the specified node for current user.
+ *
+ * @param $node
+ * A node object.
+ */
+function node_tag_new($node) {
+ global $user;
+ if ($user->uid) {
+ db_merge('history')
+ ->key(array(
+ 'uid' => $user->uid,
+ 'nid' => $node->nid,
+ ))
+ ->fields(array('timestamp' => REQUEST_TIME))
+ ->execute();
+ }
+}
+
+/**
+ * Retrieves the timestamp for the current user's last view of a specified node.
+ *
+ * @param $nid
+ * A node ID.
+ *
+ * @return
+ * If a node has been previously viewed by the user, the timestamp in seconds
+ * of when the last view occurred; otherwise, zero.
+ */
+function node_last_viewed($nid) {
+ global $user;
+ $history = &drupal_static(__FUNCTION__, array());
+
+ if (!isset($history[$nid])) {
+ $history[$nid] = db_query("SELECT timestamp FROM {history} WHERE uid = :uid AND nid = :nid", array(':uid' => $user->uid, ':nid' => $nid))->fetchObject();
+ }
+
+ return (isset($history[$nid]->timestamp) ? $history[$nid]->timestamp : 0);
+}
+
+/**
+ * Determines the type of marker to be displayed for a given node.
+ *
+ * @param $nid
+ * Node ID whose history supplies the "last viewed" timestamp.
+ * @param $timestamp
+ * Time which is compared against node's "last viewed" timestamp.
+ *
+ * @return
+ * One of the MARK constants.
+ */
+function node_mark($nid, $timestamp) {
+ global $user;
+ $cache = &drupal_static(__FUNCTION__, array());
+
+ if (!$user->uid) {
+ return MARK_READ;
+ }
+ if (!isset($cache[$nid])) {
+ $cache[$nid] = node_last_viewed($nid);
+ }
+ if ($cache[$nid] == 0 && $timestamp > NODE_NEW_LIMIT) {
+ return MARK_NEW;
+ }
+ elseif ($timestamp > $cache[$nid] && $timestamp > NODE_NEW_LIMIT) {
+ return MARK_UPDATED;
+ }
+ return MARK_READ;
+}
+
+/**
+ * Extracts the type name.
+ *
+ * @param $node
+ * Either a string or object, containing the node type information.
+ *
+ * @return
+ * Node type of the passed-in data.
+ */
+function _node_extract_type($node) {
+ return is_object($node) ? $node->type : $node;
+}
+
+/**
+ * Returns a list of all the available node types.
+ *
+ * This list can include types that are queued for addition or deletion.
+ * See _node_types_build() for details.
+ *
+ * @return
+ * An array of node types, as objects, keyed by the type.
+ *
+ * @see node_type_get_type()
+ */
+function node_type_get_types() {
+ return _node_types_build()->types;
+}
+
+/**
+ * Returns the node type of the passed node or node type string.
+ *
+ * @param $node
+ * A node object or string that indicates the node type to return.
+ *
+ * @return
+ * A single node type, as an object, or FALSE if the node type is not found.
+ * The node type is an object containing fields from hook_node_info() return
+ * values, as well as the field 'type' (the machine-readable type) and other
+ * fields used internally and defined in _node_types_build(),
+ * hook_node_info(), and node_type_set_defaults().
+ */
+function node_type_get_type($node) {
+ $type = _node_extract_type($node);
+ $types = _node_types_build()->types;
+ return isset($types[$type]) ? $types[$type] : FALSE;
+}
+
+/**
+ * Returns the node type base of the passed node or node type string.
+ *
+ * The base indicates which module implements this node type and is used to
+ * execute node-type-specific hooks. For types defined in the user interface
+ * and managed by node.module, the base is 'node_content'.
+ *
+ * @param $node
+ * A node object or string that indicates the node type to return.
+ *
+ * @return
+ * The node type base or FALSE if the node type is not found.
+ *
+ * @see node_invoke()
+ */
+function node_type_get_base($node) {
+ $type = _node_extract_type($node);
+ $types = _node_types_build()->types;
+ return isset($types[$type]) && isset($types[$type]->base) ? $types[$type]->base : FALSE;
+}
+
+/**
+ * Returns a list of available node type names.
+ *
+ * This list can include types that are queued for addition or deletion.
+ * See _node_types_build() for details.
+ *
+ * @return
+ * An array of node type names, keyed by the type.
+ */
+function node_type_get_names() {
+ return _node_types_build()->names;
+}
+
+/**
+ * Returns the node type name of the passed node or node type string.
+ *
+ * @param $node
+ * A node object or string that indicates the node type to return.
+ *
+ * @return
+ * The node type name or FALSE if the node type is not found.
+ */
+function node_type_get_name($node) {
+ $type = _node_extract_type($node);
+ $types = _node_types_build()->names;
+ return isset($types[$type]) ? $types[$type] : FALSE;
+}
+
+/**
+ * Updates the database cache of node types.
+ *
+ * All new module-defined node types are saved to the database via a call to
+ * node_type_save(), and obsolete ones are deleted via a call to
+ * node_type_delete(). See _node_types_build() for an explanation of the new
+ * and obsolete types.
+ *
+ * @see _node_types_build()
+ */
+function node_types_rebuild() {
+ _node_types_build(TRUE);
+}
+
+/**
+ * Menu argument loader: loads a node type by string.
+ *
+ * @param $name
+ * The machine-readable name of a node type to load, where '_' is replaced
+ * with '-'.
+ *
+ * @return
+ * A node type object or FALSE if $name does not exist.
+ */
+function node_type_load($name) {
+ return node_type_get_type(strtr($name, array('-' => '_')));
+}
+
+/**
+ * Saves a node type to the database.
+ *
+ * @param object $info
+ * The node type to save; an object with the following properties:
+ * - type: A string giving the machine name of the node type.
+ * - name: A string giving the human-readable name of the node type.
+ * - base: A string that indicates the base string for hook functions. For
+ * example, 'node_content' is the value used by the UI when creating a new
+ * node type.
+ * - description: A string that describes the node type.
+ * - help: A string giving the help information shown to the user when
+ * creating a node of this type.
+ * - custom: TRUE or FALSE indicating whether this type is defined by a module
+ * (FALSE) or by a user (TRUE) via Add Content Type.
+ * - modified: TRUE or FALSE indicating whether this type has been modified by
+ * an administrator. Currently not used in any way.
+ * - locked: TRUE or FALSE indicating whether the administrator can change the
+ * machine name of this type.
+ * - disabled: TRUE or FALSE indicating whether this type has been disabled.
+ * - has_title: TRUE or FALSE indicating whether this type uses the node title
+ * field.
+ * - title_label: A string containing the label for the title.
+ * - module: A string giving the module defining this type of node.
+ * - orig_type: A string giving the original machine-readable name of this
+ * node type. This may be different from the current type name if the
+ * 'locked' key is FALSE.
+ *
+ * @return int
+ * A status flag indicating the outcome of the operation, either SAVED_NEW or
+ * SAVED_UPDATED.
+ */
+function node_type_save($info) {
+ $existing_type = !empty($info->old_type) ? $info->old_type : $info->type;
+ $is_existing = (bool) db_query_range('SELECT 1 FROM {node_type} WHERE type = :type', 0, 1, array(':type' => $existing_type))->fetchField();
+ $type = node_type_set_defaults($info);
+
+ $fields = array(
+ 'type' => (string) $type->type,
+ 'name' => (string) $type->name,
+ 'base' => (string) $type->base,
+ 'has_title' => (int) $type->has_title,
+ 'title_label' => (string) $type->title_label,
+ 'description' => (string) $type->description,
+ 'help' => (string) $type->help,
+ 'custom' => (int) $type->custom,
+ 'modified' => (int) $type->modified,
+ 'locked' => (int) $type->locked,
+ 'disabled' => (int) $type->disabled,
+ 'module' => $type->module,
+ );
+
+ if ($is_existing) {
+ db_update('node_type')
+ ->fields($fields)
+ ->condition('type', $existing_type)
+ ->execute();
+
+ if (!empty($type->old_type) && $type->old_type != $type->type) {
+ field_attach_rename_bundle('node', $type->old_type, $type->type);
+ }
+ module_invoke_all('node_type_update', $type);
+ $status = SAVED_UPDATED;
+ }
+ else {
+ $fields['orig_type'] = (string) $type->orig_type;
+ db_insert('node_type')
+ ->fields($fields)
+ ->execute();
+
+ field_attach_create_bundle('node', $type->type);
+
+ module_invoke_all('node_type_insert', $type);
+ $status = SAVED_NEW;
+ }
+
+ // Clear the node type cache.
+ node_type_cache_reset();
+
+ return $status;
+}
+
+/**
+ * Adds default body field to a node type.
+ *
+ * @param $type
+ * A node type object.
+ * @param $label
+ * The label for the body instance.
+ *
+ * @return
+ * Body field instance.
+ */
+function node_add_body_field($type, $label = 'Body') {
+ // Add or remove the body field, as needed.
+ $field = field_info_field('body');
+ $instance = field_info_instance('node', 'body', $type->type);
+ if (empty($field)) {
+ $field = array(
+ 'field_name' => 'body',
+ 'type' => 'text_with_summary',
+ 'entity_types' => array('node'),
+ );
+ $field = field_create_field($field);
+ }
+ if (empty($instance)) {
+ $instance = array(
+ 'field_name' => 'body',
+ 'entity_type' => 'node',
+ 'bundle' => $type->type,
+ 'label' => $label,
+ 'widget' => array('type' => 'text_textarea_with_summary'),
+ 'settings' => array('display_summary' => TRUE),
+ 'display' => array(
+ 'default' => array(
+ 'label' => 'hidden',
+ 'type' => 'text_default',
+ ),
+ 'teaser' => array(
+ 'label' => 'hidden',
+ 'type' => 'text_summary_or_trimmed',
+ ),
+ ),
+ );
+ $instance = field_create_instance($instance);
+ }
+ return $instance;
+}
+
+/**
+ * Implements hook_field_extra_fields().
+ */
+function node_field_extra_fields() {
+ $extra = array();
+
+ foreach (node_type_get_types() as $type) {
+ if ($type->has_title) {
+ $extra['node'][$type->type] = array(
+ 'form' => array(
+ 'title' => array(
+ 'label' => $type->title_label,
+ 'description' => t('Node module element'),
+ 'weight' => -5,
+ ),
+ ),
+ );
+ }
+ }
+
+ return $extra;
+}
+
+/**
+ * Deletes a node type from the database.
+ *
+ * @param $type
+ * The machine-readable name of the node type to be deleted.
+ */
+function node_type_delete($type) {
+ $info = node_type_get_type($type);
+ db_delete('node_type')
+ ->condition('type', $type)
+ ->execute();
+ field_attach_delete_bundle('node', $type);
+ module_invoke_all('node_type_delete', $info);
+
+ // Clear the node type cache.
+ node_type_cache_reset();
+}
+
+/**
+ * Updates all nodes of one type to be of another type.
+ *
+ * @param $old_type
+ * The current node type of the nodes.
+ * @param $type
+ * The new node type of the nodes.
+ *
+ * @return
+ * The number of nodes whose node type field was modified.
+ */
+function node_type_update_nodes($old_type, $type) {
+ return db_update('node')
+ ->fields(array('type' => $type))
+ ->condition('type', $old_type)
+ ->execute();
+}
+
+/**
+ * Builds and returns the list of available node types.
+ *
+ * The list of types is built by invoking hook_node_info() on all modules and
+ * comparing this information with the node types in the {node_type} table.
+ * These two information sources are not synchronized during module installation
+ * until node_types_rebuild() is called.
+ *
+ * @param $rebuild
+ * TRUE to rebuild node types. Equivalent to calling node_types_rebuild().
+ *
+ * @return
+ * An object with two properties:
+ * - names: Associative array of the names of node types, keyed by the type.
+ * - types: Associative array of node type objects, keyed by the type.
+ * Both of these arrays will include new types that have been defined by
+ * hook_node_info() implementations but not yet saved in the {node_type}
+ * table. These are indicated in the type object by $type->is_new being set
+ * to the value 1. These arrays will also include obsolete types: types that
+ * were previously defined by modules that have now been disabled, or for
+ * whatever reason are no longer being defined in hook_node_info()
+ * implementations, but are still in the database. These are indicated in the
+ * type object by $type->disabled being set to TRUE.
+ */
+function _node_types_build($rebuild = FALSE) {
+ $cid = 'node_types:' . $GLOBALS['language']->language;
+
+ if (!$rebuild) {
+ $_node_types = &drupal_static(__FUNCTION__);
+ if (isset($_node_types)) {
+ return $_node_types;
+ }
+ if ($cache = cache_get($cid)) {
+ $_node_types = $cache->data;
+ return $_node_types;
+ }
+ }
+
+ $_node_types = (object) array('types' => array(), 'names' => array());
+
+ foreach (module_implements('node_info') as $module) {
+ $info_array = module_invoke($module, 'node_info');
+ foreach ($info_array as $type => $info) {
+ $info['type'] = $type;
+ $_node_types->types[$type] = node_type_set_defaults($info);
+ $_node_types->types[$type]->module = $module;
+ $_node_types->names[$type] = $info['name'];
+ }
+ }
+ $query = db_select('node_type', 'nt')
+ ->addTag('translatable')
+ ->addTag('node_type_access')
+ ->fields('nt')
+ ->orderBy('nt.type', 'ASC');
+ if (!$rebuild) {
+ $query->condition('disabled', 0);
+ }
+ foreach ($query->execute() as $type_object) {
+ $type_db = $type_object->type;
+ // Original disabled value.
+ $disabled = $type_object->disabled;
+ // Check for node types from disabled modules and mark their types for removal.
+ // Types defined by the node module in the database (rather than by a separate
+ // module using hook_node_info) have a base value of 'node_content'. The isset()
+ // check prevents errors on old (pre-Drupal 7) databases.
+ if (isset($type_object->base) && $type_object->base != 'node_content' && empty($_node_types->types[$type_db])) {
+ $type_object->disabled = TRUE;
+ }
+ if (isset($_node_types->types[$type_db])) {
+ $type_object->disabled = FALSE;
+ }
+ if (!isset($_node_types->types[$type_db]) || $type_object->modified) {
+ $_node_types->types[$type_db] = $type_object;
+ $_node_types->names[$type_db] = $type_object->name;
+
+ if ($type_db != $type_object->orig_type) {
+ unset($_node_types->types[$type_object->orig_type]);
+ unset($_node_types->names[$type_object->orig_type]);
+ }
+ }
+ $_node_types->types[$type_db]->disabled = $type_object->disabled;
+ $_node_types->types[$type_db]->disabled_changed = $disabled != $type_object->disabled;
+ }
+
+ if ($rebuild) {
+ foreach ($_node_types->types as $type => $type_object) {
+ if (!empty($type_object->is_new) || !empty($type_object->disabled_changed)) {
+ node_type_save($type_object);
+ }
+ }
+ }
+
+ asort($_node_types->names);
+
+ cache_set($cid, $_node_types);
+
+ return $_node_types;
+}
+
+/**
+ * Clears the node type cache.
+ */
+function node_type_cache_reset() {
+ cache_clear_all('node_types:', 'cache', TRUE);
+ drupal_static_reset('_node_types_build');
+}
+
+/**
+ * Sets the default values for a node type.
+ *
+ * The defaults are appropriate for a type defined through hook_node_info(),
+ * since 'custom' is TRUE for types defined in the user interface, and FALSE
+ * for types defined by modules. (The 'custom' flag prevents types from being
+ * deleted through the user interface.) Also, the default for 'locked' is TRUE,
+ * which prevents users from changing the machine name of the type.
+ *
+ * @param $info
+ * (optional) An object or array containing values to override the defaults.
+ * See hook_node_info() for details on what the array elements mean. Defaults
+ * to an empty array.
+ *
+ * @return
+ * A node type object, with missing values in $info set to their defaults.
+ */
+function node_type_set_defaults($info = array()) {
+ $info = (array) $info;
+ $new_type = $info + array(
+ 'type' => '',
+ 'name' => '',
+ 'base' => '',
+ 'description' => '',
+ 'help' => '',
+ 'custom' => 0,
+ 'modified' => 0,
+ 'locked' => 1,
+ 'disabled' => 0,
+ 'is_new' => 1,
+ 'has_title' => 1,
+ 'title_label' => 'Title',
+ );
+ $new_type = (object) $new_type;
+
+ // If the type has no title, set an empty label.
+ if (!$new_type->has_title) {
+ $new_type->title_label = '';
+ }
+ if (empty($new_type->module)) {
+ $new_type->module = $new_type->base == 'node_content' ? 'node' : '';
+ }
+ $new_type->orig_type = isset($info['type']) ? $info['type'] : '';
+
+ return $new_type;
+}
+
+/**
+ * Implements hook_rdf_mapping().
+ */
+function node_rdf_mapping() {
+ return array(
+ array(
+ 'type' => 'node',
+ 'bundle' => RDF_DEFAULT_BUNDLE,
+ 'mapping' => array(
+ 'rdftype' => array('sioc:Item', 'foaf:Document'),
+ 'title' => array(
+ 'predicates' => array('dc:title'),
+ ),
+ 'created' => array(
+ 'predicates' => array('dc:date', 'dc:created'),
+ 'datatype' => 'xsd:dateTime',
+ 'callback' => 'date_iso8601',
+ ),
+ 'changed' => array(
+ 'predicates' => array('dc:modified'),
+ 'datatype' => 'xsd:dateTime',
+ 'callback' => 'date_iso8601',
+ ),
+ 'body' => array(
+ 'predicates' => array('content:encoded'),
+ ),
+ 'uid' => array(
+ 'predicates' => array('sioc:has_creator'),
+ 'type' => 'rel',
+ ),
+ 'name' => array(
+ 'predicates' => array('foaf:name'),
+ ),
+ 'comment_count' => array(
+ 'predicates' => array('sioc:num_replies'),
+ 'datatype' => 'xsd:integer',
+ ),
+ 'last_activity' => array(
+ 'predicates' => array('sioc:last_activity_date'),
+ 'datatype' => 'xsd:dateTime',
+ 'callback' => 'date_iso8601',
+ ),
+ ),
+ ),
+ );
+}
+
+/**
+ * Determines whether a node hook exists.
+ *
+ * @param $node
+ * A node object or a string containing the node type.
+ * @param $hook
+ * A string containing the name of the hook.
+ *
+ * @return
+ * TRUE if the $hook exists in the node type of $node.
+ */
+function node_hook($node, $hook) {
+ $base = node_type_get_base($node);
+ return module_hook($base, $hook);
+}
+
+/**
+ * Invokes a node hook.
+ *
+ * @param $node
+ * A node object or a string containing the node type.
+ * @param $hook
+ * A string containing the name of the hook.
+ * @param $a2, $a3, $a4
+ * Arguments to pass on to the hook, after the $node argument.
+ *
+ * @return
+ * The returned value of the invoked hook.
+ */
+function node_invoke($node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) {
+ if (node_hook($node, $hook)) {
+ $base = node_type_get_base($node);
+ $function = $base . '_' . $hook;
+ return ($function($node, $a2, $a3, $a4));
+ }
+}
+
+/**
+ * Loads node entities from the database.
+ *
+ * This function should be used whenever you need to load more than one node
+ * from the database. Nodes are loaded into memory and will not require database
+ * access if loaded again during the same page request.
+ *
+ * @see entity_load()
+ * @see EntityFieldQuery
+ *
+ * @param $nids
+ * An array of node IDs.
+ * @param $conditions
+ * (deprecated) An associative array of conditions on the {node}
+ * table, where the keys are the database fields and the values are the
+ * values those fields must have. Instead, it is preferable to use
+ * EntityFieldQuery to retrieve a list of entity IDs loadable by
+ * this function.
+ * @param $reset
+ * Whether to reset the internal node_load cache.
+ *
+ * @return
+ * An array of node objects indexed by nid.
+ *
+ * @todo Remove $conditions in Drupal 8.
+ */
+function node_load_multiple($nids = array(), $conditions = array(), $reset = FALSE) {
+ return entity_load('node', $nids, $conditions, $reset);
+}
+
+/**
+ * Loads a node object from the database.
+ *
+ * @param $nid
+ * The node ID.
+ * @param $vid
+ * The revision ID.
+ * @param $reset
+ * Whether to reset the node_load_multiple cache.
+ *
+ * @return
+ * A fully-populated node object, or FALSE if the node is not found.
+ */
+function node_load($nid = NULL, $vid = NULL, $reset = FALSE) {
+ $nids = (isset($nid) ? array($nid) : array());
+ $conditions = (isset($vid) ? array('vid' => $vid) : array());
+ $node = node_load_multiple($nids, $conditions, $reset);
+ return $node ? reset($node) : FALSE;
+}
+
+/**
+ * Prepares a node object for editing.
+ *
+ * Fills in a few default values, and then invokes hook_prepare() on the node
+ * type module, and hook_node_prepare() on all modules.
+ *
+ * @param $node
+ * A node object.
+ */
+function node_object_prepare($node) {
+ // Set up default values, if required.
+ $node_options = variable_get('node_options_' . $node->type, array('status', 'promote'));
+ // If this is a new node, fill in the default values.
+ if (!isset($node->nid) || isset($node->is_new)) {
+ foreach (array('status', 'promote', 'sticky') as $key) {
+ // Multistep node forms might have filled in something already.
+ if (!isset($node->$key)) {
+ $node->$key = (int) in_array($key, $node_options);
+ }
+ }
+ global $user;
+ $node->uid = $user->uid;
+ $node->created = REQUEST_TIME;
+ }
+ else {
+ $node->date = format_date($node->created, 'custom', 'Y-m-d H:i:s O');
+ // Remove the log message from the original node object.
+ $node->log = NULL;
+ }
+ // Always use the default revision setting.
+ $node->revision = in_array('revision', $node_options);
+
+ node_invoke($node, 'prepare');
+ module_invoke_all('node_prepare', $node);
+}
+
+/**
+ * Implements hook_validate().
+ *
+ * Performs validation checks on the given node.
+ */
+function node_validate($node, $form, &$form_state) {
+ if (isset($node->nid) && (node_last_changed($node->nid) > $node->changed)) {
+ form_set_error('changed', t('The content on this page has either been modified by another user, or you have already submitted modifications using this form. As a result, your changes cannot be saved.'));
+ }
+
+ // Validate the "authored by" field.
+ if (!empty($node->name) && !($account = user_load_by_name($node->name))) {
+ // The use of empty() is mandatory in the context of usernames
+ // as the empty string denotes the anonymous user. In case we
+ // are dealing with an anonymous user we set the user ID to 0.
+ form_set_error('name', t('The username %name does not exist.', array('%name' => $node->name)));
+ }
+
+ // Validate the "authored on" field.
+ if (!empty($node->date) && strtotime($node->date) === FALSE) {
+ form_set_error('date', t('You have to specify a valid date.'));
+ }
+
+ // Invoke hook_validate() for node type specific validation and
+ // hook_node_validate() for miscellaneous validation needed by modules. Can't
+ // use node_invoke() or module_invoke_all(), because $form_state must be
+ // receivable by reference.
+ $function = node_type_get_base($node) . '_validate';
+ if (function_exists($function)) {
+ $function($node, $form, $form_state);
+ }
+ foreach (module_implements('node_validate') as $module) {
+ $function = $module . '_node_validate';
+ $function($node, $form, $form_state);
+ }
+}
+
+/**
+ * Prepares node for saving by populating author and creation date.
+ *
+ * @param $node
+ * A node object.
+ *
+ * @return
+ * An updated node object.
+ */
+function node_submit($node) {
+ // A user might assign the node author by entering a user name in the node
+ // form, which we then need to translate to a user ID.
+ if (isset($node->name)) {
+ if ($account = user_load_by_name($node->name)) {
+ $node->uid = $account->uid;
+ }
+ else {
+ $node->uid = 0;
+ }
+ }
+
+ $node->created = !empty($node->date) ? strtotime($node->date) : REQUEST_TIME;
+ $node->validated = TRUE;
+
+ return $node;
+}
+
+/**
+ * Saves changes to a node or adds a new node.
+ *
+ * @param $node
+ * The $node object to be saved. If $node->nid is
+ * omitted (or $node->is_new is TRUE), a new node will be added.
+ */
+function node_save($node) {
+ $transaction = db_transaction();
+
+ try {
+ // Load the stored entity, if any.
+ if (!empty($node->nid) && !isset($node->original)) {
+ $node->original = entity_load_unchanged('node', $node->nid);
+ }
+
+ field_attach_presave('node', $node);
+ global $user;
+
+ // Determine if we will be inserting a new node.
+ if (!isset($node->is_new)) {
+ $node->is_new = empty($node->nid);
+ }
+
+ // Set the timestamp fields.
+ if (empty($node->created)) {
+ $node->created = REQUEST_TIME;
+ }
+ // The changed timestamp is always updated for bookkeeping purposes,
+ // for example: revisions, searching, etc.
+ $node->changed = REQUEST_TIME;
+
+ $node->timestamp = REQUEST_TIME;
+ $update_node = TRUE;
+
+ // Let modules modify the node before it is saved to the database.
+ module_invoke_all('node_presave', $node);
+ module_invoke_all('entity_presave', $node, 'node');
+
+ if ($node->is_new || !empty($node->revision)) {
+ // When inserting either a new node or a new node revision, $node->log
+ // must be set because {node_revision}.log is a text column and therefore
+ // cannot have a default value. However, it might not be set at this
+ // point (for example, if the user submitting a node form does not have
+ // permission to create revisions), so we ensure that it is at least an
+ // empty string in that case.
+ // @todo: Make the {node_revision}.log column nullable so that we can
+ // remove this check.
+ if (!isset($node->log)) {
+ $node->log = '';
+ }
+ }
+ elseif (!isset($node->log) || $node->log === '') {
+ // If we are updating an existing node without adding a new revision, we
+ // need to make sure $node->log is unset whenever it is empty. As long as
+ // $node->log is unset, drupal_write_record() will not attempt to update
+ // the existing database column when re-saving the revision; therefore,
+ // this code allows us to avoid clobbering an existing log entry with an
+ // empty one.
+ unset($node->log);
+ }
+
+ // When saving a new node revision, unset any existing $node->vid so as to
+ // ensure that a new revision will actually be created, then store the old
+ // revision ID in a separate property for use by node hook implementations.
+ if (!$node->is_new && !empty($node->revision) && $node->vid) {
+ $node->old_vid = $node->vid;
+ unset($node->vid);
+ }
+
+ // Save the node and node revision.
+ if ($node->is_new) {
+ // For new nodes, save new records for both the node itself and the node
+ // revision.
+ drupal_write_record('node', $node);
+ _node_save_revision($node, $user->uid);
+ $op = 'insert';
+ }
+ else {
+ // For existing nodes, update the node record which matches the value of
+ // $node->nid.
+ drupal_write_record('node', $node, 'nid');
+ // Then, if a new node revision was requested, save a new record for
+ // that; otherwise, update the node revision record which matches the
+ // value of $node->vid.
+ if (!empty($node->revision)) {
+ _node_save_revision($node, $user->uid);
+ }
+ else {
+ _node_save_revision($node, $user->uid, 'vid');
+ $update_node = FALSE;
+ }
+ $op = 'update';
+ }
+ if ($update_node) {
+ db_update('node')
+ ->fields(array('vid' => $node->vid))
+ ->condition('nid', $node->nid)
+ ->execute();
+ }
+
+ // Call the node specific callback (if any). This can be
+ // node_invoke($node, 'insert') or
+ // node_invoke($node, 'update').
+ node_invoke($node, $op);
+
+ // Save fields.
+ $function = "field_attach_$op";
+ $function('node', $node);
+
+ module_invoke_all('node_' . $op, $node);
+ module_invoke_all('entity_' . $op, $node, 'node');
+
+ // Update the node access table for this node.
+ node_access_acquire_grants($node);
+
+ // Clear internal properties.
+ unset($node->is_new);
+ unset($node->original);
+ // Clear the static loading cache.
+ entity_get_controller('node')->resetCache(array($node->nid));
+
+ // Ignore slave server temporarily to give time for the
+ // saved node to be propagated to the slave.
+ db_ignore_slave();
+ }
+ catch (Exception $e) {
+ $transaction->rollback();
+ watchdog_exception('node', $e);
+ throw $e;
+ }
+}
+
+/**
+ * Helper function to save a revision with the uid of the current user.
+ *
+ * The resulting revision ID is available afterward in $node->vid.
+ *
+ * @param $node
+ * A node object.
+ * @param $uid
+ * The current user's UID.
+ * @param $update
+ * (optional) An array of primary keys' field names to update.
+ */
+function _node_save_revision($node, $uid, $update = NULL) {
+ $temp_uid = $node->uid;
+ $node->uid = $uid;
+ if (isset($update)) {
+ drupal_write_record('node_revision', $node, $update);
+ }
+ else {
+ drupal_write_record('node_revision', $node);
+ }
+ // Have node object still show node owner's uid, not revision author's.
+ $node->uid = $temp_uid;
+}
+
+/**
+ * Deletes a node.
+ *
+ * @param $nid
+ * A node ID.
+ */
+function node_delete($nid) {
+ node_delete_multiple(array($nid));
+}
+
+/**
+ * Deletes multiple nodes.
+ *
+ * @param $nids
+ * An array of node IDs.
+ */
+function node_delete_multiple($nids) {
+ $transaction = db_transaction();
+ if (!empty($nids)) {
+ $nodes = node_load_multiple($nids, array());
+
+ try {
+ foreach ($nodes as $nid => $node) {
+ // Call the node-specific callback (if any):
+ node_invoke($node, 'delete');
+ module_invoke_all('node_delete', $node);
+ module_invoke_all('entity_delete', $node, 'node');
+ field_attach_delete('node', $node);
+
+ // Remove this node from the search index if needed.
+ // This code is implemented in node module rather than in search module,
+ // because node module is implementing search module's API, not the other
+ // way around.
+ if (module_exists('search')) {
+ search_reindex($nid, 'node');
+ }
+ }
+
+ // Delete after calling hooks so that they can query node tables as needed.
+ db_delete('node')
+ ->condition('nid', $nids, 'IN')
+ ->execute();
+ db_delete('node_revision')
+ ->condition('nid', $nids, 'IN')
+ ->execute();
+ db_delete('history')
+ ->condition('nid', $nids, 'IN')
+ ->execute();
+ db_delete('node_access')
+ ->condition('nid', $nids, 'IN')
+ ->execute();
+ }
+ catch (Exception $e) {
+ $transaction->rollback();
+ watchdog_exception('node', $e);
+ throw $e;
+ }
+
+ // Clear the page and block and node_load_multiple caches.
+ entity_get_controller('node')->resetCache();
+ }
+}
+
+/**
+ * Deletes a node revision.
+ *
+ * @param $revision_id
+ * The revision ID to delete.
+ */
+function node_revision_delete($revision_id) {
+ if ($revision = node_load(NULL, $revision_id)) {
+ // Prevent deleting the current revision.
+ $node = node_load($revision->nid);
+ if ($revision_id == $node->vid) {
+ return FALSE;
+ }
+
+ db_delete('node_revision')
+ ->condition('nid', $revision->nid)
+ ->condition('vid', $revision->vid)
+ ->execute();
+ module_invoke_all('node_revision_delete', $revision);
+ field_attach_delete_revision('node', $revision);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Generates an array for rendering the given node.
+ *
+ * @param $node
+ * A node object.
+ * @param $view_mode
+ * View mode, e.g. 'full', 'teaser'...
+ * @param $langcode
+ * (optional) A language code to use for rendering. Defaults to the global
+ * content language of the current request.
+ *
+ * @return
+ * An array as expected by drupal_render().
+ */
+function node_view($node, $view_mode = 'full', $langcode = NULL) {
+ if (!isset($langcode)) {
+ $langcode = $GLOBALS['language_content']->language;
+ }
+
+ // Populate $node->content with a render() array.
+ node_build_content($node, $view_mode, $langcode);
+
+ $build = $node->content;
+ // We don't need duplicate rendering info in node->content.
+ unset($node->content);
+
+ $build += array(
+ '#theme' => 'node',
+ '#node' => $node,
+ '#view_mode' => $view_mode,
+ '#language' => $langcode,
+ );
+
+ // Add contextual links for this node, except when the node is already being
+ // displayed on its own page. Modules may alter this behavior (for example,
+ // to restrict contextual links to certain view modes) by implementing
+ // hook_node_view_alter().
+ if (!empty($node->nid) && !($view_mode == 'full' && node_is_page($node))) {
+ $build['#contextual_links']['node'] = array('node', array($node->nid));
+ }
+
+ // Allow modules to modify the structured node.
+ $type = 'node';
+ drupal_alter(array('node_view', 'entity_view'), $build, $type);
+
+ return $build;
+}
+
+/**
+ * Builds a structured array representing the node's content.
+ *
+ * The content built for the node (field values, comments, file attachments or
+ * other node components) will vary depending on the $view_mode parameter.
+ *
+ * Drupal core defines the following view modes for nodes, with the following
+ * default use cases:
+ * - full (default): node is being displayed on its own page (node/123)
+ * - teaser: node is being displayed on the default home page listing, on
+ * taxonomy listing pages, or on blog listing pages.
+ * - rss: node displayed in an RSS feed.
+ * If search.module is enabled:
+ * - search_index: node is being indexed for search.
+ * - search_result: node is being displayed as a search result.
+ * If book.module is enabled:
+ * - print: node is being displayed in print-friendly mode.
+ * Contributed modules might define additional view modes, or use existing
+ * view modes in additional contexts.
+ *
+ * @param $node
+ * A node object.
+ * @param $view_mode
+ * View mode, e.g. 'full', 'teaser'...
+ * @param $langcode
+ * (optional) A language code to use for rendering. Defaults to the global
+ * content language of the current request.
+ */
+function node_build_content($node, $view_mode = 'full', $langcode = NULL) {
+ if (!isset($langcode)) {
+ $langcode = $GLOBALS['language_content']->language;
+ }
+
+ // Remove previously built content, if exists.
+ $node->content = array();
+
+ // Allow modules to change the view mode.
+ $context = array(
+ 'entity_type' => 'node',
+ 'entity' => $node,
+ 'langcode' => $langcode,
+ );
+ drupal_alter('entity_view_mode', $view_mode, $context);
+
+ // The 'view' hook can be implemented to overwrite the default function
+ // to display nodes.
+ if (node_hook($node, 'view')) {
+ $node = node_invoke($node, 'view', $view_mode, $langcode);
+ }
+
+ // Build fields content.
+ // In case of a multiple view, node_view_multiple() already ran the
+ // 'prepare_view' step. An internal flag prevents the operation from running
+ // twice.
+ field_attach_prepare_view('node', array($node->nid => $node), $view_mode, $langcode);
+ entity_prepare_view('node', array($node->nid => $node), $langcode);
+ $node->content += field_attach_view('node', $node, $view_mode, $langcode);
+
+ // Always display a read more link on teasers because we have no way to know
+ // when a teaser view is different than a full view.
+ $links = array();
+ $node->content['links'] = array(
+ '#theme' => 'links__node',
+ '#pre_render' => array('drupal_pre_render_links'),
+ '#attributes' => array('class' => array('links', 'inline')),
+ );
+ if ($view_mode == 'teaser') {
+ $node_title_stripped = strip_tags($node->title);
+ $links['node-readmore'] = array(
+ 'title' => t('Read more<span class="element-invisible"> about @title</span>', array('@title' => $node_title_stripped)),
+ 'href' => 'node/' . $node->nid,
+ 'html' => TRUE,
+ 'attributes' => array('rel' => 'tag', 'title' => $node_title_stripped),
+ );
+ }
+ $node->content['links']['node'] = array(
+ '#theme' => 'links__node__node',
+ '#links' => $links,
+ '#attributes' => array('class' => array('links', 'inline')),
+ );
+
+ // Allow modules to make their own additions to the node.
+ module_invoke_all('node_view', $node, $view_mode, $langcode);
+ module_invoke_all('entity_view', $node, 'node', $view_mode, $langcode);
+
+ // Make sure the current view mode is stored if no module has already
+ // populated the related key.
+ $node->content += array('#view_mode' => $view_mode);
+}
+
+/**
+ * Generates an array which displays a node detail page.
+ *
+ * @param $node
+ * A node object.
+ * @param $message
+ * A flag which sets a page title relevant to the revision being viewed.
+ *
+ * @return
+ * A $page element suitable for use by drupal_render().
+ */
+function node_show($node, $message = FALSE) {
+ if ($message) {
+ drupal_set_title(t('Revision of %title from %date', array('%title' => $node->title, '%date' => format_date($node->revision_timestamp))), PASS_THROUGH);
+ }
+
+ // For markup consistency with other pages, use node_view_multiple() rather than node_view().
+ $nodes = node_view_multiple(array($node->nid => $node), 'full');
+
+ // Update the history table, stating that this user viewed this node.
+ node_tag_new($node);
+
+ return $nodes;
+}
+
+/**
+ * Returns whether the current page is the full page view of the passed-in node.
+ *
+ * @param $node
+ * A node object.
+ *
+ * @return
+ * The ID of the node if this is a full page view, otherwise FALSE.
+ */
+function node_is_page($node) {
+ $page_node = menu_get_object();
+ return (!empty($page_node) ? $page_node->nid == $node->nid : FALSE);
+}
+
+/**
+ * Processes variables for node.tpl.php
+ *
+ * Most themes utilize their own copy of node.tpl.php. The default is located
+ * inside "modules/node/node.tpl.php". Look in there for the full list of
+ * variables.
+ *
+ * The $variables array contains the following arguments:
+ * - $node
+ * - $view_mode
+ * - $page
+ *
+ * @see node.tpl.php
+ */
+function template_preprocess_node(&$variables) {
+ $variables['view_mode'] = $variables['elements']['#view_mode'];
+ // Provide a distinct $teaser boolean.
+ $variables['teaser'] = $variables['view_mode'] == 'teaser';
+ $variables['node'] = $variables['elements']['#node'];
+ $node = $variables['node'];
+
+ $variables['date'] = format_date($node->created);
+ $variables['name'] = theme('username', array('account' => $node));
+
+ $uri = entity_uri('node', $node);
+ $variables['node_url'] = url($uri['path'], $uri['options']);
+ $variables['title'] = check_plain($node->title);
+ $variables['page'] = $variables['view_mode'] == 'full' && node_is_page($node);
+
+ // Flatten the node object's member fields.
+ $variables = array_merge((array) $node, $variables);
+
+ // Helpful $content variable for templates.
+ $variables += array('content' => array());
+ foreach (element_children($variables['elements']) as $key) {
+ $variables['content'][$key] = $variables['elements'][$key];
+ }
+
+ // Make the field variables available with the appropriate language.
+ field_attach_preprocess('node', $node, $variables['content'], $variables);
+
+ // Display post information only on certain node types.
+ if (variable_get('node_submitted_' . $node->type, TRUE)) {
+ $variables['display_submitted'] = TRUE;
+ $variables['submitted'] = t('Submitted by !username on !datetime', array('!username' => $variables['name'], '!datetime' => $variables['date']));
+ $variables['user_picture'] = theme_get_setting('toggle_node_user_picture') ? theme('user_picture', array('account' => $node)) : '';
+ }
+ else {
+ $variables['display_submitted'] = FALSE;
+ $variables['submitted'] = '';
+ $variables['user_picture'] = '';
+ }
+
+ // Gather node classes.
+ $variables['classes_array'][] = drupal_html_class('node-' . $node->type);
+ if ($variables['promote']) {
+ $variables['classes_array'][] = 'node-promoted';
+ }
+ if ($variables['sticky']) {
+ $variables['classes_array'][] = 'node-sticky';
+ }
+ if (!$variables['status']) {
+ $variables['classes_array'][] = 'node-unpublished';
+ }
+ if ($variables['teaser']) {
+ $variables['classes_array'][] = 'node-teaser';
+ }
+ if (isset($variables['preview'])) {
+ $variables['classes_array'][] = 'node-preview';
+ }
+
+ // Clean up name so there are no underscores.
+ $variables['theme_hook_suggestions'][] = 'node__' . $node->type;
+ $variables['theme_hook_suggestions'][] = 'node__' . $node->nid;
+}
+
+/**
+ * Implements hook_permission().
+ */
+function node_permission() {
+ $perms = array(
+ 'bypass node access' => array(
+ 'title' => t('Bypass content access control'),
+ 'description' => t('View, edit and delete all content regardless of permission restrictions.'),
+ 'restrict access' => TRUE,
+ ),
+ 'administer content types' => array(
+ 'title' => t('Administer content types'),
+ 'restrict access' => TRUE,
+ ),
+ 'administer nodes' => array(
+ 'title' => t('Administer content'),
+ 'restrict access' => TRUE,
+ ),
+ 'access content overview' => array(
+ 'title' => t('Access the content overview page'),
+ 'description' => user_access('access content overview')
+ ? t('Get an overview of <a href="@url">all content</a>.', array('@url' => url('admin/content')))
+ : t('Get an overview of all content.'),
+ ),
+ 'access content' => array(
+ 'title' => t('View published content'),
+ ),
+ 'view own unpublished content' => array(
+ 'title' => t('View own unpublished content'),
+ ),
+ 'view revisions' => array(
+ 'title' => t('View content revisions'),
+ ),
+ 'revert revisions' => array(
+ 'title' => t('Revert content revisions'),
+ ),
+ 'delete revisions' => array(
+ 'title' => t('Delete content revisions'),
+ ),
+ );
+
+ // Generate standard node permissions for all applicable node types.
+ foreach (node_permissions_get_configured_types() as $type) {
+ $perms += node_list_permissions($type);
+ }
+
+ return $perms;
+}
+
+/**
+ * Gathers the rankings from the the hook_ranking() implementations.
+ *
+ * @param $query
+ * A query object that has been extended with the Search DB Extender.
+ */
+function _node_rankings(SelectQueryExtender $query) {
+ if ($ranking = module_invoke_all('ranking')) {
+ $tables = &$query->getTables();
+ foreach ($ranking as $rank => $values) {
+ if ($node_rank = variable_get('node_rank_' . $rank, 0)) {
+ // If the table defined in the ranking isn't already joined, then add it.
+ if (isset($values['join']) && !isset($tables[$values['join']['alias']])) {
+ $query->addJoin($values['join']['type'], $values['join']['table'], $values['join']['alias'], $values['join']['on']);
+ }
+ $arguments = isset($values['arguments']) ? $values['arguments'] : array();
+ $query->addScore($values['score'], $arguments, $node_rank);
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_search_info().
+ */
+function node_search_info() {
+ return array(
+ 'title' => 'Content',
+ 'path' => 'node',
+ );
+}
+
+/**
+ * Implements hook_search_access().
+ */
+function node_search_access() {
+ return user_access('access content');
+}
+
+/**
+ * Implements hook_search_reset().
+ */
+function node_search_reset() {
+ db_update('search_dataset')
+ ->fields(array('reindex' => REQUEST_TIME))
+ ->condition('type', 'node')
+ ->execute();
+}
+
+/**
+ * Implements hook_search_status().
+ */
+function node_search_status() {
+ $total = db_query('SELECT COUNT(*) FROM {node}')->fetchField();
+ $remaining = db_query("SELECT COUNT(*) FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE d.sid IS NULL OR d.reindex <> 0")->fetchField();
+ return array('remaining' => $remaining, 'total' => $total);
+}
+
+/**
+ * Implements hook_search_admin().
+ */
+function node_search_admin() {
+ // Output form for defining rank factor weights.
+ $form['content_ranking'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Content ranking'),
+ );
+ $form['content_ranking']['#theme'] = 'node_search_admin';
+ $form['content_ranking']['info'] = array(
+ '#value' => '<em>' . t('The following numbers control which properties the content search should favor when ordering the results. Higher numbers mean more influence, zero means the property is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') . '</em>'
+ );
+
+ // Note: reversed to reflect that higher number = higher ranking.
+ $options = drupal_map_assoc(range(0, 10));
+ foreach (module_invoke_all('ranking') as $var => $values) {
+ $form['content_ranking']['factors']['node_rank_' . $var] = array(
+ '#title' => $values['title'],
+ '#type' => 'select',
+ '#options' => $options,
+ '#default_value' => variable_get('node_rank_' . $var, 0),
+ );
+ }
+ return $form;
+}
+
+/**
+ * Implements hook_search_execute().
+ */
+function node_search_execute($keys = NULL, $conditions = NULL) {
+ // Build matching conditions
+ $query = db_select('search_index', 'i', array('target' => 'slave'))->extend('SearchQuery')->extend('PagerDefault');
+ $query->join('node', 'n', 'n.nid = i.sid');
+ $query
+ ->condition('n.status', 1)
+ ->addTag('node_access')
+ ->searchExpression($keys, 'node');
+
+ // Insert special keywords.
+ $query->setOption('type', 'n.type');
+ $query->setOption('language', 'n.language');
+ if ($query->setOption('term', 'ti.tid')) {
+ $query->join('taxonomy_index', 'ti', 'n.nid = ti.nid');
+ }
+ // Only continue if the first pass query matches.
+ if (!$query->executeFirstPass()) {
+ return array();
+ }
+
+ // Add the ranking expressions.
+ _node_rankings($query);
+
+ // Load results.
+ $find = $query
+ ->limit(10)
+ ->execute();
+ $results = array();
+ foreach ($find as $item) {
+ // Render the node.
+ $node = node_load($item->sid);
+ $build = node_view($node, 'search_result');
+ unset($build['#theme']);
+ $node->rendered = drupal_render($build);
+
+ // Fetch comments for snippet.
+ $node->rendered .= ' ' . module_invoke('comment', 'node_update_index', $node);
+
+ $extra = module_invoke_all('node_search_result', $node);
+
+ $uri = entity_uri('node', $node);
+ $results[] = array(
+ 'link' => url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE))),
+ 'type' => check_plain(node_type_get_name($node)),
+ 'title' => $node->title,
+ 'user' => theme('username', array('account' => $node)),
+ 'date' => $node->changed,
+ 'node' => $node,
+ 'extra' => $extra,
+ 'score' => $item->calculated_score,
+ 'snippet' => search_excerpt($keys, $node->rendered),
+ 'language' => entity_language('node', $node),
+ );
+ }
+ return $results;
+}
+
+/**
+ * Implements hook_ranking().
+ */
+function node_ranking() {
+ // Create the ranking array and add the basic ranking options.
+ $ranking = array(
+ 'relevance' => array(
+ 'title' => t('Keyword relevance'),
+ // Average relevance values hover around 0.15
+ 'score' => 'i.relevance',
+ ),
+ 'sticky' => array(
+ 'title' => t('Content is sticky at top of lists'),
+ // The sticky flag is either 0 or 1, which is automatically normalized.
+ 'score' => 'n.sticky',
+ ),
+ 'promote' => array(
+ 'title' => t('Content is promoted to the front page'),
+ // The promote flag is either 0 or 1, which is automatically normalized.
+ 'score' => 'n.promote',
+ ),
+ );
+
+ // Add relevance based on creation or changed date.
+ if ($node_cron_last = variable_get('node_cron_last', 0)) {
+ $ranking['recent'] = array(
+ 'title' => t('Recently posted'),
+ // Exponential decay with half-life of 6 months, starting at last indexed node
+ 'score' => 'POW(2.0, (GREATEST(n.created, n.changed) - :node_cron_last) * 6.43e-8)',
+ 'arguments' => array(':node_cron_last' => $node_cron_last),
+ );
+ }
+ return $ranking;
+}
+
+/**
+ * Implements hook_user_cancel().
+ */
+function node_user_cancel($edit, $account, $method) {
+ switch ($method) {
+ case 'user_cancel_block_unpublish':
+ // Unpublish nodes (current revisions).
+ module_load_include('inc', 'node', 'node.admin');
+ $nodes = db_select('node', 'n')
+ ->fields('n', array('nid'))
+ ->condition('uid', $account->uid)
+ ->execute()
+ ->fetchCol();
+ node_mass_update($nodes, array('status' => 0));
+ break;
+
+ case 'user_cancel_reassign':
+ // Anonymize nodes (current revisions).
+ module_load_include('inc', 'node', 'node.admin');
+ $nodes = db_select('node', 'n')
+ ->fields('n', array('nid'))
+ ->condition('uid', $account->uid)
+ ->execute()
+ ->fetchCol();
+ node_mass_update($nodes, array('uid' => 0));
+ // Anonymize old revisions.
+ db_update('node_revision')
+ ->fields(array('uid' => 0))
+ ->condition('uid', $account->uid)
+ ->execute();
+ // Clean history.
+ db_delete('history')
+ ->condition('uid', $account->uid)
+ ->execute();
+ break;
+ }
+}
+
+/**
+ * Implements hook_user_delete().
+ */
+function node_user_delete($account) {
+ // Delete nodes (current revisions).
+ // @todo Introduce node_mass_delete() or make node_mass_update() more flexible.
+ $nodes = db_select('node', 'n')
+ ->fields('n', array('nid'))
+ ->condition('uid', $account->uid)
+ ->execute()
+ ->fetchCol();
+ node_delete_multiple($nodes);
+ // Delete old revisions.
+ $revisions = db_query('SELECT vid FROM {node_revision} WHERE uid = :uid', array(':uid' => $account->uid))->fetchCol();
+ foreach ($revisions as $revision) {
+ node_revision_delete($revision);
+ }
+ // Clean history.
+ db_delete('history')
+ ->condition('uid', $account->uid)
+ ->execute();
+}
+
+/**
+ * Returns HTML for the content ranking part of the search settings admin page.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - form: A render element representing the form.
+ *
+ * @see node_search_admin()
+ * @ingroup themeable
+ */
+function theme_node_search_admin($variables) {
+ $form = $variables['form'];
+
+ $output = drupal_render($form['info']);
+
+ $header = array(t('Factor'), t('Weight'));
+ foreach (element_children($form['factors']) as $key) {
+ $row = array();
+ $row[] = $form['factors'][$key]['#title'];
+ $form['factors'][$key]['#title_display'] = 'invisible';
+ $row[] = drupal_render($form['factors'][$key]);
+ $rows[] = $row;
+ }
+ $output .= theme('table', array('header' => $header, 'rows' => $rows));
+
+ $output .= drupal_render_children($form);
+ return $output;
+}
+
+/**
+ * Access callback: Checks node revision access.
+ *
+ * @param $node
+ * The node to check.
+ * @param $op
+ * (optional) The specific operation being checked. Defaults to 'view.'
+ * @param object $account
+ * (optional) A user object representing the user for whom the operation is
+ * to be performed. Determines access for a user other than the current user.
+ *
+ * @return
+ * TRUE if the operation may be performed, FALSE otherwise.
+ *
+ * @see node_menu()
+ */
+function _node_revision_access($node, $op = 'view', $account = NULL) {
+ $access = &drupal_static(__FUNCTION__, array());
+
+ $map = array(
+ 'view' => 'view revisions',
+ 'update' => 'revert revisions',
+ 'delete' => 'delete revisions',
+ );
+
+ if (!$node || !isset($map[$op])) {
+ // If there was no node to check against, or the $op was not one of the
+ // supported ones, we return access denied.
+ return FALSE;
+ }
+
+ if (!isset($account)) {
+ $account = $GLOBALS['user'];
+ }
+
+ // Statically cache access by revision ID, user account ID, and operation.
+ $cid = $node->vid . ':' . $account->uid . ':' . $op;
+
+ if (!isset($access[$cid])) {
+ // Perform basic permission checks first.
+ if (!user_access($map[$op], $account) && !user_access('administer nodes', $account)) {
+ return $access[$cid] = FALSE;
+ }
+
+ $node_current_revision = node_load($node->nid);
+ $is_current_revision = $node_current_revision->vid == $node->vid;
+
+ // There should be at least two revisions. If the vid of the given node and
+ // the vid of the current revision differ, then we already have two
+ // different revisions so there is no need for a separate database check.
+ // Also, if you try to revert to or delete the current revision, that's not
+ // good.
+ if ($is_current_revision && (db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid', array(':nid' => $node->nid))->fetchField() == 1 || $op == 'update' || $op == 'delete')) {
+ $access[$cid] = FALSE;
+ }
+ elseif (user_access('administer nodes', $account)) {
+ $access[$cid] = TRUE;
+ }
+ else {
+ // First check the access to the current revision and finally, if the node
+ // passed in is not the current revision then access to that, too.
+ $access[$cid] = node_access($op, $node_current_revision, $account) && ($is_current_revision || node_access($op, $node, $account));
+ }
+ }
+
+ return $access[$cid];
+}
+
+/**
+ * Access callback: Checks whether the user has permission to add a node.
+ *
+ * @return
+ * TRUE if the user has add permission, otherwise FALSE.
+ *
+ * @see node_menu()
+ */
+function _node_add_access() {
+ $types = node_type_get_types();
+ foreach ($types as $type) {
+ if (node_hook($type->type, 'form') && node_access('create', $type->type)) {
+ return TRUE;
+ }
+ }
+ if (user_access('administer content types')) {
+ // There are no content types defined that the user has permission to create,
+ // but the user does have the permission to administer the content types, so
+ // grant them access to the page anyway.
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Implements hook_menu().
+ */
+function node_menu() {
+ $items['admin/content'] = array(
+ 'title' => 'Content',
+ 'description' => 'Find and manage content.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('node_admin_content'),
+ 'access arguments' => array('access content overview'),
+ 'weight' => -10,
+ 'file' => 'node.admin.inc',
+ );
+ $items['admin/content/node'] = array(
+ 'title' => 'Content',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
+
+ $items['admin/reports/status/rebuild'] = array(
+ 'title' => 'Rebuild permissions',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('node_configure_rebuild_confirm'),
+ // Any user than can potentially trigger a node_access_needs_rebuild(TRUE)
+ // has to be allowed access to the 'node access rebuild' confirm form.
+ 'access arguments' => array('access administration pages'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'node.admin.inc',
+ );
+
+ $items['admin/structure/types'] = array(
+ 'title' => 'Content types',
+ 'description' => 'Manage content types, including default status, front page promotion, comment settings, etc.',
+ 'page callback' => 'node_overview_types',
+ 'access arguments' => array('administer content types'),
+ 'file' => 'content_types.inc',
+ );
+ $items['admin/structure/types/list'] = array(
+ 'title' => 'List',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
+ $items['admin/structure/types/add'] = array(
+ 'title' => 'Add content type',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('node_type_form'),
+ 'access arguments' => array('administer content types'),
+ 'type' => MENU_LOCAL_ACTION,
+ 'file' => 'content_types.inc',
+ );
+ $items['admin/structure/types/manage/%node_type'] = array(
+ 'title' => 'Edit content type',
+ 'title callback' => 'node_type_page_title',
+ 'title arguments' => array(4),
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('node_type_form', 4),
+ 'access arguments' => array('administer content types'),
+ 'file' => 'content_types.inc',
+ );
+ $items['admin/structure/types/manage/%node_type/edit'] = array(
+ 'title' => 'Edit',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ );
+ $items['admin/structure/types/manage/%node_type/delete'] = array(
+ 'title' => 'Delete',
+ 'page arguments' => array('node_type_delete_confirm', 4),
+ 'access arguments' => array('administer content types'),
+ 'file' => 'content_types.inc',
+ );
+
+ $items['node'] = array(
+ 'page callback' => 'node_page_default',
+ 'access arguments' => array('access content'),
+ 'menu_name' => 'navigation',
+ 'type' => MENU_CALLBACK,
+ );
+ $items['node/add'] = array(
+ 'title' => 'Add content',
+ 'page callback' => 'node_add_page',
+ 'access callback' => '_node_add_access',
+ 'file' => 'node.pages.inc',
+ );
+ $items['rss.xml'] = array(
+ 'title' => 'RSS feed',
+ 'page callback' => 'node_feed',
+ 'access arguments' => array('access content'),
+ 'type' => MENU_CALLBACK,
+ // Pass a FALSE and array argument to ensure that additional path components
+ // are not passed to node_feed().
+ 'page arguments' => array(FALSE, array()),
+ );
+ // @todo Remove this loop when we have a 'description callback' property.
+ // Reset internal static cache of _node_types_build(), forces to rebuild the
+ // node type information.
+ node_type_cache_reset();
+ foreach (node_type_get_types() as $type) {
+ $type_url_str = str_replace('_', '-', $type->type);
+ $items['node/add/' . $type_url_str] = array(
+ 'title' => $type->name,
+ 'title callback' => 'check_plain',
+ 'page callback' => 'node_add',
+ 'page arguments' => array($type->type),
+ 'access callback' => 'node_access',
+ 'access arguments' => array('create', $type->type),
+ 'description' => $type->description,
+ 'file' => 'node.pages.inc',
+ );
+ }
+ $items['node/%node'] = array(
+ 'title callback' => 'node_page_title',
+ 'title arguments' => array(1),
+ // The page callback also invokes drupal_set_title() in case
+ // the menu router's title is overridden by a menu link.
+ 'page callback' => 'node_page_view',
+ 'page arguments' => array(1),
+ 'access callback' => 'node_access',
+ 'access arguments' => array('view', 1),
+ );
+ $items['node/%node/view'] = array(
+ 'title' => 'View',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
+ $items['node/%node/edit'] = array(
+ 'title' => 'Edit',
+ 'page callback' => 'node_page_edit',
+ 'page arguments' => array(1),
+ 'access callback' => 'node_access',
+ 'access arguments' => array('update', 1),
+ 'weight' => 0,
+ 'type' => MENU_LOCAL_TASK,
+ 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
+ 'file' => 'node.pages.inc',
+ );
+ $items['node/%node/delete'] = array(
+ 'title' => 'Delete',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('node_delete_confirm', 1),
+ 'access callback' => 'node_access',
+ 'access arguments' => array('delete', 1),
+ 'weight' => 1,
+ 'type' => MENU_LOCAL_TASK,
+ 'context' => MENU_CONTEXT_INLINE,
+ 'file' => 'node.pages.inc',
+ );
+ $items['node/%node/revisions'] = array(
+ 'title' => 'Revisions',
+ 'page callback' => 'node_revision_overview',
+ 'page arguments' => array(1),
+ 'access callback' => '_node_revision_access',
+ 'access arguments' => array(1),
+ 'weight' => 2,
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => 'node.pages.inc',
+ );
+ $items['node/%node/revisions/%/view'] = array(
+ 'title' => 'Revisions',
+ 'load arguments' => array(3),
+ 'page callback' => 'node_show',
+ 'page arguments' => array(1, TRUE),
+ 'access callback' => '_node_revision_access',
+ 'access arguments' => array(1),
+ );
+ $items['node/%node/revisions/%/revert'] = array(
+ 'title' => 'Revert to earlier revision',
+ 'load arguments' => array(3),
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('node_revision_revert_confirm', 1),
+ 'access callback' => '_node_revision_access',
+ 'access arguments' => array(1, 'update'),
+ 'file' => 'node.pages.inc',
+ );
+ $items['node/%node/revisions/%/delete'] = array(
+ 'title' => 'Delete earlier revision',
+ 'load arguments' => array(3),
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('node_revision_delete_confirm', 1),
+ 'access callback' => '_node_revision_access',
+ 'access arguments' => array(1, 'delete'),
+ 'file' => 'node.pages.inc',
+ );
+ return $items;
+}
+
+/**
+ * Implements hook_menu_local_tasks_alter().
+ */
+function node_menu_local_tasks_alter(&$data, $router_item, $root_path) {
+ // Add action link to 'node/add' on 'admin/content' page.
+ if ($root_path == 'admin/content') {
+ $item = menu_get_item('node/add');
+ if ($item['access']) {
+ $data['actions']['output'][] = array(
+ '#theme' => 'menu_local_action',
+ '#link' => $item,
+ );
+ }
+ }
+}
+
+/**
+ * Title callback: Returns the unsanitized title of the node type edit form.
+ *
+ * @param $type
+ * The node type object.
+ *
+ * @return string
+ * An unsanitized string that is the title of the node type edit form.
+ *
+ * @see node_menu()
+ */
+function node_type_page_title($type) {
+ return $type->name;
+}
+
+/**
+ * Title callback: Returns the title of the node.
+ *
+ * @param $node
+ * The node object.
+ *
+ * @return
+ * An unsanitized string that is the title of the node.
+ *
+ * @see node_menu()
+ */
+function node_page_title($node) {
+ return $node->title;
+}
+
+/**
+ * Finds the last time a node was changed.
+ *
+ * @param $nid
+ * The ID of a node.
+ *
+ * @return
+ * A unix timestamp indicating the last time the node was changed.
+ */
+function node_last_changed($nid) {
+ return db_query('SELECT changed FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetch()->changed;
+}
+
+/**
+ * Returns a list of all the existing revision numbers.
+ *
+ * @param $node
+ * The node object.
+ *
+ * @return
+ * An associative array keyed by node revision number.
+ */
+function node_revision_list($node) {
+ $revisions = array();
+ $result = db_query('SELECT r.vid, r.title, r.log, r.uid, n.vid AS current_vid, r.timestamp, u.name FROM {node_revision} r LEFT JOIN {node} n ON n.vid = r.vid INNER JOIN {users} u ON u.uid = r.uid WHERE r.nid = :nid ORDER BY r.vid DESC', array(':nid' => $node->nid));
+ foreach ($result as $revision) {
+ $revisions[$revision->vid] = $revision;
+ }
+
+ return $revisions;
+}
+
+/**
+ * Implements hook_block_info().
+ */
+function node_block_info() {
+ $blocks['syndicate']['info'] = t('Syndicate');
+ // Not worth caching.
+ $blocks['syndicate']['cache'] = DRUPAL_NO_CACHE;
+
+ $blocks['recent']['info'] = t('Recent content');
+ $blocks['recent']['properties']['administrative'] = TRUE;
+
+ return $blocks;
+}
+
+/**
+ * Implements hook_block_view().
+ */
+function node_block_view($delta = '') {
+ $block = array();
+
+ switch ($delta) {
+ case 'syndicate':
+ $block['subject'] = t('Syndicate');
+ $block['content'] = theme('feed_icon', array('url' => 'rss.xml', 'title' => t('Syndicate')));
+ break;
+
+ case 'recent':
+ if (user_access('access content')) {
+ $block['subject'] = t('Recent content');
+ if ($nodes = node_get_recent(variable_get('node_recent_block_count', 10))) {
+ $block['content'] = theme('node_recent_block', array(
+ 'nodes' => $nodes,
+ ));
+ } else {
+ $block['content'] = t('No content available.');
+ }
+ }
+ break;
+ }
+ return $block;
+}
+
+/**
+ * Implements hook_block_configure().
+ */
+function node_block_configure($delta = '') {
+ $form = array();
+ if ($delta == 'recent') {
+ $form['node_recent_block_count'] = array(
+ '#type' => 'select',
+ '#title' => t('Number of recent content items to display'),
+ '#default_value' => variable_get('node_recent_block_count', 10),
+ '#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 30)),
+ );
+ }
+ return $form;
+}
+
+/**
+ * Implements hook_block_save().
+ */
+function node_block_save($delta = '', $edit = array()) {
+ if ($delta == 'recent') {
+ variable_set('node_recent_block_count', $edit['node_recent_block_count']);
+ }
+}
+
+/**
+ * Finds the most recently changed nodes that are available to the current user.
+ *
+ * @param $number
+ * (optional) The maximum number of nodes to find. Defaults to 10.
+ *
+ * @return
+ * An array of node entities or an empty array if there are no recent nodes
+ * visible to the current user.
+ */
+function node_get_recent($number = 10) {
+ $query = db_select('node', 'n');
+
+ if (!user_access('bypass node access')) {
+ // If the user is able to view their own unpublished nodes, allow them to
+ // see these in addition to published nodes. Check that they actually have
+ // some unpublished nodes to view before adding the condition.
+ if (user_access('view own unpublished content') && $own_unpublished = db_query('SELECT nid FROM {node} WHERE uid = :uid AND status = :status', array(':uid' => $GLOBALS['user']->uid, ':status' => NODE_NOT_PUBLISHED))->fetchCol()) {
+ $query->condition(db_or()
+ ->condition('n.status', NODE_PUBLISHED)
+ ->condition('n.nid', $own_unpublished, 'IN')
+ );
+ }
+ else {
+ // If not, restrict the query to published nodes.
+ $query->condition('n.status', NODE_PUBLISHED);
+ }
+ }
+ $nids = $query
+ ->fields('n', array('nid'))
+ ->orderBy('n.changed', 'DESC')
+ ->range(0, $number)
+ ->addTag('node_access')
+ ->execute()
+ ->fetchCol();
+
+ $nodes = node_load_multiple($nids);
+
+ return $nodes ? $nodes : array();
+}
+
+/**
+ * Returns HTML for a list of recent content.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - nodes: An array of recent node objects.
+ *
+ * @ingroup themeable
+ */
+function theme_node_recent_block($variables) {
+ $rows = array();
+ $output = '';
+
+ $l_options = array('query' => drupal_get_destination());
+ foreach ($variables['nodes'] as $node) {
+ $row = array();
+ $row[] = array(
+ 'data' => theme('node_recent_content', array('node' => $node)),
+ 'class' => 'title-author',
+ );
+ $row[] = array(
+ 'data' => node_access('update', $node) ? l(t('edit'), 'node/' . $node->nid . '/edit', $l_options) : '',
+ 'class' => 'edit',
+ );
+ $row[] = array(
+ 'data' => node_access('delete', $node) ? l(t('delete'), 'node/' . $node->nid . '/delete', $l_options) : '',
+ 'class' => 'delete',
+ );
+ $rows[] = $row;
+ }
+
+ if ($rows) {
+ $output = theme('table', array('rows' => $rows));
+ if (user_access('access content overview')) {
+ $output .= theme('more_link', array('url' => 'admin/content', 'title' => t('Show more content')));
+ }
+ }
+
+ return $output;
+}
+
+/**
+ * Returns HTML for a recent node to be displayed in the recent content block.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - node: A node object.
+ *
+ * @ingroup themeable
+ */
+function theme_node_recent_content($variables) {
+ $node = $variables['node'];
+
+ $output = '<div class="node-title">';
+ $output .= l($node->title, 'node/' . $node->nid);
+ $output .= theme('mark', array('type' => node_mark($node->nid, $node->changed)));
+ $output .= '</div><div class="node-author">';
+ $output .= theme('username', array('account' => user_load($node->uid)));
+ $output .= '</div>';
+
+ return $output;
+}
+
+/**
+ * Implements hook_form_FORMID_alter().
+ *
+ * Adds node-type specific visibility options to add block form.
+ *
+ * @see block_add_block_form()
+ */
+function node_form_block_add_block_form_alter(&$form, &$form_state) {
+ node_form_block_admin_configure_alter($form, $form_state);
+}
+
+/**
+ * Implements hook_form_FORMID_alter().
+ *
+ * Adds node-type specific visibility options to block configuration form.
+ *
+ * @see block_admin_configure()
+ */
+function node_form_block_admin_configure_alter(&$form, &$form_state) {
+ $default_type_options = db_query("SELECT type FROM {block_node_type} WHERE module = :module AND delta = :delta", array(
+ ':module' => $form['module']['#value'],
+ ':delta' => $form['delta']['#value'],
+ ))->fetchCol();
+ $form['visibility']['node_type'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Content types'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#group' => 'visibility',
+ '#weight' => 5,
+ );
+ $form['visibility']['node_type']['types'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Show block for specific content types'),
+ '#default_value' => $default_type_options,
+ '#options' => node_type_get_names(),
+ '#description' => t('Show this block only on pages that display content of the given type(s). If you select no types, there will be no type-specific limitation.'),
+ );
+ $form['#submit'][] = 'node_form_block_admin_configure_submit';
+}
+
+/**
+ * Form submission handler for node_form_block_admin_configure_alter().
+ *
+ * @see node_form_block_admin_configure_alter()
+ */
+function node_form_block_admin_configure_submit($form, &$form_state) {
+ db_delete('block_node_type')
+ ->condition('module', $form_state['values']['module'])
+ ->condition('delta', $form_state['values']['delta'])
+ ->execute();
+ $query = db_insert('block_node_type')->fields(array('type', 'module', 'delta'));
+ foreach (array_filter($form_state['values']['types']) as $type) {
+ $query->values(array(
+ 'type' => $type,
+ 'module' => $form_state['values']['module'],
+ 'delta' => $form_state['values']['delta'],
+ ));
+ }
+ $query->execute();
+}
+
+/**
+ * Implements hook_form_FORMID_alter().
+ *
+ * Adds node specific submit handler to delete custom block form.
+ *
+ * @see block_custom_block_delete()
+ */
+function node_form_block_custom_block_delete_alter(&$form, &$form_state) {
+ $form['#submit'][] = 'node_form_block_custom_block_delete_submit';
+}
+
+/**
+ * Form submission handler for node_form_block_custom_block_delete_alter().
+ *
+ * @see node_form_block_custom_block_delete_alter()
+ */
+function node_form_block_custom_block_delete_submit($form, &$form_state) {
+ db_delete('block_node_type')
+ ->condition('module', 'block')
+ ->condition('delta', $form_state['values']['bid'])
+ ->execute();
+}
+
+/**
+ * Implements hook_modules_uninstalled().
+ *
+ * Cleanup {block_node_type} table from modules' blocks.
+ */
+function node_modules_uninstalled($modules) {
+ db_delete('block_node_type')
+ ->condition('module', $modules, 'IN')
+ ->execute();
+}
+
+/**
+ * Implements hook_block_list_alter().
+ *
+ * Check the content type specific visibilty settings. Remove the block if the
+ * visibility conditions are not met.
+ */
+function node_block_list_alter(&$blocks) {
+ global $theme_key;
+
+ // Build an array of node types for each block.
+ $block_node_types = array();
+ $result = db_query('SELECT module, delta, type FROM {block_node_type}');
+ foreach ($result as $record) {
+ $block_node_types[$record->module][$record->delta][$record->type] = TRUE;
+ }
+
+ $node = menu_get_object();
+ $node_types = node_type_get_types();
+ if (arg(0) == 'node' && arg(1) == 'add' && arg(2)) {
+ $node_add_arg = strtr(arg(2), '-', '_');
+ }
+ foreach ($blocks as $key => $block) {
+ if (!isset($block->theme) || !isset($block->status) || $block->theme != $theme_key || $block->status != 1) {
+ // This block was added by a contrib module, leave it in the list.
+ continue;
+ }
+
+ // If a block has no node types associated, it is displayed for every type.
+ // For blocks with node types associated, if the node type does not match
+ // the settings from this block, remove it from the block list.
+ if (isset($block_node_types[$block->module][$block->delta])) {
+ if (!empty($node)) {
+ // This is a node or node edit page.
+ if (!isset($block_node_types[$block->module][$block->delta][$node->type])) {
+ // This block should not be displayed for this node type.
+ unset($blocks[$key]);
+ continue;
+ }
+ }
+ elseif (isset($node_add_arg) && isset($node_types[$node_add_arg])) {
+ // This is a node creation page
+ if (!isset($block_node_types[$block->module][$block->delta][$node_add_arg])) {
+ // This block should not be displayed for this node type.
+ unset($blocks[$key]);
+ continue;
+ }
+ }
+ else {
+ // This is not a node page, remove the block.
+ unset($blocks[$key]);
+ continue;
+ }
+ }
+ }
+}
+
+/**
+ * Generates and prints an RSS feed.
+ *
+ * Generates an RSS feed from an array of node IDs, and prints it with an HTTP
+ * header, with Content Type set to RSS/XML.
+ *
+ * @param $nids
+ * An array of node IDs (nid). Defaults to FALSE so empty feeds can be
+ * generated with passing an empty array, if no items are to be added
+ * to the feed.
+ * @param $channel
+ * An associative array containing title, link, description and other keys,
+ * to be parsed by format_rss_channel() and format_xml_elements().
+ * A list of channel elements can be found at the
+ * @link http://cyber.law.harvard.edu/rss/rss.html RSS 2.0 Specification. @endlink
+ * The link should be an absolute URL.
+ */
+function node_feed($nids = FALSE, $channel = array()) {
+ global $base_url, $language_content;
+
+ if ($nids === FALSE) {
+ $nids = db_select('node', 'n')
+ ->fields('n', array('nid', 'created'))
+ ->condition('n.promote', 1)
+ ->condition('n.status', 1)
+ ->orderBy('n.created', 'DESC')
+ ->range(0, variable_get('feed_default_items', 10))
+ ->addTag('node_access')
+ ->execute()
+ ->fetchCol();
+ }
+
+ $item_length = variable_get('feed_item_length', 'fulltext');
+ $namespaces = array('xmlns:dc' => 'http://purl.org/dc/elements/1.1/');
+
+ // Load all nodes to be rendered.
+ $nodes = node_load_multiple($nids);
+ $items = '';
+ foreach ($nodes as $node) {
+ $item_text = '';
+
+ $node->link = url("node/$node->nid", array('absolute' => TRUE));
+ $node->rss_namespaces = array();
+ $node->rss_elements = array(
+ array('key' => 'pubDate', 'value' => gmdate('r', $node->created)),
+ array('key' => 'dc:creator', 'value' => $node->name),
+ array('key' => 'guid', 'value' => $node->nid . ' at ' . $base_url, 'attributes' => array('isPermaLink' => 'false'))
+ );
+
+ // The node gets built and modules add to or modify $node->rss_elements
+ // and $node->rss_namespaces.
+ $build = node_view($node, 'rss');
+ unset($build['#theme']);
+
+ if (!empty($node->rss_namespaces)) {
+ $namespaces = array_merge($namespaces, $node->rss_namespaces);
+ }
+
+ if ($item_length != 'title') {
+ // We render node contents and force links to be last.
+ $build['links']['#weight'] = 1000;
+ $item_text .= drupal_render($build);
+ }
+
+ $items .= format_rss_item($node->title, $node->link, $item_text, $node->rss_elements);
+ }
+
+ $channel_defaults = array(
+ 'version' => '2.0',
+ 'title' => variable_get('site_name', 'Drupal'),
+ 'link' => $base_url,
+ 'description' => variable_get('feed_description', ''),
+ 'language' => $language_content->language
+ );
+ $channel_extras = array_diff_key($channel, $channel_defaults);
+ $channel = array_merge($channel_defaults, $channel);
+
+ $output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
+ $output .= "<rss version=\"" . $channel["version"] . "\" xml:base=\"" . $base_url . "\" " . drupal_attributes($namespaces) . ">\n";
+ $output .= format_rss_channel($channel['title'], $channel['link'], $channel['description'], $items, $channel['language'], $channel_extras);
+ $output .= "</rss>\n";
+
+ drupal_add_http_header('Content-Type', 'application/rss+xml; charset=utf-8');
+ print $output;
+}
+
+/**
+ * Constructs a drupal_render() style array from an array of loaded nodes.
+ *
+ * @param $nodes
+ * An array of nodes as returned by node_load_multiple().
+ * @param $view_mode
+ * View mode, e.g. 'full', 'teaser'...
+ * @param $weight
+ * An integer representing the weight of the first node in the list.
+ * @param $langcode
+ * (optional) A language code to use for rendering. Defaults to NULL which is
+ * the global content language of the current request.
+ *
+ * @return
+ * An array in the format expected by drupal_render().
+ */
+function node_view_multiple($nodes, $view_mode = 'teaser', $weight = 0, $langcode = NULL) {
+ field_attach_prepare_view('node', $nodes, $view_mode, $langcode);
+ entity_prepare_view('node', $nodes, $langcode);
+ $build = array();
+ foreach ($nodes as $node) {
+ $build['nodes'][$node->nid] = node_view($node, $view_mode, $langcode);
+ $build['nodes'][$node->nid]['#weight'] = $weight;
+ $weight++;
+ }
+ $build['nodes']['#sorted'] = TRUE;
+ return $build;
+}
+
+/**
+ * Menu callback: Generates a listing of promoted nodes.
+ *
+ * @return array
+ * An array in the format expected by drupal_render().
+ *
+ * @see node_menu()
+ */
+function node_page_default() {
+ $select = db_select('node', 'n')
+ ->fields('n', array('nid', 'sticky', 'created'))
+ ->condition('n.promote', 1)
+ ->condition('n.status', 1)
+ ->orderBy('n.sticky', 'DESC')
+ ->orderBy('n.created', 'DESC')
+ ->extend('PagerDefault')
+ ->limit(variable_get('default_nodes_main', 10))
+ ->addTag('node_access');
+
+ $nids = $select->execute()->fetchCol();
+
+ if (!empty($nids)) {
+ $nodes = node_load_multiple($nids);
+ $build = node_view_multiple($nodes);
+
+ // 'rss.xml' is a path, not a file, registered in node_menu().
+ drupal_add_feed('rss.xml', variable_get('site_name', 'Drupal') . ' ' . t('RSS'));
+ $build['pager'] = array(
+ '#theme' => 'pager',
+ '#weight' => 5,
+ );
+ drupal_set_title('');
+ }
+ else {
+ drupal_set_title(t('Welcome to @site-name', array('@site-name' => variable_get('site_name', 'Drupal'))), PASS_THROUGH);
+
+ $default_message = '<p>' . t('No front page content has been created yet.') . '</p>';
+
+ $default_links = array();
+ if (_node_add_access()) {
+ $default_links[] = l(t('Add new content'), 'node/add');
+ }
+ if (!empty($default_links)) {
+ $default_message .= theme('item_list', array('items' => $default_links));
+ }
+
+ $build['default_message'] = array(
+ '#markup' => $default_message,
+ '#prefix' => '<div id="first-time">',
+ '#suffix' => '</div>',
+ );
+ }
+ return $build;
+}
+
+/**
+ * Menu callback: Displays a single node.
+ *
+ * @param $node
+ * The node object.
+ *
+ * @return
+ * A page array suitable for use by drupal_render().
+ *
+ * @see node_menu()
+ */
+function node_page_view($node) {
+ // If there is a menu link to this node, the link becomes the last part
+ // of the active trail, and the link name becomes the page title.
+ // Thus, we must explicitly set the page title to be the node title.
+ drupal_set_title($node->title);
+ $uri = entity_uri('node', $node);
+ // Set the node path as the canonical URL to prevent duplicate content.
+ drupal_add_html_head_link(array('rel' => 'canonical', 'href' => url($uri['path'], $uri['options'])), TRUE);
+ // Set the non-aliased path as a default shortlink.
+ drupal_add_html_head_link(array('rel' => 'shortlink', 'href' => url($uri['path'], array_merge($uri['options'], array('alias' => TRUE)))), TRUE);
+ return node_show($node);
+}
+
+/**
+ * Implements hook_update_index().
+ */
+function node_update_index() {
+ $limit = (int)variable_get('search_cron_limit', 100);
+
+ $result = db_query_range("SELECT n.nid FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE d.sid IS NULL OR d.reindex <> 0 ORDER BY d.reindex ASC, n.nid ASC", 0, $limit, array(), array('target' => 'slave'));
+
+ foreach ($result as $node) {
+ _node_index_node($node);
+ }
+}
+
+/**
+ * Indexes a single node.
+ *
+ * @param $node
+ * The node to index.
+ */
+function _node_index_node($node) {
+ $node = node_load($node->nid);
+
+ // Save the changed time of the most recent indexed node, for the search
+ // results half-life calculation.
+ variable_set('node_cron_last', $node->changed);
+
+ // Render the node.
+ $build = node_view($node, 'search_index');
+ unset($build['#theme']);
+ $node->rendered = drupal_render($build);
+
+ $text = '<h1>' . check_plain($node->title) . '</h1>' . $node->rendered;
+
+ // Fetch extra data normally not visible
+ $extra = module_invoke_all('node_update_index', $node);
+ foreach ($extra as $t) {
+ $text .= $t;
+ }
+
+ // Update index
+ search_index($node->nid, 'node', $text);
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function node_form_search_form_alter(&$form, $form_state) {
+ if (isset($form['module']) && $form['module']['#value'] == 'node' && user_access('use advanced search')) {
+ // Keyword boxes:
+ $form['advanced'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Advanced search'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#attributes' => array('class' => array('search-advanced')),
+ );
+ $form['advanced']['keywords'] = array(
+ '#prefix' => '<div class="criterion">',
+ '#suffix' => '</div>',
+ );
+ $form['advanced']['keywords']['or'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Containing any of the words'),
+ '#size' => 30,
+ '#maxlength' => 255,
+ );
+ $form['advanced']['keywords']['phrase'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Containing the phrase'),
+ '#size' => 30,
+ '#maxlength' => 255,
+ );
+ $form['advanced']['keywords']['negative'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Containing none of the words'),
+ '#size' => 30,
+ '#maxlength' => 255,
+ );
+
+ // Node types:
+ $types = array_map('check_plain', node_type_get_names());
+ $form['advanced']['type'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Only of the type(s)'),
+ '#prefix' => '<div class="criterion">',
+ '#suffix' => '</div>',
+ '#options' => $types,
+ );
+ $form['advanced']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Advanced search'),
+ '#prefix' => '<div class="action">',
+ '#suffix' => '</div>',
+ '#weight' => 100,
+ );
+
+ // Languages:
+ $language_options = array();
+ foreach (language_list('language') as $key => $entity) {
+ if ($entity->enabled) {
+ $language_options[$key] = $entity->name;
+ }
+ }
+ if (count($language_options) > 1) {
+ $form['advanced']['language'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Languages'),
+ '#prefix' => '<div class="criterion">',
+ '#suffix' => '</div>',
+ '#options' => $language_options,
+ );
+ }
+
+ $form['#validate'][] = 'node_search_validate';
+ }
+}
+
+/**
+ * Form validation handler for node_form_alter().
+ */
+function node_search_validate($form, &$form_state) {
+ // Initialize using any existing basic search keywords.
+ $keys = $form_state['values']['processed_keys'];
+
+ // Insert extra restrictions into the search keywords string.
+ if (isset($form_state['values']['type']) && is_array($form_state['values']['type'])) {
+ // Retrieve selected types - Form API sets the value of unselected
+ // checkboxes to 0.
+ $form_state['values']['type'] = array_filter($form_state['values']['type']);
+ if (count($form_state['values']['type'])) {
+ $keys = search_expression_insert($keys, 'type', implode(',', array_keys($form_state['values']['type'])));
+ }
+ }
+
+ if (isset($form_state['values']['term']) && is_array($form_state['values']['term']) && count($form_state['values']['term'])) {
+ $keys = search_expression_insert($keys, 'term', implode(',', $form_state['values']['term']));
+ }
+ if (isset($form_state['values']['language']) && is_array($form_state['values']['language'])) {
+ $languages = array_filter($form_state['values']['language']);
+ if (count($languages)) {
+ $keys = search_expression_insert($keys, 'language', implode(',', $languages));
+ }
+ }
+ if ($form_state['values']['or'] != '') {
+ if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state['values']['or'], $matches)) {
+ $keys .= ' ' . implode(' OR ', $matches[1]);
+ }
+ }
+ if ($form_state['values']['negative'] != '') {
+ if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state['values']['negative'], $matches)) {
+ $keys .= ' -' . implode(' -', $matches[1]);
+ }
+ }
+ if ($form_state['values']['phrase'] != '') {
+ $keys .= ' "' . str_replace('"', ' ', $form_state['values']['phrase']) . '"';
+ }
+ if (!empty($keys)) {
+ form_set_value($form['basic']['processed_keys'], trim($keys), $form_state);
+ }
+}
+
+/**
+ * @defgroup node_access Node access rights
+ * @{
+ * The node access system determines who can do what to which nodes.
+ *
+ * In determining access rights for a node, node_access() first checks whether
+ * the user has the "bypass node access" permission. Such users have
+ * unrestricted access to all nodes. user 1 will always pass this check.
+ *
+ * Next, all implementations of hook_node_access() will be called. Each
+ * implementation may explicitly allow, explicitly deny, or ignore the access
+ * request. If at least one module says to deny the request, it will be rejected.
+ * If no modules deny the request and at least one says to allow it, the request
+ * will be permitted.
+ *
+ * If all modules ignore the access request, then the node_access table is used
+ * to determine access. All node access modules are queried using
+ * hook_node_grants() to assemble a list of "grant IDs" for the user. This list
+ * is compared against the table. If any row contains the node ID in question
+ * (or 0, which stands for "all nodes"), one of the grant IDs returned, and a
+ * value of TRUE for the operation in question, then access is granted. Note
+ * that this table is a list of grants; any matching row is sufficient to
+ * grant access to the node.
+ *
+ * In node listings (lists of nodes generated from a select query, such as the
+ * default home page at path 'node', an RSS feed, a recent content block, etc.),
+ * the process above is followed except that hook_node_access() is not called on
+ * each node for performance reasons and for proper functioning of the pager
+ * system. When adding a node listing to your module, be sure to use a dynamic
+ * query created by db_select() and add a tag of "node_access". This will allow
+ * modules dealing with node access to ensure only nodes to which the user has
+ * access are retrieved, through the use of hook_query_TAG_alter().
+ *
+ * Note: Even a single module returning NODE_ACCESS_DENY from hook_node_access()
+ * will block access to the node. Therefore, implementers should take care to
+ * not deny access unless they really intend to. Unless a module wishes to
+ * actively deny access it should return NODE_ACCESS_IGNORE (or simply return
+ * nothing) to allow other modules or the node_access table to control access.
+ *
+ * To see how to write a node access module of your own, see
+ * node_access_example.module.
+ */
+
+/**
+ * Determines whether the current user may perform the operation on the node.
+ *
+ * @param $op
+ * The operation to be performed on the node. Possible values are:
+ * - "view"
+ * - "update"
+ * - "delete"
+ * - "create"
+ * @param $node
+ * The node object on which the operation is to be performed, or node type
+ * (e.g. 'forum') for "create" operation.
+ * @param $account
+ * Optional, a user object representing the user for whom the operation is to
+ * be performed. Determines access for a user other than the current user.
+ *
+ * @return
+ * TRUE if the operation may be performed, FALSE otherwise.
+ */
+function node_access($op, $node, $account = NULL) {
+ $rights = &drupal_static(__FUNCTION__, array());
+
+ if (!$node || !in_array($op, array('view', 'update', 'delete', 'create'), TRUE)) {
+ // If there was no node to check against, or the $op was not one of the
+ // supported ones, we return access denied.
+ return FALSE;
+ }
+ // If no user object is supplied, the access check is for the current user.
+ if (empty($account)) {
+ $account = $GLOBALS['user'];
+ }
+
+ // $node may be either an object or a node type. Since node types cannot be
+ // an integer, use either nid or type as the static cache id.
+
+ $cid = is_object($node) ? $node->nid : $node;
+
+ // If we've already checked access for this node, user and op, return from
+ // cache.
+ if (isset($rights[$account->uid][$cid][$op])) {
+ return $rights[$account->uid][$cid][$op];
+ }
+
+ if (user_access('bypass node access', $account)) {
+ $rights[$account->uid][$cid][$op] = TRUE;
+ return TRUE;
+ }
+ if (!user_access('access content', $account)) {
+ $rights[$account->uid][$cid][$op] = FALSE;
+ return FALSE;
+ }
+
+ // We grant access to the node if both of the following conditions are met:
+ // - No modules say to deny access.
+ // - At least one module says to grant access.
+ // If no module specified either allow or deny, we fall back to the
+ // node_access table.
+ $access = module_invoke_all('node_access', $node, $op, $account);
+ if (in_array(NODE_ACCESS_DENY, $access, TRUE)) {
+ $rights[$account->uid][$cid][$op] = FALSE;
+ return FALSE;
+ }
+ elseif (in_array(NODE_ACCESS_ALLOW, $access, TRUE)) {
+ $rights[$account->uid][$cid][$op] = TRUE;
+ return TRUE;
+ }
+
+ // Check if authors can view their own unpublished nodes.
+ if ($op == 'view' && !$node->status && user_access('view own unpublished content', $account) && $account->uid == $node->uid && $account->uid != 0) {
+ $rights[$account->uid][$cid][$op] = TRUE;
+ return TRUE;
+ }
+
+ // If the module did not override the access rights, use those set in the
+ // node_access table.
+ if ($op != 'create' && $node->nid) {
+ if (module_implements('node_grants')) {
+ $query = db_select('node_access');
+ $query->addExpression('1');
+ $query->condition('grant_' . $op, 1, '>=');
+ $nids = db_or()->condition('nid', $node->nid);
+ if ($node->status) {
+ $nids->condition('nid', 0);
+ }
+ $query->condition($nids);
+ $query->range(0, 1);
+
+ $grants = db_or();
+ foreach (node_access_grants($op, $account) as $realm => $gids) {
+ foreach ($gids as $gid) {
+ $grants->condition(db_and()
+ ->condition('gid', $gid)
+ ->condition('realm', $realm)
+ );
+ }
+ }
+ if (count($grants) > 0) {
+ $query->condition($grants);
+ }
+ $result = (bool) $query
+ ->execute()
+ ->fetchField();
+ $rights[$account->uid][$cid][$op] = $result;
+ return $result;
+ }
+ elseif (is_object($node) && $op == 'view' && $node->status) {
+ // If no modules implement hook_node_grants(), the default behavior is to
+ // allow all users to view published nodes, so reflect that here.
+ $rights[$account->uid][$cid][$op] = TRUE;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * Implements hook_node_access().
+ */
+function node_node_access($node, $op, $account) {
+ $type = is_string($node) ? $node : $node->type;
+
+ if (in_array($type, node_permissions_get_configured_types())) {
+ if ($op == 'create' && user_access('create ' . $type . ' content', $account)) {
+ return NODE_ACCESS_ALLOW;
+ }
+
+ if ($op == 'update') {
+ if (user_access('edit any ' . $type . ' content', $account) || (user_access('edit own ' . $type . ' content', $account) && ($account->uid == $node->uid))) {
+ return NODE_ACCESS_ALLOW;
+ }
+ }
+
+ if ($op == 'delete') {
+ if (user_access('delete any ' . $type . ' content', $account) || (user_access('delete own ' . $type . ' content', $account) && ($account->uid == $node->uid))) {
+ return NODE_ACCESS_ALLOW;
+ }
+ }
+ }
+
+ return NODE_ACCESS_IGNORE;
+}
+
+/**
+ * Helper function to generate standard node permission list for a given type.
+ *
+ * @param $type
+ * The machine-readable name of the node type.
+ *
+ * @return array
+ * An array of permission names and descriptions.
+ */
+function node_list_permissions($type) {
+ $info = node_type_get_type($type);
+
+ // Build standard list of node permissions for this type.
+ $perms = array(
+ "create $type content" => array(
+ 'title' => t('%type_name: Create new content', array('%type_name' => $info->name)),
+ ),
+ "edit own $type content" => array(
+ 'title' => t('%type_name: Edit own content', array('%type_name' => $info->name)),
+ ),
+ "edit any $type content" => array(
+ 'title' => t('%type_name: Edit any content', array('%type_name' => $info->name)),
+ ),
+ "delete own $type content" => array(
+ 'title' => t('%type_name: Delete own content', array('%type_name' => $info->name)),
+ ),
+ "delete any $type content" => array(
+ 'title' => t('%type_name: Delete any content', array('%type_name' => $info->name)),
+ ),
+ );
+
+ return $perms;
+}
+
+/**
+ * Returns an array of node types that should be managed by permissions.
+ *
+ * By default, this will include all node types in the system. To exclude a
+ * specific node from getting permissions defined for it, set the
+ * node_permissions_$type variable to 0. Core does not provide an interface for
+ * doing so. However, contrib modules may exclude their own nodes in
+ * hook_install(). Alternatively, contrib modules may configure all node types
+ * at once, or decide to apply some other hook_node_access() implementation to
+ * some or all node types.
+ *
+ * @return
+ * An array of node types managed by this module.
+ */
+function node_permissions_get_configured_types() {
+
+ $configured_types = array();
+
+ foreach (node_type_get_types() as $type => $info) {
+ if (variable_get('node_permissions_' . $type, 1)) {
+ $configured_types[] = $type;
+ }
+ }
+
+ return $configured_types;
+}
+
+/**
+ * Fetches an array of permission IDs granted to the given user ID.
+ *
+ * The implementation here provides only the universal "all" grant. A node
+ * access module should implement hook_node_grants() to provide a grant list for
+ * the user.
+ *
+ * After the default grants have been loaded, we allow modules to alter the
+ * grants array by reference. This hook allows for complex business logic to be
+ * applied when integrating multiple node access modules.
+ *
+ * @param $op
+ * The operation that the user is trying to perform.
+ * @param $account
+ * The user object for the user performing the operation. If omitted, the
+ * current user is used.
+ *
+ * @return
+ * An associative array in which the keys are realms, and the values are
+ * arrays of grants for those realms.
+ */
+function node_access_grants($op, $account = NULL) {
+
+ if (!isset($account)) {
+ $account = $GLOBALS['user'];
+ }
+
+ // Fetch node access grants from other modules.
+ $grants = module_invoke_all('node_grants', $account, $op);
+ // Allow modules to alter the assigned grants.
+ drupal_alter('node_grants', $grants, $account, $op);
+
+ return array_merge(array('all' => array(0)), $grants);
+}
+
+/**
+ * Determines whether the user has a global viewing grant for all nodes.
+ *
+ * Checks to see whether any module grants global 'view' access to a user
+ * account; global 'view' access is encoded in the {node_access} table as a
+ * grant with nid=0. If no node access modules are enabled, node.module defines
+ * such a global 'view' access grant.
+ *
+ * This function is called when a node listing query is tagged with
+ * 'node_access'; when this function returns TRUE, no node access joins are
+ * added to the query.
+ *
+ * @param $account
+ * The user object for the user whose access is being checked. If omitted,
+ * the current user is used.
+ *
+ * @return
+ * TRUE if 'view' access to all nodes is granted, FALSE otherwise.
+ *
+ * @see hook_node_grants()
+ * @see _node_query_node_access_alter()
+ */
+function node_access_view_all_nodes($account = NULL) {
+ global $user;
+ if (!$account) {
+ $account = $user;
+ }
+
+ // Statically cache results in an array keyed by $account->uid.
+ $access = &drupal_static(__FUNCTION__);
+ if (isset($access[$account->uid])) {
+ return $access[$account->uid];
+ }
+
+ // If no modules implement the node access system, access is always TRUE.
+ if (!module_implements('node_grants')) {
+ $access[$account->uid] = TRUE;
+ }
+ else {
+ $query = db_select('node_access');
+ $query->addExpression('COUNT(*)');
+ $query
+ ->condition('nid', 0)
+ ->condition('grant_view', 1, '>=');
+
+ $grants = db_or();
+ foreach (node_access_grants('view', $account) as $realm => $gids) {
+ foreach ($gids as $gid) {
+ $grants->condition(db_and()
+ ->condition('gid', $gid)
+ ->condition('realm', $realm)
+ );
+ }
+ }
+ if (count($grants) > 0 ) {
+ $query->condition($grants);
+ }
+ $access[$account->uid] = $query
+ ->execute()
+ ->fetchField();
+ }
+
+ return $access[$account->uid];
+}
+
+
+/**
+ * Implements hook_query_TAG_alter().
+ *
+ * This is the hook_query_alter() for queries tagged with 'node_access'. It adds
+ * node access checks for the user account given by the 'account' meta-data (or
+ * global $user if not provided), for an operation given by the 'op' meta-data
+ * (or 'view' if not provided; other possible values are 'update' and 'delete').
+ */
+function node_query_node_access_alter(QueryAlterableInterface $query) {
+ _node_query_node_access_alter($query, 'node');
+}
+
+/**
+ * Implements hook_query_TAG_alter().
+ *
+ * This function implements the same functionality as
+ * node_query_node_access_alter() for the SQL field storage engine. Node access
+ * conditions are added for field values belonging to nodes only.
+ */
+function node_query_entity_field_access_alter(QueryAlterableInterface $query) {
+ _node_query_node_access_alter($query, 'entity');
+}
+
+/**
+ * Helper for node access functions.
+ *
+ * @param $query
+ * The query to add conditions to.
+ * @param $type
+ * Either 'node' or 'entity' depending on what sort of query it is. See
+ * node_query_node_access_alter() and node_query_entity_field_access_alter()
+ * for more.
+ */
+function _node_query_node_access_alter($query, $type) {
+ global $user;
+
+ // Read meta-data from query, if provided.
+ if (!$account = $query->getMetaData('account')) {
+ $account = $user;
+ }
+ if (!$op = $query->getMetaData('op')) {
+ $op = 'view';
+ }
+
+ // If $account can bypass node access, or there are no node access modules,
+ // or the operation is 'view' and the $account has a global view grant
+ // (such as a view grant for node ID 0), we don't need to alter the query.
+ if (user_access('bypass node access', $account)) {
+ return;
+ }
+ if (!count(module_implements('node_grants'))) {
+ return;
+ }
+ if ($op == 'view' && node_access_view_all_nodes($account)) {
+ return;
+ }
+
+ $tables = $query->getTables();
+ $base_table = $query->getMetaData('base_table');
+ // If no base table is specified explicitly, search for one.
+ if (!$base_table) {
+ $fallback = '';
+ foreach ($tables as $alias => $table_info) {
+ if (!($table_info instanceof SelectQueryInterface)) {
+ $table = $table_info['table'];
+ // If the node table is in the query, it wins immediately.
+ if ($table == 'node') {
+ $base_table = $table;
+ break;
+ }
+ // Check whether the table has a foreign key to node.nid. If it does,
+ // do not run this check again as we found a base table and only node
+ // can triumph that.
+ if (!$base_table) {
+ // The schema is cached.
+ $schema = drupal_get_schema($table);
+ if (isset($schema['fields']['nid'])) {
+ if (isset($schema['foreign keys'])) {
+ foreach ($schema['foreign keys'] as $relation) {
+ if ($relation['table'] === 'node' && $relation['columns'] === array('nid' => 'nid')) {
+ $base_table = $table;
+ }
+ }
+ }
+ else {
+ // At least it's a nid. A table with a field called nid is very
+ // very likely to be a node.nid in a node access query.
+ $fallback = $table;
+ }
+ }
+ }
+ }
+ }
+ // If there is nothing else, use the fallback.
+ if (!$base_table) {
+ if ($fallback) {
+ watchdog('security', 'Your node listing query is using @fallback as a base table in a query tagged for node access. This might not be secure and might not even work. Specify foreign keys in your schema to node.nid ', array('@fallback' => $fallback), WATCHDOG_WARNING);
+ $base_table = $fallback;
+ }
+ else {
+ throw new Exception(t('Query tagged for node access but there is no nid. Add foreign keys to node.nid in schema to fix.'));
+ }
+ }
+ }
+
+ // Find all instances of the base table being joined -- could appear
+ // more than once in the query, and could be aliased. Join each one to
+ // the node_access table.
+
+ $grants = node_access_grants($op, $account);
+ if ($type == 'entity') {
+ // The original query looked something like:
+ // @code
+ // SELECT nid FROM sometable s
+ // INNER JOIN node_access na ON na.nid = s.nid
+ // WHERE ($node_access_conditions)
+ // @endcode
+ //
+ // Our query will look like:
+ // @code
+ // SELECT entity_type, entity_id
+ // FROM field_data_something s
+ // LEFT JOIN node_access na ON s.entity_id = na.nid
+ // WHERE (entity_type = 'node' AND $node_access_conditions) OR (entity_type <> 'node')
+ // @endcode
+ //
+ // So instead of directly adding to the query object, we need to collect
+ // all of the node access conditions in a separate db_and() object and
+ // then add it to the query at the end.
+ $node_conditions = db_and();
+ }
+ foreach ($tables as $nalias => $tableinfo) {
+ $table = $tableinfo['table'];
+ if (!($table instanceof SelectQueryInterface) && $table == $base_table) {
+ // Set the subquery.
+ $subquery = db_select('node_access', 'na')
+ ->fields('na', array('nid'));
+
+ $grant_conditions = db_or();
+ // If any grant exists for the specified user, then user has access
+ // to the node for the specified operation.
+ foreach ($grants as $realm => $gids) {
+ foreach ($gids as $gid) {
+ $grant_conditions->condition(db_and()
+ ->condition('na.gid', $gid)
+ ->condition('na.realm', $realm)
+ );
+ }
+ }
+
+ // Attach conditions to the subquery for nodes.
+ if (count($grant_conditions->conditions())) {
+ $subquery->condition($grant_conditions);
+ }
+ $subquery->condition('na.grant_' . $op, 1, '>=');
+ $field = 'nid';
+ // Now handle entities.
+ if ($type == 'entity') {
+ // Set a common alias for entities.
+ $base_alias = $nalias;
+ $field = 'entity_id';
+ }
+ $subquery->where("$nalias.$field = na.nid");
+
+ // For an entity query, attach the subquery to entity conditions.
+ if ($type == 'entity') {
+ $node_conditions->exists($subquery);
+ }
+ // Otherwise attach it to the node query itself.
+ else {
+ $query->exists($subquery);
+ }
+ }
+ }
+
+ if ($type == 'entity' && count($subquery->conditions())) {
+ // All the node access conditions are only for field values belonging to
+ // nodes.
+ $node_conditions->condition("$base_alias.entity_type", 'node');
+ $or = db_or();
+ $or->condition($node_conditions);
+ // If the field value belongs to a non-node entity type then this function
+ // does not do anything with it.
+ $or->condition("$base_alias.entity_type", 'node', '<>');
+ // Add the compiled set of rules to the query.
+ $query->condition($or);
+ }
+
+}
+
+/**
+ * Gets the list of node access grants and writes them to the database.
+ *
+ * This function is called when a node is saved, and can also be called by
+ * modules if something other than a node save causes node access permissions to
+ * change. It collects all node access grants for the node from
+ * hook_node_access_records() implementations, allows these grants to be altered
+ * via hook_node_access_records_alter() implementations, and saves the collected
+ * and altered grants to the database.
+ *
+ * @param $node
+ * The $node to acquire grants for.
+ *
+ * @param $delete
+ * Whether to delete existing node access records before inserting new ones.
+ * Defaults to TRUE.
+ */
+function node_access_acquire_grants($node, $delete = TRUE) {
+ $grants = module_invoke_all('node_access_records', $node);
+ // Let modules alter the grants.
+ drupal_alter('node_access_records', $grants, $node);
+ // If no grants are set and the node is published, then use the default grant.
+ if (empty($grants) && !empty($node->status)) {
+ $grants[] = array('realm' => 'all', 'gid' => 0, 'grant_view' => 1, 'grant_update' => 0, 'grant_delete' => 0);
+ }
+ else {
+ // Retain grants by highest priority.
+ $grant_by_priority = array();
+ foreach ($grants as $g) {
+ $grant_by_priority[intval($g['priority'])][] = $g;
+ }
+ krsort($grant_by_priority);
+ $grants = array_shift($grant_by_priority);
+ }
+
+ node_access_write_grants($node, $grants, NULL, $delete);
+}
+
+/**
+ * Writes a list of grants to the database, deleting any previously saved ones.
+ *
+ * If a realm is provided, it will only delete grants from that realm, but it
+ * will always delete a grant from the 'all' realm. Modules that utilize
+ * node_access() can use this function when doing mass updates due to widespread
+ * permission changes.
+ *
+ * Note: Don't call this function directly from a contributed module. Call
+ * node_access_acquire_grants() instead.
+ *
+ * @param $node
+ * The node whose grants are being written.
+ * @param $grants
+ * A list of grants to write. Each grant is an array that must contain the
+ * following keys: realm, gid, grant_view, grant_update, grant_delete.
+ * The realm is specified by a particular module; the gid is as well, and
+ * is a module-defined id to define grant privileges. each grant_* field
+ * is a boolean value.
+ * @param $realm
+ * (optional) If provided, read/write grants for that realm only. Defaults to
+ * NULL.
+ * @param $delete
+ * (optional) If false, does not delete records. This is only for optimization
+ * purposes, and assumes the caller has already performed a mass delete of
+ * some form. Defaults to TRUE.
+ *
+ * @see node_access_acquire_grants()
+ */
+function node_access_write_grants($node, $grants, $realm = NULL, $delete = TRUE) {
+ if ($delete) {
+ $query = db_delete('node_access')->condition('nid', $node->nid);
+ if ($realm) {
+ $query->condition('realm', array($realm, 'all'), 'IN');
+ }
+ $query->execute();
+ }
+
+ // Only perform work when node_access modules are active.
+ if (!empty($grants) && count(module_implements('node_grants'))) {
+ $query = db_insert('node_access')->fields(array('nid', 'realm', 'gid', 'grant_view', 'grant_update', 'grant_delete'));
+ foreach ($grants as $grant) {
+ if ($realm && $realm != $grant['realm']) {
+ continue;
+ }
+ // Only write grants; denies are implicit.
+ if ($grant['grant_view'] || $grant['grant_update'] || $grant['grant_delete']) {
+ $grant['nid'] = $node->nid;
+ $query->values($grant);
+ }
+ }
+ $query->execute();
+ }
+}
+
+/**
+ * Flags or unflags the node access grants for rebuilding.
+ *
+ * If the argument isn't specified, the current value of the flag is returned.
+ * When the flag is set, a message is displayed to users with 'access
+ * administration pages' permission, pointing to the 'rebuild' confirm form.
+ * This can be used as an alternative to direct node_access_rebuild calls,
+ * allowing administrators to decide when they want to perform the actual
+ * (possibly time consuming) rebuild. When unsure if the current user is an
+ * administrator, node_access_rebuild() should be used instead.
+ *
+ * @param $rebuild
+ * (Optional) The boolean value to be written.
+ *
+ * @return
+ * The current value of the flag if no value was provided for $rebuild.
+ *
+ * @see node_access_rebuild()
+ */
+function node_access_needs_rebuild($rebuild = NULL) {
+ if (!isset($rebuild)) {
+ return variable_get('node_access_needs_rebuild', FALSE);
+ }
+ elseif ($rebuild) {
+ variable_set('node_access_needs_rebuild', TRUE);
+ }
+ else {
+ variable_del('node_access_needs_rebuild');
+ }
+}
+
+/**
+ * Rebuilds the node access database.
+ *
+ * This is occasionally needed by modules that make system-wide changes to
+ * access levels. When the rebuild is required by an admin-triggered action (e.g
+ * module settings form), calling node_access_needs_rebuild(TRUE) instead of
+ * node_access_rebuild() lets the user perform his changes and actually
+ * rebuild only once he is done.
+ *
+ * Note: As of Drupal 6, node access modules are not required to (and actually
+ * should not) call node_access_rebuild() in hook_enable/disable anymore.
+ *
+ * @see node_access_needs_rebuild()
+ *
+ * @param $batch_mode
+ * Set to TRUE to process in 'batch' mode, spawning processing over several
+ * HTTP requests (thus avoiding the risk of PHP timeout if the site has a
+ * large number of nodes).
+ * hook_update_N and any form submit handler are safe contexts to use the
+ * 'batch mode'. Less decidable cases (such as calls from hook_user,
+ * hook_taxonomy, etc...) might consider using the non-batch mode.
+ */
+function node_access_rebuild($batch_mode = FALSE) {
+ db_delete('node_access')->execute();
+ // Only recalculate if the site is using a node_access module.
+ if (count(module_implements('node_grants'))) {
+ if ($batch_mode) {
+ $batch = array(
+ 'title' => t('Rebuilding content access permissions'),
+ 'operations' => array(
+ array('_node_access_rebuild_batch_operation', array()),
+ ),
+ 'finished' => '_node_access_rebuild_batch_finished'
+ );
+ batch_set($batch);
+ }
+ else {
+ // Try to allocate enough time to rebuild node grants
+ drupal_set_time_limit(240);
+
+ $nids = db_query("SELECT nid FROM {node}")->fetchCol();
+ foreach ($nids as $nid) {
+ $node = node_load($nid, NULL, TRUE);
+ // To preserve database integrity, only acquire grants if the node
+ // loads successfully.
+ if (!empty($node)) {
+ node_access_acquire_grants($node);
+ }
+ }
+ }
+ }
+ else {
+ // Not using any node_access modules. Add the default grant.
+ db_insert('node_access')
+ ->fields(array(
+ 'nid' => 0,
+ 'realm' => 'all',
+ 'gid' => 0,
+ 'grant_view' => 1,
+ 'grant_update' => 0,
+ 'grant_delete' => 0,
+ ))
+ ->execute();
+ }
+
+ if (!isset($batch)) {
+ drupal_set_message(t('Content permissions have been rebuilt.'));
+ node_access_needs_rebuild(FALSE);
+ cache_clear_all();
+ }
+}
+
+/**
+ * Performs batch operation for node_access_rebuild().
+ *
+ * This is a multistep operation: we go through all nodes by packs of 20. The
+ * batch processing engine interrupts processing and sends progress feedback
+ * after 1 second execution time.
+ *
+ * @param array $context
+ * An array of contextual key/value information for rebuild batch process.
+ */
+function _node_access_rebuild_batch_operation(&$context) {
+ if (empty($context['sandbox'])) {
+ // Initiate multistep processing.
+ $context['sandbox']['progress'] = 0;
+ $context['sandbox']['current_node'] = 0;
+ $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT nid) FROM {node}')->fetchField();
+ }
+
+ // Process the next 20 nodes.
+ $limit = 20;
+ $nids = db_query_range("SELECT nid FROM {node} WHERE nid > :nid ORDER BY nid ASC", 0, $limit, array(':nid' => $context['sandbox']['current_node']))->fetchCol();
+ $nodes = node_load_multiple($nids, array(), TRUE);
+ foreach ($nodes as $nid => $node) {
+ // To preserve database integrity, only acquire grants if the node
+ // loads successfully.
+ if (!empty($node)) {
+ node_access_acquire_grants($node);
+ }
+ $context['sandbox']['progress']++;
+ $context['sandbox']['current_node'] = $nid;
+ }
+
+ // Multistep processing : report progress.
+ if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
+ $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+ }
+}
+
+/**
+ * Performs post-processing for node_access_rebuild().
+ *
+ * @param bool $success
+ * A boolean indicating whether the re-build process has completed.
+ * @param array $results
+ * An array of results information.
+ * @param array $operations
+ * An array of function calls (not used in this function).
+ */
+function _node_access_rebuild_batch_finished($success, $results, $operations) {
+ if ($success) {
+ drupal_set_message(t('The content access permissions have been rebuilt.'));
+ node_access_needs_rebuild(FALSE);
+ }
+ else {
+ drupal_set_message(t('The content access permissions have not been properly rebuilt.'), 'error');
+ }
+ cache_clear_all();
+}
+
+/**
+ * @} End of "defgroup node_access".
+ */
+
+/**
+ * @defgroup node_content Hook implementations for user-created content types
+ * @{
+ * Functions that implement hooks for user-created content types.
+ */
+
+/**
+ * Implements hook_form().
+ */
+function node_content_form($node, $form_state) {
+ // It is impossible to define a content type without implementing hook_form()
+ // @todo: remove this requirement.
+ $form = array();
+ $type = node_type_get_type($node);
+
+ if ($type->has_title) {
+ $form['title'] = array(
+ '#type' => 'textfield',
+ '#title' => check_plain($type->title_label),
+ '#required' => TRUE,
+ '#default_value' => $node->title,
+ '#maxlength' => 255,
+ '#weight' => -5,
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * @} End of "defgroup node_content".
+ */
+
+/**
+ * Implements hook_forms().
+ *
+ * All node forms share the same form handler.
+ */
+function node_forms() {
+ $forms = array();
+ if ($types = node_type_get_types()) {
+ foreach (array_keys($types) as $type) {
+ $forms[$type . '_node_form']['callback'] = 'node_form';
+ }
+ }
+ return $forms;
+}
+
+/**
+ * Implements hook_action_info().
+ */
+function node_action_info() {
+ return array(
+ 'node_publish_action' => array(
+ 'type' => 'node',
+ 'label' => t('Publish content'),
+ 'configurable' => FALSE,
+ 'behavior' => array('changes_property'),
+ 'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
+ ),
+ 'node_unpublish_action' => array(
+ 'type' => 'node',
+ 'label' => t('Unpublish content'),
+ 'configurable' => FALSE,
+ 'behavior' => array('changes_property'),
+ 'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
+ ),
+ 'node_make_sticky_action' => array(
+ 'type' => 'node',
+ 'label' => t('Make content sticky'),
+ 'configurable' => FALSE,
+ 'behavior' => array('changes_property'),
+ 'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
+ ),
+ 'node_make_unsticky_action' => array(
+ 'type' => 'node',
+ 'label' => t('Make content unsticky'),
+ 'configurable' => FALSE,
+ 'behavior' => array('changes_property'),
+ 'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
+ ),
+ 'node_promote_action' => array(
+ 'type' => 'node',
+ 'label' => t('Promote content to front page'),
+ 'configurable' => FALSE,
+ 'behavior' => array('changes_property'),
+ 'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
+ ),
+ 'node_unpromote_action' => array(
+ 'type' => 'node',
+ 'label' => t('Remove content from front page'),
+ 'configurable' => FALSE,
+ 'behavior' => array('changes_property'),
+ 'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
+ ),
+ 'node_assign_owner_action' => array(
+ 'type' => 'node',
+ 'label' => t('Change the author of content'),
+ 'configurable' => TRUE,
+ 'behavior' => array('changes_property'),
+ 'triggers' => array('node_presave', 'comment_insert', 'comment_update', 'comment_delete'),
+ ),
+ 'node_save_action' => array(
+ 'type' => 'node',
+ 'label' => t('Save content'),
+ 'configurable' => FALSE,
+ 'triggers' => array('comment_insert', 'comment_update', 'comment_delete'),
+ ),
+ 'node_unpublish_by_keyword_action' => array(
+ 'type' => 'node',
+ 'label' => t('Unpublish content containing keyword(s)'),
+ 'configurable' => TRUE,
+ 'triggers' => array('node_presave', 'node_insert', 'node_update'),
+ ),
+ );
+}
+
+/**
+ * Sets the status of a node to 1 (published).
+ *
+ * @param $node
+ * A node object.
+ * @param $context
+ * (optional) Array of additional information about what triggered the action.
+ * Not used for this action.
+ *
+ * @ingroup actions
+ */
+function node_publish_action($node, $context = array()) {
+ $node->status = NODE_PUBLISHED;
+ watchdog('action', 'Set @type %title to published.', array('@type' => node_type_get_name($node), '%title' => $node->title));
+}
+
+/**
+ * Sets the status of a node to 0 (unpublished).
+ *
+ * @param $node
+ * A node object.
+ * @param $context
+ * (optional) Array of additional information about what triggered the action.
+ * Not used for this action.
+ *
+ * @ingroup actions
+ */
+function node_unpublish_action($node, $context = array()) {
+ $node->status = NODE_NOT_PUBLISHED;
+ watchdog('action', 'Set @type %title to unpublished.', array('@type' => node_type_get_name($node), '%title' => $node->title));
+}
+
+/**
+ * Sets the sticky-at-top-of-list property of a node to 1.
+ *
+ * @param $node
+ * A node object.
+ * @param $context
+ * (optional) Array of additional information about what triggered the action.
+ * Not used for this action.
+ *
+ * @ingroup actions
+ */
+function node_make_sticky_action($node, $context = array()) {
+ $node->sticky = NODE_STICKY;
+ watchdog('action', 'Set @type %title to sticky.', array('@type' => node_type_get_name($node), '%title' => $node->title));
+}
+
+/**
+ * Sets the sticky-at-top-of-list property of a node to 0.
+ *
+ * @param $node
+ * A node object.
+ * @param $context
+ * (optional) Array of additional information about what triggered the action.
+ * Not used for this action.
+ *
+ * @ingroup actions
+ */
+function node_make_unsticky_action($node, $context = array()) {
+ $node->sticky = NODE_NOT_STICKY;
+ watchdog('action', 'Set @type %title to unsticky.', array('@type' => node_type_get_name($node), '%title' => $node->title));
+}
+
+/**
+ * Sets the promote property of a node to 1.
+ *
+ * @param $node
+ * A node object.
+ * @param $context
+ * (optional) Array of additional information about what triggered the action.
+ * Not used for this action.
+ *
+ * @ingroup actions
+ */
+function node_promote_action($node, $context = array()) {
+ $node->promote = NODE_PROMOTED;
+ watchdog('action', 'Promoted @type %title to front page.', array('@type' => node_type_get_name($node), '%title' => $node->title));
+}
+
+/**
+ * Sets the promote property of a node to 0.
+ *
+ * @param $node
+ * A node object.
+ * @param $context
+ * (optional) Array of additional information about what triggered the action.
+ * Not used for this action.
+ *
+ * @ingroup actions
+ */
+function node_unpromote_action($node, $context = array()) {
+ $node->promote = NODE_NOT_PROMOTED;
+ watchdog('action', 'Removed @type %title from front page.', array('@type' => node_type_get_name($node), '%title' => $node->title));
+}
+
+/**
+ * Saves a node.
+ *
+ * @param $node
+ * The node to be saved.
+ *
+ * @ingroup actions
+ */
+function node_save_action($node) {
+ node_save($node);
+ watchdog('action', 'Saved @type %title', array('@type' => node_type_get_name($node), '%title' => $node->title));
+}
+
+/**
+ * Assigns ownership of a node to a user.
+ *
+ * @param $node
+ * A node object to modify.
+ * @param $context
+ * Array with the following elements:
+ * - 'owner_uid': User ID to assign to the node.
+ *
+ * @see node_assign_owner_action_form()
+ * @see node_assign_owner_action_validate()
+ * @see node_assign_owner_action_submit()
+ * @ingroup actions
+ */
+function node_assign_owner_action($node, $context) {
+ $node->uid = $context['owner_uid'];
+ $owner_name = db_query("SELECT name FROM {users} WHERE uid = :uid", array(':uid' => $context['owner_uid']))->fetchField();
+ watchdog('action', 'Changed owner of @type %title to uid %name.', array('@type' => node_type_get_name($node), '%title' => $node->title, '%name' => $owner_name));
+}
+
+/**
+ * Generates the settings form for node_assign_owner_action().
+ *
+ * @param $context
+ * Array of additional information about what triggered the action. Includes
+ * the following elements:
+ * - 'owner_uid': User ID to assign to the node.
+ *
+ * @see node_assign_owner_action_submit()
+ * @see node_assign_owner_action_validate()
+ *
+ * @ingroup forms
+ */
+function node_assign_owner_action_form($context) {
+ $description = t('The username of the user to which you would like to assign ownership.');
+ $count = db_query("SELECT COUNT(*) FROM {users}")->fetchField();
+ $owner_name = '';
+ if (isset($context['owner_uid'])) {
+ $owner_name = db_query("SELECT name FROM {users} WHERE uid = :uid", array(':uid' => $context['owner_uid']))->fetchField();
+ }
+
+ // Use dropdown for fewer than 200 users; textbox for more than that.
+ if (intval($count) < 200) {
+ $options = array();
+ $result = db_query("SELECT uid, name FROM {users} WHERE uid > 0 ORDER BY name");
+ foreach ($result as $data) {
+ $options[$data->name] = $data->name;
+ }
+ $form['owner_name'] = array(
+ '#type' => 'select',
+ '#title' => t('Username'),
+ '#default_value' => $owner_name,
+ '#options' => $options,
+ '#description' => $description,
+ );
+ }
+ else {
+ $form['owner_name'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Username'),
+ '#default_value' => $owner_name,
+ '#autocomplete_path' => 'user/autocomplete',
+ '#size' => '6',
+ '#maxlength' => '60',
+ '#description' => $description,
+ );
+ }
+ return $form;
+}
+
+/**
+ * Validates settings form for node_assign_owner_action().
+ *
+ * @see node_assign_owner_action_submit()
+ */
+function node_assign_owner_action_validate($form, $form_state) {
+ $exists = (bool) db_query_range('SELECT 1 FROM {users} WHERE name = :name', 0, 1, array(':name' => $form_state['values']['owner_name']))->fetchField();
+ if (!$exists) {
+ form_set_error('owner_name', t('Enter a valid username.'));
+ }
+}
+
+/**
+ * Saves settings form for node_assign_owner_action().
+ *
+ * @see node_assign_owner_action_validate()
+ */
+function node_assign_owner_action_submit($form, $form_state) {
+ // Username can change, so we need to store the ID, not the username.
+ $uid = db_query('SELECT uid from {users} WHERE name = :name', array(':name' => $form_state['values']['owner_name']))->fetchField();
+ return array('owner_uid' => $uid);
+}
+
+/**
+ * Generates settings form for node_unpublish_by_keyword_action().
+ *
+ * @param array $context
+ * Array of additional information about what triggered this action.
+ *
+ * @return array
+ * A form array.
+ *
+ * @see node_unpublish_by_keyword_action_submit()
+ */
+function node_unpublish_by_keyword_action_form($context) {
+ $form['keywords'] = array(
+ '#title' => t('Keywords'),
+ '#type' => 'textarea',
+ '#description' => t('The content will be unpublished if it contains any of the phrases above. Use a case-sensitive, comma-separated list of phrases. Example: funny, bungee jumping, "Company, Inc."'),
+ '#default_value' => isset($context['keywords']) ? drupal_implode_tags($context['keywords']) : '',
+ );
+ return $form;
+}
+
+/**
+ * Saves settings form for node_unpublish_by_keyword_action().
+ */
+function node_unpublish_by_keyword_action_submit($form, $form_state) {
+ return array('keywords' => drupal_explode_tags($form_state['values']['keywords']));
+}
+
+/**
+ * Unpublishes a node containing certain keywords.
+ *
+ * @param $node
+ * A node object to modify.
+ * @param $context
+ * Array with the following elements:
+ * - 'keywords': Array of keywords. If any keyword is present in the rendered
+ * node, the node's status flag is set to unpublished.
+ *
+ * @ingroup actions
+ */
+function node_unpublish_by_keyword_action($node, $context) {
+ foreach ($context['keywords'] as $keyword) {
+ $elements = node_view(clone $node);
+ if (strpos(drupal_render($elements), $keyword) !== FALSE || strpos($node->title, $keyword) !== FALSE) {
+ $node->status = NODE_NOT_PUBLISHED;
+ watchdog('action', 'Set @type %title to unpublished.', array('@type' => node_type_get_name($node), '%title' => $node->title));
+ break;
+ }
+ }
+}
+
+/**
+ * Implements hook_requirements().
+ */
+function node_requirements($phase) {
+ $requirements = array();
+ if ($phase === 'runtime') {
+ // Only show rebuild button if there are either 0, or 2 or more, rows
+ // in the {node_access} table, or if there are modules that
+ // implement hook_node_grants().
+ $grant_count = db_query('SELECT COUNT(*) FROM {node_access}')->fetchField();
+ if ($grant_count != 1 || count(module_implements('node_grants')) > 0) {
+ $value = format_plural($grant_count, 'One permission in use', '@count permissions in use', array('@count' => $grant_count));
+ }
+ else {
+ $value = t('Disabled');
+ }
+ $description = t('If the site is experiencing problems with permissions to content, you may have to rebuild the permissions cache. Rebuilding will remove all privileges to content and replace them with permissions based on the current modules and settings. Rebuilding may take some time if there is a lot of content or complex permission settings. After rebuilding has completed, content will automatically use the new permissions.');
+
+ $requirements['node_access'] = array(
+ 'title' => t('Node Access Permissions'),
+ 'value' => $value,
+ 'description' => $description . ' ' . l(t('Rebuild permissions'), 'admin/reports/status/rebuild'),
+ );
+ }
+ return $requirements;
+}
+
+/**
+ * Implements hook_modules_enabled().
+ */
+function node_modules_enabled($modules) {
+ // Check if any of the newly enabled modules require the node_access table to
+ // be rebuilt.
+ if (!node_access_needs_rebuild() && array_intersect($modules, module_implements('node_grants'))) {
+ node_access_needs_rebuild(TRUE);
+ }
+}
+
+/**
+ * Controller class for nodes.
+ *
+ * This extends the DrupalDefaultEntityController class, adding required
+ * special handling for node objects.
+ */
+class NodeController extends DrupalDefaultEntityController {
+
+ protected function attachLoad(&$nodes, $revision_id = FALSE) {
+ // Create an array of nodes for each content type and pass this to the
+ // object type specific callback.
+ $typed_nodes = array();
+ foreach ($nodes as $id => $entity) {
+ $typed_nodes[$entity->type][$id] = $entity;
+ }
+
+ // Call object type specific callbacks on each typed array of nodes.
+ foreach ($typed_nodes as $node_type => $nodes_of_type) {
+ if (node_hook($node_type, 'load')) {
+ $function = node_type_get_base($node_type) . '_load';
+ $function($nodes_of_type);
+ }
+ }
+ // Besides the list of nodes, pass one additional argument to
+ // hook_node_load(), containing a list of node types that were loaded.
+ $argument = array_keys($typed_nodes);
+ $this->hookLoadArguments = array($argument);
+ parent::attachLoad($nodes, $revision_id);
+ }
+
+ protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
+ // Ensure that uid is taken from the {node} table,
+ // alias timestamp to revision_timestamp and add revision_uid.
+ $query = parent::buildQuery($ids, $conditions, $revision_id);
+ $fields =& $query->getFields();
+ unset($fields['timestamp']);
+ $query->addField('revision', 'timestamp', 'revision_timestamp');
+ $fields['uid']['table'] = 'base';
+ $query->addField('revision', 'uid', 'revision_uid');
+ return $query;
+ }
+}
+
+/**
+ * Implements hook_file_download_access().
+ */
+function node_file_download_access($field, $entity_type, $entity) {
+ if ($entity_type == 'node') {
+ return node_access('view', $entity);
+ }
+}
diff --git a/kolab.org/www/drupal-7.26/modules/node/node.pages.inc b/kolab.org/www/drupal-7.26/modules/node/node.pages.inc
new file mode 100644
index 0000000..6267463
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/node/node.pages.inc
@@ -0,0 +1,678 @@
+<?php
+
+/**
+ * @file
+ * Page callbacks for adding, editing, deleting, and revisions management for content.
+ */
+
+/**
+ * Menu callback; presents the node editing form.
+ */
+function node_page_edit($node) {
+ $type_name = node_type_get_name($node);
+ drupal_set_title(t('<em>Edit @type</em> @title', array('@type' => $type_name, '@title' => $node->title)), PASS_THROUGH);
+ return drupal_get_form($node->type . '_node_form', $node);
+}
+
+/**
+ * Page callback: Displays add content links for available content types.
+ *
+ * Redirects to node/add/[type] if only one content type is available.
+ *
+ * @see node_menu()
+ */
+function node_add_page() {
+ $item = menu_get_item();
+ $content = system_admin_menu_block($item);
+ // Bypass the node/add listing if only one content type is available.
+ if (count($content) == 1) {
+ $item = array_shift($content);
+ drupal_goto($item['href']);
+ }
+ return theme('node_add_list', array('content' => $content));
+}
+
+/**
+ * Returns HTML for a list of available node types for node creation.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - content: An array of content types.
+ *
+ * @ingroup themeable
+ */
+function theme_node_add_list($variables) {
+ $content = $variables['content'];
+ $output = '';
+
+ if ($content) {
+ $output = '<dl class="node-type-list">';
+ foreach ($content as $item) {
+ $output .= '<dt>' . l($item['title'], $item['href'], $item['localized_options']) . '</dt>';
+ $output .= '<dd>' . filter_xss_admin($item['description']) . '</dd>';
+ }
+ $output .= '</dl>';
+ }
+ else {
+ $output = '<p>' . t('You have not created any content types yet. Go to the <a href="@create-content">content type creation page</a> to add a new content type.', array('@create-content' => url('admin/structure/types/add'))) . '</p>';
+ }
+ return $output;
+}
+
+
+/**
+ * Returns a node submission form.
+ *
+ * @param $type
+ * The node type for the submitted node.
+ *
+ * @return
+ * The themed form.
+ */
+function node_add($type) {
+ global $user;
+
+ $types = node_type_get_types();
+ $node = (object) array('uid' => $user->uid, 'name' => (isset($user->name) ? $user->name : ''), 'type' => $type, 'language' => LANGUAGE_NONE);
+ drupal_set_title(t('Create @name', array('@name' => $types[$type]->name)), PASS_THROUGH);
+ $output = drupal_get_form($type . '_node_form', $node);
+
+ return $output;
+}
+
+/**
+ * Form validation handler for node_form().
+ *
+ * @see node_form()
+ * @see node_form_submit()
+ */
+function node_form_validate($form, &$form_state) {
+ // $form_state['node'] contains the actual entity being edited, but we must
+ // not update it with form values that have not yet been validated, so we
+ // create a pseudo-entity to use during validation.
+ $node = (object) $form_state['values'];
+ node_validate($node, $form, $form_state);
+ entity_form_field_validate('node', $form, $form_state);
+}
+
+/**
+ * Form constructor for the node add/edit form.
+ *
+ * @see node_form_validate()
+ * @see node_form_submit()
+ * @see node_form_build_preview()
+ * @see node_form_delete_submit()
+ * @ingroup forms
+ */
+function node_form($form, &$form_state, $node) {
+ global $user;
+
+ // During initial form build, add the node entity to the form state for use
+ // during form building and processing. During a rebuild, use what is in the
+ // form state.
+ if (!isset($form_state['node'])) {
+ if (!isset($node->title)) {
+ $node->title = NULL;
+ }
+ node_object_prepare($node);
+ $form_state['node'] = $node;
+ }
+ else {
+ $node = $form_state['node'];
+ }
+
+ // Some special stuff when previewing a node.
+ if (isset($form_state['node_preview'])) {
+ $form['#prefix'] = $form_state['node_preview'];
+ $node->in_preview = TRUE;
+ }
+ else {
+ unset($node->in_preview);
+ }
+
+ // Identify this as a node edit form.
+ // @todo D8: Remove. Modules can implement hook_form_BASE_FORM_ID_alter() now.
+ $form['#node_edit_form'] = TRUE;
+
+ $form['#attributes']['class'][] = 'node-form';
+ if (!empty($node->type)) {
+ $form['#attributes']['class'][] = 'node-' . $node->type . '-form';
+ }
+
+ // Basic node information.
+ // These elements are just values so they are not even sent to the client.
+ foreach (array('nid', 'vid', 'uid', 'created', 'type', 'language') as $key) {
+ $form[$key] = array(
+ '#type' => 'value',
+ '#value' => isset($node->$key) ? $node->$key : NULL,
+ );
+ }
+
+ // Changed must be sent to the client, for later overwrite error checking.
+ $form['changed'] = array(
+ '#type' => 'hidden',
+ '#default_value' => isset($node->changed) ? $node->changed : NULL,
+ );
+ // Invoke hook_form() to get the node-specific bits. Can't use node_invoke(),
+ // because hook_form() needs to be able to receive $form_state by reference.
+ // @todo hook_form() implementations are unable to add #validate or #submit
+ // handlers to the form buttons below. Remove hook_form() entirely.
+ $function = node_type_get_base($node) . '_form';
+ if (function_exists($function) && ($extra = $function($node, $form_state))) {
+ $form = array_merge_recursive($form, $extra);
+ }
+ // If the node type has a title, and the node type form defined no special
+ // weight for it, we default to a weight of -5 for consistency.
+ if (isset($form['title']) && !isset($form['title']['#weight'])) {
+ $form['title']['#weight'] = -5;
+ }
+ // @todo D8: Remove. Modules should access the node using $form_state['node'].
+ $form['#node'] = $node;
+
+ $form['additional_settings'] = array(
+ '#type' => 'vertical_tabs',
+ '#weight' => 99,
+ );
+
+ // Add a log field if the "Create new revision" option is checked, or if the
+ // current user has the ability to check that option.
+ $form['revision_information'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Revision information'),
+ '#collapsible' => TRUE,
+ // Collapsed by default when "Create new revision" is unchecked
+ '#collapsed' => !$node->revision,
+ '#group' => 'additional_settings',
+ '#attributes' => array(
+ 'class' => array('node-form-revision-information'),
+ ),
+ '#attached' => array(
+ 'js' => array(drupal_get_path('module', 'node') . '/node.js'),
+ ),
+ '#weight' => 20,
+ '#access' => $node->revision || user_access('administer nodes'),
+ );
+ $form['revision_information']['revision'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Create new revision'),
+ '#default_value' => $node->revision,
+ '#access' => user_access('administer nodes'),
+ );
+ // Check the revision log checkbox when the log textarea is filled in.
+ // This must not happen if "Create new revision" is enabled by default, since
+ // the state would auto-disable the checkbox otherwise.
+ if (!$node->revision) {
+ $form['revision_information']['revision']['#states'] = array(
+ 'checked' => array(
+ 'textarea[name="log"]' => array('empty' => FALSE),
+ ),
+ );
+ }
+ $form['revision_information']['log'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Revision log message'),
+ '#rows' => 4,
+ '#default_value' => !empty($node->log) ? $node->log : '',
+ '#description' => t('Provide an explanation of the changes you are making. This will help other authors understand your motivations.'),
+ );
+
+ // Node author information for administrators
+ $form['author'] = array(
+ '#type' => 'fieldset',
+ '#access' => user_access('administer nodes'),
+ '#title' => t('Authoring information'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#group' => 'additional_settings',
+ '#attributes' => array(
+ 'class' => array('node-form-author'),
+ ),
+ '#attached' => array(
+ 'js' => array(
+ drupal_get_path('module', 'node') . '/node.js',
+ array(
+ 'type' => 'setting',
+ 'data' => array('anonymous' => variable_get('anonymous', t('Anonymous'))),
+ ),
+ ),
+ ),
+ '#weight' => 90,
+ );
+ $form['author']['name'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Authored by'),
+ '#maxlength' => 60,
+ '#autocomplete_path' => 'user/autocomplete',
+ '#default_value' => !empty($node->name) ? $node->name : '',
+ '#weight' => -1,
+ '#description' => t('Leave blank for %anonymous.', array('%anonymous' => variable_get('anonymous', t('Anonymous')))),
+ );
+ $form['author']['date'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Authored on'),
+ '#maxlength' => 25,
+ '#description' => t('Format: %time. The date format is YYYY-MM-DD and %timezone is the time zone offset from UTC. Leave blank to use the time of form submission.', array('%time' => !empty($node->date) ? date_format(date_create($node->date), 'Y-m-d H:i:s O') : format_date($node->created, 'custom', 'Y-m-d H:i:s O'), '%timezone' => !empty($node->date) ? date_format(date_create($node->date), 'O') : format_date($node->created, 'custom', 'O'))),
+ '#default_value' => !empty($node->date) ? $node->date : '',
+ );
+
+ // Node options for administrators
+ $form['options'] = array(
+ '#type' => 'fieldset',
+ '#access' => user_access('administer nodes'),
+ '#title' => t('Publishing options'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#group' => 'additional_settings',
+ '#attributes' => array(
+ 'class' => array('node-form-options'),
+ ),
+ '#attached' => array(
+ 'js' => array(drupal_get_path('module', 'node') . '/node.js'),
+ ),
+ '#weight' => 95,
+ );
+ $form['options']['status'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Published'),
+ '#default_value' => $node->status,
+ );
+ $form['options']['promote'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Promoted to front page'),
+ '#default_value' => $node->promote,
+ );
+ $form['options']['sticky'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Sticky at top of lists'),
+ '#default_value' => $node->sticky,
+ );
+
+ // Add the buttons.
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#access' => variable_get('node_preview_' . $node->type, DRUPAL_OPTIONAL) != DRUPAL_REQUIRED || (!form_get_errors() && isset($form_state['node_preview'])),
+ '#value' => t('Save'),
+ '#weight' => 5,
+ '#submit' => array('node_form_submit'),
+ );
+ $form['actions']['preview'] = array(
+ '#access' => variable_get('node_preview_' . $node->type, DRUPAL_OPTIONAL) != DRUPAL_DISABLED,
+ '#type' => 'submit',
+ '#value' => t('Preview'),
+ '#weight' => 10,
+ '#submit' => array('node_form_build_preview'),
+ );
+ if (!empty($node->nid) && node_access('delete', $node)) {
+ $form['actions']['delete'] = array(
+ '#type' => 'submit',
+ '#value' => t('Delete'),
+ '#weight' => 15,
+ '#submit' => array('node_form_delete_submit'),
+ );
+ }
+ // This form uses a button-level #submit handler for the form's main submit
+ // action. node_form_submit() manually invokes all form-level #submit handlers
+ // of the form. Without explicitly setting #submit, Form API would auto-detect
+ // node_form_submit() as submit handler, but that is the button-level #submit
+ // handler for the 'Save' action. To maintain backwards compatibility, a
+ // #submit handler is auto-suggested for custom node type modules.
+ $form['#validate'][] = 'node_form_validate';
+ if (!isset($form['#submit']) && function_exists($node->type . '_node_form_submit')) {
+ $form['#submit'][] = $node->type . '_node_form_submit';
+ }
+ $form += array('#submit' => array());
+
+ field_attach_form('node', $node, $form, $form_state, entity_language('node', $node));
+ return $form;
+}
+
+/**
+ * Form submission handler for node_form().
+ *
+ * Handles the 'Delete' button on the node form.
+ *
+ * @see node_form()
+ * @see node_form_validate()
+ */
+function node_form_delete_submit($form, &$form_state) {
+ $destination = array();
+ if (isset($_GET['destination'])) {
+ $destination = drupal_get_destination();
+ unset($_GET['destination']);
+ }
+ $node = $form['#node'];
+ $form_state['redirect'] = array('node/' . $node->nid . '/delete', array('query' => $destination));
+}
+
+/**
+ * Form submission handler for node_form().
+ *
+ * Handles the 'Preview' button on the node form.
+ *
+ * @see node_form()
+ * @see node_form_validate()
+ */
+function node_form_build_preview($form, &$form_state) {
+ $node = node_form_submit_build_node($form, $form_state);
+ $form_state['node_preview'] = node_preview($node);
+ $form_state['rebuild'] = TRUE;
+}
+
+/**
+ * Generates a node preview.
+ *
+ * @param $node
+ * The node to preview.
+ *
+ * @return
+ * An HTML-formatted string of a node preview.
+ *
+ * @see node_form_build_preview()
+ */
+function node_preview($node) {
+ if (node_access('create', $node) || node_access('update', $node)) {
+ _field_invoke_multiple('load', 'node', array($node->nid => $node));
+ // Load the user's name when needed.
+ if (isset($node->name)) {
+ // The use of isset() is mandatory in the context of user IDs, because
+ // user ID 0 denotes the anonymous user.
+ if ($user = user_load_by_name($node->name)) {
+ $node->uid = $user->uid;
+ $node->picture = $user->picture;
+ }
+ else {
+ $node->uid = 0; // anonymous user
+ }
+ }
+ elseif ($node->uid) {
+ $user = user_load($node->uid);
+ $node->name = $user->name;
+ $node->picture = $user->picture;
+ }
+
+ $node->changed = REQUEST_TIME;
+ $nodes = array($node->nid => $node);
+ field_attach_prepare_view('node', $nodes, 'full');
+
+ // Display a preview of the node.
+ if (!form_get_errors()) {
+ $node->in_preview = TRUE;
+ $output = theme('node_preview', array('node' => $node));
+ unset($node->in_preview);
+ }
+ drupal_set_title(t('Preview'), PASS_THROUGH);
+
+ return $output;
+ }
+}
+
+/**
+ * Returns HTML for a node preview for display during node creation and editing.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - node: The node object which is being previewed.
+ *
+ * @see node_preview()
+ * @ingroup themeable
+ */
+function theme_node_preview($variables) {
+ $node = $variables['node'];
+
+ $output = '<div class="preview">';
+
+ $preview_trimmed_version = FALSE;
+
+ $elements = node_view(clone $node, 'teaser');
+ $trimmed = drupal_render($elements);
+ $elements = node_view($node, 'full');
+ $full = drupal_render($elements);
+
+ // Do we need to preview trimmed version of post as well as full version?
+ if ($trimmed != $full) {
+ 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>'));
+ $output .= '<h3>' . t('Preview trimmed version') . '</h3>';
+ $output .= $trimmed;
+ $output .= '<h3>' . t('Preview full version') . '</h3>';
+ $output .= $full;
+ }
+ else {
+ $output .= $full;
+ }
+ $output .= "</div>\n";
+
+ return $output;
+}
+
+/**
+ * Form submission handler for node_form().
+ *
+ * @see node_form()
+ * @see node_form_validate()
+ */
+function node_form_submit($form, &$form_state) {
+ $node = node_form_submit_build_node($form, $form_state);
+ $insert = empty($node->nid);
+ node_save($node);
+ $node_link = l(t('view'), 'node/' . $node->nid);
+ $watchdog_args = array('@type' => $node->type, '%title' => $node->title);
+ $t_args = array('@type' => node_type_get_name($node), '%title' => $node->title);
+
+ if ($insert) {
+ watchdog('content', '@type: added %title.', $watchdog_args, WATCHDOG_NOTICE, $node_link);
+ drupal_set_message(t('@type %title has been created.', $t_args));
+ }
+ else {
+ watchdog('content', '@type: updated %title.', $watchdog_args, WATCHDOG_NOTICE, $node_link);
+ drupal_set_message(t('@type %title has been updated.', $t_args));
+ }
+ if ($node->nid) {
+ $form_state['values']['nid'] = $node->nid;
+ $form_state['nid'] = $node->nid;
+ $form_state['redirect'] = node_access('view', $node) ? 'node/' . $node->nid : '<front>';
+ }
+ else {
+ // In the unlikely case something went wrong on save, the node will be
+ // rebuilt and node form redisplayed the same way as in preview.
+ drupal_set_message(t('The post could not be saved.'), 'error');
+ $form_state['rebuild'] = TRUE;
+ }
+ // Clear the page and block caches.
+ cache_clear_all();
+}
+
+/**
+ * Updates the form state's node entity by processing this submission's values.
+ *
+ * This is the default builder function for the node form. It is called
+ * during the "Save" and "Preview" submit handlers to retrieve the entity to
+ * save or preview. This function can also be called by a "Next" button of a
+ * wizard to update the form state's entity with the current step's values
+ * before proceeding to the next step.
+ *
+ * @see node_form()
+ */
+function node_form_submit_build_node($form, &$form_state) {
+ // @todo Legacy support for modules that extend the node form with form-level
+ // submit handlers that adjust $form_state['values'] prior to those values
+ // being used to update the entity. Module authors are encouraged to instead
+ // adjust the node directly within a hook_node_submit() implementation. For
+ // Drupal 8, evaluate whether the pattern of triggering form-level submit
+ // handlers during button-level submit processing is worth supporting
+ // properly, and if so, add a Form API function for doing so.
+ unset($form_state['submit_handlers']);
+ form_execute_handlers('submit', $form, $form_state);
+
+ $node = $form_state['node'];
+ entity_form_submit_build_entity('node', $node, $form, $form_state);
+
+ node_submit($node);
+ foreach (module_implements('node_submit') as $module) {
+ $function = $module . '_node_submit';
+ $function($node, $form, $form_state);
+ }
+ return $node;
+}
+
+/**
+ * Form constructor for the node deletion confirmation form.
+ *
+ * @see node_delete_confirm_submit()
+ */
+function node_delete_confirm($form, &$form_state, $node) {
+ $form['#node'] = $node;
+ // Always provide entity id in the same form key as in the entity edit form.
+ $form['nid'] = array('#type' => 'value', '#value' => $node->nid);
+ return confirm_form($form,
+ t('Are you sure you want to delete %title?', array('%title' => $node->title)),
+ 'node/' . $node->nid,
+ t('This action cannot be undone.'),
+ t('Delete'),
+ t('Cancel')
+ );
+}
+
+/**
+ * Executes node deletion.
+ *
+ * @see node_delete_confirm()
+ */
+function node_delete_confirm_submit($form, &$form_state) {
+ if ($form_state['values']['confirm']) {
+ $node = node_load($form_state['values']['nid']);
+ node_delete($form_state['values']['nid']);
+ cache_clear_all();
+ watchdog('content', '@type: deleted %title.', array('@type' => $node->type, '%title' => $node->title));
+ drupal_set_message(t('@type %title has been deleted.', array('@type' => node_type_get_name($node), '%title' => $node->title)));
+ }
+
+ $form_state['redirect'] = '<front>';
+}
+
+/**
+ * Generates an overview table of older revisions of a node.
+ *
+ * @param $node
+ * A node object.
+ *
+ * @return array
+ * An array as expected by drupal_render().
+ *
+ * @see node_menu()
+ */
+function node_revision_overview($node) {
+ drupal_set_title(t('Revisions for %title', array('%title' => $node->title)), PASS_THROUGH);
+
+ $header = array(t('Revision'), array('data' => t('Operations'), 'colspan' => 2));
+
+ $revisions = node_revision_list($node);
+
+ $rows = array();
+ $revert_permission = FALSE;
+ if ((user_access('revert revisions') || user_access('administer nodes')) && node_access('update', $node)) {
+ $revert_permission = TRUE;
+ }
+ $delete_permission = FALSE;
+ if ((user_access('delete revisions') || user_access('administer nodes')) && node_access('delete', $node)) {
+ $delete_permission = TRUE;
+ }
+ foreach ($revisions as $revision) {
+ $row = array();
+ $operations = array();
+
+ if ($revision->current_vid > 0) {
+ $row[] = array('data' => t('!date by !username', array('!date' => l(format_date($revision->timestamp, 'short'), "node/$node->nid"), '!username' => theme('username', array('account' => $revision))))
+ . (($revision->log != '') ? '<p class="revision-log">' . filter_xss($revision->log) . '</p>' : ''),
+ 'class' => array('revision-current'));
+ $operations[] = array('data' => drupal_placeholder(t('current revision')), 'class' => array('revision-current'), 'colspan' => 2);
+ }
+ else {
+ $row[] = t('!date by !username', array('!date' => l(format_date($revision->timestamp, 'short'), "node/$node->nid/revisions/$revision->vid/view"), '!username' => theme('username', array('account' => $revision))))
+ . (($revision->log != '') ? '<p class="revision-log">' . filter_xss($revision->log) . '</p>' : '');
+ if ($revert_permission) {
+ $operations[] = l(t('revert'), "node/$node->nid/revisions/$revision->vid/revert");
+ }
+ if ($delete_permission) {
+ $operations[] = l(t('delete'), "node/$node->nid/revisions/$revision->vid/delete");
+ }
+ }
+ $rows[] = array_merge($row, $operations);
+ }
+
+ $build['node_revisions_table'] = array(
+ '#theme' => 'table',
+ '#rows' => $rows,
+ '#header' => $header,
+ );
+
+ return $build;
+}
+
+/**
+ * Asks for confirmation of the reversion to prevent against CSRF attacks.
+ *
+ * @param int $node_revision
+ * The node revision ID.
+ *
+ * @return array
+ * An array as expected by drupal_render().
+ *
+ * @see node_menu()
+ * @see node_revision_revert_confirm_submit()
+ * @ingroup forms
+ */
+function node_revision_revert_confirm($form, $form_state, $node_revision) {
+ $form['#node_revision'] = $node_revision;
+ 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'));
+}
+
+/**
+ * Form submission handler for node_revision_revert_confirm().
+ */
+function node_revision_revert_confirm_submit($form, &$form_state) {
+ $node_revision = $form['#node_revision'];
+ $node_revision->revision = 1;
+ $node_revision->log = t('Copy of the revision from %date.', array('%date' => format_date($node_revision->revision_timestamp)));
+
+ node_save($node_revision);
+
+ watchdog('content', '@type: reverted %title revision %revision.', array('@type' => $node_revision->type, '%title' => $node_revision->title, '%revision' => $node_revision->vid));
+ 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))));
+ $form_state['redirect'] = 'node/' . $node_revision->nid . '/revisions';
+}
+
+/**
+ * Form constructor for the revision deletion confirmation form.
+ *
+ * This form prevents against CSRF attacks.
+ *
+ * @param $node_revision
+ * The node revision ID.
+ *
+ * @return
+ * An array as expected by drupal_render().
+ *
+ * @see node_menu()
+ * @see node_revision_delete_confirm_submit()
+ * @ingroup forms
+ */
+function node_revision_delete_confirm($form, $form_state, $node_revision) {
+ $form['#node_revision'] = $node_revision;
+ 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'));
+}
+
+/**
+ * Form submission handler for node_revision_delete_confirm().
+ */
+function node_revision_delete_confirm_submit($form, &$form_state) {
+ $node_revision = $form['#node_revision'];
+ node_revision_delete($node_revision->vid);
+
+ watchdog('content', '@type: deleted %title revision %revision.', array('@type' => $node_revision->type, '%title' => $node_revision->title, '%revision' => $node_revision->vid));
+ 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)));
+ $form_state['redirect'] = 'node/' . $node_revision->nid;
+ if (db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid', array(':nid' => $node_revision->nid))->fetchField() > 1) {
+ $form_state['redirect'] .= '/revisions';
+ }
+}
diff --git a/kolab.org/www/drupal-7.26/modules/node/node.test b/kolab.org/www/drupal-7.26/modules/node/node.test
new file mode 100644
index 0000000..bfe3717
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/node/node.test
@@ -0,0 +1,2848 @@
+<?php
+
+/**
+ * @file
+ * Tests for node.module.
+ */
+
+/**
+ * Defines a base class for testing the Node module.
+ */
+class NodeWebTestCase extends DrupalWebTestCase {
+ function setUp() {
+ $modules = func_get_args();
+ if (isset($modules[0]) && is_array($modules[0])) {
+ $modules = $modules[0];
+ }
+ $modules[] = 'node';
+ parent::setUp($modules);
+
+ // Create Basic page and Article node types.
+ if ($this->profile != 'standard') {
+ $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
+ $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
+ }
+ }
+}
+
+/**
+ * Test the node_load_multiple() function.
+ */
+class NodeLoadMultipleTestCase extends DrupalWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Load multiple nodes',
+ 'description' => 'Test the loading of multiple nodes.',
+ 'group' => 'Node',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ $web_user = $this->drupalCreateUser(array('create article content', 'create page content'));
+ $this->drupalLogin($web_user);
+ }
+
+ /**
+ * Create four nodes and ensure they're loaded correctly.
+ */
+ function testNodeMultipleLoad() {
+ $node1 = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1));
+ $node2 = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1));
+ $node3 = $this->drupalCreateNode(array('type' => 'article', 'promote' => 0));
+ $node4 = $this->drupalCreateNode(array('type' => 'page', 'promote' => 0));
+
+ // Confirm that promoted nodes appear in the default node listing.
+ $this->drupalGet('node');
+ $this->assertText($node1->title, 'Node title appears on the default listing.');
+ $this->assertText($node2->title, 'Node title appears on the default listing.');
+ $this->assertNoText($node3->title, 'Node title does not appear in the default listing.');
+ $this->assertNoText($node4->title, 'Node title does not appear in the default listing.');
+
+ // Load nodes with only a condition. Nodes 3 and 4 will be loaded.
+ $nodes = node_load_multiple(NULL, array('promote' => 0));
+ $this->assertEqual($node3->title, $nodes[$node3->nid]->title, 'Node was loaded.');
+ $this->assertEqual($node4->title, $nodes[$node4->nid]->title, 'Node was loaded.');
+ $count = count($nodes);
+ $this->assertTrue($count == 2, format_string('@count nodes loaded.', array('@count' => $count)));
+
+ // Load nodes by nid. Nodes 1, 2 and 4 will be loaded.
+ $nodes = node_load_multiple(array(1, 2, 4));
+ $count = count($nodes);
+ $this->assertTrue(count($nodes) == 3, format_string('@count nodes loaded', array('@count' => $count)));
+ $this->assertTrue(isset($nodes[$node1->nid]), 'Node is correctly keyed in the array');
+ $this->assertTrue(isset($nodes[$node2->nid]), 'Node is correctly keyed in the array');
+ $this->assertTrue(isset($nodes[$node4->nid]), 'Node is correctly keyed in the array');
+ foreach ($nodes as $node) {
+ $this->assertTrue(is_object($node), 'Node is an object');
+ }
+
+ // Load nodes by nid, where type = article. Nodes 1, 2 and 3 will be loaded.
+ $nodes = node_load_multiple(array(1, 2, 3, 4), array('type' => 'article'));
+ $count = count($nodes);
+ $this->assertTrue($count == 3, format_string('@count nodes loaded', array('@count' => $count)));
+ $this->assertEqual($nodes[$node1->nid]->title, $node1->title, 'Node successfully loaded.');
+ $this->assertEqual($nodes[$node2->nid]->title, $node2->title, 'Node successfully loaded.');
+ $this->assertEqual($nodes[$node3->nid]->title, $node3->title, 'Node successfully loaded.');
+ $this->assertFalse(isset($nodes[$node4->nid]));
+
+ // Now that all nodes have been loaded into the static cache, ensure that
+ // they are loaded correctly again when a condition is passed.
+ $nodes = node_load_multiple(array(1, 2, 3, 4), array('type' => 'article'));
+ $count = count($nodes);
+ $this->assertTrue($count == 3, format_string('@count nodes loaded.', array('@count' => $count)));
+ $this->assertEqual($nodes[$node1->nid]->title, $node1->title, 'Node successfully loaded');
+ $this->assertEqual($nodes[$node2->nid]->title, $node2->title, 'Node successfully loaded');
+ $this->assertEqual($nodes[$node3->nid]->title, $node3->title, 'Node successfully loaded');
+ $this->assertFalse(isset($nodes[$node4->nid]), 'Node was not loaded');
+
+ // Load nodes by nid, where type = article and promote = 0.
+ $nodes = node_load_multiple(array(1, 2, 3, 4), array('type' => 'article', 'promote' => 0));
+ $count = count($nodes);
+ $this->assertTrue($count == 1, format_string('@count node loaded', array('@count' => $count)));
+ $this->assertEqual($nodes[$node3->nid]->title, $node3->title, 'Node successfully loaded.');
+ }
+}
+
+/**
+ * Tests for the hooks invoked during node_load().
+ */
+class NodeLoadHooksTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node load hooks',
+ 'description' => 'Test the hooks invoked when a node is being loaded.',
+ 'group' => 'Node',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('node_test');
+ }
+
+ /**
+ * Test that hook_node_load() is invoked correctly.
+ */
+ function testHookNodeLoad() {
+ // Create some sample articles and pages.
+ $node1 = $this->drupalCreateNode(array('type' => 'article', 'status' => NODE_PUBLISHED));
+ $node2 = $this->drupalCreateNode(array('type' => 'article', 'status' => NODE_PUBLISHED));
+ $node3 = $this->drupalCreateNode(array('type' => 'article', 'status' => NODE_NOT_PUBLISHED));
+ $node4 = $this->drupalCreateNode(array('type' => 'page', 'status' => NODE_NOT_PUBLISHED));
+
+ // Check that when a set of nodes that only contains articles is loaded,
+ // the properties added to the node by node_test_load_node() correctly
+ // reflect the expected values.
+ $nodes = node_load_multiple(array(), array('status' => NODE_PUBLISHED));
+ $loaded_node = end($nodes);
+ $this->assertEqual($loaded_node->node_test_loaded_nids, array($node1->nid, $node2->nid), 'hook_node_load() received the correct list of node IDs the first time it was called.');
+ $this->assertEqual($loaded_node->node_test_loaded_types, array('article'), 'hook_node_load() received the correct list of node types the first time it was called.');
+
+ // Now, as part of the same page request, load a set of nodes that contain
+ // both articles and pages, and make sure the parameters passed to
+ // node_test_node_load() are correctly updated.
+ $nodes = node_load_multiple(array(), array('status' => NODE_NOT_PUBLISHED));
+ $loaded_node = end($nodes);
+ $this->assertEqual($loaded_node->node_test_loaded_nids, array($node3->nid, $node4->nid), 'hook_node_load() received the correct list of node IDs the second time it was called.');
+ $this->assertEqual($loaded_node->node_test_loaded_types, array('article', 'page'), 'hook_node_load() received the correct list of node types the second time it was called.');
+ }
+}
+
+/**
+ * Tests the node revision functionality.
+ */
+class NodeRevisionsTestCase extends DrupalWebTestCase {
+
+ /**
+ * Nodes used by the test.
+ *
+ * @var array
+ */
+ protected $nodes;
+
+ /**
+ * The revision messages for node revisions created in the test.
+ *
+ * @var array
+ */
+ protected $logs;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node revisions',
+ 'description' => 'Create a node with revisions and test viewing, saving, reverting, and deleting revisions.',
+ 'group' => 'Node',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ // Create and login user.
+ $web_user = $this->drupalCreateUser(array('view revisions', 'revert revisions', 'edit any page content',
+ 'delete revisions', 'delete any page content'));
+ $this->drupalLogin($web_user);
+
+ // Create initial node.
+ $node = $this->drupalCreateNode();
+ $settings = get_object_vars($node);
+ $settings['revision'] = 1;
+
+ $nodes = array();
+ $logs = array();
+
+ // Get original node.
+ $nodes[] = $node;
+
+ // Create three revisions.
+ $revision_count = 3;
+ for ($i = 0; $i < $revision_count; $i++) {
+ $logs[] = $settings['log'] = $this->randomName(32);
+
+ // Create revision with random title and body and update variables.
+ $this->drupalCreateNode($settings);
+ $node = node_load($node->nid); // Make sure we get revision information.
+ $settings = get_object_vars($node);
+
+ $nodes[] = $node;
+ }
+
+ $this->nodes = $nodes;
+ $this->logs = $logs;
+ }
+
+ /**
+ * Checks node revision related operations.
+ */
+ function testRevisions() {
+ $nodes = $this->nodes;
+ $logs = $this->logs;
+
+ // Get last node for simple checks.
+ $node = $nodes[3];
+
+ // Confirm the correct revision text appears on "view revisions" page.
+ $this->drupalGet("node/$node->nid/revisions/$node->vid/view");
+ $this->assertText($node->body[LANGUAGE_NONE][0]['value'], 'Correct text displays for version.');
+
+ // Confirm the correct log message appears on "revisions overview" page.
+ $this->drupalGet("node/$node->nid/revisions");
+ foreach ($logs as $log) {
+ $this->assertText($log, 'Log message found.');
+ }
+
+ // Confirm that revisions revert properly.
+ $this->drupalPost("node/$node->nid/revisions/{$nodes[1]->vid}/revert", array(), t('Revert'));
+ $this->assertRaw(t('@type %title has been reverted back to the revision from %revision-date.',
+ array('@type' => 'Basic page', '%title' => $nodes[1]->title,
+ '%revision-date' => format_date($nodes[1]->revision_timestamp))), 'Revision reverted.');
+ $reverted_node = node_load($node->nid);
+ $this->assertTrue(($nodes[1]->body[LANGUAGE_NONE][0]['value'] == $reverted_node->body[LANGUAGE_NONE][0]['value']), 'Node reverted correctly.');
+
+ // Confirm revisions delete properly.
+ $this->drupalPost("node/$node->nid/revisions/{$nodes[1]->vid}/delete", array(), t('Delete'));
+ $this->assertRaw(t('Revision from %revision-date of @type %title has been deleted.',
+ array('%revision-date' => format_date($nodes[1]->revision_timestamp),
+ '@type' => 'Basic page', '%title' => $nodes[1]->title)), 'Revision deleted.');
+ $this->assertTrue(db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid and vid = :vid', array(':nid' => $node->nid, ':vid' => $nodes[1]->vid))->fetchField() == 0, 'Revision not found.');
+ }
+
+ /**
+ * Checks that revisions are correctly saved without log messages.
+ */
+ function testNodeRevisionWithoutLogMessage() {
+ // Create a node with an initial log message.
+ $log = $this->randomName(10);
+ $node = $this->drupalCreateNode(array('log' => $log));
+
+ // Save over the same revision and explicitly provide an empty log message
+ // (for example, to mimic the case of a node form submitted with no text in
+ // the "log message" field), and check that the original log message is
+ // preserved.
+ $new_title = $this->randomName(10) . 'testNodeRevisionWithoutLogMessage1';
+ $updated_node = (object) array(
+ 'nid' => $node->nid,
+ 'vid' => $node->vid,
+ 'uid' => $node->uid,
+ 'type' => $node->type,
+ 'title' => $new_title,
+ 'log' => '',
+ );
+ node_save($updated_node);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertText($new_title, 'New node title appears on the page.');
+ $node_revision = node_load($node->nid, NULL, TRUE);
+ $this->assertEqual($node_revision->log, $log, 'After an existing node revision is re-saved without a log message, the original log message is preserved.');
+
+ // Create another node with an initial log message.
+ $node = $this->drupalCreateNode(array('log' => $log));
+
+ // Save a new node revision without providing a log message, and check that
+ // this revision has an empty log message.
+ $new_title = $this->randomName(10) . 'testNodeRevisionWithoutLogMessage2';
+ $updated_node = (object) array(
+ 'nid' => $node->nid,
+ 'vid' => $node->vid,
+ 'uid' => $node->uid,
+ 'type' => $node->type,
+ 'title' => $new_title,
+ 'revision' => 1,
+ );
+ node_save($updated_node);
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertText($new_title, 'New node title appears on the page.');
+ $node_revision = node_load($node->nid, NULL, TRUE);
+ $this->assertTrue(empty($node_revision->log), 'After a new node revision is saved with an empty log message, the log message for the node is empty.');
+ }
+}
+
+/**
+ * Tests the node edit functionality.
+ */
+class PageEditTestCase extends DrupalWebTestCase {
+
+ /**
+ * A user with permission to create and edit own page content.
+ *
+ * @var object
+ */
+ protected $web_user;
+
+ /**
+ * A user with permission to bypass node access and administer nodes.
+ *
+ * @var object
+ */
+ protected $admin_user;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node edit',
+ 'description' => 'Create a node and test node edit functionality.',
+ 'group' => 'Node',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ $this->web_user = $this->drupalCreateUser(array('edit own page content', 'create page content'));
+ $this->admin_user = $this->drupalCreateUser(array('bypass node access', 'administer nodes'));
+ }
+
+ /**
+ * Checks node edit functionality.
+ */
+ function testPageEdit() {
+ $this->drupalLogin($this->web_user);
+
+ $langcode = LANGUAGE_NONE;
+ $title_key = "title";
+ $body_key = "body[$langcode][0][value]";
+ // Create node to edit.
+ $edit = array();
+ $edit[$title_key] = $this->randomName(8);
+ $edit[$body_key] = $this->randomName(16);
+ $this->drupalPost('node/add/page', $edit, t('Save'));
+
+ // Check that the node exists in the database.
+ $node = $this->drupalGetNodeByTitle($edit[$title_key]);
+ $this->assertTrue($node, 'Node found in database.');
+
+ // Check that "edit" link points to correct page.
+ $this->clickLink(t('Edit'));
+ $edit_url = url("node/$node->nid/edit", array('absolute' => TRUE));
+ $actual_url = $this->getURL();
+ $this->assertEqual($edit_url, $actual_url, 'On edit page.');
+
+ // Check that the title and body fields are displayed with the correct values.
+ $active = '<span class="element-invisible">' . t('(active tab)') . '</span>';
+ $link_text = t('!local-task-title!active', array('!local-task-title' => t('Edit'), '!active' => $active));
+ $this->assertText(strip_tags($link_text), 0, 'Edit tab found and marked active.');
+ $this->assertFieldByName($title_key, $edit[$title_key], 'Title field displayed.');
+ $this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.');
+
+ // Edit the content of the node.
+ $edit = array();
+ $edit[$title_key] = $this->randomName(8);
+ $edit[$body_key] = $this->randomName(16);
+ // Stay on the current page, without reloading.
+ $this->drupalPost(NULL, $edit, t('Save'));
+
+ // Check that the title and body fields are displayed with the updated values.
+ $this->assertText($edit[$title_key], 'Title displayed.');
+ $this->assertText($edit[$body_key], 'Body displayed.');
+
+ // Login as a second administrator user.
+ $second_web_user = $this->drupalCreateUser(array('administer nodes', 'edit any page content'));
+ $this->drupalLogin($second_web_user);
+ // Edit the same node, creating a new revision.
+ $this->drupalGet("node/$node->nid/edit");
+ $edit = array();
+ $edit['title'] = $this->randomName(8);
+ $edit[$body_key] = $this->randomName(16);
+ $edit['revision'] = TRUE;
+ $this->drupalPost(NULL, $edit, t('Save'));
+
+ // Ensure that the node revision has been created.
+ $revised_node = $this->drupalGetNodeByTitle($edit['title']);
+ $this->assertNotIdentical($node->vid, $revised_node->vid, 'A new revision has been created.');
+ // Ensure that the node author is preserved when it was not changed in the
+ // edit form.
+ $this->assertIdentical($node->uid, $revised_node->uid, 'The node author has been preserved.');
+ // Ensure that the revision authors are different since the revisions were
+ // made by different users.
+ $first_node_version = node_load($node->nid, $node->vid);
+ $second_node_version = node_load($node->nid, $revised_node->vid);
+ $this->assertNotIdentical($first_node_version->revision_uid, $second_node_version->revision_uid, 'Each revision has a distinct user.');
+ }
+
+ /**
+ * Tests changing a node's "authored by" field.
+ */
+ function testPageAuthoredBy() {
+ $this->drupalLogin($this->admin_user);
+
+ // Create node to edit.
+ $langcode = LANGUAGE_NONE;
+ $body_key = "body[$langcode][0][value]";
+ $edit = array();
+ $edit['title'] = $this->randomName(8);
+ $edit[$body_key] = $this->randomName(16);
+ $this->drupalPost('node/add/page', $edit, t('Save'));
+
+ // Check that the node was authored by the currently logged in user.
+ $node = $this->drupalGetNodeByTitle($edit['title']);
+ $this->assertIdentical($node->uid, $this->admin_user->uid, 'Node authored by admin user.');
+
+ // Try to change the 'authored by' field to an invalid user name.
+ $edit = array(
+ 'name' => 'invalid-name',
+ );
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+ $this->assertText('The username invalid-name does not exist.');
+
+ // Change the authored by field to an empty string, which should assign
+ // authorship to the anonymous user (uid 0).
+ $edit['name'] = '';
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+ $node = node_load($node->nid, NULL, TRUE);
+ $this->assertIdentical($node->uid, '0', 'Node authored by anonymous user.');
+
+ // Change the authored by field to another user's name (that is not
+ // logged in).
+ $edit['name'] = $this->web_user->name;
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+ $node = node_load($node->nid, NULL, TRUE);
+ $this->assertIdentical($node->uid, $this->web_user->uid, 'Node authored by normal user.');
+
+ // Check that normal users cannot change the authored by information.
+ $this->drupalLogin($this->web_user);
+ $this->drupalGet('node/' . $node->nid . '/edit');
+ $this->assertNoFieldByName('name');
+ }
+}
+
+/**
+ * Tests the node entity preview functionality.
+ */
+class PagePreviewTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node preview',
+ 'description' => 'Test node preview functionality.',
+ 'group' => 'Node',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ $web_user = $this->drupalCreateUser(array('edit own page content', 'create page content'));
+ $this->drupalLogin($web_user);
+ }
+
+ /**
+ * Checks the node preview functionality.
+ */
+ function testPagePreview() {
+ $langcode = LANGUAGE_NONE;
+ $title_key = "title";
+ $body_key = "body[$langcode][0][value]";
+
+ // Fill in node creation form and preview node.
+ $edit = array();
+ $edit[$title_key] = $this->randomName(8);
+ $edit[$body_key] = $this->randomName(16);
+ $this->drupalPost('node/add/page', $edit, t('Preview'));
+
+ // Check that the preview is displaying the title and body.
+ $this->assertTitle(t('Preview | Drupal'), 'Basic page title is preview.');
+ $this->assertText($edit[$title_key], 'Title displayed.');
+ $this->assertText($edit[$body_key], 'Body displayed.');
+
+ // Check that the title and body fields are displayed with the correct values.
+ $this->assertFieldByName($title_key, $edit[$title_key], 'Title field displayed.');
+ $this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.');
+ }
+
+ /**
+ * Checks the node preview functionality, when using revisions.
+ */
+ function testPagePreviewWithRevisions() {
+ $langcode = LANGUAGE_NONE;
+ $title_key = "title";
+ $body_key = "body[$langcode][0][value]";
+ // Force revision on "Basic page" content.
+ variable_set('node_options_page', array('status', 'revision'));
+
+ // Fill in node creation form and preview node.
+ $edit = array();
+ $edit[$title_key] = $this->randomName(8);
+ $edit[$body_key] = $this->randomName(16);
+ $edit['log'] = $this->randomName(32);
+ $this->drupalPost('node/add/page', $edit, t('Preview'));
+
+ // Check that the preview is displaying the title and body.
+ $this->assertTitle(t('Preview | Drupal'), 'Basic page title is preview.');
+ $this->assertText($edit[$title_key], 'Title displayed.');
+ $this->assertText($edit[$body_key], 'Body displayed.');
+
+ // Check that the title and body fields are displayed with the correct values.
+ $this->assertFieldByName($title_key, $edit[$title_key], 'Title field displayed.');
+ $this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.');
+
+ // Check that the log field has the correct value.
+ $this->assertFieldByName('log', $edit['log'], 'Log field displayed.');
+ }
+}
+
+/**
+ * Tests creating and saving a node.
+ */
+class NodeCreationTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node creation',
+ 'description' => 'Create a node and test saving it.',
+ 'group' => 'Node',
+ );
+ }
+
+ function setUp() {
+ // Enable dummy module that implements hook_node_insert for exceptions.
+ parent::setUp('node_test_exception');
+
+ $web_user = $this->drupalCreateUser(array('create page content', 'edit own page content'));
+ $this->drupalLogin($web_user);
+ }
+
+ /**
+ * Creates a "Basic page" node and verifies its consistency in the database.
+ */
+ function testNodeCreation() {
+ // Create a node.
+ $edit = array();
+ $langcode = LANGUAGE_NONE;
+ $edit["title"] = $this->randomName(8);
+ $edit["body[$langcode][0][value]"] = $this->randomName(16);
+ $this->drupalPost('node/add/page', $edit, t('Save'));
+
+ // Check that the Basic page has been created.
+ $this->assertRaw(t('!post %title has been created.', array('!post' => 'Basic page', '%title' => $edit["title"])), 'Basic page created.');
+
+ // Check that the node exists in the database.
+ $node = $this->drupalGetNodeByTitle($edit["title"]);
+ $this->assertTrue($node, 'Node found in database.');
+ }
+
+ /**
+ * Verifies that a transaction rolls back the failed creation.
+ */
+ function testFailedPageCreation() {
+ // Create a node.
+ $edit = array(
+ 'uid' => $this->loggedInUser->uid,
+ 'name' => $this->loggedInUser->name,
+ 'type' => 'page',
+ 'language' => LANGUAGE_NONE,
+ 'title' => 'testing_transaction_exception',
+ );
+
+ try {
+ node_save((object) $edit);
+ $this->fail(t('Expected exception has not been thrown.'));
+ }
+ catch (Exception $e) {
+ $this->pass(t('Expected exception has been thrown.'));
+ }
+
+ if (Database::getConnection()->supportsTransactions()) {
+ // Check that the node does not exist in the database.
+ $node = $this->drupalGetNodeByTitle($edit['title']);
+ $this->assertFalse($node, 'Transactions supported, and node not found in database.');
+ }
+ else {
+ // Check that the node exists in the database.
+ $node = $this->drupalGetNodeByTitle($edit['title']);
+ $this->assertTrue($node, 'Transactions not supported, and node found in database.');
+
+ // Check that the failed rollback was logged.
+ $records = db_query("SELECT wid FROM {watchdog} WHERE message LIKE 'Explicit rollback failed%'")->fetchAll();
+ $this->assertTrue(count($records) > 0, 'Transactions not supported, and rollback error logged to watchdog.');
+ }
+
+ // Check that the rollback error was logged.
+ $records = db_query("SELECT wid FROM {watchdog} WHERE variables LIKE '%Test exception for rollback.%'")->fetchAll();
+ $this->assertTrue(count($records) > 0, 'Rollback explanatory error logged to watchdog.');
+ }
+
+ /**
+ * Create an unpublished node and confirm correct redirect behavior.
+ */
+ function testUnpublishedNodeCreation() {
+ // Set "Basic page" content type to be unpublished by default.
+ variable_set('node_options_page', array());
+ // Set the front page to the default "node" page.
+ variable_set('site_frontpage', 'node');
+
+ // Create a node.
+ $edit = array();
+ $edit["title"] = $this->randomName(8);
+ $edit["body[" . LANGUAGE_NONE . "][0][value]"] = $this->randomName(16);
+ $this->drupalPost('node/add/page', $edit, t('Save'));
+
+ // Check that the user was redirected to the home page.
+ $this->assertText(t('Welcome to Drupal'), t('The user is redirected to the home page.'));
+ }
+}
+
+/**
+ * Tests the functionality of node entity edit permissions.
+ */
+class PageViewTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node edit permissions',
+ 'description' => 'Create a node and test edit permissions.',
+ 'group' => 'Node',
+ );
+ }
+
+ /**
+ * Tests an anonymous and unpermissioned user attempting to edit the node.
+ */
+ function testPageView() {
+ // Create a node to view.
+ $node = $this->drupalCreateNode();
+ $this->assertTrue(node_load($node->nid), 'Node created.');
+
+ // Try to edit with anonymous user.
+ $html = $this->drupalGet("node/$node->nid/edit");
+ $this->assertResponse(403);
+
+ // Create a user without permission to edit node.
+ $web_user = $this->drupalCreateUser(array('access content'));
+ $this->drupalLogin($web_user);
+
+ // Attempt to access edit page.
+ $this->drupalGet("node/$node->nid/edit");
+ $this->assertResponse(403);
+
+ // Create user with permission to edit node.
+ $web_user = $this->drupalCreateUser(array('bypass node access'));
+ $this->drupalLogin($web_user);
+
+ // Attempt to access edit page.
+ $this->drupalGet("node/$node->nid/edit");
+ $this->assertResponse(200);
+ }
+}
+
+/**
+ * Tests the summary length functionality.
+ */
+class SummaryLengthTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Summary length',
+ 'description' => 'Test summary length.',
+ 'group' => 'Node',
+ );
+ }
+
+ /**
+ * Tests the node summary length functionality.
+ */
+ function testSummaryLength() {
+ // Create a node to view.
+ $settings = array(
+ 'body' => array(LANGUAGE_NONE => array(array('value' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vitae arcu at leo cursus laoreet. Curabitur dui tortor, adipiscing malesuada tempor in, bibendum ac diam. Cras non tellus a libero pellentesque condimentum. What is a Drupalism? Suspendisse ac lacus libero. Ut non est vel nisl faucibus interdum nec sed leo. Pellentesque sem risus, vulputate eu semper eget, auctor in libero. Ut fermentum est vitae metus convallis scelerisque. Phasellus pellentesque rhoncus tellus, eu dignissim purus posuere id. Quisque eu fringilla ligula. Morbi ullamcorper, lorem et mattis egestas, tortor neque pretium velit, eget eleifend odio turpis eu purus. Donec vitae metus quis leo pretium tincidunt a pulvinar sem. Morbi adipiscing laoreet mauris vel placerat. Nullam elementum, nisl sit amet scelerisque malesuada, dolor nunc hendrerit quam, eu ultrices erat est in orci. Curabitur feugiat egestas nisl sed accumsan.'))),
+ 'promote' => 1,
+ );
+ $node = $this->drupalCreateNode($settings);
+ $this->assertTrue(node_load($node->nid), 'Node created.');
+
+ // Create user with permission to view the node.
+ $web_user = $this->drupalCreateUser(array('access content', 'administer content types'));
+ $this->drupalLogin($web_user);
+
+ // Attempt to access the front page.
+ $this->drupalGet("node");
+ // The node teaser when it has 600 characters in length
+ $expected = 'What is a Drupalism?';
+ $this->assertRaw($expected, 'Check that the summary is 600 characters in length', 'Node');
+
+ // Change the teaser length for "Basic page" content type.
+ $instance = field_info_instance('node', 'body', $node->type);
+ $instance['display']['teaser']['settings']['trim_length'] = 200;
+ field_update_instance($instance);
+
+ // Attempt to access the front page again and check if the summary is now only 200 characters in length.
+ $this->drupalGet("node");
+ $this->assertNoRaw($expected, 'Check that the summary is not longer than 200 characters', 'Node');
+ }
+}
+
+/**
+ * Tests XSS functionality with a node entity.
+ */
+class NodeTitleXSSTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node title XSS filtering',
+ 'description' => 'Create a node with dangerous tags in its title and test that they are escaped.',
+ 'group' => 'Node',
+ );
+ }
+
+ /**
+ * Tests XSS functionality with a node entity.
+ */
+ function testNodeTitleXSS() {
+ // Prepare a user to do the stuff.
+ $web_user = $this->drupalCreateUser(array('create page content', 'edit any page content'));
+ $this->drupalLogin($web_user);
+
+ $xss = '<script>alert("xss")</script>';
+ $title = $xss . $this->randomName();
+ $edit = array("title" => $title);
+
+ $this->drupalPost('node/add/page', $edit, t('Preview'));
+ $this->assertNoRaw($xss, 'Harmful tags are escaped when previewing a node.');
+
+ $settings = array('title' => $title);
+ $node = $this->drupalCreateNode($settings);
+
+ $this->drupalGet('node/' . $node->nid);
+ // assertTitle() decodes HTML-entities inside the <title> element.
+ $this->assertTitle($edit["title"] . ' | Drupal', 'Title is diplayed when viewing a node.');
+ $this->assertNoRaw($xss, 'Harmful tags are escaped when viewing a node.');
+
+ $this->drupalGet('node/' . $node->nid . '/edit');
+ $this->assertNoRaw($xss, 'Harmful tags are escaped when editing a node.');
+ }
+}
+
+/**
+ * Tests the availability of the syndicate block.
+ */
+class NodeBlockTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Block availability',
+ 'description' => 'Check if the syndicate block is available.',
+ 'group' => 'Node',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ // Create and login user
+ $admin_user = $this->drupalCreateUser(array('administer blocks'));
+ $this->drupalLogin($admin_user);
+ }
+
+ /**
+ * Tests that the "Syndicate" block is shown when enabled.
+ */
+ function testSyndicateBlock() {
+ // Set block title to confirm that the interface is available.
+ $this->drupalPost('admin/structure/block/manage/node/syndicate/configure', array('title' => $this->randomName(8)), t('Save block'));
+ $this->assertText(t('The block configuration has been saved.'), 'Block configuration set.');
+
+ // Set the block to a region to confirm block is available.
+ $edit = array();
+ $edit['blocks[node_syndicate][region]'] = 'footer';
+ $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
+ $this->assertText(t('The block settings have been updated.'), 'Block successfully move to footer region.');
+ }
+}
+
+/**
+ * Checks that the post information displays when enabled for a content type.
+ */
+class NodePostSettingsTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node post information display',
+ 'description' => 'Check that the post information (submitted by Username on date) text displays appropriately.',
+ 'group' => 'Node',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ $web_user = $this->drupalCreateUser(array('create page content', 'administer content types', 'access user profiles'));
+ $this->drupalLogin($web_user);
+ }
+
+ /**
+ * Confirms "Basic page" content type and post information is on a new node.
+ */
+ function testPagePostInfo() {
+
+ // Set "Basic page" content type to display post information.
+ $edit = array();
+ $edit['node_submitted'] = TRUE;
+ $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
+
+ // Create a node.
+ $edit = array();
+ $langcode = LANGUAGE_NONE;
+ $edit["title"] = $this->randomName(8);
+ $edit["body[$langcode][0][value]"] = $this->randomName(16);
+ $this->drupalPost('node/add/page', $edit, t('Save'));
+
+ // Check that the post information is displayed.
+ $node = $this->drupalGetNodeByTitle($edit["title"]);
+ $elements = $this->xpath('//div[contains(@class,:class)]', array(':class' => 'submitted'));
+ $this->assertEqual(count($elements), 1, 'Post information is displayed.');
+ }
+
+ /**
+ * Confirms absence of post information on a new node.
+ */
+ function testPageNotPostInfo() {
+
+ // Set "Basic page" content type to display post information.
+ $edit = array();
+ $edit['node_submitted'] = FALSE;
+ $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
+
+ // Create a node.
+ $edit = array();
+ $langcode = LANGUAGE_NONE;
+ $edit["title"] = $this->randomName(8);
+ $edit["body[$langcode][0][value]"] = $this->randomName(16);
+ $this->drupalPost('node/add/page', $edit, t('Save'));
+
+ // Check that the post information is displayed.
+ $node = $this->drupalGetNodeByTitle($edit["title"]);
+ $this->assertNoRaw('<span class="submitted">', 'Post information is not displayed.');
+ }
+}
+
+/**
+ * Ensures that data added to nodes by other modules appears in RSS feeds.
+ *
+ * Create a node, enable the node_test module to ensure that extra data is
+ * added to the node->content array, then verify that the data appears on the
+ * sitewide RSS feed at rss.xml.
+ */
+class NodeRSSContentTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node RSS Content',
+ 'description' => 'Ensure that data added to nodes by other modules appears in RSS feeds.',
+ 'group' => 'Node',
+ );
+ }
+
+ function setUp() {
+ // Enable dummy module that implements hook_node_view.
+ parent::setUp('node_test');
+
+ // Use bypass node access permission here, because the test class uses
+ // hook_grants_alter() to deny access to everyone on node_access
+ // queries.
+ $user = $this->drupalCreateUser(array('bypass node access', 'access content', 'create article content'));
+ $this->drupalLogin($user);
+ }
+
+ /**
+ * Ensures that a new node includes the custom data when added to an RSS feed.
+ */
+ function testNodeRSSContent() {
+ // Create a node.
+ $node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1));
+
+ $this->drupalGet('rss.xml');
+
+ // Check that content added in 'rss' view mode appear in RSS feed.
+ $rss_only_content = t('Extra data that should appear only in the RSS feed for node !nid.', array('!nid' => $node->nid));
+ $this->assertText($rss_only_content, 'Node content designated for RSS appear in RSS feed.');
+
+ // Check that content added in view modes other than 'rss' doesn't
+ // appear in RSS feed.
+ $non_rss_content = t('Extra data that should appear everywhere except the RSS feed for node !nid.', array('!nid' => $node->nid));
+ $this->assertNoText($non_rss_content, 'Node content not designed for RSS doesn\'t appear in RSS feed.');
+
+ // Check that extra RSS elements and namespaces are added to RSS feed.
+ $test_element = array(
+ 'key' => 'testElement',
+ 'value' => t('Value of testElement RSS element for node !nid.', array('!nid' => $node->nid)),
+ );
+ $test_ns = 'xmlns:drupaltest="http://example.com/test-namespace"';
+ $this->assertRaw(format_xml_elements(array($test_element)), 'Extra RSS elements appear in RSS feed.');
+ $this->assertRaw($test_ns, 'Extra namespaces appear in RSS feed.');
+
+ // Check that content added in 'rss' view mode doesn't appear when
+ // viewing node.
+ $this->drupalGet("node/$node->nid");
+ $this->assertNoText($rss_only_content, 'Node content designed for RSS doesn\'t appear when viewing node.');
+
+ // Check that the node feed page does not try to interpret additional path
+ // components as arguments for node_feed() and returns default content.
+ $this->drupalGet('rss.xml/' . $this->randomName() . '/' . $this->randomName());
+ $this->assertText($rss_only_content, 'Ignore page arguments when delivering rss.xml.');
+ }
+}
+
+/**
+ * Tests basic node_access functionality.
+ *
+ * Note that hook_node_access_records() is covered in another test class.
+ *
+ * @todo Cover hook_node_access in a separate test class.
+ */
+class NodeAccessTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node access',
+ 'description' => 'Test node_access function',
+ 'group' => 'Node',
+ );
+ }
+
+ /**
+ * Asserts node_access() correctly grants or denies access.
+ */
+ function assertNodeAccess($ops, $node, $account) {
+ foreach ($ops as $op => $result) {
+ $msg = format_string("node_access returns @result with operation '@op'.", array('@result' => $result ? 'true' : 'false', '@op' => $op));
+ $this->assertEqual($result, node_access($op, $node, $account), $msg);
+ }
+ }
+
+ function setUp() {
+ parent::setUp();
+ // Clear permissions for authenticated users.
+ db_delete('role_permission')
+ ->condition('rid', DRUPAL_AUTHENTICATED_RID)
+ ->execute();
+ }
+
+ /**
+ * Runs basic tests for node_access function.
+ */
+ function testNodeAccess() {
+ // Ensures user without 'access content' permission can do nothing.
+ $web_user1 = $this->drupalCreateUser(array('create page content', 'edit any page content', 'delete any page content'));
+ $node1 = $this->drupalCreateNode(array('type' => 'page'));
+ $this->assertNodeAccess(array('create' => FALSE), 'page', $web_user1);
+ $this->assertNodeAccess(array('view' => FALSE, 'update' => FALSE, 'delete' => FALSE), $node1, $web_user1);
+
+ // Ensures user with 'bypass node access' permission can do everything.
+ $web_user2 = $this->drupalCreateUser(array('bypass node access'));
+ $node2 = $this->drupalCreateNode(array('type' => 'page'));
+ $this->assertNodeAccess(array('create' => TRUE), 'page', $web_user2);
+ $this->assertNodeAccess(array('view' => TRUE, 'update' => TRUE, 'delete' => TRUE), $node2, $web_user2);
+
+ // User cannot 'view own unpublished content'.
+ $web_user3 = $this->drupalCreateUser(array('access content'));
+ $node3 = $this->drupalCreateNode(array('status' => 0, 'uid' => $web_user3->uid));
+ $this->assertNodeAccess(array('view' => FALSE), $node3, $web_user3);
+
+ // User cannot create content without permission.
+ $this->assertNodeAccess(array('create' => FALSE), 'page', $web_user3);
+
+ // User can 'view own unpublished content', but another user cannot.
+ $web_user4 = $this->drupalCreateUser(array('access content', 'view own unpublished content'));
+ $web_user5 = $this->drupalCreateUser(array('access content', 'view own unpublished content'));
+ $node4 = $this->drupalCreateNode(array('status' => 0, 'uid' => $web_user4->uid));
+ $this->assertNodeAccess(array('view' => TRUE, 'update' => FALSE), $node4, $web_user4);
+ $this->assertNodeAccess(array('view' => FALSE), $node4, $web_user5);
+
+ // Tests the default access provided for a published node.
+ $node5 = $this->drupalCreateNode();
+ $this->assertNodeAccess(array('view' => TRUE, 'update' => FALSE, 'delete' => FALSE), $node5, $web_user3);
+ }
+}
+
+/**
+ * Tests hook_node_access_records() functionality.
+ */
+class NodeAccessRecordsTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node access records',
+ 'description' => 'Test hook_node_access_records when acquiring grants.',
+ 'group' => 'Node',
+ );
+ }
+
+ function setUp() {
+ // Enable dummy module that implements hook_node_grants(),
+ // hook_node_access_records(), hook_node_grants_alter() and
+ // hook_node_access_records_alter().
+ parent::setUp('node_test');
+ }
+
+ /**
+ * Creates a node and tests the creation of node access rules.
+ */
+ function testNodeAccessRecords() {
+ // Create an article node.
+ $node1 = $this->drupalCreateNode(array('type' => 'article'));
+ $this->assertTrue(node_load($node1->nid), 'Article node created.');
+
+ // Check to see if grants added by node_test_node_access_records made it in.
+ $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node1->nid))->fetchAll();
+ $this->assertEqual(count($records), 1, 'Returned the correct number of rows.');
+ $this->assertEqual($records[0]->realm, 'test_article_realm', 'Grant with article_realm acquired for node without alteration.');
+ $this->assertEqual($records[0]->gid, 1, 'Grant with gid = 1 acquired for node without alteration.');
+
+ // Create an unpromoted "Basic page" node.
+ $node2 = $this->drupalCreateNode(array('type' => 'page', 'promote' => 0));
+ $this->assertTrue(node_load($node2->nid), 'Unpromoted basic page node created.');
+
+ // Check to see if grants added by node_test_node_access_records made it in.
+ $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node2->nid))->fetchAll();
+ $this->assertEqual(count($records), 1, 'Returned the correct number of rows.');
+ $this->assertEqual($records[0]->realm, 'test_page_realm', 'Grant with page_realm acquired for node without alteration.');
+ $this->assertEqual($records[0]->gid, 1, 'Grant with gid = 1 acquired for node without alteration.');
+
+ // Create an unpromoted, unpublished "Basic page" node.
+ $node3 = $this->drupalCreateNode(array('type' => 'page', 'promote' => 0, 'status' => 0));
+ $this->assertTrue(node_load($node3->nid), 'Unpromoted, unpublished basic page node created.');
+
+ // Check to see if grants added by node_test_node_access_records made it in.
+ $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node3->nid))->fetchAll();
+ $this->assertEqual(count($records), 1, 'Returned the correct number of rows.');
+ $this->assertEqual($records[0]->realm, 'test_page_realm', 'Grant with page_realm acquired for node without alteration.');
+ $this->assertEqual($records[0]->gid, 1, 'Grant with gid = 1 acquired for node without alteration.');
+
+ // Create a promoted "Basic page" node.
+ $node4 = $this->drupalCreateNode(array('type' => 'page', 'promote' => 1));
+ $this->assertTrue(node_load($node4->nid), 'Promoted basic page node created.');
+
+ // Check to see if grant added by node_test_node_access_records was altered
+ // by node_test_node_access_records_alter.
+ $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node4->nid))->fetchAll();
+ $this->assertEqual(count($records), 1, 'Returned the correct number of rows.');
+ $this->assertEqual($records[0]->realm, 'test_alter_realm', 'Altered grant with alter_realm acquired for node.');
+ $this->assertEqual($records[0]->gid, 2, 'Altered grant with gid = 2 acquired for node.');
+
+ // Check to see if we can alter grants with hook_node_grants_alter().
+ $operations = array('view', 'update', 'delete');
+ // Create a user that is allowed to access content.
+ $web_user = $this->drupalCreateUser(array('access content'));
+ foreach ($operations as $op) {
+ $grants = node_test_node_grants($op, $web_user);
+ $altered_grants = $grants;
+ drupal_alter('node_grants', $altered_grants, $web_user, $op);
+ $this->assertNotEqual($grants, $altered_grants, format_string('Altered the %op grant for a user.', array('%op' => $op)));
+ }
+
+ // Check that core does not grant access to an unpublished node when an
+ // empty $grants array is returned.
+ $node6 = $this->drupalCreateNode(array('status' => 0, 'disable_node_access' => TRUE));
+ $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node6->nid))->fetchAll();
+ $this->assertEqual(count($records), 0, 'Returned no records for unpublished node.');
+ }
+}
+
+/**
+ * Tests for Node Access with a non-node base table.
+ */
+class NodeAccessBaseTableTestCase extends DrupalWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node Access on any table',
+ 'description' => 'Checks behavior of the node access subsystem if the base table is not node.',
+ 'group' => 'Node',
+ );
+ }
+
+ public function setUp() {
+ parent::setUp('node_access_test');
+ node_access_rebuild();
+ variable_set('node_access_test_private', TRUE);
+ }
+
+ /**
+ * Tests the "private" node access functionality.
+ *
+ * - Create 2 users with "access content" and "create article" permissions.
+ * - Each user creates one private and one not private article.
+
+ * - Test that each user can view the other user's non-private article.
+ * - Test that each user cannot view the other user's private article.
+ * - Test that each user finds only appropriate (non-private + own private)
+ * in taxonomy listing.
+ * - Create another user with 'view any private content'.
+ * - Test that user 4 can view all content created above.
+ * - Test that user 4 can view all content on taxonomy listing.
+ */
+ function testNodeAccessBasic() {
+ $num_simple_users = 2;
+ $simple_users = array();
+
+ // nodes keyed by uid and nid: $nodes[$uid][$nid] = $is_private;
+ $this->nodesByUser = array();
+ $titles = array(); // Titles keyed by nid
+ $private_nodes = array(); // Array of nids marked private.
+ for ($i = 0; $i < $num_simple_users; $i++) {
+ $simple_users[$i] = $this->drupalCreateUser(array('access content', 'create article content'));
+ }
+ foreach ($simple_users as $this->webUser) {
+ $this->drupalLogin($this->webUser);
+ foreach (array(0 => 'Public', 1 => 'Private') as $is_private => $type) {
+ $edit = array(
+ 'title' => t('@private_public Article created by @user', array('@private_public' => $type, '@user' => $this->webUser->name)),
+ );
+ if ($is_private) {
+ $edit['private'] = TRUE;
+ $edit['body[und][0][value]'] = 'private node';
+ $edit['field_tags[und]'] = 'private';
+ }
+ else {
+ $edit['body[und][0][value]'] = 'public node';
+ $edit['field_tags[und]'] = 'public';
+ }
+
+ $this->drupalPost('node/add/article', $edit, t('Save'));
+ $nid = db_query('SELECT nid FROM {node} WHERE title = :title', array(':title' => $edit['title']))->fetchField();
+ $private_status = db_query('SELECT private FROM {node_access_test} where nid = :nid', array(':nid' => $nid))->fetchField();
+ $this->assertTrue($is_private == $private_status, 'The private status of the node was properly set in the node_access_test table.');
+ if ($is_private) {
+ $private_nodes[] = $nid;
+ }
+ $titles[$nid] = $edit['title'];
+ $this->nodesByUser[$this->webUser->uid][$nid] = $is_private;
+ }
+ }
+ $this->publicTid = db_query('SELECT tid FROM {taxonomy_term_data} WHERE name = :name', array(':name' => 'public'))->fetchField();
+ $this->privateTid = db_query('SELECT tid FROM {taxonomy_term_data} WHERE name = :name', array(':name' => 'private'))->fetchField();
+ $this->assertTrue($this->publicTid, 'Public tid was found');
+ $this->assertTrue($this->privateTid, 'Private tid was found');
+ foreach ($simple_users as $this->webUser) {
+ $this->drupalLogin($this->webUser);
+ // Check own nodes to see that all are readable.
+ foreach ($this->nodesByUser as $uid => $data) {
+ foreach ($data as $nid => $is_private) {
+ $this->drupalGet('node/' . $nid);
+ if ($is_private) {
+ $should_be_visible = $uid == $this->webUser->uid;
+ }
+ else {
+ $should_be_visible = TRUE;
+ }
+ $this->assertResponse($should_be_visible ? 200 : 403, strtr('A %private node by user %uid is %visible for user %current_uid.', array(
+ '%private' => $is_private ? 'private' : 'public',
+ '%uid' => $uid,
+ '%visible' => $should_be_visible ? 'visible' : 'not visible',
+ '%current_uid' => $this->webUser->uid,
+ )));
+ }
+ }
+
+ // Check to see that the correct nodes are shown on taxonomy/private
+ // and taxonomy/public.
+ $this->assertTaxonomyPage(FALSE);
+ }
+
+ // Now test that a user with 'access any private content' can view content.
+ $access_user = $this->drupalCreateUser(array('access content', 'create article content', 'node test view', 'search content'));
+ $this->drupalLogin($access_user);
+
+ foreach ($this->nodesByUser as $uid => $private_status) {
+ foreach ($private_status as $nid => $is_private) {
+ $this->drupalGet('node/' . $nid);
+ $this->assertResponse(200);
+ }
+ }
+
+ // This user should be able to see all of the nodes on the relevant
+ // taxonomy pages.
+ $this->assertTaxonomyPage(TRUE);
+ }
+
+ /**
+ * Checks taxonomy/term listings to ensure only accessible nodes are listed.
+ *
+ * @param $is_admin
+ * A boolean indicating whether the current user is an administrator. If
+ * TRUE, all nodes should be listed. If FALSE, only public nodes and the
+ * user's own private nodes should be listed.
+ */
+ protected function assertTaxonomyPage($is_admin) {
+ foreach (array($this->publicTid, $this->privateTid) as $tid_is_private => $tid) {
+ $this->drupalGet("taxonomy/term/$tid");
+ $this->nids_visible = array();
+ foreach ($this->xpath("//a[text()='Read more']") as $link) {
+ $this->assertTrue(preg_match('|node/(\d+)$|', (string) $link['href'], $matches), 'Read more points to a node');
+ $this->nids_visible[$matches[1]] = TRUE;
+ }
+ foreach ($this->nodesByUser as $uid => $data) {
+ foreach ($data as $nid => $is_private) {
+ // Private nodes should be visible on the private term page,
+ // public nodes should be visible on the public term page.
+ $should_be_visible = $tid_is_private == $is_private;
+ // Non-administrators can only see their own nodes on the private
+ // term page.
+ if (!$is_admin && $tid_is_private) {
+ $should_be_visible = $should_be_visible && $uid == $this->webUser->uid;
+ }
+ $this->assertIdentical(isset($this->nids_visible[$nid]), $should_be_visible, strtr('A %private node by user %uid is %visible for user %current_uid on the %tid_is_private page.', array(
+ '%private' => $is_private ? 'private' : 'public',
+ '%uid' => $uid,
+ '%visible' => isset($this->nids_visible[$nid]) ? 'visible' : 'not visible',
+ '%current_uid' => $this->webUser->uid,
+ '%tid_is_private' => $tid_is_private ? 'private' : 'public',
+ )));
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Tests node save related functionality, including import-save.
+ */
+class NodeSaveTestCase extends DrupalWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node save',
+ 'description' => 'Test node_save() for saving content.',
+ 'group' => 'Node',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('node_test');
+ // Create a user that is allowed to post; we'll use this to test the submission.
+ $web_user = $this->drupalCreateUser(array('create article content'));
+ $this->drupalLogin($web_user);
+ $this->web_user = $web_user;
+ }
+
+ /**
+ * Checks whether custom node IDs are saved properly during an import operation.
+ *
+ * Workflow:
+ * - first create a piece of content
+ * - save the content
+ * - check if node exists
+ */
+ function testImport() {
+ // Node ID must be a number that is not in the database.
+ $max_nid = db_query('SELECT MAX(nid) FROM {node}')->fetchField();
+ $test_nid = $max_nid + mt_rand(1000, 1000000);
+ $title = $this->randomName(8);
+ $node = array(
+ 'title' => $title,
+ 'body' => array(LANGUAGE_NONE => array(array('value' => $this->randomName(32)))),
+ 'uid' => $this->web_user->uid,
+ 'type' => 'article',
+ 'nid' => $test_nid,
+ 'is_new' => TRUE,
+ );
+ $node = node_submit((object) $node);
+
+ // Verify that node_submit did not overwrite the user ID.
+ $this->assertEqual($node->uid, $this->web_user->uid, 'Function node_submit() preserves user ID');
+
+ node_save($node);
+ // Test the import.
+ $node_by_nid = node_load($test_nid);
+ $this->assertTrue($node_by_nid, 'Node load by node ID.');
+
+ $node_by_title = $this->drupalGetNodeByTitle($title);
+ $this->assertTrue($node_by_title, 'Node load by node title.');
+ }
+
+ /**
+ * Verifies accuracy of the "created" and "changed" timestamp functionality.
+ */
+ function testTimestamps() {
+ // Use the default timestamps.
+ $edit = array(
+ 'uid' => $this->web_user->uid,
+ 'type' => 'article',
+ 'title' => $this->randomName(8),
+ );
+
+ node_save((object) $edit);
+ $node = $this->drupalGetNodeByTitle($edit['title']);
+ $this->assertEqual($node->created, REQUEST_TIME, 'Creating a node sets default "created" timestamp.');
+ $this->assertEqual($node->changed, REQUEST_TIME, 'Creating a node sets default "changed" timestamp.');
+
+ // Store the timestamps.
+ $created = $node->created;
+ $changed = $node->changed;
+
+ node_save($node);
+ $node = $this->drupalGetNodeByTitle($edit['title'], TRUE);
+ $this->assertEqual($node->created, $created, 'Updating a node preserves "created" timestamp.');
+
+ // Programmatically set the timestamps using hook_node_presave.
+ $node->title = 'testing_node_presave';
+
+ node_save($node);
+ $node = $this->drupalGetNodeByTitle('testing_node_presave', TRUE);
+ $this->assertEqual($node->created, 280299600, 'Saving a node uses "created" timestamp set in presave hook.');
+ $this->assertEqual($node->changed, 979534800, 'Saving a node uses "changed" timestamp set in presave hook.');
+
+ // Programmatically set the timestamps on the node.
+ $edit = array(
+ 'uid' => $this->web_user->uid,
+ 'type' => 'article',
+ 'title' => $this->randomName(8),
+ 'created' => 280299600, // Sun, 19 Nov 1978 05:00:00 GMT
+ 'changed' => 979534800, // Drupal 1.0 release.
+ );
+
+ node_save((object) $edit);
+ $node = $this->drupalGetNodeByTitle($edit['title']);
+ $this->assertEqual($node->created, 280299600, 'Creating a node uses user-set "created" timestamp.');
+ $this->assertNotEqual($node->changed, 979534800, 'Creating a node doesn\'t use user-set "changed" timestamp.');
+
+ // Update the timestamps.
+ $node->created = 979534800;
+ $node->changed = 280299600;
+
+ node_save($node);
+ $node = $this->drupalGetNodeByTitle($edit['title'], TRUE);
+ $this->assertEqual($node->created, 979534800, 'Updating a node uses user-set "created" timestamp.');
+ $this->assertNotEqual($node->changed, 280299600, 'Updating a node doesn\'t use user-set "changed" timestamp.');
+ }
+
+ /**
+ * Tests determing changes in hook_node_presave() and verifies the static node
+ * load cache is cleared upon save.
+ */
+ function testDeterminingChanges() {
+ // Initial creation.
+ $node = (object) array(
+ 'uid' => $this->web_user->uid,
+ 'type' => 'article',
+ 'title' => 'test_changes',
+ );
+ node_save($node);
+
+ // Update the node without applying changes.
+ node_save($node);
+ $this->assertEqual($node->title, 'test_changes', 'No changes have been determined.');
+
+ // Apply changes.
+ $node->title = 'updated';
+ node_save($node);
+
+ // The hook implementations node_test_node_presave() and
+ // node_test_node_update() determine changes and change the title.
+ $this->assertEqual($node->title, 'updated_presave_update', 'Changes have been determined.');
+
+ // Test the static node load cache to be cleared.
+ $node = node_load($node->nid);
+ $this->assertEqual($node->title, 'updated_presave', 'Static cache has been cleared.');
+ }
+
+ /**
+ * Tests saving a node on node insert.
+ *
+ * This test ensures that a node has been fully saved when hook_node_insert()
+ * is invoked, so that the node can be saved again in a hook implementation
+ * without errors.
+ *
+ * @see node_test_node_insert()
+ */
+ function testNodeSaveOnInsert() {
+ // node_test_node_insert() tiggers a save on insert if the title equals
+ // 'new'.
+ $node = $this->drupalCreateNode(array('title' => 'new'));
+ $this->assertEqual($node->title, 'Node ' . $node->nid, 'Node saved on node insert.');
+ }
+}
+
+/**
+ * Tests related to node types.
+ */
+class NodeTypeTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node types',
+ 'description' => 'Ensures that node type functions work correctly.',
+ 'group' => 'Node',
+ );
+ }
+
+ /**
+ * Ensures that node type functions (node_type_get_*) work correctly.
+ *
+ * Load available node types and validate the returned data.
+ */
+ function testNodeTypeGetFunctions() {
+ $node_types = node_type_get_types();
+ $node_names = node_type_get_names();
+
+ $this->assertTrue(isset($node_types['article']), 'Node type article is available.');
+ $this->assertTrue(isset($node_types['page']), 'Node type basic page is available.');
+
+ $this->assertEqual($node_types['article']->name, $node_names['article'], 'Correct node type base has been returned.');
+
+ $this->assertEqual($node_types['article'], node_type_get_type('article'), 'Correct node type has been returned.');
+ $this->assertEqual($node_types['article']->name, node_type_get_name('article'), 'Correct node type name has been returned.');
+ $this->assertEqual($node_types['page']->base, node_type_get_base('page'), 'Correct node type base has been returned.');
+ }
+
+ /**
+ * Tests creating a content type programmatically and via a form.
+ */
+ function testNodeTypeCreation() {
+ // Create a content type programmaticaly.
+ $type = $this->drupalCreateContentType();
+
+ $type_exists = db_query('SELECT 1 FROM {node_type} WHERE type = :type', array(':type' => $type->type))->fetchField();
+ $this->assertTrue($type_exists, 'The new content type has been created in the database.');
+
+ // Login a test user.
+ $web_user = $this->drupalCreateUser(array('create ' . $type->name . ' content'));
+ $this->drupalLogin($web_user);
+
+ $this->drupalGet('node/add/' . str_replace('_', '-', $type->name));
+ $this->assertResponse(200, 'The new content type can be accessed at node/add.');
+
+ // Create a content type via the user interface.
+ $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types'));
+ $this->drupalLogin($web_user);
+ $edit = array(
+ 'name' => 'foo',
+ 'title_label' => 'title for foo',
+ 'type' => 'foo',
+ );
+ $this->drupalPost('admin/structure/types/add', $edit, t('Save content type'));
+ $type_exists = db_query('SELECT 1 FROM {node_type} WHERE type = :type', array(':type' => 'foo'))->fetchField();
+ $this->assertTrue($type_exists, 'The new content type has been created in the database.');
+ }
+
+ /**
+ * Tests editing a node type using the UI.
+ */
+ function testNodeTypeEditing() {
+ $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types'));
+ $this->drupalLogin($web_user);
+
+ $instance = field_info_instance('node', 'body', 'page');
+ $this->assertEqual($instance['label'], 'Body', 'Body field was found.');
+
+ // Verify that title and body fields are displayed.
+ $this->drupalGet('node/add/page');
+ $this->assertRaw('Title', 'Title field was found.');
+ $this->assertRaw('Body', 'Body field was found.');
+
+ // Rename the title field.
+ $edit = array(
+ 'title_label' => 'Foo',
+ );
+ $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
+ // Refresh the field information for the rest of the test.
+ field_info_cache_clear();
+
+ $this->drupalGet('node/add/page');
+ $this->assertRaw('Foo', 'New title label was displayed.');
+ $this->assertNoRaw('Title', 'Old title label was not displayed.');
+
+ // Change the name, machine name and description.
+ $edit = array(
+ 'name' => 'Bar',
+ 'type' => 'bar',
+ 'description' => 'Lorem ipsum.',
+ );
+ $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
+ field_info_cache_clear();
+
+ $this->drupalGet('node/add');
+ $this->assertRaw('Bar', 'New name was displayed.');
+ $this->assertRaw('Lorem ipsum', 'New description was displayed.');
+ $this->clickLink('Bar');
+ $this->assertEqual(url('node/add/bar', array('absolute' => TRUE)), $this->getUrl(), 'New machine name was used in URL.');
+ $this->assertRaw('Foo', 'Title field was found.');
+ $this->assertRaw('Body', 'Body field was found.');
+
+ // Remove the body field.
+ $this->drupalPost('admin/structure/types/manage/bar/fields/body/delete', NULL, t('Delete'));
+ // Resave the settings for this type.
+ $this->drupalPost('admin/structure/types/manage/bar', array(), t('Save content type'));
+ // Check that the body field doesn't exist.
+ $this->drupalGet('node/add/bar');
+ $this->assertNoRaw('Body', 'Body field was not found.');
+ }
+
+ /**
+ * Tests that node_types_rebuild() correctly handles the 'disabled' flag.
+ */
+ function testNodeTypeStatus() {
+ // Enable all core node modules, and all types should be active.
+ module_enable(array('blog', 'book', 'poll'), FALSE);
+ node_types_rebuild();
+ $types = node_type_get_types();
+ foreach (array('blog', 'book', 'poll', 'article', 'page') as $type) {
+ $this->assertTrue(isset($types[$type]), format_string('%type is found in node types.', array('%type' => $type)));
+ $this->assertTrue(isset($types[$type]->disabled) && empty($types[$type]->disabled), format_string('%type type is enabled.', array('%type' => $type)));
+ }
+
+ // Disable poll module and the respective type should be marked as disabled.
+ module_disable(array('poll'), FALSE);
+ node_types_rebuild();
+ $types = node_type_get_types();
+ $this->assertTrue(!empty($types['poll']->disabled), "Poll module's node type disabled.");
+ $this->assertTrue(isset($types['blog']) && empty($types['blog']->disabled), "Blog module's node type still active.");
+
+ // Disable blog module and the respective type should be marked as disabled.
+ module_disable(array('blog'), FALSE);
+ node_types_rebuild();
+ $types = node_type_get_types();
+ $this->assertTrue(!empty($types['blog']->disabled), "Blog module's node type disabled.");
+ $this->assertTrue(!empty($types['poll']->disabled), "Poll module's node type still disabled.");
+
+ // Disable book module and the respective type should still be active, since
+ // it is not provided by hook_node_info().
+ module_disable(array('book'), FALSE);
+ node_types_rebuild();
+ $types = node_type_get_types();
+ $this->assertTrue(isset($types['book']) && empty($types['book']->disabled), "Book module's node type still active.");
+ $this->assertTrue(!empty($types['blog']->disabled), "Blog module's node type still disabled.");
+ $this->assertTrue(!empty($types['poll']->disabled), "Poll module's node type still disabled.");
+ $this->assertTrue(isset($types['article']) && empty($types['article']->disabled), "Article node type still active.");
+ $this->assertTrue(isset($types['page']) && empty($types['page']->disabled), "Basic page node type still active.");
+
+ // Re-enable the modules and verify that the types are active again.
+ module_enable(array('blog', 'book', 'poll'), FALSE);
+ node_types_rebuild();
+ $types = node_type_get_types();
+ foreach (array('blog', 'book', 'poll', 'article', 'page') as $type) {
+ $this->assertTrue(isset($types[$type]), format_string('%type is found in node types.', array('%type' => $type)));
+ $this->assertTrue(isset($types[$type]->disabled) && empty($types[$type]->disabled), format_string('%type type is enabled.', array('%type' => $type)));
+ }
+ }
+}
+
+/**
+ * Test node type customizations persistence.
+ */
+class NodeTypePersistenceTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node type persist',
+ 'description' => 'Ensures that node type customization survives module enabling and disabling.',
+ 'group' => 'Node',
+ );
+ }
+
+ /**
+ * Tests that node type customizations persist through disable and uninstall.
+ */
+ function testNodeTypeCustomizationPersistence() {
+ $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types', 'administer modules'));
+ $this->drupalLogin($web_user);
+ $poll_key = 'modules[Core][poll][enable]';
+ $poll_enable = array($poll_key => "1");
+ $poll_disable = array($poll_key => FALSE);
+
+ // Enable poll and verify that the node type is in the DB and is not
+ // disabled.
+ $this->drupalPost('admin/modules', $poll_enable, t('Save configuration'));
+ $disabled = db_query('SELECT disabled FROM {node_type} WHERE type = :type', array(':type' => 'poll'))->fetchField();
+ $this->assertNotIdentical($disabled, FALSE, 'Poll node type found in the database');
+ $this->assertEqual($disabled, 0, 'Poll node type is not disabled');
+
+ // Check that poll node type (uncustomized) shows up.
+ $this->drupalGet('node/add');
+ $this->assertText('poll', 'poll type is found on node/add');
+
+ // Customize poll description.
+ $description = $this->randomName();
+ $edit = array('description' => $description);
+ $this->drupalPost('admin/structure/types/manage/poll', $edit, t('Save content type'));
+
+ // Check that poll node type customization shows up.
+ $this->drupalGet('node/add');
+ $this->assertText($description, 'Customized description found');
+
+ // Disable poll and check that the node type gets disabled.
+ $this->drupalPost('admin/modules', $poll_disable, t('Save configuration'));
+ $disabled = db_query('SELECT disabled FROM {node_type} WHERE type = :type', array(':type' => 'poll'))->fetchField();
+ $this->assertEqual($disabled, 1, 'Poll node type is disabled');
+ $this->drupalGet('node/add');
+ $this->assertNoText('poll', 'poll type is not found on node/add');
+
+ // Reenable poll and check that the customization survived the module
+ // disable.
+ $this->drupalPost('admin/modules', $poll_enable, t('Save configuration'));
+ $disabled = db_query('SELECT disabled FROM {node_type} WHERE type = :type', array(':type' => 'poll'))->fetchField();
+ $this->assertNotIdentical($disabled, FALSE, 'Poll node type found in the database');
+ $this->assertEqual($disabled, 0, 'Poll node type is not disabled');
+ $this->drupalGet('node/add');
+ $this->assertText($description, 'Customized description found');
+
+ // Disable and uninstall poll.
+ $this->drupalPost('admin/modules', $poll_disable, t('Save configuration'));
+ $edit = array('uninstall[poll]' => 'poll');
+ $this->drupalPost('admin/modules/uninstall', $edit, t('Uninstall'));
+ $this->drupalPost(NULL, array(), t('Uninstall'));
+ $disabled = db_query('SELECT disabled FROM {node_type} WHERE type = :type', array(':type' => 'poll'))->fetchField();
+ $this->assertTrue($disabled, 'Poll node type is in the database and is disabled');
+ $this->drupalGet('node/add');
+ $this->assertNoText('poll', 'poll type is no longer found on node/add');
+
+ // Reenable poll and check that the customization survived the module
+ // uninstall.
+ $this->drupalPost('admin/modules', $poll_enable, t('Save configuration'));
+ $this->drupalGet('node/add');
+ $this->assertText($description, 'Customized description is found even after uninstall and reenable.');
+ }
+}
+
+/**
+ * Verifies the rebuild functionality for the node_access table.
+ */
+class NodeAccessRebuildTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node access rebuild',
+ 'description' => 'Ensures that node access rebuild functions work correctly.',
+ 'group' => 'Node',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ $web_user = $this->drupalCreateUser(array('administer site configuration', 'access administration pages', 'access site reports'));
+ $this->drupalLogin($web_user);
+ $this->web_user = $web_user;
+ }
+
+ /**
+ * Tests rebuilding the node access permissions table.
+ */
+ function testNodeAccessRebuild() {
+ $this->drupalGet('admin/reports/status');
+ $this->clickLink(t('Rebuild permissions'));
+ $this->drupalPost(NULL, array(), t('Rebuild permissions'));
+ $this->assertText(t('Content permissions have been rebuilt.'));
+ }
+}
+
+/**
+ * Tests node administration page functionality.
+ */
+class NodeAdminTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node administration',
+ 'description' => 'Test node administration page functionality.',
+ 'group' => 'Node',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ // Remove the "view own unpublished content" permission which is set
+ // by default for authenticated users so we can test this permission
+ // correctly.
+ user_role_revoke_permissions(DRUPAL_AUTHENTICATED_RID, array('view own unpublished content'));
+
+ $this->admin_user = $this->drupalCreateUser(array('access administration pages', 'access content overview', 'administer nodes', 'bypass node access'));
+ $this->base_user_1 = $this->drupalCreateUser(array('access content overview'));
+ $this->base_user_2 = $this->drupalCreateUser(array('access content overview', 'view own unpublished content'));
+ $this->base_user_3 = $this->drupalCreateUser(array('access content overview', 'bypass node access'));
+ }
+
+ /**
+ * Tests that the table sorting works on the content admin pages.
+ */
+ function testContentAdminSort() {
+ $this->drupalLogin($this->admin_user);
+ foreach (array('dd', 'aa', 'DD', 'bb', 'cc', 'CC', 'AA', 'BB') as $prefix) {
+ $this->drupalCreateNode(array('title' => $prefix . $this->randomName(6)));
+ }
+
+ // Test that the default sort by node.changed DESC actually fires properly.
+ $nodes_query = db_select('node', 'n')
+ ->fields('n', array('nid'))
+ ->orderBy('changed', 'DESC')
+ ->execute()
+ ->fetchCol();
+
+ $nodes_form = array();
+ $this->drupalGet('admin/content');
+ foreach ($this->xpath('//table/tbody/tr/td/div/input/@value') as $input) {
+ $nodes_form[] = $input;
+ }
+ $this->assertEqual($nodes_query, $nodes_form, 'Nodes are sorted in the form according to the default query.');
+
+ // Compare the rendered HTML node list to a query for the nodes ordered by
+ // title to account for possible database-dependent sort order.
+ $nodes_query = db_select('node', 'n')
+ ->fields('n', array('nid'))
+ ->orderBy('title')
+ ->execute()
+ ->fetchCol();
+
+ $nodes_form = array();
+ $this->drupalGet('admin/content', array('query' => array('sort' => 'asc', 'order' => 'Title')));
+ foreach ($this->xpath('//table/tbody/tr/td/div/input/@value') as $input) {
+ $nodes_form[] = $input;
+ }
+ $this->assertEqual($nodes_query, $nodes_form, 'Nodes are sorted in the form the same as they are in the query.');
+ }
+
+ /**
+ * Tests content overview with different user permissions.
+ *
+ * Taxonomy filters are tested separately.
+ *
+ * @see TaxonomyNodeFilterTestCase
+ */
+ function testContentAdminPages() {
+ $this->drupalLogin($this->admin_user);
+
+ $nodes['published_page'] = $this->drupalCreateNode(array('type' => 'page'));
+ $nodes['published_article'] = $this->drupalCreateNode(array('type' => 'article'));
+ $nodes['unpublished_page_1'] = $this->drupalCreateNode(array('type' => 'page', 'uid' => $this->base_user_1->uid, 'status' => 0));
+ $nodes['unpublished_page_2'] = $this->drupalCreateNode(array('type' => 'page', 'uid' => $this->base_user_2->uid, 'status' => 0));
+
+ // Verify view, edit, and delete links for any content.
+ $this->drupalGet('admin/content');
+ $this->assertResponse(200);
+ foreach ($nodes as $node) {
+ $this->assertLinkByHref('node/' . $node->nid);
+ $this->assertLinkByHref('node/' . $node->nid . '/edit');
+ $this->assertLinkByHref('node/' . $node->nid . '/delete');
+ // Verify tableselect.
+ $this->assertFieldByName('nodes[' . $node->nid . ']', '', 'Tableselect found.');
+ }
+
+ // Verify filtering by publishing status.
+ $edit = array(
+ 'status' => 'status-1',
+ );
+ $this->drupalPost(NULL, $edit, t('Filter'));
+
+ $this->assertRaw(t('where %property is %value', array('%property' => t('status'), '%value' => 'published')), 'Content list is filtered by status.');
+
+ $this->assertLinkByHref('node/' . $nodes['published_page']->nid . '/edit');
+ $this->assertLinkByHref('node/' . $nodes['published_article']->nid . '/edit');
+ $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid . '/edit');
+
+ // Verify filtering by status and content type.
+ $edit = array(
+ 'type' => 'page',
+ );
+ $this->drupalPost(NULL, $edit, t('Refine'));
+
+ $this->assertRaw(t('where %property is %value', array('%property' => t('status'), '%value' => 'published')), 'Content list is filtered by status.');
+ $this->assertRaw(t('and where %property is %value', array('%property' => t('type'), '%value' => 'Basic page')), 'Content list is filtered by content type.');
+
+ $this->assertLinkByHref('node/' . $nodes['published_page']->nid . '/edit');
+ $this->assertNoLinkByHref('node/' . $nodes['published_article']->nid . '/edit');
+
+ // Verify no operation links are displayed for regular users.
+ $this->drupalLogout();
+ $this->drupalLogin($this->base_user_1);
+ $this->drupalGet('admin/content');
+ $this->assertResponse(200);
+ $this->assertLinkByHref('node/' . $nodes['published_page']->nid);
+ $this->assertLinkByHref('node/' . $nodes['published_article']->nid);
+ $this->assertNoLinkByHref('node/' . $nodes['published_page']->nid . '/edit');
+ $this->assertNoLinkByHref('node/' . $nodes['published_page']->nid . '/delete');
+ $this->assertNoLinkByHref('node/' . $nodes['published_article']->nid . '/edit');
+ $this->assertNoLinkByHref('node/' . $nodes['published_article']->nid . '/delete');
+
+ // Verify no unpublished content is displayed without permission.
+ $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid);
+ $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid . '/edit');
+ $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid . '/delete');
+
+ // Verify no tableselect.
+ $this->assertNoFieldByName('nodes[' . $nodes['published_page']->nid . ']', '', 'No tableselect found.');
+
+ // Verify unpublished content is displayed with permission.
+ $this->drupalLogout();
+ $this->drupalLogin($this->base_user_2);
+ $this->drupalGet('admin/content');
+ $this->assertResponse(200);
+ $this->assertLinkByHref('node/' . $nodes['unpublished_page_2']->nid);
+ // Verify no operation links are displayed.
+ $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_2']->nid . '/edit');
+ $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_2']->nid . '/delete');
+
+ // Verify user cannot see unpublished content of other users.
+ $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid);
+ $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid . '/edit');
+ $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid . '/delete');
+
+ // Verify no tableselect.
+ $this->assertNoFieldByName('nodes[' . $nodes['unpublished_page_2']->nid . ']', '', 'No tableselect found.');
+
+ // Verify node access can be bypassed.
+ $this->drupalLogout();
+ $this->drupalLogin($this->base_user_3);
+ $this->drupalGet('admin/content');
+ $this->assertResponse(200);
+ foreach ($nodes as $node) {
+ $this->assertLinkByHref('node/' . $node->nid);
+ $this->assertLinkByHref('node/' . $node->nid . '/edit');
+ $this->assertLinkByHref('node/' . $node->nid . '/delete');
+ }
+ }
+}
+
+/**
+ * Tests node title functionality.
+ */
+class NodeTitleTestCase extends DrupalWebTestCase {
+
+ /**
+ * A user with permission to create and edit content and to administer nodes.
+ *
+ * @var object
+ */
+ protected $admin_user;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node title',
+ 'description' => 'Test node title.',
+ 'group' => 'Node'
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ $this->admin_user = $this->drupalCreateUser(array('administer nodes', 'create article content', 'create page content'));
+ $this->drupalLogin($this->admin_user);
+ }
+
+ /**
+ * Creates one node and tests if the node title has the correct value.
+ */
+ function testNodeTitle() {
+ // Create "Basic page" content with title.
+ // Add the node to the frontpage so we can test if teaser links are clickable.
+ $settings = array(
+ 'title' => $this->randomName(8),
+ 'promote' => 1,
+ );
+ $node = $this->drupalCreateNode($settings);
+
+ // Test <title> tag.
+ $this->drupalGet("node/$node->nid");
+ $xpath = '//title';
+ $this->assertEqual(current($this->xpath($xpath)), $node->title .' | Drupal', 'Page title is equal to node title.', 'Node');
+
+ // Test breadcrumb in comment preview.
+ $this->drupalGet("comment/reply/$node->nid");
+ $xpath = '//div[@class="breadcrumb"]/a[last()]';
+ $this->assertEqual(current($this->xpath($xpath)), $node->title, 'Node breadcrumb is equal to node title.', 'Node');
+
+ // Test node title in comment preview.
+ $this->assertEqual(current($this->xpath('//div[@id=:id]/h2/a', array(':id' => 'node-' . $node->nid))), $node->title, 'Node preview title is equal to node title.', 'Node');
+
+ // Test node title is clickable on teaser list (/node).
+ $this->drupalGet('node');
+ $this->clickLink($node->title);
+ }
+}
+
+/**
+ * Test the node_feed() functionality.
+ */
+class NodeFeedTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node feed',
+ 'description' => 'Ensures that node_feed() functions correctly.',
+ 'group' => 'Node',
+ );
+ }
+
+ /**
+ * Ensures that node_feed() accepts and prints extra channel elements.
+ */
+ function testNodeFeedExtraChannelElements() {
+ ob_start();
+ node_feed(array(), array('copyright' => 'Drupal is a registered trademark of Dries Buytaert.'));
+ $output = ob_get_clean();
+
+ $this->assertTrue(strpos($output, '<copyright>Drupal is a registered trademark of Dries Buytaert.</copyright>') !== FALSE);
+ }
+}
+
+/**
+ * Functional tests for the node module blocks.
+ */
+class NodeBlockFunctionalTest extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node blocks',
+ 'description' => 'Test node block functionality.',
+ 'group' => 'Node',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('node', 'block');
+
+ // Create users and test node.
+ $this->admin_user = $this->drupalCreateUser(array('administer content types', 'administer nodes', 'administer blocks'));
+ $this->web_user = $this->drupalCreateUser(array('access content', 'create article content'));
+ }
+
+ /**
+ * Tests the recent comments block.
+ */
+ function testRecentNodeBlock() {
+ $this->drupalLogin($this->admin_user);
+
+ // Disallow anonymous users to view content.
+ user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array(
+ 'access content' => FALSE,
+ ));
+
+ // Set the block to a region to confirm block is available.
+ $edit = array(
+ 'blocks[node_recent][region]' => 'sidebar_first',
+ );
+ $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
+ $this->assertText(t('The block settings have been updated.'), 'Block saved to first sidebar region.');
+
+ // Set block title and variables.
+ $block = array(
+ 'title' => $this->randomName(),
+ 'node_recent_block_count' => 2,
+ );
+ $this->drupalPost('admin/structure/block/manage/node/recent/configure', $block, t('Save block'));
+ $this->assertText(t('The block configuration has been saved.'), 'Block saved.');
+
+ // Test that block is not visible without nodes
+ $this->drupalGet('');
+ $this->assertText(t('No content available.'), 'Block with "No content available." found.');
+
+ // Add some test nodes.
+ $default_settings = array('uid' => $this->web_user->uid, 'type' => 'article');
+ $node1 = $this->drupalCreateNode($default_settings);
+ $node2 = $this->drupalCreateNode($default_settings);
+ $node3 = $this->drupalCreateNode($default_settings);
+
+ // Change the changed time for node so that we can test ordering.
+ db_update('node')
+ ->fields(array(
+ 'changed' => $node1->changed + 100,
+ ))
+ ->condition('nid', $node2->nid)
+ ->execute();
+ db_update('node')
+ ->fields(array(
+ 'changed' => $node1->changed + 200,
+ ))
+ ->condition('nid', $node3->nid)
+ ->execute();
+
+ // Test that a user without the 'access content' permission cannot
+ // see the block.
+ $this->drupalLogout();
+ $this->drupalGet('');
+ $this->assertNoText($block['title'], 'Block was not found.');
+
+ // Test that only the 2 latest nodes are shown.
+ $this->drupalLogin($this->web_user);
+ $this->assertNoText($node1->title, 'Node not found in block.');
+ $this->assertText($node2->title, 'Node found in block.');
+ $this->assertText($node3->title, 'Node found in block.');
+
+ // Check to make sure nodes are in the right order.
+ $this->assertTrue($this->xpath('//div[@id="block-node-recent"]/div/table/tbody/tr[position() = 1]/td/div/a[text() = "' . $node3->title . '"]'), 'Nodes were ordered correctly in block.');
+
+ // Set the number of recent nodes to show to 10.
+ $this->drupalLogout();
+ $this->drupalLogin($this->admin_user);
+ $block = array(
+ 'node_recent_block_count' => 10,
+ );
+ $this->drupalPost('admin/structure/block/manage/node/recent/configure', $block, t('Save block'));
+ $this->assertText(t('The block configuration has been saved.'), 'Block saved.');
+
+ // Post an additional node.
+ $node4 = $this->drupalCreateNode($default_settings);
+
+ // Test that all four nodes are shown.
+ $this->drupalGet('');
+ $this->assertText($node1->title, 'Node found in block.');
+ $this->assertText($node2->title, 'Node found in block.');
+ $this->assertText($node3->title, 'Node found in block.');
+ $this->assertText($node4->title, 'Node found in block.');
+
+ // Create the custom block.
+ $custom_block = array();
+ $custom_block['info'] = $this->randomName();
+ $custom_block['title'] = $this->randomName();
+ $custom_block['types[article]'] = TRUE;
+ $custom_block['body[value]'] = $this->randomName(32);
+ $custom_block['regions[' . variable_get('theme_default', 'bartik') . ']'] = 'content';
+ if ($admin_theme = variable_get('admin_theme')) {
+ $custom_block['regions[' . $admin_theme . ']'] = 'content';
+ }
+ $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block'));
+
+ $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField();
+ $this->assertTrue($bid, 'Custom block with visibility rule was created.');
+
+ // Verify visibility rules.
+ $this->drupalGet('');
+ $this->assertNoText($custom_block['title'], 'Block was displayed on the front page.');
+ $this->drupalGet('node/add/article');
+ $this->assertText($custom_block['title'], 'Block was displayed on the node/add/article page.');
+ $this->drupalGet('node/' . $node1->nid);
+ $this->assertText($custom_block['title'], 'Block was displayed on the node/N.');
+
+ // Delete the created custom block & verify that it's been deleted.
+ $this->drupalPost('admin/structure/block/manage/block/' . $bid . '/delete', array(), t('Delete'));
+ $bid = db_query("SELECT 1 FROM {block_node_type} WHERE module = 'block' AND delta = :delta", array(':delta' => $bid))->fetchField();
+ $this->assertFalse($bid, 'Custom block was deleted.');
+ }
+}
+/**
+ * Tests basic options of multi-step node forms.
+ */
+class MultiStepNodeFormBasicOptionsTest extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Multistep node form basic options',
+ 'description' => 'Test the persistence of basic options through multiple steps.',
+ 'group' => 'Node',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('poll');
+ $web_user = $this->drupalCreateUser(array('administer nodes', 'create poll content'));
+ $this->drupalLogin($web_user);
+ }
+
+ /**
+ * Tests changing the default values of basic options to ensure they persist.
+ */
+ function testMultiStepNodeFormBasicOptions() {
+ $edit = array(
+ 'title' => 'a',
+ 'status' => FALSE,
+ 'promote' => FALSE,
+ 'sticky' => 1,
+ 'choice[new:0][chtext]' => 'a',
+ 'choice[new:1][chtext]' => 'a',
+ );
+ $this->drupalPost('node/add/poll', $edit, t('More choices'));
+ $this->assertNoFieldChecked('edit-status', 'status stayed unchecked');
+ $this->assertNoFieldChecked('edit-promote', 'promote stayed unchecked');
+ $this->assertFieldChecked('edit-sticky', 'sticky stayed checked');
+ }
+}
+
+/**
+ * Test to ensure that a node's content is always rebuilt.
+ */
+class NodeBuildContent extends DrupalWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Rebuild content',
+ 'description' => 'Test the rebuilding of content for different build modes.',
+ 'group' => 'Node',
+ );
+ }
+
+ /**
+ * Ensures that content array is rebuilt on every call to node_build_content().
+ */
+ function testNodeRebuildContent() {
+ $node = $this->drupalCreateNode();
+
+ // Set a property in the content array so we can test for its existence later on.
+ $node->content['test_content_property'] = array('#value' => $this->randomString());
+ $content = node_build_content($node);
+
+ // If the property doesn't exist it means the node->content was rebuilt.
+ $this->assertFalse(isset($content['test_content_property']), 'Node content was emptied prior to being built.');
+ }
+}
+
+/**
+ * Tests node_query_node_access_alter().
+ */
+class NodeQueryAlter extends DrupalWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node query alter',
+ 'description' => 'Test that node access queries are properly altered by the node module.',
+ 'group' => 'Node',
+ );
+ }
+
+ /**
+ * User with permission to view content.
+ *
+ * @var object
+ */
+ protected $accessUser;
+
+ /**
+ * User without permission to view content.
+ *
+ * @var object
+ */
+ protected $noAccessUser;
+
+ function setUp() {
+ parent::setUp('node_access_test');
+ node_access_rebuild();
+
+ // Create some content.
+ $this->drupalCreateNode();
+ $this->drupalCreateNode();
+ $this->drupalCreateNode();
+ $this->drupalCreateNode();
+
+ // Create user with simple node access permission. The 'node test view'
+ // permission is implemented and granted by the node_access_test module.
+ $this->accessUser = $this->drupalCreateUser(array('access content overview', 'access content', 'node test view'));
+ $this->noAccessUser = $this->drupalCreateUser(array('access content overview', 'access content'));
+ $this->noAccessUser2 = $this->drupalCreateUser(array('access content overview', 'access content'));
+ }
+
+ /**
+ * Tests that node access permissions are followed.
+ */
+ function testNodeQueryAlterWithUI() {
+ // Verify that a user with access permission can see at least one node.
+ $this->drupalLogin($this->accessUser);
+ $this->drupalGet('node_access_test_page');
+ $this->assertText('Yes, 4 nodes', "4 nodes were found for access user");
+ $this->assertNoText('Exception', "No database exception");
+
+ // Test the content overview page.
+ $this->drupalGet('admin/content');
+ $table_rows = $this->xpath('//tbody/tr');
+ $this->assertEqual(4, count($table_rows), "4 nodes were found for access user");
+
+ // Verify that a user with no access permission cannot see nodes.
+ $this->drupalLogin($this->noAccessUser);
+ $this->drupalGet('node_access_test_page');
+ $this->assertText('No nodes', "No nodes were found for no access user");
+ $this->assertNoText('Exception', "No database exception");
+
+ $this->drupalGet('admin/content');
+ $this->assertText(t('No content available.'));
+ }
+
+ /**
+ * Tests 'node_access' query alter, for user with access.
+ *
+ * Verifies that a non-standard table alias can be used, and that a user with
+ * node access can view the nodes.
+ */
+ function testNodeQueryAlterLowLevelWithAccess() {
+ // User with access should be able to view 4 nodes.
+ try {
+ $query = db_select('node', 'mytab')
+ ->fields('mytab');
+ $query->addTag('node_access');
+ $query->addMetaData('op', 'view');
+ $query->addMetaData('account', $this->accessUser);
+
+ $result = $query->execute()->fetchAll();
+ $this->assertEqual(count($result), 4, 'User with access can see correct nodes');
+ }
+ catch (Exception $e) {
+ $this->fail(t('Altered query is malformed'));
+ }
+ }
+
+ /**
+ * Tests 'node_access' query alter, for user without access.
+ *
+ * Verifies that a non-standard table alias can be used, and that a user
+ * without node access cannot view the nodes.
+ */
+ function testNodeQueryAlterLowLevelNoAccess() {
+ // User without access should be able to view 0 nodes.
+ try {
+ $query = db_select('node', 'mytab')
+ ->fields('mytab');
+ $query->addTag('node_access');
+ $query->addMetaData('op', 'view');
+ $query->addMetaData('account', $this->noAccessUser);
+
+ $result = $query->execute()->fetchAll();
+ $this->assertEqual(count($result), 0, 'User with no access cannot see nodes');
+ }
+ catch (Exception $e) {
+ $this->fail(t('Altered query is malformed'));
+ }
+ }
+
+ /**
+ * Tests 'node_access' query alter, for edit access.
+ *
+ * Verifies that a non-standard table alias can be used, and that a user with
+ * view-only node access cannot edit the nodes.
+ */
+ function testNodeQueryAlterLowLevelEditAccess() {
+ // User with view-only access should not be able to edit nodes.
+ try {
+ $query = db_select('node', 'mytab')
+ ->fields('mytab');
+ $query->addTag('node_access');
+ $query->addMetaData('op', 'update');
+ $query->addMetaData('account', $this->accessUser);
+
+ $result = $query->execute()->fetchAll();
+ $this->assertEqual(count($result), 0, 'User with view-only access cannot edit nodes');
+ }
+ catch (Exception $e) {
+ $this->fail($e->getMessage());
+ $this->fail((string) $query);
+ $this->fail(t('Altered query is malformed'));
+ }
+ }
+
+ /**
+ * Tests 'node_access' query alter override.
+ *
+ * Verifies that node_access_view_all_nodes() is called from
+ * node_query_node_access_alter(). We do this by checking that a user who
+ * normally would not have view privileges is able to view the nodes when we
+ * add a record to {node_access} paired with a corresponding privilege in
+ * hook_node_grants().
+ */
+ function testNodeQueryAlterOverride() {
+ $record = array(
+ 'nid' => 0,
+ 'gid' => 0,
+ 'realm' => 'node_access_all',
+ 'grant_view' => 1,
+ 'grant_update' => 0,
+ 'grant_delete' => 0,
+ );
+ drupal_write_record('node_access', $record);
+
+ // Test that the noAccessUser still doesn't have the 'view'
+ // privilege after adding the node_access record.
+ drupal_static_reset('node_access_view_all_nodes');
+ try {
+ $query = db_select('node', 'mytab')
+ ->fields('mytab');
+ $query->addTag('node_access');
+ $query->addMetaData('op', 'view');
+ $query->addMetaData('account', $this->noAccessUser);
+
+ $result = $query->execute()->fetchAll();
+ $this->assertEqual(count($result), 0, 'User view privileges are not overridden');
+ }
+ catch (Exception $e) {
+ $this->fail(t('Altered query is malformed'));
+ }
+
+ // Have node_test_node_grants return a node_access_all privilege,
+ // to grant the noAccessUser 'view' access. To verify that
+ // node_access_view_all_nodes is properly checking the specified
+ // $account instead of the global $user, we will log in as
+ // noAccessUser2.
+ $this->drupalLogin($this->noAccessUser2);
+ variable_set('node_test_node_access_all_uid', $this->noAccessUser->uid);
+ drupal_static_reset('node_access_view_all_nodes');
+ try {
+ $query = db_select('node', 'mytab')
+ ->fields('mytab');
+ $query->addTag('node_access');
+ $query->addMetaData('op', 'view');
+ $query->addMetaData('account', $this->noAccessUser);
+
+ $result = $query->execute()->fetchAll();
+ $this->assertEqual(count($result), 4, 'User view privileges are overridden');
+ }
+ catch (Exception $e) {
+ $this->fail(t('Altered query is malformed'));
+ }
+ variable_del('node_test_node_access_all_uid');
+ }
+}
+
+
+/**
+ * Tests node_query_entity_field_access_alter().
+ */
+class NodeEntityFieldQueryAlter extends DrupalWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node entity query alter',
+ 'description' => 'Test that node access entity queries are properly altered by the node module.',
+ 'group' => 'Node',
+ );
+ }
+
+ /**
+ * User with permission to view content.
+ *
+ * @var object
+ */
+ protected $accessUser;
+
+ /**
+ * User without permission to view content.
+ *
+ * @var object
+ */
+ protected $noAccessUser;
+
+ function setUp() {
+ parent::setUp('node_access_test');
+ node_access_rebuild();
+
+ // Creating 4 nodes with an entity field so we can test that sort of query
+ // alter. All field values starts with 'A' so we can identify and fetch them
+ // in the node_access_test module.
+ $settings = array('language' => LANGUAGE_NONE);
+ for ($i = 0; $i < 4; $i++) {
+ $body = array(
+ 'value' => 'A' . $this->randomName(32),
+ 'format' => filter_default_format(),
+ );
+ $settings['body'][LANGUAGE_NONE][0] = $body;
+ $this->drupalCreateNode($settings);
+ }
+
+ // Create user with simple node access permission. The 'node test view'
+ // permission is implemented and granted by the node_access_test module.
+ $this->accessUser = $this->drupalCreateUser(array('access content', 'node test view'));
+ $this->noAccessUser = $this->drupalCreateUser(array('access content'));
+ }
+
+ /**
+ * Tests that node access permissions are followed.
+ */
+ function testNodeQueryAlterWithUI() {
+ // Verify that a user with access permission can see at least one node.
+ $this->drupalLogin($this->accessUser);
+ $this->drupalGet('node_access_entity_test_page');
+ $this->assertText('Yes, 4 nodes', "4 nodes were found for access user");
+ $this->assertNoText('Exception', "No database exception");
+
+ // Verify that a user with no access permission cannot see nodes.
+ $this->drupalLogin($this->noAccessUser);
+ $this->drupalGet('node_access_entity_test_page');
+ $this->assertText('No nodes', "No nodes were found for no access user");
+ $this->assertNoText('Exception', "No database exception");
+ }
+}
+
+/**
+ * Test node token replacement in strings.
+ */
+class NodeTokenReplaceTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node token replacement',
+ 'description' => 'Generates text using placeholders for dummy content to check node token replacement.',
+ 'group' => 'Node',
+ );
+ }
+
+ /**
+ * Creates a node, then tests the tokens generated from it.
+ */
+ function testNodeTokenReplacement() {
+ global $language;
+ $url_options = array(
+ 'absolute' => TRUE,
+ 'language' => $language,
+ );
+
+ // Create a user and a node.
+ $account = $this->drupalCreateUser();
+ $settings = array(
+ 'type' => 'article',
+ 'uid' => $account->uid,
+ 'title' => '<blink>Blinking Text</blink>',
+ 'body' => array(LANGUAGE_NONE => array(array('value' => $this->randomName(32), 'summary' => $this->randomName(16)))),
+ );
+ $node = $this->drupalCreateNode($settings);
+
+ // Load node so that the body and summary fields are structured properly.
+ $node = node_load($node->nid);
+ $instance = field_info_instance('node', 'body', $node->type);
+
+ // Generate and test sanitized tokens.
+ $tests = array();
+ $langcode = entity_language('node', $node);
+ $tests['[node:nid]'] = $node->nid;
+ $tests['[node:vid]'] = $node->vid;
+ $tests['[node:tnid]'] = $node->tnid;
+ $tests['[node:type]'] = 'article';
+ $tests['[node:type-name]'] = 'Article';
+ $tests['[node:title]'] = check_plain($node->title);
+ $tests['[node:body]'] = _text_sanitize($instance, $langcode, $node->body[$langcode][0], 'value');
+ $tests['[node:summary]'] = _text_sanitize($instance, $langcode, $node->body[$langcode][0], 'summary');
+ $tests['[node:language]'] = check_plain($langcode);
+ $tests['[node:url]'] = url('node/' . $node->nid, $url_options);
+ $tests['[node:edit-url]'] = url('node/' . $node->nid . '/edit', $url_options);
+ $tests['[node:author]'] = check_plain(format_username($account));
+ $tests['[node:author:uid]'] = $node->uid;
+ $tests['[node:author:name]'] = check_plain(format_username($account));
+ $tests['[node:created:since]'] = format_interval(REQUEST_TIME - $node->created, 2, $language->language);
+ $tests['[node:changed:since]'] = format_interval(REQUEST_TIME - $node->changed, 2, $language->language);
+
+ // Test to make sure that we generated something for each token.
+ $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
+
+ foreach ($tests as $input => $expected) {
+ $output = token_replace($input, array('node' => $node), array('language' => $language));
+ $this->assertEqual($output, $expected, format_string('Sanitized node token %token replaced.', array('%token' => $input)));
+ }
+
+ // Generate and test unsanitized tokens.
+ $tests['[node:title]'] = $node->title;
+ $tests['[node:body]'] = $node->body[$langcode][0]['value'];
+ $tests['[node:summary]'] = $node->body[$langcode][0]['summary'];
+ $tests['[node:language]'] = $langcode;
+ $tests['[node:author:name]'] = format_username($account);
+
+ foreach ($tests as $input => $expected) {
+ $output = token_replace($input, array('node' => $node), array('language' => $language, 'sanitize' => FALSE));
+ $this->assertEqual($output, $expected, format_string('Unsanitized node token %token replaced.', array('%token' => $input)));
+ }
+ }
+}
+
+/**
+ * Tests user permissions for node revisions.
+ */
+class NodeRevisionPermissionsTestCase extends DrupalWebTestCase {
+
+ /**
+ * Nodes used by the test.
+ *
+ * @var array
+ */
+ protected $node_revisions = array();
+
+ /**
+ * Users with different revision permission used by the test.
+ *
+ * @var array
+ */
+ protected $accounts = array();
+
+ /**
+ * Map revision permission names to node revision access ops.
+ *
+ * @var array
+ */
+ protected $map = array(
+ 'view' => 'view revisions',
+ 'update' => 'revert revisions',
+ 'delete' => 'delete revisions',
+ );
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node revision permissions',
+ 'description' => 'Tests user permissions for node revision operations.',
+ 'group' => 'Node',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ // Create a node with several revisions.
+ $node = $this->drupalCreateNode();
+ $this->node_revisions[] = $node;
+
+ for ($i = 0; $i < 3; $i++) {
+ // Create a revision for the same nid and settings with a random log.
+ $revision = clone $node;
+ $revision->revision = 1;
+ $revision->log = $this->randomName(32);
+ node_save($revision);
+ $this->node_revisions[] = $revision;
+ }
+
+ // Create three users, one with each revision permission.
+ foreach ($this->map as $op => $permission) {
+ // Create the user.
+ $account = $this->drupalCreateUser(
+ array(
+ 'access content',
+ 'edit any page content',
+ 'delete any page content',
+ $permission,
+ )
+ );
+ $account->op = $op;
+ $this->accounts[] = $account;
+ }
+
+ // Create an admin account (returns TRUE for all revision permissions).
+ $admin_account = $this->drupalCreateUser(array('access content', 'administer nodes'));
+ $admin_account->is_admin = TRUE;
+ $this->accounts['admin'] = $admin_account;
+
+ // Create a normal account (returns FALSE for all revision permissions).
+ $normal_account = $this->drupalCreateUser();
+ $normal_account->op = FALSE;
+ $this->accounts[] = $normal_account;
+ }
+
+ /**
+ * Tests the _node_revision_access() function.
+ */
+ function testNodeRevisionAccess() {
+ $revision = $this->node_revisions[1];
+
+ $parameters = array(
+ 'op' => array_keys($this->map),
+ 'account' => $this->accounts,
+ );
+
+ $permutations = $this->generatePermutations($parameters);
+ foreach ($permutations as $case) {
+ if (!empty($case['account']->is_admin) || $case['op'] == $case['account']->op) {
+ $this->assertTrue(_node_revision_access($revision, $case['op'], $case['account']), "{$this->map[$case['op']]} granted.");
+ }
+ else {
+ $this->assertFalse(_node_revision_access($revision, $case['op'], $case['account']), "{$this->map[$case['op']]} not granted.");
+ }
+ }
+
+ // Test that access is FALSE for a node administrator with an invalid $node
+ // or $op parameters.
+ $admin_account = $this->accounts['admin'];
+ $this->assertFalse(_node_revision_access(FALSE, 'view', $admin_account), '_node_revision_access() returns FALSE with an invalid node.');
+ $this->assertFalse(_node_revision_access($revision, 'invalid-op', $admin_account), '_node_revision_access() returns FALSE with an invalid op.');
+
+ // Test that the $account parameter defaults to the "logged in" user.
+ $original_user = $GLOBALS['user'];
+ $GLOBALS['user'] = $admin_account;
+ $this->assertTrue(_node_revision_access($revision, 'view'), '_node_revision_access() returns TRUE when used with global user.');
+ $GLOBALS['user'] = $original_user;
+ }
+}
+
+/**
+ * Tests pagination with a node access module enabled.
+ */
+class NodeAccessPagerTestCase extends DrupalWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node access pagination',
+ 'description' => 'Test access controlled node views have the right amount of comment pages.',
+ 'group' => 'Node',
+ );
+ }
+
+ public function setUp() {
+ parent::setUp('node_access_test', 'comment', 'forum');
+ node_access_rebuild();
+ $this->web_user = $this->drupalCreateUser(array('access content', 'access comments', 'node test view'));
+ }
+
+ /**
+ * Tests the comment pager for nodes with multiple grants per realm.
+ */
+ public function testCommentPager() {
+ // Create a node.
+ $node = $this->drupalCreateNode();
+
+ // Create 60 comments.
+ for ($i = 0; $i < 60; $i++) {
+ $comment = new stdClass();
+ $comment->cid = 0;
+ $comment->pid = 0;
+ $comment->uid = $this->web_user->uid;
+ $comment->nid = $node->nid;
+ $comment->subject = $this->randomName();
+ $comment->comment_body = array(
+ LANGUAGE_NONE => array(
+ array('value' => $this->randomName()),
+ ),
+ );
+ comment_save($comment);
+ }
+
+ $this->drupalLogin($this->web_user);
+
+ // View the node page. With the default 50 comments per page there should
+ // be two pages (0, 1) but no third (2) page.
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertText($node->title);
+ $this->assertText(t('Comments'));
+ $this->assertRaw('page=1');
+ $this->assertNoRaw('page=2');
+ }
+
+ /**
+ * Tests the forum node pager for nodes with multiple grants per realm.
+ */
+ public function testForumPager() {
+ // Look up the forums vocabulary ID.
+ $vid = variable_get('forum_nav_vocabulary', 0);
+ $this->assertTrue($vid, 'Forum navigation vocabulary ID is set.');
+
+ // Look up the general discussion term.
+ $tree = taxonomy_get_tree($vid, 0, 1);
+ $tid = reset($tree)->tid;
+ $this->assertTrue($tid, 'General discussion term is found in the forum vocabulary.');
+
+ // Create 30 nodes.
+ for ($i = 0; $i < 30; $i++) {
+ $this->drupalCreateNode(array(
+ 'nid' => NULL,
+ 'type' => 'forum',
+ 'taxonomy_forums' => array(
+ LANGUAGE_NONE => array(
+ array('tid' => $tid, 'vid' => $vid, 'vocabulary_machine_name' => 'forums'),
+ ),
+ ),
+ ));
+ }
+
+ // View the general discussion forum page. With the default 25 nodes per
+ // page there should be two pages for 30 nodes, no more.
+ $this->drupalLogin($this->web_user);
+ $this->drupalGet('forum/' . $tid);
+ $this->assertRaw('page=1');
+ $this->assertNoRaw('page=2');
+ }
+}
+
+
+/**
+ * Tests the interaction of the node access system with fields.
+ */
+class NodeAccessFieldTestCase extends NodeWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node access and fields',
+ 'description' => 'Tests the interaction of the node access system with fields.',
+ 'group' => 'Node',
+ );
+ }
+
+ public function setUp() {
+ parent::setUp('node_access_test', 'field_ui');
+ node_access_rebuild();
+
+ // Create some users.
+ $this->admin_user = $this->drupalCreateUser(array('access content', 'bypass node access'));
+ $this->content_admin_user = $this->drupalCreateUser(array('access content', 'administer content types'));
+
+ // Add a custom field to the page content type.
+ $this->field_name = drupal_strtolower($this->randomName() . '_field_name');
+ $this->field = field_create_field(array('field_name' => $this->field_name, 'type' => 'text'));
+ $this->instance = field_create_instance(array(
+ 'field_name' => $this->field_name,
+ 'entity_type' => 'node',
+ 'bundle' => 'page',
+ ));
+ }
+
+ /**
+ * Tests administering fields when node access is restricted.
+ */
+ function testNodeAccessAdministerField() {
+ // Create a page node.
+ $langcode = LANGUAGE_NONE;
+ $field_data = array();
+ $value = $field_data[$langcode][0]['value'] = $this->randomName();
+ $node = $this->drupalCreateNode(array($this->field_name => $field_data));
+
+ // Log in as the administrator and confirm that the field value is present.
+ $this->drupalLogin($this->admin_user);
+ $this->drupalGet("node/{$node->nid}");
+ $this->assertText($value, 'The saved field value is visible to an administrator.');
+
+ // Log in as the content admin and try to view the node.
+ $this->drupalLogin($this->content_admin_user);
+ $this->drupalGet("node/{$node->nid}");
+ $this->assertText('Access denied', 'Access is denied for the content admin.');
+
+ // Modify the field default as the content admin.
+ $edit = array();
+ $default = 'Sometimes words have two meanings';
+ $edit["{$this->field_name}[$langcode][0][value]"] = $default;
+ $this->drupalPost(
+ "admin/structure/types/manage/page/fields/{$this->field_name}",
+ $edit,
+ t('Save settings')
+ );
+
+ // Log in as the administrator.
+ $this->drupalLogin($this->admin_user);
+
+ // Confirm that the existing node still has the correct field value.
+ $this->drupalGet("node/{$node->nid}");
+ $this->assertText($value, 'The original field value is visible to an administrator.');
+
+ // Confirm that the new default value appears when creating a new node.
+ $this->drupalGet('node/add/page');
+ $this->assertRaw($default, 'The updated default value is displayed when creating a new node.');
+ }
+}
+
+/**
+ * Tests changing view modes for nodes.
+ */
+class NodeEntityViewModeAlterTest extends NodeWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node entity view mode',
+ 'description' => 'Test changing view mode.',
+ 'group' => 'Node'
+ );
+ }
+
+ function setUp() {
+ parent::setUp(array('node_test'));
+ }
+
+ /**
+ * Create a "Basic page" node and verify its consistency in the database.
+ */
+ function testNodeViewModeChange() {
+ $web_user = $this->drupalCreateUser(array('create page content', 'edit own page content'));
+ $this->drupalLogin($web_user);
+
+ // Create a node.
+ $edit = array();
+ $langcode = LANGUAGE_NONE;
+ $edit["title"] = $this->randomName(8);
+ $edit["body[$langcode][0][value]"] = t('Data that should appear only in the body for the node.');
+ $edit["body[$langcode][0][summary]"] = t('Extra data that should appear only in the teaser for the node.');
+ $this->drupalPost('node/add/page', $edit, t('Save'));
+
+ $node = $this->drupalGetNodeByTitle($edit["title"]);
+
+ // Set the flag to alter the view mode and view the node.
+ variable_set('node_test_change_view_mode', 'teaser');
+ $this->drupalGet('node/' . $node->nid);
+
+ // Check that teaser mode is viewed.
+ $this->assertText('Extra data that should appear only in the teaser for the node.', 'Teaser text present');
+ // Make sure body text is not present.
+ $this->assertNoText('Data that should appear only in the body for the node.', 'Body text not present');
+
+ // Test that the correct build mode has been set.
+ $build = node_view($node);
+ $this->assertEqual($build['#view_mode'], 'teaser', 'The view mode has correctly been set to teaser.');
+ }
+}
+
+/**
+ * Tests the cache invalidation of node operations.
+ */
+class NodePageCacheTest extends NodeWebTestCase {
+
+ /**
+ * An admin user with administrative permissions for nodes.
+ */
+ protected $admin_user;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Node page cache test',
+ 'description' => 'Test cache invalidation of node operations.',
+ 'group' => 'Node',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ variable_set('cache', 1);
+ variable_set('page_cache_maximum_age', 300);
+
+ $this->admin_user = $this->drupalCreateUser(array(
+ 'bypass node access',
+ 'access content overview',
+ 'administer nodes',
+ ));
+ }
+
+ /**
+ * Tests deleting nodes clears page cache.
+ */
+ public function testNodeDelete() {
+ $node_path = 'node/' . $this->drupalCreateNode()->nid;
+
+ // Populate page cache.
+ $this->drupalGet($node_path);
+
+ // Login and delete the node.
+ $this->drupalLogin($this->admin_user);
+ $this->drupalPost($node_path . '/delete', array(), t('Delete'));
+
+ // Logout and check the node is not available.
+ $this->drupalLogout();
+ $this->drupalGet($node_path);
+ $this->assertResponse(404);
+
+ // Create two new nodes.
+ $nodes[0] = $this->drupalCreateNode();
+ $nodes[1] = $this->drupalCreateNode();
+ $node_path = 'node/' . $nodes[0]->nid;
+
+ // Populate page cache.
+ $this->drupalGet($node_path);
+
+ // Login and delete the nodes.
+ $this->drupalLogin($this->admin_user);
+ $this->drupalGet('admin/content');
+ $edit = array(
+ 'operation' => 'delete',
+ 'nodes[' . $nodes[0]->nid . ']' => TRUE,
+ 'nodes[' . $nodes[1]->nid . ']' => TRUE,
+ );
+ $this->drupalPost(NULL, $edit, t('Update'));
+ $this->drupalPost(NULL, array(), t('Delete'));
+
+ // Logout and check the node is not available.
+ $this->drupalLogout();
+ $this->drupalGet($node_path);
+ $this->assertResponse(404);
+ }
+}
diff --git a/kolab.org/www/drupal-7.26/modules/node/node.tokens.inc b/kolab.org/www/drupal-7.26/modules/node/node.tokens.inc
new file mode 100644
index 0000000..e43db5e
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/node/node.tokens.inc
@@ -0,0 +1,191 @@
+<?php
+
+/**
+ * @file
+ * Builds placeholder replacement tokens for node-related data.
+ */
+
+
+
+/**
+ * Implements hook_token_info().
+ */
+function node_token_info() {
+ $type = array(
+ 'name' => t('Nodes'),
+ 'description' => t('Tokens related to individual content items, or "nodes".'),
+ 'needs-data' => 'node',
+ );
+
+ // Core tokens for nodes.
+ $node['nid'] = array(
+ 'name' => t("Content ID"),
+ 'description' => t('The unique ID of the content item, or "node".'),
+ );
+ $node['vid'] = array(
+ 'name' => t("Revision ID"),
+ 'description' => t("The unique ID of the node's latest revision."),
+ );
+ $node['tnid'] = array(
+ 'name' => t("Translation set ID"),
+ 'description' => t("The unique ID of the original-language version of this node, if one exists."),
+ );
+ $node['type'] = array(
+ 'name' => t("Content type"),
+ 'description' => t("The type of the node."),
+ );
+ $node['type-name'] = array(
+ 'name' => t("Content type name"),
+ 'description' => t("The human-readable name of the node type."),
+ );
+ $node['title'] = array(
+ 'name' => t("Title"),
+ 'description' => t("The title of the node."),
+ );
+ $node['body'] = array(
+ 'name' => t("Body"),
+ 'description' => t("The main body text of the node."),
+ );
+ $node['summary'] = array(
+ 'name' => t("Summary"),
+ 'description' => t("The summary of the node's main body text."),
+ );
+ $node['language'] = array(
+ 'name' => t("Language"),
+ 'description' => t("The language the node is written in."),
+ );
+ $node['url'] = array(
+ 'name' => t("URL"),
+ 'description' => t("The URL of the node."),
+ );
+ $node['edit-url'] = array(
+ 'name' => t("Edit URL"),
+ 'description' => t("The URL of the node's edit page."),
+ );
+
+ // Chained tokens for nodes.
+ $node['created'] = array(
+ 'name' => t("Date created"),
+ 'description' => t("The date the node was posted."),
+ 'type' => 'date',
+ );
+ $node['changed'] = array(
+ 'name' => t("Date changed"),
+ 'description' => t("The date the node was most recently updated."),
+ 'type' => 'date',
+ );
+ $node['author'] = array(
+ 'name' => t("Author"),
+ 'description' => t("The author of the node."),
+ 'type' => 'user',
+ );
+
+ return array(
+ 'types' => array('node' => $type),
+ 'tokens' => array('node' => $node),
+ );
+}
+
+/**
+ * Implements hook_tokens().
+ */
+function node_tokens($type, $tokens, array $data = array(), array $options = array()) {
+ $url_options = array('absolute' => TRUE);
+ if (isset($options['language'])) {
+ $url_options['language'] = $options['language'];
+ $language_code = $options['language']->language;
+ }
+ else {
+ $language_code = NULL;
+ }
+ $sanitize = !empty($options['sanitize']);
+
+ $replacements = array();
+
+ if ($type == 'node' && !empty($data['node'])) {
+ $node = $data['node'];
+
+ foreach ($tokens as $name => $original) {
+ switch ($name) {
+ // Simple key values on the node.
+ case 'nid':
+ $replacements[$original] = $node->nid;
+ break;
+
+ case 'vid':
+ $replacements[$original] = $node->vid;
+ break;
+
+ case 'tnid':
+ $replacements[$original] = $node->tnid;
+ break;
+
+ case 'type':
+ $replacements[$original] = $sanitize ? check_plain($node->type) : $node->type;
+ break;
+
+ case 'type-name':
+ $type_name = node_type_get_name($node);
+ $replacements[$original] = $sanitize ? check_plain($type_name) : $type_name;
+ break;
+
+ case 'title':
+ $replacements[$original] = $sanitize ? check_plain($node->title) : $node->title;
+ break;
+
+ case 'body':
+ case 'summary':
+ if ($items = field_get_items('node', $node, 'body', $language_code)) {
+ $column = ($name == 'body') ? 'value' : 'summary';
+ $instance = field_info_instance('node', 'body', $node->type);
+ $field_langcode = field_language('node', $node, 'body', $language_code);
+ $replacements[$original] = $sanitize ? _text_sanitize($instance, $field_langcode, $items[0], $column) : $items[0][$column];
+ }
+ break;
+
+ case 'language':
+ $langcode = entity_language('node', $node);
+ $replacements[$original] = $sanitize ? check_plain($langcode) : $langcode;
+ break;
+
+ case 'url':
+ $replacements[$original] = url('node/' . $node->nid, $url_options);
+ break;
+
+ case 'edit-url':
+ $replacements[$original] = url('node/' . $node->nid . '/edit', $url_options);
+ break;
+
+ // Default values for the chained tokens handled below.
+ case 'author':
+ $account = user_load($node->uid);
+ $name = format_username($account);
+ $replacements[$original] = $sanitize ? check_plain($name) : $name;
+ break;
+
+ case 'created':
+ $replacements[$original] = format_date($node->created, 'medium', '', NULL, $language_code);
+ break;
+
+ case 'changed':
+ $replacements[$original] = format_date($node->changed, 'medium', '', NULL, $language_code);
+ break;
+ }
+ }
+
+ if ($author_tokens = token_find_with_prefix($tokens, 'author')) {
+ $author = user_load($node->uid);
+ $replacements += token_generate('user', $author_tokens, array('user' => $author), $options);
+ }
+
+ if ($created_tokens = token_find_with_prefix($tokens, 'created')) {
+ $replacements += token_generate('date', $created_tokens, array('date' => $node->created), $options);
+ }
+
+ if ($changed_tokens = token_find_with_prefix($tokens, 'changed')) {
+ $replacements += token_generate('date', $changed_tokens, array('date' => $node->changed), $options);
+ }
+ }
+
+ return $replacements;
+}
diff --git a/kolab.org/www/drupal-7.26/modules/node/node.tpl.php b/kolab.org/www/drupal-7.26/modules/node/node.tpl.php
new file mode 100644
index 0000000..4c358a1
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/node/node.tpl.php
@@ -0,0 +1,112 @@
+<?php
+
+/**
+ * @file
+ * Default theme implementation to display a node.
+ *
+ * Available variables:
+ * - $title: the (sanitized) title of the node.
+ * - $content: An array of node items. Use render($content) to print them all,
+ * or print a subset such as render($content['field_example']). Use
+ * hide($content['field_example']) to temporarily suppress the printing of a
+ * given element.
+ * - $user_picture: The node author's picture from user-picture.tpl.php.
+ * - $date: Formatted creation date. Preprocess functions can reformat it by
+ * calling format_date() with the desired parameters on the $created variable.
+ * - $name: Themed username of node author output from theme_username().
+ * - $node_url: Direct URL of the current node.
+ * - $display_submitted: Whether submission information should be displayed.
+ * - $submitted: Submission information created from $name and $date during
+ * template_preprocess_node().
+ * - $classes: String of classes that can be used to style contextually through
+ * CSS. It can be manipulated through the variable $classes_array from
+ * preprocess functions. The default values can be one or more of the
+ * following:
+ * - node: The current template type; for example, "theming hook".
+ * - node-[type]: The current node type. For example, if the node is a
+ * "Blog entry" it would result in "node-blog". Note that the machine
+ * name will often be in a short form of the human readable label.
+ * - node-teaser: Nodes in teaser form.
+ * - node-preview: Nodes in preview mode.
+ * The following are controlled through the node publishing options.
+ * - node-promoted: Nodes promoted to the front page.
+ * - node-sticky: Nodes ordered above other non-sticky nodes in teaser
+ * listings.
+ * - node-unpublished: Unpublished nodes visible only to administrators.
+ * - $title_prefix (array): An array containing additional output populated by
+ * modules, intended to be displayed in front of the main title tag that
+ * appears in the template.
+ * - $title_suffix (array): An array containing additional output populated by
+ * modules, intended to be displayed after the main title tag that appears in
+ * the template.
+ *
+ * Other variables:
+ * - $node: Full node object. Contains data that may not be safe.
+ * - $type: Node type; for example, story, page, blog, etc.
+ * - $comment_count: Number of comments attached to the node.
+ * - $uid: User ID of the node author.
+ * - $created: Time the node was published formatted in Unix timestamp.
+ * - $classes_array: Array of html class attribute values. It is flattened
+ * into a string within the variable $classes.
+ * - $zebra: Outputs either "even" or "odd". Useful for zebra striping in
+ * teaser listings.
+ * - $id: Position of the node. Increments each time it's output.
+ *
+ * Node status variables:
+ * - $view_mode: View mode; for example, "full", "teaser".
+ * - $teaser: Flag for the teaser state (shortcut for $view_mode == 'teaser').
+ * - $page: Flag for the full page state.
+ * - $promote: Flag for front page promotion state.
+ * - $sticky: Flags for sticky post setting.
+ * - $status: Flag for published status.
+ * - $comment: State of comment settings for the node.
+ * - $readmore: Flags true if the teaser content of the node cannot hold the
+ * main body content.
+ * - $is_front: Flags true when presented in the front page.
+ * - $logged_in: Flags true when the current user is a logged-in member.
+ * - $is_admin: Flags true when the current user is an administrator.
+ *
+ * Field variables: for each field instance attached to the node a corresponding
+ * variable is defined; for example, $node->body becomes $body. When needing to
+ * access a field's raw values, developers/themers are strongly encouraged to
+ * use these variables. Otherwise they will have to explicitly specify the
+ * desired field language; for example, $node->body['en'], thus overriding any
+ * language negotiation rule that was previously applied.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_node()
+ * @see template_process()
+ *
+ * @ingroup themeable
+ */
+?>
+<div id="node-<?php print $node->nid; ?>" class="<?php print $classes; ?> clearfix"<?php print $attributes; ?>>
+
+ <?php print $user_picture; ?>
+
+ <?php print render($title_prefix); ?>
+ <?php if (!$page): ?>
+ <h2<?php print $title_attributes; ?>><a href="<?php print $node_url; ?>"><?php print $title; ?></a></h2>
+ <?php endif; ?>
+ <?php print render($title_suffix); ?>
+
+ <?php if ($display_submitted): ?>
+ <div class="submitted">
+ <?php print $submitted; ?>
+ </div>
+ <?php endif; ?>
+
+ <div class="content"<?php print $content_attributes; ?>>
+ <?php
+ // We hide the comments and links now so that we can render them later.
+ hide($content['comments']);
+ hide($content['links']);
+ print render($content);
+ ?>
+ </div>
+
+ <?php print render($content['links']); ?>
+
+ <?php print render($content['comments']); ?>
+
+</div>
diff --git a/kolab.org/www/drupal-7.26/modules/node/tests/node_access_test.info b/kolab.org/www/drupal-7.26/modules/node/tests/node_access_test.info
new file mode 100644
index 0000000..d17db64
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/node/tests/node_access_test.info
@@ -0,0 +1,12 @@
+name = "Node module access tests"
+description = "Support module for node permission testing."
+package = Testing
+version = VERSION
+core = 7.x
+hidden = TRUE
+
+; Information added by Drupal.org packaging script on 2014-01-15
+version = "7.26"
+project = "drupal"
+datestamp = "1389815930"
+
diff --git a/kolab.org/www/drupal-7.26/modules/node/tests/node_access_test.install b/kolab.org/www/drupal-7.26/modules/node/tests/node_access_test.install
new file mode 100644
index 0000000..1f33d51
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/node/tests/node_access_test.install
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the node_access_test module.
+ */
+
+/**
+ * Implements hook_schema().
+ */
+function node_access_test_schema() {
+ $schema['node_access_test'] = array(
+ 'description' => 'The base table for node_access_test.',
+ 'fields' => array(
+ 'nid' => array(
+ 'description' => 'The {node}.nid this record affects.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'private' => array(
+ 'description' => 'Boolean indicating whether the node is private (visible to administrator) or not (visible to non-administrators).',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ ),
+ 'indexes' => array(
+ 'nid' => array('nid'),
+ ),
+ 'primary key' => array('nid'),
+ 'foreign keys' => array(
+ 'versioned_node' => array(
+ 'table' => 'node',
+ 'columns' => array('nid' => 'nid'),
+ ),
+ ),
+ );
+
+ return $schema;
+}
diff --git a/kolab.org/www/drupal-7.26/modules/node/tests/node_access_test.module b/kolab.org/www/drupal-7.26/modules/node/tests/node_access_test.module
new file mode 100644
index 0000000..ec35c41
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/node/tests/node_access_test.module
@@ -0,0 +1,230 @@
+<?php
+
+/**
+ * @file
+ * A dummy module implementing node access related hooks for testing purposes.
+ *
+ * A dummy module implementing node access related hooks to test API interaction
+ * with the Node module. This module restricts view permission to those with
+ * a special 'node test view' permission.
+ */
+
+/**
+ * Implements hook_node_grants().
+ */
+function node_access_test_node_grants($account, $op) {
+ $grants = array();
+ // First grant a grant to the author for own content.
+ $grants['node_access_test_author'] = array($account->uid);
+ if ($op == 'view' && user_access('node test view', $account)) {
+ $grants['node_access_test'] = array(8888, 8889);
+ }
+ if ($op == 'view' && $account->uid == variable_get('node_test_node_access_all_uid', 0)) {
+ $grants['node_access_all'] = array(0);
+ }
+ return $grants;
+}
+
+/**
+ * Implements hook_node_access_records().
+ */
+function node_access_test_node_access_records($node) {
+ $grants = array();
+ // For NodeAccessBaseTableTestCase, only set records for private nodes.
+ if (!variable_get('node_access_test_private') || $node->private) {
+ $grants[] = array(
+ 'realm' => 'node_access_test',
+ 'gid' => 8888,
+ 'grant_view' => 1,
+ 'grant_update' => 0,
+ 'grant_delete' => 0,
+ 'priority' => 0,
+ );
+ $grants[] = array(
+ 'realm' => 'node_access_test',
+ 'gid' => 8889,
+ 'grant_view' => 1,
+ 'grant_update' => 0,
+ 'grant_delete' => 0,
+ 'priority' => 0,
+ );
+ // For the author realm, the GID is equivalent to a UID, which
+ // means there are many many groups of just 1 user.
+ $grants[] = array(
+ 'realm' => 'node_access_test_author',
+ 'gid' => $node->uid,
+ 'grant_view' => 1,
+ 'grant_update' => 1,
+ 'grant_delete' => 1,
+ 'priority' => 0,
+ );
+ }
+
+ return $grants;
+}
+
+/**
+ * Implements hook_permission().
+ *
+ * Sets up permissions for this module.
+ */
+function node_access_test_permission() {
+ return array('node test view' => array('title' => 'View content'));
+}
+
+/**
+ * Implements hook_menu().
+ *
+ * Sets up a page that lists nodes.
+ */
+function node_access_test_menu() {
+ $items = array();
+ $items['node_access_test_page'] = array(
+ 'title' => 'Node access test',
+ 'page callback' => 'node_access_test_page',
+ 'access arguments' => array('access content'),
+ 'type' => MENU_SUGGESTED_ITEM,
+ );
+ $items['node_access_entity_test_page'] = array(
+ 'title' => 'Node access test',
+ 'page callback' => 'node_access_entity_test_page',
+ 'access arguments' => array('access content'),
+ 'type' => MENU_SUGGESTED_ITEM,
+ );
+ return $items;
+}
+
+/**
+ * Page callback for node access test page.
+ *
+ * Page should say "No nodes" if there are no nodes, and "Yes, # nodes" (with
+ * the number filled in) if there were nodes the user could access. Also, the
+ * database query is shown, and a list of the node IDs, for debugging purposes.
+ * And if there is a query exception, the page says "Exception" and gives the
+ * error.
+ */
+function node_access_test_page() {
+ $output = '';
+
+ try {
+ $query = db_select('node', 'mytab')
+ ->fields('mytab');
+ $query->addTag('node_access');
+ $result = $query->execute()->fetchAll();
+
+ if (count($result)) {
+ $output .= '<p>Yes, ' . count($result) . ' nodes</p>';
+ $output .= '<ul>';
+ foreach ($result as $item) {
+ $output .= '<li>' . $item->nid . '</li>';
+ }
+ $output .= '</ul>';
+ }
+ else {
+ $output .= '<p>No nodes</p>';
+ }
+
+ $output .= '<p>' . ((string) $query ) . '</p>';
+ }
+ catch (Exception $e) {
+ $output = '<p>Exception</p>';
+ $output .= '<p>' . $e->getMessage() . '</p>';
+ }
+
+ return $output;
+}
+
+/**
+ * Page callback for node access entity test page.
+ *
+ * Page should say "No nodes" if there are no nodes, and "Yes, # nodes" (with
+ * the number filled in) if there were nodes the user could access. Also, the
+ * database query is shown, and a list of the node IDs, for debugging purposes.
+ * And if there is a query exception, the page says "Exception" and gives the
+ * error.
+ *
+ * @see node_access_test_menu()
+ */
+function node_access_entity_test_page() {
+ $output = '';
+ try {
+ $query = new EntityFieldQuery;
+ $result = $query->fieldCondition('body', 'value', 'A', 'STARTS_WITH')->execute();
+ if (!empty($result['node'])) {
+ $output .= '<p>Yes, ' . count($result['node']) . ' nodes</p>';
+ $output .= '<ul>';
+ foreach ($result['node'] as $nid => $v) {
+ $output .= '<li>' . $nid . '</li>';
+ }
+ $output .= '</ul>';
+ }
+ else {
+ $output .= '<p>No nodes</p>';
+ }
+ }
+ catch (Exception $e) {
+ $output = '<p>Exception</p>';
+ $output .= '<p>' . $e->getMessage() . '</p>';
+ }
+
+ return $output;
+}
+
+/**
+ * Implements hook_form_BASE_FORM_ID_alter().
+ */
+function node_access_test_form_node_form_alter(&$form, $form_state) {
+ // Only show this checkbox for NodeAccessBaseTableTestCase.
+ if (variable_get('node_access_test_private')) {
+ $form['private'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Private'),
+ '#description' => t('Check here if this content should be set private and only shown to privileged users.'),
+ '#default_value' => isset($form['#node']->private) ? $form['#node']->private : FALSE,
+ );
+ }
+}
+
+/**
+ * Implements hook_node_load().
+ */
+function node_access_test_node_load($nodes, $types) {
+ $result = db_query('SELECT nid, private FROM {node_access_test} WHERE nid IN(:nids)', array(':nids' => array_keys($nodes)));
+ foreach ($result as $record) {
+ $nodes[$record->nid]->private = $record->private;
+ }
+}
+
+/**
+ * Implements hook_node_delete().
+ */
+
+function node_access_test_node_delete($node) {
+ db_delete('node_access_test')->condition('nid', $node->nid)->execute();
+}
+
+/**
+ * Implements hook_node_insert().
+ */
+function node_access_test_node_insert($node) {
+ _node_access_test_node_write($node);
+}
+
+/**
+ * Implements hook_nodeapi_update().
+ */
+function node_access_test_node_update($node) {
+ _node_access_test_node_write($node);
+}
+
+/**
+ * Helper for node insert/update.
+ */
+function _node_access_test_node_write($node) {
+ if (isset($node->private)) {
+ db_merge('node_access_test')
+ ->key(array('nid' => $node->nid))
+ ->fields(array('private' => (int) $node->private))
+ ->execute();
+ }
+}
diff --git a/kolab.org/www/drupal-7.26/modules/node/tests/node_test.info b/kolab.org/www/drupal-7.26/modules/node/tests/node_test.info
new file mode 100644
index 0000000..240a2b7
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/node/tests/node_test.info
@@ -0,0 +1,12 @@
+name = "Node module tests"
+description = "Support module for node related testing."
+package = Testing
+version = VERSION
+core = 7.x
+hidden = TRUE
+
+; Information added by Drupal.org packaging script on 2014-01-15
+version = "7.26"
+project = "drupal"
+datestamp = "1389815930"
+
diff --git a/kolab.org/www/drupal-7.26/modules/node/tests/node_test.module b/kolab.org/www/drupal-7.26/modules/node/tests/node_test.module
new file mode 100644
index 0000000..edc175f
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/node/tests/node_test.module
@@ -0,0 +1,181 @@
+<?php
+
+/**
+ * @file
+ * A dummy module for testing node related hooks.
+ *
+ * This is a dummy module that implements node related hooks to test API
+ * interaction with the Node module.
+ */
+
+/**
+ * Implements hook_node_load().
+ */
+function node_test_node_load($nodes, $types) {
+ // Add properties to each loaded node which record the parameters that were
+ // passed in to this function, so the tests can check that (a) this hook was
+ // called, and (b) the parameters were what we expected them to be.
+ $nids = array_keys($nodes);
+ ksort($nids);
+ sort($types);
+ foreach ($nodes as $node) {
+ $node->node_test_loaded_nids = $nids;
+ $node->node_test_loaded_types = $types;
+ }
+}
+
+/**
+ * Implements hook_node_view().
+ */
+function node_test_node_view($node, $view_mode) {
+ if ($view_mode == 'rss') {
+ // Add RSS elements and namespaces when building the RSS feed.
+ $node->rss_elements[] = array(
+ 'key' => 'testElement',
+ 'value' => t('Value of testElement RSS element for node !nid.', array('!nid' => $node->nid)),
+ );
+ $node->rss_namespaces['xmlns:drupaltest'] = 'http://example.com/test-namespace';
+
+ // Add content that should be displayed only in the RSS feed.
+ $node->content['extra_feed_content'] = array(
+ '#markup' => '<p>' . t('Extra data that should appear only in the RSS feed for node !nid.', array('!nid' => $node->nid)) . '</p>',
+ '#weight' => 10,
+ );
+ }
+
+ if ($view_mode != 'rss') {
+ // Add content that should NOT be displayed in the RSS feed.
+ $node->content['extra_non_feed_content'] = array(
+ '#markup' => '<p>' . t('Extra data that should appear everywhere except the RSS feed for node !nid.', array('!nid' => $node->nid)) . '</p>',
+ );
+ }
+}
+
+/**
+ * Implements hook_node_grants().
+ */
+function node_test_node_grants($account, $op) {
+ // Give everyone full grants so we don't break other node tests.
+ // Our node access tests asserts three realms of access.
+ // See testGrantAlter().
+ return array(
+ 'test_article_realm' => array(1),
+ 'test_page_realm' => array(1),
+ 'test_alter_realm' => array(2),
+ );
+}
+
+/**
+ * Implements hook_node_access_records().
+ */
+function node_test_node_access_records($node) {
+ // Return nothing when testing for empty responses.
+ if (!empty($node->disable_node_access)) {
+ return;
+ }
+ $grants = array();
+ if ($node->type == 'article') {
+ // Create grant in arbitrary article_realm for article nodes.
+ $grants[] = array(
+ 'realm' => 'test_article_realm',
+ 'gid' => 1,
+ 'grant_view' => 1,
+ 'grant_update' => 0,
+ 'grant_delete' => 0,
+ 'priority' => 0,
+ );
+ }
+ elseif ($node->type == 'page') {
+ // Create grant in arbitrary page_realm for page nodes.
+ $grants[] = array(
+ 'realm' => 'test_page_realm',
+ 'gid' => 1,
+ 'grant_view' => 1,
+ 'grant_update' => 0,
+ 'grant_delete' => 0,
+ 'priority' => 0,
+ );
+ }
+ return $grants;
+}
+
+/**
+ * Implements hook_node_access_records_alter().
+ */
+function node_test_node_access_records_alter(&$grants, $node) {
+ if (!empty($grants)) {
+ foreach ($grants as $key => $grant) {
+ // Alter grant from test_page_realm to test_alter_realm and modify the gid.
+ if ($grant['realm'] == 'test_page_realm' && $node->promote) {
+ $grants[$key]['realm'] = 'test_alter_realm';
+ $grants[$key]['gid'] = 2;
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_node_grants_alter().
+ */
+function node_test_node_grants_alter(&$grants, $account, $op) {
+ // Return an empty array of grants to prove that we can alter by reference.
+ $grants = array();
+}
+
+/**
+ * Implements hook_node_presave().
+ */
+function node_test_node_presave($node) {
+ if ($node->title == 'testing_node_presave') {
+ // Sun, 19 Nov 1978 05:00:00 GMT
+ $node->created = 280299600;
+ // Drupal 1.0 release.
+ $node->changed = 979534800;
+ }
+ // Determine changes.
+ if (!empty($node->original) && $node->original->title == 'test_changes') {
+ if ($node->original->title != $node->title) {
+ $node->title .= '_presave';
+ }
+ }
+}
+
+/**
+ * Implements hook_node_update().
+ */
+function node_test_node_update($node) {
+ // Determine changes on update.
+ if (!empty($node->original) && $node->original->title == 'test_changes') {
+ if ($node->original->title != $node->title) {
+ $node->title .= '_update';
+ }
+ }
+}
+
+/**
+ * Implements hook_entity_view_mode_alter().
+ */
+function node_test_entity_view_mode_alter(&$view_mode, $context) {
+ // Only alter the view mode if we are on the test callback.
+ if ($change_view_mode = variable_get('node_test_change_view_mode', '')) {
+ $view_mode = $change_view_mode;
+ }
+}
+
+/**
+ * Implements hook_node_insert().
+ *
+ * This tests saving a node on node insert.
+ *
+ * @see NodeSaveTest::testNodeSaveOnInsert()
+ */
+function node_test_node_insert($node) {
+ // Set the node title to the node ID and save.
+ if ($node->title == 'new') {
+ $node->title = 'Node '. $node->nid;
+ // Remove the is_new flag, so that the node is updated and not inserted
+ // again.
+ unset($node->is_new);
+ node_save($node);
+ }
+}
diff --git a/kolab.org/www/drupal-7.26/modules/node/tests/node_test_exception.info b/kolab.org/www/drupal-7.26/modules/node/tests/node_test_exception.info
new file mode 100644
index 0000000..b5a0dcd
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/node/tests/node_test_exception.info
@@ -0,0 +1,12 @@
+name = "Node module exception tests"
+description = "Support module for node related exception testing."
+package = Testing
+version = VERSION
+core = 7.x
+hidden = TRUE
+
+; Information added by Drupal.org packaging script on 2014-01-15
+version = "7.26"
+project = "drupal"
+datestamp = "1389815930"
+
diff --git a/kolab.org/www/drupal-7.26/modules/node/tests/node_test_exception.module b/kolab.org/www/drupal-7.26/modules/node/tests/node_test_exception.module
new file mode 100644
index 0000000..66bc717
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/node/tests/node_test_exception.module
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * A module implementing node related hooks to test API interaction.
+ */
+
+/**
+ * Implements hook_node_insert().
+ */
+function node_test_exception_node_insert($node) {
+ if ($node->title == 'testing_transaction_exception') {
+ throw new Exception('Test exception for rollback.');
+ }
+}