Commit db479361 authored by fago's avatar fago Committed by fago

Issue #198041 by fago: Ease rendering of entity properties.

parent b2dc7f5f
......@@ -119,6 +119,13 @@
* Override the controller class to adapt the defaults and to improve and
* complete the generated metadata. Set it to FALSE to disable this feature.
* Defaults to the EntityDefaultMetadataController class.
* - extra fields controller class: (optional) A controller class for providing
* field API extra fields. Defaults to none.
* The class must implement the EntityExtraFieldsControllerInterface. Display
* extra fields that are exposed that way are rendered by default by the
* EntityAPIController. The EntityDefaultExtraFieldsController class may be
* used to generate extra fields based upon property metadata, which in turn
* get rendered by default by the EntityAPIController.
* - features controller class: (optional) A controller class for providing
* Features module integration for exportable entities. The given class has to
* inherit from the default class being EntityDefaultFeaturesController. Set
......
......@@ -179,3 +179,84 @@ function _entity_metadata_convert_schema_type($type) {
return 'text';
}
}
/**
* Interface for extra fields controller.
*
* Note: Displays extra fields exposed by this controller are rendered by
* default by the EntityAPIController.
*/
interface EntityExtraFieldsControllerInterface {
/**
* Returns extra fields for this entity type.
*
* @see hook_field_extra_fields().
*/
public function fieldExtraFields();
}
/**
* Default controller for generating extra fields based on property metadata.
*
* By default a display extra field for each property not being a field, ID or
* bundle is generated.
*/
class EntityDefaultExtraFieldsController implements EntityExtraFieldsControllerInterface {
/**
* @var string
*/
protected $entityType;
/**
* @var array
*/
protected $entityInfo;
/**
* Constructor.
*/
public function __construct($type) {
$this->entityType = $type;
$this->entityInfo = entity_get_info($type);
$this->propertyInfo = entity_get_property_info($type);
}
/**
* Implements EntityExtraFieldsControllerInterface::fieldExtraFields().
*/
public function fieldExtraFields() {
$extra = array();
foreach ($this->propertyInfo['properties'] as $name => $property_info) {
// Skip adding the ID or bundle.
if ($this->entityInfo['entity keys']['id'] == $name || $this->entityInfo['entity keys']['bundle'] == $name) {
continue;
}
$extra[$this->entityType][$this->entityType]['display'][$name] = $this->generateExtraFieldInfo($name, $property_info);
}
// Handle bundle properties.
$this->propertyInfo += array('bundles' => array());
foreach ($this->propertyInfo['bundles'] as $bundle_name => $info) {
foreach ($info['properties'] as $name => $property_info) {
$extra[$this->entityType][$bundle_name]['display'][$name] = $this->generateExtraFieldInfo($name, $property_info);
}
}
return $extra;
}
/**
* Generates the display field info for a given property.
*/
protected function generateExtraFieldInfo($name, $property_info) {
$info = array(
'label' => $property_info['label'],
'weight' => 0,
);
if (!empty($property_info['description'])) {
$info['description'] = $property_info['description'];
}
return $info;
}
}
......@@ -971,11 +971,18 @@ function entity_theme() {
return array(
'entity_status' => array(
'variables' => array('status' => NULL, 'html' => TRUE),
'file' => 'theme/entity.theme.inc',
),
'entity' => array(
'render element' => 'elements',
'template' => 'entity',
'pattern' => $pattern,
'path' => drupal_get_path('module', 'entity') . '/theme',
'file' => 'entity.theme.inc',
),
'entity_property' => array(
'render element' => 'elements',
'file' => 'theme/entity.theme.inc',
),
'entity_ui_overview_item' => array(
'variables' => array('label' => NULL, 'entity_type' => NULL, 'url' => FALSE, 'name' => FALSE),
......@@ -984,90 +991,6 @@ function entity_theme() {
);
}
/**
* Themes the exportable status of an entity.
*/
function theme_entity_status($variables) {
$status = $variables['status'];
$html = $variables['html'];
if (($status & ENTITY_FIXED) == ENTITY_FIXED) {
$label = t('Fixed');
$help = t('The configuration is fixed and cannot be changed.');
return $html ? "<span class='entity-status-fixed' title='$help'>" . $label . "</span>" : $label;
}
elseif (($status & ENTITY_OVERRIDDEN) == ENTITY_OVERRIDDEN) {
$label = t('Overridden');
$help = t('This configuration is provided by a module, but has been changed.');
return $html ? "<span class='entity-status-overridden' title='$help'>" . $label . "</span>" : $label;
}
elseif ($status & ENTITY_IN_CODE) {
$label = t('Default');
$help = t('A module provides this configuration.');
return $html ? "<span class='entity-status-default' title='$help'>" . $label . "</span>" : $label;
}
elseif ($status & ENTITY_CUSTOM) {
$label = t('Custom');
$help = t('A custom configuration by a user.');
return $html ? "<span class='entity-status-custom' title='$help'>" . $label . "</span>" : $label;
}
}
/**
* Process variables for entity.tpl.php.
*/
function template_preprocess_entity(&$variables) {
$variables['view_mode'] = $variables['elements']['#view_mode'];
$entity_type = $variables['elements']['#entity_type'];
$variables['entity_type'] = $entity_type;
$entity = $variables['elements']['#entity'];
$variables[$variables['elements']['#entity_type']] = $entity;
$info = entity_get_info($entity_type);
$variables['title'] = check_plain(entity_label($entity_type, $entity));
$uri = entity_uri($entity_type, $entity);
$variables['url'] = $uri ? url($uri['path'], $uri['options']) : FALSE;
if (isset($variables['elements']['#page'])) {
// If set by the caller, respect the page property.
$variables['page'] = $variables['elements']['#page'];
}
else {
// Else, try to automatically detect it.
$variables['page'] = $uri && $uri['path'] == $_GET['q'];
}
// Helpful $content variable for templates.
$variables['content'] = array();
foreach (element_children($variables['elements']) as $key) {
$variables['content'][$key] = $variables['elements'][$key];
}
if (!empty($info['fieldable'])) {
// Make the field variables available with the appropriate language.
field_attach_preprocess($entity_type, $entity, $variables['content'], $variables);
}
list(, , $bundle) = entity_extract_ids($entity_type, $entity);
// Gather css classes.
$variables['classes_array'][] = drupal_html_class('entity-' . $entity_type);
$variables['classes_array'][] = drupal_html_class($entity_type . '-' . $bundle);
// Add RDF type and about URI.
if (module_exists('rdf')) {
$variables['attributes_array']['about'] = empty($uri['path']) ? NULL: url($uri['path']);
$variables['attributes_array']['typeof'] = empty($entity->rdf_mapping['rdftype']) ? NULL : $entity->rdf_mapping['rdftype'];
}
// Add suggestions.
$variables['theme_hook_suggestions'][] = $entity_type;
$variables['theme_hook_suggestions'][] = $entity_type . '__' . $bundle;
$variables['theme_hook_suggestions'][] = $entity_type . '__' . $bundle . '__' . $variables['view_mode'];
if ($id = entity_id($entity_type, $entity)) {
$variables['theme_hook_suggestions'][] = $entity_type . '__' . $id;
}
}
/**
* Label callback that refers to the entity classes label method.
*/
......@@ -1245,6 +1168,39 @@ function entity_views_api() {
);
}
/**
* Implements hook_field_extra_fields().
*/
function entity_field_extra_fields() {
// Invoke specified controllers for entity types provided by the CRUD API.
$items = array();
foreach (entity_crud_get_info() as $type => $info) {
if (!empty($info['extra fields controller class'])) {
$items = array_merge_recursive($items, entity_get_extra_fields_controller($type)->fieldExtraFields());
}
}
return $items;
}
/**
* Gets the extra field controller class for a given entity type.
*
* @return EntityExtraFieldsControllerInterface|false
* The controller for the given entity type or FALSE if none is specified.
*/
function entity_get_extra_fields_controller($type = NULL) {
$static = &drupal_static(__FUNCTION__);
if (!isset($static[$type])) {
$static[$type] = FALSE;
$info = entity_get_info($type);
if (!empty($info['extra fields controller class'])) {
$static[$type] = new $info['extra fields controller class']($type);
}
}
return $static[$type];
}
/**
* Returns a property wrapper for the given data.
*
......
......@@ -587,6 +587,20 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit
$entity->content = $content;
$langcode = isset($langcode) ? $langcode : $GLOBALS['language_content']->language;
// By default add in properties for all defined extra fields.
if ($extra_field_controller = entity_get_extra_fields_controller($this->entityType)) {
$wrapper = entity_metadata_wrapper($this->entityType, $entity);
$extra = $extra_field_controller->fieldExtraFields();
$type_extra = &$extra[$this->entityType][$this->entityType]['display'];
$bundle_extra = &$extra[$this->entityType][$wrapper->getBundle()]['display'];
foreach ($wrapper as $name => $property) {
if (isset($type_extra[$name]) || isset($bundle_extra[$name])) {
$this->renderEntityProperty($wrapper, $name, $property, $view_mode, $langcode, $entity->content);
}
}
}
// Add in fields.
if (!empty($this->entityInfo['fieldable'])) {
// Perform the preparation tasks if they have not been performed yet.
......@@ -608,6 +622,24 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit
return $build;
}
/**
* Renders a single entity property.
*/
protected function renderEntityProperty($wrapper, $name, $property, $view_mode, $langcode, &$content) {
$info = $property->info();
$content[$name] = array(
'#label_hidden' => FALSE,
'#label' => $info['label'],
'#entity_wrapped' => $wrapper,
'#theme' => 'entity_property',
'#property_name' => $name,
'#access' => $property->access('view'),
'#entity_type' => $this->entityType,
);
$content['#attached']['css']['entity.theme'] = drupal_get_path('module', 'entity') . '/theme/entity.theme.css';
}
/**
* Implements EntityAPIControllerInterface.
*/
......
.entity-property-label {
font-weight: bold;
}
<?php
/**
* @file
* Holds entity module's theme functions.
*/
/**
* Returns HTML for an entity property.
*
* This is the default theme implementation to display the value of a property.
* This function can be overridden with varying levels of specificity. For
* example, for a property named 'title' displayed on the 'article' bundle,
* any of the following functions will override this default implementation.
* The first of these functions that exists is used:
* - THEMENAME_property__body__article()
* - THEMENAME_property__article()
* - THEMENAME_property__body()
* - THEMENAME_property()
*
* @param $variables
* An associative array containing:
* - label: A boolean indicating to show or hide the property label.
* - title_attributes: A string containing the attributes for the title.
* - label: The label for the property.
* - content_attributes: A string containing the attributes for the content's
* div.
* - content: The rendered property value.
* - attributes: A string containing the attributes for the wrapping div.
*
* @ingroup themeable
*/
function theme_entity_property($variables) {
$output = '';
// Render the label, if it's not hidden.
if (!$variables['label_hidden']) {
$output .= '<div' . $variables['title_attributes'] . '>' . $variables['label'] . ':&nbsp;</div>';
}
// Render the content.
$content_suffix = '';
if (!$variables['label_hidden'] || $variables['content_attributes']) {
$output .= '<div' . $variables['content_attributes'] . '>';
$content_suffix = '</div>';
}
$output .= $variables['content'] . $content_suffix;
// Render the top-level DIV.
return '<div' . $variables['attributes'] . '>' . $output . '</div>';
}
/**
* Theme preprocess function for theme_entity_property().
*
* @see theme_entity_property()
*/
function template_preprocess_entity_property(&$variables, $hook) {
$element = $variables['elements'];
$variables += array(
'theme_hook_suggestions' => array(),
'attributes_array' => array(),
);
// Generate variables from element properties.
foreach (array('label_hidden', 'label', 'property_name') as $name) {
$variables[$name] = check_plain($element['#' . $name]);
}
$variables['title_attributes_array']['class'][] = 'entity-property-label';
$variables['attributes_array'] = array_merge($variables['attributes_array'], isset($element['#attributes']) ? $element['#attributes'] : array());
$variables['property_name_css'] = strtr($element['#property_name'], '_', '-');
$variables['attributes_array']['class'][] = 'entity-property';
$variables['attributes_array']['class'][] = 'entity-property-' . $variables['property_name_css'];
// Add specific suggestions that can override the default implementation.
$variables['theme_hook_suggestions'] += array(
'entity_property__' . $element['#property_name'],
'entity_property__' . $element['#entity_type'] . '__' . $element['#property_name'],
);
// Populate the content with sensible defaults.
if (!isset($variables['content'])) {
$variables['content'] = entity_property_default_render_value_by_type($element['#entity_wrapped']->{$element['#property_name']});
}
}
/**
* Renders a property using simple defaults based upon the property type.
*
* @return string
*/
function entity_property_default_render_value_by_type(EntityMetadataWrapper $property) {
// If there is an options list or entity label, render that by default.
if ($label = $property->label()) {
if ($property instanceof EntityDrupalWrapper && $uri = entity_uri($property->type(), $property->value())) {
return l($label, $uri['path'], $uri['options']);
}
else {
return check_plain($label);
}
}
switch ($property->type()) {
case 'boolean':
return $property->value() ? t('yes') : t('no');
default:
return check_plain($property->value());
}
}
/**
* Theme process function for theme_entity_property().
*
* Taken over from template_process_field()
*
* @see theme_entity_property()
*/
function template_process_entity_property(&$variables, $hook) {
$element = $variables['elements'];
// The default theme implementation is a function, so template_process() does
// not automatically run, so we need to flatten the classes and attributes
// here. For best performance, only call drupal_attributes() when needed, and
// note that template_preprocess_field() does not initialize the
// *_attributes_array variables.
$variables['attributes'] = empty($variables['attributes_array']) ? '' : drupal_attributes($variables['attributes_array']);
$variables['title_attributes'] = empty($variables['title_attributes_array']) ? '' : drupal_attributes($variables['title_attributes_array']);
$variables['content_attributes'] = empty($variables['content_attributes_array']) ? '' : drupal_attributes($variables['content_attributes_array']);
}
/**
* Themes the exportable status of an entity.
*/
function theme_entity_status($variables) {
$status = $variables['status'];
$html = $variables['html'];
if (($status & ENTITY_FIXED) == ENTITY_FIXED) {
$label = t('Fixed');
$help = t('The configuration is fixed and cannot be changed.');
return $html ? "<span class='entity-status-fixed' title='$help'>" . $label . "</span>" : $label;
}
elseif (($status & ENTITY_OVERRIDDEN) == ENTITY_OVERRIDDEN) {
$label = t('Overridden');
$help = t('This configuration is provided by a module, but has been changed.');
return $html ? "<span class='entity-status-overridden' title='$help'>" . $label . "</span>" : $label;
}
elseif ($status & ENTITY_IN_CODE) {
$label = t('Default');
$help = t('A module provides this configuration.');
return $html ? "<span class='entity-status-default' title='$help'>" . $label . "</span>" : $label;
}
elseif ($status & ENTITY_CUSTOM) {
$label = t('Custom');
$help = t('A custom configuration by a user.');
return $html ? "<span class='entity-status-custom' title='$help'>" . $label . "</span>" : $label;
}
}
/**
* Process variables for entity.tpl.php.
*/
function template_preprocess_entity(&$variables) {
$variables['view_mode'] = $variables['elements']['#view_mode'];
$entity_type = $variables['elements']['#entity_type'];
$variables['entity_type'] = $entity_type;
$entity = $variables['elements']['#entity'];
$variables[$variables['elements']['#entity_type']] = $entity;
$info = entity_get_info($entity_type);
$variables['title'] = check_plain(entity_label($entity_type, $entity));
$uri = entity_uri($entity_type, $entity);
$variables['url'] = $uri ? url($uri['path'], $uri['options']) : FALSE;
if (isset($variables['elements']['#page'])) {
// If set by the caller, respect the page property.
$variables['page'] = $variables['elements']['#page'];
}
else {
// Else, try to automatically detect it.
$variables['page'] = $uri && $uri['path'] == $_GET['q'];
}
// Helpful $content variable for templates.
$variables['content'] = array();
foreach (element_children($variables['elements']) as $key) {
$variables['content'][$key] = $variables['elements'][$key];
}
if (!empty($info['fieldable'])) {
// Make the field variables available with the appropriate language.
field_attach_preprocess($entity_type, $entity, $variables['content'], $variables);
}
list(, , $bundle) = entity_extract_ids($entity_type, $entity);
// Gather css classes.
$variables['classes_array'][] = drupal_html_class('entity-' . $entity_type);
$variables['classes_array'][] = drupal_html_class($entity_type . '-' . $bundle);
// Add RDF type and about URI.
if (module_exists('rdf')) {
$variables['attributes_array']['about'] = empty($uri['path']) ? NULL: url($uri['path']);
$variables['attributes_array']['typeof'] = empty($entity->rdf_mapping['rdftype']) ? NULL : $entity->rdf_mapping['rdftype'];
}
// Add suggestions.
$variables['theme_hook_suggestions'][] = $entity_type;
$variables['theme_hook_suggestions'][] = $entity_type . '__' . $bundle;
$variables['theme_hook_suggestions'][] = $entity_type . '__' . $bundle . '__' . $variables['view_mode'];
if ($id = entity_id($entity_type, $entity)) {
$variables['theme_hook_suggestions'][] = $entity_type . '__' . $id;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment