diff --git a/core/includes/entity.api.php b/core/includes/entity.api.php index c12e4db..fbf6271 100644 --- a/core/includes/entity.api.php +++ b/core/includes/entity.api.php @@ -279,6 +279,29 @@ function hook_entity_view_mode_alter(&$view_mode, Drupal\Core\Entity\EntityInter } /** + * Alters the settings used for displaying an entity. + * + * @param \Drupal\entity\Plugin\Core\Entity\EntityDisplay $display + * The entity_display object that will be used to display the entity + * components. + * @param array $context + * An associative array containing: + * - entity_type: The entity type, e.g., 'node' or 'user'. + * - bundle: The bundle, e.g., 'page' or 'article'. + * - view_mode: The view mode, e.g. 'full', 'teaser'... + */ +function hook_entity_display_alter(Drupal\field\Plugin\Core\Entity\EntityDisplay $display, array $context) { + // Leave field labels out of the search index. + if ($context['entity_type'] == 'node' && $context['view_mode'] == 'search_index') { + foreach ($display->content as $name => &$properties) { + if (isset($properties['label'])) { + $properties['label'] = 'hidden'; + } + } + } +} + +/** * Define custom entity properties. * * @param string $entity_type diff --git a/core/includes/entity.inc b/core/includes/entity.inc index a866864..93b27e8 100644 --- a/core/includes/entity.inc +++ b/core/includes/entity.inc @@ -564,6 +564,100 @@ function entity_view_multiple(array $entities, $view_mode, $langcode = NULL) { } /** + * Returns the entity_display object configured for a bundle and view mode. + * + * The function reads the entity_display object from the current configuration, + * or returns a ready-to-use empty one (with no cmponent set to be displayed) + * if no display is currently configured yet for this bundle and view mode. We + * do not preemptively create new empty entity_display configuration entries + * for each existing entity type and bundle whenever a new view mode becomes + * available. Instead, the actual configuration entries are only created when + * an entity_display object is explicitely configured and saved. This function + * streamlines manipulation of entity_display objects by always returning a + * consistent object that reflects the current state of the configuration. + * + * Note that the entity_display is only actually used at render time if the view + * mode itself is configured to use dedicated display settings for the bundle + * (if not, the entity_display for the 'default' view mode is used intead). + * Thus, when fecthing the display object to use for rendering an entity, the + * field_get_actual_view_mode() function is used first to determine which + * display to load. + * + * In the much more frequent use case of assigning suggested display options + * for a field in a given view mode, the entity_display object for this view + * mode should be used directly, without using field_get_actual_view_mode(), in + * order to avoid inadvertently modifying the output of other view modes that + * might use the 'default' display too. Those options will then be effectively + * applied only if the view mode is configured to use them. + * + * Example usage: + * - Set display options for the 'body' field on article nodes in the 'default' mode: + * @code + * entity_get_display('article', 'node', 'default') + * ->setContent('body', array( + * 'type' => 'text_summary_or_trimmed', + * 'settings' => array('trim_length' => '200') + * 'weight' => 1, + * )) + * ->save(); + * @endcode + * - Get options used for the 'body' field when rendering an article node in 'full' view mode; + * @code + * $view_mode = field_get_actual_view_mode('node', 'article', 'full'); + * $display = entity_get_display('node', 'article', $view_mode); + * $options = $display->getContent('body'); + * @endcode + * + * @param string $entity_type + * The entity type. + * @param string $bundle + * The bundle. + * @param string $view_mode + * The view mode. + * + * @return \Drupal\entity\Plugin\Core\Entity\EntityDisplay + * The current entity_display configuration object for the view mode. + * + * @see field_get_actual_view_mode() + */ +function entity_get_display($entity_type, $bundle, $view_mode) { + // Try loading the display from configuration. + if ($display = entity_load('entity_display', $entity_type . '.' . $bundle . '.' . $view_mode)) { + return $display; + } + + // If not found, create a fresh display object. + return entity_create('entity_display', array( + 'targetEntityType' => $entity_type, + 'bundle' => $bundle, + 'viewMode' => $view_mode, + )); +} + +/** + * Adjusts weights and visibility of components in displayed entities. + * + * This is used as a #pre_render callback. + */ +function _entity_view_pre_render($elements) { + $display = $elements['#entity_display']; + + $extra_fields = field_info_extra_fields($display->targetEntityType, $display->bundle, 'display'); + foreach (array_keys($extra_fields) as $name) { + if (isset($elements[$name]) && (!isset($elements[$name]['#access']) || $elements[$name]['#access'])) { + if ($options = $display->getComponent($name)) { + $elements[$name]['#weight'] = $options['weight']; + } + else { + $elements[$name]['#access'] = FALSE; + } + } + } + + return $elements; +} + +/** * Returns the entity query object for this entity type. * * @param $entity_type diff --git a/core/lib/Drupal/Core/Entity/EntityRenderController.php b/core/lib/Drupal/Core/Entity/EntityRenderController.php index 2cd0578..8448e48 100644 --- a/core/lib/Drupal/Core/Entity/EntityRenderController.php +++ b/core/lib/Drupal/Core/Entity/EntityRenderController.php @@ -30,37 +30,69 @@ public function buildContent(array $entities = array(), $view_mode = 'full', $la // Allow modules to change the view mode. $context = array('langcode' => $langcode); - $prepare = array(); - foreach ($entities as $key => $entity) { + $view_modes = array(); + $displays = array(); + + foreach ($entities as $entity) { // Remove previously built content, if exists. $entity->content = array(); drupal_alter('entity_view_mode', $view_mode, $entity, $context); $entity->content['#view_mode'] = $view_mode; - $prepare[$view_mode][$key] = $entity; + $view_modes[$view_mode][$entity->id()] = $entity; + + $bundle = $entity->bundle(); + + // Load the corresponding display settings if not stored yet. + if (!isset($displays[$view_mode][$bundle])) { + // If the view mode is not configured to use dedicated settings, + // rendering is done using 'default'. + $actual_mode = field_get_actual_view_mode($this->entityType, $bundle, $view_mode); + + // Get the display object for this bundle and view mode. + $display = entity_get_display($this->entityType, $bundle, $actual_mode); + $display->originalViewMode = $view_mode; + + // Let modules alter the display. + // Note: if config entities get a static cache at some point, the + // objects should be cloned before running drupal_alter(). + $display_context = array( + 'entity_type' => $this->entityType, + 'bundle' => $bundle, + 'view_mode' => $view_mode, + ); + drupal_alter('entity_display', $display, $display_context); + + $displays[$view_mode][$bundle] = $display; + } + + // Assigning weights to 'extra fields' is done in a pre_render callback. + $entity->content['#pre_render'] = array('_entity_view_pre_render'); + $entity->content['#entity_display'] = $displays[$view_mode][$bundle]; } // Prepare and build field content, grouped by view mode. - foreach ($prepare as $view_mode => $prepare_entities) { - $call = array(); + foreach ($view_modes as $view_mode => $view_mode_entities) { + $call_prepare = array(); // To ensure hooks are only run once per entity, check for an - // entity_view_prepared flag and only process items without it. - foreach ($prepare_entities as $entity) { - if (empty($entity->entity_view_prepared)) { + // entity_view_prepared flag and only process relevant entities. + foreach ($view_mode_entities as $entity) { + if (empty($entity->entity_view_prepared) || $entity->entity_view_prepared != $view_mode) { // Add this entity to the items to be prepared. - $call[$entity->id()] = $entity; + $call_prepare[$entity->id()] = $entity; - // Mark this item as prepared. - $entity->entity_view_prepared = TRUE; + // Mark this item as prepared for this view mode. + $entity->entity_view_prepared = $view_mode; } } - if (!empty($call)) { - field_attach_prepare_view($this->entityType, $call, $view_mode, $langcode); - module_invoke_all('entity_prepare_view', $call, $this->entityType); + if (!empty($call_prepare)) { + field_attach_prepare_view($this->entityType, $call_prepare, $displays[$view_mode], $langcode); + module_invoke_all('entity_prepare_view', $call_prepare, $this->entityType); } - foreach ($entities as $entity) { - $entity->content += field_attach_view($this->entityType, $entity, $view_mode, $langcode); + + foreach ($view_mode_entities as $entity) { + $entity->content += field_attach_view($this->entityType, $entity, $displays[$view_mode][$entity->bundle()], $langcode); } } } diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index ab757a9..14d6d3c 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -376,15 +376,15 @@ function _comment_body_field_create($info) { 'bundle' => 'comment_node_' . $info->type, 'settings' => array('text_processing' => 1), 'required' => TRUE, - 'display' => array( - 'default' => array( - 'label' => 'hidden', - 'type' => 'text_default', - 'weight' => 0, - ), - ), ); field_create_instance($instance); + entity_get_display('comment', 'comment_node_' . $info->type, 'default') + ->setComponent('comment_body', array( + 'label' => 'hidden', + 'type' => 'text_default', + 'weight' => 0, + )) + ->save(); } } diff --git a/core/modules/entity/entity.install b/core/modules/entity/entity.install new file mode 100644 index 0000000..5d344b2 --- /dev/null +++ b/core/modules/entity/entity.install @@ -0,0 +1,47 @@ +get()) { + return $config; + } + + // Initialize a fresh structure. + $uuid = new Uuid(); + $properties = array( + 'id' => $id, + 'uuid' => $uuid->generate(), + 'targetEntityType' => $entity_type, + 'bundle' => $bundle, + 'viewMode' => $view_mode, + 'content' => array(), + ); + foreach ($properties as $key => $value) { + $config->set($key, $value); + } + return $config; +} diff --git a/core/modules/entity/lib/Drupal/entity/EntityDisplayStorageController.php b/core/modules/entity/lib/Drupal/entity/EntityDisplayStorageController.php new file mode 100644 index 0000000..4ec76c7 --- /dev/null +++ b/core/modules/entity/lib/Drupal/entity/EntityDisplayStorageController.php @@ -0,0 +1,37 @@ +get($name); + } + return $properties; + } + +} diff --git a/core/modules/entity/lib/Drupal/entity/Plugin/Core/Entity/EntityDisplay.php b/core/modules/entity/lib/Drupal/entity/Plugin/Core/Entity/EntityDisplay.php new file mode 100644 index 0000000..8b4b808 --- /dev/null +++ b/core/modules/entity/lib/Drupal/entity/Plugin/Core/Entity/EntityDisplay.php @@ -0,0 +1,321 @@ +originalViewMode = $this->viewMode; + } + + /** + * Overrides \Drupal\Core\Entity\Entity::id(). + */ + public function id() { + return $this->targetEntityType . '.' . $this->bundle . '.' . $this->viewMode; + } + + /** + * Overrides \Drupal\config\ConfigEntityBase::save(). + */ + public function save() { + // Build an ID if none is set. + if (empty($this->id)) { + $this->id = $this->id(); + } + return parent::save(); + } + + /** + * Creates a duplicate of the EntityDisplay object on a different view mode. + * + * The new object necessarily has the same $targetEntityType and $bundle + * properties than the original one. + * + * @param $view_mode + * The view mode for the new object. + * + * @return \Drupal\entity\Plugin\Core\Entity\EntityDisplay + * The new object. + */ + public function createCopy($view_mode) { + $display = $this->createDuplicate(); + $display->viewMode = $display->originalViewMode = $view_mode; + return $display; + } + + /** + * Gets the display options for all components. + * + * @return array + * The array of display options, keyed by component name. + */ + public function getComponents() { + $options = array(); + foreach ($this->content as $name => $options) { + if (!isset($options['visible']) || $options['visible']) { + $options[$name] = $options; + } + } + return $options; + } + + /** + * Gets the display options set for a component. + * + * @param string $name + * The name of the component. + * + * @return array|null + * The display options for the comppnent, or NULL if the component is not + * displayed. + */ + public function getComponent($name) { + // We always store 'extra fields', whether they are visible or hidden. + $extra_fields = field_info_extra_fields($this->targetEntityType, $this->bundle, 'display'); + if (isset($extra_fields[$name])) { + // If we have explicit settings, return an array or NULL depending on + // visibility. + if (isset($this->content[$name])) { + if ($this->content[$name]['visible']) { + return array( + 'weight' => $this->content[$name]['weight'], + ); + } + else { + return NULL; + } + } + + // If no explicit settings for the extra field, look at the default + // visibility in its definition. + $definition = $extra_fields[$name]; + if (!isset($definition['visible']) || $definition['visible'] == TRUE) { + return array( + 'weight' => $definition['weight'] + ); + } + else { + return NULL; + } + } + + if (isset($this->content[$name])) { + return $this->content[$name]; + } + } + + /** + * Sets the display options for a component. + * + * @param string $name + * The name of the component. + * @param array $options + * The display options. + * + * @return \Drupal\entity\Plugin\Core\Entity\EntityDisplay + * The EntityDisplay object. + */ + public function setComponent($name, array $options = array()) { + // If no weight specified, make sure the field sinks at the bottom. + if (!isset($options['weight'])) { + $max = $this->getHighestWeight(); + $options['weight'] = isset($max) ? $max + 1 : 0; + } + + if ($instance = field_info_instance($this->targetEntityType, $name, $this->bundle)) { + $field = field_info_field($instance['field_name']); + $options = drupal_container()->get('plugin.manager.field.formatter')->prepareConfiguration($field['type'], $options); + + // Clear the persisted formatter, if any. + unset($this->formatters[$name]); + } + + // We always store 'extra fields', whether they are visible or hidden. + $extra_fields = field_info_extra_fields($this->targetEntityType, $this->bundle, 'display'); + if (isset($extra_fields[$name])) { + $options['visible'] = TRUE; + } + + $this->content[$name] = $options; + + return $this; + } + + /** + * Sets a component to be hidden. + * + * @param string $name + * The name of the component. + * + * @return \Drupal\entity\Plugin\Core\Entity\EntityDisplay + * The EntityDisplay object. + */ + public function removeComponent($name) { + $extra_fields = field_info_extra_fields($this->targetEntityType, $this->bundle, 'display'); + if (isset($extra_fields[$name])) { + // 'Extra fields' are exposed in hooks and can appear at any given time. + // Therefore we store extra fields that are explicitly being hidden, so + // that we can differenciate with those that are simply not configured + // yet. + $this->content[$name] = array( + 'visible' => FALSE, + ); + } + else { + unset($this->content[$name]); + unset($this->formatters[$name]); + } + + return $this; + } + + /** + * Returns the highest weight of the components in the display. + * + * @return int|null + * The highest weight of the components in the display, or NULL if the + * display is empty. + */ + public function getHighestWeight() { + $weights = array(); + + // Collect weights for the components in the display. + foreach ($this->content as $options) { + if (isset($options['weight'])) { + $weights[] = $options['weight']; + } + } + + // Let other modules feedback about their own additions. + $weights = array_merge($weights, module_invoke_all('field_info_max_weight', $this->targetEntityType, $this->bundle, $this->viewMode)); + + return $weights ? max($weights) : NULL; + } + + /** + * Returns the Formatter plugin for a field. + * + * @param string $field_name + * The field name. + * + * @return \Drupal\field\Plugin\Type\Formatter\FormatterInterface + * If the field is not hidden, the Formatter plugin to use for rendering + * it. + */ + public function getFormatter($field_name) { + if (isset($this->formatters[$field_name])) { + return $this->formatters[$field_name]; + } + + // Instanciate the formatter object from the stored display properties. + if ($configuration = $this->getComponent($field_name)) { + $instance = field_info_instance($this->targetEntityType, $field_name, $this->bundle); + $formatter = drupal_container()->get('plugin.manager.field.formatter')->getInstance(array( + 'instance' => $instance, + 'view_mode' => $this->originalViewMode, + // No need to prepare, defaults have been merged in setComponent(). + 'prepare' => FALSE, + 'configuration' => $configuration + )); + } + else { + $formatter = NULL; + } + + // Persist the formatter object. + $this->formatters[$field_name] = $formatter; + return $formatter; + } + +} diff --git a/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayTest.php b/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayTest.php new file mode 100644 index 0000000..20d6964 --- /dev/null +++ b/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayTest.php @@ -0,0 +1,188 @@ + 'Entity display configuration entities', + 'description' => 'Tests the entity display configuration entities.', + 'group' => 'Entity API', + ); + } + + protected function setUp() { + parent::setUp(); + + $this->enableModules(array('system', 'field')); + } + + /** + * Tests basic CRUD operations on EntityDisplay objects. + */ + public function testEntityDisplayCRUD() { + $display = entity_create('entity_display', array( + 'targetEntityType' => 'entity_test', + 'bundle' => 'entity_test', + 'viewMode' => 'default', + )); + + $expected = array(); + + // Check that providing no 'weight' results in the highest current weight + // being assigned. + $expected['component_1'] = array('weight' => 0); + $expected['component_2'] = array('weight' => 1); + $display->setComponent('component_1'); + $display->setComponent('component_2'); + $this->assertEqual($display->getComponent('component_1'), $expected['component_1']); + $this->assertEqual($display->getComponent('component_2'), $expected['component_2']); + + // Check that arbitrary options are correctly stored. + $expected['component_3'] = array('weight' => 10, 'foo' => 'bar'); + $display->setComponent('component_3', $expected['component_3']); + $this->assertEqual($display->getComponent('component_3'), $expected['component_3']); + + // Check that the display can be properly saved and read back. + $display->save(); + $display = entity_load('entity_display', $display->id()); + foreach (array('component_1', 'component_2', 'component_3') as $name) { + $this->assertEqual($display->getComponent($name), $expected[$name]); + } + + // Check that a component can be removed. + $display->removeComponent('component_3'); + $this->assertNULL($display->getComponent('component_3')); + + // Check that the removal is correctly persisted. + $display->save(); + $display = entity_load('entity_display', $display->id()); + $this->assertNULL($display->getComponent('component_3')); + } + + /** + * Tests entity_get_display(). + */ + public function testEntityGetDisplay() { + // Check that entity_get_display() returns a fresh object when no + // configuration entry exists. + $display = entity_get_display('entity_test', 'entity_test', 'default'); + $this->assertTrue($display->isNew()); + + // Add some components and save the display. + $display->setComponent('component_1', array('weight' => 10)) + ->save(); + + // Check that entity_get_display() returns the correct object. + $display = entity_get_display('entity_test', 'entity_test', 'default'); + $this->assertFalse($display->isNew()); + $this->assertEqual($display->id, 'entity_test.entity_test.default'); + $this->assertEqual($display->getComponent('component_1'), array('weight' => 10)); + } + + /** + * Tests the behavior of a field conponent within an EntityDisplay object. + */ + public function testExtraFieldComponent() { + $display = entity_create('entity_display', array( + 'targetEntityType' => 'entity_test', + 'bundle' => 'entity_test', + 'viewMode' => 'default', + )); + + // Check that the default visibility taken into account for extra fields + // unknown in the display. + $this->assertEqual($display->getComponent('display_extra_field'), array('weight' => 5)); + $this->assertNull($display->getComponent('display_extra_field_hidden')); + + // Check that setting explicit options overrides the defaults. + $display->removeComponent('display_extra_field'); + $display->setComponent('display_extra_field_hidden', array('weight' => 10)); + $this->assertNull($display->getComponent('display_extra_field')); + $this->assertEqual($display->getComponent('display_extra_field_hidden'), array('weight' => 10)); + } + + /** + * Tests the behavior of a field conponent within an EntityDisplay object. + */ + public function testFieldComponent() { + $this->enableModules(array('field_sql_storage', 'field_test')); + + $display = entity_create('entity_display', array( + 'targetEntityType' => 'entity_test', + 'bundle' => 'entity_test', + 'viewMode' => 'default', + )); + + // Create a field and an instance. + $field = array( + 'field_name' => 'test_field', + 'type' => 'test_field' + ); + field_create_field($field); + $instance = array( + 'field_name' => $field['field_name'], + 'entity_type' => 'entity_test', + 'bundle' => 'entity_test', + ); + field_create_instance($instance); + + // Check that providing no options results in default values being used. + $display->setComponent($field['field_name']); + $field_type_info = field_info_field_types($field['type']); + $default_formatter = $field_type_info['default_formatter']; + $default_settings = field_info_formatter_settings($default_formatter); + $expected = array( + 'weight' => 0, + 'label' => 'above', + 'type' => $default_formatter, + 'settings' => $default_settings, + ); + $this->assertEqual($display->getComponent($field['field_name']), $expected); + + // Check that the getFormatter() method returns the correct formatter plugin. + $formatter = $display->getFormatter($field['field_name']); + $this->assertEqual($formatter->getPluginId(), $default_formatter); + $this->assertEqual($formatter->getSettings(), $default_settings); + + // Check that the formatter is statically persisted, by assigning an + // arbitrary property and reading it back. + $random_value = $this->randomString(); + $formatter->randomValue = $random_value; + $formatter = $display->getFormatter($field['field_name']); + $this->assertEqual($formatter->randomValue, $random_value ); + + // Check that changing the definition creates a new formatter. + $display->setComponent($field['field_name'], array( + 'type' => 'field_test_multiple', + )); + $formatter = $display->getFormatter($field['field_name']); + $this->assertEqual($formatter->getPluginId(), 'field_test_multiple'); + $this->assertFalse(isset($formatter->randomValue)); + + // Check that specifying an unknown formatter (e.g. case of a disabled + // module) gets stored as is in the display, but results in the default + // formatter being used. + $display->setComponent($field['field_name'], array( + 'type' => 'unknown_formatter', + )); + $options = $display->getComponent($field['field_name']); + $this->assertEqual($options['type'], 'unknown_formatter'); + $formatter = $display->getFormatter($field['field_name']); + $this->assertEqual($formatter->getPluginId(), $default_formatter); + } + +} diff --git a/core/modules/field/field.api.php b/core/modules/field/field.api.php index 22a9ed7..f9ff7af 100644 --- a/core/modules/field/field.api.php +++ b/core/modules/field/field.api.php @@ -1104,12 +1104,13 @@ function hook_field_attach_purge($entity_type, $entity, $field, $instance) { * - entity_type: The type of $entity; for example, 'node' or 'user'. * - entity: The entity with fields to render. * - view_mode: View mode; for example, 'full' or 'teaser'. - * - display: Either a view mode string or an array of display settings. If - * this hook is being invoked from field_attach_view(), the 'display' - * element is set to the view mode string. If this hook is being invoked - * from field_view_field(), this element is set to the $display argument and - * the view_mode element is set to '_custom'. See field_view_field() for - * more information on what its $display argument contains. + * - display_options: Either a view mode string or an array of display + * options. If this hook is being invoked from field_attach_view(), the + * 'display_options' element is set to the view mode string. If this hook + * is being invoked from field_view_field(), this element is set to the + * $display_options argument and the view_mode element is set to '_custom'. + * See field_view_field() for more information on what its $display_options + * argument contains. * - language: The language code used for rendering. */ function hook_field_attach_view_alter(&$output, $context) { @@ -1942,94 +1943,6 @@ function hook_field_info_max_weight($entity_type, $bundle, $context) { } /** - * Alters the display settings of a field before it is displayed. - * - * Note that instead of hook_field_display_alter(), which is called for all - * fields on all entity types, hook_field_display_ENTITY_TYPE_alter() may be - * used to alter display settings for fields on a specific entity type only. - * - * This hook is called once per field per displayed entity. If the result of the - * hook involves reading from the database, it is highly recommended to - * statically cache the information. - * - * @param array $display_properties - * The display settings that will be used to display the field values, as - * found in the 'display' key of $instance definitions. - * @param array $context - * An associative array containing: - * - entity_type: The entity type, e.g., 'node' or 'user'. - * - bundle: The bundle, e.g., 'page' or 'article'. - * - field: The field being rendered. - * - instance: The instance being rendered. - * - view_mode: The view mode, e.g. 'full', 'teaser'... - * - * @see hook_field_display_ENTITY_TYPE_alter() - */ -function hook_field_display_alter(array &$display_properties, array $context) { - // Leave field labels out of the search index. - // Note: The check against $context['entity_type'] == 'node' could be avoided - // by using hook_field_display_node_alter() instead of - // hook_field_display_alter(), resulting in less function calls when - // rendering non-node entities. - if ($context['entity_type'] == 'node' && $context['view_mode'] == 'search_index') { - $display_properties['label'] = 'hidden'; - } -} - -/** - * Alters the display settings of a field before it is displayed. - * - * Modules can implement hook_field_display_ENTITY_TYPE_alter() to alter display - * settings for fields on a specific entity type, rather than implementing - * hook_field_display_alter(). - * - * This hook is called once per field per displayed entity. If the result of the - * hook involves reading from the database, it is highly recommended to - * statically cache the information. - * - * @param array $display_properties - * The display settings that will be used to display the field values, as - * found in the 'display' key of $instance definitions. - * @param array $context - * An associative array containing: - * - entity_type: The entity type, e.g., 'node' or 'user'. - * - bundle: The bundle, e.g., 'page' or 'article'. - * - field: The field being rendered. - * - instance: The instance being rendered. - * - view_mode: The view mode, e.g. 'full', 'teaser'... - * - * @see hook_field_display_alter() - */ -function hook_field_display_ENTITY_TYPE_alter(array &$display_properties, array $context) { - // Leave field labels out of the search index. - if ($context['view_mode'] == 'search_index') { - $display_properties['label'] = 'hidden'; - } -} - -/** - * Alters the display settings of pseudo-fields before an entity is displayed. - * - * This hook is called once per displayed entity. If the result of the hook - * involves reading from the database, it is highly recommended to statically - * cache the information. - * - * @param $displays - * An array of display settings for the pseudo-fields in the entity, keyed by - * pseudo-field names. - * @param $context - * An associative array containing: - * - entity_type: The entity type; e.g., 'node' or 'user'. - * - bundle: The bundle name. - * - view_mode: The view mode, e.g. 'full', 'teaser'... - */ -function hook_field_extra_fields_display_alter(&$displays, $context) { - if ($context['entity_type'] == 'taxonomy_term' && $context['view_mode'] == 'full') { - $displays['description']['visible'] = FALSE; - } -} - -/** * Alters the widget properties of a field instance on a given entity type * before it gets displayed. * diff --git a/core/modules/field/field.attach.inc b/core/modules/field/field.attach.inc index 29ebd3b..1a006ec 100644 --- a/core/modules/field/field.attach.inc +++ b/core/modules/field/field.attach.inc @@ -7,6 +7,7 @@ use Drupal\field\FieldValidationException; use Drupal\Core\Entity\EntityInterface; +use Drupal\entity\Plugin\Core\Entity\EntityDisplay; /** * @defgroup field_storage Field Storage API @@ -715,26 +716,6 @@ function _field_invoke_widget_target() { } /** - * Defines a 'target function' for field_invoke_method(). - * - * Used to invoke methods on an instance's formatter. - * - * @param mixed $display - * Can be either: - * - The name of a view mode. - * - An array of display properties, as found in - * $instance['display'][$view_mode]. - * - * @return callable $target_function - * A 'target function' for field_invoke_method(). - */ -function _field_invoke_formatter_target($display) { - return function ($instance) use ($display) { - return $instance->getFormatter($display); - }; -} - -/** * Adds form elements for all fields for an entity to a form structure. * * The form elements for the entity's fields are added by reference as direct @@ -1361,10 +1342,10 @@ function field_attach_delete_revision($entity_type, $entity) { * * @param $entity_type * The type of entities in $entities; e.g. 'node' or 'user'. - * @param $entities + * @param array $entities * An array of entities, keyed by entity ID. - * @param $view_mode - * View mode, e.g. 'full', 'teaser'... + * @param array $displays + * An array of entity display objects, keyed by bundle name. * @param $langcode * (Optional) The language the field values are to be shown in. If no language * is provided the current language is used. @@ -1372,7 +1353,7 @@ function field_attach_delete_revision($entity_type, $entity) { * An associative array of additional options. See field_invoke_method() for * details. */ -function field_attach_prepare_view($entity_type, $entities, $view_mode, $langcode = NULL, array $options = array()) { +function field_attach_prepare_view($entity_type, array $entities, array $displays, $langcode = NULL, array $options = array()) { $options['langcode'] = array(); // To ensure hooks are only run once per entity, only process items without @@ -1396,8 +1377,15 @@ function field_attach_prepare_view($entity_type, $entities, $view_mode, $langcod $null = NULL; // First let the field types do their preparation. _field_invoke_multiple('prepare_view', $entity_type, $prepare, $null, $null, $options); - // Then let the formatters do their own specific massaging. - field_invoke_method_multiple('prepareView', _field_invoke_formatter_target($view_mode), $prepare, $view_mode, $null, $options); + // Then let the formatters do their own specific massaging. For each + // instance, call the prepareView() method on the formatter object handed by + // the entity display. + $target_function = function ($instance) use ($displays) { + if (isset($displays[$instance['bundle']])) { + return $displays[$instance['bundle']]->getFormatter($instance['field_name']); + } + }; + field_invoke_method_multiple('prepareView', $target_function, $prepare, $null, $null, $options); } /** @@ -1437,37 +1425,38 @@ function field_attach_prepare_view($entity_type, $entities, $view_mode, $langcod * The type of $entity; e.g. 'node' or 'user'. * @param Drupal\Core\Entity\EntityInterface $entity * The entity with fields to render. - * @param $view_mode - * View mode, e.g. 'full', 'teaser'... + * @param \Drupal\entity\Plugin\Core\Entity\EntityDisplay $display + * The entity display object. * @param $langcode * The language the field values are to be shown in. If no language is * provided the current language is used. * @param array $options * An associative array of additional options. See field_invoke_method() for * details. - * @return + + * @return array * A renderable array for the field values. */ -function field_attach_view($entity_type, EntityInterface $entity, $view_mode, $langcode = NULL, array $options = array()) { +function field_attach_view($entity_type, EntityInterface $entity, EntityDisplay $display, $langcode = NULL, array $options = array()) { // Determine the actual language code to display for each field, given the // language codes available in the field data. $options['langcode'] = field_language($entity_type, $entity, NULL, $langcode); - // Invoke field_default_view(). + // For each instance, call the view() method on the formatter object handed + // by the entity display. + $target_function = function ($instance) use ($display) { + return $display->getFormatter($instance['field_name']); + }; $null = NULL; - $output = field_invoke_method('view', _field_invoke_formatter_target($view_mode), $entity, $view_mode, $null, $options); - - // Add custom weight handling. - $output['#pre_render'][] = '_field_extra_fields_pre_render'; - $output['#entity_type'] = $entity_type; - $output['#bundle'] = $entity->bundle(); + $output = field_invoke_method('view', $target_function, $entity, $null, $null, $options); // Let other modules alter the renderable array. + $view_mode = $display->originalViewMode; $context = array( 'entity_type' => $entity_type, 'entity' => $entity, 'view_mode' => $view_mode, - 'display' => $view_mode, + 'display_options' => $view_mode, 'langcode' => $langcode, ); drupal_alter('field_attach_view', $output, $context); diff --git a/core/modules/field/field.crud.inc b/core/modules/field/field.crud.inc index ac66c77..a62ad2e 100644 --- a/core/modules/field/field.crud.inc +++ b/core/modules/field/field.crud.inc @@ -588,7 +588,6 @@ function _field_write_instance(&$instance, $update = FALSE) { // Set defaults. $instance += array( 'settings' => array(), - 'display' => array(), 'widget' => array(), 'required' => FALSE, 'label' => $instance['field_name'], @@ -615,30 +614,6 @@ function _field_write_instance(&$instance, $update = FALSE) { $instance['widget']['module'] = $widget_type['module']; $instance['widget']['settings'] += field_info_widget_settings($instance['widget']['type']); - // Make sure there are at least display settings for the 'default' view mode, - // and fill in defaults for each view mode specified in the definition. - $instance['display'] += array( - 'default' => array(), - ); - foreach ($instance['display'] as $view_mode => $display) { - $display += array( - 'label' => 'above', - 'type' => isset($field_type['default_formatter']) ? $field_type['default_formatter'] : 'hidden', - 'settings' => array(), - ); - if ($display['type'] != 'hidden') { - $formatter_type = field_info_formatter_types($display['type']); - $display['module'] = $formatter_type['module']; - $display['settings'] += field_info_formatter_settings($display['type']); - } - // If no weight specified, make sure the field sinks at the bottom. - if (!isset($display['weight'])) { - $max_weight = field_info_max_weight($instance['entity_type'], $instance['bundle'], $view_mode); - $display['weight'] = isset($max_weight) ? $max_weight + 1 : 0; - } - $instance['display'][$view_mode] = $display; - } - // The serialized 'data' column contains everything from $instance that does // not have its own column and is not automatically populated when the // instance is read. diff --git a/core/modules/field/field.info.inc b/core/modules/field/field.info.inc index 5fab889..688d611 100644 --- a/core/modules/field/field.info.inc +++ b/core/modules/field/field.info.inc @@ -522,7 +522,7 @@ function field_info_extra_fields($entity_type, $bundle, $context) { } /** - * Returns the maximum weight of all the components in an entity. + * Returns the maximum weight of all the components in a form entity. * * This includes fields, 'extra_fields', and other components added by * third-party modules (e.g. field_group). @@ -531,33 +531,25 @@ function field_info_extra_fields($entity_type, $bundle, $context) { * The type of entity; e.g. 'node' or 'user'. * @param $bundle * The bundle name. - * @param $context - * The context for which the maximum weight is requested. Either 'form', or - * the name of a view mode. * * @return * The maximum weight of the entity's components, or NULL if no components * were found. */ -function field_info_max_weight($entity_type, $bundle, $context) { +function field_info_max_weight($entity_type, $bundle) { $weights = array(); // Collect weights for fields. foreach (field_info_instances($entity_type, $bundle) as $instance) { - if ($context == 'form') { - $weights[] = $instance['widget']['weight']; - } - elseif (isset($instance['display'][$context]['weight'])) { - $weights[] = $instance['display'][$context]['weight']; - } + $weights[] = $instance['widget']['weight']; } // Collect weights for extra fields. - foreach (field_info_extra_fields($entity_type, $bundle, $context) as $extra) { + foreach (field_info_extra_fields($entity_type, $bundle, 'form') as $extra) { $weights[] = $extra['weight']; } // Let other modules feedback about their own additions. - $weights = array_merge($weights, module_invoke_all('field_info_max_weight', $entity_type, $bundle, $context)); + $weights = array_merge($weights, module_invoke_all('field_info_max_weight', $entity_type, $bundle, 'form')); $max_weight = $weights ? max($weights) : NULL; return $max_weight; diff --git a/core/modules/field/field.install b/core/modules/field/field.install index b88faa8..a8ae8ad 100644 --- a/core/modules/field/field.install +++ b/core/modules/field/field.install @@ -357,15 +357,6 @@ function _update_7000_field_create_instance($field, &$instance) { 'deleted' => 0, ); - // Merge in display defaults. - if (isset($instance['display'])) { - foreach ($instance['display'] as &$display) { - $display += array( - 'weight' => 0, - ); - } - } - // The serialized 'data' column contains everything from $instance that does // not have its own column and is not automatically populated when the // instance is read. @@ -403,6 +394,95 @@ function field_update_8001() { } /** + * Migrate all instance display settings to configuration. + * + * @ingroup config_upgrade + */ +function field_update_8002() { + global $conf; + + $displays = array(); + module_load_install('entity'); + + $query = db_select('field_config_instance', 'fc')->fields('fc'); + foreach ($query->execute() as $record) { + // Unserialize the data array and start investigating the display key + // which holds the configuration of this instance for all view modes. + $data = unserialize($record->data); + + // Skip field instances that were created directly with the new API earlier + // in the upgrade path. + if (!isset($data['display'])) { + continue; + } + + foreach ($data['display'] as $view_mode => $display_options) { + // Determine name and create initial entry in the $displays array if it + // does not exist yet. + $display_id = $record->entity_type . '.' . $record->bundle . '.' . $view_mode; + if (!isset($displays[$display_id])) { + $displays[$display_id] = _entity_update_8000_get_display_config($record->entity_type, $record->bundle, $view_mode); + } + + // The display object does not store hidden fields. + if ($display_options['type'] != 'hidden') { + // We do not need the 'module' key anymore. + unset($display_options['module']); + $displays[$display_id]->set("content.$record->field_name", $display_options); + } + } + + // Remove the 'display' key and save the record back into the table. + unset($data['display']); + db_update('field_config_instance') + ->condition('id', $record->id) + ->fields(array( + 'data' => serialize($data), + )) + ->execute(); + } + + // Migration of 'extra_fields' display settings. Avoid calling + // entity_get_info() by iterating directly on the global $conf array. + foreach ($conf as $variable_name => $variable_value) { + if (preg_match('/field_bundle_settings_(.*)__(.*)/', $variable_name, $matches)) { + $entity_type = $matches[1]; + $bundle = $matches[2]; + if (isset($variable_value['extra_fields']['display'])) { + foreach ($variable_value['extra_fields']['display'] as $field_name => $field_settings) { + foreach ($field_settings as $view_mode => $display_options) { + // Determine name and create initial entry in the $displays array + // if it does not exist yet. + $display_id = $entity_type . '.' . $bundle . '.' . $view_mode; + if (!isset($displays[$display_id])) { + $displays[$display_id] = _entity_update_8000_get_display_config($entity_type, $bundle, $view_mode); + } + + // Set options in the display. + $new_options = array('visible' => $display_options['visible']); + // The display object only stores the weght for 'visible' extra fields. + if ($display_options['visible']) { + $new_options['weight'] = $display_options['weight']; + } + $displays[$display_id]->set("content.$field_name", $new_options); + + // Remove the old entry. + unset($variable_value['extra_fields']['display']); + variable_set($variable_name, $variable_value); + } + } + } + } + } + + // Save the displays to configuration. + foreach ($displays as $config) { + $config->save(); + } +} + +/** * @} End of "addtogroup updates-7.x-to-8.x". * The next series of updates should start at 9000. */ + diff --git a/core/modules/field/field.module b/core/modules/field/field.module index 686e3f5..bbf984c 100644 --- a/core/modules/field/field.module +++ b/core/modules/field/field.module @@ -682,7 +682,6 @@ function field_bundle_settings($entity_type, $bundle, $settings = NULL) { ); $settings['extra_fields'] += array( 'form' => array(), - 'display' => array(), ); return $settings; @@ -725,7 +724,33 @@ function field_view_mode_settings($entity_type, $bundle) { } /** - * Returns the display settings to use for pseudo-fields in a given view mode. + * Returns the actual view mode to use for a bundle. + * + * Depending on the current configuration of the bundle for this view mode, the + * function returns either: + * - the value of the incoming $view_mode parameter, + * - or 'default'. + * + * @param string $entity_type + * The type of $entity; e.g., 'node' or 'user'. + * @param string $bundle + * The bundle name. + * @param string $view_mode + * The view mode. + * + * @return string + * The actual view mode to use for this entity type and bundle. + * + * @todo Move this function over to the entity API when the underlying + * variable is moved to configuration files. + */ +function field_get_actual_view_mode($entity_type, $bundle, $view_mode) { + $view_mode_settings = field_view_mode_settings($entity_type, $bundle); + return (!empty($view_mode_settings[$view_mode]['custom_settings']) ? $view_mode : 'default'); +} + +/** + * Returns the display options to use for pseudo-fields in a given view mode. * * @param $entity_type * The type of $entity; e.g., 'node' or 'user'. @@ -735,29 +760,20 @@ function field_view_mode_settings($entity_type, $bundle) { * The view mode. * * @return - * The display settings to be used when viewing the bundle's pseudo-fields. + * The display options to be used when viewing the bundle's pseudo-fields. */ function field_extra_fields_get_display($entity_type, $bundle, $view_mode) { - // Check whether the view mode uses custom display settings or the 'default' - // mode. - $view_mode_settings = field_view_mode_settings($entity_type, $bundle); - $actual_mode = (!empty($view_mode_settings[$view_mode]['custom_settings'])) ? $view_mode : 'default'; + + $actual_mode = field_get_actual_view_mode($entity_type, $bundle, $view_mode); + $entity_display = entity_get_display($entity_type, $bundle, $actual_mode); $extra_fields = field_info_extra_fields($entity_type, $bundle, 'display'); - $displays = array(); + $options = array(); foreach ($extra_fields as $name => $value) { - $displays[$name] = $extra_fields[$name]['display'][$actual_mode]; + $options[$name] = $entity_display->getComponent($name); } - // Let modules alter the display settings. - $context = array( - 'entity_type' => $entity_type, - 'bundle' => $bundle, - 'view_mode' => $view_mode, - ); - drupal_alter('field_extra_fields_display', $displays, $context); - - return $displays; + return $options; } /** @@ -767,23 +783,10 @@ function _field_extra_fields_pre_render($elements) { $entity_type = $elements['#entity_type']; $bundle = $elements['#bundle']; - if (isset($elements['#type']) && $elements['#type'] == 'form') { - $extra_fields = field_info_extra_fields($entity_type, $bundle, 'form'); - foreach ($extra_fields as $name => $settings) { - if (isset($elements[$name])) { - $elements[$name]['#weight'] = $settings['weight']; - } - } - } - elseif (isset($elements['#view_mode'])) { - $view_mode = $elements['#view_mode']; - $extra_fields = field_extra_fields_get_display($entity_type, $bundle, $view_mode); - foreach ($extra_fields as $name => $settings) { - if (isset($elements[$name])) { - $elements[$name]['#weight'] = $settings['weight']; - // Visibility: make sure we do not accidentally show a hidden element. - $elements[$name]['#access'] = isset($elements[$name]['#access']) ? ($elements[$name]['#access'] && $settings['visible']) : $settings['visible']; - } + $extra_fields = field_info_extra_fields($entity_type, $bundle, 'form'); + foreach ($extra_fields as $name => $settings) { + if (isset($elements[$name])) { + $elements[$name]['#weight'] = $settings['weight']; } } @@ -902,15 +905,14 @@ function field_view_value($entity_type, $entity, $field_name, $item, $display = * key and the field data to display. * @param $field_name * The name of the field to display. - * @param $display + * @param $display_options * Can be either: * - The name of a view mode. The field will be displayed according to the * display settings specified for this view mode in the $instance * definition for the field in the entity's bundle. If no display settings * are found for the view mode, the settings for the 'default' view mode * will be used. - * - An array of display settings, as found in the 'display' entry of - * $instance definitions. The following key/value pairs are allowed: + * - An array of display options. The following key/value pairs are allowed: * - label: (string) Position of the label. The default 'field' theme * implementation supports the values 'inline', 'above' and 'hidden'. * Defaults to 'above'. @@ -933,51 +935,73 @@ function field_view_value($entity_type, $entity, $field_name, $item, $display = * * @see field_view_value() */ -function field_view_field($entity_type, $entity, $field_name, $display = array(), $langcode = NULL) { +function field_view_field($entity_type, $entity, $field_name, $display_options = array(), $langcode = NULL) { $output = array(); + $bundle = $entity->bundle(); + + // Return nothing if the field doesn't exist. + $instance = field_info_instance($entity_type, $field_name, $bundle); + if (!$instance) { + return $output; + } + + // Get the formatter object. + if (is_string($display_options)) { + $view_mode = $display_options; + // If the view mode is not configured to use dedicated settings, + // rendering is done using 'default'. + $actual_mode = field_get_actual_view_mode($entity_type, $entity->bundle(), $view_mode); + $formatter = entity_get_display($entity_type, $bundle, $actual_mode)->getFormatter($field_name); + } + else { + $view_mode = '_custom'; + // hook_field_attach_display_alter() needs to receive the 'prepared' + // $display_options, so we cannot let preparation happen internally. + $field = field_info_field($field_name); + $formatter_manager = drupal_container()->get('plugin.manager.field.formatter'); + $display_options = $formatter_manager->prepareConfiguration($field['type'], $display_options); + $formatter = $formatter_manager->getInstance(array( + 'instance' => $instance, + 'view_mode' => $view_mode, + 'prepare' => FALSE, + 'configuration' => $display_options, + )); + } + + if ($formatter) { + $display_langcode = field_language($entity_type, $entity, $field_name, $langcode); + $items = $entity->{$field_name}[$display_langcode]; + + // Invoke prepare_view steps if needed. + if (empty($entity->_field_view_prepared)) { + $id = $entity->id(); + + // First let the field types do their preparation. + $options = array('field_name' => $field_name, 'langcode' => $display_langcode); + $null = NULL; + _field_invoke_multiple('prepare_view', $entity_type, array($id => $entity), $null, $null, $options); - if ($instance = field_info_instance($entity_type, $field_name, $entity->bundle())) { - if (is_array($display) && empty($display['type'])) { - $field = field_info_field($instance['field_name']); - $field_type_info = field_info_field_types($field['type']); - $display['type'] = $field_type_info['default_formatter']; + // Then let the formatter do its own specific massaging. + $items_multi = array($id => $entity->{$field_name}[$display_langcode]); + $formatter->prepareView(array($id => $entity), $display_langcode, $items_multi); + $items = $items_multi[$id]; } - if ($formatter = $instance->getFormatter($display)) { - $display_langcode = field_language($entity_type, $entity, $field_name, $langcode); - $items = $entity->{$field_name}[$display_langcode]; - - // Invoke prepare_view steps if needed. - if (empty($entity->_field_view_prepared)) { - $id = $entity->id(); - - // First let the field types do their preparation. - // @todo invoke hook_field_prepare_view() directly ? - $options = array('field_name' => $field_name, 'langcode' => $display_langcode); - $null = NULL; - _field_invoke_multiple('prepare_view', $entity_type, array($id => $entity), $display, $null, $options); - - // Then let the formatters do their own specific massaging. - $items_multi = array($id => $entity->{$field_name}[$display_langcode]); - $formatter->prepareView(array($id => $entity), $display_langcode, $items_multi); - $items = $items_multi[$id]; - } - // Build the renderable array. - $result = $formatter->view($entity, $display_langcode, $items); + // Build the renderable array. + $result = $formatter->view($entity, $display_langcode, $items); - // Invoke hook_field_attach_view_alter() to let other modules alter the - // renderable array, as in a full field_attach_view() execution. - $context = array( - 'entity_type' => $entity_type, - 'entity' => $entity, - 'view_mode' => '_custom', - 'display' => $display, - ); - drupal_alter('field_attach_view', $result, $context); + // Invoke hook_field_attach_view_alter() to let other modules alter the + // renderable array, as in a full field_attach_view() execution. + $context = array( + 'entity_type' => $entity_type, + 'entity' => $entity, + 'view_mode' => $view_mode, + 'display_options' => $display_options, + ); + drupal_alter('field_attach_view', $result, $context); - if (isset($result[$field_name])) { - $output = $result[$field_name]; - } + if (isset($result[$field_name])) { + $output = $result[$field_name]; } } diff --git a/core/modules/field/lib/Drupal/field/FieldInfo.php b/core/modules/field/lib/Drupal/field/FieldInfo.php index 65aa26c..2f33c89 100644 --- a/core/modules/field/lib/Drupal/field/FieldInfo.php +++ b/core/modules/field/lib/Drupal/field/FieldInfo.php @@ -543,25 +543,7 @@ public function prepareExtraFields($extra_fields, $entity_type, $bundle) { } // Extra fields in displayed entities. - $data = $extra_fields['display']; - foreach ($extra_fields['display'] as $name => $field_data) { - $settings = isset($bundle_settings['extra_fields']['display'][$name]) ? $bundle_settings['extra_fields']['display'][$name] : array(); - $view_modes = array_merge(array('default'), array_keys($entity_type_info['view_modes'])); - foreach ($view_modes as $view_mode) { - if (isset($settings[$view_mode])) { - $field_data['display'][$view_mode] = $settings[$view_mode]; - } - else { - $field_data['display'][$view_mode] = array( - 'weight' => $field_data['weight'], - 'visible' => isset($field_data['visible']) ? $field_data['visible'] : TRUE, - ); - } - } - unset($field_data['weight']); - unset($field_data['visible']); - $result['display'][$name] = $field_data; - } + $result['display'] = $extra_fields['display']; return $result; } diff --git a/core/modules/field/lib/Drupal/field/FieldInstance.php b/core/modules/field/lib/Drupal/field/FieldInstance.php index a1e7a4f..ba6ea44 100644 --- a/core/modules/field/lib/Drupal/field/FieldInstance.php +++ b/core/modules/field/lib/Drupal/field/FieldInstance.php @@ -27,13 +27,6 @@ class FieldInstance implements \ArrayAccess { protected $widget; /** - * The formatter objects used for this instance, keyed by view mode. - * - * @var array - */ - protected $formatters; - - /** * Constructs a FieldInstance object. * * @param array $definition @@ -75,88 +68,6 @@ public function getWidget() { } /** - * Returns a Formatter plugin for the instance. - * - * @param mixed $display_properties - * Can be either: - * - The name of a view mode. - * - An array of display properties, as found in the 'display' entry of - * $instance definitions. - * - * @return Drupal\field\Plugin\Type\Formatter\FormatterInterface|null - * The Formatter plugin to be used for the instance, or NULL if the field - * is hidden. - */ - public function getFormatter($display_properties) { - if (is_string($display_properties)) { - // A view mode was provided. Switch to 'default' if the view mode is not - // configured to use dedicated settings. - $view_mode = $display_properties; - $view_mode_settings = field_view_mode_settings($this->definition['entity_type'], $this->definition['bundle']); - $actual_mode = (!empty($view_mode_settings[$view_mode]['custom_settings']) ? $view_mode : 'default'); - - if (isset($this->formatters[$actual_mode])) { - return $this->formatters[$actual_mode]; - } - - // Switch to 'hidden' if the instance has no properties for the view - // mode. - if (isset($this->definition['display'][$actual_mode])) { - $display_properties = $this->definition['display'][$actual_mode]; - } - else { - $display_properties = array( - 'type' => 'hidden', - 'settings' => array(), - 'label' => 'above', - 'weight' => 0, - ); - } - - // Let modules alter the widget properties. - $context = array( - 'entity_type' => $this->definition['entity_type'], - 'bundle' => $this->definition['bundle'], - 'field' => field_info_field($this->definition['field_name']), - 'instance' => $this, - 'view_mode' => $view_mode, - ); - drupal_alter(array('field_display', 'field_display_' . $this->definition['entity_type']), $display_properties, $context); - } - else { - // Arbitrary display settings. Make sure defaults are present. - $display_properties += array( - 'settings' => array(), - 'label' => 'above', - 'weight' => 0, - ); - $view_mode = '_custom_display'; - } - - if (!empty($display_properties['type']) && $display_properties['type'] != 'hidden') { - $options = array( - 'instance' => $this, - 'type' => $display_properties['type'], - 'settings' => $display_properties['settings'], - 'label' => $display_properties['label'], - 'weight' => $display_properties['weight'], - 'view_mode' => $view_mode, - ); - $formatter = drupal_container()->get('plugin.manager.field.formatter')->getInstance($options); - } - else { - $formatter = NULL; - } - - // Persist the object if we were not passed custom display settings. - if (isset($actual_mode)) { - $this->formatters[$actual_mode] = $formatter; - } - - return $formatter; - } - - /** * Implements ArrayAccess::offsetExists(). */ public function offsetExists($offset) { @@ -185,9 +96,6 @@ public function offsetSet($offset, $value) { if ($offset == 'widget') { unset($this->widget); } - if ($offset == 'display') { - unset($this->formatters); - } } /** @@ -201,9 +109,6 @@ public function offsetUnset($offset) { if ($offset == 'widget') { unset($this->widget); } - if ($offset == 'display') { - unset($this->formatters); - } } /** diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php index 55d0e3f..551d056 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php @@ -11,8 +11,9 @@ use Drupal\Component\Plugin\Discovery\ProcessDecorator; use Drupal\Core\Plugin\Discovery\CacheDecorator; use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery; -use Drupal\field\Plugin\Type\Formatter\FormatterLegacyDiscoveryDecorator; use Drupal\Core\Plugin\Discovery\AlterDecorator; +use Drupal\field\Plugin\Type\Formatter\FormatterLegacyDiscoveryDecorator; +use Drupal\field\FieldInstance; /** * Plugin type manager for field formatters. @@ -42,31 +43,87 @@ public function __construct() { /** * Overrides PluginManagerBase::getInstance(). + * + * @param array $options + * An array with the following key/value pairs: + * - instance; (FieldInstance) The field instance. + * - view_mode: (string) The view mode. + * - prepare: (bool, optional) Whether default values should get merged in + * the 'configuration' array. Defaults to TRUE. + * - configuration: (array) the configuration for the formatter. The + * following key value pairs are allowed, and are all optional if + * 'prepare' is TRUE: + * - label: (string) Position of the label. The default 'field' theme + * implementation supports the values 'inline', 'above' and 'hidden'. + * Defaults to 'above'. + * - type: (string) The formatter to use. Defaults to the + * 'default_formatter' for the field type, specified in + * hook_field_info(). The default formatter will also be used if the + * requested formatter is not available. + * - settings: (array) Settings specific to the formatter. Each setting + * defaults to the default value specified in the formatter definition. + * - weight: (float) The weight to assign to the renderable element. + * Defaults to 0. + * + * @return \Drupal\field\Plugin\Type\Formatter\FormatterInterface + * A formatter object. */ public function getInstance(array $options) { + $configuration = $options['configuration']; $instance = $options['instance']; - $type = $options['type']; - - $definition = $this->getDefinition($type); $field = field_info_field($instance['field_name']); + // Fill in default configuration if needed. + if (!isset($options['prepare']) || $options['prepare'] == TRUE) { + $configuration = $this->prepareConfiguration($field['type'], $configuration); + } + + $plugin_id = $configuration['type']; + // Switch back to default formatter if either: // - $type_info doesn't exist (the widget type is unknown), // - the field type is not allowed for the widget. + $definition = $this->getDefinition($configuration['type']); if (!isset($definition['class']) || !in_array($field['type'], $definition['field_types'])) { // Grab the default widget for the field type. $field_type_definition = field_info_field_types($field['type']); - $type = $field_type_definition['default_formatter']; + $plugin_id = $field_type_definition['default_formatter']; } - $configuration = array( + $configuration += array( 'instance' => $instance, - 'settings' => $options['settings'], - 'weight' => $options['weight'], - 'label' => $options['label'], 'view_mode' => $options['view_mode'], ); - return $this->createInstance($type, $configuration); + return $this->createInstance($plugin_id, $configuration); + } + + /** + * Merges default values for formatter configuration. + * + * @param string $field_type + * The field type. + * @param array $properties + * An array of formatter configuration. + * + * @return array + * The display properties with defaults added. + */ + public function prepareConfiguration($field_type, array $configuration) { + // Fill in defaults for missing properties. + $configuration += array( + 'label' => 'above', + 'settings' => array(), + 'weight' => 0, + ); + // If no formatter is specified, use the default formatter. + if (!isset($configuration['type'])) { + $field_type = field_info_field_types($field_type); + $configuration['type'] = $field_type['default_formatter']; + } + // Fill in default settings values for the formatter. + $configuration['settings'] += field_info_formatter_settings($configuration['type']); + + return $configuration; } } diff --git a/core/modules/field/tests/modules/field_test/field_test.module b/core/modules/field/tests/modules/field_test/field_test.module index 903c2f9..b7448f5 100644 --- a/core/modules/field/tests/modules/field_test/field_test.module +++ b/core/modules/field/tests/modules/field_test/field_test.module @@ -206,7 +206,7 @@ function field_test_entity_label_callback($entity_type, $entity, $langcode = NUL * Implements hook_field_attach_view_alter(). */ function field_test_field_attach_view_alter(&$output, $context) { - if (!empty($context['display']['settings']['alter'])) { + if (!empty($context['display_options']['settings']['alter'])) { $output['test_field'][] = array('#markup' => 'field_test_field_attach_view_alter'); } } diff --git a/core/modules/field_ui/field_ui.admin.inc b/core/modules/field_ui/field_ui.admin.inc index 00cb8de..27dad1e 100644 --- a/core/modules/field_ui/field_ui.admin.inc +++ b/core/modules/field_ui/field_ui.admin.inc @@ -374,51 +374,6 @@ function field_ui_display_overview($entity_type, $bundle, $view_mode) { } /** - * Populates display settings for a new view mode from the default view mode. - * - * When an administrator decides to use custom display settings for a view mode, - * that view mode needs to be initialized with the display settings for the - * 'default' view mode, which it was previously using. This helper function - * adds the new custom display settings to this bundle's instances, and saves - * them. It also modifies the passed-in $settings array, which the caller can - * then save using field_bundle_settings(). - * - * @param $entity_type - * The bundle's entity type. - * @param $bundle - * The bundle whose view mode is being customized. - * @param $view_mode - * The view mode that the administrator has set to use custom settings. - * @param $settings - * An associative array of bundle settings, as expected by - * field_bundle_settings(). - * - * @see Drupal\field_ui\DisplayOverview::submit(). - * @see field_bundle_settings() - */ -function _field_ui_add_default_view_mode_settings($entity_type, $bundle, $view_mode, &$settings) { - // Update display settings for field instances. - $instances = field_read_instances(array('entity_type' => $entity_type, 'bundle' => $bundle)); - foreach ($instances as $instance) { - // If this field instance has display settings defined for this view mode, - // respect those settings. - if (!isset($instance['display'][$view_mode])) { - // The instance doesn't specify anything for this view mode, so use the - // default display settings. - $instance['display'][$view_mode] = $instance['display']['default']; - field_update_instance($instance); - } - } - - // Update display settings for 'extra fields'. - foreach (array_keys($settings['extra_fields']['display']) as $name) { - if (!isset($settings['extra_fields']['display'][$name][$view_mode])) { - $settings['extra_fields']['display'][$name][$view_mode] = $settings['extra_fields']['display'][$name]['default']; - } - } -} - -/** * Returns an array of field_type options. */ function field_ui_field_type_options() { diff --git a/core/modules/field_ui/lib/Drupal/field_ui/DisplayOverview.php b/core/modules/field_ui/lib/Drupal/field_ui/DisplayOverview.php index 6117de9..0a86f78 100644 --- a/core/modules/field_ui/lib/Drupal/field_ui/DisplayOverview.php +++ b/core/modules/field_ui/lib/Drupal/field_ui/DisplayOverview.php @@ -49,6 +49,7 @@ public function form(array $form, array &$form_state) { $instances = field_info_instances($this->entity_type, $this->bundle); $field_types = field_info_field_types(); $extra_fields = field_info_extra_fields($this->entity_type, $this->bundle, 'display'); + $entity_display = entity_get_display($this->entity_type, $this->bundle, $this->view_mode); $form_state += array( 'formatter_settings_edit' => NULL, @@ -94,24 +95,14 @@ public function form(array $form, array &$form_state) { 'hidden' => t(''), ); $extra_visibility_options = array( - 'content' => t('Visible'), + 'visible' => t('Visible'), 'hidden' => t('Hidden'), ); // Field rows. foreach ($instances as $name => $instance) { - $field = field_info_field($instance['field_name']); - - if (isset($instance['display'][$this->view_mode])) { - $display = $instance['display'][$this->view_mode]; - } - else { - $display = array( - 'type' => 'hidden', - 'label' => 'above', - 'weight' => 0, - ); - } + $field = field_info_field($name); + $display_options = $entity_display->getComponent($name); $table[$name] = array( '#attributes' => array('class' => array('draggable', 'tabledrag-leaf')), @@ -128,7 +119,7 @@ public function form(array $form, array &$form_state) { '#type' => 'textfield', '#title' => t('Weight for @title', array('@title' => $instance['label'])), '#title_display' => 'invisible', - '#default_value' => $display['weight'], + '#default_value' => $display_options ? $display_options['weight'] : '0', '#size' => 3, '#attributes' => array('class' => array('field-weight')), ), @@ -153,7 +144,7 @@ public function form(array $form, array &$form_state) { '#title' => t('Label display for @title', array('@title' => $instance['label'])), '#title_display' => 'invisible', '#options' => $field_label_options, - '#default_value' => $display['label'], + '#default_value' => $display_options ? $display_options['label'] : 'above', ), ); @@ -165,7 +156,7 @@ public function form(array $form, array &$form_state) { '#title' => t('Formatter for @title', array('@title' => $instance['label'])), '#title_display' => 'invisible', '#options' => $formatter_options, - '#default_value' => $display['type'], + '#default_value' => $display_options ? $display_options['type'] : 'hidden', '#parents' => array('fields', $name, 'type'), '#attributes' => array('class' => array('field-formatter-type')), ), @@ -175,12 +166,23 @@ public function form(array $form, array &$form_state) { // Check the currently selected formatter, and merge persisted values for // formatter settings. if (isset($form_state['values']['fields'][$name]['type'])) { - $display['type'] = $form_state['values']['fields'][$name]['type']; + $display_options['type'] = $form_state['values']['fields'][$name]['type']; } if (isset($form_state['formatter_settings'][$name])) { - $display['settings'] = $form_state['formatter_settings'][$name]; + $display_options['settings'] = $form_state['formatter_settings'][$name]; + } + + // Get the corresponding formatter object. + if ($display_options && $display_options['type'] != 'hidden') { + $formatter = drupal_container()->get('plugin.manager.field.formatter')->getInstance(array( + 'instance' => $instance, + 'view_mode' => $this->view_mode, + 'configuration' => $display_options + )); + } + else { + $formatter = NULL; } - $formatter = $instance->getFormatter($display); // Base button element for the various formatter settings actions. $base_button = array( @@ -287,7 +289,8 @@ public function form(array $form, array &$form_state) { // Non-field elements. foreach ($extra_fields as $name => $extra_field) { - $display = $extra_field['display'][$this->view_mode]; + $display_options = $entity_display->getComponent($name); + $table[$name] = array( '#attributes' => array('class' => array('draggable', 'tabledrag-leaf')), '#row_type' => 'extra_field', @@ -300,7 +303,7 @@ public function form(array $form, array &$form_state) { '#type' => 'textfield', '#title' => t('Weight for @title', array('@title' => $extra_field['label'])), '#title_display' => 'invisible', - '#default_value' => $display['weight'], + '#default_value' => $display_options ? $display_options['weight'] : 0, '#size' => 3, '#attributes' => array('class' => array('field-weight')), ), @@ -329,7 +332,7 @@ public function form(array $form, array &$form_state) { '#title' => t('Visibility for @title', array('@title' => $extra_field['label'])), '#title_display' => 'invisible', '#options' => $extra_visibility_options, - '#default_value' => $display['visible'] ? 'content' : 'hidden', + '#default_value' => $display_options ? 'visible' : 'hidden', '#parents' => array('fields', $name, 'type'), '#attributes' => array('class' => array('field-formatter-type')), ), @@ -418,73 +421,86 @@ public function form(array $form, array &$form_state) { */ public function submit(array $form, array &$form_state) { $form_values = $form_state['values']; + $display = entity_get_display($this->entity_type, $this->bundle, $this->view_mode); - // Save data for 'regular' fields. + // Collect data for 'regular' fields. foreach ($form['#fields'] as $field_name) { // Retrieve the stored instance settings to merge with the incoming // values. - $instance = field_read_instance($this->entity_type, $field_name, $this->bundle); $values = $form_values['fields'][$field_name]; - // Get formatter settings. They lie either directly in submitted form - // values (if the whole form was submitted while some formatter - // settings were being edited), or have been persisted in - // $form_state. - $settings = array(); - if (isset($values['settings_edit_form']['settings'])) { - $settings = $values['settings_edit_form']['settings']; - } - elseif (isset($form_state['formatter_settings'][$field_name])) { - $settings = $form_state['formatter_settings'][$field_name]; - } - elseif (isset($instance['display'][$this->view_mode]['settings'])) { - $settings = $instance['display'][$this->view_mode]['settings']; + + if ($values['type'] == 'hidden') { + $display->removeComponent($field_name); } + else { + // Get formatter settings. They lie either directly in submitted form + // values (if the whole form was submitted while some formatter + // settings were being edited), or have been persisted in $form_state. + $settings = array(); + if (isset($values['settings_edit_form']['settings'])) { + $settings = $values['settings_edit_form']['settings']; + } + elseif (isset($form_state['formatter_settings'][$field_name])) { + $settings = $form_state['formatter_settings'][$field_name]; + } + elseif ($current_options = $display->getComponent($field_name)) { + $settings = $current_options['settings']; + } - // Only save settings actually used by the selected formatter. - $default_settings = field_info_formatter_settings($values['type']); - $settings = array_intersect_key($settings, $default_settings); + // Only save settings actually used by the selected formatter. + $default_settings = field_info_formatter_settings($values['type']); + $settings = array_intersect_key($settings, $default_settings); - $instance['display'][$this->view_mode] = array( - 'label' => $values['label'], - 'type' => $values['type'], - 'weight' => $values['weight'], - 'settings' => $settings, - ); - field_update_instance($instance); + $display->setComponent($field_name, array( + 'label' => $values['label'], + 'type' => $values['type'], + 'weight' => $values['weight'], + 'settings' => $settings, + )); + } } - // Get current bundle settings. - $bundle_settings = field_bundle_settings($this->entity_type, $this->bundle); - - // Save data for 'extra' fields. + // Collect data for 'extra' fields. foreach ($form['#extra'] as $name) { - $bundle_settings['extra_fields']['display'][$name][$this->view_mode] = array( - 'weight' => $form_values['fields'][$name]['weight'], - 'visible' => $form_values['fields'][$name]['type'] == 'content', - ); + if ($form_values['fields'][$name]['type'] == 'hidden') { + $display->removeComponent($name); + } + else { + $display->setComponent($name, array( + 'weight' => $form_values['fields'][$name]['weight'], + )); + } } - // Save view modes data. + // Save the display. + $display->save(); + + // Handle the 'view modes' checkboxes if present. if ($this->view_mode == 'default' && !empty($form_values['view_modes_custom'])) { $entity_info = entity_get_info($this->entity_type); - foreach ($form_values['view_modes_custom'] as $view_mode_name => $value) { - // Display a message for each view mode newly configured to use custom - // settings. - $view_mode_settings = field_view_mode_settings($this->entity_type, $this->bundle); - if (!empty($value) && empty($view_mode_settings[$view_mode_name]['custom_settings'])) { - $view_mode_label = $entity_info['view_modes'][$view_mode_name]['label']; - $path = field_ui_bundle_admin_path($this->entity_type, $this->bundle) . "/display/$view_mode_name"; + $bundle_settings = field_bundle_settings($this->entity_type, $this->bundle); + $view_mode_settings = field_view_mode_settings($this->entity_type, $this->bundle); + + foreach ($form_values['view_modes_custom'] as $view_mode => $value) { + if (!empty($value) && empty($view_mode_settings[$view_mode]['custom_settings'])) { + // If no display exists for the newly enabled view mode, initialize + // it with those from the 'default' view mode, which were used so + // far. + if (!entity_load('entity_display', $this->entity_type . '.' . $this->bundle . '.' . $view_mode)) { + $display = entity_get_display($this->entity_type, $this->bundle, 'default')->createCopy($view_mode); + $display->save(); + } + + $view_mode_label = $entity_info['view_modes'][$view_mode]['label']; + $path = field_ui_bundle_admin_path($this->entity_type, $this->bundle) . "/display/$view_mode"; drupal_set_message(t('The %view_mode mode now uses custom display settings. You might want to configure them.', array('%view_mode' => $view_mode_label, '@url' => url($path)))); - // Initialize the newly customized view mode with the display settings - // from the default view mode. - _field_ui_add_default_view_mode_settings($this->entity_type, $this->bundle, $view_mode_name, $bundle_settings); } - $bundle_settings['view_modes'][$view_mode_name]['custom_settings'] = !empty($value); + $bundle_settings['view_modes'][$view_mode]['custom_settings'] = !empty($value); } - } - // Save updated bundle settings. - field_bundle_settings($this->entity_type, $this->bundle, $bundle_settings); + // Save updated bundle settings. + field_bundle_settings($this->entity_type, $this->bundle, $bundle_settings); + } drupal_set_message(t('Your settings have been saved.')); } diff --git a/core/modules/field_ui/lib/Drupal/field_ui/FieldOverview.php b/core/modules/field_ui/lib/Drupal/field_ui/FieldOverview.php index 2d88051..a977df9 100644 --- a/core/modules/field_ui/lib/Drupal/field_ui/FieldOverview.php +++ b/core/modules/field_ui/lib/Drupal/field_ui/FieldOverview.php @@ -586,6 +586,13 @@ public function submit(array $form, array &$form_state) { field_create_field($field); field_create_instance($instance); + // Make sure the field is displayed in the 'default' view mode (using + // default formatter and settings). It stays hidden for other view + // modes until it is explicitly configured. + entity_get_display($this->entity_type, $this->bundle, 'default') + ->setComponent($field['field_name']) + ->save(); + // Always show the field settings step, as the cardinality needs to be // configured for new fields. $destinations[] = $this->adminPath. '/fields/' . $field['field_name'] . '/field-settings'; @@ -620,6 +627,14 @@ public function submit(array $form, array &$form_state) { try { field_create_instance($instance); + + // Make sure the field is displayed in the 'default' view mode (using + // default formatter and settings). It stays hidden for other view + // modes until it is explicitly configured. + entity_get_display($this->entity_type, $this->bundle, 'default') + ->setComponent($field['field_name']) + ->save(); + $destinations[] = $this->adminPath . '/fields/' . $instance['field_name']; // Store new field information for any additional submit handlers. $form_state['fields_added']['_add_existing_field'] = $instance['field_name']; diff --git a/core/modules/forum/forum.install b/core/modules/forum/forum.install index 56f8402..69954cb 100644 --- a/core/modules/forum/forum.install +++ b/core/modules/forum/forum.install @@ -90,18 +90,22 @@ function forum_enable() { 'widget' => array( 'type' => 'options_select', ), - 'display' => array( - 'default' => array( - 'type' => 'taxonomy_term_reference_link', - 'weight' => 10, - ), - 'teaser' => array( - 'type' => 'taxonomy_term_reference_link', - 'weight' => 10, - ), - ), ); field_create_instance($instance); + + // Assign display settings for the 'default' and 'teaser' view modes. + entity_get_display('node', 'forum', 'default') + ->setComponent('taxonomy_forums', array( + 'type' => 'taxonomy_term_reference_link', + 'weight' => 10, + )) + ->save(); + entity_get_display('node', 'forum', 'teaser') + ->setComponent('taxonomy_forums', array( + 'type' => 'taxonomy_term_reference_link', + 'weight' => 10, + )) + ->save(); } // Ensure the forum node type is available. diff --git a/core/modules/image/image.module b/core/modules/image/image.module index 221e07c..8312420 100644 --- a/core/modules/image/image.module +++ b/core/modules/image/image.module @@ -334,21 +334,23 @@ function image_image_style_save($style) { // Loop through all fields searching for image fields. foreach ($instances as $instance) { if ($instance['widget']['module'] == 'image') { - $instance_changed = FALSE; - foreach ($instance['display'] as $view_mode => $display) { + $entity_info = entity_get_info($instance['entity_type']); + $view_modes = array('default') + array_keys($entity_info['view_modes']); + foreach ($view_modes as $view_mode) { + $display = entity_get_display($instance['entity_type'], $instance['bundle'], $view_mode); + $display_options = $display->getComponent($instance['field_name']); + // Check if the formatter involves an image style. - if ($display['type'] == 'image' && $display['settings']['image_style'] == $style->getOriginalID()) { + if ($display_options && $display_options['type'] == 'image' && $display_options['settings']['image_style'] == $style->getOriginalID()) { // Update display information for any instance using the image // style that was just deleted. - $instance['display'][$view_mode]['settings']['image_style'] = $style->id(); - $instance_changed = TRUE; + $display_options['settings']['image_style'] = $style->id(); + $display->setComponent($instance['field_name'], $display_options) + ->save(); } } if ($instance['widget']['settings']['preview_image_style'] == $style->getOriginalID()) { $instance['widget']['settings']['preview_image_style'] = $style->id(); - $instance_changed = TRUE; - } - if ($instance_changed) { field_update_instance($instance); } } diff --git a/core/modules/node/node.module b/core/modules/node/node.module index a37679c..d56bee5 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -19,6 +19,7 @@ use Drupal\node\Plugin\Core\Entity\Node; use Drupal\file\Plugin\Core\Entity\File; use Drupal\Core\Entity\EntityInterface; +use Drupal\entity\Plugin\Core\Entity\EntityDisplay; /** * Denotes that the node is not published. @@ -212,12 +213,17 @@ function node_entity_info(&$info) { } /** - * Implements hook_field_display_ENTITY_TYPE_alter(). + * Implements hook_entity_display_alter(). */ -function node_field_display_node_alter(&$display, $context) { +function node_entity_display_alter(EntityDisplay $display, $context) { // Hide field labels in search index. - if ($context['view_mode'] == 'search_index') { - $display['label'] = 'hidden'; + 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); + } + } } } @@ -559,19 +565,24 @@ function node_add_body_field($type, $label = 'Body') { 'label' => $label, 'widget' => array('type' => 'text_textarea_with_summary'), 'settings' => array('display_summary' => TRUE), - 'display' => array( - 'default' => array( - 'label' => 'hidden', - 'type' => 'text_default', - ), - 'teaser' => array( - 'label' => 'hidden', - 'type' => 'text_summary_or_trimmed', - ), - ), ); $instance = field_create_instance($instance); + + // 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; } diff --git a/core/modules/node/node.pages.inc b/core/modules/node/node.pages.inc index 0a18ec1..10e734a 100644 --- a/core/modules/node/node.pages.inc +++ b/core/modules/node/node.pages.inc @@ -144,8 +144,6 @@ function node_preview(Node $node) { } $node->changed = REQUEST_TIME; - $nodes = array($node->nid => $node); - field_attach_prepare_view('node', $nodes, 'full'); // Display a preview of the node. if (!form_get_errors()) { @@ -180,7 +178,7 @@ function theme_node_preview($variables) { $elements = node_view(clone $node, 'teaser'); $elements['#attached']['library'][] = array('node', 'drupal.node.preview'); $trimmed = drupal_render($elements); - $elements = node_view($node, 'full'); + $elements = node_view(clone $node, 'full'); $full = drupal_render($elements); // Do we need to preview trimmed version of post as well as full version? diff --git a/core/modules/node/node.tokens.inc b/core/modules/node/node.tokens.inc index 2d783c9..8827b98 100644 --- a/core/modules/node/node.tokens.inc +++ b/core/modules/node/node.tokens.inc @@ -149,7 +149,19 @@ function node_tokens($type, $tokens, array $data = array(), array $options = arr // A summary was requested. if ($name == 'summary') { // Generate an optionally trimmed summary of the body field. - $output = text_summary($output, $instance['settings']['text_processing'] ? $items[0]['format'] : NULL, $instance['display']['teaser']['settings']['trim_length']); + + // Get the 'trim_length' size used for the 'teaser' mode, if + // present, or use the default trim_length size. + $display_options = entity_get_display('node', $node->type, 'teaser')->getComponent('body'); + if (isset($display_options['settings']['trim_length'])) { + $length = $display_options['settings']['trim_length']; + } + else { + $settings = field_info_formatter_settings('text_summary_or_trimmed'); + $length = $settings['trim_length']; + } + + $output = text_summary($output, $instance['settings']['text_processing'] ? $items[0]['format'] : NULL, $length); } } $replacements[$original] = $output; diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/FieldUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/FieldUpgradePathTest.php new file mode 100644 index 0000000..5d92522 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/FieldUpgradePathTest.php @@ -0,0 +1,83 @@ + 'Field upgrade test', + 'description' => 'Tests upgrade of Field API.', + 'group' => 'Upgrade path', + ); + } + + public function setUp() { + $this->databaseDumpFiles = array( + drupal_get_path('module', 'system') . '/tests/upgrade/drupal-7.bare.standard_all.database.php.gz', + drupal_get_path('module', 'system') . '/tests/upgrade/drupal-7.field.database.php', + ); + parent::setUp(); + } + + /** + * Tests upgrade of entity displays. + */ + public function testEntityDisplayUpgrade() { + $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.'); + + // Check that the configuration entries were created. + $displays = array( + 'default' => config('entity.display.node.article.default')->get(), + 'teaser' => config('entity.display.node.article.teaser')->get(), + ); + $this->assertTrue(!empty($displays['default'])); + $this->assertTrue(!empty($displays['teaser'])); + + // Check that the 'body' field is configured as expected. + $expected = array( + 'default' => array( + 'label' => 'hidden', + 'type' => 'text_default', + 'weight' => 0, + 'settings' => array(), + ), + 'teaser' => array( + 'label' => 'hidden', + 'type' => 'text_summary_or_trimmed', + 'weight' => 0, + 'settings' => array( + 'trim_length' => 600, + ), + ), + ); + $this->assertEqual($displays['default']['content']['body'], $expected['default']); + $this->assertEqual($displays['teaser']['content']['body'], $expected['teaser']); + + // Check that the display key in the instance data was removed. + $body_instance = field_info_instance('node', 'body', 'article'); + $this->assertTrue(!isset($body_instance['display'])); + + // Check that the 'language' extra field is configured as expected. + $expected = array( + 'default' => array( + 'weight' => -1, + 'visible' => 1, + ), + 'teaser' => array( + 'visible' => 0, + ), + ); + $this->assertEqual($displays['default']['content']['language'], $expected['default']); + $this->assertEqual($displays['teaser']['content']['language'], $expected['teaser']); + } + +} diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module index dcc020a..44983e4 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.module +++ b/core/modules/system/tests/modules/entity_test/entity_test.module @@ -19,6 +19,32 @@ function entity_test_entity_info_alter(&$info) { } /** + * Implements hook_field_extra_fields(). + */ +function entity_test_field_extra_fields() { + $extra['entity_test']['entity_test'] = array( + 'display' => array( + // Note: those extra fields do not currently display anything, they are + // just used in \Drupal\entity\Tests\EntityDisplayTest to test the + // behavior of entity display objects, + 'display_extra_field' => array( + 'label' => t('Display extra field'), + 'description' => t('An extra field on the display side.'), + 'weight' => 5, + 'vidible' => TRUE, + ), + 'display_extra_field_hidden' => array( + 'label' => t('Display extra field (hidden)'), + 'description' => t('An extra field on the display side, hidden by default.'), + 'visible' => FALSE, + ), + ) + ); + + return $extra; +} + +/** * Implements hook_permission(). */ function entity_test_permission() { diff --git a/core/modules/system/tests/upgrade/drupal-7.field.database.php b/core/modules/system/tests/upgrade/drupal-7.field.database.php new file mode 100644 index 0000000..0862650 --- /dev/null +++ b/core/modules/system/tests/upgrade/drupal-7.field.database.php @@ -0,0 +1,51 @@ + array( + 'teaser' => array( + 'custom_settings' => 1, + ), + 'full' => array( + 'custom_settings' => 0, + ), + 'rss' => array( + 'custom_settings' => 0, + ), + 'search_index' => array( + 'custom_settings' => 0, + ), + 'search_result' => array( + 'custom_settings' => 0, + ), + ), + 'extra_fields' => array( + 'form' => array(), + 'display' => array( + 'language' => array( + 'default' => array( + 'weight' => -1, + 'visible' => 1, + ), + 'teaser' => array( + 'weight' => 0, + 'visible' => 0, + ), + ), + ), + ), +); +db_insert('variable') + ->fields(array( + 'name' => 'field_bundle_settings_node__article', + 'value' => serialize($value), + )) + ->execute(); diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermRenderController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermRenderController.php index c22f6e3..56cc036 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/TermRenderController.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermRenderController.php @@ -25,11 +25,11 @@ public function buildContent(array $entities = array(), $view_mode = 'full', $la // Add the description if enabled. $bundle = $entity->bundle(); $entity_view_mode = $entity->content['#view_mode']; - $fields = field_extra_fields_get_display($this->entityType, $bundle, $entity_view_mode); - if (!empty($entity->description) && isset($fields['description']) && $fields['description']['visible']) { + $display = field_extra_fields_get_display($this->entityType, $bundle, $entity_view_mode); + if (!empty($entity->description) && !empty($display['description'])) { $entity->content['description'] = array( '#markup' => check_markup($entity->description, $entity->format, '', TRUE), - '#weight' => $fields['description']['weight'], + '#weight' => $display['description']['weight'], '#prefix' => '
', '#suffix' => '
', ); diff --git a/core/modules/user/user.install b/core/modules/user/user.install index bb86002..351455c 100644 --- a/core/modules/user/user.install +++ b/core/modules/user/user.install @@ -701,25 +701,88 @@ function user_update_8011() { ->execute(); $default_image_fid = db_query('SELECT fid FROM {file_managed} WHERE uri = :uri', array(':uri' => $destination))->fetchField(); } - $settings = array( - // In D7, user pictures did not require Image module to work. Image module - // only allowed usage of an image style to format user pictures in the - // output. The 'user_pictures' variable had a global effect on the - // presence of the user picture functionality before. The new user picture - // image field is created regardless of that global setting, which means - // the field appears on the user account form after migration, even if - // user pictures were disabled previously. The picture is only hidden in - // the output. - 'formatter' => update_variable_get('user_pictures', 0) ? 'image' : 'hidden', - 'file_directory' => update_variable_get('user_picture_path', 'pictures'), - 'default_image' => !empty($default_image_fid) ? $default_image_fid : 0, - 'image_style' => update_variable_get('user_picture_style', ''), - 'max_resolution' => update_variable_get('user_picture_dimensions', '85x85'), - 'max_filesize' => update_variable_get('user_picture_file_size', '30') . ' KB', + + // Create the field and instance. + $field = array( + 'field_name' => 'user_picture', + 'module' => 'image', + 'type' => 'image', + 'cardinality' => 1, + 'locked' => FALSE, + 'indexes' => array('fid' => array('fid')), + 'settings' => array( + 'uri_scheme' => 'public', + 'default_image' => FALSE, + ), + 'storage' => array( + 'type' => 'field_sql_storage', + 'settings' => array(), + ), + ); + _update_7000_field_create_field($field); + + $instance = array( + 'field_name' => 'user_picture', + 'entity_type' => 'user', + 'label' => 'Picture', + 'bundle' => 'user', 'description' => update_variable_get('user_picture_guidelines', ''), + 'required' => FALSE, + 'settings' => array( + 'file_extensions' => 'png gif jpg jpeg', + 'file_directory' => update_variable_get('user_picture_path', 'pictures'), + 'max_filesize' => update_variable_get('user_picture_file_size', '30') . ' KB', + 'alt_field' => 0, + 'title_field' => 0, + 'max_resolution' => update_variable_get('user_picture_dimensions', '85x85'), + 'min_resolution' => '', + 'default_image' => !empty($default_image_fid) ? $default_image_fid : 0, + ), + 'widget' => array( + 'module' => 'image', + 'type' => 'image_image', + 'settings' => array( + 'progress_indicator' => 'throbber', + 'preview_image_style' => 'thumbnail', + ), + 'weight' => -1, + ), ); + _update_7000_field_create_instance($field, $instance); + + // Assign display settings for the 'default' and 'compact' view modes. In D7, + // user pictures did not require Image module to work. Image module only + // allowed usage of an image style to format user pictures in the output. + // The 'user_pictures' variable had a global effect on the presence of the + // user picture functionality before. The new user picture image field is + // created regardless of that global setting, which means the field appears + // on the user account form after migration, even if user pictures were + // disabled previously. The picture is only hidden in the output. + $formatter = update_variable_get('user_pictures', 0) ? 'image' : 'hidden'; + module_load_install('entity'); - $field = _user_install_picture_field($settings); + _entity_update_8000_get_display_config('user', 'user', 'default') + ->set('content.user_picture', array( + 'label' => 'hidden', + 'type' => $formatter, + 'settings' => array( + 'image_style' => 'thumbnail', + 'image_link' => 'content', + ), + 'weight' => 0, + )) + ->save(); + _entity_update_8000_get_display_config('user', 'user', 'compact') + ->set('content.user_picture', array( + 'label' => 'hidden', + 'type' => $formatter, + 'settings' => array( + 'image_style' => update_variable_get('user_picture_style', ''), + 'image_link' => 'content', + ), + 'weight' => 0, + )) + ->save(); // Add file usage for the default field. if (!empty($default_image_fid)) { @@ -966,79 +1029,3 @@ function user_update_8016() { /** * @} End of "addtogroup updates-7.x-to-8.x". */ - -/** - * Creates a user picture image field for the User entity. - */ -function _user_install_picture_field(array $settings = array()) { - $t = get_t(); - $settings += array( - 'formatter' => 'image', - 'file_directory' => 'pictures', - 'default_image' => 0, - 'image_style' => 'thumbnail', - 'max_resolution' => '85x85', - 'max_filesize' => '30 KB', - 'description' => $t('Your virtual face or picture.'), - ); - - $field = array( - 'field_name' => 'user_picture', - 'module' => 'image', - 'type' => 'image', - 'cardinality' => 1, - 'locked' => FALSE, - 'indexes' => array('fid' => array('fid')), - 'settings' => array( - 'uri_scheme' => 'public', - 'default_image' => FALSE, - ), - 'storage' => array( - 'type' => 'field_sql_storage', - 'settings' => array(), - ), - ); - _update_7000_field_create_field($field); - - $instance = array( - 'field_name' => 'user_picture', - 'entity_type' => 'user', - 'label' => 'Picture', - 'bundle' => 'user', - 'description' => $settings['description'], - 'required' => FALSE, - 'settings' => array( - 'file_extensions' => 'png gif jpg jpeg', - 'file_directory' => $settings['file_directory'], - 'max_filesize' => $settings['max_filesize'], - 'alt_field' => 0, - 'title_field' => 0, - 'max_resolution' => $settings['max_resolution'], - 'min_resolution' => '', - 'default_image' => $settings['default_image'], - ), - 'widget' => array( - 'module' => 'image', - 'type' => 'image_image', - 'settings' => array( - 'progress_indicator' => 'throbber', - 'preview_image_style' => 'thumbnail', - ), - 'weight' => -1, - ), - 'display' => array( - 'default' => array( - 'label' => 'hidden', - 'type' => $settings['formatter'], - 'settings' => array('image_style' => 'thumbnail', 'image_link' => 'content'), - ), - 'compact' => array( - 'label' => 'hidden', - 'type' => $settings['formatter'], - 'settings' => array('image_style' => $settings['image_style'], 'image_link' => 'content'), - ), - ), - ); - _update_7000_field_create_instance($field, $instance); - return $field; -} diff --git a/core/profiles/standard/standard.install b/core/profiles/standard/standard.install index b24946e..17883c0 100644 --- a/core/profiles/standard/standard.install +++ b/core/profiles/standard/standard.install @@ -295,19 +295,22 @@ function standard_install() { 'type' => 'taxonomy_autocomplete', 'weight' => -4, ), - 'display' => array( - 'default' => array( - 'type' => 'taxonomy_term_reference_link', - 'weight' => 10, - ), - 'teaser' => array( - 'type' => 'taxonomy_term_reference_link', - 'weight' => 10, - ), - ), ); field_create_instance($instance); + // Assign display settings for the 'default' and 'teaser' view modes. + entity_get_display('node', 'article', 'default') + ->setComponent($instance['field_name'], array( + 'type' => 'taxonomy_term_reference_link', + 'weight' => 10, + )) + ->save(); + entity_get_display('node', 'article', 'teaser') + ->setComponent($instance['field_name'], array( + 'type' => 'taxonomy_term_reference_link', + 'weight' => 10, + )) + ->save(); // Create an image field named "Image", enabled for the 'article' content type. // Many of the following values will be defaulted, they're included here as an illustrative examples. @@ -359,34 +362,29 @@ function standard_install() { ), 'weight' => -1, ), - - 'display' => array( - 'default' => array( - 'label' => 'hidden', - 'type' => 'image', - 'settings' => array('image_style' => 'large', 'image_link' => ''), - 'weight' => -1, - ), - 'teaser' => array( - 'label' => 'hidden', - 'type' => 'image', - 'settings' => array('image_style' => 'medium', 'image_link' => 'content'), - 'weight' => -1, - ), - ), ); field_create_instance($instance); + // Assign display settings for the 'default' and 'teaser' view modes. + entity_get_display('node', 'article', 'default') + ->setComponent($instance['field_name'], array( + 'label' => 'hidden', + 'type' => 'image', + 'settings' => array('image_style' => 'large', 'image_link' => ''), + 'weight' => -1, + )) + ->save(); + entity_get_display('node', 'article', 'teaser') + ->setComponent($instance['field_name'], array( + 'label' => 'hidden', + 'type' => 'image', + 'settings' => array('image_style' => 'medium', 'image_link' => 'content'), + 'weight' => -1, + )) + ->save(); + // Create user picture field. - module_load_install('user'); - _user_install_picture_field(); - // Remove 'summary' pseudo-field from compact view mode on the User entity. - $bundle_settings = field_bundle_settings('user', 'user'); - $bundle_settings['extra_fields']['display']['member_for']['compact'] = array( - 'visible' => FALSE, - 'weight' => 10, - ); - field_bundle_settings('user', 'user', $bundle_settings); + _standard_create_user_picture_field(); // Enable default permissions for system roles. $filtered_html_permission = filter_permission_name($filtered_html_format); @@ -440,3 +438,85 @@ function standard_install() { config('system.theme')->set('admin', 'seven')->save(); variable_set('node_admin_theme', '1'); } + +/** + * Creates a user picture image field for the User entity. + * + * This is kept as a separate function so that + * \Drupal\user\Tests\UserPictureTest can test the user_picture functionnality + * without requiring the standard profile. + */ +function _standard_create_user_picture_field() { + $t = get_t(); + + $field = array( + 'field_name' => 'user_picture', + 'module' => 'image', + 'type' => 'image', + 'cardinality' => 1, + 'locked' => FALSE, + 'indexes' => array('fid' => array('fid')), + 'settings' => array( + 'uri_scheme' => 'public', + 'default_image' => FALSE, + ), + 'storage' => array( + 'type' => 'field_sql_storage', + 'settings' => array(), + ), + ); + $field = field_create_field($field); + + $instance = array( + 'field_name' => 'user_picture', + 'entity_type' => 'user', + 'label' => 'Picture', + 'bundle' => 'user', + 'description' => $t('Your virtual face or picture.'), + 'required' => FALSE, + 'settings' => array( + 'file_extensions' => 'png gif jpg jpeg', + 'file_directory' => 'pictures', + 'max_filesize' => '30 KB', + 'alt_field' => 0, + 'title_field' => 0, + 'max_resolution' => '85x85', + 'min_resolution' => '', + 'default_image' => 0, + ), + 'widget' => array( + 'module' => 'image', + 'type' => 'image_image', + 'settings' => array( + 'progress_indicator' => 'throbber', + 'preview_image_style' => 'thumbnail', + ), + 'weight' => -1, + ), + ); + field_create_instance($instance); + + // Assign display settings for the 'default' and 'compact' view modes. + entity_get_display('user', 'user', 'default') + ->setComponent('user_picture', array( + 'label' => 'hidden', + 'type' => 'image', + 'settings' => array( + 'image_style' => 'thumbnail', + 'image_link' => 'content', + ), + )) + ->save(); + entity_get_display('user', 'user', 'compact') + ->setComponent('user_picture', array( + 'label' => 'hidden', + 'type' => 'image', + 'settings' => array( + 'image_style' => 'thumbnail', + 'image_link' => 'content', + ), + )) + // Additionally, hide 'summary' pseudo-field from compact view mode.. + ->removeComponent('member_for') + ->save(); +}