Rebuild permissions.', array('@node_access_rebuild' => url('admin/reports/status/rebuild'))); } drupal_set_message($message, 'error'); } switch ($path) { case 'admin/help#node': $output = ''; $output .= '
' . 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 Field module). For more information, see the online handbook entry for Node module.', array('@node' => 'http://drupal.org/documentation/modules/node', '@field' => url('admin/help/field'))) . '
'; $output .= '' . t('Individual content types can have different fields, behaviors, and permissions assigned to them.') . '
'; case 'admin/structure/types/manage/%/display': return '' . t('Content items can be displayed using different view modes: Teaser, Full content, Print, RSS, etc. Teaser is a short format that is typically used in lists of multiple content items. Full content is typically used when the content is displayed on its own page.') . '
' . '' . 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_label($arg[4]))) . '
'; case 'node/%/revisions': return '' . t('Revisions allow you to track differences between multiple versions of your content, and revert back to older versions.') . '
'; case 'node/%/edit': $node = node_load($arg[1]); $type = node_type_load($node->type); return (!empty($type->help) ? '' . filter_xss_admin($type->help) . '
' : ''); } if ($arg[0] == 'node' && $arg[1] == 'add' && $arg[2]) { $type = node_type_load($arg[2]); return (!empty($type->help) ? '' . filter_xss_admin($type->help) . '
' : ''); } } /** * 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), ), 'node_edit_form' => array( 'render element' => 'form', 'template' => 'node-edit-form', ), ); } /** * Implements hook_entity_bundle_info(). */ function node_entity_bundle_info() { $bundles = array(); // Bundles must provide a human readable name so we can create help and error // messages. node_type_cache_reset(); foreach (node_type_get_names() as $type => $name) { $bundles['node'][$type]['label'] = $name; } return $bundles; } /** * Implements hook_entity_display_alter(). */ function node_entity_display_alter(EntityDisplay $display, $context) { // Hide field labels in search index. if ($context['entity_type'] == 'node' && $context['view_mode'] == 'search_index') { foreach ($display->getComponents() as $name => $options) { if (isset($options['label'])) { $options['label'] = 'hidden'; $display->setComponent($name, $options); } } } } /** * Entity URI callback. * * @param \Drupal\Core\Entity\EntityInterface $node * A node entity. * * @return array * An array with 'path' as the key and the path to the node as its value. */ function node_uri(EntityInterface $node) { return array( 'path' => 'node/' . $node->nid->value, ); } /** * 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/*/translations' => TRUE, 'node/*/translations/*' => 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 * (optional) 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) { // Do not use $node->label() here, because $node comes from the database. $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; } /** * 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 || !module_exists('history')) { return MARK_READ; } if (!isset($cache[$nid])) { $cache[$nid] = history_read($nid); } if ($cache[$nid] == 0 && $timestamp > HISTORY_READ_LIMIT) { return MARK_NEW; } elseif ($timestamp > $cache[$nid] && $timestamp > HISTORY_READ_LIMIT) { return MARK_UPDATED; } return MARK_READ; } /** * 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_types_build() * @see node_type_load() */ function node_type_get_types() { return _node_types_build()->types; } /** * 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 string $type * A string that indicates the node type to return. * * @return string|false * The node type base or FALSE if the node type is not found. */ function node_type_get_base($type) { $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 labels, keyed by the node type name. * * @see _node_types_build() */ function node_type_get_names() { return _node_types_build()->names; } /** * Returns the node type label for the passed node type name. * * @param string $name * The machine name of a node type. * * @return string|false * The node type label or FALSE if the node type is not found. */ function node_type_get_label($name) { $types = _node_types_build()->names; return isset($types[$name]) ? $types[$name] : FALSE; } /** * Returns the node type label for the passed node. * * @param \Drupal\Core\Entity\EntityInterface $node * A node entity to return the node type's label for. * * @return string|false * The node type label or FALSE if the node type is not found. */ function node_get_type_label(EntityInterface $node) { $types = _node_types_build()->names; return isset($types[$node->type]) ? $types[$node->type] : FALSE; } /** * Title callback: Returns the sanitized node type name. * * @param $node_type * The node type object. * * @return * The node type name that is safe for printing. */ function node_type_get_clean_name($node_type) { return check_plain($node_type->name); } /** * Description callback: Returns the node type description. * * @param $node_type * The node type object. * * @return * The node type description. */ function node_type_get_description($node_type) { return $node_type->description; } /** * 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 name of a node type to load. * * @return * A node type object or FALSE if $name does not exist. */ function node_type_load($name) { $types = _node_types_build()->types; return isset($types[$name]) ? $types[$name] : FALSE; } /** * 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 * field is 0. * * @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) { entity_invoke_bundle_hook('rename', '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(); entity_invoke_bundle_hook('create', '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 the default body field to a node type. * * @param $type * A node type object. * @param $label * (optional) 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, 'settings' => array('display_summary' => TRUE), ); $instance = field_create_instance($instance); // Assign widget settings for the 'default' form mode. entity_get_form_display('node', $type->type, 'default') ->setComponent($field['field_name'], array( 'type' => 'text_textarea_with_summary', )) ->save(); // Assign display settings for the 'default' and 'teaser' view modes. entity_get_display('node', $type->type, 'default') ->setComponent($field['field_name'], array( 'label' => 'hidden', 'type' => 'text_default', )) ->save(); entity_get_display('node', $type->type, 'teaser') ->setComponent($field['field_name'], array( 'label' => 'hidden', 'type' => 'text_summary_or_trimmed', )) ->save(); } return $instance; } /** * Implements hook_field_extra_fields(). */ function node_field_extra_fields() { $extra = array(); $module_language_enabled = module_exists('language'); $description = t('Node module element'); foreach (node_type_get_types() as $bundle) { if ($bundle->has_title) { $extra['node'][$bundle->type]['form']['title'] = array( 'label' => $bundle->title_label, 'description' => $description, 'weight' => -5, ); } // Add also the 'language' select if Language module is enabled and the // bundle has multilingual support. // Visibility of the ordering of the language selector is the same as on the // node/add form. if ($module_language_enabled) { $configuration = language_get_default_configuration('node', $bundle->type); if ($configuration['language_show']) { $extra['node'][$bundle->type]['form']['language'] = array( 'label' => t('Language'), 'description' => $description, 'weight' => 0, ); } } $extra['node'][$bundle->type]['display']['language'] = array( 'label' => t('Language'), 'description' => $description, 'weight' => 0, 'visible' => FALSE, ); } return $extra; } /** * Deletes a node type from the database. * * @param $name * The machine name of the node type to delete. */ function node_type_delete($name) { $type = node_type_load($name); db_delete('node_type') ->condition('type', $name) ->execute(); entity_invoke_bundle_hook('delete', 'node', $name); module_invoke_all('node_type_delete', $type); // 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 * (optional) TRUE to rebuild node types. Equivalent to calling * node_types_rebuild(). Defaults to FALSE. * * @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:' . language(Language::TYPE_INTERFACE)->langcode; 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('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, CacheBackendInterface::CACHE_PERMANENT, array('node_types' => TRUE)); return $_node_types; } /** * Clears the node type cache. */ function node_type_cache_reset() { cache()->deleteTags(array('node_types' => 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. * * @see hook_node_info() */ 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', ), ), ), ); } /** * 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. * * @param array $nids * (optional) An array of entity IDs. If omitted, all entities are loaded. * @param bool $reset * (optional) Whether to reset the internal node_load() cache. Defaults to * FALSE. * * @return array * An array of node entities indexed by nid. * * @see entity_load_multiple() * @see Drupal\Core\Entity\Query\EntityQueryInterface */ function node_load_multiple(array $nids = NULL, $reset = FALSE) { $entities = entity_load_multiple('node', $nids, $reset); // Return BC-entities. foreach ($entities as $id => $entity) { $entities[$id] = $entity->getBCEntity(); } return $entities; } /** * Loads a node entity from the database. * * @param int $nid * The node ID. * @param bool $reset * (optional) Whether to reset the node_load_multiple() cache. Defaults to * FALSE. * * @return Drupal\node\Node|false * A fully-populated node entity, or FALSE if the node is not found. */ function node_load($nid = NULL, $reset = FALSE) { $entity = entity_load('node', $nid, $reset); return $entity ? $entity->getBCEntity() : FALSE; } /** * Loads a node revision from the database. * * @param int $nid * The node revision id. * * @return Drupal\node\Node|false * A fully-populated node entity, or FALSE if the node is not found. */ function node_revision_load($vid = NULL) { return entity_revision_load('node', $vid); } /** * Prepares a node for saving by populating the author and creation date. * * @param \Drupal\Core\Entity\EntityInterface $node * A node object. * * @return Drupal\node\Node * An updated node object. */ function node_submit(EntityInterface $node) { global $user; // 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; } } // If a new revision is created, save the current user as revision author. if ($node->isNewRevision()) { $node->revision_uid = $user->uid; $node->revision_timestamp = REQUEST_TIME; } $node->created = !empty($node->date) && $node->date instanceOf DrupalDateTime ? $node->date->getTimestamp() : REQUEST_TIME; $node->validated = TRUE; return $node; } /** * Deletes a node revision. * * @param $revision_id * The revision ID to delete. * * @return * TRUE if the revision deletion was successful; otherwise, FALSE. */ function node_revision_delete($revision_id) { entity_revision_delete('node', $revision_id); } /** * Page callback: Generates an array which displays a node detail page. * * @param \Drupal\Core\Entity\EntityInterface $node * A node entity. * @param $message * (optional) A flag which sets a page title relevant to the revision being * viewed. Default is FALSE. * * @return * A $page element suitable for use by drupal_render(). * * @see node_menu() */ function node_show(EntityInterface $node, $message = FALSE) { if ($message) { drupal_set_title(t('Revision of %title from %date', array('%title' => $node->label(), '%date' => format_date($node->revision_timestamp))), PASS_THROUGH); } // For markup consistency with other pages, use node_view_multiple() rather than node_view(). $nodes = array('nodes' => node_view_multiple(array($node->nid => $node), 'full')); // Update the history table, stating that this user viewed this node. if (module_exists('history')) { history_write($node->nid); } return $nodes; } /** * Checks whether the current page is the full page view of the passed-in node. * * @param \Drupal\Core\Entity\EntityInterface $node * A node entity. * * @return * The ID of the node if this is a full page view, otherwise FALSE. */ function node_is_page(EntityInterface $node) { $page_node = menu_get_object(); return (!empty($page_node) ? $page_node->nid == $node->nid : FALSE); } /** * Implements hook_preprocess_HOOK() for block.html.twig. */ function node_preprocess_block(&$variables) { if ($variables['configuration']['module'] == 'node') { switch ($variables['elements']['#plugin_id']) { case 'node_syndicate_block': $variables['attributes']['role'] = 'complementary'; break; case 'node_recent_block': $variables['attributes']['role'] = 'navigation'; break; } } } /** * Prepares variables for node templates. * * Default template: node.html.twig. * * Most themes utilize their own copy of node.html.twig. The default is located * inside "/core/modules/node/templates/node.html.twig". Look in there for the full * list of variables. * * @param array $variables * An associative array containing: * - elements: An array of elements to display in view mode. * - node: The node object. * - view_mode: View mode; e.g., 'full', 'teaser'... */ 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); // @todo Change 'name' to 'author' and also convert to a render array pending // http://drupal.org/node/1941286. $username = array( '#theme' => 'username', '#account' => $node, '#link_options' => array('attributes' => array('rel' => 'author')), ); $variables['name'] = drupal_render($username); $uri = $node->uri(); $variables['node_url'] = url($uri['path'], $uri['options']); $variables['label'] = check_plain($node->label()); $variables['page'] = $variables['view_mode'] == 'full' && node_is_page($node); // 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, $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'])); if (theme_get_setting('features.node_user_picture')) { // To change user picture settings (e.g. image style), edit the 'compact' // view mode on the User entity. Note that the 'compact' view mode might // not be configured, so remember to always check the theme setting first. $variables['user_picture'] = user_view($node->account, 'compact'); } else { $variables['user_picture'] = array(); } } else { $variables['display_submitted'] = FALSE; $variables['submitted'] = ''; $variables['user_picture'] = ''; } // Add article ARIA role. $variables['attributes']['role'] = 'article'; // Gather node classes. $variables['attributes']['class'][] = 'node'; $variables['attributes']['class'][] = drupal_html_class('node-' . $node->type); if ($node->promote) { $variables['attributes']['class'][] = 'promoted'; } if ($node->sticky) { $variables['attributes']['class'][] = 'sticky'; } if (!$node->status) { $variables['attributes']['class'][] = 'unpublished'; } if ($variables['view_mode']) { $variables['attributes']['class'][] = drupal_html_class('view-mode-' . $variables['view_mode']); } if (isset($variables['preview'])) { $variables['attributes']['class'][] = 'preview'; } // Clean up name so there are no underscores. $variables['theme_hook_suggestions'][] = 'node__' . $node->type; $variables['theme_hook_suggestions'][] = 'node__' . $node->nid; $variables['content_attributes']['class'][] = 'content'; } /** * 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 all content.', 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 all revisions' => array( 'title' => t('View all revisions'), ), 'revert all revisions' => array( 'title' => t('Revert all revisions'), 'description' => t('Role requires permission view revisions and edit rights for nodes in question, or administer nodes.'), ), 'delete all revisions' => array( 'title' => t('Delete all revisions'), 'description' => t('Role requires permission to view revisions and delete rights for nodes in question, or administer nodes.'), ), ); // Generate standard node permissions for all applicable node types. foreach (node_permissions_get_configured_types() as $name => $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(SelectExtender $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' => 'details', '#title' => t('Content ranking'), ); $form['content_ranking']['#theme'] = 'node_search_admin'; $form['content_ranking']['info'] = array( '#value' => '' . 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.') . '' ); // 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('Drupal\search\SearchQuery') ->extend('Drupal\Core\Database\Query\PagerSelectExtender'); $query->join('node_field_data', '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('langcode', 'n.langcode'); 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 // Add the language code of the indexed item to the result of the query, // since the node will be rendered using the respective language. ->fields('i', array('langcode')) ->limit(10) ->execute(); $results = array(); foreach ($find as $item) { // Render the node. $node = node_load($item->sid); $build = node_view($node, 'search_result', $item->langcode); unset($build['#theme']); $node->rendered = drupal_render($build); // Fetch comments for snippet. $node->rendered .= ' ' . module_invoke('comment', 'node_update_index', $node, $item->langcode); $extra = module_invoke_all('node_search_result', $node, $item->langcode); $language = language_load($item->langcode); $uri = $node->uri(); $username = array( '#theme' => 'username', '#account' => $node, ); $results[] = array( 'link' => url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE, 'language' => $language))), 'type' => check_plain(node_get_type_label($node)), 'title' => $node->label($item->langcode), 'user' => drupal_render($username), 'date' => $node->changed, 'node' => $node, 'extra' => $extra, 'score' => $item->calculated_score, 'snippet' => search_excerpt($keys, $node->rendered, $item->langcode), 'langcode' => $node->langcode, ); } 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 = Drupal::state()->get('node.cron_last')) { $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_field_data', 'n') ->distinct() ->fields('n', array('nid')) ->condition('uid', $account->uid) ->execute() ->fetchCol(); node_mass_update($nodes, array('status' => 0), NULL, TRUE); break; case 'user_cancel_reassign': // Anonymize nodes (current revisions). module_load_include('inc', 'node', 'node.admin'); $nodes = db_select('node_field_data', 'n') ->distinct() ->fields('n', array('nid')) ->condition('uid', $account->uid) ->execute() ->fetchCol(); node_mass_update($nodes, array('uid' => 0), NULL, TRUE); // Anonymize old revisions. db_update('node_field_revision') ->fields(array('uid' => 0)) ->condition('uid', $account->uid) ->execute(); break; } } /** * Implements hook_user_predelete(). */ function node_user_predelete($account) { // Delete nodes (current revisions). // @todo Introduce node_mass_delete() or make node_mass_update() more flexible. $nodes = db_select('node_field_data', 'n') ->distinct() ->fields('n', array('nid')) ->condition('uid', $account->uid) ->execute() ->fetchCol(); entity_delete_multiple('node', $nodes); // Delete old revisions. $revisions = db_query('SELECT DISTINCT vid FROM {node_field_revision} WHERE uid = :uid', array(':uid' => $account->uid))->fetchCol(); foreach ($revisions as $revision) { node_revision_delete($revision); } } /** * 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; } $table = array( '#theme' => 'table', '#header' => $header, '#rows' => $rows, ); $output .= drupal_render($table); $output .= drupal_render_children($form); return $output; } /** * Access callback: Checks node revision access. * * @param \Drupal\Core\Entity\EntityInterface $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. * Defaults to NULL. * @param $langcode * (optional) Language code for the variant of the node. Different language * variants might have different permissions associated. If NULL, the * original langcode of the node is used. Defaults to NULL. * * @return * TRUE if the operation may be performed, FALSE otherwise. * * @see node_menu() */ function _node_revision_access(EntityInterface $node, $op = 'view', $account = NULL, $langcode = NULL) { $access = &drupal_static(__FUNCTION__, array()); $map = array( 'view' => 'view all revisions', 'update' => 'revert all revisions', 'delete' => 'delete all revisions', ); $type_map = array( 'view' => "view $node->type revisions", 'update' => "revert $node->type revisions", 'delete' => "delete $node->type revisions", ); if (!$node || !isset($map[$op]) || !isset($type_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']; } // If no language code was provided, default to the node revision's langcode. if (empty($langcode)) { $langcode = $node->langcode; } // Statically cache access by revision ID, language code, user account ID, // and operation. $cid = $node->vid . ':' . $langcode . ':' . $account->uid . ':' . $op; if (!isset($access[$cid])) { // Perform basic permission checks first. if (!user_access($map[$op], $account) && !user_access($type_map[$op], $account) && !user_access('administer nodes', $account)) { return $access[$cid] = FALSE; } // There should be at least two revisions. If the vid of the given node // and the vid of the default 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 default revision, that's // not good. if ($node->isDefaultRevision() && (db_query('SELECT COUNT(*) FROM {node_field_revision} WHERE nid = :nid AND default_langcode = 1', 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 default revision and finally, if the // node passed in is not the default revision then access to that, too. $access[$cid] = node_access($op, node_load($node->nid), $account, $langcode) && ($node->isDefaultRevision() || node_access($op, $node, $account, $langcode)); } } 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_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' => 'node_admin_nodes', 'access arguments' => array('access content overview'), 'weight' => -10, 'file' => 'node.admin.inc', ); $items['admin/content/node'] = array( 'title' => 'Content', 'type' => MENU_DEFAULT_LOCAL_TASK, ); $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, ); $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/add'] = array( 'title' => 'Add content', 'page callback' => 'node_add_page', 'access callback' => '_node_add_access', 'file' => 'node.pages.inc', ); $items['node/add/%node_type'] = array( 'title callback' => 'node_type_get_clean_name', 'title arguments' => array(2), 'page callback' => 'node_add', 'page arguments' => array(2), 'access callback' => 'node_access', 'access arguments' => array('create', 2), 'description callback' => 'node_type_get_description', 'description arguments' => array(2), '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, ); $items['node/%node/edit'] = array( 'title' => 'Edit', 'route_name' => 'node_page_edit', 'type' => MENU_LOCAL_TASK, 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, ); $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' => 10, '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' => 20, 'type' => MENU_LOCAL_TASK, 'file' => 'node.pages.inc', ); $items['node/%node/revisions/%node_revision/view'] = array( 'title' => 'Revisions', 'page callback' => 'node_show', 'page arguments' => array(3, TRUE), 'access callback' => '_node_revision_access', 'access arguments' => array(3), ); $items['node/%node/revisions/%node_revision/revert'] = array( 'title' => 'Revert to earlier revision', 'page callback' => 'drupal_get_form', 'page arguments' => array('node_revision_revert_confirm', 3), 'access callback' => '_node_revision_access', 'access arguments' => array(3, 'update'), 'file' => 'node.pages.inc', ); $items['node/%node/revisions/%node_revision/delete'] = array( 'title' => 'Delete earlier revision', 'page callback' => 'drupal_get_form', 'page arguments' => array('node_revision_delete_confirm', 3), 'access callback' => '_node_revision_access', 'access arguments' => array(3, 'delete'), 'file' => 'node.pages.inc', ); return $items; } /** * Implements hook_menu_local_tasks(). */ function node_menu_local_tasks(&$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'][] = array( '#theme' => 'menu_local_action', '#link' => $item, ); } } } /** * Title callback: Provides the title for a 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: Displays the node's title. * * @param \Drupal\Core\Entity\EntityInterface $node * The node entity. * * @return * An unsanitized string that is the title of the node. * * @see node_menu() */ function node_page_title(EntityInterface $node) { return $node->label(); } /** * Finds the last time a node was changed. * * @param $nid * The ID of a node. * @param string $langcode * (optional) The language the node has been last modified in. Defaults to the * node language. * * @return string * A unix timestamp indicating the last time the node was changed. */ function node_last_changed($nid, $langcode = NULL) { $language_clause = isset($langcode) ? 'langcode = :langcode' : 'default_langcode = 1'; $result = db_query('SELECT changed FROM {node_field_data} WHERE nid = :nid AND ' . $language_clause, array(':nid' => $nid, ':langcode' => $langcode))->fetch(); return is_object($result) ? $result->changed : FALSE; } /** * Returns a list of all the existing revision numbers for the node passed in. * * @param \Drupal\Core\Entity\EntityInterface $node * The node entity. * * @return * An associative array keyed by node revision number. */ function node_revision_list(EntityInterface $node) { $revisions = array(); $result = db_query('SELECT nfr.vid, nfr.title, nfr.log, nfr.revision_uid AS uid, n.vid AS current_vid, nfr.revision_timestamp, u.name FROM {node_field_revision} nfr LEFT JOIN {node} n ON n.vid = nfr.vid INNER JOIN {users} u ON u.uid = nfr.revision_uid WHERE nfr.nid = :nid AND nfr.default_langcode = 1 ORDER BY nfr.vid DESC', array(':nid' => $node->nid)); foreach ($result as $revision) { $revisions[$revision->vid] = $revision; } return $revisions; } /** * 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_field_data', '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 DISTINCT nid FROM {node_field_data} 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 ->distinct() ->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 entities. * * @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(); $node_recent_content = array( '#theme' => 'node_recent_content', '#node' => $node, ); $row[] = array( 'data' => drupal_render($node_recent_content), 'class' => 'title-author', ); if (node_access('update', $node)) { $row[] = array( 'data' => l(t('edit'), 'node/' . $node->nid . '/edit', $l_options), 'class' => 'edit', ); } if (node_access('delete', $node)) { $row[] = array( 'data' => l(t('delete'), 'node/' . $node->nid . '/delete', $l_options), 'class' => 'delete', ); } $rows[] = $row; } if ($rows) { $table = array( '#theme' => 'table', '#rows' => $rows, ); $output = drupal_render($table); if (user_access('access content overview')) { $more_link = array( '#theme' => 'more_link', '#url' => 'admin/content', '#title' => t('Show more content'), ); $output .= drupal_render($more_link); } } 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 entity. * * @ingroup themeable */ function theme_node_recent_content($variables) { $node = $variables['node']; $output = '