Commit f96c141f authored by Dries's avatar Dries

- Patch #409750 by yched et al: overhaul and extend node build modes.

parent bb930bf6
......@@ -79,7 +79,7 @@ function blog_form($node, $form_state) {
/**
* Implement hook_view().
*/
function blog_view($node, $teaser) {
function blog_view($node, $build_mode) {
if ((bool)menu_get_object()) {
// Breadcrumb navigation.
drupal_set_breadcrumb(array(l(t('Home'), NULL), l(t('Blogs'), 'blog'), l(t("!name's blog", array('!name' => $node->name)), 'blog/' . $node->uid)));
......@@ -88,10 +88,10 @@ function blog_view($node, $teaser) {
}
/**
* Implement hook_node_view.
* Implement hook_node_view().
*/
function blog_node_view($node, $teaser = FALSE) {
if ($node->build_mode != NODE_BUILD_RSS) {
function blog_node_view($node, $build_mode = 'full') {
if ($build_mode != 'rss') {
if ($node->type == 'blog' && arg(0) != 'blog' || arg(1) != $node->uid) {
$links['blog_usernames_blog'] = array(
'title' => t("!username's blog", array('!username' => $node->name)),
......
......@@ -63,11 +63,11 @@ function book_perm() {
/**
* Inject links into $node as needed.
*/
function book_node_view_link($node, $teaser) {
function book_node_view_link($node, $build_mode) {
$links = array();
if (isset($node->book['depth'])) {
if (!$teaser) {
if ($build_mode == 'full') {
$child_type = variable_get('book_child_type', 'book');
if ((user_access('add content to books') || user_access('administer book outlines')) && node_access('create', $child_type) && $node->status == 1 && $node->book['depth'] < MENU_MAX_DEPTH) {
$links['book_add_child'] = array(
......@@ -184,6 +184,19 @@ function book_init() {
drupal_add_css(drupal_get_path('module', 'book') . '/book.css');
}
/**
* Implement hook_field_build_modes().
*/
function book_field_build_modes($obj_type) {
$modes = array();
if ($obj_type == 'node') {
$modes = array(
'print' => t('Print'),
);
}
return $modes;
}
/**
* Implement hook_block_list().
*/
......@@ -705,9 +718,9 @@ function book_node_load($nodes, $types) {
/**
* Implement hook_node_view().
*/
function book_node_view($node, $teaser) {
if (!$teaser) {
if (!empty($node->book['bid']) && $node->build_mode == NODE_BUILD_NORMAL) {
function book_node_view($node, $build_mode) {
if ($build_mode == 'full') {
if (!empty($node->book['bid']) && empty($node->in_preview)) {
$node->content['book_navigation'] = array(
'#markup' => theme('book_navigation', $node->book),
'#weight' => 100,
......@@ -715,8 +728,8 @@ function book_node_view($node, $teaser) {
}
}
if ($node->build_mode != NODE_BUILD_RSS) {
book_node_view_link($node, $teaser);
if ($build_mode != 'rss') {
book_node_view_link($node, $build_mode);
}
}
......@@ -1035,8 +1048,7 @@ function book_export_traverse($tree, $visit_func) {
* The HTML generated for the given node.
*/
function book_node_export($node, $children = '') {
$node->build_mode = NODE_BUILD_PRINT;
$node = node_build_content($node, FALSE, FALSE);
$node = node_build_content($node, 'print');
$node->rendered = drupal_render($node->content);
return theme('book_node_export_html', $node, $children);
......
......@@ -457,13 +457,13 @@ function theme_comment_block() {
}
/**
* An implementation of hook_node_view().
* Implement hook_node_view().
*/
function comment_node_view($node, $teaser) {
function comment_node_view($node, $build_mode) {
$links = array();
if ($node->comment) {
if ($node->build_mode == NODE_BUILD_RSS) {
if ($build_mode == 'rss') {
if ($node->comment != COMMENT_NODE_HIDDEN) {
// Add a comments RSS element which is a URL to the comments of this node.
$node->rss_elements[] = array(
......@@ -472,7 +472,7 @@ function comment_node_view($node, $teaser) {
);
}
}
else if ($teaser) {
elseif ($build_mode == 'teaser') {
// Main page: display the number of comments that have been posted.
if (user_access('access comments')) {
if (!empty($node->comment_count)) {
......@@ -548,7 +548,7 @@ function comment_node_view($node, $teaser) {
);
// Append the list of comments to $node->content for node detail pages.
if ($node->comment && (bool)menu_get_object() && $node->build_mode != NODE_BUILD_PREVIEW) {
if ($node->comment && (bool)menu_get_object() && empty($node->in_preview)) {
$node->content['comments'] = array(
'#markup' => comment_render($node),
'#sorted' => TRUE,
......@@ -1016,7 +1016,7 @@ function comment_save($edit) {
/**
* Implement hook_link().
*/
function comment_link($type, $object, $teaser) {
function comment_link($type, $object, $build_mode) {
if ($type == 'comment') {
$links = comment_links($object, FALSE);
return $links;
......@@ -2032,7 +2032,7 @@ function template_preprocess_comment_folded(&$variables) {
$variables['classes_array'][] = 'comment-new';
}
}
}
/**
......
......@@ -657,10 +657,10 @@ function hook_field_attach_delete_revision($obj_type, $object) {
* The type of $object; e.g. 'node' or 'user'.
* @param $object
* The object with fields to render.
* @param $teaser
* Whether to display the teaser only, as on the main page.
* @param $build_mode
* Build mode, e.g. 'full', 'teaser'...
*/
function hook_field_attach_view_alter($output, $obj_type, $object, $teaser) {
function hook_field_attach_view_alter($output, $obj_type, $object, $build_mode) {
}
/**
......
......@@ -149,7 +149,7 @@ class FieldQueryException extends FieldException {}
* The fully formed $obj_type object.
* @param $a
* - The $form in the 'form' operation.
* - The value of $teaser in the 'view' operation.
* - The value of $build_mode in the 'view' operation.
* - Otherwise NULL.
* @param $b
* - The $form_state in the 'submit' operation.
......@@ -883,19 +883,19 @@ function field_attach_query_revisions($field_name, $conditions, $result_format =
* The type of $object; e.g. 'node' or 'user'.
* @param $object
* The object with fields to render.
* @param $teaser
* Whether to display the teaser only, as on the main page.
* @param $build_mode
* Build mode, e.g. 'full', 'teaser'...
* @return
* A structured content array tree for drupal_render().
*/
function field_attach_view($obj_type, $object, $teaser = FALSE) {
function field_attach_view($obj_type, $object, $build_mode = 'full') {
// Let field modules sanitize their data for output.
_field_invoke('sanitize', $obj_type, $object);
$output = _field_invoke_default('view', $obj_type, $object, $teaser);
$output = _field_invoke_default('view', $obj_type, $object, $build_mode);
// Let other modules make changes after rendering the view.
drupal_alter('field_attach_view', $output, $obj_type, $object, $teaser);
drupal_alter('field_attach_view', $output, $obj_type, $object, $build_mode);
return $output;
......
......@@ -76,8 +76,8 @@ function field_default_insert($obj_type, $object, $field, $instance, &$items) {
* '#field_name' => 'field_name',
* '#object' => $object,
* '#object_type' => $obj_type,
* // Value of the $teaser param of hook_node('view').
* '#teaser' => $teaser,
* // Value of the $build_mode param of hook_node('view').
* '#build_mode' => $build_mode,
* 'items' =>
* 0 => array(
* '#item' => $items[0],
......@@ -112,27 +112,14 @@ function field_default_insert($obj_type, $object, $field, $instance, &$items) {
* ),
* );
*/
function field_default_view($obj_type, $object, $field, $instance, $items, $teaser) {
function field_default_view($obj_type, $object, $field, $instance, $items, $build_mode) {
list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
$addition = array();
// Entities without build modes should provide a 'full' context.
// NODE_BUILD_NORMAL is 0, and ('whatever' == 0) is TRUE, so we need a ===.
if (!isset($object->build_mode)) {
$context = 'full';
}
elseif ($object->build_mode === NODE_BUILD_NORMAL
|| $object->build_mode == NODE_BUILD_PREVIEW) {
$context = $teaser ? 'teaser' : 'full';
}
else {
$context = $object->build_mode;
}
// If we don't have specific settings for the current build_mode, we use the
// (required) 'full' build_mode.
$display = isset($instance['display'][$context]) ? $instance['display'][$context] : $instance['display']['full'];
$display = isset($instance['display'][$build_mode]) ? $instance['display'][$build_mode] : $instance['display']['full'];
// Ensure we have a valid formatter and formatter settings.
$display = _field_get_formatter($display, $field);
......@@ -141,7 +128,7 @@ function field_default_view($obj_type, $object, $field, $instance, $items, $teas
$single = (field_behaviors_formatter('multiple values', $display) == FIELD_BEHAVIOR_DEFAULT);
$label_display = $display['label'];
if (isset($object->build_mode) && $object->build_mode == NODE_BUILD_SEARCH_INDEX) {
if ($build_mode == 'search_index') {
$label_display = 'hidden';
}
......@@ -157,7 +144,7 @@ function field_default_view($obj_type, $object, $field, $instance, $items, $teas
'#title' => check_plain(t($instance['label'])),
'#access' => field_access('view', $field),
'#label_display' => $label_display,
'#teaser' => $teaser,
'#build_mode' => $build_mode,
'#single' => $single,
'items' => array(),
);
......@@ -196,7 +183,7 @@ function field_default_view($obj_type, $object, $field, $instance, $items, $teas
'field' => $element,
'#weight' => $instance['weight'],
'#post_render' => array('field_wrapper_post_render'),
'#context' => $context,
'#build_mode' => $build_mode,
);
$addition = array($field['field_name'] => $wrapper);
......@@ -209,7 +196,7 @@ function field_default_view($obj_type, $object, $field, $instance, $items, $teas
*/
function field_wrapper_post_render($content, $element) {
$instance = field_info_instance($element['#field_name'], $element['#bundle']);
if (theme('field_exclude', $content, $instance, $element['#context'])) {
if (theme('field_exclude', $content, $instance, $element['#build_mode'])) {
return '';
}
return $content;
......@@ -231,10 +218,10 @@ function field_wrapper_post_render($content, $element) {
* Whether or not the field's content is to be added in this context.
* Uses the 'exclude' value from the field's display settings.
*/
function theme_field_exclude($content, $object, $context) {
function theme_field_exclude($content, $object, $build_mode) {
if (empty($object['display'])
|| empty($object['display'][$context])
|| empty($object['display'][$context]['exclude'])) {
|| empty($object['display'][$build_mode])
|| empty($object['display'][$build_mode]['exclude'])) {
return FALSE;
}
else {
......
......@@ -482,8 +482,7 @@ function field_format($obj_type, $object, $field, $item, $formatter_name = NULL,
* $FIELD_NAME_rendered variables instead.
*
* By default, the field is displayed using the settings defined for the
* 'full' or 'teaser' contexts (depending on the value of the $teaser param).
* Set $node->build_mode to a different value to use a different context.
* 'full' or 'teaser' contexts (depending on the value of the $build_mode param).
*
* Different settings can be specified by adjusting $field['display'].
*
......@@ -492,19 +491,16 @@ function field_format($obj_type, $object, $field, $item, $formatter_name = NULL,
* @param $object
* The object containing the field to display. Must at least contain the id key,
* revision key (if applicable), bundle key, and the field data.
* @param $teaser
* Similar to hook_node('view')
* @param $build_mode
* Build mode, e.g. 'full', 'teaser'...
* @return
* The themed output for the field.
*/
function field_view_field($obj_type, $object, $field, $instance, $teaser = FALSE) {
function field_view_field($obj_type, $object, $field, $instance, $build_mode = 'full') {
$output = '';
if (isset($object->$field['field_name'])) {
$items = $object->$field['field_name'];
// Use 'full'/'teaser' if not specified otherwise.
$object->build_mode = isset($object->build_mode) ? $object->build_mode : NODE_BUILD_NORMAL;
// One-field equivalent to _field_invoke('sanitize').
$function = $field['module'] . '_field_sanitize';
if (drupal_function_exists($function)) {
......@@ -512,7 +508,7 @@ function field_view_field($obj_type, $object, $field, $instance, $teaser = FALSE
$object->$field['field_name'] = $items;
}
$view = field_default_view($obj_type, $object, $field, $instance, $items, $teaser);
$view = field_default_view($obj_type, $object, $field, $instance, $items, $build_mode);
// TODO : what about hook_field_attach_view ?
// field_default_view() adds a wrapper to handle variables and 'excluded'
......@@ -594,7 +590,7 @@ function template_preprocess_field(&$variables) {
$variables['items'][0]['view'] = drupal_render_children($element, array('items'));
}
$variables['teaser'] = $element['#teaser'];
$variables['build_mode'] = $element['#build_mode'];
$variables['page'] = (bool)menu_get_object();
$field_empty = TRUE;
......
......@@ -176,12 +176,10 @@ function text_field_load($obj_type, $objects, $field, $instances, &$items) {
// handled by text_field_sanitize().
$format = $item['format'];
if (filter_format_allowcache($format)) {
// TODO D7 : this code is really node-related.
$check = is_null($object) || (isset($object->build_mode) && $object->build_mode == NODE_BUILD_PREVIEW);
$lang = isset($object->language) ? $object->language : $language->language;
$items[$id][$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format, $lang, $check, FALSE) : '';
$items[$id][$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format, $lang, FALSE, FALSE) : '';
if ($field['type'] == 'text_with_summary') {
$items[$id][$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $lang, $check, FALSE) : '';
$items[$id][$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $lang, FALSE, FALSE) : '';
}
}
}
......@@ -209,12 +207,10 @@ function text_field_sanitize($obj_type, $object, $field, $instance, &$items) {
if (!isset($items[$delta]['safe'])) {
if (!empty($instance['settings']['text_processing'])) {
$format = $item['format'];
// TODO D7 : this code is really node-related.
$check = is_null($object) || (isset($object->build_mode) && $object->build_mode == NODE_BUILD_PREVIEW);
$lang = isset($object->language) ? $object->language : $language->language;
$items[$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format, $lang, $check) : '';
$items[$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format, $lang, FALSE) : '';
if ($field['type'] == 'text_with_summary') {
$items[$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $lang, $check) : '';
$items[$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $lang, FALSE) : '';
}
}
else {
......@@ -770,3 +766,4 @@ function theme_text_textarea_with_summary($element) {
return $element['#children'];
}
}
......@@ -9,7 +9,7 @@
* - $object: The object to which the field is attached.
* - $field: The field array.
* - $items: An array of values for each item in the field array.
* - $teaser: Whether this is displayed as a teaser.
* - $build_mode: Build mode, e.g. 'full', 'teaser'...
* - $page: Whether this is displayed as a page.
* - $field_name: The field name.
* - $field_type: The field type.
......
......@@ -187,7 +187,7 @@ function _forum_node_check_node_type($node, $vocabulary) {
/**
* Implement hook_node_view().
*/
function forum_node_view($node, $teaser) {
function forum_node_view($node, $build_mode) {
$vid = variable_get('forum_nav_vocabulary', 0);
$vocabulary = taxonomy_vocabulary_load($vid);
if (_forum_node_check_node_type($node, $vocabulary)) {
......@@ -212,7 +212,7 @@ function forum_node_view($node, $teaser) {
}
drupal_set_breadcrumb($breadcrumb);
if (!$teaser) {
if ($build_mode == 'full') {
$node->content['forum_navigation'] = array(
'#markup' => theme('forum_topic_navigation', $node),
'#weight' => 100,
......@@ -642,7 +642,7 @@ function forum_get_forums($tid = 0) {
$query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
$query->join('users', 'u2', 'ncs.last_comment_uid = u2.uid');
$query->addExpression('IF (ncs.last_comment_uid != 0, u2.name, ncs.last_comment_name)', 'last_comment_name');
$topic = $query
->fields('ncs', array('last_comment_timestamp', 'last_comment_uid'))
->condition('n.status', 1)
......@@ -651,7 +651,7 @@ function forum_get_forums($tid = 0) {
->addTag('node_access')
->execute()
->fetchObject();
$last_post = new stdClass();
if (!empty($topic->last_comment_timestamp)) {
$last_post->timestamp = $topic->last_comment_timestamp;
......@@ -724,14 +724,14 @@ function forum_get_topics($tid, $sortby, $forum_per_page) {
->orderByHeader($forum_topic_list_header)
->orderBy('n.created', 'DESC')
->limit($forum_per_page);
$count_query = db_select('node', 'n');
$count_query->join('taxonomy_term_node', 'r', 'n.vid = r.vid AND r.tid = :tid', array(':tid' => $tid));
$count_query->addExpression('COUNT(*)');
$count_query
->condition('n.status', 1)
->addTag('node_access');
$query->setCountQuery($count_query);
$result = $query->execute();
$topics = array();
......
......@@ -474,10 +474,10 @@ function hook_node_validate($node, $form) {
* will be called after hook_view(). The structure of $node->content is a
* renderable array as expected by drupal_render().
*
* When $node->build_mode is NODE_BUILD_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()
* When $build_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 taxonomy_node_view()
* @see upload_node_view()
......@@ -485,10 +485,10 @@ function hook_node_validate($node, $form) {
*
* @param $node
* The node the action is being performed on.
* @param $teaser
* The $teaser parameter from node_build().
* @param $build_mode
* The $build_mode parameter from node_build().
*/
function hook_node_view($node, $teaser) {
function hook_node_view($node, $build_mode) {
$node->content['my_additional_field'] = array(
'#value' => $additional_field,
'#weight' => 10,
......@@ -510,10 +510,10 @@ function hook_node_view($node, $teaser) {
*
* @param $node
* The node the action is being performed on.
* @param $teaser
* The $teaser parameter from node_build().
* @param $build_mode
* The $build_mode parameter from node_build().
*/
function hook_node_build_alter($node, $teaser) {
function hook_node_build_alter($node, $build_mode) {
// Check for the existence of a field added by another module.
if (isset($node->content['an_additional_field'])) {
// Change its weight.
......@@ -855,7 +855,7 @@ function hook_update($node) {
*
* Changes made to the $node object within a hook_validate() function 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
* hook_node_presave() instead. If it is really necessary to change
* the node at the validate stage, you can use function form_set_value().
*
* For a detailed usage example, see node_example.module.
......@@ -877,9 +877,8 @@ function hook_validate($node, &$form) {
*
* @param $node
* The node to be displayed.
* @param $teaser
* Whether we are to generate a "teaser" or summary of the node, rather than
* display the whole thing.
* @param $build_mode
* Build mode, e.g. 'full', 'teaser'...
* @return
* $node. The passed $node parameter should be modified as necessary and
* returned so it can be properly presented. Nodes are prepared for display
......@@ -894,7 +893,7 @@ function hook_validate($node, &$form) {
*
* For a detailed usage example, see node_example.module.
*/
function hook_view($node, $teaser = FALSE) {
function hook_view($node, $build_mode = 'full') {
if ((bool)menu_get_object()) {
$breadcrumb = array();
$breadcrumb[] = array('path' => 'example', 'title' => t('example'));
......
......@@ -15,36 +15,6 @@
*/
define('NODE_NEW_LIMIT', REQUEST_TIME - 30 * 24 * 60 * 60);
/**
* Node is being built before being viewed normally.
*/
define('NODE_BUILD_NORMAL', 0);
/**
* Node is being built before being previewed.
*/
define('NODE_BUILD_PREVIEW', 1);
/**
* Node is being built before being indexed by search module.
*/
define('NODE_BUILD_SEARCH_INDEX', 2);
/**
* Node is being built before being displayed as a search result.
*/
define('NODE_BUILD_SEARCH_RESULT', 3);
/**
* Node is being built before being displayed as part of an RSS feed.
*/
define('NODE_BUILD_RSS', 4);
/**
* Node is being built before being printed.
*/
define('NODE_BUILD_PRINT', 5);
/**
* Implement hook_help().
*/
......@@ -181,13 +151,14 @@ function node_field_build_modes($obj_type) {
$modes = array(
'teaser' => t('Teaser'),
'full' => t('Full node'),
NODE_BUILD_RSS => t('RSS'),
NODE_BUILD_PRINT => t('Print'),
'rss' => t('RSS'),
);
// Search integration is provided by node.module, so search-related
// build-modes for nodes are defined here and not in search.module.
if (module_exists('search')) {
$modes += array(
NODE_BUILD_SEARCH_INDEX => t('Search Index'),
NODE_BUILD_SEARCH_RESULT => t('Search Result'),
'search_index' => t('Search Index'),
'search_result' => t('Search Result'),
);
}
}
......@@ -1107,22 +1078,22 @@ function node_delete_multiple($nids) {
*
* @param $node
* A node array or node object.
* @param $teaser
* Whether to display the teaser only or the full form.
* @param $build_mode
* Build mode, e.g. 'full', 'teaser'...
*
* @return
* An array as expected by drupal_render().
*/
function node_build($node, $teaser = FALSE) {
function node_build($node, $build_mode = 'full') {
$node = (object)$node;
$node = node_build_content($node, $teaser);
$node = node_build_content($node, $build_mode);
$build = $node->content;
$build += array(
'#theme' => 'node',
'#node' => $node,
'#teaser' => $teaser,
'#build_mode' => $build_mode,
);
return $build;
}
......@@ -1130,52 +1101,49 @@ function node_build($node, $teaser = FALSE) {
/**
* Builds a structured array representing the node's content.
*
* The content built for the node will vary depending on the $node->build_mode
* attribute. The node module defines a set of common build mode constants:
* - NODE_BUILD_NORMAL: Node is being built to be viewed normally.
* - NODE_BUILD_PREVIEW: Node is being built to be previewed.
* - NODE_BUILD_SEARCH_INDEX: Node is being built to be indexed for search.
* - NODE_BUILD_SEARCH_RESULT: Node is being built as a search result.
* - NODE_BUILD_RSS: Node is being built to be displayed in an RSS feed.
*
* The default mode is NODE_BUILD_NORMAL, which will be used if
* $node->build_mode is not set.
*
* When defining an additional build mode constant in a contributed module,
* the suggested standard is to use the unix timestamp of when you write the
* code to minimize the likelihood of two modules using the same value.
* The content built for the node (field values, comments, file attachments or
* other node components) will vary depending on the $build_mode parameter.
*
* Drupal core defines the following build 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 build modes, or use existing
* build modes in additional contexts.
*
* @param $node
* A node object.
* @param $teaser
* Whether to display the teaser only, as on the main page.
* @param $build_mode
* Build mode, e.g. 'full', 'teaser'...
*
* @return
* An structured array containing the individual elements
* A structured array containing the individual elements
* of the node's content.
*/
function node_build_content($node, $teaser = FALSE) {
// The build mode identifies the target for which the node is built.
if (!isset($node->build_mode)) {
$node->build_mode = NODE_BUILD_NORMAL;
}
function node_build_content($node, $build_mode = 'full') {
// The 'view' hook can be implemented to overwrite the default function
// to display nodes.
if (node_hook($node, 'view')) {
$node = node_invoke($node, 'view', $teaser);